summaryrefslogtreecommitdiffstats
path: root/layout/style
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /layout/style
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/style')
-rw-r--r--layout/style/AnimatedPropertyID.h94
-rw-r--r--layout/style/AnimatedPropertyIDSet.h149
-rw-r--r--layout/style/AnimationCollection.cpp79
-rw-r--r--layout/style/AnimationCollection.h71
-rw-r--r--layout/style/AnimationCommon.h225
-rw-r--r--layout/style/AttributeStyles.cpp112
-rw-r--r--layout/style/AttributeStyles.h86
-rw-r--r--layout/style/BuiltinCounterStyleList.h38
-rw-r--r--layout/style/CSS.cpp71
-rw-r--r--layout/style/CSS.h47
-rw-r--r--layout/style/CSSContainerRule.cpp91
-rw-r--r--layout/style/CSSContainerRule.h55
-rw-r--r--layout/style/CSSCounterStyleRule.cpp101
-rw-r--r--layout/style/CSSCounterStyleRule.h63
-rw-r--r--layout/style/CSSEnabledState.h38
-rw-r--r--layout/style/CSSFontFaceRule.cpp223
-rw-r--r--layout/style/CSSFontFaceRule.h104
-rw-r--r--layout/style/CSSFontFeatureValuesRule.cpp84
-rw-r--r--layout/style/CSSFontFeatureValuesRule.h57
-rw-r--r--layout/style/CSSFontPaletteValuesRule.cpp74
-rw-r--r--layout/style/CSSFontPaletteValuesRule.h59
-rw-r--r--layout/style/CSSImportRule.cpp156
-rw-r--r--layout/style/CSSImportRule.h63
-rw-r--r--layout/style/CSSKeyframeRule.cpp220
-rw-r--r--layout/style/CSSKeyframeRule.h62
-rw-r--r--layout/style/CSSKeyframesRule.cpp364
-rw-r--r--layout/style/CSSKeyframesRule.h70
-rw-r--r--layout/style/CSSLayerBlockRule.cpp66
-rw-r--r--layout/style/CSSLayerBlockRule.h48
-rw-r--r--layout/style/CSSLayerStatementRule.cpp68
-rw-r--r--layout/style/CSSLayerStatementRule.h49
-rw-r--r--layout/style/CSSMediaRule.cpp113
-rw-r--r--layout/style/CSSMediaRule.h55
-rw-r--r--layout/style/CSSMozDocumentRule.cpp143
-rw-r--r--layout/style/CSSMozDocumentRule.h53
-rw-r--r--layout/style/CSSNamespaceRule.cpp51
-rw-r--r--layout/style/CSSNamespaceRule.h63
-rw-r--r--layout/style/CSSPageRule.cpp198
-rw-r--r--layout/style/CSSPageRule.h108
-rw-r--r--layout/style/CSSPropFlags.h68
-rw-r--r--layout/style/CSSPropertyRule.cpp72
-rw-r--r--layout/style/CSSPropertyRule.h61
-rw-r--r--layout/style/CSSRuleList.cpp31
-rw-r--r--layout/style/CSSRuleList.h39
-rw-r--r--layout/style/CSSStyleRule.cpp323
-rw-r--r--layout/style/CSSStyleRule.h125
-rw-r--r--layout/style/CSSSupportsRule.cpp76
-rw-r--r--layout/style/CSSSupportsRule.h47
-rw-r--r--layout/style/CSSValue.h54
-rw-r--r--layout/style/CachedInheritingStyles.cpp70
-rw-r--r--layout/style/CachedInheritingStyles.h66
-rw-r--r--layout/style/ComputedStyle.cpp433
-rw-r--r--layout/style/ComputedStyle.h354
-rw-r--r--layout/style/ComputedStyleInlines.h132
-rw-r--r--layout/style/CounterStyleManager.cpp1884
-rw-r--r--layout/style/CounterStyleManager.h355
-rw-r--r--layout/style/DeclarationBlock.cpp32
-rw-r--r--layout/style/DeclarationBlock.h245
-rw-r--r--layout/style/DocumentMatchingFunction.h30
-rw-r--r--layout/style/DocumentStyleRootIterator.cpp47
-rw-r--r--layout/style/DocumentStyleRootIterator.h45
-rw-r--r--layout/style/ErrorReporter.cpp277
-rw-r--r--layout/style/ErrorReporter.h73
-rw-r--r--layout/style/FontFace.cpp314
-rw-r--r--layout/style/FontFace.h126
-rw-r--r--layout/style/FontFaceImpl.cpp840
-rw-r--r--layout/style/FontFaceImpl.h308
-rw-r--r--layout/style/FontFaceSet.cpp487
-rw-r--r--layout/style/FontFaceSet.h174
-rw-r--r--layout/style/FontFaceSetDocumentImpl.cpp763
-rw-r--r--layout/style/FontFaceSetDocumentImpl.h123
-rw-r--r--layout/style/FontFaceSetImpl.cpp954
-rw-r--r--layout/style/FontFaceSetImpl.h315
-rw-r--r--layout/style/FontFaceSetIterator.cpp82
-rw-r--r--layout/style/FontFaceSetIterator.h42
-rw-r--r--layout/style/FontFaceSetWorkerImpl.cpp374
-rw-r--r--layout/style/FontFaceSetWorkerImpl.h68
-rw-r--r--layout/style/FontLoaderUtils.cpp185
-rw-r--r--layout/style/FontLoaderUtils.h66
-rw-r--r--layout/style/FontPreloader.cpp32
-rw-r--r--layout/style/FontPreloader.h30
-rw-r--r--layout/style/GeckoBindings.cpp1819
-rw-r--r--layout/style/GeckoBindings.h660
-rw-r--r--layout/style/GenerateCSSPropertyID.py40
-rw-r--r--layout/style/GenerateCSSPropsGenerated.py114
-rw-r--r--layout/style/GenerateCompositorAnimatableProperties.py35
-rw-r--r--layout/style/GenerateComputedDOMStyleGenerated.py81
-rw-r--r--layout/style/GenerateCountedUnknownProperties.py24
-rw-r--r--layout/style/GenerateServoCSSPropList.py138
-rw-r--r--layout/style/GlobalStyleSheetCache.cpp561
-rw-r--r--layout/style/GlobalStyleSheetCache.h132
-rw-r--r--layout/style/GroupRule.cpp147
-rw-r--r--layout/style/GroupRule.h96
-rw-r--r--layout/style/ImageDocument.css43
-rw-r--r--layout/style/ImageLoader.cpp834
-rw-r--r--layout/style/ImageLoader.h170
-rw-r--r--layout/style/ImportScanner.cpp235
-rw-r--r--layout/style/ImportScanner.h94
-rw-r--r--layout/style/LayerAnimationInfo.cpp46
-rw-r--r--layout/style/LayerAnimationInfo.h91
-rw-r--r--layout/style/Loader.cpp2344
-rw-r--r--layout/style/Loader.h665
-rw-r--r--layout/style/MappedDeclarationsBuilder.cpp42
-rw-r--r--layout/style/MappedDeclarationsBuilder.h220
-rw-r--r--layout/style/MediaFeatureChange.h91
-rw-r--r--layout/style/MediaList.cpp176
-rw-r--r--layout/style/MediaList.h101
-rw-r--r--layout/style/MediaQueryList.cpp147
-rw-r--r--layout/style/MediaQueryList.h107
-rw-r--r--layout/style/PaintWorkletGlobalScope.cpp35
-rw-r--r--layout/style/PaintWorkletGlobalScope.h38
-rw-r--r--layout/style/PaintWorkletImpl.cpp40
-rw-r--r--layout/style/PaintWorkletImpl.h36
-rw-r--r--layout/style/PostTraversalTask.cpp58
-rw-r--r--layout/style/PostTraversalTask.h128
-rw-r--r--layout/style/PreferenceSheet.cpp341
-rw-r--r--layout/style/PreferenceSheet.h135
-rw-r--r--layout/style/PreloadedStyleSheet.cpp93
-rw-r--r--layout/style/PreloadedStyleSheet.h75
-rw-r--r--layout/style/PseudoStyleType.cpp39
-rw-r--r--layout/style/PseudoStyleType.h113
-rw-r--r--layout/style/Rule.cpp130
-rw-r--r--layout/style/Rule.h141
-rw-r--r--layout/style/RustCell.h45
-rw-r--r--layout/style/ServoBindingTypes.h161
-rw-r--r--layout/style/ServoBindings.h132
-rw-r--r--layout/style/ServoBindings.toml691
-rw-r--r--layout/style/ServoBoxedTypeList.h32
-rw-r--r--layout/style/ServoCSSParser.cpp90
-rw-r--r--layout/style/ServoCSSParser.h159
-rw-r--r--layout/style/ServoCSSPropList.mako.py169
-rw-r--r--layout/style/ServoCSSRuleList.cpp306
-rw-r--r--layout/style/ServoCSSRuleList.h96
-rw-r--r--layout/style/ServoComputedData.h111
-rw-r--r--layout/style/ServoElementSnapshot.cpp85
-rw-r--r--layout/style/ServoElementSnapshot.h171
-rw-r--r--layout/style/ServoElementSnapshotTable.h23
-rw-r--r--layout/style/ServoLockedArcTypeList.h25
-rw-r--r--layout/style/ServoStyleConstsForwards.h236
-rw-r--r--layout/style/ServoStyleConstsInlines.h1202
-rw-r--r--layout/style/ServoStyleSet.cpp1579
-rw-r--r--layout/style/ServoStyleSet.h745
-rw-r--r--layout/style/ServoStyleSetInlines.h27
-rw-r--r--layout/style/ServoTraversalStatistics.h31
-rw-r--r--layout/style/ServoTypes.h141
-rw-r--r--layout/style/ServoUtils.h38
-rw-r--r--layout/style/ShadowParts.cpp142
-rw-r--r--layout/style/ShadowParts.h47
-rw-r--r--layout/style/SharedStyleSheetCache.cpp222
-rw-r--r--layout/style/SharedStyleSheetCache.h80
-rw-r--r--layout/style/SharedSubResourceCache.h507
-rw-r--r--layout/style/SheetLoadData.h286
-rw-r--r--layout/style/SheetParsingMode.h49
-rw-r--r--layout/style/StreamLoader.cpp212
-rw-r--r--layout/style/StreamLoader.h64
-rw-r--r--layout/style/StyleAnimationValue.cpp284
-rw-r--r--layout/style/StyleAnimationValue.h138
-rw-r--r--layout/style/StyleColor.cpp81
-rw-r--r--layout/style/StyleColorInlines.h78
-rw-r--r--layout/style/StylePreloadKind.h34
-rw-r--r--layout/style/StyleSheet.cpp1484
-rw-r--r--layout/style/StyleSheet.h615
-rw-r--r--layout/style/StyleSheetInfo.h92
-rw-r--r--layout/style/StyleSheetInlines.h43
-rw-r--r--layout/style/TimelineCollection.cpp67
-rw-r--r--layout/style/TimelineCollection.h70
-rw-r--r--layout/style/TimelineManager.cpp187
-rw-r--r--layout/style/TimelineManager.h86
-rw-r--r--layout/style/TopLevelImageDocument.css49
-rw-r--r--layout/style/TopLevelVideoDocument.css32
-rw-r--r--layout/style/URLExtraData.cpp52
-rw-r--r--layout/style/URLExtraData.h87
-rw-r--r--layout/style/UserAgentStyleSheetID.h23
-rw-r--r--layout/style/UserAgentStyleSheetList.h34
-rw-r--r--layout/style/contenteditable.css315
-rw-r--r--layout/style/crashtests/1017798-1.css84
-rw-r--r--layout/style/crashtests/1017798-1.html126
-rw-r--r--layout/style/crashtests/1028514-1.html18
-rw-r--r--layout/style/crashtests/105619-1.html33
-rw-r--r--layout/style/crashtests/1066089-1.html21
-rw-r--r--layout/style/crashtests/1074651-1.html4
-rw-r--r--layout/style/crashtests/1089463-1.html20
-rw-r--r--layout/style/crashtests/1135534.html1
-rw-r--r--layout/style/crashtests/1136010-1.html16
-rw-r--r--layout/style/crashtests/1146101-1.html10
-rw-r--r--layout/style/crashtests/1153693-1.html22
-rw-r--r--layout/style/crashtests/1156969.svg8
-rw-r--r--layout/style/crashtests/1161320-1.html25
-rw-r--r--layout/style/crashtests/1161320-2.html25
-rw-r--r--layout/style/crashtests/1161366-1.html7
-rw-r--r--layout/style/crashtests/1163446-1.html4
-rw-r--r--layout/style/crashtests/1164813-1.html33
-rw-r--r--layout/style/crashtests/1167782-1.html11
-rw-r--r--layout/style/crashtests/1186768-1.xhtml10
-rw-r--r--layout/style/crashtests/1200568-1.html16
-rw-r--r--layout/style/crashtests/1206105-1.html6
-rw-r--r--layout/style/crashtests/1223688-1.html19
-rw-r--r--layout/style/crashtests/1223694-1.html17
-rw-r--r--layout/style/crashtests/1226400-1.html55
-rw-r--r--layout/style/crashtests/1227498.html26
-rw-r--r--layout/style/crashtests/1227501-1.html8
-rw-r--r--layout/style/crashtests/1228789-1.html4
-rw-r--r--layout/style/crashtests/1230408-1.html8
-rw-r--r--layout/style/crashtests/1233135-1.html13
-rw-r--r--layout/style/crashtests/1233135-2.html11
-rw-r--r--layout/style/crashtests/1236398.xhtml71
-rw-r--r--layout/style/crashtests/1238660-1.html19
-rw-r--r--layout/style/crashtests/1245260-1.html53
-rw-r--r--layout/style/crashtests/1247865-1.html19
-rw-r--r--layout/style/crashtests/1250791.html8
-rw-r--r--layout/style/crashtests/1264396-1.html14
-rw-r--r--layout/style/crashtests/1264949.html23
-rw-r--r--layout/style/crashtests/1265611-1.html24
-rw-r--r--layout/style/crashtests/1270795.html15
-rw-r--r--layout/style/crashtests/1275026.html4
-rw-r--r--layout/style/crashtests/1277908-1.html26
-rw-r--r--layout/style/crashtests/1277908-2.html19
-rw-r--r--layout/style/crashtests/1278463-1.html21
-rw-r--r--layout/style/crashtests/1279819-1.html18
-rw-r--r--layout/style/crashtests/1282076-1.html51
-rw-r--r--layout/style/crashtests/1282076-2.html46
-rw-r--r--layout/style/crashtests/1290994-1.html11
-rw-r--r--layout/style/crashtests/1290994-2.html11
-rw-r--r--layout/style/crashtests/1290994-3.html11
-rw-r--r--layout/style/crashtests/1290994-4.html8
-rw-r--r--layout/style/crashtests/1314531.html2
-rw-r--r--layout/style/crashtests/1315889-1.html12
-rw-r--r--layout/style/crashtests/1315894-1.html9
-rw-r--r--layout/style/crashtests/1319072-1.html20
-rw-r--r--layout/style/crashtests/1320423-1.html22
-rw-r--r--layout/style/crashtests/1321357-1.html12
-rw-r--r--layout/style/crashtests/1328535-1.html17
-rw-r--r--layout/style/crashtests/1331272.html16
-rw-r--r--layout/style/crashtests/1332550.html19
-rw-r--r--layout/style/crashtests/1333001-1.css1
-rw-r--r--layout/style/crashtests/1333001-1.html9
-rw-r--r--layout/style/crashtests/1340248.html14
-rw-r--r--layout/style/crashtests/1340344.html15
-rw-r--r--layout/style/crashtests/1342316-1.html20
-rw-r--r--layout/style/crashtests/1344210.html22
-rw-r--r--layout/style/crashtests/1353312.html14
-rw-r--r--layout/style/crashtests/1356601-1.html18
-rw-r--r--layout/style/crashtests/1364139-1.html20
-rw-r--r--layout/style/crashtests/1371450-1.html34
-rw-r--r--layout/style/crashtests/1374175-1.html12
-rw-r--r--layout/style/crashtests/1375812-1.html25
-rw-r--r--layout/style/crashtests/1377053-1.html10
-rw-r--r--layout/style/crashtests/1377256-1-helper.html13
-rw-r--r--layout/style/crashtests/1377256-1.html9
-rw-r--r--layout/style/crashtests/1378064-1.html38
-rw-r--r--layout/style/crashtests/1378814.html4
-rw-r--r--layout/style/crashtests/1380800.html5
-rw-r--r--layout/style/crashtests/1381420-1.html35
-rw-r--r--layout/style/crashtests/1381682.html18
-rw-r--r--layout/style/crashtests/1382672.html11
-rw-r--r--layout/style/crashtests/1382710.html8
-rw-r--r--layout/style/crashtests/1383001-2.html15
-rw-r--r--layout/style/crashtests/1383001.html23
-rw-r--r--layout/style/crashtests/1383319.html18
-rw-r--r--layout/style/crashtests/1383493-1.html10
-rw-r--r--layout/style/crashtests/1383589-1.html14
-rw-r--r--layout/style/crashtests/1383975.html10
-rw-r--r--layout/style/crashtests/1383981-2.html21
-rw-r--r--layout/style/crashtests/1383981-3.html36
-rw-r--r--layout/style/crashtests/1383981.html22
-rw-r--r--layout/style/crashtests/1384232.html9
-rw-r--r--layout/style/crashtests/1384824-1.html27
-rw-r--r--layout/style/crashtests/1384824-2.html31
-rw-r--r--layout/style/crashtests/1386773.html17
-rw-r--r--layout/style/crashtests/1387481-1-iframe.html26
-rw-r--r--layout/style/crashtests/1387481-1.html13
-rw-r--r--layout/style/crashtests/1387499.html15
-rw-r--r--layout/style/crashtests/1388234.html9
-rw-r--r--layout/style/crashtests/1389645.html13
-rw-r--r--layout/style/crashtests/1390726.html27
-rw-r--r--layout/style/crashtests/1391577.html14
-rw-r--r--layout/style/crashtests/1393189.html13
-rw-r--r--layout/style/crashtests/1393580.html10
-rw-r--r--layout/style/crashtests/1393791.html12
-rw-r--r--layout/style/crashtests/1395719.html19
-rw-r--r--layout/style/crashtests/1395725.html15
-rw-r--r--layout/style/crashtests/1396041.html9
-rw-r--r--layout/style/crashtests/1397091.html13
-rw-r--r--layout/style/crashtests/1397363-1.html13
-rw-r--r--layout/style/crashtests/1397439-1.html6
-rw-r--r--layout/style/crashtests/1398479.html1
-rw-r--r--layout/style/crashtests/1398581.html17
-rw-r--r--layout/style/crashtests/1399006.html29
-rw-r--r--layout/style/crashtests/1399546.html17
-rw-r--r--layout/style/crashtests/1400035.html18
-rw-r--r--layout/style/crashtests/1400325.html6
-rw-r--r--layout/style/crashtests/1400926.html10
-rw-r--r--layout/style/crashtests/1400936-1.html26
-rw-r--r--layout/style/crashtests/1400936-2.html12
-rw-r--r--layout/style/crashtests/1401256.html5
-rw-r--r--layout/style/crashtests/1401706.html10
-rw-r--r--layout/style/crashtests/1401801.html24
-rw-r--r--layout/style/crashtests/1401825.html7
-rw-r--r--layout/style/crashtests/1402218-1.html15
-rw-r--r--layout/style/crashtests/1402366.html10
-rw-r--r--layout/style/crashtests/1402419.html3
-rw-r--r--layout/style/crashtests/1402472.html13
-rw-r--r--layout/style/crashtests/1403028.html9
-rw-r--r--layout/style/crashtests/1403433.html6
-rw-r--r--layout/style/crashtests/1403465.html24
-rw-r--r--layout/style/crashtests/1403592.html19
-rw-r--r--layout/style/crashtests/1403615.html24
-rw-r--r--layout/style/crashtests/1403712.html26
-rw-r--r--layout/style/crashtests/1404057.html6
-rw-r--r--layout/style/crashtests/1404180-1.html22
-rw-r--r--layout/style/crashtests/1404316.html20
-rw-r--r--layout/style/crashtests/1404324-1.html12
-rw-r--r--layout/style/crashtests/1404324-2.html10
-rw-r--r--layout/style/crashtests/1404324-3.html14
-rw-r--r--layout/style/crashtests/1405880.html21
-rw-r--r--layout/style/crashtests/1406222-1.html25
-rw-r--r--layout/style/crashtests/1406222-2.html15
-rw-r--r--layout/style/crashtests/1409183.html15
-rw-r--r--layout/style/crashtests/1409502.html13
-rw-r--r--layout/style/crashtests/1409931.html14
-rw-r--r--layout/style/crashtests/1410226-1.html8
-rw-r--r--layout/style/crashtests/1410226-2.html16
-rw-r--r--layout/style/crashtests/1411008.html22
-rw-r--r--layout/style/crashtests/1411143.html7
-rw-r--r--layout/style/crashtests/1411478.html6
-rw-r--r--layout/style/crashtests/1413288.html17
-rw-r--r--layout/style/crashtests/1413361.html8
-rw-r--r--layout/style/crashtests/1413670.html17
-rw-r--r--layout/style/crashtests/1415353.html8
-rw-r--r--layout/style/crashtests/1418059.html24
-rw-r--r--layout/style/crashtests/1418867.html33
-rw-r--r--layout/style/crashtests/1419554.html9
-rw-r--r--layout/style/crashtests/1426312.html4
-rw-r--r--layout/style/crashtests/1439793.html10
-rw-r--r--layout/style/crashtests/1445682.html16
-rw-r--r--layout/style/crashtests/1449243.html13
-rw-r--r--layout/style/crashtests/1450691.html12
-rw-r--r--layout/style/crashtests/1453206.html8
-rw-r--r--layout/style/crashtests/1454140.html4
-rw-r--r--layout/style/crashtests/1455108.html17
-rw-r--r--layout/style/crashtests/1457288.html9
-rw-r--r--layout/style/crashtests/1457985.html2
-rw-r--r--layout/style/crashtests/1468640.html10
-rw-r--r--layout/style/crashtests/1469076.html12
-rw-r--r--layout/style/crashtests/1475003.html21
-rw-r--r--layout/style/crashtests/1479681.html14
-rw-r--r--layout/style/crashtests/1488817.html27
-rw-r--r--layout/style/crashtests/1490012.html11
-rw-r--r--layout/style/crashtests/1502893.html29
-rw-r--r--layout/style/crashtests/1507674.html16
-rw-r--r--layout/style/crashtests/1509989.html11
-rw-r--r--layout/style/crashtests/1514086.html13
-rw-r--r--layout/style/crashtests/1533783.html10
-rw-r--r--layout/style/crashtests/1533891.html13
-rw-r--r--layout/style/crashtests/1533968.html22
-rw-r--r--layout/style/crashtests/1545177.html15
-rw-r--r--layout/style/crashtests/1546255.html29
-rw-r--r--layout/style/crashtests/1552911.html15
-rw-r--r--layout/style/crashtests/1562361.html15
-rw-r--r--layout/style/crashtests/1566684.html16
-rw-r--r--layout/style/crashtests/1579788.html15
-rw-r--r--layout/style/crashtests/1580307.html1
-rw-r--r--layout/style/crashtests/1581579.html3
-rw-r--r--layout/style/crashtests/1586444.html15
-rw-r--r--layout/style/crashtests/1593766.html3
-rw-r--r--layout/style/crashtests/1594949.html13
-rw-r--r--layout/style/crashtests/1594960.html15
-rw-r--r--layout/style/crashtests/1599286.html2
-rw-r--r--layout/style/crashtests/1609786.html9
-rw-r--r--layout/style/crashtests/1616407.html7
-rw-r--r--layout/style/crashtests/1616433.html9
-rw-r--r--layout/style/crashtests/1639533.html15
-rw-r--r--layout/style/crashtests/1640040.html11
-rw-r--r--layout/style/crashtests/1806189-1.html15
-rw-r--r--layout/style/crashtests/1821416.html21
-rw-r--r--layout/style/crashtests/1872309.html18
-rw-r--r--layout/style/crashtests/187671-1.html12
-rw-r--r--layout/style/crashtests/192408-1.html15
-rw-r--r--layout/style/crashtests/285727-1.html13
-rw-r--r--layout/style/crashtests/286707-1.html2
-rw-r--r--layout/style/crashtests/317561-1.html104
-rw-r--r--layout/style/crashtests/330998-1.html30
-rw-r--r--layout/style/crashtests/363950.html20
-rw-r--r--layout/style/crashtests/368175-1.html14
-rw-r--r--layout/style/crashtests/368740.html25
-rw-r--r--layout/style/crashtests/379788-1.html9
-rw-r--r--layout/style/crashtests/383979-1.xhtml31
-rw-r--r--layout/style/crashtests/383979-2.html35
-rw-r--r--layout/style/crashtests/386939-1.html24
-rw-r--r--layout/style/crashtests/391034-1.xhtml17
-rw-r--r--layout/style/crashtests/397022-1.html17
-rw-r--r--layout/style/crashtests/399289-1.svg3
-rw-r--r--layout/style/crashtests/404470-1.html15
-rw-r--r--layout/style/crashtests/411603-1.html7
-rw-r--r--layout/style/crashtests/412588-1.html5
-rw-r--r--layout/style/crashtests/413274-1.xhtml18
-rw-r--r--layout/style/crashtests/416461-1.xhtml6
-rw-r--r--layout/style/crashtests/418007-1.xhtml24
-rw-r--r--layout/style/crashtests/431705-1.xhtml6
-rw-r--r--layout/style/crashtests/432561-1.html17
-rw-r--r--layout/style/crashtests/437170-1.html23
-rw-r--r--layout/style/crashtests/437532-1.html12
-rw-r--r--layout/style/crashtests/439184-1.html31
-rw-r--r--layout/style/crashtests/444237-1.html1
-rw-r--r--layout/style/crashtests/444848-1.html9
-rw-r--r--layout/style/crashtests/447776-1.html7
-rw-r--r--layout/style/crashtests/447783-1.html8
-rw-r--r--layout/style/crashtests/448161-1.html22
-rw-r--r--layout/style/crashtests/448161-2.html9
-rw-r--r--layout/style/crashtests/452150-1.xhtml6
-rw-r--r--layout/style/crashtests/456196.html16
-rw-r--r--layout/style/crashtests/460209-1.html9
-rw-r--r--layout/style/crashtests/460217-1.html15
-rw-r--r--layout/style/crashtests/460323-1.html30
-rw-r--r--layout/style/crashtests/466845-1.html14
-rw-r--r--layout/style/crashtests/469432-1.xhtml8
-rw-r--r--layout/style/crashtests/472195-1.html13
-rw-r--r--layout/style/crashtests/472237-1.html26
-rw-r--r--layout/style/crashtests/473720-1.html15
-rw-r--r--layout/style/crashtests/473892-1.html12
-rw-r--r--layout/style/crashtests/473914-1.html23
-rw-r--r--layout/style/crashtests/474377-1.xhtml18
-rw-r--r--layout/style/crashtests/478321-1.xhtml1
-rw-r--r--layout/style/crashtests/481557.html9
-rw-r--r--layout/style/crashtests/495269-1.html12
-rw-r--r--layout/style/crashtests/495269-2.html12
-rw-r--r--layout/style/crashtests/498036-1.html15
-rw-r--r--layout/style/crashtests/509155-1.html4
-rw-r--r--layout/style/crashtests/509156-1.html5
-rw-r--r--layout/style/crashtests/509569-1.html2
-rw-r--r--layout/style/crashtests/512851-1.xhtml23
-rw-r--r--layout/style/crashtests/524252-1.html10
-rw-r--r--layout/style/crashtests/536789-1.html11
-rw-r--r--layout/style/crashtests/539613-1.xhtml5
-rw-r--r--layout/style/crashtests/558943-1.xhtml11
-rw-r--r--layout/style/crashtests/559491.html29
-rw-r--r--layout/style/crashtests/565248-1.html2
-rw-r--r--layout/style/crashtests/571105-1.xhtml1
-rw-r--r--layout/style/crashtests/573127-1.html20
-rw-r--r--layout/style/crashtests/575464-1.html1
-rw-r--r--layout/style/crashtests/580685.html10
-rw-r--r--layout/style/crashtests/585185-1.html1
-rw-r--r--layout/style/crashtests/588627-1.html4
-rw-r--r--layout/style/crashtests/592698-1.html29
-rw-r--r--layout/style/crashtests/601437-1.html7
-rw-r--r--layout/style/crashtests/601439-1.html8
-rw-r--r--layout/style/crashtests/605689-1.html13
-rw-r--r--layout/style/crashtests/611922-1.html13
-rw-r--r--layout/style/crashtests/612213.html17
-rw-r--r--layout/style/crashtests/621596-1.html18
-rw-r--r--layout/style/crashtests/622314-1.xhtml26
-rw-r--r--layout/style/crashtests/635153.html16
-rw-r--r--layout/style/crashtests/637242.xhtml27
-rw-r--r--layout/style/crashtests/645142.html11
-rw-r--r--layout/style/crashtests/652976-1.svg10
-rw-r--r--layout/style/crashtests/653675.html1
-rw-r--r--layout/style/crashtests/665209-1.html16
-rw-r--r--layout/style/crashtests/671799-1.html6
-rw-r--r--layout/style/crashtests/671799-2.html17
-rw-r--r--layout/style/crashtests/690990-1.html20
-rw-r--r--layout/style/crashtests/694775.html7
-rw-r--r--layout/style/crashtests/696188-1.html20
-rw-r--r--layout/style/crashtests/696869-1.html2
-rw-r--r--layout/style/crashtests/700116.html5
-rw-r--r--layout/style/crashtests/729126-1.html10
-rw-r--r--layout/style/crashtests/729126-2.html10
-rw-r--r--layout/style/crashtests/786108-1.html22
-rw-r--r--layout/style/crashtests/786108-2.html23
-rw-r--r--layout/style/crashtests/788836.html3
-rw-r--r--layout/style/crashtests/806310-1.html4
-rw-r--r--layout/style/crashtests/809762.html22
-rw-r--r--layout/style/crashtests/812824.html1
-rw-r--r--layout/style/crashtests/822766-1.html31
-rw-r--r--layout/style/crashtests/822877.html15
-rw-r--r--layout/style/crashtests/827213-1.html9
-rw-r--r--layout/style/crashtests/827220.html5
-rw-r--r--layout/style/crashtests/827591-1.html19
-rw-r--r--layout/style/crashtests/829817.html20
-rw-r--r--layout/style/crashtests/842134.html1
-rw-r--r--layout/style/crashtests/861489-1.html29
-rw-r--r--layout/style/crashtests/862113.html16
-rw-r--r--layout/style/crashtests/867487.html24
-rw-r--r--layout/style/crashtests/873222.html17
-rw-r--r--layout/style/crashtests/873260-1.html16
-rw-r--r--layout/style/crashtests/873260-2.html16
-rw-r--r--layout/style/crashtests/880862.html28
-rw-r--r--layout/style/crashtests/894245-1.html4
-rw-r--r--layout/style/crashtests/915440.html4
-rw-r--r--layout/style/crashtests/927734-1.html10
-rw-r--r--layout/style/crashtests/930270-1.html6
-rw-r--r--layout/style/crashtests/930270-2.html9
-rw-r--r--layout/style/crashtests/945048-1.html5
-rw-r--r--layout/style/crashtests/972199-1.html37
-rw-r--r--layout/style/crashtests/989965-1.html9
-rw-r--r--layout/style/crashtests/992333-1.html10
-rw-r--r--layout/style/crashtests/blue-32x32.pngbin0 -> 110 bytes
-rw-r--r--layout/style/crashtests/border-image-visited-link.html10
-rw-r--r--layout/style/crashtests/content-only-on-link-before.html5
-rw-r--r--layout/style/crashtests/content-only-on-visited-before.html5
-rw-r--r--layout/style/crashtests/crashtests.list325
-rw-r--r--layout/style/crashtests/font-face-truncated-src.html2
-rw-r--r--layout/style/crashtests/large_border_image_width.html9
-rw-r--r--layout/style/crashtests/link-transition-before.html27
-rw-r--r--layout/style/crashtests/long-url-list-stack-overflow.html23
-rw-r--r--layout/style/crashtests/scale-on-block-continuation.html43
-rw-r--r--layout/style/designmode.css8
-rw-r--r--layout/style/extra-bindgen-flags.in1
-rw-r--r--layout/style/jar.mn35
-rw-r--r--layout/style/moz.build353
-rw-r--r--layout/style/nsAnimationManager.cpp467
-rw-r--r--layout/style/nsAnimationManager.h109
-rw-r--r--layout/style/nsCSSAnonBoxList.h163
-rw-r--r--layout/style/nsCSSAnonBoxes.cpp45
-rw-r--r--layout/style/nsCSSAnonBoxes.h63
-rw-r--r--layout/style/nsCSSCounterDescList.h16
-rw-r--r--layout/style/nsCSSFontDescList.h20
-rw-r--r--layout/style/nsCSSPropertyID.h.in87
-rw-r--r--layout/style/nsCSSPropertyIDSet.h304
-rw-r--r--layout/style/nsCSSProps.cpp247
-rw-r--r--layout/style/nsCSSProps.h230
-rw-r--r--layout/style/nsCSSPseudoElementList.h109
-rw-r--r--layout/style/nsCSSPseudoElements.cpp154
-rw-r--r--layout/style/nsCSSPseudoElements.h180
-rw-r--r--layout/style/nsCSSValue.cpp122
-rw-r--r--layout/style/nsCSSValue.h148
-rw-r--r--layout/style/nsCSSVisitedDependentPropList.h36
-rw-r--r--layout/style/nsComputedDOMStyle.cpp2434
-rw-r--r--layout/style/nsComputedDOMStyle.h400
-rw-r--r--layout/style/nsDOMCSSAttrDeclaration.cpp278
-rw-r--r--layout/style/nsDOMCSSAttrDeclaration.h101
-rw-r--r--layout/style/nsDOMCSSDeclaration.cpp373
-rw-r--r--layout/style/nsDOMCSSDeclaration.h187
-rw-r--r--layout/style/nsDOMCSSValueList.cpp62
-rw-r--r--layout/style/nsDOMCSSValueList.h39
-rw-r--r--layout/style/nsFontFaceLoader.cpp379
-rw-r--r--layout/style/nsFontFaceLoader.h74
-rw-r--r--layout/style/nsFontFaceUtils.cpp231
-rw-r--r--layout/style/nsFontFaceUtils.h22
-rw-r--r--layout/style/nsICSSDeclaration.cpp28
-rw-r--r--layout/style/nsICSSDeclaration.h127
-rw-r--r--layout/style/nsICSSLoaderObserver.h48
-rw-r--r--layout/style/nsMediaFeatures.cpp407
-rw-r--r--layout/style/nsROCSSPrimitiveValue.cpp169
-rw-r--r--layout/style/nsROCSSPrimitiveValue.h83
-rw-r--r--layout/style/nsStyleAutoArray.h81
-rw-r--r--layout/style/nsStyleConsts.h595
-rw-r--r--layout/style/nsStyleStruct.cpp3604
-rw-r--r--layout/style/nsStyleStruct.h2085
-rw-r--r--layout/style/nsStyleStructFwd.h79
-rw-r--r--layout/style/nsStyleStructInlines.h141
-rw-r--r--layout/style/nsStyleStructList.h68
-rw-r--r--layout/style/nsStyleTransformMatrix.cpp653
-rw-r--r--layout/style/nsStyleTransformMatrix.h190
-rw-r--r--layout/style/nsStyleUtil.cpp388
-rw-r--r--layout/style/nsStyleUtil.h175
-rw-r--r--layout/style/nsTransitionManager.cpp497
-rw-r--r--layout/style/nsTransitionManager.h87
-rw-r--r--layout/style/res/Mozilla_Bullet.bf139
-rw-r--r--layout/style/res/Mozilla_Bullet.ttf0
-rw-r--r--layout/style/res/Mozilla_Bullet.woff20
-rw-r--r--layout/style/res/accessiblecaret-normal.svg10
-rw-r--r--layout/style/res/accessiblecaret-normal@1.5x.pngbin0 -> 1453 bytes
-rw-r--r--layout/style/res/accessiblecaret-normal@1x.pngbin0 -> 999 bytes
-rw-r--r--layout/style/res/accessiblecaret-normal@2.25x.pngbin0 -> 2053 bytes
-rw-r--r--layout/style/res/accessiblecaret-normal@2x.pngbin0 -> 2118 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-left.svg10
-rw-r--r--layout/style/res/accessiblecaret-tilt-left@1.5x.pngbin0 -> 1443 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-left@1x.pngbin0 -> 993 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-left@2.25x.pngbin0 -> 2092 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-left@2x.pngbin0 -> 1775 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-right.svg10
-rw-r--r--layout/style/res/accessiblecaret-tilt-right@1.5x.pngbin0 -> 1440 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-right@1x.pngbin0 -> 982 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-right@2.25x.pngbin0 -> 2075 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-right@2x.pngbin0 -> 1766 bytes
-rw-r--r--layout/style/res/accessiblecaret.css108
-rw-r--r--layout/style/res/counterstyles.css365
-rw-r--r--layout/style/res/details.css21
-rw-r--r--layout/style/res/forms.css932
-rw-r--r--layout/style/res/html.css927
-rw-r--r--layout/style/res/noframes.css13
-rw-r--r--layout/style/res/password-hide.svg7
-rw-r--r--layout/style/res/password.svg7
-rw-r--r--layout/style/res/plaintext.css36
-rw-r--r--layout/style/res/quirk.css105
-rw-r--r--layout/style/res/scrollbars.css234
-rw-r--r--layout/style/res/searchfield-cancel.svg20
-rw-r--r--layout/style/res/ua.css455
-rw-r--r--layout/style/res/viewsource.css116
-rw-r--r--layout/style/test/Ahem.ttfbin0 -> 12480 bytes
-rw-r--r--layout/style/test/BitPattern.woffbin0 -> 6248 bytes
-rw-r--r--layout/style/test/ListCSSProperties.cpp178
-rw-r--r--layout/style/test/ParseCSS.cpp80
-rw-r--r--layout/style/test/additional_sheets_helper.html7
-rw-r--r--layout/style/test/animation_utils.js935
-rw-r--r--layout/style/test/browser.toml19
-rw-r--r--layout/style/test/browser_bug453896.js16
-rw-r--r--layout/style/test/browser_sourcemap.js41
-rw-r--r--layout/style/test/browser_sourcemap_comment.js47
-rw-r--r--layout/style/test/browser_sourceurl_comment.js43
-rw-r--r--layout/style/test/bug1382568-iframe.html8
-rw-r--r--layout/style/test/bug1729861.js81
-rw-r--r--layout/style/test/bug453896_iframe.html66
-rw-r--r--layout/style/test/bug517224.sjs27
-rw-r--r--layout/style/test/bug732209-css.sjs30
-rw-r--r--layout/style/test/ccd-quirks.html129
-rw-r--r--layout/style/test/ccd-standards.html128
-rw-r--r--layout/style/test/ccd.sjs71
-rw-r--r--layout/style/test/chrome/bug418986-2.js318
-rw-r--r--layout/style/test/chrome/bug535806-css.css1
-rw-r--r--layout/style/test/chrome/bug535806-html.html8
-rw-r--r--layout/style/test/chrome/bug535806-xul.xhtml8
-rw-r--r--layout/style/test/chrome/chrome-only-media-queries.js34
-rw-r--r--layout/style/test/chrome/chrome.toml51
-rw-r--r--layout/style/test/chrome/display_mode.html122
-rw-r--r--layout/style/test/chrome/display_mode_reflow.html84
-rw-r--r--layout/style/test/chrome/display_mode_reflow_iframe.html23
-rw-r--r--layout/style/test/chrome/hover_empty.html4
-rw-r--r--layout/style/test/chrome/hover_helper.html270
-rw-r--r--layout/style/test/chrome/import_useless1.css3
-rw-r--r--layout/style/test/chrome/import_useless2.css3
-rw-r--r--layout/style/test/chrome/match.pngbin0 -> 1210 bytes
-rw-r--r--layout/style/test/chrome/mismatch.pngbin0 -> 1573 bytes
-rw-r--r--layout/style/test/chrome/moz_document_helper.html2
-rw-r--r--layout/style/test/chrome/test_bug1157097.html27
-rw-r--r--layout/style/test/chrome/test_bug1346623.html60
-rw-r--r--layout/style/test/chrome/test_bug1371453.html33
-rw-r--r--layout/style/test/chrome/test_bug418986-2.xhtml29
-rw-r--r--layout/style/test/chrome/test_bug511909.html194
-rw-r--r--layout/style/test/chrome/test_bug535806.xhtml43
-rw-r--r--layout/style/test/chrome/test_chrome_only_media_queries.html84
-rw-r--r--layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html11
-rw-r--r--layout/style/test/chrome/test_display_mode.html39
-rw-r--r--layout/style/test/chrome/test_display_mode_reflow.html41
-rw-r--r--layout/style/test/chrome/test_hover.html29
-rw-r--r--layout/style/test/chrome/test_moz_document_rules.html97
-rw-r--r--layout/style/test/chrome/test_moz_document_serialization.html58
-rw-r--r--layout/style/test/chrome/test_scrollbar_inline_size.html36
-rw-r--r--layout/style/test/chrome/test_stylesheet_clone_import_rule.html86
-rw-r--r--layout/style/test/css_properties_like_longhand.js1
-rw-r--r--layout/style/test/descriptor_database.js142
-rw-r--r--layout/style/test/empty.html1
-rw-r--r--layout/style/test/file_animations_async_tests.html77
-rw-r--r--layout/style/test/file_animations_omta_scroll.html392
-rw-r--r--layout/style/test/file_animations_omta_scroll_rtl.html112
-rw-r--r--layout/style/test/file_animations_with_disabled_properties.html50
-rw-r--r--layout/style/test/file_bug1055933_circle-xxl.pngbin0 -> 4857 bytes
-rw-r--r--layout/style/test/file_bug1089417_iframe.html17
-rw-r--r--layout/style/test/file_bug1375944.html13
-rw-r--r--layout/style/test/file_bug1381233.html4
-rw-r--r--layout/style/test/file_bug1443344.css1
-rw-r--r--layout/style/test/file_bug645998-1.css1
-rw-r--r--layout/style/test/file_bug645998-2.css1
-rw-r--r--layout/style/test/file_bug829816.cssbin0 -> 76 bytes
-rw-r--r--layout/style/test/file_computed_style_bfcache_display_none.html6
-rw-r--r--layout/style/test/file_computed_style_bfcache_display_none2.html6
-rw-r--r--layout/style/test/file_font_loading_api_vframe.html2
-rw-r--r--layout/style/test/file_shared_sheet_caching.css3
-rw-r--r--layout/style/test/file_shared_sheet_caching.html12
-rw-r--r--layout/style/test/file_specified_value_serialization_individual_transforms.html65
-rw-r--r--layout/style/test/flexbox_layout_testcases.js1317
-rw-r--r--layout/style/test/gen-css-properties.py24
-rw-r--r--layout/style/test/gtest/ImportScannerTest.cpp119
-rw-r--r--layout/style/test/gtest/StyloParsingBench.cpp116
-rw-r--r--layout/style/test/gtest/example.css2925
-rw-r--r--layout/style/test/gtest/generate_example_stylesheet.py16
-rw-r--r--layout/style/test/gtest/moz.build24
-rw-r--r--layout/style/test/mapped.css3
-rw-r--r--layout/style/test/mapped.css^headers^1
-rw-r--r--layout/style/test/mapped2.css4
-rw-r--r--layout/style/test/mapped2.css^headers^2
-rw-r--r--layout/style/test/media_queries_iframe.html15
-rw-r--r--layout/style/test/media_queries_iframe2.html37
-rw-r--r--layout/style/test/mochitest.toml783
-rw-r--r--layout/style/test/moz.build152
-rw-r--r--layout/style/test/mq_changes_child.html8
-rw-r--r--layout/style/test/neverending_font_load.sjs5
-rw-r--r--layout/style/test/neverending_stylesheet_load.sjs5
-rw-r--r--layout/style/test/post-redirect-1.css1
-rw-r--r--layout/style/test/post-redirect-2.css1
-rw-r--r--layout/style/test/post-redirect-3.css1
-rw-r--r--layout/style/test/property_database.js14128
-rw-r--r--layout/style/test/redirect.sjs4
-rw-r--r--layout/style/test/redundant_font_download.sjs63
-rw-r--r--layout/style/test/slow_broken_sheet.sjs19
-rw-r--r--layout/style/test/slow_load.sjs29
-rw-r--r--layout/style/test/slow_ok_sheet.sjs21
-rw-r--r--layout/style/test/sourcemap_css.html11
-rw-r--r--layout/style/test/style_attribute_tests.js25
-rw-r--r--layout/style/test/support/1x1-transparent.pngbin0 -> 89 bytes
-rw-r--r--layout/style/test/support/blue-100x100.pngbin0 -> 40279 bytes
-rw-r--r--layout/style/test/support/external-variable-url.css3
-rw-r--r--layout/style/test/test_acid3_test46.html140
-rw-r--r--layout/style/test/test_addSheet.html44
-rw-r--r--layout/style/test/test_additional_sheets.html310
-rw-r--r--layout/style/test/test_align_justify_computed_values.html484
-rw-r--r--layout/style/test/test_all_shorthand.html157
-rw-r--r--layout/style/test/test_animations.html2107
-rw-r--r--layout/style/test/test_animations_async_tests.html24
-rw-r--r--layout/style/test/test_animations_dynamic_changes.html65
-rw-r--r--layout/style/test/test_animations_effect_timing_duration.html81
-rw-r--r--layout/style/test/test_animations_effect_timing_enddelay.html141
-rw-r--r--layout/style/test/test_animations_effect_timing_iterations.html68
-rw-r--r--layout/style/test/test_animations_event_handler_attribute.html204
-rw-r--r--layout/style/test/test_animations_event_order.html710
-rw-r--r--layout/style/test/test_animations_iterationstart.html53
-rw-r--r--layout/style/test/test_animations_omta.html2969
-rw-r--r--layout/style/test/test_animations_omta_scroll.html25
-rw-r--r--layout/style/test/test_animations_omta_scroll_rtl.html25
-rw-r--r--layout/style/test/test_animations_omta_start.html187
-rw-r--r--layout/style/test/test_animations_pausing.html79
-rw-r--r--layout/style/test/test_animations_playbackrate.html94
-rw-r--r--layout/style/test/test_animations_reverse.html64
-rw-r--r--layout/style/test/test_animations_styles_on_event.html66
-rw-r--r--layout/style/test/test_animations_variable_changes.html58
-rw-r--r--layout/style/test/test_animations_with_disabled_properties.html33
-rw-r--r--layout/style/test/test_any_dynamic.html49
-rw-r--r--layout/style/test/test_area_url_cursor.html34
-rw-r--r--layout/style/test/test_asyncopen.html54
-rw-r--r--layout/style/test/test_at_rule_parse_serialize.html43
-rw-r--r--layout/style/test/test_attribute_selector_eof_behavior.html18
-rw-r--r--layout/style/test/test_backdrop_filter_enabled_state.html21
-rw-r--r--layout/style/test/test_background_blend_mode.html57
-rw-r--r--layout/style/test/test_border_device_pixel_rounding_initial_style.html20
-rw-r--r--layout/style/test/test_box_size_keywords.html170
-rw-r--r--layout/style/test/test_bug1055933.html41
-rw-r--r--layout/style/test/test_bug1089417.html47
-rw-r--r--layout/style/test/test_bug1112014.html89
-rw-r--r--layout/style/test/test_bug1203766.html112
-rw-r--r--layout/style/test/test_bug1232829.html37
-rw-r--r--layout/style/test/test_bug1292447.html350
-rw-r--r--layout/style/test/test_bug1330375.html59
-rw-r--r--layout/style/test/test_bug1371488.html23
-rw-r--r--layout/style/test/test_bug1375944.html34
-rw-r--r--layout/style/test/test_bug1382568.html14
-rw-r--r--layout/style/test/test_bug1394302.html32
-rw-r--r--layout/style/test/test_bug1443344-1.html48
-rw-r--r--layout/style/test/test_bug1443344-2.html48
-rw-r--r--layout/style/test/test_bug1451199-1.html42
-rw-r--r--layout/style/test/test_bug1451199-2.html43
-rw-r--r--layout/style/test/test_bug1490890.html112
-rw-r--r--layout/style/test/test_bug1505254.html152
-rw-r--r--layout/style/test/test_bug160403.html73
-rw-r--r--layout/style/test/test_bug1729861.html26
-rw-r--r--layout/style/test/test_bug200089.html30
-rw-r--r--layout/style/test/test_bug221428.html68
-rw-r--r--layout/style/test/test_bug229915.html95
-rw-r--r--layout/style/test/test_bug302186.html508
-rw-r--r--layout/style/test/test_bug319381.html67
-rw-r--r--layout/style/test/test_bug357614.html73
-rw-r--r--layout/style/test/test_bug363146.html62
-rw-r--r--layout/style/test/test_bug372770.html85
-rw-r--r--layout/style/test/test_bug373293.html29
-rw-r--r--layout/style/test/test_bug377947.html110
-rw-r--r--layout/style/test/test_bug379440.html74
-rw-r--r--layout/style/test/test_bug379741.html43
-rw-r--r--layout/style/test/test_bug382027.html37
-rw-r--r--layout/style/test/test_bug383075.html84
-rw-r--r--layout/style/test/test_bug387615.html53
-rw-r--r--layout/style/test/test_bug389464.html48
-rw-r--r--layout/style/test/test_bug391034.html71
-rw-r--r--layout/style/test/test_bug391221.html43
-rw-r--r--layout/style/test/test_bug397427.html91
-rw-r--r--layout/style/test/test_bug399349.html80
-rw-r--r--layout/style/test/test_bug401046.html82
-rw-r--r--layout/style/test/test_bug405818.html72
-rw-r--r--layout/style/test/test_bug412901.html42
-rw-r--r--layout/style/test/test_bug413958.html75
-rw-r--r--layout/style/test/test_bug418986-2.html32
-rw-r--r--layout/style/test/test_bug437915.html70
-rw-r--r--layout/style/test/test_bug450191.html64
-rw-r--r--layout/style/test/test_bug470769.html31
-rw-r--r--layout/style/test/test_bug499655.html45
-rw-r--r--layout/style/test/test_bug499655.xhtml48
-rw-r--r--layout/style/test/test_bug517224.html45
-rw-r--r--layout/style/test/test_bug524175.html28
-rw-r--r--layout/style/test/test_bug525952.html46
-rw-r--r--layout/style/test/test_bug534804.html89
-rw-r--r--layout/style/test/test_bug573255.html32
-rw-r--r--layout/style/test/test_bug580685.html41
-rw-r--r--layout/style/test/test_bug621351.html35
-rw-r--r--layout/style/test/test_bug635286.html52
-rw-r--r--layout/style/test/test_bug645998.html29
-rw-r--r--layout/style/test/test_bug652486.html192
-rw-r--r--layout/style/test/test_bug657143.html120
-rw-r--r--layout/style/test/test_bug667520.html50
-rw-r--r--layout/style/test/test_bug716226.html52
-rw-r--r--layout/style/test/test_bug732153.html22
-rw-r--r--layout/style/test/test_bug732209.html95
-rw-r--r--layout/style/test/test_bug73586.html189
-rw-r--r--layout/style/test/test_bug74880.html119
-rw-r--r--layout/style/test/test_bug765590.html21
-rw-r--r--layout/style/test/test_bug771043.html69
-rw-r--r--layout/style/test/test_bug795520.html39
-rw-r--r--layout/style/test/test_bug798843_pref.html53
-rw-r--r--layout/style/test/test_bug829816.html56
-rw-r--r--layout/style/test/test_bug874919.html55
-rw-r--r--layout/style/test/test_bug887741_at-rules_in_declaration_lists.html75
-rw-r--r--layout/style/test/test_bug892929.html74
-rw-r--r--layout/style/test/test_bug98997.html144
-rw-r--r--layout/style/test/test_cascade.html91
-rw-r--r--layout/style/test/test_ch_ex_no_infloops.html60
-rw-r--r--layout/style/test/test_change_hint_optimizations.html57
-rw-r--r--layout/style/test/test_clip-path_polygon.html40
-rw-r--r--layout/style/test/test_color_rounding.html38
-rw-r--r--layout/style/test/test_compute_data_with_start_struct.html87
-rw-r--r--layout/style/test/test_computed_style.html664
-rw-r--r--layout/style/test/test_computed_style_bfcache_display_none.html60
-rw-r--r--layout/style/test/test_computed_style_difference.html104
-rw-r--r--layout/style/test/test_computed_style_grid_with_pseudo.html91
-rw-r--r--layout/style/test/test_computed_style_in_created_document.html52
-rw-r--r--layout/style/test/test_computed_style_min_size_auto.html129
-rw-r--r--layout/style/test/test_computed_style_no_flush.html63
-rw-r--r--layout/style/test/test_computed_style_no_pseudo.html53
-rw-r--r--layout/style/test/test_computed_style_prefs.html94
-rw-r--r--layout/style/test/test_condition_text.html74
-rw-r--r--layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html17
-rw-r--r--layout/style/test/test_counter_descriptor_storage.html268
-rw-r--r--layout/style/test/test_counter_style.html121
-rw-r--r--layout/style/test/test_crash_with_content_policy.html75
-rw-r--r--layout/style/test/test_css_cross_domain.html158
-rw-r--r--layout/style/test/test_css_cross_domain_no_orb.html147
-rw-r--r--layout/style/test/test_css_eof_handling.html268
-rw-r--r--layout/style/test/test_css_escape_api.html94
-rw-r--r--layout/style/test/test_css_function_mismatched_parenthesis.html63
-rw-r--r--layout/style/test/test_css_loader_crossorigin_data_url.html17
-rw-r--r--layout/style/test/test_css_parse_error_smoketest.html160
-rw-r--r--layout/style/test/test_css_supports.html134
-rw-r--r--layout/style/test/test_css_supports_variables.html247
-rw-r--r--layout/style/test/test_cue_restrictions.html34
-rw-r--r--layout/style/test/test_custom_content_inheritance.html24
-rw-r--r--layout/style/test/test_default_bidi_css.html80
-rw-r--r--layout/style/test/test_default_computed_style.html58
-rw-r--r--layout/style/test/test_descriptor_storage.html118
-rw-r--r--layout/style/test/test_descriptor_syntax_errors.html53
-rw-r--r--layout/style/test/test_display_mode.html70
-rw-r--r--layout/style/test/test_dont_use_document_colors.html201
-rw-r--r--layout/style/test/test_dont_use_document_fonts.html116
-rw-r--r--layout/style/test/test_dynamic_change_causing_reflow.html1014
-rw-r--r--layout/style/test/test_exposed_prop_accessors.html41
-rw-r--r--layout/style/test/test_extra_inherit_initial.html109
-rw-r--r--layout/style/test/test_first_letter_restrictions.html56
-rw-r--r--layout/style/test/test_first_line_restrictions.html56
-rw-r--r--layout/style/test/test_flexbox_child_display_values.xhtml178
-rw-r--r--layout/style/test/test_flexbox_flex_grow_and_shrink.html154
-rw-r--r--layout/style/test/test_flexbox_flex_shorthand.html280
-rw-r--r--layout/style/test/test_flexbox_focus_order.html83
-rw-r--r--layout/style/test/test_flexbox_layout.html184
-rw-r--r--layout/style/test/test_flexbox_order.html194
-rw-r--r--layout/style/test/test_flexbox_order_abspos.html217
-rw-r--r--layout/style/test/test_flexbox_order_table.html198
-rw-r--r--layout/style/test/test_flexbox_reflow_counts.html199
-rw-r--r--layout/style/test/test_flushing_frame.html40
-rw-r--r--layout/style/test/test_font_face_cascade.html35
-rw-r--r--layout/style/test/test_font_face_parser.html386
-rw-r--r--layout/style/test/test_font_family_parsing.html272
-rw-r--r--layout/style/test/test_font_family_serialization.html51
-rw-r--r--layout/style/test/test_font_loading_api.html1895
-rw-r--r--layout/style/test/test_garbage_at_end_of_declarations.html156
-rw-r--r--layout/style/test/test_grid_computed_values.html113
-rw-r--r--layout/style/test/test_grid_container_shorthands.html271
-rw-r--r--layout/style/test/test_grid_item_shorthands.html153
-rw-r--r--layout/style/test/test_grid_shorthand_serialization.html221
-rw-r--r--layout/style/test/test_group_insertRule.html243
-rw-r--r--layout/style/test/test_hover_on_part.html52
-rw-r--r--layout/style/test/test_hover_quirk.html118
-rw-r--r--layout/style/test/test_html_attribute_computed_values.html84
-rw-r--r--layout/style/test/test_ident_escaping.html56
-rw-r--r--layout/style/test/test_img_src_causing_reflow.html36
-rw-r--r--layout/style/test/test_import_preload.html22
-rw-r--r--layout/style/test/test_inherit_computation.html176
-rw-r--r--layout/style/test/test_inherit_storage.html120
-rw-r--r--layout/style/test/test_initial_computation.html159
-rw-r--r--layout/style/test/test_initial_storage.html134
-rw-r--r--layout/style/test/test_invalidation_basic.html45
-rw-r--r--layout/style/test/test_keyframes_rules.html134
-rw-r--r--layout/style/test/test_keyframes_vendor_prefix.html167
-rw-r--r--layout/style/test/test_load_events_on_stylesheets.html176
-rw-r--r--layout/style/test/test_logical_properties.html422
-rw-r--r--layout/style/test/test_marker_restrictions.html35
-rw-r--r--layout/style/test/test_mask_image_CORS.html63
-rw-r--r--layout/style/test/test_media_queries.html867
-rw-r--r--layout/style/test/test_media_queries_dynamic.html207
-rw-r--r--layout/style/test/test_media_query_list.html373
-rw-r--r--layout/style/test/test_media_query_serialization.html53
-rw-r--r--layout/style/test/test_moz_device_pixel_ratio.html73
-rw-r--r--layout/style/test/test_moz_prefixed_cursor.html14
-rw-r--r--layout/style/test/test_mq_any_hover_and_any_pointer.html97
-rw-r--r--layout/style/test/test_mq_changes_in_iframe.html54
-rw-r--r--layout/style/test/test_mq_hover_and_pointer.html127
-rw-r--r--layout/style/test/test_mq_prefers_contrast_dynamic.html82
-rw-r--r--layout/style/test/test_mq_prefers_reduced_motion_dynamic.html86
-rw-r--r--layout/style/test/test_mql_event_listener_leaks.html43
-rw-r--r--layout/style/test/test_namespace_rule.html461
-rw-r--r--layout/style/test/test_non_content_accessible_env_vars.html39
-rw-r--r--layout/style/test/test_non_content_accessible_properties.html85
-rw-r--r--layout/style/test/test_non_content_accessible_pseudos.html69
-rw-r--r--layout/style/test/test_non_content_accessible_values.html172
-rw-r--r--layout/style/test/test_non_matching_sheet_media.html30
-rw-r--r--layout/style/test/test_of_type_selectors.xhtml97
-rw-r--r--layout/style/test/test_overscroll_behavior_pref.html24
-rw-r--r--layout/style/test/test_page_parser.html93
-rw-r--r--layout/style/test/test_parse_eof.html69
-rw-r--r--layout/style/test/test_parse_ident.html56
-rw-r--r--layout/style/test/test_parse_rule.html261
-rw-r--r--layout/style/test/test_parse_url.html195
-rw-r--r--layout/style/test/test_parser_diagnostics_unprintables.html220
-rw-r--r--layout/style/test/test_pixel_lengths.html61
-rw-r--r--layout/style/test/test_placeholder_restrictions.html57
-rw-r--r--layout/style/test/test_pointer-events.html114
-rw-r--r--layout/style/test/test_position_float_display.html111
-rw-r--r--layout/style/test/test_position_sticky.html89
-rw-r--r--layout/style/test/test_prefers_contrast_color_pairs.html49
-rw-r--r--layout/style/test/test_priority_preservation.html141
-rw-r--r--layout/style/test/test_property_database.html173
-rw-r--r--layout/style/test/test_property_syntax_errors.html155
-rw-r--r--layout/style/test/test_pseudo_display_fixup.html29
-rw-r--r--layout/style/test/test_pseudoelement_parsing.html43
-rw-r--r--layout/style/test/test_pseudoelement_state.html185
-rw-r--r--layout/style/test/test_query_container_for.html62
-rw-r--r--layout/style/test/test_redundant_font_download.html131
-rw-r--r--layout/style/test/test_reframe_cb.html56
-rw-r--r--layout/style/test/test_reframe_image_loading.html29
-rw-r--r--layout/style/test/test_reframe_input.html48
-rw-r--r--layout/style/test/test_reframe_pseudo_element.html46
-rw-r--r--layout/style/test/test_rem_unit.html80
-rw-r--r--layout/style/test/test_restyle_table_wrapper.html33
-rw-r--r--layout/style/test/test_restyles_in_smil_animation.html137
-rw-r--r--layout/style/test/test_revert.html103
-rw-r--r--layout/style/test/test_root_node_display.html74
-rw-r--r--layout/style/test/test_rule_insertion.html240
-rw-r--r--layout/style/test/test_rules_out_of_sheets.html115
-rw-r--r--layout/style/test/test_selectors.html1348
-rw-r--r--layout/style/test/test_setPropertyWithNull.html47
-rw-r--r--layout/style/test/test_shape_outside_CORS.html59
-rw-r--r--layout/style/test/test_shared_sheet_caching.html35
-rw-r--r--layout/style/test/test_sheet_privilege.html33
-rw-r--r--layout/style/test/test_shorthand_property_getters.html248
-rw-r--r--layout/style/test/test_specified_value_serialization.html281
-rw-r--r--layout/style/test/test_style_attr_listener.html52
-rw-r--r--layout/style/test/test_style_attribute_quirks.html18
-rw-r--r--layout/style/test/test_style_attribute_standards.html19
-rw-r--r--layout/style/test/test_style_struct_copy_constructors.html93
-rw-r--r--layout/style/test/test_stylesheet_additions.html68
-rw-r--r--layout/style/test/test_stylesheet_clone_font_face.html26
-rw-r--r--layout/style/test/test_supports_rules.html88
-rw-r--r--layout/style/test/test_system_font_serialization.html84
-rw-r--r--layout/style/test/test_text_decoration_shorthands.html136
-rw-r--r--layout/style/test/test_transitions.html787
-rw-r--r--layout/style/test/test_transitions_and_reframes.html298
-rw-r--r--layout/style/test/test_transitions_and_restyles.html48
-rw-r--r--layout/style/test/test_transitions_and_zoom.html49
-rw-r--r--layout/style/test/test_transitions_at_start.html39
-rw-r--r--layout/style/test/test_transitions_bug537151.html51
-rw-r--r--layout/style/test/test_transitions_cancel_near_end.html82
-rw-r--r--layout/style/test/test_transitions_computed_value_combinations.html170
-rw-r--r--layout/style/test/test_transitions_computed_values.html113
-rw-r--r--layout/style/test/test_transitions_dynamic_changes.html106
-rw-r--r--layout/style/test/test_transitions_events.html294
-rw-r--r--layout/style/test/test_transitions_per_property.html3245
-rw-r--r--layout/style/test/test_transitions_replacement_on_busy_frame.html100
-rw-r--r--layout/style/test/test_transitions_replacement_with_setKeyframes.html88
-rw-r--r--layout/style/test/test_transitions_step_functions.html131
-rw-r--r--layout/style/test/test_unclosed_parentheses.html262
-rw-r--r--layout/style/test/test_unicode_range_loading.html366
-rw-r--r--layout/style/test/test_units_angle.html52
-rw-r--r--layout/style/test/test_units_frequency.html56
-rw-r--r--layout/style/test/test_units_length.html60
-rw-r--r--layout/style/test/test_units_time.html47
-rw-r--r--layout/style/test/test_use_counters.html159
-rw-r--r--layout/style/test/test_user_sheet_shadow_dom.html48
-rw-r--r--layout/style/test/test_value_cloning.html181
-rw-r--r--layout/style/test/test_value_computation.html236
-rw-r--r--layout/style/test/test_value_storage.html365
-rw-r--r--layout/style/test/test_variable_serialization_computed.html79
-rw-r--r--layout/style/test/test_variable_serialization_specified.html116
-rw-r--r--layout/style/test/test_variables.html129
-rw-r--r--layout/style/test/test_variables_loop.html31
-rw-r--r--layout/style/test/test_variables_order.html53
-rw-r--r--layout/style/test/test_video_object_fit.html53
-rw-r--r--layout/style/test/test_viewport_scrollbar_causing_reflow.html130
-rw-r--r--layout/style/test/test_viewport_units.html66
-rw-r--r--layout/style/test/test_visited_image_loading.html68
-rw-r--r--layout/style/test/test_visited_image_loading_empty.html68
-rw-r--r--layout/style/test/test_visited_lying.html97
-rw-r--r--layout/style/test/test_visited_pref.html112
-rw-r--r--layout/style/test/test_visited_reftests.html210
-rw-r--r--layout/style/test/test_webkit_device_pixel_ratio.html73
-rw-r--r--layout/style/test/test_webkit_flex_display.html46
-rw-r--r--layout/style/test/unstyled-frame.css0
-rw-r--r--layout/style/test/unstyled-frame.xml4
-rw-r--r--layout/style/test/unstyled.css2
-rw-r--r--layout/style/test/unstyled.xml3
-rw-r--r--layout/style/test/viewport_units_iframe.html6
-rw-r--r--layout/style/test/visited-lying-inner.html8
-rw-r--r--layout/style/test/visited-pref-iframe.html7
-rw-r--r--layout/style/test/visited_image_loading.sjs83
-rw-r--r--layout/style/test/visited_image_loading_frame.html15
-rw-r--r--layout/style/test/visited_image_loading_frame_empty.html15
-rw-r--r--layout/style/tools/cleanup_computed_getters.py85
1000 files changed, 132168 insertions, 0 deletions
diff --git a/layout/style/AnimatedPropertyID.h b/layout/style/AnimatedPropertyID.h
new file mode 100644
index 0000000000..f532e72122
--- /dev/null
+++ b/layout/style/AnimatedPropertyID.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AnimatedPropertyID_h
+#define mozilla_AnimatedPropertyID_h
+
+#include "nsCSSPropertyID.h"
+#include "nsCSSProps.h"
+#include "nsString.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla {
+
+struct AnimatedPropertyID {
+ explicit AnimatedPropertyID(nsCSSPropertyID aProperty) : mID(aProperty) {
+ MOZ_ASSERT(aProperty != eCSSPropertyExtra_variable,
+ "Cannot create an AnimatedPropertyID from only a "
+ "eCSSPropertyExtra_variable.");
+ }
+
+ explicit AnimatedPropertyID(RefPtr<nsAtom> aCustomName)
+ : mID(eCSSPropertyExtra_variable), mCustomName(std::move(aCustomName)) {
+ MOZ_ASSERT(mCustomName, "Null custom property name");
+ }
+
+ nsCSSPropertyID mID = eCSSProperty_UNKNOWN;
+ RefPtr<nsAtom> mCustomName;
+
+ bool IsCustom() const { return mID == eCSSPropertyExtra_variable; }
+ bool operator==(const AnimatedPropertyID& aOther) const {
+ return mID == aOther.mID && mCustomName == aOther.mCustomName;
+ }
+ bool operator!=(const AnimatedPropertyID& aOther) const {
+ return !(*this == aOther);
+ }
+
+ bool IsValid() const {
+ if (mID == eCSSProperty_UNKNOWN) {
+ return false;
+ }
+ return IsCustom() == !!mCustomName;
+ }
+
+ void ToString(nsACString& aString) const {
+ if (IsCustom()) {
+ MOZ_ASSERT(mCustomName, "Custom property should have a name");
+ // mCustomName does not include the "--" prefix
+ aString.AssignLiteral("--");
+ AppendUTF16toUTF8(nsDependentAtomString(mCustomName), aString);
+ } else {
+ aString.Assign(nsCSSProps::GetStringValue(mID));
+ }
+ }
+
+ void ToString(nsAString& aString) const {
+ if (IsCustom()) {
+ MOZ_ASSERT(mCustomName, "Custom property should have a name");
+ // mCustomName does not include the "--" prefix
+ aString.AssignLiteral("--");
+ aString.Append(nsDependentAtomString(mCustomName));
+ } else {
+ aString.Assign(NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mID)));
+ }
+ }
+
+ HashNumber Hash() const {
+ HashNumber hash = mCustomName ? mCustomName->hash() : 0;
+ return AddToHash(hash, mID);
+ }
+
+ AnimatedPropertyID ToPhysical(const ComputedStyle& aStyle) const {
+ if (IsCustom()) {
+ return *this;
+ }
+ return AnimatedPropertyID{nsCSSProps::Physicalize(mID, aStyle)};
+ }
+};
+
+// MOZ_DBG support for AnimatedPropertyId
+inline std::ostream& operator<<(std::ostream& aOut,
+ const AnimatedPropertyID& aProperty) {
+ if (aProperty.IsCustom()) {
+ return aOut << nsAtomCString(aProperty.mCustomName);
+ }
+ return aOut << nsCSSProps::GetStringValue(aProperty.mID);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_AnimatedPropertyID_h
diff --git a/layout/style/AnimatedPropertyIDSet.h b/layout/style/AnimatedPropertyIDSet.h
new file mode 100644
index 0000000000..667f081421
--- /dev/null
+++ b/layout/style/AnimatedPropertyIDSet.h
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_AnimatedPropertyIDSet_h
+#define mozilla_AnimatedPropertyIDSet_h
+
+#include "mozilla/ServoBindings.h"
+
+#include "AnimatedPropertyID.h"
+#include "nsCSSPropertyIDSet.h"
+#include "nsTHashSet.h"
+
+namespace mozilla {
+
+class AnimatedPropertyIDSet {
+ public:
+ void AddProperty(const AnimatedPropertyID& aProperty) {
+ if (aProperty.IsCustom()) {
+ mCustomNames.Insert(aProperty.mCustomName);
+ } else {
+ mIDs.AddProperty(aProperty.mID);
+ }
+ }
+
+ void RemoveProperty(const AnimatedPropertyID& aProperty) {
+ if (aProperty.IsCustom()) {
+ mCustomNames.Remove(aProperty.mCustomName);
+ } else {
+ mIDs.RemoveProperty(aProperty.mID);
+ }
+ }
+
+ bool HasProperty(const AnimatedPropertyID& aProperty) const {
+ if (aProperty.IsCustom()) {
+ return mCustomNames.Contains(aProperty.mCustomName);
+ }
+ return mIDs.HasProperty(aProperty.mID);
+ }
+
+ bool Intersects(const nsCSSPropertyIDSet& aIDs) const {
+ return mIDs.Intersects(aIDs);
+ }
+
+ bool IsSubsetOf(const AnimatedPropertyIDSet& aOther) const {
+ if (!mIDs.IsSubsetOf(aOther.mIDs) ||
+ mCustomNames.Count() > aOther.mCustomNames.Count()) {
+ return false;
+ }
+ for (const auto& name : mCustomNames) {
+ if (!aOther.mCustomNames.Contains(name)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void AddProperties(const AnimatedPropertyIDSet& aOther) {
+ mIDs |= aOther.mIDs;
+ for (const auto& name : aOther.mCustomNames) {
+ mCustomNames.Insert(name);
+ }
+ }
+
+ using CustomNameSet = nsTHashSet<RefPtr<nsAtom>>;
+
+ // Iterator for use in range-based for loops
+ class Iterator {
+ public:
+ Iterator(Iterator&& aOther)
+ : mPropertySet(aOther.mPropertySet),
+ mIDIterator(std::move(aOther.mIDIterator)),
+ mCustomNameIterator(std::move(aOther.mCustomNameIterator)),
+ mPropertyID(eCSSProperty_UNKNOWN) {}
+ Iterator() = delete;
+ Iterator(const Iterator&) = delete;
+ Iterator& operator=(const Iterator&) = delete;
+ Iterator& operator=(const Iterator&&) = delete;
+
+ static Iterator BeginIterator(const AnimatedPropertyIDSet& aPropertySet) {
+ return Iterator(aPropertySet, aPropertySet.mIDs.begin(),
+ aPropertySet.mCustomNames.begin());
+ }
+
+ static Iterator EndIterator(const AnimatedPropertyIDSet& aPropertySet) {
+ return Iterator(aPropertySet, aPropertySet.mIDs.end(),
+ aPropertySet.mCustomNames.end());
+ }
+
+ bool operator!=(const Iterator& aOther) const {
+ return mIDIterator != aOther.mIDIterator ||
+ mCustomNameIterator != aOther.mCustomNameIterator;
+ }
+
+ Iterator& operator++() {
+ MOZ_ASSERT(mIDIterator != mPropertySet.mIDs.end() ||
+ mCustomNameIterator != mPropertySet.mCustomNames.end(),
+ "Should not iterate beyond end");
+ if (mIDIterator != mPropertySet.mIDs.end()) {
+ ++mIDIterator;
+ } else {
+ ++mCustomNameIterator;
+ }
+ return *this;
+ }
+
+ AnimatedPropertyID operator*() {
+ if (mIDIterator != mPropertySet.mIDs.end()) {
+ mPropertyID.mID = *mIDIterator;
+ mPropertyID.mCustomName = nullptr;
+ } else if (mCustomNameIterator != mPropertySet.mCustomNames.end()) {
+ mPropertyID.mID = eCSSPropertyExtra_variable;
+ mPropertyID.mCustomName = *mCustomNameIterator;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Should not dereference beyond end");
+ mPropertyID.mID = eCSSProperty_UNKNOWN;
+ mPropertyID.mCustomName = nullptr;
+ }
+ return mPropertyID;
+ }
+
+ private:
+ Iterator(const AnimatedPropertyIDSet& aPropertySet,
+ nsCSSPropertyIDSet::Iterator aIDIterator,
+ CustomNameSet::const_iterator aCustomNameIterator)
+ : mPropertySet(aPropertySet),
+ mIDIterator(std::move(aIDIterator)),
+ mCustomNameIterator(std::move(aCustomNameIterator)),
+ mPropertyID(eCSSProperty_UNKNOWN) {}
+
+ const AnimatedPropertyIDSet& mPropertySet;
+ nsCSSPropertyIDSet::Iterator mIDIterator;
+ CustomNameSet::const_iterator mCustomNameIterator;
+ AnimatedPropertyID mPropertyID;
+ };
+
+ Iterator begin() const { return Iterator::BeginIterator(*this); }
+ Iterator end() const { return Iterator::EndIterator(*this); }
+
+ private:
+ nsCSSPropertyIDSet mIDs;
+ CustomNameSet mCustomNames;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AnimatedPropertyIDSet_h
diff --git a/layout/style/AnimationCollection.cpp b/layout/style/AnimationCollection.cpp
new file mode 100644
index 0000000000..69d555fd2d
--- /dev/null
+++ b/layout/style/AnimationCollection.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/AnimationCollection.h"
+#include <type_traits>
+
+#include "mozilla/ElementAnimationData.h"
+#include "mozilla/RestyleManager.h"
+#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
+#include "mozilla/dom/CSSAnimation.h" // For dom::CSSAnimation
+#include "mozilla/dom/CSSTransition.h" // For dom::CSSTransition
+
+namespace mozilla {
+
+template <class AnimationType>
+AnimationCollection<AnimationType>::~AnimationCollection() {
+ MOZ_COUNT_DTOR(AnimationCollection);
+
+ const PostRestyleMode postRestyle =
+ mCalledDestroy ? PostRestyleMode::IfNeeded : PostRestyleMode::Never;
+ {
+ nsAutoAnimationMutationBatch mb(mElement.OwnerDoc());
+
+ for (size_t animIdx = mAnimations.Length(); animIdx-- != 0;) {
+ mAnimations[animIdx]->CancelFromStyle(postRestyle);
+ }
+ }
+ LinkedListElement<SelfType>::remove();
+}
+
+template <class AnimationType>
+void AnimationCollection<AnimationType>::Destroy() {
+ mCalledDestroy = true;
+ auto* data = mElement.GetAnimationData();
+ MOZ_ASSERT(data);
+ if constexpr (std::is_same_v<AnimationType, dom::CSSAnimation>) {
+ MOZ_ASSERT(data->GetAnimationCollection(mPseudo) == this);
+ data->ClearAnimationCollectionFor(mPseudo);
+ } else {
+ MOZ_ASSERT(data->GetTransitionCollection(mPseudo) == this);
+ data->ClearTransitionCollectionFor(mPseudo);
+ }
+}
+
+template <class AnimationType>
+AnimationCollection<AnimationType>* AnimationCollection<AnimationType>::Get(
+ const dom::Element* aElement, const PseudoStyleType aType) {
+ auto* data = aElement->GetAnimationData();
+ if (!data) {
+ return nullptr;
+ }
+ if constexpr (std::is_same_v<AnimationType, dom::CSSAnimation>) {
+ return data->GetAnimationCollection(aType);
+ } else {
+ return data->GetTransitionCollection(aType);
+ }
+}
+
+template <class AnimationType>
+/* static */ AnimationCollection<AnimationType>*
+AnimationCollection<AnimationType>::Get(const nsIFrame* aFrame) {
+ Maybe<NonOwningAnimationTarget> target =
+ EffectCompositor::GetAnimationElementAndPseudoForFrame(aFrame);
+ if (!target) {
+ return nullptr;
+ }
+
+ return Get(target->mElement, target->mPseudoType);
+}
+
+// Explicit class instantiations
+
+template class AnimationCollection<dom::CSSAnimation>;
+template class AnimationCollection<dom::CSSTransition>;
+
+} // namespace mozilla
diff --git a/layout/style/AnimationCollection.h b/layout/style/AnimationCollection.h
new file mode 100644
index 0000000000..84f68506d1
--- /dev/null
+++ b/layout/style/AnimationCollection.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AnimationCollection_h
+#define mozilla_AnimationCollection_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/RefPtr.h"
+#include "nsCSSPseudoElements.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsAtom;
+class nsIFrame;
+class nsPresContext;
+
+namespace mozilla {
+namespace dom {
+class Element;
+}
+enum class PseudoStyleType : uint8_t;
+
+template <class AnimationType>
+class AnimationCollection
+ : public LinkedListElement<AnimationCollection<AnimationType>> {
+ typedef AnimationCollection<AnimationType> SelfType;
+
+ public:
+ AnimationCollection(dom::Element& aOwner, PseudoStyleType aPseudoType)
+ : mElement(aOwner), mPseudo(aPseudoType) {
+ MOZ_COUNT_CTOR(AnimationCollection);
+ }
+
+ ~AnimationCollection();
+
+ void Destroy();
+
+ // Given the frame |aFrame| with possibly animated content, finds its
+ // associated collection of animations. If |aFrame| is a generated content
+ // frame, this function may examine the parent frame to search for such
+ // animations.
+ static AnimationCollection* Get(const nsIFrame* aFrame);
+ static AnimationCollection* Get(const dom::Element* aElement,
+ PseudoStyleType aPseudoType);
+
+ // The element. Weak reference is fine since it owns us.
+ // FIXME(emilio): These are only needed for Destroy(), so maybe remove and
+ // rely on the caller clearing us properly?
+ dom::Element& mElement;
+ const PseudoStyleType mPseudo;
+
+ nsTArray<RefPtr<AnimationType>> mAnimations;
+
+ private:
+ // We distinguish between destroying this by calling Destroy() vs directly
+ // clearing the collection.
+ //
+ // The former case represents regular updating due to style changes and should
+ // trigger subsequent restyles.
+ //
+ // The latter case represents document tear-down or other DOM surgery in
+ // which case we should not trigger restyles.
+ bool mCalledDestroy = false;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AnimationCollection_h
diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h
new file mode 100644
index 0000000000..d7a54c9751
--- /dev/null
+++ b/layout/style/AnimationCommon.h
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_css_AnimationCommon_h
+#define mozilla_css_AnimationCommon_h
+
+#include "mozilla/AnimationCollection.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/BaseKeyframeTypesBinding.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TimingParams.h"
+#include "mozilla/dom/Nullable.h"
+#include "nsContentUtils.h"
+#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
+
+class nsPresContext;
+
+namespace mozilla {
+enum class PseudoStyleType : uint8_t;
+
+namespace dom {
+class Element;
+}
+
+template <class AnimationType>
+class CommonAnimationManager {
+ public:
+ explicit CommonAnimationManager(nsPresContext* aPresContext)
+ : mPresContext(aPresContext) {}
+
+ // NOTE: This can return null after Disconnect().
+ nsPresContext* PresContext() const { return mPresContext; }
+
+ /**
+ * Notify the manager that the pres context is going away.
+ */
+ void Disconnect() {
+ // Content nodes might outlive the transition or animation manager.
+ RemoveAllElementCollections();
+
+ mPresContext = nullptr;
+ }
+
+ /**
+ * Stop animations on the element. This method takes the real element
+ * rather than the element for the generated content for animations on
+ * ::before, ::after and ::marker.
+ */
+ void StopAnimationsForElement(dom::Element* aElement,
+ PseudoStyleType aPseudoType) {
+ MOZ_ASSERT(aElement);
+ auto* collection =
+ AnimationCollection<AnimationType>::Get(aElement, aPseudoType);
+ if (!collection) {
+ return;
+ }
+
+ nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
+ collection->Destroy();
+ }
+
+ protected:
+ virtual ~CommonAnimationManager() {
+ MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
+ }
+
+ void AddElementCollection(AnimationCollection<AnimationType>* aCollection) {
+ mElementCollections.insertBack(aCollection);
+ }
+ void RemoveAllElementCollections() {
+ while (AnimationCollection<AnimationType>* head =
+ mElementCollections.getFirst()) {
+ head->Destroy(); // Note: this removes 'head' from mElementCollections.
+ }
+ }
+
+ LinkedList<AnimationCollection<AnimationType>> mElementCollections;
+ nsPresContext* mPresContext; // weak (non-null from ctor to Disconnect)
+};
+
+/**
+ * Utility class for referencing the element that created a CSS animation or
+ * transition. It is non-owning (i.e. it uses a raw pointer) since it is only
+ * expected to be set by the owned animation while it actually being managed
+ * by the owning element.
+ *
+ * This class also abstracts the comparison of an element/pseudo-class pair
+ * for the sake of composite ordering since this logic is common to both CSS
+ * animations and transitions.
+ *
+ * (We call this OwningElementRef instead of just OwningElement so that we can
+ * call the getter on CSSAnimation/CSSTransition OwningElement() without
+ * clashing with this object's contructor.)
+ */
+class OwningElementRef final {
+ public:
+ OwningElementRef() = default;
+
+ explicit OwningElementRef(const NonOwningAnimationTarget& aTarget)
+ : mTarget(aTarget) {}
+
+ OwningElementRef(dom::Element& aElement, PseudoStyleType aPseudoType)
+ : mTarget(&aElement, aPseudoType) {}
+
+ bool Equals(const OwningElementRef& aOther) const {
+ return mTarget == aOther.mTarget;
+ }
+
+ bool LessThan(Maybe<uint32_t>& aChildIndex, const OwningElementRef& aOther,
+ Maybe<uint32_t>& aOtherChildIndex) const {
+ MOZ_ASSERT(mTarget.mElement && aOther.mTarget.mElement,
+ "Elements to compare should not be null");
+
+ if (mTarget.mElement != aOther.mTarget.mElement) {
+ return nsContentUtils::PositionIsBefore(mTarget.mElement,
+ aOther.mTarget.mElement,
+ &aChildIndex, &aOtherChildIndex);
+ }
+
+ return mTarget.mPseudoType == PseudoStyleType::NotPseudo ||
+ (mTarget.mPseudoType == PseudoStyleType::before &&
+ aOther.mTarget.mPseudoType == PseudoStyleType::after) ||
+ (mTarget.mPseudoType == PseudoStyleType::marker &&
+ aOther.mTarget.mPseudoType == PseudoStyleType::before) ||
+ (mTarget.mPseudoType == PseudoStyleType::marker &&
+ aOther.mTarget.mPseudoType == PseudoStyleType::after);
+ }
+
+ bool IsSet() const { return !!mTarget.mElement; }
+
+ void GetElement(dom::Element*& aElement, PseudoStyleType& aPseudoType) const {
+ aElement = mTarget.mElement;
+ aPseudoType = mTarget.mPseudoType;
+ }
+
+ const NonOwningAnimationTarget& Target() const { return mTarget; }
+
+ nsPresContext* GetPresContext() const {
+ return nsContentUtils::GetContextForContent(mTarget.mElement);
+ }
+
+ private:
+ NonOwningAnimationTarget mTarget;
+};
+
+// Return the TransitionPhase or AnimationPhase to use when the animation
+// doesn't have a target effect.
+template <typename PhaseType>
+PhaseType GetAnimationPhaseWithoutEffect(const dom::Animation& aAnimation) {
+ MOZ_ASSERT(!aAnimation.GetEffect(),
+ "Should only be called when we do not have an effect");
+
+ dom::Nullable<TimeDuration> currentTime =
+ aAnimation.GetCurrentTimeAsDuration();
+ if (currentTime.IsNull()) {
+ return PhaseType::Idle;
+ }
+
+ // If we don't have a target effect, the duration will be zero so the phase is
+ // 'before' if the current time is less than zero.
+ return currentTime.Value() < TimeDuration() ? PhaseType::Before
+ : PhaseType::After;
+};
+
+inline dom::PlaybackDirection StyleToDom(StyleAnimationDirection aDirection) {
+ switch (aDirection) {
+ case StyleAnimationDirection::Normal:
+ return dom::PlaybackDirection::Normal;
+ case StyleAnimationDirection::Reverse:
+ return dom::PlaybackDirection::Reverse;
+ case StyleAnimationDirection::Alternate:
+ return dom::PlaybackDirection::Alternate;
+ case StyleAnimationDirection::AlternateReverse:
+ return dom::PlaybackDirection::Alternate_reverse;
+ }
+ MOZ_ASSERT_UNREACHABLE("Wrong style value?");
+ return dom::PlaybackDirection::Normal;
+}
+
+inline dom::FillMode StyleToDom(StyleAnimationFillMode aFillMode) {
+ switch (aFillMode) {
+ case StyleAnimationFillMode::None:
+ return dom::FillMode::None;
+ case StyleAnimationFillMode::Both:
+ return dom::FillMode::Both;
+ case StyleAnimationFillMode::Forwards:
+ return dom::FillMode::Forwards;
+ case StyleAnimationFillMode::Backwards:
+ return dom::FillMode::Backwards;
+ }
+ MOZ_ASSERT_UNREACHABLE("Wrong style value?");
+ return dom::FillMode::None;
+}
+
+inline dom::CompositeOperation StyleToDom(StyleAnimationComposition aStyle) {
+ switch (aStyle) {
+ case StyleAnimationComposition::Replace:
+ return dom::CompositeOperation::Replace;
+ case StyleAnimationComposition::Add:
+ return dom::CompositeOperation::Add;
+ case StyleAnimationComposition::Accumulate:
+ return dom::CompositeOperation::Accumulate;
+ }
+ MOZ_ASSERT_UNREACHABLE("Invalid style composite operation?");
+ return dom::CompositeOperation::Replace;
+}
+
+inline TimingParams TimingParamsFromCSSParams(
+ float aDuration, float aDelay, float aIterationCount,
+ StyleAnimationDirection aDirection, StyleAnimationFillMode aFillMode) {
+ MOZ_ASSERT(aIterationCount >= 0.0 && !std::isnan(aIterationCount),
+ "aIterations should be nonnegative & finite, as ensured by "
+ "CSSParser");
+ return TimingParams{aDuration, aDelay, aIterationCount,
+ StyleToDom(aDirection), StyleToDom(aFillMode)};
+}
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_css_AnimationCommon_h) */
diff --git a/layout/style/AttributeStyles.cpp b/layout/style/AttributeStyles.cpp
new file mode 100644
index 0000000000..5bf74d2697
--- /dev/null
+++ b/layout/style/AttributeStyles.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Data from presentational HTML attributes and style attributes */
+
+#include "mozilla/AttributeStyles.h"
+
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Element.h"
+#include "nsStyleConsts.h"
+#include "nsError.h"
+#include "nsHashKeys.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleSet.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+// -----------------------------------------------------------
+
+AttributeStyles::AttributeStyles(Document* aDocument) : mDocument(aDocument) {
+ MOZ_ASSERT(aDocument);
+}
+
+void AttributeStyles::SetOwningDocument(Document* aDocument) {
+ mDocument = aDocument; // not refcounted
+}
+
+void AttributeStyles::Reset() {
+ mServoUnvisitedLinkDecl = nullptr;
+ mServoVisitedLinkDecl = nullptr;
+ mServoActiveLinkDecl = nullptr;
+}
+
+nsresult AttributeStyles::ImplLinkColorSetter(
+ RefPtr<StyleLockedDeclarationBlock>& aDecl, nscolor aColor) {
+ if (!mDocument || !mDocument->GetPresShell()) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
+ aDecl = Servo_DeclarationBlock_CreateEmpty().Consume();
+ Servo_DeclarationBlock_SetColorValue(aDecl.get(), eCSSProperty_color, aColor);
+
+ // Now make sure we restyle any links that might need it. This
+ // shouldn't happen often, so just rebuilding everything is ok.
+ if (Element* root = mDocument->GetRootElement()) {
+ RestyleManager* rm = mDocument->GetPresContext()->RestyleManager();
+ rm->PostRestyleEvent(root, RestyleHint::RestyleSubtree(), nsChangeHint(0));
+ }
+ return NS_OK;
+}
+
+nsresult AttributeStyles::SetLinkColor(nscolor aColor) {
+ return ImplLinkColorSetter(mServoUnvisitedLinkDecl, aColor);
+}
+
+nsresult AttributeStyles::SetActiveLinkColor(nscolor aColor) {
+ return ImplLinkColorSetter(mServoActiveLinkDecl, aColor);
+}
+
+nsresult AttributeStyles::SetVisitedLinkColor(nscolor aColor) {
+ return ImplLinkColorSetter(mServoVisitedLinkDecl, aColor);
+}
+
+size_t AttributeStyles::DOMSizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mServoUnvisitedLinkDecl;
+ // - mServoVisitedLinkDecl;
+ // - mServoActiveLinkDecl;
+ //
+ // The following members are not measured:
+ // - mDocument, because it's non-owning
+ return n;
+}
+
+AttributeStyles::~AttributeStyles() {
+ // We may go away before all of our cached style attributes do, so clean up
+ // any that are left.
+ for (auto iter = mCachedStyleAttrs.Iter(); !iter.Done(); iter.Next()) {
+ MiscContainer*& value = iter.Data();
+
+ // Ideally we'd just call MiscContainer::Evict, but we can't do that since
+ // we're iterating the hashtable.
+ if (value->mType == nsAttrValue::eCSSDeclaration) {
+ DeclarationBlock* declaration = value->mValue.mCSSDeclaration;
+ declaration->SetAttributeStyles(nullptr);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
+ }
+
+ value->mValue.mCached = 0;
+ iter.Remove();
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/style/AttributeStyles.h b/layout/style/AttributeStyles.h
new file mode 100644
index 0000000000..82220cdb56
--- /dev/null
+++ b/layout/style/AttributeStyles.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * style sheet and style rule processor representing data from presentational
+ * HTML attributes
+ */
+
+#ifndef mozilla_AttributeStyles_h_
+#define mozilla_AttributeStyles_h_
+
+#include "nsColor.h"
+#include "nsCOMPtr.h"
+#include "nsAtom.h"
+#include "PLDHashTable.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsTHashMap.h"
+#include "nsString.h"
+
+struct MiscContainer;
+namespace mozilla {
+struct StyleLockedDeclarationBlock;
+namespace dom {
+class Document;
+} // namespace dom
+
+class AttributeStyles final {
+ public:
+ explicit AttributeStyles(dom::Document* aDocument);
+ void SetOwningDocument(dom::Document* aDocument);
+
+ NS_INLINE_DECL_REFCOUNTING(AttributeStyles)
+
+ size_t DOMSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ void Reset();
+ nsresult SetLinkColor(nscolor aColor);
+ nsresult SetActiveLinkColor(nscolor aColor);
+ nsresult SetVisitedLinkColor(nscolor aColor);
+
+ const StyleLockedDeclarationBlock* GetServoUnvisitedLinkDecl() const {
+ return mServoUnvisitedLinkDecl;
+ }
+ const StyleLockedDeclarationBlock* GetServoVisitedLinkDecl() const {
+ return mServoVisitedLinkDecl;
+ }
+ const StyleLockedDeclarationBlock* GetServoActiveLinkDecl() const {
+ return mServoActiveLinkDecl;
+ }
+
+ void CacheStyleAttr(const nsAString& aSerialized, MiscContainer* aValue) {
+ mCachedStyleAttrs.InsertOrUpdate(aSerialized, aValue);
+ }
+ void EvictStyleAttr(const nsAString& aSerialized, MiscContainer* aValue) {
+ NS_ASSERTION(LookupStyleAttr(aSerialized) == aValue,
+ "Cached value doesn't match?");
+ mCachedStyleAttrs.Remove(aSerialized);
+ }
+ MiscContainer* LookupStyleAttr(const nsAString& aSerialized) {
+ return mCachedStyleAttrs.Get(aSerialized);
+ }
+
+ AttributeStyles(const AttributeStyles& aCopy) = delete;
+ AttributeStyles& operator=(const AttributeStyles& aCopy) = delete;
+
+ private:
+ ~AttributeStyles();
+
+ // Implementation of SetLink/VisitedLink/ActiveLinkColor
+ nsresult ImplLinkColorSetter(RefPtr<StyleLockedDeclarationBlock>& aDecl,
+ nscolor aColor);
+
+ dom::Document* mDocument;
+ RefPtr<StyleLockedDeclarationBlock> mServoUnvisitedLinkDecl;
+ RefPtr<StyleLockedDeclarationBlock> mServoVisitedLinkDecl;
+ RefPtr<StyleLockedDeclarationBlock> mServoActiveLinkDecl;
+ nsTHashMap<nsStringHashKey, MiscContainer*> mCachedStyleAttrs;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/BuiltinCounterStyleList.h b/layout/style/BuiltinCounterStyleList.h
new file mode 100644
index 0000000000..e68b72e59d
--- /dev/null
+++ b/layout/style/BuiltinCounterStyleList.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 builtin counter styles */
+
+/* Each entry is defined as a BUILTIN_COUNTER_STYLE macro with the
+ * following parameters:
+ * - 'value_' which is final part of name of NS_STYLE_LIST_STYLE_* macros
+ * - 'atom_' which is the corresponding atom in nsGkAtoms table
+ *
+ * Users of this list should define the following macro before including
+ * this file: BUILTIN_COUNTER_STYLE(value_, atom_)
+ */
+
+// none and decimal are not redefinable, so they have to be builtin.
+BUILTIN_COUNTER_STYLE(None, none)
+BUILTIN_COUNTER_STYLE(Decimal, decimal)
+// the following graphic styles are processed in a different way.
+BUILTIN_COUNTER_STYLE(Disc, disc)
+BUILTIN_COUNTER_STYLE(Circle, circle)
+BUILTIN_COUNTER_STYLE(Square, square)
+BUILTIN_COUNTER_STYLE(DisclosureClosed, disclosure_closed)
+BUILTIN_COUNTER_STYLE(DisclosureOpen, disclosure_open)
+// the following counter styles require specific algorithms to generate.
+BUILTIN_COUNTER_STYLE(Hebrew, hebrew)
+BUILTIN_COUNTER_STYLE(JapaneseInformal, japanese_informal)
+BUILTIN_COUNTER_STYLE(JapaneseFormal, japanese_formal)
+BUILTIN_COUNTER_STYLE(KoreanHangulFormal, korean_hangul_formal)
+BUILTIN_COUNTER_STYLE(KoreanHanjaInformal, korean_hanja_informal)
+BUILTIN_COUNTER_STYLE(KoreanHanjaFormal, korean_hanja_formal)
+BUILTIN_COUNTER_STYLE(SimpChineseInformal, simp_chinese_informal)
+BUILTIN_COUNTER_STYLE(SimpChineseFormal, simp_chinese_formal)
+BUILTIN_COUNTER_STYLE(TradChineseInformal, trad_chinese_informal)
+BUILTIN_COUNTER_STYLE(TradChineseFormal, trad_chinese_formal)
+BUILTIN_COUNTER_STYLE(EthiopicNumeric, ethiopic_numeric)
diff --git a/layout/style/CSS.cpp b/layout/style/CSS.cpp
new file mode 100644
index 0000000000..28db5a34c5
--- /dev/null
+++ b/layout/style/CSS.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object holding utility CSS functions */
+
+#include "CSS.h"
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/HighlightRegistry.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsStyleUtil.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+/* static */
+bool CSS::Supports(const GlobalObject&, const nsACString& aProperty,
+ const nsACString& aValue) {
+ return Servo_CSSSupports2(&aProperty, &aValue);
+}
+
+/* static */
+bool CSS::Supports(const GlobalObject&, const nsACString& aCondition) {
+ return Servo_CSSSupports(&aCondition, /* ua = */ false, /* chrome = */ false,
+ /* quirks = */ false);
+}
+
+/* static */
+void CSS::Escape(const GlobalObject&, const nsAString& aIdent,
+ nsAString& aReturn) {
+ nsStyleUtil::AppendEscapedCSSIdent(aIdent, aReturn);
+}
+
+static Document* GetDocument(const GlobalObject& aGlobal) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_DIAGNOSTIC_ASSERT(window, "CSS is only exposed to window globals");
+ if (!window) {
+ return nullptr;
+ }
+ return window->GetExtantDoc();
+}
+
+/* static */
+HighlightRegistry* CSS::GetHighlights(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ Document* doc = GetDocument(aGlobal);
+ if (!doc) {
+ aRv.ThrowUnknownError("No document associated to this global?");
+ return nullptr;
+ }
+ return &doc->HighlightRegistry();
+}
+
+/* static */
+void CSS::RegisterProperty(const GlobalObject& aGlobal,
+ const PropertyDefinition& aDefinition,
+ ErrorResult& aRv) {
+ Document* doc = GetDocument(aGlobal);
+ if (!doc) {
+ return aRv.ThrowUnknownError("No document associated to this global?");
+ }
+ doc->EnsureStyleSet().RegisterProperty(aDefinition, aRv);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSS.h b/layout/style/CSS.h
new file mode 100644
index 0000000000..fc3d6ff96f
--- /dev/null
+++ b/layout/style/CSS.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object holding utility CSS functions */
+
+#ifndef mozilla_dom_CSS_h_
+#define mozilla_dom_CSS_h_
+
+#include "mozilla/Attributes.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class HighlightRegistry;
+struct PropertyDefinition;
+
+class CSS {
+ public:
+ CSS() = delete;
+
+ static bool Supports(const GlobalObject&, const nsACString& aProperty,
+ const nsACString& aValue);
+
+ static bool Supports(const GlobalObject&, const nsACString& aDeclaration);
+
+ static void Escape(const GlobalObject&, const nsAString& aIdent,
+ nsAString& aReturn);
+
+ static HighlightRegistry* GetHighlights(const GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
+ static void RegisterProperty(const GlobalObject&, const PropertyDefinition&,
+ ErrorResult&);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CSS_h_
diff --git a/layout/style/CSSContainerRule.cpp b/layout/style/CSSContainerRule.cpp
new file mode 100644
index 0000000000..4d798c0dda
--- /dev/null
+++ b/layout/style/CSSContainerRule.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSContainerRule.h"
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/dom/CSSContainerRuleBinding.h"
+#include "mozilla/ServoBindings.h"
+
+using namespace mozilla::css;
+
+namespace mozilla::dom {
+
+CSSContainerRule::CSSContainerRule(RefPtr<StyleContainerRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::ConditionRule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+CSSContainerRule::~CSSContainerRule() = default;
+
+NS_IMPL_ADDREF_INHERITED(CSSContainerRule, ConditionRule)
+NS_IMPL_RELEASE_INHERITED(CSSContainerRule, ConditionRule)
+
+// QueryInterface implementation for ContainerRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSContainerRule)
+NS_INTERFACE_MAP_END_INHERITING(ConditionRule)
+
+#ifdef DEBUG
+/* virtual */
+void CSSContainerRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_ContainerRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+already_AddRefed<StyleLockedCssRules> CSSContainerRule::GetOrCreateRawRules() {
+ return Servo_ContainerRule_GetRules(mRawRule).Consume();
+}
+
+StyleCssRuleType CSSContainerRule::Type() const {
+ return StyleCssRuleType::Container;
+}
+
+void CSSContainerRule::GetConditionText(nsACString& aConditionText) {
+ Servo_ContainerRule_GetConditionText(mRawRule, &aConditionText);
+}
+
+/* virtual */
+void CSSContainerRule::GetCssText(nsACString& aCssText) const {
+ Servo_ContainerRule_GetCssText(mRawRule, &aCssText);
+}
+
+void CSSContainerRule::GetContainerName(nsACString& aName) const {
+ Servo_ContainerRule_GetContainerName(mRawRule, &aName);
+}
+
+void CSSContainerRule::GetContainerQuery(nsACString& aQuery) const {
+ Servo_ContainerRule_GetContainerQuery(mRawRule, &aQuery);
+}
+
+Element* CSSContainerRule::QueryContainerFor(const Element& aElement) const {
+ return const_cast<Element*>(
+ Servo_ContainerRule_QueryContainerFor(mRawRule, &aElement));
+}
+
+void CSSContainerRule::SetRawAfterClone(RefPtr<StyleContainerRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ css::ConditionRule::DidSetRawAfterClone();
+}
+
+/* virtual */
+size_t CSSContainerRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ // TODO Implement this!
+ return aMallocSizeOf(this);
+}
+
+/* virtual */
+JSObject* CSSContainerRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSContainerRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSContainerRule.h b/layout/style/CSSContainerRule.h
new file mode 100644
index 0000000000..683a17604e
--- /dev/null
+++ b/layout/style/CSSContainerRule.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSContainerRule_h
+#define mozilla_dom_CSSContainerRule_h
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+namespace mozilla::dom {
+
+class CSSContainerRule final : public css::ConditionRule {
+ public:
+ CSSContainerRule(RefPtr<StyleContainerRule> aRawRule, StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ StyleContainerRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleContainerRule>);
+
+ already_AddRefed<StyleLockedCssRules> GetOrCreateRawRules() final;
+
+ // WebIDL interface
+ StyleCssRuleType Type() const override;
+ // WebIDL interface
+ void GetCssText(nsACString& aCssText) const final;
+ void GetConditionText(nsACString& aConditionText) final;
+
+ void GetContainerName(nsACString&) const;
+ void GetContainerQuery(nsACString&) const;
+
+ size_t SizeOfIncludingThis(MallocSizeOf) const override;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ Element* QueryContainerFor(const Element&) const;
+
+ private:
+ virtual ~CSSContainerRule();
+
+ RefPtr<StyleContainerRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSContainerRule_h
diff --git a/layout/style/CSSCounterStyleRule.cpp b/layout/style/CSSCounterStyleRule.cpp
new file mode 100644
index 0000000000..2a3f5a0f82
--- /dev/null
+++ b/layout/style/CSSCounterStyleRule.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/CSSCounterStyleRule.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/dom/CSSCounterStyleRuleBinding.h"
+#include "mozilla/ServoBindings.h"
+#include "nsStyleUtil.h"
+
+namespace mozilla {
+namespace dom {
+
+bool CSSCounterStyleRule::IsCCLeaf() const { return Rule::IsCCLeaf(); }
+
+#ifdef DEBUG
+void CSSCounterStyleRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_CounterStyleRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+StyleCssRuleType CSSCounterStyleRule::Type() const {
+ return StyleCssRuleType::CounterStyle;
+}
+
+void CSSCounterStyleRule::SetRawAfterClone(
+ RefPtr<StyleLockedCounterStyleRule> aRaw) {
+ mRawRule = std::move(aRaw);
+}
+
+void CSSCounterStyleRule::GetCssText(nsACString& aCssText) const {
+ Servo_CounterStyleRule_GetCssText(mRawRule, &aCssText);
+}
+
+void CSSCounterStyleRule::GetName(nsAString& aName) {
+ aName.Truncate();
+ nsAtom* name = Servo_CounterStyleRule_GetName(mRawRule);
+ nsDependentAtomString nameStr(name);
+ nsStyleUtil::AppendEscapedCSSIdent(nameStr, aName);
+}
+
+template <typename Func>
+void CSSCounterStyleRule::ModifyRule(Func aCallback) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ StyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->WillDirty();
+ }
+
+ if (aCallback() && sheet) {
+ sheet->RuleChanged(this, StyleRuleChangeKind::Generic);
+ }
+}
+
+void CSSCounterStyleRule::SetName(const nsAString& aName) {
+ ModifyRule([&] {
+ NS_ConvertUTF16toUTF8 name(aName);
+ return Servo_CounterStyleRule_SetName(mRawRule, &name);
+ });
+}
+
+#define CSS_COUNTER_DESC(name_, method_) \
+ void CSSCounterStyleRule::Get##method_(nsACString& aValue) { \
+ MOZ_ASSERT(aValue.IsEmpty()); \
+ Servo_CounterStyleRule_GetDescriptorCssText( \
+ mRawRule, eCSSCounterDesc_##method_, &aValue); \
+ } \
+ void CSSCounterStyleRule::Set##method_(const nsACString& aValue) { \
+ ModifyRule([&] { \
+ return Servo_CounterStyleRule_SetDescriptor( \
+ mRawRule, eCSSCounterDesc_##method_, &aValue); \
+ }); \
+ }
+#include "nsCSSCounterDescList.h"
+#undef CSS_COUNTER_DESC
+
+/* virtual */
+size_t CSSCounterStyleRule::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+}
+
+/* virtual */
+JSObject* CSSCounterStyleRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSCounterStyleRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/CSSCounterStyleRule.h b/layout/style/CSSCounterStyleRule.h
new file mode 100644
index 0000000000..fa8bb19c5e
--- /dev/null
+++ b/layout/style/CSSCounterStyleRule.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CSSCounterStyleRule_h
+#define mozilla_CSSCounterStyleRule_h
+
+#include "mozilla/css/Rule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+struct StyleLockedCounterStyleRule;
+
+namespace mozilla::dom {
+
+class CSSCounterStyleRule final : public css::Rule {
+ public:
+ CSSCounterStyleRule(already_AddRefed<StyleLockedCounterStyleRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+ private:
+ CSSCounterStyleRule(const CSSCounterStyleRule& aCopy) = delete;
+ ~CSSCounterStyleRule() = default;
+
+ template <typename Func>
+ void ModifyRule(Func);
+
+ public:
+ bool IsCCLeaf() const final;
+
+ const StyleLockedCounterStyleRule* Raw() const { return mRawRule.get(); }
+ void SetRawAfterClone(RefPtr<StyleLockedCounterStyleRule>);
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ // WebIDL interface
+ StyleCssRuleType Type() const override;
+ void GetCssText(nsACString& aCssText) const override;
+ void GetName(nsAString& aName);
+ void SetName(const nsAString& aName);
+#define CSS_COUNTER_DESC(name_, method_) \
+ void Get##method_(nsACString& aValue); \
+ void Set##method_(const nsACString& aValue);
+#include "nsCSSCounterDescList.h"
+#undef CSS_COUNTER_DESC
+
+ size_t SizeOfIncludingThis(MallocSizeOf) const final;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ private:
+ RefPtr<StyleLockedCounterStyleRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_CSSCounterStyleRule_h
diff --git a/layout/style/CSSEnabledState.h b/layout/style/CSSEnabledState.h
new file mode 100644
index 0000000000..b8793d1643
--- /dev/null
+++ b/layout/style/CSSEnabledState.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 for whether a CSS feature (property, pseudo-class, etc.) is
+ * enabled in a specific context
+ */
+
+#ifndef mozilla_CSSEnabledState_h
+#define mozilla_CSSEnabledState_h
+
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+
+enum class CSSEnabledState {
+ // The default CSSEnabledState: only enable what's enabled for all
+ // content, given the current values of preferences.
+ ForAllContent = 0,
+ // Enable features available in UA sheets.
+ InUASheets = 0x01,
+ // Enable features available in chrome code.
+ InChrome = 0x02,
+ // Special value to unconditionally enable everything. This implies
+ // all the bits above, but is strictly more than just their OR-ed
+ // union. This just skips any test so a feature will be enabled even
+ // if it would have been disabled with all the bits above set.
+ IgnoreEnabledState = 0xff
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CSSEnabledState)
+
+} // namespace mozilla
+
+#endif // mozilla_CSSEnabledState_h
diff --git a/layout/style/CSSFontFaceRule.cpp b/layout/style/CSSFontFaceRule.cpp
new file mode 100644
index 0000000000..1115c9223f
--- /dev/null
+++ b/layout/style/CSSFontFaceRule.cpp
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSFontFaceRule.h"
+
+#include "mozilla/dom/CSSFontFaceRuleBinding.h"
+#include "mozilla/dom/CSSStyleDeclarationBinding.h"
+#include "mozilla/ServoBindings.h"
+#include "nsCSSProps.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// -------------------------------------------
+// CSSFontFaceRuleDecl and related routines
+//
+
+// QueryInterface implementation for CSSFontFaceRuleDecl
+NS_INTERFACE_MAP_BEGIN(CSSFontFaceRuleDecl)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsICSSDeclaration)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ // We forward the cycle collection interfaces to ContainingRule(), which is
+ // never null (in fact, we're part of that object!)
+ if (aIID.Equals(NS_GET_IID(nsCycleCollectionISupports)) ||
+ aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant))) {
+ return ContainingRule()->QueryInterface(aIID, aInstancePtr);
+ } else
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF_USING_AGGREGATOR(CSSFontFaceRuleDecl, ContainingRule())
+NS_IMPL_RELEASE_USING_AGGREGATOR(CSSFontFaceRuleDecl, ContainingRule())
+
+void CSSFontFaceRuleDecl::SetRawAfterClone(
+ RefPtr<StyleLockedFontFaceRule> aRaw) {
+ mRawRule = std::move(aRaw);
+}
+
+// helper for string GetPropertyValue and RemovePropertyValue
+void CSSFontFaceRuleDecl::GetPropertyValue(nsCSSFontDesc aFontDescID,
+ nsACString& aResult) const {
+ MOZ_ASSERT(aResult.IsEmpty());
+ Servo_FontFaceRule_GetDescriptorCssText(mRawRule, aFontDescID, &aResult);
+}
+
+void CSSFontFaceRuleDecl::GetCssText(nsACString& aCssText) {
+ MOZ_ASSERT(aCssText.IsEmpty());
+ Servo_FontFaceRule_GetDeclCssText(mRawRule, &aCssText);
+}
+
+void CSSFontFaceRuleDecl::SetCssText(const nsACString& aCssText,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (ContainingRule()->IsReadOnly()) {
+ return;
+ }
+ // bug 443978
+ aRv.ThrowNotSupportedError(
+ "Can't set cssText on CSSFontFaceRule declarations");
+}
+
+void CSSFontFaceRuleDecl::GetPropertyValue(const nsACString& aPropName,
+ nsACString& aResult) {
+ aResult.Truncate();
+ nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(aPropName);
+ if (descID != eCSSFontDesc_UNKNOWN) {
+ GetPropertyValue(descID, aResult);
+ }
+}
+
+void CSSFontFaceRuleDecl::RemoveProperty(const nsACString& aPropName,
+ nsACString& aResult,
+ ErrorResult& aRv) {
+ nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(aPropName);
+ NS_ASSERTION(descID >= eCSSFontDesc_UNKNOWN && descID < eCSSFontDesc_COUNT,
+ "LookupFontDesc returned value out of range");
+
+ if (ContainingRule()->IsReadOnly()) {
+ return;
+ }
+
+ aResult.Truncate();
+ if (descID != eCSSFontDesc_UNKNOWN) {
+ GetPropertyValue(descID, aResult);
+ Servo_FontFaceRule_ResetDescriptor(mRawRule, descID);
+ }
+}
+
+void CSSFontFaceRuleDecl::GetPropertyPriority(const nsACString& aPropName,
+ nsACString& aResult) {
+ // font descriptors do not have priorities at present
+ aResult.Truncate();
+}
+
+void CSSFontFaceRuleDecl::SetProperty(const nsACString& aPropName,
+ const nsACString& aValue,
+ const nsACString& aPriority,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv) {
+ // FIXME(heycam): If we are changing unicode-range, then a FontFace object
+ // representing this rule must have its mUnicodeRange value invalidated.
+
+ if (ContainingRule()->IsReadOnly()) {
+ return;
+ }
+
+ // bug 443978
+ aRv.ThrowNotSupportedError(
+ "Can't set properties on CSSFontFaceRule declarations");
+}
+
+uint32_t CSSFontFaceRuleDecl::Length() {
+ return Servo_FontFaceRule_Length(mRawRule);
+}
+
+void CSSFontFaceRuleDecl::IndexedGetter(uint32_t aIndex, bool& aFound,
+ nsACString& aResult) {
+ nsCSSFontDesc id = Servo_FontFaceRule_IndexGetter(mRawRule, aIndex);
+ if (id != eCSSFontDesc_UNKNOWN) {
+ aFound = true;
+ aResult.Assign(nsCSSProps::GetStringValue(id));
+ } else {
+ aFound = false;
+ }
+}
+
+css::Rule* CSSFontFaceRuleDecl::GetParentRule() { return ContainingRule(); }
+
+nsINode* CSSFontFaceRuleDecl::GetAssociatedNode() const {
+ return ContainingRule()->GetAssociatedDocumentOrShadowRoot();
+}
+
+nsISupports* CSSFontFaceRuleDecl::GetParentObject() const {
+ return ContainingRule()->GetParentObject();
+}
+
+JSObject* CSSFontFaceRuleDecl::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ // If this changes to use a different type, remove the 'concrete'
+ // annotation from CSSStyleDeclaration.
+ return CSSStyleDeclaration_Binding::Wrap(cx, this, aGivenProto);
+}
+
+// -------------------------------------------
+// CSSFontFaceRule
+//
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CSSFontFaceRule)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CSSFontFaceRule,
+ mozilla::css::Rule)
+ // Keep this in sync with IsCCLeaf.
+
+ // Trace the wrapper for our declaration. This just expands out
+ // NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER which we can't use
+ // directly because the wrapper is on the declaration, not on us.
+ tmp->mDecl.TraceWrapper(aCallbacks, aClosure);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CSSFontFaceRule)
+ // Keep this in sync with IsCCLeaf.
+
+ // Unlink the wrapper for our declaration.
+ //
+ // Note that this has to happen before unlinking css::Rule.
+ tmp->UnlinkDeclarationWrapper(tmp->mDecl);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(mozilla::css::Rule)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSFontFaceRule,
+ mozilla::css::Rule)
+ // Keep this in sync with IsCCLeaf.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+bool CSSFontFaceRule::IsCCLeaf() const {
+ if (!Rule::IsCCLeaf()) {
+ return false;
+ }
+
+ return !mDecl.PreservingWrapper();
+}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(CSSFontFaceRule,
+ mozilla::css::Rule)
+
+#ifdef DEBUG
+void CSSFontFaceRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_FontFaceRule_Debug(Raw(), &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+StyleCssRuleType CSSFontFaceRule::Type() const {
+ return StyleCssRuleType::FontFace;
+}
+
+void CSSFontFaceRule::SetRawAfterClone(RefPtr<StyleLockedFontFaceRule> aRaw) {
+ mDecl.SetRawAfterClone(std::move(aRaw));
+}
+
+void CSSFontFaceRule::GetCssText(nsACString& aCssText) const {
+ aCssText.Truncate();
+ Servo_FontFaceRule_GetCssText(Raw(), &aCssText);
+}
+
+nsICSSDeclaration* CSSFontFaceRule::Style() { return &mDecl; }
+
+/* virtual */
+size_t CSSFontFaceRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+}
+
+/* virtual */
+JSObject* CSSFontFaceRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSFontFaceRule_Binding::Wrap(aCx, this, aGivenProto);
+}
diff --git a/layout/style/CSSFontFaceRule.h b/layout/style/CSSFontFaceRule.h
new file mode 100644
index 0000000000..b29249b8a3
--- /dev/null
+++ b/layout/style/CSSFontFaceRule.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CSSFontFaceRule_h
+#define mozilla_CSSFontFaceRule_h
+
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/css/Rule.h"
+#include "nsICSSDeclaration.h"
+
+namespace mozilla::dom {
+
+// A CSSFontFaceRuleDecl is always embeded in a CSSFontFaceRule.
+class CSSFontFaceRule;
+class CSSFontFaceRuleDecl final : public nsICSSDeclaration {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMCSSSTYLEDECLARATION_HELPER
+
+ nsINode* GetAssociatedNode() const final;
+ nsISupports* GetParentObject() const final;
+ void IndexedGetter(uint32_t aIndex, bool& aFound,
+ nsACString& aPropName) final;
+
+ void GetPropertyValue(nsCSSFontDesc aFontDescID, nsACString& aResult) const;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ protected:
+ // For accessing the constructor.
+ friend class CSSFontFaceRule;
+
+ explicit CSSFontFaceRuleDecl(already_AddRefed<StyleLockedFontFaceRule> aDecl)
+ : mRawRule(std::move(aDecl)) {}
+
+ ~CSSFontFaceRuleDecl() = default;
+
+ inline CSSFontFaceRule* ContainingRule();
+ inline const CSSFontFaceRule* ContainingRule() const;
+
+ RefPtr<StyleLockedFontFaceRule> mRawRule;
+ void SetRawAfterClone(RefPtr<StyleLockedFontFaceRule>);
+
+ private:
+ void* operator new(size_t size) noexcept(true) = delete;
+};
+
+class CSSFontFaceRule final : public css::Rule {
+ public:
+ CSSFontFaceRule(already_AddRefed<StyleLockedFontFaceRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule, uint32_t aLine,
+ uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn),
+ mDecl(std::move(aRawRule)) {}
+
+ CSSFontFaceRule(const CSSFontFaceRule&) = delete;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(CSSFontFaceRule,
+ css::Rule)
+ bool IsCCLeaf() const final;
+
+ StyleLockedFontFaceRule* Raw() const { return mDecl.mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleLockedFontFaceRule>);
+
+ // WebIDL interface
+ StyleCssRuleType Type() const final;
+ void GetCssText(nsACString& aCssText) const final;
+ nsICSSDeclaration* Style();
+
+ // Methods of mozilla::css::Rule
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ private:
+ virtual ~CSSFontFaceRule() = default;
+
+ // For computing the offset of mDecl.
+ friend class CSSFontFaceRuleDecl;
+
+ CSSFontFaceRuleDecl mDecl;
+};
+
+inline CSSFontFaceRule* CSSFontFaceRuleDecl::ContainingRule() {
+ return reinterpret_cast<CSSFontFaceRule*>(reinterpret_cast<char*>(this) -
+ offsetof(CSSFontFaceRule, mDecl));
+}
+
+inline const CSSFontFaceRule* CSSFontFaceRuleDecl::ContainingRule() const {
+ return reinterpret_cast<const CSSFontFaceRule*>(
+ reinterpret_cast<const char*>(this) - offsetof(CSSFontFaceRule, mDecl));
+}
+
+} // namespace mozilla::dom
+
+#endif // mozilla_CSSFontFaceRule_h
diff --git a/layout/style/CSSFontFeatureValuesRule.cpp b/layout/style/CSSFontFeatureValuesRule.cpp
new file mode 100644
index 0000000000..80e4a909f6
--- /dev/null
+++ b/layout/style/CSSFontFeatureValuesRule.cpp
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSFontFeatureValuesRule.h"
+#include "mozilla/dom/CSSFontFeatureValuesRuleBinding.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla::dom {
+
+size_t CSSFontFeatureValuesRule::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ // TODO Implement this!
+ return aMallocSizeOf(this);
+}
+
+StyleCssRuleType CSSFontFeatureValuesRule::Type() const {
+ return StyleCssRuleType::FontFeatureValues;
+}
+
+#ifdef DEBUG
+void CSSFontFeatureValuesRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_FontFeatureValuesRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+void CSSFontFeatureValuesRule::SetRawAfterClone(
+ RefPtr<StyleFontFeatureValuesRule> aRaw) {
+ mRawRule = std::move(aRaw);
+}
+
+/* CSSRule implementation */
+
+void CSSFontFeatureValuesRule::GetCssText(nsACString& aCssText) const {
+ Servo_FontFeatureValuesRule_GetCssText(mRawRule, &aCssText);
+}
+
+/* CSSFontFeatureValuesRule implementation */
+
+void CSSFontFeatureValuesRule::GetFontFamily(nsACString& aFamilyListStr) {
+ Servo_FontFeatureValuesRule_GetFontFamily(mRawRule, &aFamilyListStr);
+}
+
+void CSSFontFeatureValuesRule::GetValueText(nsACString& aValueText) {
+ Servo_FontFeatureValuesRule_GetValueText(mRawRule, &aValueText);
+}
+
+void CSSFontFeatureValuesRule::SetFontFamily(const nsACString& aFontFamily,
+ ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void CSSFontFeatureValuesRule::SetValueText(const nsACString& aValueText,
+ ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+// If this ever gets its own cycle-collection bits, reevaluate our IsCCLeaf
+// implementation.
+
+bool CSSFontFeatureValuesRule::IsCCLeaf() const { return Rule::IsCCLeaf(); }
+
+/* virtual */
+JSObject* CSSFontFeatureValuesRule::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return CSSFontFeatureValuesRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSFontFeatureValuesRule.h b/layout/style/CSSFontFeatureValuesRule.h
new file mode 100644
index 0000000000..517d2ebd3a
--- /dev/null
+++ b/layout/style/CSSFontFeatureValuesRule.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_CSSFontFeatureValuesRule_h
+#define mozilla_dom_CSSFontFeatureValuesRule_h
+
+#include "mozilla/css/Rule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+#include "nsICSSDeclaration.h"
+
+namespace mozilla::dom {
+
+class CSSFontFeatureValuesRule final : public css::Rule {
+ public:
+ CSSFontFeatureValuesRule(RefPtr<StyleFontFeatureValuesRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+ virtual bool IsCCLeaf() const override;
+
+ StyleFontFeatureValuesRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleFontFeatureValuesRule> aRaw);
+
+ // WebIDL interfaces
+ StyleCssRuleType Type() const final;
+
+ void GetCssText(nsACString& aCssText) const override;
+ void GetFontFamily(nsACString& aFamily);
+ void SetFontFamily(const nsACString& aFamily, mozilla::ErrorResult& aRv);
+ void GetValueText(nsACString& aValueText);
+ void SetValueText(const nsACString& aValueText, mozilla::ErrorResult& aRv);
+
+ size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ ~CSSFontFeatureValuesRule() = default;
+
+ RefPtr<StyleFontFeatureValuesRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSFontFeatureValuesRule_h
diff --git a/layout/style/CSSFontPaletteValuesRule.cpp b/layout/style/CSSFontPaletteValuesRule.cpp
new file mode 100644
index 0000000000..b795461eb6
--- /dev/null
+++ b/layout/style/CSSFontPaletteValuesRule.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSFontPaletteValuesRule.h"
+#include "mozilla/dom/CSSFontPaletteValuesRuleBinding.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla::dom {
+
+size_t CSSFontPaletteValuesRule::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ // TODO Implement this!
+ return aMallocSizeOf(this);
+}
+
+StyleCssRuleType CSSFontPaletteValuesRule::Type() const {
+ return StyleCssRuleType::FontPaletteValues;
+}
+
+#ifdef DEBUG
+void CSSFontPaletteValuesRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_FontPaletteValuesRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+void CSSFontPaletteValuesRule::SetRawAfterClone(
+ RefPtr<StyleFontPaletteValuesRule> aRaw) {
+ mRawRule = std::move(aRaw);
+}
+
+/* CSSRule implementation */
+
+void CSSFontPaletteValuesRule::GetCssText(nsACString& aCssText) const {
+ Servo_FontPaletteValuesRule_GetCssText(mRawRule, &aCssText);
+}
+
+/* CSSFontPaletteValuesRule implementation */
+
+void CSSFontPaletteValuesRule::GetName(nsACString& aNameStr) const {
+ Servo_FontPaletteValuesRule_GetName(mRawRule, &aNameStr);
+}
+
+void CSSFontPaletteValuesRule::GetFontFamily(nsACString& aFamilyListStr) const {
+ Servo_FontPaletteValuesRule_GetFontFamily(mRawRule, &aFamilyListStr);
+}
+
+void CSSFontPaletteValuesRule::GetBasePalette(nsACString& aPaletteStr) const {
+ Servo_FontPaletteValuesRule_GetBasePalette(mRawRule, &aPaletteStr);
+}
+
+void CSSFontPaletteValuesRule::GetOverrideColors(nsACString& aColorsStr) const {
+ Servo_FontPaletteValuesRule_GetOverrideColors(mRawRule, &aColorsStr);
+}
+
+// If this ever gets its own cycle-collection bits, reevaluate our IsCCLeaf
+// implementation.
+
+bool CSSFontPaletteValuesRule::IsCCLeaf() const { return Rule::IsCCLeaf(); }
+
+/* virtual */
+JSObject* CSSFontPaletteValuesRule::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return CSSFontPaletteValuesRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSFontPaletteValuesRule.h b/layout/style/CSSFontPaletteValuesRule.h
new file mode 100644
index 0000000000..9020a2b35f
--- /dev/null
+++ b/layout/style/CSSFontPaletteValuesRule.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_CSSFontPaletteValuesRule_h
+#define mozilla_dom_CSSFontPaletteValuesRule_h
+
+#include "mozilla/css/Rule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+#include "nsICSSDeclaration.h"
+
+namespace mozilla::dom {
+
+class CSSFontPaletteValuesRule final : public css::Rule {
+ public:
+ CSSFontPaletteValuesRule(RefPtr<StyleFontPaletteValuesRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+ bool IsCCLeaf() const final;
+
+ StyleFontPaletteValuesRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleFontPaletteValuesRule> aRaw);
+
+ // WebIDL interfaces
+ StyleCssRuleType Type() const final;
+
+ void GetCssText(nsACString& aCssText) const final;
+
+ void GetFontFamily(nsACString& aFamily) const;
+
+ void GetName(nsACString& aName) const;
+
+ void GetBasePalette(nsACString& aPalette) const;
+
+ void GetOverrideColors(nsACString& aColors) const;
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final;
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ private:
+ ~CSSFontPaletteValuesRule() = default;
+
+ RefPtr<StyleFontPaletteValuesRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSFontPaletteValuesRule_h
diff --git a/layout/style/CSSImportRule.cpp b/layout/style/CSSImportRule.cpp
new file mode 100644
index 0000000000..6ff6b4d44e
--- /dev/null
+++ b/layout/style/CSSImportRule.cpp
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/CSSImportRule.h"
+
+#include "mozilla/dom/CSSImportRuleBinding.h"
+#include "mozilla/dom/MediaList.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/StyleSheet.h"
+
+namespace mozilla {
+namespace dom {
+
+CSSImportRule::CSSImportRule(RefPtr<StyleLockedImportRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {
+ const auto* sheet = Servo_ImportRule_GetSheet(mRawRule.get());
+ mChildSheet = const_cast<StyleSheet*>(sheet);
+ if (mChildSheet) {
+ mChildSheet->AddReferencingRule(*this);
+ }
+}
+
+CSSImportRule::~CSSImportRule() {
+ if (mChildSheet) {
+ mChildSheet->RemoveReferencingRule(*this);
+ }
+}
+
+// QueryInterface implementation for CSSImportRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSImportRule)
+NS_INTERFACE_MAP_END_INHERITING(css::Rule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CSSImportRule)
+
+NS_IMPL_ADDREF_INHERITED(CSSImportRule, css::Rule)
+NS_IMPL_RELEASE_INHERITED(CSSImportRule, css::Rule)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSImportRule, css::Rule)
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChildSheet");
+ cb.NoteXPCOMChild(tmp->mChildSheet);
+ MOZ_ASSERT_IF(tmp->mRawRule,
+ Servo_ImportRule_GetSheet(tmp->mRawRule) == tmp->mChildSheet);
+ // Note the child sheet twice, since the Servo rule also holds a strong
+ // reference to it, but only if we're the "primary" rule reference.
+ if (tmp->mChildSheet && tmp->mChildSheet->GetOwnerRule() == tmp) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mRawRule.stylesheet");
+ cb.NoteXPCOMChild(tmp->mChildSheet);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CSSImportRule)
+ if (tmp->mChildSheet) {
+ tmp->mChildSheet->RemoveReferencingRule(*tmp);
+ tmp->mChildSheet = nullptr;
+ }
+ tmp->mRawRule = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(css::Rule)
+
+#ifdef DEBUG
+/* virtual */
+void CSSImportRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_ImportRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+StyleCssRuleType CSSImportRule::Type() const {
+ return StyleCssRuleType::Import;
+}
+
+void CSSImportRule::SetRawAfterClone(RefPtr<StyleLockedImportRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ if (mChildSheet) {
+ mChildSheet->RemoveReferencingRule(*this);
+ }
+ mChildSheet =
+ const_cast<StyleSheet*>(Servo_ImportRule_GetSheet(mRawRule.get()));
+ if (mChildSheet) {
+ mChildSheet->AddReferencingRule(*this);
+ }
+}
+
+StyleSheet* CSSImportRule::GetStyleSheetForBindings() {
+ if (mChildSheet) {
+ // FIXME(emilio): This is needed to make sure we don't expose shared sheets
+ // to the OM (see wpt /css/cssom/cssimportrule-sheet-identity.html for
+ // example).
+ //
+ // Perhaps instead we could create a clone of the stylesheet and keep it in
+ // mChildSheet, without calling EnsureUniqueInner(), or something like that?
+ if (StyleSheet* parent = GetParentStyleSheet()) {
+ parent->EnsureUniqueInner();
+ }
+ }
+ return mChildSheet;
+}
+
+dom::MediaList* CSSImportRule::GetMedia() {
+ auto* sheet = GetStyleSheetForBindings();
+ return sheet ? sheet->Media() : nullptr;
+}
+
+void CSSImportRule::DropSheetReference() {
+ if (mChildSheet) {
+ mChildSheet->RemoveFromParent();
+ }
+ Rule::DropSheetReference();
+}
+
+void CSSImportRule::GetHref(nsAString& aHref) const {
+ Servo_ImportRule_GetHref(mRawRule, &aHref);
+}
+
+void CSSImportRule::GetLayerName(nsACString& aName) const {
+ Servo_ImportRule_GetLayerName(mRawRule, &aName);
+}
+
+void CSSImportRule::GetSupportsText(nsACString& aSupportsText) const {
+ Servo_ImportRule_GetSupportsText(mRawRule, &aSupportsText);
+}
+
+/* virtual */
+void CSSImportRule::GetCssText(nsACString& aCssText) const {
+ Servo_ImportRule_GetCssText(mRawRule, &aCssText);
+}
+
+/* virtual */
+size_t CSSImportRule::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ // TODO Implement this!
+ return aMallocSizeOf(this);
+}
+
+bool CSSImportRule::IsCCLeaf() const {
+ // We're not a leaf.
+ return false;
+}
+
+/* virtual */
+JSObject* CSSImportRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSImportRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/CSSImportRule.h b/layout/style/CSSImportRule.h
new file mode 100644
index 0000000000..fbe21a576a
--- /dev/null
+++ b/layout/style/CSSImportRule.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSImportRule_h
+#define mozilla_dom_CSSImportRule_h
+
+#include "mozilla/css/Rule.h"
+
+namespace mozilla {
+
+class StyleSheet;
+
+namespace dom {
+
+class CSSImportRule final : public css::Rule {
+ public:
+ CSSImportRule(RefPtr<StyleLockedImportRule> aRawRule, StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CSSImportRule, css::Rule)
+
+ bool IsCCLeaf() const final;
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ size_t SizeOfIncludingThis(MallocSizeOf) const override;
+
+ // WebIDL interface
+ StyleCssRuleType Type() const final;
+ void GetCssText(nsACString& aCssText) const override;
+ void GetHref(nsAString& aHref) const;
+ dom::MediaList* GetMedia();
+ StyleSheet* GetStyleSheet() const { return mChildSheet; }
+ StyleSheet* GetStyleSheetForBindings();
+ void GetLayerName(nsACString&) const;
+ void GetSupportsText(nsACString&) const;
+
+ // Clear the mSheet pointer on this rule and descendants.
+ void DropSheetReference() final;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ const StyleLockedImportRule* Raw() const { return mRawRule.get(); }
+ void SetRawAfterClone(RefPtr<StyleLockedImportRule>);
+
+ private:
+ ~CSSImportRule();
+
+ RefPtr<StyleLockedImportRule> mRawRule;
+ RefPtr<StyleSheet> mChildSheet;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CSSImportRule_h
diff --git a/layout/style/CSSKeyframeRule.cpp b/layout/style/CSSKeyframeRule.cpp
new file mode 100644
index 0000000000..e6ae2bada0
--- /dev/null
+++ b/layout/style/CSSKeyframeRule.cpp
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSKeyframeRule.h"
+
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/dom/CSSKeyframeRuleBinding.h"
+#include "nsDOMCSSDeclaration.h"
+
+namespace mozilla::dom {
+
+// -------------------------------------------
+// CSSKeyframeDeclaration
+//
+
+class CSSKeyframeDeclaration : public nsDOMCSSDeclaration {
+ public:
+ explicit CSSKeyframeDeclaration(CSSKeyframeRule* aRule) : mRule(aRule) {
+ mDecls =
+ new DeclarationBlock(Servo_Keyframe_GetStyle(aRule->Raw()).Consume());
+ mDecls->SetOwningRule(aRule);
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(CSSKeyframeDeclaration,
+ nsICSSDeclaration)
+
+ css::Rule* GetParentRule() final { return mRule; }
+
+ void DropReference() {
+ mRule = nullptr;
+ mDecls->SetOwningRule(nullptr);
+ }
+
+ DeclarationBlock* GetOrCreateCSSDeclaration(
+ Operation aOperation, DeclarationBlock** aCreated) final {
+ if (aOperation != Operation::Read && mRule) {
+ if (StyleSheet* sheet = mRule->GetStyleSheet()) {
+ sheet->WillDirty();
+ }
+ }
+ return mDecls;
+ }
+ nsresult SetCSSDeclaration(DeclarationBlock* aDecls,
+ MutationClosureData* aClosureData) final {
+ if (!mRule) {
+ return NS_OK;
+ }
+ mRule->UpdateRule([this, aDecls]() {
+ if (mDecls != aDecls) {
+ mDecls->SetOwningRule(nullptr);
+ mDecls = aDecls;
+ mDecls->SetOwningRule(mRule);
+ Servo_Keyframe_SetStyle(mRule->Raw(), mDecls->Raw());
+ }
+ });
+ return NS_OK;
+ }
+ ParsingEnvironment GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal) const final {
+ return GetParsingEnvironmentForRule(mRule, StyleCssRuleType::Keyframe);
+ }
+ Document* DocToUpdate() final { return nullptr; }
+
+ nsINode* GetAssociatedNode() const final {
+ return mRule ? mRule->GetAssociatedDocumentOrShadowRoot() : nullptr;
+ }
+
+ nsISupports* GetParentObject() const final {
+ return mRule ? mRule->GetParentObject() : nullptr;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ // TODO we may want to add size of mDecls as well
+ return n;
+ }
+
+ void SetRawAfterClone(StyleLockedKeyframe* aKeyframe) {
+ mDecls->SetOwningRule(nullptr);
+ mDecls = new DeclarationBlock(Servo_Keyframe_GetStyle(aKeyframe).Consume());
+ mDecls->SetOwningRule(mRule);
+ }
+
+ private:
+ virtual ~CSSKeyframeDeclaration() {
+ MOZ_ASSERT(!mRule, "Backpointer should have been cleared");
+ }
+
+ CSSKeyframeRule* mRule;
+ RefPtr<DeclarationBlock> mDecls;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CSSKeyframeDeclaration)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CSSKeyframeDeclaration)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(CSSKeyframeDeclaration)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSKeyframeDeclaration)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
+
+// -------------------------------------------
+// CSSKeyframeRule
+//
+
+CSSKeyframeRule::CSSKeyframeRule(already_AddRefed<StyleLockedKeyframe> aRaw,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn), mRaw(aRaw) {}
+
+CSSKeyframeRule::~CSSKeyframeRule() {
+ if (mDeclaration) {
+ mDeclaration->DropReference();
+ }
+}
+
+NS_IMPL_ADDREF_INHERITED(CSSKeyframeRule, css::Rule)
+NS_IMPL_RELEASE_INHERITED(CSSKeyframeRule, css::Rule)
+
+// QueryInterface implementation for nsCSSKeyframeRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSKeyframeRule)
+NS_INTERFACE_MAP_END_INHERITING(css::Rule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CSSKeyframeRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CSSKeyframeRule, css::Rule)
+ if (tmp->mDeclaration) {
+ tmp->mDeclaration->DropReference();
+ tmp->mDeclaration = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSKeyframeRule, css::Rule)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeclaration)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+bool CSSKeyframeRule::IsCCLeaf() const {
+ return Rule::IsCCLeaf() && !mDeclaration;
+}
+
+StyleCssRuleType CSSKeyframeRule::Type() const {
+ return StyleCssRuleType::Keyframe;
+}
+
+void CSSKeyframeRule::SetRawAfterClone(RefPtr<StyleLockedKeyframe> aRaw) {
+ mRaw = std::move(aRaw);
+
+ if (mDeclaration) {
+ mDeclaration->SetRawAfterClone(mRaw);
+ }
+}
+
+#ifdef DEBUG
+/* virtual */
+void CSSKeyframeRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_Keyframe_Debug(mRaw, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+template <typename Func>
+void CSSKeyframeRule::UpdateRule(Func aCallback) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ StyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->WillDirty();
+ }
+
+ aCallback();
+
+ if (sheet) {
+ sheet->RuleChanged(this, StyleRuleChangeKind::Generic);
+ }
+}
+
+void CSSKeyframeRule::GetKeyText(nsACString& aKeyText) {
+ Servo_Keyframe_GetKeyText(mRaw, &aKeyText);
+}
+
+void CSSKeyframeRule::SetKeyText(const nsACString& aKeyText) {
+ UpdateRule(
+ [this, &aKeyText]() { Servo_Keyframe_SetKeyText(mRaw, &aKeyText); });
+}
+
+void CSSKeyframeRule::GetCssText(nsACString& aCssText) const {
+ Servo_Keyframe_GetCssText(mRaw, &aCssText);
+}
+
+nsICSSDeclaration* CSSKeyframeRule::Style() {
+ if (!mDeclaration) {
+ mDeclaration = new CSSKeyframeDeclaration(this);
+ }
+ return mDeclaration;
+}
+
+size_t CSSKeyframeRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ if (mDeclaration) {
+ n += mDeclaration->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+/* virtual */
+JSObject* CSSKeyframeRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSKeyframeRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSKeyframeRule.h b/layout/style/CSSKeyframeRule.h
new file mode 100644
index 0000000000..b1a202ced1
--- /dev/null
+++ b/layout/style/CSSKeyframeRule.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_CSSKeyframeRule_h
+#define mozilla_dom_CSSKeyframeRule_h
+
+#include "mozilla/css/Rule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+class nsICSSDeclaration;
+
+namespace mozilla::dom {
+
+class CSSKeyframeDeclaration;
+
+class CSSKeyframeRule final : public css::Rule {
+ public:
+ CSSKeyframeRule(already_AddRefed<StyleLockedKeyframe> aRaw,
+ StyleSheet* aSheet, css::Rule* aParentRule, uint32_t aLine,
+ uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CSSKeyframeRule, css::Rule)
+ bool IsCCLeaf() const final;
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ StyleLockedKeyframe* Raw() const { return mRaw; }
+ void SetRawAfterClone(RefPtr<StyleLockedKeyframe>);
+
+ // WebIDL interface
+ StyleCssRuleType Type() const final;
+ void GetCssText(nsACString& aCssText) const final;
+ void GetKeyText(nsACString& aKey);
+ void SetKeyText(const nsACString& aKey);
+ nsICSSDeclaration* Style();
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ private:
+ virtual ~CSSKeyframeRule();
+
+ friend class CSSKeyframeDeclaration;
+
+ template <typename Func>
+ void UpdateRule(Func aCallback);
+
+ RefPtr<StyleLockedKeyframe> mRaw;
+ // lazily created when needed
+ RefPtr<CSSKeyframeDeclaration> mDeclaration;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSKeyframeRule_h
diff --git a/layout/style/CSSKeyframesRule.cpp b/layout/style/CSSKeyframesRule.cpp
new file mode 100644
index 0000000000..89fe1da32f
--- /dev/null
+++ b/layout/style/CSSKeyframesRule.cpp
@@ -0,0 +1,364 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/CSSKeyframesRule.h"
+
+#include "mozilla/dom/CSSKeyframesRuleBinding.h"
+#include "mozilla/dom/CSSRuleList.h"
+#include "mozilla/ServoBindings.h"
+#include "nsCOMArray.h"
+
+#include <limits>
+
+namespace mozilla::dom {
+
+// -------------------------------------------
+// CSSKeyframeList
+//
+
+class CSSKeyframeList : public dom::CSSRuleList {
+ public:
+ CSSKeyframeList(already_AddRefed<StyleLockedKeyframesRule> aRawRule,
+ StyleSheet* aSheet, CSSKeyframesRule* aParentRule)
+ : mStyleSheet(aSheet), mParentRule(aParentRule), mRawRule(aRawRule) {
+ mRules.SetCount(Servo_KeyframesRule_GetCount(mRawRule));
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CSSKeyframeList, dom::CSSRuleList)
+
+ void SetRawAfterClone(RefPtr<StyleLockedKeyframesRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ uint32_t index = 0;
+ for (css::Rule* rule : mRules) {
+ if (rule) {
+ uint32_t line = 0, column = 0;
+ RefPtr<StyleLockedKeyframe> keyframe =
+ Servo_KeyframesRule_GetKeyframeAt(mRawRule, index, &line, &column)
+ .Consume();
+ static_cast<CSSKeyframeRule*>(rule)->SetRawAfterClone(
+ std::move(keyframe));
+ }
+ index++;
+ }
+ }
+
+ void DropSheetReference() {
+ if (!mStyleSheet) {
+ return;
+ }
+ mStyleSheet = nullptr;
+ for (css::Rule* rule : mRules) {
+ if (rule) {
+ rule->DropSheetReference();
+ }
+ }
+ }
+
+ StyleSheet* GetParentObject() final { return mStyleSheet; }
+
+ CSSKeyframeRule* GetRule(uint32_t aIndex) {
+ if (!mRules[aIndex]) {
+ uint32_t line = 0, column = 0;
+ RefPtr<StyleLockedKeyframe> rule =
+ Servo_KeyframesRule_GetKeyframeAt(mRawRule, aIndex, &line, &column)
+ .Consume();
+ CSSKeyframeRule* ruleObj = new CSSKeyframeRule(rule.forget(), mStyleSheet,
+ mParentRule, line, column);
+ mRules.ReplaceObjectAt(ruleObj, aIndex);
+ }
+ return static_cast<CSSKeyframeRule*>(mRules[aIndex]);
+ }
+
+ CSSKeyframeRule* IndexedGetter(uint32_t aIndex, bool& aFound) final {
+ if (aIndex >= mRules.Length()) {
+ aFound = false;
+ return nullptr;
+ }
+ aFound = true;
+ return GetRule(aIndex);
+ }
+
+ void AppendRule() {
+ MOZ_ASSERT(!mParentRule->IsReadOnly());
+ mRules.AppendObject(nullptr);
+ }
+
+ void RemoveRule(uint32_t aIndex) {
+ MOZ_ASSERT(!mParentRule->IsReadOnly());
+
+ if (aIndex >= mRules.Length()) {
+ return;
+ }
+ if (css::Rule* child = mRules[aIndex]) {
+ child->DropReferences();
+ }
+ mRules.RemoveObjectAt(aIndex);
+ }
+
+ uint32_t Length() final { return mRules.Length(); }
+
+ void DropReferences() {
+ if (!mStyleSheet && !mParentRule) {
+ return;
+ }
+ mStyleSheet = nullptr;
+ mParentRule = nullptr;
+ for (css::Rule* rule : mRules) {
+ if (rule) {
+ rule->DropParentRuleReference();
+ rule->DropSheetReference();
+ }
+ }
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ for (const css::Rule* rule : mRules) {
+ n += rule ? rule->SizeOfIncludingThis(aMallocSizeOf) : 0;
+ }
+ return n;
+ }
+
+ private:
+ virtual ~CSSKeyframeList() {
+ MOZ_ASSERT(!mParentRule, "Backpointer should have been cleared");
+ MOZ_ASSERT(!mStyleSheet, "Backpointer should have been cleared");
+ DropAllRules();
+ }
+
+ void DropAllRules() {
+ DropReferences();
+ mRules.Clear();
+ mRawRule = nullptr;
+ }
+
+ // may be nullptr when the style sheet drops the reference to us.
+ StyleSheet* mStyleSheet = nullptr;
+ CSSKeyframesRule* mParentRule = nullptr;
+ RefPtr<StyleLockedKeyframesRule> mRawRule;
+ nsCOMArray<css::Rule> mRules;
+};
+
+// QueryInterface implementation for CSSKeyframeList
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSKeyframeList)
+NS_INTERFACE_MAP_END_INHERITING(dom::CSSRuleList)
+
+NS_IMPL_ADDREF_INHERITED(CSSKeyframeList, dom::CSSRuleList)
+NS_IMPL_RELEASE_INHERITED(CSSKeyframeList, dom::CSSRuleList)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CSSKeyframeList)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CSSKeyframeList)
+ tmp->DropAllRules();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(dom::CSSRuleList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSKeyframeList,
+ dom::CSSRuleList)
+ for (css::Rule* rule : tmp->mRules) {
+ if (rule) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mRules[i]");
+ cb.NoteXPCOMChild(rule);
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// -------------------------------------------
+// CSSKeyframesRule
+//
+
+CSSKeyframesRule::CSSKeyframesRule(RefPtr<StyleLockedKeyframesRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+CSSKeyframesRule::~CSSKeyframesRule() {
+ if (mKeyframeList) {
+ mKeyframeList->DropReferences();
+ }
+}
+
+NS_IMPL_ADDREF_INHERITED(CSSKeyframesRule, css::Rule)
+NS_IMPL_RELEASE_INHERITED(CSSKeyframesRule, css::Rule)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSKeyframesRule)
+NS_INTERFACE_MAP_END_INHERITING(css::Rule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CSSKeyframesRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CSSKeyframesRule, css::Rule)
+ if (tmp->mKeyframeList) {
+ tmp->mKeyframeList->DropReferences();
+ tmp->mKeyframeList = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSKeyframesRule, Rule)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mKeyframeList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+/* virtual */
+bool CSSKeyframesRule::IsCCLeaf() const {
+ // If we don't have rule list constructed, we are a leaf.
+ return Rule::IsCCLeaf() && !mKeyframeList;
+}
+
+StyleCssRuleType CSSKeyframesRule::Type() const {
+ return StyleCssRuleType::Keyframes;
+}
+
+void CSSKeyframesRule::SetRawAfterClone(RefPtr<StyleLockedKeyframesRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ if (mKeyframeList) {
+ mKeyframeList->SetRawAfterClone(mRawRule);
+ }
+}
+
+#ifdef DEBUG
+/* virtual */
+void CSSKeyframesRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_KeyframesRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+/* virtual */
+void CSSKeyframesRule::DropSheetReference() {
+ if (mKeyframeList) {
+ mKeyframeList->DropSheetReference();
+ }
+ css::Rule::DropSheetReference();
+}
+
+static const uint32_t kRuleNotFound = std::numeric_limits<uint32_t>::max();
+
+uint32_t CSSKeyframesRule::FindRuleIndexForKey(const nsAString& aKey) {
+ NS_ConvertUTF16toUTF8 key(aKey);
+ return Servo_KeyframesRule_FindRule(mRawRule, &key);
+}
+
+template <typename Func>
+nsresult CSSKeyframesRule::UpdateRule(Func aCallback) {
+ if (IsReadOnly()) {
+ return NS_OK;
+ }
+
+ StyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->WillDirty();
+ }
+
+ aCallback();
+
+ if (sheet) {
+ sheet->RuleChanged(this, StyleRuleChangeKind::Generic);
+ }
+
+ return NS_OK;
+}
+
+void CSSKeyframesRule::GetName(nsAString& aName) const {
+ nsAtom* name = Servo_KeyframesRule_GetName(mRawRule);
+ aName = nsDependentAtomString(name);
+}
+
+void CSSKeyframesRule::SetName(const nsAString& aName) {
+ RefPtr<nsAtom> name = NS_Atomize(aName);
+ nsAtom* oldName = Servo_KeyframesRule_GetName(mRawRule);
+ if (name == oldName) {
+ return;
+ }
+
+ UpdateRule([this, &name]() {
+ Servo_KeyframesRule_SetName(mRawRule, name.forget().take());
+ });
+}
+
+void CSSKeyframesRule::AppendRule(const nsAString& aRule) {
+ StyleSheet* sheet = GetStyleSheet();
+ if (!sheet) {
+ // We cannot parse the rule if we don't have a stylesheet.
+ return;
+ }
+
+ NS_ConvertUTF16toUTF8 rule(aRule);
+ UpdateRule([this, sheet, &rule]() {
+ bool parsedOk =
+ Servo_KeyframesRule_AppendRule(mRawRule, sheet->RawContents(), &rule);
+ if (parsedOk && mKeyframeList) {
+ mKeyframeList->AppendRule();
+ }
+ });
+}
+
+void CSSKeyframesRule::DeleteRule(const nsAString& aKey) {
+ auto index = FindRuleIndexForKey(aKey);
+ if (index == kRuleNotFound) {
+ return;
+ }
+
+ UpdateRule([this, index]() {
+ Servo_KeyframesRule_DeleteRule(mRawRule, index);
+ if (mKeyframeList) {
+ mKeyframeList->RemoveRule(index);
+ }
+ });
+}
+
+/* virtual */
+void CSSKeyframesRule::GetCssText(nsACString& aCssText) const {
+ Servo_KeyframesRule_GetCssText(mRawRule, &aCssText);
+}
+
+/* virtual */ dom::CSSRuleList* CSSKeyframesRule::CssRules() {
+ return EnsureRules();
+}
+
+/* virtual */ dom::CSSKeyframeRule* CSSKeyframesRule::IndexedGetter(
+ uint32_t aIndex, bool& aFound) {
+ return EnsureRules()->IndexedGetter(aIndex, aFound);
+}
+
+/* virtual */ uint32_t CSSKeyframesRule::Length() {
+ return EnsureRules()->Length();
+}
+
+/* virtual */ dom::CSSKeyframeRule* CSSKeyframesRule::FindRule(
+ const nsAString& aKey) {
+ auto index = FindRuleIndexForKey(aKey);
+ if (index != kRuleNotFound) {
+ return EnsureRules()->GetRule(index);
+ }
+ return nullptr;
+}
+
+/* virtual */
+size_t CSSKeyframesRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ if (mKeyframeList) {
+ n += mKeyframeList->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+/* virtual */
+JSObject* CSSKeyframesRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSKeyframesRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+dom::CSSKeyframeList* CSSKeyframesRule::EnsureRules() {
+ if (!mKeyframeList) {
+ mKeyframeList = new CSSKeyframeList(do_AddRef(mRawRule), mSheet, this);
+ }
+ return mKeyframeList;
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSKeyframesRule.h b/layout/style/CSSKeyframesRule.h
new file mode 100644
index 0000000000..191e9a5f18
--- /dev/null
+++ b/layout/style/CSSKeyframesRule.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSKeyframesRule_h
+#define mozilla_dom_CSSKeyframesRule_h
+
+#include "mozilla/css/Rule.h"
+#include "mozilla/dom/CSSKeyframeRule.h"
+
+namespace mozilla::dom {
+
+class CSSKeyframeList;
+
+class CSSKeyframesRule final : public css::Rule {
+ public:
+ CSSKeyframesRule(RefPtr<StyleLockedKeyframesRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule, uint32_t aLine,
+ uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CSSKeyframesRule, css::Rule)
+
+ bool IsCCLeaf() const final;
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ void DropSheetReference() final;
+
+ // WebIDL interface
+ StyleCssRuleType Type() const final;
+ const StyleLockedKeyframesRule* Raw() const { return mRawRule.get(); }
+ void SetRawAfterClone(RefPtr<StyleLockedKeyframesRule>);
+
+ void GetCssText(nsACString& aCssText) const final;
+ void GetName(nsAString& aName) const;
+ void SetName(const nsAString& aName);
+ CSSRuleList* CssRules();
+
+ CSSKeyframeRule* IndexedGetter(uint32_t aIndex, bool& aFound);
+ uint32_t Length();
+
+ void AppendRule(const nsAString& aRule);
+ void DeleteRule(const nsAString& aKey);
+ CSSKeyframeRule* FindRule(const nsAString& aKey);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ private:
+ CSSKeyframeList* EnsureRules();
+ uint32_t FindRuleIndexForKey(const nsAString& aKey);
+
+ template <typename Func>
+ nsresult UpdateRule(Func aCallback);
+
+ virtual ~CSSKeyframesRule();
+
+ RefPtr<StyleLockedKeyframesRule> mRawRule;
+ RefPtr<CSSKeyframeList> mKeyframeList; // lazily constructed
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSKeyframesRule_h
diff --git a/layout/style/CSSLayerBlockRule.cpp b/layout/style/CSSLayerBlockRule.cpp
new file mode 100644
index 0000000000..23453fb3af
--- /dev/null
+++ b/layout/style/CSSLayerBlockRule.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSLayerBlockRule.h"
+#include "mozilla/dom/CSSLayerBlockRuleBinding.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla::dom {
+
+CSSLayerBlockRule::CSSLayerBlockRule(RefPtr<StyleLayerBlockRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::GroupRule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(CSSLayerBlockRule,
+ css::GroupRule)
+
+// QueryInterface implementation for SupportsRule
+
+#ifdef DEBUG
+void CSSLayerBlockRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_LayerBlockRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+StyleCssRuleType CSSLayerBlockRule::Type() const {
+ return StyleCssRuleType::LayerBlock;
+}
+
+already_AddRefed<StyleLockedCssRules> CSSLayerBlockRule::GetOrCreateRawRules() {
+ return Servo_LayerBlockRule_GetRules(mRawRule).Consume();
+}
+
+void CSSLayerBlockRule::SetRawAfterClone(RefPtr<StyleLayerBlockRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ css::GroupRule::DidSetRawAfterClone();
+}
+
+void CSSLayerBlockRule::GetCssText(nsACString& aCssText) const {
+ Servo_LayerBlockRule_GetCssText(mRawRule.get(), &aCssText);
+}
+
+void CSSLayerBlockRule::GetName(nsACString& aName) const {
+ Servo_LayerBlockRule_GetName(mRawRule.get(), &aName);
+}
+
+size_t CSSLayerBlockRule::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+}
+
+JSObject* CSSLayerBlockRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSLayerBlockRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSLayerBlockRule.h b/layout/style/CSSLayerBlockRule.h
new file mode 100644
index 0000000000..fbba832eef
--- /dev/null
+++ b/layout/style/CSSLayerBlockRule.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSLayerBlockRule_h
+#define mozilla_dom_CSSLayerBlockRule_h
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+namespace mozilla::dom {
+
+class CSSLayerBlockRule final : public css::GroupRule {
+ public:
+ CSSLayerBlockRule(RefPtr<StyleLayerBlockRule> aRawRule, StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ StyleLayerBlockRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleLayerBlockRule>);
+
+ already_AddRefed<StyleLockedCssRules> GetOrCreateRawRules() final;
+
+ // WebIDL interface
+ StyleCssRuleType Type() const final;
+ void GetCssText(nsACString& aCssText) const final;
+
+ void GetName(nsACString&) const;
+
+ size_t SizeOfIncludingThis(MallocSizeOf) const override;
+ JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
+
+ private:
+ ~CSSLayerBlockRule() = default;
+
+ RefPtr<StyleLayerBlockRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSLayerBlockRule_h
diff --git a/layout/style/CSSLayerStatementRule.cpp b/layout/style/CSSLayerStatementRule.cpp
new file mode 100644
index 0000000000..55e3d6159a
--- /dev/null
+++ b/layout/style/CSSLayerStatementRule.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSLayerStatementRule.h"
+#include "mozilla/dom/CSSLayerStatementRuleBinding.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla::dom {
+
+CSSLayerStatementRule::CSSLayerStatementRule(
+ RefPtr<StyleLayerStatementRule> aRawRule, StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn)
+ : Rule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+NS_IMPL_ADDREF_INHERITED(CSSLayerStatementRule, Rule)
+NS_IMPL_RELEASE_INHERITED(CSSLayerStatementRule, Rule)
+
+// QueryInterface implementation for SupportsRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSLayerStatementRule)
+NS_INTERFACE_MAP_END_INHERITING(Rule)
+
+#ifdef DEBUG
+void CSSLayerStatementRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_LayerStatementRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+StyleCssRuleType CSSLayerStatementRule::Type() const {
+ return StyleCssRuleType::LayerStatement;
+}
+
+void CSSLayerStatementRule::SetRawAfterClone(
+ RefPtr<StyleLayerStatementRule> aRaw) {
+ mRawRule = std::move(aRaw);
+}
+
+void CSSLayerStatementRule::GetCssText(nsACString& aCssText) const {
+ Servo_LayerStatementRule_GetCssText(mRawRule.get(), &aCssText);
+}
+
+void CSSLayerStatementRule::GetNameList(nsTArray<nsCString>& aNames) const {
+ size_t size = Servo_LayerStatementRule_GetNameCount(mRawRule.get());
+ for (size_t i = 0; i < size; ++i) {
+ Servo_LayerStatementRule_GetNameAt(mRawRule.get(), i,
+ aNames.AppendElement());
+ }
+}
+
+size_t CSSLayerStatementRule::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+}
+
+JSObject* CSSLayerStatementRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSLayerStatementRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSLayerStatementRule.h b/layout/style/CSSLayerStatementRule.h
new file mode 100644
index 0000000000..77e5e71255
--- /dev/null
+++ b/layout/style/CSSLayerStatementRule.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSLayerStatementRule_h
+#define mozilla_dom_CSSLayerStatementRule_h
+
+#include "mozilla/css/Rule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+namespace mozilla::dom {
+
+class CSSLayerStatementRule final : public css::Rule {
+ public:
+ CSSLayerStatementRule(RefPtr<StyleLayerStatementRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool IsCCLeaf() const final { return css::Rule::IsCCLeaf(); }
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ StyleLayerStatementRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleLayerStatementRule>);
+
+ // WebIDL interface
+ StyleCssRuleType Type() const final;
+ void GetCssText(nsACString& aCssText) const final;
+
+ void GetNameList(nsTArray<nsCString>&) const;
+
+ size_t SizeOfIncludingThis(MallocSizeOf) const override;
+ JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
+
+ private:
+ ~CSSLayerStatementRule() = default;
+
+ RefPtr<StyleLayerStatementRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSLayerStatementRule_h
diff --git a/layout/style/CSSMediaRule.cpp b/layout/style/CSSMediaRule.cpp
new file mode 100644
index 0000000000..75b4ce4571
--- /dev/null
+++ b/layout/style/CSSMediaRule.cpp
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSMediaRule.h"
+
+#include "mozilla/dom/CSSMediaRuleBinding.h"
+#include "mozilla/dom/MediaList.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla::dom {
+
+CSSMediaRule::CSSMediaRule(RefPtr<StyleMediaRule> aRawRule, StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine,
+ uint32_t aColumn)
+ : ConditionRule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+CSSMediaRule::~CSSMediaRule() {
+ if (mMediaList) {
+ mMediaList->SetStyleSheet(nullptr);
+ }
+}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(CSSMediaRule, css::ConditionRule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CSSMediaRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CSSMediaRule,
+ css::ConditionRule)
+ if (tmp->mMediaList) {
+ tmp->mMediaList->SetStyleSheet(nullptr);
+ tmp->mMediaList = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSMediaRule,
+ css::ConditionRule)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+bool CSSMediaRule::IsCCLeaf() const {
+ return ConditionRule::IsCCLeaf() && !mMediaList;
+}
+
+/* virtual */
+void CSSMediaRule::DropSheetReference() {
+ if (mMediaList) {
+ mMediaList->SetStyleSheet(nullptr);
+ }
+ ConditionRule::DropSheetReference();
+}
+
+void CSSMediaRule::SetRawAfterClone(RefPtr<StyleMediaRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ if (mMediaList) {
+ mMediaList->SetRawAfterClone(Servo_MediaRule_GetMedia(mRawRule).Consume());
+ mMediaList->SetStyleSheet(nullptr);
+ mMediaList->SetStyleSheet(GetStyleSheet());
+ }
+ css::ConditionRule::DidSetRawAfterClone();
+}
+
+already_AddRefed<StyleLockedCssRules> CSSMediaRule::GetOrCreateRawRules() {
+ return Servo_MediaRule_GetRules(mRawRule).Consume();
+}
+
+StyleCssRuleType CSSMediaRule::Type() const { return StyleCssRuleType::Media; }
+
+#ifdef DEBUG
+/* virtual */
+void CSSMediaRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_MediaRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+void CSSMediaRule::GetConditionText(nsACString& aConditionText) {
+ Media()->GetMediaText(aConditionText);
+}
+
+/* virtual */
+void CSSMediaRule::GetCssText(nsACString& aCssText) const {
+ Servo_MediaRule_GetCssText(mRawRule, &aCssText);
+}
+
+/* virtual */ dom::MediaList* CSSMediaRule::Media() {
+ if (!mMediaList) {
+ mMediaList = new MediaList(Servo_MediaRule_GetMedia(mRawRule).Consume());
+ mMediaList->SetStyleSheet(GetStyleSheet());
+ }
+ return mMediaList;
+}
+
+/* virtual */
+size_t CSSMediaRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ // TODO Implement this!
+ return aMallocSizeOf(this);
+}
+
+/* virtual */
+JSObject* CSSMediaRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSMediaRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSMediaRule.h b/layout/style/CSSMediaRule.h
new file mode 100644
index 0000000000..d5904ca5cb
--- /dev/null
+++ b/layout/style/CSSMediaRule.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSMediaRule_h
+#define mozilla_dom_CSSMediaRule_h
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+namespace mozilla::dom {
+
+class CSSMediaRule final : public css::ConditionRule {
+ public:
+ CSSMediaRule(RefPtr<StyleMediaRule> aRawRule, StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CSSMediaRule, css::ConditionRule)
+
+ void DropSheetReference() override;
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ StyleMediaRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleMediaRule>);
+ already_AddRefed<StyleLockedCssRules> GetOrCreateRawRules() final;
+ bool IsCCLeaf() const override;
+
+ // WebIDL interface
+ StyleCssRuleType Type() const override;
+ // WebIDL interface
+ void GetCssText(nsACString& aCssText) const final;
+ void GetConditionText(nsACString& aConditionText) final;
+ dom::MediaList* Media();
+
+ size_t SizeOfIncludingThis(MallocSizeOf) const override;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ virtual ~CSSMediaRule();
+
+ RefPtr<StyleMediaRule> mRawRule;
+ RefPtr<dom::MediaList> mMediaList;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSMediaRule_h
diff --git a/layout/style/CSSMozDocumentRule.cpp b/layout/style/CSSMozDocumentRule.cpp
new file mode 100644
index 0000000000..b59a66d856
--- /dev/null
+++ b/layout/style/CSSMozDocumentRule.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/CSSMozDocumentRule.h"
+#include "mozilla/dom/CSSMozDocumentRuleBinding.h"
+
+#include "js/RegExpFlags.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/ServoBindings.h"
+#include "nsContentUtils.h"
+#include "nsHTMLDocument.h"
+
+namespace mozilla::dom {
+
+using namespace mozilla::css;
+
+/* virtual */
+JSObject* CSSMozDocumentRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSMozDocumentRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+bool CSSMozDocumentRule::Match(const Document* aDoc, nsIURI* aDocURI,
+ const nsACString& aDocURISpec,
+ const nsACString& aPattern,
+ DocumentMatchingFunction aMatchingFunction) {
+ switch (aMatchingFunction) {
+ case DocumentMatchingFunction::MediaDocument: {
+ auto kind = aDoc->MediaDocumentKind();
+ if (aPattern.EqualsLiteral("all")) {
+ return kind != Document::MediaDocumentKind::NotMedia;
+ }
+ MOZ_ASSERT(aPattern.EqualsLiteral("plugin") ||
+ aPattern.EqualsLiteral("image") ||
+ aPattern.EqualsLiteral("video"));
+ switch (kind) {
+ case Document::MediaDocumentKind::NotMedia:
+ return false;
+ case Document::MediaDocumentKind::Plugin:
+ return aPattern.EqualsLiteral("plugin");
+ case Document::MediaDocumentKind::Image:
+ return aPattern.EqualsLiteral("image");
+ case Document::MediaDocumentKind::Video:
+ return aPattern.EqualsLiteral("video");
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown media document kind");
+ return false;
+ }
+ case DocumentMatchingFunction::URL:
+ return aDocURISpec == aPattern;
+ case DocumentMatchingFunction::URLPrefix:
+ return StringBeginsWith(aDocURISpec, aPattern);
+ case DocumentMatchingFunction::Domain: {
+ nsAutoCString host;
+ if (aDocURI) {
+ aDocURI->GetHost(host);
+ }
+ int32_t lenDiff = host.Length() - aPattern.Length();
+ if (lenDiff == 0) {
+ return host == aPattern;
+ }
+ return StringEndsWith(host, aPattern) && host.CharAt(lenDiff - 1) == '.';
+ }
+ case DocumentMatchingFunction::RegExp: {
+ // Using JS::RegExpFlag::Unicode to allow patterns containing for example
+ // [^/].
+ return nsContentUtils::IsPatternMatching(
+ NS_ConvertUTF8toUTF16(aDocURISpec),
+ NS_ConvertUTF8toUTF16(aPattern), aDoc,
+ /* aHasMultiple = */ false, JS::RegExpFlag::Unicode)
+ .valueOr(false);
+ }
+ case DocumentMatchingFunction::PlainTextDocument:
+ return aDoc->IsHTMLOrXHTML() && aDoc->AsHTMLDocument()->IsPlainText();
+ case DocumentMatchingFunction::UnobservableDocument: {
+ const BrowsingContext* bc = aDoc->GetBrowsingContext();
+ return bc && bc->IsTop() && !bc->HasOpener();
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown matching function");
+ return false;
+}
+
+CSSMozDocumentRule::CSSMozDocumentRule(RefPtr<StyleDocumentRule> aRawRule,
+ StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine,
+ uint32_t aColumn)
+ : css::ConditionRule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+NS_IMPL_ADDREF_INHERITED(CSSMozDocumentRule, css::ConditionRule)
+NS_IMPL_RELEASE_INHERITED(CSSMozDocumentRule, css::ConditionRule)
+
+// QueryInterface implementation for MozDocumentRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSMozDocumentRule)
+NS_INTERFACE_MAP_END_INHERITING(css::ConditionRule)
+
+#ifdef DEBUG
+/* virtual */
+void CSSMozDocumentRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_DocumentRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+void CSSMozDocumentRule::SetRawAfterClone(RefPtr<StyleDocumentRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ css::ConditionRule::DidSetRawAfterClone();
+}
+
+already_AddRefed<StyleLockedCssRules>
+CSSMozDocumentRule::GetOrCreateRawRules() {
+ return Servo_DocumentRule_GetRules(mRawRule).Consume();
+}
+
+StyleCssRuleType CSSMozDocumentRule::Type() const {
+ return StyleCssRuleType::Document;
+}
+
+void CSSMozDocumentRule::GetConditionText(nsACString& aConditionText) {
+ Servo_DocumentRule_GetConditionText(mRawRule, &aConditionText);
+}
+
+/* virtual */
+void CSSMozDocumentRule::GetCssText(nsACString& aCssText) const {
+ Servo_DocumentRule_GetCssText(mRawRule, &aCssText);
+}
+
+/* virtual */
+size_t CSSMozDocumentRule::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ // TODO Implement this!
+ return aMallocSizeOf(this);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSMozDocumentRule.h b/layout/style/CSSMozDocumentRule.h
new file mode 100644
index 0000000000..0c1b0ab022
--- /dev/null
+++ b/layout/style/CSSMozDocumentRule.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_CSSMozDocumentRule_h
+#define mozilla_dom_CSSMozDocumentRule_h
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/css/DocumentMatchingFunction.h"
+#include "mozilla/ServoBindingTypes.h"
+
+namespace mozilla::dom {
+
+class CSSMozDocumentRule final : public css::ConditionRule {
+ public:
+ CSSMozDocumentRule(RefPtr<StyleDocumentRule> aRawRule, StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ static bool Match(const Document*, nsIURI* aDocURI,
+ const nsACString& aDocURISpec, const nsACString& aPattern,
+ css::DocumentMatchingFunction);
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ StyleDocumentRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleDocumentRule>);
+ already_AddRefed<StyleLockedCssRules> GetOrCreateRawRules() final;
+
+ // WebIDL interface
+ StyleCssRuleType Type() const final;
+ void GetCssText(nsACString& aCssText) const final;
+ void GetConditionText(nsACString& aConditionText) final;
+
+ size_t SizeOfIncludingThis(MallocSizeOf) const override;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ ~CSSMozDocumentRule() = default;
+
+ RefPtr<StyleDocumentRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSMozDocumentRule_h
diff --git a/layout/style/CSSNamespaceRule.cpp b/layout/style/CSSNamespaceRule.cpp
new file mode 100644
index 0000000000..5baa2bba0b
--- /dev/null
+++ b/layout/style/CSSNamespaceRule.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSNamespaceRule.h"
+
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla::dom {
+
+CSSNamespaceRule::~CSSNamespaceRule() = default;
+
+#ifdef DEBUG
+void CSSNamespaceRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_NamespaceRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+StyleCssRuleType CSSNamespaceRule::Type() const {
+ return StyleCssRuleType::Namespace;
+}
+
+nsAtom* CSSNamespaceRule::GetPrefix() const {
+ return Servo_NamespaceRule_GetPrefix(mRawRule);
+}
+
+void CSSNamespaceRule::GetURLSpec(nsString& aURLSpec) const {
+ nsAtom* atom = Servo_NamespaceRule_GetURI(mRawRule);
+ atom->ToString(aURLSpec);
+}
+
+void CSSNamespaceRule::GetCssText(nsACString& aCssText) const {
+ Servo_NamespaceRule_GetCssText(mRawRule, &aCssText);
+}
+
+void CSSNamespaceRule::SetRawAfterClone(RefPtr<StyleNamespaceRule> aRaw) {
+ mRawRule = std::move(aRaw);
+}
+
+size_t CSSNamespaceRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSNamespaceRule.h b/layout/style/CSSNamespaceRule.h
new file mode 100644
index 0000000000..ff3b5f8b5b
--- /dev/null
+++ b/layout/style/CSSNamespaceRule.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSNamespaceRule_h
+#define mozilla_dom_CSSNamespaceRule_h
+
+#include "mozilla/css/Rule.h"
+#include "mozilla/dom/CSSNamespaceRuleBinding.h"
+#include "mozilla/ServoBindingTypes.h"
+
+class nsAtom;
+
+namespace mozilla::dom {
+
+class CSSNamespaceRule final : public css::Rule {
+ public:
+ CSSNamespaceRule(already_AddRefed<StyleNamespaceRule> aRule,
+ StyleSheet* aSheet, css::Rule* aParentRule, uint32_t aLine,
+ uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRule)) {}
+
+ bool IsCCLeaf() const final { return Rule::IsCCLeaf(); }
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ nsAtom* GetPrefix() const;
+ void GetURLSpec(nsString& aURLSpec) const;
+
+ // WebIDL interface
+ void GetCssText(nsACString& aCssText) const final;
+
+ StyleCssRuleType Type() const final;
+
+ const StyleNamespaceRule* Raw() const { return mRawRule.get(); }
+ void SetRawAfterClone(RefPtr<StyleNamespaceRule>);
+
+ void GetNamespaceURI(nsString& aNamespaceURI) { GetURLSpec(aNamespaceURI); }
+
+ void GetPrefix(DOMString& aPrefix) {
+ aPrefix.SetKnownLiveAtom(GetPrefix(), DOMString::eTreatNullAsEmpty);
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) final {
+ return CSSNamespaceRule_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ private:
+ ~CSSNamespaceRule();
+ RefPtr<StyleNamespaceRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSNamespaceRule_h
diff --git a/layout/style/CSSPageRule.cpp b/layout/style/CSSPageRule.cpp
new file mode 100644
index 0000000000..04d064b362
--- /dev/null
+++ b/layout/style/CSSPageRule.cpp
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSPageRule.h"
+#include "mozilla/dom/CSSPageRuleBinding.h"
+
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla::dom {
+
+// -- CSSPageRuleDeclaration ---------------------------------------
+
+CSSPageRuleDeclaration::CSSPageRuleDeclaration(
+ already_AddRefed<StyleLockedDeclarationBlock> aDecls)
+ : mDecls(new DeclarationBlock(std::move(aDecls))) {
+ mDecls->SetOwningRule(Rule());
+}
+
+CSSPageRuleDeclaration::~CSSPageRuleDeclaration() {
+ mDecls->SetOwningRule(nullptr);
+}
+
+// QueryInterface implementation for CSSPageRuleDeclaration
+NS_INTERFACE_MAP_BEGIN(CSSPageRuleDeclaration)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ // We forward the cycle collection interfaces to Rule(), which is
+ // never null (in fact, we're part of that object!)
+ if (aIID.Equals(NS_GET_IID(nsCycleCollectionISupports)) ||
+ aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant))) {
+ return Rule()->QueryInterface(aIID, aInstancePtr);
+ }
+NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
+
+NS_IMPL_ADDREF_USING_AGGREGATOR(CSSPageRuleDeclaration, Rule())
+NS_IMPL_RELEASE_USING_AGGREGATOR(CSSPageRuleDeclaration, Rule())
+
+/* nsDOMCSSDeclaration implementation */
+
+css::Rule* CSSPageRuleDeclaration::GetParentRule() { return Rule(); }
+
+nsINode* CSSPageRuleDeclaration::GetAssociatedNode() const {
+ return Rule()->GetAssociatedDocumentOrShadowRoot();
+}
+
+nsISupports* CSSPageRuleDeclaration::GetParentObject() const {
+ return Rule()->GetParentObject();
+}
+
+DeclarationBlock* CSSPageRuleDeclaration::GetOrCreateCSSDeclaration(
+ Operation aOperation, DeclarationBlock** aCreated) {
+ if (aOperation != Operation::Read) {
+ if (StyleSheet* sheet = Rule()->GetStyleSheet()) {
+ sheet->WillDirty();
+ }
+ }
+ return mDecls;
+}
+
+void CSSPageRuleDeclaration::SetRawAfterClone(
+ RefPtr<StyleLockedDeclarationBlock> aDeclarationBlock) {
+ mDecls->SetOwningRule(nullptr);
+ mDecls = new DeclarationBlock(aDeclarationBlock.forget());
+ mDecls->SetOwningRule(Rule());
+}
+
+nsresult CSSPageRuleDeclaration::SetCSSDeclaration(
+ DeclarationBlock* aDecl, MutationClosureData* aClosureData) {
+ MOZ_ASSERT(aDecl, "must be non-null");
+ CSSPageRule* rule = Rule();
+
+ if (aDecl != mDecls) {
+ mDecls->SetOwningRule(nullptr);
+ RefPtr<DeclarationBlock> decls = aDecl;
+ Servo_PageRule_SetStyle(rule->Raw(), decls->Raw());
+ mDecls = std::move(decls);
+ mDecls->SetOwningRule(rule);
+ }
+
+ return NS_OK;
+}
+
+nsDOMCSSDeclaration::ParsingEnvironment
+CSSPageRuleDeclaration::GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal) const {
+ return GetParsingEnvironmentForRule(Rule(), StyleCssRuleType::Page);
+}
+
+// -- CSSPageRule --------------------------------------------------
+
+CSSPageRule::CSSPageRule(RefPtr<StyleLockedPageRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)),
+ mDecls(Servo_PageRule_GetStyle(mRawRule).Consume()) {}
+
+NS_IMPL_ADDREF_INHERITED(CSSPageRule, css::Rule)
+NS_IMPL_RELEASE_INHERITED(CSSPageRule, css::Rule)
+
+// QueryInterface implementation for PageRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSPageRule)
+NS_INTERFACE_MAP_END_INHERITING(css::Rule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CSSPageRule)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CSSPageRule, css::Rule)
+ // Keep this in sync with IsCCLeaf.
+
+ // Trace the wrapper for our declaration. This just expands out
+ // NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER which we can't use
+ // directly because the wrapper is on the declaration, not on us.
+ tmp->mDecls.TraceWrapper(aCallbacks, aClosure);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CSSPageRule)
+ // Keep this in sync with IsCCLeaf.
+
+ // Unlink the wrapper for our declaration.
+ //
+ // Note that this has to happen before unlinking css::Rule.
+ tmp->UnlinkDeclarationWrapper(tmp->mDecls);
+ tmp->mDecls.mDecls->SetOwningRule(nullptr);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(css::Rule)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSPageRule, css::Rule)
+ // Keep this in sync with IsCCLeaf.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+bool CSSPageRule::IsCCLeaf() const {
+ if (!Rule::IsCCLeaf()) {
+ return false;
+ }
+
+ return !mDecls.PreservingWrapper();
+}
+
+void CSSPageRule::SetRawAfterClone(RefPtr<StyleLockedPageRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ mDecls.SetRawAfterClone(Servo_PageRule_GetStyle(mRawRule.get()).Consume());
+}
+
+StyleCssRuleType CSSPageRule::Type() const { return StyleCssRuleType::Page; }
+
+size_t CSSPageRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ // TODO Implement this!
+ return aMallocSizeOf(this);
+}
+
+#ifdef DEBUG
+void CSSPageRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_PageRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+/* CSSRule implementation */
+
+void CSSPageRule::GetCssText(nsACString& aCssText) const {
+ Servo_PageRule_GetCssText(mRawRule, &aCssText);
+}
+
+/* CSSPageRule implementation */
+
+void CSSPageRule::GetSelectorText(nsACString& aSelectorText) const {
+ Servo_PageRule_GetSelectorText(mRawRule.get(), &aSelectorText);
+}
+
+void CSSPageRule::SetSelectorText(const nsACString& aSelectorText) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ if (StyleSheet* const sheet = GetStyleSheet()) {
+ sheet->WillDirty();
+ const StyleStylesheetContents* const contents = sheet->RawContents();
+ if (Servo_PageRule_SetSelectorText(contents, mRawRule.get(),
+ &aSelectorText)) {
+ sheet->RuleChanged(this, StyleRuleChangeKind::Generic);
+ }
+ }
+}
+
+nsICSSDeclaration* CSSPageRule::Style() { return &mDecls; }
+
+JSObject* CSSPageRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSPageRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSPageRule.h b/layout/style/CSSPageRule.h
new file mode 100644
index 0000000000..705d526bdf
--- /dev/null
+++ b/layout/style/CSSPageRule.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSPageRule_h
+#define mozilla_dom_CSSPageRule_h
+
+#include "mozilla/css/Rule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+#include "nsDOMCSSDeclaration.h"
+#include "nsICSSDeclaration.h"
+
+namespace mozilla {
+class DeclarationBlock;
+
+namespace dom {
+class DocGroup;
+class CSSPageRule;
+
+class CSSPageRuleDeclaration final : public nsDOMCSSDeclaration {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ css::Rule* GetParentRule() final;
+ nsINode* GetAssociatedNode() const final;
+ nsISupports* GetParentObject() const final;
+
+ protected:
+ DeclarationBlock* GetOrCreateCSSDeclaration(
+ Operation aOperation, DeclarationBlock** aCreated) final;
+ nsresult SetCSSDeclaration(DeclarationBlock* aDecl,
+ MutationClosureData* aClosureData) final;
+ Document* DocToUpdate() final { return nullptr; }
+ nsDOMCSSDeclaration::ParsingEnvironment GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal) const final;
+
+ private:
+ // For accessing the constructor.
+ friend class CSSPageRule;
+
+ explicit CSSPageRuleDeclaration(
+ already_AddRefed<StyleLockedDeclarationBlock> aDecls);
+ void SetRawAfterClone(RefPtr<StyleLockedDeclarationBlock>);
+
+ ~CSSPageRuleDeclaration();
+
+ inline CSSPageRule* Rule();
+ inline const CSSPageRule* Rule() const;
+
+ RefPtr<DeclarationBlock> mDecls;
+};
+
+class CSSPageRule final : public css::Rule {
+ public:
+ CSSPageRule(RefPtr<StyleLockedPageRule> aRawRule, StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(CSSPageRule, css::Rule)
+
+ bool IsCCLeaf() const final;
+
+ StyleLockedPageRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleLockedPageRule>);
+
+ // WebIDL interfaces
+ StyleCssRuleType Type() const final;
+ void GetCssText(nsACString& aCssText) const final;
+ nsICSSDeclaration* Style();
+
+ void GetSelectorText(nsACString& aSelectorText) const;
+ void SetSelectorText(const nsACString& aSelectorText);
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final;
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ private:
+ ~CSSPageRule() = default;
+
+ // For computing the offset of mDecls.
+ friend class CSSPageRuleDeclaration;
+
+ RefPtr<StyleLockedPageRule> mRawRule;
+ CSSPageRuleDeclaration mDecls;
+};
+
+CSSPageRule* CSSPageRuleDeclaration::Rule() {
+ return reinterpret_cast<CSSPageRule*>(reinterpret_cast<uint8_t*>(this) -
+ offsetof(CSSPageRule, mDecls));
+}
+
+const CSSPageRule* CSSPageRuleDeclaration::Rule() const {
+ return reinterpret_cast<const CSSPageRule*>(
+ reinterpret_cast<const uint8_t*>(this) - offsetof(CSSPageRule, mDecls));
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CSSPageRule_h
diff --git a/layout/style/CSSPropFlags.h b/layout/style/CSSPropFlags.h
new file mode 100644
index 0000000000..a2bcff417e
--- /dev/null
+++ b/layout/style/CSSPropFlags.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CSSPropFlags_h
+#define mozilla_CSSPropFlags_h
+
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+
+enum class CSSPropFlags : uint16_t {
+ // This property is not parsed. It is only there for internal use like
+ // attribute mapping.
+ Inaccessible = 1 << 0,
+
+ // The following two flags along with the pref defines where the this
+ // property can be used:
+ // * If none of the two flags is presented, the pref completely controls
+ // the availability of this property. And in that case, if it has no
+ // pref, this property is usable everywhere.
+ // * If any of the flags is set, this property is always enabled in the
+ // specific contexts regardless of the value of the pref. If there is
+ // no pref for this property at all in this case, it is an internal-
+ // only property, which cannot be used anywhere else, and should be
+ // wrapped in "#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL".
+ // Note that, these flags have no effect on the use of aliases of this
+ // property.
+ // Furthermore, for the purposes of animation (including triggering
+ // transitions) these flags are ignored. That is, if the property is disabled
+ // by a pref, we will *not* run animations or transitions on it even in
+ // UA sheets or chrome.
+ EnabledInUASheets = 1 << 1,
+ EnabledInChrome = 1 << 2,
+ EnabledInUASheetsAndChrome = EnabledInUASheets | EnabledInChrome,
+ EnabledMask = EnabledInUASheetsAndChrome,
+
+ // This property can be animated on the compositor.
+ CanAnimateOnCompositor = 1 << 3,
+
+ // This property is an internal property that is not represented in
+ // the DOM. Properties with this flag are defined in an #ifndef
+ // CSS_PROP_LIST_EXCLUDE_INTERNAL section.
+ Internal = 1 << 4,
+
+ // Whether this property should be serialized by Servo in getComputedStyle.
+ SerializedByServo = 1 << 5,
+
+ // Whether this is a logical property.
+ IsLogical = 1 << 6,
+
+ // Whether this shorthand property is unconditionally exposed in
+ // getComputedStyle.
+ ShorthandUnconditionallyExposedOnGetCS = 1 << 7,
+
+ // Whether this property, when changed, may affect layout, overflow, or paint.
+ AffectsLayout = 1 << 8,
+ AffectsOverflow = 1 << 9,
+ AffectsPaint = 1 << 10,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CSSPropFlags)
+
+} // namespace mozilla
+
+#endif // mozilla_CSSPropFlags_h
diff --git a/layout/style/CSSPropertyRule.cpp b/layout/style/CSSPropertyRule.cpp
new file mode 100644
index 0000000000..019e008b08
--- /dev/null
+++ b/layout/style/CSSPropertyRule.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSPropertyRule.h"
+#include "mozilla/dom/CSSPropertyRuleBinding.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla::dom {
+
+bool CSSPropertyRule::IsCCLeaf() const { return Rule::IsCCLeaf(); }
+
+void CSSPropertyRule::SetRawAfterClone(RefPtr<StylePropertyRule> aRaw) {
+ mRawRule = std::move(aRaw);
+}
+
+/* virtual */
+JSObject* CSSPropertyRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSPropertyRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+#ifdef DEBUG
+void CSSPropertyRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_PropertyRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+size_t CSSPropertyRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ // TODO Implement this!
+ return aMallocSizeOf(this);
+}
+
+StyleCssRuleType CSSPropertyRule::Type() const {
+ return StyleCssRuleType::Property;
+}
+
+/* CSSRule implementation */
+
+void CSSPropertyRule::GetCssText(nsACString& aCssText) const {
+ Servo_PropertyRule_GetCssText(mRawRule, &aCssText);
+}
+
+/* CSSPropertyRule implementation */
+
+void CSSPropertyRule::GetName(nsACString& aNameStr) const {
+ Servo_PropertyRule_GetName(mRawRule, &aNameStr);
+}
+
+void CSSPropertyRule::GetSyntax(nsACString& aSyntaxStr) const {
+ Servo_PropertyRule_GetSyntax(mRawRule, &aSyntaxStr);
+}
+
+bool CSSPropertyRule::Inherits() const {
+ return Servo_PropertyRule_GetInherits(mRawRule);
+}
+
+void CSSPropertyRule::GetInitialValue(nsACString& aInitialValueStr) const {
+ bool found = Servo_PropertyRule_GetInitialValue(mRawRule, &aInitialValueStr);
+ if (!found) {
+ aInitialValueStr.SetIsVoid(true);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSPropertyRule.h b/layout/style/CSSPropertyRule.h
new file mode 100644
index 0000000000..ca6d5a12f2
--- /dev/null
+++ b/layout/style/CSSPropertyRule.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_CSSPropertyRule_h
+#define mozilla_dom_CSSPropertyRule_h
+
+#include "mozilla/css/Rule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+#include "nsICSSDeclaration.h"
+
+struct StylePropertyRule;
+
+namespace mozilla::dom {
+
+class CSSPropertyRule final : public css::Rule {
+ public:
+ CSSPropertyRule(already_AddRefed<StylePropertyRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule, uint32_t aLine,
+ uint32_t aColumn)
+ : css::Rule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+ bool IsCCLeaf() const final;
+
+ StylePropertyRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StylePropertyRule> aRaw);
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final;
+
+ // WebIDL interfaces
+ StyleCssRuleType Type() const final;
+
+ void GetName(nsACString& aName) const;
+
+ void GetSyntax(nsACString& aSyntax) const;
+
+ bool Inherits() const;
+
+ void GetInitialValue(nsACString& aInitialValueStr) const;
+
+ void GetCssText(nsACString& aCssText) const final;
+
+ private:
+ ~CSSPropertyRule() = default;
+
+ RefPtr<StylePropertyRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSPropertyRule_h
diff --git a/layout/style/CSSRuleList.cpp b/layout/style/CSSRuleList.cpp
new file mode 100644
index 0000000000..e4e9179022
--- /dev/null
+++ b/layout/style/CSSRuleList.cpp
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSRuleList.h"
+
+#include "mozilla/dom/CSSRuleListBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(CSSRuleList)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSRuleList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CSSRuleList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CSSRuleList)
+
+/* virtual */
+JSObject* CSSRuleList::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSRuleList_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/CSSRuleList.h b/layout/style/CSSRuleList.h
new file mode 100644
index 0000000000..b3802233d0
--- /dev/null
+++ b/layout/style/CSSRuleList.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSRuleList_h
+#define mozilla_dom_CSSRuleList_h
+
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Rule.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class CSSRuleList : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CSSRuleList)
+
+ virtual StyleSheet* GetParentObject() = 0;
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ // WebIDL API
+ css::Rule* Item(uint32_t aIndex) {
+ bool unused;
+ return IndexedGetter(aIndex, unused);
+ }
+
+ virtual css::Rule* IndexedGetter(uint32_t aIndex, bool& aFound) = 0;
+ virtual uint32_t Length() = 0;
+
+ protected:
+ virtual ~CSSRuleList() = default;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_CSSRuleList_h */
diff --git a/layout/style/CSSStyleRule.cpp b/layout/style/CSSStyleRule.cpp
new file mode 100644
index 0000000000..5cdb47fddb
--- /dev/null
+++ b/layout/style/CSSStyleRule.cpp
@@ -0,0 +1,323 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/CSSStyleRule.h"
+
+#include "mozilla/CSSEnabledState.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/PseudoStyleType.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsCSSPseudoElements.h"
+
+#include "mozAutoDocUpdate.h"
+#include "nsISupports.h"
+
+namespace mozilla::dom {
+
+// -- CSSStyleRuleDeclaration ---------------------------------------
+
+CSSStyleRuleDeclaration::CSSStyleRuleDeclaration(
+ already_AddRefed<StyleLockedDeclarationBlock> aDecls)
+ : mDecls(new DeclarationBlock(std::move(aDecls))) {
+ mDecls->SetOwningRule(Rule());
+}
+
+CSSStyleRuleDeclaration::~CSSStyleRuleDeclaration() {
+ mDecls->SetOwningRule(nullptr);
+}
+
+// QueryInterface implementation for CSSStyleRuleDeclaration
+NS_INTERFACE_MAP_BEGIN(CSSStyleRuleDeclaration)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ // We forward the cycle collection interfaces to Rule(), which is
+ // never null (in fact, we're part of that object!)
+ if (aIID.Equals(NS_GET_IID(nsCycleCollectionISupports)) ||
+ aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant))) {
+ return Rule()->QueryInterface(aIID, aInstancePtr);
+ }
+NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
+
+NS_IMPL_ADDREF_USING_AGGREGATOR(CSSStyleRuleDeclaration, Rule())
+NS_IMPL_RELEASE_USING_AGGREGATOR(CSSStyleRuleDeclaration, Rule())
+
+/* nsDOMCSSDeclaration implementation */
+
+css::Rule* CSSStyleRuleDeclaration::GetParentRule() { return Rule(); }
+
+nsINode* CSSStyleRuleDeclaration::GetAssociatedNode() const {
+ return Rule()->GetAssociatedDocumentOrShadowRoot();
+}
+
+nsISupports* CSSStyleRuleDeclaration::GetParentObject() const {
+ return Rule()->GetParentObject();
+}
+
+DeclarationBlock* CSSStyleRuleDeclaration::GetOrCreateCSSDeclaration(
+ Operation aOperation, DeclarationBlock** aCreated) {
+ if (aOperation != Operation::Read) {
+ if (StyleSheet* sheet = Rule()->GetStyleSheet()) {
+ sheet->WillDirty();
+ }
+ }
+ return mDecls;
+}
+
+void CSSStyleRule::SetRawAfterClone(RefPtr<StyleLockedStyleRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ mDecls.SetRawAfterClone(Servo_StyleRule_GetStyle(mRawRule).Consume());
+ GroupRule::DidSetRawAfterClone();
+}
+
+already_AddRefed<StyleLockedCssRules> CSSStyleRule::GetOrCreateRawRules() {
+ return Servo_StyleRule_EnsureRules(mRawRule, IsReadOnly()).Consume();
+}
+
+void CSSStyleRuleDeclaration::SetRawAfterClone(
+ RefPtr<StyleLockedDeclarationBlock> aRaw) {
+ RefPtr<DeclarationBlock> block = new DeclarationBlock(aRaw.forget());
+ mDecls->SetOwningRule(nullptr);
+ mDecls = std::move(block);
+ mDecls->SetOwningRule(Rule());
+}
+
+nsresult CSSStyleRuleDeclaration::SetCSSDeclaration(
+ DeclarationBlock* aDecl, MutationClosureData* aClosureData) {
+ CSSStyleRule* rule = Rule();
+
+ if (StyleSheet* sheet = rule->GetStyleSheet()) {
+ if (aDecl != mDecls) {
+ mDecls->SetOwningRule(nullptr);
+ RefPtr<DeclarationBlock> decls = aDecl;
+ Servo_StyleRule_SetStyle(rule->Raw(), decls->Raw());
+ mDecls = std::move(decls);
+ mDecls->SetOwningRule(rule);
+ }
+ sheet->RuleChanged(rule, StyleRuleChangeKind::StyleRuleDeclarations);
+ }
+ return NS_OK;
+}
+
+Document* CSSStyleRuleDeclaration::DocToUpdate() { return nullptr; }
+
+nsDOMCSSDeclaration::ParsingEnvironment
+CSSStyleRuleDeclaration::GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal) const {
+ return GetParsingEnvironmentForRule(Rule(), StyleCssRuleType::Style);
+}
+
+// -- CSSStyleRule --------------------------------------------------
+
+CSSStyleRule::CSSStyleRule(already_AddRefed<StyleLockedStyleRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : GroupRule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(aRawRule),
+ mDecls(Servo_StyleRule_GetStyle(mRawRule).Consume()) {}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(CSSStyleRule, GroupRule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CSSStyleRule)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CSSStyleRule, GroupRule)
+ // Keep this in sync with IsCCLeaf.
+
+ // Trace the wrapper for our declaration. This just expands out
+ // NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER which we can't use
+ // directly because the wrapper is on the declaration, not on us.
+ tmp->mDecls.TraceWrapper(aCallbacks, aClosure);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CSSStyleRule)
+ // Keep this in sync with IsCCLeaf.
+
+ // Unlink the wrapper for our declaration.
+ //
+ // Note that this has to happen before unlinking css::Rule.
+ tmp->UnlinkDeclarationWrapper(tmp->mDecls);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(GroupRule)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSStyleRule, GroupRule)
+ // Keep this in sync with IsCCLeaf.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+bool CSSStyleRule::IsCCLeaf() const {
+ if (!GroupRule::IsCCLeaf()) {
+ return false;
+ }
+ return !mDecls.PreservingWrapper();
+}
+
+size_t CSSStyleRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mRawRule
+ // - mDecls
+
+ return n;
+}
+
+#ifdef DEBUG
+void CSSStyleRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_StyleRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+/* CSSRule implementation */
+
+StyleCssRuleType CSSStyleRule::Type() const { return StyleCssRuleType::Style; }
+
+void CSSStyleRule::GetCssText(nsACString& aCssText) const {
+ Servo_StyleRule_GetCssText(mRawRule, &aCssText);
+}
+
+nsICSSDeclaration* CSSStyleRule::Style() { return &mDecls; }
+
+/* CSSStyleRule implementation */
+
+void CSSStyleRule::GetSelectorText(nsACString& aSelectorText) {
+ Servo_StyleRule_GetSelectorText(mRawRule, &aSelectorText);
+}
+
+void CSSStyleRule::SetSelectorText(const nsACString& aSelectorText) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ if (StyleSheet* sheet = GetStyleSheet()) {
+ sheet->WillDirty();
+
+ // TODO(emilio): May actually be more efficient to handle this as rule
+ // removal + addition, from the point of view of invalidation...
+ const StyleStylesheetContents* contents = sheet->RawContents();
+ if (Servo_StyleRule_SetSelectorText(contents, mRawRule, &aSelectorText)) {
+ sheet->RuleChanged(this, StyleRuleChangeKind::Generic);
+ }
+ }
+}
+
+uint32_t CSSStyleRule::SelectorCount() const {
+ return Servo_StyleRule_GetSelectorCount(mRawRule);
+}
+
+static void CollectStyleRules(CSSStyleRule& aDeepestRule, bool aDesugared,
+ nsTArray<const StyleLockedStyleRule*>& aResult) {
+ aResult.AppendElement(aDeepestRule.Raw());
+ if (aDesugared) {
+ for (auto* rule = aDeepestRule.GetParentRule(); rule;
+ rule = rule->GetParentRule()) {
+ if (rule->Type() == StyleCssRuleType::Style) {
+ aResult.AppendElement(static_cast<CSSStyleRule*>(rule)->Raw());
+ }
+ }
+ }
+}
+
+void CSSStyleRule::GetSelectorDataAtIndex(uint32_t aSelectorIndex,
+ bool aDesugared, nsACString* aText,
+ uint64_t* aSpecificity) {
+ AutoTArray<const StyleLockedStyleRule*, 8> rules;
+ CollectStyleRules(*this, aDesugared, rules);
+ Servo_StyleRule_GetSelectorDataAtIndex(&rules, aSelectorIndex, aText,
+ aSpecificity);
+}
+
+void CSSStyleRule::SelectorTextAt(uint32_t aSelectorIndex, bool aDesugared,
+ nsACString& aText) {
+ GetSelectorDataAtIndex(aSelectorIndex, aDesugared, &aText, nullptr);
+}
+
+uint64_t CSSStyleRule::SelectorSpecificityAt(uint32_t aSelectorIndex,
+ bool aDesugared) {
+ uint64_t s = 0;
+ GetSelectorDataAtIndex(aSelectorIndex, aDesugared, nullptr, &s);
+ return s;
+}
+
+bool CSSStyleRule::SelectorMatchesElement(uint32_t aSelectorIndex,
+ Element& aElement,
+ const nsAString& aPseudo,
+ bool aRelevantLinkVisited) {
+ Maybe<PseudoStyleType> pseudoType = nsCSSPseudoElements::GetPseudoType(
+ aPseudo, CSSEnabledState::IgnoreEnabledState);
+ if (!pseudoType) {
+ return false;
+ }
+
+ auto* host = [&]() -> Element* {
+ auto* sheet = GetStyleSheet();
+ if (!sheet) {
+ return nullptr;
+ }
+ if (auto* owner = sheet->GetAssociatedDocumentOrShadowRoot()) {
+ if (auto* shadow = ShadowRoot::FromNode(owner->AsNode())) {
+ return shadow->Host();
+ }
+ }
+ for (auto* adopter : sheet->SelfOrAncestorAdopters()) {
+ // Try to guess. This is not fully correct but it's the best we can do
+ // with the info at hand...
+ auto* shadow = ShadowRoot::FromNode(adopter->AsNode());
+ if (!shadow) {
+ continue;
+ }
+ if (shadow->Host() == &aElement ||
+ shadow == aElement.GetContainingShadow()) {
+ return shadow->Host();
+ }
+ }
+ return nullptr;
+ }();
+
+ AutoTArray<const StyleLockedStyleRule*, 8> rules;
+ CollectStyleRules(*this, /* aDesugared = */ true, rules);
+
+ return Servo_StyleRule_SelectorMatchesElement(
+ &rules, &aElement, aSelectorIndex, host, *pseudoType,
+ aRelevantLinkVisited);
+}
+
+NotNull<DeclarationBlock*> CSSStyleRule::GetDeclarationBlock() const {
+ return WrapNotNull(mDecls.mDecls);
+}
+
+SelectorWarningKind ToWebIDLSelectorWarningKind(
+ StyleSelectorWarningKind aKind) {
+ switch (aKind) {
+ case StyleSelectorWarningKind::UnconstraintedRelativeSelector:
+ return SelectorWarningKind::UnconstrainedHas;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unhandled selector warning kind");
+ // Return something for assert-disabled builds.
+ return SelectorWarningKind::UnconstrainedHas;
+}
+
+void CSSStyleRule::GetSelectorWarnings(
+ nsTArray<SelectorWarning>& aResult) const {
+ nsTArray<StyleSelectorWarningData> result;
+ Servo_GetSelectorWarnings(mRawRule, &result);
+ for (const auto& warning : result) {
+ auto& entry = *aResult.AppendElement();
+ entry.mIndex = warning.index;
+ entry.mKind = ToWebIDLSelectorWarningKind(warning.kind);
+ }
+}
+
+/* virtual */
+JSObject* CSSStyleRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSStyleRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSStyleRule.h b/layout/style/CSSStyleRule.h
new file mode 100644
index 0000000000..05eaae5c10
--- /dev/null
+++ b/layout/style/CSSStyleRule.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_CSSStyleRule_h
+#define mozilla_CSSStyleRule_h
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/CSSStyleRuleBinding.h"
+
+#include "nsDOMCSSDeclaration.h"
+
+namespace mozilla {
+
+class DeclarationBlock;
+
+namespace dom {
+class DocGroup;
+class CSSStyleRule;
+
+class CSSStyleRuleDeclaration final : public nsDOMCSSDeclaration {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ css::Rule* GetParentRule() final;
+ nsINode* GetAssociatedNode() const final;
+ nsISupports* GetParentObject() const final;
+
+ protected:
+ mozilla::DeclarationBlock* GetOrCreateCSSDeclaration(
+ Operation aOperation, mozilla::DeclarationBlock** aCreated) final;
+ nsresult SetCSSDeclaration(DeclarationBlock* aDecl,
+ MutationClosureData* aClosureData) final;
+ Document* DocToUpdate() final;
+ ParsingEnvironment GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal) const final;
+
+ private:
+ // For accessing the constructor.
+ friend class CSSStyleRule;
+
+ explicit CSSStyleRuleDeclaration(
+ already_AddRefed<StyleLockedDeclarationBlock> aDecls);
+ ~CSSStyleRuleDeclaration();
+
+ inline CSSStyleRule* Rule();
+ inline const CSSStyleRule* Rule() const;
+
+ void SetRawAfterClone(RefPtr<StyleLockedDeclarationBlock>);
+
+ RefPtr<DeclarationBlock> mDecls;
+};
+
+class CSSStyleRule final : public css::GroupRule, public SupportsWeakPtr {
+ public:
+ CSSStyleRule(already_AddRefed<StyleLockedStyleRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule, uint32_t aLine,
+ uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(CSSStyleRule,
+ css::GroupRule)
+ bool IsCCLeaf() const final MOZ_MUST_OVERRIDE;
+
+ uint32_t SelectorCount() const;
+ void SelectorTextAt(uint32_t aSelectorIndex, bool aDesugared,
+ nsACString& aText);
+ uint64_t SelectorSpecificityAt(uint32_t aSelectorIndex, bool aDesugared);
+ bool SelectorMatchesElement(uint32_t aSelectorIndex, dom::Element&,
+ const nsAString& aPseudo,
+ bool aRelevantLinkVisited);
+ NotNull<DeclarationBlock*> GetDeclarationBlock() const;
+ void GetSelectorWarnings(nsTArray<SelectorWarning>& aResult) const;
+
+ // WebIDL interface
+ StyleCssRuleType Type() const final;
+ void GetCssText(nsACString& aCssText) const final;
+ void GetSelectorText(nsACString& aSelectorText);
+ void SetSelectorText(const nsACString& aSelectorText);
+ nsICSSDeclaration* Style();
+
+ StyleLockedStyleRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleLockedStyleRule>);
+ already_AddRefed<StyleLockedCssRules> GetOrCreateRawRules() final;
+
+ // Methods of mozilla::css::Rule
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final;
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ ~CSSStyleRule() = default;
+
+ void GetSelectorDataAtIndex(uint32_t aSelectorIndex, bool aDesugared,
+ nsACString* aText, uint64_t* aSpecificity);
+
+ // For computing the offset of mDecls.
+ friend class CSSStyleRuleDeclaration;
+
+ RefPtr<StyleLockedStyleRule> mRawRule;
+ CSSStyleRuleDeclaration mDecls;
+};
+
+CSSStyleRule* CSSStyleRuleDeclaration::Rule() {
+ return reinterpret_cast<CSSStyleRule*>(reinterpret_cast<uint8_t*>(this) -
+ offsetof(CSSStyleRule, mDecls));
+}
+
+const CSSStyleRule* CSSStyleRuleDeclaration::Rule() const {
+ return reinterpret_cast<const CSSStyleRule*>(
+ reinterpret_cast<const uint8_t*>(this) - offsetof(CSSStyleRule, mDecls));
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_CSSStyleRule_h
diff --git a/layout/style/CSSSupportsRule.cpp b/layout/style/CSSSupportsRule.cpp
new file mode 100644
index 0000000000..e848258ed8
--- /dev/null
+++ b/layout/style/CSSSupportsRule.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSSupportsRule.h"
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/dom/CSSSupportsRuleBinding.h"
+#include "mozilla/ServoBindings.h"
+
+using namespace mozilla::css;
+
+namespace mozilla::dom {
+
+CSSSupportsRule::CSSSupportsRule(RefPtr<StyleSupportsRule> aRawRule,
+ StyleSheet* aSheet, css::Rule* aParentRule,
+ uint32_t aLine, uint32_t aColumn)
+ : css::ConditionRule(aSheet, aParentRule, aLine, aColumn),
+ mRawRule(std::move(aRawRule)) {}
+
+NS_IMPL_ADDREF_INHERITED(CSSSupportsRule, ConditionRule)
+NS_IMPL_RELEASE_INHERITED(CSSSupportsRule, ConditionRule)
+
+// QueryInterface implementation for SupportsRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSSupportsRule)
+NS_INTERFACE_MAP_END_INHERITING(ConditionRule)
+
+#ifdef DEBUG
+/* virtual */
+void CSSSupportsRule::List(FILE* out, int32_t aIndent) const {
+ nsAutoCString str;
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ Servo_SupportsRule_Debug(mRawRule, &str);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+StyleCssRuleType CSSSupportsRule::Type() const {
+ return StyleCssRuleType::Supports;
+}
+
+void CSSSupportsRule::GetConditionText(nsACString& aConditionText) {
+ Servo_SupportsRule_GetConditionText(mRawRule, &aConditionText);
+}
+
+/* virtual */
+void CSSSupportsRule::GetCssText(nsACString& aCssText) const {
+ Servo_SupportsRule_GetCssText(mRawRule, &aCssText);
+}
+
+void CSSSupportsRule::SetRawAfterClone(RefPtr<StyleSupportsRule> aRaw) {
+ mRawRule = std::move(aRaw);
+ css::ConditionRule::DidSetRawAfterClone();
+}
+
+already_AddRefed<StyleLockedCssRules> CSSSupportsRule::GetOrCreateRawRules() {
+ return Servo_SupportsRule_GetRules(mRawRule).Consume();
+}
+
+/* virtual */
+size_t CSSSupportsRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ // TODO Implement this!
+ return aMallocSizeOf(this);
+}
+
+/* virtual */
+JSObject* CSSSupportsRule::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSSSupportsRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/CSSSupportsRule.h b/layout/style/CSSSupportsRule.h
new file mode 100644
index 0000000000..58b622c96f
--- /dev/null
+++ b/layout/style/CSSSupportsRule.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSSupportsRule_h
+#define mozilla_dom_CSSSupportsRule_h
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+namespace mozilla::dom {
+
+class CSSSupportsRule final : public css::ConditionRule {
+ public:
+ CSSSupportsRule(RefPtr<StyleSupportsRule> aRawRule, StyleSheet* aSheet,
+ css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+ StyleSupportsRule* Raw() const { return mRawRule; }
+ void SetRawAfterClone(RefPtr<StyleSupportsRule>);
+
+ // WebIDL interface
+ StyleCssRuleType Type() const final;
+ void GetCssText(nsACString& aCssText) const final;
+ void GetConditionText(nsACString& aConditionText) final;
+ already_AddRefed<StyleLockedCssRules> GetOrCreateRawRules() final;
+
+ size_t SizeOfIncludingThis(MallocSizeOf) const override;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ ~CSSSupportsRule() = default;
+
+ RefPtr<StyleSupportsRule> mRawRule;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CSSSupportsRule_h
diff --git a/layout/style/CSSValue.h b/layout/style/CSSValue.h
new file mode 100644
index 0000000000..532d9a07df
--- /dev/null
+++ b/layout/style/CSSValue.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object representing values in DOM computed style */
+
+#ifndef mozilla_dom_CSSValue_h_
+#define mozilla_dom_CSSValue_h_
+
+#include "nsStringFwd.h"
+#include "mozilla/RefCounted.h"
+
+class nsROCSSPrimitiveValue;
+namespace mozilla {
+class ErrorResult;
+} // namespace mozilla
+
+namespace mozilla::dom {
+
+/**
+ * CSSValue - a DOM object representing values in DOM computed style.
+ */
+class CSSValue : public RefCounted<CSSValue> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(CSSValue);
+ enum : uint16_t {
+ CSS_INHERIT,
+ CSS_PRIMITIVE_VALUE,
+ CSS_VALUE_LIST,
+ CSS_CUSTOM,
+ };
+
+ // CSSValue
+ virtual void GetCssText(nsAString&) = 0;
+ virtual uint16_t CssValueType() const = 0;
+
+ virtual ~CSSValue() = default;
+
+ // Downcasting
+
+ /**
+ * Return this as a nsROCSSPrimitiveValue* if its a primitive value, and null
+ * otherwise.
+ *
+ * Defined in nsROCSSPrimitiveValue.h.
+ */
+ inline nsROCSSPrimitiveValue* AsPrimitiveValue();
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/layout/style/CachedInheritingStyles.cpp b/layout/style/CachedInheritingStyles.cpp
new file mode 100644
index 0000000000..da6e4d24e6
--- /dev/null
+++ b/layout/style/CachedInheritingStyles.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/CachedInheritingStyles.h"
+
+#include "mozilla/ComputedStyle.h"
+#include "nsCOMPtr.h"
+#include "nsWindowSizes.h"
+
+namespace mozilla {
+
+void CachedInheritingStyles::Insert(ComputedStyle* aStyle) {
+ MOZ_ASSERT(aStyle);
+ MOZ_ASSERT(aStyle->IsInheritingAnonBox() ||
+ aStyle->IsLazilyCascadedPseudoElement());
+
+ if (IsEmpty()) {
+ RefPtr<ComputedStyle> s = aStyle;
+ mBits = reinterpret_cast<uintptr_t>(s.forget().take());
+ MOZ_ASSERT(!IsEmpty() && !IsIndirect());
+ } else if (IsIndirect()) {
+ AsIndirect()->AppendElement(aStyle);
+ } else {
+ IndirectCache* cache = new IndirectCache();
+ cache->AppendElement(dont_AddRef(AsDirect()));
+ cache->AppendElement(aStyle);
+ mBits = reinterpret_cast<uintptr_t>(cache) | 1;
+ MOZ_ASSERT(IsIndirect());
+ }
+}
+
+ComputedStyle* CachedInheritingStyles::Lookup(PseudoStyleType aType) const {
+ MOZ_ASSERT(PseudoStyle::IsPseudoElement(aType) ||
+ PseudoStyle::IsInheritingAnonBox(aType));
+ if (IsIndirect()) {
+ for (auto& style : *AsIndirect()) {
+ if (style->GetPseudoType() == aType) {
+ return style;
+ }
+ }
+
+ return nullptr;
+ }
+
+ ComputedStyle* direct = AsDirect();
+ return direct && direct->GetPseudoType() == aType ? direct : nullptr;
+}
+
+void CachedInheritingStyles::AddSizeOfIncludingThis(nsWindowSizes& aSizes,
+ size_t* aCVsSize) const {
+ if (IsIndirect()) {
+ for (auto& style : *AsIndirect()) {
+ if (!aSizes.mState.HaveSeenPtr(style)) {
+ style->AddSizeOfIncludingThis(aSizes, aCVsSize);
+ }
+ }
+
+ return;
+ }
+
+ ComputedStyle* direct = AsDirect();
+ if (direct && !aSizes.mState.HaveSeenPtr(direct)) {
+ direct->AddSizeOfIncludingThis(aSizes, aCVsSize);
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/style/CachedInheritingStyles.h b/layout/style/CachedInheritingStyles.h
new file mode 100644
index 0000000000..cfe86594ca
--- /dev/null
+++ b/layout/style/CachedInheritingStyles.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CachedInheritingStyles_h
+#define mozilla_CachedInheritingStyles_h
+
+#include "nsAtom.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class nsWindowSizes;
+
+namespace mozilla {
+
+enum class PseudoStyleType : uint8_t;
+class ComputedStyle;
+
+// Cache of anonymous box and lazy pseudo styles that inherit from a given
+// style.
+//
+// To minimize memory footprint, the cache is word-sized with a tagged pointer
+// If there is only one entry, it's stored inline. If there are more, they're
+// stored in an out-of-line buffer. See bug 1429126 comment 0 and comment 1 for
+// the measurements and rationale that influenced the design.
+class CachedInheritingStyles {
+ public:
+ void Insert(ComputedStyle* aStyle);
+ ComputedStyle* Lookup(PseudoStyleType) const;
+
+ CachedInheritingStyles() : mBits(0) {}
+ ~CachedInheritingStyles() {
+ if (IsIndirect()) {
+ delete AsIndirect();
+ } else if (!IsEmpty()) {
+ RefPtr<ComputedStyle> ref = dont_AddRef(AsDirect());
+ }
+ }
+
+ void AddSizeOfIncludingThis(nsWindowSizes& aSizes, size_t* aCVsSize) const;
+
+ private:
+ // See bug 1429126 comment 1 for the choice of four here.
+ typedef AutoTArray<RefPtr<ComputedStyle>, 4> IndirectCache;
+
+ bool IsEmpty() const { return !mBits; }
+ bool IsIndirect() const { return (mBits & 1); }
+
+ ComputedStyle* AsDirect() const {
+ MOZ_ASSERT(!IsIndirect());
+ return reinterpret_cast<ComputedStyle*>(mBits);
+ }
+
+ IndirectCache* AsIndirect() const {
+ MOZ_ASSERT(IsIndirect());
+ return reinterpret_cast<IndirectCache*>(mBits & ~1);
+ }
+
+ uintptr_t mBits;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_CachedInheritingStyles_h
diff --git a/layout/style/ComputedStyle.cpp b/layout/style/ComputedStyle.cpp
new file mode 100644
index 0000000000..1cb291b27b
--- /dev/null
+++ b/layout/style/ComputedStyle.cpp
@@ -0,0 +1,433 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 interface (to internal code) for retrieving computed style data */
+
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ToString.h"
+
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSPseudoElements.h"
+#include "nsFontMetrics.h"
+#include "nsStyleConsts.h"
+#include "nsStyleStruct.h"
+#include "nsStyleStructInlines.h"
+#include "nsString.h"
+#include "nsPresContext.h"
+#include "nsWindowSizes.h"
+
+#include "nsCOMPtr.h"
+
+#include "mozilla/dom/Document.h"
+#include "nsPrintfCString.h"
+#include "RubyUtils.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+
+#include "mozilla/ReflowInput.h"
+#include "nsLayoutUtils.h"
+#include "nsCoord.h"
+
+// Ensure the binding function declarations in ComputedStyle.h matches
+// those in ServoBindings.h.
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla {
+
+ComputedStyle::ComputedStyle(PseudoStyleType aPseudoType,
+ ServoComputedDataForgotten aComputedValues)
+ : mSource(aComputedValues), mPseudoType(aPseudoType) {}
+
+// If a struct returned nsChangeHint_UpdateContainingBlock, that means that one
+// property's influence on whether we're a containing block for abs-pos or
+// fixed-pos elements has changed.
+//
+// However, we only need to return the hint if the overall computation of
+// whether we establish a containing block has really changed.
+static bool ContainingBlockMayHaveChanged(const ComputedStyle& aOldStyle,
+ const ComputedStyle& aNewStyle) {
+ const auto& oldDisp = *aOldStyle.StyleDisplay();
+ const auto& newDisp = *aNewStyle.StyleDisplay();
+
+ if (oldDisp.IsPositionedStyle() != newDisp.IsPositionedStyle()) {
+ // XXX This check can probably be moved to after the fixedCB check, since
+ // IsPositionedStyle() is also only relevant for non-svg text frame
+ // subtrees.
+ return true;
+ }
+
+ const bool fixedCB = aOldStyle.IsFixedPosContainingBlockForNonSVGTextFrames();
+ if (fixedCB != aNewStyle.IsFixedPosContainingBlockForNonSVGTextFrames()) {
+ return true;
+ }
+ // If we were both before and after a fixed-pos containing-block that means
+ // that everything else doesn't matter, since all the other conditions are a
+ // subset of this.
+ if (fixedCB) {
+ return false;
+ }
+
+ // Note that neither of these two following sets of frames
+ // (transform-supporting and layout-and-paint-supporting frames) is a subset
+ // of the other, because table frames support contain: layout/paint but not
+ // transforms (which are instead inherited to the table wrapper), and quite a
+ // few frame types support transforms but not contain: layout/paint (e.g.,
+ // table rows and row groups, many SVG frames).
+ if (oldDisp.IsFixedPosContainingBlockForTransformSupportingFrames() !=
+ newDisp.IsFixedPosContainingBlockForTransformSupportingFrames()) {
+ return true;
+ }
+ if (oldDisp
+ .IsFixedPosContainingBlockForContainLayoutAndPaintSupportingFrames() !=
+ newDisp
+ .IsFixedPosContainingBlockForContainLayoutAndPaintSupportingFrames()) {
+ return true;
+ }
+ return false;
+}
+
+nsChangeHint ComputedStyle::CalcStyleDifference(const ComputedStyle& aNewStyle,
+ uint32_t* aEqualStructs) const {
+ AUTO_PROFILER_LABEL_HOT("ComputedStyle::CalcStyleDifference", LAYOUT);
+ static_assert(StyleStructConstants::kStyleStructCount <= 32,
+ "aEqualStructs is not big enough");
+
+ *aEqualStructs = 0;
+
+ nsChangeHint hint = nsChangeHint(0);
+ // We must always ensure that we populate the structs on the new style
+ // context that are filled in on the old context, so that if we get
+ // two style changes in succession, the second of which causes a real
+ // style change, the PeekStyleData doesn't return null (implying that
+ // nobody ever looked at that struct's data). In other words, we
+ // can't skip later structs if we get a big change up front, because
+ // we could later get a small change in one of those structs that we
+ // don't want to miss.
+
+ DebugOnly<uint32_t> structsFound = 0;
+
+ DebugOnly<int> styleStructCount = 0;
+
+ // Servo's optimization to stop the cascade when there are no style changes
+ // that children need to be recascade for relies on comparing all of the
+ // structs, not just those that are returned from PeekStyleData, although
+ // if PeekStyleData does return null we could avoid to accumulate any change
+ // hints for those structs.
+ //
+ // FIXME(emilio): Reintroduce that optimization either for all kind of structs
+ // after bug 1368290 with a weak parent pointer from text, or just for reset
+ // structs.
+#define STYLE_STRUCT_BIT(name_) \
+ StyleStructConstants::BitFor(StyleStructID::name_)
+
+#define EXPAND(...) __VA_ARGS__
+#define DO_STRUCT_DIFFERENCE_WITH_ARGS(struct_, extra_args_) \
+ PR_BEGIN_MACRO \
+ const nsStyle##struct_* this##struct_ = Style##struct_(); \
+ structsFound |= STYLE_STRUCT_BIT(struct_); \
+ \
+ const nsStyle##struct_* other##struct_ = aNewStyle.Style##struct_(); \
+ if (this##struct_ == other##struct_) { \
+ /* The very same struct, so we know that there will be no */ \
+ /* differences. */ \
+ *aEqualStructs |= STYLE_STRUCT_BIT(struct_); \
+ } else { \
+ nsChangeHint difference = \
+ this##struct_->CalcDifference(*other##struct_ EXPAND extra_args_); \
+ hint |= difference; \
+ if (!difference) { \
+ *aEqualStructs |= STYLE_STRUCT_BIT(struct_); \
+ } \
+ } \
+ styleStructCount++; \
+ PR_END_MACRO
+#define DO_STRUCT_DIFFERENCE(struct_) \
+ DO_STRUCT_DIFFERENCE_WITH_ARGS(struct_, ())
+
+ // FIXME: The order of these DO_STRUCT_DIFFERENCE calls is no longer
+ // significant. With a small amount of effort, we could replace them with a
+ // #include "nsStyleStructList.h".
+ DO_STRUCT_DIFFERENCE_WITH_ARGS(Display, (, *this));
+ DO_STRUCT_DIFFERENCE(XUL);
+ DO_STRUCT_DIFFERENCE(Column);
+ DO_STRUCT_DIFFERENCE(Content);
+ DO_STRUCT_DIFFERENCE(UI);
+ DO_STRUCT_DIFFERENCE(Visibility);
+ DO_STRUCT_DIFFERENCE(Outline);
+ DO_STRUCT_DIFFERENCE(TableBorder);
+ DO_STRUCT_DIFFERENCE(Table);
+ DO_STRUCT_DIFFERENCE(UIReset);
+ DO_STRUCT_DIFFERENCE(Text);
+ DO_STRUCT_DIFFERENCE_WITH_ARGS(List, (, *this));
+ DO_STRUCT_DIFFERENCE(SVGReset);
+ DO_STRUCT_DIFFERENCE(SVG);
+ DO_STRUCT_DIFFERENCE_WITH_ARGS(Position, (, *this));
+ DO_STRUCT_DIFFERENCE(Font);
+ DO_STRUCT_DIFFERENCE(Margin);
+ DO_STRUCT_DIFFERENCE(Padding);
+ DO_STRUCT_DIFFERENCE(Border);
+ DO_STRUCT_DIFFERENCE(TextReset);
+ DO_STRUCT_DIFFERENCE(Effects);
+ DO_STRUCT_DIFFERENCE(Background);
+ DO_STRUCT_DIFFERENCE(Page);
+
+#undef DO_STRUCT_DIFFERENCE
+#undef DO_STRUCT_DIFFERENCE_WITH_ARGS
+#undef EXPAND
+
+ MOZ_ASSERT(styleStructCount == StyleStructConstants::kStyleStructCount,
+ "missing a call to DO_STRUCT_DIFFERENCE");
+
+ // Note that we do not check whether this->RelevantLinkVisited() !=
+ // aNewContext->RelevantLinkVisited(); we don't need to since
+ // nsCSSFrameConstructor::DoContentStateChanged always adds
+ // nsChangeHint_RepaintFrame for ElementState::VISITED changes (and
+ // needs to, since HasStateDependentStyle probably doesn't work right
+ // for ElementState::VISITED). Hopefully this doesn't actually
+ // expose whether links are visited to performance tests since all
+ // link coloring happens asynchronously at a time when it's hard for
+ // the page to measure.
+ // However, we do need to compute the larger of the changes that can
+ // happen depending on whether the link is visited or unvisited, since
+ // doing only the one that's currently appropriate would expose which
+ // links are in history to easy performance measurement. Therefore,
+ // here, we add nsChangeHint_RepaintFrame hints (the maximum for
+ // things that can depend on :visited) for the properties on which we
+ // call GetVisitedDependentColor.
+ const ComputedStyle* thisVis = GetStyleIfVisited();
+ const ComputedStyle* otherVis = aNewStyle.GetStyleIfVisited();
+ if (!thisVis != !otherVis) {
+ // One style has a style-if-visited and the other doesn't.
+ // Presume a difference.
+#define STYLE_STRUCT(name_, fields_) *aEqualStructs &= ~STYLE_STRUCT_BIT(name_);
+#include "nsCSSVisitedDependentPropList.h"
+#undef STYLE_STRUCT
+ hint |= nsChangeHint_RepaintFrame;
+ } else if (thisVis) {
+ // Both styles have a style-if-visited.
+ bool change = false;
+
+ // NB: Calling Peek on |this|, not |thisVis|, since callers may look
+ // at a struct on |this| without looking at the same struct on
+ // |thisVis| (including this function if we skip one of these checks
+ // due to change being true already or due to the old style not having a
+ // style-if-visited), but not the other way around.
+#define STYLE_FIELD(name_) thisVisStruct->name_ != otherVisStruct->name_
+#define STYLE_STRUCT(name_, fields_) \
+ { \
+ const nsStyle##name_* thisVisStruct = thisVis->Style##name_(); \
+ const nsStyle##name_* otherVisStruct = otherVis->Style##name_(); \
+ if (MOZ_FOR_EACH_SEPARATED(STYLE_FIELD, (||), (), fields_)) { \
+ *aEqualStructs &= ~STYLE_STRUCT_BIT(name_); \
+ change = true; \
+ } \
+ }
+#include "nsCSSVisitedDependentPropList.h"
+#undef STYLE_STRUCT
+#undef STYLE_FIELD
+#undef STYLE_STRUCT_BIT
+
+ if (change) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ }
+
+ if (hint & nsChangeHint_UpdateContainingBlock) {
+ if (!ContainingBlockMayHaveChanged(*this, aNewStyle)) {
+ // While some styles that cause the frame to be a containing block
+ // has changed, the overall result cannot have changed (no matter
+ // what the frame type is).
+ hint &= ~nsChangeHint_UpdateContainingBlock;
+ }
+ }
+
+ if (HasAuthorSpecifiedBorderOrBackground() !=
+ aNewStyle.HasAuthorSpecifiedBorderOrBackground()) {
+ const StyleAppearance appearance = StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None &&
+ nsLayoutUtils::AuthorSpecifiedBorderBackgroundDisablesTheming(
+ appearance)) {
+ // A background-specified change may cause padding to change, so we may
+ // need to reflow. We use the same hint here as we do for "appearance"
+ // changes.
+ hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
+ }
+ }
+
+ MOZ_ASSERT(NS_IsHintSubset(hint, nsChangeHint_AllHints),
+ "Added a new hint without bumping AllHints?");
+ return hint & ~nsChangeHint_NeutralChange;
+}
+
+#ifdef DEBUG
+void ComputedStyle::List(FILE* out, int32_t aIndent) {
+ nsAutoCString str;
+ // Indent
+ int32_t ix;
+ for (ix = aIndent; --ix >= 0;) {
+ str.AppendLiteral(" ");
+ }
+ str.Append(nsPrintfCString("%p(%d) parent=%p ", (void*)this, 0, nullptr));
+ if (mPseudoType != PseudoStyleType::NotPseudo) {
+ str.Append(nsPrintfCString("%s ", ToString(mPseudoType).c_str()));
+ }
+
+ fprintf_stderr(out, "%s{ServoComputedData}\n", str.get());
+}
+#endif
+
+template <typename Func>
+static nscolor GetVisitedDependentColorInternal(const ComputedStyle& aStyle,
+ Func aColorFunc) {
+ nscolor colors[2];
+ colors[0] = aColorFunc(aStyle);
+ if (const ComputedStyle* visitedStyle = aStyle.GetStyleIfVisited()) {
+ colors[1] = aColorFunc(*visitedStyle);
+ return ComputedStyle::CombineVisitedColors(colors,
+ aStyle.RelevantLinkVisited());
+ }
+ return colors[0];
+}
+
+static nscolor ExtractColor(const ComputedStyle& aStyle,
+ const StyleAbsoluteColor& aColor) {
+ return aColor.ToColor();
+}
+
+static nscolor ExtractColor(const ComputedStyle& aStyle,
+ const StyleColor& aColor) {
+ return aColor.CalcColor(aStyle);
+}
+
+// Currently caret-color, the only property in the list which is a ColorOrAuto,
+// always maps auto to currentcolor.
+static nscolor ExtractColor(const ComputedStyle& aStyle,
+ const StyleColorOrAuto& aColor) {
+ if (aColor.IsAuto()) {
+ return ExtractColor(aStyle, StyleColor::CurrentColor());
+ }
+ return ExtractColor(aStyle, aColor.AsColor());
+}
+
+static nscolor ExtractColor(const ComputedStyle& aStyle,
+ const StyleSVGPaint& aPaintServer) {
+ return aPaintServer.kind.IsColor()
+ ? ExtractColor(aStyle, aPaintServer.kind.AsColor())
+ : NS_RGBA(0, 0, 0, 0);
+}
+
+#define STYLE_FIELD(struct_, field_) aField == &struct_::field_ ||
+#define STYLE_STRUCT(name_, fields_) \
+ template <> \
+ nscolor ComputedStyle::GetVisitedDependentColor( \
+ decltype(nsStyle##name_::MOZ_ARG_1 fields_) nsStyle##name_::*aField) \
+ const { \
+ MOZ_ASSERT(MOZ_FOR_EACH(STYLE_FIELD, (nsStyle##name_, ), fields_) false, \
+ "Getting visited-dependent color for a field in nsStyle" #name_ \
+ " which is not listed in nsCSSVisitedDependentPropList.h"); \
+ return GetVisitedDependentColorInternal( \
+ *this, [aField](const ComputedStyle& aStyle) { \
+ return ExtractColor(aStyle, aStyle.Style##name_()->*aField); \
+ }); \
+ }
+#include "nsCSSVisitedDependentPropList.h"
+#undef STYLE_STRUCT
+#undef STYLE_FIELD
+
+struct ColorIndexSet {
+ uint8_t colorIndex, alphaIndex;
+};
+
+static const ColorIndexSet gVisitedIndices[2] = {{0, 0}, {1, 0}};
+
+/* static */
+nscolor ComputedStyle::CombineVisitedColors(nscolor* aColors,
+ bool aLinkIsVisited) {
+ if (NS_GET_A(aColors[1]) == 0) {
+ // If the style-if-visited is transparent, then just use the
+ // unvisited style rather than using the (meaningless) color
+ // components of the visited style along with a potentially
+ // non-transparent alpha value.
+ aLinkIsVisited = false;
+ }
+
+ // NOTE: We want this code to have as little timing dependence as
+ // possible on whether this->RelevantLinkVisited() is true.
+ const ColorIndexSet& set = gVisitedIndices[aLinkIsVisited ? 1 : 0];
+
+ nscolor colorColor = aColors[set.colorIndex];
+ nscolor alphaColor = aColors[set.alphaIndex];
+ return NS_RGBA(NS_GET_R(colorColor), NS_GET_G(colorColor),
+ NS_GET_B(colorColor), NS_GET_A(alphaColor));
+}
+
+#ifdef DEBUG
+/* static */ const char* ComputedStyle::StructName(StyleStructID aSID) {
+ switch (aSID) {
+# define STYLE_STRUCT(name_) \
+ case StyleStructID::name_: \
+ return #name_;
+# include "nsStyleStructList.h"
+# undef STYLE_STRUCT
+ default:
+ return "Unknown";
+ }
+}
+
+/* static */
+Maybe<StyleStructID> ComputedStyle::LookupStruct(const nsACString& aName) {
+# define STYLE_STRUCT(name_) \
+ if (aName.EqualsLiteral(#name_)) return Some(StyleStructID::name_);
+# include "nsStyleStructList.h"
+# undef STYLE_STRUCT
+ return Nothing();
+}
+#endif // DEBUG
+
+ComputedStyle* ComputedStyle::GetCachedLazyPseudoStyle(
+ PseudoStyleType aPseudo) const {
+ MOZ_ASSERT(PseudoStyle::IsPseudoElement(aPseudo));
+
+ if (nsCSSPseudoElements::PseudoElementSupportsUserActionState(aPseudo)) {
+ return nullptr;
+ }
+
+ return mCachedInheritingStyles.Lookup(aPseudo);
+}
+
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoComputedValuesMallocEnclosingSizeOf)
+
+void ComputedStyle::AddSizeOfIncludingThis(nsWindowSizes& aSizes,
+ size_t* aCVsSize) const {
+ // Note: |this| sits within a servo_arc::Arc, i.e. it is preceded by a
+ // refcount. So we need to measure it with a function that can handle an
+ // interior pointer. We use ServoComputedValuesMallocEnclosingSizeOf to
+ // clearly identify in DMD's output the memory measured here.
+ *aCVsSize += ServoComputedValuesMallocEnclosingSizeOf(this);
+ mSource.AddSizeOfExcludingThis(aSizes);
+ mCachedInheritingStyles.AddSizeOfIncludingThis(aSizes, aCVsSize);
+}
+
+#ifdef DEBUG
+bool ComputedStyle::EqualForCachedAnonymousContentStyle(
+ const ComputedStyle& aOther) const {
+ // One thing we can't add UA rules to prevent is different -x-lang
+ // values being inherited in. So we use this FFI function function rather
+ // than rely on CalcStyleDifference, which can't tell us which specific
+ // properties have changed.
+ return Servo_ComputedValues_EqualForCachedAnonymousContentStyle(this,
+ &aOther);
+}
+
+void ComputedStyle::DumpMatchedRules() const {
+ Servo_ComputedValues_DumpMatchedRules(this);
+}
+#endif
+
+} // namespace mozilla
diff --git a/layout/style/ComputedStyle.h b/layout/style/ComputedStyle.h
new file mode 100644
index 0000000000..5e3d0d157f
--- /dev/null
+++ b/layout/style/ComputedStyle.h
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 interface (to internal code) for retrieving computed style data */
+
+#ifndef _ComputedStyle_h_
+#define _ComputedStyle_h_
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CachedInheritingStyles.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PseudoStyleType.h"
+#include "mozilla/ServoComputedData.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "nsCSSPseudoElements.h"
+#include "nsColor.h"
+
+#include "nsStyleStructFwd.h"
+
+enum nsChangeHint : uint32_t;
+class nsWindowSizes;
+
+#define STYLE_STRUCT(name_) struct nsStyle##name_;
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+extern "C" {
+void Gecko_ComputedStyle_Destroy(mozilla::ComputedStyle*);
+}
+
+namespace mozilla {
+
+enum class StylePointerEvents : uint8_t;
+enum class StyleUserSelect : uint8_t;
+
+namespace dom {
+class Document;
+}
+
+/**
+ * A ComputedStyle represents the computed style data for an element.
+ *
+ * The computed style data are stored in a set of reference counted structs
+ * (see nsStyleStruct.h) that are stored directly on the ComputedStyle.
+ *
+ * Style structs are immutable once they have been produced, so when any change
+ * is made that needs a restyle, we create a new ComputedStyle.
+ *
+ * ComputedStyles are reference counted. References are generally held by:
+ *
+ * 1. nsIFrame::mComputedStyle, for every frame
+ * 2. Element::mServoData, for every element not inside a display:none subtree
+ * 3. nsComputedDOMStyle, when created for elements in display:none subtrees
+ * 4. media_queries::Device, which holds the initial value of every property
+ */
+
+class ComputedStyle {
+ using Flag = StyleComputedValueFlags;
+
+ const StyleComputedValueFlags& Flags() const { return mSource.flags; }
+
+ public:
+ ComputedStyle(PseudoStyleType aPseudoType,
+ ServoComputedDataForgotten aComputedValues);
+
+ // Returns the computed (not resolved) value of the given property.
+ void GetComputedPropertyValue(nsCSSPropertyID aId, nsACString& aOut) const {
+ Servo_GetComputedValue(this, aId, &aOut);
+ }
+
+ // Return the ComputedStyle whose style data should be used for the R,
+ // G, and B components of color, background-color, and border-*-color
+ // if RelevantLinkIsVisited().
+ //
+ // GetPseudo() and GetPseudoType() on this ComputedStyle return the
+ // same as on |this|, and its depth in the tree (number of GetParent()
+ // calls until null is returned) is the same as |this|, since its
+ // parent is either |this|'s parent or |this|'s parent's
+ // style-if-visited.
+ //
+ // Structs on this context should never be examined without also
+ // examining the corresponding struct on |this|. Doing so will likely
+ // both (1) lead to a privacy leak and (2) lead to dynamic change bugs
+ // related to the Peek code in ComputedStyle::CalcStyleDifference.
+ const ComputedStyle* GetStyleIfVisited() const {
+ return mSource.visited_style;
+ }
+
+ bool IsLazilyCascadedPseudoElement() const {
+ return IsPseudoElement() &&
+ !nsCSSPseudoElements::IsEagerlyCascadedInServo(GetPseudoType());
+ }
+
+ PseudoStyleType GetPseudoType() const { return mPseudoType; }
+
+ bool IsPseudoElement() const {
+ return PseudoStyle::IsPseudoElement(mPseudoType);
+ }
+
+ bool IsInheritingAnonBox() const {
+ return PseudoStyle::IsInheritingAnonBox(mPseudoType);
+ }
+
+ bool IsNonInheritingAnonBox() const {
+ return PseudoStyle::IsNonInheritingAnonBox(mPseudoType);
+ }
+
+ bool IsWrapperAnonBox() const {
+ return PseudoStyle::IsWrapperAnonBox(mPseudoType);
+ }
+
+ bool IsAnonBox() const { return PseudoStyle::IsAnonBox(mPseudoType); }
+
+ bool IsPseudoOrAnonBox() const {
+ return mPseudoType != PseudoStyleType::NotPseudo;
+ }
+
+ // Whether there are author-specified rules for border or background
+ // properties.
+ // Only returns something meaningful if the appearance property is not `none`.
+ bool HasAuthorSpecifiedBorderOrBackground() const {
+ return bool(Flags() & Flag::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND);
+ }
+
+ // Whether there are author-specific rules for text color.
+ bool HasAuthorSpecifiedTextColor() const {
+ return bool(Flags() & Flag::HAS_AUTHOR_SPECIFIED_TEXT_COLOR);
+ }
+
+ // Does this ComputedStyle or any of its ancestors have text
+ // decoration lines?
+ // Differs from nsStyleTextReset::HasTextDecorationLines, which tests
+ // only the data for a single context.
+ bool HasTextDecorationLines() const {
+ return bool(Flags() & Flag::HAS_TEXT_DECORATION_LINES);
+ }
+
+ // Whether any line break inside should be suppressed? If this returns
+ // true, the line should not be broken inside, which means inlines act
+ // as if nowrap is set, <br> is suppressed, and blocks are inlinized.
+ // This bit is propogated to all children of line partitipants. It is
+ // currently used by ruby to make its content frames unbreakable.
+ // NOTE: for nsTextFrame, use nsTextFrame::ShouldSuppressLineBreak()
+ // instead of this method.
+ bool ShouldSuppressLineBreak() const {
+ return bool(Flags() & Flag::SHOULD_SUPPRESS_LINEBREAK);
+ }
+
+ // Is this horizontal-in-vertical (tate-chu-yoko) text? This flag is
+ // only set on ComputedStyles whose pseudo is nsCSSAnonBoxes::mozText().
+ bool IsTextCombined() const { return bool(Flags() & Flag::IS_TEXT_COMBINED); }
+
+ // Whether there's any font metric dependency coming directly from our style.
+ bool DependsOnSelfFontMetrics() const {
+ return bool(Flags() & Flag::DEPENDS_ON_SELF_FONT_METRICS);
+ }
+
+ // Whether there's any font metric dependency coming directly from our parent
+ // style.
+ bool DependsOnInheritedFontMetrics() const {
+ return bool(Flags() & Flag::DEPENDS_ON_INHERITED_FONT_METRICS);
+ }
+
+ // Does this ComputedStyle represent the style for a pseudo-element or
+ // inherit data from such a ComputedStyle? Whether this returns true
+ // is equivalent to whether it or any of its ancestors returns
+ // non-null for IsPseudoElement().
+ bool HasPseudoElementData() const {
+ return bool(Flags() & Flag::IS_IN_PSEUDO_ELEMENT_SUBTREE);
+ }
+
+ bool SelfOrAncestorHasContainStyle() const {
+ return bool(Flags() & Flag::SELF_OR_ANCESTOR_HAS_CONTAIN_STYLE);
+ }
+
+ // Is the only link whose visitedness is allowed to influence the
+ // style of the node this ComputedStyle is for (which is that element
+ // or its nearest ancestor that is a link) visited?
+ bool RelevantLinkVisited() const {
+ return bool(Flags() & Flag::IS_RELEVANT_LINK_VISITED);
+ }
+
+ // Whether this style is for the root element of the document.
+ bool IsRootElementStyle() const {
+ return bool(Flags() & Flag::IS_ROOT_ELEMENT_STYLE);
+ }
+
+ bool IsInOpacityZeroSubtree() const {
+ return bool(Flags() & Flag::IS_IN_OPACITY_ZERO_SUBTREE);
+ }
+
+ ComputedStyle* GetCachedInheritingAnonBoxStyle(
+ PseudoStyleType aPseudoType) const {
+ MOZ_ASSERT(PseudoStyle::IsInheritingAnonBox(aPseudoType));
+ return mCachedInheritingStyles.Lookup(aPseudoType);
+ }
+
+ void SetCachedInheritedAnonBoxStyle(ComputedStyle* aStyle) {
+ mCachedInheritingStyles.Insert(aStyle);
+ }
+
+ ComputedStyle* GetCachedLazyPseudoStyle(PseudoStyleType aPseudo) const;
+
+ void SetCachedLazyPseudoStyle(ComputedStyle* aStyle) {
+ MOZ_ASSERT(aStyle->IsPseudoElement());
+ MOZ_ASSERT(!GetCachedLazyPseudoStyle(aStyle->GetPseudoType()));
+ MOZ_ASSERT(aStyle->IsLazilyCascadedPseudoElement());
+
+ // Since we're caching lazy pseudo styles on the ComputedValues of the
+ // originating element, we can assume that we either have the same
+ // originating element, or that they were at least similar enough to share
+ // the same ComputedValues, which means that they would match the same
+ // pseudo rules. This allows us to avoid matching selectors and checking
+ // the rule node before deciding to share.
+ //
+ // The one place this optimization breaks is with pseudo-elements that
+ // support state (like :hover). So we just avoid sharing in those cases.
+ if (nsCSSPseudoElements::PseudoElementSupportsUserActionState(
+ aStyle->GetPseudoType())) {
+ return;
+ }
+
+ mCachedInheritingStyles.Insert(aStyle);
+ }
+
+#define STYLE_STRUCT(name_) \
+ inline const nsStyle##name_* Style##name_() const MOZ_NONNULL_RETURN { \
+ return mSource.Style##name_(); \
+ }
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ inline mozilla::StylePointerEvents PointerEvents() const;
+ inline mozilla::StyleUserSelect UserSelect() const;
+
+ /**
+ * Returns whether the element is a containing block for its absolutely
+ * positioned descendants.
+ * aContextFrame is the frame for which this is the style (or an old style).
+ */
+ inline bool IsAbsPosContainingBlock(const nsIFrame*) const;
+
+ /**
+ * Returns true when the element is a containing block for its fixed-pos
+ * descendants.
+ * aContextFrame is the frame for which this is the style (or an old style).
+ */
+ inline bool IsFixedPosContainingBlock(const nsIFrame*) const;
+
+ /**
+ * Tests for only the sub-parts of IsFixedPosContainingBlock that apply to:
+ * - nearly all frames, except those that are in SVG text subtrees.
+ * - frames that support CSS contain:layout and contain:paint and are not
+ * in SVG text subtrees.
+ * - frames that support CSS transforms and are not in SVG text subtrees.
+ *
+ * This should be used only when the caller has the style but not the
+ * frame (i.e., when calculating style changes).
+ */
+ inline bool IsFixedPosContainingBlockForNonSVGTextFrames() const;
+
+ /**
+ * Compute the style changes needed during restyling when this style
+ * context is being replaced by aNewContext. (This is nonsymmetric since
+ * we optimize by skipping comparison for styles that have never been
+ * requested.)
+ *
+ * This method returns a change hint (see nsChangeHint.h). All change
+ * hints apply to the frame and its later continuations or ib-split
+ * siblings. Most (all of those except the "NotHandledForDescendants"
+ * hints) also apply to all descendants.
+ *
+ * aEqualStructs must not be null. Into it will be stored a bitfield
+ * representing which structs were compared to be non-equal.
+ *
+ * CSS Variables are not compared here. Instead, the caller is responsible for
+ * that when needed (basically only for elements).
+ */
+ nsChangeHint CalcStyleDifference(const ComputedStyle& aNewContext,
+ uint32_t* aEqualStructs) const;
+
+#ifdef DEBUG
+ bool EqualForCachedAnonymousContentStyle(const ComputedStyle&) const;
+#endif
+
+#ifdef DEBUG
+ void DumpMatchedRules() const;
+#endif
+
+ /**
+ * Get a color that depends on link-visitedness using this and
+ * this->GetStyleIfVisited().
+ *
+ * @param aField A pointer to a member variable in a style struct.
+ * The member variable and its style struct must have
+ * been listed in nsCSSVisitedDependentPropList.h.
+ */
+ template <typename T, typename S>
+ nscolor GetVisitedDependentColor(T S::*aField) const;
+
+ /**
+ * aColors should be a two element array of nscolor in which the first
+ * color is the unvisited color and the second is the visited color.
+ *
+ * Combine the R, G, and B components of whichever of aColors should
+ * be used based on aLinkIsVisited with the A component of aColors[0].
+ */
+ static nscolor CombineVisitedColors(nscolor* aColors, bool aLinkIsVisited);
+
+ /**
+ * Start image loads for this style.
+ *
+ * The Document is used to get a hand on the image loader. The old style is a
+ * hack for bug 1439285.
+ */
+ inline void StartImageLoads(dom::Document&,
+ const ComputedStyle* aOldStyle = nullptr);
+
+#ifdef DEBUG
+ void List(FILE* out, int32_t aIndent);
+ static const char* StructName(StyleStructID aSID);
+ static Maybe<StyleStructID> LookupStruct(const nsACString& aName);
+#endif
+
+ // The |aCVsSize| outparam on this function is where the actual CVs size
+ // value is added. It's done that way because the callers know which value
+ // the size should be added to.
+ void AddSizeOfIncludingThis(nsWindowSizes& aSizes, size_t* aCVsSize) const;
+
+ StyleWritingMode WritingMode() const { return {mSource.WritingMode().mBits}; }
+
+ const StyleZoom& EffectiveZoom() const { return mSource.effective_zoom; }
+
+ protected:
+ // Needs to be friend so that it can call the destructor without making it
+ // public.
+ friend void ::Gecko_ComputedStyle_Destroy(ComputedStyle*);
+
+ ~ComputedStyle() = default;
+
+ ServoComputedData mSource;
+
+ // A cache of anonymous box and lazy pseudo styles inheriting from this style.
+ CachedInheritingStyles mCachedInheritingStyles;
+
+ const PseudoStyleType mPseudoType;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/ComputedStyleInlines.h b/layout/style/ComputedStyleInlines.h
new file mode 100644
index 0000000000..f9e87c808f
--- /dev/null
+++ b/layout/style/ComputedStyleInlines.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Inlined methods for ComputedStyle. Will just redirect to
+ * GeckoComputedStyle methods when compiled without stylo, but will do
+ * virtual dispatch (by checking which kind of container it is)
+ * in stylo mode.
+ */
+
+#ifndef ComputedStyleInlines_h
+#define ComputedStyleInlines_h
+
+#include "mozilla/ComputedStyle.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Unused.h"
+#include "nsStyleStructInlines.h"
+
+namespace mozilla {
+
+namespace detail {
+
+template <typename T, typename Enable = void>
+struct HasTriggerImageLoads : public std::false_type {};
+
+template <typename T>
+struct HasTriggerImageLoads<T, decltype(std::declval<T&>().TriggerImageLoads(
+ std::declval<dom::Document&>(), nullptr))>
+ : public std::true_type {};
+
+template <typename T, const T* (ComputedStyle::*Method)() const>
+void TriggerImageLoads(dom::Document& aDocument, const ComputedStyle* aOldStyle,
+ ComputedStyle* aStyle) {
+ if constexpr (HasTriggerImageLoads<T>::value) {
+ auto* old = aOldStyle ? (aOldStyle->*Method)() : nullptr;
+ auto* current = const_cast<T*>((aStyle->*Method)());
+ current->TriggerImageLoads(aDocument, old);
+ } else {
+ Unused << aOldStyle;
+ Unused << aStyle;
+ }
+}
+
+} // namespace detail
+
+void ComputedStyle::StartImageLoads(dom::Document& aDocument,
+ const ComputedStyle* aOldStyle) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+#define STYLE_STRUCT(name_) \
+ detail::TriggerImageLoads<nsStyle##name_, &ComputedStyle::Style##name_>( \
+ aDocument, aOldStyle, this);
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+}
+
+StylePointerEvents ComputedStyle::PointerEvents() const {
+ if (IsRootElementStyle()) {
+ // The root frame is not allowed to have pointer-events: none, or else no
+ // frames could be hit test against and scrolling the viewport would not
+ // work.
+ return StylePointerEvents::Auto;
+ }
+ const auto& ui = *StyleUI();
+ if (ui.IsInert()) {
+ return StylePointerEvents::None;
+ }
+ return ui.ComputedPointerEvents();
+}
+
+StyleUserSelect ComputedStyle::UserSelect() const {
+ return StyleUI()->IsInert() ? StyleUserSelect::None
+ : StyleUIReset()->ComputedUserSelect();
+}
+
+bool ComputedStyle::IsFixedPosContainingBlockForNonSVGTextFrames() const {
+ // NOTE: Any CSS properties that influence the output of this function
+ // should return FIXPOS_CB_NON_SVG for will-change.
+ if (IsRootElementStyle()) {
+ return false;
+ }
+
+ const auto& disp = *StyleDisplay();
+ if (disp.mWillChange.bits & mozilla::StyleWillChangeBits::FIXPOS_CB_NON_SVG) {
+ return true;
+ }
+
+ const auto& effects = *StyleEffects();
+ return effects.HasFilters() || effects.HasBackdropFilters();
+}
+
+bool ComputedStyle::IsFixedPosContainingBlock(
+ const nsIFrame* aContextFrame) const {
+ // NOTE: Any CSS properties that influence the output of this function
+ // should also handle will-change appropriately.
+ if (aContextFrame->IsInSVGTextSubtree()) {
+ return false;
+ }
+ if (IsFixedPosContainingBlockForNonSVGTextFrames()) {
+ return true;
+ }
+ const auto& disp = *StyleDisplay();
+ if (disp.IsFixedPosContainingBlockForContainLayoutAndPaintSupportingFrames() &&
+ aContextFrame->SupportsContainLayoutAndPaint()) {
+ return true;
+ }
+ if (disp.IsFixedPosContainingBlockForTransformSupportingFrames() &&
+ aContextFrame->SupportsCSSTransforms()) {
+ return true;
+ }
+ return false;
+}
+
+bool ComputedStyle::IsAbsPosContainingBlock(
+ const nsIFrame* aContextFrame) const {
+ if (IsFixedPosContainingBlock(aContextFrame)) {
+ return true;
+ }
+ // NOTE: Any CSS properties that influence the output of this function
+ // should also handle will-change appropriately.
+ return StyleDisplay()->IsPositionedStyle() &&
+ !aContextFrame->IsInSVGTextSubtree();
+}
+
+} // namespace mozilla
+
+#endif // ComputedStyleInlines_h
diff --git a/layout/style/CounterStyleManager.cpp b/layout/style/CounterStyleManager.cpp
new file mode 100644
index 0000000000..356948de27
--- /dev/null
+++ b/layout/style/CounterStyleManager.cpp
@@ -0,0 +1,1884 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "CounterStyleManager.h"
+
+#include <type_traits>
+
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/Types.h"
+#include "mozilla/WritingModes.h"
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsUnicodeProperties.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleSet.h"
+
+namespace mozilla {
+
+using AdditiveSymbol = StyleAdditiveSymbol;
+
+struct NegativeType {
+ nsString before, after;
+};
+
+struct PadType {
+ int32_t width;
+ nsString symbol;
+};
+
+// This limitation will be applied to some systems, and pad descriptor.
+// Any initial representation generated by symbolic or additive which is
+// longer than this limitation will be dropped. If any pad is longer
+// than this, the whole counter text will be dropped as well.
+// The spec requires user agents to support at least 60 Unicode code-
+// points for counter text. However, this constant only limits the
+// length in 16-bit units. So it has to be at least 120, since code-
+// points outside the BMP will need 2 16-bit units.
+#define LENGTH_LIMIT 150
+
+static bool GetCyclicCounterText(CounterValue aOrdinal, nsAString& aResult,
+ Span<const nsString> aSymbols) {
+ MOZ_ASSERT(aSymbols.Length() >= 1, "No symbol available for cyclic counter.");
+ auto n = CounterValue(aSymbols.Length());
+ CounterValue index = (aOrdinal - 1) % n;
+ aResult = aSymbols[index >= 0 ? index : index + n];
+ return true;
+}
+
+static bool GetFixedCounterText(CounterValue aOrdinal, nsAString& aResult,
+ CounterValue aStart,
+ Span<const nsString> aSymbols) {
+ CounterValue index = aOrdinal - aStart;
+ if (index >= 0 && index < CounterValue(aSymbols.Length())) {
+ aResult = aSymbols[index];
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool GetSymbolicCounterText(CounterValue aOrdinal, nsAString& aResult,
+ Span<const nsString> aSymbols) {
+ MOZ_ASSERT(aSymbols.Length() >= 1,
+ "No symbol available for symbolic counter.");
+ MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
+ if (aOrdinal == 0) {
+ return false;
+ }
+
+ aResult.Truncate();
+ auto n = aSymbols.Length();
+ const nsString& symbol = aSymbols[(aOrdinal - 1) % n];
+ size_t len = (aOrdinal + n - 1) / n;
+ auto symbolLength = symbol.Length();
+ if (symbolLength > 0) {
+ if (len > LENGTH_LIMIT || symbolLength > LENGTH_LIMIT ||
+ len * symbolLength > LENGTH_LIMIT) {
+ return false;
+ }
+ for (size_t i = 0; i < len; ++i) {
+ aResult.Append(symbol);
+ }
+ }
+ return true;
+}
+
+static bool GetAlphabeticCounterText(CounterValue aOrdinal, nsAString& aResult,
+ Span<const nsString> aSymbols) {
+ MOZ_ASSERT(aSymbols.Length() >= 2, "Too few symbols for alphabetic counter.");
+ MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
+ if (aOrdinal == 0) {
+ return false;
+ }
+
+ auto n = aSymbols.Length();
+ // The precise length of this array should be
+ // ceil(log((double) aOrdinal / n * (n - 1) + 1) / log(n)).
+ // The max length is slightly smaller than which defined below.
+ AutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
+ while (aOrdinal > 0) {
+ --aOrdinal;
+ indexes.AppendElement(aOrdinal % n);
+ aOrdinal /= n;
+ }
+
+ aResult.Truncate();
+ for (auto i = indexes.Length(); i > 0; --i) {
+ aResult.Append(aSymbols[indexes[i - 1]]);
+ }
+ return true;
+}
+
+static bool GetNumericCounterText(CounterValue aOrdinal, nsAString& aResult,
+ Span<const nsString> aSymbols) {
+ MOZ_ASSERT(aSymbols.Length() >= 2, "Too few symbols for numeric counter.");
+ MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
+
+ if (aOrdinal == 0) {
+ aResult = aSymbols[0];
+ return true;
+ }
+
+ auto n = aSymbols.Length();
+ AutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
+ while (aOrdinal > 0) {
+ indexes.AppendElement(aOrdinal % n);
+ aOrdinal /= n;
+ }
+
+ aResult.Truncate();
+ for (auto i = indexes.Length(); i > 0; --i) {
+ aResult.Append(aSymbols[indexes[i - 1]]);
+ }
+ return true;
+}
+
+static bool GetAdditiveCounterText(CounterValue aOrdinal, nsAString& aResult,
+ Span<const AdditiveSymbol> aSymbols) {
+ MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
+
+ if (aOrdinal == 0) {
+ const AdditiveSymbol& last = aSymbols[aSymbols.Length() - 1];
+ if (last.weight == 0) {
+ aResult = last.symbol;
+ return true;
+ }
+ return false;
+ }
+
+ aResult.Truncate();
+ size_t length = 0;
+ for (size_t i = 0, iEnd = aSymbols.Length(); i < iEnd; ++i) {
+ const AdditiveSymbol& symbol = aSymbols[i];
+ if (symbol.weight == 0) {
+ break;
+ }
+ CounterValue times = aOrdinal / symbol.weight;
+ if (times > 0) {
+ auto symbolLength = symbol.symbol.Length();
+ if (symbolLength > 0) {
+ length += times * symbolLength;
+ if (times > LENGTH_LIMIT || symbolLength > LENGTH_LIMIT ||
+ length > LENGTH_LIMIT) {
+ return false;
+ }
+ for (CounterValue j = 0; j < times; ++j) {
+ aResult.Append(symbol.symbol);
+ }
+ }
+ aOrdinal -= times * symbol.weight;
+ }
+ }
+ return aOrdinal == 0;
+}
+
+static bool DecimalToText(CounterValue aOrdinal, nsAString& aResult) {
+ aResult.AppendInt(aOrdinal);
+ return true;
+}
+
+// We know cjk-ideographic need 31 characters to display 99,999,999,999,999,999
+// georgian needs 6 at most
+// armenian needs 12 at most
+// hebrew may need more...
+
+#define NUM_BUF_SIZE 34
+
+enum CJKIdeographicLang { CHINESE, KOREAN, JAPANESE };
+struct CJKIdeographicData {
+ char16_t digit[10];
+ char16_t unit[3];
+ char16_t unit10K[2];
+ uint8_t lang;
+ bool informal;
+};
+static const CJKIdeographicData gDataJapaneseInformal = {
+ {0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
+ 0x4e5d}, // digit
+ {0x5341, 0x767e, 0x5343}, // unit
+ {0x4e07, 0x5104}, // unit10K
+ JAPANESE, // lang
+ true // informal
+};
+static const CJKIdeographicData gDataJapaneseFormal = {
+ {0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db, 0x4f0d, 0x516d, 0x4e03, 0x516b,
+ 0x4e5d}, // digit
+ {0x62fe, 0x767e, 0x9621}, // unit
+ {0x842c, 0x5104}, // unit10K
+ JAPANESE, // lang
+ false // informal
+};
+static const CJKIdeographicData gDataKoreanHangulFormal = {
+ {0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac, 0xc624, 0xc721, 0xce60, 0xd314,
+ 0xad6c}, // digit
+ {0xc2ed, 0xbc31, 0xcc9c}, // unit
+ {0xb9cc, 0xc5b5}, // unit10K
+ KOREAN, // lang
+ false // informal
+};
+static const CJKIdeographicData gDataKoreanHanjaInformal = {
+ {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
+ 0x4e5d}, // digit
+ {0x5341, 0x767e, 0x5343}, // unit
+ {0x842c, 0x5104}, // unit10K
+ KOREAN, // lang
+ true // informal
+};
+static const CJKIdeographicData gDataKoreanHanjaFormal = {
+ {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
+ 0x4e5d}, // digit
+ {0x62fe, 0x767e, 0x4edf}, // unit
+ {0x842c, 0x5104}, // unit10K
+ KOREAN, // lang
+ false // informal
+};
+static const CJKIdeographicData gDataSimpChineseInformal = {
+ {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
+ 0x4e5d}, // digit
+ {0x5341, 0x767e, 0x5343}, // unit
+ {0x4e07, 0x4ebf}, // unit10K
+ CHINESE, // lang
+ true // informal
+};
+static const CJKIdeographicData gDataSimpChineseFormal = {
+ {0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086, 0x4f0d, 0x9646, 0x67d2, 0x634c,
+ 0x7396}, // digit
+ {0x62fe, 0x4f70, 0x4edf}, // unit
+ {0x4e07, 0x4ebf}, // unit10K
+ CHINESE, // lang
+ false // informal
+};
+static const CJKIdeographicData gDataTradChineseInformal = {
+ {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
+ 0x4e5d}, // digit
+ {0x5341, 0x767e, 0x5343}, // unit
+ {0x842c, 0x5104}, // unit10K
+ CHINESE, // lang
+ true // informal
+};
+static const CJKIdeographicData gDataTradChineseFormal = {
+ {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x8086, 0x4f0d, 0x9678, 0x67d2, 0x634c,
+ 0x7396}, // digit
+ {0x62fe, 0x4f70, 0x4edf}, // unit
+ {0x842c, 0x5104}, // unit10K
+ CHINESE, // lang
+ false // informal
+};
+
+static bool CJKIdeographicToText(CounterValue aOrdinal, nsAString& aResult,
+ const CJKIdeographicData& data) {
+ NS_ASSERTION(aOrdinal >= 0, "Only accept non-negative ordinal");
+ char16_t buf[NUM_BUF_SIZE];
+ int32_t idx = NUM_BUF_SIZE;
+ int32_t pos = 0;
+ bool needZero = (aOrdinal == 0);
+ int32_t unitidx = 0, unit10Kidx = 0;
+ do {
+ unitidx = pos % 4;
+ if (unitidx == 0) {
+ unit10Kidx = pos / 4;
+ }
+ auto cur = static_cast<std::make_unsigned_t<CounterValue>>(aOrdinal) % 10;
+ if (cur == 0) {
+ if (needZero) {
+ needZero = false;
+ buf[--idx] = data.digit[0];
+ }
+ } else {
+ if (data.lang == CHINESE) {
+ needZero = true;
+ }
+ if (unit10Kidx != 0) {
+ if (data.lang == KOREAN && idx != NUM_BUF_SIZE) {
+ buf[--idx] = ' ';
+ }
+ buf[--idx] = data.unit10K[unit10Kidx - 1];
+ }
+ if (unitidx != 0) {
+ buf[--idx] = data.unit[unitidx - 1];
+ }
+ if (cur != 1) {
+ buf[--idx] = data.digit[cur];
+ } else {
+ bool needOne = true;
+ if (data.informal) {
+ switch (data.lang) {
+ case CHINESE:
+ if (unitidx == 1 &&
+ (aOrdinal == 1 || (pos > 4 && aOrdinal % 1000 == 1))) {
+ needOne = false;
+ }
+ break;
+ case JAPANESE:
+ if (unitidx > 0 &&
+ (unitidx != 3 || (pos == 3 && aOrdinal == 1))) {
+ needOne = false;
+ }
+ break;
+ case KOREAN:
+ if (unitidx > 0 || (pos == 4 && (aOrdinal % 1000) == 1)) {
+ needOne = false;
+ }
+ break;
+ }
+ }
+ if (needOne) {
+ buf[--idx] = data.digit[1];
+ }
+ }
+ unit10Kidx = 0;
+ }
+ aOrdinal /= 10;
+ pos++;
+ } while (aOrdinal > 0);
+ aResult.Assign(buf + idx, NUM_BUF_SIZE - idx);
+ return true;
+}
+
+#define HEBREW_GERESH 0x05F3
+static const char16_t gHebrewDigit[22] = {
+ // 1 2 3 4 5 6 7 8 9
+ 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8,
+ // 10 20 30 40 50 60 70 80 90
+ 0x05D9, 0x05DB, 0x05DC, 0x05DE, 0x05E0, 0x05E1, 0x05E2, 0x05E4, 0x05E6,
+ // 100 200 300 400
+ 0x05E7, 0x05E8, 0x05E9, 0x05EA};
+
+static bool HebrewToText(CounterValue aOrdinal, nsAString& aResult) {
+ if (aOrdinal < 1 || aOrdinal > 999999) {
+ return false;
+ }
+
+ bool outputSep = false;
+ nsAutoString allText, thousandsGroup;
+ do {
+ thousandsGroup.Truncate();
+ int32_t n3 = aOrdinal % 1000;
+ // Process digit for 100 - 900
+ for (int32_t n1 = 400; n1 > 0;) {
+ if (n3 >= n1) {
+ n3 -= n1;
+ thousandsGroup.Append(gHebrewDigit[(n1 / 100) - 1 + 18]);
+ } else {
+ n1 -= 100;
+ } // if
+ } // for
+
+ // Process digit for 10 - 90
+ int32_t n2;
+ if (n3 >= 10) {
+ // Special process for 15 and 16
+ if ((15 == n3) || (16 == n3)) {
+ // Special rule for religious reason...
+ // 15 is represented by 9 and 6, not 10 and 5
+ // 16 is represented by 9 and 7, not 10 and 6
+ n2 = 9;
+ thousandsGroup.Append(gHebrewDigit[n2 - 1]);
+ } else {
+ n2 = n3 - (n3 % 10);
+ thousandsGroup.Append(gHebrewDigit[(n2 / 10) - 1 + 9]);
+ } // if
+ n3 -= n2;
+ } // if
+
+ // Process digit for 1 - 9
+ if (n3 > 0) thousandsGroup.Append(gHebrewDigit[n3 - 1]);
+ if (outputSep) thousandsGroup.Append((char16_t)HEBREW_GERESH);
+ if (allText.IsEmpty())
+ allText = thousandsGroup;
+ else
+ allText = thousandsGroup + allText;
+ aOrdinal /= 1000;
+ outputSep = true;
+ } while (aOrdinal >= 1);
+
+ aResult = allText;
+ return true;
+}
+
+// Convert ordinal to Ethiopic numeric representation.
+// The detail is available at http://www.ethiopic.org/Numerals/
+// The algorithm used here is based on the pseudo-code put up there by
+// Daniel Yacob <yacob@geez.org>.
+// Another reference is Unicode 3.0 standard section 11.1.
+#define ETHIOPIC_ONE 0x1369
+#define ETHIOPIC_TEN 0x1372
+#define ETHIOPIC_HUNDRED 0x137B
+#define ETHIOPIC_TEN_THOUSAND 0x137C
+
+static bool EthiopicToText(CounterValue aOrdinal, nsAString& aResult) {
+ if (aOrdinal < 1) {
+ return false;
+ }
+
+ nsAutoString asciiNumberString; // decimal string representation of ordinal
+ DecimalToText(aOrdinal, asciiNumberString);
+ uint8_t asciiStringLength = asciiNumberString.Length();
+
+ // If number length is odd, add a leading "0"
+ // the leading "0" preconditions the string to always have the
+ // leading tens place populated, this avoids a check within the loop.
+ // If we didn't add the leading "0", decrement asciiStringLength so
+ // it will be equivalent to a zero-based index in both cases.
+ if (asciiStringLength & 1) {
+ asciiNumberString.InsertLiteral(u"0", 0);
+ } else {
+ asciiStringLength--;
+ }
+
+ aResult.Truncate();
+ // Iterate from the highest digits to lowest
+ // indexFromLeft indexes digits (0 = most significant)
+ // groupIndexFromRight indexes pairs of digits (0 = least significant)
+ for (uint8_t indexFromLeft = 0, groupIndexFromRight = asciiStringLength >> 1;
+ indexFromLeft <= asciiStringLength;
+ indexFromLeft += 2, groupIndexFromRight--) {
+ uint8_t tensValue = asciiNumberString.CharAt(indexFromLeft) & 0x0F;
+ uint8_t unitsValue = asciiNumberString.CharAt(indexFromLeft + 1) & 0x0F;
+ uint8_t groupValue = tensValue * 10 + unitsValue;
+
+ bool oddGroup = (groupIndexFromRight & 1);
+
+ // we want to clear ETHIOPIC_ONE when it is superfluous
+ if (aOrdinal > 1 && groupValue == 1 && // one without a leading ten
+ (oddGroup ||
+ indexFromLeft == 0)) { // preceding (100) or leading the sequence
+ unitsValue = 0;
+ }
+
+ // put it all together...
+ if (tensValue) {
+ // map onto Ethiopic "tens":
+ aResult.Append((char16_t)(tensValue + ETHIOPIC_TEN - 1));
+ }
+ if (unitsValue) {
+ // map onto Ethiopic "units":
+ aResult.Append((char16_t)(unitsValue + ETHIOPIC_ONE - 1));
+ }
+ // Add a separator for all even groups except the last,
+ // and for odd groups with non-zero value.
+ if (oddGroup) {
+ if (groupValue) {
+ aResult.Append((char16_t)ETHIOPIC_HUNDRED);
+ }
+ } else {
+ if (groupIndexFromRight) {
+ aResult.Append((char16_t)ETHIOPIC_TEN_THOUSAND);
+ }
+ }
+ }
+ return true;
+}
+
+static SpeakAs GetDefaultSpeakAsForSystem(StyleCounterSystem aSystem) {
+ MOZ_ASSERT(aSystem != StyleCounterSystem::Extends,
+ "Extends system does not have static default speak-as");
+ switch (aSystem) {
+ case StyleCounterSystem::Alphabetic:
+ return SpeakAs::Spellout;
+ case StyleCounterSystem::Cyclic:
+ return SpeakAs::Bullets;
+ default:
+ return SpeakAs::Numbers;
+ }
+}
+
+static bool SystemUsesNegativeSign(StyleCounterSystem aSystem) {
+ MOZ_ASSERT(aSystem != StyleCounterSystem::Extends,
+ "Cannot check this for extending style");
+ switch (aSystem) {
+ case StyleCounterSystem::Symbolic:
+ case StyleCounterSystem::Alphabetic:
+ case StyleCounterSystem::Numeric:
+ case StyleCounterSystem::Additive:
+ return true;
+ default:
+ return false;
+ }
+}
+
+class BuiltinCounterStyle : public CounterStyle {
+ public:
+ constexpr BuiltinCounterStyle(ListStyle aStyle, nsStaticAtom* aName)
+ : CounterStyle(aStyle), mName(aName) {}
+
+ nsStaticAtom* GetStyleName() const { return mName; }
+
+ virtual void GetPrefix(nsAString& aResult) override;
+ virtual void GetSuffix(nsAString& aResult) override;
+ virtual void GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult,
+ bool& aIsBullet) override;
+ virtual bool IsBullet() override;
+
+ virtual void GetNegative(NegativeType& aResult) override;
+ virtual bool IsOrdinalInRange(CounterValue aOrdinal) override;
+ virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal) override;
+ virtual void GetPad(PadType& aResult) override;
+ virtual CounterStyle* GetFallback() override;
+ virtual SpeakAs GetSpeakAs() override;
+ virtual bool UseNegativeSign() override;
+
+ virtual bool GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult, bool& aIsRTL) override;
+
+ protected:
+ constexpr BuiltinCounterStyle(const BuiltinCounterStyle& aOther)
+ : CounterStyle(aOther.mStyle), mName(aOther.mName) {}
+
+ private:
+ nsStaticAtom* mName;
+};
+
+/* virtual */
+void BuiltinCounterStyle::GetPrefix(nsAString& aResult) { aResult.Truncate(); }
+
+/* virtual */
+void BuiltinCounterStyle::GetSuffix(nsAString& aResult) {
+ switch (mStyle) {
+ case ListStyle::None:
+ aResult.Truncate();
+ break;
+
+ case ListStyle::Disc:
+ case ListStyle::Circle:
+ case ListStyle::Square:
+ case ListStyle::DisclosureClosed:
+ case ListStyle::DisclosureOpen:
+ case ListStyle::EthiopicNumeric:
+ aResult = ' ';
+ break;
+
+ case ListStyle::TradChineseInformal:
+ case ListStyle::TradChineseFormal:
+ case ListStyle::SimpChineseInformal:
+ case ListStyle::SimpChineseFormal:
+ case ListStyle::JapaneseInformal:
+ case ListStyle::JapaneseFormal:
+ aResult = 0x3001;
+ break;
+
+ case ListStyle::KoreanHangulFormal:
+ case ListStyle::KoreanHanjaInformal:
+ case ListStyle::KoreanHanjaFormal:
+ aResult.AssignLiteral(u", ");
+ break;
+
+ default:
+ aResult.AssignLiteral(u". ");
+ break;
+ }
+}
+
+static const char16_t kDiscCharacter = 0x2022;
+static const char16_t kCircleCharacter = 0x25e6;
+static const char16_t kSquareCharacter = 0x25aa;
+static const char16_t kRightPointingCharacter = 0x25b8;
+static const char16_t kLeftPointingCharacter = 0x25c2;
+static const char16_t kDownPointingCharacter = 0x25be;
+
+/* virtual */
+void BuiltinCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult,
+ bool& aIsBullet) {
+ switch (mStyle) {
+ case ListStyle::None:
+ case ListStyle::Disc:
+ case ListStyle::Circle:
+ case ListStyle::Square:
+ case ListStyle::DisclosureClosed:
+ case ListStyle::DisclosureOpen: {
+ // Same as the initial representation
+ bool isRTL;
+ GetInitialCounterText(aOrdinal, aWritingMode, aResult, isRTL);
+ aIsBullet = true;
+ break;
+ }
+ default:
+ CounterStyle::GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
+ aIsBullet);
+ break;
+ }
+}
+
+/* virtual */
+bool BuiltinCounterStyle::IsBullet() {
+ switch (mStyle) {
+ case ListStyle::Disc:
+ case ListStyle::Circle:
+ case ListStyle::Square:
+ case ListStyle::DisclosureClosed:
+ case ListStyle::DisclosureOpen:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const char16_t gJapaneseNegative[] = {0x30de, 0x30a4, 0x30ca, 0x30b9,
+ 0x0000};
+static const char16_t gKoreanNegative[] = {0xb9c8, 0xc774, 0xb108,
+ 0xc2a4, 0x0020, 0x0000};
+static const char16_t gSimpChineseNegative[] = {0x8d1f, 0x0000};
+static const char16_t gTradChineseNegative[] = {0x8ca0, 0x0000};
+
+/* virtual */
+void BuiltinCounterStyle::GetNegative(NegativeType& aResult) {
+ switch (mStyle) {
+ case ListStyle::JapaneseFormal:
+ case ListStyle::JapaneseInformal:
+ aResult.before = gJapaneseNegative;
+ break;
+
+ case ListStyle::KoreanHangulFormal:
+ case ListStyle::KoreanHanjaInformal:
+ case ListStyle::KoreanHanjaFormal:
+ aResult.before = gKoreanNegative;
+ break;
+
+ case ListStyle::SimpChineseFormal:
+ case ListStyle::SimpChineseInformal:
+ aResult.before = gSimpChineseNegative;
+ break;
+
+ case ListStyle::TradChineseFormal:
+ case ListStyle::TradChineseInformal:
+ aResult.before = gTradChineseNegative;
+ break;
+
+ default:
+ aResult.before.AssignLiteral(u"-");
+ }
+ aResult.after.Truncate();
+}
+
+/* virtual */
+bool BuiltinCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
+ switch (mStyle) {
+ default:
+ // cyclic
+ case ListStyle::None:
+ case ListStyle::Disc:
+ case ListStyle::Circle:
+ case ListStyle::Square:
+ case ListStyle::DisclosureClosed:
+ case ListStyle::DisclosureOpen:
+ // use DecimalToText
+ case ListStyle::Decimal:
+ // use CJKIdeographicToText
+ case ListStyle::JapaneseFormal:
+ case ListStyle::JapaneseInformal:
+ case ListStyle::KoreanHanjaFormal:
+ case ListStyle::KoreanHanjaInformal:
+ case ListStyle::KoreanHangulFormal:
+ case ListStyle::TradChineseFormal:
+ case ListStyle::TradChineseInformal:
+ case ListStyle::SimpChineseFormal:
+ case ListStyle::SimpChineseInformal:
+ return true;
+
+ // use EthiopicToText
+ case ListStyle::EthiopicNumeric:
+ return aOrdinal >= 1;
+
+ // use HebrewToText
+ case ListStyle::Hebrew:
+ return aOrdinal >= 1 && aOrdinal <= 999999;
+ }
+}
+
+/* virtual */
+bool BuiltinCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
+ switch (mStyle) {
+ // cyclic:
+ case ListStyle::None:
+ case ListStyle::Disc:
+ case ListStyle::Circle:
+ case ListStyle::Square:
+ case ListStyle::DisclosureClosed:
+ case ListStyle::DisclosureOpen:
+ // numeric:
+ case ListStyle::Decimal:
+ return true;
+
+ // additive:
+ case ListStyle::Hebrew:
+ return aOrdinal >= 0;
+
+ // complex predefined:
+ case ListStyle::JapaneseFormal:
+ case ListStyle::JapaneseInformal:
+ case ListStyle::KoreanHanjaFormal:
+ case ListStyle::KoreanHanjaInformal:
+ case ListStyle::KoreanHangulFormal:
+ case ListStyle::TradChineseFormal:
+ case ListStyle::TradChineseInformal:
+ case ListStyle::SimpChineseFormal:
+ case ListStyle::SimpChineseInformal:
+ case ListStyle::EthiopicNumeric:
+ return IsOrdinalInRange(aOrdinal);
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown counter style");
+ return false;
+ }
+}
+
+/* virtual */
+void BuiltinCounterStyle::GetPad(PadType& aResult) {
+ aResult.width = 0;
+ aResult.symbol.Truncate();
+}
+
+/* virtual */
+CounterStyle* BuiltinCounterStyle::GetFallback() {
+ // Fallback of dependent builtin counter styles are handled in class
+ // DependentBuiltinCounterStyle.
+ return CounterStyleManager::GetDecimalStyle();
+}
+
+/* virtual */
+SpeakAs BuiltinCounterStyle::GetSpeakAs() {
+ switch (mStyle) {
+ case ListStyle::None:
+ case ListStyle::Disc:
+ case ListStyle::Circle:
+ case ListStyle::Square:
+ case ListStyle::DisclosureClosed:
+ case ListStyle::DisclosureOpen:
+ return SpeakAs::Bullets;
+ default:
+ return SpeakAs::Numbers;
+ }
+}
+
+/* virtual */
+bool BuiltinCounterStyle::UseNegativeSign() {
+ switch (mStyle) {
+ case ListStyle::None:
+ case ListStyle::Disc:
+ case ListStyle::Circle:
+ case ListStyle::Square:
+ case ListStyle::DisclosureClosed:
+ case ListStyle::DisclosureOpen:
+ return false;
+ default:
+ return true;
+ }
+}
+
+/* virtual */
+bool BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult,
+ bool& aIsRTL) {
+ aIsRTL = false;
+ switch (mStyle) {
+ // used by counters & extends counter-style code only
+ // XXX We really need to do this the same way we do list bullets.
+ case ListStyle::None:
+ aResult.Truncate();
+ return true;
+ case ListStyle::Disc:
+ aResult.Assign(kDiscCharacter);
+ return true;
+ case ListStyle::Circle:
+ aResult.Assign(kCircleCharacter);
+ return true;
+ case ListStyle::Square:
+ aResult.Assign(kSquareCharacter);
+ return true;
+ case ListStyle::DisclosureClosed:
+ if (aWritingMode.IsVertical()) {
+ aResult.Assign(kDownPointingCharacter);
+ } else if (aWritingMode.IsBidiLTR()) {
+ aResult.Assign(kRightPointingCharacter);
+ } else {
+ aResult.Assign(kLeftPointingCharacter);
+ }
+ return true;
+ case ListStyle::DisclosureOpen:
+ if (!aWritingMode.IsVertical()) {
+ aResult.Assign(kDownPointingCharacter);
+ } else if (aWritingMode.IsVerticalLR()) {
+ aResult.Assign(kRightPointingCharacter);
+ } else {
+ aResult.Assign(kLeftPointingCharacter);
+ }
+ return true;
+
+ case ListStyle::Decimal:
+ return DecimalToText(aOrdinal, aResult);
+
+ case ListStyle::TradChineseInformal:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseInformal);
+ case ListStyle::TradChineseFormal:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseFormal);
+ case ListStyle::SimpChineseInformal:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseInformal);
+ case ListStyle::SimpChineseFormal:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseFormal);
+ case ListStyle::JapaneseInformal:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseInformal);
+ case ListStyle::JapaneseFormal:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseFormal);
+ case ListStyle::KoreanHangulFormal:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHangulFormal);
+ case ListStyle::KoreanHanjaInformal:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaInformal);
+ case ListStyle::KoreanHanjaFormal:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaFormal);
+
+ case ListStyle::Hebrew:
+ aIsRTL = true;
+ return HebrewToText(aOrdinal, aResult);
+
+ case ListStyle::EthiopicNumeric:
+ return EthiopicToText(aOrdinal, aResult);
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown builtin counter style");
+ return false;
+ }
+}
+
+static constexpr BuiltinCounterStyle gBuiltinStyleTable[] = {
+#define BUILTIN_COUNTER_STYLE(value_, atom_) \
+ {ListStyle::value_, nsGkAtoms::atom_},
+#include "BuiltinCounterStyleList.h"
+#undef BUILTIN_COUNTER_STYLE
+};
+
+#define BUILTIN_COUNTER_STYLE(value_, atom_) \
+ static_assert( \
+ gBuiltinStyleTable[static_cast<size_t>(ListStyle::value_)].GetStyle() == \
+ ListStyle::value_, \
+ "Builtin counter style " #atom_ " has unmatched index and value.");
+#include "BuiltinCounterStyleList.h"
+#undef BUILTIN_COUNTER_STYLE
+
+class DependentBuiltinCounterStyle final : public BuiltinCounterStyle {
+ public:
+ DependentBuiltinCounterStyle(ListStyle aStyle, CounterStyleManager* aManager)
+ : BuiltinCounterStyle(gBuiltinStyleTable[static_cast<size_t>(aStyle)]),
+ mManager(aManager) {
+ NS_ASSERTION(IsDependentStyle(), "Not a dependent builtin style");
+ MOZ_ASSERT(!IsCustomStyle(), "Not a builtin style");
+ }
+
+ virtual CounterStyle* GetFallback() override;
+
+ void* operator new(size_t sz, nsPresContext* aPresContext) {
+ return aPresContext->PresShell()->AllocateByObjectID(
+ eArenaObjectID_DependentBuiltinCounterStyle, sz);
+ }
+
+ void Destroy() {
+ PresShell* presShell = mManager->PresContext()->PresShell();
+ this->~DependentBuiltinCounterStyle();
+ presShell->FreeByObjectID(eArenaObjectID_DependentBuiltinCounterStyle,
+ this);
+ }
+
+ private:
+ ~DependentBuiltinCounterStyle() = default;
+
+ CounterStyleManager* mManager;
+};
+
+/* virtual */
+CounterStyle* DependentBuiltinCounterStyle::GetFallback() {
+ switch (GetStyle()) {
+ case ListStyle::JapaneseInformal:
+ case ListStyle::JapaneseFormal:
+ case ListStyle::KoreanHangulFormal:
+ case ListStyle::KoreanHanjaInformal:
+ case ListStyle::KoreanHanjaFormal:
+ case ListStyle::SimpChineseInformal:
+ case ListStyle::SimpChineseFormal:
+ case ListStyle::TradChineseInformal:
+ case ListStyle::TradChineseFormal:
+ // These styles all have a larger range than cjk-decimal, so the
+ // only case fallback is accessed is that they are extended.
+ // Since extending styles will cache the data themselves, we need
+ // not cache it here.
+ return mManager->ResolveCounterStyle(nsGkAtoms::cjk_decimal);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Not a valid dependent builtin style");
+ return BuiltinCounterStyle::GetFallback();
+ }
+}
+
+class CustomCounterStyle final : public CounterStyle {
+ public:
+ CustomCounterStyle(CounterStyleManager* aManager,
+ const StyleLockedCounterStyleRule* aRule)
+ : CounterStyle(ListStyle::Custom),
+ mManager(aManager),
+ mRule(aRule),
+ mRuleGeneration(Servo_CounterStyleRule_GetGeneration(aRule)),
+ mSystem(Servo_CounterStyleRule_GetSystem(aRule)),
+ mFlags(0),
+ mFallback(nullptr),
+ mSpeakAsCounter(nullptr),
+ mExtends(nullptr),
+ mExtendsRoot(nullptr) {}
+
+ // This method will clear all cached data in the style and update the
+ // generation number of the rule. It should be called when the rule of
+ // this style is changed.
+ void ResetCachedData();
+
+ // This method will reset all cached data which may depend on other
+ // counter style. It will reset all pointers to other counter styles.
+ // For counter style extends other, in addition, all fields will be
+ // reset to uninitialized state. This method should be called when any
+ // other counter style is added, removed, or changed.
+ void ResetDependentData();
+
+ const StyleLockedCounterStyleRule* GetRule() const { return mRule; }
+ uint32_t GetRuleGeneration() const { return mRuleGeneration; }
+
+ virtual void GetPrefix(nsAString& aResult) override;
+ virtual void GetSuffix(nsAString& aResult) override;
+ virtual void GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult,
+ bool& aIsBullet) override;
+ virtual bool IsBullet() override;
+
+ virtual void GetNegative(NegativeType& aResult) override;
+ virtual bool IsOrdinalInRange(CounterValue aOrdinal) override;
+ virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal) override;
+ virtual void GetPad(PadType& aResult) override;
+ virtual CounterStyle* GetFallback() override;
+ virtual SpeakAs GetSpeakAs() override;
+ virtual bool UseNegativeSign() override;
+
+ virtual void CallFallbackStyle(CounterValue aOrdinal,
+ WritingMode aWritingMode, nsAString& aResult,
+ bool& aIsRTL) override;
+ virtual bool GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult, bool& aIsRTL) override;
+
+ bool IsExtendsSystem() { return mSystem == StyleCounterSystem::Extends; }
+
+ void* operator new(size_t sz, nsPresContext* aPresContext) {
+ return aPresContext->PresShell()->AllocateByObjectID(
+ eArenaObjectID_CustomCounterStyle, sz);
+ }
+
+ void Destroy() {
+ PresShell* presShell = mManager->PresContext()->PresShell();
+ this->~CustomCounterStyle();
+ presShell->FreeByObjectID(eArenaObjectID_CustomCounterStyle, this);
+ }
+
+ private:
+ ~CustomCounterStyle() = default;
+
+ Span<const nsString> GetSymbols();
+ Span<const AdditiveSymbol> GetAdditiveSymbols();
+
+ // The speak-as values of counter styles may form a loop, and the
+ // loops may have complex interaction with the loop formed by
+ // extending. To solve this problem, the computation of speak-as is
+ // divided into two phases:
+ // 1. figure out the raw value, by ComputeRawSpeakAs, and
+ // 2. eliminate loop, by ComputeSpeakAs.
+ // See comments before the definitions of these methods for details.
+ SpeakAs GetSpeakAsAutoValue();
+ void ComputeRawSpeakAs(SpeakAs& aSpeakAs, CounterStyle*& aSpeakAsCounter);
+ CounterStyle* ComputeSpeakAs();
+
+ CounterStyle* ComputeExtends();
+ CounterStyle* GetExtends();
+ CounterStyle* GetExtendsRoot();
+
+ // CounterStyleManager should always overlive any CounterStyle as it
+ // is owned by nsPresContext, and will be released after all nodes and
+ // frames are released.
+ CounterStyleManager* mManager;
+
+ RefPtr<const StyleLockedCounterStyleRule> mRule;
+ uint32_t mRuleGeneration;
+
+ StyleCounterSystem mSystem;
+ // GetSpeakAs will ensure that private member mSpeakAs is initialized before
+ // used
+ MOZ_INIT_OUTSIDE_CTOR SpeakAs mSpeakAs;
+
+ enum {
+ // loop detection
+ FLAG_EXTENDS_VISITED = 1 << 0,
+ FLAG_EXTENDS_LOOP = 1 << 1,
+ FLAG_SPEAKAS_VISITED = 1 << 2,
+ FLAG_SPEAKAS_LOOP = 1 << 3,
+ // field status
+ FLAG_NEGATIVE_INITED = 1 << 4,
+ FLAG_PREFIX_INITED = 1 << 5,
+ FLAG_SUFFIX_INITED = 1 << 6,
+ FLAG_PAD_INITED = 1 << 7,
+ FLAG_SPEAKAS_INITED = 1 << 8,
+ };
+ uint16_t mFlags;
+
+ // Fields below will be initialized when necessary.
+ StyleOwnedSlice<nsString> mSymbols;
+ StyleOwnedSlice<AdditiveSymbol> mAdditiveSymbols;
+ NegativeType mNegative;
+ nsString mPrefix, mSuffix;
+ PadType mPad;
+
+ // CounterStyleManager will guarantee that none of the pointers below
+ // refers to a freed CounterStyle. There are two possible cases where
+ // the manager will release its reference to a CounterStyle: 1. the
+ // manager itself is released, 2. a rule is invalidated. In the first
+ // case, all counter style are removed from the manager, and should
+ // also have been dereferenced from other objects. All styles will be
+ // released all together. In the second case, CounterStyleManager::
+ // NotifyRuleChanged will guarantee that all pointers will be reset
+ // before any CounterStyle is released.
+
+ CounterStyle* mFallback;
+ // This field refers to the last counter in a speak-as chain.
+ // That counter must not speak as another counter.
+ CounterStyle* mSpeakAsCounter;
+
+ CounterStyle* mExtends;
+ // This field refers to the last counter in the extends chain. The
+ // counter must be either a builtin style or a style whose system is
+ // not 'extends'.
+ CounterStyle* mExtendsRoot;
+};
+
+void CustomCounterStyle::ResetCachedData() {
+ mSymbols.Clear();
+ mAdditiveSymbols.Clear();
+ mFlags &= ~(FLAG_NEGATIVE_INITED | FLAG_PREFIX_INITED | FLAG_SUFFIX_INITED |
+ FLAG_PAD_INITED | FLAG_SPEAKAS_INITED);
+ mFallback = nullptr;
+ mSpeakAsCounter = nullptr;
+ mExtends = nullptr;
+ mExtendsRoot = nullptr;
+ mRuleGeneration = Servo_CounterStyleRule_GetGeneration(mRule);
+}
+
+void CustomCounterStyle::ResetDependentData() {
+ mFlags &= ~FLAG_SPEAKAS_INITED;
+ mSpeakAsCounter = nullptr;
+ mFallback = nullptr;
+ mExtends = nullptr;
+ mExtendsRoot = nullptr;
+ if (IsExtendsSystem()) {
+ mFlags &= ~(FLAG_NEGATIVE_INITED | FLAG_PREFIX_INITED | FLAG_SUFFIX_INITED |
+ FLAG_PAD_INITED);
+ }
+}
+
+/* virtual */
+void CustomCounterStyle::GetPrefix(nsAString& aResult) {
+ if (!(mFlags & FLAG_PREFIX_INITED)) {
+ mFlags |= FLAG_PREFIX_INITED;
+
+ if (!Servo_CounterStyleRule_GetPrefix(mRule, &mPrefix)) {
+ if (IsExtendsSystem()) {
+ GetExtends()->GetPrefix(mPrefix);
+ } else {
+ mPrefix.Truncate();
+ }
+ }
+ }
+ aResult = mPrefix;
+}
+
+/* virtual */
+void CustomCounterStyle::GetSuffix(nsAString& aResult) {
+ if (!(mFlags & FLAG_SUFFIX_INITED)) {
+ mFlags |= FLAG_SUFFIX_INITED;
+
+ if (!Servo_CounterStyleRule_GetSuffix(mRule, &mSuffix)) {
+ if (IsExtendsSystem()) {
+ GetExtends()->GetSuffix(mSuffix);
+ } else {
+ mSuffix.AssignLiteral(u". ");
+ }
+ }
+ }
+ aResult = mSuffix;
+}
+
+/* virtual */
+void CustomCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult,
+ bool& aIsBullet) {
+ if (GetSpeakAs() != SpeakAs::Other) {
+ CounterStyle::GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
+ aIsBullet);
+ } else {
+ MOZ_ASSERT(mSpeakAsCounter,
+ "mSpeakAsCounter should have been initialized.");
+ mSpeakAsCounter->GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
+ aIsBullet);
+ }
+}
+
+/* virtual */
+bool CustomCounterStyle::IsBullet() {
+ switch (mSystem) {
+ case StyleCounterSystem::Cyclic:
+ // Only use ::-moz-list-bullet for cyclic system
+ return true;
+ case StyleCounterSystem::Extends:
+ return GetExtendsRoot()->IsBullet();
+ default:
+ return false;
+ }
+}
+
+/* virtual */
+void CustomCounterStyle::GetNegative(NegativeType& aResult) {
+ if (!(mFlags & FLAG_NEGATIVE_INITED)) {
+ mFlags |= FLAG_NEGATIVE_INITED;
+ if (!Servo_CounterStyleRule_GetNegative(mRule, &mNegative.before,
+ &mNegative.after)) {
+ if (IsExtendsSystem()) {
+ GetExtends()->GetNegative(mNegative);
+ } else {
+ mNegative.before.AssignLiteral(u"-");
+ mNegative.after.Truncate();
+ }
+ }
+ }
+ aResult = mNegative;
+}
+
+/* virtual */
+bool CustomCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
+ auto inRange = Servo_CounterStyleRule_IsInRange(mRule, aOrdinal);
+ switch (inRange) {
+ case StyleIsOrdinalInRange::InRange:
+ return true;
+ case StyleIsOrdinalInRange::NotInRange:
+ return false;
+ case StyleIsOrdinalInRange::NoOrdinalSpecified:
+ if (IsExtendsSystem()) {
+ return GetExtends()->IsOrdinalInRange(aOrdinal);
+ }
+ break;
+ case StyleIsOrdinalInRange::Auto:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unkown result from IsInRange?");
+ }
+ return IsOrdinalInAutoRange(aOrdinal);
+}
+
+/* virtual */
+bool CustomCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
+ switch (mSystem) {
+ case StyleCounterSystem::Cyclic:
+ case StyleCounterSystem::Numeric:
+ case StyleCounterSystem::Fixed:
+ return true;
+ case StyleCounterSystem::Alphabetic:
+ case StyleCounterSystem::Symbolic:
+ return aOrdinal >= 1;
+ case StyleCounterSystem::Additive:
+ return aOrdinal >= 0;
+ case StyleCounterSystem::Extends:
+ return GetExtendsRoot()->IsOrdinalInAutoRange(aOrdinal);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid system for computing auto value.");
+ return false;
+ }
+}
+
+/* virtual */
+void CustomCounterStyle::GetPad(PadType& aResult) {
+ if (!(mFlags & FLAG_PAD_INITED)) {
+ mFlags |= FLAG_PAD_INITED;
+ if (!Servo_CounterStyleRule_GetPad(mRule, &mPad.width, &mPad.symbol)) {
+ if (IsExtendsSystem()) {
+ GetExtends()->GetPad(mPad);
+ } else {
+ mPad.width = 0;
+ mPad.symbol.Truncate();
+ }
+ }
+ }
+ aResult = mPad;
+}
+
+/* virtual */
+CounterStyle* CustomCounterStyle::GetFallback() {
+ if (!mFallback) {
+ mFallback = CounterStyleManager::GetDecimalStyle();
+ if (nsAtom* fallback = Servo_CounterStyleRule_GetFallback(mRule)) {
+ mFallback = mManager->ResolveCounterStyle(fallback);
+ } else if (IsExtendsSystem()) {
+ mFallback = GetExtends()->GetFallback();
+ }
+ }
+ return mFallback;
+}
+
+/* virtual */
+SpeakAs CustomCounterStyle::GetSpeakAs() {
+ if (!(mFlags & FLAG_SPEAKAS_INITED)) {
+ ComputeSpeakAs();
+ }
+ return mSpeakAs;
+}
+
+/* virtual */
+bool CustomCounterStyle::UseNegativeSign() {
+ if (mSystem == StyleCounterSystem::Extends) {
+ return GetExtendsRoot()->UseNegativeSign();
+ }
+ return SystemUsesNegativeSign(mSystem);
+}
+
+/* virtual */
+void CustomCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult, bool& aIsRTL) {
+ CounterStyle* fallback = GetFallback();
+ // If it recursively falls back to this counter style again,
+ // it will then fallback to decimal to break the loop.
+ mFallback = CounterStyleManager::GetDecimalStyle();
+ fallback->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
+ mFallback = fallback;
+}
+
+/* virtual */
+bool CustomCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult,
+ bool& aIsRTL) {
+ switch (mSystem) {
+ case StyleCounterSystem::Cyclic:
+ return GetCyclicCounterText(aOrdinal, aResult, GetSymbols());
+ case StyleCounterSystem::Fixed: {
+ int32_t start = Servo_CounterStyleRule_GetFixedFirstValue(mRule);
+ return GetFixedCounterText(aOrdinal, aResult, start, GetSymbols());
+ }
+ case StyleCounterSystem::Symbolic:
+ return GetSymbolicCounterText(aOrdinal, aResult, GetSymbols());
+ case StyleCounterSystem::Alphabetic:
+ return GetAlphabeticCounterText(aOrdinal, aResult, GetSymbols());
+ case StyleCounterSystem::Numeric:
+ return GetNumericCounterText(aOrdinal, aResult, GetSymbols());
+ case StyleCounterSystem::Additive:
+ return GetAdditiveCounterText(aOrdinal, aResult, GetAdditiveSymbols());
+ case StyleCounterSystem::Extends:
+ return GetExtendsRoot()->GetInitialCounterText(aOrdinal, aWritingMode,
+ aResult, aIsRTL);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid system.");
+ return false;
+ }
+}
+
+Span<const nsString> CustomCounterStyle::GetSymbols() {
+ if (mSymbols.IsEmpty()) {
+ Servo_CounterStyleRule_GetSymbols(mRule, &mSymbols);
+ }
+ return mSymbols.AsSpan();
+}
+
+Span<const AdditiveSymbol> CustomCounterStyle::GetAdditiveSymbols() {
+ if (mAdditiveSymbols.IsEmpty()) {
+ Servo_CounterStyleRule_GetAdditiveSymbols(mRule, &mAdditiveSymbols);
+ }
+ return mAdditiveSymbols.AsSpan();
+}
+
+// This method is used to provide the computed value for 'auto'.
+SpeakAs CustomCounterStyle::GetSpeakAsAutoValue() {
+ auto system = mSystem;
+ if (IsExtendsSystem()) {
+ CounterStyle* root = GetExtendsRoot();
+ if (!root->IsCustomStyle()) {
+ // It is safe to call GetSpeakAs on non-custom style.
+ return root->GetSpeakAs();
+ }
+ system = static_cast<CustomCounterStyle*>(root)->mSystem;
+ }
+ return GetDefaultSpeakAsForSystem(system);
+}
+
+// This method corresponds to the first stage of computation of the
+// value of speak-as. It will extract the value from the rule and
+// possibly recursively call itself on the extended style to figure
+// out the raw value. To keep things clear, this method is designed to
+// have no side effects (but functions it calls may still affect other
+// fields in the style.)
+void CustomCounterStyle::ComputeRawSpeakAs(SpeakAs& aSpeakAs,
+ CounterStyle*& aSpeakAsCounter) {
+ NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_INITED),
+ "ComputeRawSpeakAs is called with speak-as inited.");
+
+ auto speakAs = StyleCounterSpeakAs::None();
+ Servo_CounterStyleRule_GetSpeakAs(mRule, &speakAs);
+ switch (speakAs.tag) {
+ case StyleCounterSpeakAs::Tag::Auto:
+ aSpeakAs = GetSpeakAsAutoValue();
+ break;
+ case StyleCounterSpeakAs::Tag::Bullets:
+ aSpeakAs = SpeakAs::Bullets;
+ break;
+ case StyleCounterSpeakAs::Tag::Numbers:
+ aSpeakAs = SpeakAs::Numbers;
+ break;
+ case StyleCounterSpeakAs::Tag::Words:
+ aSpeakAs = SpeakAs::Words;
+ break;
+ case StyleCounterSpeakAs::Tag::Ident:
+ aSpeakAs = SpeakAs::Other;
+ aSpeakAsCounter = mManager->ResolveCounterStyle(speakAs.AsIdent());
+ break;
+ case StyleCounterSpeakAs::Tag::None: {
+ if (!IsExtendsSystem()) {
+ aSpeakAs = GetSpeakAsAutoValue();
+ } else {
+ CounterStyle* extended = GetExtends();
+ if (!extended->IsCustomStyle()) {
+ // It is safe to call GetSpeakAs on non-custom style.
+ aSpeakAs = extended->GetSpeakAs();
+ } else {
+ CustomCounterStyle* custom =
+ static_cast<CustomCounterStyle*>(extended);
+ if (!(custom->mFlags & FLAG_SPEAKAS_INITED)) {
+ custom->ComputeRawSpeakAs(aSpeakAs, aSpeakAsCounter);
+ } else {
+ aSpeakAs = custom->mSpeakAs;
+ aSpeakAsCounter = custom->mSpeakAsCounter;
+ }
+ }
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
+ }
+}
+
+// This method corresponds to the second stage of getting speak-as
+// related values. It will recursively figure out the final value of
+// mSpeakAs and mSpeakAsCounter. This method returns nullptr if the
+// caller is in a loop, and the root counter style in the chain
+// otherwise. It use the same loop detection algorithm as
+// CustomCounterStyle::ComputeExtends, see comments before that
+// method for more details.
+CounterStyle* CustomCounterStyle::ComputeSpeakAs() {
+ if (mFlags & FLAG_SPEAKAS_INITED) {
+ if (mSpeakAs == SpeakAs::Other) {
+ return mSpeakAsCounter;
+ }
+ return this;
+ }
+
+ if (mFlags & FLAG_SPEAKAS_VISITED) {
+ // loop detected
+ mFlags |= FLAG_SPEAKAS_LOOP;
+ return nullptr;
+ }
+
+ CounterStyle* speakAsCounter;
+ ComputeRawSpeakAs(mSpeakAs, speakAsCounter);
+
+ bool inLoop = false;
+ if (mSpeakAs != SpeakAs::Other) {
+ mSpeakAsCounter = nullptr;
+ } else if (!speakAsCounter->IsCustomStyle()) {
+ mSpeakAsCounter = speakAsCounter;
+ } else {
+ mFlags |= FLAG_SPEAKAS_VISITED;
+ CounterStyle* target =
+ static_cast<CustomCounterStyle*>(speakAsCounter)->ComputeSpeakAs();
+ mFlags &= ~FLAG_SPEAKAS_VISITED;
+
+ if (target) {
+ NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_LOOP),
+ "Invalid state for speak-as loop detecting");
+ mSpeakAsCounter = target;
+ } else {
+ mSpeakAs = GetSpeakAsAutoValue();
+ mSpeakAsCounter = nullptr;
+ if (mFlags & FLAG_SPEAKAS_LOOP) {
+ mFlags &= ~FLAG_SPEAKAS_LOOP;
+ } else {
+ inLoop = true;
+ }
+ }
+ }
+
+ mFlags |= FLAG_SPEAKAS_INITED;
+ if (inLoop) {
+ return nullptr;
+ }
+ return mSpeakAsCounter ? mSpeakAsCounter : this;
+}
+
+// This method will recursively figure out mExtends in the whole chain.
+// It will return nullptr if the caller is in a loop, and return this
+// otherwise. To detect the loop, this method marks the style VISITED
+// before the recursive call. When a VISITED style is reached again, the
+// loop is detected, and flag LOOP will be marked on the first style in
+// loop. mExtends of all counter styles in loop will be set to decimal
+// according to the spec.
+CounterStyle* CustomCounterStyle::ComputeExtends() {
+ if (!IsExtendsSystem() || mExtends) {
+ return this;
+ }
+ if (mFlags & FLAG_EXTENDS_VISITED) {
+ // loop detected
+ mFlags |= FLAG_EXTENDS_LOOP;
+ return nullptr;
+ }
+
+ nsAtom* extended = Servo_CounterStyleRule_GetExtended(mRule);
+ CounterStyle* nextCounter = mManager->ResolveCounterStyle(extended);
+ CounterStyle* target = nextCounter;
+ if (nextCounter->IsCustomStyle()) {
+ mFlags |= FLAG_EXTENDS_VISITED;
+ target = static_cast<CustomCounterStyle*>(nextCounter)->ComputeExtends();
+ mFlags &= ~FLAG_EXTENDS_VISITED;
+ }
+
+ if (target) {
+ NS_ASSERTION(!(mFlags & FLAG_EXTENDS_LOOP),
+ "Invalid state for extends loop detecting");
+ mExtends = nextCounter;
+ return this;
+ } else {
+ mExtends = CounterStyleManager::GetDecimalStyle();
+ if (mFlags & FLAG_EXTENDS_LOOP) {
+ mFlags &= ~FLAG_EXTENDS_LOOP;
+ return this;
+ } else {
+ return nullptr;
+ }
+ }
+}
+
+CounterStyle* CustomCounterStyle::GetExtends() {
+ if (!mExtends) {
+ // Any extends loop will be eliminated in the method below.
+ ComputeExtends();
+ }
+ return mExtends;
+}
+
+CounterStyle* CustomCounterStyle::GetExtendsRoot() {
+ if (!mExtendsRoot) {
+ CounterStyle* extended = GetExtends();
+ mExtendsRoot = extended;
+ if (extended->IsCustomStyle()) {
+ CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(extended);
+ if (custom->IsExtendsSystem()) {
+ // This will make mExtendsRoot in the whole extends chain be
+ // set recursively, which could save work when part of a chain
+ // is shared by multiple counter styles.
+ mExtendsRoot = custom->GetExtendsRoot();
+ }
+ }
+ }
+ return mExtendsRoot;
+}
+
+AnonymousCounterStyle::AnonymousCounterStyle(const nsAString& aContent)
+ : CounterStyle(ListStyle::Custom),
+ mSingleString(true),
+ mSymbolsType(StyleSymbolsType::Cyclic) {
+ mSymbols.SetCapacity(1);
+ mSymbols.AppendElement(aContent);
+}
+
+AnonymousCounterStyle::AnonymousCounterStyle(StyleSymbolsType aType,
+ nsTArray<nsString> aSymbols)
+ : CounterStyle(ListStyle::Custom),
+ mSingleString(false),
+ mSymbolsType(aType),
+ mSymbols(std::move(aSymbols)) {}
+
+/* virtual */
+void AnonymousCounterStyle::GetPrefix(nsAString& aResult) {
+ aResult.Truncate();
+}
+
+/* virtual */
+void AnonymousCounterStyle::GetSuffix(nsAString& aResult) {
+ if (IsSingleString()) {
+ aResult.Truncate();
+ } else {
+ aResult = ' ';
+ }
+}
+
+/* virtual */
+bool AnonymousCounterStyle::IsBullet() {
+ // Only use ::-moz-list-bullet for cyclic system
+ return mSymbolsType == StyleSymbolsType::Cyclic;
+}
+
+/* virtual */
+void AnonymousCounterStyle::GetNegative(NegativeType& aResult) {
+ aResult.before.AssignLiteral(u"-");
+ aResult.after.Truncate();
+}
+
+/* virtual */
+bool AnonymousCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
+ switch (mSymbolsType) {
+ case StyleSymbolsType::Cyclic:
+ case StyleSymbolsType::Numeric:
+ case StyleSymbolsType::Fixed:
+ return true;
+ case StyleSymbolsType::Alphabetic:
+ case StyleSymbolsType::Symbolic:
+ return aOrdinal >= 1;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid system.");
+ return false;
+ }
+}
+
+/* virtual */
+bool AnonymousCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
+ return AnonymousCounterStyle::IsOrdinalInRange(aOrdinal);
+}
+
+/* virtual */
+void AnonymousCounterStyle::GetPad(PadType& aResult) {
+ aResult.width = 0;
+ aResult.symbol.Truncate();
+}
+
+/* virtual */
+CounterStyle* AnonymousCounterStyle::GetFallback() {
+ return CounterStyleManager::GetDecimalStyle();
+}
+
+StyleCounterSystem AnonymousCounterStyle::GetSystem() const {
+ switch (mSymbolsType) {
+ case StyleSymbolsType::Cyclic:
+ return StyleCounterSystem::Cyclic;
+ case StyleSymbolsType::Numeric:
+ return StyleCounterSystem::Numeric;
+ case StyleSymbolsType::Fixed:
+ return StyleCounterSystem::Fixed;
+ case StyleSymbolsType::Alphabetic:
+ return StyleCounterSystem::Alphabetic;
+ case StyleSymbolsType::Symbolic:
+ return StyleCounterSystem::Symbolic;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown symbols() type");
+ return StyleCounterSystem::Cyclic;
+}
+
+/* virtual */
+SpeakAs AnonymousCounterStyle::GetSpeakAs() {
+ return GetDefaultSpeakAsForSystem(GetSystem());
+}
+
+/* virtual */
+bool AnonymousCounterStyle::UseNegativeSign() {
+ return SystemUsesNegativeSign(GetSystem());
+}
+
+/* virtual */
+bool AnonymousCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult,
+ bool& aIsRTL) {
+ switch (mSymbolsType) {
+ case StyleSymbolsType::Cyclic:
+ return GetCyclicCounterText(aOrdinal, aResult, mSymbols);
+ case StyleSymbolsType::Numeric:
+ return GetNumericCounterText(aOrdinal, aResult, mSymbols);
+ case StyleSymbolsType::Fixed:
+ return GetFixedCounterText(aOrdinal, aResult, 1, mSymbols);
+ case StyleSymbolsType::Alphabetic:
+ return GetAlphabeticCounterText(aOrdinal, aResult, mSymbols);
+ case StyleSymbolsType::Symbolic:
+ return GetSymbolicCounterText(aOrdinal, aResult, mSymbols);
+ }
+ MOZ_ASSERT_UNREACHABLE("Invalid system.");
+ return false;
+}
+
+bool CounterStyle::IsDependentStyle() const {
+ switch (mStyle) {
+ // CustomCounterStyle
+ case ListStyle::Custom:
+ // DependentBuiltinCounterStyle
+ case ListStyle::JapaneseInformal:
+ case ListStyle::JapaneseFormal:
+ case ListStyle::KoreanHangulFormal:
+ case ListStyle::KoreanHanjaInformal:
+ case ListStyle::KoreanHanjaFormal:
+ case ListStyle::SimpChineseInformal:
+ case ListStyle::SimpChineseFormal:
+ case ListStyle::TradChineseInformal:
+ case ListStyle::TradChineseFormal:
+ return true;
+
+ // BuiltinCounterStyle
+ default:
+ return false;
+ }
+}
+
+void CounterStyle::GetCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode, nsAString& aResult,
+ bool& aIsRTL) {
+ bool success = IsOrdinalInRange(aOrdinal);
+ aIsRTL = false;
+
+ if (success) {
+ // generate initial representation
+ bool useNegativeSign = UseNegativeSign();
+ nsAutoString initialText;
+ CounterValue ordinal;
+ if (!useNegativeSign) {
+ ordinal = aOrdinal;
+ } else {
+ CheckedInt<CounterValue> absolute(Abs(aOrdinal));
+ ordinal = absolute.isValid() ? absolute.value()
+ : std::numeric_limits<CounterValue>::max();
+ }
+ success = GetInitialCounterText(ordinal, aWritingMode, initialText, aIsRTL);
+
+ // add pad & negative, build the final result
+ if (success) {
+ aResult.Truncate();
+ if (useNegativeSign && aOrdinal < 0) {
+ NegativeType negative;
+ GetNegative(negative);
+ aResult.Append(negative.before);
+ // There is nothing between the suffix part of negative and initial
+ // representation, so we append it directly here.
+ initialText.Append(negative.after);
+ }
+ PadType pad;
+ GetPad(pad);
+ int32_t diff =
+ pad.width -
+ narrow_cast<int32_t>(unicode::CountGraphemeClusters(initialText) +
+ unicode::CountGraphemeClusters(aResult));
+ if (diff > 0) {
+ auto length = pad.symbol.Length();
+ if (diff > LENGTH_LIMIT || length > LENGTH_LIMIT ||
+ diff * length > LENGTH_LIMIT) {
+ success = false;
+ } else if (length > 0) {
+ for (int32_t i = 0; i < diff; ++i) {
+ aResult.Append(pad.symbol);
+ }
+ }
+ }
+ if (success) {
+ aResult.Append(initialText);
+ }
+ }
+ }
+
+ if (!success) {
+ CallFallbackStyle(aOrdinal, aWritingMode, aResult, aIsRTL);
+ }
+}
+
+/* virtual */
+void CounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult, bool& aIsBullet) {
+ bool isRTL; // we don't care about direction for spoken text
+ aIsBullet = false;
+ switch (GetSpeakAs()) {
+ case SpeakAs::Bullets:
+ aResult.Assign(kDiscCharacter);
+ aIsBullet = true;
+ break;
+ case SpeakAs::Numbers:
+ DecimalToText(aOrdinal, aResult);
+ break;
+ case SpeakAs::Spellout:
+ // we currently do not actually support 'spell-out',
+ // so 'words' is used instead.
+ case SpeakAs::Words:
+ GetCounterText(aOrdinal, WritingMode(), aResult, isRTL);
+ break;
+ case SpeakAs::Other:
+ // This should be processed by CustomCounterStyle
+ MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown speak-as value");
+ break;
+ }
+}
+
+/* virtual */
+void CounterStyle::CallFallbackStyle(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult, bool& aIsRTL) {
+ GetFallback()->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
+}
+
+CounterStyleManager::CounterStyleManager(nsPresContext* aPresContext)
+ : mPresContext(aPresContext) {
+ // Insert the static styles into cache table
+ mStyles.InsertOrUpdate(nsGkAtoms::none, GetNoneStyle());
+ mStyles.InsertOrUpdate(nsGkAtoms::decimal, GetDecimalStyle());
+ mStyles.InsertOrUpdate(nsGkAtoms::disc, GetDiscStyle());
+}
+
+CounterStyleManager::~CounterStyleManager() {
+ MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
+}
+
+void CounterStyleManager::DestroyCounterStyle(CounterStyle* aCounterStyle) {
+ if (aCounterStyle->IsCustomStyle()) {
+ MOZ_ASSERT(!aCounterStyle->AsAnonymous(),
+ "Anonymous counter styles "
+ "are not managed by CounterStyleManager");
+ static_cast<CustomCounterStyle*>(aCounterStyle)->Destroy();
+ } else if (aCounterStyle->IsDependentStyle()) {
+ static_cast<DependentBuiltinCounterStyle*>(aCounterStyle)->Destroy();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Builtin counter styles should not be destroyed");
+ }
+}
+
+void CounterStyleManager::Disconnect() {
+ CleanRetiredStyles();
+ for (CounterStyle* style : mStyles.Values()) {
+ if (style->IsDependentStyle()) {
+ DestroyCounterStyle(style);
+ }
+ }
+ mStyles.Clear();
+ mPresContext = nullptr;
+}
+
+CounterStyle* CounterStyleManager::ResolveCounterStyle(nsAtom* aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ CounterStyle* data = GetCounterStyle(aName);
+ if (data) {
+ return data;
+ }
+
+ // Names are compared case-sensitively here. Predefined names should
+ // have been lowercased by the parser.
+ ServoStyleSet* styleSet = mPresContext->StyleSet();
+ auto* rule = styleSet->CounterStyleRuleForName(aName);
+ if (rule) {
+ MOZ_ASSERT(Servo_CounterStyleRule_GetName(rule) == aName);
+ data = new (mPresContext) CustomCounterStyle(this, rule);
+ } else {
+ for (const BuiltinCounterStyle& item : gBuiltinStyleTable) {
+ if (item.GetStyleName() == aName) {
+ const auto style = item.GetStyle();
+ data = item.IsDependentStyle()
+ ? new (mPresContext)
+ DependentBuiltinCounterStyle(style, this)
+ : GetBuiltinStyle(style);
+ break;
+ }
+ }
+ }
+ if (!data) {
+ data = GetDecimalStyle();
+ }
+ mStyles.InsertOrUpdate(aName, data);
+ return data;
+}
+
+/* static */
+CounterStyle* CounterStyleManager::GetBuiltinStyle(ListStyle aStyle) {
+ MOZ_ASSERT(size_t(aStyle) < ArrayLength(gBuiltinStyleTable),
+ "Require a valid builtin style constant");
+ MOZ_ASSERT(!gBuiltinStyleTable[size_t(aStyle)].IsDependentStyle(),
+ "Cannot get dependent builtin style");
+ // No method of BuiltinCounterStyle mutates the struct itself, so it
+ // should be fine to cast const away.
+ return const_cast<BuiltinCounterStyle*>(&gBuiltinStyleTable[size_t(aStyle)]);
+}
+
+bool CounterStyleManager::NotifyRuleChanged() {
+ bool changed = false;
+ for (auto iter = mStyles.Iter(); !iter.Done(); iter.Next()) {
+ CounterStyle* style = iter.Data();
+ bool toBeUpdated = false;
+ bool toBeRemoved = false;
+ ServoStyleSet* styleSet = mPresContext->StyleSet();
+ auto* newRule = styleSet->CounterStyleRuleForName(iter.Key());
+ if (!newRule) {
+ if (style->IsCustomStyle()) {
+ toBeRemoved = true;
+ }
+ } else {
+ if (!style->IsCustomStyle()) {
+ toBeRemoved = true;
+ } else {
+ auto custom = static_cast<CustomCounterStyle*>(style);
+ if (custom->GetRule() != newRule) {
+ toBeRemoved = true;
+ } else {
+ auto generation = Servo_CounterStyleRule_GetGeneration(newRule);
+ if (custom->GetRuleGeneration() != generation) {
+ toBeUpdated = true;
+ custom->ResetCachedData();
+ }
+ }
+ }
+ }
+ changed = changed || toBeUpdated || toBeRemoved;
+ if (toBeRemoved) {
+ if (style->IsDependentStyle()) {
+ // Add object to retired list so we can clean them up later.
+ mRetiredStyles.AppendElement(style);
+ }
+ iter.Remove();
+ }
+ }
+
+ if (changed) {
+ for (CounterStyle* style : mStyles.Values()) {
+ if (style->IsCustomStyle()) {
+ CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(style);
+ custom->ResetDependentData();
+ }
+ // There is no dependent data cached in DependentBuiltinCounterStyle
+ // instances, so we don't need to reset their data.
+ }
+ }
+ return changed;
+}
+
+void CounterStyleManager::CleanRetiredStyles() {
+ nsTArray<CounterStyle*> list(std::move(mRetiredStyles));
+ for (CounterStyle* style : list) {
+ DestroyCounterStyle(style);
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/style/CounterStyleManager.h b/layout/style/CounterStyleManager.h
new file mode 100644
index 0000000000..b6d9c2a0a9
--- /dev/null
+++ b/layout/style/CounterStyleManager.h
@@ -0,0 +1,355 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_CounterStyleManager_h_
+#define mozilla_CounterStyleManager_h_
+
+#include "nsGkAtoms.h"
+#include "nsStringFwd.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+
+#include "nsStyleConsts.h"
+
+#include "mozilla/Attributes.h"
+
+class nsPresContext;
+
+namespace mozilla {
+
+enum class SpeakAs : uint8_t {
+ Bullets = 0,
+ Numbers = 1,
+ Words = 2,
+ Spellout = 3,
+ Other = 255
+};
+
+class WritingMode;
+
+typedef int32_t CounterValue;
+
+class CounterStyleManager;
+class AnonymousCounterStyle;
+
+struct NegativeType;
+struct PadType;
+
+class CounterStyle {
+ protected:
+ explicit constexpr CounterStyle(ListStyle aStyle) : mStyle(aStyle) {}
+
+ private:
+ CounterStyle(const CounterStyle& aOther) = delete;
+ void operator=(const CounterStyle& other) = delete;
+
+ public:
+ constexpr ListStyle GetStyle() const { return mStyle; }
+ bool IsNone() const { return mStyle == ListStyle::None; }
+ bool IsCustomStyle() const { return mStyle == ListStyle::Custom; }
+ // A style is dependent if it depends on the counter style manager.
+ // Custom styles are certainly dependent. In addition, some builtin
+ // styles are dependent for fallback.
+ bool IsDependentStyle() const;
+
+ virtual void GetPrefix(nsAString& aResult) = 0;
+ virtual void GetSuffix(nsAString& aResult) = 0;
+ void GetCounterText(CounterValue aOrdinal, WritingMode aWritingMode,
+ nsAString& aResult, bool& aIsRTL);
+ virtual void GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult, bool& aIsBullet);
+
+ // XXX This method could be removed once ::-moz-list-bullet and
+ // ::-moz-list-number are completely merged into ::marker.
+ virtual bool IsBullet() = 0;
+
+ virtual void GetNegative(NegativeType& aResult) = 0;
+ /**
+ * This method returns whether an ordinal is in the range of this
+ * counter style. Note that, it is possible that an ordinal in range
+ * is rejected by the generating algorithm.
+ */
+ virtual bool IsOrdinalInRange(CounterValue aOrdinal) = 0;
+ /**
+ * This method returns whether an ordinal is in the default range of
+ * this counter style. This is the effective range when no 'range'
+ * descriptor is specified.
+ */
+ virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal) = 0;
+ virtual void GetPad(PadType& aResult) = 0;
+ virtual CounterStyle* GetFallback() = 0;
+ virtual SpeakAs GetSpeakAs() = 0;
+ virtual bool UseNegativeSign() = 0;
+
+ virtual void CallFallbackStyle(CounterValue aOrdinal,
+ WritingMode aWritingMode, nsAString& aResult,
+ bool& aIsRTL);
+ virtual bool GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult, bool& aIsRTL) = 0;
+
+ virtual AnonymousCounterStyle* AsAnonymous() { return nullptr; }
+
+ protected:
+ const ListStyle mStyle;
+};
+
+class AnonymousCounterStyle final : public CounterStyle {
+ public:
+ explicit AnonymousCounterStyle(const nsAString& aContent);
+ AnonymousCounterStyle(StyleSymbolsType, nsTArray<nsString> aSymbols);
+
+ virtual void GetPrefix(nsAString& aResult) override;
+ virtual void GetSuffix(nsAString& aResult) override;
+ virtual bool IsBullet() override;
+
+ virtual void GetNegative(NegativeType& aResult) override;
+ virtual bool IsOrdinalInRange(CounterValue aOrdinal) override;
+ virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal) override;
+ virtual void GetPad(PadType& aResult) override;
+ virtual CounterStyle* GetFallback() override;
+ virtual SpeakAs GetSpeakAs() override;
+ virtual bool UseNegativeSign() override;
+
+ virtual bool GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult, bool& aIsRTL) override;
+
+ virtual AnonymousCounterStyle* AsAnonymous() override { return this; }
+
+ bool IsSingleString() const { return mSingleString; }
+ auto GetSymbols() const { return Span<const nsString>{mSymbols}; }
+
+ StyleCounterSystem GetSystem() const;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousCounterStyle)
+
+ private:
+ ~AnonymousCounterStyle() = default;
+
+ bool mSingleString;
+ StyleSymbolsType mSymbolsType;
+ nsTArray<nsString> mSymbols;
+};
+
+// A smart pointer to CounterStyle. It either owns a reference to an
+// anonymous counter style, or weakly refers to a named counter style
+// managed by counter style manager.
+class CounterStylePtr {
+ public:
+ CounterStylePtr() : mRaw(0) {}
+ CounterStylePtr(const CounterStylePtr& aOther) : mRaw(aOther.mRaw) {
+ if (!mRaw) {
+ return;
+ }
+ switch (GetType()) {
+ case eAnonymousCounterStyle:
+ AsAnonymous()->AddRef();
+ break;
+ case eAtom:
+ AsAtom()->AddRef();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown type");
+ break;
+ }
+ }
+ CounterStylePtr(CounterStylePtr&& aOther) : mRaw(aOther.mRaw) {
+ aOther.mRaw = 0;
+ }
+ ~CounterStylePtr() { Reset(); }
+
+ CounterStylePtr& operator=(const CounterStylePtr& aOther) {
+ if (this != &aOther) {
+ Reset();
+ new (this) CounterStylePtr(aOther);
+ }
+ return *this;
+ }
+ CounterStylePtr& operator=(CounterStylePtr&& aOther) {
+ if (this != &aOther) {
+ Reset();
+ mRaw = aOther.mRaw;
+ aOther.mRaw = 0;
+ }
+ return *this;
+ }
+ CounterStylePtr& operator=(decltype(nullptr)) {
+ Reset();
+ return *this;
+ }
+ CounterStylePtr& operator=(nsStaticAtom* aStaticAtom) {
+ Reset();
+ mRaw = reinterpret_cast<uintptr_t>(aStaticAtom) | eAtom;
+ return *this;
+ }
+ CounterStylePtr& operator=(already_AddRefed<nsAtom> aAtom) {
+ Reset();
+ mRaw = reinterpret_cast<uintptr_t>(aAtom.take()) | eAtom;
+ return *this;
+ }
+ CounterStylePtr& operator=(AnonymousCounterStyle* aCounterStyle) {
+ Reset();
+ if (aCounterStyle) {
+ CounterStyle* raw = do_AddRef(aCounterStyle).take();
+ mRaw = reinterpret_cast<uintptr_t>(raw) | eAnonymousCounterStyle;
+ }
+ return *this;
+ }
+
+ // TODO(emilio): Make CounterStyle have a single representation, either by
+ // removing CounterStylePtr or by moving this representation to Rust.
+ static CounterStylePtr FromStyle(const StyleCounterStyle& aStyle) {
+ CounterStylePtr ret;
+ if (aStyle.IsName()) {
+ ret = do_AddRef(aStyle.AsName().AsAtom());
+ } else {
+ StyleSymbolsType type = aStyle.AsSymbols()._0;
+ Span<const StyleSymbol> symbols = aStyle.AsSymbols()._1._0.AsSpan();
+ nsTArray<nsString> transcoded(symbols.Length());
+ for (const auto& symbol : symbols) {
+ MOZ_ASSERT(symbol.IsString(), "Should not have <ident> in symbols()");
+ transcoded.AppendElement(
+ NS_ConvertUTF8toUTF16(symbol.AsString().AsString()));
+ }
+ ret = new AnonymousCounterStyle(type, std::move(transcoded));
+ }
+ return ret;
+ }
+
+ explicit operator bool() const { return !!mRaw; }
+ bool operator!() const { return !mRaw; }
+ bool operator==(const CounterStylePtr& aOther) const {
+ // FIXME(emilio): For atoms this is all right, but for symbols doesn't this
+ // cause us to compare as unequal all the time, even if the specified
+ // symbols didn't change?
+ return mRaw == aOther.mRaw;
+ }
+ bool operator!=(const CounterStylePtr& aOther) const {
+ return mRaw != aOther.mRaw;
+ }
+
+ nsAtom* AsAtom() const {
+ MOZ_ASSERT(IsAtom());
+ return reinterpret_cast<nsAtom*>(mRaw & ~eMask);
+ }
+ AnonymousCounterStyle* AsAnonymous() const {
+ MOZ_ASSERT(IsAnonymous());
+ return static_cast<AnonymousCounterStyle*>(
+ reinterpret_cast<CounterStyle*>(mRaw & ~eMask));
+ }
+
+ bool IsAtom() const { return GetType() == eAtom; }
+ bool IsAnonymous() const { return GetType() == eAnonymousCounterStyle; }
+
+ bool IsNone() const { return IsAtom() && AsAtom() == nsGkAtoms::none; }
+
+ private:
+ enum Type : uintptr_t {
+ eAnonymousCounterStyle = 0,
+ eAtom = 1,
+ eMask = 1,
+ };
+
+ static_assert(alignof(CounterStyle) >= 1 << eMask,
+ "We're gonna tag the pointer, so it better fit");
+ static_assert(alignof(nsAtom) >= 1 << eMask,
+ "We're gonna tag the pointer, so it better fit");
+
+ Type GetType() const { return static_cast<Type>(mRaw & eMask); }
+
+ void Reset() {
+ if (!mRaw) {
+ return;
+ }
+ switch (GetType()) {
+ case eAnonymousCounterStyle:
+ AsAnonymous()->Release();
+ break;
+ case eAtom:
+ AsAtom()->Release();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown type");
+ break;
+ }
+ mRaw = 0;
+ }
+
+ // mRaw contains the pointer, and its last bit is used to store the type of
+ // the pointer.
+ // If the type is eAtom, the pointer owns a reference to an nsAtom
+ // (potentially null).
+ // If the type is eAnonymousCounterStyle, it owns a reference to an
+ // anonymous counter style (never null).
+ uintptr_t mRaw;
+};
+
+class CounterStyleManager final {
+ private:
+ ~CounterStyleManager();
+
+ public:
+ explicit CounterStyleManager(nsPresContext* aPresContext);
+
+ void Disconnect();
+
+ bool IsInitial() const {
+ // only 'none', 'decimal', and 'disc'
+ return mStyles.Count() == 3;
+ }
+
+ // Returns the counter style object for the given name from the style
+ // table if it is already built, and nullptr otherwise.
+ CounterStyle* GetCounterStyle(nsAtom* aName) const {
+ return mStyles.Get(aName);
+ }
+ // Same as GetCounterStyle but try to build the counter style object
+ // rather than returning nullptr if that hasn't been built.
+ CounterStyle* ResolveCounterStyle(nsAtom* aName);
+ CounterStyle* ResolveCounterStyle(const CounterStylePtr& aPtr) {
+ if (aPtr.IsAtom()) {
+ return ResolveCounterStyle(aPtr.AsAtom());
+ }
+ return aPtr.AsAnonymous();
+ }
+
+ static CounterStyle* GetBuiltinStyle(ListStyle aStyle);
+ static CounterStyle* GetNoneStyle() {
+ return GetBuiltinStyle(ListStyle::None);
+ }
+ static CounterStyle* GetDecimalStyle() {
+ return GetBuiltinStyle(ListStyle::Decimal);
+ }
+ static CounterStyle* GetDiscStyle() {
+ return GetBuiltinStyle(ListStyle::Disc);
+ }
+
+ // This method will scan all existing counter styles generated by this
+ // manager, and remove or mark data dirty accordingly. It returns true
+ // if any counter style is changed, false elsewise. This method should
+ // be called when any counter style may be affected.
+ bool NotifyRuleChanged();
+ // NotifyRuleChanged will evict no longer needed counter styles into
+ // mRetiredStyles, and this function destroys all objects listed there.
+ // It should be called only after no one may ever use those objects.
+ void CleanRetiredStyles();
+
+ nsPresContext* PresContext() const { return mPresContext; }
+
+ NS_INLINE_DECL_REFCOUNTING(CounterStyleManager)
+
+ private:
+ void DestroyCounterStyle(CounterStyle* aCounterStyle);
+
+ nsPresContext* mPresContext;
+ nsTHashMap<RefPtr<nsAtom>, CounterStyle*> mStyles;
+ nsTArray<CounterStyle*> mRetiredStyles;
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_CounterStyleManager_h_) */
diff --git a/layout/style/DeclarationBlock.cpp b/layout/style/DeclarationBlock.cpp
new file mode 100644
index 0000000000..03d07049b9
--- /dev/null
+++ b/layout/style/DeclarationBlock.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DeclarationBlock.h"
+
+#include "mozilla/css/Rule.h"
+
+#include "nsCSSProps.h"
+#include "nsIMemoryReporter.h"
+
+namespace mozilla {
+
+MOZ_DEFINE_MALLOC_SIZE_OF(ServoDeclarationBlockMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoDeclarationBlockEnclosingSizeOf)
+
+size_t DeclarationBlock::SizeofIncludingThis(MallocSizeOf aMallocSizeOf) {
+ size_t n = aMallocSizeOf(this);
+ n += Servo_DeclarationBlock_SizeOfIncludingThis(
+ ServoDeclarationBlockMallocSizeOf, ServoDeclarationBlockEnclosingSizeOf,
+ mRaw.get());
+ return n;
+}
+
+bool DeclarationBlock::OwnerIsReadOnly() const {
+ css::Rule* rule = GetOwningRule();
+ return rule && rule->IsReadOnly();
+}
+
+} // namespace mozilla
diff --git a/layout/style/DeclarationBlock.h b/layout/style/DeclarationBlock.h
new file mode 100644
index 0000000000..837ab74bce
--- /dev/null
+++ b/layout/style/DeclarationBlock.h
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * representation of a declaration block in a CSS stylesheet, or of
+ * a style attribute
+ */
+
+#ifndef mozilla_DeclarationBlock_h
+#define mozilla_DeclarationBlock_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/ServoBindings.h"
+
+#include "nsCSSPropertyID.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+class AttributeStyles;
+namespace css {
+class Declaration;
+class Rule;
+} // namespace css
+
+class DeclarationBlock final {
+ DeclarationBlock(const DeclarationBlock& aCopy)
+ : mRaw(Servo_DeclarationBlock_Clone(aCopy.mRaw).Consume()),
+ mImmutable(false),
+ mIsDirty(false) {
+ mContainer.mRaw = 0;
+ }
+
+ public:
+ explicit DeclarationBlock(already_AddRefed<StyleLockedDeclarationBlock> aRaw)
+ : mRaw(aRaw), mImmutable(false), mIsDirty(false) {
+ mContainer.mRaw = 0;
+ }
+
+ DeclarationBlock()
+ : DeclarationBlock(Servo_DeclarationBlock_CreateEmpty().Consume()) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DeclarationBlock)
+
+ already_AddRefed<DeclarationBlock> Clone() const {
+ return do_AddRef(new DeclarationBlock(*this));
+ }
+
+ /**
+ * Return whether |this| may be modified.
+ */
+ bool IsMutable() const { return !mImmutable; }
+
+ /**
+ * Crash in debug builds if |this| cannot be modified.
+ */
+ void AssertMutable() const {
+ MOZ_ASSERT(IsMutable(), "someone forgot to call EnsureMutable");
+ MOZ_ASSERT(!OwnerIsReadOnly(), "User Agent sheets shouldn't be modified");
+ }
+
+ /**
+ * Mark this declaration as unmodifiable.
+ */
+ void SetImmutable() { mImmutable = true; }
+
+ /**
+ * Return whether |this| has been restyled after modified.
+ */
+ bool IsDirty() const { return mIsDirty; }
+
+ /**
+ * Mark this declaration as dirty.
+ */
+ void SetDirty() { mIsDirty = true; }
+
+ /**
+ * Mark this declaration as not dirty.
+ */
+ void UnsetDirty() { mIsDirty = false; }
+
+ /**
+ * Copy |this|, if necessary to ensure that it can be modified.
+ */
+ already_AddRefed<DeclarationBlock> EnsureMutable() {
+ MOZ_ASSERT(!OwnerIsReadOnly());
+
+ if (!IsDirty()) {
+ // In stylo, the old DeclarationBlock is stored in element's rule node
+ // tree directly, to avoid new values replacing the DeclarationBlock in
+ // the tree directly, we need to copy the old one here if we haven't yet
+ // copied. As a result the new value does not replace rule node tree until
+ // traversal happens.
+ //
+ // FIXME(emilio, bug 1606413): This is a hack for ::first-line and
+ // transitions starting due to CSSOM changes when other transitions are
+ // already running. Try to simplify this setup, so that rule tree updates
+ // find the mutated declaration block properly rather than having to
+ // insert the cloned declaration in the tree.
+ return Clone();
+ }
+
+ if (!IsMutable()) {
+ return Clone();
+ }
+
+ return do_AddRef(this);
+ }
+
+ void SetOwningRule(css::Rule* aRule) {
+ MOZ_ASSERT(!mContainer.mOwningRule || !aRule,
+ "should never overwrite one rule with another");
+ mContainer.mOwningRule = aRule;
+ }
+
+ css::Rule* GetOwningRule() const {
+ if (mContainer.mRaw & 0x1) {
+ return nullptr;
+ }
+ return mContainer.mOwningRule;
+ }
+
+ void SetAttributeStyles(AttributeStyles* aAttributeStyles) {
+ MOZ_ASSERT(!mContainer.mAttributeStyles || !aAttributeStyles,
+ "should never overwrite one sheet with another");
+ mContainer.mAttributeStyles = aAttributeStyles;
+ if (aAttributeStyles) {
+ mContainer.mRaw |= uintptr_t(1);
+ }
+ }
+
+ AttributeStyles* GetAttributeStyles() const {
+ if (!(mContainer.mRaw & 0x1)) {
+ return nullptr;
+ }
+ auto c = mContainer;
+ c.mRaw &= ~uintptr_t(1);
+ return c.mAttributeStyles;
+ }
+
+ bool IsReadOnly() const;
+
+ size_t SizeofIncludingThis(MallocSizeOf);
+
+ static already_AddRefed<DeclarationBlock> FromCssText(
+ const nsACString& aCssText, URLExtraData* aExtraData,
+ nsCompatibility aMode, css::Loader* aLoader, StyleCssRuleType aRuleType) {
+ RefPtr<StyleLockedDeclarationBlock> raw =
+ Servo_ParseStyleAttribute(&aCssText, aExtraData, aMode, aLoader,
+ aRuleType)
+ .Consume();
+ return MakeAndAddRef<DeclarationBlock>(raw.forget());
+ }
+
+ static already_AddRefed<DeclarationBlock> FromCssText(
+ const nsAString& aCssText, URLExtraData* aExtraData,
+ nsCompatibility aMode, css::Loader* aLoader, StyleCssRuleType aRuleType) {
+ NS_ConvertUTF16toUTF8 value(aCssText);
+ return FromCssText(value, aExtraData, aMode, aLoader, aRuleType);
+ }
+
+ StyleLockedDeclarationBlock* Raw() const { return mRaw; }
+
+ void ToString(nsACString& aResult) const {
+ Servo_DeclarationBlock_GetCssText(mRaw, &aResult);
+ }
+
+ uint32_t Count() const { return Servo_DeclarationBlock_Count(mRaw); }
+
+ bool GetNthProperty(uint32_t aIndex, nsACString& aReturn) const {
+ aReturn.Truncate();
+ return Servo_DeclarationBlock_GetNthProperty(mRaw, aIndex, &aReturn);
+ }
+
+ void GetPropertyValue(const nsACString& aProperty, nsACString& aValue) const {
+ Servo_DeclarationBlock_GetPropertyValue(mRaw, &aProperty, &aValue);
+ }
+
+ void GetPropertyValueByID(nsCSSPropertyID aPropID, nsACString& aValue) const {
+ Servo_DeclarationBlock_GetPropertyValueById(mRaw, aPropID, &aValue);
+ }
+
+ bool GetPropertyIsImportant(const nsACString& aProperty) const {
+ return Servo_DeclarationBlock_GetPropertyIsImportant(mRaw, &aProperty);
+ }
+
+ // Returns whether the property was removed.
+ bool RemoveProperty(const nsACString& aProperty,
+ DeclarationBlockMutationClosure aClosure = {}) {
+ AssertMutable();
+ return Servo_DeclarationBlock_RemoveProperty(mRaw, &aProperty, aClosure);
+ }
+
+ // Returns whether the property was removed.
+ bool RemovePropertyByID(nsCSSPropertyID aProperty,
+ DeclarationBlockMutationClosure aClosure = {}) {
+ AssertMutable();
+ return Servo_DeclarationBlock_RemovePropertyById(mRaw, aProperty, aClosure);
+ }
+
+ private:
+ ~DeclarationBlock() = default;
+
+ bool OwnerIsReadOnly() const;
+
+ union {
+ // We only ever have one of these since we have a AttributeStyles only for
+ // style attributes, and style attributes never have an owning rule. It's a
+ // AttributeStyles if the low bit is set.
+
+ uintptr_t mRaw;
+
+ // The style rule that owns this declaration. May be null.
+ css::Rule* mOwningRule;
+
+ // The AttributeStyles that is responsible for this declaration. Only
+ // non-null for style attributes.
+ AttributeStyles* mAttributeStyles;
+ } mContainer;
+
+ RefPtr<StyleLockedDeclarationBlock> mRaw;
+
+ // set when declaration put in the rule tree;
+ bool mImmutable;
+
+ // True if this declaration has not been restyled after modified.
+ //
+ // Since we can clear this flag from style worker threads, we use an Atomic.
+ //
+ // Note that although a single DeclarationBlock can be shared between
+ // different rule nodes (due to the style="" attribute cache), whenever a
+ // DeclarationBlock has its mIsDirty flag set to true, we always clone it to
+ // a unique object first. So when we clear this flag during Servo traversal,
+ // we know that we are clearing it on a DeclarationBlock that has a single
+ // reference, and there is no problem with another user of the same
+ // DeclarationBlock thinking that it is not dirty.
+ Atomic<bool, MemoryOrdering::Relaxed> mIsDirty;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_DeclarationBlock_h
diff --git a/layout/style/DocumentMatchingFunction.h b/layout/style/DocumentMatchingFunction.h
new file mode 100644
index 0000000000..f22161c339
--- /dev/null
+++ b/layout/style/DocumentMatchingFunction.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_css_DocumentMatchingFunction_h
+#define mozilla_css_DocumentMatchingFunction_h
+
+namespace mozilla {
+namespace css {
+
+/**
+ * Enum defining the type of matching function for a @-moz-document rule
+ * condition.
+ */
+enum class DocumentMatchingFunction {
+ URL = 0,
+ URLPrefix,
+ Domain,
+ RegExp,
+ MediaDocument,
+ PlainTextDocument,
+ UnobservableDocument,
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif // mozilla_css_DocumentMatchingFunction_h
diff --git a/layout/style/DocumentStyleRootIterator.cpp b/layout/style/DocumentStyleRootIterator.cpp
new file mode 100644
index 0000000000..0d9595c434
--- /dev/null
+++ b/layout/style/DocumentStyleRootIterator.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DocumentStyleRootIterator.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+DocumentStyleRootIterator::DocumentStyleRootIterator(nsINode* aStyleRoot)
+ : mPosition(0) {
+ MOZ_COUNT_CTOR(DocumentStyleRootIterator);
+ MOZ_ASSERT(aStyleRoot);
+ if (aStyleRoot->IsElement()) {
+ mStyleRoots.AppendElement(aStyleRoot->AsElement());
+ return;
+ }
+
+ dom::Document* doc = aStyleRoot->OwnerDoc();
+ MOZ_ASSERT(doc == aStyleRoot);
+ if (dom::Element* root = doc->GetRootElement()) {
+ mStyleRoots.AppendElement(root);
+ }
+ nsContentUtils::AppendDocumentLevelNativeAnonymousContentTo(doc, mStyleRoots);
+}
+
+dom::Element* DocumentStyleRootIterator::GetNextStyleRoot() {
+ for (;;) {
+ if (mPosition >= mStyleRoots.Length()) {
+ return nullptr;
+ }
+
+ nsIContent* next = mStyleRoots[mPosition];
+ ++mPosition;
+
+ if (next->IsElement()) {
+ return next->AsElement();
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/style/DocumentStyleRootIterator.h b/layout/style/DocumentStyleRootIterator.h
new file mode 100644
index 0000000000..84f48d02d7
--- /dev/null
+++ b/layout/style/DocumentStyleRootIterator.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DocumentStyleRootIterator_h
+#define DocumentStyleRootIterator_h
+
+#include "nsTArray.h"
+
+class nsIContent;
+
+class nsINode;
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+} // namespace dom
+
+/**
+ * DocumentStyleRootIterator traverses the roots of the document from the
+ * perspective of the Servo-backed style system. In the general case, this
+ * will first traverse the document root, followed by any document level
+ * native anonymous content.
+ *
+ * If the caller passes an element to the constructor rather than the document,
+ * that element (and nothing else) is returned from GetNextStyleRoot.
+ */
+class DocumentStyleRootIterator {
+ public:
+ explicit DocumentStyleRootIterator(nsINode* aStyleRoot);
+ MOZ_COUNTED_DTOR(DocumentStyleRootIterator)
+
+ dom::Element* GetNextStyleRoot();
+
+ private:
+ AutoTArray<nsIContent*, 8> mStyleRoots;
+ uint32_t mPosition;
+};
+
+} // namespace mozilla
+
+#endif // DocumentStyleRootIterator_h
diff --git a/layout/style/ErrorReporter.cpp b/layout/style/ErrorReporter.cpp
new file mode 100644
index 0000000000..6335c695ac
--- /dev/null
+++ b/layout/style/ErrorReporter.cpp
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* diagnostic reporting for CSS style sheet parser */
+
+#include "mozilla/css/ErrorReporter.h"
+
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Components.h"
+#include "nsIConsoleService.h"
+#include "mozilla/dom/Document.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIDocShell.h"
+#include "nsIFactory.h"
+#include "nsINode.h"
+#include "nsIScriptError.h"
+#include "nsIStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStyleUtil.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+
+namespace {
+class ShortTermURISpecCache : public Runnable {
+ public:
+ ShortTermURISpecCache()
+ : Runnable("ShortTermURISpecCache"), mPending(false) {}
+
+ nsString const& GetSpec(nsIURI* aURI) {
+ if (mURI != aURI) {
+ mURI = aURI;
+
+ if (NS_FAILED(NS_GetSanitizedURIStringFromURI(mURI, mSpec))) {
+ mSpec.AssignLiteral("[nsIURI::GetSpec failed]");
+ }
+ }
+ return mSpec;
+ }
+
+ bool IsInUse() const { return mURI != nullptr; }
+ bool IsPending() const { return mPending; }
+ void SetPending() { mPending = true; }
+
+ // When invoked as a runnable, zap the cache.
+ NS_IMETHOD Run() override {
+ mURI = nullptr;
+ mSpec.Truncate();
+ mPending = false;
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIURI> mURI;
+ nsString mSpec;
+ bool mPending;
+};
+
+} // namespace
+
+bool ErrorReporter::sInitialized = false;
+
+static nsIConsoleService* sConsoleService;
+static nsIFactory* sScriptErrorFactory;
+static nsIStringBundle* sStringBundle;
+static ShortTermURISpecCache* sSpecCache;
+
+void ErrorReporter::InitGlobals() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sInitialized, "should not have been called");
+
+ sInitialized = true;
+
+ nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!cs) {
+ return;
+ }
+
+ nsCOMPtr<nsIFactory> sf = do_GetClassObject(NS_SCRIPTERROR_CONTRACTID);
+ if (!sf) {
+ return;
+ }
+
+ nsCOMPtr<nsIStringBundleService> sbs = components::StringBundle::Service();
+ if (!sbs) {
+ return;
+ }
+
+ nsCOMPtr<nsIStringBundle> sb;
+ nsresult rv = sbs->CreateBundle("chrome://global/locale/css.properties",
+ getter_AddRefs(sb));
+ if (NS_FAILED(rv) || !sb) {
+ return;
+ }
+
+ cs.forget(&sConsoleService);
+ sf.forget(&sScriptErrorFactory);
+ sb.forget(&sStringBundle);
+}
+
+namespace mozilla {
+namespace css {
+
+/* static */
+void ErrorReporter::ReleaseGlobals() {
+ NS_IF_RELEASE(sConsoleService);
+ NS_IF_RELEASE(sScriptErrorFactory);
+ NS_IF_RELEASE(sStringBundle);
+ NS_IF_RELEASE(sSpecCache);
+}
+
+uint64_t ErrorReporter::FindInnerWindowId(const StyleSheet* aSheet,
+ const Loader* aLoader) {
+ if (aSheet) {
+ if (uint64_t id = aSheet->FindOwningWindowInnerID()) {
+ return id;
+ }
+ }
+ if (aLoader) {
+ if (Document* doc = aLoader->GetDocument()) {
+ return doc->InnerWindowID();
+ }
+ }
+ return 0;
+}
+
+ErrorReporter::ErrorReporter(uint64_t aInnerWindowId)
+ : mInnerWindowId(aInnerWindowId) {
+ EnsureGlobalsInitialized();
+}
+
+ErrorReporter::~ErrorReporter() {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Schedule deferred cleanup for cached data. We want to strike a
+ // balance between performance and memory usage, so we only allow
+ // short-term caching.
+ if (sSpecCache && sSpecCache->IsInUse() && !sSpecCache->IsPending()) {
+ nsCOMPtr<nsIRunnable> runnable(sSpecCache);
+ nsresult rv = SchedulerGroup::Dispatch(runnable.forget());
+ if (NS_FAILED(rv)) {
+ // Peform the "deferred" cleanup immediately if the dispatch fails.
+ sSpecCache->Run();
+ } else {
+ sSpecCache->SetPending();
+ }
+ }
+}
+
+bool ErrorReporter::ShouldReportErrors(const Document& aDoc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsIDocShell* shell = aDoc.GetDocShell();
+ if (!shell) {
+ return false;
+ }
+
+ bool report = false;
+ shell->GetCssErrorReportingEnabled(&report);
+ return report;
+}
+
+static nsINode* SheetOwner(const StyleSheet& aSheet) {
+ if (nsINode* owner = aSheet.GetOwnerNode()) {
+ return owner;
+ }
+
+ auto* associated = aSheet.GetAssociatedDocumentOrShadowRoot();
+ return associated ? &associated->AsNode() : nullptr;
+}
+
+bool ErrorReporter::ShouldReportErrors(const StyleSheet* aSheet,
+ const Loader* aLoader) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!StaticPrefs::layout_css_report_errors()) {
+ return false;
+ }
+
+ if (aSheet) {
+ nsINode* owner = SheetOwner(*aSheet);
+ if (owner && ShouldReportErrors(*owner->OwnerDoc())) {
+ return true;
+ }
+ }
+
+ if (aLoader && aLoader->GetDocument() &&
+ ShouldReportErrors(*aLoader->GetDocument())) {
+ return true;
+ }
+
+ return false;
+}
+
+void ErrorReporter::OutputError(const nsACString& aSourceLine,
+ const nsACString& aSelectors,
+ uint32_t aLineNumber, uint32_t aColNumber,
+ nsIURI* aURI) {
+ nsAutoString errorLine;
+ // This could be a really long string for minified CSS; just leave it empty
+ // if we OOM.
+ if (!AppendUTF8toUTF16(aSourceLine, errorLine, fallible)) {
+ errorLine.Truncate();
+ }
+
+ nsAutoString selectors;
+ if (!AppendUTF8toUTF16(aSelectors, selectors, fallible)) {
+ selectors.Truncate();
+ }
+
+ if (mError.IsEmpty()) {
+ return;
+ }
+
+ nsAutoString fileName;
+ if (aURI) {
+ if (!sSpecCache) {
+ sSpecCache = new ShortTermURISpecCache;
+ NS_ADDREF(sSpecCache);
+ }
+ fileName = sSpecCache->GetSpec(aURI);
+ } else {
+ fileName.AssignLiteral("from DOM");
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIScriptError> errorObject =
+ do_CreateInstance(sScriptErrorFactory, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ // It is safe to used InitWithSanitizedSource because fileName is
+ // an already anonymized uri spec.
+ rv = errorObject->InitWithSanitizedSource(
+ mError, fileName, errorLine, aLineNumber, aColNumber,
+ nsIScriptError::warningFlag, "CSS Parser", mInnerWindowId);
+
+ if (NS_SUCCEEDED(rv)) {
+ errorObject->SetCssSelectors(selectors);
+ sConsoleService->LogMessage(errorObject);
+ }
+ }
+
+ mError.Truncate();
+}
+
+void ErrorReporter::AddToError(const nsString& aErrorText) {
+ if (mError.IsEmpty()) {
+ mError = aErrorText;
+ } else {
+ mError.AppendLiteral(" ");
+ mError.Append(aErrorText);
+ }
+}
+
+void ErrorReporter::ReportUnexpected(const char* aMessage) {
+ nsAutoString str;
+ sStringBundle->GetStringFromName(aMessage, str);
+ AddToError(str);
+}
+
+void ErrorReporter::ReportUnexpectedUnescaped(
+ const char* aMessage, const nsTArray<nsString>& aParam) {
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(aMessage, aParam, str);
+ AddToError(str);
+}
+
+} // namespace css
+} // namespace mozilla
diff --git a/layout/style/ErrorReporter.h b/layout/style/ErrorReporter.h
new file mode 100644
index 0000000000..3f24cf089f
--- /dev/null
+++ b/layout/style/ErrorReporter.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* diagnostic reporting for CSS style sheet parser */
+
+#ifndef mozilla_css_ErrorReporter_h_
+#define mozilla_css_ErrorReporter_h_
+
+#include "nsString.h"
+
+struct nsCSSToken;
+class nsIURI;
+
+namespace mozilla {
+class StyleSheet;
+
+namespace dom {
+class Document;
+}
+
+namespace css {
+
+class Loader;
+
+// FIXME(emilio): Probably better to call this ErrorBuilder or something?
+class MOZ_STACK_CLASS ErrorReporter final {
+ public:
+ explicit ErrorReporter(uint64_t aInnerWindowId);
+ ~ErrorReporter();
+
+ static void ReleaseGlobals();
+ static void EnsureGlobalsInitialized() {
+ if (MOZ_UNLIKELY(!sInitialized)) {
+ InitGlobals();
+ }
+ }
+
+ static bool ShouldReportErrors(const dom::Document&);
+ static bool ShouldReportErrors(const StyleSheet*, const Loader*);
+ static uint64_t FindInnerWindowId(const StyleSheet*, const Loader*);
+
+ void OutputError(const nsACString& aSource, const nsACString& aSelectors,
+ uint32_t aLineNumber, uint32_t aColNumber, nsIURI* aURI);
+ void ClearError();
+
+ // In all overloads of ReportUnexpected, aMessage is a stringbundle
+ // name, which will be processed as a format string with the
+ // indicated number of parameters.
+
+ // no parameters
+ void ReportUnexpected(const char* aMessage);
+ // one parameter which has already been escaped appropriately
+ void ReportUnexpectedUnescaped(const char* aMessage,
+ const nsTArray<nsString>& aParam);
+
+ private:
+ void AddToError(const nsString& aErrorText);
+ static void InitGlobals();
+
+ static bool sInitialized;
+ static bool sReportErrors;
+
+ nsString mError;
+ const uint64_t mInnerWindowId;
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif // mozilla_css_ErrorReporter_h_
diff --git a/layout/style/FontFace.cpp b/layout/style/FontFace.cpp
new file mode 100644
index 0000000000..f1a90334ea
--- /dev/null
+++ b/layout/style/FontFace.cpp
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/FontFace.h"
+
+#include <algorithm>
+#include "gfxFontUtils.h"
+#include "mozilla/dom/CSSFontFaceRule.h"
+#include "mozilla/dom/FontFaceBinding.h"
+#include "mozilla/dom/FontFaceImpl.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoCSSParser.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsStyleUtil.h"
+
+namespace mozilla {
+namespace dom {
+
+// -- Utility functions ------------------------------------------------------
+
+template <typename T>
+static void GetDataFrom(const T& aObject, uint8_t*& aBuffer,
+ uint32_t& aLength) {
+ MOZ_ASSERT(!aBuffer);
+ // We need to use malloc here because the gfxUserFontEntry will be calling
+ // free on it, so the Vector's default AllocPolicy (MallocAllocPolicy) is
+ // fine.
+ Maybe<Vector<uint8_t>> buffer =
+ aObject.template CreateFromData<Vector<uint8_t>>();
+ if (buffer.isNothing()) {
+ return;
+ }
+ aLength = buffer->length();
+ aBuffer = buffer->extractOrCopyRawBuffer();
+}
+
+// -- FontFace ---------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FontFace)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FontFace)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoaded)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FontFace)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoaded)
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FontFace)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FontFace)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FontFace)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FontFace)
+
+FontFace::FontFace(nsIGlobalObject* aParent)
+ : mParent(aParent), mLoadedRejection(NS_OK) {}
+
+FontFace::~FontFace() {
+ // Assert that we don't drop any FontFace objects during a Servo traversal,
+ // since PostTraversalTask objects can hold raw pointers to FontFaces.
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+ Destroy();
+}
+
+void FontFace::Destroy() { mImpl->Destroy(); }
+
+JSObject* FontFace::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return FontFace_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<FontFace> FontFace::CreateForRule(
+ nsIGlobalObject* aGlobal, FontFaceSet* aFontFaceSet,
+ StyleLockedFontFaceRule* aRule) {
+ FontFaceSetImpl* setImpl = aFontFaceSet->GetImpl();
+ MOZ_ASSERT(setImpl);
+
+ RefPtr<FontFace> obj = new FontFace(aGlobal);
+ obj->mImpl = FontFaceImpl::CreateForRule(obj, setImpl, aRule);
+ return obj.forget();
+}
+
+already_AddRefed<FontFace> FontFace::Constructor(
+ const GlobalObject& aGlobal, const nsACString& aFamily,
+ const UTF8StringOrArrayBufferOrArrayBufferView& aSource,
+ const FontFaceDescriptors& aDescriptors, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
+ FontFaceSet* set = global->GetFonts();
+ if (NS_WARN_IF(!set)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ FontFaceSetImpl* setImpl = set->GetImpl();
+ if (NS_WARN_IF(!setImpl)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<FontFace> obj = new FontFace(global);
+ obj->mImpl = new FontFaceImpl(obj, setImpl);
+ if (!obj->mImpl->SetDescriptors(aFamily, aDescriptors)) {
+ return obj.forget();
+ }
+
+ if (aSource.IsUTF8String()) {
+ obj->mImpl->InitializeSourceURL(aSource.GetAsUTF8String());
+ } else {
+ uint8_t* buffer = nullptr;
+ uint32_t length = 0;
+ if (aSource.IsArrayBuffer()) {
+ GetDataFrom(aSource.GetAsArrayBuffer(), buffer, length);
+ } else if (aSource.IsArrayBufferView()) {
+ GetDataFrom(aSource.GetAsArrayBufferView(), buffer, length);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unhandled source type!");
+ return nullptr;
+ }
+
+ obj->mImpl->InitializeSourceBuffer(buffer, length);
+ }
+
+ return obj.forget();
+}
+
+void FontFace::GetFamily(nsACString& aResult) { mImpl->GetFamily(aResult); }
+
+void FontFace::SetFamily(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetFamily(aValue, aRv);
+}
+
+void FontFace::GetStyle(nsACString& aResult) { mImpl->GetStyle(aResult); }
+
+void FontFace::SetStyle(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetStyle(aValue, aRv);
+}
+
+void FontFace::GetWeight(nsACString& aResult) { mImpl->GetWeight(aResult); }
+
+void FontFace::SetWeight(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetWeight(aValue, aRv);
+}
+
+void FontFace::GetStretch(nsACString& aResult) { mImpl->GetStretch(aResult); }
+
+void FontFace::SetStretch(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetStretch(aValue, aRv);
+}
+
+void FontFace::GetUnicodeRange(nsACString& aResult) {
+ mImpl->GetUnicodeRange(aResult);
+}
+
+void FontFace::SetUnicodeRange(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetUnicodeRange(aValue, aRv);
+}
+
+void FontFace::GetVariant(nsACString& aResult) { mImpl->GetVariant(aResult); }
+
+void FontFace::SetVariant(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetVariant(aValue, aRv);
+}
+
+void FontFace::GetFeatureSettings(nsACString& aResult) {
+ mImpl->GetFeatureSettings(aResult);
+}
+
+void FontFace::SetFeatureSettings(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetFeatureSettings(aValue, aRv);
+}
+
+void FontFace::GetVariationSettings(nsACString& aResult) {
+ mImpl->GetVariationSettings(aResult);
+}
+
+void FontFace::SetVariationSettings(const nsACString& aValue,
+ ErrorResult& aRv) {
+ mImpl->SetVariationSettings(aValue, aRv);
+}
+
+void FontFace::GetDisplay(nsACString& aResult) { mImpl->GetDisplay(aResult); }
+
+void FontFace::SetDisplay(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetDisplay(aValue, aRv);
+}
+
+void FontFace::GetAscentOverride(nsACString& aResult) {
+ mImpl->GetAscentOverride(aResult);
+}
+
+void FontFace::SetAscentOverride(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetAscentOverride(aValue, aRv);
+}
+
+void FontFace::GetDescentOverride(nsACString& aResult) {
+ mImpl->GetDescentOverride(aResult);
+}
+
+void FontFace::SetDescentOverride(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetDescentOverride(aValue, aRv);
+}
+
+void FontFace::GetLineGapOverride(nsACString& aResult) {
+ mImpl->GetLineGapOverride(aResult);
+}
+
+void FontFace::SetLineGapOverride(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetLineGapOverride(aValue, aRv);
+}
+
+void FontFace::GetSizeAdjust(nsACString& aResult) {
+ mImpl->GetSizeAdjust(aResult);
+}
+
+void FontFace::SetSizeAdjust(const nsACString& aValue, ErrorResult& aRv) {
+ mImpl->SetSizeAdjust(aValue, aRv);
+}
+
+FontFaceLoadStatus FontFace::Status() { return mImpl->Status(); }
+
+Promise* FontFace::Load(ErrorResult& aRv) {
+ EnsurePromise();
+
+ if (!mLoaded) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ mImpl->Load(aRv);
+ return mLoaded;
+}
+
+Promise* FontFace::GetLoaded(ErrorResult& aRv) {
+ EnsurePromise();
+
+ if (!mLoaded) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return mLoaded;
+}
+
+void FontFace::MaybeResolve() {
+ gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked();
+
+ if (!mLoaded) {
+ return;
+ }
+
+ if (ServoStyleSet* ss = gfxFontUtils::CurrentServoStyleSet()) {
+ // See comments in Gecko_GetFontMetrics.
+ ss->AppendTask(PostTraversalTask::ResolveFontFaceLoadedPromise(this));
+ return;
+ }
+
+ mLoaded->MaybeResolve(this);
+}
+
+void FontFace::MaybeReject(nsresult aResult) {
+ gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked();
+
+ if (ServoStyleSet* ss = gfxFontUtils::CurrentServoStyleSet()) {
+ // See comments in Gecko_GetFontMetrics.
+ ss->AppendTask(
+ PostTraversalTask::RejectFontFaceLoadedPromise(this, aResult));
+ return;
+ }
+
+ if (mLoaded) {
+ mLoaded->MaybeReject(aResult);
+ } else if (mLoadedRejection == NS_OK) {
+ mLoadedRejection = aResult;
+ }
+}
+
+void FontFace::EnsurePromise() {
+ if (mLoaded || !mImpl || !mParent) {
+ return;
+ }
+
+ ErrorResult rv;
+ mLoaded = Promise::Create(mParent, rv);
+
+ if (mImpl->Status() == FontFaceLoadStatus::Loaded) {
+ mLoaded->MaybeResolve(this);
+ } else if (mLoadedRejection != NS_OK) {
+ mLoaded->MaybeReject(mLoadedRejection);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/FontFace.h b/layout/style/FontFace.h
new file mode 100644
index 0000000000..bdb33a0449
--- /dev/null
+++ b/layout/style/FontFace.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_FontFace_h
+#define mozilla_dom_FontFace_h
+
+#include "mozilla/dom/FontFaceBinding.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "gfxUserFontSet.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSValue.h"
+#include "nsWrapperCache.h"
+
+class gfxFontFaceBufferSource;
+class nsIGlobalObject;
+
+namespace mozilla {
+struct CSSFontFaceDescriptors;
+class PostTraversalTask;
+struct StyleLockedFontFaceRule;
+
+namespace dom {
+class CSSFontFaceRule;
+class FontFaceBufferSource;
+struct FontFaceDescriptors;
+class FontFaceImpl;
+class FontFaceSet;
+class FontFaceSetImpl;
+class Promise;
+class UTF8StringOrArrayBufferOrArrayBufferView;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla::dom {
+
+class FontFace final : public nsISupports, public nsWrapperCache {
+ friend class mozilla::PostTraversalTask;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FontFace)
+
+ nsIGlobalObject* GetParentObject() const { return mParent; }
+ JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<FontFace> CreateForRule(
+ nsIGlobalObject* aGlobal, FontFaceSet* aFontFaceSet,
+ StyleLockedFontFaceRule* aRule);
+
+ // Web IDL
+ static already_AddRefed<FontFace> Constructor(
+ const GlobalObject& aGlobal, const nsACString& aFamily,
+ const UTF8StringOrArrayBufferOrArrayBufferView& aSource,
+ const FontFaceDescriptors& aDescriptors, ErrorResult& aRV);
+
+ void GetFamily(nsACString& aResult);
+ void SetFamily(const nsACString& aValue, ErrorResult& aRv);
+ void GetStyle(nsACString& aResult);
+ void SetStyle(const nsACString& aValue, ErrorResult& aRv);
+ void GetWeight(nsACString& aResult);
+ void SetWeight(const nsACString& aValue, ErrorResult& aRv);
+ void GetStretch(nsACString& aResult);
+ void SetStretch(const nsACString& aValue, ErrorResult& aRv);
+ void GetUnicodeRange(nsACString& aResult);
+ void SetUnicodeRange(const nsACString& aValue, ErrorResult& aRv);
+ void GetVariant(nsACString& aResult);
+ void SetVariant(const nsACString& aValue, ErrorResult& aRv);
+ void GetFeatureSettings(nsACString& aResult);
+ void SetFeatureSettings(const nsACString& aValue, ErrorResult& aRv);
+ void GetVariationSettings(nsACString& aResult);
+ void SetVariationSettings(const nsACString& aValue, ErrorResult& aRv);
+ void GetDisplay(nsACString& aResult);
+ void SetDisplay(const nsACString& aValue, ErrorResult& aRv);
+ void GetAscentOverride(nsACString& aResult);
+ void SetAscentOverride(const nsACString& aValue, ErrorResult& aRv);
+ void GetDescentOverride(nsACString& aResult);
+ void SetDescentOverride(const nsACString& aValue, ErrorResult& aRv);
+ void GetLineGapOverride(nsACString& aResult);
+ void SetLineGapOverride(const nsACString& aValue, ErrorResult& aRv);
+ void GetSizeAdjust(nsACString& aResult);
+ void SetSizeAdjust(const nsACString& aValue, ErrorResult& aRv);
+
+ FontFaceLoadStatus Status();
+ Promise* Load(ErrorResult& aRv);
+ Promise* GetLoaded(ErrorResult& aRv);
+
+ FontFaceImpl* GetImpl() const { return mImpl; }
+
+ void Destroy();
+ void MaybeResolve();
+ void MaybeReject(nsresult aResult);
+
+ private:
+ explicit FontFace(nsIGlobalObject* aParent);
+ ~FontFace();
+
+ /**
+ * Returns and takes ownership of the buffer storing the font data.
+ */
+ void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength);
+
+ // Creates mLoaded if it doesn't already exist. It may immediately resolve or
+ // reject mLoaded based on mStatus and mLoadedRejection.
+ void EnsurePromise();
+
+ nsCOMPtr<nsIGlobalObject> mParent;
+
+ RefPtr<FontFaceImpl> mImpl;
+
+ // A Promise that is fulfilled once the font represented by this FontFace is
+ // loaded, and is rejected if the load fails. This promise is created lazily
+ // when JS asks for it.
+ RefPtr<Promise> mLoaded;
+
+ // Saves the rejection code for mLoaded if mLoaded hasn't been created yet.
+ nsresult mLoadedRejection;
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_FontFace_h)
diff --git a/layout/style/FontFaceImpl.cpp b/layout/style/FontFaceImpl.cpp
new file mode 100644
index 0000000000..9d1f7c8c5b
--- /dev/null
+++ b/layout/style/FontFaceImpl.cpp
@@ -0,0 +1,840 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/FontFaceImpl.h"
+
+#include <algorithm>
+#include "gfxFontUtils.h"
+#include "gfxPlatformFontList.h"
+#include "mozilla/dom/CSSFontFaceRule.h"
+#include "mozilla/dom/FontFaceBinding.h"
+#include "mozilla/dom/FontFaceSetImpl.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoCSSParser.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/dom/Document.h"
+#include "nsStyleUtil.h"
+
+namespace mozilla {
+namespace dom {
+
+// -- FontFaceBufferSource ---------------------------------------------------
+
+/**
+ * An object that wraps a FontFace object and exposes its ArrayBuffer
+ * or ArrayBufferView data in a form the user font set can consume.
+ */
+class FontFaceBufferSource : public gfxFontFaceBufferSource {
+ public:
+ FontFaceBufferSource(uint8_t* aBuffer, uint32_t aLength)
+ : mBuffer(aBuffer), mLength(aLength) {}
+
+ void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength) override {
+ MOZ_ASSERT(mBuffer,
+ "only call TakeBuffer once on a given "
+ "FontFaceBufferSource object");
+ aBuffer = mBuffer;
+ aLength = mLength;
+ mBuffer = nullptr;
+ mLength = 0;
+ }
+
+ private:
+ ~FontFaceBufferSource() override {
+ if (mBuffer) {
+ free(mBuffer);
+ }
+ }
+
+ uint8_t* mBuffer;
+ uint32_t mLength;
+};
+
+// -- FontFaceImpl -----------------------------------------------------------
+
+FontFaceImpl::FontFaceImpl(FontFace* aOwner, FontFaceSetImpl* aFontFaceSet)
+ : mOwner(aOwner),
+ mStatus(FontFaceLoadStatus::Unloaded),
+ mSourceType(SourceType(0)),
+ mFontFaceSet(aFontFaceSet),
+ mUnicodeRangeDirty(true),
+ mInFontFaceSet(false) {}
+
+FontFaceImpl::~FontFaceImpl() {
+ // Assert that we don't drop any FontFace objects during a Servo traversal,
+ // since PostTraversalTask objects can hold raw pointers to FontFaces.
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+
+ SetUserFontEntry(nullptr);
+}
+
+#ifdef DEBUG
+void FontFaceImpl::AssertIsOnOwningThread() const {
+ mFontFaceSet->AssertIsOnOwningThread();
+}
+#endif
+
+void FontFaceImpl::Destroy() {
+ mInFontFaceSet = false;
+ SetUserFontEntry(nullptr);
+ mOwner = nullptr;
+}
+
+static FontFaceLoadStatus LoadStateToStatus(
+ gfxUserFontEntry::UserFontLoadState aLoadState) {
+ switch (aLoadState) {
+ case gfxUserFontEntry::UserFontLoadState::STATUS_NOT_LOADED:
+ return FontFaceLoadStatus::Unloaded;
+ case gfxUserFontEntry::UserFontLoadState::STATUS_LOAD_PENDING:
+ case gfxUserFontEntry::UserFontLoadState::STATUS_LOADING:
+ return FontFaceLoadStatus::Loading;
+ case gfxUserFontEntry::UserFontLoadState::STATUS_LOADED:
+ return FontFaceLoadStatus::Loaded;
+ case gfxUserFontEntry::UserFontLoadState::STATUS_FAILED:
+ return FontFaceLoadStatus::Error;
+ }
+ MOZ_ASSERT_UNREACHABLE("invalid aLoadState value");
+ return FontFaceLoadStatus::Error;
+}
+
+already_AddRefed<FontFaceImpl> FontFaceImpl::CreateForRule(
+ FontFace* aOwner, FontFaceSetImpl* aFontFaceSet,
+ StyleLockedFontFaceRule* aRule) {
+ RefPtr<FontFaceImpl> obj = new FontFaceImpl(aOwner, aFontFaceSet);
+ obj->mRule = aRule;
+ obj->mSourceType = eSourceType_FontFaceRule;
+ obj->mInFontFaceSet = true;
+ return obj.forget();
+}
+
+void FontFaceImpl::InitializeSourceURL(const nsACString& aURL) {
+ MOZ_ASSERT(mOwner);
+ mSourceType = eSourceType_URLs;
+
+ IgnoredErrorResult rv;
+ SetDescriptor(eCSSFontDesc_Src, aURL, rv);
+ if (rv.Failed()) {
+ mOwner->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
+ SetStatus(FontFaceLoadStatus::Error);
+ }
+}
+
+void FontFaceImpl::InitializeSourceBuffer(uint8_t* aBuffer, uint32_t aLength) {
+ MOZ_ASSERT(mOwner);
+ MOZ_ASSERT(!mBufferSource);
+ mSourceType = FontFaceImpl::eSourceType_Buffer;
+
+ if (aBuffer) {
+ mBufferSource = new FontFaceBufferSource(aBuffer, aLength);
+ }
+
+ SetStatus(FontFaceLoadStatus::Loading);
+ DoLoad();
+}
+
+void FontFaceImpl::GetFamily(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_Family, aResult);
+}
+
+void FontFaceImpl::SetFamily(const nsACString& aValue, ErrorResult& aRv) {
+ mFontFaceSet->FlushUserFontSet();
+ if (SetDescriptor(eCSSFontDesc_Family, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetStyle(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_Style, aResult);
+}
+
+void FontFaceImpl::SetStyle(const nsACString& aValue, ErrorResult& aRv) {
+ if (SetDescriptor(eCSSFontDesc_Style, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetWeight(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_Weight, aResult);
+}
+
+void FontFaceImpl::SetWeight(const nsACString& aValue, ErrorResult& aRv) {
+ mFontFaceSet->FlushUserFontSet();
+ if (SetDescriptor(eCSSFontDesc_Weight, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetStretch(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_Stretch, aResult);
+}
+
+void FontFaceImpl::SetStretch(const nsACString& aValue, ErrorResult& aRv) {
+ mFontFaceSet->FlushUserFontSet();
+ if (SetDescriptor(eCSSFontDesc_Stretch, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetUnicodeRange(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_UnicodeRange, aResult);
+}
+
+void FontFaceImpl::SetUnicodeRange(const nsACString& aValue, ErrorResult& aRv) {
+ mFontFaceSet->FlushUserFontSet();
+ if (SetDescriptor(eCSSFontDesc_UnicodeRange, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetVariant(nsACString& aResult) {
+ // XXX Just expose the font-variant descriptor as "normal" until we
+ // support it properly (bug 1055385).
+ aResult.AssignLiteral("normal");
+}
+
+void FontFaceImpl::SetVariant(const nsACString& aValue, ErrorResult& aRv) {
+ // XXX Ignore assignments to variant until we support font-variant
+ // descriptors (bug 1055385).
+}
+
+void FontFaceImpl::GetFeatureSettings(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_FontFeatureSettings, aResult);
+}
+
+void FontFaceImpl::SetFeatureSettings(const nsACString& aValue,
+ ErrorResult& aRv) {
+ mFontFaceSet->FlushUserFontSet();
+ if (SetDescriptor(eCSSFontDesc_FontFeatureSettings, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetVariationSettings(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_FontVariationSettings, aResult);
+}
+
+void FontFaceImpl::SetVariationSettings(const nsACString& aValue,
+ ErrorResult& aRv) {
+ mFontFaceSet->FlushUserFontSet();
+ if (SetDescriptor(eCSSFontDesc_FontVariationSettings, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetDisplay(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_Display, aResult);
+}
+
+void FontFaceImpl::SetDisplay(const nsACString& aValue, ErrorResult& aRv) {
+ if (SetDescriptor(eCSSFontDesc_Display, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetAscentOverride(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_AscentOverride, aResult);
+}
+
+void FontFaceImpl::SetAscentOverride(const nsACString& aValue,
+ ErrorResult& aRv) {
+ if (SetDescriptor(eCSSFontDesc_AscentOverride, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetDescentOverride(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_DescentOverride, aResult);
+}
+
+void FontFaceImpl::SetDescentOverride(const nsACString& aValue,
+ ErrorResult& aRv) {
+ if (SetDescriptor(eCSSFontDesc_DescentOverride, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetLineGapOverride(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_LineGapOverride, aResult);
+}
+
+void FontFaceImpl::SetLineGapOverride(const nsACString& aValue,
+ ErrorResult& aRv) {
+ if (SetDescriptor(eCSSFontDesc_LineGapOverride, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::GetSizeAdjust(nsACString& aResult) {
+ GetDesc(eCSSFontDesc_SizeAdjust, aResult);
+}
+
+void FontFaceImpl::SetSizeAdjust(const nsACString& aValue, ErrorResult& aRv) {
+ if (SetDescriptor(eCSSFontDesc_SizeAdjust, aValue, aRv)) {
+ DescriptorUpdated();
+ }
+}
+
+void FontFaceImpl::DescriptorUpdated() {
+ // If we haven't yet initialized mUserFontEntry, no need to do anything here;
+ // we'll respect the updated descriptor when the time comes to create it.
+ if (!mUserFontEntry) {
+ return;
+ }
+
+ gfxUserFontAttributes attr;
+ RefPtr<gfxUserFontEntry> newEntry;
+ if (GetAttributes(attr)) {
+ newEntry = mFontFaceSet->FindOrCreateUserFontEntryFromFontFace(
+ this, std::move(attr), StyleOrigin::Author);
+ }
+ SetUserFontEntry(newEntry);
+
+ // Behind the scenes, this will actually update the existing entry and return
+ // it, rather than create a new one.
+
+ if (mInFontFaceSet) {
+ mFontFaceSet->MarkUserFontSetDirty();
+ }
+ for (auto& set : mOtherFontFaceSets) {
+ set->MarkUserFontSetDirty();
+ }
+}
+
+FontFaceLoadStatus FontFaceImpl::Status() { return mStatus; }
+
+void FontFaceImpl::Load(ErrorResult& aRv) {
+ mFontFaceSet->FlushUserFontSet();
+
+ // Calling Load on a FontFace constructed with an ArrayBuffer data source,
+ // or on one that is already loading (or has finished loading), has no
+ // effect.
+ if (mSourceType == eSourceType_Buffer ||
+ mStatus != FontFaceLoadStatus::Unloaded) {
+ return;
+ }
+
+ // Calling the user font entry's Load method will end up setting our
+ // status to Loading, but the spec requires us to set it to Loading
+ // here.
+ SetStatus(FontFaceLoadStatus::Loading);
+
+ DoLoad();
+}
+
+gfxUserFontEntry* FontFaceImpl::CreateUserFontEntry() {
+ if (!mUserFontEntry) {
+ MOZ_ASSERT(!HasRule(),
+ "Rule backed FontFace objects should already have a user font "
+ "entry by the time Load() can be called on them");
+
+ gfxUserFontAttributes attr;
+ if (GetAttributes(attr)) {
+ RefPtr<gfxUserFontEntry> newEntry =
+ mFontFaceSet->FindOrCreateUserFontEntryFromFontFace(
+ this, std::move(attr), StyleOrigin::Author);
+ if (newEntry) {
+ SetUserFontEntry(newEntry);
+ }
+ }
+ }
+
+ return mUserFontEntry;
+}
+
+void FontFaceImpl::DoLoad() {
+ if (!CreateUserFontEntry()) {
+ return;
+ }
+ mUserFontEntry->Load();
+}
+
+void FontFaceImpl::SetStatus(FontFaceLoadStatus aStatus) {
+ gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked();
+
+ if (mStatus == aStatus) {
+ return;
+ }
+
+ if (aStatus < mStatus) {
+ // We're being asked to go backwards in status! Normally, this shouldn't
+ // happen. But it can if the FontFace had a user font entry that had
+ // loaded, but then was given a new one by FontFaceSet::InsertRuleFontFace
+ // if we used a local() rule. For now, just ignore the request to
+ // go backwards in status.
+ return;
+ }
+
+ mStatus = aStatus;
+
+ if (mInFontFaceSet) {
+ mFontFaceSet->OnFontFaceStatusChanged(this);
+ }
+
+ for (FontFaceSetImpl* otherSet : mOtherFontFaceSets) {
+ otherSet->OnFontFaceStatusChanged(this);
+ }
+
+ UpdateOwnerPromise();
+}
+
+void FontFaceImpl::UpdateOwnerPromise() {
+ if (!mFontFaceSet->IsOnOwningThread()) {
+ mFontFaceSet->DispatchToOwningThread(
+ "FontFaceImpl::UpdateOwnerPromise",
+ [self = RefPtr{this}] { self->UpdateOwnerPromise(); });
+ return;
+ }
+
+ if (NS_WARN_IF(!mOwner)) {
+ return;
+ }
+
+ if (mStatus == FontFaceLoadStatus::Loaded) {
+ mOwner->MaybeResolve();
+ } else if (mStatus == FontFaceLoadStatus::Error) {
+ if (mSourceType == eSourceType_Buffer) {
+ mOwner->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
+ } else {
+ mOwner->MaybeReject(NS_ERROR_DOM_NETWORK_ERR);
+ }
+ }
+}
+
+// Boolean result indicates whether the value of the descriptor was actually
+// changed.
+bool FontFaceImpl::SetDescriptor(nsCSSFontDesc aFontDesc,
+ const nsACString& aValue, ErrorResult& aRv) {
+ // FIXME We probably don't need to distinguish between this anymore
+ // since we have common backend now.
+ NS_ASSERTION(!HasRule(), "we don't handle rule backed FontFace objects yet");
+ if (HasRule()) {
+ return false;
+ }
+
+ RefPtr<URLExtraData> url = mFontFaceSet->GetURLExtraData();
+ if (NS_WARN_IF(!url)) {
+ // This should only happen on worker threads, where we failed to initialize
+ // the worker before it was shutdown.
+ aRv.ThrowInvalidStateError("Missing URLExtraData");
+ return false;
+ }
+
+ // FIXME(heycam): Should not allow modification of FontFaces that are
+ // CSS-connected and whose rule is read only.
+ bool changed;
+ if (!Servo_FontFaceRule_SetDescriptor(GetData(), aFontDesc, &aValue, url,
+ &changed)) {
+ aRv.ThrowSyntaxError("Invalid font descriptor");
+ return false;
+ }
+
+ if (!changed) {
+ return false;
+ }
+
+ if (aFontDesc == eCSSFontDesc_UnicodeRange) {
+ mUnicodeRangeDirty = true;
+ }
+
+ return true;
+}
+
+bool FontFaceImpl::SetDescriptors(const nsACString& aFamily,
+ const FontFaceDescriptors& aDescriptors) {
+ MOZ_ASSERT(!HasRule());
+ MOZ_ASSERT(!mDescriptors);
+
+ mDescriptors = Servo_FontFaceRule_CreateEmpty().Consume();
+
+ // Helper to call SetDescriptor and return true on success, false on failure.
+ auto setDesc = [=](nsCSSFontDesc aDesc, const nsACString& aVal) -> bool {
+ IgnoredErrorResult rv;
+ SetDescriptor(aDesc, aVal, rv);
+ return !rv.Failed();
+ };
+
+ // Parse all of the mDescriptors in aInitializer, which are the values
+ // we got from the JS constructor.
+ if (!setDesc(eCSSFontDesc_Family, aFamily) ||
+ !setDesc(eCSSFontDesc_Style, aDescriptors.mStyle) ||
+ !setDesc(eCSSFontDesc_Weight, aDescriptors.mWeight) ||
+ !setDesc(eCSSFontDesc_Stretch, aDescriptors.mStretch) ||
+ !setDesc(eCSSFontDesc_UnicodeRange, aDescriptors.mUnicodeRange) ||
+ !setDesc(eCSSFontDesc_FontFeatureSettings,
+ aDescriptors.mFeatureSettings) ||
+ (StaticPrefs::layout_css_font_variations_enabled() &&
+ !setDesc(eCSSFontDesc_FontVariationSettings,
+ aDescriptors.mVariationSettings)) ||
+ !setDesc(eCSSFontDesc_Display, aDescriptors.mDisplay) ||
+ ((!setDesc(eCSSFontDesc_AscentOverride, aDescriptors.mAscentOverride) ||
+ !setDesc(eCSSFontDesc_DescentOverride, aDescriptors.mDescentOverride) ||
+ !setDesc(eCSSFontDesc_LineGapOverride,
+ aDescriptors.mLineGapOverride))) ||
+ (StaticPrefs::layout_css_size_adjust_enabled() &&
+ !setDesc(eCSSFontDesc_SizeAdjust, aDescriptors.mSizeAdjust))) {
+ // XXX Handle font-variant once we support it (bug 1055385).
+
+ // If any of the descriptors failed to parse, none of them should be set
+ // on the FontFace.
+ mDescriptors = Servo_FontFaceRule_CreateEmpty().Consume();
+
+ if (mOwner) {
+ mOwner->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ SetStatus(FontFaceLoadStatus::Error);
+ return false;
+ }
+
+ return true;
+}
+
+void FontFaceImpl::GetDesc(nsCSSFontDesc aDescID, nsACString& aResult) const {
+ aResult.Truncate();
+ Servo_FontFaceRule_GetDescriptorCssText(GetData(), aDescID, &aResult);
+
+ // Fill in a default value for missing descriptors.
+ if (aResult.IsEmpty()) {
+ if (aDescID == eCSSFontDesc_UnicodeRange) {
+ aResult.AssignLiteral("U+0-10FFFF");
+ } else if (aDescID == eCSSFontDesc_Display) {
+ aResult.AssignLiteral("auto");
+ } else if (aDescID != eCSSFontDesc_Family && aDescID != eCSSFontDesc_Src) {
+ aResult.AssignLiteral("normal");
+ }
+ }
+}
+
+void FontFaceImpl::SetUserFontEntry(gfxUserFontEntry* aEntry) {
+ AssertIsOnOwningThread();
+
+ if (mUserFontEntry == aEntry) {
+ return;
+ }
+
+ if (mUserFontEntry) {
+ mUserFontEntry->RemoveFontFace(this);
+ }
+
+ auto* entry = static_cast<Entry*>(aEntry);
+ if (entry) {
+ entry->AddFontFace(this);
+ }
+
+ mUserFontEntry = entry;
+
+ if (!mUserFontEntry) {
+ return;
+ }
+
+ MOZ_ASSERT(mUserFontEntry->HasUserFontSet(mFontFaceSet),
+ "user font entry must be associated with the same user font set "
+ "as the FontFace");
+
+ // Our newly assigned user font entry might be in the process of or
+ // finished loading, so set our status accordingly. But only do so
+ // if we're not going "backwards" in status, which could otherwise
+ // happen in this case:
+ //
+ // new FontFace("ABC", "url(x)").load();
+ //
+ // where the SetUserFontEntry call (from the after-initialization
+ // DoLoad call) comes after the author's call to load(), which set mStatus
+ // to Loading.
+ FontFaceLoadStatus newStatus = LoadStateToStatus(mUserFontEntry->LoadState());
+ if (newStatus > mStatus) {
+ SetStatus(newStatus);
+ }
+}
+
+bool FontFaceImpl::GetAttributes(gfxUserFontAttributes& aAttr) {
+ StyleLockedFontFaceRule* data = GetData();
+ if (!data) {
+ return false;
+ }
+
+ nsAtom* fontFamily = Servo_FontFaceRule_GetFamilyName(data);
+ if (!fontFamily) {
+ return false;
+ }
+
+ aAttr.mFamilyName = nsAtomCString(fontFamily);
+
+ StyleComputedFontWeightRange weightRange;
+ if (Servo_FontFaceRule_GetFontWeight(data, &weightRange)) {
+ aAttr.mRangeFlags &= ~gfxFontEntry::RangeFlags::eAutoWeight;
+ aAttr.mWeight = WeightRange(FontWeight::FromFloat(weightRange._0),
+ FontWeight::FromFloat(weightRange._1));
+ }
+
+ StyleComputedFontStretchRange stretchRange;
+ if (Servo_FontFaceRule_GetFontStretch(data, &stretchRange)) {
+ aAttr.mRangeFlags &= ~gfxFontEntry::RangeFlags::eAutoStretch;
+ aAttr.mStretch = StretchRange(stretchRange._0, stretchRange._1);
+ }
+
+ auto styleDesc = StyleComputedFontStyleDescriptor::Normal();
+ if (Servo_FontFaceRule_GetFontStyle(data, &styleDesc)) {
+ aAttr.mRangeFlags &= ~gfxFontEntry::RangeFlags::eAutoSlantStyle;
+ switch (styleDesc.tag) {
+ case StyleComputedFontStyleDescriptor::Tag::Normal:
+ aAttr.mStyle = SlantStyleRange(FontSlantStyle::NORMAL);
+ break;
+ case StyleComputedFontStyleDescriptor::Tag::Italic:
+ aAttr.mStyle = SlantStyleRange(FontSlantStyle::ITALIC);
+ break;
+ case StyleComputedFontStyleDescriptor::Tag::Oblique:
+ aAttr.mStyle = SlantStyleRange(
+ FontSlantStyle::FromFloat(styleDesc.AsOblique()._0),
+ FontSlantStyle::FromFloat(styleDesc.AsOblique()._1));
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhandled tag");
+ }
+ }
+
+ StylePercentage ascent{0};
+ if (Servo_FontFaceRule_GetAscentOverride(data, &ascent)) {
+ aAttr.mAscentOverride = ascent._0;
+ }
+
+ StylePercentage descent{0};
+ if (Servo_FontFaceRule_GetDescentOverride(data, &descent)) {
+ aAttr.mDescentOverride = descent._0;
+ }
+
+ StylePercentage lineGap{0};
+ if (Servo_FontFaceRule_GetLineGapOverride(data, &lineGap)) {
+ aAttr.mLineGapOverride = lineGap._0;
+ }
+
+ StylePercentage sizeAdjust;
+ if (Servo_FontFaceRule_GetSizeAdjust(data, &sizeAdjust)) {
+ aAttr.mSizeAdjust = sizeAdjust._0;
+ }
+
+ StyleFontLanguageOverride langOverride;
+ if (Servo_FontFaceRule_GetFontLanguageOverride(data, &langOverride)) {
+ aAttr.mLanguageOverride = langOverride._0;
+ }
+
+ Servo_FontFaceRule_GetFontDisplay(data, &aAttr.mFontDisplay);
+ Servo_FontFaceRule_GetFeatureSettings(data, &aAttr.mFeatureSettings);
+ Servo_FontFaceRule_GetVariationSettings(data, &aAttr.mVariationSettings);
+ Servo_FontFaceRule_GetSources(data, &aAttr.mSources);
+ aAttr.mUnicodeRanges = GetUnicodeRangeAsCharacterMap();
+ return true;
+}
+
+bool FontFaceImpl::HasLocalSrc() const {
+ AutoTArray<StyleFontFaceSourceListComponent, 8> components;
+ Servo_FontFaceRule_GetSources(GetData(), &components);
+ for (auto& component : components) {
+ if (component.tag == StyleFontFaceSourceListComponent::Tag::Local) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsAtom* FontFaceImpl::GetFamilyName() const {
+ return Servo_FontFaceRule_GetFamilyName(GetData());
+}
+
+void FontFaceImpl::DisconnectFromRule() {
+ MOZ_ASSERT(HasRule());
+
+ // Make a copy of the descriptors.
+ mDescriptors = Servo_FontFaceRule_Clone(mRule).Consume();
+ mRule = nullptr;
+ mInFontFaceSet = false;
+}
+
+bool FontFaceImpl::HasFontData() const {
+ return mSourceType == eSourceType_Buffer && mBufferSource;
+}
+
+already_AddRefed<gfxFontFaceBufferSource> FontFaceImpl::TakeBufferSource() {
+ MOZ_ASSERT(mBufferSource);
+ return mBufferSource.forget();
+}
+
+bool FontFaceImpl::IsInFontFaceSet(FontFaceSetImpl* aFontFaceSet) const {
+ if (mFontFaceSet == aFontFaceSet) {
+ return mInFontFaceSet;
+ }
+ return mOtherFontFaceSets.Contains(aFontFaceSet);
+}
+
+void FontFaceImpl::AddFontFaceSet(FontFaceSetImpl* aFontFaceSet) {
+ MOZ_ASSERT(!IsInFontFaceSet(aFontFaceSet));
+
+ if (mFontFaceSet == aFontFaceSet) {
+ mInFontFaceSet = true;
+ } else {
+ mOtherFontFaceSets.AppendElement(aFontFaceSet);
+ }
+}
+
+void FontFaceImpl::RemoveFontFaceSet(FontFaceSetImpl* aFontFaceSet) {
+ MOZ_ASSERT(IsInFontFaceSet(aFontFaceSet));
+
+ if (mFontFaceSet == aFontFaceSet) {
+ mInFontFaceSet = false;
+ } else {
+ mOtherFontFaceSets.RemoveElement(aFontFaceSet);
+ }
+
+ // The caller should be holding a strong reference to the FontFaceSetImpl.
+ if (mUserFontEntry) {
+ mUserFontEntry->CheckUserFontSet();
+ }
+}
+
+gfxCharacterMap* FontFaceImpl::GetUnicodeRangeAsCharacterMap() {
+ if (!mUnicodeRangeDirty) {
+ return mUnicodeRange;
+ }
+
+ size_t len;
+ const StyleUnicodeRange* rangesPtr =
+ Servo_FontFaceRule_GetUnicodeRanges(GetData(), &len);
+
+ Span<const StyleUnicodeRange> ranges(rangesPtr, len);
+ if (!ranges.IsEmpty()) {
+ RefPtr<gfxCharacterMap> charMap = new gfxCharacterMap();
+ for (auto& range : ranges) {
+ charMap->SetRange(range.start, range.end);
+ }
+ charMap->Compact();
+ // As it's common for multiple font resources to have the same
+ // unicode-range list, look for an existing copy of this map to share,
+ // or add this one to the sharing cache if not already present.
+ mUnicodeRange =
+ gfxPlatformFontList::PlatformFontList()->FindCharMap(charMap);
+ } else {
+ mUnicodeRange = nullptr;
+ }
+
+ mUnicodeRangeDirty = false;
+ return mUnicodeRange;
+}
+
+// -- FontFaceImpl::Entry
+// --------------------------------------------------------
+
+/* virtual */
+void FontFaceImpl::Entry::SetLoadState(UserFontLoadState aLoadState) {
+ gfxUserFontEntry::SetLoadState(aLoadState);
+ FontFaceLoadStatus status = LoadStateToStatus(aLoadState);
+
+ nsTArray<RefPtr<FontFaceImpl>> fontFaces;
+ {
+ MutexAutoLock lock(mMutex);
+ fontFaces.SetCapacity(mFontFaces.Length());
+ for (FontFaceImpl* f : mFontFaces) {
+ fontFaces.AppendElement(f);
+ }
+ }
+
+ for (FontFaceImpl* impl : fontFaces) {
+ auto* setImpl = impl->GetPrimaryFontFaceSet();
+ if (setImpl->IsOnOwningThread()) {
+ impl->SetStatus(status);
+ } else {
+ setImpl->DispatchToOwningThread(
+ "FontFaceImpl::Entry::SetLoadState",
+ [self = RefPtr{impl}, status] { self->SetStatus(status); });
+ }
+ }
+}
+
+/* virtual */
+void FontFaceImpl::Entry::GetUserFontSets(
+ nsTArray<RefPtr<gfxUserFontSet>>& aResult) {
+ MutexAutoLock lock(mMutex);
+
+ aResult.Clear();
+
+ if (mFontSet) {
+ aResult.AppendElement(mFontSet);
+ }
+
+ for (FontFaceImpl* f : mFontFaces) {
+ if (f->mInFontFaceSet) {
+ aResult.AppendElement(f->mFontFaceSet);
+ }
+ for (FontFaceSetImpl* s : f->mOtherFontFaceSets) {
+ aResult.AppendElement(s);
+ }
+ }
+
+ // Remove duplicates.
+ aResult.Sort();
+ auto it = std::unique(aResult.begin(), aResult.end());
+ aResult.TruncateLength(it - aResult.begin());
+}
+
+/* virtual */ already_AddRefed<gfxUserFontSet>
+FontFaceImpl::Entry::GetUserFontSet() const {
+ MutexAutoLock lock(mMutex);
+ if (mFontSet) {
+ return do_AddRef(mFontSet);
+ }
+ if (NS_IsMainThread() && mLoadingFontSet) {
+ return do_AddRef(mLoadingFontSet);
+ }
+ return nullptr;
+}
+
+void FontFaceImpl::Entry::CheckUserFontSetLocked() {
+ // If this is the last font containing a strong reference to the set, we need
+ // to clear the reference as there is no longer anything guaranteeing the set
+ // will be kept alive.
+ if (mFontSet) {
+ auto* set = static_cast<FontFaceSetImpl*>(mFontSet);
+ for (FontFaceImpl* f : mFontFaces) {
+ if (f->mFontFaceSet == set || f->mOtherFontFaceSets.Contains(set)) {
+ return;
+ }
+ }
+ }
+
+ // If possible, promote the most recently added FontFace and its owning
+ // FontFaceSetImpl as the primary set.
+ if (!mFontFaces.IsEmpty()) {
+ mFontSet = mFontFaces.LastElement()->mFontFaceSet;
+ } else {
+ mFontSet = nullptr;
+ }
+}
+
+void FontFaceImpl::Entry::FindFontFaceOwners(nsTHashSet<FontFace*>& aOwners) {
+ MutexAutoLock lock(mMutex);
+ for (FontFaceImpl* f : mFontFaces) {
+ if (FontFace* owner = f->GetOwner()) {
+ aOwners.Insert(owner);
+ }
+ }
+}
+
+void FontFaceImpl::Entry::AddFontFace(FontFaceImpl* aFontFace) {
+ MutexAutoLock lock(mMutex);
+ mFontFaces.AppendElement(aFontFace);
+ CheckUserFontSetLocked();
+}
+
+void FontFaceImpl::Entry::RemoveFontFace(FontFaceImpl* aFontFace) {
+ MutexAutoLock lock(mMutex);
+ mFontFaces.RemoveElement(aFontFace);
+ CheckUserFontSetLocked();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/FontFaceImpl.h b/layout/style/FontFaceImpl.h
new file mode 100644
index 0000000000..70c06609e9
--- /dev/null
+++ b/layout/style/FontFaceImpl.h
@@ -0,0 +1,308 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_FontFaceImpl_h
+#define mozilla_dom_FontFaceImpl_h
+
+#include "mozilla/dom/FontFaceBinding.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "gfxUserFontSet.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSValue.h"
+#include "nsTHashSet.h"
+
+class gfxFontFaceBufferSource;
+
+namespace mozilla {
+struct CSSFontFaceDescriptors;
+class PostTraversalTask;
+struct StyleLockedFontFaceRule;
+namespace dom {
+class CSSFontFaceRule;
+class FontFace;
+class FontFaceBufferSource;
+struct FontFaceDescriptors;
+class FontFaceSetImpl;
+class UTF8StringOrArrayBufferOrArrayBufferView;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla::dom {
+
+class FontFaceImpl final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FontFaceImpl)
+
+ friend class mozilla::PostTraversalTask;
+ friend class FontFaceBufferSource;
+ friend class Entry;
+
+ public:
+ class Entry final : public gfxUserFontEntry {
+ friend class FontFaceImpl;
+
+ public:
+ Entry(gfxUserFontSet* aFontSet, nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
+ gfxUserFontAttributes&& aAttr)
+ : gfxUserFontEntry(std::move(aFontFaceSrcList), std::move(aAttr)),
+ mMutex("FontFaceImpl::Entry::mMutex"),
+ mFontSet(aFontSet) {}
+
+ void SetLoadState(UserFontLoadState aLoadState) override;
+ void GetUserFontSets(nsTArray<RefPtr<gfxUserFontSet>>& aResult) override;
+ already_AddRefed<gfxUserFontSet> GetUserFontSet() const override;
+
+ void CheckUserFontSet() {
+ MutexAutoLock lock(mMutex);
+ CheckUserFontSetLocked();
+ }
+
+#ifdef DEBUG
+ bool HasUserFontSet(gfxUserFontSet* aFontSet) const {
+ MutexAutoLock lock(mMutex);
+ return mFontSet == aFontSet;
+ }
+#endif
+
+ void AddFontFace(FontFaceImpl* aOwner);
+ void RemoveFontFace(FontFaceImpl* aOwner);
+ void FindFontFaceOwners(nsTHashSet<FontFace*>& aOwners);
+
+ protected:
+ void CheckUserFontSetLocked() MOZ_REQUIRES(mMutex);
+
+ mutable Mutex mMutex;
+
+ // Font set which owns this entry;
+ gfxUserFontSet* MOZ_NON_OWNING_REF mFontSet MOZ_GUARDED_BY(mMutex);
+
+ // The FontFace objects that use this user font entry. We need to store
+ // an array of these, not just a single pointer, since the user font
+ // cache can return the same entry for different FontFaces that have
+ // the same descriptor values and come from the same origin.
+ AutoTArray<FontFaceImpl*, 1> mFontFaces MOZ_GUARDED_BY(mMutex);
+ };
+
+#ifdef DEBUG
+ void AssertIsOnOwningThread() const;
+#else
+ void AssertIsOnOwningThread() const {}
+#endif
+
+ FontFace* GetOwner() const {
+ AssertIsOnOwningThread();
+ return mOwner;
+ }
+
+ static already_AddRefed<FontFaceImpl> CreateForRule(
+ FontFace* aOwner, FontFaceSetImpl* aFontFaceSet,
+ StyleLockedFontFaceRule* aRule);
+
+ StyleLockedFontFaceRule* GetRule() { return mRule; }
+
+ bool HasLocalSrc() const;
+
+ bool GetAttributes(gfxUserFontAttributes& aAttr);
+ gfxUserFontEntry* CreateUserFontEntry();
+ gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; }
+ void SetUserFontEntry(gfxUserFontEntry* aEntry);
+
+ /**
+ * Returns whether this object is in the specified FontFaceSet.
+ */
+ bool IsInFontFaceSet(FontFaceSetImpl* aFontFaceSet) const;
+
+ void AddFontFaceSet(FontFaceSetImpl* aFontFaceSet);
+ void RemoveFontFaceSet(FontFaceSetImpl* aFontFaceSet);
+
+ FontFaceSetImpl* GetPrimaryFontFaceSet() const { return mFontFaceSet; }
+
+ /**
+ * Gets the family name of the FontFace as a raw string (such as 'Times', as
+ * opposed to GetFamily, which returns a CSS-escaped string, such as
+ * '"Times"'). Returns null if a valid family name was not available.
+ */
+ nsAtom* GetFamilyName() const;
+
+ /**
+ * Returns whether this object is CSS-connected, i.e. reflecting an
+ * @font-face rule.
+ */
+ bool HasRule() const { return mRule; }
+
+ /**
+ * Breaks the connection between this FontFace and its @font-face rule.
+ */
+ void DisconnectFromRule();
+
+ /**
+ * Returns whether there is an ArrayBuffer or ArrayBufferView of font
+ * data.
+ */
+ bool HasFontData() const;
+
+ /**
+ * Takes the gfxFontFaceBufferSource to represent the font data
+ * in this object.
+ */
+ already_AddRefed<gfxFontFaceBufferSource> TakeBufferSource();
+
+ /**
+ * Gets a pointer to and the length of the font data stored in the
+ * ArrayBuffer or ArrayBufferView.
+ */
+ bool GetData(uint8_t*& aBuffer, uint32_t& aLength);
+
+ /**
+ * Returns the value of the unicode-range descriptor as a gfxCharacterMap.
+ */
+ gfxCharacterMap* GetUnicodeRangeAsCharacterMap();
+
+ // Web IDL
+ void GetFamily(nsACString& aResult);
+ void SetFamily(const nsACString& aValue, ErrorResult& aRv);
+ void GetStyle(nsACString& aResult);
+ void SetStyle(const nsACString& aValue, ErrorResult& aRv);
+ void GetWeight(nsACString& aResult);
+ void SetWeight(const nsACString& aValue, ErrorResult& aRv);
+ void GetStretch(nsACString& aResult);
+ void SetStretch(const nsACString& aValue, ErrorResult& aRv);
+ void GetUnicodeRange(nsACString& aResult);
+ void SetUnicodeRange(const nsACString& aValue, ErrorResult& aRv);
+ void GetVariant(nsACString& aResult);
+ void SetVariant(const nsACString& aValue, ErrorResult& aRv);
+ void GetFeatureSettings(nsACString& aResult);
+ void SetFeatureSettings(const nsACString& aValue, ErrorResult& aRv);
+ void GetVariationSettings(nsACString& aResult);
+ void SetVariationSettings(const nsACString& aValue, ErrorResult& aRv);
+ void GetDisplay(nsACString& aResult);
+ void SetDisplay(const nsACString& aValue, ErrorResult& aRv);
+ void GetAscentOverride(nsACString& aResult);
+ void SetAscentOverride(const nsACString& aValue, ErrorResult& aRv);
+ void GetDescentOverride(nsACString& aResult);
+ void SetDescentOverride(const nsACString& aValue, ErrorResult& aRv);
+ void GetLineGapOverride(nsACString& aResult);
+ void SetLineGapOverride(const nsACString& aValue, ErrorResult& aRv);
+ void GetSizeAdjust(nsACString& aResult);
+ void SetSizeAdjust(const nsACString& aValue, ErrorResult& aRv);
+
+ FontFaceLoadStatus Status();
+ void Load(ErrorResult& aRv);
+
+ void Destroy();
+
+ FontFaceImpl(FontFace* aOwner, FontFaceSetImpl* aFontFaceSet);
+
+ void InitializeSourceURL(const nsACString& aURL);
+ void InitializeSourceBuffer(uint8_t* aBuffer, uint32_t aLength);
+
+ /**
+ * Sets all of the descriptor values in mDescriptors using values passed
+ * to the JS constructor.
+ * Returns true on success, false if parsing any descriptor failed.
+ */
+ bool SetDescriptors(const nsACString& aFamily,
+ const FontFaceDescriptors& aDescriptors);
+
+ private:
+ ~FontFaceImpl();
+
+ // Helper function for Load.
+ void DoLoad();
+ void UpdateOwnerPromise();
+
+ // Helper function for the descriptor setter methods.
+ // Returns true if the descriptor was modified, false if descriptor is
+ // unchanged (which may not be an error: check aRv for actual failure).
+ bool SetDescriptor(nsCSSFontDesc aFontDesc, const nsACString& aValue,
+ ErrorResult& aRv);
+
+ /**
+ * Called when a descriptor has been modified, so font-face sets can
+ * be told to refresh.
+ */
+ void DescriptorUpdated();
+
+ /**
+ * Sets the current loading status.
+ */
+ void SetStatus(FontFaceLoadStatus aStatus);
+
+ void GetDesc(nsCSSFontDesc aDescID, nsACString& aResult) const;
+
+ StyleLockedFontFaceRule* GetData() const {
+ AssertIsOnOwningThread();
+ return HasRule() ? mRule : mDescriptors;
+ }
+
+ /**
+ * Returns and takes ownership of the buffer storing the font data.
+ */
+ void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength);
+
+ FontFace* MOZ_NON_OWNING_REF mOwner;
+
+ // The @font-face rule this FontFace object is reflecting, if it is a
+ // rule backed FontFace.
+ RefPtr<StyleLockedFontFaceRule> mRule;
+
+ // The FontFace object's user font entry. This is initially null, but is set
+ // during FontFaceSet::UpdateRules and when a FontFace is explicitly loaded.
+ RefPtr<Entry> mUserFontEntry;
+
+ // The current load status of the font represented by this FontFace.
+ // Note that we can't just reflect the value of the gfxUserFontEntry's
+ // status, since the spec sometimes requires us to go through the event
+ // loop before updating the status, rather than doing it immediately.
+ FontFaceLoadStatus mStatus;
+
+ // Represents where a FontFace's data is coming from.
+ enum SourceType {
+ eSourceType_FontFaceRule = 1,
+ eSourceType_URLs,
+ eSourceType_Buffer
+ };
+
+ // Where the font data for this FontFace is coming from.
+ SourceType mSourceType;
+
+ // If the FontFace was constructed with an ArrayBuffer(View), this is a
+ // copy of the data from it.
+ RefPtr<FontFaceBufferSource> mBufferSource;
+
+ // The values corresponding to the font face descriptors, if we are not
+ // a rule backed FontFace object. For rule backed objects, we use
+ // the descriptors stored in mRule.
+ // FIXME This should hold a unique ptr to just the descriptors inside,
+ // so that we don't need to create a rule for it and don't need to
+ // assign a fake line number and column number. See bug 1450904.
+ RefPtr<StyleLockedFontFaceRule> mDescriptors;
+
+ // The value of the unicode-range descriptor as a gfxCharacterMap. Valid
+ // only when mUnicodeRangeDirty is false.
+ RefPtr<gfxCharacterMap> mUnicodeRange;
+
+ // The primary FontFaceSet this FontFace is associated with,
+ // regardless of whether it is currently "in" the set.
+ RefPtr<FontFaceSetImpl> mFontFaceSet;
+
+ // Other FontFaceSets (apart from mFontFaceSet) that this FontFace
+ // appears in.
+ nsTArray<RefPtr<FontFaceSetImpl>> mOtherFontFaceSets;
+
+ // Whether mUnicodeRange needs to be rebuilt before being returned from
+ // GetUnicodeRangeAsCharacterMap.
+ bool mUnicodeRangeDirty;
+
+ // Whether this FontFace appears in mFontFaceSet.
+ bool mInFontFaceSet;
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_FontFaceImpl_h)
diff --git a/layout/style/FontFaceSet.cpp b/layout/style/FontFaceSet.cpp
new file mode 100644
index 0000000000..f350e5210a
--- /dev/null
+++ b/layout/style/FontFaceSet.cpp
@@ -0,0 +1,487 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FontFaceSet.h"
+
+#include "gfxFontConstants.h"
+#include "gfxFontSrcPrincipal.h"
+#include "gfxFontSrcURI.h"
+#include "gfxFontUtils.h"
+#include "FontPreloader.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/CSSFontFaceRule.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/FontFaceImpl.h"
+#include "mozilla/dom/FontFaceSetBinding.h"
+#include "mozilla/dom/FontFaceSetDocumentImpl.h"
+#include "mozilla/dom/FontFaceSetWorkerImpl.h"
+#include "mozilla/dom/FontFaceSetIterator.h"
+#include "mozilla/dom/FontFaceSetLoadEvent.h"
+#include "mozilla/dom/FontFaceSetLoadEventBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoCSSParser.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/LoadInfo.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsDeviceContext.h"
+#include "nsFontFaceLoader.h"
+#include "nsIConsoleService.h"
+#include "nsIContentPolicy.h"
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "nsILoadContext.h"
+#include "nsINetworkPredictor.h"
+#include "nsIPrincipal.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "nsUTF8Utils.h"
+#include "nsDOMNavigationTiming.h"
+#include "ReferrerInfo.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FontFaceSet)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FontFaceSet,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mImpl->GetDocument());
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReady);
+ for (size_t i = 0; i < tmp->mRuleFaces.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleFaces[i].mFontFace);
+ }
+ for (size_t i = 0; i < tmp->mNonRuleFaces.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNonRuleFaces[i].mFontFace);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FontFaceSet,
+ DOMEventTargetHelper)
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mReady);
+ for (size_t i = 0; i < tmp->mRuleFaces.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRuleFaces[i].mFontFace);
+ }
+ for (size_t i = 0; i < tmp->mNonRuleFaces.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNonRuleFaces[i].mFontFace);
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(FontFaceSet, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(FontFaceSet, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FontFaceSet)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+FontFaceSet::FontFaceSet(nsIGlobalObject* aParent)
+ : DOMEventTargetHelper(aParent) {}
+
+FontFaceSet::~FontFaceSet() {
+ // Assert that we don't drop any FontFaceSet objects during a Servo traversal,
+ // since PostTraversalTask objects can hold raw pointers to FontFaceSets.
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+
+ Destroy();
+}
+
+/* static */ already_AddRefed<FontFaceSet> FontFaceSet::CreateForDocument(
+ dom::Document* aDocument) {
+ RefPtr<FontFaceSet> set = new FontFaceSet(aDocument->GetScopeObject());
+ RefPtr<FontFaceSetDocumentImpl> impl =
+ new FontFaceSetDocumentImpl(set, aDocument);
+ set->mImpl = impl;
+ impl->Initialize();
+ return set.forget();
+}
+
+/* static */ already_AddRefed<FontFaceSet> FontFaceSet::CreateForWorker(
+ nsIGlobalObject* aParent, WorkerPrivate* aWorkerPrivate) {
+ RefPtr<FontFaceSet> set = new FontFaceSet(aParent);
+ RefPtr<FontFaceSetWorkerImpl> impl = new FontFaceSetWorkerImpl(set);
+ set->mImpl = impl;
+ if (NS_WARN_IF(!impl->Initialize(aWorkerPrivate))) {
+ return nullptr;
+ }
+ return set.forget();
+}
+
+JSObject* FontFaceSet::WrapObject(JSContext* aContext,
+ JS::Handle<JSObject*> aGivenProto) {
+ return FontFaceSet_Binding::Wrap(aContext, this, aGivenProto);
+}
+
+void FontFaceSet::Destroy() { mImpl->Destroy(); }
+
+already_AddRefed<Promise> FontFaceSet::Load(JSContext* aCx,
+ const nsACString& aFont,
+ const nsAString& aText,
+ ErrorResult& aRv) {
+ FlushUserFontSet();
+
+ nsTArray<RefPtr<Promise>> promises;
+
+ nsTArray<FontFace*> faces;
+ mImpl->FindMatchingFontFaces(aFont, aText, faces, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ for (FontFace* f : faces) {
+ RefPtr<Promise> promise = f->Load(aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ if (!promises.AppendElement(promise, fallible)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ return Promise::All(aCx, promises, aRv);
+}
+
+bool FontFaceSet::Check(const nsACString& aFont, const nsAString& aText,
+ ErrorResult& aRv) {
+ FlushUserFontSet();
+
+ nsTArray<FontFace*> faces;
+ mImpl->FindMatchingFontFaces(aFont, aText, faces, aRv);
+ if (aRv.Failed()) {
+ return false;
+ }
+
+ for (FontFace* f : faces) {
+ if (f->Status() != FontFaceLoadStatus::Loaded) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool FontFaceSet::ReadyPromiseIsPending() const {
+ return mReady ? mReady->State() == Promise::PromiseState::Pending
+ : !mResolveLazilyCreatedReadyPromise;
+}
+
+Promise* FontFaceSet::GetReady(ErrorResult& aRv) {
+ mImpl->EnsureReady();
+
+ if (!mReady) {
+ nsCOMPtr<nsIGlobalObject> global = GetParentObject();
+ mReady = Promise::Create(global, aRv);
+ if (!mReady) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ if (mResolveLazilyCreatedReadyPromise) {
+ mReady->MaybeResolve(this);
+ mResolveLazilyCreatedReadyPromise = false;
+ }
+ }
+
+ return mReady;
+}
+
+FontFaceSetLoadStatus FontFaceSet::Status() { return mImpl->Status(); }
+
+#ifdef DEBUG
+bool FontFaceSet::HasRuleFontFace(FontFace* aFontFace) {
+ for (size_t i = 0; i < mRuleFaces.Length(); i++) {
+ if (mRuleFaces[i].mFontFace == aFontFace) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+void FontFaceSet::Add(FontFace& aFontFace, ErrorResult& aRv) {
+ FlushUserFontSet();
+
+ FontFaceImpl* fontImpl = aFontFace.GetImpl();
+ MOZ_ASSERT(fontImpl);
+
+ if (!mImpl->Add(fontImpl, aRv)) {
+ return;
+ }
+
+ MOZ_ASSERT(!aRv.Failed());
+
+#ifdef DEBUG
+ for (const FontFaceRecord& rec : mNonRuleFaces) {
+ MOZ_ASSERT(rec.mFontFace != &aFontFace,
+ "FontFace should not occur in mNonRuleFaces twice");
+ }
+#endif
+
+ FontFaceRecord* rec = mNonRuleFaces.AppendElement();
+ rec->mFontFace = &aFontFace;
+ rec->mOrigin = Nothing();
+ rec->mLoadEventShouldFire =
+ fontImpl->Status() == FontFaceLoadStatus::Unloaded ||
+ fontImpl->Status() == FontFaceLoadStatus::Loading;
+}
+
+void FontFaceSet::Clear() {
+ nsTArray<FontFaceRecord> oldRecords = std::move(mNonRuleFaces);
+ mImpl->Clear();
+}
+
+bool FontFaceSet::Delete(FontFace& aFontFace) {
+ // Hold onto a strong reference to make sure that when we remove FontFace from
+ // the list, the FontFaceImpl does not get freed right away. We need to check
+ // the FontFaceSetImpl first.
+ RefPtr<FontFaceImpl> fontImpl = aFontFace.GetImpl();
+ MOZ_ASSERT(fontImpl);
+
+ // Ensure that we remove from mNonRuleFaces first. This is important so that
+ // when we check to see if all of the fonts have finished loading, the list in
+ // FontFaceSet and FontFaceSetImpl match.
+ bool removed = false;
+ for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
+ if (mNonRuleFaces[i].mFontFace == &aFontFace) {
+ mNonRuleFaces.RemoveElementAt(i);
+ removed = true;
+ break;
+ }
+ }
+
+ if (!mImpl->Delete(fontImpl)) {
+ MOZ_ASSERT(!removed, "Missing rule present in Impl!");
+ } else {
+ MOZ_ASSERT(removed, "Rule present but missing in Impl!");
+ }
+
+ return removed;
+}
+
+bool FontFaceSet::HasAvailableFontFace(FontFace* aFontFace) {
+ return aFontFace->GetImpl()->IsInFontFaceSet(mImpl);
+}
+
+bool FontFaceSet::Has(FontFace& aFontFace) {
+ FlushUserFontSet();
+
+ return HasAvailableFontFace(&aFontFace);
+}
+
+FontFace* FontFaceSet::GetFontFaceAt(uint32_t aIndex) {
+ FlushUserFontSet();
+
+ if (aIndex < mRuleFaces.Length()) {
+ auto& entry = mRuleFaces[aIndex];
+ if (entry.mOrigin.value() != StyleOrigin::Author) {
+ return nullptr;
+ }
+ return entry.mFontFace;
+ }
+
+ aIndex -= mRuleFaces.Length();
+ if (aIndex < mNonRuleFaces.Length()) {
+ return mNonRuleFaces[aIndex].mFontFace;
+ }
+
+ return nullptr;
+}
+
+uint32_t FontFaceSet::Size() {
+ FlushUserFontSet();
+
+ // Web IDL objects can only expose array index properties up to INT32_MAX.
+
+ size_t total = mNonRuleFaces.Length();
+ for (const auto& entry : mRuleFaces) {
+ if (entry.mOrigin.value() == StyleOrigin::Author) {
+ ++total;
+ }
+ }
+ return std::min<size_t>(total, INT32_MAX);
+}
+
+uint32_t FontFaceSet::SizeIncludingNonAuthorOrigins() {
+ FlushUserFontSet();
+
+ // Web IDL objects can only expose array index properties up to INT32_MAX.
+
+ size_t total = mRuleFaces.Length() + mNonRuleFaces.Length();
+ return std::min<size_t>(total, INT32_MAX);
+}
+
+already_AddRefed<FontFaceSetIterator> FontFaceSet::Entries() {
+ RefPtr<FontFaceSetIterator> it = new FontFaceSetIterator(this, true);
+ return it.forget();
+}
+
+already_AddRefed<FontFaceSetIterator> FontFaceSet::Values() {
+ RefPtr<FontFaceSetIterator> it = new FontFaceSetIterator(this, false);
+ return it.forget();
+}
+
+void FontFaceSet::ForEach(JSContext* aCx, FontFaceSetForEachCallback& aCallback,
+ JS::Handle<JS::Value> aThisArg, ErrorResult& aRv) {
+ JS::Rooted<JS::Value> thisArg(aCx, aThisArg);
+ for (size_t i = 0; i < SizeIncludingNonAuthorOrigins(); i++) {
+ RefPtr<FontFace> face = GetFontFaceAt(i);
+ if (!face) {
+ // The font at index |i| is a non-Author origin font, which we shouldn't
+ // expose per spec.
+ continue;
+ }
+ aCallback.Call(thisArg, *face, *face, *this, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+}
+
+bool FontFaceSet::UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules) {
+ // The impl object handles the callbacks for recreating the mRulesFaces array.
+ nsTArray<FontFaceRecord> oldRecords = std::move(mRuleFaces);
+ return mImpl->UpdateRules(aRules);
+}
+
+void FontFaceSet::InsertRuleFontFace(FontFace* aFontFace, StyleOrigin aOrigin) {
+ MOZ_ASSERT(!HasRuleFontFace(aFontFace));
+
+ FontFaceRecord* rec = mRuleFaces.AppendElement();
+ rec->mFontFace = aFontFace;
+ rec->mOrigin = Some(aOrigin);
+ rec->mLoadEventShouldFire =
+ aFontFace->Status() == FontFaceLoadStatus::Unloaded ||
+ aFontFace->Status() == FontFaceLoadStatus::Loading;
+}
+
+void FontFaceSet::DidRefresh() { mImpl->CheckLoadingFinished(); }
+
+void FontFaceSet::DispatchLoadingEventAndReplaceReadyPromise() {
+ gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked();
+
+ if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) {
+ // See comments in Gecko_GetFontMetrics.
+ //
+ // We can't just dispatch the runnable below if we're not on the main
+ // thread, since it needs to take a strong reference to the FontFaceSet,
+ // and being a DOM object, FontFaceSet doesn't support thread-safe
+ // refcounting. (Also, the Promise object creation must be done on
+ // the main thread.)
+ set->AppendTask(
+ PostTraversalTask::DispatchLoadingEventAndReplaceReadyPromise(this));
+ return;
+ }
+
+ (new AsyncEventDispatcher(this, u"loading"_ns, CanBubble::eNo))
+ ->PostDOMEvent();
+
+ if (mReady && mReady->State() != Promise::PromiseState::Pending) {
+ if (GetParentObject()) {
+ ErrorResult rv;
+ mReady = Promise::Create(GetParentObject(), rv);
+ }
+ }
+
+ // We may previously have been in a state where all fonts had finished
+ // loading and we'd set mResolveLazilyCreatedReadyPromise to make sure that
+ // if we lazily create mReady for a consumer that we resolve it before
+ // returning it. We're now loading fonts, so we need to clear that flag.
+ mResolveLazilyCreatedReadyPromise = false;
+}
+
+void FontFaceSet::MaybeResolve() {
+ if (mReady) {
+ mReady->MaybeResolve(this);
+ } else {
+ mResolveLazilyCreatedReadyPromise = true;
+ }
+
+ // Now dispatch the loadingdone/loadingerror events.
+ nsTArray<OwningNonNull<FontFace>> loaded;
+ nsTArray<OwningNonNull<FontFace>> failed;
+
+ auto checkStatus = [&](nsTArray<FontFaceRecord>& faces) -> void {
+ for (auto& face : faces) {
+ if (!face.mLoadEventShouldFire) {
+ continue;
+ }
+ FontFace* f = face.mFontFace;
+ switch (f->Status()) {
+ case FontFaceLoadStatus::Unloaded:
+ break;
+ case FontFaceLoadStatus::Loaded:
+ loaded.AppendElement(*f);
+ face.mLoadEventShouldFire = false;
+ break;
+ case FontFaceLoadStatus::Error:
+ failed.AppendElement(*f);
+ face.mLoadEventShouldFire = false;
+ break;
+ case FontFaceLoadStatus::Loading:
+ // We should've returned above at MightHavePendingFontLoads()!
+ case FontFaceLoadStatus::EndGuard_:
+ MOZ_ASSERT_UNREACHABLE("unexpected FontFaceLoadStatus");
+ break;
+ }
+ }
+ };
+
+ checkStatus(mRuleFaces);
+ checkStatus(mNonRuleFaces);
+
+ DispatchLoadingFinishedEvent(u"loadingdone"_ns, std::move(loaded));
+
+ if (!failed.IsEmpty()) {
+ DispatchLoadingFinishedEvent(u"loadingerror"_ns, std::move(failed));
+ }
+}
+
+void FontFaceSet::DispatchLoadingFinishedEvent(
+ const nsAString& aType, nsTArray<OwningNonNull<FontFace>>&& aFontFaces) {
+ FontFaceSetLoadEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mFontfaces = std::move(aFontFaces);
+ RefPtr<FontFaceSetLoadEvent> event =
+ FontFaceSetLoadEvent::Constructor(this, aType, init);
+ (new AsyncEventDispatcher(this, event.forget()))->PostDOMEvent();
+}
+
+void FontFaceSet::FlushUserFontSet() { mImpl->FlushUserFontSet(); }
+
+void FontFaceSet::RefreshStandardFontLoadPrincipal() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mImpl->RefreshStandardFontLoadPrincipal();
+}
+
+void FontFaceSet::CopyNonRuleFacesTo(FontFaceSet* aFontFaceSet) const {
+ for (const FontFaceRecord& rec : mNonRuleFaces) {
+ IgnoredErrorResult rv;
+ RefPtr<FontFace> f = rec.mFontFace;
+ aFontFaceSet->Add(*f, rv);
+ MOZ_ASSERT(!rv.Failed());
+ }
+}
+
+#undef LOG_ENABLED
+#undef LOG
diff --git a/layout/style/FontFaceSet.h b/layout/style/FontFaceSet.h
new file mode 100644
index 0000000000..35c22ca0f9
--- /dev/null
+++ b/layout/style/FontFaceSet.h
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_FontFaceSet_h
+#define mozilla_dom_FontFaceSet_h
+
+#include "mozilla/dom/FontFace.h"
+#include "mozilla/dom/FontFaceSetBinding.h"
+#include "mozilla/dom/FontFaceSetImpl.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsIDOMEventListener.h"
+
+class nsFontFaceLoader;
+class nsIPrincipal;
+class nsIGlobalObject;
+
+namespace mozilla {
+class PostTraversalTask;
+class SharedFontList;
+namespace dom {
+class Promise;
+class WorkerPrivate;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla::dom {
+
+class FontFaceSet final : public DOMEventTargetHelper {
+ friend class mozilla::PostTraversalTask;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FontFaceSet, DOMEventTargetHelper)
+
+ static already_AddRefed<FontFaceSet> CreateForDocument(
+ dom::Document* aDocument);
+
+ static already_AddRefed<FontFaceSet> CreateForWorker(
+ nsIGlobalObject* aParent, WorkerPrivate* aWorkerPrivate);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ bool UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules);
+
+ /**
+ * Notification method called by the nsPresContext to indicate that the
+ * refresh driver ticked and flushed style and layout.
+ * were just flushed.
+ */
+ void DidRefresh();
+
+ void FlushUserFontSet();
+
+ void RefreshStandardFontLoadPrincipal();
+
+ void CopyNonRuleFacesTo(FontFaceSet* aFontFaceSet) const;
+
+ void CacheFontLoadability() { mImpl->CacheFontLoadability(); }
+
+ FontFaceSetImpl* GetImpl() const { return mImpl; }
+
+ // -- Web IDL --------------------------------------------------------------
+
+ IMPL_EVENT_HANDLER(loading)
+ IMPL_EVENT_HANDLER(loadingdone)
+ IMPL_EVENT_HANDLER(loadingerror)
+ already_AddRefed<dom::Promise> Load(JSContext* aCx, const nsACString& aFont,
+ const nsAString& aText, ErrorResult& aRv);
+ bool Check(const nsACString& aFont, const nsAString& aText, ErrorResult& aRv);
+ dom::Promise* GetReady(ErrorResult& aRv);
+ dom::FontFaceSetLoadStatus Status();
+
+ void Add(FontFace& aFontFace, ErrorResult& aRv);
+ void Clear();
+ bool Delete(FontFace& aFontFace);
+ bool Has(FontFace& aFontFace);
+ /**
+ * This returns the number of Author origin fonts only.
+ * (see also SizeIncludingNonAuthorOrigins() below)
+ */
+ uint32_t Size();
+ already_AddRefed<dom::FontFaceSetIterator> Entries();
+ already_AddRefed<dom::FontFaceSetIterator> Values();
+ MOZ_CAN_RUN_SCRIPT
+ void ForEach(JSContext* aCx, FontFaceSetForEachCallback& aCallback,
+ JS::Handle<JS::Value> aThisArg, ErrorResult& aRv);
+
+ /**
+ * Unlike Size(), this returns the size including non-Author origin fonts.
+ */
+ uint32_t SizeIncludingNonAuthorOrigins();
+
+ void MaybeResolve();
+
+ void DispatchLoadingFinishedEvent(
+ const nsAString& aType, nsTArray<OwningNonNull<FontFace>>&& aFontFaces);
+
+ void DispatchLoadingEventAndReplaceReadyPromise();
+ void DispatchCheckLoadingFinishedAfterDelay();
+
+ // Whether mReady is pending, or would be when created.
+ bool ReadyPromiseIsPending() const;
+
+ void InsertRuleFontFace(FontFace* aFontFace, StyleOrigin aOrigin);
+
+ private:
+ friend mozilla::dom::FontFaceSetIterator; // needs GetFontFaceAt()
+
+ explicit FontFaceSet(nsIGlobalObject* aParent);
+ ~FontFaceSet();
+
+ /**
+ * Returns whether the given FontFace is currently "in" the FontFaceSet.
+ */
+ bool HasAvailableFontFace(FontFace* aFontFace);
+
+ /**
+ * Removes any listeners and observers.
+ */
+ void Destroy();
+
+ /**
+ * Returns the font at aIndex if it's an Author origin font, or nullptr
+ * otherwise.
+ */
+ FontFace* GetFontFaceAt(uint32_t aIndex);
+
+ // Note: if you add new cycle collected objects to FontFaceRecord,
+ // make sure to update FontFaceSet's cycle collection macros
+ // accordingly.
+ struct FontFaceRecord {
+ RefPtr<FontFace> mFontFace;
+ Maybe<StyleOrigin> mOrigin; // only relevant for mRuleFaces entries
+
+ // When true, indicates that when finished loading, the FontFace should be
+ // included in the subsequent loadingdone/loadingerror event fired at the
+ // FontFaceSet.
+ bool mLoadEventShouldFire;
+ };
+
+#ifdef DEBUG
+ bool HasRuleFontFace(FontFace* aFontFace);
+#endif
+
+ // The underlying implementation for FontFaceSet.
+ RefPtr<FontFaceSetImpl> mImpl;
+
+ // A Promise that is fulfilled once all of the FontFace objects
+ // in mRuleFaces and mNonRuleFaces that started or were loading at the
+ // time the Promise was created have finished loading. It is rejected if
+ // any of those fonts failed to load. mReady is replaced with
+ // a new Promise object whenever mReady is settled and another
+ // FontFace in mRuleFaces or mNonRuleFaces starts to load.
+ // Note that mReady is created lazily when GetReady() is called.
+ RefPtr<dom::Promise> mReady;
+ // Whether the ready promise must be resolved when it's created.
+ bool mResolveLazilyCreatedReadyPromise = false;
+
+ // The @font-face rule backed FontFace objects in the FontFaceSet.
+ nsTArray<FontFaceRecord> mRuleFaces;
+
+ // The non rule backed FontFace objects that have been added to this
+ // FontFaceSet.
+ nsTArray<FontFaceRecord> mNonRuleFaces;
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_FontFaceSet_h)
diff --git a/layout/style/FontFaceSetDocumentImpl.cpp b/layout/style/FontFaceSetDocumentImpl.cpp
new file mode 100644
index 0000000000..33f7404c7c
--- /dev/null
+++ b/layout/style/FontFaceSetDocumentImpl.cpp
@@ -0,0 +1,763 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "FontFaceSetDocumentImpl.h"
+#include "mozilla/FontLoaderUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/FontFaceImpl.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "nsContentPolicyUtils.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsFontFaceLoader.h"
+#include "nsIDocShell.h"
+#include "nsINetworkPredictor.h"
+#include "nsISupportsPriority.h"
+#include "nsIWebNavigation.h"
+#include "nsPresContext.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+
+#define LOG(args) \
+ MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug)
+
+NS_IMPL_ISUPPORTS_INHERITED(FontFaceSetDocumentImpl, FontFaceSetImpl,
+ nsIDOMEventListener, nsICSSLoaderObserver)
+
+FontFaceSetDocumentImpl::FontFaceSetDocumentImpl(FontFaceSet* aOwner,
+ dom::Document* aDocument)
+ : FontFaceSetImpl(aOwner), mDocument(aDocument) {}
+
+FontFaceSetDocumentImpl::~FontFaceSetDocumentImpl() = default;
+
+void FontFaceSetDocumentImpl::Initialize() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mDocument, "We should get a valid document from the caller!");
+
+ // Record the state of the "bypass cache" flags from the docshell now,
+ // since we want to look at them from style worker threads, and we can
+ // only get to the docshell through a weak pointer (which is only
+ // possible on the main thread).
+ //
+ // In theory the load type of a docshell could change after the document
+ // is loaded, but handling that doesn't seem too important.
+ if (nsCOMPtr<nsIDocShell> docShell = mDocument->GetDocShell()) {
+ uint32_t loadType;
+ uint32_t flags;
+ if ((NS_SUCCEEDED(docShell->GetLoadType(&loadType)) &&
+ ((loadType >> 16) & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE)) ||
+ (NS_SUCCEEDED(docShell->GetDefaultLoadFlags(&flags)) &&
+ (flags & nsIRequest::LOAD_BYPASS_CACHE))) {
+ mBypassCache = true;
+ }
+ }
+
+ // Same for the "private browsing" flag.
+ if (nsCOMPtr<nsILoadContext> loadContext = mDocument->GetLoadContext()) {
+ mPrivateBrowsing = loadContext->UsePrivateBrowsing();
+ }
+
+ if (!mDocument->DidFireDOMContentLoaded()) {
+ mDocument->AddSystemEventListener(u"DOMContentLoaded"_ns, this, false,
+ false);
+ } else {
+ // In some cases we can't rely on CheckLoadingFinished being called from
+ // the refresh driver. For example, documents in display:none iframes.
+ // Or if the document has finished loading and painting at the time that
+ // script requests document.fonts and causes us to get here.
+ CheckLoadingFinished();
+ }
+
+ mDocument->CSSLoader()->AddObserver(this);
+
+ mStandardFontLoadPrincipal = MakeRefPtr<gfxFontSrcPrincipal>(
+ mDocument->NodePrincipal(), mDocument->PartitionedPrincipal());
+}
+
+void FontFaceSetDocumentImpl::Destroy() {
+ RemoveDOMContentLoadedListener();
+
+ if (mDocument && mDocument->CSSLoader()) {
+ // We're null checking CSSLoader() since FontFaceSetImpl::Disconnect() might
+ // be being called during unlink, at which time the loader may already have
+ // been unlinked from the document.
+ mDocument->CSSLoader()->RemoveObserver(this);
+ }
+
+ mRuleFaces.Clear();
+
+ // Since the base class may depend on the document remaining set, we need to
+ // clear mDocument after.
+ FontFaceSetImpl::Destroy();
+
+ mDocument = nullptr;
+}
+
+bool FontFaceSetDocumentImpl::IsOnOwningThread() { return NS_IsMainThread(); }
+
+#ifdef DEBUG
+void FontFaceSetDocumentImpl::AssertIsOnOwningThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+#endif
+
+void FontFaceSetDocumentImpl::DispatchToOwningThread(
+ const char* aName, std::function<void()>&& aFunc) {
+ class FontFaceSetDocumentRunnable final : public Runnable {
+ public:
+ FontFaceSetDocumentRunnable(const char* aName,
+ std::function<void()>&& aFunc)
+ : Runnable(aName), mFunc(std::move(aFunc)) {}
+
+ NS_IMETHOD Run() final {
+ mFunc();
+ return NS_OK;
+ }
+
+ private:
+ std::function<void()> mFunc;
+ };
+
+ RefPtr<FontFaceSetDocumentRunnable> runnable =
+ new FontFaceSetDocumentRunnable(aName, std::move(aFunc));
+ NS_DispatchToMainThread(runnable.forget());
+}
+
+uint64_t FontFaceSetDocumentImpl::GetInnerWindowID() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mDocument) {
+ return 0;
+ }
+
+ return mDocument->InnerWindowID();
+}
+
+nsPresContext* FontFaceSetDocumentImpl::GetPresContext() const {
+ mozilla::AssertIsMainThreadOrServoFontMetricsLocked();
+ if (!mDocument) {
+ return nullptr;
+ }
+
+ return mDocument->GetPresContext();
+}
+
+void FontFaceSetDocumentImpl::RefreshStandardFontLoadPrincipal() {
+ MOZ_ASSERT(NS_IsMainThread());
+ RecursiveMutexAutoLock lock(mMutex);
+ if (NS_WARN_IF(!mDocument)) {
+ return;
+ }
+ mStandardFontLoadPrincipal = MakeRefPtr<gfxFontSrcPrincipal>(
+ mDocument->NodePrincipal(), mDocument->PartitionedPrincipal());
+ FontFaceSetImpl::RefreshStandardFontLoadPrincipal();
+}
+
+already_AddRefed<URLExtraData> FontFaceSetDocumentImpl::GetURLExtraData() {
+ if (!mDocument) {
+ return nullptr;
+ }
+ return do_AddRef(mDocument->DefaultStyleAttrURLData());
+}
+
+void FontFaceSetDocumentImpl::RemoveDOMContentLoadedListener() {
+ if (mDocument) {
+ mDocument->RemoveSystemEventListener(u"DOMContentLoaded"_ns, this, false);
+ }
+}
+
+void FontFaceSetDocumentImpl::FindMatchingFontFaces(
+ const nsTHashSet<FontFace*>& aMatchingFaces,
+ nsTArray<FontFace*>& aFontFaces) {
+ FontFaceSetImpl::FindMatchingFontFaces(aMatchingFaces, aFontFaces);
+ for (FontFaceRecord& record : mRuleFaces) {
+ FontFace* owner = record.mFontFace->GetOwner();
+ if (owner && aMatchingFaces.Contains(owner)) {
+ aFontFaces.AppendElement(owner);
+ }
+ }
+}
+
+TimeStamp FontFaceSetDocumentImpl::GetNavigationStartTimeStamp() {
+ TimeStamp navStart;
+ RefPtr<nsDOMNavigationTiming> timing(mDocument->GetNavigationTiming());
+ if (timing) {
+ navStart = timing->GetNavigationStartTimeStamp();
+ }
+ return navStart;
+}
+
+void FontFaceSetDocumentImpl::EnsureReady() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // There may be outstanding style changes that will trigger the loading of
+ // new fonts. We need to flush layout to initiate any such loads so that
+ // if mReady is currently resolved we replace it with a new pending Promise.
+ // (That replacement will happen under this flush call.)
+ if (!ReadyPromiseIsPending() && mDocument) {
+ mDocument->FlushPendingNotifications(FlushType::Layout);
+ }
+}
+
+#ifdef DEBUG
+bool FontFaceSetDocumentImpl::HasRuleFontFace(FontFaceImpl* aFontFace) {
+ for (size_t i = 0; i < mRuleFaces.Length(); i++) {
+ if (mRuleFaces[i].mFontFace == aFontFace) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+bool FontFaceSetDocumentImpl::Add(FontFaceImpl* aFontFace, ErrorResult& aRv) {
+ if (NS_WARN_IF(!mDocument)) {
+ return false;
+ }
+
+ if (!FontFaceSetImpl::Add(aFontFace, aRv)) {
+ return false;
+ }
+
+ RefPtr<dom::Document> clonedDoc = mDocument->GetLatestStaticClone();
+ if (clonedDoc) {
+ // The document is printing, copy the font to the static clone as well.
+ nsCOMPtr<nsIPrincipal> principal = mDocument->GetPrincipal();
+ if (principal->IsSystemPrincipal() || nsContentUtils::IsPDFJS(principal)) {
+ ErrorResult rv;
+ clonedDoc->Fonts()->Add(*aFontFace->GetOwner(), rv);
+ MOZ_ASSERT(!rv.Failed());
+ }
+ }
+
+ return true;
+}
+
+nsresult FontFaceSetDocumentImpl::StartLoad(gfxUserFontEntry* aUserFontEntry,
+ uint32_t aSrcIndex) {
+ if (NS_WARN_IF(!mDocument)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamLoader> streamLoader;
+ RefPtr<nsFontFaceLoader> fontLoader;
+
+ const gfxFontFaceSrc& src = aUserFontEntry->SourceAt(aSrcIndex);
+
+ auto preloadKey =
+ PreloadHashKey::CreateAsFont(src.mURI->get(), CORS_ANONYMOUS);
+ RefPtr<PreloaderBase> preload =
+ mDocument->Preloads().LookupPreload(preloadKey);
+
+ if (preload) {
+ fontLoader = new nsFontFaceLoader(aUserFontEntry, aSrcIndex, this,
+ preload->Channel());
+ rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader,
+ fontLoader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = preload->AsyncConsume(streamLoader);
+
+ // We don't want this to hang around regardless of the result, there will be
+ // no coalescing of later found <link preload> tags for fonts.
+ preload->RemoveSelf(mDocument);
+ } else {
+ // No preload found, open a channel.
+ rv = NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup(mDocument->GetDocumentLoadGroup());
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIChannel> channel;
+ rv = FontLoaderUtils::BuildChannel(
+ getter_AddRefs(channel), src.mURI->get(), CORS_ANONYMOUS,
+ dom::ReferrerPolicy::_empty /* not used */, aUserFontEntry, &src,
+ mDocument, loadGroup, nullptr, false,
+ nsISupportsPriority::PRIORITY_HIGH);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ fontLoader = new nsFontFaceLoader(aUserFontEntry, aSrcIndex, this, channel);
+
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> referrer = src.mReferrerInfo
+ ? src.mReferrerInfo->GetOriginalReferrer()
+ : nullptr;
+ LOG((
+ "userfonts (%p) download start - font uri: (%s) referrer uri: (%s)\n",
+ fontLoader.get(), src.mURI->GetSpecOrDefault().get(),
+ referrer ? referrer->GetSpecOrDefault().get() : ""));
+ }
+
+ rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader,
+ fontLoader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->AsyncOpen(streamLoader);
+ if (NS_FAILED(rv)) {
+ fontLoader->DropChannel(); // explicitly need to break ref cycle
+ }
+ }
+
+ {
+ RecursiveMutexAutoLock lock(mMutex);
+ mLoaders.PutEntry(fontLoader);
+ }
+
+ net::PredictorLearn(src.mURI->get(), mDocument->GetDocumentURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, loadGroup);
+
+ if (NS_SUCCEEDED(rv)) {
+ fontLoader->StartedLoading(streamLoader);
+ // let the font entry remember the loader, in case we need to cancel it
+ aUserFontEntry->SetLoader(fontLoader);
+ }
+
+ return rv;
+}
+
+bool FontFaceSetDocumentImpl::IsFontLoadAllowed(const gfxFontFaceSrc& aSrc) {
+ MOZ_ASSERT(aSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL);
+
+ if (ServoStyleSet::IsInServoTraversal()) {
+ RecursiveMutexAutoLock lock(mMutex);
+ auto entry = mAllowedFontLoads.Lookup(&aSrc);
+ MOZ_DIAGNOSTIC_ASSERT(entry, "Missed an update?");
+ return entry ? *entry : false;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aSrc.mUseOriginPrincipal) {
+ return true;
+ }
+
+ if (NS_WARN_IF(!mDocument)) {
+ return false;
+ }
+
+ RefPtr<gfxFontSrcPrincipal> gfxPrincipal =
+ aSrc.mURI->InheritsSecurityContext() ? nullptr
+ : aSrc.LoadPrincipal(*this);
+
+ nsIPrincipal* principal =
+ gfxPrincipal ? gfxPrincipal->NodePrincipal() : nullptr;
+
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
+ mDocument->NodePrincipal(), // loading principal
+ principal, // triggering principal
+ mDocument, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_FONT);
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv =
+ NS_CheckContentLoadPolicy(aSrc.mURI->get(), secCheckLoadInfo, &shouldLoad,
+ nsContentUtils::GetContentPolicy());
+
+ return NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(shouldLoad);
+}
+
+nsresult FontFaceSetDocumentImpl::CreateChannelForSyncLoadFontData(
+ nsIChannel** aOutChannel, gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc) {
+ gfxFontSrcPrincipal* principal = aFontToLoad->GetPrincipal();
+
+ // Note we are calling NS_NewChannelWithTriggeringPrincipal() with both a
+ // node and a principal. This is because the document where the font is
+ // being loaded might have a different origin from the principal of the
+ // stylesheet that initiated the font load.
+ // Further, we only get here for data: loads, so it doesn't really matter
+ // whether we use SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT or not, to be
+ // more restrictive we use SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT.
+ return NS_NewChannelWithTriggeringPrincipal(
+ aOutChannel, aFontFaceSrc->mURI->get(), mDocument,
+ principal ? principal->NodePrincipal() : nullptr,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ aFontFaceSrc->mUseOriginPrincipal ? nsIContentPolicy::TYPE_UA_FONT
+ : nsIContentPolicy::TYPE_FONT);
+}
+
+bool FontFaceSetDocumentImpl::UpdateRules(
+ const nsTArray<nsFontFaceRuleContainer>& aRules) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ // If there was a change to the mNonRuleFaces array, then there could
+ // have been a modification to the user font set.
+ bool modified = mNonRuleFacesDirty;
+ mNonRuleFacesDirty = false;
+
+ // reuse existing FontFace objects mapped to rules already
+ nsTHashMap<nsPtrHashKey<StyleLockedFontFaceRule>, FontFaceImpl*> ruleFaceMap;
+ for (size_t i = 0, i_end = mRuleFaces.Length(); i < i_end; ++i) {
+ FontFaceImpl* f = mRuleFaces[i].mFontFace;
+ if (!f || !f->GetOwner()) {
+ continue;
+ }
+ ruleFaceMap.InsertOrUpdate(f->GetRule(), f);
+ }
+
+ // The @font-face rules that make up the user font set have changed,
+ // so we need to update the set. However, we want to preserve existing
+ // font entries wherever possible, so that we don't discard and then
+ // re-download resources in the (common) case where at least some of the
+ // same rules are still present.
+
+ nsTArray<FontFaceRecord> oldRecords = std::move(mRuleFaces);
+
+ // Remove faces from the font family records; we need to re-insert them
+ // because we might end up with faces in a different order even if they're
+ // the same font entries as before. (The order can affect font selection
+ // where multiple faces match the requested style, perhaps with overlapping
+ // unicode-range coverage.)
+ for (const auto& fontFamily : mFontFamilies.Values()) {
+ fontFamily->DetachFontEntries();
+ }
+
+ // Sometimes aRules has duplicate @font-face rules in it; we should make
+ // that not happen, but in the meantime, don't try to insert the same
+ // FontFace object more than once into mRuleFaces. We track which
+ // ones we've handled in this table.
+ nsTHashSet<StyleLockedFontFaceRule*> handledRules;
+
+ for (size_t i = 0, i_end = aRules.Length(); i < i_end; ++i) {
+ // Insert each FontFace objects for each rule into our list, migrating old
+ // font entries if possible rather than creating new ones; set modified to
+ // true if we detect that rule ordering has changed, or if a new entry is
+ // created.
+ StyleLockedFontFaceRule* rule = aRules[i].mRule;
+ if (!handledRules.EnsureInserted(rule)) {
+ // rule was already present in the hashtable
+ continue;
+ }
+ RefPtr<FontFaceImpl> faceImpl = ruleFaceMap.Get(rule);
+ RefPtr<FontFace> face = faceImpl ? faceImpl->GetOwner() : nullptr;
+ if (mOwner && (!faceImpl || !face)) {
+ face = FontFace::CreateForRule(mOwner->GetParentObject(), mOwner, rule);
+ faceImpl = face->GetImpl();
+ }
+ InsertRuleFontFace(faceImpl, face, aRules[i].mOrigin, oldRecords, modified);
+ }
+
+ for (size_t i = 0, i_end = mNonRuleFaces.Length(); i < i_end; ++i) {
+ // Do the same for the non rule backed FontFace objects.
+ InsertNonRuleFontFace(mNonRuleFaces[i].mFontFace);
+ }
+
+ // Remove any residual families that have no font entries (i.e., they were
+ // not defined at all by the updated set of @font-face rules).
+ for (auto it = mFontFamilies.Iter(); !it.Done(); it.Next()) {
+ if (!it.Data()->FontListLength()) {
+ it.Remove();
+ }
+ }
+
+ // If any FontFace objects for rules are left in the old list, note that the
+ // set has changed (even if the new set was built entirely by migrating old
+ // font entries).
+ if (oldRecords.Length() > 0) {
+ modified = true;
+ // Any in-progress loaders for obsolete rules should be cancelled,
+ // as the resource being downloaded will no longer be required.
+ // We need to explicitly remove any loaders here, otherwise the loaders
+ // will keep their "orphaned" font entries alive until they complete,
+ // even after the oldRules array is deleted.
+ //
+ // XXX Now that it is possible for the author to hold on to a rule backed
+ // FontFace object, we shouldn't cancel loading here; instead we should do
+ // it when the FontFace is GCed, if we can detect that.
+ size_t count = oldRecords.Length();
+ for (size_t i = 0; i < count; ++i) {
+ RefPtr<FontFaceImpl> f = oldRecords[i].mFontFace;
+ gfxUserFontEntry* userFontEntry = f->GetUserFontEntry();
+ if (userFontEntry) {
+ nsFontFaceLoader* loader = userFontEntry->GetLoader();
+ if (loader) {
+ loader->Cancel();
+ RemoveLoader(loader);
+ }
+ }
+
+ // Any left over FontFace objects should also cease being rule backed.
+ f->DisconnectFromRule();
+ }
+ }
+
+ if (modified) {
+ IncrementGeneration(true);
+ mHasLoadingFontFacesIsDirty = true;
+ CheckLoadingStarted();
+ CheckLoadingFinished();
+ }
+
+ // if local rules needed to be rebuilt, they have been rebuilt at this point
+ if (mRebuildLocalRules) {
+ mLocalRulesUsed = false;
+ mRebuildLocalRules = false;
+ }
+
+ if (LOG_ENABLED() && !mRuleFaces.IsEmpty()) {
+ LOG(("userfonts (%p) userfont rules update (%s) rule count: %d", this,
+ (modified ? "modified" : "not modified"), (int)(mRuleFaces.Length())));
+ }
+
+ return modified;
+}
+
+void FontFaceSetDocumentImpl::InsertRuleFontFace(
+ FontFaceImpl* aFontFace, FontFace* aFontFaceOwner, StyleOrigin aSheetType,
+ nsTArray<FontFaceRecord>& aOldRecords, bool& aFontSetModified) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ gfxUserFontAttributes attr;
+ if (!aFontFace->GetAttributes(attr)) {
+ // If there is no family name, this rule cannot contribute a
+ // usable font, so there is no point in processing it further.
+ return;
+ }
+
+ bool remove = false;
+ size_t removeIndex;
+
+ // This is a rule backed FontFace. First, we check in aOldRecords; if
+ // the FontFace for the rule exists there, just move it to the new record
+ // list, and put the entry into the appropriate family.
+ for (size_t i = 0; i < aOldRecords.Length(); ++i) {
+ FontFaceRecord& rec = aOldRecords[i];
+
+ if (rec.mFontFace == aFontFace && rec.mOrigin == Some(aSheetType)) {
+ // if local rules were used, don't use the old font entry
+ // for rules containing src local usage
+ if (mLocalRulesUsed && mRebuildLocalRules) {
+ if (aFontFace->HasLocalSrc()) {
+ // Remove the old record, but wait to see if we successfully create a
+ // new user font entry below.
+ remove = true;
+ removeIndex = i;
+ break;
+ }
+ }
+
+ gfxUserFontEntry* entry = rec.mFontFace->GetUserFontEntry();
+ MOZ_ASSERT(entry, "FontFace should have a gfxUserFontEntry by now");
+
+ AddUserFontEntry(attr.mFamilyName, entry);
+
+ MOZ_ASSERT(!HasRuleFontFace(rec.mFontFace),
+ "FontFace should not occur in mRuleFaces twice");
+
+ mRuleFaces.AppendElement(rec);
+ aOldRecords.RemoveElementAt(i);
+
+ if (mOwner && aFontFaceOwner) {
+ mOwner->InsertRuleFontFace(aFontFaceOwner, aSheetType);
+ }
+
+ // note the set has been modified if an old rule was skipped to find
+ // this one - something has been dropped, or ordering changed
+ if (i > 0) {
+ aFontSetModified = true;
+ }
+ return;
+ }
+ }
+
+ // this is a new rule:
+ nsAutoCString family(attr.mFamilyName);
+ RefPtr<gfxUserFontEntry> entry = FindOrCreateUserFontEntryFromFontFace(
+ aFontFace, std::move(attr), aSheetType);
+
+ if (!entry) {
+ return;
+ }
+
+ if (remove) {
+ // Although we broke out of the aOldRecords loop above, since we found
+ // src local usage, and we're not using the old user font entry, we still
+ // are adding a record to mRuleFaces with the same FontFace object.
+ // Remove the old record so that we don't have the same FontFace listed
+ // in both mRuleFaces and oldRecords, which would cause us to call
+ // DisconnectFromRule on a FontFace that should still be rule backed.
+ aOldRecords.RemoveElementAt(removeIndex);
+ }
+
+ FontFaceRecord rec;
+ rec.mFontFace = aFontFace;
+ rec.mOrigin = Some(aSheetType);
+
+ aFontFace->SetUserFontEntry(entry);
+
+ MOZ_ASSERT(!HasRuleFontFace(aFontFace),
+ "FontFace should not occur in mRuleFaces twice");
+
+ mRuleFaces.AppendElement(rec);
+
+ if (mOwner && aFontFaceOwner) {
+ mOwner->InsertRuleFontFace(aFontFaceOwner, aSheetType);
+ }
+
+ // this was a new rule and font entry, so note that the set was modified
+ aFontSetModified = true;
+
+ // Add the entry to the end of the list. If an existing userfont entry was
+ // returned by FindOrCreateUserFontEntryFromFontFace that was already stored
+ // on the family, gfxUserFontFamily::AddFontEntry(), which AddUserFontEntry
+ // calls, will automatically remove the earlier occurrence of the same
+ // userfont entry.
+ AddUserFontEntry(family, entry);
+}
+
+StyleLockedFontFaceRule* FontFaceSetDocumentImpl::FindRuleForEntry(
+ gfxFontEntry* aFontEntry) {
+ NS_ASSERTION(!aFontEntry->mIsUserFontContainer, "only platform font entries");
+ for (uint32_t i = 0; i < mRuleFaces.Length(); ++i) {
+ FontFaceImpl* f = mRuleFaces[i].mFontFace;
+ gfxUserFontEntry* entry = f->GetUserFontEntry();
+ if (entry && entry->GetPlatformFontEntry() == aFontEntry) {
+ return f->GetRule();
+ }
+ }
+ return nullptr;
+}
+
+StyleLockedFontFaceRule* FontFaceSetDocumentImpl::FindRuleForUserFontEntry(
+ gfxUserFontEntry* aUserFontEntry) {
+ for (uint32_t i = 0; i < mRuleFaces.Length(); ++i) {
+ FontFaceImpl* f = mRuleFaces[i].mFontFace;
+ if (f->GetUserFontEntry() == aUserFontEntry) {
+ return f->GetRule();
+ }
+ }
+ return nullptr;
+}
+
+void FontFaceSetDocumentImpl::CacheFontLoadability() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ // TODO(emilio): We could do it a bit more incrementally maybe?
+ for (const auto& fontFamily : mFontFamilies.Values()) {
+ fontFamily->ReadLock();
+ for (const gfxFontEntry* entry : fontFamily->GetFontList()) {
+ if (!entry->mIsUserFontContainer) {
+ continue;
+ }
+
+ const auto& sourceList =
+ static_cast<const gfxUserFontEntry*>(entry)->SourceList();
+ for (const gfxFontFaceSrc& src : sourceList) {
+ if (src.mSourceType != gfxFontFaceSrc::eSourceType_URL) {
+ continue;
+ }
+ mAllowedFontLoads.LookupOrInsertWith(
+ &src, [&] { return IsFontLoadAllowed(src); });
+ }
+ }
+ fontFamily->ReadUnlock();
+ }
+}
+
+void FontFaceSetDocumentImpl::DidRefresh() { CheckLoadingFinished(); }
+
+void FontFaceSetDocumentImpl::UpdateHasLoadingFontFaces() {
+ RecursiveMutexAutoLock lock(mMutex);
+ FontFaceSetImpl::UpdateHasLoadingFontFaces();
+
+ if (mHasLoadingFontFaces) {
+ return;
+ }
+
+ for (size_t i = 0; i < mRuleFaces.Length(); i++) {
+ FontFaceImpl* f = mRuleFaces[i].mFontFace;
+ if (f->Status() == FontFaceLoadStatus::Loading) {
+ mHasLoadingFontFaces = true;
+ return;
+ }
+ }
+}
+
+bool FontFaceSetDocumentImpl::MightHavePendingFontLoads() {
+ if (FontFaceSetImpl::MightHavePendingFontLoads()) {
+ return true;
+ }
+
+ // Check for pending restyles or reflows, as they might cause fonts to
+ // load as new styles apply and text runs are rebuilt.
+ nsPresContext* presContext = GetPresContext();
+ if (presContext && presContext->HasPendingRestyleOrReflow()) {
+ return true;
+ }
+
+ if (mDocument) {
+ // We defer resolving mReady until the document as fully loaded.
+ if (!mDocument->DidFireDOMContentLoaded()) {
+ return true;
+ }
+
+ // And we also wait for any CSS style sheets to finish loading, as their
+ // styles might cause new fonts to load.
+ if (mDocument->CSSLoader()->HasPendingLoads()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// nsIDOMEventListener
+
+NS_IMETHODIMP
+FontFaceSetDocumentImpl::HandleEvent(Event* aEvent) {
+ nsString type;
+ aEvent->GetType(type);
+
+ if (!type.EqualsLiteral("DOMContentLoaded")) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RemoveDOMContentLoadedListener();
+ CheckLoadingFinished();
+
+ return NS_OK;
+}
+
+// nsICSSLoaderObserver
+
+NS_IMETHODIMP
+FontFaceSetDocumentImpl::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) {
+ CheckLoadingFinished();
+ return NS_OK;
+}
+
+void FontFaceSetDocumentImpl::FlushUserFontSet() {
+ if (mDocument) {
+ mDocument->FlushUserFontSet();
+ }
+}
+
+void FontFaceSetDocumentImpl::MarkUserFontSetDirty() {
+ if (mDocument) {
+ // Ensure we trigger at least a style flush, that will eventually flush the
+ // user font set. Otherwise the font loads that that flush may cause could
+ // never be triggered.
+ if (PresShell* presShell = mDocument->GetPresShell()) {
+ presShell->EnsureStyleFlush();
+ }
+ mDocument->MarkUserFontSetDirty();
+ }
+}
+
+#undef LOG_ENABLED
+#undef LOG
diff --git a/layout/style/FontFaceSetDocumentImpl.h b/layout/style/FontFaceSetDocumentImpl.h
new file mode 100644
index 0000000000..d32103e203
--- /dev/null
+++ b/layout/style/FontFaceSetDocumentImpl.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_FontFaceSetDocumentImpl_h
+#define mozilla_dom_FontFaceSetDocumentImpl_h
+
+#include "mozilla/dom/FontFaceSetImpl.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsIDOMEventListener.h"
+
+namespace mozilla::dom {
+
+class FontFaceSetDocumentImpl final : public FontFaceSetImpl,
+ public nsIDOMEventListener,
+ public nsICSSLoaderObserver {
+ NS_DECL_ISUPPORTS_INHERITED
+
+ public:
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ FontFaceSetDocumentImpl(FontFaceSet* aOwner, dom::Document* aDocument);
+
+ void Initialize();
+ void Destroy() override;
+
+ bool IsOnOwningThread() override;
+#ifdef DEBUG
+ void AssertIsOnOwningThread() override;
+#endif
+ void DispatchToOwningThread(const char* aName,
+ std::function<void()>&& aFunc) override;
+
+ void RefreshStandardFontLoadPrincipal() override;
+
+ dom::Document* GetDocument() const override { return mDocument; }
+
+ already_AddRefed<URLExtraData> GetURLExtraData() override;
+
+ // gfxUserFontSet
+
+ nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
+ uint32_t aSrcIndex) override;
+
+ bool IsFontLoadAllowed(const gfxFontFaceSrc&) override;
+
+ nsPresContext* GetPresContext() const override;
+
+ bool UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules) override;
+
+ StyleLockedFontFaceRule* FindRuleForEntry(gfxFontEntry* aFontEntry) override;
+
+ /**
+ * Notification method called by the nsPresContext to indicate that the
+ * refresh driver ticked and flushed style and layout.
+ * were just flushed.
+ */
+ void DidRefresh() override;
+
+ // nsICSSLoaderObserver
+ NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) override;
+
+ // For ServoStyleSet to know ahead of time whether a font is loadable.
+ void CacheFontLoadability() override;
+
+ void EnsureReady() override;
+
+ bool Add(FontFaceImpl* aFontFace, ErrorResult& aRv) override;
+
+ void FlushUserFontSet() override;
+ void MarkUserFontSetDirty() override;
+
+ private:
+ ~FontFaceSetDocumentImpl() override;
+
+ uint64_t GetInnerWindowID() override;
+
+ void RemoveDOMContentLoadedListener();
+
+ nsresult CreateChannelForSyncLoadFontData(
+ nsIChannel** aOutChannel, gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc) override;
+
+ // search for @font-face rule that matches a userfont font entry
+ StyleLockedFontFaceRule* FindRuleForUserFontEntry(
+ gfxUserFontEntry* aUserFontEntry) override;
+
+ void FindMatchingFontFaces(const nsTHashSet<FontFace*>& aMatchingFaces,
+ nsTArray<FontFace*>& aFontFaces) override;
+
+ void InsertRuleFontFace(FontFaceImpl* aFontFace, FontFace* aFontFaceOwner,
+ StyleOrigin aOrigin,
+ nsTArray<FontFaceRecord>& aOldRecords,
+ bool& aFontSetModified);
+
+ // Helper function for HasLoadingFontFaces.
+ void UpdateHasLoadingFontFaces() override;
+
+ /**
+ * Returns whether there might be any pending font loads, which should cause
+ * the mReady Promise not to be resolved yet.
+ */
+ bool MightHavePendingFontLoads() override;
+
+#ifdef DEBUG
+ bool HasRuleFontFace(FontFaceImpl* aFontFace);
+#endif
+
+ TimeStamp GetNavigationStartTimeStamp() override;
+
+ // The document this is a FontFaceSet for.
+ RefPtr<dom::Document> mDocument;
+
+ // The @font-face rule backed FontFace objects in the FontFaceSet.
+ nsTArray<FontFaceRecord> mRuleFaces;
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_FontFaceSetDocumentImpl_h)
diff --git a/layout/style/FontFaceSetImpl.cpp b/layout/style/FontFaceSetImpl.cpp
new file mode 100644
index 0000000000..7383842a41
--- /dev/null
+++ b/layout/style/FontFaceSetImpl.cpp
@@ -0,0 +1,954 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "FontFaceSetImpl.h"
+
+#include "gfxFontConstants.h"
+#include "gfxFontSrcPrincipal.h"
+#include "gfxFontSrcURI.h"
+#include "gfxFontUtils.h"
+#include "gfxPlatformFontList.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/CSSFontFaceRule.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/FontFaceImpl.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/FontFaceSetBinding.h"
+#include "mozilla/dom/FontFaceSetLoadEvent.h"
+#include "mozilla/dom/FontFaceSetLoadEventBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoCSSParser.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/LoadInfo.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsDeviceContext.h"
+#include "nsFontFaceLoader.h"
+#include "nsIConsoleService.h"
+#include "nsIContentPolicy.h"
+#include "nsIDocShell.h"
+#include "nsILoadContext.h"
+#include "nsIPrincipal.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "nsUTF8Utils.h"
+#include "nsDOMNavigationTiming.h"
+#include "ReferrerInfo.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+
+#define LOG(args) \
+ MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug)
+
+NS_IMPL_ISUPPORTS0(FontFaceSetImpl)
+
+FontFaceSetImpl::FontFaceSetImpl(FontFaceSet* aOwner)
+ : mMutex("mozilla::dom::FontFaceSetImpl"),
+ mOwner(aOwner),
+ mStatus(FontFaceSetLoadStatus::Loaded),
+ mNonRuleFacesDirty(false),
+ mHasLoadingFontFaces(false),
+ mHasLoadingFontFacesIsDirty(false),
+ mDelayedLoadCheck(false),
+ mBypassCache(false),
+ mPrivateBrowsing(false) {}
+
+FontFaceSetImpl::~FontFaceSetImpl() {
+ // Assert that we don't drop any FontFaceSet objects during a Servo traversal,
+ // since PostTraversalTask objects can hold raw pointers to FontFaceSets.
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+
+ Destroy();
+}
+
+void FontFaceSetImpl::DestroyLoaders() {
+ mMutex.AssertCurrentThreadIn();
+ if (mLoaders.IsEmpty()) {
+ return;
+ }
+ if (NS_IsMainThread()) {
+ for (const auto& key : mLoaders.Keys()) {
+ key->Cancel();
+ }
+ mLoaders.Clear();
+ return;
+ }
+
+ class DestroyLoadersRunnable final : public Runnable {
+ public:
+ explicit DestroyLoadersRunnable(FontFaceSetImpl* aFontFaceSet)
+ : Runnable("FontFaceSetImpl::DestroyLoaders"),
+ mFontFaceSet(aFontFaceSet) {}
+
+ protected:
+ ~DestroyLoadersRunnable() override = default;
+
+ NS_IMETHOD Run() override {
+ RecursiveMutexAutoLock lock(mFontFaceSet->mMutex);
+ mFontFaceSet->DestroyLoaders();
+ return NS_OK;
+ }
+
+ // We need to save a reference to the FontFaceSetImpl because the
+ // loaders contain a non-owning reference to it.
+ RefPtr<FontFaceSetImpl> mFontFaceSet;
+ };
+
+ auto runnable = MakeRefPtr<DestroyLoadersRunnable>(this);
+ NS_DispatchToMainThread(runnable);
+}
+
+void FontFaceSetImpl::Destroy() {
+ nsTArray<FontFaceRecord> nonRuleFaces;
+ nsRefPtrHashtable<nsCStringHashKey, gfxUserFontFamily> fontFamilies;
+
+ {
+ RecursiveMutexAutoLock lock(mMutex);
+ DestroyLoaders();
+ nonRuleFaces = std::move(mNonRuleFaces);
+ fontFamilies = std::move(mFontFamilies);
+ mOwner = nullptr;
+ }
+
+ if (gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList()) {
+ fp->RemoveUserFontSet(this);
+ }
+}
+
+void FontFaceSetImpl::ParseFontShorthandForMatching(
+ const nsACString& aFont, StyleFontFamilyList& aFamilyList,
+ FontWeight& aWeight, FontStretch& aStretch, FontSlantStyle& aStyle,
+ ErrorResult& aRv) {
+ RefPtr<URLExtraData> url = GetURLExtraData();
+ if (!url) {
+ aRv.ThrowInvalidStateError("Missing URLExtraData");
+ return;
+ }
+
+ if (!ServoCSSParser::ParseFontShorthandForMatching(
+ aFont, url, aFamilyList, aStyle, aStretch, aWeight)) {
+ aRv.ThrowSyntaxError("Invalid font shorthand");
+ return;
+ }
+}
+
+static bool HasAnyCharacterInUnicodeRange(gfxUserFontEntry* aEntry,
+ const nsAString& aInput) {
+ const char16_t* p = aInput.Data();
+ const char16_t* end = p + aInput.Length();
+
+ while (p < end) {
+ uint32_t c = UTF16CharEnumerator::NextChar(&p, end);
+ if (aEntry->CharacterInUnicodeRange(c)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void FontFaceSetImpl::FindMatchingFontFaces(const nsACString& aFont,
+ const nsAString& aText,
+ nsTArray<FontFace*>& aFontFaces,
+ ErrorResult& aRv) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ StyleFontFamilyList familyList;
+ FontWeight weight;
+ FontStretch stretch;
+ FontSlantStyle italicStyle;
+ ParseFontShorthandForMatching(aFont, familyList, weight, stretch, italicStyle,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ gfxFontStyle style;
+ style.style = italicStyle;
+ style.weight = weight;
+ style.stretch = stretch;
+
+ // Set of FontFaces that we want to return.
+ nsTHashSet<FontFace*> matchingFaces;
+
+ for (const StyleSingleFontFamily& fontFamilyName : familyList.list.AsSpan()) {
+ if (!fontFamilyName.IsFamilyName()) {
+ continue;
+ }
+
+ const auto& name = fontFamilyName.AsFamilyName();
+ RefPtr<gfxFontFamily> family =
+ LookupFamily(nsAtomCString(name.name.AsAtom()));
+
+ if (!family) {
+ continue;
+ }
+
+ AutoTArray<gfxFontEntry*, 4> entries;
+ family->FindAllFontsForStyle(style, entries);
+
+ for (gfxFontEntry* e : entries) {
+ FontFaceImpl::Entry* entry = static_cast<FontFaceImpl::Entry*>(e);
+ if (HasAnyCharacterInUnicodeRange(entry, aText)) {
+ entry->FindFontFaceOwners(matchingFaces);
+ }
+ }
+ }
+
+ if (matchingFaces.IsEmpty()) {
+ return;
+ }
+
+ // Add all FontFaces in matchingFaces to aFontFaces, in the order
+ // they appear in the FontFaceSet.
+ FindMatchingFontFaces(matchingFaces, aFontFaces);
+}
+
+void FontFaceSetImpl::FindMatchingFontFaces(
+ const nsTHashSet<FontFace*>& aMatchingFaces,
+ nsTArray<FontFace*>& aFontFaces) {
+ RecursiveMutexAutoLock lock(mMutex);
+ for (FontFaceRecord& record : mNonRuleFaces) {
+ FontFace* owner = record.mFontFace->GetOwner();
+ if (owner && aMatchingFaces.Contains(owner)) {
+ aFontFaces.AppendElement(owner);
+ }
+ }
+}
+
+bool FontFaceSetImpl::ReadyPromiseIsPending() const {
+ RecursiveMutexAutoLock lock(mMutex);
+ return mOwner && mOwner->ReadyPromiseIsPending();
+}
+
+FontFaceSetLoadStatus FontFaceSetImpl::Status() {
+ RecursiveMutexAutoLock lock(mMutex);
+ FlushUserFontSet();
+ return mStatus;
+}
+
+bool FontFaceSetImpl::Add(FontFaceImpl* aFontFace, ErrorResult& aRv) {
+ RecursiveMutexAutoLock lock(mMutex);
+ FlushUserFontSet();
+
+ if (aFontFace->IsInFontFaceSet(this)) {
+ return false;
+ }
+
+ if (aFontFace->HasRule()) {
+ aRv.ThrowInvalidModificationError(
+ "Can't add face to FontFaceSet that comes from an @font-face rule");
+ return false;
+ }
+
+ aFontFace->AddFontFaceSet(this);
+
+#ifdef DEBUG
+ for (const FontFaceRecord& rec : mNonRuleFaces) {
+ MOZ_ASSERT(rec.mFontFace != aFontFace,
+ "FontFace should not occur in mNonRuleFaces twice");
+ }
+#endif
+
+ FontFaceRecord* rec = mNonRuleFaces.AppendElement();
+ rec->mFontFace = aFontFace;
+ rec->mOrigin = Nothing();
+
+ mNonRuleFacesDirty = true;
+ MarkUserFontSetDirty();
+ mHasLoadingFontFacesIsDirty = true;
+ CheckLoadingStarted();
+ return true;
+}
+
+void FontFaceSetImpl::Clear() {
+ RecursiveMutexAutoLock lock(mMutex);
+ FlushUserFontSet();
+
+ if (mNonRuleFaces.IsEmpty()) {
+ return;
+ }
+
+ for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
+ FontFaceImpl* f = mNonRuleFaces[i].mFontFace;
+ f->RemoveFontFaceSet(this);
+ }
+
+ mNonRuleFaces.Clear();
+ mNonRuleFacesDirty = true;
+ MarkUserFontSetDirty();
+ mHasLoadingFontFacesIsDirty = true;
+ CheckLoadingFinished();
+}
+
+bool FontFaceSetImpl::Delete(FontFaceImpl* aFontFace) {
+ RecursiveMutexAutoLock lock(mMutex);
+ FlushUserFontSet();
+
+ if (aFontFace->HasRule()) {
+ return false;
+ }
+
+ bool removed = false;
+ for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
+ if (mNonRuleFaces[i].mFontFace == aFontFace) {
+ mNonRuleFaces.RemoveElementAt(i);
+ removed = true;
+ break;
+ }
+ }
+ if (!removed) {
+ return false;
+ }
+
+ aFontFace->RemoveFontFaceSet(this);
+
+ mNonRuleFacesDirty = true;
+ MarkUserFontSetDirty();
+ mHasLoadingFontFacesIsDirty = true;
+ CheckLoadingFinished();
+ return true;
+}
+
+bool FontFaceSetImpl::HasAvailableFontFace(FontFaceImpl* aFontFace) {
+ return aFontFace->IsInFontFaceSet(this);
+}
+
+void FontFaceSetImpl::RemoveLoader(nsFontFaceLoader* aLoader) {
+ RecursiveMutexAutoLock lock(mMutex);
+ mLoaders.RemoveEntry(aLoader);
+}
+
+void FontFaceSetImpl::InsertNonRuleFontFace(FontFaceImpl* aFontFace) {
+ gfxUserFontAttributes attr;
+ if (!aFontFace->GetAttributes(attr)) {
+ // If there is no family name, this rule cannot contribute a
+ // usable font, so there is no point in processing it further.
+ return;
+ }
+
+ nsAutoCString family(attr.mFamilyName);
+
+ // Just create a new font entry if we haven't got one already.
+ if (!aFontFace->GetUserFontEntry()) {
+ // XXX Should we be checking mLocalRulesUsed like InsertRuleFontFace does?
+ RefPtr<gfxUserFontEntry> entry = FindOrCreateUserFontEntryFromFontFace(
+ aFontFace, std::move(attr), StyleOrigin::Author);
+ if (!entry) {
+ return;
+ }
+ aFontFace->SetUserFontEntry(entry);
+ }
+ AddUserFontEntry(family, aFontFace->GetUserFontEntry());
+}
+
+void FontFaceSetImpl::UpdateUserFontEntry(gfxUserFontEntry* aEntry,
+ gfxUserFontAttributes&& aAttr) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool resetFamilyName = !aEntry->mFamilyName.IsEmpty() &&
+ aEntry->mFamilyName != aAttr.mFamilyName;
+ // aFontFace already has a user font entry, so we update its attributes
+ // rather than creating a new one.
+ aEntry->UpdateAttributes(std::move(aAttr));
+ // If the family name has changed, remove the entry from its current family
+ // and clear the mFamilyName field so it can be reset when added to a new
+ // family.
+ if (resetFamilyName) {
+ RefPtr<gfxUserFontFamily> family = LookupFamily(aEntry->mFamilyName);
+ if (family) {
+ family->RemoveFontEntry(aEntry);
+ }
+ aEntry->mFamilyName.Truncate(0);
+ }
+}
+
+class FontFaceSetImpl::UpdateUserFontEntryRunnable final
+ : public WorkerMainThreadRunnable {
+ public:
+ UpdateUserFontEntryRunnable(FontFaceSetImpl* aSet, gfxUserFontEntry* aEntry,
+ gfxUserFontAttributes& aAttr)
+ : WorkerMainThreadRunnable(
+ GetCurrentThreadWorkerPrivate(),
+ "FontFaceSetImpl :: FindOrCreateUserFontEntryFromFontFace"_ns),
+ mSet(aSet),
+ mEntry(aEntry),
+ mAttr(aAttr) {}
+
+ bool MainThreadRun() override {
+ mSet->UpdateUserFontEntry(mEntry, std::move(mAttr));
+ return true;
+ }
+
+ private:
+ FontFaceSetImpl* mSet;
+ gfxUserFontEntry* mEntry;
+ gfxUserFontAttributes& mAttr;
+};
+
+// TODO(emilio): Should this take an nsAtom* aFamilyName instead?
+//
+// All callers have one handy.
+/* static */
+already_AddRefed<gfxUserFontEntry>
+FontFaceSetImpl::FindOrCreateUserFontEntryFromFontFace(
+ FontFaceImpl* aFontFace, gfxUserFontAttributes&& aAttr,
+ StyleOrigin aOrigin) {
+ FontFaceSetImpl* set = aFontFace->GetPrimaryFontFaceSet();
+
+ RefPtr<gfxUserFontEntry> existingEntry = aFontFace->GetUserFontEntry();
+ if (existingEntry) {
+ if (NS_IsMainThread()) {
+ set->UpdateUserFontEntry(existingEntry, std::move(aAttr));
+ } else {
+ auto task =
+ MakeRefPtr<UpdateUserFontEntryRunnable>(set, existingEntry, aAttr);
+ IgnoredErrorResult ignoredRv;
+ task->Dispatch(Canceling, ignoredRv);
+ }
+ return existingEntry.forget();
+ }
+
+ // set up src array
+ nsTArray<gfxFontFaceSrc> srcArray;
+
+ if (aFontFace->HasFontData()) {
+ gfxFontFaceSrc* face = srcArray.AppendElement();
+ if (!face) {
+ return nullptr;
+ }
+
+ face->mSourceType = gfxFontFaceSrc::eSourceType_Buffer;
+ face->mBuffer = aFontFace->TakeBufferSource();
+ } else {
+ size_t len = aAttr.mSources.Length();
+ for (size_t i = 0; i < len; ++i) {
+ gfxFontFaceSrc* face = srcArray.AppendElement();
+ const auto& component = aAttr.mSources[i];
+ switch (component.tag) {
+ case StyleFontFaceSourceListComponent::Tag::Local: {
+ nsAtom* atom = component.AsLocal();
+ face->mLocalName.Append(nsAtomCString(atom));
+ face->mSourceType = gfxFontFaceSrc::eSourceType_Local;
+ face->mURI = nullptr;
+ face->mFormatHint = StyleFontFaceSourceFormatKeyword::None;
+ break;
+ }
+
+ case StyleFontFaceSourceListComponent::Tag::Url: {
+ face->mSourceType = gfxFontFaceSrc::eSourceType_URL;
+ const StyleCssUrl* url = component.AsUrl();
+ nsIURI* uri = url->GetURI();
+ face->mURI = uri ? new gfxFontSrcURI(uri) : nullptr;
+ const URLExtraData& extraData = url->ExtraData();
+ face->mReferrerInfo = extraData.ReferrerInfo();
+
+ // agent and user stylesheets are treated slightly differently,
+ // the same-site origin check and access control headers are
+ // enforced against the sheet principal rather than the document
+ // principal to allow user stylesheets to include @font-face rules
+ if (aOrigin == StyleOrigin::User ||
+ aOrigin == StyleOrigin::UserAgent) {
+ face->mUseOriginPrincipal = true;
+ face->mOriginPrincipal = new gfxFontSrcPrincipal(
+ extraData.Principal(), extraData.Principal());
+ }
+
+ face->mLocalName.Truncate();
+ face->mFormatHint = StyleFontFaceSourceFormatKeyword::None;
+ face->mTechFlags = StyleFontFaceSourceTechFlags::Empty();
+
+ if (i + 1 < len) {
+ // Check for a format hint.
+ const auto& next = aAttr.mSources[i + 1];
+ switch (next.tag) {
+ case StyleFontFaceSourceListComponent::Tag::FormatHintKeyword:
+ face->mFormatHint = next.format_hint_keyword._0;
+ i++;
+ break;
+ case StyleFontFaceSourceListComponent::Tag::FormatHintString: {
+ nsDependentCSubstring valueString(
+ reinterpret_cast<const char*>(
+ next.format_hint_string.utf8_bytes),
+ next.format_hint_string.length);
+
+ if (valueString.LowerCaseEqualsASCII("woff")) {
+ face->mFormatHint = StyleFontFaceSourceFormatKeyword::Woff;
+ } else if (valueString.LowerCaseEqualsASCII("woff2")) {
+ face->mFormatHint = StyleFontFaceSourceFormatKeyword::Woff2;
+ } else if (valueString.LowerCaseEqualsASCII("opentype")) {
+ face->mFormatHint =
+ StyleFontFaceSourceFormatKeyword::Opentype;
+ } else if (valueString.LowerCaseEqualsASCII("truetype")) {
+ face->mFormatHint =
+ StyleFontFaceSourceFormatKeyword::Truetype;
+ } else if (valueString.LowerCaseEqualsASCII("truetype-aat")) {
+ face->mFormatHint =
+ StyleFontFaceSourceFormatKeyword::Truetype;
+ } else if (valueString.LowerCaseEqualsASCII(
+ "embedded-opentype")) {
+ face->mFormatHint =
+ StyleFontFaceSourceFormatKeyword::EmbeddedOpentype;
+ } else if (valueString.LowerCaseEqualsASCII("svg")) {
+ face->mFormatHint = StyleFontFaceSourceFormatKeyword::Svg;
+ } else if (StaticPrefs::layout_css_font_variations_enabled()) {
+ // Non-standard values that Firefox accepted, for back-compat;
+ // these are superseded by the tech() function.
+ if (valueString.LowerCaseEqualsASCII("woff-variations")) {
+ face->mFormatHint = StyleFontFaceSourceFormatKeyword::Woff;
+ } else if (valueString.LowerCaseEqualsASCII(
+ "woff2-variations")) {
+ face->mFormatHint = StyleFontFaceSourceFormatKeyword::Woff2;
+ } else if (valueString.LowerCaseEqualsASCII(
+ "opentype-variations")) {
+ face->mFormatHint =
+ StyleFontFaceSourceFormatKeyword::Opentype;
+ } else if (valueString.LowerCaseEqualsASCII(
+ "truetype-variations")) {
+ face->mFormatHint =
+ StyleFontFaceSourceFormatKeyword::Truetype;
+ } else {
+ face->mFormatHint =
+ StyleFontFaceSourceFormatKeyword::Unknown;
+ }
+ } else {
+ // unknown format specified, mark to distinguish from the
+ // case where no format hints are specified
+ face->mFormatHint = StyleFontFaceSourceFormatKeyword::Unknown;
+ }
+ i++;
+ break;
+ }
+ case StyleFontFaceSourceListComponent::Tag::TechFlags:
+ case StyleFontFaceSourceListComponent::Tag::Local:
+ case StyleFontFaceSourceListComponent::Tag::Url:
+ break;
+ }
+ }
+
+ if (i + 1 < len) {
+ // Check for a set of font-technologies flags.
+ const auto& next = aAttr.mSources[i + 1];
+ if (next.IsTechFlags()) {
+ face->mTechFlags = next.AsTechFlags();
+ i++;
+ }
+ }
+
+ if (!face->mURI) {
+ // if URI not valid, omit from src array
+ srcArray.RemoveLastElement();
+ NS_WARNING("null url in @font-face rule");
+ continue;
+ }
+ break;
+ }
+
+ case StyleFontFaceSourceListComponent::Tag::FormatHintKeyword:
+ case StyleFontFaceSourceListComponent::Tag::FormatHintString:
+ case StyleFontFaceSourceListComponent::Tag::TechFlags:
+ MOZ_ASSERT_UNREACHABLE(
+ "Should always come after a URL source, and be consumed already");
+ break;
+ }
+ }
+ }
+
+ if (srcArray.IsEmpty()) {
+ return nullptr;
+ }
+
+ return set->FindOrCreateUserFontEntry(std::move(srcArray), std::move(aAttr));
+}
+
+nsresult FontFaceSetImpl::LogMessage(gfxUserFontEntry* aUserFontEntry,
+ uint32_t aSrcIndex, const char* aMessage,
+ uint32_t aFlags, nsresult aStatus) {
+ nsAutoCString familyName;
+ nsAutoCString fontURI;
+ aUserFontEntry->GetFamilyNameAndURIForLogging(aSrcIndex, familyName, fontURI);
+
+ nsAutoCString weightString;
+ aUserFontEntry->Weight().ToString(weightString);
+ nsAutoCString stretchString;
+ aUserFontEntry->Stretch().ToString(stretchString);
+ nsPrintfCString message(
+ "downloadable font: %s "
+ "(font-family: \"%s\" style:%s weight:%s stretch:%s src index:%d)",
+ aMessage, familyName.get(),
+ aUserFontEntry->IsItalic() ? "italic" : "normal", // XXX todo: oblique?
+ weightString.get(), stretchString.get(), aSrcIndex);
+
+ if (NS_FAILED(aStatus)) {
+ message.AppendLiteral(": ");
+ switch (aStatus) {
+ case NS_ERROR_DOM_BAD_URI:
+ message.AppendLiteral("bad URI or cross-site access not allowed");
+ break;
+ case NS_ERROR_CONTENT_BLOCKED:
+ message.AppendLiteral("content blocked");
+ break;
+ default:
+ message.AppendLiteral("status=");
+ message.AppendInt(static_cast<uint32_t>(aStatus));
+ break;
+ }
+ }
+ message.AppendLiteral(" source: ");
+ message.Append(fontURI);
+
+ LOG(("userfonts (%p) %s", this, message.get()));
+
+ if (GetCurrentThreadWorkerPrivate()) {
+ // TODO(aosmond): Log to the console for workers. See bug 1778537.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (!console) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // try to give the user an indication of where the rule came from
+ StyleLockedFontFaceRule* rule = FindRuleForUserFontEntry(aUserFontEntry);
+ nsString href;
+ nsAutoCString text;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ if (rule) {
+ Servo_FontFaceRule_GetCssText(rule, &text);
+ Servo_FontFaceRule_GetSourceLocation(rule, &line, &column);
+ // FIXME We need to figure out an approach to get the style sheet
+ // of this raw rule. See bug 1450903.
+#if 0
+ StyleSheet* sheet = rule->GetStyleSheet();
+ // if the style sheet is removed while the font is loading can be null
+ if (sheet) {
+ nsCString spec = sheet->GetSheetURI()->GetSpecOrDefault();
+ CopyUTF8toUTF16(spec, href);
+ } else {
+ NS_WARNING("null parent stylesheet for @font-face rule");
+ href.AssignLiteral("unknown");
+ }
+#endif
+ // Leave href empty if we don't know how to get the correct sheet.
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = scriptError->InitWithWindowID(NS_ConvertUTF8toUTF16(message),
+ href, // file
+ NS_ConvertUTF8toUTF16(text), // src line
+ line, column,
+ aFlags, // flags
+ "CSS Loader", // category (make separate?)
+ GetInnerWindowID());
+ if (NS_SUCCEEDED(rv)) {
+ console->LogMessage(scriptError);
+ }
+
+ return NS_OK;
+}
+
+nsresult FontFaceSetImpl::SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ uint8_t*& aBuffer,
+ uint32_t& aBufferLength) {
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = CreateChannelForSyncLoadFontData(getter_AddRefs(channel),
+ aFontToLoad, aFontFaceSrc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // blocking stream is OK for data URIs
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open(getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t bufferLength64;
+ rv = stream->Available(&bufferLength64);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (bufferLength64 == 0) {
+ return NS_ERROR_FAILURE;
+ }
+ if (bufferLength64 > UINT32_MAX) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ aBufferLength = static_cast<uint32_t>(bufferLength64);
+
+ // read all the decoded data
+ aBuffer = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * aBufferLength));
+ if (!aBuffer) {
+ aBufferLength = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t numRead, totalRead = 0;
+ while (NS_SUCCEEDED(
+ rv = stream->Read(reinterpret_cast<char*>(aBuffer + totalRead),
+ aBufferLength - totalRead, &numRead)) &&
+ numRead != 0) {
+ totalRead += numRead;
+ if (totalRead > aBufferLength) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ }
+
+ // make sure there's a mime type
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString mimeType;
+ rv = channel->GetContentType(mimeType);
+ aBufferLength = totalRead;
+ }
+
+ if (NS_FAILED(rv)) {
+ free(aBuffer);
+ aBuffer = nullptr;
+ aBufferLength = 0;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void FontFaceSetImpl::OnFontFaceStatusChanged(FontFaceImpl* aFontFace) {
+ gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked();
+ RecursiveMutexAutoLock lock(mMutex);
+ MOZ_ASSERT(HasAvailableFontFace(aFontFace));
+
+ mHasLoadingFontFacesIsDirty = true;
+
+ if (aFontFace->Status() == FontFaceLoadStatus::Loading) {
+ CheckLoadingStarted();
+ } else {
+ MOZ_ASSERT(aFontFace->Status() == FontFaceLoadStatus::Loaded ||
+ aFontFace->Status() == FontFaceLoadStatus::Error);
+ // When a font finishes downloading, nsPresContext::UserFontSetUpdated
+ // will be called immediately afterwards to request a reflow of the
+ // relevant elements in the document. We want to wait until the reflow
+ // request has been done before the FontFaceSet is marked as Loaded so
+ // that we don't briefly set the FontFaceSet to Loaded and then Loading
+ // again once the reflow is pending. So we go around the event loop
+ // and call CheckLoadingFinished() after the reflow has been queued.
+ if (!mDelayedLoadCheck) {
+ mDelayedLoadCheck = true;
+ DispatchCheckLoadingFinishedAfterDelay();
+ }
+ }
+}
+
+void FontFaceSetImpl::DispatchCheckLoadingFinishedAfterDelay() {
+ gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked();
+
+ if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) {
+ // See comments in Gecko_GetFontMetrics.
+ //
+ // We can't just dispatch the runnable below if we're not on the main
+ // thread, since it needs to take a strong reference to the FontFaceSet,
+ // and being a DOM object, FontFaceSet doesn't support thread-safe
+ // refcounting.
+ set->AppendTask(
+ PostTraversalTask::DispatchFontFaceSetCheckLoadingFinishedAfterDelay(
+ this));
+ return;
+ }
+
+ DispatchToOwningThread(
+ "FontFaceSetImpl::DispatchCheckLoadingFinishedAfterDelay",
+ [self = RefPtr{this}]() { self->CheckLoadingFinishedAfterDelay(); });
+}
+
+void FontFaceSetImpl::CheckLoadingFinishedAfterDelay() {
+ RecursiveMutexAutoLock lock(mMutex);
+ mDelayedLoadCheck = false;
+ CheckLoadingFinished();
+}
+
+void FontFaceSetImpl::CheckLoadingStarted() {
+ gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked();
+ RecursiveMutexAutoLock lock(mMutex);
+
+ if (!HasLoadingFontFaces()) {
+ return;
+ }
+
+ if (mStatus == FontFaceSetLoadStatus::Loading) {
+ // We have already dispatched a loading event and replaced mReady
+ // with a fresh, unresolved promise.
+ return;
+ }
+
+ mStatus = FontFaceSetLoadStatus::Loading;
+
+ if (IsOnOwningThread()) {
+ OnLoadingStarted();
+ return;
+ }
+
+ DispatchToOwningThread("FontFaceSetImpl::CheckLoadingStarted",
+ [self = RefPtr{this}]() { self->OnLoadingStarted(); });
+}
+
+void FontFaceSetImpl::OnLoadingStarted() {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (mOwner) {
+ mOwner->DispatchLoadingEventAndReplaceReadyPromise();
+ }
+}
+
+void FontFaceSetImpl::UpdateHasLoadingFontFaces() {
+ RecursiveMutexAutoLock lock(mMutex);
+ mHasLoadingFontFacesIsDirty = false;
+ mHasLoadingFontFaces = false;
+ for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
+ if (mNonRuleFaces[i].mFontFace->Status() == FontFaceLoadStatus::Loading) {
+ mHasLoadingFontFaces = true;
+ return;
+ }
+ }
+}
+
+bool FontFaceSetImpl::HasLoadingFontFaces() {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (mHasLoadingFontFacesIsDirty) {
+ UpdateHasLoadingFontFaces();
+ }
+ return mHasLoadingFontFaces;
+}
+
+bool FontFaceSetImpl::MightHavePendingFontLoads() {
+ // Check for FontFace objects in the FontFaceSet that are still loading.
+ return HasLoadingFontFaces();
+}
+
+void FontFaceSetImpl::CheckLoadingFinished() {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (mDelayedLoadCheck) {
+ // Wait until the runnable posted in OnFontFaceStatusChanged calls us.
+ return;
+ }
+
+ if (!ReadyPromiseIsPending()) {
+ // We've already resolved mReady (or set the flag to do that lazily) and
+ // dispatched the loadingdone/loadingerror events.
+ return;
+ }
+
+ if (MightHavePendingFontLoads()) {
+ // We're not finished loading yet.
+ return;
+ }
+
+ mStatus = FontFaceSetLoadStatus::Loaded;
+
+ if (IsOnOwningThread()) {
+ OnLoadingFinished();
+ return;
+ }
+
+ DispatchToOwningThread(
+ "FontFaceSetImpl::CheckLoadingFinished",
+ [self = RefPtr{this}]() { self->OnLoadingFinished(); });
+}
+
+void FontFaceSetImpl::OnLoadingFinished() {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (mOwner) {
+ mOwner->MaybeResolve();
+ }
+}
+
+void FontFaceSetImpl::RefreshStandardFontLoadPrincipal() {
+ RecursiveMutexAutoLock lock(mMutex);
+ mAllowedFontLoads.Clear();
+ IncrementGeneration(false);
+}
+
+// -- gfxUserFontSet
+// ------------------------------------------------
+
+already_AddRefed<gfxFontSrcPrincipal>
+FontFaceSetImpl::GetStandardFontLoadPrincipal() const {
+ RecursiveMutexAutoLock lock(mMutex);
+ return RefPtr{mStandardFontLoadPrincipal}.forget();
+}
+
+void FontFaceSetImpl::RecordFontLoadDone(uint32_t aFontSize,
+ TimeStamp aDoneTime) {
+ mDownloadCount++;
+ mDownloadSize += aFontSize;
+ Telemetry::Accumulate(Telemetry::WEBFONT_SIZE, aFontSize / 1024);
+
+ TimeStamp navStart = GetNavigationStartTimeStamp();
+ TimeStamp zero;
+ if (navStart != zero) {
+ mozilla::glean::network::font_download_end.AccumulateRawDuration(aDoneTime -
+ navStart);
+ }
+}
+
+void FontFaceSetImpl::DoRebuildUserFontSet() { MarkUserFontSetDirty(); }
+
+already_AddRefed<gfxUserFontEntry> FontFaceSetImpl::CreateUserFontEntry(
+ nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
+ gfxUserFontAttributes&& aAttr) {
+ RefPtr<gfxUserFontEntry> entry = new FontFaceImpl::Entry(
+ this, std::move(aFontFaceSrcList), std::move(aAttr));
+ return entry.forget();
+}
+
+void FontFaceSetImpl::ForgetLocalFaces() {
+ // We cannot hold our lock at the same time as the gfxUserFontFamily lock, so
+ // we need to make a copy of the table first.
+ nsTArray<RefPtr<gfxUserFontFamily>> fontFamilies;
+ {
+ RecursiveMutexAutoLock lock(mMutex);
+ fontFamilies.SetCapacity(mFontFamilies.Count());
+ for (const auto& fam : mFontFamilies.Values()) {
+ fontFamilies.AppendElement(fam);
+ }
+ }
+
+ for (const auto& fam : fontFamilies) {
+ ForgetLocalFace(fam);
+ }
+}
+
+already_AddRefed<gfxUserFontFamily> FontFaceSetImpl::GetFamily(
+ const nsACString& aFamilyName) {
+ RecursiveMutexAutoLock lock(mMutex);
+ return gfxUserFontSet::GetFamily(aFamilyName);
+}
+
+#undef LOG_ENABLED
+#undef LOG
diff --git a/layout/style/FontFaceSetImpl.h b/layout/style/FontFaceSetImpl.h
new file mode 100644
index 0000000000..6f245c6599
--- /dev/null
+++ b/layout/style/FontFaceSetImpl.h
@@ -0,0 +1,315 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_FontFaceSetImpl_h
+#define mozilla_dom_FontFaceSetImpl_h
+
+#include "mozilla/dom/FontFace.h"
+#include "mozilla/dom/FontFaceSetBinding.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/RecursiveMutex.h"
+#include "gfxUserFontSet.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsIDOMEventListener.h"
+
+#include <functional>
+
+struct gfxFontFaceSrc;
+class gfxFontSrcPrincipal;
+class gfxUserFontEntry;
+class nsFontFaceLoader;
+class nsIChannel;
+class nsIPrincipal;
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+struct StyleLockedFontFaceRule;
+class PostTraversalTask;
+class Runnable;
+class SharedFontList;
+namespace dom {
+class FontFace;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla::dom {
+
+class FontFaceSetImpl : public nsISupports, public gfxUserFontSet {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ public:
+ // gfxUserFontSet
+
+ already_AddRefed<gfxFontSrcPrincipal> GetStandardFontLoadPrincipal()
+ const final;
+
+ void RecordFontLoadDone(uint32_t aFontSize, TimeStamp aDoneTime) override;
+
+ bool BypassCache() final { return mBypassCache; }
+
+ void ForgetLocalFaces() final;
+
+ protected:
+ virtual nsresult CreateChannelForSyncLoadFontData(
+ nsIChannel** aOutChannel, gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc) = 0;
+
+ // gfxUserFontSet
+
+ bool GetPrivateBrowsing() override { return mPrivateBrowsing; }
+ nsresult SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ uint8_t*& aBuffer,
+ uint32_t& aBufferLength) override;
+ nsresult LogMessage(gfxUserFontEntry* aUserFontEntry, uint32_t aSrcIndex,
+ const char* aMessage,
+ uint32_t aFlags = nsIScriptError::errorFlag,
+ nsresult aStatus = NS_OK) override;
+ void DoRebuildUserFontSet() override;
+ already_AddRefed<gfxUserFontEntry> CreateUserFontEntry(
+ nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
+ gfxUserFontAttributes&& aAttr) override;
+
+ already_AddRefed<gfxUserFontFamily> GetFamily(
+ const nsACString& aFamilyName) final;
+
+ explicit FontFaceSetImpl(FontFaceSet* aOwner);
+
+ void DestroyLoaders();
+
+ public:
+ virtual void Destroy();
+ virtual bool IsOnOwningThread() = 0;
+#ifdef DEBUG
+ virtual void AssertIsOnOwningThread() = 0;
+#else
+ void AssertIsOnOwningThread() {}
+#endif
+ virtual void DispatchToOwningThread(const char* aName,
+ std::function<void()>&& aFunc) = 0;
+
+ // Called by nsFontFaceLoader when the loader has completed normally.
+ // It's removed from the mLoaders set.
+ virtual void RemoveLoader(nsFontFaceLoader* aLoader);
+
+ virtual bool UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules) {
+ MOZ_ASSERT_UNREACHABLE("Not implemented!");
+ return false;
+ }
+
+ // search for @font-face rule that matches a platform font entry
+ virtual StyleLockedFontFaceRule* FindRuleForEntry(gfxFontEntry* aFontEntry) {
+ MOZ_ASSERT_UNREACHABLE("Not implemented!");
+ return nullptr;
+ }
+
+ /**
+ * Finds an existing entry in the user font cache or creates a new user
+ * font entry for the given FontFace object.
+ */
+ static already_AddRefed<gfxUserFontEntry>
+ FindOrCreateUserFontEntryFromFontFace(FontFaceImpl* aFontFace,
+ gfxUserFontAttributes&& aAttr,
+ StyleOrigin);
+
+ /**
+ * Notification method called by a FontFace to indicate that its loading
+ * status has changed.
+ */
+ virtual void OnFontFaceStatusChanged(FontFaceImpl* aFontFace);
+
+ /**
+ * Notification method called by the nsPresContext to indicate that the
+ * refresh driver ticked and flushed style and layout.
+ * were just flushed.
+ */
+ virtual void DidRefresh() { MOZ_ASSERT_UNREACHABLE("Not implemented!"); }
+
+ virtual void FlushUserFontSet() = 0;
+
+ static nsPresContext* GetPresContextFor(gfxUserFontSet* aUserFontSet) {
+ const auto* set = static_cast<FontFaceSetImpl*>(aUserFontSet);
+ return set ? set->GetPresContext() : nullptr;
+ }
+
+ virtual void RefreshStandardFontLoadPrincipal();
+
+ virtual dom::Document* GetDocument() const { return nullptr; }
+
+ virtual already_AddRefed<URLExtraData> GetURLExtraData() = 0;
+
+ // -- Web IDL --------------------------------------------------------------
+
+ virtual void EnsureReady() {}
+ dom::FontFaceSetLoadStatus Status();
+
+ virtual bool Add(FontFaceImpl* aFontFace, ErrorResult& aRv);
+ virtual void Clear();
+ virtual bool Delete(FontFaceImpl* aFontFace);
+
+ // For ServoStyleSet to know ahead of time whether a font is loadable.
+ virtual void CacheFontLoadability() {
+ MOZ_ASSERT_UNREACHABLE("Not implemented!");
+ }
+
+ virtual void MarkUserFontSetDirty() {}
+
+ /**
+ * Checks to see whether it is time to resolve mReady and dispatch any
+ * "loadingdone" and "loadingerror" events.
+ */
+ virtual void CheckLoadingFinished();
+
+ virtual void FindMatchingFontFaces(const nsACString& aFont,
+ const nsAString& aText,
+ nsTArray<FontFace*>& aFontFaces,
+ ErrorResult& aRv);
+
+ virtual void DispatchCheckLoadingFinishedAfterDelay();
+
+ protected:
+ ~FontFaceSetImpl() override;
+
+ virtual uint64_t GetInnerWindowID() = 0;
+
+ /**
+ * Returns whether the given FontFace is currently "in" the FontFaceSet.
+ */
+ bool HasAvailableFontFace(FontFaceImpl* aFontFace);
+
+ /**
+ * Returns whether there might be any pending font loads, which should cause
+ * the mReady Promise not to be resolved yet.
+ */
+ virtual bool MightHavePendingFontLoads();
+
+ /**
+ * Checks to see whether it is time to replace mReady and dispatch a
+ * "loading" event.
+ */
+ void CheckLoadingStarted();
+
+ /**
+ * Callback for invoking CheckLoadingFinished after going through the
+ * event loop. See OnFontFaceStatusChanged.
+ */
+ void CheckLoadingFinishedAfterDelay();
+
+ void OnLoadingStarted();
+ void OnLoadingFinished();
+
+ // Note: if you add new cycle collected objects to FontFaceRecord,
+ // make sure to update FontFaceSet's cycle collection macros
+ // accordingly.
+ struct FontFaceRecord {
+ RefPtr<FontFaceImpl> mFontFace;
+ Maybe<StyleOrigin> mOrigin; // only relevant for mRuleFaces entries
+ };
+
+ // search for @font-face rule that matches a userfont font entry
+ virtual StyleLockedFontFaceRule* FindRuleForUserFontEntry(
+ gfxUserFontEntry* aUserFontEntry) {
+ return nullptr;
+ }
+
+ virtual void FindMatchingFontFaces(
+ const nsTHashSet<FontFace*>& aMatchingFaces,
+ nsTArray<FontFace*>& aFontFaces);
+
+ class UpdateUserFontEntryRunnable;
+ void UpdateUserFontEntry(gfxUserFontEntry* aEntry,
+ gfxUserFontAttributes&& aAttr);
+
+ nsresult CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
+ gfxFontSrcPrincipal** aPrincipal, bool* aBypassCache);
+
+ void InsertNonRuleFontFace(FontFaceImpl* aFontFace);
+
+ /**
+ * Returns whether we have any loading FontFace objects in the FontFaceSet.
+ */
+ bool HasLoadingFontFaces();
+
+ // Whether mReady is pending, or would be when created.
+ bool ReadyPromiseIsPending() const;
+
+ // Helper function for HasLoadingFontFaces.
+ virtual void UpdateHasLoadingFontFaces();
+
+ void ParseFontShorthandForMatching(const nsACString& aFont,
+ StyleFontFamilyList& aFamilyList,
+ FontWeight& aWeight, FontStretch& aStretch,
+ FontSlantStyle& aStyle, ErrorResult& aRv);
+
+ virtual TimeStamp GetNavigationStartTimeStamp() = 0;
+
+ mutable RecursiveMutex mMutex;
+
+ FontFaceSet* MOZ_NON_OWNING_REF mOwner MOZ_GUARDED_BY(mMutex);
+
+ // The document's node principal, which is the principal font loads for
+ // this FontFaceSet will generally use. (This principal is not used for
+ // @font-face rules in UA and user sheets, where the principal of the
+ // sheet is used instead.)
+ //
+ // This field is used from GetStandardFontLoadPrincipal. When on a
+ // style worker thread, we use mStandardFontLoadPrincipal assuming
+ // it is up to date.
+ //
+ // Because mDocument's principal can change over time,
+ // its value must be updated by a call to ResetStandardFontLoadPrincipal.
+ mutable RefPtr<gfxFontSrcPrincipal> mStandardFontLoadPrincipal
+ MOZ_GUARDED_BY(mMutex);
+
+ // Set of all loaders pointing to us. These are not strong pointers,
+ // but that's OK because nsFontFaceLoader always calls RemoveLoader on
+ // us before it dies (unless we die first).
+ nsTHashtable<nsPtrHashKey<nsFontFaceLoader>> mLoaders MOZ_GUARDED_BY(mMutex);
+
+ // The non rule backed FontFace objects that have been added to this
+ // FontFaceSet.
+ nsTArray<FontFaceRecord> mNonRuleFaces MOZ_GUARDED_BY(mMutex);
+
+ // The overall status of the loading or loaded fonts in the FontFaceSet.
+ dom::FontFaceSetLoadStatus mStatus MOZ_GUARDED_BY(mMutex);
+
+ // A map from gfxFontFaceSrc pointer identity to whether the load is allowed
+ // by CSP or other checks. We store this here because querying CSP off the
+ // main thread is not a great idea.
+ //
+ // We could use just the pointer and use this as a hash set, but then we'd
+ // have no way to verify that we've checked all the loads we should.
+ nsTHashMap<nsPtrHashKey<const gfxFontFaceSrc>, bool> mAllowedFontLoads
+ MOZ_GUARDED_BY(mMutex);
+
+ // Whether mNonRuleFaces has changed since last time UpdateRules ran.
+ bool mNonRuleFacesDirty MOZ_GUARDED_BY(mMutex);
+
+ // Whether any FontFace objects in mRuleFaces or mNonRuleFaces are
+ // loading. Only valid when mHasLoadingFontFacesIsDirty is false. Don't use
+ // this variable directly; call the HasLoadingFontFaces method instead.
+ bool mHasLoadingFontFaces MOZ_GUARDED_BY(mMutex);
+
+ // This variable is only valid when mLoadingDirty is false.
+ bool mHasLoadingFontFacesIsDirty MOZ_GUARDED_BY(mMutex);
+
+ // Whether CheckLoadingFinished calls should be ignored. See comment in
+ // OnFontFaceStatusChanged.
+ bool mDelayedLoadCheck MOZ_GUARDED_BY(mMutex);
+
+ // Whether the docshell for our document indicated that loads should
+ // bypass the cache.
+ bool mBypassCache;
+
+ // Whether the docshell for our document indicates that we are in private
+ // browsing mode.
+ bool mPrivateBrowsing;
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_FontFaceSetImpl_h)
diff --git a/layout/style/FontFaceSetIterator.cpp b/layout/style/FontFaceSetIterator.cpp
new file mode 100644
index 0000000000..ca05ee87d7
--- /dev/null
+++ b/layout/style/FontFaceSetIterator.cpp
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/FontFaceSetIterator.h"
+
+#include "js/Array.h" // JS::NewArrayObject
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION(FontFaceSetIterator, mFontFaceSet)
+
+FontFaceSetIterator::FontFaceSetIterator(FontFaceSet* aFontFaceSet,
+ bool aIsKeyAndValue)
+ : mFontFaceSet(aFontFaceSet),
+ mNextIndex(0),
+ mIsKeyAndValue(aIsKeyAndValue) {
+ MOZ_COUNT_CTOR(FontFaceSetIterator);
+}
+
+FontFaceSetIterator::~FontFaceSetIterator() {
+ MOZ_COUNT_DTOR(FontFaceSetIterator);
+}
+
+bool FontFaceSetIterator::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return FontFaceSetIterator_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+void FontFaceSetIterator::Next(JSContext* aCx,
+ FontFaceSetIteratorResult& aResult,
+ ErrorResult& aRv) {
+ if (!mFontFaceSet) {
+ aResult.mDone = true;
+ return;
+ }
+
+ // Skip over non-Author origin fonts (GetFontFaceAt returns nullptr
+ // for those).
+ FontFace* face;
+ while (!(face = mFontFaceSet->GetFontFaceAt(mNextIndex++))) {
+ if (mNextIndex >= mFontFaceSet->SizeIncludingNonAuthorOrigins()) {
+ break; // this iterator is done
+ }
+ }
+
+ if (!face) {
+ aResult.mValue.setUndefined();
+ aResult.mDone = true;
+ mFontFaceSet = nullptr;
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, face, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (mIsKeyAndValue) {
+ JS::RootedValueArray<2> values(aCx);
+ values[0].set(value);
+ values[1].set(value);
+
+ JS::Rooted<JSObject*> array(aCx);
+ array = JS::NewArrayObject(aCx, values);
+ if (array) {
+ aResult.mValue.setObject(*array);
+ }
+ } else {
+ aResult.mValue = value;
+ }
+
+ aResult.mDone = false;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/FontFaceSetIterator.h b/layout/style/FontFaceSetIterator.h
new file mode 100644
index 0000000000..e18669e9b5
--- /dev/null
+++ b/layout/style/FontFaceSetIterator.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_FontFaceSetIterator_h
+#define mozilla_dom_FontFaceSetIterator_h
+
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/FontFaceSetBinding.h"
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+
+namespace mozilla {
+namespace dom {
+
+class FontFaceSetIterator final {
+ public:
+ FontFaceSetIterator(FontFaceSet*, bool aIsKeyAndValue);
+
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(FontFaceSetIterator)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(FontFaceSetIterator)
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ // WebIDL
+ void Next(JSContext* aCx, FontFaceSetIteratorResult& aResult,
+ ErrorResult& aRv);
+
+ private:
+ ~FontFaceSetIterator();
+
+ RefPtr<FontFaceSet> mFontFaceSet;
+ uint32_t mNextIndex;
+ bool mIsKeyAndValue;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_FontFaceSetIterator_h)
diff --git a/layout/style/FontFaceSetWorkerImpl.cpp b/layout/style/FontFaceSetWorkerImpl.cpp
new file mode 100644
index 0000000000..015c9e5b18
--- /dev/null
+++ b/layout/style/FontFaceSetWorkerImpl.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "FontFaceSetWorkerImpl.h"
+#include "mozilla/FontLoaderUtils.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/LoadInfo.h"
+#include "nsContentPolicyUtils.h"
+#include "nsFontFaceLoader.h"
+#include "nsINetworkPredictor.h"
+#include "nsIWebNavigation.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+
+namespace mozilla::dom {
+
+#define LOG(...) \
+ MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, \
+ (__VA_ARGS__))
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug)
+
+NS_IMPL_ISUPPORTS_INHERITED0(FontFaceSetWorkerImpl, FontFaceSetImpl);
+
+FontFaceSetWorkerImpl::FontFaceSetWorkerImpl(FontFaceSet* aOwner)
+ : FontFaceSetImpl(aOwner) {}
+
+FontFaceSetWorkerImpl::~FontFaceSetWorkerImpl() = default;
+
+bool FontFaceSetWorkerImpl::Initialize(WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+
+ RefPtr<StrongWorkerRef> workerRef =
+ StrongWorkerRef::Create(aWorkerPrivate, "FontFaceSetWorkerImpl",
+ [self = RefPtr{this}] { self->Destroy(); });
+ if (NS_WARN_IF(!workerRef)) {
+ return false;
+ }
+
+ {
+ RecursiveMutexAutoLock lock(mMutex);
+ mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+ }
+
+ class InitRunnable final : public WorkerMainThreadRunnable {
+ public:
+ InitRunnable(WorkerPrivate* aWorkerPrivate, FontFaceSetWorkerImpl* aImpl)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ "FontFaceSetWorkerImpl :: Initialize"_ns),
+ mImpl(aImpl) {}
+
+ protected:
+ ~InitRunnable() override = default;
+
+ bool MainThreadRun() override {
+ mImpl->InitializeOnMainThread();
+ return true;
+ }
+
+ FontFaceSetWorkerImpl* mImpl;
+ };
+
+ IgnoredErrorResult rv;
+ auto runnable = MakeRefPtr<InitRunnable>(aWorkerPrivate, this);
+ runnable->Dispatch(Canceling, rv);
+ return !NS_WARN_IF(rv.Failed());
+}
+
+void FontFaceSetWorkerImpl::InitializeOnMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+ RecursiveMutexAutoLock lock(mMutex);
+
+ if (!mWorkerRef) {
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = mWorkerRef->Private();
+ nsIPrincipal* principal = workerPrivate->GetPrincipal();
+ nsIPrincipal* loadingPrincipal = workerPrivate->GetLoadingPrincipal();
+ nsIPrincipal* partitionedPrincipal = workerPrivate->GetPartitionedPrincipal();
+ nsIPrincipal* defaultPrincipal = principal ? principal : loadingPrincipal;
+
+ nsLoadFlags loadFlags = workerPrivate->GetLoadFlags();
+ uint32_t loadType = 0;
+
+ // Get the top-level worker.
+ WorkerPrivate* topWorkerPrivate = workerPrivate;
+ WorkerPrivate* parent = workerPrivate->GetParent();
+ while (parent) {
+ topWorkerPrivate = parent;
+ parent = topWorkerPrivate->GetParent();
+ }
+
+ // If the top-level worker is a dedicated worker and has a window, and the
+ // window has a docshell, the caching behavior of this worker should match
+ // that of that docshell. This matches the behaviour from
+ // WorkerScriptLoader::LoadScript.
+ if (topWorkerPrivate->IsDedicatedWorker()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow();
+ if (window) {
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ if (docShell) {
+ docShell->GetDefaultLoadFlags(&loadFlags);
+ docShell->GetLoadType(&loadType);
+ }
+ }
+ }
+
+ // Record the state of the "bypass cache" flags now. In theory the load type
+ // of a docshell could change after the document is loaded, but handling that
+ // doesn't seem too important. This matches the behaviour from
+ // FontFaceSetDocumentImpl::Initialize.
+ mBypassCache =
+ ((loadType >> 16) & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) ||
+ (loadFlags & nsIRequest::LOAD_BYPASS_CACHE);
+
+ // Same for the "private browsing" flag.
+ if (defaultPrincipal) {
+ mPrivateBrowsing = defaultPrincipal->GetPrivateBrowsingId() > 0;
+ }
+
+ mStandardFontLoadPrincipal =
+ MakeRefPtr<gfxFontSrcPrincipal>(defaultPrincipal, partitionedPrincipal);
+
+ mURLExtraData =
+ new URLExtraData(workerPrivate->GetBaseURI(),
+ workerPrivate->GetReferrerInfo(), defaultPrincipal);
+}
+
+void FontFaceSetWorkerImpl::Destroy() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ mWorkerRef = nullptr;
+ FontFaceSetImpl::Destroy();
+}
+
+bool FontFaceSetWorkerImpl::IsOnOwningThread() {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (!mWorkerRef) {
+ return false;
+ }
+
+ return mWorkerRef->Private()->IsOnCurrentThread();
+}
+
+#ifdef DEBUG
+void FontFaceSetWorkerImpl::AssertIsOnOwningThread() {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (mWorkerRef) {
+ MOZ_ASSERT(mWorkerRef->Private()->IsOnCurrentThread());
+ } else {
+ // Asserting during cycle collection if we are tearing down the worker is
+ // difficult. The only other thread that uses FontFace(Set)Impl objects is
+ // the main thread (if created from a worker).
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+}
+#endif
+
+void FontFaceSetWorkerImpl::DispatchToOwningThread(
+ const char* aName, std::function<void()>&& aFunc) {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (!mWorkerRef) {
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = mWorkerRef->Private();
+ if (workerPrivate->IsOnCurrentThread()) {
+ NS_DispatchToCurrentThread(
+ NS_NewCancelableRunnableFunction(aName, std::move(aFunc)));
+ return;
+ }
+
+ class FontFaceSetWorkerRunnable final : public WorkerRunnable {
+ public:
+ FontFaceSetWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ std::function<void()>&& aFunc)
+ : WorkerRunnable(aWorkerPrivate, "FontFaceSetWorkerRunnable"),
+ mFunc(std::move(aFunc)) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mFunc();
+ return true;
+ }
+
+ private:
+ std::function<void()> mFunc;
+ };
+
+ RefPtr<FontFaceSetWorkerRunnable> runnable =
+ new FontFaceSetWorkerRunnable(workerPrivate, std::move(aFunc));
+ runnable->Dispatch();
+}
+
+uint64_t FontFaceSetWorkerImpl::GetInnerWindowID() {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (!mWorkerRef) {
+ return 0;
+ }
+
+ return mWorkerRef->Private()->WindowID();
+}
+
+void FontFaceSetWorkerImpl::FlushUserFontSet() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ // If there was a change to the mNonRuleFaces array, then there could
+ // have been a modification to the user font set.
+ const bool modified = mNonRuleFacesDirty;
+ mNonRuleFacesDirty = false;
+
+ for (size_t i = 0, i_end = mNonRuleFaces.Length(); i < i_end; ++i) {
+ InsertNonRuleFontFace(mNonRuleFaces[i].mFontFace);
+ }
+
+ // Remove any residual families that have no font entries.
+ for (auto it = mFontFamilies.Iter(); !it.Done(); it.Next()) {
+ if (!it.Data()->FontListLength()) {
+ it.Remove();
+ }
+ }
+
+ if (modified) {
+ IncrementGeneration(true);
+ mHasLoadingFontFacesIsDirty = true;
+ CheckLoadingStarted();
+ CheckLoadingFinished();
+ }
+}
+
+already_AddRefed<gfxUserFontFamily> FontFaceSetWorkerImpl::LookupFamily(
+ const nsACString& aName) const {
+ RecursiveMutexAutoLock lock(mMutex);
+ return gfxUserFontSet::LookupFamily(aName);
+}
+
+nsresult FontFaceSetWorkerImpl::StartLoad(gfxUserFontEntry* aUserFontEntry,
+ uint32_t aSrcIndex) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ if (NS_WARN_IF(!mWorkerRef)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamLoader> streamLoader;
+
+ const gfxFontFaceSrc& src = aUserFontEntry->SourceAt(aSrcIndex);
+
+ nsCOMPtr<nsILoadGroup> loadGroup(mWorkerRef->Private()->GetLoadGroup());
+ nsCOMPtr<nsIChannel> channel;
+ rv = FontLoaderUtils::BuildChannel(
+ getter_AddRefs(channel), src.mURI->get(), CORS_ANONYMOUS,
+ dom::ReferrerPolicy::_empty /* not used */, aUserFontEntry, &src,
+ mWorkerRef->Private(), loadGroup, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsFontFaceLoader> fontLoader =
+ new nsFontFaceLoader(aUserFontEntry, aSrcIndex, this, channel);
+
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> referrer =
+ src.mReferrerInfo ? src.mReferrerInfo->GetOriginalReferrer() : nullptr;
+ LOG("userfonts (%p) download start - font uri: (%s) referrer uri: (%s)\n",
+ fontLoader.get(), src.mURI->GetSpecOrDefault().get(),
+ referrer ? referrer->GetSpecOrDefault().get() : "");
+ }
+
+ rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader, fontLoader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->AsyncOpen(streamLoader);
+ if (NS_FAILED(rv)) {
+ fontLoader->DropChannel(); // explicitly need to break ref cycle
+ }
+
+ mLoaders.PutEntry(fontLoader);
+
+ net::PredictorLearn(src.mURI->get(), mWorkerRef->Private()->GetBaseURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, loadGroup);
+
+ if (NS_SUCCEEDED(rv)) {
+ fontLoader->StartedLoading(streamLoader);
+ // let the font entry remember the loader, in case we need to cancel it
+ aUserFontEntry->SetLoader(fontLoader);
+ }
+
+ return rv;
+}
+
+bool FontFaceSetWorkerImpl::IsFontLoadAllowed(const gfxFontFaceSrc& aSrc) {
+ MOZ_ASSERT(aSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RecursiveMutexAutoLock lock(mMutex);
+
+ if (aSrc.mUseOriginPrincipal) {
+ return true;
+ }
+
+ if (NS_WARN_IF(!mWorkerRef)) {
+ return false;
+ }
+
+ RefPtr<gfxFontSrcPrincipal> gfxPrincipal =
+ aSrc.mURI->InheritsSecurityContext() ? nullptr
+ : aSrc.LoadPrincipal(*this);
+
+ nsIPrincipal* principal =
+ gfxPrincipal ? gfxPrincipal->NodePrincipal() : nullptr;
+
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
+ mWorkerRef->Private()->GetLoadingPrincipal(), // loading principal
+ principal, // triggering principal
+ nullptr, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_FONT);
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv =
+ NS_CheckContentLoadPolicy(aSrc.mURI->get(), secCheckLoadInfo, &shouldLoad,
+ nsContentUtils::GetContentPolicy());
+
+ return NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(shouldLoad);
+}
+
+nsresult FontFaceSetWorkerImpl::CreateChannelForSyncLoadFontData(
+ nsIChannel** aOutChannel, gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc) {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (NS_WARN_IF(!mWorkerRef)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gfxFontSrcPrincipal* principal = aFontToLoad->GetPrincipal();
+
+ // We only get here for data: loads, so it doesn't really matter whether we
+ // use SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT or not, to be more
+ // restrictive we use SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT.
+ return NS_NewChannelWithTriggeringPrincipal(
+ aOutChannel, aFontFaceSrc->mURI->get(),
+ mWorkerRef->Private()->GetLoadingPrincipal(),
+ principal ? principal->NodePrincipal() : nullptr,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ aFontFaceSrc->mUseOriginPrincipal ? nsIContentPolicy::TYPE_UA_FONT
+ : nsIContentPolicy::TYPE_FONT);
+}
+
+nsPresContext* FontFaceSetWorkerImpl::GetPresContext() const { return nullptr; }
+
+TimeStamp FontFaceSetWorkerImpl::GetNavigationStartTimeStamp() {
+ RecursiveMutexAutoLock lock(mMutex);
+ if (!mWorkerRef) {
+ return TimeStamp();
+ }
+
+ return mWorkerRef->Private()->CreationTimeStamp();
+}
+
+already_AddRefed<URLExtraData> FontFaceSetWorkerImpl::GetURLExtraData() {
+ RecursiveMutexAutoLock lock(mMutex);
+ return RefPtr{mURLExtraData}.forget();
+}
+
+#undef LOG_ENABLED
+#undef LOG
+
+} // namespace mozilla::dom
diff --git a/layout/style/FontFaceSetWorkerImpl.h b/layout/style/FontFaceSetWorkerImpl.h
new file mode 100644
index 0000000000..b699f8d52d
--- /dev/null
+++ b/layout/style/FontFaceSetWorkerImpl.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FontFaceSetWorkerImpl_h
+#define mozilla_dom_FontFaceSetWorkerImpl_h
+
+#include "mozilla/dom/FontFaceSetImpl.h"
+
+namespace mozilla::dom {
+class ThreadSafeWorkerRef;
+class WorkerPrivate;
+
+class FontFaceSetWorkerImpl final : public FontFaceSetImpl {
+ NS_DECL_ISUPPORTS_INHERITED
+
+ public:
+ explicit FontFaceSetWorkerImpl(FontFaceSet* aOwner);
+
+ bool Initialize(WorkerPrivate* aWorkerPrivate);
+ void Destroy() override;
+
+ bool IsOnOwningThread() override;
+#ifdef DEBUG
+ void AssertIsOnOwningThread() override;
+#endif
+ void DispatchToOwningThread(const char* aName,
+ std::function<void()>&& aFunc) override;
+
+ already_AddRefed<URLExtraData> GetURLExtraData() override;
+
+ void FlushUserFontSet() override;
+
+ // gfxUserFontSet
+
+ already_AddRefed<gfxUserFontFamily> LookupFamily(
+ const nsACString& aName) const override;
+
+ nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
+ uint32_t aSrcIndex) override;
+
+ bool IsFontLoadAllowed(const gfxFontFaceSrc&) override;
+
+ nsPresContext* GetPresContext() const override;
+
+ private:
+ ~FontFaceSetWorkerImpl() override;
+
+ void InitializeOnMainThread();
+
+ uint64_t GetInnerWindowID() override;
+
+ nsresult CreateChannelForSyncLoadFontData(
+ nsIChannel** aOutChannel, gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc) override;
+
+ TimeStamp GetNavigationStartTimeStamp() override;
+
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef MOZ_GUARDED_BY(mMutex);
+
+ RefPtr<URLExtraData> mURLExtraData MOZ_GUARDED_BY(mMutex);
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_FontFaceSetWorkerImpl_h)
diff --git a/layout/style/FontLoaderUtils.cpp b/layout/style/FontLoaderUtils.cpp
new file mode 100644
index 0000000000..9690e703fd
--- /dev/null
+++ b/layout/style/FontLoaderUtils.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/FontLoaderUtils.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "gfxUserFontSet.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsIClassOfService.h"
+#include "nsIContentPolicy.h"
+#include "nsIHttpChannel.h"
+#include "nsILoadInfo.h"
+#include "nsIReferrerInfo.h"
+#include "nsISupportsPriority.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+
+/* static */ void FontLoaderUtils::BuildChannelFlags(
+ nsIURI* aURI, bool aIsPreload,
+ nsContentSecurityManager::CORSSecurityMapping& aCorsMapping,
+ nsSecurityFlags& aSecurityFlags, nsContentPolicyType& aContentPolicyType) {
+ // aCORSMode is ignored. We always load as crossorigin=anonymous, but a
+ // preload started with anything other then "anonymous" will never be found.
+ aCorsMapping =
+ aURI->SchemeIs("file")
+ ? nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_INHERITED_CONTEXT
+ : nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS;
+
+ aSecurityFlags = nsContentSecurityManager::ComputeSecurityFlags(
+ CORSMode::CORS_NONE, aCorsMapping);
+
+ aContentPolicyType = aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD
+ : nsIContentPolicy::TYPE_FONT;
+}
+
+/* static */ nsresult FontLoaderUtils::BuildChannelSetup(
+ nsIChannel* aChannel, nsIHttpChannel* aHttpChannel,
+ nsIReferrerInfo* aReferrerInfo, const gfxFontFaceSrc* aFontFaceSrc,
+ int32_t aSupportsPriorityValue) {
+ if (aHttpChannel) {
+ nsresult rv = aHttpChannel->SetRequestHeader(
+ "Accept"_ns,
+ "application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8"_ns,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aReferrerInfo) {
+ rv = aHttpChannel->SetReferrerInfoWithoutClone(aReferrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ MOZ_ASSERT(aFontFaceSrc);
+
+ rv = aHttpChannel->SetReferrerInfo(aFontFaceSrc->mReferrerInfo);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ // For WOFF and WOFF2, we should tell servers/proxies/etc NOT to try
+ // and apply additional compression at the content-encoding layer
+ if (aFontFaceSrc->mFormatHint == StyleFontFaceSourceFormatKeyword::Woff ||
+ aFontFaceSrc->mFormatHint ==
+ StyleFontFaceSourceFormatKeyword::Woff2) {
+ rv = aHttpChannel->SetRequestHeader("Accept-Encoding"_ns, "identity"_ns,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ nsCOMPtr<nsISupportsPriority> priorityChannel(do_QueryInterface(aChannel));
+ if (priorityChannel) {
+ priorityChannel->SetPriority(aSupportsPriorityValue);
+ }
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(aChannel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::TailForbidden);
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult FontLoaderUtils::BuildChannel(
+ nsIChannel** aChannel, nsIURI* aURI, const CORSMode aCORSMode,
+ const dom::ReferrerPolicy& aReferrerPolicy,
+ gfxUserFontEntry* aUserFontEntry, const gfxFontFaceSrc* aFontFaceSrc,
+ dom::Document* aDocument, nsILoadGroup* aLoadGroup,
+ nsIInterfaceRequestor* aCallbacks, bool aIsPreload,
+ int32_t aSupportsPriorityValue) {
+ nsresult rv;
+
+ nsIPrincipal* principal =
+ aUserFontEntry ? (aUserFontEntry->GetPrincipal()
+ ? aUserFontEntry->GetPrincipal()->NodePrincipal()
+ : nullptr)
+ : aDocument->NodePrincipal();
+
+ nsContentSecurityManager::CORSSecurityMapping corsMapping;
+ nsSecurityFlags securityFlags;
+ nsContentPolicyType contentPolicyType;
+ BuildChannelFlags(aURI, aIsPreload, corsMapping, securityFlags,
+ contentPolicyType);
+
+ nsCOMPtr<nsIChannel> channel;
+ // Note we are calling NS_NewChannelWithTriggeringPrincipal() with both a
+ // node and a principal. This is because the document where the font is
+ // being loaded might have a different origin from the principal of the
+ // stylesheet that initiated the font load.
+ rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel), aURI,
+ aDocument, principal, securityFlags,
+ contentPolicyType,
+ nullptr, // PerformanceStorage
+ aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (httpChannel && !aFontFaceSrc) {
+ referrerInfo = new dom::ReferrerInfo(aDocument->GetDocumentURIAsReferrer(),
+ aReferrerPolicy);
+ rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ rv = BuildChannelSetup(channel, httpChannel, referrerInfo, aFontFaceSrc,
+ aSupportsPriorityValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(aChannel);
+ return NS_OK;
+}
+
+// static
+nsresult FontLoaderUtils::BuildChannel(
+ nsIChannel** aChannel, nsIURI* aURI, const CORSMode aCORSMode,
+ const dom::ReferrerPolicy& aReferrerPolicy,
+ gfxUserFontEntry* aUserFontEntry, const gfxFontFaceSrc* aFontFaceSrc,
+ dom::WorkerPrivate* aWorkerPrivate, nsILoadGroup* aLoadGroup,
+ nsIInterfaceRequestor* aCallbacks) {
+ nsresult rv;
+
+ nsIPrincipal* principal =
+ aUserFontEntry ? (aUserFontEntry->GetPrincipal()
+ ? aUserFontEntry->GetPrincipal()->NodePrincipal()
+ : nullptr)
+ : aWorkerPrivate->GetPrincipal();
+
+ nsContentSecurityManager::CORSSecurityMapping corsMapping;
+ nsSecurityFlags securityFlags;
+ nsContentPolicyType contentPolicyType;
+ BuildChannelFlags(aURI, /* aIsPreload */ false, corsMapping, securityFlags,
+ contentPolicyType);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannelWithTriggeringPrincipal(
+ getter_AddRefs(channel), aURI, aWorkerPrivate->GetLoadingPrincipal(),
+ principal, securityFlags, contentPolicyType, nullptr, nullptr,
+ aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (httpChannel && !aFontFaceSrc) {
+ referrerInfo =
+ static_cast<dom::ReferrerInfo*>(aWorkerPrivate->GetReferrerInfo())
+ ->CloneWithNewPolicy(aReferrerPolicy);
+ }
+
+ rv = BuildChannelSetup(channel, httpChannel, referrerInfo, aFontFaceSrc,
+ nsISupportsPriority::PRIORITY_HIGH);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(aChannel);
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/layout/style/FontLoaderUtils.h b/layout/style/FontLoaderUtils.h
new file mode 100644
index 0000000000..0c6c77866f
--- /dev/null
+++ b/layout/style/FontLoaderUtils.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GECKO_LAYOUT_STYLE_FONTLOADERUTILS_H_
+#define GECKO_LAYOUT_STYLE_FONTLOADERUTILS_H_
+
+#include "ErrorList.h"
+#include "nsContentSecurityManager.h"
+
+class gfxUserFontEntry;
+class nsIChannel;
+class nsIHttpChannel;
+class nsIInterfaceRequestor;
+class nsILoadGroup;
+class nsIURI;
+struct gfxFontFaceSrc;
+
+namespace mozilla {
+enum CORSMode : uint8_t;
+namespace dom {
+class Document;
+class WorkerPrivate;
+enum class ReferrerPolicy : uint8_t;
+} // namespace dom
+
+class FontLoaderUtils {
+ public:
+ // @param aSuppportsPriorityValue See <nsISupportsPriority.idl>.
+ static nsresult BuildChannel(nsIChannel** aChannel, nsIURI* aURI,
+ const CORSMode aCORSMode,
+ const dom::ReferrerPolicy& aReferrerPolicy,
+ gfxUserFontEntry* aUserFontEntry,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ dom::Document* aDocument,
+ nsILoadGroup* aLoadGroup,
+ nsIInterfaceRequestor* aCallbacks,
+ bool aIsPreload, int32_t aSupportsPriorityValue);
+
+ static nsresult BuildChannel(nsIChannel** aChannel, nsIURI* aURI,
+ const CORSMode aCORSMode,
+ const dom::ReferrerPolicy& aReferrerPolicy,
+ gfxUserFontEntry* aUserFontEntry,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ dom::WorkerPrivate* aWorkerPrivate,
+ nsILoadGroup* aLoadGroup,
+ nsIInterfaceRequestor* aCallbacks);
+
+ private:
+ static void BuildChannelFlags(
+ nsIURI* aURI, bool aIsPreload,
+ nsContentSecurityManager::CORSSecurityMapping& aCorsMapping,
+ nsSecurityFlags& aSecurityFlags, nsContentPolicyType& aContentPolicyType);
+
+ static nsresult BuildChannelSetup(nsIChannel* aChannel,
+ nsIHttpChannel* aHttpChannel,
+ nsIReferrerInfo* aReferrerInfo,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ int32_t aSupportsPriorityValue);
+};
+
+} // namespace mozilla
+
+#endif // GECKO_LAYOUT_STYLE_FONTLOADERUTILS_H_
diff --git a/layout/style/FontPreloader.cpp b/layout/style/FontPreloader.cpp
new file mode 100644
index 0000000000..564d6004da
--- /dev/null
+++ b/layout/style/FontPreloader.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FontPreloader.h"
+
+#include "mozilla/FontLoaderUtils.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+
+FontPreloader::FontPreloader()
+ : FetchPreloader(nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD) {}
+
+nsresult FontPreloader::CreateChannel(
+ nsIChannel** aChannel, nsIURI* aURI, const CORSMode aCORSMode,
+ const dom::ReferrerPolicy& aReferrerPolicy, dom::Document* aDocument,
+ nsILoadGroup* aLoadGroup, nsIInterfaceRequestor* aCallbacks,
+ uint64_t aEarlyHintPreloaderId, int32_t aSupportsPriorityValue) {
+ // Don't preload fonts if they've been preffed-off.
+ if (!gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return FontLoaderUtils::BuildChannel(
+ aChannel, aURI, aCORSMode, aReferrerPolicy, nullptr, nullptr, aDocument,
+ aLoadGroup, aCallbacks, true, aSupportsPriorityValue);
+}
+
+} // namespace mozilla
diff --git a/layout/style/FontPreloader.h b/layout/style/FontPreloader.h
new file mode 100644
index 0000000000..23e4cc38da
--- /dev/null
+++ b/layout/style/FontPreloader.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 FontPreloader_h_
+#define FontPreloader_h_
+
+#include "mozilla/FetchPreloader.h"
+
+namespace mozilla {
+
+class FontPreloader final : public FetchPreloader {
+ public:
+ FontPreloader();
+
+ protected:
+ nsresult CreateChannel(nsIChannel** aChannel, nsIURI* aURI,
+ const CORSMode aCORSMode,
+ const dom::ReferrerPolicy& aReferrerPolicy,
+ dom::Document* aDocument, nsILoadGroup* aLoadGroup,
+ nsIInterfaceRequestor* aCallbacks,
+ uint64_t aEarlyHintPreloaderId,
+ int32_t aSupportsPriorityValue) override;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/GeckoBindings.cpp b/layout/style/GeckoBindings.cpp
new file mode 100644
index 0000000000..5c7992f8db
--- /dev/null
+++ b/layout/style/GeckoBindings.cpp
@@ -0,0 +1,1819 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* FFI functions for Servo to call into Gecko */
+
+#include "mozilla/GeckoBindings.h"
+
+#include "ChildIterator.h"
+#include "ErrorReporter.h"
+#include "gfxFontFeatures.h"
+#include "gfxMathTable.h"
+#include "gfxTextRun.h"
+#include "imgLoader.h"
+#include "nsAnimationManager.h"
+#include "nsAttrValueInlines.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCSSProps.h"
+#include "nsCSSPseudoElements.h"
+#include "nsContentUtils.h"
+#include "nsDOMTokenList.h"
+#include "nsDeviceContext.h"
+#include "nsLayoutUtils.h"
+#include "nsIContentInlines.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsILoadContext.h"
+#include "nsIFrame.h"
+#include "nsIMozBrowserFrame.h"
+#include "nsINode.h"
+#include "nsIURI.h"
+#include "nsFontMetrics.h"
+#include "nsNameSpaceManager.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsString.h"
+#include "nsStyleStruct.h"
+#include "nsStyleUtil.h"
+#include "nsTArray.h"
+#include "nsTransitionManager.h"
+#include "nsWindowSizes.h"
+
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/AttributeStyles.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Keyframe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/ShadowParts.h"
+#include "mozilla/StaticPresData.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/SizeOfState.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoTraversalStatistics.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimelineManager.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLTableCellElement.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "mozilla/dom/MediaList.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/SVGElement.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/URLExtraData.h"
+#include "mozilla/dom/CSSMozDocumentRule.h"
+
+#if defined(MOZ_MEMORY)
+# include "mozmemory.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+
+// Definitions of the global traversal stats.
+bool ServoTraversalStatistics::sActive = false;
+ServoTraversalStatistics ServoTraversalStatistics::sSingleton;
+
+static StaticAutoPtr<RWLock> sServoFFILock;
+
+static const LangGroupFontPrefs* ThreadSafeGetLangGroupFontPrefs(
+ const Document& aDocument, nsAtom* aLanguage) {
+ bool needsCache = false;
+ {
+ AutoReadLock guard(*sServoFFILock);
+ if (auto* prefs = aDocument.GetFontPrefsForLang(aLanguage, &needsCache)) {
+ return prefs;
+ }
+ }
+ MOZ_ASSERT(needsCache);
+ AutoWriteLock guard(*sServoFFILock);
+ return aDocument.GetFontPrefsForLang(aLanguage);
+}
+
+static const nsFont& ThreadSafeGetDefaultVariableFont(const Document& aDocument,
+ nsAtom* aLanguage) {
+ return ThreadSafeGetLangGroupFontPrefs(aDocument, aLanguage)
+ ->mDefaultVariableFont;
+}
+
+/*
+ * Does this child count as significant for selector matching?
+ *
+ * See nsStyleUtil::IsSignificantChild for details.
+ */
+bool Gecko_IsSignificantChild(const nsINode* aNode,
+ bool aWhitespaceIsSignificant) {
+ return nsStyleUtil::ThreadSafeIsSignificantChild(aNode->AsContent(),
+ aWhitespaceIsSignificant);
+}
+
+const nsINode* Gecko_GetLastChild(const nsINode* aNode) {
+ return aNode->GetLastChild();
+}
+
+const nsINode* Gecko_GetFlattenedTreeParentNode(const nsINode* aNode) {
+ return aNode->GetFlattenedTreeParentNodeForStyle();
+}
+
+const Element* Gecko_GetBeforeOrAfterPseudo(const Element* aElement,
+ bool aIsBefore) {
+ MOZ_ASSERT(aElement);
+ MOZ_ASSERT(aElement->HasProperties());
+
+ return aIsBefore ? nsLayoutUtils::GetBeforePseudo(aElement)
+ : nsLayoutUtils::GetAfterPseudo(aElement);
+}
+
+const Element* Gecko_GetMarkerPseudo(const Element* aElement) {
+ MOZ_ASSERT(aElement);
+ MOZ_ASSERT(aElement->HasProperties());
+
+ return nsLayoutUtils::GetMarkerPseudo(aElement);
+}
+
+nsTArray<nsIContent*>* Gecko_GetAnonymousContentForElement(
+ const Element* aElement) {
+ nsIAnonymousContentCreator* ac = do_QueryFrame(aElement->GetPrimaryFrame());
+ if (!ac) {
+ return nullptr;
+ }
+
+ auto* array = new nsTArray<nsIContent*>();
+ nsContentUtils::AppendNativeAnonymousChildren(aElement, *array, 0);
+ return array;
+}
+
+void Gecko_DestroyAnonymousContentList(nsTArray<nsIContent*>* aAnonContent) {
+ MOZ_ASSERT(aAnonContent);
+ delete aAnonContent;
+}
+
+const nsTArray<RefPtr<nsINode>>* Gecko_GetAssignedNodes(
+ const Element* aElement) {
+ MOZ_ASSERT(HTMLSlotElement::FromNode(aElement));
+ return &static_cast<const HTMLSlotElement*>(aElement)->AssignedNodes();
+}
+
+void Gecko_GetQueryContainerSize(const Element* aElement, nscoord* aOutWidth,
+ nscoord* aOutHeight) {
+ MOZ_ASSERT(aElement);
+ const nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (!frame) {
+ return;
+ }
+ const auto containAxes = frame->GetContainSizeAxes();
+ if (!containAxes.IsAny()) {
+ return;
+ }
+ nsSize size = frame->GetContentRectRelativeToSelf().Size();
+ bool isVertical = frame->GetWritingMode().IsVertical();
+ if (isVertical ? containAxes.mBContained : containAxes.mIContained) {
+ *aOutWidth = size.width;
+ }
+ if (isVertical ? containAxes.mIContained : containAxes.mBContained) {
+ *aOutHeight = size.height;
+ }
+}
+
+void Gecko_ComputedStyle_Init(ComputedStyle* aStyle,
+ const ServoComputedData* aValues,
+ PseudoStyleType aPseudoType) {
+ new (KnownNotNull, aStyle)
+ ComputedStyle(aPseudoType, ServoComputedDataForgotten(aValues));
+}
+
+ServoComputedData::ServoComputedData(const ServoComputedDataForgotten aValue) {
+ PodAssign(this, aValue.mPtr);
+}
+
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoStyleStructsMallocEnclosingSizeOf)
+
+void ServoComputedData::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
+ // Note: GetStyleFoo() returns a pointer to an nsStyleFoo that sits within a
+ // servo_arc::Arc, i.e. it is preceded by a word-sized refcount. So we need
+ // to measure it with a function that can handle an interior pointer. We use
+ // ServoStyleStructsEnclosingMallocSizeOf to clearly identify in DMD's
+ // output the memory measured here.
+#define STYLE_STRUCT(name_) \
+ static_assert(alignof(nsStyle##name_) <= sizeof(size_t), \
+ "alignment will break AddSizeOfExcludingThis()"); \
+ const void* p##name_ = Style##name_(); \
+ if (!aSizes.mState.HaveSeenPtr(p##name_)) { \
+ aSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_) += \
+ ServoStyleStructsMallocEnclosingSizeOf(p##name_); \
+ }
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ if (visited_style && !aSizes.mState.HaveSeenPtr(visited_style)) {
+ visited_style->AddSizeOfIncludingThis(aSizes,
+ &aSizes.mLayoutComputedValuesVisited);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - custom_properties
+ // - writing_mode
+ // - rules
+ // - font_computation_data
+}
+
+void Gecko_ComputedStyle_Destroy(ComputedStyle* aStyle) {
+ aStyle->~ComputedStyle();
+}
+
+void Gecko_ConstructStyleChildrenIterator(const Element* aElement,
+ StyleChildrenIterator* aIterator) {
+ MOZ_ASSERT(aElement);
+ MOZ_ASSERT(aIterator);
+ new (aIterator) StyleChildrenIterator(aElement);
+}
+
+void Gecko_DestroyStyleChildrenIterator(StyleChildrenIterator* aIterator) {
+ MOZ_ASSERT(aIterator);
+
+ aIterator->~StyleChildrenIterator();
+}
+
+const nsINode* Gecko_GetNextStyleChild(StyleChildrenIterator* aIterator) {
+ MOZ_ASSERT(aIterator);
+ return aIterator->GetNextChild();
+}
+
+bool Gecko_VisitedStylesEnabled(const Document* aDoc) {
+ MOZ_ASSERT(aDoc);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!StaticPrefs::layout_css_visited_links_enabled()) {
+ return false;
+ }
+
+ if (aDoc->IsBeingUsedAsImage()) {
+ return false;
+ }
+
+ nsILoadContext* loadContext = aDoc->GetLoadContext();
+ if (loadContext && loadContext->UsePrivateBrowsing()) {
+ return false;
+ }
+
+ return true;
+}
+
+ElementState::InternalType Gecko_ElementState(const Element* aElement) {
+ return aElement->StyleState().GetInternalValue();
+}
+
+bool Gecko_IsRootElement(const Element* aElement) {
+ return aElement->OwnerDoc()->GetRootElement() == aElement;
+}
+
+void Gecko_NoteDirtyElement(const Element* aElement) {
+ MOZ_ASSERT(NS_IsMainThread());
+ const_cast<Element*>(aElement)->NoteDirtyForServo();
+}
+
+void Gecko_NoteDirtySubtreeForInvalidation(const Element* aElement) {
+ MOZ_ASSERT(NS_IsMainThread());
+ const_cast<Element*>(aElement)->NoteDirtySubtreeForServo();
+}
+
+void Gecko_NoteAnimationOnlyDirtyElement(const Element* aElement) {
+ MOZ_ASSERT(NS_IsMainThread());
+ const_cast<Element*>(aElement)->NoteAnimationOnlyDirtyForServo();
+}
+
+bool Gecko_AnimationNameMayBeReferencedFromStyle(
+ const nsPresContext* aPresContext, nsAtom* aName) {
+ MOZ_ASSERT(aPresContext);
+ return aPresContext->AnimationManager()->AnimationMayBeReferenced(aName);
+}
+
+float Gecko_GetScrollbarInlineSize(const nsPresContext* aPc) {
+ MOZ_ASSERT(aPc);
+ AutoWriteLock guard(*sServoFFILock); // We read some look&feel values.
+ auto overlay = aPc->UseOverlayScrollbars() ? nsITheme::Overlay::Yes
+ : nsITheme::Overlay::No;
+ LayoutDeviceIntCoord size =
+ aPc->Theme()->GetScrollbarSize(aPc, StyleScrollbarWidth::Auto, overlay);
+ return aPc->DevPixelsToFloatCSSPixels(size);
+}
+
+PseudoStyleType Gecko_GetImplementedPseudo(const Element* aElement) {
+ return aElement->GetPseudoElementType();
+}
+
+uint32_t Gecko_CalcStyleDifference(const ComputedStyle* aOldStyle,
+ const ComputedStyle* aNewStyle,
+ bool* aAnyStyleStructChanged,
+ bool* aOnlyResetStructsChanged) {
+ MOZ_ASSERT(aOldStyle);
+ MOZ_ASSERT(aNewStyle);
+
+ uint32_t equalStructs;
+ nsChangeHint result =
+ aOldStyle->CalcStyleDifference(*aNewStyle, &equalStructs);
+
+ *aAnyStyleStructChanged =
+ equalStructs != StyleStructConstants::kAllStructsMask;
+
+ const auto kInheritedStructsMask =
+ StyleStructConstants::kInheritedStructsMask;
+ *aOnlyResetStructsChanged =
+ (equalStructs & kInheritedStructsMask) == kInheritedStructsMask;
+
+ return result;
+}
+
+nscoord Gecko_CalcLineHeight(const StyleLineHeight* aLh,
+ const nsPresContext* aPc, bool aVertical,
+ const nsStyleFont* aAgainstFont,
+ const mozilla::dom::Element* aElement) {
+ // Normal line-height depends on font metrics.
+ AutoWriteLock guard(*sServoFFILock);
+ return ReflowInput::CalcLineHeight(*aLh, *aAgainstFont,
+ const_cast<nsPresContext*>(aPc), aVertical,
+ aElement, NS_UNCONSTRAINEDSIZE, 1.0f);
+}
+
+const ServoElementSnapshot* Gecko_GetElementSnapshot(
+ const ServoElementSnapshotTable* aTable, const Element* aElement) {
+ MOZ_ASSERT(aTable);
+ MOZ_ASSERT(aElement);
+
+ return aTable->Get(const_cast<Element*>(aElement));
+}
+
+bool Gecko_HaveSeenPtr(SeenPtrs* aTable, const void* aPtr) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTable);
+ // Empty Rust allocations are indicated by small values up to the alignment
+ // of the relevant type. We shouldn't see anything like that here.
+ MOZ_ASSERT(uintptr_t(aPtr) > 16);
+
+ return aTable->HaveSeenPtr(aPtr);
+}
+
+const StyleLockedDeclarationBlock* Gecko_GetStyleAttrDeclarationBlock(
+ const Element* aElement) {
+ DeclarationBlock* decl = aElement->GetInlineStyleDeclaration();
+ if (!decl) {
+ return nullptr;
+ }
+ return decl->Raw();
+}
+
+void Gecko_UnsetDirtyStyleAttr(const Element* aElement) {
+ DeclarationBlock* decl = aElement->GetInlineStyleDeclaration();
+ if (!decl) {
+ return;
+ }
+ decl->UnsetDirty();
+}
+
+const StyleLockedDeclarationBlock*
+Gecko_GetHTMLPresentationAttrDeclarationBlock(const Element* aElement) {
+ return aElement->GetMappedAttributeStyle();
+}
+
+const StyleLockedDeclarationBlock* Gecko_GetExtraContentStyleDeclarations(
+ const Element* aElement) {
+ if (const auto* cell = HTMLTableCellElement::FromNode(aElement)) {
+ return cell->GetMappedAttributesInheritedFromTable();
+ }
+ if (const auto* img = HTMLImageElement::FromNode(aElement)) {
+ return img->GetMappedAttributesFromSource();
+ }
+ return nullptr;
+}
+
+const StyleLockedDeclarationBlock* Gecko_GetUnvisitedLinkAttrDeclarationBlock(
+ const Element* aElement) {
+ AttributeStyles* attrStyles = aElement->OwnerDoc()->GetAttributeStyles();
+ if (!attrStyles) {
+ return nullptr;
+ }
+
+ return attrStyles->GetServoUnvisitedLinkDecl();
+}
+
+StyleSheet* Gecko_StyleSheet_Clone(const StyleSheet* aSheet,
+ const StyleSheet* aNewParentSheet) {
+ MOZ_ASSERT(aSheet);
+ MOZ_ASSERT(aSheet->GetParentSheet(), "Should only be used for @import");
+ MOZ_ASSERT(aNewParentSheet, "Wat");
+
+ RefPtr<StyleSheet> newSheet = aSheet->Clone(nullptr, nullptr);
+
+ // NOTE(emilio): This code runs in the StylesheetInner constructor, which
+ // means that the inner pointer of `aNewParentSheet` still points to the old
+ // one.
+ //
+ // So we _don't_ update neither the parent pointer of the stylesheet, nor the
+ // child list (yet). This is fixed up in that same constructor.
+ return static_cast<StyleSheet*>(newSheet.forget().take());
+}
+
+void Gecko_StyleSheet_AddRef(const StyleSheet* aSheet) {
+ MOZ_ASSERT(NS_IsMainThread());
+ const_cast<StyleSheet*>(aSheet)->AddRef();
+}
+
+void Gecko_StyleSheet_Release(const StyleSheet* aSheet) {
+ MOZ_ASSERT(NS_IsMainThread());
+ const_cast<StyleSheet*>(aSheet)->Release();
+}
+
+const StyleLockedDeclarationBlock* Gecko_GetVisitedLinkAttrDeclarationBlock(
+ const Element* aElement) {
+ AttributeStyles* attrStyles = aElement->OwnerDoc()->GetAttributeStyles();
+ if (!attrStyles) {
+ return nullptr;
+ }
+ return attrStyles->GetServoVisitedLinkDecl();
+}
+
+const StyleLockedDeclarationBlock* Gecko_GetActiveLinkAttrDeclarationBlock(
+ const Element* aElement) {
+ AttributeStyles* attrStyles = aElement->OwnerDoc()->GetAttributeStyles();
+ if (!attrStyles) {
+ return nullptr;
+ }
+ return attrStyles->GetServoActiveLinkDecl();
+}
+
+bool Gecko_GetAnimationRule(const Element* aElement,
+ EffectCompositor::CascadeLevel aCascadeLevel,
+ StyleAnimationValueMap* aAnimationValues) {
+ MOZ_ASSERT(aElement);
+
+ Document* doc = aElement->GetComposedDoc();
+ if (!doc) {
+ return false;
+ }
+ nsPresContext* presContext = doc->GetPresContext();
+ if (!presContext) {
+ return false;
+ }
+
+ const auto [element, pseudoType] =
+ AnimationUtils::GetElementPseudoPair(aElement);
+ return presContext->EffectCompositor()->GetServoAnimationRule(
+ element, pseudoType, aCascadeLevel, aAnimationValues);
+}
+
+bool Gecko_StyleAnimationsEquals(const nsStyleAutoArray<StyleAnimation>* aA,
+ const nsStyleAutoArray<StyleAnimation>* aB) {
+ return *aA == *aB;
+}
+
+bool Gecko_StyleScrollTimelinesEquals(
+ const nsStyleAutoArray<StyleScrollTimeline>* aA,
+ const nsStyleAutoArray<StyleScrollTimeline>* aB) {
+ return *aA == *aB;
+}
+
+bool Gecko_StyleViewTimelinesEquals(
+ const nsStyleAutoArray<StyleViewTimeline>* aA,
+ const nsStyleAutoArray<StyleViewTimeline>* aB) {
+ return *aA == *aB;
+}
+
+void Gecko_UpdateAnimations(const Element* aElement,
+ const ComputedStyle* aOldComputedData,
+ const ComputedStyle* aComputedData,
+ UpdateAnimationsTasks aTasks) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aElement);
+
+ if (!aElement->IsInComposedDoc()) {
+ return;
+ }
+
+ nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
+ if (!presContext || !presContext->IsDynamic()) {
+ return;
+ }
+
+ nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
+
+ const auto [element, pseudoType] =
+ AnimationUtils::GetElementPseudoPair(aElement);
+
+ // Handle scroll/view timelines first because CSS animations may refer to the
+ // timeline defined by itself.
+ if (aTasks & UpdateAnimationsTasks::ScrollTimelines) {
+ presContext->TimelineManager()->UpdateTimelines(
+ const_cast<Element*>(element), pseudoType, aComputedData,
+ TimelineManager::ProgressTimelineType::Scroll);
+ }
+
+ if (aTasks & UpdateAnimationsTasks::ViewTimelines) {
+ presContext->TimelineManager()->UpdateTimelines(
+ const_cast<Element*>(element), pseudoType, aComputedData,
+ TimelineManager::ProgressTimelineType::View);
+ }
+
+ if (aTasks & UpdateAnimationsTasks::CSSAnimations) {
+ presContext->AnimationManager()->UpdateAnimations(
+ const_cast<Element*>(element), pseudoType, aComputedData);
+ }
+
+ // aComputedData might be nullptr if the target element is now in a
+ // display:none subtree. We still call Gecko_UpdateAnimations in this case
+ // because we need to stop CSS animations in the display:none subtree.
+ // However, we don't need to update transitions since they are stopped by
+ // RestyleManager::AnimationsWithDestroyedFrame so we just return early
+ // here.
+ if (!aComputedData) {
+ return;
+ }
+
+ if (aTasks & UpdateAnimationsTasks::CSSTransitions) {
+ MOZ_ASSERT(aOldComputedData);
+ presContext->TransitionManager()->UpdateTransitions(
+ const_cast<Element*>(element), pseudoType, *aOldComputedData,
+ *aComputedData);
+ }
+
+ if (aTasks & UpdateAnimationsTasks::EffectProperties) {
+ presContext->EffectCompositor()->UpdateEffectProperties(
+ aComputedData, const_cast<Element*>(element), pseudoType);
+ }
+
+ if (aTasks & UpdateAnimationsTasks::CascadeResults) {
+ EffectSet* effectSet = EffectSet::Get(element, pseudoType);
+ // CSS animations/transitions might have been destroyed as part of the above
+ // steps so before updating cascade results, we check if there are still any
+ // animations to update.
+ if (effectSet) {
+ // We call UpdateCascadeResults directly (intead of
+ // MaybeUpdateCascadeResults) since we know for sure that the cascade has
+ // changed, but we were unable to call MarkCascadeUpdated when we noticed
+ // it since we avoid mutating state as part of the Servo parallel
+ // traversal.
+ presContext->EffectCompositor()->UpdateCascadeResults(
+ *effectSet, const_cast<Element*>(element), pseudoType);
+ }
+ }
+
+ if (aTasks & UpdateAnimationsTasks::DisplayChangedFromNone) {
+ presContext->EffectCompositor()->RequestRestyle(
+ const_cast<Element*>(element), pseudoType,
+ EffectCompositor::RestyleType::Standard,
+ EffectCompositor::CascadeLevel::Animations);
+ }
+}
+
+size_t Gecko_GetAnimationEffectCount(const Element* aElementOrPseudo) {
+ const auto [element, pseudoType] =
+ AnimationUtils::GetElementPseudoPair(aElementOrPseudo);
+
+ EffectSet* effectSet = EffectSet::Get(element, pseudoType);
+ return effectSet ? effectSet->Count() : 0;
+}
+
+bool Gecko_ElementHasAnimations(const Element* aElement) {
+ const auto [element, pseudoType] =
+ AnimationUtils::GetElementPseudoPair(aElement);
+ return !!EffectSet::Get(element, pseudoType);
+}
+
+bool Gecko_ElementHasCSSAnimations(const Element* aElement) {
+ const auto [element, pseudoType] =
+ AnimationUtils::GetElementPseudoPair(aElement);
+ auto* collection =
+ nsAnimationManager::CSSAnimationCollection::Get(element, pseudoType);
+ return collection && !collection->mAnimations.IsEmpty();
+}
+
+bool Gecko_ElementHasCSSTransitions(const Element* aElement) {
+ const auto [element, pseudoType] =
+ AnimationUtils::GetElementPseudoPair(aElement);
+ auto* collection =
+ nsTransitionManager::CSSTransitionCollection::Get(element, pseudoType);
+ return collection && !collection->mAnimations.IsEmpty();
+}
+
+size_t Gecko_ElementTransitions_Length(const Element* aElement) {
+ const auto [element, pseudoType] =
+ AnimationUtils::GetElementPseudoPair(aElement);
+ auto* collection =
+ nsTransitionManager::CSSTransitionCollection::Get(element, pseudoType);
+ return collection ? collection->mAnimations.Length() : 0;
+}
+
+static CSSTransition* GetCurrentTransitionAt(const Element* aElement,
+ size_t aIndex) {
+ const auto [element, pseudoType] =
+ AnimationUtils::GetElementPseudoPair(aElement);
+ auto* collection =
+ nsTransitionManager::CSSTransitionCollection ::Get(element, pseudoType);
+ if (!collection) {
+ return nullptr;
+ }
+ return collection->mAnimations.SafeElementAt(aIndex);
+}
+
+nsCSSPropertyID Gecko_ElementTransitions_PropertyAt(const Element* aElement,
+ size_t aIndex) {
+ CSSTransition* transition = GetCurrentTransitionAt(aElement, aIndex);
+ return transition ? transition->TransitionProperty().mID
+ : nsCSSPropertyID::eCSSProperty_UNKNOWN;
+}
+
+const StyleAnimationValue* Gecko_ElementTransitions_EndValueAt(
+ const Element* aElement, size_t aIndex) {
+ CSSTransition* transition = GetCurrentTransitionAt(aElement, aIndex);
+ return transition ? transition->ToValue().mServo.get() : nullptr;
+}
+
+double Gecko_GetProgressFromComputedTiming(const ComputedTiming* aTiming) {
+ return aTiming->mProgress.Value();
+}
+
+double Gecko_GetPositionInSegment(const AnimationPropertySegment* aSegment,
+ double aProgress, bool aBeforeFlag) {
+ MOZ_ASSERT(aSegment->mFromKey < aSegment->mToKey,
+ "The segment from key should be less than to key");
+
+ double positionInSegment = (aProgress - aSegment->mFromKey) /
+ // To avoid floating precision inaccuracies, make
+ // sure we calculate both the numerator and
+ // denominator using double precision.
+ (double(aSegment->mToKey) - aSegment->mFromKey);
+
+ return StyleComputedTimingFunction::GetPortion(
+ aSegment->mTimingFunction, positionInSegment, aBeforeFlag);
+}
+
+const StyleAnimationValue* Gecko_AnimationGetBaseStyle(
+ const RawServoAnimationValueTable* aBaseStyles,
+ const mozilla::AnimatedPropertyID* aProperty) {
+ const auto* base = reinterpret_cast<const nsRefPtrHashtable<
+ nsGenericHashKey<AnimatedPropertyID>, StyleAnimationValue>*>(aBaseStyles);
+ return base->GetWeak(*aProperty);
+}
+
+void Gecko_FillAllImageLayers(nsStyleImageLayers* aLayers, uint32_t aMaxLen) {
+ aLayers->FillAllLayers(aMaxLen);
+}
+
+bool Gecko_IsDocumentBody(const Element* aElement) {
+ Document* doc = aElement->GetUncomposedDoc();
+ return doc && doc->GetBodyElement() == aElement;
+}
+
+bool Gecko_IsDarkColorScheme(const Document* aDoc,
+ const StyleColorScheme* aStyle) {
+ return LookAndFeel::ColorSchemeForStyle(*aDoc, aStyle->bits) ==
+ ColorScheme::Dark;
+}
+
+nscolor Gecko_ComputeSystemColor(StyleSystemColor aColor, const Document* aDoc,
+ const StyleColorScheme* aStyle) {
+ auto colorScheme = LookAndFeel::ColorSchemeForStyle(*aDoc, aStyle->bits);
+ const auto& prefs = PreferenceSheet::PrefsFor(*aDoc);
+ if (prefs.mMustUseLightSystemColors) {
+ colorScheme = ColorScheme::Light;
+ }
+ const auto& colors = prefs.ColorsFor(colorScheme);
+ switch (aColor) {
+ case StyleSystemColor::Canvastext:
+ return colors.mDefault;
+ case StyleSystemColor::Canvas:
+ return colors.mDefaultBackground;
+ case StyleSystemColor::Linktext:
+ return colors.mLink;
+ case StyleSystemColor::Activetext:
+ return colors.mActiveLink;
+ case StyleSystemColor::Visitedtext:
+ return colors.mVisitedLink;
+ default:
+ break;
+ }
+
+ auto useStandins = LookAndFeel::ShouldUseStandins(*aDoc, aColor);
+
+ AutoWriteLock guard(*sServoFFILock);
+ return LookAndFeel::Color(aColor, colorScheme, useStandins);
+}
+
+int32_t Gecko_GetLookAndFeelInt(int32_t aId) {
+ auto intId = static_cast<LookAndFeel::IntID>(aId);
+ AutoWriteLock guard(*sServoFFILock);
+ return LookAndFeel::GetInt(intId);
+}
+
+float Gecko_GetLookAndFeelFloat(int32_t aId) {
+ auto id = static_cast<LookAndFeel::FloatID>(aId);
+ AutoWriteLock guard(*sServoFFILock);
+ return LookAndFeel::GetFloat(id);
+}
+
+bool Gecko_MatchLang(const Element* aElement, nsAtom* aOverrideLang,
+ bool aHasOverrideLang, const char16_t* aValue) {
+ MOZ_ASSERT(!(aOverrideLang && !aHasOverrideLang),
+ "aHasOverrideLang should only be set when aOverrideLang is null");
+ MOZ_ASSERT(aValue, "null lang parameter");
+ if (!aValue || !*aValue) {
+ return false;
+ }
+
+ // We have to determine the language of the current element. Since
+ // this is currently no property and since the language is inherited
+ // from the parent we have to be prepared to look at all parent
+ // nodes. The language itself is encoded in the LANG attribute.
+ if (auto* language = aHasOverrideLang ? aOverrideLang : aElement->GetLang()) {
+ return nsStyleUtil::LangTagCompare(nsAtomCString(language),
+ NS_ConvertUTF16toUTF8(aValue));
+ }
+
+ // Try to get the language from the HTTP header or if this
+ // is missing as well from the preferences.
+ // The content language can be a comma-separated list of
+ // language codes.
+ // FIXME: We're not really consistent in our treatment of comma-separated
+ // content-language values.
+ if (nsAtom* language = aElement->OwnerDoc()->GetContentLanguage()) {
+ const NS_ConvertUTF16toUTF8 langString(aValue);
+ nsAtomCString docLang(language);
+ docLang.StripWhitespace();
+ for (auto const& lang : docLang.Split(',')) {
+ if (nsStyleUtil::LangTagCompare(lang, langString)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+nsAtom* Gecko_GetXMLLangValue(const Element* aElement) {
+ const nsAttrValue* attr =
+ aElement->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML);
+
+ if (!attr) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
+
+ RefPtr<nsAtom> atom = attr->GetAtomValue();
+ return atom.forget().take();
+}
+
+const PreferenceSheet::Prefs* Gecko_GetPrefSheetPrefs(const Document* aDoc) {
+ return &PreferenceSheet::PrefsFor(*aDoc);
+}
+
+bool Gecko_IsTableBorderNonzero(const Element* aElement) {
+ if (!aElement->IsHTMLElement(nsGkAtoms::table)) {
+ return false;
+ }
+ const nsAttrValue* val = aElement->GetParsedAttr(nsGkAtoms::border);
+ return val &&
+ (val->Type() != nsAttrValue::eInteger || val->GetIntegerValue() != 0);
+}
+
+bool Gecko_IsSelectListBox(const Element* aElement) {
+ const auto* select = HTMLSelectElement::FromNode(aElement);
+ return select && !select->IsCombobox();
+}
+
+template <typename Implementor>
+static nsAtom* LangValue(Implementor* aElement) {
+ // TODO(emilio): Deduplicate a bit with nsIContent::GetLang().
+ const nsAttrValue* attr =
+ aElement->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML);
+ if (!attr && aElement->SupportsLangAttr()) {
+ attr = aElement->GetParsedAttr(nsGkAtoms::lang);
+ }
+
+ if (!attr) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
+ RefPtr<nsAtom> atom = attr->GetAtomValue();
+ return atom.forget().take();
+}
+
+bool Gecko_AttrEquals(const nsAttrValue* aValue, const nsAtom* aStr,
+ bool aIgnoreCase) {
+ return aValue->Equals(aStr, aIgnoreCase ? eIgnoreCase : eCaseMatters);
+}
+
+#define WITH_COMPARATOR(ignore_case_, c_, expr_) \
+ auto c_ = (ignore_case_) ? nsASCIICaseInsensitiveStringComparator \
+ : nsTDefaultStringComparator<char16_t>; \
+ return expr_;
+
+bool Gecko_AttrDashEquals(const nsAttrValue* aValue, const nsAtom* aStr,
+ bool aIgnoreCase) {
+ nsAutoString str;
+ aValue->ToString(str);
+ WITH_COMPARATOR(
+ aIgnoreCase, c,
+ nsStyleUtil::DashMatchCompare(str, nsDependentAtomString(aStr), c))
+}
+
+bool Gecko_AttrIncludes(const nsAttrValue* aValue, const nsAtom* aStr,
+ bool aIgnoreCase) {
+ if (aStr == nsGkAtoms::_empty) {
+ return false;
+ }
+ nsAutoString str;
+ aValue->ToString(str);
+ WITH_COMPARATOR(
+ aIgnoreCase, c,
+ nsStyleUtil::ValueIncludes(str, nsDependentAtomString(aStr), c))
+}
+
+bool Gecko_AttrHasSubstring(const nsAttrValue* aValue, const nsAtom* aStr,
+ bool aIgnoreCase) {
+ return aStr != nsGkAtoms::_empty &&
+ aValue->HasSubstring(nsDependentAtomString(aStr),
+ aIgnoreCase ? eIgnoreCase : eCaseMatters);
+}
+
+bool Gecko_AttrHasPrefix(const nsAttrValue* aValue, const nsAtom* aStr,
+ bool aIgnoreCase) {
+ return aStr != nsGkAtoms::_empty &&
+ aValue->HasPrefix(nsDependentAtomString(aStr),
+ aIgnoreCase ? eIgnoreCase : eCaseMatters);
+}
+
+bool Gecko_AttrHasSuffix(const nsAttrValue* aValue, const nsAtom* aStr,
+ bool aIgnoreCase) {
+ return aStr != nsGkAtoms::_empty &&
+ aValue->HasSuffix(nsDependentAtomString(aStr),
+ aIgnoreCase ? eIgnoreCase : eCaseMatters);
+}
+
+#define SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_) \
+ nsAtom* prefix_##LangValue(implementor_ aElement) { \
+ return LangValue(aElement); \
+ }
+
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, const Element*)
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot,
+ const ServoElementSnapshot*)
+
+#undef SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS
+
+nsAtom* Gecko_Atomize(const char* aString, uint32_t aLength) {
+ return NS_Atomize(nsDependentCSubstring(aString, aLength)).take();
+}
+
+nsAtom* Gecko_Atomize16(const nsAString* aString) {
+ return NS_Atomize(*aString).take();
+}
+
+void Gecko_AddRefAtom(nsAtom* aAtom) { NS_ADDREF(aAtom); }
+
+void Gecko_ReleaseAtom(nsAtom* aAtom) { NS_RELEASE(aAtom); }
+
+void Gecko_nsFont_InitSystem(nsFont* aDest, StyleSystemFont aFontId,
+ const nsStyleFont* aFont,
+ const Document* aDocument) {
+ const nsFont& defaultVariableFont =
+ ThreadSafeGetDefaultVariableFont(*aDocument, aFont->mLanguage);
+
+ // We have passed uninitialized memory to this function,
+ // initialize it. We can't simply return an nsFont because then
+ // we need to know its size beforehand. Servo cannot initialize nsFont
+ // itself, so this will do.
+ new (aDest) nsFont(defaultVariableFont);
+
+ AutoWriteLock guard(*sServoFFILock);
+ nsLayoutUtils::ComputeSystemFont(aDest, aFontId, defaultVariableFont,
+ aDocument);
+}
+
+void Gecko_nsFont_Destroy(nsFont* aDest) { aDest->~nsFont(); }
+
+StyleGenericFontFamily Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
+ const Document* aDoc, nsAtom* aLanguage) {
+ return ThreadSafeGetLangGroupFontPrefs(*aDoc, aLanguage)->GetDefaultGeneric();
+}
+
+Length Gecko_GetBaseSize(const Document* aDoc, nsAtom* aLang,
+ StyleGenericFontFamily aGeneric) {
+ return ThreadSafeGetLangGroupFontPrefs(*aDoc, aLang)
+ ->GetDefaultFont(aGeneric)
+ ->size;
+}
+
+gfxFontFeatureValueSet* Gecko_ConstructFontFeatureValueSet() {
+ return new gfxFontFeatureValueSet();
+}
+
+nsTArray<uint32_t>* Gecko_AppendFeatureValueHashEntry(
+ gfxFontFeatureValueSet* aFontFeatureValues, nsAtom* aFamily,
+ uint32_t aAlternate, nsAtom* aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return aFontFeatureValues->AppendFeatureValueHashEntry(nsAtomCString(aFamily),
+ aName, aAlternate);
+}
+
+gfx::FontPaletteValueSet* Gecko_ConstructFontPaletteValueSet() {
+ return new gfx::FontPaletteValueSet();
+}
+
+gfx::FontPaletteValueSet::PaletteValues* Gecko_AppendPaletteValueHashEntry(
+ gfx::FontPaletteValueSet* aPaletteValueSet, nsAtom* aFamily,
+ nsAtom* aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return aPaletteValueSet->Insert(aName, nsAtomCString(aFamily));
+}
+
+void Gecko_SetFontPaletteBase(gfx::FontPaletteValueSet::PaletteValues* aValues,
+ int32_t aBasePaletteIndex) {
+ aValues->mBasePalette = aBasePaletteIndex;
+}
+
+void Gecko_SetFontPaletteOverride(
+ gfx::FontPaletteValueSet::PaletteValues* aValues, int32_t aIndex,
+ StyleAbsoluteColor* aColor) {
+ if (aIndex < 0) {
+ return;
+ }
+ aValues->mOverrides.AppendElement(gfx::FontPaletteValueSet::OverrideColor{
+ uint32_t(aIndex), gfx::sRGBColor::FromABGR(aColor->ToColor())});
+}
+
+void Gecko_CounterStyle_ToPtr(const StyleCounterStyle* aStyle,
+ CounterStylePtr* aPtr) {
+ *aPtr = CounterStylePtr::FromStyle(*aStyle);
+}
+
+void Gecko_SetCounterStyleToNone(CounterStylePtr* aPtr) {
+ *aPtr = nsGkAtoms::none;
+}
+
+void Gecko_SetCounterStyleToString(CounterStylePtr* aPtr,
+ const nsACString* aSymbol) {
+ *aPtr = new AnonymousCounterStyle(NS_ConvertUTF8toUTF16(*aSymbol));
+}
+
+void Gecko_CopyCounterStyle(CounterStylePtr* aDst,
+ const CounterStylePtr* aSrc) {
+ *aDst = *aSrc;
+}
+
+nsAtom* Gecko_CounterStyle_GetName(const CounterStylePtr* aPtr) {
+ return aPtr->IsAtom() ? aPtr->AsAtom() : nullptr;
+}
+
+const AnonymousCounterStyle* Gecko_CounterStyle_GetAnonymous(
+ const CounterStylePtr* aPtr) {
+ return aPtr->AsAnonymous();
+}
+
+void Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity,
+ size_t aElemSize) {
+ auto base =
+ reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator,
+ nsTArray_RelocateUsingMemutils>*>(aArray);
+
+ base->EnsureCapacity<nsTArrayInfallibleAllocator>(aCapacity, aElemSize);
+}
+
+void Gecko_ClearPODTArray(void* aArray, size_t aElementSize,
+ size_t aElementAlign) {
+ auto base =
+ reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator,
+ nsTArray_RelocateUsingMemutils>*>(aArray);
+
+ base->template ShiftData<nsTArrayInfallibleAllocator>(
+ 0, base->Length(), 0, aElementSize, aElementAlign);
+}
+
+void Gecko_ResizeTArrayForStrings(nsTArray<nsString>* aArray,
+ uint32_t aLength) {
+ aArray->SetLength(aLength);
+}
+
+void Gecko_ResizeAtomArray(nsTArray<RefPtr<nsAtom>>* aArray, uint32_t aLength) {
+ aArray->SetLength(aLength);
+}
+
+void Gecko_EnsureImageLayersLength(nsStyleImageLayers* aLayers, size_t aLen,
+ nsStyleImageLayers::LayerType aLayerType) {
+ size_t oldLength = aLayers->mLayers.Length();
+
+ aLayers->mLayers.EnsureLengthAtLeast(aLen);
+
+ for (size_t i = oldLength; i < aLen; ++i) {
+ aLayers->mLayers[i].Initialize(aLayerType);
+ }
+}
+
+template <typename StyleType>
+static void EnsureStyleAutoArrayLength(StyleType* aArray, size_t aLen) {
+ aArray->EnsureLengthAtLeast(aLen);
+}
+
+void Gecko_EnsureStyleAnimationArrayLength(void* aArray, size_t aLen) {
+ auto* base = static_cast<nsStyleAutoArray<StyleAnimation>*>(aArray);
+ EnsureStyleAutoArrayLength(base, aLen);
+}
+
+void Gecko_EnsureStyleTransitionArrayLength(void* aArray, size_t aLen) {
+ auto* base = reinterpret_cast<nsStyleAutoArray<StyleTransition>*>(aArray);
+ EnsureStyleAutoArrayLength(base, aLen);
+}
+
+void Gecko_EnsureStyleScrollTimelineArrayLength(void* aArray, size_t aLen) {
+ auto* base = static_cast<nsStyleAutoArray<StyleScrollTimeline>*>(aArray);
+ EnsureStyleAutoArrayLength(base, aLen);
+}
+
+void Gecko_EnsureStyleViewTimelineArrayLength(void* aArray, size_t aLen) {
+ auto* base = static_cast<nsStyleAutoArray<StyleViewTimeline>*>(aArray);
+ EnsureStyleAutoArrayLength(base, aLen);
+}
+
+enum class KeyframeSearchDirection {
+ Forwards,
+ Backwards,
+};
+
+enum class KeyframeInsertPosition {
+ Prepend,
+ LastForOffset,
+};
+
+static Keyframe* GetOrCreateKeyframe(
+ nsTArray<Keyframe>* aKeyframes, float aOffset,
+ const StyleComputedTimingFunction* aTimingFunction,
+ const CompositeOperationOrAuto aComposition,
+ KeyframeSearchDirection aSearchDirection,
+ KeyframeInsertPosition aInsertPosition) {
+ MOZ_ASSERT(aKeyframes, "The keyframe array should be valid");
+ MOZ_ASSERT(aTimingFunction, "The timing function should be valid");
+ MOZ_ASSERT(aOffset >= 0. && aOffset <= 1.,
+ "The offset should be in the range of [0.0, 1.0]");
+
+ size_t keyframeIndex;
+ switch (aSearchDirection) {
+ case KeyframeSearchDirection::Forwards:
+ if (nsAnimationManager::FindMatchingKeyframe(
+ *aKeyframes, aOffset, *aTimingFunction, aComposition,
+ keyframeIndex)) {
+ return &(*aKeyframes)[keyframeIndex];
+ }
+ break;
+ case KeyframeSearchDirection::Backwards:
+ if (nsAnimationManager::FindMatchingKeyframe(
+ Reversed(*aKeyframes), aOffset, *aTimingFunction, aComposition,
+ keyframeIndex)) {
+ return &(*aKeyframes)[aKeyframes->Length() - 1 - keyframeIndex];
+ }
+ keyframeIndex = aKeyframes->Length() - 1;
+ break;
+ }
+
+ Keyframe* keyframe = aKeyframes->InsertElementAt(
+ aInsertPosition == KeyframeInsertPosition::Prepend ? 0 : keyframeIndex);
+ keyframe->mOffset.emplace(aOffset);
+ if (!aTimingFunction->IsLinearKeyword()) {
+ keyframe->mTimingFunction.emplace(*aTimingFunction);
+ }
+ keyframe->mComposite = aComposition;
+
+ return keyframe;
+}
+
+Keyframe* Gecko_GetOrCreateKeyframeAtStart(
+ nsTArray<Keyframe>* aKeyframes, float aOffset,
+ const StyleComputedTimingFunction* aTimingFunction,
+ const CompositeOperationOrAuto aComposition) {
+ MOZ_ASSERT(aKeyframes->IsEmpty() ||
+ aKeyframes->ElementAt(0).mOffset.value() >= aOffset,
+ "The offset should be less than or equal to the first keyframe's "
+ "offset if there are exisiting keyframes");
+
+ return GetOrCreateKeyframe(aKeyframes, aOffset, aTimingFunction, aComposition,
+ KeyframeSearchDirection::Forwards,
+ KeyframeInsertPosition::Prepend);
+}
+
+Keyframe* Gecko_GetOrCreateInitialKeyframe(
+ nsTArray<Keyframe>* aKeyframes,
+ const StyleComputedTimingFunction* aTimingFunction,
+ const CompositeOperationOrAuto aComposition) {
+ return GetOrCreateKeyframe(aKeyframes, 0., aTimingFunction, aComposition,
+ KeyframeSearchDirection::Forwards,
+ KeyframeInsertPosition::LastForOffset);
+}
+
+Keyframe* Gecko_GetOrCreateFinalKeyframe(
+ nsTArray<Keyframe>* aKeyframes,
+ const StyleComputedTimingFunction* aTimingFunction,
+ const CompositeOperationOrAuto aComposition) {
+ return GetOrCreateKeyframe(aKeyframes, 1., aTimingFunction, aComposition,
+ KeyframeSearchDirection::Backwards,
+ KeyframeInsertPosition::LastForOffset);
+}
+
+PropertyValuePair* Gecko_AppendPropertyValuePair(
+ nsTArray<PropertyValuePair>* aProperties,
+ const mozilla::AnimatedPropertyID* aProperty) {
+ MOZ_ASSERT(aProperties);
+ MOZ_ASSERT(
+ aProperty->IsCustom() ||
+ !nsCSSProps::PropHasFlags(aProperty->mID, CSSPropFlags::IsLogical));
+ return aProperties->AppendElement(PropertyValuePair{*aProperty});
+}
+
+void Gecko_GetComputedURLSpec(const StyleComputedUrl* aURL, nsCString* aOut) {
+ MOZ_ASSERT(aURL);
+ MOZ_ASSERT(aOut);
+ if (aURL->IsLocalRef()) {
+ aOut->Assign(aURL->SpecifiedSerialization());
+ return;
+ }
+ Gecko_GetComputedImageURLSpec(aURL, aOut);
+}
+
+void Gecko_GetComputedImageURLSpec(const StyleComputedUrl* aURL,
+ nsCString* aOut) {
+ if (aURL->IsLocalRef() &&
+ StaticPrefs::layout_css_computed_style_dont_resolve_image_local_refs()) {
+ aOut->Assign(aURL->SpecifiedSerialization());
+ return;
+ }
+
+ if (nsIURI* uri = aURL->GetURI()) {
+ nsresult rv = uri->GetSpec(*aOut);
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ }
+
+ // Empty URL computes to empty, per spec:
+ if (aURL->SpecifiedSerialization().IsEmpty()) {
+ aOut->Truncate();
+ } else {
+ aOut->AssignLiteral("about:invalid");
+ }
+}
+
+bool Gecko_IsSupportedImageMimeType(const uint8_t* aMimeType,
+ const uint32_t aLen) {
+ nsDependentCSubstring mime(reinterpret_cast<const char*>(aMimeType), aLen);
+ return imgLoader::SupportImageWithMimeType(
+ mime, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
+}
+
+void Gecko_nsIURI_Debug(nsIURI* aURI, nsCString* aOut) {
+ // TODO(emilio): Do we have more useful stuff to put here, maybe?
+ if (aURI) {
+ *aOut = aURI->GetSpecOrDefault();
+ }
+}
+
+// XXX Implemented by hand because even though it's thread-safe, only the
+// subclasses have the HasThreadSafeRefCnt bits.
+void Gecko_AddRefnsIURIArbitraryThread(nsIURI* aPtr) { NS_ADDREF(aPtr); }
+void Gecko_ReleasensIURIArbitraryThread(nsIURI* aPtr) { NS_RELEASE(aPtr); }
+
+void Gecko_nsIReferrerInfo_Debug(nsIReferrerInfo* aReferrerInfo,
+ nsCString* aOut) {
+ if (aReferrerInfo) {
+ if (nsCOMPtr<nsIURI> referrer = aReferrerInfo->GetComputedReferrer()) {
+ *aOut = referrer->GetSpecOrDefault();
+ }
+ }
+}
+
+template <typename ElementLike>
+void DebugListAttributes(const ElementLike& aElement, nsCString& aOut) {
+ const uint32_t kMaxAttributeLength = 40;
+
+ uint32_t i = 0;
+ while (BorrowedAttrInfo info = aElement.GetAttrInfoAt(i++)) {
+ aOut.AppendLiteral(" ");
+ if (nsAtom* prefix = info.mName->GetPrefix()) {
+ aOut.Append(NS_ConvertUTF16toUTF8(nsDependentAtomString(prefix)));
+ aOut.AppendLiteral(":");
+ }
+ aOut.Append(
+ NS_ConvertUTF16toUTF8(nsDependentAtomString(info.mName->LocalName())));
+ if (!info.mValue) {
+ continue;
+ }
+ aOut.AppendLiteral("=\"");
+ nsAutoString value;
+ info.mValue->ToString(value);
+ if (value.Length() > kMaxAttributeLength) {
+ value.Truncate(kMaxAttributeLength - 3);
+ value.AppendLiteral("...");
+ }
+ aOut.Append(NS_ConvertUTF16toUTF8(value));
+ aOut.AppendLiteral("\"");
+ }
+}
+
+void Gecko_Element_DebugListAttributes(const Element* aElement,
+ nsCString* aOut) {
+ DebugListAttributes(*aElement, *aOut);
+}
+
+void Gecko_Snapshot_DebugListAttributes(const ServoElementSnapshot* aSnapshot,
+ nsCString* aOut) {
+ DebugListAttributes(*aSnapshot, *aOut);
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(URLExtraData, URLExtraData);
+
+void Gecko_nsStyleFont_SetLang(nsStyleFont* aFont, nsAtom* aAtom) {
+ aFont->mLanguage = dont_AddRef(aAtom);
+ aFont->mExplicitLanguage = true;
+}
+
+void Gecko_nsStyleFont_CopyLangFrom(nsStyleFont* aFont,
+ const nsStyleFont* aSource) {
+ aFont->mLanguage = aSource->mLanguage;
+}
+
+Length Gecko_nsStyleFont_ComputeMinSize(const nsStyleFont* aFont,
+ const Document* aDocument) {
+ // Don't change font-size:0, since that would un-hide hidden text.
+ if (aFont->mSize.IsZero()) {
+ return {0};
+ }
+ // Don't change it for docs where we don't enable the min-font-size.
+ if (!aFont->MinFontSizeEnabled()) {
+ return {0};
+ }
+ Length minFontSize;
+ bool needsCache = false;
+
+ auto MinFontSize = [&](bool* aNeedsToCache) {
+ const auto* prefs =
+ aDocument->GetFontPrefsForLang(aFont->mLanguage, aNeedsToCache);
+ return prefs ? prefs->mMinimumFontSize : Length{0};
+ };
+
+ {
+ AutoReadLock guard(*sServoFFILock);
+ minFontSize = MinFontSize(&needsCache);
+ }
+
+ if (needsCache) {
+ AutoWriteLock guard(*sServoFFILock);
+ minFontSize = MinFontSize(nullptr);
+ }
+
+ if (minFontSize.ToCSSPixels() <= 0.0f) {
+ return {0};
+ }
+
+ minFontSize.ScaleBy(aFont->mMinFontSizeRatio);
+ minFontSize.ScaleBy(1.0f / 100.0f);
+ return minFontSize;
+}
+
+static StaticRefPtr<UACacheReporter> gUACacheReporter;
+
+namespace mozilla {
+
+void InitializeServo() {
+ URLExtraData::Init();
+ Servo_Initialize(URLExtraData::Dummy(), URLExtraData::DummyChrome());
+
+ gUACacheReporter = new UACacheReporter();
+ RegisterWeakMemoryReporter(gUACacheReporter);
+
+ sServoFFILock = new RWLock("Servo::FFILock");
+}
+
+void ShutdownServo() {
+ MOZ_ASSERT(sServoFFILock);
+
+ UnregisterWeakMemoryReporter(gUACacheReporter);
+ gUACacheReporter = nullptr;
+
+ sServoFFILock = nullptr;
+ Servo_Shutdown();
+
+ URLExtraData::Shutdown();
+}
+
+void AssertIsMainThreadOrServoFontMetricsLocked() {
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(sServoFFILock &&
+ sServoFFILock->LockedForWritingByCurrentThread());
+ }
+}
+
+} // namespace mozilla
+
+GeckoFontMetrics Gecko_GetFontMetrics(const nsPresContext* aPresContext,
+ bool aIsVertical,
+ const nsStyleFont* aFont,
+ Length aFontSize, bool aUseUserFontSet,
+ bool aRetrieveMathScales) {
+ AutoWriteLock guard(*sServoFFILock);
+
+ // Getting font metrics can require some main thread only work to be
+ // done, such as work that needs to touch non-threadsafe refcounted
+ // objects (like the DOM FontFace/FontFaceSet objects), network loads, etc.
+ //
+ // To handle this work, font code checks whether we are in a Servo traversal
+ // and if so, appends PostTraversalTasks to the current ServoStyleSet
+ // to be performed immediately after the traversal is finished. This
+ // works well for starting downloadable font loads, since we don't have
+ // those fonts available to get metrics for anyway. Platform fonts and
+ // ArrayBuffer-backed FontFace objects are handled synchronously.
+
+ nsPresContext* presContext = const_cast<nsPresContext*>(aPresContext);
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetMetricsFor(
+ presContext, aIsVertical, aFont, aFontSize, aUseUserFontSet);
+ auto* fontGroup = fm->GetThebesFontGroup();
+ auto metrics = fontGroup->GetMetricsForCSSUnits(fm->Orientation());
+
+ float scriptPercentScaleDown = 0;
+ float scriptScriptPercentScaleDown = 0;
+ if (aRetrieveMathScales) {
+ RefPtr<gfxFont> font = fontGroup->GetFirstValidFont();
+ if (font->TryGetMathTable()) {
+ scriptPercentScaleDown = static_cast<float>(
+ font->MathTable()->Constant(gfxMathTable::ScriptPercentScaleDown));
+ scriptScriptPercentScaleDown =
+ static_cast<float>(font->MathTable()->Constant(
+ gfxMathTable::ScriptScriptPercentScaleDown));
+ }
+ }
+
+ int32_t d2a = aPresContext->AppUnitsPerDevPixel();
+ auto ToLength = [](nscoord aLen) {
+ return Length::FromPixels(CSSPixel::FromAppUnits(aLen));
+ };
+ return {ToLength(NS_round(metrics.xHeight * d2a)),
+ ToLength(NS_round(metrics.zeroWidth * d2a)),
+ ToLength(NS_round(metrics.capHeight * d2a)),
+ ToLength(NS_round(metrics.ideographicWidth * d2a)),
+ ToLength(NS_round(metrics.maxAscent * d2a)),
+ ToLength(NS_round(fontGroup->GetStyle()->size * d2a)),
+ scriptPercentScaleDown,
+ scriptScriptPercentScaleDown};
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(SheetLoadDataHolder, SheetLoadDataHolder);
+
+void Gecko_StyleSheet_FinishAsyncParse(
+ SheetLoadDataHolder* aData,
+ StyleStrong<StyleStylesheetContents> aSheetContents,
+ StyleUseCounters* aUseCounters) {
+ UniquePtr<StyleUseCounters> useCounters(aUseCounters);
+ RefPtr<SheetLoadDataHolder> loadData = aData;
+ RefPtr<StyleStylesheetContents> sheetContents = aSheetContents.Consume();
+ NS_DispatchToMainThreadQueue(
+ NS_NewRunnableFunction(
+ __func__,
+ [d = std::move(loadData), contents = std::move(sheetContents),
+ counters = std::move(useCounters)]() mutable {
+ MOZ_ASSERT(NS_IsMainThread());
+ SheetLoadData* data = d->get();
+ data->mSheet->FinishAsyncParse(contents.forget(),
+ std::move(counters));
+ }),
+ EventQueuePriority::RenderBlocking);
+}
+
+static already_AddRefed<StyleSheet> LoadImportSheet(
+ Loader* aLoader, StyleSheet* aParent, SheetLoadData* aParentLoadData,
+ LoaderReusableStyleSheets* aReusableSheets, const StyleCssUrl& aURL,
+ already_AddRefed<StyleLockedMediaList> aMediaList) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aLoader, "Should've catched this before");
+ MOZ_ASSERT(aParent, "Only used for @import, so parent should exist!");
+
+ RefPtr<MediaList> media = new MediaList(std::move(aMediaList));
+ nsCOMPtr<nsIURI> uri = aURL.GetURI();
+ nsresult rv = uri ? NS_OK : NS_ERROR_FAILURE;
+
+ size_t previousSheetCount = aParent->ChildSheets().Length();
+ if (NS_SUCCEEDED(rv)) {
+ // TODO(emilio): We should probably make LoadChildSheet return the
+ // stylesheet rather than the return code.
+ rv = aLoader->LoadChildSheet(*aParent, aParentLoadData, uri, media,
+ aReusableSheets);
+ }
+
+ if (NS_FAILED(rv) || previousSheetCount == aParent->ChildSheets().Length()) {
+ // Servo and Gecko have different ideas of what a valid URL is, so we might
+ // get in here with a URL string that NS_NewURI can't handle. We may also
+ // reach here via an import cycle. For the import cycle case, we need some
+ // sheet object per spec, even if its empty. DevTools uses the URI to
+ // realize it has hit an import cycle, so we mark it complete to make the
+ // sheet readable from JS.
+ RefPtr<StyleSheet> emptySheet =
+ aParent->CreateEmptyChildSheet(media.forget());
+ // Make a dummy URI if we don't have one because some methods assume
+ // non-null URIs.
+ if (!uri) {
+ NS_NewURI(getter_AddRefs(uri), "about:invalid"_ns);
+ }
+ emptySheet->SetURIs(uri, uri, uri);
+ emptySheet->SetPrincipal(aURL.ExtraData().Principal());
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateForExternalCSSResources(emptySheet);
+ emptySheet->SetReferrerInfo(referrerInfo);
+ emptySheet->SetComplete();
+ aParent->AppendStyleSheet(*emptySheet);
+ return emptySheet.forget();
+ }
+
+ RefPtr<StyleSheet> sheet = aParent->ChildSheets().LastElement();
+ return sheet.forget();
+}
+
+StyleSheet* Gecko_LoadStyleSheet(Loader* aLoader, StyleSheet* aParent,
+ SheetLoadData* aParentLoadData,
+ LoaderReusableStyleSheets* aReusableSheets,
+ const StyleCssUrl* aUrl,
+ StyleStrong<StyleLockedMediaList> aMediaList) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aUrl);
+
+ return LoadImportSheet(aLoader, aParent, aParentLoadData, aReusableSheets,
+ *aUrl, aMediaList.Consume())
+ .take();
+}
+
+void Gecko_LoadStyleSheetAsync(SheetLoadDataHolder* aParentData,
+ const StyleCssUrl* aUrl,
+ StyleStrong<StyleLockedMediaList> aMediaList,
+ StyleStrong<StyleLockedImportRule> aImportRule) {
+ MOZ_ASSERT(aUrl);
+ RefPtr<SheetLoadDataHolder> loadData = aParentData;
+ RefPtr<StyleLockedMediaList> mediaList = aMediaList.Consume();
+ RefPtr<StyleLockedImportRule> importRule = aImportRule.Consume();
+ NS_DispatchToMainThreadQueue(
+ NS_NewRunnableFunction(
+ __func__,
+ [data = std::move(loadData), url = StyleCssUrl(*aUrl),
+ media = std::move(mediaList),
+ import = std::move(importRule)]() mutable {
+ MOZ_ASSERT(NS_IsMainThread());
+ SheetLoadData* d = data->get();
+ RefPtr<StyleSheet> sheet = LoadImportSheet(
+ d->mLoader, d->mSheet, d, nullptr, url, media.forget());
+ Servo_ImportRule_SetSheet(import, sheet);
+ }),
+ EventQueuePriority::RenderBlocking);
+}
+
+void Gecko_AddPropertyToSet(nsCSSPropertyIDSet* aPropertySet,
+ nsCSSPropertyID aProperty) {
+ aPropertySet->AddProperty(aProperty);
+}
+
+bool Gecko_DocumentRule_UseForPresentation(
+ const Document* aDocument, const nsACString* aPattern,
+ DocumentMatchingFunction aMatchingFunction) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsIURI* docURI = aDocument->GetDocumentURI();
+ nsAutoCString docURISpec;
+ if (docURI) {
+ // If GetSpec fails (due to OOM) just skip these URI-specific CSS rules.
+ nsresult rv = docURI->GetSpec(docURISpec);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ return CSSMozDocumentRule::Match(aDocument, docURI, docURISpec, *aPattern,
+ aMatchingFunction);
+}
+
+void Gecko_SetJemallocThreadLocalArena(bool enabled) {
+#if defined(MOZ_MEMORY)
+ jemalloc_thread_local_arena(enabled);
+#endif
+}
+
+template <typename T>
+void Construct(T* aPtr, const Document* aDoc) {
+ if constexpr (std::is_constructible_v<T, const Document&>) {
+ MOZ_ASSERT(aDoc);
+ new (KnownNotNull, aPtr) T(*aDoc);
+ } else {
+ MOZ_ASSERT(!aDoc);
+ new (KnownNotNull, aPtr) T();
+ // These instance are intentionally global, and we don't want leakcheckers
+ // to report them.
+ aPtr->MarkLeaked();
+ }
+}
+
+#define STYLE_STRUCT(name) \
+ void Gecko_Construct_Default_nsStyle##name(nsStyle##name* ptr, \
+ const Document* doc) { \
+ Construct(ptr, doc); \
+ } \
+ void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr, \
+ const nsStyle##name* other) { \
+ new (ptr) nsStyle##name(*other); \
+ } \
+ void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr) { \
+ ptr->~nsStyle##name(); \
+ }
+
+#include "nsStyleStructList.h"
+
+#undef STYLE_STRUCT
+
+bool Gecko_ErrorReportingEnabled(const StyleSheet* aSheet,
+ const Loader* aLoader,
+ uint64_t* aOutWindowId) {
+ if (!ErrorReporter::ShouldReportErrors(aSheet, aLoader)) {
+ return false;
+ }
+ *aOutWindowId = ErrorReporter::FindInnerWindowId(aSheet, aLoader);
+ return true;
+}
+
+void Gecko_ReportUnexpectedCSSError(
+ const uint64_t aWindowId, nsIURI* aURI, const char* message,
+ const char* param, uint32_t paramLen, const char* prefix,
+ const char* prefixParam, uint32_t prefixParamLen, const char* suffix,
+ const char* source, uint32_t sourceLen, const char* selectors,
+ uint32_t selectorsLen, uint32_t lineNumber, uint32_t colNumber) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ ErrorReporter reporter(aWindowId);
+
+ if (prefix) {
+ if (prefixParam) {
+ nsDependentCSubstring paramValue(prefixParam, prefixParamLen);
+ AutoTArray<nsString, 1> wideParam;
+ CopyUTF8toUTF16(paramValue, *wideParam.AppendElement());
+ reporter.ReportUnexpectedUnescaped(prefix, wideParam);
+ } else {
+ reporter.ReportUnexpected(prefix);
+ }
+ }
+
+ if (param) {
+ nsDependentCSubstring paramValue(param, paramLen);
+ AutoTArray<nsString, 1> wideParam;
+ CopyUTF8toUTF16(paramValue, *wideParam.AppendElement());
+ reporter.ReportUnexpectedUnescaped(message, wideParam);
+ } else {
+ reporter.ReportUnexpected(message);
+ }
+
+ if (suffix) {
+ reporter.ReportUnexpected(suffix);
+ }
+ nsDependentCSubstring sourceValue(source, sourceLen);
+ nsDependentCSubstring selectorsValue(selectors, selectorsLen);
+ reporter.OutputError(sourceValue, selectorsValue, lineNumber + 1, colNumber,
+ aURI);
+}
+
+void Gecko_ContentList_AppendAll(nsSimpleContentList* aList,
+ const Element** aElements, size_t aLength) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aElements);
+ MOZ_ASSERT(aLength);
+ MOZ_ASSERT(aList);
+
+ aList->SetCapacity(aLength);
+
+ for (size_t i = 0; i < aLength; ++i) {
+ aList->AppendElement(const_cast<Element*>(aElements[i]));
+ }
+}
+
+const nsTArray<Element*>* Gecko_Document_GetElementsWithId(const Document* aDoc,
+ nsAtom* aId) {
+ MOZ_ASSERT(aDoc);
+ MOZ_ASSERT(aId);
+ return aDoc->GetAllElementsForId(aId);
+}
+
+const nsTArray<Element*>* Gecko_ShadowRoot_GetElementsWithId(
+ const ShadowRoot* aShadowRoot, nsAtom* aId) {
+ MOZ_ASSERT(aShadowRoot);
+ MOZ_ASSERT(aId);
+ return aShadowRoot->GetAllElementsForId(aId);
+}
+
+bool Gecko_ComputeBoolPrefMediaQuery(nsAtom* aPref) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // This map leaks until shutdown, but that's fine, all the values are
+ // controlled by us so it's not expected to be big.
+ static StaticAutoPtr<nsTHashMap<RefPtr<nsAtom>, bool>> sRegisteredPrefs;
+ if (!sRegisteredPrefs) {
+ if (PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal)) {
+ // Styling doesn't really matter much at this point, don't bother.
+ return false;
+ }
+ sRegisteredPrefs = new nsTHashMap<RefPtr<nsAtom>, bool>();
+ ClearOnShutdown(&sRegisteredPrefs);
+ }
+ return sRegisteredPrefs->LookupOrInsertWith(aPref, [&] {
+ nsAutoAtomCString prefName(aPref);
+ Preferences::RegisterCallback(
+ [](const char* aPrefName, void*) {
+ if (sRegisteredPrefs) {
+ RefPtr<nsAtom> name = NS_Atomize(nsDependentCString(aPrefName));
+ sRegisteredPrefs->InsertOrUpdate(name,
+ Preferences::GetBool(aPrefName));
+ }
+ LookAndFeel::NotifyChangedAllWindows(
+ widget::ThemeChangeKind::MediaQueriesOnly);
+ },
+ prefName);
+ return Preferences::GetBool(prefName.get());
+ });
+}
+
+bool Gecko_IsFontFormatSupported(StyleFontFaceSourceFormatKeyword aFormat) {
+ return gfxPlatform::GetPlatform()->IsFontFormatSupported(
+ aFormat, StyleFontFaceSourceTechFlags::Empty());
+}
+
+bool Gecko_IsFontTechSupported(StyleFontFaceSourceTechFlags aFlag) {
+ return gfxPlatform::GetPlatform()->IsFontFormatSupported(
+ StyleFontFaceSourceFormatKeyword::None, aFlag);
+}
+
+bool Gecko_IsKnownIconFontFamily(const nsAtom* aFamilyName) {
+ return gfxPlatform::GetPlatform()->IsKnownIconFontFamily(aFamilyName);
+}
+
+bool Gecko_IsInServoTraversal() { return ServoStyleSet::IsInServoTraversal(); }
+
+bool Gecko_IsMainThread() { return NS_IsMainThread(); }
+
+bool Gecko_IsDOMWorkerThread() { return !!GetCurrentThreadWorkerPrivate(); }
+
+int32_t Gecko_GetNumStyleThreads() {
+ if (const auto& cpuInfo = hal::GetHeterogeneousCpuInfo()) {
+ size_t numBigCpus = cpuInfo->mBigCpus.Count();
+ // If CPUs are homogeneous we do not need to override stylo's
+ // default number of threads.
+ if (numBigCpus != cpuInfo->mTotalNumCpus) {
+ // From testing on a variety of devices it appears using only
+ // the number of big cores gives best performance when there are
+ // 2 or more big cores. If there are fewer than 2 big cores then
+ // additionally using the medium cores performs better.
+ if (numBigCpus >= 2) {
+ return static_cast<int32_t>(numBigCpus);
+ }
+ return static_cast<int32_t>(numBigCpus + cpuInfo->mMediumCpus.Count());
+ }
+ }
+
+ return -1;
+}
+
+const nsAttrValue* Gecko_GetSVGAnimatedClass(const Element* aElement) {
+ MOZ_ASSERT(aElement->IsSVGElement());
+ return static_cast<const SVGElement*>(aElement)->GetAnimatedClassName();
+}
+
+bool Gecko_AssertClassAttrValueIsSane(const nsAttrValue* aValue) {
+ MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom ||
+ aValue->Type() == nsAttrValue::eString ||
+ aValue->Type() == nsAttrValue::eAtomArray);
+ MOZ_ASSERT_IF(
+ aValue->Type() == nsAttrValue::eString,
+ nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
+ aValue->GetStringValue())
+ .IsEmpty());
+ return true;
+}
+
+void Gecko_GetSafeAreaInsets(const nsPresContext* aPresContext, float* aTop,
+ float* aRight, float* aBottom, float* aLeft) {
+ MOZ_ASSERT(aPresContext);
+ ScreenIntMargin safeAreaInsets = aPresContext->GetSafeAreaInsets();
+ *aTop = aPresContext->DevPixelsToFloatCSSPixels(safeAreaInsets.top);
+ *aRight = aPresContext->DevPixelsToFloatCSSPixels(safeAreaInsets.right);
+ *aBottom = aPresContext->DevPixelsToFloatCSSPixels(safeAreaInsets.bottom);
+ *aLeft = aPresContext->DevPixelsToFloatCSSPixels(safeAreaInsets.left);
+}
+
+void Gecko_PrintfStderr(const nsCString* aStr) {
+ printf_stderr("%s", aStr->get());
+}
+
+nsAtom* Gecko_Element_ImportedPart(const nsAttrValue* aValue,
+ nsAtom* aPartName) {
+ if (aValue->Type() != nsAttrValue::eShadowParts) {
+ return nullptr;
+ }
+ return aValue->GetShadowPartsValue().GetReverse(aPartName);
+}
+
+nsAtom** Gecko_Element_ExportedParts(const nsAttrValue* aValue,
+ nsAtom* aPartName, size_t* aOutLength) {
+ if (aValue->Type() != nsAttrValue::eShadowParts) {
+ return nullptr;
+ }
+ auto* parts = aValue->GetShadowPartsValue().Get(aPartName);
+ if (!parts) {
+ return nullptr;
+ }
+ *aOutLength = parts->Length();
+ static_assert(sizeof(RefPtr<nsAtom>) == sizeof(nsAtom*));
+ static_assert(alignof(RefPtr<nsAtom>) == alignof(nsAtom*));
+ return reinterpret_cast<nsAtom**>(parts->Elements());
+}
+
+bool StyleSingleFontFamily::IsNamedFamily(const nsAString& aFamilyName) const {
+ if (!IsFamilyName()) {
+ return false;
+ }
+ nsDependentAtomString name(AsFamilyName().name.AsAtom());
+ return name.Equals(aFamilyName, nsCaseInsensitiveStringComparator);
+}
+
+StyleSingleFontFamily StyleSingleFontFamily::Parse(
+ const nsACString& aFamilyOrGenericName) {
+ // should only be passed a single font - not entirely correct, a family
+ // *could* have a comma in it but in practice never does so
+ // for debug purposes this is fine
+ NS_ASSERTION(aFamilyOrGenericName.FindChar(',') == -1,
+ "Convert method should only be passed a single family name");
+
+ auto genericType = Servo_GenericFontFamily_Parse(&aFamilyOrGenericName);
+ if (genericType != StyleGenericFontFamily::None) {
+ return Generic(genericType);
+ }
+ return FamilyName({StyleAtom(NS_Atomize(aFamilyOrGenericName)),
+ StyleFontFamilyNameSyntax::Identifiers});
+}
+
+void StyleSingleFontFamily::AppendToString(nsACString& aName,
+ bool aQuote) const {
+ if (IsFamilyName()) {
+ const auto& name = AsFamilyName();
+ if (!aQuote) {
+ aName.Append(nsAutoAtomCString(name.name.AsAtom()));
+ return;
+ }
+ Servo_FamilyName_Serialize(&name, &aName);
+ return;
+ }
+
+ switch (AsGeneric()) {
+ case StyleGenericFontFamily::None:
+ case StyleGenericFontFamily::MozEmoji:
+ MOZ_FALLTHROUGH_ASSERT("Should never appear in a font-family name!");
+ case StyleGenericFontFamily::Serif:
+ return aName.AppendLiteral("serif");
+ case StyleGenericFontFamily::SansSerif:
+ return aName.AppendLiteral("sans-serif");
+ case StyleGenericFontFamily::Monospace:
+ return aName.AppendLiteral("monospace");
+ case StyleGenericFontFamily::Cursive:
+ return aName.AppendLiteral("cursive");
+ case StyleGenericFontFamily::Fantasy:
+ return aName.AppendLiteral("fantasy");
+ case StyleGenericFontFamily::SystemUi:
+ return aName.AppendLiteral("system-ui");
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown generic font-family!");
+ return aName.AppendLiteral("serif");
+}
+
+StyleFontFamilyList StyleFontFamilyList::WithNames(
+ nsTArray<StyleSingleFontFamily>&& aNames) {
+ StyleFontFamilyList list;
+ Servo_FontFamilyList_WithNames(&aNames, &list);
+ return list;
+}
+
+StyleFontFamilyList StyleFontFamilyList::WithOneUnquotedFamily(
+ const nsACString& aName) {
+ AutoTArray<StyleSingleFontFamily, 1> names;
+ names.AppendElement(StyleSingleFontFamily::FamilyName(
+ {StyleAtom(NS_Atomize(aName)), StyleFontFamilyNameSyntax::Identifiers}));
+ return WithNames(std::move(names));
+}
diff --git a/layout/style/GeckoBindings.h b/layout/style/GeckoBindings.h
new file mode 100644
index 0000000000..7bb839ae18
--- /dev/null
+++ b/layout/style/GeckoBindings.h
@@ -0,0 +1,660 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* FFI functions for Servo to call into Gecko */
+
+#ifndef mozilla_GeckoBindings_h
+#define mozilla_GeckoBindings_h
+
+#include <stdint.h>
+
+#include "mozilla/ServoTypes.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/css/DocumentMatchingFunction.h"
+#include "mozilla/css/SheetLoadData.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/PreferenceSheet.h"
+#include "nsStyleStruct.h"
+#include "COLRFonts.h"
+
+class nsAtom;
+class nsIURI;
+class nsSimpleContentList;
+struct nsFont;
+class ServoComputedData;
+
+namespace mozilla {
+class ComputedStyle;
+class SeenPtrs;
+class ServoElementSnapshot;
+class ServoElementSnapshotTable;
+class StyleSheet;
+enum class PseudoStyleType : uint8_t;
+enum class PointerCapabilities : uint8_t;
+enum class UpdateAnimationsTasks : uint8_t;
+struct Keyframe;
+struct StyleStylesheetContents;
+
+namespace css {
+class LoaderReusableStyleSheets;
+}
+namespace dom {
+enum class CompositeOperationOrAuto : uint8_t;
+enum class ScreenColorGamut : uint8_t;
+} // namespace dom
+} // namespace mozilla
+
+#ifdef NIGHTLY_BUILD
+const bool GECKO_IS_NIGHTLY = true;
+#else
+const bool GECKO_IS_NIGHTLY = false;
+#endif
+
+#define NS_DECL_THREADSAFE_FFI_REFCOUNTING(class_, name_) \
+ void Gecko_AddRef##name_##ArbitraryThread(class_* aPtr); \
+ void Gecko_Release##name_##ArbitraryThread(class_* aPtr);
+#define NS_IMPL_THREADSAFE_FFI_REFCOUNTING(class_, name_) \
+ static_assert(class_::HasThreadSafeRefCnt::value, \
+ "NS_DECL_THREADSAFE_FFI_REFCOUNTING can only be used with " \
+ "classes that have thread-safe refcounting"); \
+ void Gecko_AddRef##name_##ArbitraryThread(class_* aPtr) { NS_ADDREF(aPtr); } \
+ void Gecko_Release##name_##ArbitraryThread(class_* aPtr) { NS_RELEASE(aPtr); }
+
+extern "C" {
+
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsIURI, nsIURI);
+
+// Debugging stuff.
+void Gecko_Element_DebugListAttributes(const mozilla::dom::Element*,
+ nsCString*);
+
+void Gecko_Snapshot_DebugListAttributes(const mozilla::ServoElementSnapshot*,
+ nsCString*);
+
+bool Gecko_IsSignificantChild(const nsINode*, bool whitespace_is_significant);
+
+const nsINode* Gecko_GetLastChild(const nsINode*);
+const nsINode* Gecko_GetFlattenedTreeParentNode(const nsINode*);
+const mozilla::dom::Element* Gecko_GetBeforeOrAfterPseudo(
+ const mozilla::dom::Element*, bool is_before);
+const mozilla::dom::Element* Gecko_GetMarkerPseudo(
+ const mozilla::dom::Element*);
+
+nsTArray<nsIContent*>* Gecko_GetAnonymousContentForElement(
+ const mozilla::dom::Element*);
+void Gecko_DestroyAnonymousContentList(nsTArray<nsIContent*>* anon_content);
+
+const nsTArray<RefPtr<nsINode>>* Gecko_GetAssignedNodes(
+ const mozilla::dom::Element*);
+
+void Gecko_GetQueryContainerSize(const mozilla::dom::Element*,
+ nscoord* aOutWidth, nscoord* aOutHeight);
+
+void Gecko_ComputedStyle_Init(mozilla::ComputedStyle* context,
+ const ServoComputedData* values,
+ mozilla::PseudoStyleType pseudo_type);
+
+void Gecko_ComputedStyle_Destroy(mozilla::ComputedStyle* context);
+
+// By default, Servo walks the DOM by traversing the siblings of the DOM-view
+// first child. This generally works, but misses anonymous children, which we
+// want to traverse during styling. To support these cases, we create an
+// optional stack-allocated iterator in aIterator for nodes that need it.
+void Gecko_ConstructStyleChildrenIterator(const mozilla::dom::Element*,
+ mozilla::dom::StyleChildrenIterator*);
+
+void Gecko_DestroyStyleChildrenIterator(mozilla::dom::StyleChildrenIterator*);
+
+const nsINode* Gecko_GetNextStyleChild(mozilla::dom::StyleChildrenIterator*);
+
+nsAtom* Gecko_Element_ImportedPart(const nsAttrValue*, nsAtom*);
+nsAtom** Gecko_Element_ExportedParts(const nsAttrValue*, nsAtom*,
+ size_t* aOutLength);
+
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(mozilla::css::SheetLoadDataHolder,
+ SheetLoadDataHolder);
+
+void Gecko_StyleSheet_FinishAsyncParse(
+ mozilla::css::SheetLoadDataHolder* data,
+ mozilla::StyleStrong<mozilla::StyleStylesheetContents> sheet_contents,
+ mozilla::StyleUseCounters* use_counters);
+
+mozilla::StyleSheet* Gecko_LoadStyleSheet(
+ mozilla::css::Loader* loader, mozilla::StyleSheet* parent,
+ mozilla::css::SheetLoadData* parent_load_data,
+ mozilla::css::LoaderReusableStyleSheets* reusable_sheets,
+ const mozilla::StyleCssUrl* url,
+ mozilla::StyleStrong<mozilla::StyleLockedMediaList> media_list);
+
+void Gecko_LoadStyleSheetAsync(
+ mozilla::css::SheetLoadDataHolder* parent_data,
+ const mozilla::StyleCssUrl* url,
+ mozilla::StyleStrong<mozilla::StyleLockedMediaList>,
+ mozilla::StyleStrong<mozilla::StyleLockedImportRule>);
+
+// Selector Matching.
+uint64_t Gecko_ElementState(const mozilla::dom::Element*);
+bool Gecko_IsRootElement(const mozilla::dom::Element*);
+
+bool Gecko_MatchLang(const mozilla::dom::Element*, nsAtom* override_lang,
+ bool has_override_lang, const char16_t* value);
+
+nsAtom* Gecko_GetXMLLangValue(const mozilla::dom::Element*);
+
+const mozilla::PreferenceSheet::Prefs* Gecko_GetPrefSheetPrefs(
+ const mozilla::dom::Document*);
+
+bool Gecko_IsTableBorderNonzero(const mozilla::dom::Element* element);
+bool Gecko_IsSelectListBox(const mozilla::dom::Element* element);
+
+// Attributes.
+#define SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_) \
+ nsAtom* prefix_##LangValue(implementor_ element);
+
+bool Gecko_AttrEquals(const nsAttrValue*, const nsAtom*, bool aIgnoreCase);
+bool Gecko_AttrDashEquals(const nsAttrValue*, const nsAtom*, bool aIgnoreCase);
+bool Gecko_AttrIncludes(const nsAttrValue*, const nsAtom*, bool aIgnoreCase);
+bool Gecko_AttrHasSubstring(const nsAttrValue*, const nsAtom*,
+ bool aIgnoreCase);
+bool Gecko_AttrHasPrefix(const nsAttrValue*, const nsAtom*, bool aIgnoreCase);
+bool Gecko_AttrHasSuffix(const nsAttrValue*, const nsAtom*, bool aIgnoreCase);
+
+bool Gecko_AssertClassAttrValueIsSane(const nsAttrValue*);
+const nsAttrValue* Gecko_GetSVGAnimatedClass(const mozilla::dom::Element*);
+
+SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_,
+ const mozilla::dom::Element*)
+
+SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(
+ Gecko_Snapshot, const mozilla::ServoElementSnapshot*)
+
+#undef SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS
+
+// Style attributes.
+const mozilla::StyleLockedDeclarationBlock* Gecko_GetStyleAttrDeclarationBlock(
+ const mozilla::dom::Element* element);
+
+void Gecko_UnsetDirtyStyleAttr(const mozilla::dom::Element* element);
+
+const mozilla::StyleLockedDeclarationBlock*
+Gecko_GetHTMLPresentationAttrDeclarationBlock(
+ const mozilla::dom::Element* element);
+
+const mozilla::StyleLockedDeclarationBlock*
+Gecko_GetExtraContentStyleDeclarations(const mozilla::dom::Element* element);
+
+const mozilla::StyleLockedDeclarationBlock*
+Gecko_GetUnvisitedLinkAttrDeclarationBlock(
+ const mozilla::dom::Element* element);
+
+const mozilla::StyleLockedDeclarationBlock*
+Gecko_GetVisitedLinkAttrDeclarationBlock(const mozilla::dom::Element* element);
+
+const mozilla::StyleLockedDeclarationBlock*
+Gecko_GetActiveLinkAttrDeclarationBlock(const mozilla::dom::Element* element);
+
+// Visited handling.
+
+// Returns whether visited styles are enabled for a given document.
+bool Gecko_VisitedStylesEnabled(const mozilla::dom::Document*);
+
+// Animations
+bool Gecko_GetAnimationRule(
+ const mozilla::dom::Element* aElementOrPseudo,
+ mozilla::EffectCompositor::CascadeLevel aCascadeLevel,
+ mozilla::StyleAnimationValueMap* aAnimationValues);
+
+bool Gecko_StyleAnimationsEquals(
+ const nsStyleAutoArray<mozilla::StyleAnimation>*,
+ const nsStyleAutoArray<mozilla::StyleAnimation>*);
+
+bool Gecko_StyleScrollTimelinesEquals(
+ const nsStyleAutoArray<mozilla::StyleScrollTimeline>*,
+ const nsStyleAutoArray<mozilla::StyleScrollTimeline>*);
+
+bool Gecko_StyleViewTimelinesEquals(
+ const nsStyleAutoArray<mozilla::StyleViewTimeline>*,
+ const nsStyleAutoArray<mozilla::StyleViewTimeline>*);
+
+void Gecko_UpdateAnimations(const mozilla::dom::Element* aElementOrPseudo,
+ const mozilla::ComputedStyle* aOldComputedValues,
+ const mozilla::ComputedStyle* aComputedValues,
+ mozilla::UpdateAnimationsTasks aTasks);
+
+size_t Gecko_GetAnimationEffectCount(
+ const mozilla::dom::Element* aElementOrPseudo);
+bool Gecko_ElementHasAnimations(const mozilla::dom::Element* aElementOrPseudo);
+bool Gecko_ElementHasCSSAnimations(
+ const mozilla::dom::Element* aElementOrPseudo);
+bool Gecko_ElementHasCSSTransitions(
+ const mozilla::dom::Element* aElementOrPseudo);
+bool Gecko_ElementHasWebAnimations(
+ const mozilla::dom::Element* aElementOrPseudo);
+size_t Gecko_ElementTransitions_Length(
+ const mozilla::dom::Element* aElementOrPseudo);
+
+nsCSSPropertyID Gecko_ElementTransitions_PropertyAt(
+ const mozilla::dom::Element* aElementOrPseudo, size_t aIndex);
+
+const mozilla::StyleAnimationValue* Gecko_ElementTransitions_EndValueAt(
+ const mozilla::dom::Element* aElementOrPseudo, size_t aIndex);
+
+double Gecko_GetProgressFromComputedTiming(const mozilla::ComputedTiming*);
+
+double Gecko_GetPositionInSegment(const mozilla::AnimationPropertySegment*,
+ double aProgress, bool aBeforeFlag);
+
+// Get servo's AnimationValue for |aProperty| from the cached base style
+// |aBaseStyles|.
+// |aBaseStyles| is nsRefPtrHashtable<nsGenericHashKey<AnimatedPropertyID>,
+// StyleAnimationValue>.
+// We use RawServoAnimationValueTableBorrowed to avoid exposing
+// nsRefPtrHashtable in FFI.
+const mozilla::StyleAnimationValue* Gecko_AnimationGetBaseStyle(
+ const RawServoAnimationValueTable* aBaseStyles,
+ const mozilla::AnimatedPropertyID* aProperty);
+
+void Gecko_StyleTransition_SetUnsupportedProperty(
+ mozilla::StyleTransition* aTransition, nsAtom* aAtom);
+
+// Atoms.
+nsAtom* Gecko_Atomize(const char* aString, uint32_t aLength);
+nsAtom* Gecko_Atomize16(const nsAString* aString);
+void Gecko_AddRefAtom(nsAtom* aAtom);
+void Gecko_ReleaseAtom(nsAtom* aAtom);
+
+// will not run destructors on dst, give it uninitialized memory
+// font_id is LookAndFeel::FontID
+void Gecko_nsFont_InitSystem(nsFont* dst, mozilla::StyleSystemFont font_id,
+ const nsStyleFont* font,
+ const mozilla::dom::Document*);
+
+void Gecko_nsFont_Destroy(nsFont* dst);
+
+// The gfxFontFeatureValueSet returned from this function has zero reference.
+gfxFontFeatureValueSet* Gecko_ConstructFontFeatureValueSet();
+
+nsTArray<uint32_t>* Gecko_AppendFeatureValueHashEntry(
+ gfxFontFeatureValueSet* value_set, nsAtom* family, uint32_t alternate,
+ nsAtom* name);
+
+// Font variant alternates
+void Gecko_ClearAlternateValues(nsFont* font, size_t length);
+
+void Gecko_AppendAlternateValues(nsFont* font, uint32_t alternate_name,
+ nsAtom* atom);
+
+void Gecko_CopyAlternateValuesFrom(nsFont* dest, const nsFont* src);
+
+// The FontPaletteValueSet returned from this function has zero reference.
+mozilla::gfx::FontPaletteValueSet* Gecko_ConstructFontPaletteValueSet();
+
+mozilla::gfx::FontPaletteValueSet::PaletteValues*
+Gecko_AppendPaletteValueHashEntry(
+ mozilla::gfx::FontPaletteValueSet* aPaletteValueSet, nsAtom* aFamily,
+ nsAtom* aName);
+
+void Gecko_SetFontPaletteBase(
+ mozilla::gfx::FontPaletteValueSet::PaletteValues* aValues,
+ int32_t aBasePaletteIndex);
+
+void Gecko_SetFontPaletteOverride(
+ mozilla::gfx::FontPaletteValueSet::PaletteValues* aValues, int32_t aIndex,
+ mozilla::StyleAbsoluteColor* aColor);
+
+// Visibility style
+void Gecko_SetImageOrientation(nsStyleVisibility* aVisibility,
+ uint8_t aOrientation, bool aFlip);
+
+void Gecko_SetImageOrientationAsFromImage(nsStyleVisibility* aVisibility);
+
+void Gecko_CopyImageOrientationFrom(nsStyleVisibility* aDst,
+ const nsStyleVisibility* aSrc);
+
+// Counter style.
+void Gecko_CounterStyle_ToPtr(const mozilla::StyleCounterStyle*,
+ mozilla::CounterStylePtr*);
+
+void Gecko_SetCounterStyleToNone(mozilla::CounterStylePtr*);
+
+void Gecko_SetCounterStyleToString(mozilla::CounterStylePtr* ptr,
+ const nsACString* symbol);
+
+void Gecko_CopyCounterStyle(mozilla::CounterStylePtr* dst,
+ const mozilla::CounterStylePtr* src);
+
+nsAtom* Gecko_CounterStyle_GetName(const mozilla::CounterStylePtr* ptr);
+
+const mozilla::AnonymousCounterStyle* Gecko_CounterStyle_GetAnonymous(
+ const mozilla::CounterStylePtr* ptr);
+
+// list-style-image style.
+void Gecko_SetListStyleImageNone(nsStyleList* style_struct);
+
+void Gecko_SetListStyleImageImageValue(
+ nsStyleList* style_struct, const mozilla::StyleComputedImageUrl* url);
+
+void Gecko_CopyListStyleImageFrom(nsStyleList* dest, const nsStyleList* src);
+
+// Dirtiness tracking.
+void Gecko_NoteDirtyElement(const mozilla::dom::Element*);
+void Gecko_NoteDirtySubtreeForInvalidation(const mozilla::dom::Element*);
+void Gecko_NoteAnimationOnlyDirtyElement(const mozilla::dom::Element*);
+
+bool Gecko_AnimationNameMayBeReferencedFromStyle(const nsPresContext*,
+ nsAtom* name);
+
+float Gecko_GetScrollbarInlineSize(const nsPresContext*);
+
+// Incremental restyle.
+mozilla::PseudoStyleType Gecko_GetImplementedPseudo(
+ const mozilla::dom::Element*);
+
+// We'd like to return `nsChangeHint` here, but bindgen bitfield enums don't
+// work as return values with the Linux 32-bit ABI at the moment because
+// they wrap the value in a struct.
+uint32_t Gecko_CalcStyleDifference(const mozilla::ComputedStyle* old_style,
+ const mozilla::ComputedStyle* new_style,
+ bool* any_style_struct_changed,
+ bool* reset_only_changed);
+
+nscoord Gecko_CalcLineHeight(const mozilla::StyleLineHeight*,
+ const nsPresContext*, bool aVertical,
+ const nsStyleFont* aAgainstFont,
+ const mozilla::dom::Element* aElement);
+
+// Get an element snapshot for a given element from the table.
+const mozilla::ServoElementSnapshot* Gecko_GetElementSnapshot(
+ const mozilla::ServoElementSnapshotTable* table,
+ const mozilla::dom::Element*);
+
+// Have we seen this pointer before?
+bool Gecko_HaveSeenPtr(mozilla::SeenPtrs* table, const void* ptr);
+
+// `array` must be an nsTArray
+// If changing this signature, please update the
+// friend function declaration in nsTArray.h
+void Gecko_EnsureTArrayCapacity(void* array, size_t capacity, size_t elem_size);
+
+// Same here, `array` must be an nsTArray<T>, for some T.
+//
+// Important note: Only valid for POD types, since destructors won't be run
+// otherwise. This is ensured with rust traits for the relevant structs.
+void Gecko_ClearPODTArray(void* array, size_t elem_size, size_t elem_align);
+
+void Gecko_ResizeTArrayForStrings(nsTArray<nsString>* array, uint32_t length);
+void Gecko_ResizeAtomArray(nsTArray<RefPtr<nsAtom>>* array, uint32_t length);
+
+void Gecko_EnsureImageLayersLength(nsStyleImageLayers* layers, size_t len,
+ nsStyleImageLayers::LayerType layer_type);
+
+void Gecko_EnsureStyleAnimationArrayLength(void* array, size_t len);
+void Gecko_EnsureStyleTransitionArrayLength(void* array, size_t len);
+void Gecko_EnsureStyleScrollTimelineArrayLength(void* array, size_t len);
+void Gecko_EnsureStyleViewTimelineArrayLength(void* array, size_t len);
+
+// Searches from the beginning of |keyframes| for a Keyframe object with the
+// specified offset and timing function. If none is found, a new Keyframe object
+// with the specified |offset| and |timingFunction| will be prepended to
+// |keyframes|.
+//
+// @param keyframes An array of Keyframe objects, sorted by offset.
+// The first Keyframe in the array, if any, MUST have an
+// offset greater than or equal to |offset|.
+// @param offset The offset to search for, or, if no suitable Keyframe is
+// found, the offset to use for the created Keyframe.
+// Must be a floating point number in the range [0.0, 1.0].
+// @param timingFunction The timing function to match, or, if no suitable
+// Keyframe is found, to set on the created Keyframe.
+// @param composition The composition to match, or, if no suitable Keyframe is
+// found, to set on the created Keyframe.
+//
+// @returns The matching or created Keyframe.
+mozilla::Keyframe* Gecko_GetOrCreateKeyframeAtStart(
+ nsTArray<mozilla::Keyframe>* keyframes, float offset,
+ const mozilla::StyleComputedTimingFunction* timingFunction,
+ const mozilla::dom::CompositeOperationOrAuto composition);
+
+// As with Gecko_GetOrCreateKeyframeAtStart except that this method will search
+// from the beginning of |keyframes| for a Keyframe with matching timing
+// function, composition, and an offset of 0.0.
+// Furthermore, if a matching Keyframe is not found, a new Keyframe will be
+// inserted after the *last* Keyframe in |keyframes| with offset 0.0.
+mozilla::Keyframe* Gecko_GetOrCreateInitialKeyframe(
+ nsTArray<mozilla::Keyframe>* keyframes,
+ const mozilla::StyleComputedTimingFunction* timingFunction,
+ const mozilla::dom::CompositeOperationOrAuto composition);
+
+// As with Gecko_GetOrCreateKeyframeAtStart except that this method will search
+// from the *end* of |keyframes| for a Keyframe with matching timing function,
+// composition, and an offset of 1.0. If a matching Keyframe is not found, a new
+// Keyframe will be appended to the end of |keyframes|.
+mozilla::Keyframe* Gecko_GetOrCreateFinalKeyframe(
+ nsTArray<mozilla::Keyframe>* keyframes,
+ const mozilla::StyleComputedTimingFunction* timingFunction,
+ const mozilla::dom::CompositeOperationOrAuto composition);
+
+// Appends and returns a new PropertyValuePair to |aProperties| initialized with
+// its mProperty member set to |aProperty| and all other members initialized to
+// their default values.
+mozilla::PropertyValuePair* Gecko_AppendPropertyValuePair(
+ nsTArray<mozilla::PropertyValuePair>*,
+ const mozilla::AnimatedPropertyID* aProperty);
+
+void Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len);
+
+void Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest);
+
+void Gecko_nsStyleSVG_SetDashArrayLength(nsStyleSVG* svg, uint32_t len);
+
+void Gecko_nsStyleSVG_CopyDashArray(nsStyleSVG* dst, const nsStyleSVG* src);
+
+void Gecko_nsStyleSVG_SetContextPropertiesLength(nsStyleSVG* svg, uint32_t len);
+
+void Gecko_nsStyleSVG_CopyContextProperties(nsStyleSVG* dst,
+ const nsStyleSVG* src);
+
+void Gecko_GetComputedURLSpec(const mozilla::StyleComputedUrl* url,
+ nsCString* spec);
+
+void Gecko_GetComputedImageURLSpec(const mozilla::StyleComputedUrl* url,
+ nsCString* spec);
+
+// Return true if the given image MIME type is supported
+bool Gecko_IsSupportedImageMimeType(const uint8_t* mime_type,
+ const uint32_t len);
+
+void Gecko_nsIURI_Debug(nsIURI*, nsCString* spec);
+
+void Gecko_nsIReferrerInfo_Debug(nsIReferrerInfo* aReferrerInfo,
+ nsCString* aOut);
+
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(mozilla::URLExtraData, URLExtraData);
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsIReferrerInfo, nsIReferrerInfo);
+
+void Gecko_FillAllImageLayers(nsStyleImageLayers* layers, uint32_t max_len);
+
+void Gecko_LoadData_Drop(mozilla::StyleLoadData*);
+
+void Gecko_nsStyleFont_SetLang(nsStyleFont* font, nsAtom* atom);
+
+void Gecko_nsStyleFont_CopyLangFrom(nsStyleFont* aFont,
+ const nsStyleFont* aSource);
+
+mozilla::Length Gecko_nsStyleFont_ComputeMinSize(const nsStyleFont*,
+ const mozilla::dom::Document*);
+
+// Computes the default generic font for a language.
+mozilla::StyleGenericFontFamily
+Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
+ const mozilla::dom::Document*, nsAtom* language);
+
+mozilla::Length Gecko_GetBaseSize(const mozilla::dom::Document*,
+ nsAtom* language,
+ mozilla::StyleGenericFontFamily);
+
+struct GeckoFontMetrics {
+ mozilla::Length mXSize;
+ mozilla::Length mChSize; // negatives indicate not found.
+ mozilla::Length mCapHeight; // negatives indicate not found.
+ mozilla::Length mIcWidth; // negatives indicate not found.
+ mozilla::Length mAscent;
+ mozilla::Length mComputedEmSize;
+ float mScriptPercentScaleDown; // zero is invalid or means not found.
+ float mScriptScriptPercentScaleDown; // zero is invalid or means not found.
+};
+
+GeckoFontMetrics Gecko_GetFontMetrics(const nsPresContext*, bool is_vertical,
+ const nsStyleFont* font,
+ mozilla::Length font_size,
+ bool use_user_font_set,
+ bool retrieve_math_scales);
+
+mozilla::StyleSheet* Gecko_StyleSheet_Clone(
+ const mozilla::StyleSheet* aSheet,
+ const mozilla::StyleSheet* aNewParentSheet);
+
+void Gecko_StyleSheet_AddRef(const mozilla::StyleSheet* aSheet);
+void Gecko_StyleSheet_Release(const mozilla::StyleSheet* aSheet);
+bool Gecko_IsDocumentBody(const mozilla::dom::Element* element);
+
+bool Gecko_IsDarkColorScheme(const mozilla::dom::Document*,
+ const mozilla::StyleColorScheme*);
+nscolor Gecko_ComputeSystemColor(mozilla::StyleSystemColor,
+ const mozilla::dom::Document*,
+ const mozilla::StyleColorScheme*);
+
+// We use an int32_t here instead of a LookAndFeel::IntID/FloatID because
+// forward-declaring a nested enum/struct is impossible.
+int32_t Gecko_GetLookAndFeelInt(int32_t int_id);
+float Gecko_GetLookAndFeelFloat(int32_t float_id);
+
+void Gecko_AddPropertyToSet(nsCSSPropertyIDSet*, nsCSSPropertyID);
+
+// Style-struct management.
+#define STYLE_STRUCT(name) \
+ void Gecko_Construct_Default_nsStyle##name(nsStyle##name* ptr, \
+ const mozilla::dom::Document*); \
+ void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr, \
+ const nsStyle##name* other); \
+ void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr);
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+bool Gecko_DocumentRule_UseForPresentation(
+ const mozilla::dom::Document*, const nsACString* aPattern,
+ mozilla::css::DocumentMatchingFunction);
+
+// Allocator hinting.
+void Gecko_SetJemallocThreadLocalArena(bool enabled);
+
+// Pseudo-element flags.
+#define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
+ const uint32_t SERVO_CSS_PSEUDO_ELEMENT_FLAGS_##name_ = flags_;
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+
+bool Gecko_ErrorReportingEnabled(const mozilla::StyleSheet* sheet,
+ const mozilla::css::Loader* loader,
+ uint64_t* aOutWindowId);
+
+void Gecko_ReportUnexpectedCSSError(
+ uint64_t windowId, nsIURI* uri, const char* message, const char* param,
+ uint32_t paramLen, const char* prefix, const char* prefixParam,
+ uint32_t prefixParamLen, const char* suffix, const char* source,
+ uint32_t sourceLen, const char* selectors, uint32_t selectorsLen,
+ uint32_t lineNumber, uint32_t colNumber);
+
+// DOM APIs.
+void Gecko_ContentList_AppendAll(nsSimpleContentList* aContentList,
+ const mozilla::dom::Element** aElements,
+ size_t aLength);
+
+// FIXME(emilio): These two below should be a single function that takes a
+// `const DocumentOrShadowRoot*`, but that doesn't make MSVC builds happy for a
+// reason I haven't really dug into.
+const nsTArray<mozilla::dom::Element*>* Gecko_Document_GetElementsWithId(
+ const mozilla::dom::Document*, nsAtom* aId);
+
+const nsTArray<mozilla::dom::Element*>* Gecko_ShadowRoot_GetElementsWithId(
+ const mozilla::dom::ShadowRoot*, nsAtom* aId);
+
+bool Gecko_ComputeBoolPrefMediaQuery(nsAtom*);
+
+// Check whether font format/tech is supported.
+bool Gecko_IsFontFormatSupported(
+ mozilla::StyleFontFaceSourceFormatKeyword aFormat);
+bool Gecko_IsFontTechSupported(mozilla::StyleFontFaceSourceTechFlags aFlag);
+
+bool Gecko_IsKnownIconFontFamily(const nsAtom* aFamilyName);
+
+// Returns true if we're currently performing the servo traversal.
+bool Gecko_IsInServoTraversal();
+
+// Returns true if we're currently on the main thread.
+bool Gecko_IsMainThread();
+
+// Returns true if we're currently on a DOM worker thread.
+bool Gecko_IsDOMWorkerThread();
+
+// Returns the preferred number of style threads to use, or -1 for no
+// preference.
+int32_t Gecko_GetNumStyleThreads();
+
+// Media feature helpers.
+//
+// Defined in nsMediaFeatures.cpp.
+mozilla::StyleDisplayMode Gecko_MediaFeatures_GetDisplayMode(
+ const mozilla::dom::Document*);
+
+bool Gecko_MediaFeatures_UseOverlayScrollbars(const mozilla::dom::Document*);
+int32_t Gecko_MediaFeatures_GetColorDepth(const mozilla::dom::Document*);
+int32_t Gecko_MediaFeatures_GetMonochromeBitsPerPixel(
+ const mozilla::dom::Document*);
+mozilla::dom::ScreenColorGamut Gecko_MediaFeatures_ColorGamut(
+ const mozilla::dom::Document*);
+
+void Gecko_MediaFeatures_GetDeviceSize(const mozilla::dom::Document*,
+ nscoord* width, nscoord* height);
+
+float Gecko_MediaFeatures_GetResolution(const mozilla::dom::Document*);
+bool Gecko_MediaFeatures_PrefersReducedMotion(const mozilla::dom::Document*);
+bool Gecko_MediaFeatures_PrefersReducedTransparency(
+ const mozilla::dom::Document*);
+mozilla::StylePrefersContrast Gecko_MediaFeatures_PrefersContrast(
+ const mozilla::dom::Document*);
+mozilla::StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme(
+ const mozilla::dom::Document*, bool aUseContent);
+bool Gecko_MediaFeatures_InvertedColors(const mozilla::dom::Document*);
+mozilla::StyleScripting Gecko_MediaFeatures_Scripting(
+ const mozilla::dom::Document*);
+
+mozilla::StyleDynamicRange Gecko_MediaFeatures_DynamicRange(
+ const mozilla::dom::Document*);
+mozilla::StyleDynamicRange Gecko_MediaFeatures_VideoDynamicRange(
+ const mozilla::dom::Document*);
+
+mozilla::PointerCapabilities Gecko_MediaFeatures_PrimaryPointerCapabilities(
+ const mozilla::dom::Document*);
+
+mozilla::PointerCapabilities Gecko_MediaFeatures_AllPointerCapabilities(
+ const mozilla::dom::Document*);
+
+float Gecko_MediaFeatures_GetDevicePixelRatio(const mozilla::dom::Document*);
+
+bool Gecko_MediaFeatures_IsResourceDocument(const mozilla::dom::Document*);
+bool Gecko_MediaFeatures_MatchesPlatform(mozilla::StylePlatform);
+mozilla::StyleGtkThemeFamily Gecko_MediaFeatures_GtkThemeFamily();
+
+void Gecko_GetSafeAreaInsets(const nsPresContext*, float*, float*, float*,
+ float*);
+
+void Gecko_PrintfStderr(const nsCString*);
+
+} // extern "C"
+
+#endif // mozilla_GeckoBindings_h
diff --git a/layout/style/GenerateCSSPropertyID.py b/layout/style/GenerateCSSPropertyID.py
new file mode 100644
index 0000000000..d9bdc22ec5
--- /dev/null
+++ b/layout/style/GenerateCSSPropertyID.py
@@ -0,0 +1,40 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 runpy
+import string
+
+
+def generate(output, template, dataFile):
+ with open(template, "r") as f:
+ template = string.Template(f.read())
+ data = runpy.run_path(dataFile)["data"]
+
+ longhand_count = 0
+ shorthand_count = 0
+ alias_count = 0
+ property_ids = []
+ for prop in data.values():
+ if prop.type() != "alias":
+ if prop.type() == "longhand":
+ assert shorthand_count == 0
+ longhand_count += 1
+ else:
+ assert alias_count == 0
+ shorthand_count += 1
+ property_ids.append("eCSSProperty_{}".format(prop.id))
+ else:
+ alias_count += 1
+ property_ids.append("eCSSPropertyAlias_{}".format(prop.alias_id))
+
+ output.write("/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n")
+ output.write(
+ template.substitute(
+ {
+ "property_ids": "\n".join(" {},".format(p) for p in property_ids),
+ "longhand_count": property_ids[longhand_count],
+ "shorthand_count": property_ids[longhand_count + shorthand_count],
+ }
+ )
+ )
diff --git a/layout/style/GenerateCSSPropsGenerated.py b/layout/style/GenerateCSSPropsGenerated.py
new file mode 100644
index 0000000000..90ed792194
--- /dev/null
+++ b/layout/style/GenerateCSSPropsGenerated.py
@@ -0,0 +1,114 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 runpy
+import sys
+import string
+import argparse
+
+
+class PropertyWrapper(object):
+ __slots__ = ["index", "prop", "idlname"]
+
+ def __init__(self, index, prop):
+ self.index = index
+ self.prop = prop
+ if "Internal" in prop.flags:
+ self.idlname = None
+ else:
+ idl_name = prop.method
+ if not idl_name.startswith("Moz"):
+ idl_name = idl_name[0].lower() + idl_name[1:]
+ self.idlname = idl_name
+
+ def __getattr__(self, name):
+ return getattr(self.prop, name)
+
+
+def generate(output, dataFile):
+ output.write(
+ """/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */
+
+/* processed file that defines CSS property tables that can't be generated
+ with the pre-processor, designed to be #included in nsCSSProps.cpp */
+
+"""
+ )
+
+ raw_properties = runpy.run_path(dataFile)["data"]
+ properties = [
+ PropertyWrapper(i, p)
+ for i, p in enumerate(raw_properties.values())
+ if p.type() != "alias"
+ ]
+
+ # Generate kIDLNameTable
+ output.write(
+ "const char* const nsCSSProps::" "kIDLNameTable[eCSSProperty_COUNT] = {\n"
+ )
+ for p in properties:
+ if p.idlname is None:
+ output.write(" nullptr, // {}\n".format(p.name))
+ else:
+ output.write(' "{}",\n'.format(p.idlname))
+ output.write("};\n\n")
+
+ # Generate kIDLNameSortPositionTable
+ ps = sorted(properties, key=lambda p: p.idlname if p.idlname else "")
+ ps = [(p, position) for position, p in enumerate(ps)]
+ ps.sort(key=lambda item: item[0].index)
+ output.write(
+ "const int32_t nsCSSProps::"
+ "kIDLNameSortPositionTable[eCSSProperty_COUNT] = {\n"
+ )
+ for p, position in ps:
+ output.write(" {},\n".format(position))
+ output.write("};\n\n")
+
+ # Generate preferences table
+ output.write(
+ "const nsCSSProps::PropertyPref " "nsCSSProps::kPropertyPrefTable[] = {\n"
+ )
+ for p in raw_properties.values():
+ if not p.pref:
+ continue
+ if p.type() != "alias":
+ prop_id = "eCSSProperty_" + p.id
+ else:
+ prop_id = "eCSSPropertyAlias_" + p.alias_id
+ output.write(' {{ {}, "{}" }},\n'.format(prop_id, p.pref))
+ output.write(" { eCSSProperty_UNKNOWN, nullptr },\n")
+ output.write("};\n\n")
+
+ # Generate shorthand subprop tables
+ names = []
+ for p in properties:
+ if p.type() != "shorthand":
+ continue
+ name = "g{}SubpropTable".format(p.method)
+ names.append(name)
+ output.write("static const nsCSSPropertyID {}[] = {{\n".format(name))
+ for subprop in p.subprops:
+ output.write(" eCSSProperty_{},\n".format(subprop))
+ output.write(" eCSSProperty_UNKNOWN\n")
+ output.write("};\n\n")
+ output.write("const nsCSSPropertyID* const\n")
+ output.write(
+ "nsCSSProps::kSubpropertyTable["
+ "eCSSProperty_COUNT - eCSSProperty_COUNT_no_shorthands"
+ "] = {\n"
+ )
+ for name in names:
+ output.write(" {},\n".format(name))
+ output.write("};\n\n")
+
+ # Generate assertions
+ msg = (
+ "GenerateCSSPropsGenerated.py did not list properties "
+ "in nsCSSPropertyID order"
+ )
+ for p in properties:
+ output.write(
+ 'static_assert(eCSSProperty_{} == {}, "{}");\n'.format(p.id, p.index, msg)
+ )
diff --git a/layout/style/GenerateCompositorAnimatableProperties.py b/layout/style/GenerateCompositorAnimatableProperties.py
new file mode 100644
index 0000000000..b64183b679
--- /dev/null
+++ b/layout/style/GenerateCompositorAnimatableProperties.py
@@ -0,0 +1,35 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 runpy
+
+
+def generate(output, dataFile):
+ output.write(
+ """/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */
+#ifndef COMPOSITOR_ANIMATABLE_PROPERTY_LIST
+#define COMPOSITOR_ANIMATABLE_PROPERTY_LIST { \\
+"""
+ )
+
+ def can_animate_on_compositor(p):
+ return "CanAnimateOnCompositor" in p.flags and p.type() != "alias"
+
+ properties = runpy.run_path(dataFile)["data"]
+ properties = filter(can_animate_on_compositor, properties.values())
+
+ count = 0
+ for p in properties:
+ output.write(" eCSSProperty_{}, \\\n".format(p.id))
+ count += 1
+
+ output.write("}\n")
+ output.write("#endif /* COMPOSITOR_ANIMATABLE_PROPERTY_LIST */\n")
+
+ output.write("\n")
+ output.write("#ifndef COMPOSITOR_ANIMATABLE_PROPERTY_LIST_LENGTH\n")
+ output.write(
+ "#define COMPOSITOR_ANIMATABLE_PROPERTY_LIST_LENGTH {}\n".format(count)
+ )
+ output.write("#endif /* COMPOSITOR_ANIMATABLE_PROPERTY_LIST_LENGTH */\n")
diff --git a/layout/style/GenerateComputedDOMStyleGenerated.py b/layout/style/GenerateComputedDOMStyleGenerated.py
new file mode 100644
index 0000000000..2dcfabd241
--- /dev/null
+++ b/layout/style/GenerateComputedDOMStyleGenerated.py
@@ -0,0 +1,81 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 runpy
+
+FILE = """/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */
+
+/* processed file that defines entries for nsComputedDOMStyle, designed
+ to be #included in nsComputedDOMStyle.cpp */
+
+static constexpr size_t kEntryIndices[eCSSProperty_COUNT] = {{
+ {indices}
+}};
+
+{asserts}
+
+static constexpr Entry kEntries[eCSSProperty_COUNT] = {{
+ {entries}
+}};
+"""
+
+
+def generate(output, dataFile):
+ def order_key(p):
+ # Put prefixed properties after normal properties.
+ # The spec is unclear about this, and Blink doesn't have any sensible
+ # order at all, so it probably doesn't matter a lot. But originally
+ # Gecko put then later so we do so as well. See w3c/csswg-drafts#2827.
+ order = p.name.startswith("-")
+ return (order, p.name)
+
+ def has_cpp_getter(p):
+ if not "ExposedOnGetCS" in p.flags:
+ return False
+ if "SerializedByServo" in p.flags:
+ return False
+ if p.type() == "longhand" and "IsLogical" in p.flags:
+ return False
+ return True
+
+ def getter_entry(p):
+ if has_cpp_getter(p):
+ return "DoGet" + p.method
+ # Put a dummy getter here instead of nullptr because MSVC seems
+ # to have bug which ruins the table when we put nullptr for
+ # pointer-to-member-function. See bug 1471426.
+ return "DummyGetter"
+
+ properties = runpy.run_path(dataFile)["data"]
+
+ entries = []
+ indices = []
+ asserts = []
+ index_map = {}
+ non_aliases = list(filter(lambda p: p.type() != "alias", properties.values()))
+ for i, p in enumerate(sorted(non_aliases, key=order_key)):
+ can_be_exposed = "true" if "ExposedOnGetCS" in p.flags else "false"
+ entries.append(
+ "{{ eCSSProperty_{}, {}, &nsComputedDOMStyle::{}}}".format(
+ p.id, can_be_exposed, getter_entry(p)
+ )
+ )
+ index_map[p.id] = i
+ i += 1
+
+ for i, p in enumerate(non_aliases):
+ indices.append(str(index_map[p.id]))
+ asserts.append(
+ 'static_assert(size_t(eCSSProperty_{}) == {}, "");'.format(p.id, i)
+ )
+
+ assert len(indices) == len(entries)
+
+ output.write(
+ FILE.format(
+ indices=", ".join(indices),
+ entries=",\n ".join(entries),
+ asserts="\n".join(asserts),
+ )
+ )
diff --git a/layout/style/GenerateCountedUnknownProperties.py b/layout/style/GenerateCountedUnknownProperties.py
new file mode 100644
index 0000000000..b46884da6b
--- /dev/null
+++ b/layout/style/GenerateCountedUnknownProperties.py
@@ -0,0 +1,24 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import re
+import runpy
+import string
+
+
+def to_camel_case(ident):
+ return re.sub(
+ "(^|_|-)([a-z0-9])", lambda m: m.group(2).upper(), ident.strip("_").strip("-")
+ )
+
+
+def generate(output, prop_file):
+ properties = runpy.run_path(prop_file)["COUNTED_UNKNOWN_PROPERTIES"]
+
+ output.write("/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n")
+
+ for prop in properties:
+ output.write(
+ "COUNTED_UNKNOWN_PROPERTY({}, {})\n".format(prop, to_camel_case(prop))
+ )
diff --git a/layout/style/GenerateServoCSSPropList.py b/layout/style/GenerateServoCSSPropList.py
new file mode 100644
index 0000000000..0e9678ac5c
--- /dev/null
+++ b/layout/style/GenerateServoCSSPropList.py
@@ -0,0 +1,138 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 buildconfig
+import mozpack.path as mozpath
+import os
+import runpy
+import subprocess
+import string
+import sys
+
+SERVO_BASE = mozpath.join(buildconfig.topsrcdir, "servo")
+SERVO_PROP_BASE = mozpath.join(SERVO_BASE, "components", "style", "properties")
+
+
+def generate_data(output, template):
+ output.write("# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT\n\n")
+ output.write(
+ subprocess.check_output(
+ [
+ sys.executable,
+ mozpath.join(SERVO_PROP_BASE, "build.py"),
+ "gecko",
+ "geckolib",
+ template,
+ ],
+ universal_newlines=True,
+ )
+ )
+
+ # Add all relevant files into the dependencies of the generated file.
+ DEP_EXTS = [".py", ".rs", ".zip"]
+ deps = set()
+ for path, dirs, files in os.walk(SERVO_PROP_BASE):
+ for file in files:
+ if os.path.splitext(file)[1] in DEP_EXTS:
+ deps.add(mozpath.join(path, file))
+ return deps
+
+
+def generate_header(output, data):
+ data = runpy.run_path(data)["data"]
+
+ output.write(
+ """/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#define CSS_PROP_DOMPROP_PREFIXED(name_) \\
+ CSS_PROP_PUBLIC_OR_PRIVATE(Moz ## name_, name_)
+
+#ifndef CSS_PROP_LONGHAND
+#define CSS_PROP_LONGHAND(name_, id_, method_, flags_, pref_) /* nothing */
+#define DEFINED_CSS_PROP_LONGHAND
+#endif
+
+#ifndef CSS_PROP_SHORTHAND
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) /* nothing */
+#define DEFINED_CSS_PROP_SHORTHAND
+#endif
+
+#ifndef CSS_PROP_ALIAS
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, flags_, pref_) /* nothing */
+#define DEFINED_CSS_PROP_ALIAS
+#endif
+
+"""
+ )
+
+ # Some flags are only used for code generation, so we don't need to
+ # expose them to runtime.
+ COMPILE_TIME_FLAGS = {"ExposedOnGetCS"}
+
+ MACRO_NAMES = {
+ "longhand": "CSS_PROP_LONGHAND",
+ "shorthand": "CSS_PROP_SHORTHAND",
+ "alias": "CSS_PROP_ALIAS",
+ }
+ for prop in data.values():
+ is_internal = "Internal" in prop.flags
+ flags = " | ".join(
+ "CSSPropFlags::{}".format(flag)
+ for flag in prop.flags
+ if flag not in COMPILE_TIME_FLAGS
+ )
+ if not flags:
+ flags = "CSSPropFlags(0)"
+ pref = '"' + prop.pref + '"'
+ method = prop.method
+ if prop.type() == "alias":
+ params = [prop.name, prop.alias_id, prop.prop_id, method, flags, pref]
+ else:
+ if method == "CssFloat":
+ method = "CSS_PROP_PUBLIC_OR_PRIVATE(CssFloat, Float)"
+ elif method.startswith("Moz"):
+ method = "CSS_PROP_DOMPROP_PREFIXED({})".format(method[3:])
+ params = [prop.name, prop.id, method, flags, pref]
+ excludes = []
+ if is_internal:
+ excludes.append("CSS_PROP_LIST_EXCLUDE_INTERNAL")
+ if "Style" not in prop.rules:
+ excludes.append("CSS_PROP_LIST_EXCLUDE_NOT_IN_STYLE")
+
+ if excludes:
+ output.write(
+ "#if {}\n".format(
+ " || ".join("!defined " + exclude for exclude in excludes)
+ )
+ )
+ output.write("{}({})\n".format(MACRO_NAMES[prop.type()], ", ".join(params)))
+ if excludes:
+ output.write("#endif\n")
+
+ output.write(
+ """
+#ifdef DEFINED_CSS_PROP_ALIAS
+#undef CSS_PROP_ALIAS
+#undef DEFINED_CSS_PROP_ALIAS
+#endif
+
+#ifdef DEFINED_CSS_PROP_SHORTHAND
+#undef CSS_PROP_SHORTHAND
+#undef DEFINED_CSS_PROP_SHORTHAND
+#endif
+
+#ifdef DEFINED_CSS_PROP_LONGHAND
+#undef CSS_PROP_LONGHAND
+#undef DEFINED_CSS_PROP_LONGHAND
+#endif
+
+#undef CSS_PROP_DOMPROP_PREFIXED
+"""
+ )
diff --git a/layout/style/GlobalStyleSheetCache.cpp b/layout/style/GlobalStyleSheetCache.cpp
new file mode 100644
index 0000000000..6a8507af7b
--- /dev/null
+++ b/layout/style/GlobalStyleSheetCache.cpp
@@ -0,0 +1,561 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "GlobalStyleSheetCache.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsExceptionHandler.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "MainThreadUtils.h"
+#include "nsContentUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULAppAPI.h"
+
+#include <mozilla/ServoBindings.h>
+
+namespace mozilla {
+
+// The GlobalStyleSheetCache is responsible for sharing user agent style sheet
+// contents across processes using shared memory. Here is a high level view of
+// how that works:
+//
+// * In the parent process, in the GlobalStyleSheetCache constructor (which is
+// called early on in a process' lifetime), we parse all UA style sheets into
+// Gecko StyleSheet objects.
+//
+// * The constructor calls InitSharedSheetsInParent, which creates a shared
+// memory segment that we know ahead of time will be big enough to store the
+// UA sheets.
+//
+// * It then creates a Rust SharedMemoryBuilder object and passes it a pointer
+// to the start of the shared memory.
+//
+// * For each UA sheet, we call Servo_SharedMemoryBuilder_AddStylesheet, which
+// takes the StylesheetContents::rules (an Arc<Locked<CssRules>>), produces a
+// deep clone of it, and writes that clone into the shared memory:
+//
+// * The deep clone isn't a clone() call, but a call to ToShmem::to_shmem. The
+// ToShmem trait must be implemented on every type that is reachable under
+// the Arc<Locked<CssRules>>. The to_shmem call for each type will clone the
+// value, but any heap allocation will be cloned and placed into the shared
+// memory buffer, rather than heap allocated.
+//
+// * For most types, the ToShmem implementation is simple, and we just
+// #[derive(ToShmem)] it. For the types that need special handling due to
+// having heap allocations (Vec<T>, Box<T>, Arc<T>, etc.) we have impls that
+// call to_shmem on the heap allocated data, and then create a new container
+// (e.g. using Box::from_raw) that points into the shared memory.
+//
+// * Arc<T> and Locked<T> want to perform atomic writes on data that needs to
+// be in the shared memory buffer (the reference count for Arc<T>, and the
+// SharedRwLock's AtomicRefCell for Locked<T>), so we add special modes to
+// those objects that skip the writes. For Arc<T>, that means never
+// dropping the object since we don't track the reference count. That's
+// fine, since we want to just drop the entire shared memory buffer at
+// shutdown. For Locked<T>, we just panic on attempting to take the lock
+// for writing. That's also fine, since we don't want devtools being able
+// to modify UA sheets.
+//
+// * For Atoms in Rust, static atoms are represented by an index into the
+// static atom table. Then if we need to Deref the Atom we look up the
+// table. We panic if any Atom we encounter in the UA style sheets is
+// not a static atom.
+//
+// * For each UA sheet, we create a new C++ StyleSheet object using the shared
+// memory clone of the sheet contents, and throw away the original heap
+// allocated one. (We could avoid creating a new C++ StyleSheet object
+// wrapping the shared contents, and update the original StyleSheet object's
+// contents, but it's doubtful that matters.)
+//
+// * When we initially map the shared memory in the parent process in
+// InitSharedSheetsInParent, we choose an address which is far away from the
+// current extent of the heap. Although not too far, since we don't want to
+// unnecessarily fragment the virtual address space.
+//
+// * In the child process, as early as possible (in
+// ContentChild::InitSharedUASheets), we try to map the shared memory at that
+// same address, then pass the shared memory buffer to
+// GlobalStyleSheetCache::SetSharedMemory. Since we map at the same
+// address, this means any internal pointers in the UA sheets back into the
+// shared memory buffer that were written by the parent process are valid in
+// the child process too.
+//
+// * In practice, mapping at the address we need in the child process this works
+// nearly all the time. If we fail to map at the address we need, the child
+// process falls back to parsing and allocating its own copy of the UA sheets.
+
+using namespace mozilla;
+using namespace css;
+
+#define PREF_LEGACY_STYLESHEET_CUSTOMIZATION \
+ "toolkit.legacyUserProfileCustomizations.stylesheets"
+
+NS_IMPL_ISUPPORTS(GlobalStyleSheetCache, nsIObserver, nsIMemoryReporter)
+
+nsresult GlobalStyleSheetCache::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "profile-before-change")) {
+ mUserContentSheet = nullptr;
+ mUserChromeSheet = nullptr;
+ } else if (!strcmp(aTopic, "profile-do-change")) {
+ InitFromProfile();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected observer topic.");
+ }
+ return NS_OK;
+}
+
+#define STYLE_SHEET(identifier_, url_, shared_) \
+ NotNull<StyleSheet*> GlobalStyleSheetCache::identifier_##Sheet() { \
+ if (!m##identifier_##Sheet) { \
+ m##identifier_##Sheet = LoadSheetURL(url_, eAgentSheetFeatures, eCrash); \
+ } \
+ return WrapNotNull(m##identifier_##Sheet); \
+ }
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+
+StyleSheet* GlobalStyleSheetCache::GetUserContentSheet() {
+ return mUserContentSheet;
+}
+
+StyleSheet* GlobalStyleSheetCache::GetUserChromeSheet() {
+ return mUserChromeSheet;
+}
+
+void GlobalStyleSheetCache::Shutdown() {
+ gCSSLoader = nullptr;
+ NS_WARNING_ASSERTION(!gStyleCache || !gUserContentSheetURL,
+ "Got the URL but never used?");
+ gStyleCache = nullptr;
+ gUserContentSheetURL = nullptr;
+ for (auto& r : URLExtraData::sShared) {
+ r = nullptr;
+ }
+ // We want to leak the shared memory forever, rather than cleaning up all
+ // potential DOM references and such that chrome code may have created.
+}
+
+void GlobalStyleSheetCache::SetUserContentCSSURL(nsIURI* aURI) {
+ MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
+ gUserContentSheetURL = aURI;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(LayoutStylesheetCacheMallocSizeOf)
+
+NS_IMETHODIMP
+GlobalStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/unshared", KIND_HEAP,
+ UNITS_BYTES,
+ SizeOfIncludingThis(LayoutStylesheetCacheMallocSizeOf),
+ "Memory used for built-in style sheets that are not "
+ "shared between processes.");
+
+ if (XRE_IsParentProcess()) {
+ MOZ_COLLECT_REPORT(
+ "explicit/layout/style-sheet-cache/shared", KIND_NONHEAP, UNITS_BYTES,
+ sSharedMemory ? sUsedSharedMemory : 0,
+ "Memory used for built-in style sheets that are shared to "
+ "child processes.");
+ }
+
+ return NS_OK;
+}
+
+size_t GlobalStyleSheetCache::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+#define MEASURE(s) n += s ? s->SizeOfIncludingThis(aMallocSizeOf) : 0;
+
+#define STYLE_SHEET(identifier_, url_, shared_) MEASURE(m##identifier_##Sheet);
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+
+ MEASURE(mUserChromeSheet);
+ MEASURE(mUserContentSheet);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - gCSSLoader
+
+ return n;
+}
+
+GlobalStyleSheetCache::GlobalStyleSheetCache() {
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ NS_ASSERTION(obsSvc, "No global observer service?");
+
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "profile-before-change", false);
+ obsSvc->AddObserver(this, "profile-do-change", false);
+ }
+
+ // Load user style sheets.
+ InitFromProfile();
+
+ if (XRE_IsParentProcess()) {
+ // We know we need xul.css for the UI, so load that now too:
+ XULSheet();
+ }
+
+ if (gUserContentSheetURL) {
+ MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
+ mUserContentSheet =
+ LoadSheet(gUserContentSheetURL, eUserSheetFeatures, eLogToConsole);
+ gUserContentSheetURL = nullptr;
+ }
+
+ // If we are the in the parent process, then we load all of the UA sheets that
+ // are shareable and store them into shared memory. In both the parent and
+ // the content process, we load these sheets out of shared memory.
+ //
+ // The shared memory buffer's format is a Header object, which contains
+ // internal pointers to each of the shared style sheets, followed by the style
+ // sheets themselves.
+ if (StaticPrefs::layout_css_shared_memory_ua_sheets_enabled()) {
+ if (XRE_IsParentProcess()) {
+ // Load the style sheets and store them in a new shared memory buffer.
+ InitSharedSheetsInParent();
+ } else if (sSharedMemory) {
+ // Use the shared memory handle that was given to us by a SetSharedMemory
+ // call under ContentChild::InitXPCOM.
+ MOZ_ASSERT(sSharedMemory->memory(),
+ "GlobalStyleSheetCache::SetSharedMemory should have mapped "
+ "the shared memory");
+ }
+ }
+
+ // If we get here and we don't have a shared memory handle, then it means
+ // either we failed to create the shared memory buffer in the parent process
+ // (unexpected), or we failed to map the shared memory buffer at the address
+ // we needed in the content process (might happen).
+ //
+ // If sSharedMemory is non-null, but it is not currently mapped, then it means
+ // we are in the parent process, and we failed to re-map the memory after
+ // freezing it. (We keep sSharedMemory around so that we can still share it
+ // to content processes.)
+ //
+ // In the parent process, this means we'll just leave our eagerly loaded
+ // non-shared sheets in the mFooSheet fields. In a content process, we'll
+ // lazily load our own copies of the sheets later.
+ if (sSharedMemory) {
+ if (auto* header = static_cast<Header*>(sSharedMemory->memory())) {
+ MOZ_RELEASE_ASSERT(header->mMagic == Header::kMagic);
+
+#define STYLE_SHEET(identifier_, url_, shared_) \
+ if (shared_) { \
+ LoadSheetFromSharedMemory(url_, &m##identifier_##Sheet, \
+ eAgentSheetFeatures, header, \
+ UserAgentStyleSheetID::identifier_); \
+ }
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+ }
+ }
+}
+
+void GlobalStyleSheetCache::LoadSheetFromSharedMemory(
+ const char* aURL, RefPtr<StyleSheet>* aSheet, SheetParsingMode aParsingMode,
+ Header* aHeader, UserAgentStyleSheetID aSheetID) {
+ auto i = size_t(aSheetID);
+
+ auto sheet =
+ MakeRefPtr<StyleSheet>(aParsingMode, CORS_NONE, dom::SRIMetadata());
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aURL));
+
+ sheet->SetPrincipal(nsContentUtils::GetSystemPrincipal());
+ sheet->SetURIs(uri, uri, uri);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ dom::ReferrerInfo::CreateForExternalCSSResources(sheet);
+ sheet->SetReferrerInfo(referrerInfo);
+ sheet->SetSharedContents(aHeader->mSheets[i]);
+ sheet->SetComplete();
+ URLExtraData::sShared[i] = sheet->URLData();
+
+ *aSheet = std::move(sheet);
+}
+
+void GlobalStyleSheetCache::InitSharedSheetsInParent() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_RELEASE_ASSERT(!sSharedMemory);
+
+ auto shm = MakeUnique<base::SharedMemory>();
+ if (NS_WARN_IF(!shm->CreateFreezeable(kSharedMemorySize))) {
+ return;
+ }
+
+ // We need to choose an address to map the shared memory in the parent process
+ // that we'll also be able to use in content processes. There's no way to
+ // pick an address that is guaranteed to be free in future content processes,
+ // so instead we pick an address that is some distance away from current heap
+ // allocations and hope that by the time the content process maps the shared
+ // memory, that address will be free.
+ //
+ // On 64 bit, we have a large amount of address space, so we pick an address
+ // half way through the next 8 GiB of free space, and this has a very good
+ // chance of succeeding. On 32 bit, address space is more constrained. We
+ // only have 3 GiB of space to work with, and we don't want to pick a location
+ // right in the middle, since that could cause future large allocations to
+ // fail. So we pick an address half way through the next 512 MiB of free
+ // space. Experimentally this seems to work 9 times out of 10; this is good
+ // enough, as it means only 1 in 10 content processes will have its own unique
+ // copies of the UA style sheets, and we're still getting a significant
+ // overall memory saving.
+ //
+ // In theory ASLR could reduce the likelihood of the mapping succeeding in
+ // content processes, due to our expectations of where the heap is being
+ // wrong, but in practice this isn't an issue.
+#ifdef HAVE_64BIT_BUILD
+ constexpr size_t kOffset = 0x200000000ULL; // 8 GiB
+#else
+ constexpr size_t kOffset = 0x20000000; // 512 MiB
+#endif
+
+ void* address = nullptr;
+ if (void* p = base::SharedMemory::FindFreeAddressSpace(2 * kOffset)) {
+ address = reinterpret_cast<void*>(uintptr_t(p) + kOffset);
+ }
+
+ if (!shm->Map(kSharedMemorySize, address)) {
+ // Failed to map at the address we computed for some reason. Fall back
+ // to just allocating at a location of the OS's choosing, and hope that
+ // it works in the content process.
+ if (NS_WARN_IF(!shm->Map(kSharedMemorySize))) {
+ return;
+ }
+ }
+ address = shm->memory();
+
+ auto* header = static_cast<Header*>(address);
+ header->mMagic = Header::kMagic;
+#ifdef DEBUG
+ for (const auto* ptr : header->mSheets) {
+ MOZ_RELEASE_ASSERT(!ptr, "expected shared memory to have been zeroed");
+ }
+#endif
+
+ UniquePtr<StyleSharedMemoryBuilder> builder(Servo_SharedMemoryBuilder_Create(
+ header->mBuffer, kSharedMemorySize - offsetof(Header, mBuffer)));
+
+ nsCString message;
+
+ // Copy each one into the shared memory, and record its pointer.
+ //
+ // Normally calling ToShared on UA sheets should not fail. It happens
+ // in practice in odd cases that seem like corrupted installations; see bug
+ // 1621773. On failure, return early and fall back to non-shared sheets.
+#define STYLE_SHEET(identifier_, url_, shared_) \
+ if (shared_) { \
+ StyleSheet* sheet = identifier_##Sheet(); \
+ size_t i = size_t(UserAgentStyleSheetID::identifier_); \
+ URLExtraData::sShared[i] = sheet->URLData(); \
+ header->mSheets[i] = sheet->ToShared(builder.get(), message); \
+ if (!header->mSheets[i]) { \
+ CrashReporter::AppendAppNotesToCrashReport("\n"_ns + message); \
+ return; \
+ } \
+ }
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+
+ // Finished writing into the shared memory. Freeze it, so that a process
+ // can't confuse other processes by changing the UA style sheet contents.
+ if (NS_WARN_IF(!shm->Freeze())) {
+ return;
+ }
+
+ // The Freeze() call unmaps the shared memory. Re-map it again as read only.
+ // If this fails, due to something else being mapped into the same place
+ // between the Freeze() and Map() call, we can just fall back to keeping our
+ // own copy of the UA style sheets in the parent, and still try sending the
+ // shared memory to the content processes.
+ shm->Map(kSharedMemorySize, address);
+
+ // Record how must of the shared memory we have used, for memory reporting
+ // later. We round up to the nearest page since the free space at the end
+ // of the page isn't really usable for anything else.
+ //
+ // TODO(heycam): This won't be true on Windows unless we allow creating the
+ // shared memory with SEC_RESERVE so that the pages are reserved but not
+ // committed.
+ size_t pageSize = ipc::SharedMemory::SystemPageSize();
+ sUsedSharedMemory =
+ (Servo_SharedMemoryBuilder_GetLength(builder.get()) + pageSize - 1) &
+ ~(pageSize - 1);
+
+ sSharedMemory = shm.release();
+}
+
+GlobalStyleSheetCache::~GlobalStyleSheetCache() {
+ UnregisterWeakMemoryReporter(this);
+}
+
+void GlobalStyleSheetCache::InitMemoryReporter() {
+ RegisterWeakMemoryReporter(this);
+}
+
+/* static */
+GlobalStyleSheetCache* GlobalStyleSheetCache::Singleton() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gStyleCache) {
+ gStyleCache = new GlobalStyleSheetCache;
+ gStyleCache->InitMemoryReporter();
+
+ // For each pref that controls a CSS feature that a UA style sheet depends
+ // on (such as a pref that enables a property that a UA style sheet uses),
+ // register DependentPrefChanged as a callback to ensure that the relevant
+ // style sheets will be re-parsed.
+ // Preferences::RegisterCallback(&DependentPrefChanged,
+ // "layout.css.example-pref.enabled");
+ }
+
+ return gStyleCache;
+}
+
+void GlobalStyleSheetCache::InitFromProfile() {
+ if (!Preferences::GetBool(PREF_LEGACY_STYLESHEET_CUSTOMIZATION)) {
+ return;
+ }
+
+ nsCOMPtr<nsIXULRuntime> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ if (appInfo) {
+ bool inSafeMode = false;
+ appInfo->GetInSafeMode(&inSafeMode);
+ if (inSafeMode) return;
+ }
+ nsCOMPtr<nsIFile> contentFile;
+ nsCOMPtr<nsIFile> chromeFile;
+
+ NS_GetSpecialDirectory(NS_APP_USER_CHROME_DIR, getter_AddRefs(contentFile));
+ if (!contentFile) {
+ // if we don't have a profile yet, that's OK!
+ return;
+ }
+
+ contentFile->Clone(getter_AddRefs(chromeFile));
+ if (!chromeFile) return;
+
+ contentFile->Append(u"userContent.css"_ns);
+ chromeFile->Append(u"userChrome.css"_ns);
+
+ mUserContentSheet = LoadSheetFile(contentFile, eUserSheetFeatures);
+ mUserChromeSheet = LoadSheetFile(chromeFile, eUserSheetFeatures);
+}
+
+RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheetURL(
+ const char* aURL, SheetParsingMode aParsingMode,
+ FailureAction aFailureAction) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aURL);
+ return LoadSheet(uri, aParsingMode, aFailureAction);
+}
+
+RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheetFile(
+ nsIFile* aFile, SheetParsingMode aParsingMode) {
+ bool exists = false;
+ aFile->Exists(&exists);
+ if (!exists) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewFileURI(getter_AddRefs(uri), aFile);
+ return LoadSheet(uri, aParsingMode, eLogToConsole);
+}
+
+static void ErrorLoadingSheet(nsIURI* aURI, const char* aMsg,
+ FailureAction aFailureAction) {
+ nsPrintfCString errorMessage("%s loading built-in stylesheet '%s'", aMsg,
+ aURI ? aURI->GetSpecOrDefault().get() : "");
+ if (aFailureAction == eLogToConsole) {
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(NS_ConvertUTF8toUTF16(errorMessage).get());
+ return;
+ }
+ }
+
+ MOZ_CRASH_UNSAFE(errorMessage.get());
+}
+
+RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheet(
+ nsIURI* aURI, SheetParsingMode aParsingMode, FailureAction aFailureAction) {
+ if (!aURI) {
+ ErrorLoadingSheet(aURI, "null URI", eCrash);
+ return nullptr;
+ }
+
+ if (!gCSSLoader) {
+ gCSSLoader = new Loader;
+ }
+
+ auto result = gCSSLoader->LoadSheetSync(aURI, aParsingMode,
+ css::Loader::UseSystemPrincipal::Yes);
+ if (MOZ_UNLIKELY(result.isErr())) {
+ ErrorLoadingSheet(
+ aURI,
+ nsPrintfCString("LoadSheetSync failed with error %" PRIx32,
+ static_cast<uint32_t>(result.unwrapErr()))
+ .get(),
+ aFailureAction);
+ }
+ return result.unwrapOr(nullptr);
+}
+
+/* static */ void GlobalStyleSheetCache::SetSharedMemory(
+ base::SharedMemoryHandle aHandle, uintptr_t aAddress) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(!gStyleCache, "Too late, GlobalStyleSheetCache already created!");
+ MOZ_ASSERT(!sSharedMemory, "Shouldn't call this more than once");
+
+ auto shm = MakeUnique<base::SharedMemory>();
+ if (!shm->SetHandle(std::move(aHandle), /* read_only */ true)) {
+ return;
+ }
+
+ if (shm->Map(kSharedMemorySize, reinterpret_cast<void*>(aAddress))) {
+ sSharedMemory = shm.release();
+ }
+}
+
+base::SharedMemoryHandle GlobalStyleSheetCache::CloneHandle() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (sSharedMemory) {
+ return sSharedMemory->CloneHandle();
+ }
+ return nullptr;
+}
+
+StaticRefPtr<GlobalStyleSheetCache> GlobalStyleSheetCache::gStyleCache;
+StaticRefPtr<css::Loader> GlobalStyleSheetCache::gCSSLoader;
+StaticRefPtr<nsIURI> GlobalStyleSheetCache::gUserContentSheetURL;
+
+StaticAutoPtr<base::SharedMemory> GlobalStyleSheetCache::sSharedMemory;
+size_t GlobalStyleSheetCache::sUsedSharedMemory;
+
+} // namespace mozilla
diff --git a/layout/style/GlobalStyleSheetCache.h b/layout/style/GlobalStyleSheetCache.h
new file mode 100644
index 0000000000..688729fede
--- /dev/null
+++ b/layout/style/GlobalStyleSheetCache.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GlobalStyleSheetCache_h__
+#define mozilla_GlobalStyleSheetCache_h__
+
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "base/shared_memory.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PreferenceSheet.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UserAgentStyleSheetID.h"
+#include "mozilla/css/Loader.h"
+
+class nsIFile;
+class nsIURI;
+
+namespace mozilla {
+class CSSStyleSheet;
+} // namespace mozilla
+
+namespace mozilla {
+namespace css {
+
+// Enum defining how error should be handled.
+enum FailureAction { eCrash = 0, eLogToConsole };
+
+} // namespace css
+
+class GlobalStyleSheetCache final : public nsIObserver,
+ public nsIMemoryReporter {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMEMORYREPORTER
+
+ static GlobalStyleSheetCache* Singleton();
+
+#define STYLE_SHEET(identifier_, url_, shared_) \
+ NotNull<StyleSheet*> identifier_##Sheet();
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+
+ StyleSheet* GetUserContentSheet();
+ StyleSheet* GetUserChromeSheet();
+
+ static void Shutdown();
+
+ static void SetUserContentCSSURL(nsIURI* aURI);
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ // Set the shared memory segment to load the shared UA sheets from.
+ // Called early on in a content process' life from
+ // ContentChild::InitSharedUASheets, before the GlobalStyleSheetCache
+ // singleton has been created.
+ static void SetSharedMemory(base::SharedMemoryHandle aHandle,
+ uintptr_t aAddress);
+
+ // Obtain a shared memory handle for the shared UA sheets to pass into a
+ // content process. Called by ContentParent::InitInternal shortly after
+ // a content process has been created.
+ base::SharedMemoryHandle CloneHandle();
+
+ // Returns the address of the shared memory segment that holds the shared UA
+ // sheets.
+ uintptr_t GetSharedMemoryAddress() {
+ return sSharedMemory ? uintptr_t(sSharedMemory->memory()) : 0;
+ }
+
+ // Size of the shared memory buffer we'll create to store the shared UA
+ // sheets. We choose a value that is big enough on both 64 bit and 32 bit.
+ //
+ // If this isn't big enough for the current contents of the shared UA
+ // sheets, we'll crash under InitSharedSheetsInParent.
+ static constexpr size_t kSharedMemorySize = 1024 * 450;
+
+ private:
+ // Shared memory header.
+ struct Header {
+ static constexpr uint32_t kMagic = 0x55415353;
+ uint32_t mMagic; // Must be set to kMagic.
+ const StyleLockedCssRules* mSheets[size_t(UserAgentStyleSheetID::Count)];
+ uint8_t mBuffer[1];
+ };
+
+ GlobalStyleSheetCache();
+ ~GlobalStyleSheetCache();
+
+ void InitFromProfile();
+ void InitSharedSheetsInParent();
+ void InitMemoryReporter();
+ RefPtr<StyleSheet> LoadSheetURL(const char* aURL,
+ css::SheetParsingMode aParsingMode,
+ css::FailureAction aFailureAction);
+ RefPtr<StyleSheet> LoadSheetFile(nsIFile* aFile,
+ css::SheetParsingMode aParsingMode);
+ RefPtr<StyleSheet> LoadSheet(nsIURI* aURI, css::SheetParsingMode aParsingMode,
+ css::FailureAction aFailureAction);
+ void LoadSheetFromSharedMemory(const char* aURL, RefPtr<StyleSheet>* aSheet,
+ css::SheetParsingMode, Header*,
+ UserAgentStyleSheetID);
+
+ static StaticRefPtr<GlobalStyleSheetCache> gStyleCache;
+ static StaticRefPtr<css::Loader> gCSSLoader;
+ static StaticRefPtr<nsIURI> gUserContentSheetURL;
+
+#define STYLE_SHEET(identifier_, url_, shared_) \
+ RefPtr<StyleSheet> m##identifier_##Sheet;
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+
+ RefPtr<StyleSheet> mUserChromeSheet;
+ RefPtr<StyleSheet> mUserContentSheet;
+
+ // Shared memory segment storing shared style sheets.
+ static StaticAutoPtr<base::SharedMemory> sSharedMemory;
+
+ // How much of the shared memory buffer we ended up using. Used for memory
+ // reporting in the parent process.
+ static size_t sUsedSharedMemory;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/GroupRule.cpp b/layout/style/GroupRule.cpp
new file mode 100644
index 0000000000..ff3cd11f8c
--- /dev/null
+++ b/layout/style/GroupRule.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * internal interface representing CSS style rules that contain other
+ * rules, such as @media rules
+ */
+
+#include "mozilla/css/GroupRule.h"
+
+#include "mozilla/dom/CSSRuleList.h"
+
+#include "nsPrintfCString.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla::css {
+
+GroupRule::GroupRule(StyleSheet* aSheet, Rule* aParentRule,
+ uint32_t aLineNumber, uint32_t aColumnNumber)
+ : Rule(aSheet, aParentRule, aLineNumber, aColumnNumber) {}
+
+GroupRule::~GroupRule() {
+ MOZ_ASSERT(!mSheet, "SetStyleSheet should have been called");
+ if (mRuleList) {
+ mRuleList->DropReferences();
+ }
+}
+
+NS_IMPL_ADDREF_INHERITED(GroupRule, Rule)
+NS_IMPL_RELEASE_INHERITED(GroupRule, Rule)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GroupRule)
+NS_INTERFACE_MAP_END_INHERITING(Rule)
+
+bool GroupRule::IsCCLeaf() const {
+ if (!Rule::IsCCLeaf()) {
+ return false;
+ }
+ return !mRuleList;
+}
+
+ServoCSSRuleList* GroupRule::CssRules() {
+ if (!mRuleList) {
+ // Lazily create the rule list since most style rules won't have child
+ // rules.
+ mRuleList =
+ new ServoCSSRuleList(GetOrCreateRawRules(), GetStyleSheet(), this);
+ }
+ return mRuleList;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(GroupRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(GroupRule, Rule)
+ if (tmp->mRuleList) {
+ // If tmp has a style sheet (which can happen if it gets unlinked
+ // earlier than its owning style sheet), then we need to null out the
+ // style sheet pointer on descendants now, before we clear mRuleList.
+ tmp->mRuleList->DropReferences();
+ tmp->mRuleList = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(GroupRule, Rule)
+ ImplCycleCollectionTraverse(cb, tmp->mRuleList, "mRuleList");
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#ifdef DEBUG
+void GroupRule::List(FILE* out, int32_t aIndent) const {
+ // TODO list something reasonable?
+}
+#endif
+
+/* virtual */
+void GroupRule::DropSheetReference() {
+ if (mRuleList) {
+ mRuleList->DropSheetReference();
+ }
+ Rule::DropSheetReference();
+}
+
+uint32_t GroupRule::InsertRule(const nsACString& aRule, uint32_t aIndex,
+ ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return 0;
+ }
+
+ StyleSheet* sheet = GetStyleSheet();
+ if (NS_WARN_IF(!sheet)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+
+ uint32_t count = StyleRuleCount();
+ if (aIndex > count) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "Can't insert rule at index %u because rule list length is %u", aIndex,
+ count));
+ return 0;
+ }
+
+ NS_ASSERTION(count <= INT32_MAX, "Too many style rules!");
+
+ nsresult rv = sheet->InsertRuleIntoGroup(aRule, this, aIndex);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return 0;
+ }
+ return aIndex;
+}
+
+void GroupRule::DeleteRule(uint32_t aIndex, ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ StyleSheet* sheet = GetStyleSheet();
+ if (NS_WARN_IF(!sheet)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ uint32_t count = StyleRuleCount();
+ if (aIndex >= count) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "Index %u is too large for list of length %u", aIndex, count));
+ return;
+ }
+
+ NS_ASSERTION(count <= INT32_MAX, "Too many style rules!");
+
+ nsresult rv = sheet->DeleteRuleFromGroup(this, aIndex);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+size_t GroupRule::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ // TODO how to implement?
+ return 0;
+}
+
+} // namespace mozilla::css
diff --git a/layout/style/GroupRule.h b/layout/style/GroupRule.h
new file mode 100644
index 0000000000..1584efd9b1
--- /dev/null
+++ b/layout/style/GroupRule.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * internal interface representing CSS style rules that contain other
+ * rules, such as @media rules
+ */
+
+#ifndef mozilla_css_GroupRule_h__
+#define mozilla_css_GroupRule_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ServoCSSRuleList.h"
+#include "mozilla/css/Rule.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+
+class ErrorResult;
+class StyleSheet;
+
+namespace dom {
+class CSSRuleList;
+} // namespace dom
+
+namespace css {
+
+// Inherits from Rule so it can be shared between MediaRule and DocumentRule
+class GroupRule : public Rule {
+ protected:
+ GroupRule(StyleSheet* aSheet, Rule* aParentRule, uint32_t aLineNumber,
+ uint32_t aColumnNumber);
+ virtual ~GroupRule();
+ virtual already_AddRefed<StyleLockedCssRules> GetOrCreateRawRules() = 0;
+
+ public:
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GroupRule, Rule)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ GroupRule(const GroupRule&) = delete;
+ bool IsCCLeaf() const override;
+
+ bool IsGroupRule() const final { return true; }
+
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ void DropSheetReference() override;
+ uint32_t StyleRuleCount() { return CssRules()->Length(); }
+ Rule* GetStyleRuleAt(int32_t aIndex) { return CssRules()->GetRule(aIndex); }
+
+ void DidSetRawAfterClone() {
+ if (mRuleList) {
+ mRuleList->SetRawAfterClone(GetOrCreateRawRules());
+ }
+ }
+
+ /*
+ * The next method should never be called unless you have first called
+ * WillDirty() on the parent stylesheet.
+ */
+ nsresult DeleteStyleRuleAt(uint32_t aIndex) {
+ return CssRules()->DeleteRule(aIndex);
+ }
+
+ // non-virtual -- it is only called by subclasses
+ size_t SizeOfExcludingThis(MallocSizeOf) const;
+ size_t SizeOfIncludingThis(MallocSizeOf) const override = 0;
+
+ // WebIDL API
+ ServoCSSRuleList* CssRules();
+ uint32_t InsertRule(const nsACString& aRule, uint32_t aIndex,
+ ErrorResult& aRv);
+ void DeleteRule(uint32_t aIndex, ErrorResult& aRv);
+
+ private:
+ RefPtr<ServoCSSRuleList> mRuleList;
+};
+
+// Implementation of WebIDL CSSConditionRule.
+class ConditionRule : public GroupRule {
+ protected:
+ using GroupRule::GroupRule;
+
+ public:
+ virtual void GetConditionText(nsACString& aConditionText) = 0;
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_GroupRule_h__ */
diff --git a/layout/style/ImageDocument.css b/layout/style/ImageDocument.css
new file mode 100644
index 0000000000..5cd22c4b5c
--- /dev/null
+++ b/layout/style/ImageDocument.css
@@ -0,0 +1,43 @@
+/* -*- 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 CSS stylesheet defines the rules to be applied to any ImageDocuments,
+ * including those in frames.
+*/
+
+body {
+ /* To give the image access to our document's full viewport, we need to
+ override the margin that the html.css UA stylesheet would otherwise apply
+ to our body. */
+ margin: 0;
+}
+
+@media not print {
+ .fullZoomOut {
+ cursor: zoom-out;
+ }
+
+ .fullZoomIn {
+ cursor: zoom-in;
+ }
+
+ .shrinkToFit {
+ cursor: zoom-in;
+ }
+
+ .overflowingVertical, .overflowingHorizontalOnly {
+ cursor: zoom-out;
+ }
+}
+
+.isInObjectOrEmbed {
+ width: 100%;
+ height: 100vh;
+}
+
+img {
+ display: block;
+}
diff --git a/layout/style/ImageLoader.cpp b/layout/style/ImageLoader.cpp
new file mode 100644
index 0000000000..2ae1822b02
--- /dev/null
+++ b/layout/style/ImageLoader.cpp
@@ -0,0 +1,834 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 that handles style system image loads (other image loads are handled
+ * by the nodes in the content tree).
+ */
+
+#include "mozilla/css/ImageLoader.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/LargestContentfulPaint.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "nsContentUtils.h"
+#include "nsIReflowCallback.h"
+#include "nsLayoutUtils.h"
+#include "nsError.h"
+#include "nsCanvasFrame.h"
+#include "nsDisplayList.h"
+#include "nsIFrameInlines.h"
+#include "imgIContainer.h"
+#include "imgINotificationObserver.h"
+#include "Image.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/layers/WebRenderUserData.h"
+#include "nsTHashSet.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla::css {
+
+// This is a singleton observer which looks in the `GlobalRequestTable` to look
+// at which loaders to notify.
+struct GlobalImageObserver final : public imgINotificationObserver {
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ GlobalImageObserver() = default;
+
+ private:
+ virtual ~GlobalImageObserver() = default;
+};
+
+NS_IMPL_ADDREF(GlobalImageObserver)
+NS_IMPL_RELEASE(GlobalImageObserver)
+
+NS_INTERFACE_MAP_BEGIN(GlobalImageObserver)
+ NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
+NS_INTERFACE_MAP_END
+
+// Data associated with every started load.
+struct ImageTableEntry {
+ // Set of all ImageLoaders that have registered this URL and care for updates
+ // for it.
+ nsTHashSet<ImageLoader*> mImageLoaders;
+
+ // The amount of style values that are sharing this image.
+ uint32_t mSharedCount = 1;
+};
+
+using GlobalRequestTable =
+ nsClassHashtable<nsRefPtrHashKey<imgIRequest>, ImageTableEntry>;
+
+// A table of all loads, keyed by their id mapping them to the set of
+// ImageLoaders they have been registered in, and recording their "canonical"
+// image request.
+//
+// We use the load id as the key since we can only access sImages on the
+// main thread, but LoadData objects might be destroyed from other threads,
+// and we don't want to leave dangling pointers around.
+static StaticAutoPtr<GlobalRequestTable> sImages;
+static StaticRefPtr<GlobalImageObserver> sImageObserver;
+
+/* static */
+void ImageLoader::Init() {
+ sImages = new GlobalRequestTable();
+ sImageObserver = new GlobalImageObserver();
+}
+
+/* static */
+void ImageLoader::Shutdown() {
+ for (const auto& entry : *sImages) {
+ imgIRequest* imgRequest = entry.GetKey();
+ // All the images we put in sImages are imgRequestProxy, see LoadImage, but
+ // it's non-trivial to make the hash table to use that without changing a
+ // lot of other code.
+ auto* req = static_cast<imgRequestProxy*>(imgRequest);
+ req->SetCancelable(true);
+ req->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+
+ sImages = nullptr;
+ sImageObserver = nullptr;
+}
+
+void ImageLoader::DropDocumentReference() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // It's okay if GetPresContext returns null here (due to the presshell pointer
+ // on the document being null) as that means the presshell has already
+ // been destroyed, and it also calls ClearFrames when it is destroyed.
+ ClearFrames(GetPresContext());
+
+ mDocument = nullptr;
+}
+
+// Arrays of requests and frames are sorted by their pointer address,
+// for faster lookup.
+template <typename Elem, typename Item,
+ typename Comparator = nsDefaultComparator<Elem, Item>>
+static size_t GetMaybeSortedIndex(const nsTArray<Elem>& aArray,
+ const Item& aItem, bool* aFound,
+ Comparator aComparator = Comparator()) {
+ size_t index = aArray.IndexOfFirstElementGt(aItem, aComparator);
+ *aFound = index > 0 && aComparator.Equals(aItem, aArray.ElementAt(index - 1));
+ return index;
+}
+
+// Returns true if an async decode is triggered for aRequest, and thus we will
+// get an OnFrameComplete callback for this request eventually.
+static bool TriggerAsyncDecodeAtIntrinsicSize(imgIRequest* aRequest) {
+ uint32_t status = 0;
+ // Don't block onload if we've already got a frame complete status
+ // (since in that case the image is already loaded), or if we get an
+ // error status (since then we know the image won't ever load).
+ if (NS_SUCCEEDED(aRequest->GetImageStatus(&status))) {
+ if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
+ // Already decoded, no need to do it again.
+ return false;
+ }
+ if (status & imgIRequest::STATUS_ERROR) {
+ // Already errored, this would be useless.
+ return false;
+ }
+ }
+
+ // We want to request decode in such a way that avoids triggering sync decode.
+ // First, we attempt to convert the aRequest into a imgIContainer. If that
+ // succeeds, then aRequest has an image and we can request decoding for size
+ // at zero size, the size will be ignored because we don't pass the
+ // FLAG_HIGH_QUALITY_SCALING flag and an async decode (because we didn't pass
+ // any sync decoding flags) at the intrinsic size will be requested. If the
+ // conversion to imgIContainer is unsuccessful, then that means aRequest
+ // doesn't have an image yet, which means we can safely call StartDecoding()
+ // on it without triggering any synchronous work.
+ nsCOMPtr<imgIContainer> imgContainer;
+ aRequest->GetImage(getter_AddRefs(imgContainer));
+ if (imgContainer) {
+ imgContainer->RequestDecodeForSize(gfx::IntSize(0, 0),
+ imgIContainer::DECODE_FLAGS_DEFAULT);
+ } else {
+ // It's safe to call StartDecoding directly, since it can't
+ // trigger synchronous decode without an image. Flags are ignored.
+ aRequest->StartDecoding(imgIContainer::FLAG_NONE);
+ }
+ return true;
+}
+
+void ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest,
+ nsIFrame* aFrame, Flags aFlags) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!(aFlags & Flags::IsBlockingLoadEvent),
+ "Shouldn't be used in the public API");
+
+ {
+ nsCOMPtr<imgINotificationObserver> observer;
+ aRequest->GetNotificationObserver(getter_AddRefs(observer));
+ if (!observer) {
+ // The request has already been canceled, so ignore it. This is ok because
+ // we're not going to get any more notifications from a canceled request.
+ return;
+ }
+ MOZ_ASSERT(observer == sImageObserver);
+ }
+
+ auto* const frameSet =
+ mRequestToFrameMap
+ .LookupOrInsertWith(
+ aRequest,
+ [&] {
+ mDocument->ImageTracker()->Add(aRequest);
+
+ if (auto entry = sImages->Lookup(aRequest)) {
+ DebugOnly<bool> inserted =
+ entry.Data()->mImageLoaders.EnsureInserted(this);
+ MOZ_ASSERT(inserted);
+ } else {
+ MOZ_ASSERT_UNREACHABLE(
+ "Shouldn't be associating images not in sImages");
+ }
+
+ if (nsPresContext* presContext = GetPresContext()) {
+ nsLayoutUtils::RegisterImageRequestIfAnimated(
+ presContext, aRequest, nullptr);
+ }
+ return MakeUnique<FrameSet>();
+ })
+ .get();
+
+ auto* const requestSet =
+ mFrameToRequestMap
+ .LookupOrInsertWith(aFrame,
+ [=]() {
+ aFrame->SetHasImageRequest(true);
+ return MakeUnique<RequestSet>();
+ })
+ .get();
+
+ // Add frame to the frameSet, and handle any special processing the
+ // frame might require.
+ FrameWithFlags fwf(aFrame);
+ FrameWithFlags* fwfToModify = &fwf;
+
+ // See if the frameSet already has this frame.
+ bool found;
+ uint32_t i =
+ GetMaybeSortedIndex(*frameSet, fwf, &found, FrameOnlyComparator());
+ if (found) {
+ // We're already tracking this frame, so prepare to modify the
+ // existing FrameWithFlags object.
+ fwfToModify = &frameSet->ElementAt(i - 1);
+ }
+
+ // Check if the frame requires special processing.
+ if (aFlags & Flags::RequiresReflowOnSizeAvailable) {
+ MOZ_ASSERT(!(aFlags &
+ Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking),
+ "These two are exclusive");
+ fwfToModify->mFlags |= Flags::RequiresReflowOnSizeAvailable;
+ }
+
+ if (aFlags & Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) {
+ fwfToModify->mFlags |=
+ Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking;
+
+ // If we weren't already blocking onload, do that now.
+ if (!(fwfToModify->mFlags & Flags::IsBlockingLoadEvent)) {
+ if (TriggerAsyncDecodeAtIntrinsicSize(aRequest)) {
+ // If there's no error, and the image has not loaded yet, so we can
+ // block onload.
+ fwfToModify->mFlags |= Flags::IsBlockingLoadEvent;
+
+ // Block document onload until we either remove the frame in
+ // RemoveRequestToFrameMapping or onLoadComplete, or complete a reflow.
+ mDocument->BlockOnload();
+ }
+ }
+ }
+
+ // Do some sanity checking to ensure that we only add to one mapping
+ // iff we also add to the other mapping.
+ DebugOnly<bool> didAddToFrameSet(false);
+ DebugOnly<bool> didAddToRequestSet(false);
+
+ // If we weren't already tracking this frame, add it to the frameSet.
+ if (!found) {
+ frameSet->InsertElementAt(i, fwf);
+ didAddToFrameSet = true;
+ }
+
+ // Add request to the request set if it wasn't already there.
+ i = GetMaybeSortedIndex(*requestSet, aRequest, &found);
+ if (!found) {
+ requestSet->InsertElementAt(i, aRequest);
+ didAddToRequestSet = true;
+ }
+
+ MOZ_ASSERT(didAddToFrameSet == didAddToRequestSet,
+ "We should only add to one map iff we also add to the other map.");
+}
+
+void ImageLoader::RemoveRequestToFrameMapping(imgIRequest* aRequest,
+ nsIFrame* aFrame) {
+#ifdef DEBUG
+ {
+ nsCOMPtr<imgINotificationObserver> observer;
+ aRequest->GetNotificationObserver(getter_AddRefs(observer));
+ MOZ_ASSERT(!observer || observer == sImageObserver);
+ }
+#endif
+
+ if (auto entry = mRequestToFrameMap.Lookup(aRequest)) {
+ const auto& frameSet = entry.Data();
+ MOZ_ASSERT(frameSet, "This should never be null");
+
+ // Before we remove aFrame from the frameSet, unblock onload if needed.
+ bool found;
+ uint32_t i = GetMaybeSortedIndex(*frameSet, FrameWithFlags(aFrame), &found,
+ FrameOnlyComparator());
+ if (found) {
+ UnblockOnloadIfNeeded(frameSet->ElementAt(i - 1));
+ frameSet->RemoveElementAtUnsafe(i - 1);
+ }
+
+ if (frameSet->IsEmpty()) {
+ DeregisterImageRequest(aRequest, GetPresContext());
+ entry.Remove();
+ }
+ }
+}
+
+void ImageLoader::DeregisterImageRequest(imgIRequest* aRequest,
+ nsPresContext* aPresContext) {
+ mDocument->ImageTracker()->Remove(aRequest);
+
+ if (auto entry = sImages->Lookup(aRequest)) {
+ entry.Data()->mImageLoaders.EnsureRemoved(this);
+ }
+
+ if (aPresContext) {
+ nsLayoutUtils::DeregisterImageRequest(aPresContext, aRequest, nullptr);
+ }
+}
+
+void ImageLoader::RemoveFrameToRequestMapping(imgIRequest* aRequest,
+ nsIFrame* aFrame) {
+ if (auto entry = mFrameToRequestMap.Lookup(aFrame)) {
+ const auto& requestSet = entry.Data();
+ MOZ_ASSERT(requestSet, "This should never be null");
+ requestSet->RemoveElementSorted(aRequest);
+ if (requestSet->IsEmpty()) {
+ aFrame->SetHasImageRequest(false);
+ entry.Remove();
+ }
+ }
+}
+
+void ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest,
+ nsIFrame* aFrame) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?");
+
+ RemoveRequestToFrameMapping(aRequest, aFrame);
+ RemoveFrameToRequestMapping(aRequest, aFrame);
+}
+
+void ImageLoader::DropRequestsForFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?");
+
+ UniquePtr<RequestSet> requestSet;
+ mFrameToRequestMap.Remove(aFrame, &requestSet);
+ aFrame->SetHasImageRequest(false);
+ if (MOZ_UNLIKELY(!requestSet)) {
+ MOZ_ASSERT_UNREACHABLE("HasImageRequest was lying");
+ return;
+ }
+ for (imgIRequest* request : *requestSet) {
+ RemoveRequestToFrameMapping(request, aFrame);
+ }
+}
+
+void ImageLoader::SetAnimationMode(uint16_t aMode) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode ||
+ aMode == imgIContainer::kDontAnimMode ||
+ aMode == imgIContainer::kLoopOnceAnimMode,
+ "Wrong Animation Mode is being set!");
+
+ for (nsISupports* key : mRequestToFrameMap.Keys()) {
+ auto* request = static_cast<imgIRequest*>(key);
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<imgIRequest> debugRequest = request;
+ NS_ASSERTION(debugRequest == request, "This is bad");
+ }
+#endif
+
+ nsCOMPtr<imgIContainer> container;
+ request->GetImage(getter_AddRefs(container));
+ if (!container) {
+ continue;
+ }
+
+ // This can fail if the image is in error, and we don't care.
+ container->SetAnimationMode(aMode);
+ }
+}
+
+void ImageLoader::ClearFrames(nsPresContext* aPresContext) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (const auto& key : mRequestToFrameMap.Keys()) {
+ auto* request = static_cast<imgIRequest*>(key);
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<imgIRequest> debugRequest = request;
+ NS_ASSERTION(debugRequest == request, "This is bad");
+ }
+#endif
+
+ DeregisterImageRequest(request, aPresContext);
+ }
+
+ mRequestToFrameMap.Clear();
+ mFrameToRequestMap.Clear();
+}
+
+static CORSMode EffectiveCorsMode(nsIURI* aURI,
+ const StyleComputedImageUrl& aImage) {
+ MOZ_ASSERT(aURI);
+ StyleCorsMode mode = aImage.CorsMode();
+ if (mode == StyleCorsMode::None) {
+ return CORSMode::CORS_NONE;
+ }
+ MOZ_ASSERT(mode == StyleCorsMode::Anonymous);
+ if (aURI->SchemeIs("resource")) {
+ return CORSMode::CORS_NONE;
+ }
+ return CORSMode::CORS_ANONYMOUS;
+}
+
+/* static */
+already_AddRefed<imgRequestProxy> ImageLoader::LoadImage(
+ const StyleComputedImageUrl& aImage, Document& aDocument) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsIURI* uri = aImage.GetURI();
+ if (!uri) {
+ return nullptr;
+ }
+
+ if (aImage.HasRef()) {
+ bool isEqualExceptRef = false;
+ nsIURI* docURI = aDocument.GetDocumentURI();
+ if (NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &isEqualExceptRef)) &&
+ isEqualExceptRef) {
+ // Prevent loading an internal resource.
+ return nullptr;
+ }
+ }
+
+ int32_t loadFlags =
+ nsIRequest::LOAD_NORMAL |
+ nsContentUtils::CORSModeToLoadImageFlags(EffectiveCorsMode(uri, aImage));
+
+ const URLExtraData& data = aImage.ExtraData();
+
+ RefPtr<imgRequestProxy> request;
+ nsresult rv = nsContentUtils::LoadImage(
+ uri, &aDocument, &aDocument, data.Principal(), 0, data.ReferrerInfo(),
+ sImageObserver, loadFlags, u"css"_ns, getter_AddRefs(request));
+
+ if (NS_FAILED(rv) || !request) {
+ return nullptr;
+ }
+
+ // This image could be shared across documents, so its load cannot be
+ // canceled, see bug 1800979.
+ request->SetCancelable(false);
+ sImages->GetOrInsertNew(request);
+ return request.forget();
+}
+
+void ImageLoader::UnloadImage(imgRequestProxy* aImage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aImage);
+
+ if (MOZ_UNLIKELY(!sImages)) {
+ return; // Shutdown() takes care of it.
+ }
+
+ auto lookup = sImages->Lookup(aImage);
+ MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
+ if (MOZ_UNLIKELY(!lookup)) {
+ return;
+ }
+
+ if (MOZ_UNLIKELY(--lookup.Data()->mSharedCount)) {
+ // Someone else still cares about this image.
+ return;
+ }
+
+ // Now we want to really cancel the request.
+ aImage->SetCancelable(true);
+ aImage->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ MOZ_DIAGNOSTIC_ASSERT(lookup.Data()->mImageLoaders.IsEmpty(),
+ "Shouldn't be keeping references to any loader "
+ "by now");
+ lookup.Remove();
+}
+
+void ImageLoader::NoteSharedLoad(imgRequestProxy* aImage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aImage);
+
+ auto lookup = sImages->Lookup(aImage);
+ MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
+ if (MOZ_UNLIKELY(!lookup)) {
+ return;
+ }
+
+ lookup.Data()->mSharedCount++;
+}
+
+nsPresContext* ImageLoader::GetPresContext() {
+ if (!mDocument) {
+ return nullptr;
+ }
+
+ return mDocument->GetPresContext();
+}
+
+static bool IsRenderNoImages(uint32_t aDisplayItemKey) {
+ DisplayItemType type = GetDisplayItemTypeFromKey(aDisplayItemKey);
+ uint8_t flags = GetDisplayItemFlagsForType(type);
+ return flags & TYPE_RENDERS_NO_IMAGES;
+}
+
+static void InvalidateImages(nsIFrame* aFrame, imgIRequest* aRequest,
+ bool aForcePaint) {
+ if (!aFrame->StyleVisibility()->IsVisible()) {
+ return;
+ }
+
+ if (aFrame->IsTablePart()) {
+ // Tables don't necessarily build border/background display items
+ // for the individual table part frames, so IterateRetainedDataFor
+ // might not find the right display item.
+ return aFrame->InvalidateFrame();
+ }
+
+ if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) {
+ if (auto* canvas = aFrame->PresShell()->GetCanvasFrame()) {
+ // Try to invalidate the canvas too, in the probable case the background
+ // was propagated to it.
+ InvalidateImages(canvas, aRequest, aForcePaint);
+ }
+ }
+
+ bool invalidateFrame = aForcePaint;
+
+ if (auto userDataTable =
+ aFrame->GetProperty(layers::WebRenderUserDataProperty::Key())) {
+ for (RefPtr<layers::WebRenderUserData> data : userDataTable->Values()) {
+ switch (data->GetType()) {
+ case layers::WebRenderUserData::UserDataType::eFallback:
+ if (!IsRenderNoImages(data->GetDisplayItemKey())) {
+ static_cast<layers::WebRenderFallbackData*>(data.get())
+ ->SetInvalid(true);
+ }
+ // XXX: handle Blob data
+ invalidateFrame = true;
+ break;
+ case layers::WebRenderUserData::UserDataType::eMask:
+ static_cast<layers::WebRenderMaskData*>(data.get())->Invalidate();
+ invalidateFrame = true;
+ break;
+ case layers::WebRenderUserData::UserDataType::eImageProvider:
+ if (static_cast<layers::WebRenderImageProviderData*>(data.get())
+ ->Invalidate(aRequest->GetProviderId())) {
+ break;
+ }
+ [[fallthrough]];
+ default:
+ invalidateFrame = true;
+ break;
+ }
+ }
+ }
+
+ // Update ancestor rendering observers (-moz-element etc)
+ //
+ // NOTE: We need to do this even if invalidateFrame is false, see bug 1114526.
+ {
+ nsIFrame* f = aFrame;
+ while (f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(f);
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f);
+ }
+ }
+
+ if (invalidateFrame) {
+ aFrame->SchedulePaint();
+ }
+}
+
+void ImageLoader::UnblockOnloadIfNeeded(FrameWithFlags& aFwf) {
+ if (aFwf.mFlags & Flags::IsBlockingLoadEvent) {
+ mDocument->UnblockOnload(false);
+ aFwf.mFlags &= ~Flags::IsBlockingLoadEvent;
+ }
+}
+
+void ImageLoader::UnblockOnloadIfNeeded(nsIFrame* aFrame,
+ imgIRequest* aRequest) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aRequest);
+
+ FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
+ if (!frameSet) {
+ return;
+ }
+
+ size_t i =
+ frameSet->BinaryIndexOf(FrameWithFlags(aFrame), FrameOnlyComparator());
+ if (i != FrameSet::NoIndex) {
+ UnblockOnloadIfNeeded(frameSet->ElementAt(i));
+ }
+}
+
+// This callback is used to unblock document onload after a reflow
+// triggered from an image load.
+struct ImageLoader::ImageReflowCallback final : public nsIReflowCallback {
+ RefPtr<ImageLoader> mLoader;
+ WeakFrame mFrame;
+ nsCOMPtr<imgIRequest> const mRequest;
+
+ ImageReflowCallback(ImageLoader* aLoader, nsIFrame* aFrame,
+ imgIRequest* aRequest)
+ : mLoader(aLoader), mFrame(aFrame), mRequest(aRequest) {}
+
+ bool ReflowFinished() override;
+ void ReflowCallbackCanceled() override;
+};
+
+bool ImageLoader::ImageReflowCallback::ReflowFinished() {
+ // Check that the frame is still valid. If it isn't, then onload was
+ // unblocked when the frame was removed from the FrameSet in
+ // RemoveRequestToFrameMapping.
+ if (mFrame.IsAlive()) {
+ mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
+ }
+
+ // Get rid of this callback object.
+ delete this;
+
+ // We don't need to trigger layout.
+ return false;
+}
+
+void ImageLoader::ImageReflowCallback::ReflowCallbackCanceled() {
+ // Check that the frame is still valid. If it isn't, then onload was
+ // unblocked when the frame was removed from the FrameSet in
+ // RemoveRequestToFrameMapping.
+ if (mFrame.IsAlive()) {
+ mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
+ }
+
+ // Get rid of this callback object.
+ delete this;
+}
+
+void GlobalImageObserver::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ auto entry = sImages->Lookup(aRequest);
+ MOZ_DIAGNOSTIC_ASSERT(entry);
+ if (MOZ_UNLIKELY(!entry)) {
+ return;
+ }
+
+ const auto loadersToNotify =
+ ToTArray<nsTArray<RefPtr<ImageLoader>>>(entry.Data()->mImageLoaders);
+ for (const auto& loader : loadersToNotify) {
+ loader->Notify(aRequest, aType, aData);
+ }
+}
+
+void ImageLoader::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ nsCString uriString;
+ if (profiler_is_active()) {
+ nsCOMPtr<nsIURI> uri;
+ aRequest->GetFinalURI(getter_AddRefs(uri));
+ if (uri) {
+ uri->GetSpec(uriString);
+ }
+ }
+
+ AUTO_PROFILER_LABEL_DYNAMIC_CSTR("ImageLoader::Notify", OTHER,
+ uriString.get());
+
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ return OnSizeAvailable(aRequest, image);
+ }
+
+ if (aType == imgINotificationObserver::IS_ANIMATED) {
+ return OnImageIsAnimated(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+ return OnFrameComplete(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_UPDATE) {
+ return OnFrameUpdate(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ if (image && mDocument) {
+ image->PropagateUseCounters(mDocument);
+ }
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ return OnLoadComplete(aRequest);
+ }
+}
+
+void ImageLoader::OnSizeAvailable(imgIRequest* aRequest,
+ imgIContainer* aImage) {
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return;
+ }
+
+ aImage->SetAnimationMode(presContext->ImageAnimationMode());
+
+ FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
+ if (!frameSet) {
+ return;
+ }
+
+ for (const FrameWithFlags& fwf : *frameSet) {
+ if (fwf.mFlags & Flags::RequiresReflowOnSizeAvailable) {
+ fwf.mFrame->PresShell()->FrameNeedsReflow(
+ fwf.mFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+}
+
+void ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) {
+ if (!mDocument) {
+ return;
+ }
+
+ FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
+ if (!frameSet) {
+ return;
+ }
+
+ // Register with the refresh driver now that we are aware that
+ // we are animated.
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ nsLayoutUtils::RegisterImageRequest(presContext, aRequest, nullptr);
+ }
+}
+
+void ImageLoader::OnFrameComplete(imgIRequest* aRequest) {
+ ImageFrameChanged(aRequest, /* aFirstFrame = */ true);
+}
+
+void ImageLoader::OnFrameUpdate(imgIRequest* aRequest) {
+ ImageFrameChanged(aRequest, /* aFirstFrame = */ false);
+}
+
+void ImageLoader::ImageFrameChanged(imgIRequest* aRequest, bool aFirstFrame) {
+ if (!mDocument) {
+ return;
+ }
+
+ FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
+ if (!frameSet) {
+ return;
+ }
+
+ for (FrameWithFlags& fwf : *frameSet) {
+ // Since we just finished decoding a frame, we always want to paint, in
+ // case we're now able to paint an image that we couldn't paint before
+ // (and hence that we don't have retained data for).
+ const bool forceRepaint = aFirstFrame;
+ InvalidateImages(fwf.mFrame, aRequest, forceRepaint);
+ if (!aFirstFrame) {
+ // We don't reflow / try to unblock onload for subsequent frame updates.
+ continue;
+ }
+ if (fwf.mFlags &
+ Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) {
+ // Tell the container of the frame to reflow because the image request
+ // has finished decoding its first frame.
+ // FIXME(emilio): Why requesting reflow on the _parent_?
+ nsIFrame* parent = fwf.mFrame->GetInFlowParent();
+ parent->PresShell()->FrameNeedsReflow(
+ parent, IntrinsicDirty::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+ // If we need to also potentially unblock onload, do it once reflow is
+ // done, with a reflow callback.
+ if (fwf.mFlags & Flags::IsBlockingLoadEvent) {
+ auto* unblocker = new ImageReflowCallback(this, fwf.mFrame, aRequest);
+ parent->PresShell()->PostReflowCallback(unblocker);
+ }
+ }
+ }
+}
+
+void ImageLoader::OnLoadComplete(imgIRequest* aRequest) {
+ if (!mDocument) {
+ return;
+ }
+
+ uint32_t status = 0;
+ if (NS_FAILED(aRequest->GetImageStatus(&status))) {
+ return;
+ }
+
+ FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
+ if (!frameSet) {
+ return;
+ }
+
+ for (FrameWithFlags& fwf : *frameSet) {
+ if (status & imgIRequest::STATUS_ERROR) {
+ // Check if aRequest has an error state. If it does, we need to unblock
+ // Document onload for all the frames associated with this request that
+ // have blocked onload. This is what happens in a CORS mode violation, and
+ // may happen during other network events.
+ UnblockOnloadIfNeeded(fwf);
+ }
+ nsIFrame* frame = fwf.mFrame;
+ if (frame->StyleVisibility()->IsVisible()) {
+ frame->SchedulePaint();
+ }
+
+ if (StaticPrefs::dom_enable_largest_contentful_paint()) {
+ LargestContentfulPaint::MaybeProcessImageForElementTiming(
+ static_cast<imgRequestProxy*>(aRequest),
+ frame->GetContent()->AsElement());
+ }
+ }
+}
+} // namespace mozilla::css
diff --git a/layout/style/ImageLoader.h b/layout/style/ImageLoader.h
new file mode 100644
index 0000000000..7b8b5338c4
--- /dev/null
+++ b/layout/style/ImageLoader.h
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 that handles style system image loads (other image loads are handled
+// by the nodes in the content tree).
+
+#ifndef mozilla_css_ImageLoader_h___
+#define mozilla_css_ImageLoader_h___
+
+#include "mozilla/CORSMode.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsRect.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+class nsIFrame;
+class imgIContainer;
+class imgIRequest;
+class imgRequestProxy;
+class nsPresContext;
+class nsIURI;
+class nsIPrincipal;
+class nsIRequest;
+
+namespace mozilla {
+struct MediaFeatureChange;
+struct StyleComputedUrl;
+namespace dom {
+class Document;
+}
+
+namespace css {
+
+/**
+ * NOTE: All methods must be called from the main thread unless otherwise
+ * specified.
+ */
+class ImageLoader final {
+ public:
+ static void Init();
+ static void Shutdown();
+
+ // We also associate flags alongside frames in the request-to-frames hashmap.
+ // These are used for special handling of events for requests.
+ enum class Flags : uint32_t {
+ // Used for bullets.
+ RequiresReflowOnSizeAvailable = 1u << 0,
+
+ // Used for shapes.
+ RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking = 1u << 1,
+
+ // Internal flag, shouldn't be used by callers.
+ IsBlockingLoadEvent = 1u << 2,
+ };
+
+ explicit ImageLoader(dom::Document* aDocument) : mDocument(aDocument) {
+ MOZ_ASSERT(mDocument);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(ImageLoader)
+
+ void DropDocumentReference();
+
+ void AssociateRequestToFrame(imgIRequest*, nsIFrame*, Flags = Flags(0));
+ void DisassociateRequestFromFrame(imgIRequest*, nsIFrame*);
+ void DropRequestsForFrame(nsIFrame*);
+
+ void SetAnimationMode(uint16_t aMode);
+
+ // The prescontext for this ImageLoader's document. We need it to be passed
+ // in because this can be called during presentation destruction after the
+ // presshell pointer on the document has been cleared.
+ void ClearFrames(nsPresContext* aPresContext);
+
+ // Triggers an image load.
+ static already_AddRefed<imgRequestProxy> LoadImage(const StyleComputedUrl&,
+ dom::Document&);
+
+ // Usually, only one style value owns a given proxy. However, we have a hack
+ // to share image proxies in chrome documents under some circumstances. We
+ // need to keep track of this so that we don't stop tracking images too early.
+ //
+ // In practice it shouldn't matter as these chrome images are mostly static,
+ // but it is always good to keep sanity.
+ static void NoteSharedLoad(imgRequestProxy*);
+
+ // Undoes what `LoadImage` does.
+ static void UnloadImage(imgRequestProxy*);
+
+ // This is called whenever an image we care about notifies the
+ // GlobalImageObserver.
+ void Notify(imgIRequest*, int32_t aType, const nsIntRect* aData);
+
+ private:
+ // Called when we stop caring about a given request.
+ void DeregisterImageRequest(imgIRequest*, nsPresContext*);
+ struct ImageReflowCallback;
+
+ ~ImageLoader() = default;
+
+ // We need to be able to look up the frames associated with a request (for
+ // delivering notifications) and the requests associated with a frame (when
+ // the frame goes away). Thus we maintain hashtables going both ways. These
+ // should always be in sync.
+
+ struct FrameWithFlags {
+ explicit FrameWithFlags(nsIFrame* aFrame) : mFrame(aFrame) {
+ MOZ_ASSERT(mFrame);
+ }
+ nsIFrame* const mFrame;
+ Flags mFlags{0};
+ };
+
+ // A helper class to compare FrameWithFlags by comparing mFrame and
+ // ignoring mFlags.
+ class FrameOnlyComparator {
+ public:
+ bool Equals(const FrameWithFlags& aElem1,
+ const FrameWithFlags& aElem2) const {
+ return aElem1.mFrame == aElem2.mFrame;
+ }
+
+ bool LessThan(const FrameWithFlags& aElem1,
+ const FrameWithFlags& aElem2) const {
+ return aElem1.mFrame < aElem2.mFrame;
+ }
+ };
+
+ typedef nsTArray<FrameWithFlags> FrameSet;
+ typedef nsTArray<nsCOMPtr<imgIRequest>> RequestSet;
+ typedef nsClassHashtable<nsISupportsHashKey, FrameSet> RequestToFrameMap;
+ typedef nsClassHashtable<nsPtrHashKey<nsIFrame>, RequestSet>
+ FrameToRequestMap;
+
+ nsPresContext* GetPresContext();
+
+ void ImageFrameChanged(imgIRequest*, bool aFirstFrame);
+ void UnblockOnloadIfNeeded(nsIFrame*, imgIRequest*);
+ void UnblockOnloadIfNeeded(FrameWithFlags&);
+
+ void OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
+ void OnFrameComplete(imgIRequest* aRequest);
+ void OnImageIsAnimated(imgIRequest* aRequest);
+ void OnFrameUpdate(imgIRequest* aRequest);
+ void OnLoadComplete(imgIRequest* aRequest);
+
+ // Helpers for DropRequestsForFrame / DisassociateRequestFromFrame above.
+ void RemoveRequestToFrameMapping(imgIRequest* aRequest, nsIFrame* aFrame);
+ void RemoveFrameToRequestMapping(imgIRequest* aRequest, nsIFrame* aFrame);
+
+ // A map of imgIRequests to the nsIFrames that are using them.
+ RequestToFrameMap mRequestToFrameMap;
+
+ // A map of nsIFrames to the imgIRequests they use.
+ FrameToRequestMap mFrameToRequestMap;
+
+ // A weak pointer to our document. Nulled out by DropDocumentReference.
+ dom::Document* mDocument;
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ImageLoader::Flags)
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_ImageLoader_h___ */
diff --git a/layout/style/ImportScanner.cpp b/layout/style/ImportScanner.cpp
new file mode 100644
index 0000000000..22d6c554f4
--- /dev/null
+++ b/layout/style/ImportScanner.cpp
@@ -0,0 +1,235 @@
+/* -*- 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 "ImportScanner.h"
+
+#include "mozilla/ServoBindings.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+static inline bool IsWhitespace(char16_t aChar) {
+ return nsContentUtils::IsHTMLWhitespace(aChar);
+}
+
+static inline bool OptionalSupportsMatches(const nsAString& aAfterRuleValue) {
+ // Ensure pref for @import supports() is enabled before wanting to check.
+ if (!StaticPrefs::layout_css_import_supports_enabled()) {
+ return true;
+ }
+
+ // Empty, don't bother checking.
+ if (aAfterRuleValue.IsEmpty()) {
+ return true;
+ }
+
+ NS_ConvertUTF16toUTF8 value(aAfterRuleValue);
+ return Servo_CSSSupportsForImport(&value);
+}
+
+void ImportScanner::ResetState() {
+ mInImportRule = false;
+ // We try to avoid freeing the buffers here.
+ mRuleName.Truncate(0);
+ mRuleValue.Truncate(0);
+ mAfterRuleValue.Truncate(0);
+}
+
+void ImportScanner::Start() {
+ Stop();
+ mState = State::Idle;
+}
+
+void ImportScanner::EmitUrl() {
+ MOZ_ASSERT(mState == State::AfterRuleValue);
+ if (mInImportRule) {
+ // Trim trailing whitespace from an unquoted URL.
+ if (mUrlValueDelimiterClosingChar == ')') {
+ // FIXME: Add a convenience function in nsContentUtils or something?
+ mRuleValue.Trim(" \t\n\r\f", false);
+ }
+
+ // If a supports(...) condition is given as part of import conditions,
+ // only emit the URL if it matches, as there is no use preloading
+ // imports for features we do not support, as this cannot change
+ // mid-page.
+ if (OptionalSupportsMatches(mAfterRuleValue)) {
+ mUrlsFound.AppendElement(std::move(mRuleValue));
+ }
+ }
+ ResetState();
+ MOZ_ASSERT(mRuleValue.IsEmpty());
+}
+
+nsTArray<nsString> ImportScanner::Stop() {
+ if (mState == State::AfterRuleValue) {
+ EmitUrl();
+ }
+ mState = State::OutsideOfStyleElement;
+ ResetState();
+ return std::move(mUrlsFound);
+}
+
+nsTArray<nsString> ImportScanner::Scan(Span<const char16_t> aFragment) {
+ MOZ_ASSERT(ShouldScan());
+
+ for (char16_t c : aFragment) {
+ mState = Scan(c);
+ if (mState == State::Done) {
+ break;
+ }
+ }
+
+ return std::move(mUrlsFound);
+}
+
+auto ImportScanner::Scan(char16_t aChar) -> State {
+ switch (mState) {
+ case State::OutsideOfStyleElement:
+ case State::Done:
+ MOZ_ASSERT_UNREACHABLE("How?");
+ return mState;
+ case State::Idle: {
+ // TODO(emilio): Maybe worth caring about html-style comments like:
+ // <style>
+ // <!--
+ // @import url(stuff);
+ // -->
+ // </style>
+ if (IsWhitespace(aChar)) {
+ return mState;
+ }
+ if (aChar == '/') {
+ return State::MaybeAtCommentStart;
+ }
+ if (aChar == '@') {
+ MOZ_ASSERT(mRuleName.IsEmpty());
+ return State::AtRuleName;
+ }
+ return State::Done;
+ }
+ case State::MaybeAtCommentStart: {
+ return aChar == '*' ? State::AtComment : State::Done;
+ }
+ case State::AtComment: {
+ return aChar == '*' ? State::MaybeAtCommentEnd : mState;
+ }
+ case State::MaybeAtCommentEnd: {
+ return aChar == '/' ? State::Idle : State::AtComment;
+ }
+ case State::AtRuleName: {
+ if (IsAsciiAlpha(aChar)) {
+ if (mRuleName.Length() > kMaxRuleNameLength - 1) {
+ return State::Done;
+ }
+ mRuleName.Append(aChar);
+ return mState;
+ }
+ if (IsWhitespace(aChar)) {
+ mInImportRule = mRuleName.LowerCaseEqualsLiteral("import");
+ if (mInImportRule) {
+ return State::AtRuleValue;
+ }
+ // Ignorable rules, we skip until the next semi-colon for these.
+ if (mRuleName.LowerCaseEqualsLiteral("charset") ||
+ mRuleName.LowerCaseEqualsLiteral("layer")) {
+ MOZ_ASSERT(mRuleValue.IsEmpty());
+ return State::AfterRuleValue;
+ }
+ }
+ return State::Done;
+ }
+ case State::AtRuleValue: {
+ MOZ_ASSERT(mInImportRule, "Should only get to this state for @import");
+ if (mRuleValue.IsEmpty()) {
+ if (IsWhitespace(aChar)) {
+ return mState;
+ }
+ if (aChar == '"' || aChar == '\'') {
+ mUrlValueDelimiterClosingChar = aChar;
+ return State::AtRuleValueDelimited;
+ }
+ if (aChar == 'u' || aChar == 'U') {
+ mRuleValue.Append('u');
+ return mState;
+ }
+ return State::Done;
+ }
+ if (mRuleValue.Length() == 1) {
+ MOZ_ASSERT(mRuleValue.EqualsLiteral("u"));
+ if (aChar == 'r' || aChar == 'R') {
+ mRuleValue.Append('r');
+ return mState;
+ }
+ return State::Done;
+ }
+ if (mRuleValue.Length() == 2) {
+ MOZ_ASSERT(mRuleValue.EqualsLiteral("ur"));
+ if (aChar == 'l' || aChar == 'L') {
+ mRuleValue.Append('l');
+ }
+ return mState;
+ }
+ if (mRuleValue.Length() == 3) {
+ MOZ_ASSERT(mRuleValue.EqualsLiteral("url"));
+ if (aChar == '(') {
+ mUrlValueDelimiterClosingChar = ')';
+ mRuleValue.Truncate(0);
+ return State::AtRuleValueDelimited;
+ }
+ return State::Done;
+ }
+ MOZ_ASSERT_UNREACHABLE(
+ "How? We should find a paren or a string delimiter");
+ return State::Done;
+ }
+ case State::AtRuleValueDelimited: {
+ MOZ_ASSERT(mInImportRule, "Should only get to this state for @import");
+ if (aChar == mUrlValueDelimiterClosingChar) {
+ return State::AfterRuleValue;
+ }
+ if (mUrlValueDelimiterClosingChar == ')' && mRuleValue.IsEmpty()) {
+ if (IsWhitespace(aChar)) {
+ return mState;
+ }
+ if (aChar == '"' || aChar == '\'') {
+ // Handle url("") and url('').
+ mUrlValueDelimiterClosingChar = aChar;
+ return mState;
+ }
+ }
+ if (!mRuleValue.Append(aChar, mozilla::fallible)) {
+ mRuleValue.Truncate(0);
+ return State::Done;
+ }
+ return mState;
+ }
+ case State::AfterRuleValue: {
+ if (aChar == ';') {
+ EmitUrl();
+ return State::Idle;
+ }
+ // If there's a selector here and the import was unterminated, just give
+ // up.
+ if (aChar == '{') {
+ return State::Done;
+ }
+
+ if (!mAfterRuleValue.Append(aChar, mozilla::fallible)) {
+ mAfterRuleValue.Truncate(0);
+ return State::Done;
+ }
+
+ return mState; // There can be all sorts of stuff here like media
+ // queries or what not.
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("Forgot to handle a state?");
+ return State::Done;
+}
+
+} // namespace mozilla
diff --git a/layout/style/ImportScanner.h b/layout/style/ImportScanner.h
new file mode 100644
index 0000000000..ff1755fb8b
--- /dev/null
+++ b/layout/style/ImportScanner.h
@@ -0,0 +1,94 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ImportScanner_h
+#define mozilla_ImportScanner_h
+
+/* A simple best-effort scanner for @import rules for the HTML parser */
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+struct ImportScanner final {
+ ImportScanner() = default;
+
+ // Called when a <style> element starts.
+ //
+ // Note that this function cannot make assumptions about the internal state,
+ // as you can nest <svg:style> elements.
+ void Start();
+
+ // Called when a <style> element ends. Returns the list of URLs scanned.
+ nsTArray<nsString> Stop();
+
+ // Whether Scan() should be called.
+ bool ShouldScan() const {
+ return mState != State::OutsideOfStyleElement && mState != State::Done;
+ }
+
+ // Scan() should be called when text content is parsed, and returns an array
+ // of found URLs, if any.
+ //
+ // Asserts ShouldScan() returns true.
+ nsTArray<nsString> Scan(Span<const char16_t> aFragment);
+
+ private:
+ enum class State {
+ // Initial state, doesn't scan anything until Start() is called.
+ OutsideOfStyleElement,
+ // In an idle state during the stylesheet scanning, either at the
+ // beginning or after parsing a rule.
+ Idle,
+ // We've seen a '/' character, but not the '*' yet, so we don't know if
+ // it's a comment.
+ MaybeAtCommentStart,
+ // We're inside a comment.
+ AtComment,
+ // We've seen a '*' while we're in a comment, but we don't now yet whether
+ // '/' comes afterwards (thus ending the comment).
+ MaybeAtCommentEnd,
+ // We're parsing the '@' rule name.
+ AtRuleName,
+ // We're parsing the '@' rule value.
+ AtRuleValue,
+ // We're parsing the '@' rule value and we've seen the delimiter (quote or
+ // url() function) that encloses the url.
+ AtRuleValueDelimited,
+ // We've seen the url, but haven't seen the ';' finishing the rule yet.
+ AfterRuleValue,
+ // We've seen anything that is not an @import or a @charset rule, and thus
+ // further @import / @charset should not be parsed.
+ Done,
+ };
+
+ void ResetState();
+ void EmitUrl();
+ [[nodiscard]] State Scan(char16_t aChar);
+
+ static constexpr const uint32_t kMaxRuleNameLength = 7; // (charset, import)
+
+ State mState = State::OutsideOfStyleElement;
+ nsAutoStringN<kMaxRuleNameLength> mRuleName;
+ nsAutoStringN<128> mRuleValue;
+ nsAutoStringN<128> mAfterRuleValue;
+ nsTArray<nsString> mUrlsFound;
+
+ // This is conceptually part of the AtRuleValue* / AfterRuleValue states,
+ // and serves to differentiate between @import (where we actually care about
+ // the value) and @charset (where we don't). It's just more convenient this
+ // way than having separate states for them.
+ bool mInImportRule = false;
+ // If we're in the AtRuleValueDelimited state, what is the closing character
+ // that will end the value. This is either a parenthesis (for unquoted
+ // urls), or a quote, either single or double.
+ char16_t mUrlValueDelimiterClosingChar = 0;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/LayerAnimationInfo.cpp b/layout/style/LayerAnimationInfo.cpp
new file mode 100644
index 0000000000..753838c439
--- /dev/null
+++ b/layout/style/LayerAnimationInfo.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "LayerAnimationInfo.h"
+
+#include "nsCSSProps.h" // For nsCSSProps::PropHasFlags
+#include "nsCSSPropertyIDSet.h" // For nsCSSPropertyIDSet::CompositorAnimatable
+
+namespace mozilla {
+
+/* static */ const Array<
+ DisplayItemType, nsCSSPropertyIDSet::CompositorAnimatableDisplayItemCount()>
+ LayerAnimationInfo::sDisplayItemTypes = {
+ DisplayItemType::TYPE_BACKGROUND_COLOR,
+ DisplayItemType::TYPE_OPACITY,
+ DisplayItemType::TYPE_TRANSFORM,
+};
+
+/* static */
+DisplayItemType LayerAnimationInfo::GetDisplayItemTypeForProperty(
+ nsCSSPropertyID aProperty) {
+ switch (aProperty) {
+ case eCSSProperty_background_color:
+ return DisplayItemType::TYPE_BACKGROUND_COLOR;
+ case eCSSProperty_opacity:
+ return DisplayItemType::TYPE_OPACITY;
+ case eCSSProperty_transform:
+ case eCSSProperty_translate:
+ case eCSSProperty_scale:
+ case eCSSProperty_rotate:
+ case eCSSProperty_offset_path:
+ case eCSSProperty_offset_distance:
+ case eCSSProperty_offset_rotate:
+ case eCSSProperty_offset_anchor:
+ case eCSSProperty_offset_position:
+ return DisplayItemType::TYPE_TRANSFORM;
+ default:
+ break;
+ }
+ return DisplayItemType::TYPE_ZERO;
+}
+
+} // namespace mozilla
diff --git a/layout/style/LayerAnimationInfo.h b/layout/style/LayerAnimationInfo.h
new file mode 100644
index 0000000000..8125d1442a
--- /dev/null
+++ b/layout/style/LayerAnimationInfo.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LayerAnimationInfo_h
+#define mozilla_LayerAnimationInfo_h
+
+#include "nsChangeHint.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSPropertyIDSet.h"
+#include "nsDisplayItemTypes.h" // For nsDisplayItem::Type
+#include "mozilla/Array.h"
+
+namespace mozilla {
+
+struct LayerAnimationInfo {
+ // Returns the corresponding display item type for |aProperty| when it is
+ // animated on the compositor.
+ // Returns DisplayItemType::TYPE_ZERO if |aProperty| cannot be animated on the
+ // compositor.
+ static DisplayItemType GetDisplayItemTypeForProperty(
+ nsCSSPropertyID aProperty);
+
+ // Returns the corresponding CSS properties for |aDisplayItemType|.
+ //
+ // This function works only for display items tied to CSS properties that can
+ // be animated on the compositor.
+ static inline const nsCSSPropertyIDSet& GetCSSPropertiesFor(
+ DisplayItemType aDisplayItemType) {
+ static const nsCSSPropertyIDSet transformProperties =
+ nsCSSPropertyIDSet::TransformLikeProperties();
+ static const nsCSSPropertyIDSet opacityProperties =
+ nsCSSPropertyIDSet{eCSSProperty_opacity};
+ static const nsCSSPropertyIDSet backgroundColorProperties =
+ nsCSSPropertyIDSet{eCSSProperty_background_color};
+ static const nsCSSPropertyIDSet empty = nsCSSPropertyIDSet();
+
+ switch (aDisplayItemType) {
+ case DisplayItemType::TYPE_BACKGROUND_COLOR:
+ return backgroundColorProperties;
+ case DisplayItemType::TYPE_OPACITY:
+ return opacityProperties;
+ case DisplayItemType::TYPE_TRANSFORM:
+ return transformProperties;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Should not be called for display item types "
+ "that are not able to have animations on the "
+ "compositor");
+ return empty;
+ }
+ }
+
+ // Returns the appropriate change hint for updating the display item for
+ // |aDisplayItemType|.
+ //
+ // This function works only for display items tied to CSS properties that can
+ // be animated on the compositor.
+ static inline nsChangeHint GetChangeHintFor(
+ DisplayItemType aDisplayItemType) {
+ switch (aDisplayItemType) {
+ case DisplayItemType::TYPE_BACKGROUND_COLOR:
+ return nsChangeHint_RepaintFrame;
+ case DisplayItemType::TYPE_OPACITY:
+ return nsChangeHint_UpdateOpacityLayer;
+ case DisplayItemType::TYPE_TRANSFORM:
+ return nsChangeHint_UpdateTransformLayer;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Should not be called for display item types "
+ "that are not able to have animations on the "
+ "compositor");
+ return nsChangeHint(0);
+ }
+ }
+
+ // An array of DisplayItemType corresponding to the display item that we can
+ // animate on the compositor.
+ //
+ // This is used to look up the appropriate change hint in cases when
+ // animations need updating but no other change hint is generated.
+ static const Array<DisplayItemType,
+ nsCSSPropertyIDSet::CompositorAnimatableDisplayItemCount()>
+ sDisplayItemTypes;
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_LayerAnimationInfo_h) */
diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp
new file mode 100644
index 0000000000..ebbc934466
--- /dev/null
+++ b/layout/style/Loader.cpp
@@ -0,0 +1,2344 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* loading of CSS style sheets using the network APIs */
+
+#include "mozilla/css/Loader.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/FetchPriority.h"
+#include "mozilla/dom/SRILogHelper.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PreloadHashKey.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/URLPreloader.h"
+#include "nsIChildChannel.h"
+#include "nsISupportsPriority.h"
+#include "nsITimedChannel.h"
+#include "nsICachingChannel.h"
+#include "nsSyncLoadService.h"
+#include "nsContentSecurityManager.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsICookieJarSettings.h"
+#include "mozilla/dom/Document.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIClassOfService.h"
+#include "nsIScriptError.h"
+#include "nsMimeTypes.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsThreadUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIThreadInternal.h"
+#include "nsINetworkPredictor.h"
+#include "nsQueryActor.h"
+#include "nsStringStream.h"
+#include "mozilla/dom/MediaList.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/URL.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/css/StreamLoader.h"
+#include "mozilla/SharedStyleSheetCache.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Try.h"
+#include "ReferrerInfo.h"
+
+#include "nsXULPrototypeCache.h"
+
+#include "nsError.h"
+
+#include "mozilla/dom/SRICheck.h"
+
+#include "mozilla/Encoding.h"
+
+using namespace mozilla::dom;
+
+// 1024 bytes is specified in https://drafts.csswg.org/css-syntax/
+#define SNIFFING_BUFFER_SIZE 1024
+
+/**
+ * OVERALL ARCHITECTURE
+ *
+ * The CSS Loader gets requests to load various sorts of style sheets:
+ * inline style from <style> elements, linked style, @import-ed child
+ * sheets, non-document sheets. The loader handles the following tasks:
+ * 1) Creation of the actual style sheet objects: CreateSheet()
+ * 2) setting of the right media, title, enabled state, etc on the
+ * sheet: PrepareSheet()
+ * 3) Insertion of the sheet in the proper cascade order:
+ * InsertSheetInTree() and InsertChildSheet()
+ * 4) Load of the sheet: LoadSheet() including security checks
+ * 5) Parsing of the sheet: ParseSheet()
+ * 6) Cleanup: SheetComplete()
+ *
+ * The detailed documentation for these functions is found with the
+ * function implementations.
+ *
+ * The following helper object is used:
+ * SheetLoadData -- a small class that is used to store all the
+ * information needed for the loading of a sheet;
+ * this class handles listening for the stream
+ * loader completion and also handles charset
+ * determination.
+ */
+
+extern mozilla::LazyLogModule sCssLoaderLog;
+mozilla::LazyLogModule sCssLoaderLog("nsCSSLoader");
+
+static mozilla::LazyLogModule gSriPRLog("SRI");
+
+static bool IsPrivilegedURI(nsIURI* aURI) {
+ return aURI->SchemeIs("chrome") || aURI->SchemeIs("resource");
+}
+
+#define LOG_ERROR(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Error, args)
+#define LOG_WARN(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Warning, args)
+#define LOG_DEBUG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
+#define LOG(args) LOG_DEBUG(args)
+
+#define LOG_ERROR_ENABLED() \
+ MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Error)
+#define LOG_WARN_ENABLED() \
+ MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Warning)
+#define LOG_DEBUG_ENABLED() \
+ MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Debug)
+#define LOG_ENABLED() LOG_DEBUG_ENABLED()
+
+#define LOG_URI(format, uri) \
+ PR_BEGIN_MACRO \
+ NS_ASSERTION(uri, "Logging null uri"); \
+ if (LOG_ENABLED()) { \
+ LOG((format, uri->GetSpecOrDefault().get())); \
+ } \
+ PR_END_MACRO
+
+// And some convenience strings...
+static const char* const gStateStrings[] = {"NeedsParser", "Pending", "Loading",
+ "Complete"};
+
+namespace mozilla {
+
+SheetLoadDataHashKey::SheetLoadDataHashKey(const css::SheetLoadData& aLoadData)
+ : mURI(aLoadData.mURI),
+ mPrincipal(aLoadData.mTriggeringPrincipal),
+ mLoaderPrincipal(aLoadData.mLoader->LoaderPrincipal()),
+ mPartitionPrincipal(aLoadData.mLoader->PartitionedPrincipal()),
+ mEncodingGuess(aLoadData.mGuessedEncoding),
+ mCORSMode(aLoadData.mSheet->GetCORSMode()),
+ mParsingMode(aLoadData.mSheet->ParsingMode()),
+ mCompatMode(aLoadData.mCompatMode),
+ mIsLinkRelPreload(aLoadData.IsLinkRelPreload()) {
+ MOZ_COUNT_CTOR(SheetLoadDataHashKey);
+ MOZ_ASSERT(mURI);
+ MOZ_ASSERT(mPrincipal);
+ MOZ_ASSERT(mLoaderPrincipal);
+ MOZ_ASSERT(mPartitionPrincipal);
+ aLoadData.mSheet->GetIntegrity(mSRIMetadata);
+}
+
+bool SheetLoadDataHashKey::KeyEquals(const SheetLoadDataHashKey& aKey) const {
+ {
+ bool eq;
+ if (NS_FAILED(mURI->Equals(aKey.mURI, &eq)) || !eq) {
+ return false;
+ }
+ }
+
+ LOG_URI("KeyEquals(%s)\n", mURI);
+
+ if (mParsingMode != aKey.mParsingMode) {
+ LOG((" > Parsing mode mismatch\n"));
+ return false;
+ }
+
+ // Chrome URIs ignore everything else.
+ if (IsPrivilegedURI(mURI)) {
+ return true;
+ }
+
+ if (!mPrincipal->Equals(aKey.mPrincipal)) {
+ LOG((" > Principal mismatch\n"));
+ return false;
+ }
+
+ // We only check for partition principal equality if any of the loads are
+ // triggered by a document rather than e.g. an extension (which have different
+ // origins than the loader principal).
+ if (mPrincipal->Equals(mLoaderPrincipal) ||
+ aKey.mPrincipal->Equals(aKey.mLoaderPrincipal)) {
+ if (!mPartitionPrincipal->Equals(aKey.mPartitionPrincipal)) {
+ LOG((" > Partition principal mismatch\n"));
+ return false;
+ }
+ }
+
+ if (mCORSMode != aKey.mCORSMode) {
+ LOG((" > CORS mismatch\n"));
+ return false;
+ }
+
+ if (mCompatMode != aKey.mCompatMode) {
+ LOG((" > Quirks mismatch\n"));
+ return false;
+ }
+
+ // If encoding differs, then don't reuse the cache.
+ //
+ // TODO(emilio): When the encoding is determined from the request (either
+ // BOM or Content-Length or @charset), we could do a bit better,
+ // theoretically.
+ if (mEncodingGuess != aKey.mEncodingGuess) {
+ LOG((" > Encoding guess mismatch\n"));
+ return false;
+ }
+
+ // Consuming stylesheet tags must never coalesce to <link preload> initiated
+ // speculative loads with a weaker SRI hash or its different value. This
+ // check makes sure that regular loads will never find such a weaker preload
+ // and rather start a new, independent load with new, stronger SRI checker
+ // set up, so that integrity is ensured.
+ if (mIsLinkRelPreload != aKey.mIsLinkRelPreload) {
+ const auto& linkPreloadMetadata =
+ mIsLinkRelPreload ? mSRIMetadata : aKey.mSRIMetadata;
+ const auto& consumerPreloadMetadata =
+ mIsLinkRelPreload ? aKey.mSRIMetadata : mSRIMetadata;
+
+ if (!consumerPreloadMetadata.CanTrustBeDelegatedTo(linkPreloadMetadata)) {
+ LOG((" > Preload SRI metadata mismatch\n"));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+namespace css {
+
+static NotNull<const Encoding*> GetFallbackEncoding(
+ Loader& aLoader, nsINode* aOwningNode,
+ const Encoding* aPreloadOrParentDataEncoding) {
+ const Encoding* encoding;
+ // Now try the charset on the <link> or processing instruction
+ // that loaded us
+ if (aOwningNode) {
+ nsAutoString label16;
+ LinkStyle::FromNode(*aOwningNode)->GetCharset(label16);
+ encoding = Encoding::ForLabel(label16);
+ if (encoding) {
+ return WrapNotNull(encoding);
+ }
+ }
+
+ // Try preload or parent sheet encoding.
+ if (aPreloadOrParentDataEncoding) {
+ return WrapNotNull(aPreloadOrParentDataEncoding);
+ }
+
+ if (auto* doc = aLoader.GetDocument()) {
+ // Use the document charset.
+ return doc->GetDocumentCharacterSet();
+ }
+
+ return UTF_8_ENCODING;
+}
+
+/********************************
+ * SheetLoadData implementation *
+ ********************************/
+NS_IMPL_ISUPPORTS(SheetLoadData, nsISupports)
+
+SheetLoadData::SheetLoadData(
+ css::Loader* aLoader, const nsAString& aTitle, nsIURI* aURI,
+ StyleSheet* aSheet, SyncLoad aSyncLoad, nsINode* aOwningNode,
+ IsAlternate aIsAlternate, MediaMatched aMediaMatches,
+ StylePreloadKind aPreloadKind, nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo,
+ const nsAString& aNonce, FetchPriority aFetchPriority)
+ : mLoader(aLoader),
+ mTitle(aTitle),
+ mEncoding(nullptr),
+ mURI(aURI),
+ mSheet(aSheet),
+ mPendingChildren(0),
+ mSyncLoad(aSyncLoad == SyncLoad::Yes),
+ mIsNonDocumentSheet(false),
+ mIsChildSheet(aSheet->GetParentSheet()),
+ mIsBeingParsed(false),
+ mIsLoading(false),
+ mIsCancelled(false),
+ mMustNotify(false),
+ mHadOwnerNode(!!aOwningNode),
+ mWasAlternate(aIsAlternate == IsAlternate::Yes),
+ mMediaMatched(aMediaMatches == MediaMatched::Yes),
+ mUseSystemPrincipal(false),
+ mSheetAlreadyComplete(false),
+ mIsCrossOriginNoCORS(false),
+ mBlockResourceTiming(false),
+ mLoadFailed(false),
+ mPreloadKind(aPreloadKind),
+ mObserver(aObserver),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mReferrerInfo(aReferrerInfo),
+ mNonce(aNonce),
+ mFetchPriority{aFetchPriority},
+ mGuessedEncoding(GetFallbackEncoding(*aLoader, aOwningNode, nullptr)),
+ mCompatMode(aLoader->CompatMode(aPreloadKind)) {
+ MOZ_ASSERT(!aOwningNode || dom::LinkStyle::FromNode(*aOwningNode),
+ "Must implement LinkStyle");
+ MOZ_ASSERT(mTriggeringPrincipal);
+ MOZ_ASSERT(mLoader, "Must have a loader!");
+}
+
+SheetLoadData::SheetLoadData(css::Loader* aLoader, nsIURI* aURI,
+ StyleSheet* aSheet, SheetLoadData* aParentData,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIReferrerInfo* aReferrerInfo)
+ : mLoader(aLoader),
+ mEncoding(nullptr),
+ mURI(aURI),
+ mSheet(aSheet),
+ mParentData(aParentData),
+ mPendingChildren(0),
+ mSyncLoad(aParentData && aParentData->mSyncLoad),
+ mIsNonDocumentSheet(aParentData && aParentData->mIsNonDocumentSheet),
+ mIsChildSheet(aSheet->GetParentSheet()),
+ mIsBeingParsed(false),
+ mIsLoading(false),
+ mIsCancelled(false),
+ mMustNotify(false),
+ mHadOwnerNode(false),
+ mWasAlternate(false),
+ mMediaMatched(true),
+ mUseSystemPrincipal(aParentData && aParentData->mUseSystemPrincipal),
+ mSheetAlreadyComplete(false),
+ mIsCrossOriginNoCORS(false),
+ mBlockResourceTiming(false),
+ mLoadFailed(false),
+ mPreloadKind(StylePreloadKind::None),
+ mObserver(aObserver),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mReferrerInfo(aReferrerInfo),
+ mNonce(u""_ns),
+ mFetchPriority(FetchPriority::Auto),
+ mGuessedEncoding(GetFallbackEncoding(
+ *aLoader, nullptr, aParentData ? aParentData->mEncoding : nullptr)),
+ mCompatMode(aLoader->CompatMode(mPreloadKind)) {
+ MOZ_ASSERT(mLoader, "Must have a loader!");
+ MOZ_ASSERT(mTriggeringPrincipal);
+ MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
+ "Shouldn't use system principal for async loads");
+ MOZ_ASSERT_IF(aParentData, mIsChildSheet);
+}
+
+SheetLoadData::SheetLoadData(
+ css::Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet, SyncLoad aSyncLoad,
+ UseSystemPrincipal aUseSystemPrincipal, StylePreloadKind aPreloadKind,
+ const Encoding* aPreloadEncoding, nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo,
+ const nsAString& aNonce, FetchPriority aFetchPriority)
+ : mLoader(aLoader),
+ mEncoding(nullptr),
+ mURI(aURI),
+ mSheet(aSheet),
+ mPendingChildren(0),
+ mSyncLoad(aSyncLoad == SyncLoad::Yes),
+ mIsNonDocumentSheet(true),
+ mIsChildSheet(false),
+ mIsBeingParsed(false),
+ mIsLoading(false),
+ mIsCancelled(false),
+ mMustNotify(false),
+ mHadOwnerNode(false),
+ mWasAlternate(false),
+ mMediaMatched(true),
+ mUseSystemPrincipal(aUseSystemPrincipal == UseSystemPrincipal::Yes),
+ mSheetAlreadyComplete(false),
+ mIsCrossOriginNoCORS(false),
+ mBlockResourceTiming(false),
+ mLoadFailed(false),
+ mPreloadKind(aPreloadKind),
+ mObserver(aObserver),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mReferrerInfo(aReferrerInfo),
+ mNonce(aNonce),
+ mFetchPriority(aFetchPriority),
+ mGuessedEncoding(
+ GetFallbackEncoding(*aLoader, nullptr, aPreloadEncoding)),
+ mCompatMode(aLoader->CompatMode(aPreloadKind)) {
+ MOZ_ASSERT(mTriggeringPrincipal);
+ MOZ_ASSERT(mLoader, "Must have a loader!");
+ MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
+ "Shouldn't use system principal for async loads");
+ MOZ_ASSERT(!aSheet->GetParentSheet(), "Shouldn't be used for child loads");
+}
+
+SheetLoadData::~SheetLoadData() {
+ MOZ_RELEASE_ASSERT(mSheetCompleteCalled || mIntentionallyDropped,
+ "Should always call SheetComplete, except when "
+ "dropping the load");
+}
+
+RefPtr<StyleSheet> SheetLoadData::ValueForCache() const {
+ // We need to clone the sheet on insertion to the cache because otherwise the
+ // stylesheets can keep full windows alive via either their JS wrapper, or via
+ // StyleSheet::mRelevantGlobal.
+ //
+ // If this ever changes, then you also need to fix up the memory reporting in
+ // both SizeOfIncludingThis and nsXULPrototypeCache::CollectMemoryReports.
+ return mSheet->Clone(nullptr, nullptr);
+}
+
+void SheetLoadData::PrioritizeAsPreload(nsIChannel* aChannel) {
+ if (nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(aChannel)) {
+ sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+ }
+}
+
+void SheetLoadData::StartPendingLoad() {
+ mLoader->LoadSheet(*this, Loader::SheetState::NeedsParser, 0,
+ Loader::PendingLoad::Yes);
+}
+
+already_AddRefed<AsyncEventDispatcher>
+SheetLoadData::PrepareLoadEventIfNeeded() {
+ nsCOMPtr<nsINode> node = mSheet->GetOwnerNode();
+ if (!node) {
+ return nullptr;
+ }
+ MOZ_ASSERT(!RootLoadData().IsLinkRelPreload(),
+ "rel=preload handled elsewhere");
+ RefPtr<AsyncEventDispatcher> dispatcher;
+ if (BlocksLoadEvent()) {
+ dispatcher = new LoadBlockingAsyncEventDispatcher(
+ node, mLoadFailed ? u"error"_ns : u"load"_ns, CanBubble::eNo,
+ ChromeOnlyDispatch::eNo);
+ } else {
+ // Fire the load event on the link, but don't block the document load.
+ dispatcher =
+ new AsyncEventDispatcher(node, mLoadFailed ? u"error"_ns : u"load"_ns,
+ CanBubble::eNo, ChromeOnlyDispatch::eNo);
+ }
+ return dispatcher.forget();
+}
+
+nsINode* SheetLoadData::GetRequestingNode() const {
+ if (nsINode* node = mSheet->GetOwnerNodeOfOutermostSheet()) {
+ return node;
+ }
+ return mLoader->GetDocument();
+}
+
+/*********************
+ * Style sheet reuse *
+ *********************/
+
+bool LoaderReusableStyleSheets::FindReusableStyleSheet(
+ nsIURI* aURL, RefPtr<StyleSheet>& aResult) {
+ MOZ_ASSERT(aURL);
+ for (size_t i = mReusableSheets.Length(); i > 0; --i) {
+ size_t index = i - 1;
+ bool sameURI;
+ MOZ_ASSERT(mReusableSheets[index]->GetOriginalURI());
+ nsresult rv =
+ aURL->Equals(mReusableSheets[index]->GetOriginalURI(), &sameURI);
+ if (!NS_FAILED(rv) && sameURI) {
+ aResult = mReusableSheets[index];
+ mReusableSheets.RemoveElementAt(index);
+ return true;
+ }
+ }
+ return false;
+}
+/*************************
+ * Loader Implementation *
+ *************************/
+
+Loader::Loader()
+ : mDocument(nullptr),
+ mDocumentCompatMode(eCompatibility_FullStandards),
+ mReporter(new ConsoleReportCollector()) {}
+
+Loader::Loader(DocGroup* aDocGroup) : Loader() { mDocGroup = aDocGroup; }
+
+Loader::Loader(Document* aDocument) : Loader() {
+ MOZ_ASSERT(aDocument, "We should get a valid document from the caller!");
+ mDocument = aDocument;
+ mIsDocumentAssociated = true;
+ mDocumentCompatMode = aDocument->GetCompatibilityMode();
+ mSheets = SharedStyleSheetCache::Get();
+ RegisterInSheetCache();
+}
+
+// Note: no real need to revoke our stylesheet loaded events -- they hold strong
+// references to us, so if we're going away that means they're all done.
+Loader::~Loader() = default;
+
+void Loader::RegisterInSheetCache() {
+ MOZ_ASSERT(mDocument);
+ MOZ_ASSERT(mSheets);
+
+ mSheets->RegisterLoader(*this);
+}
+
+void Loader::DeregisterFromSheetCache() {
+ MOZ_ASSERT(mDocument);
+ MOZ_ASSERT(mSheets);
+
+ mSheets->CancelLoadsForLoader(*this);
+ mSheets->UnregisterLoader(*this);
+}
+
+void Loader::DropDocumentReference() {
+ // Flush out pending datas just so we don't leak by accident.
+ if (mSheets) {
+ DeregisterFromSheetCache();
+ }
+ mDocument = nullptr;
+}
+
+void Loader::DocumentStyleSheetSetChanged() {
+ MOZ_ASSERT(mDocument);
+
+ // start any pending alternates that aren't alternates anymore
+ mSheets->StartPendingLoadsForLoader(*this, [&](const SheetLoadData& aData) {
+ return IsAlternateSheet(aData.mTitle, true) != IsAlternate::Yes;
+ });
+}
+
+static const char kCharsetSym[] = "@charset \"";
+
+static bool GetCharsetFromData(const char* aStyleSheetData,
+ uint32_t aDataLength, nsACString& aCharset) {
+ aCharset.Truncate();
+ if (aDataLength <= sizeof(kCharsetSym) - 1) return false;
+
+ if (strncmp(aStyleSheetData, kCharsetSym, sizeof(kCharsetSym) - 1)) {
+ return false;
+ }
+
+ for (uint32_t i = sizeof(kCharsetSym) - 1; i < aDataLength; ++i) {
+ char c = aStyleSheetData[i];
+ if (c == '"') {
+ ++i;
+ if (i < aDataLength && aStyleSheetData[i] == ';') {
+ return true;
+ }
+ // fail
+ break;
+ }
+ aCharset.Append(c);
+ }
+
+ // Did not see end quote or semicolon
+ aCharset.Truncate();
+ return false;
+}
+
+NotNull<const Encoding*> SheetLoadData::DetermineNonBOMEncoding(
+ const nsACString& aSegment, nsIChannel* aChannel) const {
+ const Encoding* encoding;
+ nsAutoCString label;
+
+ // Check HTTP
+ if (aChannel && NS_SUCCEEDED(aChannel->GetContentCharset(label))) {
+ encoding = Encoding::ForLabel(label);
+ if (encoding) {
+ return WrapNotNull(encoding);
+ }
+ }
+
+ // Check @charset
+ auto sniffingLength = aSegment.Length();
+ if (sniffingLength > SNIFFING_BUFFER_SIZE) {
+ sniffingLength = SNIFFING_BUFFER_SIZE;
+ }
+ if (GetCharsetFromData(aSegment.BeginReading(), sniffingLength, label)) {
+ encoding = Encoding::ForLabel(label);
+ if (encoding == UTF_16BE_ENCODING || encoding == UTF_16LE_ENCODING) {
+ return UTF_8_ENCODING;
+ }
+ if (encoding) {
+ return WrapNotNull(encoding);
+ }
+ }
+ return mGuessedEncoding;
+}
+
+static nsresult VerifySheetIntegrity(const SRIMetadata& aMetadata,
+ nsIChannel* aChannel,
+ const nsACString& aFirst,
+ const nsACString& aSecond,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter) {
+ NS_ENSURE_ARG_POINTER(aReporter);
+
+ if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), LogLevel::Debug)) {
+ nsAutoCString requestURL;
+ nsCOMPtr<nsIURI> originalURI;
+ if (aChannel &&
+ NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
+ originalURI) {
+ originalURI->GetAsciiSpec(requestURL);
+ }
+ MOZ_LOG(SRILogHelper::GetSriLog(), LogLevel::Debug,
+ ("VerifySheetIntegrity (unichar stream)"));
+ }
+
+ SRICheckDataVerifier verifier(aMetadata, aSourceFileURI, aReporter);
+ nsresult rv =
+ verifier.Update(aFirst.Length(), (const uint8_t*)aFirst.BeginReading());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv =
+ verifier.Update(aSecond.Length(), (const uint8_t*)aSecond.BeginReading());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return verifier.Verify(aMetadata, aChannel, aSourceFileURI, aReporter);
+}
+
+static bool AllLoadsCanceled(const SheetLoadData& aData) {
+ const SheetLoadData* data = &aData;
+ do {
+ if (!data->IsCancelled()) {
+ return false;
+ }
+ } while ((data = data->mNext));
+ return true;
+}
+
+/*
+ * Stream completion code shared by Stylo and the old style system.
+ *
+ * Here we need to check that the load did not give us an http error
+ * page and check the mimetype on the channel to make sure we're not
+ * loading non-text/css data in standards mode.
+ */
+nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
+ const nsACString& aBytes1,
+ const nsACString& aBytes2,
+ nsIChannel* aChannel) {
+ LOG(("SheetLoadData::VerifySheetReadyToParse"));
+ NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");
+
+ if (AllLoadsCanceled(*this)) {
+ LOG_WARN((" All loads are canceled, dropping"));
+ mLoader->SheetComplete(*this, NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aStatus)) {
+ LOG_WARN(
+ (" Load failed: status 0x%" PRIx32, static_cast<uint32_t>(aStatus)));
+ // Handle sheet not loading error because source was a tracking URL (or
+ // fingerprinting, cryptomining, etc).
+ // We make a note of this sheet node by including it in a dedicated
+ // array of blocked tracking nodes under its parent document.
+ //
+ // Multiple sheet load instances might be tied to this request,
+ // we annotate each one linked to a valid owning element (node).
+ if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ aStatus)) {
+ if (Document* doc = mLoader->GetDocument()) {
+ for (SheetLoadData* data = this; data; data = data->mNext) {
+ // owner node may be null but AddBlockTrackingNode can cope
+ doc->AddBlockedNodeByClassifier(data->mSheet->GetOwnerNode());
+ }
+ }
+ }
+ mLoader->SheetComplete(*this, aStatus);
+ return NS_OK;
+ }
+
+ if (!aChannel) {
+ mLoader->SheetComplete(*this, NS_OK);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> originalURI;
+ aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+
+ // If the channel's original URI is "chrome:", we want that, since
+ // the observer code in nsXULPrototypeCache depends on chrome stylesheets
+ // having a chrome URI. (Whether or not chrome stylesheets come through
+ // this codepath seems nondeterministic.)
+ // Otherwise we want the potentially-HTTP-redirected URI.
+ nsCOMPtr<nsIURI> channelURI;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+
+ if (!channelURI || !originalURI) {
+ NS_ERROR("Someone just violated the nsIRequest contract");
+ LOG_WARN((" Channel without a URI. Bad!"));
+ mLoader->SheetComplete(*this, NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ nsresult result = NS_ERROR_NOT_AVAILABLE;
+ if (secMan) { // Could be null if we already shut down
+ if (mUseSystemPrincipal) {
+ result = secMan->GetSystemPrincipal(getter_AddRefs(principal));
+ } else {
+ result = secMan->GetChannelResultPrincipal(aChannel,
+ getter_AddRefs(principal));
+ }
+ }
+
+ if (NS_FAILED(result)) {
+ LOG_WARN((" Couldn't get principal"));
+ mLoader->SheetComplete(*this, result);
+ return NS_OK;
+ }
+
+ mSheet->SetPrincipal(principal);
+
+ if (mSheet->GetCORSMode() == CORS_NONE &&
+ !mTriggeringPrincipal->Subsumes(principal)) {
+ mIsCrossOriginNoCORS = true;
+ }
+
+ // If it's an HTTP channel, we want to make sure this is not an
+ // error document we got.
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
+ bool requestSucceeded;
+ result = httpChannel->GetRequestSucceeded(&requestSucceeded);
+ if (NS_SUCCEEDED(result) && !requestSucceeded) {
+ LOG((" Load returned an error page"));
+ mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
+ return NS_OK;
+ }
+
+ nsCString sourceMapURL;
+ if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
+ mSheet->SetSourceMapURL(std::move(sourceMapURL));
+ }
+ }
+
+ nsAutoCString contentType;
+ aChannel->GetContentType(contentType);
+
+ // In standards mode, a style sheet must have one of these MIME
+ // types to be processed at all. In quirks mode, we accept any
+ // MIME type, but only if the style sheet is same-origin with the
+ // requesting document or parent sheet. See bug 524223.
+
+ bool validType = contentType.EqualsLiteral("text/css") ||
+ contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
+ contentType.IsEmpty();
+
+ if (!validType) {
+ const char* errorMessage;
+ uint32_t errorFlag;
+ bool sameOrigin = true;
+
+ bool subsumed;
+ result = mTriggeringPrincipal->Subsumes(principal, &subsumed);
+ if (NS_FAILED(result) || !subsumed) {
+ sameOrigin = false;
+ }
+
+ if (sameOrigin && mCompatMode == eCompatibility_NavQuirks) {
+ errorMessage = "MimeNotCssWarn";
+ errorFlag = nsIScriptError::warningFlag;
+ } else {
+ errorMessage = "MimeNotCss";
+ errorFlag = nsIScriptError::errorFlag;
+ }
+
+ AutoTArray<nsString, 2> strings;
+ CopyUTF8toUTF16(channelURI->GetSpecOrDefault(), *strings.AppendElement());
+ CopyASCIItoUTF16(contentType, *strings.AppendElement());
+
+ nsCOMPtr<nsIURI> referrer = ReferrerInfo()->GetOriginalReferrer();
+ nsContentUtils::ReportToConsole(
+ errorFlag, "CSS Loader"_ns, mLoader->mDocument,
+ nsContentUtils::eCSS_PROPERTIES, errorMessage, strings, referrer);
+
+ if (errorFlag == nsIScriptError::errorFlag) {
+ LOG_WARN(
+ (" Ignoring sheet with improper MIME type %s", contentType.get()));
+ mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
+ return NS_OK;
+ }
+ }
+
+ SRIMetadata sriMetadata;
+ mSheet->GetIntegrity(sriMetadata);
+ if (!sriMetadata.IsEmpty()) {
+ nsAutoCString sourceUri;
+ if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
+ mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ nsresult rv = VerifySheetIntegrity(sriMetadata, aChannel, aBytes1, aBytes2,
+ sourceUri, mLoader->mReporter);
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ mLoader->mReporter->FlushConsoleReports(loadGroup);
+ } else {
+ mLoader->mReporter->FlushConsoleReports(mLoader->mDocument);
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG((" Load was blocked by SRI"));
+ MOZ_LOG(gSriPRLog, LogLevel::Debug,
+ ("css::Loader::OnStreamComplete, bad metadata"));
+ mLoader->SheetComplete(*this, NS_ERROR_SRI_CORRUPT);
+ return NS_OK;
+ }
+ }
+
+ // Enough to set the URIs on mSheet, since any sibling datas we have share
+ // the same mInner as mSheet and will thus get the same URI.
+ mSheet->SetURIs(channelURI, originalURI, channelURI);
+
+ ReferrerPolicy policy =
+ nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateForExternalCSSResources(mSheet, policy);
+
+ mSheet->SetReferrerInfo(referrerInfo);
+ return NS_OK_PARSE_SHEET;
+}
+
+Loader::IsAlternate Loader::IsAlternateSheet(const nsAString& aTitle,
+ bool aHasAlternateRel) {
+ // A sheet is alternate if it has a nonempty title that doesn't match the
+ // currently selected style set. But if there _is_ no currently selected
+ // style set, the sheet wasn't marked as an alternate explicitly, and aTitle
+ // is nonempty, we should select the style set corresponding to aTitle, since
+ // that's a preferred sheet.
+ if (aTitle.IsEmpty()) {
+ return IsAlternate::No;
+ }
+
+ if (mDocument) {
+ const nsString& currentSheetSet = mDocument->GetCurrentStyleSheetSet();
+ if (!aHasAlternateRel && currentSheetSet.IsEmpty()) {
+ // There's no preferred set yet, and we now have a sheet with a title.
+ // Make that be the preferred set.
+ // FIXME(emilio): This is kinda wild, can we do it somewhere else?
+ mDocument->SetPreferredStyleSheetSet(aTitle);
+ // We're definitely not an alternate. Also, beware that at this point
+ // currentSheetSet may dangle.
+ return IsAlternate::No;
+ }
+
+ if (aTitle.Equals(currentSheetSet)) {
+ return IsAlternate::No;
+ }
+ }
+
+ return IsAlternate::Yes;
+}
+
+nsresult Loader::CheckContentPolicy(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIURI* aTargetURI,
+ nsINode* aRequestingNode,
+ const nsAString& aNonce,
+ StylePreloadKind aPreloadKind) {
+ // When performing a system load don't consult content policies.
+ if (!mDocument) {
+ return NS_OK;
+ }
+
+ nsContentPolicyType contentPolicyType =
+ aPreloadKind == StylePreloadKind::None
+ ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
+ : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
+
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aRequestingNode,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);
+ secCheckLoadInfo->SetCspNonce(aNonce);
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv =
+ NS_CheckContentLoadPolicy(aTargetURI, secCheckLoadInfo, &shouldLoad,
+ nsContentUtils::GetContentPolicy());
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ // Asynchronously notify observers (e.g devtools) of CSP failure.
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ "Loader::NotifyOnFailedCheckPolicy",
+ [targetURI = RefPtr<nsIURI>(aTargetURI),
+ requestingNode = RefPtr<nsINode>(aRequestingNode),
+ contentPolicyType]() {
+ nsCOMPtr<nsIChannel> channel;
+ NS_NewChannel(
+ getter_AddRefs(channel), targetURI, requestingNode,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType);
+ NS_SetRequestBlockingReason(
+ channel, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_GENERAL);
+ nsCOMPtr<nsIObserverService> obsService =
+ services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(
+ channel, "http-on-failed-opening-request", nullptr);
+ }
+ }));
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+ return NS_OK;
+}
+
+static void RecordUseCountersIfNeeded(Document* aDoc,
+ const StyleSheet& aSheet) {
+ if (!aDoc) {
+ return;
+ }
+ const StyleUseCounters* docCounters = aDoc->GetStyleUseCounters();
+ if (!docCounters) {
+ return;
+ }
+ if (aSheet.URLData()->ChromeRulesEnabled()) {
+ return;
+ }
+ const auto* sheetCounters = aSheet.GetStyleUseCounters();
+ if (!sheetCounters) {
+ return;
+ }
+ Servo_UseCounters_Merge(docCounters, sheetCounters);
+ aDoc->MaybeWarnAboutZoom();
+}
+
+/**
+ * CreateSheet() creates a StyleSheet object for the given URI.
+ *
+ * We check for an existing style sheet object for that uri in various caches
+ * and clone it if we find it. Cloned sheets will have the title/media/enabled
+ * state of the sheet they are clones off; make sure to call PrepareSheet() on
+ * the result of CreateSheet().
+ */
+std::tuple<RefPtr<StyleSheet>, Loader::SheetState> Loader::CreateSheet(
+ nsIURI* aURI, nsIContent* aLinkingContent,
+ nsIPrincipal* aTriggeringPrincipal, css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode, const Encoding* aPreloadOrParentDataEncoding,
+ const nsAString& aIntegrity, bool aSyncLoad,
+ StylePreloadKind aPreloadKind) {
+ MOZ_ASSERT(aURI, "This path is not taken for inline stylesheets");
+ LOG(("css::Loader::CreateSheet(%s)", aURI->GetSpecOrDefault().get()));
+
+ SRIMetadata sriMetadata;
+ if (!aIntegrity.IsEmpty()) {
+ MOZ_LOG(gSriPRLog, LogLevel::Debug,
+ ("css::Loader::CreateSheet, integrity=%s",
+ NS_ConvertUTF16toUTF8(aIntegrity).get()));
+ nsAutoCString sourceUri;
+ if (mDocument && mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
+ }
+
+ if (mSheets) {
+ SheetLoadDataHashKey key(aURI, aTriggeringPrincipal, LoaderPrincipal(),
+ PartitionedPrincipal(),
+ GetFallbackEncoding(*this, aLinkingContent,
+ aPreloadOrParentDataEncoding),
+ aCORSMode, aParsingMode, CompatMode(aPreloadKind),
+ sriMetadata, aPreloadKind);
+ auto cacheResult = mSheets->Lookup(*this, key, aSyncLoad);
+ if (cacheResult.mState != CachedSubResourceState::Miss) {
+ SheetState sheetState = SheetState::Complete;
+ RefPtr<StyleSheet> sheet;
+ if (cacheResult.mCompleteValue) {
+ sheet = cacheResult.mCompleteValue->Clone(nullptr, nullptr);
+ mDocument->SetDidHitCompleteSheetCache();
+ RecordUseCountersIfNeeded(mDocument, *sheet);
+ mLoadsPerformed.PutEntry(key);
+ } else {
+ MOZ_ASSERT(cacheResult.mLoadingOrPendingValue);
+ sheet = cacheResult.mLoadingOrPendingValue->ValueForCache();
+ sheetState = cacheResult.mState == CachedSubResourceState::Loading
+ ? SheetState::Loading
+ : SheetState::Pending;
+ }
+ LOG((" Hit cache with state: %s", gStateStrings[size_t(sheetState)]));
+ return {std::move(sheet), sheetState};
+ }
+ }
+
+ nsIURI* sheetURI = aURI;
+ nsIURI* baseURI = aURI;
+ nsIURI* originalURI = aURI;
+
+ auto sheet = MakeRefPtr<StyleSheet>(aParsingMode, aCORSMode, sriMetadata);
+ sheet->SetURIs(sheetURI, originalURI, baseURI);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateForExternalCSSResources(sheet);
+ sheet->SetReferrerInfo(referrerInfo);
+ LOG((" Needs parser"));
+ return {std::move(sheet), SheetState::NeedsParser};
+}
+
+static Loader::MediaMatched MediaListMatches(const MediaList* aMediaList,
+ const Document* aDocument) {
+ if (!aMediaList || !aDocument) {
+ return Loader::MediaMatched::Yes;
+ }
+
+ if (aMediaList->Matches(*aDocument)) {
+ return Loader::MediaMatched::Yes;
+ }
+
+ return Loader::MediaMatched::No;
+}
+
+/**
+ * PrepareSheet() handles setting the media and title on the sheet, as
+ * well as setting the enabled state based on the title and whether
+ * the sheet had "alternate" in its rel.
+ */
+Loader::MediaMatched Loader::PrepareSheet(
+ StyleSheet& aSheet, const nsAString& aTitle, const nsAString& aMediaString,
+ MediaList* aMediaList, IsAlternate aIsAlternate,
+ IsExplicitlyEnabled aIsExplicitlyEnabled) {
+ RefPtr<MediaList> mediaList(aMediaList);
+
+ if (!aMediaString.IsEmpty()) {
+ NS_ASSERTION(!aMediaList,
+ "must not provide both aMediaString and aMediaList");
+ mediaList = MediaList::Create(NS_ConvertUTF16toUTF8(aMediaString));
+ }
+
+ aSheet.SetMedia(do_AddRef(mediaList));
+
+ aSheet.SetTitle(aTitle);
+ aSheet.SetEnabled(aIsAlternate == IsAlternate::No ||
+ aIsExplicitlyEnabled == IsExplicitlyEnabled::Yes);
+ return MediaListMatches(mediaList, mDocument);
+}
+
+/**
+ * InsertSheetInTree handles ordering of sheets in the document or shadow root.
+ *
+ * Here we have two types of sheets -- those with linking elements and
+ * those without. The latter are loaded by Link: headers, and are only added to
+ * the document.
+ *
+ * The following constraints are observed:
+ * 1) Any sheet with a linking element comes after all sheets without
+ * linking elements
+ * 2) Sheets without linking elements are inserted in the order in
+ * which the inserting requests come in, since all of these are
+ * inserted during header data processing in the content sink
+ * 3) Sheets with linking elements are ordered based on document order
+ * as determined by CompareDocumentPosition.
+ */
+void Loader::InsertSheetInTree(StyleSheet& aSheet) {
+ LOG(("css::Loader::InsertSheetInTree"));
+ MOZ_ASSERT(mDocument, "Must have a document to insert into");
+
+ nsINode* owningNode = aSheet.GetOwnerNode();
+ MOZ_ASSERT(!owningNode || owningNode->IsInUncomposedDoc() ||
+ owningNode->IsInShadowTree(),
+ "Why would we insert it anywhere?");
+ ShadowRoot* shadow = owningNode ? owningNode->GetContainingShadow() : nullptr;
+
+ auto& target = shadow ? static_cast<DocumentOrShadowRoot&>(*shadow)
+ : static_cast<DocumentOrShadowRoot&>(*mDocument);
+
+ // XXX Need to cancel pending sheet loads for this element, if any
+
+ int32_t sheetCount = target.SheetCount();
+
+ /*
+ * Start the walk at the _end_ of the list, since in the typical
+ * case we'll just want to append anyway. We want to break out of
+ * the loop when insertionPoint points to just before the index we
+ * want to insert at. In other words, when we leave the loop
+ * insertionPoint is the index of the stylesheet that immediately
+ * precedes the one we're inserting.
+ */
+ int32_t insertionPoint = sheetCount - 1;
+ for (; insertionPoint >= 0; --insertionPoint) {
+ nsINode* sheetOwner = target.SheetAt(insertionPoint)->GetOwnerNode();
+ if (sheetOwner && !owningNode) {
+ // Keep moving; all sheets with a sheetOwner come after all
+ // sheets without a linkingNode
+ continue;
+ }
+
+ if (!sheetOwner) {
+ // Aha! The current sheet has no sheet owner, so we want to insert after
+ // it no matter whether we have a linking content or not.
+ break;
+ }
+
+ MOZ_ASSERT(owningNode != sheetOwner, "Why do we still have our old sheet?");
+
+ // Have to compare
+ if (nsContentUtils::PositionIsBefore(sheetOwner, owningNode)) {
+ // The current sheet comes before us, and it better be the first
+ // such, because now we break
+ break;
+ }
+ }
+
+ ++insertionPoint;
+
+ if (shadow) {
+ shadow->InsertSheetAt(insertionPoint, aSheet);
+ } else {
+ mDocument->InsertSheetAt(insertionPoint, aSheet);
+ }
+
+ LOG((" Inserting into target (doc: %d) at position %d",
+ target.AsNode().IsDocument(), insertionPoint));
+}
+
+/**
+ * InsertChildSheet handles ordering of @import-ed sheet in their
+ * parent sheets. Here we want to just insert based on order of the
+ * @import rules that imported the sheets. In theory we can't just
+ * append to the end because the CSSOM can insert @import rules. In
+ * practice, we get the call to load the child sheet before the CSSOM
+ * has finished inserting the @import rule, so we have no idea where
+ * to put it anyway. So just append for now. (In the future if we
+ * want to insert the sheet at the correct position, we'll need to
+ * restore CSSStyleSheet::InsertStyleSheetAt, which was removed in
+ * bug 1220506.)
+ */
+void Loader::InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet) {
+ LOG(("css::Loader::InsertChildSheet"));
+
+ // child sheets should always start out enabled, even if they got
+ // cloned off of top-level sheets which were disabled
+ aSheet.SetEnabled(true);
+ aParentSheet.AppendStyleSheet(aSheet);
+
+ LOG((" Inserting into parent sheet"));
+}
+
+nsresult Loader::LoadSheetSyncInternal(SheetLoadData& aLoadData,
+ SheetState aSheetState) {
+ LOG((" Synchronous load"));
+ MOZ_ASSERT(!aLoadData.mObserver, "Observer for a sync load?");
+ MOZ_ASSERT(aSheetState == SheetState::NeedsParser,
+ "Sync loads can't reuse existing async loads");
+
+ nsINode* requestingNode = aLoadData.GetRequestingNode();
+
+ nsresult rv = NS_OK;
+
+ // Create a StreamLoader instance to which we will feed
+ // the data from the sync load. Do this before creating the
+ // channel to make error recovery simpler.
+ auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
+
+ if (mDocument) {
+ net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument);
+ }
+
+ // Synchronous loads should only be used internally. Therefore no CORS
+ // policy is needed.
+ nsSecurityFlags securityFlags =
+ nsContentSecurityManager::ComputeSecurityFlags(
+ CORSMode::CORS_NONE, nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_INHERITED_CONTEXT);
+
+ securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
+
+ nsContentPolicyType contentPolicyType =
+ aLoadData.mPreloadKind == StylePreloadKind::None
+ ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
+ : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
+
+ // Just load it
+ nsCOMPtr<nsIChannel> channel;
+ // Note that we are calling NS_NewChannelWithTriggeringPrincipal() with both
+ // a node and a principal.
+ // This is because of a case where the node is the document being styled and
+ // the principal is the stylesheet (perhaps from a different origin) that is
+ // applying the styles.
+ if (requestingNode) {
+ rv = NS_NewChannelWithTriggeringPrincipal(
+ getter_AddRefs(channel), aLoadData.mURI, requestingNode,
+ aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
+ } else {
+ MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
+ auto result = URLPreloader::ReadURI(aLoadData.mURI);
+ if (result.isOk()) {
+ nsCOMPtr<nsIInputStream> stream;
+ MOZ_TRY(
+ NS_NewCStringInputStream(getter_AddRefs(stream), result.unwrap()));
+
+ rv = NS_NewInputStreamChannel(
+ getter_AddRefs(channel), aLoadData.mURI, stream.forget(),
+ aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
+ } else {
+ rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
+ aLoadData.mTriggeringPrincipal, securityFlags,
+ contentPolicyType);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to create channel"));
+ streamLoader->ChannelOpenFailed(rv);
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ loadInfo->SetCspNonce(aLoadData.Nonce());
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open(getter_AddRefs(stream));
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to open URI synchronously"));
+ streamLoader->ChannelOpenFailed(rv);
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ // Force UA sheets to be UTF-8.
+ // XXX this is only necessary because the default in
+ // SheetLoadData::OnDetermineCharset is wrong (bug 521039).
+ channel->SetContentCharset("UTF-8"_ns);
+
+ // Manually feed the streamloader the contents of the stream.
+ // This will call back into OnStreamComplete
+ // and thence to ParseSheet. Regardless of whether this fails,
+ // SheetComplete has been called.
+ return nsSyncLoadService::PushSyncStreamToListener(stream.forget(),
+ streamLoader, channel);
+}
+
+bool Loader::MaybeDeferLoad(SheetLoadData& aLoadData, SheetState aSheetState,
+ PendingLoad aPendingLoad,
+ const SheetLoadDataHashKey& aKey) {
+ MOZ_ASSERT(mSheets);
+
+ // If we have at least one other load ongoing, then we can defer it until
+ // all non-pending loads are done.
+ if (aSheetState == SheetState::NeedsParser &&
+ aPendingLoad == PendingLoad::No && aLoadData.ShouldDefer() &&
+ mOngoingLoadCount > mPendingLoadCount + 1) {
+ LOG((" Deferring sheet load"));
+ ++mPendingLoadCount;
+ mSheets->DeferLoad(aKey, aLoadData);
+ return true;
+ }
+ return false;
+}
+
+bool Loader::MaybeCoalesceLoadAndNotifyOpen(SheetLoadData& aLoadData,
+ SheetState aSheetState,
+ const SheetLoadDataHashKey& aKey,
+ const PreloadHashKey& aPreloadKey) {
+ bool coalescedLoad = false;
+ auto cacheState = [&aSheetState] {
+ switch (aSheetState) {
+ case SheetState::Complete:
+ return CachedSubResourceState::Complete;
+ case SheetState::Pending:
+ return CachedSubResourceState::Pending;
+ case SheetState::Loading:
+ return CachedSubResourceState::Loading;
+ case SheetState::NeedsParser:
+ return CachedSubResourceState::Miss;
+ }
+ MOZ_ASSERT_UNREACHABLE("wat");
+ return CachedSubResourceState::Miss;
+ }();
+
+ if ((coalescedLoad = mSheets->CoalesceLoad(aKey, aLoadData, cacheState))) {
+ if (aSheetState == SheetState::Pending) {
+ ++mPendingLoadCount;
+ } else {
+ aLoadData.NotifyOpen(
+ aPreloadKey, mDocument,
+ aLoadData.IsLinkRelPreload() /* TODO: why not `IsPreload()`?*/);
+ }
+ }
+ return coalescedLoad;
+}
+
+/**
+ * LoadSheet handles the actual load of a sheet. If the load is
+ * supposed to be synchronous it just opens a channel synchronously
+ * using the given uri, wraps the resulting stream in a converter
+ * stream and calls ParseSheet. Otherwise it tries to look for an
+ * existing load for this URI and piggyback on it. Failing all that,
+ * a new load is kicked off asynchronously.
+ */
+nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
+ uint64_t aEarlyHintPreloaderId,
+ PendingLoad aPendingLoad) {
+ LOG(("css::Loader::LoadSheet"));
+ MOZ_ASSERT(aLoadData.mURI, "Need a URI to load");
+ MOZ_ASSERT(aLoadData.mSheet, "Need a sheet to load into");
+ MOZ_ASSERT(aSheetState != SheetState::Complete, "Why bother?");
+ MOZ_ASSERT(!aLoadData.mUseSystemPrincipal || aLoadData.mSyncLoad,
+ "Shouldn't use system principal for async loads");
+
+ LOG_URI(" Load from: '%s'", aLoadData.mURI);
+
+ // If we're firing a pending load, this load is already accounted for the
+ // first time it went through this function.
+ if (aPendingLoad == PendingLoad::No) {
+ if (aLoadData.BlocksLoadEvent()) {
+ IncrementOngoingLoadCountAndMaybeBlockOnload();
+ }
+
+ // We technically never defer non-top-level sheets, so this condition could
+ // be outside the branch, but conceptually it should be here.
+ if (aLoadData.mParentData) {
+ ++aLoadData.mParentData->mPendingChildren;
+ }
+ }
+
+ if (!mDocument && !aLoadData.mIsNonDocumentSheet) {
+ // No point starting the load; just release all the data and such.
+ LOG_WARN((" No document and not non-document sheet; pre-dropping load"));
+ SheetComplete(aLoadData, NS_BINDING_ABORTED);
+ return NS_BINDING_ABORTED;
+ }
+
+ if (aLoadData.mSyncLoad) {
+ return LoadSheetSyncInternal(aLoadData, aSheetState);
+ }
+
+ SheetLoadDataHashKey key(aLoadData);
+
+ auto preloadKey = PreloadHashKey::CreateAsStyle(aLoadData);
+ if (mSheets) {
+ if (MaybeDeferLoad(aLoadData, aSheetState, aPendingLoad, key)) {
+ return NS_OK;
+ }
+
+ if (MaybeCoalesceLoadAndNotifyOpen(aLoadData, aSheetState, key,
+ preloadKey)) {
+ // All done here; once the load completes we'll be marked complete
+ // automatically.
+ return NS_OK;
+ }
+ }
+
+ aLoadData.NotifyOpen(preloadKey, mDocument, aLoadData.IsLinkRelPreload());
+
+ return LoadSheetAsyncInternal(aLoadData, aEarlyHintPreloaderId, key);
+}
+
+void Loader::AdjustPriority(const SheetLoadData& aLoadData,
+ nsIChannel* aChannel) {
+ if (!StaticPrefs::network_fetchpriority_enabled()) {
+ if (!aLoadData.ShouldDefer() && aLoadData.IsLinkRelPreload()) {
+ SheetLoadData::PrioritizeAsPreload(aChannel);
+ }
+
+ return;
+ }
+
+ nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(aChannel);
+
+ if (!sp) {
+ return;
+ }
+
+ // Adjusting priorites is specified as implementation-defined. To align with
+ // other browsers for potentially important cases, some adjustments are made
+ // according to
+ // <https://web.dev/articles/fetch-priority#browser_priority_and_fetchpriority>.
+ const int32_t supportsPriority = [&]() {
+ if (aLoadData.ShouldDefer()) {
+ return nsISupportsPriority::PRIORITY_LOW;
+ }
+ switch (aLoadData.mFetchPriority) {
+ case FetchPriority::Auto: {
+ return nsISupportsPriority::PRIORITY_HIGHEST;
+ }
+ case FetchPriority::High: {
+ return nsISupportsPriority::PRIORITY_HIGHEST;
+ }
+ case FetchPriority::Low: {
+ return nsISupportsPriority::PRIORITY_HIGH;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE();
+ return nsISupportsPriority::PRIORITY_HIGHEST;
+ }();
+
+ LogPriorityMapping(sCssLoaderLog, aLoadData.mFetchPriority, supportsPriority);
+ sp->SetPriority(supportsPriority);
+}
+
+nsresult Loader::LoadSheetAsyncInternal(SheetLoadData& aLoadData,
+ uint64_t aEarlyHintPreloaderId,
+ const SheetLoadDataHashKey& aKey) {
+ nsresult rv = NS_OK;
+
+ SRIMetadata sriMetadata;
+ aLoadData.mSheet->GetIntegrity(sriMetadata);
+
+ nsINode* requestingNode = aLoadData.GetRequestingNode();
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ if (mDocument) {
+ loadGroup = mDocument->GetDocumentLoadGroup();
+ // load for a document with no loadgrup indicates that something is
+ // completely bogus, let's bail out early.
+ if (!loadGroup) {
+ LOG_ERROR((" Failed to query loadGroup from document"));
+ SheetComplete(aLoadData, NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ cookieJarSettings = mDocument->CookieJarSettings();
+ }
+
+#ifdef DEBUG
+ AutoRestore<bool> syncCallbackGuard(mSyncCallback);
+ mSyncCallback = true;
+#endif
+
+ nsSecurityFlags securityFlags =
+ nsContentSecurityManager::ComputeSecurityFlags(
+ aLoadData.mSheet->GetCORSMode(),
+ nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_INHERITED_CONTEXT);
+
+ securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
+
+ nsContentPolicyType contentPolicyType =
+ aLoadData.mPreloadKind == StylePreloadKind::None
+ ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
+ : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
+
+ nsCOMPtr<nsIChannel> channel;
+ // Note we are calling NS_NewChannelWithTriggeringPrincipal here with a node
+ // and a principal. This is because of a case where the node is the document
+ // being styled and the principal is the stylesheet (perhaps from a different
+ // origin) that is applying the styles.
+ if (requestingNode) {
+ rv = NS_NewChannelWithTriggeringPrincipal(
+ getter_AddRefs(channel), aLoadData.mURI, requestingNode,
+ aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType,
+ /* PerformanceStorage */ nullptr, loadGroup);
+ } else {
+ MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
+ rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
+ aLoadData.mTriggeringPrincipal, securityFlags,
+ contentPolicyType, cookieJarSettings,
+ /* aPerformanceStorage */ nullptr, loadGroup);
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to create channel"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ loadInfo->SetCspNonce(aLoadData.Nonce());
+
+ if (!aLoadData.ShouldDefer()) {
+ if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
+ cos->AddClassFlags(nsIClassOfService::Leader);
+ }
+
+ if (aLoadData.IsLinkRelPreload()) {
+ SheetLoadData::AddLoadBackgroundFlag(channel);
+ }
+ }
+
+ AdjustPriority(aLoadData, channel);
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
+ if (nsCOMPtr<nsIReferrerInfo> referrerInfo = aLoadData.ReferrerInfo()) {
+ rv = httpChannel->SetReferrerInfo(referrerInfo);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(httpChannel);
+ if (internalChannel) {
+ rv = internalChannel->SetIntegrityMetadata(
+ sriMetadata.GetIntegrityString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set the initiator type
+ if (nsCOMPtr<nsITimedChannel> timedChannel =
+ do_QueryInterface(httpChannel)) {
+ if (aLoadData.mParentData) {
+ timedChannel->SetInitiatorType(u"css"_ns);
+
+ // This is a child sheet load.
+ //
+ // The resource timing of the sub-resources that a document loads
+ // should normally be reported to the document. One exception is any
+ // sub-resources of any cross-origin resources that are loaded. We
+ // don't mind reporting timing data for a direct child cross-origin
+ // resource since the resource that linked to it (and hence potentially
+ // anything in that parent origin) is aware that the cross-origin
+ // resources is to be loaded. However, we do not want to report
+ // timings for any sub-resources that a cross-origin resource may load
+ // since that obviously leaks information about what the cross-origin
+ // resource loads, which is bad.
+ //
+ // In addition to checking whether we're an immediate child resource of
+ // a cross-origin resource (by checking if mIsCrossOriginNoCORS is set
+ // to true on our parent), we also check our parent to see whether it
+ // itself is a sub-resource of a cross-origin resource by checking
+ // mBlockResourceTiming. If that is set then we too are such a
+ // sub-resource and so we set the flag on ourself too to propagate it
+ // on down.
+ if (aLoadData.mParentData->mIsCrossOriginNoCORS ||
+ aLoadData.mParentData->mBlockResourceTiming) {
+ // Set a flag so any other stylesheet triggered by this one will
+ // not be reported
+ aLoadData.mBlockResourceTiming = true;
+
+ // Mark the channel so PerformanceMainThread::AddEntry will not
+ // report the resource.
+ timedChannel->SetReportResourceTiming(false);
+ }
+
+ } else if (aEarlyHintPreloaderId) {
+ timedChannel->SetInitiatorType(u"early-hints"_ns);
+ } else {
+ timedChannel->SetInitiatorType(u"link"_ns);
+ }
+ }
+ }
+
+ // Now tell the channel we expect text/css data back.... We do
+ // this before opening it, so it's only treated as a hint.
+ channel->SetContentType("text/css"_ns);
+
+ // We don't have to hold on to the stream loader. The ownership
+ // model is: Necko owns the stream loader, which owns the load data,
+ // which owns us
+ auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
+ if (mDocument) {
+ net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument);
+ }
+
+ if (aEarlyHintPreloaderId) {
+ nsCOMPtr<nsIHttpChannelInternal> channelInternal =
+ do_QueryInterface(channel);
+ NS_ENSURE_TRUE(channelInternal != nullptr, NS_ERROR_FAILURE);
+
+ rv = channelInternal->SetEarlyHintPreloaderId(aEarlyHintPreloaderId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = channel->AsyncOpen(streamLoader);
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to create stream loader"));
+ streamLoader->ChannelOpenFailed(rv);
+ // NOTE: NotifyStop will be done in SheetComplete -> NotifyObservers.
+ aLoadData.NotifyStart(channel);
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (nsCOMPtr<nsIHttpChannelInternal> hci = do_QueryInterface(channel)) {
+ hci->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
+ }
+#endif
+
+ if (mSheets) {
+ mSheets->LoadStarted(aKey, aLoadData);
+ }
+ return NS_OK;
+}
+
+/**
+ * ParseSheet handles parsing the data stream.
+ */
+Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
+ SheetLoadData& aLoadData,
+ AllowAsyncParse aAllowAsync) {
+ LOG(("css::Loader::ParseSheet"));
+ if (aLoadData.mURI) {
+ LOG_URI(" Load succeeded for URI: '%s', parsing", aLoadData.mURI);
+ }
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR_RELEVANT_FOR_JS(LAYOUT_CSSParsing);
+
+ ++mParsedSheetCount;
+
+ aLoadData.mIsBeingParsed = true;
+
+ StyleSheet* sheet = aLoadData.mSheet;
+ MOZ_ASSERT(sheet);
+
+ // Some cases, like inline style and UA stylesheets, need to be parsed
+ // synchronously. The former may trigger child loads, the latter must not.
+ if (aLoadData.mSyncLoad || aAllowAsync == AllowAsyncParse::No) {
+ sheet->ParseSheetSync(this, aBytes, &aLoadData);
+ aLoadData.mIsBeingParsed = false;
+
+ bool noPendingChildren = aLoadData.mPendingChildren == 0;
+ MOZ_ASSERT_IF(aLoadData.mSyncLoad, noPendingChildren);
+ if (noPendingChildren) {
+ SheetComplete(aLoadData, NS_OK);
+ return Completed::Yes;
+ }
+ return Completed::No;
+ }
+
+ // This parse does not need to be synchronous. \o/
+ //
+ // Note that load is already blocked from
+ // IncrementOngoingLoadCountAndMaybeBlockOnload(), and will be unblocked from
+ // SheetFinishedParsingAsync which will end up in NotifyObservers as needed.
+ sheet->ParseSheet(*this, aBytes, aLoadData)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [loadData = RefPtr<SheetLoadData>(&aLoadData)](bool aDummy) {
+ MOZ_ASSERT(NS_IsMainThread());
+ loadData->SheetFinishedParsingAsync();
+ },
+ [] { MOZ_CRASH("rejected parse promise"); });
+ return Completed::No;
+}
+
+void Loader::NotifyObservers(SheetLoadData& aData, nsresult aStatus) {
+ RecordUseCountersIfNeeded(mDocument, *aData.mSheet);
+ RefPtr loadDispatcher = aData.PrepareLoadEventIfNeeded();
+ if (aData.mURI) {
+ mLoadsPerformed.PutEntry(SheetLoadDataHashKey(aData));
+ aData.NotifyStop(aStatus);
+ // NOTE(emilio): This needs to happen before notifying observers, as
+ // FontFaceSet for example checks for pending sheet loads from the
+ // StyleSheetLoaded callback.
+ if (aData.BlocksLoadEvent()) {
+ DecrementOngoingLoadCountAndMaybeUnblockOnload();
+ if (mPendingLoadCount && mPendingLoadCount == mOngoingLoadCount) {
+ LOG((" No more loading sheets; starting deferred loads"));
+ StartDeferredLoads();
+ }
+ }
+ }
+ if (!aData.mTitle.IsEmpty() && NS_SUCCEEDED(aStatus)) {
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ "Loader::NotifyObservers - Create PageStyle actor",
+ [doc = RefPtr{mDocument}] {
+ // Force creating the page style actor, if available.
+ // This will no-op if no actor with this name is registered (outside
+ // of desktop Firefox).
+ nsCOMPtr<nsISupports> pageStyleActor =
+ do_QueryActor("PageStyle", doc);
+ Unused << pageStyleActor;
+ }));
+ }
+ if (aData.mMustNotify) {
+ if (nsCOMPtr<nsICSSLoaderObserver> observer = std::move(aData.mObserver)) {
+ LOG((" Notifying observer %p for data %p. deferred: %d", observer.get(),
+ &aData, aData.ShouldDefer()));
+ observer->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(), aStatus);
+ }
+
+ for (nsCOMPtr<nsICSSLoaderObserver> obs : mObservers.ForwardRange()) {
+ LOG((" Notifying global observer %p for data %p. deferred: %d",
+ obs.get(), &aData, aData.ShouldDefer()));
+ obs->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(), aStatus);
+ }
+
+ if (loadDispatcher) {
+ loadDispatcher->RunDOMEventWhenSafe();
+ }
+ } else if (loadDispatcher) {
+ loadDispatcher->PostDOMEvent();
+ }
+}
+
+/**
+ * SheetComplete is the do-it-all cleanup function. It removes the
+ * load data from the "loading" hashtable, adds the sheet to the
+ * "completed" hashtable, massages the XUL cache, handles siblings of
+ * the load data (other loads for the same URI), handles unblocking
+ * blocked parent loads as needed, and most importantly calls
+ * NS_RELEASE on the load data to destroy the whole mess.
+ */
+void Loader::SheetComplete(SheetLoadData& aLoadData, nsresult aStatus) {
+ LOG(("css::Loader::SheetComplete, status: 0x%" PRIx32,
+ static_cast<uint32_t>(aStatus)));
+ SharedStyleSheetCache::LoadCompleted(mSheets.get(), aLoadData, aStatus);
+}
+
+// static
+void Loader::MarkLoadTreeFailed(SheetLoadData& aLoadData,
+ Loader* aOnlyForLoader) {
+ if (aLoadData.mURI) {
+ LOG_URI(" Load failed: '%s'", aLoadData.mURI);
+ }
+
+ SheetLoadData* data = &aLoadData;
+ do {
+ if (!aOnlyForLoader || aOnlyForLoader == data->mLoader) {
+ data->mLoadFailed = true;
+ data->mSheet->MaybeRejectReplacePromise();
+ }
+
+ if (data->mParentData) {
+ MarkLoadTreeFailed(*data->mParentData, aOnlyForLoader);
+ }
+
+ data = data->mNext;
+ } while (data);
+}
+
+RefPtr<StyleSheet> Loader::LookupInlineSheetInCache(
+ const nsAString& aBuffer, nsIPrincipal* aSheetPrincipal) {
+ auto result = mInlineSheets.Lookup(aBuffer);
+ if (!result) {
+ return nullptr;
+ }
+ StyleSheet* sheet = result.Data();
+ if (NS_WARN_IF(sheet->HasModifiedRules())) {
+ // Remove it now that we know that we're never going to use this stylesheet
+ // again.
+ result.Remove();
+ return nullptr;
+ }
+ if (NS_WARN_IF(!sheet->Principal()->Equals(aSheetPrincipal))) {
+ // If the sheet is going to have different access rights, don't return it
+ // from the cache.
+ return nullptr;
+ }
+ return sheet->Clone(nullptr, nullptr);
+}
+
+void Loader::MaybeNotifyPreloadUsed(SheetLoadData& aData) {
+ if (!mDocument) {
+ return;
+ }
+
+ auto key = PreloadHashKey::CreateAsStyle(aData);
+ RefPtr<PreloaderBase> preload = mDocument->Preloads().LookupPreload(key);
+ if (!preload) {
+ return;
+ }
+
+ preload->NotifyUsage(mDocument);
+}
+
+Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
+ const SheetInfo& aInfo, const nsAString& aBuffer,
+ nsICSSLoaderObserver* aObserver) {
+ LOG(("css::Loader::LoadInlineStyle"));
+ MOZ_ASSERT(aInfo.mContent);
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ }
+
+ if (!mDocument) {
+ return Err(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ MOZ_ASSERT(LinkStyle::FromNodeOrNull(aInfo.mContent),
+ "Element is not a style linking element!");
+
+ // Since we're not planning to load a URI, no need to hand a principal to the
+ // load data or to CreateSheet().
+
+ // Check IsAlternateSheet now, since it can mutate our document.
+ auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
+ LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
+
+ // Use the document's base URL so that @import in the inline sheet picks up
+ // the right base.
+ nsIURI* baseURI = aInfo.mContent->GetBaseURI();
+ nsIURI* sheetURI = aInfo.mContent->OwnerDoc()->GetDocumentURI();
+ nsIURI* originalURI = nullptr;
+
+ MOZ_ASSERT(aInfo.mIntegrity.IsEmpty());
+ nsIPrincipal* loadingPrincipal = LoaderPrincipal();
+ nsIPrincipal* principal = aInfo.mTriggeringPrincipal
+ ? aInfo.mTriggeringPrincipal.get()
+ : loadingPrincipal;
+ nsIPrincipal* sheetPrincipal = [&] {
+ // The triggering principal may be an expanded principal, which is safe to
+ // use for URL security checks, but not as the loader principal for a
+ // stylesheet. So treat this as principal inheritance, and downgrade if
+ // necessary.
+ //
+ // FIXME(emilio): Why doing this for inline sheets but not for links?
+ if (aInfo.mTriggeringPrincipal) {
+ return BasePrincipal::Cast(aInfo.mTriggeringPrincipal)
+ ->PrincipalToInherit();
+ }
+ return LoaderPrincipal();
+ }();
+
+ // We only cache sheets if in shadow trees, since regular document sheets are
+ // likely to be unique.
+ const bool isWorthCaching =
+ StaticPrefs::layout_css_inline_style_caching_always_enabled() ||
+ aInfo.mContent->IsInShadowTree();
+ RefPtr<StyleSheet> sheet;
+ if (isWorthCaching) {
+ sheet = LookupInlineSheetInCache(aBuffer, sheetPrincipal);
+ }
+ const bool isSheetFromCache = !!sheet;
+ if (!isSheetFromCache) {
+ sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
+ SRIMetadata{});
+ sheet->SetURIs(sheetURI, originalURI, baseURI);
+ nsIReferrerInfo* referrerInfo =
+ aInfo.mContent->OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources();
+ sheet->SetReferrerInfo(referrerInfo);
+ // We never actually load this, so just set its principal directly.
+ sheet->SetPrincipal(sheetPrincipal);
+ }
+
+ auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
+ isAlternate, aInfo.mIsExplicitlyEnabled);
+ if (auto* linkStyle = LinkStyle::FromNode(*aInfo.mContent)) {
+ linkStyle->SetStyleSheet(sheet);
+ }
+ MOZ_ASSERT(sheet->IsComplete() == isSheetFromCache);
+
+ Completed completed;
+ auto data = MakeRefPtr<SheetLoadData>(
+ this, aInfo.mTitle, /* aURI = */ nullptr, sheet, SyncLoad::No,
+ aInfo.mContent, isAlternate, matched, StylePreloadKind::None, aObserver,
+ principal, aInfo.mReferrerInfo, aInfo.mNonce, aInfo.mFetchPriority);
+ MOZ_ASSERT(data->GetRequestingNode() == aInfo.mContent);
+ if (isSheetFromCache) {
+ MOZ_ASSERT(sheet->IsComplete());
+ MOZ_ASSERT(sheet->GetOwnerNode() == aInfo.mContent);
+ completed = Completed::Yes;
+ InsertSheetInTree(*sheet);
+ NotifyOfCachedLoad(std::move(data));
+ } else {
+ // Parse completion releases the load data.
+ //
+ // Note that we need to parse synchronously, since the web expects that the
+ // effects of inline stylesheets are visible immediately (aside from
+ // @imports).
+ NS_ConvertUTF16toUTF8 utf8(aBuffer);
+ completed = ParseSheet(utf8, *data, AllowAsyncParse::No);
+ if (completed == Completed::Yes) {
+ if (isWorthCaching) {
+ mInlineSheets.InsertOrUpdate(aBuffer, std::move(sheet));
+ }
+ } else {
+ data->mMustNotify = true;
+ }
+ }
+
+ return LoadSheetResult{completed, isAlternate, matched};
+}
+
+Result<Loader::LoadSheetResult, nsresult> Loader::LoadStyleLink(
+ const SheetInfo& aInfo, nsICSSLoaderObserver* aObserver) {
+ MOZ_ASSERT(aInfo.mURI, "Must have URL to load");
+ LOG(("css::Loader::LoadStyleLink"));
+ LOG_URI(" Link uri: '%s'", aInfo.mURI);
+ LOG((" Link title: '%s'", NS_ConvertUTF16toUTF8(aInfo.mTitle).get()));
+ LOG((" Link media: '%s'", NS_ConvertUTF16toUTF8(aInfo.mMedia).get()));
+ LOG((" Link alternate rel: %d", aInfo.mHasAlternateRel));
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ }
+
+ if (!mDocument) {
+ return Err(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ MOZ_ASSERT_IF(aInfo.mContent,
+ aInfo.mContent->NodePrincipal() == mDocument->NodePrincipal());
+ nsIPrincipal* loadingPrincipal = LoaderPrincipal();
+ nsIPrincipal* principal = aInfo.mTriggeringPrincipal
+ ? aInfo.mTriggeringPrincipal.get()
+ : loadingPrincipal;
+
+ nsINode* requestingNode =
+ aInfo.mContent ? static_cast<nsINode*>(aInfo.mContent) : mDocument;
+ const bool syncLoad = [&] {
+ if (!aInfo.mContent) {
+ return false;
+ }
+ const bool privilegedShadowTree =
+ aInfo.mContent->IsInShadowTree() &&
+ (aInfo.mContent->ChromeOnlyAccess() ||
+ aInfo.mContent->OwnerDoc()->ChromeRulesEnabled());
+ if (!privilegedShadowTree) {
+ return false;
+ }
+ if (!IsPrivilegedURI(aInfo.mURI)) {
+ return false;
+ }
+ // We're loading a chrome/resource URI in a chrome doc shadow tree or UA
+ // widget. Load synchronously to avoid FOUC.
+ return true;
+ }();
+ LOG((" Link sync load: '%s'", syncLoad ? "true" : "false"));
+ MOZ_ASSERT_IF(syncLoad, !aObserver);
+
+ nsresult rv =
+ CheckContentPolicy(loadingPrincipal, principal, aInfo.mURI,
+ requestingNode, aInfo.mNonce, StylePreloadKind::None);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Don't fire the error event if our document is loaded as data. We're
+ // supposed to not even try to do loads in that case... Unfortunately, we
+ // implement that via nsDataDocumentContentPolicy, which doesn't have a good
+ // way to communicate back to us that _it_ is the thing that blocked the
+ // load.
+ if (aInfo.mContent && !mDocument->IsLoadedAsData()) {
+ // Fire an async error event on it.
+ RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
+ new LoadBlockingAsyncEventDispatcher(aInfo.mContent, u"error"_ns,
+ CanBubble::eNo,
+ ChromeOnlyDispatch::eNo);
+ loadBlockingAsyncDispatcher->PostDOMEvent();
+ }
+ return Err(rv);
+ }
+
+ // Check IsAlternateSheet now, since it can mutate our document and make
+ // pending sheets go to the non-pending state.
+ auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
+ auto [sheet, state] = CreateSheet(aInfo, eAuthorSheetFeatures, syncLoad,
+ StylePreloadKind::None);
+
+ LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
+
+ auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
+ isAlternate, aInfo.mIsExplicitlyEnabled);
+
+ if (auto* linkStyle = LinkStyle::FromNodeOrNull(aInfo.mContent)) {
+ linkStyle->SetStyleSheet(sheet);
+ }
+ MOZ_ASSERT(sheet->IsComplete() == (state == SheetState::Complete));
+
+ // We may get here with no content for Link: headers for example.
+ MOZ_ASSERT(!aInfo.mContent || LinkStyle::FromNode(*aInfo.mContent),
+ "If there is any node, it should be a LinkStyle");
+ auto data = MakeRefPtr<SheetLoadData>(
+ this, aInfo.mTitle, aInfo.mURI, sheet, SyncLoad(syncLoad), aInfo.mContent,
+ isAlternate, matched, StylePreloadKind::None, aObserver, principal,
+ aInfo.mReferrerInfo, aInfo.mNonce, aInfo.mFetchPriority);
+
+ MOZ_ASSERT(data->GetRequestingNode() == requestingNode);
+
+ MaybeNotifyPreloadUsed(*data);
+
+ if (state == SheetState::Complete) {
+ LOG((" Sheet already complete: 0x%p", sheet.get()));
+ MOZ_ASSERT(sheet->GetOwnerNode() == aInfo.mContent);
+ InsertSheetInTree(*sheet);
+ NotifyOfCachedLoad(std::move(data));
+ return LoadSheetResult{Completed::Yes, isAlternate, matched};
+ }
+
+ // Now we need to actually load it.
+ auto result = LoadSheetResult{Completed::No, isAlternate, matched};
+
+ MOZ_ASSERT(result.ShouldBlock() == !data->ShouldDefer(),
+ "These should better match!");
+
+ // Load completion will free the data
+ rv = LoadSheet(*data, state, 0);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ if (!syncLoad) {
+ data->mMustNotify = true;
+ }
+ return result;
+}
+
+static bool HaveAncestorDataWithURI(SheetLoadData& aData, nsIURI* aURI) {
+ if (!aData.mURI) {
+ // Inline style; this won't have any ancestors
+ MOZ_ASSERT(!aData.mParentData, "How does inline style have a parent?");
+ return false;
+ }
+
+ bool equal;
+ if (NS_FAILED(aData.mURI->Equals(aURI, &equal)) || equal) {
+ return true;
+ }
+
+ // Datas down the mNext chain have the same URI as aData, so we
+ // don't have to compare to them. But they might have different
+ // parents, and we have to check all of those.
+ SheetLoadData* data = &aData;
+ do {
+ if (data->mParentData &&
+ HaveAncestorDataWithURI(*data->mParentData, aURI)) {
+ return true;
+ }
+
+ data = data->mNext;
+ } while (data);
+
+ return false;
+}
+
+nsresult Loader::LoadChildSheet(StyleSheet& aParentSheet,
+ SheetLoadData* aParentData, nsIURI* aURL,
+ dom::MediaList* aMedia,
+ LoaderReusableStyleSheets* aReusableSheets) {
+ LOG(("css::Loader::LoadChildSheet"));
+ MOZ_ASSERT(aURL, "Must have a URI to load");
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG_URI(" Child uri: '%s'", aURL);
+
+ nsCOMPtr<nsINode> owningNode;
+
+ nsINode* requestingNode = aParentSheet.GetOwnerNodeOfOutermostSheet();
+ if (requestingNode) {
+ MOZ_ASSERT(LoaderPrincipal() == requestingNode->NodePrincipal());
+ } else {
+ requestingNode = mDocument;
+ }
+
+ nsIPrincipal* principal = aParentSheet.Principal();
+ nsresult rv =
+ CheckContentPolicy(LoaderPrincipal(), principal, aURL, requestingNode,
+ /* aNonce = */ u""_ns, StylePreloadKind::None);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (aParentData) {
+ MarkLoadTreeFailed(*aParentData);
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsICSSLoaderObserver> observer;
+
+ if (aParentData) {
+ LOG((" Have a parent load"));
+ // Check for cycles
+ if (HaveAncestorDataWithURI(*aParentData, aURL)) {
+ // Houston, we have a loop, blow off this child and pretend this never
+ // happened
+ LOG_ERROR((" @import cycle detected, dropping load"));
+ return NS_OK;
+ }
+
+ NS_ASSERTION(aParentData->mSheet == &aParentSheet,
+ "Unexpected call to LoadChildSheet");
+ } else {
+ LOG((" No parent load; must be CSSOM"));
+ // No parent load data, so the sheet will need to be notified when
+ // we finish, if it can be, if we do the load asynchronously.
+ observer = &aParentSheet;
+ }
+
+ // Now that we know it's safe to load this (passes security check and not a
+ // loop) do so.
+ RefPtr<StyleSheet> sheet;
+ SheetState state;
+ if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, sheet)) {
+ state = SheetState::Complete;
+ } else {
+ // For now, use CORS_NONE for child sheets
+ std::tie(sheet, state) = CreateSheet(
+ aURL, nullptr, principal, aParentSheet.ParsingMode(), CORS_NONE,
+ aParentData ? aParentData->mEncoding : nullptr,
+ u""_ns, // integrity is only checked on main sheet
+ aParentData && aParentData->mSyncLoad, StylePreloadKind::None);
+ PrepareSheet(*sheet, u""_ns, u""_ns, aMedia, IsAlternate::No,
+ IsExplicitlyEnabled::No);
+ }
+
+ MOZ_ASSERT(sheet);
+ InsertChildSheet(*sheet, aParentSheet);
+
+ auto data =
+ MakeRefPtr<SheetLoadData>(this, aURL, sheet, aParentData, observer,
+ principal, aParentSheet.GetReferrerInfo());
+ MOZ_ASSERT(data->GetRequestingNode() == requestingNode);
+
+ MaybeNotifyPreloadUsed(*data);
+
+ if (state == SheetState::Complete) {
+ LOG((" Sheet already complete"));
+ // We're completely done. No need to notify, even, since the
+ // @import rule addition/modification will trigger the right style
+ // changes automatically.
+ data->mIntentionallyDropped = true;
+ return NS_OK;
+ }
+
+ bool syncLoad = data->mSyncLoad;
+
+ // Load completion will release the data
+ rv = LoadSheet(*data, state, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!syncLoad) {
+ data->mMustNotify = true;
+ }
+ return rv;
+}
+
+Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheetSync(
+ nsIURI* aURL, SheetParsingMode aParsingMode,
+ UseSystemPrincipal aUseSystemPrincipal) {
+ LOG(("css::Loader::LoadSheetSync"));
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
+ return InternalLoadNonDocumentSheet(
+ aURL, StylePreloadKind::None, aParsingMode, aUseSystemPrincipal, nullptr,
+ referrerInfo, nullptr, CORS_NONE, u""_ns, u""_ns, 0, FetchPriority::Auto);
+}
+
+Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
+ nsIURI* aURI, SheetParsingMode aParsingMode,
+ UseSystemPrincipal aUseSystemPrincipal, nsICSSLoaderObserver* aObserver) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
+ return InternalLoadNonDocumentSheet(
+ aURI, StylePreloadKind::None, aParsingMode, aUseSystemPrincipal, nullptr,
+ referrerInfo, aObserver, CORS_NONE, u""_ns, u""_ns, 0,
+ FetchPriority::Auto);
+}
+
+Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
+ nsIURI* aURL, StylePreloadKind aPreloadKind,
+ const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo,
+ nsICSSLoaderObserver* aObserver, uint64_t aEarlyHintPreloaderId,
+ CORSMode aCORSMode, const nsAString& aNonce, const nsAString& aIntegrity,
+ FetchPriority aFetchPriority) {
+ LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
+ return InternalLoadNonDocumentSheet(
+ aURL, aPreloadKind, eAuthorSheetFeatures, UseSystemPrincipal::No,
+ aPreloadEncoding, aReferrerInfo, aObserver, aCORSMode, aNonce, aIntegrity,
+ aEarlyHintPreloaderId, aFetchPriority);
+}
+
+Result<RefPtr<StyleSheet>, nsresult> Loader::InternalLoadNonDocumentSheet(
+ nsIURI* aURL, StylePreloadKind aPreloadKind, SheetParsingMode aParsingMode,
+ UseSystemPrincipal aUseSystemPrincipal, const Encoding* aPreloadEncoding,
+ nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver,
+ CORSMode aCORSMode, const nsAString& aNonce, const nsAString& aIntegrity,
+ uint64_t aEarlyHintPreloaderId, FetchPriority aFetchPriority) {
+ MOZ_ASSERT(aURL, "Must have a URI to load");
+ MOZ_ASSERT(aUseSystemPrincipal == UseSystemPrincipal::No || !aObserver,
+ "Shouldn't load system-principal sheets async");
+ MOZ_ASSERT(aReferrerInfo, "Must have referrerInfo");
+
+ LOG_URI(" Non-document sheet uri: '%s'", aURL);
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ }
+
+ nsIPrincipal* loadingPrincipal = LoaderPrincipal();
+ nsIPrincipal* triggeringPrincipal = loadingPrincipal;
+ nsresult rv = CheckContentPolicy(loadingPrincipal, triggeringPrincipal, aURL,
+ mDocument, aNonce, aPreloadKind);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ bool syncLoad = !aObserver;
+ auto [sheet, state] =
+ CreateSheet(aURL, nullptr, triggeringPrincipal, aParsingMode, aCORSMode,
+ aPreloadEncoding, aIntegrity, syncLoad, aPreloadKind);
+
+ PrepareSheet(*sheet, u""_ns, u""_ns, nullptr, IsAlternate::No,
+ IsExplicitlyEnabled::No);
+
+ auto data = MakeRefPtr<SheetLoadData>(
+ this, aURL, sheet, SyncLoad(syncLoad), aUseSystemPrincipal, aPreloadKind,
+ aPreloadEncoding, aObserver, triggeringPrincipal, aReferrerInfo, aNonce,
+ aFetchPriority);
+ MOZ_ASSERT(data->GetRequestingNode() == mDocument);
+ if (state == SheetState::Complete) {
+ LOG((" Sheet already complete"));
+ NotifyOfCachedLoad(std::move(data));
+ return sheet;
+ }
+
+ rv = LoadSheet(*data, state, aEarlyHintPreloaderId);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+ if (aObserver) {
+ data->mMustNotify = true;
+ }
+ return sheet;
+}
+
+void Loader::NotifyOfCachedLoad(RefPtr<SheetLoadData> aLoadData) {
+ LOG(("css::Loader::PostLoadEvent"));
+ MOZ_ASSERT(aLoadData->mSheet->IsComplete(),
+ "Only expected to be used for cached sheets");
+ // If we get to this code, the stylesheet loaded correctly at some point, so
+ // we can just schedule a load event and don't need to touch the data's
+ // mLoadFailed.
+ // Note that we do this here and not from inside our SheetComplete so that we
+ // don't end up running the load event more async than needed.
+ MOZ_ASSERT(!aLoadData->mLoadFailed, "Why are we marked as failed?");
+ aLoadData->mSheetAlreadyComplete = true;
+
+ // We need to check mURI to match
+ // DecrementOngoingLoadCountAndMaybeUnblockOnload().
+ if (aLoadData->mURI && aLoadData->BlocksLoadEvent()) {
+ IncrementOngoingLoadCountAndMaybeBlockOnload();
+ }
+ SheetComplete(*aLoadData, NS_OK);
+}
+
+void Loader::Stop() {
+ if (mSheets) {
+ mSheets->CancelLoadsForLoader(*this);
+ }
+}
+
+bool Loader::HasPendingLoads() { return mOngoingLoadCount; }
+
+void Loader::AddObserver(nsICSSLoaderObserver* aObserver) {
+ MOZ_ASSERT(aObserver, "Must have observer");
+ mObservers.AppendElementUnlessExists(aObserver);
+}
+
+void Loader::RemoveObserver(nsICSSLoaderObserver* aObserver) {
+ mObservers.RemoveElement(aObserver);
+}
+
+void Loader::StartDeferredLoads() {
+ if (mSheets && mPendingLoadCount) {
+ mSheets->StartPendingLoadsForLoader(
+ *this, [](const SheetLoadData&) { return true; });
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Loader)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Loader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSheets);
+ for (const auto& data : tmp->mInlineSheets.Values()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "Inline sheet cache in Loader");
+ cb.NoteXPCOMChild(data);
+ }
+ for (nsCOMPtr<nsICSSLoaderObserver>& obs : tmp->mObservers.ForwardRange()) {
+ ImplCycleCollectionTraverse(cb, obs, "mozilla::css::Loader.mObservers");
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
+ if (tmp->mSheets) {
+ if (tmp->mDocument) {
+ tmp->DeregisterFromSheetCache();
+ }
+ tmp->mSheets = nullptr;
+ }
+ tmp->mInlineSheets.Clear();
+ tmp->mObservers.Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocGroup)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+size_t Loader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ n += mInlineSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& entry : mInlineSheets) {
+ n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ // If the sheet has a parent, then its parent will report it so we don't
+ // have to worry about it here.
+ const StyleSheet* sheet = entry.GetWeak();
+ MOZ_ASSERT(!sheet->GetParentSheet(),
+ "How did an @import rule end up here?");
+ if (!sheet->GetOwnerNode()) {
+ n += sheet->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // The following members aren't measured:
+ // - mDocument, because it's a weak backpointer
+
+ return n;
+}
+
+nsIPrincipal* Loader::LoaderPrincipal() const {
+ if (mDocument) {
+ return mDocument->NodePrincipal();
+ }
+ // Loaders without a document do system loads.
+ return nsContentUtils::GetSystemPrincipal();
+}
+
+nsIPrincipal* Loader::PartitionedPrincipal() const {
+ if (mDocument && StaticPrefs::privacy_partition_network_state()) {
+ return mDocument->PartitionedPrincipal();
+ }
+ return LoaderPrincipal();
+}
+
+bool Loader::ShouldBypassCache() const {
+ if (!mDocument) {
+ return false;
+ }
+ RefPtr<nsILoadGroup> lg = mDocument->GetDocumentLoadGroup();
+ if (!lg) {
+ return false;
+ }
+ nsLoadFlags flags;
+ if (NS_FAILED(lg->GetLoadFlags(&flags))) {
+ return false;
+ }
+ return flags & (nsIRequest::LOAD_BYPASS_CACHE |
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE);
+}
+
+void Loader::BlockOnload() {
+ if (mDocument) {
+ mDocument->BlockOnload();
+ }
+}
+
+void Loader::UnblockOnload(bool aFireSync) {
+ if (mDocument) {
+ mDocument->UnblockOnload(aFireSync);
+ }
+}
+
+} // namespace css
+} // namespace mozilla
diff --git a/layout/style/Loader.h b/layout/style/Loader.h
new file mode 100644
index 0000000000..ba60218e94
--- /dev/null
+++ b/layout/style/Loader.h
@@ -0,0 +1,665 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* loading of CSS style sheets using the network APIs */
+
+#ifndef mozilla_css_Loader_h
+#define mozilla_css_Loader_h
+
+#include <tuple>
+#include <utility>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/css/StylePreloadKind.h"
+#include "mozilla/dom/LinkStyle.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCompatibility.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsTObserverArray.h"
+#include "nsURIHashKey.h"
+#include "nsRefPtrHashtable.h"
+
+class nsICSSLoaderObserver;
+class nsIConsoleReportCollector;
+class nsIContent;
+class nsIPrincipal;
+
+namespace mozilla {
+
+class PreloadHashKey;
+class SharedStyleSheetCache;
+class SheetLoadDataHashKey;
+class StyleSheet;
+
+namespace dom {
+class DocGroup;
+class Element;
+enum class FetchPriority : uint8_t;
+} // namespace dom
+
+// The load data for a <link> or @import style-sheet.
+//
+// This must contain all the state that affects CSS parsing.
+class SheetLoadDataHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = const SheetLoadDataHashKey&;
+ using KeyTypePointer = const SheetLoadDataHashKey*;
+
+ explicit SheetLoadDataHashKey(const SheetLoadDataHashKey* aKey)
+ : mURI(aKey->mURI),
+ mPrincipal(aKey->mPrincipal),
+ mLoaderPrincipal(aKey->mLoaderPrincipal),
+ mPartitionPrincipal(aKey->mPartitionPrincipal),
+ mEncodingGuess(aKey->mEncodingGuess),
+ mCORSMode(aKey->mCORSMode),
+ mParsingMode(aKey->mParsingMode),
+ mCompatMode(aKey->mCompatMode),
+ mSRIMetadata(aKey->mSRIMetadata),
+ mIsLinkRelPreload(aKey->mIsLinkRelPreload) {
+ MOZ_COUNT_CTOR(SheetLoadDataHashKey);
+ }
+
+ SheetLoadDataHashKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIPrincipal* aLoaderPrincipal,
+ nsIPrincipal* aPartitionPrincipal,
+ NotNull<const Encoding*> aEncodingGuess,
+ CORSMode aCORSMode, css::SheetParsingMode aParsingMode,
+ nsCompatibility aCompatMode,
+ const dom::SRIMetadata& aSRIMetadata,
+ css::StylePreloadKind aPreloadKind)
+ : mURI(aURI),
+ mPrincipal(aPrincipal),
+ mLoaderPrincipal(aLoaderPrincipal),
+ mPartitionPrincipal(aPartitionPrincipal),
+ mEncodingGuess(aEncodingGuess),
+ mCORSMode(aCORSMode),
+ mParsingMode(aParsingMode),
+ mCompatMode(aCompatMode),
+ mSRIMetadata(aSRIMetadata),
+ mIsLinkRelPreload(IsLinkRelPreload(aPreloadKind)) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aLoaderPrincipal);
+ MOZ_COUNT_CTOR(SheetLoadDataHashKey);
+ }
+
+ SheetLoadDataHashKey(SheetLoadDataHashKey&& toMove)
+ : mURI(std::move(toMove.mURI)),
+ mPrincipal(std::move(toMove.mPrincipal)),
+ mLoaderPrincipal(std::move(toMove.mLoaderPrincipal)),
+ mPartitionPrincipal(std::move(toMove.mPartitionPrincipal)),
+ mEncodingGuess(std::move(toMove.mEncodingGuess)),
+ mCORSMode(std::move(toMove.mCORSMode)),
+ mParsingMode(std::move(toMove.mParsingMode)),
+ mCompatMode(std::move(toMove.mCompatMode)),
+ mSRIMetadata(std::move(toMove.mSRIMetadata)),
+ mIsLinkRelPreload(std::move(toMove.mIsLinkRelPreload)) {
+ MOZ_COUNT_CTOR(SheetLoadDataHashKey);
+ }
+
+ explicit SheetLoadDataHashKey(const css::SheetLoadData&);
+
+ MOZ_COUNTED_DTOR(SheetLoadDataHashKey)
+
+ const SheetLoadDataHashKey& GetKey() const { return *this; }
+ const SheetLoadDataHashKey* GetKeyPointer() const { return this; }
+
+ bool KeyEquals(const SheetLoadDataHashKey* aKey) const {
+ return KeyEquals(*aKey);
+ }
+
+ bool KeyEquals(const SheetLoadDataHashKey&) const;
+
+ static const SheetLoadDataHashKey* KeyToPointer(
+ const SheetLoadDataHashKey& aKey) {
+ return &aKey;
+ }
+ static PLDHashNumber HashKey(const SheetLoadDataHashKey* aKey) {
+ return nsURIHashKey::HashKey(aKey->mURI);
+ }
+
+ nsIURI* URI() const { return mURI; }
+
+ nsIPrincipal* Principal() const { return mPrincipal; }
+
+ nsIPrincipal* LoaderPrincipal() const { return mLoaderPrincipal; }
+
+ nsIPrincipal* PartitionPrincipal() const { return mPartitionPrincipal; }
+
+ css::SheetParsingMode ParsingMode() const { return mParsingMode; }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ const nsCOMPtr<nsIURI> mURI;
+ const nsCOMPtr<nsIPrincipal> mPrincipal;
+ const nsCOMPtr<nsIPrincipal> mLoaderPrincipal;
+ const nsCOMPtr<nsIPrincipal> mPartitionPrincipal;
+ // The encoding guess is the encoding the sheet would get if the request
+ // didn't have any encoding information like @charset or a Content-Encoding
+ // header.
+ const NotNull<const Encoding*> mEncodingGuess;
+ const CORSMode mCORSMode;
+ const css::SheetParsingMode mParsingMode;
+ const nsCompatibility mCompatMode;
+ dom::SRIMetadata mSRIMetadata;
+ const bool mIsLinkRelPreload;
+};
+
+namespace css {
+
+class SheetLoadData;
+class ImportRule;
+
+/*********************
+ * Style sheet reuse *
+ *********************/
+
+class MOZ_RAII LoaderReusableStyleSheets {
+ public:
+ LoaderReusableStyleSheets() = default;
+
+ /**
+ * Look for a reusable sheet (see AddReusableSheet) matching the
+ * given URL. If found, set aResult, remove the reused sheet from
+ * the internal list, and return true. If not found, return false;
+ * in this case, aResult is not modified.
+ *
+ * @param aURL the url to match
+ * @param aResult [out] the style sheet which can be reused
+ */
+ bool FindReusableStyleSheet(nsIURI* aURL, RefPtr<StyleSheet>& aResult);
+
+ /**
+ * Indicate that a certain style sheet is available for reuse if its
+ * URI matches the URI of an @import. Sheets should be added in the
+ * opposite order in which they are intended to be reused.
+ *
+ * @param aSheet the sheet which can be reused
+ */
+ void AddReusableSheet(StyleSheet* aSheet) {
+ mReusableSheets.AppendElement(aSheet);
+ }
+
+ private:
+ LoaderReusableStyleSheets(const LoaderReusableStyleSheets&) = delete;
+ LoaderReusableStyleSheets& operator=(const LoaderReusableStyleSheets&) =
+ delete;
+
+ // The sheets that can be reused.
+ nsTArray<RefPtr<StyleSheet>> mReusableSheets;
+};
+
+class Loader final {
+ using ReferrerPolicy = dom::ReferrerPolicy;
+
+ public:
+ using Completed = dom::LinkStyle::Completed;
+ using HasAlternateRel = dom::LinkStyle::HasAlternateRel;
+ using IsAlternate = dom::LinkStyle::IsAlternate;
+ using IsInline = dom::LinkStyle::IsInline;
+ using IsExplicitlyEnabled = dom::LinkStyle::IsExplicitlyEnabled;
+ using MediaMatched = dom::LinkStyle::MediaMatched;
+ using LoadSheetResult = dom::LinkStyle::Update;
+ using SheetInfo = dom::LinkStyle::SheetInfo;
+
+ Loader();
+ // aDocGroup is used for dispatching SheetLoadData in PostLoadEvent(). It
+ // can be null if you want to use this constructor, and there's no
+ // document when the Loader is constructed.
+ explicit Loader(dom::DocGroup*);
+ explicit Loader(dom::Document*);
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~Loader();
+
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Loader)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Loader)
+
+ void DropDocumentReference(); // notification that doc is going away
+
+ void DeregisterFromSheetCache();
+ void RegisterInSheetCache();
+
+ void SetCompatibilityMode(nsCompatibility aCompatMode) {
+ mDocumentCompatMode = aCompatMode;
+ }
+
+ using StylePreloadKind = css::StylePreloadKind;
+
+ bool HasLoaded(const SheetLoadDataHashKey& aKey) const {
+ return mLoadsPerformed.Contains(aKey);
+ }
+
+ void WillStartPendingLoad() {
+ MOZ_DIAGNOSTIC_ASSERT(mPendingLoadCount, "Where did this load come from?");
+ mPendingLoadCount--;
+ }
+
+ nsCompatibility CompatMode(StylePreloadKind aPreloadKind) const {
+ // For Link header preload, we guess non-quirks, because otherwise it is
+ // useless for modern pages.
+ //
+ // Link element preload is generally good because the speculative html
+ // parser deals with quirks mode properly.
+ if (aPreloadKind == StylePreloadKind::FromLinkRelPreloadHeader) {
+ return eCompatibility_FullStandards;
+ }
+ return mDocumentCompatMode;
+ }
+
+ // TODO(emilio): Is the complexity of this method and carrying the titles
+ // around worth it? The alternate sheets will load anyhow eventually...
+ void DocumentStyleSheetSetChanged();
+
+ // XXXbz sort out what the deal is with events! When should they fire?
+
+ /**
+ * Load an inline style sheet. If a successful result is returned and
+ * result.WillNotify() is true, then aObserver is guaranteed to be notified
+ * asynchronously once the sheet is marked complete. If an error is
+ * returned, or if result.WillNotify() is false, aObserver will not be
+ * notified. In addition to parsing the sheet, this method will insert it
+ * into the stylesheet list of this CSSLoader's document.
+ * @param aObserver the observer to notify when the load completes.
+ * May be null.
+ * @param aBuffer the stylesheet data
+ */
+ Result<LoadSheetResult, nsresult> LoadInlineStyle(
+ const SheetInfo&, const nsAString& aBuffer,
+ nsICSSLoaderObserver* aObserver);
+
+ /**
+ * Load a linked (document) stylesheet. If a successful result is returned,
+ * aObserver is guaranteed to be notified asynchronously once the sheet is
+ * loaded and marked complete, i.e., result.WillNotify() will always return
+ * true. If an error is returned, aObserver will not be notified. In
+ * addition to loading the sheet, this method will insert it into the
+ * stylesheet list of this CSSLoader's document.
+ * @param aObserver the observer to notify when the load completes.
+ * May be null.
+ */
+ Result<LoadSheetResult, nsresult> LoadStyleLink(
+ const SheetInfo&, nsICSSLoaderObserver* aObserver);
+
+ /**
+ * Load a child (@import-ed) style sheet. In addition to loading the sheet,
+ * this method will insert it into the child sheet list of aParentSheet. If
+ * there is no sheet currently being parsed and the child sheet is not
+ * complete when this method returns, then when the child sheet becomes
+ * complete aParentSheet will be QIed to nsICSSLoaderObserver and
+ * asynchronously notified, just like for LoadStyleLink. Note that if the
+ * child sheet is already complete when this method returns, no
+ * nsICSSLoaderObserver notification will be sent.
+ *
+ * @param aParentSheet the parent of this child sheet
+ * @param aParentData the SheetLoadData corresponding to the load of the
+ * parent sheet. May be null for @import rules inserted via
+ * CSSOM.
+ * @param aURL the URL of the child sheet
+ * @param aMedia the already-parsed media list for the child sheet
+ * @param aSavedSheets any saved style sheets which could be reused
+ * for this load
+ */
+ nsresult LoadChildSheet(StyleSheet& aParentSheet, SheetLoadData* aParentData,
+ nsIURI* aURL, dom::MediaList* aMedia,
+ LoaderReusableStyleSheets* aSavedSheets);
+
+ /**
+ * Called when we hit the internal memory cache with a complete stylesheet.
+ */
+ void DidHitCompleteSheetCache(const SheetLoadDataHashKey&,
+ const StyleUseCounters* aCounters);
+
+ enum class UseSystemPrincipal { No, Yes };
+
+ /**
+ * Synchronously load and return the stylesheet at aURL. Any child sheets
+ * will also be loaded synchronously. Note that synchronous loads over some
+ * protocols may involve spinning up a new event loop, so use of this method
+ * does NOT guarantee not receiving any events before the sheet loads. This
+ * method can be used to load sheets not associated with a document.
+ *
+ * @param aURL the URL of the sheet to load
+ * @param aParsingMode the mode in which to parse the sheet
+ * (see comments at enum SheetParsingMode, above).
+ * @param aUseSystemPrincipal if true, give the resulting sheet the system
+ * principal no matter where it's being loaded from.
+ *
+ * NOTE: At the moment, this method assumes the sheet will be UTF-8, but
+ * ideally it would allow arbitrary encodings. Callers should NOT depend on
+ * non-UTF8 sheets being treated as UTF-8 by this method.
+ *
+ * NOTE: A successful return from this method doesn't indicate anything about
+ * whether the data could be parsed as CSS and doesn't indicate anything
+ * about the status of child sheets of the returned sheet.
+ */
+ Result<RefPtr<StyleSheet>, nsresult> LoadSheetSync(
+ nsIURI*, SheetParsingMode = eAuthorSheetFeatures,
+ UseSystemPrincipal = UseSystemPrincipal::No);
+
+ /**
+ * Asynchronously load the stylesheet at aURL. If a successful result is
+ * returned, aObserver is guaranteed to be notified asynchronously once the
+ * sheet is loaded and marked complete. This method can be used to load
+ * sheets not associated with a document.
+ *
+ * @param aURL the URL of the sheet to load
+ * @param aParsingMode the mode in which to parse the sheet
+ * (see comments at enum SheetParsingMode, above).
+ * @param aUseSystemPrincipal if true, give the resulting sheet the system
+ * principal no matter where it's being loaded from.
+ * @param aReferrerInfo referrer information of the sheet.
+ * @param aObserver the observer to notify when the load completes.
+ * Must not be null.
+ * @param aEarlyHintPreloaderId to connect back to the early hint preload
+ * channel. Null means no connect back should happen
+ * @return the sheet to load. Note that the sheet may well not be loaded by
+ * the time this method returns.
+ *
+ * NOTE: At the moment, this method assumes the sheet will be UTF-8, but
+ * ideally it would allow arbitrary encodings. Callers should NOT depend on
+ * non-UTF8 sheets being treated as UTF-8 by this method.
+ */
+ Result<RefPtr<StyleSheet>, nsresult> LoadSheet(
+ nsIURI* aURI, StylePreloadKind, const Encoding* aPreloadEncoding,
+ nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver,
+ uint64_t aEarlyHintPreloaderId, CORSMode aCORSMode,
+ const nsAString& aNonce, const nsAString& aIntegrity,
+ dom::FetchPriority aFetchPriority);
+
+ /**
+ * As above, but without caring for a couple things.
+ * Only to be called by `PreloadedStyleSheet::PreloadAsync`.
+ */
+ Result<RefPtr<StyleSheet>, nsresult> LoadSheet(nsIURI*, SheetParsingMode,
+ UseSystemPrincipal,
+ nsICSSLoaderObserver*);
+
+ /**
+ * Stop loading all sheets. All nsICSSLoaderObservers involved will be
+ * notified with NS_BINDING_ABORTED as the status, possibly synchronously.
+ */
+ void Stop();
+
+ /**
+ * nsresult Loader::StopLoadingSheet(nsIURI* aURL), which notifies the
+ * nsICSSLoaderObserver with NS_BINDING_ABORTED, was removed in Bug 556446.
+ * It can be found in revision 2c44a32052ad.
+ */
+
+ /**
+ * Whether the loader is enabled or not.
+ * When disabled, processing of new styles is disabled and an attempt
+ * to do so will fail with a return code of
+ * NS_ERROR_NOT_AVAILABLE. Note that this DOES NOT disable
+ * currently loading styles or already processed styles.
+ */
+ bool GetEnabled() { return mEnabled; }
+ void SetEnabled(bool aEnabled) { mEnabled = aEnabled; }
+
+ uint32_t ParsedSheetCount() const { return mParsedSheetCount; }
+
+ /**
+ * Get the document we live for. May return null.
+ */
+ dom::Document* GetDocument() const { return mDocument; }
+
+ bool IsDocumentAssociated() const { return mIsDocumentAssociated; }
+
+ /**
+ * Return true if this loader has pending loads (ones that would send
+ * notifications to an nsICSSLoaderObserver attached to this loader).
+ * If called from inside nsICSSLoaderObserver::StyleSheetLoaded, this will
+ * return false if and only if that is the last StyleSheetLoaded
+ * notification the CSSLoader knows it's going to send. In other words, if
+ * two sheets load at once (via load coalescing, e.g.), HasPendingLoads()
+ * will return true during notification for the first one, and false
+ * during notification for the second one.
+ */
+ bool HasPendingLoads();
+
+ /**
+ * Add an observer to this loader. The observer will be notified
+ * for all loads that would have notified their own observers (even
+ * if those loads don't have observers attached to them).
+ * Load-specific observers will be notified before generic
+ * observers. The loader holds a reference to the observer.
+ *
+ * aObserver must not be null.
+ */
+ void AddObserver(nsICSSLoaderObserver* aObserver);
+
+ /**
+ * Remove an observer added via AddObserver.
+ */
+ void RemoveObserver(nsICSSLoaderObserver* aObserver);
+
+ // These interfaces are public only for the benefit of static functions
+ // within nsCSSLoader.cpp.
+
+ // IsAlternateSheet can change our currently selected style set if none is
+ // selected and aHasAlternateRel is false.
+ IsAlternate IsAlternateSheet(const nsAString& aTitle, bool aHasAlternateRel);
+
+ // Measure our size.
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ enum class SheetState : uint8_t {
+ NeedsParser = 0,
+ Pending,
+ Loading,
+ Complete
+ };
+
+ // The loader principal is the document's node principal, if this loader is
+ // owned by a document, or the system principal otherwise.
+ nsIPrincipal* LoaderPrincipal() const;
+
+ // The partitioned principal is the document's partitioned principal, if this
+ // loader is owned by a document, or the system principal otherwise.
+ nsIPrincipal* PartitionedPrincipal() const;
+
+ bool ShouldBypassCache() const;
+
+ enum class PendingLoad { No, Yes };
+
+ private:
+ friend class mozilla::SharedStyleSheetCache;
+ friend class SheetLoadData;
+ friend class StreamLoader;
+
+ // Only to be called by `LoadSheet`.
+ [[nodiscard]] bool MaybeDeferLoad(SheetLoadData& aLoadData,
+ SheetState aSheetState,
+ PendingLoad aPendingLoad,
+ const SheetLoadDataHashKey& aKey);
+
+ // Only to be called by `LoadSheet`.
+ bool MaybeCoalesceLoadAndNotifyOpen(SheetLoadData& aLoadData,
+ SheetState aSheetState,
+ const SheetLoadDataHashKey& aKey,
+ const PreloadHashKey& aPreloadKey);
+
+ // Only to be called by `LoadSheet`.
+ [[nodiscard]] nsresult LoadSheetSyncInternal(SheetLoadData& aLoadData,
+ SheetState aSheetState);
+
+ void AdjustPriority(const SheetLoadData& aLoadData, nsIChannel* aChannel);
+
+ // Only to be called by `LoadSheet`.
+ [[nodiscard]] nsresult LoadSheetAsyncInternal(
+ SheetLoadData& aLoadData, uint64_t aEarlyHintPreloaderId,
+ const SheetLoadDataHashKey& aKey);
+
+ // Helpers to conditionally block onload if mDocument is non-null.
+ void IncrementOngoingLoadCountAndMaybeBlockOnload() {
+ if (!mOngoingLoadCount++) {
+ BlockOnload();
+ }
+ }
+
+ void DecrementOngoingLoadCountAndMaybeUnblockOnload() {
+ MOZ_DIAGNOSTIC_ASSERT(mOngoingLoadCount);
+ MOZ_DIAGNOSTIC_ASSERT(mOngoingLoadCount > mPendingLoadCount);
+ if (!--mOngoingLoadCount) {
+ UnblockOnload(false);
+ }
+ }
+
+ void BlockOnload();
+ void UnblockOnload(bool aFireSync);
+
+ nsresult CheckContentPolicy(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIURI* aTargetURI, nsINode* aRequestingNode,
+ const nsAString& aNonce, StylePreloadKind);
+
+ std::tuple<RefPtr<StyleSheet>, SheetState> CreateSheet(
+ const SheetInfo& aInfo, css::SheetParsingMode aParsingMode,
+ bool aSyncLoad, css::StylePreloadKind aPreloadKind) {
+ nsIPrincipal* triggeringPrincipal = aInfo.mTriggeringPrincipal
+ ? aInfo.mTriggeringPrincipal.get()
+ : LoaderPrincipal();
+ return CreateSheet(aInfo.mURI, aInfo.mContent, triggeringPrincipal,
+ aParsingMode, aInfo.mCORSMode,
+ /* aPreloadOrParentDataEncoding = */ nullptr,
+ aInfo.mIntegrity, aSyncLoad, aPreloadKind);
+ }
+
+ // For inline style, the aURI param is null, but the aLinkingContent
+ // must be non-null then. The loader principal must never be null
+ // if aURI is not null.
+ std::tuple<RefPtr<StyleSheet>, SheetState> CreateSheet(
+ nsIURI* aURI, nsIContent* aLinkingContent,
+ nsIPrincipal* aTriggeringPrincipal, css::SheetParsingMode, CORSMode,
+ const Encoding* aPreloadOrParentDataEncoding, const nsAString& aIntegrity,
+ bool aSyncLoad, StylePreloadKind);
+
+ // Pass in either a media string or the MediaList from the CSSParser. Don't
+ // pass both.
+ //
+ // This method will set the sheet's enabled state based on IsAlternate and co.
+ MediaMatched PrepareSheet(StyleSheet&, const nsAString& aTitle,
+ const nsAString& aMediaString, dom::MediaList*,
+ IsAlternate, IsExplicitlyEnabled);
+
+ // Inserts a style sheet in a document or a ShadowRoot.
+ void InsertSheetInTree(StyleSheet& aSheet);
+ // Inserts a style sheet into a parent style sheet.
+ void InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet);
+
+ Result<RefPtr<StyleSheet>, nsresult> InternalLoadNonDocumentSheet(
+ nsIURI* aURL, StylePreloadKind, SheetParsingMode aParsingMode,
+ UseSystemPrincipal, const Encoding* aPreloadEncoding,
+ nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver,
+ CORSMode aCORSMode, const nsAString& aNonce, const nsAString& aIntegrity,
+ uint64_t aEarlyHintPreloaderId, dom::FetchPriority aFetchPriority);
+
+ RefPtr<StyleSheet> LookupInlineSheetInCache(const nsAString&, nsIPrincipal*);
+
+ // Synchronously notify of a cached load data.
+ void NotifyOfCachedLoad(RefPtr<SheetLoadData>);
+
+ // Start the loads of all the sheets in mPendingDatas
+ void StartDeferredLoads();
+
+ // Note: LoadSheet is responsible for setting the sheet to complete on
+ // failure.
+ nsresult LoadSheet(SheetLoadData&, SheetState, uint64_t aEarlyHintPreloaderId,
+ PendingLoad = PendingLoad::No);
+
+ enum class AllowAsyncParse {
+ Yes,
+ No,
+ };
+
+ // Parse the stylesheet in the load data.
+ //
+ // Returns whether the parse finished. It may not finish e.g. if the sheet had
+ // an @import.
+ //
+ // If this function returns Completed::Yes, then ParseSheet also called
+ // SheetComplete on aLoadData.
+ Completed ParseSheet(const nsACString&, SheetLoadData&, AllowAsyncParse);
+
+ // The load of the sheet in the load data is done, one way or another.
+ // Do final cleanup.
+ void SheetComplete(SheetLoadData&, nsresult);
+
+ // Notify observers on an individual data. This is different from
+ // SheetComplete for loads that are shared.
+ void NotifyObservers(SheetLoadData&, nsresult);
+
+ // Mark the given SheetLoadData, as well as any of its siblings, parents, etc
+ // transitively, as failed. The idea is to mark as failed any load that was
+ // directly or indirectly @importing the sheet this SheetLoadData represents.
+ //
+ // if aOnlyForLoader is non-null, then only loads for a given loader will be
+ // marked as failing. This is useful to only cancel loads associated to a
+ // given loader, in case they were marked as canceled.
+ static void MarkLoadTreeFailed(SheetLoadData&,
+ Loader* aOnlyForLoader = nullptr);
+
+ // A shorthand to mark a possible link preload as used to supress "unused"
+ // warning in the console.
+ void MaybeNotifyPreloadUsed(SheetLoadData&);
+
+ nsRefPtrHashtable<nsStringHashKey, StyleSheet> mInlineSheets;
+
+ // A set with all the different loads we've done in a given document, for the
+ // purpose of not posting duplicate performance entries for them.
+ nsTHashtable<const SheetLoadDataHashKey> mLoadsPerformed;
+
+ RefPtr<SharedStyleSheetCache> mSheets;
+
+ // Our array of "global" observers
+ nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>> mObservers;
+
+ // This reference is nulled by the Document in it's destructor through
+ // DropDocumentReference().
+ dom::Document* MOZ_NON_OWNING_REF mDocument; // the document we live for
+
+ // For dispatching events via DocGroup::Dispatch() when mDocument is nullptr.
+ RefPtr<dom::DocGroup> mDocGroup;
+
+ nsCompatibility mDocumentCompatMode;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReporter;
+
+ // Number of datas for asynchronous sheet loads still waiting to be notified.
+ // This includes pending stylesheets whose load hasn't started yet but which
+ // we need to, but not inline or constructable stylesheets, though the
+ // constructable stylesheets bit may change, see bug 1642227.
+ uint32_t mOngoingLoadCount = 0;
+
+ // The number of sheets that have been deferred / are in a pending state.
+ uint32_t mPendingLoadCount = 0;
+
+ // The number of stylesheets that we have parsed, for testing purposes.
+ uint32_t mParsedSheetCount = 0;
+
+ bool mEnabled = true;
+
+ // Whether we had a document at the point of creation.
+ bool mIsDocumentAssociated = false;
+
+#ifdef DEBUG
+ // Whether we're in a necko callback atm.
+ bool mSyncCallback = false;
+#endif
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_Loader_h */
diff --git a/layout/style/MappedDeclarationsBuilder.cpp b/layout/style/MappedDeclarationsBuilder.cpp
new file mode 100644
index 0000000000..bfb1eedff1
--- /dev/null
+++ b/layout/style/MappedDeclarationsBuilder.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "MappedDeclarationsBuilder.h"
+
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "mozilla/dom/Document.h"
+#include "nsPresContext.h"
+
+namespace mozilla {
+
+void MappedDeclarationsBuilder::SetIdentAtomValue(nsCSSPropertyID aId,
+ nsAtom* aValue) {
+ Servo_DeclarationBlock_SetIdentStringValue(&EnsureDecls(), aId, aValue);
+ if (aId == eCSSProperty__x_lang) {
+ // This forces the lang prefs result to be cached so that we can access them
+ // off main thread during traversal.
+ //
+ // FIXME(emilio): Can we move mapped attribute declarations across
+ // documents? Isn't this wrong in that case? This is pretty out of place
+ // anyway.
+ mDocument.ForceCacheLang(aValue);
+ }
+}
+
+void MappedDeclarationsBuilder::SetBackgroundImage(const nsAttrValue& aValue) {
+ if (aValue.Type() != nsAttrValue::eURL) {
+ return;
+ }
+ nsAutoString str;
+ aValue.ToString(str);
+ nsAutoCString utf8;
+ CopyUTF16toUTF8(str, utf8);
+ Servo_DeclarationBlock_SetBackgroundImage(
+ &EnsureDecls(), &utf8, mDocument.DefaultStyleAttrURLData());
+}
+
+} // namespace mozilla
diff --git a/layout/style/MappedDeclarationsBuilder.h b/layout/style/MappedDeclarationsBuilder.h
new file mode 100644
index 0000000000..c7388d1358
--- /dev/null
+++ b/layout/style/MappedDeclarationsBuilder.h
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Representation of a declaration block used for attribute mapping */
+
+#ifndef mozilla_MappedDeclarationsBuilder_h
+#define mozilla_MappedDeclarationsBuilder_h
+
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/ServoBindings.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSValue.h"
+#include "nsColor.h"
+
+class nsAttrValue;
+
+namespace mozilla {
+
+// This provides a convenient interface for attribute mappers
+// (MapAttributesIntoRule) to modify the presentation attribute declaration
+// block for a given element.
+class MOZ_STACK_CLASS MappedDeclarationsBuilder final {
+ public:
+ explicit MappedDeclarationsBuilder(
+ dom::Element& aElement, dom::Document& aDoc,
+ StyleLockedDeclarationBlock* aDecls = nullptr)
+ : mDocument(aDoc), mElement(aElement), mDecls(aDecls) {
+ if (mDecls) {
+ Servo_DeclarationBlock_Clear(mDecls);
+ }
+ }
+
+ ~MappedDeclarationsBuilder() {
+ MOZ_ASSERT(!mDecls, "Forgot to take the block?");
+ }
+
+ dom::Document& Document() { return mDocument; }
+
+ already_AddRefed<StyleLockedDeclarationBlock> TakeDeclarationBlock() {
+ return mDecls.forget();
+ }
+
+ // Check if we already contain a certain longhand
+ bool PropertyIsSet(nsCSSPropertyID aId) const {
+ return mDecls && Servo_DeclarationBlock_PropertyIsSet(mDecls, aId);
+ }
+
+ // Set a property to an identifier (string)
+ void SetIdentStringValue(nsCSSPropertyID aId, const nsString& aValue) {
+ RefPtr<nsAtom> atom = NS_AtomizeMainThread(aValue);
+ SetIdentAtomValue(aId, atom);
+ }
+
+ void SetIdentStringValueIfUnset(nsCSSPropertyID aId, const nsString& aValue) {
+ if (!PropertyIsSet(aId)) {
+ SetIdentStringValue(aId, aValue);
+ }
+ }
+
+ void SetIdentAtomValue(nsCSSPropertyID aId, nsAtom* aValue);
+
+ void SetIdentAtomValueIfUnset(nsCSSPropertyID aId, nsAtom* aValue) {
+ if (!PropertyIsSet(aId)) {
+ SetIdentAtomValue(aId, aValue);
+ }
+ }
+
+ // Set a property to a keyword (usually NS_STYLE_* or StyleFoo::*)
+ void SetKeywordValue(nsCSSPropertyID aId, int32_t aValue) {
+ Servo_DeclarationBlock_SetKeywordValue(&EnsureDecls(), aId, aValue);
+ }
+
+ void SetKeywordValueIfUnset(nsCSSPropertyID aId, int32_t aValue) {
+ if (!PropertyIsSet(aId)) {
+ SetKeywordValue(aId, aValue);
+ }
+ }
+
+ template <typename T,
+ typename = typename std::enable_if<std::is_enum<T>::value>::type>
+ void SetKeywordValue(nsCSSPropertyID aId, T aValue) {
+ static_assert(EnumTypeFitsWithin<T, int32_t>::value,
+ "aValue must be an enum that fits within 32 bits");
+ SetKeywordValue(aId, static_cast<int32_t>(aValue));
+ }
+ template <typename T,
+ typename = typename std::enable_if<std::is_enum<T>::value>::type>
+ void SetKeywordValueIfUnset(nsCSSPropertyID aId, T aValue) {
+ static_assert(EnumTypeFitsWithin<T, int32_t>::value,
+ "aValue must be an enum that fits within 32 bits");
+ SetKeywordValueIfUnset(aId, static_cast<int32_t>(aValue));
+ }
+
+ // Set a property to an integer value
+ void SetIntValue(nsCSSPropertyID aId, int32_t aValue) {
+ Servo_DeclarationBlock_SetIntValue(&EnsureDecls(), aId, aValue);
+ }
+
+ // Set "math-depth: <integer>" or "math-depth: add(<integer>)"
+ void SetMathDepthValue(int32_t aValue, bool aIsRelative) {
+ Servo_DeclarationBlock_SetMathDepthValue(&EnsureDecls(), aValue,
+ aIsRelative);
+ }
+
+ // Set "counter-reset: list-item <integer>". If aIsReversed is true then
+ // "list-item" instead becomes "reversed(list-item)".
+ void SetCounterResetListItem(int32_t aValue, bool aIsReversed) {
+ Servo_DeclarationBlock_SetCounterResetListItem(&EnsureDecls(), aValue,
+ aIsReversed);
+ }
+
+ // Set "counter-set: list-item <integer>".
+ void SetCounterSetListItem(int32_t aValue) {
+ Servo_DeclarationBlock_SetCounterSetListItem(&EnsureDecls(), aValue);
+ }
+
+ // Set a property to a pixel value
+ void SetPixelValue(nsCSSPropertyID aId, float aValue) {
+ Servo_DeclarationBlock_SetPixelValue(&EnsureDecls(), aId, aValue);
+ }
+
+ void SetPixelValueIfUnset(nsCSSPropertyID aId, float aValue) {
+ if (!PropertyIsSet(aId)) {
+ SetPixelValue(aId, aValue);
+ }
+ }
+
+ void SetLengthValue(nsCSSPropertyID aId, const nsCSSValue& aValue) {
+ MOZ_ASSERT(aValue.IsLengthUnit());
+ Servo_DeclarationBlock_SetLengthValue(
+ &EnsureDecls(), aId, aValue.GetFloatValue(), aValue.GetUnit());
+ }
+
+ // Set a property to a percent value
+ void SetPercentValue(nsCSSPropertyID aId, float aValue) {
+ Servo_DeclarationBlock_SetPercentValue(&EnsureDecls(), aId, aValue);
+ }
+
+ void SetPercentValueIfUnset(nsCSSPropertyID aId, float aValue) {
+ if (!PropertyIsSet(aId)) {
+ SetPercentValue(aId, aValue);
+ }
+ }
+
+ // Set a property to `auto`
+ void SetAutoValue(nsCSSPropertyID aId) {
+ Servo_DeclarationBlock_SetAutoValue(&EnsureDecls(), aId);
+ }
+
+ void SetAutoValueIfUnset(nsCSSPropertyID aId) {
+ if (!PropertyIsSet(aId)) {
+ SetAutoValue(aId);
+ }
+ }
+
+ // Set a property to `currentcolor`
+ void SetCurrentColor(nsCSSPropertyID aId) {
+ Servo_DeclarationBlock_SetCurrentColor(&EnsureDecls(), aId);
+ }
+
+ void SetCurrentColorIfUnset(nsCSSPropertyID aId) {
+ if (!PropertyIsSet(aId)) {
+ SetCurrentColor(aId);
+ }
+ }
+
+ // Set a property to an RGBA nscolor value
+ void SetColorValue(nsCSSPropertyID aId, nscolor aValue) {
+ Servo_DeclarationBlock_SetColorValue(&EnsureDecls(), aId, aValue);
+ }
+
+ void SetColorValueIfUnset(nsCSSPropertyID aId, nscolor aValue) {
+ if (!PropertyIsSet(aId)) {
+ SetColorValue(aId, aValue);
+ }
+ }
+
+ // Set font-family to a string
+ void SetFontFamily(const nsACString& aValue) {
+ Servo_DeclarationBlock_SetFontFamily(&EnsureDecls(), &aValue);
+ }
+
+ // Add a quirks-mode override to the decoration color of elements nested in
+ // <a>
+ void SetTextDecorationColorOverride() {
+ Servo_DeclarationBlock_SetTextDecorationColorOverride(&EnsureDecls());
+ }
+
+ void SetBackgroundImage(const nsAttrValue& value);
+
+ void SetAspectRatio(float aWidth, float aHeight) {
+ Servo_DeclarationBlock_SetAspectRatio(&EnsureDecls(), aWidth, aHeight);
+ }
+
+ const nsAttrValue* GetAttr(nsAtom* aName) {
+ MOZ_ASSERT(mElement.IsAttributeMapped(aName));
+ return mElement.GetParsedAttr(aName);
+ }
+
+ private:
+ StyleLockedDeclarationBlock& EnsureDecls() {
+ if (!mDecls) {
+ mDecls = Servo_DeclarationBlock_CreateEmpty().Consume();
+ }
+ return *mDecls;
+ }
+
+ dom::Document& mDocument;
+ dom::Element& mElement;
+ RefPtr<StyleLockedDeclarationBlock> mDecls;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/MediaFeatureChange.h b/layout/style/MediaFeatureChange.h
new file mode 100644
index 0000000000..a766406219
--- /dev/null
+++ b/layout/style/MediaFeatureChange.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* A struct defining a media feature change. */
+
+#ifndef mozilla_MediaFeatureChange_h__
+#define mozilla_MediaFeatureChange_h__
+
+#include "nsChangeHint.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/ServoStyleConsts.h"
+
+namespace mozilla {
+
+enum class MediaFeatureChangeReason : uint8_t {
+ // The viewport size the document has used has changed.
+ //
+ // This affects size media queries like min-width.
+ ViewportChange = 1 << 0,
+ // The effective text zoom has changed. This affects the meaning of em units,
+ // and thus affects any media query that uses a Length.
+ ZoomChange = 1 << 1,
+ // The resolution has changed. This can affect device-pixel-ratio media
+ // queries, for example.
+ ResolutionChange = 1 << 2,
+ // The medium has changed.
+ MediumChange = 1 << 3,
+ // The size-mode has changed.
+ SizeModeChange = 1 << 4,
+ // A system metric or multiple have changed. This affects all the media
+ // features that expose the presence of a system metric directly.
+ SystemMetricsChange = 1 << 5,
+ // display-mode changed on the document, thus the display-mode media queries
+ // may have changed.
+ DisplayModeChange = 1 << 6,
+ // A preference that affects media query results has changed. For
+ // example, changes to document_color_use will affect
+ // prefers-contrast.
+ PreferenceChange = 1 << 7,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(MediaFeatureChangeReason)
+
+enum class MediaFeatureChangePropagation : uint8_t {
+ JustThisDocument = 0,
+ SubDocuments = 1 << 0,
+ Images = 1 << 1,
+ All = Images | SubDocuments,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(MediaFeatureChangePropagation)
+
+struct MediaFeatureChange {
+ static const auto kAllChanges = static_cast<MediaFeatureChangeReason>(~0);
+
+ RestyleHint mRestyleHint;
+ nsChangeHint mChangeHint;
+ MediaFeatureChangeReason mReason;
+
+ MOZ_IMPLICIT MediaFeatureChange(MediaFeatureChangeReason aReason)
+ : MediaFeatureChange(RestyleHint{0}, nsChangeHint(0), aReason) {}
+
+ MediaFeatureChange(RestyleHint aRestyleHint, nsChangeHint aChangeHint,
+ MediaFeatureChangeReason aReason)
+ : mRestyleHint(aRestyleHint),
+ mChangeHint(aChangeHint),
+ mReason(aReason) {}
+
+ inline MediaFeatureChange& operator|=(const MediaFeatureChange& aOther) {
+ mRestyleHint |= aOther.mRestyleHint;
+ mChangeHint |= aOther.mChangeHint;
+ mReason |= aOther.mReason;
+ return *this;
+ }
+
+ static MediaFeatureChange ForPreferredColorSchemeChange() {
+ // We need to restyle because not only media queries have changed, system
+ // colors may as well via the prefers-color-scheme meta tag / effective
+ // color-scheme property value.
+ return {RestyleHint::RecascadeSubtree(), nsChangeHint(0),
+ MediaFeatureChangeReason::SystemMetricsChange};
+ }
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/MediaList.cpp b/layout/style/MediaList.cpp
new file mode 100644
index 0000000000..7df0fd46d8
--- /dev/null
+++ b/layout/style/MediaList.cpp
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for representation of media lists */
+
+#include "mozilla/dom/MediaList.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/MediaListBinding.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+namespace mozilla::dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaList)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(MediaList)
+
+JSObject* MediaList::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MediaList_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void MediaList::SetStyleSheet(StyleSheet* aSheet) {
+ MOZ_ASSERT(aSheet == mStyleSheet || !aSheet || !mStyleSheet,
+ "Multiple style sheets competing for one media list");
+ mStyleSheet = aSheet;
+}
+
+nsISupports* MediaList::GetParentObject() const { return mStyleSheet; }
+
+template <typename Func>
+void MediaList::DoMediaChange(Func aCallback, ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ if (mStyleSheet) {
+ mStyleSheet->WillDirty();
+ }
+
+ aCallback(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (mStyleSheet) {
+ // FIXME(emilio): We should discern between "owned by a rule" (as in @media)
+ // and "owned by a sheet" (as in <style media>), and then pass something
+ // meaningful here.
+ mStyleSheet->RuleChanged(nullptr, StyleRuleChangeKind::Generic);
+ }
+}
+
+already_AddRefed<MediaList> MediaList::Clone() {
+ RefPtr<MediaList> clone =
+ new MediaList(Servo_MediaList_DeepClone(mRawList).Consume());
+ return clone.forget();
+}
+
+MediaList::MediaList() : mRawList(Servo_MediaList_Create().Consume()) {}
+
+MediaList::MediaList(const nsACString& aMedia, CallerType aCallerType)
+ : MediaList() {
+ SetTextInternal(aMedia, aCallerType);
+}
+
+void MediaList::GetText(nsACString& aMediaText) const {
+ Servo_MediaList_GetText(mRawList, &aMediaText);
+}
+
+/* static */
+already_AddRefed<MediaList> MediaList::Create(const nsACString& aMedia,
+ CallerType aCallerType) {
+ return do_AddRef(new MediaList(aMedia, aCallerType));
+}
+
+void MediaList::SetText(const nsACString& aMediaText) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ SetTextInternal(aMediaText, CallerType::NonSystem);
+}
+
+void MediaList::SetTextInternal(const nsACString& aMediaText,
+ CallerType aCallerType) {
+ Servo_MediaList_SetText(mRawList, &aMediaText, aCallerType);
+}
+
+uint32_t MediaList::Length() const {
+ return Servo_MediaList_GetLength(mRawList);
+}
+
+bool MediaList::IsViewportDependent() const {
+ return Servo_MediaList_IsViewportDependent(mRawList);
+}
+
+void MediaList::IndexedGetter(uint32_t aIndex, bool& aFound,
+ nsACString& aReturn) const {
+ aFound = Servo_MediaList_GetMediumAt(mRawList, aIndex, &aReturn);
+ if (!aFound) {
+ aReturn.SetIsVoid(true);
+ }
+}
+
+void MediaList::Delete(const nsACString& aOldMedium, ErrorResult& aRv) {
+ MOZ_ASSERT(!IsReadOnly());
+ if (Servo_MediaList_DeleteMedium(mRawList, &aOldMedium)) {
+ return;
+ }
+ aRv.ThrowNotFoundError("Medium not in list");
+}
+
+bool MediaList::Matches(const Document& aDocument) const {
+ const auto* rawData = aDocument.EnsureStyleSet().RawData();
+ MOZ_ASSERT(rawData, "The per doc data should be valid!");
+ return Servo_MediaList_Matches(mRawList, rawData);
+}
+
+void MediaList::Append(const nsACString& aNewMedium, ErrorResult& aRv) {
+ MOZ_ASSERT(!IsReadOnly());
+ if (aNewMedium.IsEmpty()) {
+ // XXXbz per spec there should not be an exception here, as far as
+ // I can tell...
+ aRv.ThrowNotFoundError("Empty medium");
+ return;
+ }
+ Servo_MediaList_AppendMedium(mRawList, &aNewMedium);
+}
+
+void MediaList::SetMediaText(const nsACString& aMediaText) {
+ DoMediaChange([&](ErrorResult& aRv) { SetText(aMediaText); }, IgnoreErrors());
+}
+
+void MediaList::Item(uint32_t aIndex, nsACString& aReturn) {
+ bool dummy;
+ IndexedGetter(aIndex, dummy, aReturn);
+}
+
+void MediaList::DeleteMedium(const nsACString& aOldMedium, ErrorResult& aRv) {
+ DoMediaChange([&](ErrorResult& aRv) { Delete(aOldMedium, aRv); }, aRv);
+}
+
+void MediaList::AppendMedium(const nsACString& aNewMedium, ErrorResult& aRv) {
+ DoMediaChange([&](ErrorResult& aRv) { Append(aNewMedium, aRv); }, aRv);
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(ServoMediaListMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoMediaListMallocEnclosingSizeOf)
+
+size_t MediaList::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ n += Servo_MediaList_SizeOfIncludingThis(ServoMediaListMallocSizeOf,
+ ServoMediaListMallocEnclosingSizeOf,
+ mRawList);
+ return n;
+}
+
+bool MediaList::IsReadOnly() const {
+ return mStyleSheet && mStyleSheet->IsReadOnly();
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/MediaList.h b/layout/style/MediaList.h
new file mode 100644
index 0000000000..afa2419534
--- /dev/null
+++ b/layout/style/MediaList.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 representation of media lists */
+
+#ifndef mozilla_dom_MediaList_h
+#define mozilla_dom_MediaList_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/ServoUtils.h"
+
+#include "nsWrapperCache.h"
+
+class nsMediaQueryResultCacheKey;
+
+namespace mozilla {
+class ErrorResult;
+class StyleSheet;
+
+namespace dom {
+
+class Document;
+
+class MediaList final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaList)
+
+ // Needed for CSSOM, but please don't use it outside of that :)
+ explicit MediaList(already_AddRefed<StyleLockedMediaList> aRawList)
+ : mRawList(aRawList) {}
+
+ static already_AddRefed<MediaList> Create(
+ const nsACString& aMedia, CallerType aCallerType = CallerType::NonSystem);
+
+ already_AddRefed<MediaList> Clone();
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+ nsISupports* GetParentObject() const;
+
+ void GetText(nsACString&) const;
+ void SetText(const nsACString&);
+ bool Matches(const Document&) const;
+ bool IsViewportDependent() const;
+
+ void SetStyleSheet(StyleSheet* aSheet);
+ void SetRawAfterClone(RefPtr<StyleLockedMediaList> aRaw) {
+ mRawList = std::move(aRaw);
+ }
+
+ // WebIDL
+ void GetMediaText(nsACString& aMediaText) const {
+ return GetText(aMediaText);
+ }
+ void SetMediaText(const nsACString&);
+ uint32_t Length() const;
+ void IndexedGetter(uint32_t aIndex, bool& aFound, nsACString&) const;
+ void Item(uint32_t aIndex, nsACString&);
+ void DeleteMedium(const nsACString&, ErrorResult&);
+ void AppendMedium(const nsACString&, ErrorResult&);
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ protected:
+ MediaList(const nsACString& aMedia, CallerType);
+ MediaList();
+
+ void SetTextInternal(const nsACString& aMediaText, CallerType);
+
+ void Delete(const nsACString& aOldMedium, ErrorResult& aRv);
+ void Append(const nsACString& aNewMedium, ErrorResult& aRv);
+
+ ~MediaList() {
+ MOZ_ASSERT(!mStyleSheet, "Backpointer should have been cleared");
+ }
+
+ bool IsReadOnly() const;
+
+ // not refcounted; sheet will let us know when it goes away
+ // mStyleSheet is the sheet that needs to be dirtied when this
+ // medialist changes
+ StyleSheet* mStyleSheet = nullptr;
+
+ private:
+ template <typename Func>
+ inline void DoMediaChange(Func aCallback, ErrorResult& aRv);
+ RefPtr<StyleLockedMediaList> mRawList;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaList_h
diff --git a/layout/style/MediaQueryList.cpp b/layout/style/MediaQueryList.cpp
new file mode 100644
index 0000000000..b0d0586248
--- /dev/null
+++ b/layout/style/MediaQueryList.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* implements DOM interface for querying and observing media queries */
+
+#include "mozilla/dom/MediaQueryList.h"
+#include "mozilla/dom/MediaQueryListEvent.h"
+#include "mozilla/dom/MediaList.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/EventTargetBinding.h"
+#include "nsPresContext.h"
+#include "mozilla/dom/Document.h"
+
+namespace mozilla::dom {
+
+MediaQueryList::MediaQueryList(Document* aDocument,
+ const nsACString& aMediaQueryList,
+ CallerType aCallerType)
+ : DOMEventTargetHelper(aDocument->GetInnerWindow()),
+ mDocument(aDocument),
+ mMediaList(MediaList::Create(aMediaQueryList, aCallerType)),
+ mViewportDependent(mMediaList->IsViewportDependent()),
+ mMatches(mMediaList->Matches(*aDocument)),
+ mMatchesOnRenderingUpdate(mMatches) {
+ KeepAliveIfHasListenersFor(nsGkAtoms::onchange);
+}
+
+MediaQueryList::~MediaQueryList() = default;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaQueryList)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaQueryList,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaQueryList,
+ DOMEventTargetHelper)
+ if (tmp->mDocument) {
+ static_cast<LinkedListElement<MediaQueryList>*>(tmp)->remove();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+ }
+ tmp->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaQueryList)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(MediaQueryList, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MediaQueryList, DOMEventTargetHelper)
+
+void MediaQueryList::GetMedia(nsACString& aMedia) const {
+ mMediaList->GetText(aMedia);
+}
+
+bool MediaQueryList::Matches() {
+ if (mViewportDependent &&
+ mDocument->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) {
+ RefPtr<Document> doc = mDocument;
+ // This is enough to trigger media query updates in the current doc, and
+ // will flush the parent document layout if appropriate.
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+ return mMatches;
+}
+
+void MediaQueryList::AddListener(EventListener* aListener, ErrorResult& aRv) {
+ if (!aListener) {
+ return;
+ }
+
+ AddEventListenerOptionsOrBoolean options;
+ options.SetAsBoolean() = false;
+
+ AddEventListener(u"change"_ns, aListener, options, Nullable<bool>(), aRv);
+}
+
+void MediaQueryList::RemoveListener(EventListener* aListener,
+ ErrorResult& aRv) {
+ if (!aListener) {
+ return;
+ }
+
+ EventListenerOptionsOrBoolean options;
+ options.SetAsBoolean() = false;
+
+ RemoveEventListener(u"change"_ns, aListener, options, aRv);
+}
+
+bool MediaQueryList::HasListeners() const {
+ return HasListenersFor(nsGkAtoms::onchange);
+}
+
+void MediaQueryList::Disconnect() {
+ DisconnectFromOwner();
+ IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onchange);
+}
+
+nsISupports* MediaQueryList::GetParentObject() const {
+ return ToSupports(mDocument);
+}
+
+JSObject* MediaQueryList::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MediaQueryList_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void MediaQueryList::MediaFeatureValuesChanged() {
+ mMatches = mDocument && mMediaList->Matches(*mDocument);
+ // Note that mMatchesOnRenderingUpdate remains with the old value here.
+ // That gets updated in EvaluateOnRenderingUpdate().
+}
+
+bool MediaQueryList::EvaluateOnRenderingUpdate() {
+ if (mMatches == mMatchesOnRenderingUpdate) {
+ return false;
+ }
+ mMatchesOnRenderingUpdate = mMatches;
+ return HasListeners();
+}
+
+void MediaQueryList::FireChangeEvent() {
+ MediaQueryListEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mMatches = mMatches;
+ mMediaList->GetText(init.mMedia);
+
+ RefPtr<MediaQueryListEvent> event =
+ MediaQueryListEvent::Constructor(this, u"change"_ns, init);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+}
+
+size_t MediaQueryList::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ // mMediaList is reference counted, but it's created and primarily owned
+ // by this MediaQueryList object.
+ n += mMediaList->SizeOfIncludingThis(aMallocSizeOf);
+ return n;
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/MediaQueryList.h b/layout/style/MediaQueryList.h
new file mode 100644
index 0000000000..046fe55dd5
--- /dev/null
+++ b/layout/style/MediaQueryList.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* implements DOM interface for querying and observing media queries */
+
+#ifndef mozilla_dom_MediaQueryList_h
+#define mozilla_dom_MediaQueryList_h
+
+#include "nsISupports.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Attributes.h"
+#include "nsWrapperCache.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/MediaQueryListBinding.h"
+
+namespace mozilla::dom {
+
+class MediaList;
+
+class MediaQueryList final : public DOMEventTargetHelper,
+ public LinkedListElement<MediaQueryList> {
+ public:
+ // The caller who constructs is responsible for calling Evaluate
+ // before calling any other methods.
+ MediaQueryList(Document* aDocument, const nsACString& aMediaQueryList,
+ CallerType);
+
+ private:
+ ~MediaQueryList();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaQueryList, DOMEventTargetHelper)
+
+ nsISupports* GetParentObject() const;
+
+ void MediaFeatureValuesChanged();
+
+ // Returns whether we need to notify of the change by dispatching a change
+ // event.
+ [[nodiscard]] bool EvaluateOnRenderingUpdate();
+ void FireChangeEvent();
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL methods
+ void GetMedia(nsACString& aMedia) const;
+ bool Matches();
+ void AddListener(EventListener* aListener, ErrorResult& aRv);
+ void RemoveListener(EventListener* aListener, ErrorResult& aRv);
+
+ IMPL_EVENT_HANDLER(change)
+
+ bool HasListeners() const;
+
+ void Disconnect();
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ void LastRelease() final {
+ auto* listElement = static_cast<LinkedListElement<MediaQueryList>*>(this);
+ if (listElement->isInList()) {
+ listElement->remove();
+ }
+ }
+
+ void RecomputeMatches();
+
+ // We only need a pointer to the document to support lazy
+ // reevaluation following dynamic changes. However, this lazy
+ // reevaluation is perhaps somewhat important, since some usage
+ // patterns may involve the creation of large numbers of
+ // MediaQueryList objects which almost immediately become garbage
+ // (after a single call to the .matches getter).
+ //
+ // This pointer does make us a little more dependent on cycle
+ // collection.
+ //
+ // We have a non-null mDocument for our entire lifetime except
+ // after cycle collection unlinking. Having a non-null mDocument
+ // is equivalent to being in that document's mDOMMediaQueryLists
+ // linked list.
+ RefPtr<Document> mDocument;
+ const RefPtr<const MediaList> mMediaList;
+ // Whether our MediaList depends on our viewport size. Our medialist is
+ // immutable, so we can just compute this once and carry on with our lives.
+ const bool mViewportDependent : 1;
+ // The matches state.
+ // https://drafts.csswg.org/cssom-view/#mediaquerylist-matches-state
+ bool mMatches : 1;
+ // The value of the matches state on creation, or on the last rendering
+ // update, in order to implement:
+ // https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes
+ bool mMatchesOnRenderingUpdate : 1;
+};
+
+} // namespace mozilla::dom
+
+#endif /* !defined(mozilla_dom_MediaQueryList_h) */
diff --git a/layout/style/PaintWorkletGlobalScope.cpp b/layout/style/PaintWorkletGlobalScope.cpp
new file mode 100644
index 0000000000..ff56e4e3be
--- /dev/null
+++ b/layout/style/PaintWorkletGlobalScope.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "PaintWorkletGlobalScope.h"
+
+#include "mozilla/dom/PaintWorkletGlobalScopeBinding.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "PaintWorkletImpl.h"
+
+namespace mozilla::dom {
+
+PaintWorkletGlobalScope::PaintWorkletGlobalScope(PaintWorkletImpl* aImpl)
+ : WorkletGlobalScope(aImpl) {}
+
+PaintWorkletImpl* PaintWorkletGlobalScope::Impl() const {
+ return static_cast<PaintWorkletImpl*>(mImpl.get());
+}
+
+bool PaintWorkletGlobalScope::WrapGlobalObject(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
+ JS::RealmOptions options = CreateRealmOptions();
+ return PaintWorkletGlobalScope_Binding::Wrap(
+ aCx, this, this, options, nsJSPrincipals::get(mImpl->Principal()),
+ aReflector);
+}
+
+void PaintWorkletGlobalScope::RegisterPaint(const nsAString& aType,
+ VoidFunction& aProcessorCtor) {
+ // Nothing to do here, yet.
+}
+
+} // namespace mozilla::dom
diff --git a/layout/style/PaintWorkletGlobalScope.h b/layout/style/PaintWorkletGlobalScope.h
new file mode 100644
index 0000000000..21858ba16b
--- /dev/null
+++ b/layout/style/PaintWorkletGlobalScope.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_dom_PaintWorkletGlobalScope_h
+#define mozilla_dom_PaintWorkletGlobalScope_h
+
+#include "mozilla/dom/WorkletGlobalScope.h"
+
+namespace mozilla {
+
+class PaintWorkletImpl;
+
+namespace dom {
+
+class VoidFunction;
+
+class PaintWorkletGlobalScope final : public WorkletGlobalScope {
+ public:
+ explicit PaintWorkletGlobalScope(PaintWorkletImpl* aImpl);
+
+ bool WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) override;
+
+ void RegisterPaint(const nsAString& aType, VoidFunction& aProcessorCtor);
+
+ PaintWorkletImpl* Impl() const;
+
+ private:
+ ~PaintWorkletGlobalScope() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PaintWorkletGlobalScope_h
diff --git a/layout/style/PaintWorkletImpl.cpp b/layout/style/PaintWorkletImpl.cpp
new file mode 100644
index 0000000000..14d38b8a1e
--- /dev/null
+++ b/layout/style/PaintWorkletImpl.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "PaintWorkletImpl.h"
+
+#include "PaintWorkletGlobalScope.h"
+#include "mozilla/dom/Worklet.h"
+#include "mozilla/dom/WorkletThread.h"
+
+namespace mozilla {
+
+/* static */ already_AddRefed<dom::Worklet> PaintWorkletImpl::CreateWorklet(
+ nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<PaintWorkletImpl> impl = new PaintWorkletImpl(aWindow, aPrincipal);
+ return MakeAndAddRef<dom::Worklet>(aWindow, std::move(impl));
+}
+
+PaintWorkletImpl::PaintWorkletImpl(nsPIDOMWindowInner* aWindow,
+ nsIPrincipal* aPrincipal)
+ : WorkletImpl(aWindow, aPrincipal) {
+#ifdef RELEASE_OR_BETA
+ MOZ_CRASH("This code should not go to release/beta yet!");
+#endif
+}
+
+PaintWorkletImpl::~PaintWorkletImpl() = default;
+
+already_AddRefed<dom::WorkletGlobalScope>
+PaintWorkletImpl::ConstructGlobalScope() {
+ dom::WorkletThread::AssertIsOnWorkletThread();
+
+ return MakeAndAddRef<dom::PaintWorkletGlobalScope>(this);
+}
+
+} // namespace mozilla
diff --git a/layout/style/PaintWorkletImpl.h b/layout/style/PaintWorkletImpl.h
new file mode 100644
index 0000000000..2562a66336
--- /dev/null
+++ b/layout/style/PaintWorkletImpl.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef PaintWorkletImpl_h
+#define PaintWorkletImpl_h
+
+#include "mozilla/dom/WorkletImpl.h"
+
+namespace mozilla {
+
+class PaintWorkletImpl final : public WorkletImpl {
+ public:
+ // Methods for parent thread only:
+
+ static already_AddRefed<dom::Worklet> CreateWorklet(
+ nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal);
+
+ nsContentPolicyType ContentPolicyType() const override {
+ return nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET;
+ }
+
+ protected:
+ // Execution thread only.
+ already_AddRefed<dom::WorkletGlobalScope> ConstructGlobalScope() override;
+
+ private:
+ PaintWorkletImpl(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal);
+ ~PaintWorkletImpl();
+};
+
+} // namespace mozilla
+
+#endif // PaintWorkletImpl_h
diff --git a/layout/style/PostTraversalTask.cpp b/layout/style/PostTraversalTask.cpp
new file mode 100644
index 0000000000..b6320641ff
--- /dev/null
+++ b/layout/style/PostTraversalTask.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "PostTraversalTask.h"
+
+#include "mozilla/dom/FontFace.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/FontFaceSetImpl.h"
+#include "gfxPlatformFontList.h"
+#include "gfxTextRun.h"
+#include "ServoStyleSet.h"
+#include "nsPresContext.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+void PostTraversalTask::Run() {
+ switch (mType) {
+ case Type::ResolveFontFaceLoadedPromise:
+ static_cast<FontFace*>(mTarget)->MaybeResolve();
+ break;
+
+ case Type::RejectFontFaceLoadedPromise:
+ static_cast<FontFace*>(mTarget)->MaybeReject(mResult);
+ break;
+
+ case Type::DispatchLoadingEventAndReplaceReadyPromise:
+ static_cast<FontFaceSet*>(mTarget)
+ ->DispatchLoadingEventAndReplaceReadyPromise();
+ break;
+
+ case Type::DispatchFontFaceSetCheckLoadingFinishedAfterDelay:
+ static_cast<FontFaceSetImpl*>(mTarget)
+ ->DispatchCheckLoadingFinishedAfterDelay();
+ break;
+
+ case Type::LoadFontEntry:
+ static_cast<gfxUserFontEntry*>(mTarget)->ContinueLoad();
+ break;
+
+ case Type::InitializeFamily:
+ Unused << gfxPlatformFontList::PlatformFontList()->InitializeFamily(
+ static_cast<fontlist::Family*>(mTarget));
+ break;
+
+ case Type::FontInfoUpdate:
+ if (auto* pc = static_cast<ServoStyleSet*>(mTarget)->GetPresContext()) {
+ pc->ForceReflowForFontInfoUpdateFromStyle();
+ }
+ break;
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/style/PostTraversalTask.h b/layout/style/PostTraversalTask.h
new file mode 100644
index 0000000000..64d67b0227
--- /dev/null
+++ b/layout/style/PostTraversalTask.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_PostTraversalTask_h
+#define mozilla_PostTraversalTask_h
+
+#include "nscore.h"
+
+/* a task to be performed immediately after a Servo traversal */
+
+namespace mozilla {
+class ServoStyleSet;
+namespace dom {
+class FontFace;
+class FontFaceSet;
+class FontFaceSetImpl;
+} // namespace dom
+namespace fontlist {
+struct Family;
+} // namespace fontlist
+} // namespace mozilla
+class gfxUserFontEntry;
+
+namespace mozilla {
+
+/**
+ * A PostTraversalTask is a task to be performed immediately after a Servo
+ * traversal. There are just a few tasks we need to perform, so we use this
+ * class rather than Runnables, to avoid virtual calls and some allocations.
+ *
+ * A PostTraversalTask is only safe to run immediately after the Servo
+ * traversal, since it can hold raw pointers to DOM objects.
+ */
+class PostTraversalTask {
+ public:
+ static PostTraversalTask ResolveFontFaceLoadedPromise(
+ dom::FontFace* aFontFace) {
+ auto task = PostTraversalTask(Type::ResolveFontFaceLoadedPromise);
+ task.mTarget = aFontFace;
+ return task;
+ }
+
+ static PostTraversalTask RejectFontFaceLoadedPromise(dom::FontFace* aFontFace,
+ nsresult aResult) {
+ auto task = PostTraversalTask(Type::ResolveFontFaceLoadedPromise);
+ task.mTarget = aFontFace;
+ task.mResult = aResult;
+ return task;
+ }
+
+ static PostTraversalTask DispatchLoadingEventAndReplaceReadyPromise(
+ dom::FontFaceSet* aFontFaceSet) {
+ auto task =
+ PostTraversalTask(Type::DispatchLoadingEventAndReplaceReadyPromise);
+ task.mTarget = aFontFaceSet;
+ return task;
+ }
+
+ static PostTraversalTask DispatchFontFaceSetCheckLoadingFinishedAfterDelay(
+ dom::FontFaceSetImpl* aFontFaceSet) {
+ auto task = PostTraversalTask(
+ Type::DispatchFontFaceSetCheckLoadingFinishedAfterDelay);
+ task.mTarget = aFontFaceSet;
+ return task;
+ }
+
+ static PostTraversalTask LoadFontEntry(gfxUserFontEntry* aFontEntry) {
+ auto task = PostTraversalTask(Type::LoadFontEntry);
+ task.mTarget = aFontEntry;
+ return task;
+ }
+
+ static PostTraversalTask InitializeFamily(fontlist::Family* aFamily) {
+ auto task = PostTraversalTask(Type::InitializeFamily);
+ task.mTarget = aFamily;
+ return task;
+ }
+
+ static PostTraversalTask FontInfoUpdate(ServoStyleSet* aSet) {
+ auto task = PostTraversalTask(Type::FontInfoUpdate);
+ task.mTarget = aSet;
+ return task;
+ }
+
+ void Run();
+
+ private:
+ // For any new raw pointer type that we need to store in a PostTraversalTask,
+ // please add an assertion that class' destructor that we are not in a Servo
+ // traversal, to protect against the possibility of having dangling pointers.
+ enum class Type {
+ // mTarget (FontFace*)
+ ResolveFontFaceLoadedPromise,
+
+ // mTarget (FontFace*)
+ // mResult
+ RejectFontFaceLoadedPromise,
+
+ // mTarget (FontFaceSet*)
+ DispatchLoadingEventAndReplaceReadyPromise,
+
+ // mTarget (FontFaceSetImpl*)
+ DispatchFontFaceSetCheckLoadingFinishedAfterDelay,
+
+ // mTarget (gfxUserFontEntry*)
+ LoadFontEntry,
+
+ // mTarget (fontlist::Family*)
+ InitializeFamily,
+
+ // mTarget (ServoStyleSet*)
+ FontInfoUpdate,
+ };
+
+ explicit PostTraversalTask(Type aType)
+ : mType(aType), mTarget(nullptr), mResult(NS_OK) {}
+
+ Type mType;
+ void* mTarget;
+ nsresult mResult;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_PostTraversalTask_h
diff --git a/layout/style/PreferenceSheet.cpp b/layout/style/PreferenceSheet.cpp
new file mode 100644
index 0000000000..a206209885
--- /dev/null
+++ b/layout/style/PreferenceSheet.cpp
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "PreferenceSheet.h"
+
+#include "ServoCSSParser.h"
+#include "MainThreadUtils.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_devtools.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+
+#define AVG2(a, b) (((a) + (b) + 1) >> 1)
+
+namespace mozilla {
+
+using dom::Document;
+
+bool PreferenceSheet::sInitialized;
+PreferenceSheet::Prefs PreferenceSheet::sContentPrefs;
+PreferenceSheet::Prefs PreferenceSheet::sChromePrefs;
+PreferenceSheet::Prefs PreferenceSheet::sPrintPrefs;
+
+static void GetColor(const char* aPrefName, ColorScheme aColorScheme,
+ nscolor& aColor) {
+ nsAutoCString darkPrefName;
+ if (aColorScheme == ColorScheme::Dark) {
+ darkPrefName.Append(aPrefName);
+ darkPrefName.AppendLiteral(".dark");
+ aPrefName = darkPrefName.get();
+ }
+
+ nsAutoCString value;
+ Preferences::GetCString(aPrefName, value);
+ if (value.IsEmpty() || Encoding::UTF8ValidUpTo(value) != value.Length()) {
+ return;
+ }
+ nscolor result;
+ if (!ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0), value, &result)) {
+ return;
+ }
+ aColor = result;
+}
+
+auto PreferenceSheet::PrefsKindFor(const Document& aDoc) -> PrefsKind {
+ if (aDoc.IsInChromeDocShell()) {
+ return PrefsKind::Chrome;
+ }
+
+ if (aDoc.IsBeingUsedAsImage() && aDoc.ChromeRulesEnabled()) {
+ return PrefsKind::Chrome;
+ }
+
+ if (aDoc.IsStaticDocument()) {
+ return PrefsKind::Print;
+ }
+
+ return PrefsKind::Content;
+}
+
+static bool UseDocumentColors(bool aUseAcccessibilityTheme) {
+ switch (StaticPrefs::browser_display_document_color_use()) {
+ case 1:
+ return true;
+ case 2:
+ return false;
+ default:
+ return !aUseAcccessibilityTheme;
+ }
+}
+
+static bool UseStandinsForNativeColors() {
+ return nsContentUtils::ShouldResistFingerprinting(
+ "we want to have consistent colors across the browser if RFP is "
+ "enabled, so we check the global preference"
+ "not excluding chrome browsers or webpages, so we call the legacy "
+ "RFP function to prevent that",
+ RFPTarget::UseStandinsForNativeColors) ||
+ StaticPrefs::ui_use_standins_for_native_colors();
+}
+
+void PreferenceSheet::Prefs::LoadColors(bool aIsLight) {
+ auto& colors = aIsLight ? mLightColors : mDarkColors;
+
+ if (!aIsLight) {
+ // Initialize the dark-color-scheme foreground/background colors as being
+ // the reverse of these members' default values, for ~reasonable fallback if
+ // the user configures broken pref values.
+ std::swap(colors.mDefault, colors.mDefaultBackground);
+ }
+
+ const auto scheme = aIsLight ? ColorScheme::Light : ColorScheme::Dark;
+
+ // Link colors might be provided by the OS, but they might not be. If they are
+ // not, then fall back to the pref colors.
+ //
+ // In particular, we don't query active link color to the OS.
+ GetColor("browser.anchor_color", scheme, colors.mLink);
+ GetColor("browser.active_color", scheme, colors.mActiveLink);
+ GetColor("browser.visited_color", scheme, colors.mVisitedLink);
+
+ // Historically we've given more weight to the "use standins" setting than the
+ // "use system colors" one. In practice most users don't use standins because
+ // it's hidden behind prefs.
+ if (mUsePrefColors && !mUseStandins) {
+ GetColor("browser.display.background_color", scheme,
+ colors.mDefaultBackground);
+ GetColor("browser.display.foreground_color", scheme, colors.mDefault);
+ } else {
+ using ColorID = LookAndFeel::ColorID;
+ const auto standins = LookAndFeel::UseStandins(mUseStandins);
+ colors.mDefault = LookAndFeel::Color(ColorID::Windowtext, scheme, standins,
+ colors.mDefault);
+ colors.mDefaultBackground = LookAndFeel::Color(
+ ColorID::Window, scheme, standins, colors.mDefaultBackground);
+ colors.mLink = LookAndFeel::Color(ColorID::MozNativehyperlinktext, scheme,
+ standins, colors.mLink);
+
+ if (auto color = LookAndFeel::GetColor(
+ ColorID::MozNativevisitedhyperlinktext, scheme, standins)) {
+ // If the system provides a visited link color, we should use it.
+ colors.mVisitedLink = *color;
+ } else if (mUseAccessibilityTheme) {
+ // The fallback visited link color on HCM (if the system doesn't provide
+ // one) is produced by preserving the foreground's green and averaging the
+ // foreground and background for the red and blue. This is how IE and
+ // Edge do it too.
+ colors.mVisitedLink = NS_RGB(
+ AVG2(NS_GET_R(colors.mDefault), NS_GET_R(colors.mDefaultBackground)),
+ NS_GET_G(colors.mDefault),
+ AVG2(NS_GET_B(colors.mDefault), NS_GET_B(colors.mDefaultBackground)));
+ } else {
+ // Otherwise we keep the default visited link color
+ }
+
+ if (mUseAccessibilityTheme) {
+ colors.mActiveLink = colors.mLink;
+ }
+ }
+
+ // Wherever we got the default background color from, ensure it is opaque.
+ colors.mDefaultBackground =
+ NS_ComposeColors(NS_RGB(0xFF, 0xFF, 0xFF), colors.mDefaultBackground);
+}
+
+bool PreferenceSheet::Prefs::NonNativeThemeShouldBeHighContrast() const {
+ // We only do that if we are overriding the document colors. Otherwise it
+ // causes issues when pages only override some of the system colors,
+ // specially in dark themes mode.
+ return StaticPrefs::widget_non_native_theme_always_high_contrast() ||
+ !mUseDocumentColors;
+}
+
+auto PreferenceSheet::ColorSchemeSettingForChrome()
+ -> ChromeColorSchemeSetting {
+ switch (StaticPrefs::browser_theme_toolbar_theme()) {
+ case 0: // Dark
+ return ChromeColorSchemeSetting::Dark;
+ case 1: // Light
+ return ChromeColorSchemeSetting::Light;
+ default:
+ return ChromeColorSchemeSetting::System;
+ }
+}
+
+ColorScheme PreferenceSheet::ThemeDerivedColorSchemeForContent() {
+ switch (StaticPrefs::browser_theme_content_theme()) {
+ case 0: // Dark
+ return ColorScheme::Dark;
+ case 1: // Light
+ return ColorScheme::Light;
+ default:
+ return LookAndFeel::SystemColorScheme();
+ }
+}
+
+void PreferenceSheet::Prefs::Load(bool aIsChrome) {
+ *this = {};
+
+ mIsChrome = aIsChrome;
+ mUseAccessibilityTheme =
+ LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme);
+ // Chrome documents always use system colors, not stand-ins, not forced, etc.
+ if (!aIsChrome) {
+ mUseDocumentColors = UseDocumentColors(mUseAccessibilityTheme);
+ mUsePrefColors = !StaticPrefs::browser_display_use_system_colors();
+ mUseStandins = UseStandinsForNativeColors();
+ }
+
+ LoadColors(true);
+ LoadColors(false);
+
+ // When forcing the pref colors, we need to forcibly use the light color-set,
+ // as those are the colors exposed to the user in the colors dialog.
+ mMustUseLightColorSet = mUsePrefColors && !mUseDocumentColors;
+#ifdef XP_WIN
+ if (mUseAccessibilityTheme) {
+ // Windows overrides the light colors with the HCM colors when HCM is
+ // active, so make sure to always use the light system colors in that case,
+ // and also make sure that we always use the light color set for the same
+ // reason.
+ mMustUseLightSystemColors = mMustUseLightColorSet = true;
+ }
+#endif
+
+ mColorScheme = [&] {
+ if (aIsChrome) {
+ switch (ColorSchemeSettingForChrome()) {
+ case ChromeColorSchemeSetting::Light:
+ return ColorScheme::Light;
+ case ChromeColorSchemeSetting::Dark:
+ return ColorScheme::Dark;
+ case ChromeColorSchemeSetting::System:
+ break;
+ }
+ return LookAndFeel::SystemColorScheme();
+ }
+ if (mMustUseLightColorSet) {
+ // When forcing colors in a way such as color-scheme isn't respected, we
+ // compute a preference based on the darkness of
+ // our background.
+ return LookAndFeel::IsDarkColor(mLightColors.mDefaultBackground)
+ ? ColorScheme::Dark
+ : ColorScheme::Light;
+ }
+ switch (StaticPrefs::layout_css_prefers_color_scheme_content_override()) {
+ case 0:
+ return ColorScheme::Dark;
+ case 1:
+ return ColorScheme::Light;
+ default:
+ return ThemeDerivedColorSchemeForContent();
+ }
+ }();
+}
+
+void PreferenceSheet::Initialize() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sInitialized);
+
+ sInitialized = true;
+
+ sContentPrefs.Load(false);
+ sChromePrefs.Load(true);
+ sPrintPrefs = sContentPrefs;
+ {
+ // For printing, we always use a preferred-light color scheme.
+ sPrintPrefs.mColorScheme = ColorScheme::Light;
+ if (!sPrintPrefs.mUseDocumentColors) {
+ // When overriding document colors, we ignore the `color-scheme` property,
+ // but we still don't want to use the system colors (which might be dark,
+ // despite having made it into mLightColors), because it both wastes ink
+ // and it might interact poorly with the color adjustments we do while
+ // printing.
+ //
+ // So we override the light colors with our hardcoded default colors, and
+ // force the use of stand-ins.
+ sPrintPrefs.mLightColors = Prefs().mLightColors;
+ sPrintPrefs.mUseStandins = true;
+ }
+ }
+
+ nsAutoString useDocumentColorPref;
+ switch (StaticPrefs::browser_display_document_color_use()) {
+ case 1:
+ useDocumentColorPref.AssignLiteral("always");
+ break;
+ case 2:
+ useDocumentColorPref.AssignLiteral("never");
+ break;
+ default:
+ useDocumentColorPref.AssignLiteral("default");
+ break;
+ }
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_THEME, useDocumentColorPref,
+ sContentPrefs.mUseAccessibilityTheme);
+ if (!sContentPrefs.mUseDocumentColors) {
+ // If a user has chosen to override doc colors through OS HCM or our HCM,
+ // we should log the user's current foreground (text) color and background
+ // color. Note, the document color use pref is the inverse of the HCM
+ // dropdown option in preferences.
+ //
+ // Note that we only look at light colors because that's the color set we
+ // use when forcing colors (since color-scheme is ignored when colors are
+ // forced).
+ //
+ // The light color set is the one that potentially contains the Windows HCM
+ // theme color/background (if we're using system colors and the user is
+ // using a High Contrast theme), and also the colors that as of today we
+ // allow setting in about:preferences.
+ Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_HCM_FOREGROUND,
+ sContentPrefs.mLightColors.mDefault);
+ Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_HCM_BACKGROUND,
+ sContentPrefs.mLightColors.mDefaultBackground);
+ }
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_BACKPLATE,
+ StaticPrefs::browser_display_permit_backplate());
+ Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_USE_SYSTEM_COLORS,
+ StaticPrefs::browser_display_use_system_colors());
+ Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_ALWAYS_UNDERLINE_LINKS,
+ StaticPrefs::layout_css_always_underline_links());
+}
+
+bool PreferenceSheet::AffectedByPref(const nsACString& aPref) {
+ const char* prefNames[] = {
+ StaticPrefs::GetPrefName_privacy_resistFingerprinting(),
+ StaticPrefs::GetPrefName_ui_use_standins_for_native_colors(),
+ "browser.anchor_color",
+ "browser.active_color",
+ "browser.visited_color",
+ };
+
+ if (StringBeginsWith(aPref, "browser.display."_ns)) {
+ return true;
+ }
+
+ for (const char* pref : prefNames) {
+ if (aPref.Equals(pref)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mozilla
+
+#undef AVG2
diff --git a/layout/style/PreferenceSheet.h b/layout/style/PreferenceSheet.h
new file mode 100644
index 0000000000..707a32a708
--- /dev/null
+++ b/layout/style/PreferenceSheet.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Some prefable colors for style system use */
+
+#ifndef mozilla_ColorPreferences_h
+#define mozilla_ColorPreferences_h
+
+#include "nsColor.h"
+#include "mozilla/ColorScheme.h"
+
+namespace mozilla {
+
+namespace dom {
+class Document;
+}
+
+struct PreferenceSheet {
+ struct Prefs {
+ struct Colors {
+ nscolor mLink = NS_RGB(0x00, 0x00, 0xEE);
+ nscolor mActiveLink = NS_RGB(0xEE, 0x00, 0x00);
+ nscolor mVisitedLink = NS_RGB(0x55, 0x1A, 0x8B);
+
+ nscolor mDefault = NS_RGB(0, 0, 0);
+ nscolor mDefaultBackground = NS_RGB(0xFF, 0xFF, 0xFF);
+ } mLightColors, mDarkColors;
+
+ const Colors& ColorsFor(ColorScheme aScheme) const {
+ return mMustUseLightColorSet || aScheme == ColorScheme::Light
+ ? mLightColors
+ : mDarkColors;
+ }
+
+ bool mIsChrome = false;
+ bool mUseAccessibilityTheme = false;
+ bool mUseDocumentColors = true;
+ bool mUsePrefColors = false;
+ bool mUseStandins = false;
+ bool mMustUseLightColorSet = false;
+ bool mMustUseLightSystemColors = false;
+
+ ColorScheme mColorScheme = ColorScheme::Light;
+
+ // Whether the non-native theme should use real system colors for widgets.
+ bool NonNativeThemeShouldBeHighContrast() const;
+
+ void Load(bool aIsChrome);
+ void LoadColors(bool aIsLight);
+ };
+
+ static void EnsureInitialized() {
+ if (sInitialized) {
+ return;
+ }
+ Initialize();
+ }
+
+ static void Refresh() {
+ sInitialized = false;
+ Initialize();
+ }
+
+ static bool AffectedByPref(const nsACString&);
+
+ enum class ChromeColorSchemeSetting { Light, Dark, System };
+ static ChromeColorSchemeSetting ColorSchemeSettingForChrome();
+
+ static ColorScheme ColorSchemeForChrome() {
+ MOZ_ASSERT(sInitialized);
+ return ChromePrefs().mColorScheme;
+ }
+
+ static ColorScheme PreferredColorSchemeForContent() {
+ MOZ_ASSERT(sInitialized);
+ return ContentPrefs().mColorScheme;
+ }
+ static ColorScheme ThemeDerivedColorSchemeForContent();
+
+ static Prefs& ContentPrefs() {
+ MOZ_ASSERT(sInitialized);
+ return sContentPrefs;
+ }
+
+ static Prefs& ChromePrefs() {
+ MOZ_ASSERT(sInitialized);
+ return sChromePrefs;
+ }
+
+ static Prefs& PrintPrefs() {
+ MOZ_ASSERT(sInitialized);
+ return sPrintPrefs;
+ }
+
+ enum class PrefsKind {
+ Chrome,
+ Print,
+ Content,
+ };
+
+ static PrefsKind PrefsKindFor(const dom::Document&);
+
+ static bool ShouldUseChromePrefs(const dom::Document& aDocument) {
+ return PrefsKindFor(aDocument) == PrefsKind::Chrome;
+ }
+
+ static bool MayForceColors() { return !ContentPrefs().mUseDocumentColors; }
+
+ static const Prefs& PrefsFor(const dom::Document& aDocument) {
+ switch (PrefsKindFor(aDocument)) {
+ case PrefsKind::Chrome:
+ return ChromePrefs();
+ case PrefsKind::Print:
+ return PrintPrefs();
+ case PrefsKind::Content:
+ break;
+ }
+ return ContentPrefs();
+ }
+
+ private:
+ static bool sInitialized;
+ static Prefs sChromePrefs;
+ static Prefs sPrintPrefs;
+ static Prefs sContentPrefs;
+
+ static void Initialize();
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/PreloadedStyleSheet.cpp b/layout/style/PreloadedStyleSheet.cpp
new file mode 100644
index 0000000000..6f5896933e
--- /dev/null
+++ b/layout/style/PreloadedStyleSheet.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a CSS style sheet returned from nsIStyleSheetService.preloadSheet */
+
+#include "PreloadedStyleSheet.h"
+
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/Promise.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla {
+
+PreloadedStyleSheet::PreloadedStyleSheet(nsIURI* aURI,
+ css::SheetParsingMode aParsingMode)
+ : mLoaded(false), mURI(aURI), mParsingMode(aParsingMode) {}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PreloadedStyleSheet)
+ NS_INTERFACE_MAP_ENTRY(nsIPreloadedStyleSheet)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PreloadedStyleSheet)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PreloadedStyleSheet)
+
+NS_IMPL_CYCLE_COLLECTION(PreloadedStyleSheet, mSheet)
+
+nsresult PreloadedStyleSheet::GetSheet(StyleSheet** aResult) {
+ *aResult = nullptr;
+
+ MOZ_DIAGNOSTIC_ASSERT(mLoaded);
+
+ if (!mSheet) {
+ RefPtr<css::Loader> loader = new css::Loader;
+ auto result = loader->LoadSheetSync(mURI, mParsingMode,
+ css::Loader::UseSystemPrincipal::Yes);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mSheet = result.unwrap();
+ }
+
+ *aResult = mSheet;
+ return NS_OK;
+}
+
+nsresult PreloadedStyleSheet::Preload() {
+ MOZ_DIAGNOSTIC_ASSERT(!mLoaded);
+ mLoaded = true;
+ StyleSheet* sheet;
+ return GetSheet(&sheet);
+}
+
+NS_IMPL_ISUPPORTS(PreloadedStyleSheet::StylesheetPreloadObserver,
+ nsICSSLoaderObserver)
+
+NS_IMETHODIMP
+PreloadedStyleSheet::StylesheetPreloadObserver::StyleSheetLoaded(
+ StyleSheet* aSheet, bool aWasDeferred, nsresult aStatus) {
+ MOZ_DIAGNOSTIC_ASSERT(!mPreloadedSheet->mLoaded);
+ mPreloadedSheet->mLoaded = true;
+
+ if (NS_FAILED(aStatus)) {
+ mPromise->MaybeReject(aStatus);
+ } else {
+ mPromise->MaybeResolve(mPreloadedSheet);
+ }
+
+ return NS_OK;
+}
+
+// Note: After calling this method, the preloaded sheet *must not* be used
+// until the observer is notified that the sheet has finished loading.
+nsresult PreloadedStyleSheet::PreloadAsync(NotNull<dom::Promise*> aPromise) {
+ MOZ_DIAGNOSTIC_ASSERT(!mLoaded);
+
+ RefPtr<css::Loader> loader = new css::Loader;
+ RefPtr<StylesheetPreloadObserver> obs =
+ new StylesheetPreloadObserver(aPromise, this);
+ auto result = loader->LoadSheet(mURI, mParsingMode,
+ css::Loader::UseSystemPrincipal::No, obs);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mSheet = result.unwrap();
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/layout/style/PreloadedStyleSheet.h b/layout/style/PreloadedStyleSheet.h
new file mode 100644
index 0000000000..e201d76daf
--- /dev/null
+++ b/layout/style/PreloadedStyleSheet.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a CSS style sheet returned from nsIStyleSheetService.preloadSheet */
+
+#ifndef mozilla_PreloadedStyleSheet_h
+#define mozilla_PreloadedStyleSheet_h
+
+#include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/Result.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsIPreloadedStyleSheet.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace dom {
+class Promise;
+}
+
+class StyleSheet;
+
+class PreloadedStyleSheet : public nsIPreloadedStyleSheet {
+ public:
+ PreloadedStyleSheet(nsIURI*, css::SheetParsingMode);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(PreloadedStyleSheet)
+
+ // *aResult is not addrefed, since the PreloadedStyleSheet holds a strong
+ // reference to the sheet.
+ nsresult GetSheet(StyleSheet** aResult);
+
+ nsresult Preload();
+ nsresult PreloadAsync(NotNull<dom::Promise*> aPromise);
+
+ protected:
+ virtual ~PreloadedStyleSheet() = default;
+
+ private:
+ class StylesheetPreloadObserver final : public nsICSSLoaderObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit StylesheetPreloadObserver(NotNull<dom::Promise*> aPromise,
+ PreloadedStyleSheet* aSheet)
+ : mPromise(aPromise), mPreloadedSheet(aSheet) {}
+
+ NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) override;
+
+ protected:
+ virtual ~StylesheetPreloadObserver() = default;
+
+ private:
+ RefPtr<dom::Promise> mPromise;
+ RefPtr<PreloadedStyleSheet> mPreloadedSheet;
+ };
+
+ RefPtr<StyleSheet> mSheet;
+
+ bool mLoaded;
+ nsCOMPtr<nsIURI> mURI;
+ css::SheetParsingMode mParsingMode;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_PreloadedStyleSheet_h
diff --git a/layout/style/PseudoStyleType.cpp b/layout/style/PseudoStyleType.cpp
new file mode 100644
index 0000000000..21d55cc25e
--- /dev/null
+++ b/layout/style/PseudoStyleType.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PseudoStyleType.h"
+
+#include <ostream>
+
+namespace mozilla {
+
+std::ostream& operator<<(std::ostream& aStream, PseudoStyleType aType) {
+ switch (aType) {
+#define CSS_PSEUDO_ELEMENT(_name, _value, _flags) \
+ case PseudoStyleType::_name: \
+ aStream << _value; \
+ break;
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+
+#define CSS_ANON_BOX(_name, _str) \
+ case PseudoStyleType::_name: \
+ aStream << _str; \
+ break;
+#include "nsCSSAnonBoxList.h"
+#undef CSS_ANON_BOX
+
+ case PseudoStyleType::XULTree:
+ case PseudoStyleType::NotPseudo:
+ default:
+ // Output nothing.
+ break;
+ }
+
+ return aStream;
+}
+
+}; // namespace mozilla
diff --git a/layout/style/PseudoStyleType.h b/layout/style/PseudoStyleType.h
new file mode 100644
index 0000000000..6804b500c3
--- /dev/null
+++ b/layout/style/PseudoStyleType.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_PseudoStyleType_h
+#define mozilla_PseudoStyleType_h
+
+#include <cstdint>
+#include <iosfwd>
+
+namespace mozilla {
+
+// The kind of pseudo-style that we have. This can be:
+//
+// * CSS pseudo-elements (see nsCSSPseudoElements.h).
+// * Anonymous boxes (see nsCSSAnonBoxes.h).
+// * XUL tree pseudo-element stuff.
+//
+// This roughly corresponds to the `PseudoElement` enum in Rust code.
+enum class PseudoStyleType : uint8_t {
+// If CSS pseudo-elements stop being first here, change GetPseudoType.
+#define CSS_PSEUDO_ELEMENT(_name, _value, _flags) _name,
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+ CSSPseudoElementsEnd,
+ AnonBoxesStart = CSSPseudoElementsEnd,
+ InheritingAnonBoxesStart = CSSPseudoElementsEnd,
+
+ // Dummy variant so the next variant also has the same discriminant as
+ // AnonBoxesStart.
+ __reset_1 = AnonBoxesStart - 1,
+
+#define CSS_ANON_BOX(_name, _str) _name,
+#define CSS_NON_INHERITING_ANON_BOX(_name, _str)
+#define CSS_WRAPPER_ANON_BOX(_name, _str)
+#include "nsCSSAnonBoxList.h"
+#undef CSS_ANON_BOX
+#undef CSS_WRAPPER_ANON_BOX
+#undef CSS_NON_INHERITING_ANON_BOX
+
+ // Wrapper anon boxes are inheriting anon boxes.
+ WrapperAnonBoxesStart,
+
+ // Dummy variant so the next variant also has the same discriminant as
+ // WrapperAnonBoxesStart.
+ __reset_2 = WrapperAnonBoxesStart - 1,
+
+#define CSS_ANON_BOX(_name, _str)
+#define CSS_NON_INHERITING_ANON_BOX(_name, _str)
+#define CSS_WRAPPER_ANON_BOX(_name, _str) _name,
+#include "nsCSSAnonBoxList.h"
+#undef CSS_ANON_BOX
+#undef CSS_WRAPPER_ANON_BOX
+#undef CSS_NON_INHERITING_ANON_BOX
+
+ WrapperAnonBoxesEnd,
+ InheritingAnonBoxesEnd = WrapperAnonBoxesEnd,
+ NonInheritingAnonBoxesStart = WrapperAnonBoxesEnd,
+
+ __reset_3 = NonInheritingAnonBoxesStart - 1,
+
+#define CSS_ANON_BOX(_name, _str)
+#define CSS_NON_INHERITING_ANON_BOX(_name, _str) _name,
+#include "nsCSSAnonBoxList.h"
+#undef CSS_ANON_BOX
+#undef CSS_NON_INHERITING_ANON_BOX
+
+ NonInheritingAnonBoxesEnd,
+ AnonBoxesEnd = NonInheritingAnonBoxesEnd,
+
+ XULTree = AnonBoxesEnd,
+ NotPseudo,
+ MAX
+};
+
+std::ostream& operator<<(std::ostream&, PseudoStyleType);
+
+class PseudoStyle final {
+ public:
+ using Type = PseudoStyleType;
+
+ // This must match EAGER_PSEUDO_COUNT in Rust code.
+ static const size_t kEagerPseudoCount = 4;
+
+ static bool IsPseudoElement(Type aType) {
+ return aType < Type::CSSPseudoElementsEnd;
+ }
+
+ static bool IsAnonBox(Type aType) {
+ return aType >= Type::AnonBoxesStart && aType < Type::AnonBoxesEnd;
+ }
+
+ static bool IsInheritingAnonBox(Type aType) {
+ return aType >= Type::InheritingAnonBoxesStart &&
+ aType < Type::InheritingAnonBoxesEnd;
+ }
+
+ static bool IsNonInheritingAnonBox(Type aType) {
+ return aType >= Type::NonInheritingAnonBoxesStart &&
+ aType < Type::NonInheritingAnonBoxesEnd;
+ }
+
+ static bool IsWrapperAnonBox(Type aType) {
+ return aType >= Type::WrapperAnonBoxesStart &&
+ aType < Type::WrapperAnonBoxesEnd;
+ }
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/Rule.cpp b/layout/style/Rule.cpp
new file mode 100644
index 0000000000..0a7de42789
--- /dev/null
+++ b/layout/style/Rule.cpp
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 all rule types in a CSS style sheet */
+
+#include "Rule.h"
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/dom/CSSImportRule.h"
+#include "mozilla/dom/DocumentOrShadowRoot.h"
+#include "nsCCUncollectableMarker.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/ServoBindings.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla::css {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Rule)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Rule)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Rule)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(Rule)
+
+bool Rule::IsCCLeaf() const { return !PreservingWrapper(); }
+
+bool Rule::IsKnownLive() const {
+ if (HasKnownLiveWrapper()) {
+ return true;
+ }
+
+ StyleSheet* sheet = GetStyleSheet();
+ if (!sheet) {
+ return false;
+ }
+
+ Document* doc = sheet->GetKeptAliveByDocument();
+ return doc &&
+ nsCCUncollectableMarker::InGeneration(doc->GetMarkedCCGeneration());
+}
+
+void Rule::UnlinkDeclarationWrapper(nsWrapperCache& aDecl) {
+ // We have to be a bit careful here. We have two separate nsWrapperCache
+ // instances, aDecl and this, that both correspond to the same CC participant:
+ // this. If we just used ReleaseWrapper() on one of them, that would
+ // unpreserve that one wrapper, then trace us with a tracer that clears JS
+ // things, and we would clear the wrapper on the cache that has not
+ // unpreserved the wrapper yet. That would violate the invariant that the
+ // cache keeps caching the wrapper until the wrapper dies.
+ //
+ // So we reimplement a modified version of nsWrapperCache::ReleaseWrapper here
+ // that unpreserves both wrappers before doing any clearing.
+ bool needDrop = PreservingWrapper() || aDecl.PreservingWrapper();
+ SetPreservingWrapper(false);
+ aDecl.SetPreservingWrapper(false);
+ if (needDrop) {
+ DropJSObjects(this);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Rule)
+ return tmp->IsCCLeaf() || tmp->IsKnownLive();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Rule)
+ // Please see documentation for nsCycleCollectionParticipant::CanSkip* for why
+ // we need to check HasNothingToTrace here but not in the other two CanSkip
+ // methods.
+ return tmp->IsCCLeaf() || (tmp->IsKnownLive() && tmp->HasNothingToTrace(tmp));
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Rule)
+ return tmp->IsCCLeaf() || tmp->IsKnownLive();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+/* virtual */
+void Rule::DropSheetReference() { mSheet = nullptr; }
+
+void Rule::SetCssText(const nsACString& aCssText) {
+ // We used to throw for some rule types, but not all. Specifically, we did
+ // not throw for StyleRule. Let's just always not throw.
+}
+
+Rule* Rule::GetParentRule() const { return mParentRule; }
+
+#ifdef DEBUG
+void Rule::AssertParentRuleType() {
+ // Would be nice to check that this->Type() is KEYFRAME_RULE when
+ // mParentRule->Tye() is KEYFRAMES_RULE, but we can't call
+ // this->Type() here since it's virtual.
+ if (mParentRule) {
+ auto type = mParentRule->Type();
+ MOZ_ASSERT(type == StyleCssRuleType::Media ||
+ type == StyleCssRuleType::Style ||
+ type == StyleCssRuleType::Document ||
+ type == StyleCssRuleType::Supports ||
+ type == StyleCssRuleType::Keyframes ||
+ type == StyleCssRuleType::LayerBlock ||
+ type == StyleCssRuleType::Container);
+ }
+}
+#endif
+
+bool Rule::IsReadOnly() const {
+ MOZ_ASSERT(!mSheet || !mParentRule ||
+ mSheet->IsReadOnly() == mParentRule->IsReadOnly(),
+ "a parent rule should be read only iff the owning sheet is "
+ "read only");
+ return mSheet && mSheet->IsReadOnly();
+}
+
+bool Rule::IsIncompleteImportRule() const {
+ if (Type() != StyleCssRuleType::Import) {
+ return false;
+ }
+ auto* sheet = static_cast<const dom::CSSImportRule*>(this)->GetStyleSheet();
+ return !sheet || !sheet->IsComplete();
+}
+
+} // namespace mozilla::css
diff --git a/layout/style/Rule.h b/layout/style/Rule.h
new file mode 100644
index 0000000000..a3ff02414e
--- /dev/null
+++ b/layout/style/Rule.h
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 all rule types in a CSS style sheet */
+
+#ifndef mozilla_css_Rule_h___
+#define mozilla_css_Rule_h___
+
+#include "mozilla/dom/CSSRuleBinding.h"
+#include "mozilla/dom/DocumentOrShadowRoot.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+template <class T>
+struct already_AddRefed;
+
+namespace mozilla {
+
+enum class StyleCssRuleType : uint8_t;
+
+namespace css {
+class GroupRule;
+
+class Rule : public nsISupports, public nsWrapperCache {
+ protected:
+ Rule(StyleSheet* aSheet, Rule* aParentRule, uint32_t aLineNumber,
+ uint32_t aColumnNumber)
+ : mSheet(aSheet),
+ mParentRule(aParentRule),
+ mLineNumber(aLineNumber),
+ mColumnNumber(aColumnNumber) {
+#ifdef DEBUG
+ AssertParentRuleType();
+#endif
+ }
+
+#ifdef DEBUG
+ void AssertParentRuleType();
+#endif
+
+ Rule(const Rule& aCopy)
+ : mSheet(aCopy.mSheet),
+ mParentRule(aCopy.mParentRule),
+ mLineNumber(aCopy.mLineNumber),
+ mColumnNumber(aCopy.mColumnNumber) {}
+
+ virtual ~Rule() = default;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS(Rule)
+ // Return true if this rule is known to be a cycle collection leaf, in the
+ // sense that it doesn't have any outgoing owning edges.
+ virtual bool IsCCLeaf() const MOZ_MUST_OVERRIDE;
+
+ virtual bool IsGroupRule() const { return false; }
+
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const = 0;
+#endif
+
+ StyleSheet* GetStyleSheet() const { return mSheet; }
+
+ // Clear the mSheet pointer on this rule and descendants.
+ virtual void DropSheetReference();
+
+ // Clear the mParentRule pointer on this rule.
+ void DropParentRuleReference() { mParentRule = nullptr; }
+
+ void DropReferences() {
+ DropSheetReference();
+ DropParentRuleReference();
+ }
+
+ uint32_t GetLineNumber() const { return mLineNumber; }
+ uint32_t GetColumnNumber() const { return mColumnNumber; }
+
+ // Whether this a rule in a read only style sheet.
+ bool IsReadOnly() const;
+
+ // Whether this rule is an @import rule that hasn't loaded yet (and thus
+ // doesn't affect the style of the page).
+ bool IsIncompleteImportRule() const;
+
+ // This is pure virtual because all of Rule's data members are non-owning and
+ // thus measured elsewhere.
+ virtual size_t SizeOfIncludingThis(MallocSizeOf) const MOZ_MUST_OVERRIDE = 0;
+
+ virtual StyleCssRuleType Type() const = 0;
+
+ // WebIDL interface
+ uint16_t TypeForBindings() const {
+ auto type = uint16_t(Type());
+ // Per https://drafts.csswg.org/cssom/#dom-cssrule-type for constants > 15
+ // we return 0.
+ return type > 15 ? 0 : type;
+ }
+ virtual void GetCssText(nsACString& aCssText) const = 0;
+ void SetCssText(const nsACString& aCssText);
+ Rule* GetParentRule() const;
+ StyleSheet* GetParentStyleSheet() const { return GetStyleSheet(); }
+ nsINode* GetAssociatedDocumentOrShadowRoot() const {
+ if (!mSheet) {
+ return nullptr;
+ }
+ auto* associated = mSheet->GetAssociatedDocumentOrShadowRoot();
+ return associated ? &associated->AsNode() : nullptr;
+ }
+ nsISupports* GetParentObject() const { return mSheet; }
+
+ protected:
+ // True if we're known-live for cycle collection purposes.
+ bool IsKnownLive() const;
+
+ // Hook subclasses can use to properly unlink the nsWrapperCache of
+ // their declarations.
+ void UnlinkDeclarationWrapper(nsWrapperCache& aDecl);
+
+ // mSheet should only ever be null when we create a synthetic CSSFontFaceRule
+ // for an InspectorFontFace.
+ //
+ // mSheet and mParentRule will be cleared when they are detached from the
+ // parent object, either because the rule is removed or the parent is
+ // destroyed.
+ StyleSheet* MOZ_NON_OWNING_REF mSheet;
+ Rule* MOZ_NON_OWNING_REF mParentRule;
+
+ // Keep the same type so that MSVC packs them.
+ uint32_t mLineNumber;
+ uint32_t mColumnNumber;
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_Rule_h___ */
diff --git a/layout/style/RustCell.h b/layout/style/RustCell.h
new file mode 100644
index 0000000000..24f2ebc74d
--- /dev/null
+++ b/layout/style/RustCell.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* an object with the same layout as a Rust std::cell::Cell<T> */
+
+#ifndef mozilla_RustCell_h
+#define mozilla_RustCell_h
+
+namespace mozilla {
+
+/**
+ * Object with the same layout as std::cell::Cell<T>.
+ *
+ * ServoBindings.toml defines a mapping so that generated bindings use a
+ * real std::cell::Cell<T>.
+ *
+ * Note that while the layout matches, this doesn't have the same ABI as a
+ * std::cell::Cell<T>, so values of this type can't be passed over FFI
+ * functions.
+ */
+template <typename T>
+class RustCell {
+ public:
+ RustCell() : mValue() {}
+ explicit RustCell(T aValue) : mValue(aValue) {}
+
+ T Get() const { return mValue; }
+ void Set(T aValue) { mValue = aValue; }
+
+ // That this API doesn't mirror the API of rust's Cell because all values in
+ // C++ effectively act like they're wrapped in Cell<...> already, so this type
+ // only exists for FFI purposes.
+ T* AsPtr() { return &mValue; }
+ const T* AsPtr() const { return &mValue; }
+
+ private:
+ T mValue;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_RustCell_h
diff --git a/layout/style/ServoBindingTypes.h b/layout/style/ServoBindingTypes.h
new file mode 100644
index 0000000000..fe4f96cf0b
--- /dev/null
+++ b/layout/style/ServoBindingTypes.h
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* C++ types corresponding to Servo and Gecko types used across bindings,
+ with some annotations to indicate ownership expectations */
+
+// This file defines RefPtrTraits and UniquePtrTraits helpers for a number of
+// C++ types used to represent strong, and owning references to Servo and Gecko
+// objects that might be used across bindings and FFI.
+//
+// The strong, and owned reference types should be used in FFI function
+// signatures where possible, to help indicate the ownership properties that
+// both sides of the function call must adhere to.
+//
+// Using these types in C++ ========================
+//
+// The Strong types are a C++ struct that wraps a raw pointer. When receiving a
+// Strong value from a Servo_* FFI function, you must call Consume() on it to
+// convert it into an already_AddRefed<RawServo{Type}>, otherwise it will leak.
+//
+// We don't currently have any cases where we pass a Strong value to Servo; this
+// could be done by creating a RawServo{Type}Strong struct value whose mPtr is
+// initialized to the result of calling `.forget().take()` on a
+// RefPtr<RawServo{Type}>, but it's probably easier just to pass a raw pointer
+// and let the Rust code turn it into an Arc.
+//
+// TODO(heycam): We should perhaps have a similar struct for Owned types with a
+// Consume() method to convert them into a UniquePtr. The struct for Strong
+// types at least have [[nodiscard]] on them.
+//
+// Using these types in Rust =========================
+//
+// The FFI type names are available in Rust in the gecko_bindings::bindings mod,
+// which is generated by servo/components/style/build_gecko.rs.
+//
+// Borrowed types in rust are represented by &T, Option<&T>, &mut T, and
+// Option<&mut T>.
+//
+// In C++ you should write them as const pointers (for &T and Option<&T>) or
+// non-const pointers (for &mut T and Option<&mut T>).
+//
+// The Strong types are defined as gecko_bindings::sugar::ownership::Strong<T>.
+//
+// This is an FFI safe type that represents the value with a strong reference
+// already added to it. Dropping a Strong<T> will leak the strong reference.
+//
+// The Owned types are defined as gecko_bindings::sugar::ownership::Owned<T>
+// (or OwnedOrNull<T>). This is another FFI safe type that represents the owning
+// reference to the value. Dropping an Owned<T> will leak the value.
+
+#ifndef mozilla_ServoBindingTypes_h
+#define mozilla_ServoBindingTypes_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/ServoTypes.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/gfx/Types.h"
+#include "nsCSSPropertyID.h"
+#include "nsStyleAutoArray.h"
+#include "nsTArray.h"
+
+// Forward declarations.
+
+class nsCSSPropertyIDSet;
+class nsCSSValue;
+class nsINode;
+class nsPresContext;
+struct nsFontFaceRuleContainer;
+
+namespace mozilla {
+class ComputedStyle;
+class ServoElementSnapshot;
+struct AnimationPropertySegment;
+struct ComputedTiming;
+struct Keyframe;
+struct PropertyStyleAnimationValuePair;
+struct PropertyValuePair;
+struct StyleAnimation;
+struct StyleCssUrlData;
+struct StyleAnimationValue;
+struct StyleStylesheetContents;
+struct URLExtraData;
+using ComputedKeyframeValues = nsTArray<PropertyStyleAnimationValuePair>;
+using GfxMatrix4x4 = mozilla::gfx::Float[16];
+
+namespace dom {
+class StyleChildrenIterator;
+class Document;
+class Element;
+} // namespace dom
+
+} // namespace mozilla
+
+#define SERVO_ARC_TYPE(name_, type_) \
+ extern "C" { \
+ void Servo_##name_##_AddRef(const type_*); \
+ void Servo_##name_##_Release(const type_*); \
+ } \
+ namespace mozilla { \
+ template <> \
+ struct RefPtrTraits<type_> { \
+ static void AddRef(type_* aPtr) { Servo_##name_##_AddRef(aPtr); } \
+ static void Release(type_* aPtr) { Servo_##name_##_Release(aPtr); } \
+ }; \
+ }
+#define SERVO_LOCKED_ARC_TYPE(name_) \
+ namespace mozilla { \
+ struct StyleLocked##name_; \
+ } \
+ SERVO_ARC_TYPE(name_, mozilla::StyleLocked##name_)
+#include "mozilla/ServoLockedArcTypeList.h"
+
+#define UNLOCKED_RULE_TYPE(name_) \
+ namespace mozilla { \
+ struct Style##name_##Rule; \
+ } \
+ SERVO_ARC_TYPE(name_##Rule, mozilla::Style##name_##Rule)
+
+UNLOCKED_RULE_TYPE(Property)
+UNLOCKED_RULE_TYPE(LayerBlock)
+UNLOCKED_RULE_TYPE(LayerStatement)
+UNLOCKED_RULE_TYPE(Namespace)
+UNLOCKED_RULE_TYPE(Container)
+UNLOCKED_RULE_TYPE(Media)
+UNLOCKED_RULE_TYPE(Supports)
+UNLOCKED_RULE_TYPE(Document)
+UNLOCKED_RULE_TYPE(FontFeatureValues)
+UNLOCKED_RULE_TYPE(FontPaletteValues)
+
+SERVO_ARC_TYPE(AnimationValue, mozilla::StyleAnimationValue)
+SERVO_ARC_TYPE(ComputedStyle, mozilla::ComputedStyle)
+SERVO_ARC_TYPE(CssUrlData, mozilla::StyleCssUrlData)
+SERVO_ARC_TYPE(StyleSheetContents, mozilla::StyleStylesheetContents)
+
+#undef UNLOCKED_RULE_TYPE
+#undef SERVO_LOCKED_ARC_TYPE
+#undef SERVO_ARC_TYPE
+
+#define SERVO_BOXED_TYPE(name_, type_) \
+ namespace mozilla { \
+ struct Style##type_; \
+ } \
+ extern "C" void Servo_##name_##_Drop(mozilla::Style##type_*); \
+ namespace mozilla { \
+ template <> \
+ class DefaultDelete<Style##type_> { \
+ public: \
+ void operator()(Style##type_* aPtr) const { Servo_##name_##_Drop(aPtr); } \
+ }; \
+ }
+#include "mozilla/ServoBoxedTypeList.h"
+#undef SERVO_BOXED_TYPE
+
+// Other special cases.
+
+struct RawServoAnimationValueTable;
+
+#endif // mozilla_ServoBindingTypes_h
diff --git a/layout/style/ServoBindings.h b/layout/style/ServoBindings.h
new file mode 100644
index 0000000000..6f19006512
--- /dev/null
+++ b/layout/style/ServoBindings.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* FFI functions for Gecko to call into Servo */
+
+#ifndef mozilla_ServoBindings_h
+#define mozilla_ServoBindings_h
+
+#include "mozilla/ServoStyleConsts.h"
+
+// These functions are defined via macros in Rust, so cbindgen doesn't see them.
+
+namespace mozilla {
+
+// The clang we use on windows complains about returning StyleStrong<> and
+// StyleOwned<>, since the template parameters are incomplete.
+//
+// But they only contain pointers so it is ok. Also, this warning hilariously
+// doesn't exist in GCC.
+//
+// A solution for this is to explicitly instantiate the template, but duplicate
+// instantiations are an error.
+//
+// https://github.com/eqrion/cbindgen/issues/402 tracks an improvement to
+// cbindgen that would allow it to autogenerate the template instantiations on
+// its own.
+#pragma GCC diagnostic push
+#ifdef __clang__
+# pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
+#endif
+
+extern "C" {
+
+#define BASIC_RULE_FUNCS_WITHOUT_GETTER_WITH_PREFIX(type_, prefix_) \
+ void Servo_##type_##_Debug(const mozilla::Style##prefix_##type_*, \
+ nsACString* result); \
+ void Servo_##type_##_GetCssText(const mozilla::Style##prefix_##type_*, \
+ nsACString* result);
+
+#define BASIC_RULE_FUNCS_WITHOUT_GETTER_LOCKED(type_) \
+ BASIC_RULE_FUNCS_WITHOUT_GETTER_WITH_PREFIX(type_, Locked)
+#define BASIC_RULE_FUNCS_WITHOUT_GETTER_UNLOCKED(type_) \
+ BASIC_RULE_FUNCS_WITHOUT_GETTER_WITH_PREFIX(type_, )
+
+#define BASIC_RULE_FUNCS_WITH_PREFIX(type_, prefix_) \
+ StyleStrong<mozilla::Style##prefix_##type_##Rule> \
+ Servo_CssRules_Get##type_##RuleAt(const StyleLockedCssRules* rules, \
+ uint32_t index, uint32_t* line, \
+ uint32_t* column); \
+ void Servo_StyleSet_##type_##RuleChanged( \
+ const StylePerDocumentStyleData*, const Style##prefix_##type_##Rule*, \
+ const StyleDomStyleSheet*, StyleRuleChangeKind); \
+ BASIC_RULE_FUNCS_WITHOUT_GETTER_WITH_PREFIX(type_##Rule, prefix_)
+
+#define BASIC_RULE_FUNCS_LOCKED(type_) \
+ BASIC_RULE_FUNCS_WITH_PREFIX(type_, Locked)
+
+#define BASIC_RULE_FUNCS_UNLOCKED(type_) BASIC_RULE_FUNCS_WITH_PREFIX(type_, )
+
+#define GROUP_RULE_FUNCS_WITH_PREFIX(type_, prefix_) \
+ BASIC_RULE_FUNCS_WITH_PREFIX(type_, prefix_) \
+ StyleStrong<mozilla::StyleLockedCssRules> Servo_##type_##Rule_GetRules( \
+ const mozilla::Style##prefix_##type_##Rule* rule);
+
+#define GROUP_RULE_FUNCS_LOCKED(type_) \
+ GROUP_RULE_FUNCS_WITH_PREFIX(type_, Locked)
+
+#define GROUP_RULE_FUNCS_UNLOCKED(type_) GROUP_RULE_FUNCS_WITH_PREFIX(type_, )
+
+BASIC_RULE_FUNCS_LOCKED(Style)
+BASIC_RULE_FUNCS_LOCKED(Import)
+BASIC_RULE_FUNCS_WITHOUT_GETTER_LOCKED(Keyframe)
+BASIC_RULE_FUNCS_LOCKED(Keyframes)
+GROUP_RULE_FUNCS_UNLOCKED(Media)
+GROUP_RULE_FUNCS_UNLOCKED(Document)
+BASIC_RULE_FUNCS_UNLOCKED(Namespace)
+BASIC_RULE_FUNCS_LOCKED(Page)
+BASIC_RULE_FUNCS_UNLOCKED(Property)
+GROUP_RULE_FUNCS_UNLOCKED(Supports)
+GROUP_RULE_FUNCS_UNLOCKED(LayerBlock)
+BASIC_RULE_FUNCS_UNLOCKED(LayerStatement)
+BASIC_RULE_FUNCS_UNLOCKED(FontFeatureValues)
+BASIC_RULE_FUNCS_UNLOCKED(FontPaletteValues)
+BASIC_RULE_FUNCS_LOCKED(FontFace)
+BASIC_RULE_FUNCS_LOCKED(CounterStyle)
+GROUP_RULE_FUNCS_UNLOCKED(Container)
+
+#undef GROUP_RULE_FUNCS_LOCKED
+#undef GROUP_RULE_FUNCS_UNLOCKED
+#undef GROUP_RULE_FUNCS_WITH_PREFIX
+#undef BASIC_RULE_FUNCS_LOCKED
+#undef BASIC_RULE_FUNCS_UNLOCKED
+#undef BASIC_RULE_FUNCS_WITH_PREFIX
+#undef BASIC_RULE_FUNCS_WITHOUT_GETTER_LOCKED
+#undef BASIC_RULE_FUNCS_WITHOUT_GETTER_UNLOCKED
+#undef BASIC_RULE_FUNCS_WITHOUT_GETTER_WITH_PREFIX
+
+#define BASIC_SERDE_FUNCS(type_) \
+ bool Servo_##type_##_Deserialize(mozilla::ipc::ByteBuf* input, type_* v); \
+ bool Servo_##type_##_Serialize(const type_* v, mozilla::ipc::ByteBuf* output);
+
+BASIC_SERDE_FUNCS(LengthPercentage)
+BASIC_SERDE_FUNCS(StyleRotate)
+BASIC_SERDE_FUNCS(StyleScale)
+BASIC_SERDE_FUNCS(StyleTranslate)
+BASIC_SERDE_FUNCS(StyleTransform)
+BASIC_SERDE_FUNCS(StyleOffsetPath)
+BASIC_SERDE_FUNCS(StyleOffsetRotate)
+BASIC_SERDE_FUNCS(StylePositionOrAuto)
+BASIC_SERDE_FUNCS(StyleOffsetPosition)
+BASIC_SERDE_FUNCS(StyleComputedTimingFunction)
+
+#undef BASIC_SERDE_FUNCS
+
+void Servo_CounterStyleRule_GetDescriptorCssText(
+ const StyleLockedCounterStyleRule* rule, nsCSSCounterDesc desc,
+ nsACString* result);
+
+bool Servo_CounterStyleRule_SetDescriptor(
+ const StyleLockedCounterStyleRule* rule, nsCSSCounterDesc desc,
+ const nsACString* value);
+
+} // extern "C"
+
+#pragma GCC diagnostic pop
+
+} // namespace mozilla
+
+#endif // mozilla_ServoBindings_h
diff --git a/layout/style/ServoBindings.toml b/layout/style/ServoBindings.toml
new file mode 100644
index 0000000000..cc6d13fb5e
--- /dev/null
+++ b/layout/style/ServoBindings.toml
@@ -0,0 +1,691 @@
+[structs]
+headers = [
+ "nsStyleStruct.h",
+ "mozilla/StyleAnimationValue.h",
+ "gfxFontConstants.h",
+ "gfxFontFeatures.h",
+ "COLRFonts.h",
+ "nsStyleConsts.h",
+ "mozilla/css/Loader.h",
+ "mozilla/AnimatedPropertyID.h",
+ "mozilla/css/SheetLoadData.h",
+ "mozilla/DeclarationBlock.h",
+ "mozilla/dom/AnimationEffectBinding.h",
+ "mozilla/dom/HTMLSlotElement.h",
+ "mozilla/dom/KeyframeEffectBinding.h",
+ "mozilla/dom/MediaList.h",
+ "mozilla/dom/ScreenBinding.h",
+ "mozilla/dom/ShadowRoot.h",
+ "mozilla/dom/SVGPathSegBinding.h",
+ "mozilla/ipc/ByteBuf.h",
+ "mozilla/AnimationPropertySegment.h",
+ "mozilla/ComputedTiming.h",
+ "mozilla/CORSMode.h",
+ "mozilla/Keyframe.h",
+ "mozilla/ServoElementSnapshot.h",
+ "mozilla/ServoElementSnapshotTable.h",
+ "mozilla/dom/Element.h",
+ "mozilla/dom/ChildIterator.h",
+ "mozilla/dom/NameSpaceConstants.h",
+ "mozilla/LookAndFeel.h",
+ "mozilla/GeckoBindings.h",
+ "mozilla/ServoBindings.h",
+ "mozilla/ComputedStyle.h",
+ "mozilla/PresShell.h",
+ "mozilla/ServoTraversalStatistics.h",
+ "mozilla/SizeOfState.h",
+ "nsCSSProps.h",
+ "nsNameSpaceManager.h",
+]
+raw-lines = [
+ # FIXME(emilio): Incrementally remove these "pub use"s. Probably
+ # mozilla::css and mozilla::dom are easier.
+ "#[allow(unknown_lints, ambiguous_glob_reexports)]",
+ "pub use self::root::*;",
+ "pub use self::root::mozilla::*;",
+ "pub use self::root::mozilla::css::*;",
+ "pub use self::root::mozilla::dom::*;",
+]
+hide-types = [
+ ".*char_traits",
+ ".*incompatible_char_type",
+ ".*string_view",
+ # https://github.com/rust-lang/rust-bindgen/issues/1503
+ "mozilla::StyleTimingFunction.*",
+ # https://github.com/rust-lang/rust-bindgen/issues/1559
+ "mozilla::StyleGeneric.*",
+ ".*ErrorResult.*",
+]
+bitfield-enums = [
+ "nsChangeHint",
+ "mozilla::OriginFlags",
+ "NodeSelectorFlags",
+]
+rusty-enums = [
+ "nsCompatibility",
+ "mozilla::EffectCompositor_CascadeLevel",
+ "mozilla::SheetType",
+ "mozilla::dom::CallerType",
+ "mozilla::dom::IterationCompositeOperation",
+ "mozilla::dom::CompositeOperation",
+ "mozilla::dom::CompositeOperationOrAuto",
+ "mozilla::InheritTarget",
+ "mozilla::css::DocumentMatchingFunction",
+ "mozilla::css::SheetParsingMode",
+ "nsStyleSVGOpacitySource",
+ "mozilla::dom::Document_DocumentTheme",
+ "mozilla::dom::Document_Type",
+ "mozilla::LookAndFeel_IntID",
+ "mozilla::LookAndFeel_FloatID",
+ "nsCSSUnit",
+ "nsCSSFontDesc",
+ "nsCSSPropertyID",
+ "nsCSSCounterDesc",
+ "nsresult",
+ "nsAtom_AtomKind",
+ "nsStyleImageLayers_LayerType",
+ "mozilla::ServoElementSnapshotFlags",
+ "mozilla::Side",
+ "mozilla::dom::PlaybackDirection",
+ "mozilla::dom::FillMode",
+ "mozilla::dom::ScreenColorGamut",
+ "mozilla::HalfCorner",
+ "mozilla::StyleFloatEdge",
+ "mozilla::StyleShapeRadius",
+ "mozilla::StyleWindowDragging",
+ "mozilla::StyleAnimationPlayState",
+ "mozilla::StyleOrient",
+ "mozilla::StyleBoxSizing",
+ "mozilla::StyleClear",
+ "mozilla::StyleColumnFill",
+ "mozilla::StyleColumnSpan",
+ "mozilla::StyleDirection",
+ "mozilla::StyleFloat",
+ "mozilla::StyleImageOrientation",
+ "mozilla::StyleInert",
+ "mozilla::StyleUserModify",
+ "mozilla::StyleUserInput",
+ "mozilla::StyleUserFind",
+ "mozilla::StyleBoxDirection",
+ "mozilla::StyleRubyAlign",
+ "mozilla::StyleTextSecurity",
+ "mozilla::StyleTextSizeAdjust",
+ "mozilla::StyleHyphens",
+ "mozilla::StyleRubyPosition",
+ "mozilla::StyleShapeSourceType",
+ "mozilla::StyleVisibility",
+ "mozilla::StyleMathStyle",
+ "nsStyleImageLayers_Size_DimensionType",
+ "mozilla::StyleBorderCollapse",
+ "mozilla::StyleBorderImageRepeat",
+ "mozilla::StyleBoxPack",
+ "mozilla::StyleWindowShadow",
+ "mozilla::StyleDominantBaseline",
+ "mozilla::StyleBoxOrient",
+ "mozilla::StyleBoxAlign",
+ "mozilla::StyleUserFocus",
+ "mozilla::StyleUserSelect",
+ "mozilla::StyleImageLayerRepeat",
+ "mozilla::StyleImageLayerAttachment",
+ "mozilla::StylePositionProperty",
+ "mozilla::StyleBoxDecorationBreak",
+ "mozilla::StyleBorderStyle",
+ "mozilla::StyleRuleInclusion",
+ "mozilla::StyleGridTrackBreadth",
+ "mozilla::StyleOverscrollBehavior",
+ "mozilla::StyleImeMode",
+ "mozilla::StyleOverflowAnchor",
+ "mozilla::StyleListStylePosition",
+ "mozilla::StylePointerEvents",
+ "mozilla::StyleScrollbarWidth",
+ "mozilla::StyleWhiteSpaceCollapse",
+ "mozilla::StyleTextWrapMode",
+ "mozilla::StyleTextRendering",
+ "mozilla::StyleFlexDirection",
+ "mozilla::StyleStrokeLinecap",
+ "mozilla::StyleStrokeLinejoin",
+ "mozilla::StyleFlexWrap",
+ "mozilla::StyleMathVariant",
+ "mozilla::StyleTextDecorationSkipInk",
+ "mozilla::StyleTextDecorationLength",
+ "mozilla::StyleMaskType",
+ "mozilla::StyleShapeRendering",
+ "mozilla::StyleTextAnchor",
+ "mozilla::StyleObjectFit",
+ "mozilla::StyleTextDecorationStyle",
+ "mozilla::StyleTopLayer",
+ "mozilla::StyleIsolation",
+ "mozilla::StyleTextOrientation",
+ "mozilla::StyleMozBoxCollapse",
+ "mozilla::StyleMozBoxLayout",
+ "mozilla::StyleTextCombineUpright",
+ "mozilla::StyleUnicodeBidi",
+ "mozilla::StyleTableLayout",
+ "mozilla::StyleEmptyCells",
+ "nsStyleImageType",
+ "nsINode_BooleanFlag",
+ "mozilla::PseudoStyleType",
+ "mozilla::LookAndFeel_ColorID",
+ "mozilla::LookAndFeel_FontID",
+ "nsStyleTransformMatrix::MatrixTransformOperator",
+ "mozilla::StyleGeometryBox",
+ "mozilla::SystemColor",
+ "mozilla::StyleMaskMode",
+ "mozilla::StyleScrollBehavior",
+ "mozilla::StyleColorInterpolation",
+ "mozilla::StyleVectorEffect",
+ "mozilla::StyleBackfaceVisibility",
+ "mozilla::StyleBlend",
+ "mozilla::StyleMaskComposite",
+ "mozilla::StyleWritingModeProperty",
+ "mozilla::StyleTextWrapStyle",
+ "StyleFontVariantEmoji",
+]
+allowlist-vars = [
+ "NS_ATTRVALUE_.*",
+ "NODE_.*",
+ "ELEMENT_.*",
+ "NS_FONT_.*",
+ "NS_STYLE_.*",
+ "NS_MATHML_.*",
+ "NS_RADIUS_.*",
+ "BORDER_COLOR_.*",
+ "BORDER_STYLE_.*",
+ "CSS_PSEUDO_ELEMENT_.*",
+ "SERVO_CSS_PSEUDO_ELEMENT_FLAGS_.*",
+ "kNameSpaceID_.*",
+ "kGenericFont_.*",
+ "kPresContext_.*",
+ "nsNameSpaceManager_.*",
+ "GECKO_IS_NIGHTLY",
+ "NS_SAME_AS_FOREGROUND_COLOR",
+ "mozilla::detail::gGkAtoms",
+ "mozilla::dom::SVGPathSeg_Binding::PATHSEG_.*",
+]
+# TODO(emilio): A bunch of types here can go away once we generate bindings and
+# structs together.
+allowlist-types = [
+ "ServoCssRules",
+ "nsFontFaceRuleContainer",
+ "Matrix4x4Components",
+ "mozilla::ComputedKeyframeValues",
+ "mozilla::Keyframe",
+ "mozilla::PropertyValuePair",
+ "mozilla::DeclarationBlockMutationClosure",
+ "mozilla::AnimatedPropertyID",
+ "mozilla::AnimationPropertySegment",
+ "mozilla::AnonymousCounterStyle",
+ "mozilla::AtomArray",
+ "mozilla::ComputedTiming",
+ "mozilla::Matrix4x4Components",
+ "mozilla::PreferenceSheet",
+ "mozilla::SeenPtrs",
+ "mozilla::ServoElementSnapshot.*",
+ "mozilla::ComputedStyle",
+ "mozilla::StyleSheet",
+ "mozilla::ServoStyleSheetInner",
+ "mozilla::ServoStyleSetSizes",
+ "mozilla::ServoTraversalStatistics",
+ "mozilla::css::LoaderReusableStyleSheets",
+ "mozilla::css::SheetLoadData",
+ "mozilla::css::SheetLoadDataHolder",
+ "mozilla::css::SheetParsingMode",
+ "mozilla::css::DocumentMatchingFunction",
+ "mozilla::dom::IterationCompositeOperation",
+ "mozilla::dom::StyleChildrenIterator",
+ "mozilla::HalfCorner",
+ "mozilla::ipc::ByteBuf",
+ "mozilla::MallocSizeOf",
+ "mozilla::OriginFlags",
+ "mozilla::PropertyStyleAnimationValuePair",
+ "mozilla::ServoTraversalFlags",
+ "mozilla::StyleShapeRadius",
+ "mozilla::StyleGrid.*",
+ "mozilla::UpdateAnimationsTasks",
+ "mozilla::PointerCapabilities",
+ "mozilla::LookAndFeel",
+ "mozilla::gfx::Float",
+ "mozilla::gfx::FontVariation",
+ "mozilla::gfx::FontPaletteValueSet",
+ "mozilla::gfx::FontPaletteValueSet::PaletteValeus",
+ "mozilla::StyleImageLayerAttachment",
+ "gfxFontFeature",
+ "gfxFontVariation",
+ ".*ThreadSafe.*Holder",
+ "AnonymousContent",
+ "AudioContext",
+ "DefaultDelete",
+ "DOMIntersectionObserverEntry",
+ "Element",
+ "mozilla::FontSizePrefs",
+ "FragmentOrURL",
+ "FrameRequestCallback",
+ "GeckoParserExtraData",
+ "GeckoFontMetrics",
+ "gfxFontFeatureValueSet",
+ "GridNamedArea",
+ "mozilla::HalfCorner",
+ "Image",
+ "ImageURL",
+ "Keyframe",
+ "mozilla::MediumFeaturesChangedResult",
+ "nsAttrName",
+ "nsAttrValue",
+ "MiscContainer",
+ "nscolor",
+ "nsChangeHint",
+ "nsCSSCounterDesc",
+ "nsCSSFontDesc",
+ "nsCSSKTableEntry",
+ "nsCSSPropertyID",
+ "nsCSSPropertyIDSet",
+ "nsCSSProps",
+ "nsCSSShadowArray",
+ "nsCSSValue",
+ "nsCSSValueList",
+ "nsCSSValueList_heap",
+ "nsCSSValuePair_heap",
+ "nsCSSValuePairList",
+ "nsCSSValuePairList_heap",
+ "nsCSSValueTriplet_heap",
+ "nsCursorImage",
+ "nsFont",
+ "nsAtom",
+ "nsDynamicAtom",
+ "nsMargin",
+ "nsRect",
+ "nsresult",
+ "nsSimpleContentList",
+ "nsSize",
+ "nsStyleBackground",
+ "nsStyleBorder",
+ "nsStyleColor",
+ "nsStyleColumn",
+ "nsStyleContent",
+ "nsStyleContentData",
+ "ComputedStyle",
+ "nsStyleCounterData",
+ "nsStyleDisplay",
+ "nsStyleEffects",
+ "nsStyleFilter",
+ "nsStyleFont",
+ "nsStyleGradient",
+ "nsStyleGridTemplate",
+ "nsStyleImage",
+ "nsStyleImageLayers",
+ "nsStyleList",
+ "nsStyleMargin",
+ "nsStyleOutline",
+ "nsStylePadding",
+ "nsStylePage",
+ "nsStylePosition",
+ "nsStyleSides",
+ "nsStyleSVG",
+ "nsStyleSVGOpacitySource",
+ "nsStyleSVGReset",
+ "nsStyleTable",
+ "nsStyleTableBorder",
+ "nsStyleText",
+ "nsStyleTextReset",
+ "nsStyleUIReset",
+ "nsStyleUnion",
+ "nsStyleUI",
+ "nsStyleVisibility",
+ "nsStyleXUL",
+ "nsTArrayHeader",
+ "Position",
+ "PropertyValuePair",
+ "Runnable",
+ "ServoAttrSnapshot",
+ "ServoComputedData",
+ "ServoComputedDataBorrowed",
+ "ServoElementSnapshot",
+ "ComputedStyleStrong",
+ "ComputedStyleBorrowed",
+ "ComputedStyleBorrowedOrNull",
+ "SheetParsingMode",
+ "StaticRefPtr",
+ "StyleAnimation",
+ "StyleGeometryBox",
+ "StyleShapeSource",
+ "StyleTransition",
+ "ThemeWidgetType",
+ "mozilla::UniquePtr",
+ "mozilla::DeclarationBlock",
+ "mozilla::DefaultDelete",
+ "mozilla::Side",
+ "mozilla::binding_danger::AssertAndSuppressCleanupPolicy",
+ "mozilla::InheritTarget",
+ "mozilla::dom::MediaList",
+ "mozilla::StyleRuleInclusion",
+ "nsStyleTransformMatrix::MatrixTransformOperator",
+ "NodeSelectorFlags",
+]
+opaque-types = [
+ "mozilla::StyleThinArc", # https://github.com/rust-lang/rust-bindgen/issues/1557
+ "std::pair__PCCP",
+ "std::namespace::atomic___base", "std::atomic__My_base",
+ "std::atomic",
+ "std::atomic___base",
+ "std::tuple.*", # Causes "Cannot find type _Pred in this scope" error on mac, like rust-skia#571
+ "std::.*::tuple.*",
+
+ # We want everything but FontVariation and Float to be opaque but we don't
+ # have negative regexes.
+ "mozilla::gfx::(.{0,4}|.{6,12}|.{14,}|([^F][^o][^n][^t][^V][^a][^r][^i][^a][^t][^i][^o][^n])|([^F][^l][^o][^a][^t]))",
+ "mozilla::dom::Sequence",
+ "mozilla::SmallPointerArray",
+ "mozilla::dom::Optional",
+ "mozilla::dom::OwningNodeOrString_Value",
+ "mozilla::dom::Nullable",
+ "mozilla::external::AtomicRefCounted",
+ "RefPtr_Proxy",
+ "RefPtr_Proxy_member_function",
+ "nsAutoPtr_Proxy",
+ "nsAutoPtr_Proxy_member_function",
+ "mozilla::detail::HashTable", # <- We should be able to remove this and
+ # HashSet below once
+ # https://github.com/rust-lang/rust-bindgen/pull/1515
+ # is available
+ "mozilla::detail::PointerType",
+ "mozilla::HashSet",
+ "mozilla::Pair",
+ "mozilla::Pair_Base",
+ "mozilla::ScrollAxis", # <- For some reason the alignment of this is 4
+ # for clang.
+ "mozilla::SeenPtrs",
+ "mozilla::SupportsWeakPtr",
+ "SupportsWeakPtr",
+ "mozilla::detail::WeakReference",
+ "mozilla::WeakPtr",
+ "nsWritingIterator_reference", "nsReadingIterator_reference",
+ "nsTObserverArray", # <- Inherits from nsAutoTObserverArray<T, 0>
+ "mozilla::DoublyLinkedList",
+ "mozilla::SafeDoublyLinkedList",
+ "nsTHashtable", # <- Inheriting from inner typedefs that clang
+ # doesn't expose properly.
+ "nsTBaseHashSet", # <- Ditto
+ "nsBaseHashtable", "nsRefCountedHashtable", "nsClassHashtable", # <- Ditto
+ "mozilla::dom::Document_SelectorCache", # <- Inherits from nsExpirationTracker<.., 4>
+ "nsPIDOMWindow", # <- Takes the vtable from a template parameter, and we can't
+ # generate it conditionally.
+ "JS::Rooted",
+ "mozilla::Maybe",
+ "mozilla::Variant",
+ "mozilla::dom::TreeOrderedArray", # AutoTArray<>
+ "gfxSize", # <- union { struct { T width; T height; }; T components[2] };
+ "gfxSize_Super", # Ditto.
+ "StyleAnimationValue", # pulls in a whole bunch of stuff we don't need in the bindings
+ "mozilla::dom::.*Callback", # Pulls in ErrorResult and other things that
+ # don't align properly on Linux 32-bit
+ "mozilla::SchedulerGroup", # Non-standard-layout packing of field into superclass
+ "mozilla::Widget.*Event", # As above
+ "mozilla::detail::ThreadLocal.*",
+ "std::make_signed_t",
+ "mozilla::ProfileChunkedBuffer",
+]
+
+# All cbindgen-types are in mod "structs::root::mozilla".
+# FIXME(emilio): We probably want to automate this somehow...
+cbindgen-types = [
+ { gecko = "StyleAnimationIterationCount", servo = "crate::values::computed::AnimationIterationCount" },
+ { gecko = "StyleAnimationTimeline", servo = "crate::values::computed::AnimationTimeline" },
+ { gecko = "StyleAppearance", servo = "crate::values::specified::Appearance" },
+ { gecko = "StyleAspectRatio", servo = "crate::values::computed::position::AspectRatio" },
+ { gecko = "StyleAtom", servo = "crate::Atom" },
+ { gecko = "StyleComputedFontStretchRange", servo = "crate::font_face::ComputedFontStretchRange" },
+ { gecko = "StyleComputedFontStyleDescriptor", servo = "crate::font_face::ComputedFontStyleDescriptor" },
+ { gecko = "StyleComputedFontWeightRange", servo = "crate::font_face::ComputedFontWeightRange" },
+ { gecko = "StyleComputedTimingFunction", servo = "crate::values::computed::easing::TimingFunction" },
+ { gecko = "StylePrefersContrast", servo = "crate::gecko::media_features::PrefersContrast" },
+ { gecko = "StyleCursorKind", servo = "crate::values::computed::ui::CursorKind" },
+ { gecko = "StyleDisplay", servo = "crate::values::specified::Display" },
+ { gecko = "StyleDisplayMode", servo = "crate::gecko::media_features::DisplayMode" },
+ { gecko = "StylePlatform", servo = "crate::gecko::media_features::Platform" },
+ { gecko = "StyleGtkThemeFamily", servo = "crate::gecko::media_features::GtkThemeFamily" },
+ { gecko = "StylePrefersColorScheme", servo = "crate::gecko::media_features::PrefersColorScheme" },
+ { gecko = "StyleScripting", servo = "crate::gecko::media_features::Scripting" },
+ { gecko = "StyleDynamicRange", servo = "crate::gecko::media_features::DynamicRange" },
+ { gecko = "StyleFillRule", servo = "crate::values::generics::basic_shape::FillRule" },
+ { gecko = "StyleFontDisplay", servo = "crate::font_face::FontDisplay" },
+ { gecko = "StyleFontFaceSourceListComponent", servo = "crate::font_face::FontFaceSourceListComponent" },
+ { gecko = "StyleFontFaceSourceFormatKeyword", servo = "crate::font_face::FontFaceSourceFormatKeyword" },
+ { gecko = "StyleFontFaceSourceTechFlags", servo = "crate::font_face::FontFaceSourceTechFlags" },
+ { gecko = "StyleFontLanguageOverride", servo = "crate::values::computed::font::FontLanguageOverride" },
+ { gecko = "StyleOffsetPath", servo = "crate::values::computed::motion::OffsetPath" },
+ { gecko = "StyleOffsetPathFunction", servo = "crate::values::computed::motion::OffsetPathFunction" },
+ { gecko = "StyleGenericOffsetPath", servo = "crate::values::generics::motion::OffsetPath" },
+ { gecko = "StyleGenericOffsetPathFunction", servo = "crate::values::generics::motion::OffsetPathFunction" },
+ { gecko = "StyleMozTheme", servo = "crate::values::computed::ui::MozTheme" },
+ { gecko = "StyleOffsetPosition", servo = "crate::values::computed::motion::OffsetPosition" },
+ { gecko = "StyleOffsetRotate", servo = "crate::values::computed::motion::OffsetRotate" },
+ { gecko = "StylePathCommand", servo = "crate::values::specified::svg_path::PathCommand" },
+ { gecko = "StyleRayFunction", servo = "crate::values::computed::motion::RayFunction" },
+ { gecko = "StyleUnicodeRange", servo = "cssparser::UnicodeRange" },
+ { gecko = "StyleOverflowWrap", servo = "crate::values::computed::OverflowWrap" },
+ { gecko = "StyleWordBreak", servo = "crate::values::computed::WordBreak" },
+ { gecko = "StyleTextJustify", servo = "crate::values::computed::TextJustify" },
+ { gecko = "StyleMozControlCharacterVisibility", servo = "crate::values::computed::text::MozControlCharacterVisibility" },
+ { gecko = "StyleLineBreak", servo = "crate::values::computed::LineBreak" },
+ { gecko = "StyleLineClamp", servo = "crate::values::computed::LineClamp" },
+ { gecko = "StyleUserSelect", servo = "crate::values::computed::UserSelect" },
+ { gecko = "StyleBreakBetween", servo = "crate::values::computed::BreakBetween" },
+ { gecko = "StyleBreakWithin", servo = "crate::values::computed::BreakWithin" },
+ { gecko = "StyleBorderStyle", servo = "crate::values::computed::BorderStyle" },
+ { gecko = "StyleOutlineStyle", servo = "crate::values::computed::OutlineStyle" },
+ { gecko = "StyleScrollSnapAlign", servo = "crate::values::computed::ScrollSnapAlign" },
+ { gecko = "StyleScrollSnapStop", servo = "crate::values::computed::ScrollSnapStop" },
+ { gecko = "StyleScrollSnapStrictness", servo = "crate::values::computed::ScrollSnapStrictness" },
+ { gecko = "StyleScrollSnapType", servo = "crate::values::computed::ScrollSnapType" },
+ { gecko = "StyleAnimationName", servo = "crate::values::computed::AnimationName" },
+ { gecko = "StyleAnimationDirection", servo = "crate::values::computed::AnimationDirection" },
+ { gecko = "StyleAnimationFillMode", servo = "crate::values::computed::AnimationFillMode" },
+ { gecko = "StyleAnimationPlayState", servo = "crate::values::computed::AnimationPlayState" },
+ { gecko = "StyleAnimationComposition", servo = "crate::values::computed::AnimationComposition" },
+ { gecko = "StyleScrollTimelineName", servo = "crate::values::computed::ScrollTimelineName" },
+ { gecko = "StyleScrollAxis", servo = "crate::values::computed::ScrollAxis" },
+ { gecko = "StyleViewTimelineInset", servo = "crate::values::computed::ViewTimelineInset" },
+ { gecko = "StyleResize", servo = "crate::values::computed::Resize" },
+ { gecko = "StyleOverflowClipBox", servo = "crate::values::computed::OverflowClipBox" },
+ { gecko = "StyleFloat", servo = "crate::values::computed::Float" },
+ { gecko = "StyleClear", servo = "crate::values::computed::Clear" },
+ { gecko = "StyleOverscrollBehavior", servo = "crate::values::computed::OverscrollBehavior" },
+ { gecko = "StyleTextAlign", servo = "crate::values::computed::TextAlign" },
+ { gecko = "StyleTextAlignLast", servo = "crate::values::computed::text::TextAlignLast" },
+ { gecko = "StyleTextIndent", servo = "crate::values::computed::text::TextIndent" },
+ { gecko = "StyleTextOverflow", servo = "crate::values::computed::TextOverflow" },
+ { gecko = "StyleOverflow", servo = "crate::values::computed::Overflow" },
+ { gecko = "StyleOverflowAnchor", servo = "crate::values::computed::OverflowAnchor" },
+ { gecko = "StyleTextDecorationSkipInk", servo = "crate::values::computed::TextDecorationSkipInk" },
+ { gecko = "StyleTextDecorationLength", servo = "crate::values::computed::TextDecorationLength" },
+ { gecko = "StyleRubyPosition", servo = "crate::values::computed::RubyPosition" },
+ { gecko = "StyleLength", servo = "crate::values::computed::CSSPixelLength" },
+ { gecko = "StyleLengthPercentage", servo = "crate::values::computed::LengthPercentage" },
+ { gecko = "StyleNonNegativeLengthPercentage", servo = "crate::values::computed::NonNegativeLengthPercentage" },
+ { gecko = "StyleGenericLengthPercentageOrAuto", servo = "crate::values::generics::length::LengthPercentageOrAuto" },
+ { gecko = "StyleGenericLengthPercentageOrNormal", servo = "crate::values::generics::length::LengthPercentageOrNormal" },
+ { gecko = "StyleLengthPercentageOrAuto", servo = "crate::values::computed::LengthPercentageOrAuto" },
+ { gecko = "StyleNonNegativeLengthPercentageOrAuto", servo = "crate::values::computed::NonNegativeLengthPercentageOrAuto" },
+ { gecko = "StyleRect", servo = "crate::values::generics::rect::Rect" },
+ { gecko = "StyleIntersectionObserverRootMargin", servo = "crate::values::specified::gecko::IntersectionObserverRootMargin" },
+ { gecko = "StyleGenericSize", servo = "crate::values::generics::length::Size" },
+ { gecko = "StyleGenericMaxSize", servo = "crate::values::generics::length::MaxSize" },
+ { gecko = "StyleGenericFlexBasis", servo = "crate::values::generics::flex::FlexBasis" },
+ { gecko = "StyleSize", servo = "crate::values::computed::Size" },
+ { gecko = "StyleMaxSize", servo = "crate::values::computed::MaxSize" },
+ { gecko = "StyleFlexBasis", servo = "crate::values::computed::FlexBasis" },
+ { gecko = "StylePosition", servo = "crate::values::computed::Position" },
+ { gecko = "StylePositionOrAuto", servo = "crate::values::computed::PositionOrAuto" },
+ { gecko = "StyleGenericPositionOrAuto", servo = "crate::values::generics::position::PositionOrAuto" },
+ { gecko = "StyleBackgroundSize", servo = "crate::values::computed::BackgroundSize" },
+ { gecko = "StyleGenericBackgroundSize", servo = "crate::values::generics::background::BackgroundSize" },
+ { gecko = "StyleBorderImageSlice", servo = "crate::values::computed::BorderImageSlice" },
+ { gecko = "StyleGenericLengthOrNumber", servo = "crate::values::generics::length::LengthOrNumber" },
+ { gecko = "StyleCSSPixelLength", servo = "crate::values::computed::length::CSSPixelLength" },
+ { gecko = "StyleNonNegativeLength", servo = "crate::values::computed::NonNegativeLength" },
+ { gecko = "StyleNonNegativeNumber", servo = "crate::values::computed::NonNegativeNumber" },
+ { gecko = "StyleZeroToOneNumber", servo = "crate::values::computed::ZeroToOneNumber" },
+ { gecko = "StylePercentage", servo = "crate::values::computed::Percentage" },
+ { gecko = "StylePerspective", servo = "crate::values::computed::Perspective" },
+ { gecko = "StyleGenericPerspective", servo = "crate::values::generics::box_::Perspective" },
+ { gecko = "StyleZIndex", servo = "crate::values::computed::ZIndex" },
+ { gecko = "StyleGenericZIndex", servo = "crate::values::generics::position::ZIndex" },
+ { gecko = "StyleTransformBox", servo = "crate::values::computed::TransformBox" },
+ { gecko = "StyleTransformOrigin", servo = "crate::values::computed::TransformOrigin" },
+ { gecko = "StyleTransformStyle", servo = "crate::values::computed::TransformStyle" },
+ { gecko = "StyleGenericBorderRadius", servo = "crate::values::generics::border::BorderRadius" },
+ { gecko = "StyleLetterSpacing", servo = "crate::values::computed::text::LetterSpacing" },
+ { gecko = "StyleGenericLineHeight", servo = "crate::values::generics::font::LineHeight" },
+ { gecko = "StyleCaretColor", servo = "crate::values::computed::color::CaretColor" },
+ { gecko = "StyleContain", servo = "crate::values::computed::Contain" },
+ { gecko = "StyleContainerType", servo = "crate::values::computed::ContainerType" },
+ { gecko = "StyleContainerName", servo = "crate::values::computed::ContainerName" },
+ { gecko = "StyleRestyleHint", servo = "crate::invalidation::element::restyle_hints::RestyleHint" },
+ { gecko = "StyleTouchAction", servo = "crate::values::computed::TouchAction" },
+ { gecko = "StyleWillChange", servo = "crate::values::specified::box_::WillChange" },
+ { gecko = "StyleColorScheme", servo = "crate::values::specified::color::ColorScheme" },
+ { gecko = "StyleTextDecorationLine", servo = "crate::values::computed::TextDecorationLine" },
+ { gecko = "StyleMasonryAutoFlow", servo = "crate::values::specified::MasonryAutoFlow" },
+ { gecko = "StyleMasonryPlacement", servo = "crate::values::specified::MasonryPlacement" },
+ { gecko = "StyleMasonryItemOrder", servo = "crate::values::specified::MasonryItemOrder" },
+ { gecko = "StyleTextTransform", servo = "crate::values::computed::TextTransform" },
+ { gecko = "StyleTextUnderlinePosition", servo = "crate::values::computed::TextUnderlinePosition" },
+ { gecko = "StyleStrong", servo = "crate::gecko_bindings::sugar::ownership::Strong" },
+ { gecko = "StyleGenericFontFamily", servo = "crate::values::computed::font::GenericFontFamily" },
+ { gecko = "StyleGenericPosition", servo = "crate::values::generics::position::GenericPosition" },
+ { gecko = "StyleGenericCounterPair", servo = "crate::values::generics::counters::GenericCounterPair" },
+ { gecko = "StyleGenericShapeRadius", servo = "crate::values::generics::basic_shape::GenericShapeRadius" },
+ { gecko = "StyleGenericClipRect", servo = "crate::values::generics::GenericClipRect" },
+ { gecko = "StyleGenericCursorImage", servo = "crate::values::generics::ui::GenericCursorImage" },
+ { gecko = "StyleFontFamily", servo = "crate::values::computed::font::FontFamily" },
+ { gecko = "StyleFontSizeAdjust", servo = "crate::values::computed::font::FontSizeAdjust" },
+ { gecko = "StyleFontFamilyNameSyntax", servo = "crate::values::computed::font::FontFamilyNameSyntax" },
+ { gecko = "StyleGenericColor", servo = "crate::values::generics::color::Color" },
+ { gecko = "StyleSystemColor", servo = "crate::values::specified::color::SystemColor" },
+ { gecko = "StyleSystemFont", servo = "crate::values::specified::font::SystemFont" },
+ { gecko = "StyleGenericColorOrAuto", servo = "crate::values::generics::color::ColorOrAuto" },
+ { gecko = "StyleGenericScrollbarColor", servo = "crate::values::generics::ui::ScrollbarColor" },
+ { gecko = "StyleAbsoluteColor", servo = "crate::color::AbsoluteColor" },
+ { gecko = "StyleOrigin", servo = "crate::stylesheets::Origin" },
+ { gecko = "StyleGenericVerticalAlign", servo = "crate::values::generics::box_::VerticalAlign" },
+ { gecko = "StyleVerticalAlignKeyword", servo = "crate::values::generics::box_::VerticalAlignKeyword" },
+ { gecko = "StyleGenericBasicShape", servo = "crate::values::generics::basic_shape::BasicShape" },
+ { gecko = "StyleBasicShape", servo = "crate::values::computed::basic_shape::BasicShape" },
+ { gecko = "StyleGenericInsetRect", servo = "crate::values::generics::basic_shape::InsetRect" },
+ { gecko = "StyleInsetRect", servo = "crate::values::computed::basic_shape::InsetRect" },
+ { gecko = "StyleArcSlice", servo = "style_traits::arc_slice::ArcSlice" },
+ { gecko = "StyleForgottenArcSlicePtr", servo = "style_traits::arc_slice::ForgottenArcSlicePtr" },
+ { gecko = "StyleOwnedSlice", servo = "style_traits::owned_slice::OwnedSlice" },
+ { gecko = "StyleMozContextProperties", servo = "crate::values::specified::svg::MozContextProperties" },
+ { gecko = "StyleQuotes", servo = "crate::values::specified::list::Quotes" },
+ { gecko = "StyleOwnedStr", servo = "style_traits::owned_str::OwnedStr" },
+ { gecko = "StyleGenericBoxShadow", servo = "crate::values::generics::effects::BoxShadow" },
+ { gecko = "StyleGenericSimpleShadow", servo = "crate::values::generics::effects::SimpleShadow" },
+ { gecko = "StyleGenericTransformOperation", servo = "crate::values::generics::transform::TransformOperation" },
+ { gecko = "StyleGenericTransform", servo = "crate::values::generics::transform::Transform" },
+ { gecko = "StyleGenericScale", servo = "crate::values::generics::transform::Scale" },
+ { gecko = "StyleGenericRotate", servo = "crate::values::generics::transform::Rotate" },
+ { gecko = "StyleGenericTranslate", servo = "crate::values::generics::transform::Translate" },
+ { gecko = "StyleAngle", servo = "crate::values::computed::Angle" },
+ { gecko = "StyleGenericBorderImageSideWidth", servo = "crate::values::generics::border::BorderImageSideWidth" },
+ { gecko = "StyleGenericUrlOrNone", servo = "crate::values::generics::url::UrlOrNone" },
+ { gecko = "StyleGenericCalcNode", servo = "crate::values::generics::calc::GenericCalcNode" },
+ { gecko = "StyleCssUrl", servo = "crate::gecko::url::CssUrl" },
+ { gecko = "StyleSpecifiedUrl", servo = "crate::gecko::url::SpecifiedUrl" },
+ { gecko = "StyleSpecifiedImageUrl", servo = "crate::gecko::url::SpecifiedImageUrl" },
+ { gecko = "StyleComputedUrl", servo = "crate::gecko::url::ComputedUrl" },
+ { gecko = "StyleComputedImageUrl", servo = "crate::gecko::url::ComputedImageUrl" },
+ { gecko = "StyleLoadData", servo = "crate::gecko::url::LoadData" },
+ { gecko = "StyleGenericFilter", servo = "crate::values::generics::effects::Filter" },
+ { gecko = "StyleGenericGradient", servo = "crate::values::generics::image::Gradient" },
+ { gecko = "StyleLineDirection", servo = "crate::values::computed::image::LineDirection" },
+ { gecko = "StyleGridTemplateAreas", servo = "crate::values::computed::position::GridTemplateAreas" },
+ { gecko = "StyleGenericGridLine", servo = "crate::values::generics::grid::GridLine" },
+ { gecko = "StyleGenericTrackSize", servo = "crate::values::generics::grid::TrackSize" },
+ { gecko = "StyleGenericTrackBreadth", servo = "crate::values::generics::grid::TrackBreadth" },
+ { gecko = "StyleGenericImplicitGridTracks", servo = "crate::values::generics::grid::ImplicitGridTracks" },
+ { gecko = "StyleImplicitGridTracks", servo = "crate::values::computed::ImplicitGridTracks" },
+ { gecko = "StyleNumberOrPercentage", servo = "crate::values::computed::NumberOrPercentage" },
+ { gecko = "StyleGenericSVGPaint", servo = "crate::values::generics::svg::SVGPaint" },
+ { gecko = "StyleGenericTrackRepeat", servo = "crate::values::generics::grid::TrackRepeat" },
+ { gecko = "StyleGenericTrackListValue", servo = "crate::values::generics::grid::TrackListValue" },
+ { gecko = "StyleGenericTrackList", servo = "crate::values::generics::grid::TrackList" },
+ { gecko = "StyleGenericGridTemplateComponent", servo = "crate::values::generics::grid::GridTemplateComponent" },
+ { gecko = "StyleTextEmphasisStyle", servo = "crate::values::computed::text::TextEmphasisStyle" },
+ { gecko = "StyleTextEmphasisPosition", servo = "crate::values::computed::TextEmphasisPosition" },
+ { gecko = "StyleFontVariantAlternates", servo = "crate::values::specified::font::FontVariantAlternates" },
+ { gecko = "StyleSVGPaintOrder", servo = "crate::values::specified::svg::SVGPaintOrder" },
+ { gecko = "StyleClipRectOrAuto", servo = "crate::values::computed::ClipRectOrAuto" },
+ { gecko = "StyleCounterReset", servo = "crate::values::computed::CounterReset" },
+ { gecko = "StyleCounterSet", servo = "crate::values::computed::CounterSet" },
+ { gecko = "StyleCounterIncrement", servo = "crate::values::computed::CounterIncrement" },
+ { gecko = "StyleContent", servo = "crate::values::computed::counters::Content" },
+ { gecko = "StyleSymbolsType", servo = "crate::values::generics::SymbolsType" },
+ { gecko = "StyleCounterStyle", servo = "crate::values::generics::CounterStyle" },
+ { gecko = "StyleComputedJustifyItems", servo = "crate::values::computed::align::ComputedJustifyItems" },
+ { gecko = "StyleAlignItems", servo = "crate::values::computed::AlignItems" },
+ { gecko = "StyleJustifySelf", servo = "crate::values::computed::JustifySelf" },
+ { gecko = "StyleAlignSelf", servo = "crate::values::computed::AlignSelf" },
+ { gecko = "StyleAlignContent", servo = "crate::values::computed::align::AlignContent" },
+ { gecko = "StyleJustifyTracks", servo = "crate::values::computed::align::JustifyTracks" },
+ { gecko = "StyleAlignTracks", servo = "crate::values::computed::align::AlignTracks" },
+ { gecko = "StyleJustifyContent", servo = "crate::values::computed::align::JustifyContent" },
+ { gecko = "StyleComputedValueFlags", servo = "crate::computed_value_flags::ComputedValueFlags" },
+ { gecko = "StyleImage", servo = "crate::values::computed::Image" },
+ { gecko = "StyleShapeOutside", servo = "crate::values::computed::basic_shape::ShapeOutside" },
+ { gecko = "StyleClipPath", servo = "crate::values::computed::basic_shape::ClipPath" },
+ { gecko = "StyleGridAutoFlow", servo = "crate::values::computed::GridAutoFlow" },
+ { gecko = "StyleCursor", servo = "crate::values::computed::Cursor" },
+ { gecko = "StyleSVGStrokeDashArray", servo = "crate::values::computed::svg::SVGStrokeDashArray" },
+ { gecko = "StyleSVGWidth", servo = "crate::values::computed::svg::SVGWidth" },
+ { gecko = "StyleSVGOpacity", servo = "crate::values::computed::svg::SVGOpacity" },
+ { gecko = "StyleSVGLength", servo = "crate::values::computed::svg::SVGLength" },
+ { gecko = "StyleFontSizeKeyword", servo = "crate::values::specified::font::FontSizeKeyword" },
+ { gecko = "StyleCaptionSide", servo = "crate::values::computed::table::CaptionSide" },
+ { gecko = "StylePageName", servo = "crate::values::specified::page::PageName" },
+ { gecko = "StylePageOrientation", servo = "crate::values::generics::page::PageOrientation" },
+ { gecko = "StylePageSize", servo = "crate::values::computed::page::PageSize" },
+ { gecko = "StyleDProperty", servo = "crate::values::specified::svg::DProperty" },
+ { gecko = "StyleImageRendering", servo = "crate::values::computed::ImageRendering" },
+ { gecko = "StylePrintColorAdjust", servo = "crate::values::computed::PrintColorAdjust" },
+ { gecko = "StyleForcedColorAdjust", servo = "crate::values::computed::ForcedColorAdjust" },
+ { gecko = "StyleScrollbarGutter", servo = "crate::values::computed::ScrollbarGutter" },
+ { gecko = "StyleHyphenateCharacter", servo = "crate::values::computed::HyphenateCharacter" },
+ { gecko = "StyleContentVisibility", servo = "crate::values::computed::ContentVisibility" },
+ { gecko = "StyleContainIntrinsicSize", servo = "crate::values::computed::ContainIntrinsicSize" },
+ { gecko = "StyleFontStyle", servo = "crate::values::computed::font::FontStyle" },
+ { gecko = "StyleFontWeight", servo = "crate::values::computed::font::FontWeight" },
+ { gecko = "StyleFontStretch", servo = "crate::values::computed::font::FontStretch" },
+ { gecko = "StyleFontPalette", servo = "crate::values::computed::font::FontPalette" },
+ { gecko = "StyleFontSynthesis", servo = "crate::values::computed::font::FontSynthesis" },
+ { gecko = "StyleBoolInteger", servo = "crate::values::computed::BoolInteger" },
+ { gecko = "StyleTime", servo = "crate::values::computed::Time" },
+ { gecko = "StyleXTextScale", servo = "crate::values::computed::XTextScale" },
+ { gecko = "StyleZoom", servo = "crate::values::computed::Zoom" },
+ { gecko = "StyleTransitionProperty", servo = "crate::values::computed::TransitionProperty" },
+ { gecko = "StyleAnimationValueMap", servo = "crate::properties::animated_properties::AnimationValueMap" },
+ { gecko = "StyleAuthorStyles", servo = "crate::gecko::data::AuthorStyles" },
+ { gecko = "StyleUseCounters", servo = "crate::use_counters::UseCounters" },
+ { gecko = "StyleStylesheetContents", servo = "crate::stylesheets::StylesheetContents" },
+ { gecko = "StyleAnimationValue", servo = "crate::properties::animated_properties::AnimationValue" },
+ { gecko = "StyleLockedDeclarationBlock", servo = "crate::gecko::arc_types::LockedDeclarationBlock" },
+ { gecko = "StyleLockedMediaList", servo = "crate::gecko::arc_types::LockedMediaList" },
+ { gecko = "StyleLockedImportRule", servo = "crate::gecko::arc_types::LockedImportRule" },
+ { gecko = "StyleLockedFontFaceRule", servo = "crate::gecko::arc_types::LockedFontFaceRule" },
+ { gecko = "StyleBaselineSource", servo = "crate::values::computed::BaselineSource" },
+ { gecko = "StyleAu", servo = "app_units::Au" },
+]
+
+mapped-generic-types = [
+ { generic = true, gecko = "mozilla::RustCell", servo = "::std::cell::Cell" },
+ { generic = false, gecko = "ServoNodeData", servo = "atomic_refcell::AtomicRefCell<crate::data::ElementData>" },
+ { generic = false, gecko = "mozilla::ServoWritingMode", servo = "crate::logical_geometry::WritingMode" },
+ { generic = false, gecko = "mozilla::ServoComputedCustomProperties", servo = "crate::custom_properties::ComputedCustomProperties" },
+ { generic = false, gecko = "mozilla::ServoRuleNode", servo = "Option<crate::rule_tree::StrongRuleNode>" },
+ { generic = false, gecko = "nsACString", servo = "nsstring::nsACString" },
+ { generic = false, gecko = "nsAString", servo = "nsstring::nsAString" },
+ { generic = false, gecko = "nsCString", servo = "nsstring::nsCString" },
+ { generic = false, gecko = "nsString", servo = "nsstring::nsString" },
+]
+
+allowlist-functions = ["Servo_.*", "Gecko_.*"]
diff --git a/layout/style/ServoBoxedTypeList.h b/layout/style/ServoBoxedTypeList.h
new file mode 100644
index 0000000000..c4a206f891
--- /dev/null
+++ b/layout/style/ServoBoxedTypeList.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a list of all Servo Box<T> types used across bindings, for preprocessing */
+
+// The first argument is the name of the Servo type used inside the Arc.
+// This doesn't need to be accurate; it's only used to generate nice looking
+// FFI function names.
+//
+// The second argument is the name of an opaque Gecko type that will
+// correspond to the Servo type used inside the Box. The convention for the
+// the name of the opaque Gecko type is "RawServo{Type}", where {Type} is
+// the name of the Servo type or something close to it.
+//
+// See the comment at the top of ServoBindingTypes.h for how to use these.
+//
+// If you add an entry to this file, you should also add an implementation of
+// HasBoxFFI in Rust.
+//
+// TODO(emilio): We should remove the opaque type now, cbindgen should be able
+// to just generate the forward declaration.
+
+SERVO_BOXED_TYPE(StyleSet, PerDocumentStyleData)
+SERVO_BOXED_TYPE(AnimationValueMap, AnimationValueMap)
+SERVO_BOXED_TYPE(AuthorStyles, AuthorStyles)
+SERVO_BOXED_TYPE(SelectorList, SelectorList)
+SERVO_BOXED_TYPE(SharedMemoryBuilder, SharedMemoryBuilder)
+SERVO_BOXED_TYPE(SourceSizeList, SourceSizeList)
+SERVO_BOXED_TYPE(UseCounters, UseCounters)
diff --git a/layout/style/ServoCSSParser.cpp b/layout/style/ServoCSSParser.cpp
new file mode 100644
index 0000000000..92e1271f2c
--- /dev/null
+++ b/layout/style/ServoCSSParser.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* CSS parsing utility functions */
+
+#include "ServoCSSParser.h"
+
+#include "mozilla/AnimatedPropertyID.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/dom/Document.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/* static */
+bool ServoCSSParser::IsValidCSSColor(const nsACString& aValue) {
+ return Servo_IsValidCSSColor(&aValue);
+}
+
+/* static */
+bool ServoCSSParser::ComputeColor(ServoStyleSet* aStyleSet,
+ nscolor aCurrentColor,
+ const nsACString& aValue,
+ nscolor* aResultColor, bool* aWasCurrentColor,
+ css::Loader* aLoader) {
+ return Servo_ComputeColor(aStyleSet ? aStyleSet->RawData() : nullptr,
+ aCurrentColor, &aValue, aResultColor,
+ aWasCurrentColor, aLoader);
+}
+
+/* static */
+already_AddRefed<StyleLockedDeclarationBlock> ServoCSSParser::ParseProperty(
+ nsCSSPropertyID aProperty, const nsACString& aValue,
+ const ParsingEnvironment& aParsingEnvironment,
+ const StyleParsingMode& aParsingMode) {
+ AnimatedPropertyID property(aProperty);
+ return ParseProperty(property, aValue, aParsingEnvironment, aParsingMode);
+}
+
+/* static */
+already_AddRefed<StyleLockedDeclarationBlock> ServoCSSParser::ParseProperty(
+ const AnimatedPropertyID& aProperty, const nsACString& aValue,
+ const ParsingEnvironment& aParsingEnvironment,
+ const StyleParsingMode& aParsingMode) {
+ return Servo_ParseProperty(
+ &aProperty, &aValue, aParsingEnvironment.mUrlExtraData,
+ aParsingMode, aParsingEnvironment.mCompatMode,
+ aParsingEnvironment.mLoader, aParsingEnvironment.mRuleType)
+ .Consume();
+}
+
+/* static */
+bool ServoCSSParser::ParseEasing(const nsACString& aValue,
+ StyleComputedTimingFunction& aResult) {
+ return Servo_ParseEasing(&aValue, &aResult);
+}
+
+/* static */
+bool ServoCSSParser::ParseTransformIntoMatrix(const nsACString& aValue,
+ bool& aContains3DTransform,
+ gfx::Matrix4x4& aResult) {
+ return Servo_ParseTransformIntoMatrix(&aValue, &aContains3DTransform,
+ &aResult.components);
+}
+
+/* static */
+bool ServoCSSParser::ParseFontShorthandForMatching(
+ const nsACString& aValue, URLExtraData* aUrl, StyleFontFamilyList& aList,
+ StyleFontStyle& aStyle, StyleFontStretch& aStretch,
+ StyleFontWeight& aWeight, float* aSize, bool* aSmallCaps) {
+ return Servo_ParseFontShorthandForMatching(
+ &aValue, aUrl, &aList, &aStyle, &aStretch, &aWeight, aSize, aSmallCaps);
+}
+
+/* static */
+already_AddRefed<URLExtraData> ServoCSSParser::GetURLExtraData(
+ Document* aDocument) {
+ MOZ_ASSERT(aDocument);
+ return do_AddRef(aDocument->DefaultStyleAttrURLData());
+}
+
+/* static */ ServoCSSParser::ParsingEnvironment
+ServoCSSParser::GetParsingEnvironment(Document* aDocument) {
+ return {GetURLExtraData(aDocument), aDocument->GetCompatibilityMode(),
+ aDocument->CSSLoader()};
+}
diff --git a/layout/style/ServoCSSParser.h b/layout/style/ServoCSSParser.h
new file mode 100644
index 0000000000..4bbde6a0e8
--- /dev/null
+++ b/layout/style/ServoCSSParser.h
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* CSS parsing utility functions */
+
+#ifndef mozilla_ServoCSSParser_h
+#define mozilla_ServoCSSParser_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/gfx/Matrix.h"
+#include "nsColor.h"
+#include "nsCSSPropertyID.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsStringFwd.h"
+
+struct nsCSSRect;
+template <class T>
+class RefPtr;
+
+namespace mozilla {
+
+struct AnimatedPropertyID;
+class ServoStyleSet;
+struct URLExtraData;
+struct StyleFontFamilyList;
+struct StyleFontStretch;
+struct StyleFontWeight;
+struct StyleFontStyle;
+struct StyleLockedDeclarationBlock;
+struct StyleParsingMode;
+union StyleComputedFontStyleDescriptor;
+
+template <typename Integer, typename Number, typename LinearStops>
+struct StyleTimingFunction;
+struct StylePiecewiseLinearFunction;
+using StyleComputedTimingFunction =
+ StyleTimingFunction<int32_t, float, StylePiecewiseLinearFunction>;
+
+namespace css {
+class Loader;
+}
+
+namespace dom {
+class Document;
+}
+
+class ServoCSSParser {
+ public:
+ using ParsingEnvironment = nsDOMCSSDeclaration::ParsingEnvironment;
+
+ /**
+ * Returns whether the specified string can be parsed as a valid CSS
+ * <color> value.
+ *
+ * This includes Mozilla-specific keywords such as -moz-default-color.
+ */
+ static bool IsValidCSSColor(const nsACString& aValue);
+
+ /**
+ * Computes an nscolor from the given CSS <color> value.
+ *
+ * @param aStyleSet The style set whose nsPresContext will be used to
+ * compute system colors and other special color values.
+ * @param aCurrentColor The color value that currentcolor should compute to.
+ * @param aValue The CSS <color> value.
+ * @param aResultColor The resulting computed color value.
+ * @param aWasCurrentColor Whether aValue was currentcolor. Can be nullptr
+ * if the caller doesn't care.
+ * @param aLoader The CSS loader for document we're parsing a color for,
+ * so that parse errors can be reported to the console. If nullptr, errors
+ * won't be reported to the console.
+ * @return Whether aValue was successfully parsed and aResultColor was set.
+ */
+ static bool ComputeColor(ServoStyleSet* aStyleSet, nscolor aCurrentColor,
+ const nsACString& aValue, nscolor* aResultColor,
+ bool* aWasCurrentColor = nullptr,
+ css::Loader* aLoader = nullptr);
+
+ /**
+ * Parse a string representing a CSS property value into a
+ * StyleLockedDeclarationBlock.
+ *
+ * @param aProperty The property to be parsed.
+ * @param aValue The specified value.
+ * @param aParsingEnvironment All the parsing environment data we need.
+ * @param aParsingMode The parsing mode we apply.
+ * @return The parsed value as a StyleLockedDeclarationBlock. We put the value
+ * in a declaration block since that is how we represent specified values
+ * in Servo.
+ */
+ static already_AddRefed<StyleLockedDeclarationBlock> ParseProperty(
+ nsCSSPropertyID aProperty, const nsACString& aValue,
+ const ParsingEnvironment& aParsingEnvironment,
+ const StyleParsingMode& aParsingMode);
+ static already_AddRefed<StyleLockedDeclarationBlock> ParseProperty(
+ const AnimatedPropertyID& aProperty, const nsACString& aValue,
+ const ParsingEnvironment& aParsingEnvironment,
+ const StyleParsingMode& aParsingMode);
+
+ /**
+ * Parse a animation timing function.
+ *
+ * @param aValue The specified value.
+ * @param aResult The output timing function. (output)
+ * @return Whether the value was successfully parsed.
+ */
+ static bool ParseEasing(const nsACString& aValue,
+ StyleComputedTimingFunction& aResult);
+
+ /**
+ * Parse a specified transform list into a gfx matrix.
+ *
+ * @param aValue The specified value.
+ * @param aContains3DTransform The output flag indicates whether this is any
+ * 3d transform function. (output)
+ * @param aResult The output matrix. (output)
+ * @return Whether the value was successfully parsed.
+ */
+ static bool ParseTransformIntoMatrix(const nsACString& aValue,
+ bool& aContains3DTransform,
+ gfx::Matrix4x4& aResult);
+
+ /**
+ * Parse a font shorthand for FontFaceSet matching, so we only care about
+ * FontFamily, FontStyle, FontStretch, and FontWeight.
+ *
+ * @param aValue The specified value.
+ * @param aUrl The parser url extra data.
+ * @param aList The parsed FontFamily list. (output)
+ * @param aStyle The parsed FontStyle. (output)
+ * @param aStretch The parsed FontStretch. (output)
+ * @param aWeight The parsed FontWeight. (output)
+ * @param aSize If non-null, returns the parsed font size. (output)
+ * @param aSmallCaps If non-null, whether small-caps was specified (output)
+ * @return Whether the value was successfully parsed.
+ */
+ static bool ParseFontShorthandForMatching(
+ const nsACString& aValue, URLExtraData* aUrl, StyleFontFamilyList& aList,
+ StyleFontStyle& aStyle, StyleFontStretch& aStretch,
+ StyleFontWeight& aWeight, float* aSize = nullptr,
+ bool* aSmallCaps = nullptr);
+
+ /**
+ * Get a URLExtraData from a document.
+ */
+ static already_AddRefed<URLExtraData> GetURLExtraData(dom::Document*);
+
+ /**
+ * Get a ParsingEnvironment from a document.
+ */
+ static ParsingEnvironment GetParsingEnvironment(dom::Document*);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoCSSParser_h
diff --git a/layout/style/ServoCSSPropList.mako.py b/layout/style/ServoCSSPropList.mako.py
new file mode 100644
index 0000000000..56061288d5
--- /dev/null
+++ b/layout/style/ServoCSSPropList.mako.py
@@ -0,0 +1,169 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+def _assign_slots(obj, args):
+ for i, attr in enumerate(obj.__slots__):
+ setattr(obj, attr, args[i])
+
+
+class Longhand(object):
+ __slots__ = ["name", "method", "id", "rules", "flags", "pref", "aliases"]
+
+ def __init__(self, *args):
+ _assign_slots(self, args)
+
+ @staticmethod
+ def type():
+ return "longhand"
+
+
+class Shorthand(object):
+ __slots__ = ["name", "method", "id", "rules", "flags", "pref", "subprops", "aliases"]
+
+ def __init__(self, *args):
+ _assign_slots(self, args)
+
+ @staticmethod
+ def type():
+ return "shorthand"
+
+
+class Alias(object):
+ __slots__ = ["name", "method", "alias_id", "prop_id", "rules", "flags", "pref"]
+
+ def __init__(self, *args):
+ _assign_slots(self, args)
+
+ @staticmethod
+ def type():
+ return "alias"
+
+<%!
+# See bug 1454823 for situation of internal flag.
+def is_internal(prop):
+ # A property which is not controlled by pref and not enabled in
+ # content by default is an internal property.
+ return not prop.gecko_pref and not prop.enabled_in_content()
+
+def method(prop):
+ if prop.name == "float":
+ return "CssFloat"
+ if prop.name.startswith("-x-"):
+ return prop.camel_case[1:]
+ return prop.camel_case
+
+# TODO(emilio): Get this to zero.
+LONGHANDS_NOT_SERIALIZED_WITH_SERVO = [
+ # Servo serializes one value when both are the same, a few tests expect two.
+ "border-spacing",
+
+ # These resolve auto to zero in a few cases, but not all.
+ "max-height",
+ "max-width",
+ "min-height",
+ "min-width",
+
+ # resistfingerprinting stuff.
+ "-moz-osx-font-smoothing",
+
+ # Layout dependent.
+ "width",
+ "height",
+ "grid-template-rows",
+ "grid-template-columns",
+ "perspective-origin",
+ "transform-origin",
+ "transform",
+ "top",
+ "right",
+ "bottom",
+ "left",
+ "border-top-width",
+ "border-right-width",
+ "border-bottom-width",
+ "border-left-width",
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left",
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left",
+]
+
+def serialized_by_servo(prop):
+ if prop.type() == "shorthand" or prop.type() == "alias":
+ return True
+ # Keywords are all fine, except -moz-osx-font-smoothing, which does
+ # resistfingerprinting stuff.
+ if prop.keyword and prop.name != "-moz-osx-font-smoothing":
+ return True
+ return prop.name not in LONGHANDS_NOT_SERIALIZED_WITH_SERVO
+
+def exposed_on_getcs(prop):
+ if "Style" not in prop.rule_types_allowed_names():
+ return False
+ if is_internal(prop):
+ return False
+ return True
+
+def rules(prop):
+ return ", ".join('"{}"'.format(rule) for rule in prop.rule_types_allowed_names())
+
+RUST_TO_CPP_FLAGS = {
+ "CAN_ANIMATE_ON_COMPOSITOR": "CanAnimateOnCompositor",
+ "AFFECTS_LAYOUT": "AffectsLayout",
+ "AFFECTS_PAINT": "AffectsPaint",
+ "AFFECTS_OVERFLOW": "AffectsOverflow",
+}
+
+def flags(prop):
+ result = []
+ if prop.explicitly_enabled_in_chrome():
+ result.append("EnabledInUASheetsAndChrome")
+ elif prop.explicitly_enabled_in_ua_sheets():
+ result.append("EnabledInUASheets")
+ if is_internal(prop):
+ result.append("Internal")
+ if prop.enabled_in == "":
+ result.append("Inaccessible")
+ for (k, v) in RUST_TO_CPP_FLAGS.items():
+ if k in prop.flags:
+ result.append(v)
+ if exposed_on_getcs(prop):
+ result.append("ExposedOnGetCS")
+ if prop.type() == "shorthand" and "SHORTHAND_IN_GETCS" in prop.flags:
+ result.append("ShorthandUnconditionallyExposedOnGetCS")
+ if serialized_by_servo(prop):
+ result.append("SerializedByServo")
+ if prop.type() == "longhand" and prop.logical:
+ result.append("IsLogical")
+ return ", ".join('"{}"'.format(flag) for flag in result)
+
+def pref(prop):
+ if prop.gecko_pref:
+ return '"' + prop.gecko_pref + '"'
+ return '""'
+
+def sub_properties(prop):
+ return ", ".join('"{}"'.format(p.ident) for p in prop.sub_properties)
+
+def aliases(prop):
+ return ", ".join('"{}"'.format(p.ident) for p in prop.aliases)
+%>
+
+data = {
+% for prop in data.longhands:
+ "${prop.ident}": Longhand("${prop.name}", "${method(prop)}", "${prop.ident}", [${rules(prop)}], [${flags(prop)}], ${pref(prop)}, [${aliases(prop)}]),
+% endfor
+
+% for prop in data.shorthands:
+ "${prop.ident}": Shorthand("${prop.name}", "${prop.camel_case}", "${prop.ident}", [${rules(prop)}], [${flags(prop)}], ${pref(prop)}, [${sub_properties(prop)}], [${aliases(prop)}]),
+% endfor
+
+% for prop in data.all_aliases():
+ "${prop.ident}": Alias("${prop.name}", "${prop.camel_case}", "${prop.ident}", "${prop.original.ident}", [${rules(prop)}], [${flags(prop)}], ${pref(prop)}),
+% endfor
+}
diff --git a/layout/style/ServoCSSRuleList.cpp b/layout/style/ServoCSSRuleList.cpp
new file mode 100644
index 0000000000..6fcdfdd4b5
--- /dev/null
+++ b/layout/style/ServoCSSRuleList.cpp
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* representation of CSSRuleList for stylo */
+
+#include "mozilla/ServoCSSRuleList.h"
+
+#include "mozilla/dom/CSSCounterStyleRule.h"
+#include "mozilla/dom/CSSFontFaceRule.h"
+#include "mozilla/dom/CSSFontFeatureValuesRule.h"
+#include "mozilla/dom/CSSFontPaletteValuesRule.h"
+#include "mozilla/dom/CSSImportRule.h"
+#include "mozilla/dom/CSSLayerBlockRule.h"
+#include "mozilla/dom/CSSLayerStatementRule.h"
+#include "mozilla/dom/CSSKeyframesRule.h"
+#include "mozilla/dom/CSSContainerRule.h"
+#include "mozilla/dom/CSSMediaRule.h"
+#include "mozilla/dom/CSSMozDocumentRule.h"
+#include "mozilla/dom/CSSNamespaceRule.h"
+#include "mozilla/dom/CSSPageRule.h"
+#include "mozilla/dom/CSSPropertyRule.h"
+#include "mozilla/dom/CSSStyleRule.h"
+#include "mozilla/dom/CSSSupportsRule.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/dom/Document.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+ServoCSSRuleList::ServoCSSRuleList(
+ already_AddRefed<StyleLockedCssRules> aRawRules, StyleSheet* aSheet,
+ css::GroupRule* aParentRule)
+ : mStyleSheet(aSheet), mParentRule(aParentRule), mRawRules(aRawRules) {
+ ResetRules();
+}
+
+// QueryInterface implementation for ServoCSSRuleList
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServoCSSRuleList)
+NS_INTERFACE_MAP_END_INHERITING(dom::CSSRuleList)
+
+NS_IMPL_ADDREF_INHERITED(ServoCSSRuleList, dom::CSSRuleList)
+NS_IMPL_RELEASE_INHERITED(ServoCSSRuleList, dom::CSSRuleList)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ServoCSSRuleList)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ServoCSSRuleList)
+ tmp->DropAllRules();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(dom::CSSRuleList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ServoCSSRuleList,
+ dom::CSSRuleList)
+ tmp->EnumerateInstantiatedRules([&](css::Rule* aRule, uint32_t) {
+ if (!aRule->IsCCLeaf()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mRules[i]");
+ cb.NoteXPCOMChild(aRule);
+ }
+ });
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+css::Rule* ServoCSSRuleList::GetRule(uint32_t aIndex) {
+ uintptr_t rule = mRules[aIndex];
+ if (rule <= kMaxRuleType) {
+ RefPtr<css::Rule> ruleObj = nullptr;
+ switch (StyleCssRuleType(rule)) {
+#define CASE_RULE_WITH_PREFIX(const_, prefix_, name_) \
+ case StyleCssRuleType::const_: { \
+ uint32_t line = 0, column = 0; \
+ RefPtr<Style##prefix_##const_##Rule> raw = \
+ Servo_CssRules_Get##const_##RuleAt(mRawRules, aIndex, &line, &column) \
+ .Consume(); \
+ MOZ_ASSERT(raw); \
+ ruleObj = new CSS##name_##Rule(raw.forget(), mStyleSheet, mParentRule, \
+ line, column); \
+ MOZ_ASSERT(ruleObj->Type() == StyleCssRuleType(rule)); \
+ break; \
+ }
+#define CASE_RULE_LOCKED(const_, name_) \
+ CASE_RULE_WITH_PREFIX(const_, Locked, name_)
+#define CASE_RULE_UNLOCKED(const_, name_) CASE_RULE_WITH_PREFIX(const_, , name_)
+ CASE_RULE_LOCKED(Style, Style)
+ CASE_RULE_LOCKED(Keyframes, Keyframes)
+ CASE_RULE_UNLOCKED(Media, Media)
+ CASE_RULE_UNLOCKED(Namespace, Namespace)
+ CASE_RULE_LOCKED(Page, Page)
+ CASE_RULE_UNLOCKED(Property, Property)
+ CASE_RULE_UNLOCKED(Supports, Supports)
+ CASE_RULE_UNLOCKED(Document, MozDocument)
+ CASE_RULE_LOCKED(Import, Import)
+ CASE_RULE_UNLOCKED(FontFeatureValues, FontFeatureValues)
+ CASE_RULE_UNLOCKED(FontPaletteValues, FontPaletteValues)
+ CASE_RULE_LOCKED(FontFace, FontFace)
+ CASE_RULE_LOCKED(CounterStyle, CounterStyle)
+ CASE_RULE_UNLOCKED(LayerBlock, LayerBlock)
+ CASE_RULE_UNLOCKED(LayerStatement, LayerStatement)
+ CASE_RULE_UNLOCKED(Container, Container)
+#undef CASE_RULE_LOCKED
+#undef CASE_RULE_UNLOCKED
+#undef CASE_RULE_WITH_PREFIX
+ case StyleCssRuleType::Keyframe:
+ MOZ_ASSERT_UNREACHABLE("keyframe rule cannot be here");
+ return nullptr;
+ case StyleCssRuleType::Margin:
+ // Margin rules not implemented yet, see bug 1864737
+ return nullptr;
+ }
+ rule = CastToUint(ruleObj.forget().take());
+ mRules[aIndex] = rule;
+ }
+ return CastToPtr(rule);
+}
+
+css::Rule* ServoCSSRuleList::IndexedGetter(uint32_t aIndex, bool& aFound) {
+ if (aIndex >= mRules.Length()) {
+ aFound = false;
+ return nullptr;
+ }
+ aFound = true;
+ return GetRule(aIndex);
+}
+
+template <typename Func>
+void ServoCSSRuleList::EnumerateInstantiatedRules(Func aCallback) {
+ uint32_t index = 0;
+ for (uintptr_t rule : mRules) {
+ if (rule > kMaxRuleType) {
+ aCallback(CastToPtr(rule), index);
+ }
+ index++;
+ }
+}
+
+static void DropRule(already_AddRefed<css::Rule> aRule) {
+ RefPtr<css::Rule> rule = aRule;
+ rule->DropReferences();
+}
+
+void ServoCSSRuleList::ResetRules() {
+ // DropRule could reenter here via the cycle collector.
+ auto rules = std::move(mRules);
+ for (uintptr_t rule : rules) {
+ if (rule > kMaxRuleType) {
+ DropRule(already_AddRefed<css::Rule>(CastToPtr(rule)));
+ }
+ }
+ MOZ_ASSERT(mRules.IsEmpty());
+ if (mRawRules) {
+ Servo_CssRules_ListTypes(mRawRules, &mRules);
+ }
+}
+
+void ServoCSSRuleList::DropAllRules() {
+ mStyleSheet = nullptr;
+ mParentRule = nullptr;
+ mRawRules = nullptr;
+
+ ResetRules();
+}
+
+void ServoCSSRuleList::DropSheetReference() {
+ // If mStyleSheet is not set on this rule list, then it is not set on any of
+ // its instantiated rules either. To avoid O(N^2) beavhior in the depth of
+ // group rule nesting, which can happen if we are unlinked starting from the
+ // deepest nested group rule, skip recursing into the rule list if we know we
+ // don't need to.
+ if (!mStyleSheet) {
+ return;
+ }
+ mStyleSheet = nullptr;
+ EnumerateInstantiatedRules(
+ [](css::Rule* rule, uint32_t) { rule->DropSheetReference(); });
+}
+
+void ServoCSSRuleList::DropParentRuleReference() {
+ mParentRule = nullptr;
+ EnumerateInstantiatedRules(
+ [](css::Rule* rule, uint32_t) { rule->DropParentRuleReference(); });
+}
+
+nsresult ServoCSSRuleList::InsertRule(const nsACString& aRule,
+ uint32_t aIndex) {
+ MOZ_ASSERT(mStyleSheet,
+ "Caller must ensure that "
+ "the list is not unlinked from stylesheet");
+
+ if (!mRawRules || IsReadOnly()) {
+ return NS_OK;
+ }
+
+ mStyleSheet->WillDirty();
+
+ css::Loader* loader = nullptr;
+ auto allowImportRules = mStyleSheet->SelfOrAncestorIsConstructed()
+ ? StyleAllowImportRules::No
+ : StyleAllowImportRules::Yes;
+
+ // TODO(emilio, bug 1535456): Should probably always be able to get a handle
+ // to some loader if we're parsing an @import rule, but which one?
+ //
+ // StyleSheet::ReparseSheet just mints a new loader, but that'd be wrong in
+ // this case I think, since such a load will bypass CSP checks.
+ if (Document* doc = mStyleSheet->GetAssociatedDocument()) {
+ loader = doc->CSSLoader();
+ }
+ StyleCssRuleType type;
+ uint32_t containingTypes = 0;
+ for (css::Rule* rule = mParentRule; rule; rule = rule->GetParentRule()) {
+ containingTypes |= (1 << uint32_t(rule->Type()));
+ }
+ nsresult rv = Servo_CssRules_InsertRule(
+ mRawRules, mStyleSheet->RawContents(), &aRule, aIndex, containingTypes,
+ loader, allowImportRules, mStyleSheet, &type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mRules.InsertElementAt(aIndex, uintptr_t(type));
+ return rv;
+}
+
+nsresult ServoCSSRuleList::DeleteRule(uint32_t aIndex) {
+ if (!mRawRules || IsReadOnly()) {
+ return NS_OK;
+ }
+
+ nsresult rv = Servo_CssRules_DeleteRule(mRawRules, aIndex);
+ if (!NS_FAILED(rv)) {
+ uintptr_t rule = mRules[aIndex];
+ mRules.RemoveElementAt(aIndex);
+ if (rule > kMaxRuleType) {
+ DropRule(already_AddRefed<css::Rule>(CastToPtr(rule)));
+ }
+ }
+ return rv;
+}
+
+void ServoCSSRuleList::SetRawContents(RefPtr<StyleLockedCssRules> aNewRules,
+ bool aFromClone) {
+ mRawRules = std::move(aNewRules);
+ if (!aFromClone) {
+ ResetRules();
+ return;
+ }
+
+ EnumerateInstantiatedRules([&](css::Rule* aRule, uint32_t aIndex) {
+#define RULE_CASE_WITH_PREFIX(constant_, prefix_, type_) \
+ case StyleCssRuleType::constant_: { \
+ uint32_t line = 0, column = 0; \
+ RefPtr<Style##prefix_##constant_##Rule> raw = \
+ Servo_CssRules_Get##constant_##RuleAt(mRawRules, aIndex, &line, \
+ &column) \
+ .Consume(); \
+ static_cast<dom::CSS##type_##Rule*>(aRule)->SetRawAfterClone( \
+ std::move(raw)); \
+ break; \
+ }
+#define RULE_CASE_LOCKED(constant_, type_) \
+ RULE_CASE_WITH_PREFIX(constant_, Locked, type_)
+#define RULE_CASE_UNLOCKED(constant_, type_) \
+ RULE_CASE_WITH_PREFIX(constant_, , type_)
+ switch (aRule->Type()) {
+ RULE_CASE_LOCKED(Style, Style)
+ RULE_CASE_LOCKED(Keyframes, Keyframes)
+ RULE_CASE_UNLOCKED(Media, Media)
+ RULE_CASE_UNLOCKED(Namespace, Namespace)
+ RULE_CASE_LOCKED(Page, Page)
+ RULE_CASE_UNLOCKED(Property, Property)
+ RULE_CASE_UNLOCKED(Supports, Supports)
+ RULE_CASE_UNLOCKED(Document, MozDocument)
+ RULE_CASE_LOCKED(Import, Import)
+ RULE_CASE_UNLOCKED(FontFeatureValues, FontFeatureValues)
+ RULE_CASE_UNLOCKED(FontPaletteValues, FontPaletteValues)
+ RULE_CASE_LOCKED(FontFace, FontFace)
+ RULE_CASE_LOCKED(CounterStyle, CounterStyle)
+ RULE_CASE_UNLOCKED(LayerBlock, LayerBlock)
+ RULE_CASE_UNLOCKED(LayerStatement, LayerStatement)
+ RULE_CASE_UNLOCKED(Container, Container)
+ case StyleCssRuleType::Keyframe:
+ MOZ_ASSERT_UNREACHABLE("keyframe rule cannot be here");
+ break;
+ case StyleCssRuleType::Margin:
+ // Margin rules not implemented yet, see bug 1864737
+ break;
+ }
+#undef RULE_CASE_WITH_PREFIX
+#undef RULE_CASE_LOCKED
+#undef RULE_CASE_UNLOCKED
+ });
+}
+
+ServoCSSRuleList::~ServoCSSRuleList() {
+ MOZ_ASSERT(!mStyleSheet, "Backpointer should have been cleared");
+ MOZ_ASSERT(!mParentRule, "Backpointer should have been cleared");
+ DropAllRules();
+}
+
+bool ServoCSSRuleList::IsReadOnly() const {
+ MOZ_ASSERT(!mStyleSheet || !mParentRule ||
+ mStyleSheet->IsReadOnly() == mParentRule->IsReadOnly(),
+ "a parent rule should be read only iff the owning sheet is "
+ "read only");
+ return mStyleSheet && mStyleSheet->IsReadOnly();
+}
+
+} // namespace mozilla
diff --git a/layout/style/ServoCSSRuleList.h b/layout/style/ServoCSSRuleList.h
new file mode 100644
index 0000000000..123f233895
--- /dev/null
+++ b/layout/style/ServoCSSRuleList.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* representation of CSSRuleList for stylo */
+
+#ifndef mozilla_ServoCSSRuleList_h
+#define mozilla_ServoCSSRuleList_h
+
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/dom/CSSRuleList.h"
+
+namespace mozilla {
+
+namespace dom {
+class CSSStyleRule;
+} // namespace dom
+class StyleSheet;
+namespace css {
+class GroupRule;
+class Rule;
+} // namespace css
+
+class ServoCSSRuleList final : public dom::CSSRuleList {
+ public:
+ ServoCSSRuleList(already_AddRefed<StyleLockedCssRules> aRawRules,
+ StyleSheet* aSheet, css::GroupRule* aParentRule);
+ css::GroupRule* GetParentRule() const { return mParentRule; }
+ void DropSheetReference();
+ void DropParentRuleReference();
+
+ void DropReferences() {
+ DropSheetReference();
+ DropParentRuleReference();
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServoCSSRuleList, dom::CSSRuleList)
+
+ StyleSheet* GetParentObject() final { return mStyleSheet; }
+
+ css::Rule* IndexedGetter(uint32_t aIndex, bool& aFound) final;
+ uint32_t Length() final { return mRules.Length(); }
+
+ css::Rule* GetRule(uint32_t aIndex);
+ nsresult InsertRule(const nsACString& aRule, uint32_t aIndex);
+ nsresult DeleteRule(uint32_t aIndex);
+
+ // aFromClone says whether this comes from a clone of the stylesheet (and thus
+ // we should also fix up the wrappers for the individual rules in the rule
+ // lists).
+ void SetRawContents(RefPtr<StyleLockedCssRules>, bool aFromClone);
+ void SetRawAfterClone(RefPtr<StyleLockedCssRules> aRules) {
+ SetRawContents(std::move(aRules), /* aFromClone = */ true);
+ }
+
+ private:
+ virtual ~ServoCSSRuleList();
+
+ // XXX Is it possible to have an address lower than or equal to 255?
+ // Is it possible to have more than 255 CSS rule types?
+ static const uintptr_t kMaxRuleType = UINT8_MAX;
+
+ static uintptr_t CastToUint(css::Rule* aPtr) {
+ return reinterpret_cast<uintptr_t>(aPtr);
+ }
+ static css::Rule* CastToPtr(uintptr_t aInt) {
+ MOZ_ASSERT(aInt > kMaxRuleType);
+ return reinterpret_cast<css::Rule*>(aInt);
+ }
+
+ template <typename Func>
+ void EnumerateInstantiatedRules(Func aCallback);
+
+ void DropAllRules();
+ void ResetRules();
+
+ bool IsReadOnly() const;
+
+ // mStyleSheet may be nullptr when it drops the reference to us.
+ StyleSheet* mStyleSheet = nullptr;
+ // mParentRule is nullptr if it isn't a nested rule list.
+ css::GroupRule* mParentRule = nullptr;
+ RefPtr<StyleLockedCssRules> mRawRules;
+ // Array stores either a number indicating rule type, or a pointer to
+ // css::Rule. If the value is less than kMaxRuleType, the given rule
+ // instance has not been constructed, and the value means the type
+ // of the rule. Otherwise, it is a pointer.
+ nsTArray<uintptr_t> mRules;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoCSSRuleList_h
diff --git a/layout/style/ServoComputedData.h b/layout/style/ServoComputedData.h
new file mode 100644
index 0000000000..9da716263b
--- /dev/null
+++ b/layout/style/ServoComputedData.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ServoComputedData_h
+#define mozilla_ServoComputedData_h
+
+class nsWindowSizes;
+
+#include "mozilla/ServoStyleConsts.h"
+
+/*
+ * ServoComputedData and its related types.
+ */
+
+namespace mozilla {
+
+struct ServoWritingMode {
+ uint8_t mBits;
+};
+
+struct ServoComputedCustomProperties {
+ uintptr_t mInherited;
+ uintptr_t mNonInherited;
+};
+
+struct ServoRuleNode {
+ uintptr_t mPtr;
+};
+
+class ComputedStyle;
+
+} // namespace mozilla
+
+#define STYLE_STRUCT(name_) struct nsStyle##name_;
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+class ServoComputedData;
+
+struct ServoComputedDataForgotten {
+ // Make sure you manually mem::forget the backing ServoComputedData
+ // after calling this
+ explicit ServoComputedDataForgotten(const ServoComputedData* aValue)
+ : mPtr(aValue) {}
+ const ServoComputedData* mPtr;
+};
+
+/**
+ * We want C++ to be able to read the style struct fields of ComputedValues
+ * so we define this type on the C++ side and use the bindgenned version
+ * on the Rust side.
+ */
+class ServoComputedData {
+ friend class mozilla::ComputedStyle;
+
+ public:
+ // Constructs via memcpy. Will not move out of aValue.
+ explicit ServoComputedData(const ServoComputedDataForgotten aValue);
+
+#define STYLE_STRUCT(name_) \
+ const nsStyle##name_* name_; \
+ const nsStyle##name_* Style##name_() const MOZ_NONNULL_RETURN { \
+ return name_; \
+ }
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ void AddSizeOfExcludingThis(nsWindowSizes& aSizes) const;
+
+ mozilla::ServoWritingMode WritingMode() const { return writing_mode; }
+
+ private:
+ mozilla::ServoComputedCustomProperties custom_properties;
+ mozilla::ServoWritingMode writing_mode;
+ /// The effective zoom (as in, the CSS zoom property) of this style.
+ ///
+ /// zoom is a non-inherited property, yet changes to it propagate through in
+ /// an inherited fashion, and all length resolution code need to access it.
+ /// This could, in theory, be stored in any other inherited struct, but it's
+ /// weird to have an inherited struct field depend on a non inherited
+ /// property.
+ ///
+ /// So the style object itself is probably a reasonable place to store it.
+ mozilla::StyleZoom effective_zoom;
+ mozilla::StyleComputedValueFlags flags;
+ /// The rule node representing the ordered list of rules matched for this
+ /// node. Can be None for default values and text nodes. This is
+ /// essentially an optimization to avoid referencing the root rule node.
+ mozilla::ServoRuleNode rules;
+ /// The element's computed values if visited, only computed if there's a
+ /// relevant link for this element. A element's "relevant link" is the
+ /// element being matched if it is a link or the nearest ancestor link.
+ const mozilla::ComputedStyle* visited_style;
+
+ // C++ just sees this struct as a bucket of bits, and will
+ // do the wrong thing if we let it use the default copy ctor/assignment
+ // operator. Remove them so that there is no footgun.
+ //
+ // We remove the move ctor/assignment operator as well, because
+ // moves in C++ don't prevent destructors from being called,
+ // which will lead to double frees.
+ ServoComputedData& operator=(const ServoComputedData&) = delete;
+ ServoComputedData(const ServoComputedData&) = delete;
+ ServoComputedData&& operator=(const ServoComputedData&&) = delete;
+ ServoComputedData(const ServoComputedData&&) = delete;
+};
+
+#endif // mozilla_ServoComputedData_h
diff --git a/layout/style/ServoElementSnapshot.cpp b/layout/style/ServoElementSnapshot.cpp
new file mode 100644
index 0000000000..e0ef2849f9
--- /dev/null
+++ b/layout/style/ServoElementSnapshot.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ServoElementSnapshot.h"
+#include "mozilla/GeckoBindings.h"
+#include "mozilla/dom/Element.h"
+#include "nsIContentInlines.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+ServoElementSnapshot::ServoElementSnapshot(const Element& aElement)
+ : mState(0),
+ mContains(Flags(0)),
+ mIsTableBorderNonzero(false),
+ mIsSelectListBox(false),
+ mClassAttributeChanged(false),
+ mIdAttributeChanged(false) {
+ MOZ_COUNT_CTOR(ServoElementSnapshot);
+ MOZ_ASSERT(NS_IsMainThread());
+ mIsInChromeDocument = nsContentUtils::IsChromeDoc(aElement.OwnerDoc());
+ mSupportsLangAttr = aElement.SupportsLangAttr();
+}
+
+void ServoElementSnapshot::AddOtherPseudoClassState(const Element& aElement) {
+ if (HasOtherPseudoClassState()) {
+ return;
+ }
+
+ mIsTableBorderNonzero = Gecko_IsTableBorderNonzero(&aElement);
+ mIsSelectListBox = Gecko_IsSelectListBox(&aElement);
+
+ mContains |= Flags::OtherPseudoClassState;
+}
+
+void ServoElementSnapshot::AddAttrs(const Element& aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute) {
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::_class) {
+ if (mClassAttributeChanged) {
+ return;
+ }
+ mClassAttributeChanged = true;
+ } else if (aAttribute == nsGkAtoms::id) {
+ if (mIdAttributeChanged) {
+ return;
+ }
+ mIdAttributeChanged = true;
+ }
+ }
+
+ if (!mChangedAttrNames.Contains(aAttribute)) {
+ mChangedAttrNames.AppendElement(aAttribute);
+ }
+
+ if (HasAttrs()) {
+ return;
+ }
+
+ uint32_t attrCount = aElement.GetAttrCount();
+ mAttrs.SetCapacity(attrCount);
+ for (uint32_t i = 0; i < attrCount; ++i) {
+ const BorrowedAttrInfo info = aElement.GetAttrInfoAt(i);
+ MOZ_ASSERT(info);
+ mAttrs.AppendElement(AttrArray::InternalAttr{*info.mName, *info.mValue});
+ }
+
+ mContains |= Flags::Attributes;
+ if (aElement.HasID()) {
+ mContains |= Flags::Id;
+ }
+
+ if (const nsAttrValue* classValue = aElement.GetClasses()) {
+ // FIXME(emilio): It's pretty unfortunate that this is only relevant for
+ // SVG, yet it's a somewhat expensive copy. We should be able to do
+ // better!
+ mClass = *classValue;
+ mContains |= Flags::MaybeClass;
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/style/ServoElementSnapshot.h b/layout/style/ServoElementSnapshot.h
new file mode 100644
index 0000000000..b702975c4b
--- /dev/null
+++ b/layout/style/ServoElementSnapshot.h
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ServoElementSnapshot_h
+#define mozilla_ServoElementSnapshot_h
+
+#include "AttrArray.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "mozilla/dom/RustTypes.h"
+#include "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsChangeHint.h"
+#include "nsGkAtoms.h"
+#include "nsAtom.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+class Element;
+}
+
+/**
+ * A bitflags enum class used to determine what data does a ServoElementSnapshot
+ * contains.
+ */
+enum class ServoElementSnapshotFlags : uint8_t {
+ State = 1 << 0,
+ Attributes = 1 << 1,
+ Id = 1 << 2,
+ MaybeClass = 1 << 3,
+ OtherPseudoClassState = 1 << 4,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoElementSnapshotFlags)
+
+/**
+ * This class holds all non-tree-structural state of an element that might be
+ * used for selector matching eventually.
+ *
+ * This means the attributes, and the element state, such as :hover, :active,
+ * etc...
+ */
+class ServoElementSnapshot {
+ typedef dom::BorrowedAttrInfo BorrowedAttrInfo;
+ typedef dom::Element Element;
+
+ // TODO: Now that the element state shares a representation with rust we
+ // should be able to do better and not use the internal type.
+ typedef dom::ElementState::InternalType ServoStateType;
+
+ public:
+ typedef ServoElementSnapshotFlags Flags;
+
+ explicit ServoElementSnapshot(const Element&);
+
+ ~ServoElementSnapshot() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_DTOR(ServoElementSnapshot);
+ }
+
+ bool HasAttrs() const { return HasAny(Flags::Attributes); }
+
+ bool HasState() const { return HasAny(Flags::State); }
+
+ bool HasOtherPseudoClassState() const {
+ return HasAny(Flags::OtherPseudoClassState);
+ }
+
+ /**
+ * Captures the given state (if not previously captured).
+ */
+ void AddState(dom::ElementState aState) {
+ if (!HasAny(Flags::State)) {
+ mState = aState.GetInternalValue();
+ mContains |= Flags::State;
+ }
+ }
+
+ /**
+ * Captures the given element attributes (if not previously captured).
+ *
+ * The attribute name and namespace are used to note which kind of attribute
+ * has changed.
+ */
+ void AddAttrs(const Element&, int32_t aNameSpaceID, nsAtom* aAttribute);
+
+ /**
+ * Captures some other pseudo-class matching state not included in
+ * ElementState.
+ */
+ void AddOtherPseudoClassState(const Element&);
+
+ /**
+ * Needed methods for attribute matching.
+ */
+ BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const {
+ MOZ_ASSERT(HasAttrs());
+ if (aIndex >= mAttrs.Length()) {
+ return BorrowedAttrInfo(nullptr, nullptr);
+ }
+ return BorrowedAttrInfo(&mAttrs[aIndex].mName, &mAttrs[aIndex].mValue);
+ }
+
+ const nsAttrValue* GetParsedAttr(nsAtom* aLocalName) const {
+ return GetParsedAttr(aLocalName, kNameSpaceID_None);
+ }
+
+ const nsAttrValue* GetParsedAttr(nsAtom* aLocalName,
+ int32_t aNamespaceID) const {
+ MOZ_ASSERT(HasAttrs());
+ uint32_t i, len = mAttrs.Length();
+ if (aNamespaceID == kNameSpaceID_None) {
+ // This should be the common case so lets make an optimized loop
+ for (i = 0; i < len; ++i) {
+ if (mAttrs[i].mName.Equals(aLocalName)) {
+ return &mAttrs[i].mValue;
+ }
+ }
+
+ return nullptr;
+ }
+
+ for (i = 0; i < len; ++i) {
+ if (mAttrs[i].mName.Equals(aLocalName, aNamespaceID)) {
+ return &mAttrs[i].mValue;
+ }
+ }
+
+ return nullptr;
+ }
+
+ bool IsInChromeDocument() const { return mIsInChromeDocument; }
+ bool SupportsLangAttr() const { return mSupportsLangAttr; }
+
+ bool HasAny(Flags aFlags) const { return bool(mContains & aFlags); }
+
+ bool IsTableBorderNonzero() const {
+ MOZ_ASSERT(HasOtherPseudoClassState());
+ return mIsTableBorderNonzero;
+ }
+
+ bool IsSelectListBox() const {
+ MOZ_ASSERT(HasOtherPseudoClassState());
+ return mIsSelectListBox;
+ }
+
+ private:
+ // TODO: Profile, a 1 or 2 element AutoTArray could be worth it, given we know
+ // we're dealing with attribute changes when we take snapshots of attributes,
+ // though it can be wasted space if we deal with a lot of state-only
+ // snapshots.
+ nsTArray<AttrArray::InternalAttr> mAttrs;
+ nsTArray<RefPtr<nsAtom>> mChangedAttrNames;
+ nsAttrValue mClass;
+ ServoStateType mState;
+ Flags mContains;
+ bool mIsInChromeDocument : 1;
+ bool mSupportsLangAttr : 1;
+ bool mIsTableBorderNonzero : 1;
+ bool mIsSelectListBox : 1;
+ bool mClassAttributeChanged : 1;
+ bool mIdAttributeChanged : 1;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/ServoElementSnapshotTable.h b/layout/style/ServoElementSnapshotTable.h
new file mode 100644
index 0000000000..e82fda8649
--- /dev/null
+++ b/layout/style/ServoElementSnapshotTable.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ServoElementSnapshotTable_h
+#define mozilla_ServoElementSnapshotTable_h
+
+#include "mozilla/dom/Element.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "ServoElementSnapshot.h"
+
+namespace mozilla {
+
+class ServoElementSnapshotTable
+ : public nsClassHashtable<nsRefPtrHashKey<dom::Element>,
+ ServoElementSnapshot> {};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/ServoLockedArcTypeList.h b/layout/style/ServoLockedArcTypeList.h
new file mode 100644
index 0000000000..2d356aabf9
--- /dev/null
+++ b/layout/style/ServoLockedArcTypeList.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a list of all Servo Arc<Locked<T>> types used across bindings */
+
+// The argument is the name of the Servo type used inside the Arc.
+//
+// If you add an entry to this file, you should also add an
+// impl_locked_arc_ffi!() call to servo/components/style/gecko/arc_types.rs, and
+// maybe also a corresponding mapping between the types in
+// layout/style/ServoBindings.toml.
+
+SERVO_LOCKED_ARC_TYPE(CssRules)
+SERVO_LOCKED_ARC_TYPE(DeclarationBlock)
+SERVO_LOCKED_ARC_TYPE(StyleRule)
+SERVO_LOCKED_ARC_TYPE(ImportRule)
+SERVO_LOCKED_ARC_TYPE(Keyframe)
+SERVO_LOCKED_ARC_TYPE(KeyframesRule)
+SERVO_LOCKED_ARC_TYPE(MediaList)
+SERVO_LOCKED_ARC_TYPE(PageRule)
+SERVO_LOCKED_ARC_TYPE(FontFaceRule)
+SERVO_LOCKED_ARC_TYPE(CounterStyleRule)
diff --git a/layout/style/ServoStyleConstsForwards.h b/layout/style/ServoStyleConstsForwards.h
new file mode 100644
index 0000000000..65128e1377
--- /dev/null
+++ b/layout/style/ServoStyleConstsForwards.h
@@ -0,0 +1,236 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file contains forward declarations and typedefs for types that cbindgen
+ * cannot understand but renames / prefixes, and includes for some of the types
+ * it needs.
+ */
+
+#ifndef mozilla_ServoStyleConsts_h
+# error "This file is only meant to be included from ServoStyleConsts.h"
+#endif
+
+#ifndef mozilla_ServoStyleConstsForwards_h
+# define mozilla_ServoStyleConstsForwards_h
+
+# include "nsColor.h"
+# include "nsCoord.h"
+# include "mozilla/AtomArray.h"
+# include "mozilla/IntegerRange.h"
+# include "mozilla/Span.h"
+# include "Units.h"
+# include "mozilla/gfx/Types.h"
+# include "mozilla/CORSMode.h"
+# include "mozilla/MemoryReporting.h"
+# include "mozilla/ServoTypes.h"
+# include "mozilla/ServoBindingTypes.h"
+# include "mozilla/Vector.h"
+# include "nsCSSPropertyID.h"
+# include "nsCompatibility.h"
+# include "nsIURI.h"
+# include "mozilla/image/Resolution.h"
+# include <atomic>
+
+struct RawServoAnimationValueTable;
+
+class nsAtom;
+class nsIFrame;
+class nsINode;
+class nsIContent;
+class nsCSSPropertyIDSet;
+class nsPresContext;
+class nsSimpleContentList;
+class imgRequestProxy;
+struct nsCSSValueSharedList;
+
+class gfxFontFeatureValueSet;
+struct gfxFontFeature;
+struct GeckoFontMetrics;
+namespace mozilla {
+namespace gfx {
+struct FontVariation;
+} // namespace gfx
+} // namespace mozilla
+typedef mozilla::gfx::FontVariation gfxFontVariation;
+
+enum nsCSSUnit : uint32_t;
+enum nsChangeHint : uint32_t;
+
+namespace nsStyleTransformMatrix {
+enum class MatrixTransformOperator : uint8_t;
+}
+
+template <typename T>
+class nsMainThreadPtrHolder;
+
+namespace mozilla {
+
+class ComputedStyle;
+
+using Matrix4x4Components = float[16];
+using StyleMatrix4x4Components = Matrix4x4Components;
+
+// This is sound because std::num::NonZeroUsize is repr(transparent).
+//
+// It is just the case that cbindgen doesn't understand it natively.
+using StyleNonZeroUsize = uintptr_t;
+
+struct Keyframe;
+struct PropertyStyleAnimationValuePair;
+
+using ComputedKeyframeValues = nsTArray<PropertyStyleAnimationValuePair>;
+
+class ComputedStyle;
+enum LogicalAxis : uint8_t;
+class SeenPtrs;
+class SharedFontList;
+class StyleSheet;
+class WritingMode;
+class ServoElementSnapshotTable;
+
+template <typename T>
+struct StyleForgottenArcSlicePtr;
+
+struct AnimatedPropertyID;
+struct AnimationPropertySegment;
+struct AspectRatio;
+struct ComputedTiming;
+struct URLExtraData;
+
+enum HalfCorner : uint8_t;
+enum LogicalSide : uint8_t;
+enum class PseudoStyleType : uint8_t;
+enum class OriginFlags : uint8_t;
+enum class UseBoxSizing : uint8_t;
+
+namespace css {
+class Loader;
+class LoaderReusableStyleSheets;
+class SheetLoadData;
+using SheetLoadDataHolder = nsMainThreadPtrHolder<SheetLoadData>;
+enum SheetParsingMode : uint8_t;
+} // namespace css
+
+namespace dom {
+enum class IterationCompositeOperation : uint8_t;
+enum class CallerType : uint32_t;
+
+class Element;
+class Document;
+class ImageTracker;
+
+} // namespace dom
+
+namespace ipc {
+class ByteBuf;
+} // namespace ipc
+
+// Replacement for a Rust Box<T> for a non-dynamically-sized-type.
+//
+// TODO(emilio): If this was some sort of nullable box then this could be made
+// to work with moves, and also reduce memory layout size of stuff, potentially.
+template <typename T>
+struct StyleBox {
+ explicit StyleBox(UniquePtr<T> aPtr) : mRaw(aPtr.release()) {
+ MOZ_DIAGNOSTIC_ASSERT(mRaw);
+ }
+
+ ~StyleBox() {
+ MOZ_DIAGNOSTIC_ASSERT(mRaw);
+ delete mRaw;
+ }
+
+ StyleBox(const StyleBox& aOther) : StyleBox(MakeUnique<T>(*aOther)) {}
+
+ StyleBox& operator=(const StyleBox& aOther) const {
+ delete mRaw;
+ mRaw = MakeUnique<T>(*aOther).release();
+ return *this;
+ }
+
+ const T* operator->() const {
+ MOZ_DIAGNOSTIC_ASSERT(mRaw);
+ return mRaw;
+ }
+
+ const T& operator*() const {
+ MOZ_DIAGNOSTIC_ASSERT(mRaw);
+ return *mRaw;
+ }
+
+ T* operator->() {
+ MOZ_DIAGNOSTIC_ASSERT(mRaw);
+ return mRaw;
+ }
+
+ T& operator*() {
+ MOZ_DIAGNOSTIC_ASSERT(mRaw);
+ return *mRaw;
+ }
+
+ bool operator==(const StyleBox& aOther) const { return *(*this) == *aOther; }
+
+ bool operator!=(const StyleBox& aOther) const { return *(*this) != *aOther; }
+
+ private:
+ T* mRaw;
+};
+
+// Work-around weird cbindgen renaming / avoiding moving stuff outside its
+// namespace.
+
+using StyleImageTracker = dom::ImageTracker;
+using StyleLoader = css::Loader;
+using StyleLoaderReusableStyleSheets = css::LoaderReusableStyleSheets;
+using StyleCallerType = dom::CallerType;
+using StyleSheetParsingMode = css::SheetParsingMode;
+using StyleSheetLoadData = css::SheetLoadData;
+using StyleSheetLoadDataHolder = css::SheetLoadDataHolder;
+using StyleGeckoMallocSizeOf = MallocSizeOf;
+using StyleDomStyleSheet = StyleSheet;
+
+using StyleRawGeckoNode = nsINode;
+using StyleRawGeckoElement = dom::Element;
+using StyleDocument = dom::Document;
+using StyleComputedValues = ComputedStyle;
+using StyleIterationCompositeOperation = dom::IterationCompositeOperation;
+
+using StyleMatrixTransformOperator =
+ nsStyleTransformMatrix::MatrixTransformOperator;
+
+# define SERVO_LOCKED_ARC_TYPE(name_) struct StyleLocked##type_;
+# include "mozilla/ServoLockedArcTypeList.h"
+# undef SERVO_LOCKED_ARC_TYPE
+
+# define SERVO_BOXED_TYPE(name_, type_) struct Style##type_;
+# include "mozilla/ServoBoxedTypeList.h"
+# undef SERVO_BOXED_TYPE
+
+using StyleAtomicUsize = std::atomic<size_t>;
+
+# define SERVO_FIXED_POINT_HELPERS(T, RawT, FractionBits) \
+ static constexpr RawT kPointFive = 1 << (FractionBits - 1); \
+ static constexpr uint16_t kScale = 1 << FractionBits; \
+ static constexpr float kInverseScale = 1.0f / kScale; \
+ static T FromRaw(RawT aRaw) { return {{aRaw}}; } \
+ static T FromFloat(float aFloat) { \
+ return FromRaw(RawT(aFloat * kScale)); \
+ } \
+ static T FromInt(RawT aInt) { return FromRaw(RawT(aInt * kScale)); } \
+ RawT Raw() const { return _0.value; } \
+ uint16_t UnsignedRaw() const { return uint16_t(Raw()); } \
+ float ToFloat() const { return Raw() * kInverseScale; } \
+ RawT ToIntRounded() const { return (Raw() + kPointFive) >> FractionBits; } \
+ inline void ToString(nsACString&) const;
+
+} // namespace mozilla
+
+# ifndef HAVE_64BIT_BUILD
+static_assert(sizeof(void*) == 4, "");
+# define SERVO_32_BITS 1
+# endif
+# define CBINDGEN_IS_GECKO
+
+#endif
diff --git a/layout/style/ServoStyleConstsInlines.h b/layout/style/ServoStyleConstsInlines.h
new file mode 100644
index 0000000000..b6a574037a
--- /dev/null
+++ b/layout/style/ServoStyleConstsInlines.h
@@ -0,0 +1,1202 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Some inline functions declared in cbindgen.toml */
+
+#ifndef mozilla_ServoStyleConstsInlines_h
+#define mozilla_ServoStyleConstsInlines_h
+
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/AspectRatio.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/URLExtraData.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "nsGkAtoms.h"
+#include "MainThreadUtils.h"
+#include "nsNetUtil.h"
+#include <type_traits>
+#include <new>
+
+// TODO(emilio): there are quite a few other implementations scattered around
+// that should move here.
+
+namespace mozilla {
+
+// We need to explicitly instantiate these so that the clang plugin can see that
+// they're trivially copyable...
+//
+// https://github.com/eqrion/cbindgen/issues/402 tracks doing something like
+// this automatically from cbindgen.
+template struct StyleStrong<ComputedStyle>;
+template struct StyleStrong<StyleLockedCssRules>;
+template struct StyleStrong<StyleAnimationValue>;
+template struct StyleStrong<StyleLockedDeclarationBlock>;
+template struct StyleStrong<StyleStylesheetContents>;
+template struct StyleStrong<StyleLockedKeyframe>;
+template struct StyleStrong<StyleLayerBlockRule>;
+template struct StyleStrong<StyleLayerStatementRule>;
+template struct StyleStrong<StyleLockedMediaList>;
+template struct StyleStrong<StyleLockedStyleRule>;
+template struct StyleStrong<StyleLockedImportRule>;
+template struct StyleStrong<StyleLockedKeyframesRule>;
+template struct StyleStrong<StyleMediaRule>;
+template struct StyleStrong<StyleDocumentRule>;
+template struct StyleStrong<StyleNamespaceRule>;
+template struct StyleStrong<StyleLockedPageRule>;
+template struct StyleStrong<StylePropertyRule>;
+template struct StyleStrong<StyleSupportsRule>;
+template struct StyleStrong<StyleFontFeatureValuesRule>;
+template struct StyleStrong<StyleFontPaletteValuesRule>;
+template struct StyleStrong<StyleLockedFontFaceRule>;
+template struct StyleStrong<StyleLockedCounterStyleRule>;
+template struct StyleStrong<StyleContainerRule>;
+
+template <typename T>
+inline void StyleOwnedSlice<T>::Clear() {
+ if (!len) {
+ return;
+ }
+ for (size_t i : IntegerRange(len)) {
+ ptr[i].~T();
+ }
+ free(ptr);
+ ptr = (T*)alignof(T);
+ len = 0;
+}
+
+template <typename T>
+inline void StyleOwnedSlice<T>::CopyFrom(const StyleOwnedSlice& aOther) {
+ Clear();
+ len = aOther.len;
+ if (!len) {
+ ptr = (T*)alignof(T);
+ } else {
+ ptr = (T*)malloc(len * sizeof(T));
+ size_t i = 0;
+ for (const T& elem : aOther.AsSpan()) {
+ new (ptr + i++) T(elem);
+ }
+ }
+}
+
+template <typename T>
+inline void StyleOwnedSlice<T>::SwapElements(StyleOwnedSlice& aOther) {
+ std::swap(ptr, aOther.ptr);
+ std::swap(len, aOther.len);
+}
+
+template <typename T>
+inline StyleOwnedSlice<T>::StyleOwnedSlice(const StyleOwnedSlice& aOther)
+ : StyleOwnedSlice() {
+ CopyFrom(aOther);
+}
+
+template <typename T>
+inline StyleOwnedSlice<T>::StyleOwnedSlice(StyleOwnedSlice&& aOther)
+ : StyleOwnedSlice() {
+ SwapElements(aOther);
+}
+
+template <typename T>
+inline StyleOwnedSlice<T>::StyleOwnedSlice(Vector<T>&& aVector)
+ : StyleOwnedSlice() {
+ if (!aVector.length()) {
+ return;
+ }
+
+ // We could handle this if Vector provided the relevant APIs, see bug 1610702.
+ MOZ_DIAGNOSTIC_ASSERT(aVector.length() == aVector.capacity(),
+ "Shouldn't over-allocate");
+ len = aVector.length();
+ ptr = aVector.extractRawBuffer();
+ MOZ_ASSERT(ptr,
+ "How did extractRawBuffer return null if we're not using inline "
+ "capacity?");
+}
+
+template <typename T>
+inline StyleOwnedSlice<T>& StyleOwnedSlice<T>::operator=(
+ const StyleOwnedSlice& aOther) {
+ CopyFrom(aOther);
+ return *this;
+}
+
+template <typename T>
+inline StyleOwnedSlice<T>& StyleOwnedSlice<T>::operator=(
+ StyleOwnedSlice&& aOther) {
+ Clear();
+ SwapElements(aOther);
+ return *this;
+}
+
+template <typename T>
+inline StyleOwnedSlice<T>::~StyleOwnedSlice() {
+ Clear();
+}
+
+// This code is basically a C++ port of the Arc::clone() implementation in
+// servo/components/servo_arc/lib.rs.
+static constexpr const size_t kStaticRefcount =
+ std::numeric_limits<size_t>::max();
+static constexpr const size_t kMaxRefcount =
+ std::numeric_limits<intptr_t>::max();
+
+template <typename T>
+inline void StyleArcInner<T>::IncrementRef() {
+ if (count.load(std::memory_order_relaxed) != kStaticRefcount) {
+ auto old_size = count.fetch_add(1, std::memory_order_relaxed);
+ if (MOZ_UNLIKELY(old_size > kMaxRefcount)) {
+ ::abort();
+ }
+ }
+}
+
+// This is a C++ port-ish of Arc::drop().
+template <typename T>
+inline bool StyleArcInner<T>::DecrementRef() {
+ if (count.load(std::memory_order_relaxed) == kStaticRefcount) {
+ return false;
+ }
+ if (count.fetch_sub(1, std::memory_order_release) != 1) {
+ return false;
+ }
+#ifdef MOZ_TSAN
+ // TSan doesn't understand std::atomic_thread_fence, so in order
+ // to avoid a false positive for every time a refcounted object
+ // is deleted, we replace the fence with an atomic operation.
+ count.load(std::memory_order_acquire);
+#else
+ std::atomic_thread_fence(std::memory_order_acquire);
+#endif
+ MOZ_LOG_DTOR(this, "ServoArc", 8);
+ return true;
+}
+
+template <typename H, typename T>
+inline bool StyleHeaderSlice<H, T>::operator==(
+ const StyleHeaderSlice& aOther) const {
+ return header == aOther.header && AsSpan() == aOther.AsSpan();
+}
+
+template <typename H, typename T>
+inline bool StyleHeaderSlice<H, T>::operator!=(
+ const StyleHeaderSlice& aOther) const {
+ return !(*this == aOther);
+}
+
+template <typename H, typename T>
+inline StyleHeaderSlice<H, T>::~StyleHeaderSlice() {
+ for (T& elem : Span(data, len)) {
+ elem.~T();
+ }
+}
+
+template <typename H, typename T>
+inline Span<const T> StyleHeaderSlice<H, T>::AsSpan() const {
+ // Explicitly specify template argument here to avoid instantiating Span<T>
+ // first and then implicitly converting to Span<const T>
+ return Span<const T>{data, len};
+}
+
+static constexpr const uint64_t kArcSliceCanary = 0xf3f3f3f3f3f3f3f3;
+
+#define ASSERT_CANARY \
+ MOZ_DIAGNOSTIC_ASSERT(_0.p->data.header == kArcSliceCanary, "Uh?");
+
+template <typename T>
+inline StyleArcSlice<T>::StyleArcSlice()
+ : _0(reinterpret_cast<decltype(_0.p)>(Servo_StyleArcSlice_EmptyPtr())) {
+ ASSERT_CANARY
+}
+
+template <typename T>
+inline StyleArcSlice<T>::StyleArcSlice(
+ const StyleForgottenArcSlicePtr<T>& aPtr) {
+ // See the forget() implementation to see why reinterpret_cast() is ok.
+ _0.p = reinterpret_cast<decltype(_0.p)>(aPtr._0);
+ ASSERT_CANARY
+}
+
+template <typename T>
+inline size_t StyleArcSlice<T>::Length() const {
+ ASSERT_CANARY
+ return _0->Length();
+}
+
+template <typename T>
+inline bool StyleArcSlice<T>::IsEmpty() const {
+ ASSERT_CANARY
+ return _0->IsEmpty();
+}
+
+template <typename T>
+inline Span<const T> StyleArcSlice<T>::AsSpan() const {
+ ASSERT_CANARY
+ return _0->AsSpan();
+}
+
+#undef ASSERT_CANARY
+
+template <typename T>
+inline StyleArc<T>::StyleArc(const StyleArc& aOther) : p(aOther.p) {
+ p->IncrementRef();
+}
+
+template <typename T>
+inline void StyleArc<T>::Release() {
+ if (MOZ_LIKELY(!p->DecrementRef())) {
+ return;
+ }
+ p->data.~T();
+ free(p);
+}
+
+template <typename T>
+inline StyleArc<T>& StyleArc<T>::operator=(const StyleArc& aOther) {
+ if (p != aOther.p) {
+ Release();
+ p = aOther.p;
+ p->IncrementRef();
+ }
+ return *this;
+}
+
+template <typename T>
+inline StyleArc<T>& StyleArc<T>::operator=(StyleArc&& aOther) {
+ std::swap(p, aOther.p);
+ return *this;
+}
+
+template <typename T>
+inline StyleArc<T>::~StyleArc() {
+ Release();
+}
+
+inline bool StyleAtom::IsStatic() const { return !!(_0 & 1); }
+
+inline nsAtom* StyleAtom::AsAtom() const {
+ if (IsStatic()) {
+ return const_cast<nsStaticAtom*>(&detail::gGkAtoms.mAtoms[_0 >> 1]);
+ }
+ return reinterpret_cast<nsAtom*>(_0);
+}
+
+inline void StyleAtom::AddRef() {
+ if (!IsStatic()) {
+ AsAtom()->AddRef();
+ }
+}
+
+inline void StyleAtom::Release() {
+ if (!IsStatic()) {
+ AsAtom()->Release();
+ }
+}
+
+inline StyleAtom::StyleAtom(already_AddRefed<nsAtom> aAtom) {
+ nsAtom* atom = aAtom.take();
+ if (atom->IsStatic()) {
+ size_t index = atom->AsStatic() - &detail::gGkAtoms.mAtoms[0];
+ _0 = (index << 1) | 1;
+ } else {
+ _0 = reinterpret_cast<uintptr_t>(atom);
+ }
+ MOZ_ASSERT(IsStatic() == atom->IsStatic());
+ MOZ_ASSERT(AsAtom() == atom);
+}
+
+inline StyleAtom::StyleAtom(nsStaticAtom* aAtom)
+ : StyleAtom(do_AddRef(static_cast<nsAtom*>(aAtom))) {}
+
+inline StyleAtom::StyleAtom(const StyleAtom& aOther) : _0(aOther._0) {
+ AddRef();
+}
+
+inline StyleAtom& StyleAtom::operator=(const StyleAtom& aOther) {
+ if (MOZ_LIKELY(this != &aOther)) {
+ Release();
+ _0 = aOther._0;
+ AddRef();
+ }
+ return *this;
+}
+
+inline StyleAtom::~StyleAtom() { Release(); }
+
+inline nsAtom* StyleCustomIdent::AsAtom() const { return _0.AsAtom(); }
+
+inline nsDependentCSubstring StyleOwnedStr::AsString() const {
+ Span<const uint8_t> s = _0.AsSpan();
+ return nsDependentCSubstring(reinterpret_cast<const char*>(s.Elements()),
+ s.Length());
+}
+
+template <typename T>
+inline Span<const T> StyleGenericTransform<T>::Operations() const {
+ return _0.AsSpan();
+}
+
+template <typename T>
+inline bool StyleGenericTransform<T>::IsNone() const {
+ return Operations().IsEmpty();
+}
+
+inline StyleAngle StyleAngle::Zero() { return {0.0f}; }
+
+inline float StyleAngle::ToDegrees() const { return _0; }
+
+inline double StyleAngle::ToRadians() const {
+ return double(ToDegrees()) * M_PI / 180.0;
+}
+
+inline bool StyleUrlExtraData::IsShared() const { return !!(_0 & 1); }
+
+inline StyleUrlExtraData::~StyleUrlExtraData() {
+ if (!IsShared()) {
+ reinterpret_cast<URLExtraData*>(_0)->Release();
+ }
+}
+
+inline const URLExtraData& StyleUrlExtraData::get() const {
+ if (IsShared()) {
+ return *URLExtraData::sShared[_0 >> 1];
+ }
+ return *reinterpret_cast<const URLExtraData*>(_0);
+}
+
+inline nsDependentCSubstring StyleCssUrl::SpecifiedSerialization() const {
+ return _0->serialization.AsString();
+}
+
+inline const URLExtraData& StyleCssUrl::ExtraData() const {
+ return _0->extra_data.get();
+}
+
+inline StyleLoadData& StyleCssUrl::LoadData() const {
+ if (MOZ_LIKELY(_0->load_data.tag == StyleLoadDataSource::Tag::Owned)) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread() ||
+ dom::IsCurrentThreadRunningWorker());
+ return const_cast<StyleLoadData&>(_0->load_data.owned._0);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "Lazy load datas should come from user-agent sheets, "
+ "which don't make sense on workers");
+ return const_cast<StyleLoadData&>(*Servo_LoadData_GetLazy(&_0->load_data));
+}
+
+inline nsIURI* StyleCssUrl::GetURI() const {
+ auto& loadData = LoadData();
+ if (!(loadData.flags & StyleLoadDataFlags::TRIED_TO_RESOLVE_URI)) {
+ loadData.flags |= StyleLoadDataFlags::TRIED_TO_RESOLVE_URI;
+ nsDependentCSubstring serialization = SpecifiedSerialization();
+ // https://drafts.csswg.org/css-values-4/#url-empty:
+ //
+ // If the value of the url() is the empty string (like url("") or
+ // url()), the url must resolve to an invalid resource (similar to what
+ // the url about:invalid does).
+ //
+ if (!serialization.IsEmpty()) {
+ RefPtr<nsIURI> resolved;
+ NS_NewURI(getter_AddRefs(resolved), serialization, nullptr,
+ ExtraData().BaseURI());
+ loadData.resolved_uri = resolved.forget().take();
+ }
+ }
+ return loadData.resolved_uri;
+}
+
+inline nsDependentCSubstring StyleComputedUrl::SpecifiedSerialization() const {
+ return _0.SpecifiedSerialization();
+}
+inline const URLExtraData& StyleComputedUrl::ExtraData() const {
+ return _0.ExtraData();
+}
+inline StyleLoadData& StyleComputedUrl::LoadData() const {
+ return _0.LoadData();
+}
+inline StyleCorsMode StyleComputedUrl::CorsMode() const {
+ return _0._0->cors_mode;
+}
+inline nsIURI* StyleComputedUrl::GetURI() const { return _0.GetURI(); }
+
+inline bool StyleComputedUrl::IsLocalRef() const {
+ return Servo_CssUrl_IsLocalRef(&_0);
+}
+
+inline bool StyleComputedUrl::HasRef() const {
+ if (IsLocalRef()) {
+ return true;
+ }
+ if (nsIURI* uri = GetURI()) {
+ bool hasRef = false;
+ return NS_SUCCEEDED(uri->GetHasRef(&hasRef)) && hasRef;
+ }
+ return false;
+}
+
+inline bool StyleComputedImageUrl::IsImageResolved() const {
+ return bool(LoadData().flags & StyleLoadDataFlags::TRIED_TO_RESOLVE_IMAGE);
+}
+
+inline imgRequestProxy* StyleComputedImageUrl::GetImage() const {
+ MOZ_ASSERT(IsImageResolved());
+ return LoadData().resolved_image;
+}
+
+template <>
+inline bool StyleGradient::Repeating() const {
+ if (IsLinear()) {
+ return bool(AsLinear().flags & StyleGradientFlags::REPEATING);
+ }
+ if (IsRadial()) {
+ return bool(AsRadial().flags & StyleGradientFlags::REPEATING);
+ }
+ return bool(AsConic().flags & StyleGradientFlags::REPEATING);
+}
+
+template <>
+bool StyleGradient::IsOpaque() const;
+
+template <>
+inline const StyleColorInterpolationMethod&
+StyleGradient::ColorInterpolationMethod() const {
+ if (IsLinear()) {
+ return AsLinear().color_interpolation_method;
+ }
+ if (IsRadial()) {
+ return AsRadial().color_interpolation_method;
+ }
+ return AsConic().color_interpolation_method;
+}
+
+template <typename Integer>
+inline StyleGenericGridLine<Integer>::StyleGenericGridLine()
+ : ident{StyleAtom(nsGkAtoms::_empty)}, line_num(0), is_span(false) {}
+
+template <>
+inline nsAtom* StyleGridLine::LineName() const {
+ return ident.AsAtom();
+}
+
+template <>
+inline bool StyleGridLine::IsAuto() const {
+ return LineName()->IsEmpty() && line_num == 0 && !is_span;
+}
+
+using LengthPercentage = StyleLengthPercentage;
+using LengthPercentageOrAuto = StyleLengthPercentageOrAuto;
+using NonNegativeLengthPercentage = StyleNonNegativeLengthPercentage;
+using NonNegativeLengthPercentageOrAuto =
+ StyleNonNegativeLengthPercentageOrAuto;
+using NonNegativeLengthPercentageOrNormal =
+ StyleNonNegativeLengthPercentageOrNormal;
+using Length = StyleLength;
+using LengthOrAuto = StyleLengthOrAuto;
+using NonNegativeLength = StyleNonNegativeLength;
+using NonNegativeLengthOrAuto = StyleNonNegativeLengthOrAuto;
+using BorderRadius = StyleBorderRadius;
+
+bool StyleCSSPixelLength::IsZero() const { return _0 == 0.0f; }
+
+void StyleCSSPixelLength::ScaleBy(float aScale) { _0 *= aScale; }
+
+StyleCSSPixelLength StyleCSSPixelLength::ScaledBy(float aScale) const {
+ return FromPixels(ToCSSPixels() * aScale);
+}
+
+nscoord StyleCSSPixelLength::ToAppUnits() const {
+ // We want to resolve the length part of the calc() expression rounding 0.5
+ // away from zero, instead of the default behavior of
+ // NSToCoordRound{,WithClamp} which do floor(x + 0.5).
+ //
+ // This is what the rust code in the app_units crate does, and not doing this
+ // would regress bug 1323735, for example.
+ //
+ // FIXME(emilio, bug 1528114): Probably we should do something smarter.
+ if (IsZero()) {
+ // Avoid the expensive FP math below.
+ return 0;
+ }
+ float length = _0 * float(mozilla::AppUnitsPerCSSPixel());
+ if (length >= float(nscoord_MAX)) {
+ return nscoord_MAX;
+ }
+ if (length <= float(nscoord_MIN)) {
+ return nscoord_MIN;
+ }
+ return NSToIntRound(length);
+}
+
+bool LengthPercentage::IsLength() const { return Tag() == TAG_LENGTH; }
+
+StyleLengthPercentageUnion::StyleLengthPercentageUnion() {
+ length = {TAG_LENGTH, {0.0f}};
+ MOZ_ASSERT(IsLength());
+}
+
+static_assert(sizeof(LengthPercentage) == sizeof(uint64_t), "");
+
+Length& LengthPercentage::AsLength() {
+ MOZ_ASSERT(IsLength());
+ return length.length;
+}
+
+const Length& LengthPercentage::AsLength() const {
+ return const_cast<LengthPercentage*>(this)->AsLength();
+}
+
+bool LengthPercentage::IsPercentage() const { return Tag() == TAG_PERCENTAGE; }
+
+StylePercentage& LengthPercentage::AsPercentage() {
+ MOZ_ASSERT(IsPercentage());
+ return percentage.percentage;
+}
+
+const StylePercentage& LengthPercentage::AsPercentage() const {
+ return const_cast<LengthPercentage*>(this)->AsPercentage();
+}
+
+bool LengthPercentage::IsCalc() const { return Tag() == TAG_CALC; }
+
+StyleCalcLengthPercentage& LengthPercentage::AsCalc() {
+ MOZ_ASSERT(IsCalc());
+ // NOTE: in 32-bits, the pointer is not swapped, and goes along with the tag.
+#ifdef SERVO_32_BITS
+ return *calc.ptr;
+#else
+ return *reinterpret_cast<StyleCalcLengthPercentage*>(
+ NativeEndian::swapFromLittleEndian(calc.ptr));
+#endif
+}
+
+const StyleCalcLengthPercentage& LengthPercentage::AsCalc() const {
+ return const_cast<LengthPercentage*>(this)->AsCalc();
+}
+
+StyleLengthPercentageUnion::StyleLengthPercentageUnion(const Self& aOther) {
+ if (aOther.IsLength()) {
+ length = {TAG_LENGTH, aOther.AsLength()};
+ } else if (aOther.IsPercentage()) {
+ percentage = {TAG_PERCENTAGE, aOther.AsPercentage()};
+ } else {
+ MOZ_ASSERT(aOther.IsCalc());
+ auto* ptr = new StyleCalcLengthPercentage(aOther.AsCalc());
+ // NOTE: in 32-bits, the pointer is not swapped, and goes along with the
+ // tag.
+ calc = {
+#ifdef SERVO_32_BITS
+ TAG_CALC,
+ ptr,
+#else
+ NativeEndian::swapToLittleEndian(reinterpret_cast<uintptr_t>(ptr)),
+#endif
+ };
+ }
+ MOZ_ASSERT(Tag() == aOther.Tag());
+}
+
+StyleLengthPercentageUnion::~StyleLengthPercentageUnion() {
+ if (IsCalc()) {
+ delete &AsCalc();
+ }
+}
+
+LengthPercentage& LengthPercentage::operator=(const LengthPercentage& aOther) {
+ if (this != &aOther) {
+ this->~LengthPercentage();
+ new (this) LengthPercentage(aOther);
+ }
+ return *this;
+}
+
+bool LengthPercentage::operator==(const LengthPercentage& aOther) const {
+ if (Tag() != aOther.Tag()) {
+ return false;
+ }
+ if (IsLength()) {
+ return AsLength() == aOther.AsLength();
+ }
+ if (IsPercentage()) {
+ return AsPercentage() == aOther.AsPercentage();
+ }
+ return AsCalc() == aOther.AsCalc();
+}
+
+bool LengthPercentage::operator!=(const LengthPercentage& aOther) const {
+ return !(*this == aOther);
+}
+
+LengthPercentage LengthPercentage::Zero() { return {}; }
+
+LengthPercentage LengthPercentage::FromPixels(CSSCoord aCoord) {
+ LengthPercentage l;
+ MOZ_ASSERT(l.IsLength());
+ l.length.length = {aCoord};
+ return l;
+}
+
+LengthPercentage LengthPercentage::FromAppUnits(nscoord aCoord) {
+ return FromPixels(CSSPixel::FromAppUnits(aCoord));
+}
+
+LengthPercentage LengthPercentage::FromPercentage(float aPercentage) {
+ LengthPercentage l;
+ l.percentage = {TAG_PERCENTAGE, {aPercentage}};
+ return l;
+}
+
+bool LengthPercentage::HasPercent() const { return IsPercentage() || IsCalc(); }
+
+bool LengthPercentage::ConvertsToLength() const { return IsLength(); }
+
+nscoord LengthPercentage::ToLength() const {
+ MOZ_ASSERT(ConvertsToLength());
+ return AsLength().ToAppUnits();
+}
+
+CSSCoord LengthPercentage::ToLengthInCSSPixels() const {
+ MOZ_ASSERT(ConvertsToLength());
+ return AsLength().ToCSSPixels();
+}
+
+bool LengthPercentage::ConvertsToPercentage() const { return IsPercentage(); }
+
+float LengthPercentage::ToPercentage() const {
+ MOZ_ASSERT(ConvertsToPercentage());
+ return AsPercentage()._0;
+}
+
+bool LengthPercentage::HasLengthAndPercentage() const {
+ if (!IsCalc()) {
+ return false;
+ }
+ MOZ_ASSERT(!ConvertsToLength() && !ConvertsToPercentage(),
+ "Should've been simplified earlier");
+ return true;
+}
+
+bool LengthPercentage::IsDefinitelyZero() const {
+ if (IsLength()) {
+ return AsLength().IsZero();
+ }
+ if (IsPercentage()) {
+ return AsPercentage()._0 == 0.0f;
+ }
+ // calc() should've been simplified to a percentage.
+ return false;
+}
+
+CSSCoord StyleCalcLengthPercentage::ResolveToCSSPixels(CSSCoord aBasis) const {
+ return Servo_ResolveCalcLengthPercentage(this, aBasis);
+}
+
+template <>
+void StyleCalcNode::ScaleLengthsBy(float);
+
+CSSCoord LengthPercentage::ResolveToCSSPixels(CSSCoord aPercentageBasis) const {
+ if (IsLength()) {
+ return AsLength().ToCSSPixels();
+ }
+ if (IsPercentage()) {
+ return AsPercentage()._0 * aPercentageBasis;
+ }
+ return AsCalc().ResolveToCSSPixels(aPercentageBasis);
+}
+
+template <typename T>
+CSSCoord LengthPercentage::ResolveToCSSPixelsWith(T aPercentageGetter) const {
+ static_assert(std::is_same<decltype(aPercentageGetter()), CSSCoord>::value,
+ "Should return CSS pixels");
+ if (ConvertsToLength()) {
+ return ToLengthInCSSPixels();
+ }
+ return ResolveToCSSPixels(aPercentageGetter());
+}
+
+template <typename T, typename U>
+nscoord LengthPercentage::Resolve(T aPercentageGetter, U aRounder) const {
+ static_assert(std::is_same<decltype(aPercentageGetter()), nscoord>::value,
+ "Should return app units");
+ static_assert(std::is_same<decltype(aRounder(1.0f)), nscoord>::value,
+ "Should return app units");
+ if (ConvertsToLength()) {
+ return ToLength();
+ }
+ if (IsPercentage() && AsPercentage()._0 == 0.0f) {
+ return 0;
+ }
+ nscoord basis = aPercentageGetter();
+ if (IsPercentage()) {
+ return aRounder(basis * AsPercentage()._0);
+ }
+ return AsCalc().Resolve(basis, aRounder);
+}
+
+// Note: the static_cast<> wrappers below are needed to disambiguate between
+// the versions of NSToCoordTruncClamped that take float vs. double as the arg.
+nscoord LengthPercentage::Resolve(nscoord aPercentageBasis) const {
+ return Resolve([=] { return aPercentageBasis; },
+ static_cast<nscoord (*)(float)>(NSToCoordTruncClamped));
+}
+
+template <typename T>
+nscoord LengthPercentage::Resolve(T aPercentageGetter) const {
+ return Resolve(aPercentageGetter,
+ static_cast<nscoord (*)(float)>(NSToCoordTruncClamped));
+}
+
+template <typename T>
+nscoord LengthPercentage::Resolve(nscoord aPercentageBasis,
+ T aPercentageRounder) const {
+ return Resolve([aPercentageBasis] { return aPercentageBasis; },
+ aPercentageRounder);
+}
+
+void LengthPercentage::ScaleLengthsBy(float aScale) {
+ if (IsLength()) {
+ AsLength().ScaleBy(aScale);
+ }
+ if (IsCalc()) {
+ AsCalc().node.ScaleLengthsBy(aScale);
+ }
+}
+
+#define IMPL_LENGTHPERCENTAGE_FORWARDS(ty_) \
+ template <> \
+ inline bool ty_::HasPercent() const { \
+ return IsLengthPercentage() && AsLengthPercentage().HasPercent(); \
+ } \
+ template <> \
+ inline bool ty_::ConvertsToLength() const { \
+ return IsLengthPercentage() && AsLengthPercentage().ConvertsToLength(); \
+ } \
+ template <> \
+ inline bool ty_::HasLengthAndPercentage() const { \
+ return IsLengthPercentage() && \
+ AsLengthPercentage().HasLengthAndPercentage(); \
+ } \
+ template <> \
+ inline nscoord ty_::ToLength() const { \
+ MOZ_ASSERT(ConvertsToLength()); \
+ return AsLengthPercentage().ToLength(); \
+ } \
+ template <> \
+ inline bool ty_::ConvertsToPercentage() const { \
+ return IsLengthPercentage() && \
+ AsLengthPercentage().ConvertsToPercentage(); \
+ } \
+ template <> \
+ inline float ty_::ToPercentage() const { \
+ MOZ_ASSERT(ConvertsToPercentage()); \
+ return AsLengthPercentage().ToPercentage(); \
+ }
+
+IMPL_LENGTHPERCENTAGE_FORWARDS(LengthPercentageOrAuto)
+IMPL_LENGTHPERCENTAGE_FORWARDS(StyleSize)
+IMPL_LENGTHPERCENTAGE_FORWARDS(StyleMaxSize)
+
+template <>
+inline bool LengthOrAuto::IsLength() const {
+ return IsLengthPercentage();
+}
+
+template <>
+inline const Length& LengthOrAuto::AsLength() const {
+ return AsLengthPercentage();
+}
+
+template <>
+inline nscoord LengthOrAuto::ToLength() const {
+ return AsLength().ToAppUnits();
+}
+
+template <>
+inline bool StyleFlexBasis::IsAuto() const {
+ return IsSize() && AsSize().IsAuto();
+}
+
+template <>
+inline bool StyleSize::BehavesLikeInitialValueOnBlockAxis() const {
+ return IsAuto() || !IsLengthPercentage();
+}
+
+template <>
+inline bool StyleMaxSize::BehavesLikeInitialValueOnBlockAxis() const {
+ return IsNone() || !IsLengthPercentage();
+}
+
+template <>
+inline bool StyleBackgroundSize::IsInitialValue() const {
+ return IsExplicitSize() && explicit_size.width.IsAuto() &&
+ explicit_size.height.IsAuto();
+}
+
+template <typename T>
+const T& StyleRect<T>::Get(mozilla::Side aSide) const {
+ static_assert(sizeof(StyleRect<T>) == sizeof(T) * 4, "");
+ static_assert(alignof(StyleRect<T>) == alignof(T), "");
+ return reinterpret_cast<const T*>(this)[aSide];
+}
+
+template <typename T>
+T& StyleRect<T>::Get(mozilla::Side aSide) {
+ return const_cast<T&>(static_cast<const StyleRect&>(*this).Get(aSide));
+}
+
+template <typename T>
+template <typename Predicate>
+bool StyleRect<T>::All(Predicate aPredicate) const {
+ return aPredicate(_0) && aPredicate(_1) && aPredicate(_2) && aPredicate(_3);
+}
+
+template <typename T>
+template <typename Predicate>
+bool StyleRect<T>::Any(Predicate aPredicate) const {
+ return aPredicate(_0) || aPredicate(_1) || aPredicate(_2) || aPredicate(_3);
+}
+
+template <>
+inline const LengthPercentage& BorderRadius::Get(HalfCorner aCorner) const {
+ static_assert(sizeof(BorderRadius) == sizeof(LengthPercentage) * 8, "");
+ static_assert(alignof(BorderRadius) == alignof(LengthPercentage), "");
+ const auto* self = reinterpret_cast<const LengthPercentage*>(this);
+ return self[aCorner];
+}
+
+template <>
+inline bool StyleTrackBreadth::HasPercent() const {
+ return IsBreadth() && AsBreadth().HasPercent();
+}
+
+// Implemented in nsStyleStructs.cpp
+template <>
+bool StyleTransform::HasPercent() const;
+
+template <>
+inline bool StyleTransformOrigin::HasPercent() const {
+ // NOTE(emilio): `depth` is just a `<length>` so doesn't have a percentage at
+ // all.
+ return horizontal.HasPercent() || vertical.HasPercent();
+}
+
+template <>
+inline Maybe<size_t> StyleGridTemplateComponent::RepeatAutoIndex() const {
+ if (!IsTrackList()) {
+ return Nothing();
+ }
+ const auto& list = *AsTrackList();
+ return list.auto_repeat_index < list.values.Length()
+ ? Some(list.auto_repeat_index)
+ : Nothing();
+}
+
+template <>
+inline bool StyleGridTemplateComponent::HasRepeatAuto() const {
+ return RepeatAutoIndex().isSome();
+}
+
+template <>
+inline Span<const StyleGenericTrackListValue<LengthPercentage, StyleInteger>>
+StyleGridTemplateComponent::TrackListValues() const {
+ if (IsTrackList()) {
+ return AsTrackList()->values.AsSpan();
+ }
+ return {};
+}
+
+template <>
+inline const StyleGenericTrackRepeat<LengthPercentage, StyleInteger>*
+StyleGridTemplateComponent::GetRepeatAutoValue() const {
+ auto index = RepeatAutoIndex();
+ if (!index) {
+ return nullptr;
+ }
+ return &TrackListValues()[*index].AsTrackRepeat();
+}
+
+constexpr const auto kPaintOrderShift = StylePAINT_ORDER_SHIFT;
+constexpr const auto kPaintOrderMask = StylePAINT_ORDER_MASK;
+
+template <>
+inline nsRect StyleGenericClipRect<LengthOrAuto>::ToLayoutRect(
+ nscoord aAutoSize) const {
+ nscoord x = left.IsLength() ? left.ToLength() : 0;
+ nscoord y = top.IsLength() ? top.ToLength() : 0;
+ nscoord width = right.IsLength() ? right.ToLength() - x : aAutoSize;
+ nscoord height = bottom.IsLength() ? bottom.ToLength() - y : aAutoSize;
+ return nsRect(x, y, width, height);
+}
+
+using RestyleHint = StyleRestyleHint;
+
+inline RestyleHint RestyleHint::RestyleSubtree() {
+ return RESTYLE_SELF | RESTYLE_DESCENDANTS;
+}
+
+inline RestyleHint RestyleHint::RecascadeSubtree() {
+ return RECASCADE_SELF | RECASCADE_DESCENDANTS;
+}
+
+inline RestyleHint RestyleHint::ForAnimations() {
+ return RESTYLE_CSS_TRANSITIONS | RESTYLE_CSS_ANIMATIONS | RESTYLE_SMIL;
+}
+
+inline bool RestyleHint::DefinitelyRecascadesAllSubtree() const {
+ if (!(*this & (RECASCADE_DESCENDANTS | RESTYLE_DESCENDANTS))) {
+ return false;
+ }
+ return bool(*this & (RESTYLE_SELF | RECASCADE_SELF));
+}
+
+template <>
+ImageResolution StyleImage::GetResolution(const ComputedStyle&) const;
+
+template <>
+inline const StyleImage& StyleImage::FinalImage() const {
+ if (!IsImageSet()) {
+ return *this;
+ }
+ const auto& set = *AsImageSet();
+ auto items = set.items.AsSpan();
+ if (MOZ_LIKELY(set.selected_index < items.Length())) {
+ return items[set.selected_index].image.FinalImage();
+ }
+ static auto sNone = StyleImage::None();
+ return sNone;
+}
+
+template <>
+inline bool StyleImage::IsImageRequestType() const {
+ const auto& finalImage = FinalImage();
+ return finalImage.IsUrl();
+}
+
+template <>
+inline const StyleComputedImageUrl* StyleImage::GetImageRequestURLValue()
+ const {
+ const auto& finalImage = FinalImage();
+ if (finalImage.IsUrl()) {
+ return &finalImage.AsUrl();
+ }
+ return nullptr;
+}
+
+template <>
+inline imgRequestProxy* StyleImage::GetImageRequest() const {
+ const auto* url = GetImageRequestURLValue();
+ return url ? url->GetImage() : nullptr;
+}
+
+template <>
+inline bool StyleImage::IsResolved() const {
+ const auto* url = GetImageRequestURLValue();
+ return !url || url->IsImageResolved();
+}
+
+template <>
+bool StyleImage::IsOpaque() const;
+template <>
+bool StyleImage::IsSizeAvailable() const;
+template <>
+bool StyleImage::IsComplete() const;
+template <>
+void StyleImage::ResolveImage(dom::Document&, const StyleImage*);
+
+template <>
+inline AspectRatio StyleRatio<StyleNonNegativeNumber>::ToLayoutRatio(
+ UseBoxSizing aUseBoxSizing) const {
+ // 0/1, 1/0, and 0/0 are all degenerate ratios (which behave as auto), and we
+ // always return 0.0f.
+ // https://drafts.csswg.org/css-values-4/#degenerate-ratio
+ return AspectRatio::FromSize(_0, _1, aUseBoxSizing);
+}
+
+template <>
+inline AspectRatio StyleAspectRatio::ToLayoutRatio() const {
+ return HasRatio() ? ratio.AsRatio().ToLayoutRatio(auto_ ? UseBoxSizing::No
+ : UseBoxSizing::Yes)
+ : AspectRatio();
+}
+
+inline void StyleFontWeight::ToString(nsACString& aString) const {
+ Servo_FontWeight_ToCss(this, &aString);
+}
+
+inline void StyleFontStretch::ToString(nsACString& aString) const {
+ Servo_FontStretch_ToCss(this, &aString);
+}
+
+inline void StyleFontStyle::ToString(nsACString& aString) const {
+ Servo_FontStyle_ToCss(this, &aString);
+}
+
+inline bool StyleFontWeight::IsBold() const { return *this >= BOLD_THRESHOLD; }
+
+inline bool StyleFontStyle::IsItalic() const { return *this == ITALIC; }
+
+inline bool StyleFontStyle::IsOblique() const {
+ return !IsItalic() && !IsNormal();
+}
+
+inline float StyleFontStyle::ObliqueAngle() const {
+ MOZ_ASSERT(IsOblique());
+ return ToFloat();
+}
+
+inline float StyleFontStyle::SlantAngle() const {
+ return IsNormal() ? 0 : IsItalic() ? DEFAULT_OBLIQUE_DEGREES : ObliqueAngle();
+}
+
+using FontStretch = StyleFontStretch;
+using FontSlantStyle = StyleFontStyle;
+using FontWeight = StyleFontWeight;
+
+template <>
+inline double StyleComputedTimingFunction::At(double aPortion,
+ bool aBeforeFlag) const {
+ return Servo_EasingFunctionAt(
+ this, aPortion,
+ aBeforeFlag ? StyleEasingBeforeFlag::Set : StyleEasingBeforeFlag::Unset);
+}
+
+template <>
+inline void StyleComputedTimingFunction::AppendToString(
+ nsACString& aOut) const {
+ return Servo_SerializeEasing(this, &aOut);
+}
+
+template <>
+inline double StyleComputedTimingFunction::GetPortion(
+ const Maybe<StyleComputedTimingFunction>& aFn, double aPortion,
+ bool aBeforeFlag) {
+ return aFn ? aFn->At(aPortion, aBeforeFlag) : aPortion;
+}
+
+/* static */
+template <>
+inline LengthPercentageOrAuto LengthPercentageOrAuto::Zero() {
+ return LengthPercentage(LengthPercentage::Zero());
+}
+
+template <>
+inline StyleViewTimelineInset::StyleGenericViewTimelineInset()
+ : start(LengthPercentageOrAuto::Auto()),
+ end(LengthPercentageOrAuto::Auto()) {}
+
+inline StyleDisplayOutside StyleDisplay::Outside() const {
+ return StyleDisplayOutside((_0 & OUTSIDE_MASK) >> OUTSIDE_SHIFT);
+}
+
+inline StyleDisplayInside StyleDisplay::Inside() const {
+ return StyleDisplayInside(_0 & INSIDE_MASK);
+}
+
+inline bool StyleDisplay::IsListItem() const { return _0 & LIST_ITEM_MASK; }
+
+inline bool StyleDisplay::IsInternalTable() const {
+ return Outside() == StyleDisplayOutside::InternalTable;
+}
+
+inline bool StyleDisplay::IsInternalTableExceptCell() const {
+ return IsInternalTable() && *this != TableCell;
+}
+
+inline bool StyleDisplay::IsInternalRuby() const {
+ return Outside() == StyleDisplayOutside::InternalRuby;
+}
+
+inline bool StyleDisplay::IsRuby() const {
+ return Inside() == StyleDisplayInside::Ruby || IsInternalRuby();
+}
+
+inline bool StyleDisplay::IsInlineFlow() const {
+ return Outside() == StyleDisplayOutside::Inline &&
+ Inside() == StyleDisplayInside::Flow;
+}
+
+inline bool StyleDisplay::IsInlineInside() const {
+ return IsInlineFlow() || IsRuby();
+}
+
+inline bool StyleDisplay::IsInlineOutside() const {
+ return Outside() == StyleDisplayOutside::Inline || IsInternalRuby();
+}
+
+inline float StyleZoom::Zoom(float aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return ToFloat() * aValue;
+}
+
+inline float StyleZoom::Unzoom(float aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return aValue / ToFloat();
+}
+
+inline nscoord StyleZoom::ZoomCoord(nscoord aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return NSToCoordRoundWithClamp(Zoom(float(aValue)));
+}
+
+inline nscoord StyleZoom::UnzoomCoord(nscoord aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return NSToCoordRoundWithClamp(Unzoom(float(aValue)));
+}
+
+inline nsSize StyleZoom::Zoom(const nsSize& aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return nsSize(ZoomCoord(aValue.Width()), ZoomCoord(aValue.Height()));
+}
+
+inline nsSize StyleZoom::Unzoom(const nsSize& aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return nsSize(UnzoomCoord(aValue.Width()), UnzoomCoord(aValue.Height()));
+}
+
+inline nsPoint StyleZoom::Zoom(const nsPoint& aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return nsPoint(ZoomCoord(aValue.X()), ZoomCoord(aValue.Y()));
+}
+
+inline nsPoint StyleZoom::Unzoom(const nsPoint& aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return nsPoint(UnzoomCoord(aValue.X()), UnzoomCoord(aValue.Y()));
+}
+
+inline nsRect StyleZoom::Zoom(const nsRect& aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return nsRect(ZoomCoord(aValue.X()), ZoomCoord(aValue.Y()),
+ ZoomCoord(aValue.Width()), ZoomCoord(aValue.Height()));
+}
+
+inline nsRect StyleZoom::Unzoom(const nsRect& aValue) const {
+ if (*this == ONE) {
+ return aValue;
+ }
+ return nsRect(UnzoomCoord(aValue.X()), UnzoomCoord(aValue.Y()),
+ UnzoomCoord(aValue.Width()), UnzoomCoord(aValue.Height()));
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp
new file mode 100644
index 0000000000..cfc38849b8
--- /dev/null
+++ b/layout/style/ServoStyleSet.cpp
@@ -0,0 +1,1579 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ServoStyleSet.h"
+#include "mozilla/ServoStyleSetInlines.h"
+
+#include "gfxPlatformFontList.h"
+#include "mozilla/DocumentStyleRootIterator.h"
+#include "mozilla/AttributeStyles.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Keyframe.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ServoStyleRuleMap.h"
+#include "mozilla/ServoTypes.h"
+#include "mozilla/SMILAnimationController.h"
+#include "mozilla/MediaFeatureChange.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/AnonymousContent.h"
+#include "mozilla/dom/CSSBinding.h"
+#include "mozilla/dom/CSSCounterStyleRule.h"
+#include "mozilla/dom/CSSFontFaceRule.h"
+#include "mozilla/dom/CSSFontFeatureValuesRule.h"
+#include "mozilla/dom/CSSFontPaletteValuesRule.h"
+#include "mozilla/dom/CSSImportRule.h"
+#include "mozilla/dom/CSSContainerRule.h"
+#include "mozilla/dom/CSSLayerBlockRule.h"
+#include "mozilla/dom/CSSLayerStatementRule.h"
+#include "mozilla/dom/CSSMediaRule.h"
+#include "mozilla/dom/CSSMozDocumentRule.h"
+#include "mozilla/dom/CSSKeyframesRule.h"
+#include "mozilla/dom/CSSKeyframeRule.h"
+#include "mozilla/dom/CSSNamespaceRule.h"
+#include "mozilla/dom/CSSPageRule.h"
+#include "mozilla/dom/CSSPropertyRule.h"
+#include "mozilla/dom/CSSSupportsRule.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCSSPseudoElements.h"
+#include "nsDeviceContext.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsPrintfCString.h"
+#include "gfxUserFontSet.h"
+#include "nsWindowSizes.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+#ifdef DEBUG
+bool ServoStyleSet::IsCurrentThreadInServoTraversal() {
+ return sInServoTraversal && (NS_IsMainThread() || Servo_IsWorkerThread());
+}
+#endif
+
+// The definition of kOrigins relies on this.
+static_assert(static_cast<uint8_t>(StyleOrigin::UserAgent) ==
+ static_cast<uint8_t>(OriginFlags::UserAgent));
+static_assert(static_cast<uint8_t>(StyleOrigin::User) ==
+ static_cast<uint8_t>(OriginFlags::User));
+static_assert(static_cast<uint8_t>(StyleOrigin::Author) ==
+ static_cast<uint8_t>(OriginFlags::Author));
+
+constexpr const StyleOrigin ServoStyleSet::kOrigins[];
+
+ServoStyleSet* sInServoTraversal = nullptr;
+
+// On construction, sets sInServoTraversal to the given ServoStyleSet.
+// On destruction, clears sInServoTraversal and calls RunPostTraversalTasks.
+class MOZ_RAII AutoSetInServoTraversal {
+ public:
+ explicit AutoSetInServoTraversal(ServoStyleSet* aSet) : mSet(aSet) {
+ MOZ_ASSERT(!sInServoTraversal);
+ MOZ_ASSERT(aSet);
+ sInServoTraversal = aSet;
+ }
+
+ ~AutoSetInServoTraversal() {
+ MOZ_ASSERT(sInServoTraversal);
+ sInServoTraversal = nullptr;
+ mSet->RunPostTraversalTasks();
+ }
+
+ private:
+ ServoStyleSet* mSet;
+};
+
+// Sets up for one or more calls to Servo_TraverseSubtree.
+class MOZ_RAII AutoPrepareTraversal {
+ public:
+ explicit AutoPrepareTraversal(ServoStyleSet* aSet)
+ : mSetInServoTraversal(aSet) {
+ MOZ_ASSERT(!aSet->StylistNeedsUpdate());
+ }
+
+ private:
+ AutoSetInServoTraversal mSetInServoTraversal;
+};
+
+ServoStyleSet::ServoStyleSet(Document& aDocument) : mDocument(&aDocument) {
+ PreferenceSheet::EnsureInitialized();
+ PodArrayZero(mCachedAnonymousContentStyleIndexes);
+ mRawData.reset(Servo_StyleSet_Init(&aDocument));
+}
+
+ServoStyleSet::~ServoStyleSet() {
+ MOZ_ASSERT(!IsInServoTraversal());
+ EnumerateStyleSheets([&](StyleSheet& aSheet) { aSheet.DropStyleSet(this); });
+}
+
+nsPresContext* ServoStyleSet::GetPresContext() {
+ return mDocument->GetPresContext();
+}
+
+template <typename Functor>
+static void EnumerateShadowRoots(const Document& aDoc, const Functor& aCb) {
+ const Document::ShadowRootSet& shadowRoots = aDoc.ComposedShadowRoots();
+ for (ShadowRoot* root : shadowRoots) {
+ MOZ_ASSERT(root);
+ MOZ_DIAGNOSTIC_ASSERT(root->IsInComposedDoc());
+ aCb(*root);
+ }
+}
+
+void ServoStyleSet::ShellDetachedFromDocument() {
+ ClearNonInheritingComputedStyles();
+ mCachedAnonymousContentStyles.Clear();
+ PodArrayZero(mCachedAnonymousContentStyleIndexes);
+ mStyleRuleMap = nullptr;
+
+ // Remove all our stylesheets...
+ for (auto origin : kOrigins) {
+ for (size_t count = SheetCount(origin); count--;) {
+ RemoveStyleSheet(*SheetAt(origin, count));
+ }
+ }
+
+ // And remove all the CascadeDatas from memory.
+ UpdateStylistIfNeeded();
+
+ // Also GC the ruletree if it got big now that the DOM no longer has
+ // references to styles around anymore.
+ MaybeGCRuleTree();
+}
+
+void ServoStyleSet::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
+ // TODO(emilio): We could keep track of the actual shadow roots that need
+ // their styles recomputed.
+ SetStylistShadowDOMStyleSheetsDirty();
+
+ // FIXME(emilio): This should be done using stylesheet invalidation instead.
+ if (nsPresContext* pc = GetPresContext()) {
+ pc->RestyleManager()->PostRestyleEvent(
+ aShadowRoot.Host(), RestyleHint::RestyleSubtree(), nsChangeHint(0));
+ }
+}
+
+void ServoStyleSet::InvalidateStyleForDocumentStateChanges(
+ DocumentState aStatesChanged) {
+ MOZ_ASSERT(mDocument);
+ MOZ_ASSERT(!aStatesChanged.IsEmpty());
+
+ nsPresContext* pc = GetPresContext();
+ if (!pc) {
+ return;
+ }
+
+ Element* root = mDocument->GetRootElement();
+ if (!root) {
+ return;
+ }
+
+ // TODO(emilio): It may be nicer to just invalidate stuff in a given subtree
+ // for Shadow DOM. Consider just enumerating shadow roots instead and run
+ // invalidation individually, passing mRawData for the UA / User sheets.
+ AutoTArray<const StyleAuthorStyles*, 20> nonDocumentStyles;
+
+ EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
+ if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
+ nonDocumentStyles.AppendElement(authorStyles);
+ }
+ });
+
+ Servo_InvalidateStyleForDocStateChanges(root, mRawData.get(),
+ &nonDocumentStyles,
+ aStatesChanged.GetInternalValue());
+}
+
+static const MediaFeatureChangeReason kMediaFeaturesAffectingDefaultStyle =
+ // Zoom changes change the meaning of em units.
+ MediaFeatureChangeReason::ZoomChange |
+ // A resolution change changes the app-units-per-dev-pixels ratio, which
+ // some structs (Border, Outline, Column) store for clamping. We should
+ // arguably not do that, maybe doing it on layout directly, to try to avoid
+ // relying on the pres context (bug 1418159).
+ MediaFeatureChangeReason::ResolutionChange;
+
+RestyleHint ServoStyleSet::MediumFeaturesChanged(
+ MediaFeatureChangeReason aReason) {
+ AutoTArray<StyleAuthorStyles*, 20> nonDocumentStyles;
+
+ EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
+ if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
+ nonDocumentStyles.AppendElement(authorStyles);
+ }
+ });
+
+ const bool mayAffectDefaultStyle =
+ bool(aReason & kMediaFeaturesAffectingDefaultStyle);
+ const MediumFeaturesChangedResult result =
+ Servo_StyleSet_MediumFeaturesChanged(mRawData.get(), &nonDocumentStyles,
+ mayAffectDefaultStyle);
+
+ const bool viewportChanged =
+ bool(aReason & MediaFeatureChangeReason::ViewportChange);
+ if (viewportChanged) {
+ InvalidateForViewportUnits(OnlyDynamic::No);
+ }
+
+ const bool rulesChanged =
+ result.mAffectsDocumentRules || result.mAffectsNonDocumentRules;
+
+ if (result.mAffectsDocumentRules) {
+ SetStylistStyleSheetsDirty();
+ }
+
+ if (result.mAffectsNonDocumentRules) {
+ SetStylistShadowDOMStyleSheetsDirty();
+ }
+
+ if (rulesChanged) {
+ // TODO(emilio): This could be more granular.
+ return RestyleHint::RestyleSubtree();
+ }
+
+ return RestyleHint{0};
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(ServoStyleSetMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoStyleSetMallocEnclosingSizeOf)
+
+void ServoStyleSet::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const {
+ MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
+
+ aSizes.mLayoutStyleSetsOther += mallocSizeOf(this);
+
+ if (mRawData) {
+ aSizes.mLayoutStyleSetsOther += mallocSizeOf(mRawData.get());
+ ServoStyleSetSizes sizes;
+ // Measure mRawData. We use ServoStyleSetMallocSizeOf rather than
+ // aMallocSizeOf to distinguish in DMD's output the memory measured within
+ // Servo code.
+ Servo_StyleSet_AddSizeOfExcludingThis(ServoStyleSetMallocSizeOf,
+ ServoStyleSetMallocEnclosingSizeOf,
+ &sizes, mRawData.get());
+
+ // The StyleSet does not contain precomputed pseudos; they are in the UA
+ // cache.
+ MOZ_RELEASE_ASSERT(sizes.mPrecomputedPseudos == 0);
+
+ aSizes.mLayoutStyleSetsStylistRuleTree += sizes.mRuleTree;
+ aSizes.mLayoutStyleSetsStylistElementAndPseudosMaps +=
+ sizes.mElementAndPseudosMaps;
+ aSizes.mLayoutStyleSetsStylistInvalidationMap += sizes.mInvalidationMap;
+ aSizes.mLayoutStyleSetsStylistRevalidationSelectors +=
+ sizes.mRevalidationSelectors;
+ aSizes.mLayoutStyleSetsStylistOther += sizes.mOther;
+ }
+
+ if (mStyleRuleMap) {
+ aSizes.mLayoutStyleSetsOther +=
+ mStyleRuleMap->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mSheets
+ // - mNonInheritingComputedStyles
+ //
+ // The following members are not measured:
+ // - mDocument, because it a non-owning pointer
+}
+
+void ServoStyleSet::SetAuthorStyleDisabled(bool aStyleDisabled) {
+ if (mAuthorStyleDisabled == aStyleDisabled) {
+ return;
+ }
+
+ mAuthorStyleDisabled = aStyleDisabled;
+ if (Element* root = mDocument->GetRootElement()) {
+ if (nsPresContext* pc = GetPresContext()) {
+ pc->RestyleManager()->PostRestyleEvent(
+ root, RestyleHint::RestyleSubtree(), nsChangeHint(0));
+ }
+ }
+ Servo_StyleSet_SetAuthorStyleDisabled(mRawData.get(), mAuthorStyleDisabled);
+ // XXX Workaround for bug 1437785.
+ SetStylistStyleSheetsDirty();
+}
+
+const ServoElementSnapshotTable& ServoStyleSet::Snapshots() {
+ MOZ_ASSERT(GetPresContext(), "Styling a document without a shell?");
+ return GetPresContext()->RestyleManager()->Snapshots();
+}
+
+void ServoStyleSet::PreTraverseSync() {
+ // Get the Document's root element to ensure that the cache is valid before
+ // calling into the (potentially-parallel) Servo traversal, where a cache hit
+ // is necessary to avoid a data race when updating the cache.
+ Unused << mDocument->GetRootElement();
+
+ // FIXME(emilio): These two shouldn't be needed in theory, the call to the
+ // same function in PresShell should do the work, but as it turns out we
+ // ProcessPendingRestyles() twice, and runnables from frames just constructed
+ // can end up doing editing stuff, which adds stylesheets etc...
+ mDocument->FlushUserFontSet();
+ UpdateStylistIfNeeded();
+
+ mDocument->ResolveScheduledPresAttrs();
+
+ LookAndFeel::NativeInit();
+
+ mDocument->CacheAllKnownLangPrefs();
+
+ if (gfxUserFontSet* userFontSet = mDocument->GetUserFontSet()) {
+ nsPresContext* presContext = GetPresContext();
+ MOZ_ASSERT(presContext,
+ "For now, we don't call into here without a pres context");
+
+ // Ensure that the @font-face data is not stale
+ uint64_t generation = userFontSet->GetGeneration();
+ if (generation != mUserFontSetUpdateGeneration) {
+ mDocument->GetFonts()->CacheFontLoadability();
+ presContext->UpdateFontCacheUserFonts(userFontSet);
+ mUserFontSetUpdateGeneration = generation;
+ }
+ }
+
+ MOZ_ASSERT(!StylistNeedsUpdate());
+}
+
+void ServoStyleSet::PreTraverse(ServoTraversalFlags aFlags, Element* aRoot) {
+ PreTraverseSync();
+
+ // Process animation stuff that we should avoid doing during the parallel
+ // traversal.
+ SMILAnimationController* smilController =
+ mDocument->HasAnimationController() ? mDocument->GetAnimationController()
+ : nullptr;
+
+ MOZ_ASSERT(GetPresContext());
+ if (aRoot) {
+ GetPresContext()->EffectCompositor()->PreTraverseInSubtree(aFlags, aRoot);
+ if (smilController) {
+ smilController->PreTraverseInSubtree(aRoot);
+ }
+ } else {
+ GetPresContext()->EffectCompositor()->PreTraverse(aFlags);
+ if (smilController) {
+ smilController->PreTraverse();
+ }
+ }
+}
+
+static inline already_AddRefed<ComputedStyle>
+ResolveStyleForTextOrFirstLetterContinuation(
+ const StylePerDocumentStyleData* aRawData, ComputedStyle& aParent,
+ PseudoStyleType aType) {
+ MOZ_ASSERT(aType == PseudoStyleType::mozText ||
+ aType == PseudoStyleType::firstLetterContinuation);
+ auto inheritTarget = aType == PseudoStyleType::mozText
+ ? InheritTarget::Text
+ : InheritTarget::FirstLetterContinuation;
+
+ RefPtr<ComputedStyle> style = aParent.GetCachedInheritingAnonBoxStyle(aType);
+ if (!style) {
+ style =
+ Servo_ComputedValues_Inherit(aRawData, aType, &aParent, inheritTarget)
+ .Consume();
+ MOZ_ASSERT(style);
+ aParent.SetCachedInheritedAnonBoxStyle(style);
+ }
+
+ return style.forget();
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleForText(
+ nsIContent* aTextNode, ComputedStyle* aParentStyle) {
+ MOZ_ASSERT(aTextNode && aTextNode->IsText());
+ MOZ_ASSERT(aTextNode->GetParent());
+ MOZ_ASSERT(aParentStyle);
+
+ return ResolveStyleForTextOrFirstLetterContinuation(
+ mRawData.get(), *aParentStyle, PseudoStyleType::mozText);
+}
+
+already_AddRefed<ComputedStyle>
+ServoStyleSet::ResolveStyleForFirstLetterContinuation(
+ ComputedStyle* aParentStyle) {
+ MOZ_ASSERT(aParentStyle);
+
+ return ResolveStyleForTextOrFirstLetterContinuation(
+ mRawData.get(), *aParentStyle, PseudoStyleType::firstLetterContinuation);
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleForPlaceholder() {
+ RefPtr<ComputedStyle>& cache = mNonInheritingComputedStyles
+ [nsCSSAnonBoxes::NonInheriting::oofPlaceholder];
+ if (cache) {
+ RefPtr<ComputedStyle> retval = cache;
+ return retval.forget();
+ }
+
+ RefPtr<ComputedStyle> computedValues =
+ Servo_ComputedValues_Inherit(mRawData.get(),
+ PseudoStyleType::oofPlaceholder, nullptr,
+ InheritTarget::PlaceholderFrame)
+ .Consume();
+ MOZ_ASSERT(computedValues);
+
+ cache = computedValues;
+ return computedValues.forget();
+}
+
+static inline bool LazyPseudoIsCacheable(PseudoStyleType aType,
+ const Element& aOriginatingElement,
+ ComputedStyle* aParentStyle) {
+ return aParentStyle &&
+ !nsCSSPseudoElements::IsEagerlyCascadedInServo(aType) &&
+ aOriginatingElement.HasServoData() &&
+ !Servo_Element_IsPrimaryStyleReusedViaRuleNode(&aOriginatingElement);
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::ResolvePseudoElementStyle(
+ const Element& aOriginatingElement, PseudoStyleType aType,
+ nsAtom* aFunctionalPseudoParameter, ComputedStyle* aParentStyle,
+ IsProbe aIsProbe) {
+ // Runs from frame construction, this should have clean styles already, except
+ // with non-lazy FC...
+ UpdateStylistIfNeeded();
+ MOZ_ASSERT(PseudoStyle::IsPseudoElement(aType));
+
+ // caching is done using `aType` only, therefore results would be wrong for
+ // pseudos with functional parameters (e.g. `::highlight(foo)`).
+ const bool cacheable =
+ !aFunctionalPseudoParameter &&
+ LazyPseudoIsCacheable(aType, aOriginatingElement, aParentStyle);
+ RefPtr<ComputedStyle> style =
+ cacheable ? aParentStyle->GetCachedLazyPseudoStyle(aType) : nullptr;
+
+ const bool isProbe = aIsProbe == IsProbe::Yes;
+
+ if (!style) {
+ // FIXME(emilio): Why passing null for probing as the parent style?
+ //
+ // There are callers which do pass the wrong parent style and it would
+ // assert (like ComputeSelectionStyle()). That's messy!
+ style = Servo_ResolvePseudoStyle(
+ &aOriginatingElement, aType, aFunctionalPseudoParameter,
+ isProbe, isProbe ? nullptr : aParentStyle, mRawData.get())
+ .Consume();
+ if (!style) {
+ MOZ_ASSERT(isProbe);
+ return nullptr;
+ }
+ if (cacheable) {
+ aParentStyle->SetCachedLazyPseudoStyle(style);
+ }
+ }
+
+ MOZ_ASSERT(style);
+
+ if (isProbe && !GeneratedContentPseudoExists(*aParentStyle, *style)) {
+ return nullptr;
+ }
+
+ return style.forget();
+}
+
+already_AddRefed<ComputedStyle>
+ServoStyleSet::ResolveInheritingAnonymousBoxStyle(PseudoStyleType aType,
+ ComputedStyle* aParentStyle) {
+ MOZ_ASSERT(PseudoStyle::IsInheritingAnonBox(aType));
+ MOZ_ASSERT_IF(aParentStyle, !StylistNeedsUpdate());
+
+ UpdateStylistIfNeeded();
+
+ RefPtr<ComputedStyle> style = nullptr;
+
+ if (aParentStyle) {
+ style = aParentStyle->GetCachedInheritingAnonBoxStyle(aType);
+ }
+
+ if (!style) {
+ style = Servo_ComputedValues_GetForAnonymousBox(aParentStyle, aType,
+ mRawData.get())
+ .Consume();
+ MOZ_ASSERT(style);
+ if (aParentStyle) {
+ aParentStyle->SetCachedInheritedAnonBoxStyle(style);
+ }
+ }
+
+ return style.forget();
+}
+
+already_AddRefed<ComputedStyle>
+ServoStyleSet::ResolveNonInheritingAnonymousBoxStyle(PseudoStyleType aType) {
+ MOZ_ASSERT(aType != PseudoStyleType::pageContent,
+ "Use ResolvePageContentStyle for page content");
+ MOZ_ASSERT(PseudoStyle::IsNonInheritingAnonBox(aType));
+
+ nsCSSAnonBoxes::NonInheriting type =
+ nsCSSAnonBoxes::NonInheritingTypeForPseudoType(aType);
+ RefPtr<ComputedStyle>& cache = mNonInheritingComputedStyles[type];
+ if (cache) {
+ RefPtr<ComputedStyle> retval = cache;
+ return retval.forget();
+ }
+
+ UpdateStylistIfNeeded();
+
+ // We always want to skip parent-based display fixup here. It never makes
+ // sense for non-inheriting anonymous boxes. (Static assertions in
+ // nsCSSAnonBoxes.cpp ensure that all non-inheriting non-anonymous boxes
+ // are indeed annotated as skipping this fixup.)
+ MOZ_ASSERT(!PseudoStyle::IsNonInheritingAnonBox(PseudoStyleType::viewport),
+ "viewport needs fixup to handle blockifying it");
+
+ RefPtr<ComputedStyle> computedValues =
+ Servo_ComputedValues_GetForAnonymousBox(nullptr, aType, mRawData.get())
+ .Consume();
+ MOZ_ASSERT(computedValues);
+
+ cache = computedValues;
+ return computedValues.forget();
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::ResolvePageContentStyle(
+ const nsAtom* aPageName, const StylePagePseudoClassFlags& aPseudo) {
+ // The empty atom is used to indicate no specified page name, and is not
+ // usable as a page-rule selector. Changing this to null is a slight
+ // optimization to avoid the Servo code from doing an unnecessary hashtable
+ // lookup, and still use the style cache in this case.
+ if (aPageName == nsGkAtoms::_empty) {
+ aPageName = nullptr;
+ }
+ // Only use the cache when we are doing a lookup for page styles without a
+ // page-name or any pseudo classes.
+ const bool useCache = !aPageName && !aPseudo;
+ RefPtr<ComputedStyle>& cache =
+ mNonInheritingComputedStyles[nsCSSAnonBoxes::NonInheriting::pageContent];
+ if (useCache && cache) {
+ RefPtr<ComputedStyle> retval = cache;
+ return retval.forget();
+ }
+
+ UpdateStylistIfNeeded();
+
+ RefPtr<ComputedStyle> computedValues =
+ Servo_ComputedValues_GetForPageContent(mRawData.get(), aPageName, aPseudo)
+ .Consume();
+ MOZ_ASSERT(computedValues);
+
+ if (useCache) {
+ cache = computedValues;
+ }
+ return computedValues.forget();
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::ResolveXULTreePseudoStyle(
+ dom::Element* aParentElement, nsCSSAnonBoxPseudoStaticAtom* aPseudoTag,
+ ComputedStyle* aParentStyle, const AtomArray& aInputWord) {
+ MOZ_ASSERT(nsCSSAnonBoxes::IsTreePseudoElement(aPseudoTag));
+ MOZ_ASSERT(aParentStyle);
+ NS_ASSERTION(!StylistNeedsUpdate(),
+ "Stylesheets modified when resolving XUL tree pseudo");
+
+ return Servo_ComputedValues_ResolveXULTreePseudoStyle(
+ aParentElement, aPseudoTag, aParentStyle, &aInputWord,
+ mRawData.get())
+ .Consume();
+}
+
+// manage the set of style sheets in the style set
+void ServoStyleSet::AppendStyleSheet(StyleSheet& aSheet) {
+ MOZ_ASSERT(aSheet.IsApplicable());
+ MOZ_ASSERT(aSheet.RawContents(),
+ "Raw sheet should be in place before insertion.");
+
+ aSheet.AddStyleSet(this);
+
+ // Maintain a mirrored list of sheets on the servo side.
+ // Servo will remove aSheet from its original position as part of the call
+ // to Servo_StyleSet_AppendStyleSheet.
+ Servo_StyleSet_AppendStyleSheet(mRawData.get(), &aSheet);
+ SetStylistStyleSheetsDirty();
+
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetAdded(aSheet);
+ }
+}
+
+void ServoStyleSet::RemoveStyleSheet(StyleSheet& aSheet) {
+ aSheet.DropStyleSet(this);
+
+ // Maintain a mirrored list of sheets on the servo side.
+ Servo_StyleSet_RemoveStyleSheet(mRawData.get(), &aSheet);
+ SetStylistStyleSheetsDirty();
+
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetRemoved(aSheet);
+ }
+}
+
+void ServoStyleSet::InsertStyleSheetBefore(StyleSheet& aNewSheet,
+ StyleSheet& aReferenceSheet) {
+ MOZ_ASSERT(aNewSheet.IsApplicable());
+ MOZ_ASSERT(aReferenceSheet.IsApplicable());
+ MOZ_ASSERT(&aNewSheet != &aReferenceSheet,
+ "Can't place sheet before itself.");
+ MOZ_ASSERT(aNewSheet.GetOrigin() == aReferenceSheet.GetOrigin(),
+ "Sheets should be in the same origin");
+ MOZ_ASSERT(aNewSheet.RawContents(),
+ "Raw sheet should be in place before insertion.");
+ MOZ_ASSERT(aReferenceSheet.RawContents(),
+ "Reference sheet should have a raw sheet.");
+
+ // Servo will remove aNewSheet from its original position as part of the
+ // call to Servo_StyleSet_InsertStyleSheetBefore.
+ aNewSheet.AddStyleSet(this);
+
+ // Maintain a mirrored list of sheets on the servo side.
+ Servo_StyleSet_InsertStyleSheetBefore(mRawData.get(), &aNewSheet,
+ &aReferenceSheet);
+ SetStylistStyleSheetsDirty();
+
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetAdded(aNewSheet);
+ }
+}
+
+size_t ServoStyleSet::SheetCount(Origin aOrigin) const {
+ return Servo_StyleSet_GetSheetCount(mRawData.get(), aOrigin);
+}
+
+StyleSheet* ServoStyleSet::SheetAt(Origin aOrigin, size_t aIndex) const {
+ return const_cast<StyleSheet*>(
+ Servo_StyleSet_GetSheetAt(mRawData.get(), aOrigin, aIndex));
+}
+
+ServoStyleSet::PageSizeAndOrientation
+ServoStyleSet::GetDefaultPageSizeAndOrientation() {
+ PageSizeAndOrientation retval;
+ const RefPtr<ComputedStyle> style =
+ ResolvePageContentStyle(nullptr, StylePagePseudoClassFlags::NONE);
+ const StylePageSize& pageSize = style->StylePage()->mSize;
+
+ if (pageSize.IsSize()) {
+ const nscoord w = pageSize.AsSize().width.ToAppUnits();
+ const nscoord h = pageSize.AsSize().height.ToAppUnits();
+ // Ignoring sizes that include a zero width or height.
+ // These are also ignored in nsPageFrame::ComputePageSize()
+ // when calculating the scaling for a page size.
+ // In bug 1807985, we might add similar handling for @page margin/size
+ // combinations that produce a zero-sized page-content box.
+ if (w > 0 && h > 0) {
+ retval.size.emplace(w, h);
+ if (w > h) {
+ retval.orientation.emplace(StylePageSizeOrientation::Landscape);
+ } else if (w < h) {
+ retval.orientation.emplace(StylePageSizeOrientation::Portrait);
+ }
+ }
+ } else if (pageSize.IsOrientation()) {
+ retval.orientation.emplace(pageSize.AsOrientation());
+ } else {
+ MOZ_ASSERT(pageSize.IsAuto(), "Impossible page size");
+ }
+ return retval;
+}
+
+void ServoStyleSet::AppendAllNonDocumentAuthorSheets(
+ nsTArray<StyleSheet*>& aArray) const {
+ EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
+ for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
+ aArray.AppendElement(aShadowRoot.SheetAt(index));
+ }
+ aArray.AppendElements(aShadowRoot.AdoptedStyleSheets());
+ });
+}
+
+void ServoStyleSet::AddDocStyleSheet(StyleSheet& aSheet) {
+ MOZ_ASSERT(aSheet.IsApplicable());
+ MOZ_ASSERT(aSheet.RawContents(),
+ "Raw sheet should be in place by this point.");
+
+ size_t index = mDocument->FindDocStyleSheetInsertionPoint(aSheet);
+ aSheet.AddStyleSet(this);
+
+ if (index < SheetCount(Origin::Author)) {
+ // This case is insert before.
+ StyleSheet* beforeSheet = SheetAt(Origin::Author, index);
+
+ // Maintain a mirrored list of sheets on the servo side.
+ Servo_StyleSet_InsertStyleSheetBefore(mRawData.get(), &aSheet, beforeSheet);
+ SetStylistStyleSheetsDirty();
+ } else {
+ // Maintain a mirrored list of sheets on the servo side.
+ Servo_StyleSet_AppendStyleSheet(mRawData.get(), &aSheet);
+ SetStylistStyleSheetsDirty();
+ }
+
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetAdded(aSheet);
+ }
+}
+
+bool ServoStyleSet::GeneratedContentPseudoExists(
+ const ComputedStyle& aParentStyle, const ComputedStyle& aPseudoStyle) {
+ auto type = aPseudoStyle.GetPseudoType();
+ MOZ_ASSERT(type != PseudoStyleType::NotPseudo);
+
+ if (type == PseudoStyleType::marker) {
+ // ::marker only exist for list items (for now).
+ if (!aParentStyle.StyleDisplay()->IsListItem()) {
+ return false;
+ }
+ const auto& content = aPseudoStyle.StyleContent()->mContent;
+ // ::marker does not exist if 'content' is 'none' (this trumps
+ // any 'list-style-type' or 'list-style-image' values).
+ if (content.IsNone()) {
+ return false;
+ }
+ // ::marker only exist if we have 'content' or at least one of
+ // 'list-style-type' or 'list-style-image'.
+ if (aPseudoStyle.StyleList()->mCounterStyle.IsNone() &&
+ aPseudoStyle.StyleList()->mListStyleImage.IsNone() &&
+ content.IsNormal()) {
+ return false;
+ }
+ // display:none is equivalent to not having a pseudo at all.
+ if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {
+ return false;
+ }
+ }
+
+ // For ::before and ::after pseudo-elements, no 'content' items is
+ // equivalent to not having the pseudo-element at all.
+ if (type == PseudoStyleType::before || type == PseudoStyleType::after) {
+ if (!aPseudoStyle.StyleContent()->mContent.IsItems()) {
+ return false;
+ }
+ MOZ_ASSERT(aPseudoStyle.StyleContent()->ContentCount() > 0,
+ "IsItems() implies we have at least one item");
+ // display:none is equivalent to not having a pseudo at all.
+ if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool ServoStyleSet::StyleDocument(ServoTraversalFlags aFlags) {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR_RELEVANT_FOR_JS(LAYOUT_StyleComputation);
+ MOZ_ASSERT(GetPresContext(), "Styling a document without a shell?");
+
+ if (!mDocument->GetServoRestyleRoot()) {
+ return false;
+ }
+
+ Element* rootElement = mDocument->GetRootElement();
+ if (rootElement && MOZ_UNLIKELY(!rootElement->HasServoData())) {
+ StyleNewSubtree(rootElement);
+ return true;
+ }
+
+ PreTraverse(aFlags);
+ AutoPrepareTraversal guard(this);
+ const SnapshotTable& snapshots = Snapshots();
+
+ // Restyle the document from the root element and each of the document level
+ // NAC subtree roots.
+ bool postTraversalRequired = false;
+
+ if (ShouldTraverseInParallel()) {
+ aFlags |= ServoTraversalFlags::ParallelTraversal;
+ }
+
+ // Do the first traversal.
+ DocumentStyleRootIterator iter(mDocument->GetServoRestyleRoot());
+ while (Element* root = iter.GetNextStyleRoot()) {
+ MOZ_ASSERT(MayTraverseFrom(root));
+
+ Element* parent = root->GetFlattenedTreeParentElementForStyle();
+ MOZ_ASSERT_IF(parent,
+ !parent->HasAnyOfFlags(Element::kAllServoDescendantBits));
+
+ postTraversalRequired |=
+ Servo_TraverseSubtree(root, mRawData.get(), &snapshots, aFlags) ||
+ root->HasAnyOfFlags(Element::kAllServoDescendantBits |
+ NODE_NEEDS_FRAME);
+
+ {
+ uint32_t existingBits = mDocument->GetServoRestyleRootDirtyBits();
+ Element* newRoot = nullptr;
+ while (parent && parent->HasDirtyDescendantsForServo()) {
+ MOZ_ASSERT(root == mDocument->GetServoRestyleRoot(),
+ "Restyle root shouldn't have magically changed");
+ // If any style invalidation was triggered in our siblings, then we may
+ // need to post-traverse them, even if the root wasn't restyled after
+ // all.
+ // We need to propagate the existing bits to the ancestor.
+ parent->SetFlags(existingBits);
+ newRoot = parent;
+ parent = parent->GetFlattenedTreeParentElementForStyle();
+ }
+
+ if (newRoot) {
+ mDocument->SetServoRestyleRoot(
+ newRoot, existingBits | ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+ postTraversalRequired = true;
+ }
+ }
+ }
+
+ // If there are still animation restyles needed, trigger a second traversal to
+ // update CSS animations or transitions' styles.
+ //
+ // Note that we need to check the style root again, because doing another
+ // PreTraverse on the EffectCompositor might alter the style root. But we
+ // don't need to worry about NAC, since document-level NAC shouldn't have
+ // animations.
+ //
+ // We don't need to do this for SMIL since SMIL only updates its animation
+ // values once at the begin of a tick. As a result, even if the previous
+ // traversal caused, for example, the font-size to change, the SMIL style
+ // won't be updated until the next tick anyway.
+ if (GetPresContext()->EffectCompositor()->PreTraverse(aFlags)) {
+ DocumentStyleRootIterator iter(mDocument->GetServoRestyleRoot());
+ while (Element* root = iter.GetNextStyleRoot()) {
+ postTraversalRequired |=
+ Servo_TraverseSubtree(root, mRawData.get(), &snapshots, aFlags) ||
+ root->HasAnyOfFlags(Element::kAllServoDescendantBits |
+ NODE_NEEDS_FRAME);
+ }
+ }
+
+ return postTraversalRequired;
+}
+
+void ServoStyleSet::StyleNewSubtree(Element* aRoot) {
+ MOZ_ASSERT(GetPresContext());
+ MOZ_ASSERT(!aRoot->HasServoData());
+ MOZ_ASSERT(aRoot->GetFlattenedTreeParentNodeForStyle(),
+ "Not in the flat tree? Fishy!");
+ PreTraverseSync();
+ AutoPrepareTraversal guard(this);
+
+ // Do the traversal. The snapshots will not be used.
+ const SnapshotTable& snapshots = Snapshots();
+ auto flags = ServoTraversalFlags::Empty;
+ if (ShouldTraverseInParallel()) {
+ flags |= ServoTraversalFlags::ParallelTraversal;
+ }
+
+ DebugOnly<bool> postTraversalRequired =
+ Servo_TraverseSubtree(aRoot, mRawData.get(), &snapshots, flags);
+ MOZ_ASSERT(!postTraversalRequired);
+
+ // Annoyingly, the newly-styled content may have animations that need
+ // starting, which requires traversing them again. Mark the elements
+ // that need animation processing, then do a forgetful traversal to
+ // update the styles and clear the animation bits.
+ if (GetPresContext()->EffectCompositor()->PreTraverseInSubtree(flags,
+ aRoot)) {
+ postTraversalRequired =
+ Servo_TraverseSubtree(aRoot, mRawData.get(), &snapshots,
+ ServoTraversalFlags::AnimationOnly |
+ ServoTraversalFlags::FinalAnimationTraversal);
+ MOZ_ASSERT(!postTraversalRequired);
+ }
+}
+
+void ServoStyleSet::MarkOriginsDirty(OriginFlags aChangedOrigins) {
+ SetStylistStyleSheetsDirty();
+ Servo_StyleSet_NoteStyleSheetsChanged(mRawData.get(), aChangedOrigins);
+}
+
+void ServoStyleSet::SetStylistStyleSheetsDirty() {
+ mStylistState |= StylistState::StyleSheetsDirty;
+
+ // We need to invalidate cached style in getComputedStyle for undisplayed
+ // elements, since we don't know if any of the style sheet change that we do
+ // would affect undisplayed elements.
+ //
+ // We don't allow to call getComputedStyle in elements without a pres shell
+ // yet, so it is fine if there's no pres context here.
+ if (nsPresContext* presContext = GetPresContext()) {
+ presContext->RestyleManager()->IncrementUndisplayedRestyleGeneration();
+ }
+}
+
+void ServoStyleSet::SetStylistShadowDOMStyleSheetsDirty() {
+ mStylistState |= StylistState::ShadowDOMStyleSheetsDirty;
+ if (nsPresContext* presContext = GetPresContext()) {
+ presContext->RestyleManager()->IncrementUndisplayedRestyleGeneration();
+ }
+}
+
+static OriginFlags ToOriginFlags(StyleOrigin aOrigin) {
+ switch (aOrigin) {
+ case StyleOrigin::UserAgent:
+ return OriginFlags::UserAgent;
+ case StyleOrigin::User:
+ return OriginFlags::User;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Unknown origin?");
+ case StyleOrigin::Author:
+ return OriginFlags::Author;
+ }
+}
+
+void ServoStyleSet::ImportRuleLoaded(dom::CSSImportRule&, StyleSheet& aSheet) {
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetAdded(aSheet);
+ }
+
+ // TODO: Should probably consider ancestor sheets too.
+ if (!aSheet.IsApplicable()) {
+ return;
+ }
+
+ // TODO(emilio): Could handle it better given we know it is an insertion, and
+ // use the style invalidation machinery stuff that we do for regular sheet
+ // insertions.
+ MarkOriginsDirty(ToOriginFlags(aSheet.GetOrigin()));
+}
+
+void ServoStyleSet::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
+ if (mStyleRuleMap) {
+ mStyleRuleMap->RuleAdded(aSheet, aRule);
+ }
+
+ if (!aSheet.IsApplicable() || aRule.IsIncompleteImportRule()) {
+ return;
+ }
+
+ RuleChangedInternal(aSheet, aRule, StyleRuleChangeKind::Insertion);
+}
+
+void ServoStyleSet::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
+ if (mStyleRuleMap) {
+ mStyleRuleMap->RuleRemoved(aSheet, aRule);
+ }
+
+ if (!aSheet.IsApplicable()) {
+ return;
+ }
+
+ RuleChangedInternal(aSheet, aRule, StyleRuleChangeKind::Removal);
+}
+
+void ServoStyleSet::RuleChangedInternal(StyleSheet& aSheet, css::Rule& aRule,
+ StyleRuleChangeKind aKind) {
+ MOZ_ASSERT(aSheet.IsApplicable());
+ SetStylistStyleSheetsDirty();
+
+#define CASE_FOR(constant_, type_) \
+ case StyleCssRuleType::constant_: \
+ return Servo_StyleSet_##constant_##RuleChanged( \
+ mRawData.get(), static_cast<dom::CSS##type_##Rule&>(aRule).Raw(), \
+ &aSheet, aKind);
+
+ switch (aRule.Type()) {
+ CASE_FOR(CounterStyle, CounterStyle)
+ CASE_FOR(Style, Style)
+ CASE_FOR(Import, Import)
+ CASE_FOR(Media, Media)
+ CASE_FOR(Keyframes, Keyframes)
+ CASE_FOR(FontFeatureValues, FontFeatureValues)
+ CASE_FOR(FontPaletteValues, FontPaletteValues)
+ CASE_FOR(FontFace, FontFace)
+ CASE_FOR(Page, Page)
+ CASE_FOR(Property, Property)
+ CASE_FOR(Document, MozDocument)
+ CASE_FOR(Supports, Supports)
+ CASE_FOR(LayerBlock, LayerBlock)
+ CASE_FOR(LayerStatement, LayerStatement)
+ CASE_FOR(Container, Container)
+ // @namespace can only be inserted / removed when there are only other
+ // @namespace and @import rules, and can't be mutated.
+ case StyleCssRuleType::Namespace:
+ break;
+ case StyleCssRuleType::Keyframe:
+ // FIXME: We should probably just forward to the parent @keyframes rule? I
+ // think that'd do the right thing, but meanwhile...
+ return MarkOriginsDirty(ToOriginFlags(aSheet.GetOrigin()));
+ case StyleCssRuleType::Margin:
+ // Margin rules not implemented yet, see bug 1864737
+ break;
+ }
+
+#undef CASE_FOR
+}
+
+void ServoStyleSet::RuleChanged(StyleSheet& aSheet, css::Rule* aRule,
+ StyleRuleChangeKind aKind) {
+ if (!aSheet.IsApplicable()) {
+ return;
+ }
+
+ if (!aRule) {
+ // FIXME: This is done for StyleSheet.media attribute changes and such
+ MarkOriginsDirty(ToOriginFlags(aSheet.GetOrigin()));
+ } else {
+ RuleChangedInternal(aSheet, *aRule, aKind);
+ }
+}
+
+void ServoStyleSet::SheetCloned(StyleSheet& aSheet) {
+ mNeedsRestyleAfterEnsureUniqueInner = true;
+ if (mStyleRuleMap) {
+ mStyleRuleMap->SheetCloned(aSheet);
+ }
+}
+
+#ifdef DEBUG
+void ServoStyleSet::AssertTreeIsClean() {
+ DocumentStyleRootIterator iter(mDocument);
+ while (Element* root = iter.GetNextStyleRoot()) {
+ Servo_AssertTreeIsClean(root);
+ }
+}
+#endif
+
+bool ServoStyleSet::GetKeyframesForName(
+ const Element& aElement, const ComputedStyle& aStyle, nsAtom* aName,
+ const StyleComputedTimingFunction& aTimingFunction,
+ nsTArray<Keyframe>& aKeyframes) {
+ MOZ_ASSERT(!StylistNeedsUpdate());
+ return Servo_StyleSet_GetKeyframesForName(
+ mRawData.get(), &aElement, &aStyle, aName, &aTimingFunction, &aKeyframes);
+}
+
+nsTArray<ComputedKeyframeValues> ServoStyleSet::GetComputedKeyframeValuesFor(
+ const nsTArray<Keyframe>& aKeyframes, Element* aElement,
+ PseudoStyleType aPseudoType, const ComputedStyle* aStyle) {
+ nsTArray<ComputedKeyframeValues> result(aKeyframes.Length());
+
+ // Construct each nsTArray<PropertyStyleAnimationValuePair> here.
+ result.AppendElements(aKeyframes.Length());
+
+ Servo_GetComputedKeyframeValues(&aKeyframes, aElement, aPseudoType, aStyle,
+ mRawData.get(), &result);
+ return result;
+}
+
+void ServoStyleSet::GetAnimationValues(
+ StyleLockedDeclarationBlock* aDeclarations, Element* aElement,
+ const ComputedStyle* aComputedStyle,
+ nsTArray<RefPtr<StyleAnimationValue>>& aAnimationValues) {
+ // Servo_GetAnimationValues below won't handle ignoring existing element
+ // data for bfcached documents. (See comment in ResolveStyleLazily
+ // about these bfcache issues.)
+ Servo_GetAnimationValues(aDeclarations, aElement, aComputedStyle,
+ mRawData.get(), &aAnimationValues);
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::GetBaseContextForElement(
+ Element* aElement, const ComputedStyle* aStyle) {
+ return Servo_StyleSet_GetBaseComputedValuesForElement(
+ mRawData.get(), aElement, aStyle, &Snapshots())
+ .Consume();
+}
+
+already_AddRefed<StyleAnimationValue> ServoStyleSet::ComputeAnimationValue(
+ Element* aElement, StyleLockedDeclarationBlock* aDeclarations,
+ const ComputedStyle* aStyle) {
+ return Servo_AnimationValue_Compute(aElement, aDeclarations, aStyle,
+ mRawData.get())
+ .Consume();
+}
+
+bool ServoStyleSet::UsesFontMetrics() const {
+ return Servo_StyleSet_UsesFontMetrics(mRawData.get());
+}
+
+bool ServoStyleSet::EnsureUniqueInnerOnCSSSheets() {
+ using SheetOwner = Variant<ServoStyleSet*, ShadowRoot*>;
+
+ AutoTArray<std::pair<StyleSheet*, SheetOwner>, 32> queue;
+ EnumerateStyleSheets([&](StyleSheet& aSheet) {
+ queue.AppendElement(std::make_pair(&aSheet, SheetOwner{this}));
+ });
+
+ EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
+ for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
+ queue.AppendElement(
+ std::make_pair(aShadowRoot.SheetAt(index), SheetOwner{&aShadowRoot}));
+ }
+ for (const auto& adopted : aShadowRoot.AdoptedStyleSheets()) {
+ queue.AppendElement(
+ std::make_pair(adopted.get(), SheetOwner{&aShadowRoot}));
+ }
+ });
+
+ while (!queue.IsEmpty()) {
+ auto [sheet, owner] = queue.PopLastElement();
+
+ if (sheet->HasForcedUniqueInner()) {
+ // We already processed this sheet and its children.
+ // Normally we don't hit this but adopted stylesheets can have dupes so we
+ // can save some work here.
+ continue;
+ }
+
+ // Only call EnsureUniqueInner for complete sheets. If we do call it on
+ // incomplete sheets, we'll cause problems when the sheet is actually
+ // loaded. We don't care about incomplete sheets here anyway, because this
+ // method is only invoked by nsPresContext::EnsureSafeToHandOutCSSRules.
+ // The CSSRule objects we are handing out won't contain any rules derived
+ // from incomplete sheets (because they aren't yet applied in styling).
+ if (sheet->IsComplete()) {
+ sheet->EnsureUniqueInner();
+ }
+
+ // Enqueue all the sheet's children.
+ for (StyleSheet* child : sheet->ChildSheets()) {
+ queue.AppendElement(std::make_pair(child, owner));
+ }
+ }
+
+ if (mNeedsRestyleAfterEnsureUniqueInner) {
+ // TODO(emilio): We could make this faster if needed tracking the specific
+ // origins and sheets that have been cloned. But the only caller of this
+ // doesn't seem to really care about perf.
+ MarkOriginsDirty(OriginFlags::All);
+ ForceDirtyAllShadowStyles();
+ }
+ bool res = mNeedsRestyleAfterEnsureUniqueInner;
+ mNeedsRestyleAfterEnsureUniqueInner = false;
+ return res;
+}
+
+void ServoStyleSet::ClearCachedStyleData() {
+ ClearNonInheritingComputedStyles();
+ Servo_StyleSet_RebuildCachedData(mRawData.get());
+ mCachedAnonymousContentStyles.Clear();
+ PodArrayZero(mCachedAnonymousContentStyleIndexes);
+}
+
+void ServoStyleSet::ForceDirtyAllShadowStyles() {
+ bool anyShadow = false;
+ EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
+ if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
+ anyShadow = true;
+ Servo_AuthorStyles_ForceDirty(authorStyles);
+ }
+ });
+ if (anyShadow) {
+ SetStylistShadowDOMStyleSheetsDirty();
+ }
+}
+
+void ServoStyleSet::CompatibilityModeChanged() {
+ Servo_StyleSet_CompatModeChanged(mRawData.get());
+ SetStylistStyleSheetsDirty();
+ ForceDirtyAllShadowStyles();
+}
+
+void ServoStyleSet::ClearNonInheritingComputedStyles() {
+ for (RefPtr<ComputedStyle>& ptr : mNonInheritingComputedStyles) {
+ ptr = nullptr;
+ }
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleLazily(
+ const Element& aElement, PseudoStyleType aPseudoType,
+ nsAtom* aFunctionalPseudoParameter, StyleRuleInclusion aRuleInclusion) {
+ PreTraverseSync();
+ MOZ_ASSERT(!StylistNeedsUpdate());
+
+ AutoSetInServoTraversal guard(this);
+
+ /**
+ * NB: This is needed because we process animations and transitions on the
+ * pseudo-elements themselves, not on the parent's EagerPseudoStyles.
+ *
+ * That means that that style doesn't account for animations, and we can't do
+ * that easily from the traversal without doing wasted work.
+ *
+ * As such, we just lie here a bit, which is the entrypoint of
+ * getComputedStyle, the only API where this can be observed, to look at the
+ * style of the pseudo-element if it exists instead.
+ */
+ const Element* elementForStyleResolution = &aElement;
+ PseudoStyleType pseudoTypeForStyleResolution = aPseudoType;
+ if (aPseudoType == PseudoStyleType::before) {
+ if (Element* pseudo = nsLayoutUtils::GetBeforePseudo(&aElement)) {
+ elementForStyleResolution = pseudo;
+ pseudoTypeForStyleResolution = PseudoStyleType::NotPseudo;
+ }
+ } else if (aPseudoType == PseudoStyleType::after) {
+ if (Element* pseudo = nsLayoutUtils::GetAfterPseudo(&aElement)) {
+ elementForStyleResolution = pseudo;
+ pseudoTypeForStyleResolution = PseudoStyleType::NotPseudo;
+ }
+ } else if (aPseudoType == PseudoStyleType::marker) {
+ if (Element* pseudo = nsLayoutUtils::GetMarkerPseudo(&aElement)) {
+ elementForStyleResolution = pseudo;
+ pseudoTypeForStyleResolution = PseudoStyleType::NotPseudo;
+ }
+ }
+
+ nsPresContext* pc = GetPresContext();
+ MOZ_ASSERT(pc, "For now, no style resolution without a pres context");
+ auto* restyleManager = pc->RestyleManager();
+ const bool canUseCache = aRuleInclusion == StyleRuleInclusion::All &&
+ aElement.OwnerDoc() == mDocument &&
+ pc->PresShell()->DidInitialize();
+ return Servo_ResolveStyleLazily(
+ elementForStyleResolution, pseudoTypeForStyleResolution,
+ aFunctionalPseudoParameter, aRuleInclusion,
+ &restyleManager->Snapshots(),
+ restyleManager->GetUndisplayedRestyleGeneration(), canUseCache,
+ mRawData.get())
+ .Consume();
+}
+
+void ServoStyleSet::AppendFontFaceRules(
+ nsTArray<nsFontFaceRuleContainer>& aArray) {
+ // TODO(emilio): Can we make this so this asserts instead?
+ UpdateStylistIfNeeded();
+ Servo_StyleSet_GetFontFaceRules(mRawData.get(), &aArray);
+}
+
+const StyleLockedCounterStyleRule* ServoStyleSet::CounterStyleRuleForName(
+ nsAtom* aName) {
+ MOZ_ASSERT(!StylistNeedsUpdate());
+ return Servo_StyleSet_GetCounterStyleRule(mRawData.get(), aName);
+}
+
+already_AddRefed<gfxFontFeatureValueSet>
+ServoStyleSet::BuildFontFeatureValueSet() {
+ MOZ_ASSERT(!StylistNeedsUpdate());
+ RefPtr<gfxFontFeatureValueSet> set =
+ Servo_StyleSet_BuildFontFeatureValueSet(mRawData.get());
+ return set.forget();
+}
+
+already_AddRefed<gfx::FontPaletteValueSet>
+ServoStyleSet::BuildFontPaletteValueSet() {
+ MOZ_ASSERT(!StylistNeedsUpdate());
+ RefPtr<gfx::FontPaletteValueSet> set =
+ Servo_StyleSet_BuildFontPaletteValueSet(mRawData.get());
+ return set.forget();
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::ResolveForDeclarations(
+ const ComputedStyle* aParentOrNull,
+ const StyleLockedDeclarationBlock* aDeclarations) {
+ // No need to update the stylist, we're only cascading aDeclarations.
+ return Servo_StyleSet_ResolveForDeclarations(mRawData.get(), aParentOrNull,
+ aDeclarations)
+ .Consume();
+}
+
+void ServoStyleSet::UpdateStylist() {
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Update stylesheet information", LAYOUT);
+ MOZ_ASSERT(StylistNeedsUpdate());
+
+ if (mStylistState & StylistState::StyleSheetsDirty) {
+ Element* root = mDocument->GetRootElement();
+ const ServoElementSnapshotTable* snapshots = nullptr;
+ if (nsPresContext* pc = GetPresContext()) {
+ snapshots = &pc->RestyleManager()->Snapshots();
+ }
+ Servo_StyleSet_FlushStyleSheets(mRawData.get(), root, snapshots);
+ }
+
+ if (MOZ_UNLIKELY(mStylistState & StylistState::ShadowDOMStyleSheetsDirty)) {
+ EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
+ if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
+ Servo_AuthorStyles_Flush(authorStyles, mRawData.get());
+ }
+ });
+ Servo_StyleSet_RemoveUniqueEntriesFromAuthorStylesCache(mRawData.get());
+ }
+
+ mStylistState = StylistState::NotDirty;
+}
+
+void ServoStyleSet::MaybeGCRuleTree() {
+ MOZ_ASSERT(NS_IsMainThread());
+ Servo_MaybeGCRuleTree(mRawData.get());
+}
+
+/* static */
+bool ServoStyleSet::MayTraverseFrom(const Element* aElement) {
+ MOZ_ASSERT(aElement->IsInComposedDoc());
+ nsINode* parent = aElement->GetFlattenedTreeParentNodeForStyle();
+ if (!parent) {
+ return false;
+ }
+
+ if (!parent->IsElement()) {
+ MOZ_ASSERT(parent->IsDocument());
+ return true;
+ }
+
+ if (!parent->AsElement()->HasServoData()) {
+ return false;
+ }
+
+ return !Servo_Element_IsDisplayNone(parent->AsElement());
+}
+
+bool ServoStyleSet::ShouldTraverseInParallel() const {
+ MOZ_ASSERT(mDocument->GetPresShell(), "Styling a document without a shell?");
+ if (!mDocument->GetPresShell()->IsActive()) {
+ return false;
+ }
+ if (profiler_feature_active(ProfilerFeature::SequentialStyle)) {
+ return false;
+ }
+ return true;
+}
+
+void ServoStyleSet::RunPostTraversalTasks() {
+ MOZ_ASSERT(!IsInServoTraversal());
+
+ if (mPostTraversalTasks.IsEmpty()) {
+ return;
+ }
+
+ nsTArray<PostTraversalTask> tasks = std::move(mPostTraversalTasks);
+
+ for (auto& task : tasks) {
+ task.Run();
+ }
+}
+
+ServoStyleRuleMap* ServoStyleSet::StyleRuleMap() {
+ if (!mStyleRuleMap) {
+ mStyleRuleMap = MakeUnique<ServoStyleRuleMap>();
+ }
+ mStyleRuleMap->EnsureTable(*this);
+ return mStyleRuleMap.get();
+}
+
+bool ServoStyleSet::MightHaveAttributeDependency(const Element& aElement,
+ nsAtom* aAttribute) const {
+ return Servo_StyleSet_MightHaveAttributeDependency(mRawData.get(), &aElement,
+ aAttribute);
+}
+
+bool ServoStyleSet::MightHaveNthOfIDDependency(const Element& aElement,
+ nsAtom* aOldID,
+ nsAtom* aNewID) const {
+ return Servo_StyleSet_MightHaveNthOfIDDependency(mRawData.get(), &aElement,
+ aOldID, aNewID);
+}
+
+bool ServoStyleSet::MightHaveNthOfClassDependency(const Element& aElement) {
+ return Servo_StyleSet_MightHaveNthOfClassDependency(mRawData.get(), &aElement,
+ &Snapshots());
+}
+
+void ServoStyleSet::MaybeInvalidateRelativeSelectorIDDependency(
+ const Element& aElement, nsAtom* aOldID, nsAtom* aNewID,
+ const ServoElementSnapshotTable& aSnapshots) {
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorIDDependency(
+ mRawData.get(), &aElement, aOldID, aNewID, &aSnapshots);
+}
+
+void ServoStyleSet::MaybeInvalidateRelativeSelectorClassDependency(
+ const Element& aElement, const ServoElementSnapshotTable& aSnapshots) {
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorClassDependency(
+ mRawData.get(), &aElement, &aSnapshots);
+}
+
+void ServoStyleSet::MaybeInvalidateRelativeSelectorAttributeDependency(
+ const Element& aElement, nsAtom* aAttribute,
+ const ServoElementSnapshotTable& aSnapshots) {
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorAttributeDependency(
+ mRawData.get(), &aElement, aAttribute, &aSnapshots);
+}
+
+void ServoStyleSet::MaybeInvalidateRelativeSelectorStateDependency(
+ const Element& aElement, ElementState aState,
+ const ServoElementSnapshotTable& aSnapshots) {
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorStateDependency(
+ mRawData.get(), &aElement, aState.GetInternalValue(), &aSnapshots);
+}
+
+void ServoStyleSet::MaybeInvalidateRelativeSelectorForEmptyDependency(
+ const Element& aElement) {
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorEmptyDependency(mRawData.get(),
+ &aElement);
+}
+
+void ServoStyleSet::MaybeInvalidateRelativeSelectorForNthEdgeDependency(
+ const Element& aElement) {
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorNthEdgeDependency(
+ mRawData.get(), &aElement);
+}
+
+void ServoStyleSet::MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
+ const Element* aFromSibling) {
+ if (aFromSibling == nullptr) {
+ return;
+ }
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorNthDependencyFromSibling(
+ mRawData.get(), aFromSibling);
+}
+
+void ServoStyleSet::MaybeInvalidateForElementInsertion(
+ const Element& aElement) {
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorForInsertion(mRawData.get(),
+ &aElement);
+}
+
+void ServoStyleSet::MaybeInvalidateForElementAppend(
+ const nsIContent& aFirstContent) {
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorForAppend(mRawData.get(),
+ &aFirstContent);
+}
+
+void ServoStyleSet::MaybeInvalidateForElementRemove(
+ const Element& aElement, const nsIContent* aFollowingSibling) {
+ Servo_StyleSet_MaybeInvalidateRelativeSelectorForRemoval(
+ mRawData.get(), &aElement, aFollowingSibling);
+}
+
+bool ServoStyleSet::MightHaveNthOfAttributeDependency(
+ const Element& aElement, nsAtom* aAttribute) const {
+ return Servo_StyleSet_MightHaveNthOfAttributeDependency(
+ mRawData.get(), &aElement, aAttribute);
+}
+
+bool ServoStyleSet::HasStateDependency(const Element& aElement,
+ dom::ElementState aState) const {
+ return Servo_StyleSet_HasStateDependency(mRawData.get(), &aElement,
+ aState.GetInternalValue());
+}
+
+bool ServoStyleSet::HasNthOfStateDependency(const Element& aElement,
+ dom::ElementState aState) const {
+ return Servo_StyleSet_HasNthOfStateDependency(mRawData.get(), &aElement,
+ aState.GetInternalValue());
+}
+
+void ServoStyleSet::RestyleSiblingsForNthOf(const Element& aElement,
+ uint32_t aFlags) const {
+ Servo_StyleSet_RestyleSiblingsForNthOf(&aElement, aFlags);
+}
+
+bool ServoStyleSet::HasDocumentStateDependency(
+ dom::DocumentState aState) const {
+ return Servo_StyleSet_HasDocumentStateDependency(mRawData.get(),
+ aState.GetInternalValue());
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::ReparentComputedStyle(
+ ComputedStyle* aComputedStyle, ComputedStyle* aNewParent,
+ ComputedStyle* aNewLayoutParent, Element* aElement) {
+ return Servo_ReparentStyle(aComputedStyle, aNewParent, aNewLayoutParent,
+ aElement, mRawData.get())
+ .Consume();
+}
+
+void ServoStyleSet::InvalidateForViewportUnits(OnlyDynamic aOnlyDynamic) {
+ dom::Element* root = mDocument->GetRootElement();
+ if (!root) {
+ return;
+ }
+
+ Servo_InvalidateForViewportUnits(mRawData.get(), root,
+ aOnlyDynamic == OnlyDynamic::Yes);
+}
+
+void ServoStyleSet::RegisterProperty(const PropertyDefinition& aDefinition,
+ ErrorResult& aRv) {
+ using Result = StyleRegisterCustomPropertyResult;
+ auto result = Servo_RegisterCustomProperty(
+ RawData(), mDocument->DefaultStyleAttrURLData(), &aDefinition.mName,
+ &aDefinition.mSyntax, aDefinition.mInherits,
+ aDefinition.mInitialValue.WasPassed() ? &aDefinition.mInitialValue.Value()
+ : nullptr);
+ switch (result) {
+ case Result::SuccessfullyRegistered:
+ if (Element* root = mDocument->GetRootElement()) {
+ if (nsPresContext* pc = GetPresContext()) {
+ pc->RestyleManager()->PostRestyleEvent(
+ root, RestyleHint::RecascadeSubtree(), nsChangeHint(0));
+ }
+ }
+ mDocument->PostCustomPropertyRegistered(aDefinition);
+ break;
+ case Result::InvalidName:
+ return aRv.ThrowSyntaxError("Invalid name");
+ case Result::InvalidSyntax:
+ return aRv.ThrowSyntaxError("Invalid syntax descriptor");
+ case Result::InvalidInitialValue:
+ return aRv.ThrowSyntaxError("Invalid initial value syntax");
+ case Result::NoInitialValue:
+ return aRv.ThrowSyntaxError(
+ "Initial value is required when syntax is not universal");
+ case Result::InitialValueNotComputationallyIndependent:
+ return aRv.ThrowSyntaxError(
+ "Initial value is required when syntax is not universal");
+ case Result::AlreadyRegistered:
+ return aRv.ThrowInvalidModificationError("Property already registered");
+ }
+}
+
+NS_IMPL_ISUPPORTS(UACacheReporter, nsIMemoryReporter)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(ServoUACacheMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoUACacheMallocEnclosingSizeOf)
+
+NS_IMETHODIMP
+UACacheReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ ServoStyleSetSizes sizes;
+ Servo_UACache_AddSizeOf(ServoUACacheMallocSizeOf,
+ ServoUACacheMallocEnclosingSizeOf, &sizes);
+
+#define REPORT(_path, _amount, _desc) \
+ do { \
+ size_t __amount = _amount; /* evaluate _amount only once */ \
+ if (__amount > 0) { \
+ MOZ_COLLECT_REPORT(_path, KIND_HEAP, UNITS_BYTES, __amount, _desc); \
+ } \
+ } while (0)
+
+ // The UA cache does not contain the rule tree; that's in the StyleSet.
+ MOZ_RELEASE_ASSERT(sizes.mRuleTree == 0);
+
+ REPORT("explicit/layout/servo-ua-cache/precomputed-pseudos",
+ sizes.mPrecomputedPseudos,
+ "Memory used by precomputed pseudo-element declarations within the "
+ "UA cache.");
+
+ REPORT("explicit/layout/servo-ua-cache/element-and-pseudos-maps",
+ sizes.mElementAndPseudosMaps,
+ "Memory used by element and pseudos maps within the UA cache.");
+
+ REPORT("explicit/layout/servo-ua-cache/invalidation-map",
+ sizes.mInvalidationMap,
+ "Memory used by invalidation maps within the UA cache.");
+
+ REPORT("explicit/layout/servo-ua-cache/revalidation-selectors",
+ sizes.mRevalidationSelectors,
+ "Memory used by selectors for cache revalidation within the UA "
+ "cache.");
+
+ REPORT("explicit/layout/servo-ua-cache/other", sizes.mOther,
+ "Memory used by other data within the UA cache");
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/layout/style/ServoStyleSet.h b/layout/style/ServoStyleSet.h
new file mode 100644
index 0000000000..d7225349cc
--- /dev/null
+++ b/layout/style/ServoStyleSet.h
@@ -0,0 +1,745 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ServoStyleSet_h
+#define mozilla_ServoStyleSet_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/AnonymousContentKey.h"
+#include "mozilla/AtomArray.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PostTraversalTask.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/dom/RustTypes.h"
+#include "mozilla/UniquePtr.h"
+#include "MainThreadUtils.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsChangeHint.h"
+#include "nsCoord.h"
+#include "nsAtom.h"
+#include "nsIMemoryReporter.h"
+#include "nsTArray.h"
+#include "nsSize.h"
+
+namespace mozilla {
+enum class MediaFeatureChangeReason : uint8_t;
+enum class StylePageSizeOrientation : uint8_t;
+enum class StyleRuleChangeKind : uint32_t;
+
+class ErrorResult;
+
+template <typename Integer, typename Number, typename LinearStops>
+struct StyleTimingFunction;
+struct StylePagePseudoClassFlags;
+struct StylePiecewiseLinearFunction;
+using StyleComputedTimingFunction =
+ StyleTimingFunction<int32_t, float, StylePiecewiseLinearFunction>;
+
+namespace css {
+class Rule;
+} // namespace css
+namespace dom {
+class CSSImportRule;
+class Element;
+class ShadowRoot;
+struct PropertyDefinition;
+} // namespace dom
+namespace gfx {
+class FontPaletteValueSet;
+} // namespace gfx
+class StyleSheet;
+struct Keyframe;
+class ServoElementSnapshotTable;
+class ComputedStyle;
+class ServoStyleRuleMap;
+class StyleSheet;
+} // namespace mozilla
+class gfxFontFeatureValueSet;
+class nsIContent;
+
+class nsPresContext;
+class nsWindowSizes;
+struct TreeMatchContext;
+
+namespace mozilla {
+
+// A few flags used to track which kind of stylist state we may need to
+// update.
+enum class StylistState : uint8_t {
+ // The stylist is not dirty, we should do nothing.
+ NotDirty = 0,
+
+ // The style sheets have changed, so we need to update the style data.
+ StyleSheetsDirty = 1 << 0,
+
+ // Some of the style sheets of the shadow trees in the document have
+ // changed.
+ ShadowDOMStyleSheetsDirty = 1 << 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StylistState)
+
+enum class StyleOrigin : uint8_t;
+
+// Bitfield type to represent Servo stylesheet origins.
+enum class OriginFlags : uint8_t {
+ UserAgent = 0x01,
+ User = 0x02,
+ Author = 0x04,
+ All = 0x07,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(OriginFlags)
+
+/**
+ * The set of style sheets that apply to a document, backed by a Servo
+ * Stylist. A ServoStyleSet contains StyleSheets.
+ */
+class ServoStyleSet {
+ friend class RestyleManager;
+ using SnapshotTable = ServoElementSnapshotTable;
+ using Origin = StyleOrigin;
+
+ // We assert that these match the Servo ones in the definition of this array.
+ static constexpr Origin kOrigins[] = {
+ Origin(static_cast<uint8_t>(OriginFlags::UserAgent)),
+ Origin(static_cast<uint8_t>(OriginFlags::User)),
+ Origin(static_cast<uint8_t>(OriginFlags::Author)),
+ };
+
+ public:
+ static bool IsInServoTraversal() { return mozilla::IsInServoTraversal(); }
+
+#ifdef DEBUG
+ // Used for debug assertions. We make this debug-only to prevent callers from
+ // accidentally using it instead of IsInServoTraversal, which is cheaper. We
+ // can change this if a use-case arises.
+ static bool IsCurrentThreadInServoTraversal();
+#endif
+
+ static ServoStyleSet* Current() { return sInServoTraversal; }
+
+ explicit ServoStyleSet(dom::Document&);
+ ~ServoStyleSet();
+
+ void ShellDetachedFromDocument();
+
+ // Called when a rules in a stylesheet in this set, or a child sheet of that,
+ // are mutated from CSSOM.
+ void RuleAdded(StyleSheet&, css::Rule&);
+ void RuleRemoved(StyleSheet&, css::Rule&);
+ void RuleChanged(StyleSheet&, css::Rule*, StyleRuleChangeKind);
+ void SheetCloned(StyleSheet&);
+ void ImportRuleLoaded(dom::CSSImportRule&, StyleSheet&);
+
+ // Runs style invalidation due to document state changes.
+ void InvalidateStyleForDocumentStateChanges(
+ dom::DocumentState aStatesChanged);
+
+ void RecordShadowStyleChange(dom::ShadowRoot&);
+
+ bool StyleSheetsHaveChanged() const { return StylistNeedsUpdate(); }
+
+ RestyleHint MediumFeaturesChanged(MediaFeatureChangeReason);
+
+ // Evaluates a given SourceSizeList, returning the optimal viewport width in
+ // app units.
+ //
+ // The SourceSizeList parameter can be null, in which case it will return
+ // 100vw.
+ inline nscoord EvaluateSourceSizeList(
+ const StyleSourceSizeList* aSourceSizeList) const;
+
+ void AddSizeOfIncludingThis(nsWindowSizes& aSizes) const;
+ const StylePerDocumentStyleData* RawData() const { return mRawData.get(); }
+
+ bool GetAuthorStyleDisabled() const { return mAuthorStyleDisabled; }
+
+ bool UsesFontMetrics() const;
+
+ void SetAuthorStyleDisabled(bool aStyleDisabled);
+
+ // Get a CopmutedStyle for a text node (which no rules will match).
+ //
+ // The returned ComputedStyle will have nsCSSAnonBoxes::mozText() as its
+ // pseudo.
+ //
+ // (Perhaps mozText should go away and we shouldn't even create style
+ // contexts for such content nodes, when text-combine-upright is not
+ // present. However, not doing any rule matching for them is a first step.)
+ already_AddRefed<ComputedStyle> ResolveStyleForText(
+ nsIContent* aTextNode, ComputedStyle* aParentStyle);
+
+ // Get a ComputedStyle for a first-letter continuation (which no rules will
+ // match).
+ //
+ // The returned ComputedStyle will have
+ // nsCSSAnonBoxes::firstLetterContinuation() as its pseudo.
+ //
+ // (Perhaps nsCSSAnonBoxes::firstLetterContinuation() should go away and we
+ // shouldn't even create ComputedStyles for such frames. However, not doing
+ // any rule matching for them is a first step. And right now we do use this
+ // ComputedStyle for some things)
+ already_AddRefed<ComputedStyle> ResolveStyleForFirstLetterContinuation(
+ ComputedStyle* aParentStyle);
+
+ // Get a ComputedStyle for a placeholder frame (which no rules will match).
+ //
+ // The returned ComputedStyle will have nsCSSAnonBoxes::oofPlaceholder() as
+ // its pseudo.
+ //
+ // (Perhaps nsCSSAnonBoxes::oofPaceholder() should go away and we shouldn't
+ // even create ComputedStyle for placeholders. However, not doing any rule
+ // matching for them is a first step.)
+ already_AddRefed<ComputedStyle> ResolveStyleForPlaceholder();
+
+ // Returns whether a given pseudo-element should exist or not.
+ static bool GeneratedContentPseudoExists(const ComputedStyle& aParentStyle,
+ const ComputedStyle& aPseudoStyle);
+
+ enum class IsProbe {
+ No,
+ Yes,
+ };
+
+ // Get a style for a pseudo-element.
+ //
+ // If IsProbe is Yes, then no style is returned if there are no rules matching
+ // for the pseudo-element, or GeneratedContentPseudoExists returns false.
+ //
+ // If IsProbe is No, then the style is guaranteed to be non-null.
+ already_AddRefed<ComputedStyle> ResolvePseudoElementStyle(
+ const dom::Element& aOriginatingElement, PseudoStyleType,
+ nsAtom* aFunctionalPseudoParameter, ComputedStyle* aParentStyle,
+ IsProbe = IsProbe::No);
+
+ already_AddRefed<ComputedStyle> ProbePseudoElementStyle(
+ const dom::Element& aOriginatingElement, PseudoStyleType aType,
+ nsAtom* aFunctionalPseudoParameter, ComputedStyle* aParentStyle) {
+ return ResolvePseudoElementStyle(aOriginatingElement, aType,
+ aFunctionalPseudoParameter, aParentStyle,
+ IsProbe::Yes);
+ }
+
+ // Resolves style for a (possibly-pseudo) Element without assuming that the
+ // style has been resolved. If the element was unstyled and a new style
+ // was resolved, it is not stored in the DOM. (That is, the element remains
+ // unstyled.)
+ already_AddRefed<ComputedStyle> ResolveStyleLazily(
+ const dom::Element&, PseudoStyleType = PseudoStyleType::NotPseudo,
+ nsAtom* aFunctionalPseudoParameter = nullptr,
+ StyleRuleInclusion = StyleRuleInclusion::All);
+
+ // Get a ComputedStyle for an anonymous box. The pseudo type must be an
+ // inheriting anon box.
+ already_AddRefed<ComputedStyle> ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType, ComputedStyle* aParentStyle);
+
+ // Get a ComputedStyle for an anonymous box. The pseudo type must be
+ // a non-inheriting anon box, and must not be page-content.
+ // See ResolvePageContentStyle for resolving page-content style.
+ already_AddRefed<ComputedStyle> ResolveNonInheritingAnonymousBoxStyle(
+ PseudoStyleType aType);
+
+ // Get a ComputedStyle for a pageContent box with the specified page-name.
+ // A page name that is null or the empty atom and has no pseudo classes gets
+ // the global page style.
+ already_AddRefed<ComputedStyle> ResolvePageContentStyle(
+ const nsAtom* aPageName, const StylePagePseudoClassFlags& aPseudo);
+
+ already_AddRefed<ComputedStyle> ResolveXULTreePseudoStyle(
+ dom::Element* aParentElement, nsCSSAnonBoxPseudoStaticAtom* aPseudoTag,
+ ComputedStyle* aParentStyle, const AtomArray& aInputWord);
+
+ size_t SheetCount(Origin) const;
+ StyleSheet* SheetAt(Origin, size_t aIndex) const;
+
+ struct PageSizeAndOrientation {
+ Maybe<StylePageSizeOrientation> orientation;
+ Maybe<nsSize> size;
+ };
+ // Gets the default page size and orientation (the size/orientation specified
+ // by @page rules without a selector list), if any.
+ //
+ // If the specified size is just an orientation, then the size will be set to
+ // nothing and the orientation will be set accordingly.
+ // If the specified size is auto or square, then the orientation will be set
+ // to nothing.
+ // Otherwise, the size will and orientation is determined by the specified
+ // page size.
+ PageSizeAndOrientation GetDefaultPageSizeAndOrientation();
+
+ void AppendAllNonDocumentAuthorSheets(nsTArray<StyleSheet*>& aArray) const;
+
+ // Manage the set of style sheets in the style set
+ void AppendStyleSheet(StyleSheet&);
+ void InsertStyleSheetBefore(StyleSheet&, StyleSheet& aReferenceSheet);
+ void RemoveStyleSheet(StyleSheet&);
+ void AddDocStyleSheet(StyleSheet&);
+
+ /**
+ * Performs a Servo traversal to compute style for all dirty nodes in the
+ * document.
+ *
+ * This will traverse all of the document's style roots (that is, its document
+ * element, and the roots of the document-level native anonymous content).
+ *
+ * We specify |ForCSSRuleChanges| to try to update all CSS animations
+ * when we call this function due to CSS rule changes since @keyframes rules
+ * may have changed.
+ *
+ * Returns true if a post-traversal is required.
+ */
+ bool StyleDocument(ServoTraversalFlags aFlags);
+
+ /**
+ * Eagerly styles a subtree of unstyled nodes that was just appended to the
+ * tree. This is used in situations where we need the style immediately and
+ * cannot wait for a future batch restyle.
+ */
+ void StyleNewSubtree(dom::Element* aRoot);
+
+ /**
+ * Helper for correctly calling UpdateStylist without paying the cost of an
+ * extra function call in the common no-rebuild-needed case.
+ */
+ void UpdateStylistIfNeeded() {
+ if (StylistNeedsUpdate()) {
+ UpdateStylist();
+ }
+ }
+
+ /**
+ * Checks whether the rule tree has crossed its threshold for unused nodes,
+ * and if so, frees them.
+ */
+ void MaybeGCRuleTree();
+
+ /**
+ * Returns true if the given element may be used as the root of a style
+ * traversal. Reasons for false include having an unstyled parent, or having
+ * a parent that is display:none.
+ *
+ * Most traversal callsites don't need to check this, but some do.
+ */
+ static bool MayTraverseFrom(const dom::Element* aElement);
+
+#ifdef DEBUG
+ void AssertTreeIsClean();
+#else
+ void AssertTreeIsClean() {}
+#endif
+
+ /**
+ * Clears any cached style data that may depend on all sorts of computed
+ * values.
+ *
+ * Right now this clears the non-inheriting ComputedStyle cache, resets the
+ * default computed values, and clears cached anonymous content style.
+ *
+ * This does _not_, however, clear the stylist.
+ */
+ void ClearCachedStyleData();
+
+ /**
+ * Notifies the Servo stylesheet that the document's compatibility mode has
+ * changed.
+ */
+ void CompatibilityModeChanged();
+
+ template <typename T>
+ void EnumerateStyleSheets(T aCb) {
+ for (auto origin : kOrigins) {
+ for (size_t i = 0, count = SheetCount(origin); i < count; ++i) {
+ aCb(*SheetAt(origin, i));
+ }
+ }
+ }
+
+ /**
+ * Resolve style for the given element, and return it as a
+ * ComputedStyle.
+ *
+ * FIXME(emilio): Is there a point in this after bug 1367904?
+ */
+ static inline already_AddRefed<ComputedStyle> ResolveServoStyle(
+ const dom::Element&);
+
+ bool GetKeyframesForName(const dom::Element&, const ComputedStyle&,
+ nsAtom* aName,
+ const StyleComputedTimingFunction& aTimingFunction,
+ nsTArray<Keyframe>& aKeyframes);
+
+ nsTArray<ComputedKeyframeValues> GetComputedKeyframeValuesFor(
+ const nsTArray<Keyframe>& aKeyframes, dom::Element* aElement,
+ PseudoStyleType aPseudoType, const ComputedStyle* aStyle);
+
+ void GetAnimationValues(
+ StyleLockedDeclarationBlock* aDeclarations, dom::Element* aElement,
+ const mozilla::ComputedStyle* aStyle,
+ nsTArray<RefPtr<StyleAnimationValue>>& aAnimationValues);
+
+ void AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray);
+
+ const StyleLockedCounterStyleRule* CounterStyleRuleForName(nsAtom* aName);
+
+ // Get all the currently-active font feature values set.
+ already_AddRefed<gfxFontFeatureValueSet> BuildFontFeatureValueSet();
+
+ // Get the set of all currently-active font-palette-values.
+ already_AddRefed<gfx::FontPaletteValueSet> BuildFontPaletteValueSet();
+
+ already_AddRefed<ComputedStyle> GetBaseContextForElement(
+ dom::Element* aElement, const ComputedStyle* aStyle);
+
+ /**
+ * Resolve style for a given declaration block with/without the parent style.
+ * If the parent style is not specified, the document default computed values
+ * is used.
+ */
+ already_AddRefed<ComputedStyle> ResolveForDeclarations(
+ const ComputedStyle* aParentOrNull,
+ const StyleLockedDeclarationBlock* aDeclarations);
+
+ already_AddRefed<StyleAnimationValue> ComputeAnimationValue(
+ dom::Element* aElement, StyleLockedDeclarationBlock* aDeclaration,
+ const mozilla::ComputedStyle* aStyle);
+
+ void AppendTask(PostTraversalTask aTask) {
+ MOZ_ASSERT(IsInServoTraversal());
+
+ // We currently only use PostTraversalTasks while the Servo font metrics
+ // mutex is locked. If we need to use them in other situations during
+ // a traversal, we should assert that we've taken appropriate
+ // synchronization measures.
+ AssertIsMainThreadOrServoFontMetricsLocked();
+
+ mPostTraversalTasks.AppendElement(aTask);
+ }
+
+ // Returns true if a restyle of the document is needed due to cloning
+ // sheet inners.
+ bool EnsureUniqueInnerOnCSSSheets();
+
+ // Returns the style rule map.
+ ServoStyleRuleMap* StyleRuleMap();
+
+ /**
+ * Returns true if a modification to an an attribute with the specified
+ * local name might require us to restyle the element.
+ *
+ * This function allows us to skip taking a an attribute snapshot when
+ * the modified attribute doesn't appear in an attribute selector in
+ * a style sheet.
+ */
+ bool MightHaveAttributeDependency(const dom::Element&,
+ nsAtom* aAttribute) const;
+
+ /**
+ * Returns true if a modification to an attribute with the specified local
+ * name might require us to restyle the element's siblings.
+ */
+ bool MightHaveNthOfAttributeDependency(const dom::Element&,
+ nsAtom* aAttribute) const;
+
+ /**
+ * Returns true if a modification to a class might require us to restyle the
+ * element's siblings.
+ */
+ bool MightHaveNthOfClassDependency(const dom::Element&);
+
+ /**
+ * Returns true if a modification to an ID might require us to restyle the
+ * element's siblings.
+ */
+ bool MightHaveNthOfIDDependency(const dom::Element&, nsAtom* aOldID,
+ nsAtom* aNewID) const;
+
+ /**
+ * Maybe invalidate if a modification to an ID might require us to restyle
+ * the relative selector it refers to.
+ */
+ void MaybeInvalidateRelativeSelectorIDDependency(
+ const dom::Element&, nsAtom* aOldID, nsAtom* aNewID,
+ const ServoElementSnapshotTable& aSnapshots);
+
+ /**
+ * Maybe invalidate if a modification to an attribute with the specified local
+ * name might require us to restyle the relative selector it refers to.
+ */
+ void MaybeInvalidateRelativeSelectorClassDependency(
+ const dom::Element&, const ServoElementSnapshotTable& aSnapshots);
+
+ /**
+ * Maybe invalidate if a modification to an ID might require us to restyle
+ * the relative selector it refers to.
+ */
+ void MaybeInvalidateRelativeSelectorAttributeDependency(
+ const dom::Element&, nsAtom* aAttribute,
+ const ServoElementSnapshotTable& aSnapshots);
+
+ /**
+ * Maybe invalidate if a change in event state on an element might require us
+ * to restyle the relative selector it refers to.
+ */
+ void MaybeInvalidateRelativeSelectorStateDependency(
+ const dom::Element&, dom::ElementState,
+ const ServoElementSnapshotTable& aSnapshots);
+
+ /**
+ * Maybe invalidate if a change on an element that might be selected by :empty
+ * that might require us to restyle the relative selector it refers to.
+ */
+ void MaybeInvalidateRelativeSelectorForEmptyDependency(const dom::Element&);
+
+ /**
+ * Maybe invalidate if a state change on an element that might be selected
+ * by a selector that can only selector first/last child, that
+ * might require us to restyle the relative selector it refers to.
+ */
+ void MaybeInvalidateRelativeSelectorForNthEdgeDependency(const dom::Element&);
+
+ /**
+ * Maybe invalidate if a state change on an element that might be selected by
+ * :nth-* (Or :nth-like) selectors that might require us to restyle the
+ * relative selector it refers to.
+ */
+ void MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
+ const dom::Element*);
+
+ /**
+ * Maybe invalidate if a DOM element insertion might require us to restyle
+ * the relative selector to ancestors/previous siblings.
+ */
+ void MaybeInvalidateForElementInsertion(const dom::Element&);
+
+ /**
+ * Maybe invalidate if a series of nodes is appended, among which may
+ * be element(s) that might require us to restyle the relative selector
+ * to ancestors/previous siblings.
+ */
+ void MaybeInvalidateForElementAppend(const nsIContent&);
+
+ /**
+ * Maybe invalidate if a DOM element removal might require us to restyle
+ * the relative selector to ancestors/previous siblings.
+ */
+ void MaybeInvalidateForElementRemove(const dom::Element& aElement,
+ const nsIContent* aFollowingSibling);
+
+ /**
+ * Returns true if a change in event state on an element might require
+ * us to restyle the element.
+ *
+ * This function allows us to skip taking a state snapshot when
+ * the changed state isn't depended upon by any pseudo-class selectors
+ * in a style sheet.
+ */
+ bool HasStateDependency(const dom::Element&, dom::ElementState) const;
+
+ /**
+ * Returns true if a change in event state on an element might require
+ * us to restyle the element's siblings.
+ */
+ bool HasNthOfStateDependency(const dom::Element&, dom::ElementState) const;
+
+ /**
+ * Restyle this element's siblings in order to propagate any potential change
+ * in :nth-child(of) styling.
+ */
+ void RestyleSiblingsForNthOf(const dom::Element&, uint32_t) const;
+
+ /**
+ * Returns true if a change in document state might require us to restyle the
+ * document.
+ */
+ bool HasDocumentStateDependency(dom::DocumentState) const;
+
+ /**
+ * Get a new ComputedStyle that uses the same rules as the given ComputedStyle
+ * but has a different parent.
+ *
+ * aElement is non-null if this is a ComputedStyle for a frame whose mContent
+ * is an element and which has no pseudo on its ComputedStyle (so it's the
+ * actual style for the element being passed).
+ */
+ already_AddRefed<ComputedStyle> ReparentComputedStyle(
+ ComputedStyle* aComputedStyle, ComputedStyle* aNewParent,
+ ComputedStyle* aNewLayoutParent, dom::Element* aElement);
+
+ /**
+ * Invalidate styles where there's any viewport units dependent style.
+ */
+ enum class OnlyDynamic : bool { No, Yes };
+ void InvalidateForViewportUnits(OnlyDynamic);
+
+ private:
+ friend class AutoSetInServoTraversal;
+ friend class AutoPrepareTraversal;
+ friend class PostTraversalTask;
+
+ bool ShouldTraverseInParallel() const;
+
+ void RuleChangedInternal(StyleSheet&, css::Rule&, StyleRuleChangeKind);
+
+ /**
+ * Forces all the ShadowRoot styles to be dirty.
+ *
+ * Only to be used for:
+ *
+ * * Devtools (dealing with sheet cloning).
+ * * Compatibility-mode changes.
+ *
+ * Try to do something more incremental for other callers that are exposed to
+ * the web.
+ */
+ void ForceDirtyAllShadowStyles();
+
+ /**
+ * Gets the pending snapshots to handle from the restyle manager.
+ */
+ const SnapshotTable& Snapshots();
+
+ /**
+ * Clear our cached mNonInheritingComputedStyles.
+ *
+ * We do this when we want to make sure those ComputedStyles won't live too
+ * long (e.g. when rebuilding all style data or when shutting down the style
+ * set).
+ */
+ void ClearNonInheritingComputedStyles();
+
+ /**
+ * Perform processes that we should do before traversing.
+ *
+ * When aRoot is null, the entire document is pre-traversed. Otherwise,
+ * only the subtree rooted at aRoot is pre-traversed.
+ */
+ void PreTraverse(ServoTraversalFlags aFlags, dom::Element* aRoot = nullptr);
+
+ // Subset of the pre-traverse steps that involve syncing up data
+ void PreTraverseSync();
+
+ /**
+ * Records that the contents of style sheets at the specified origin have
+ * changed since the last. Calling this will ensure that the Stylist
+ * rebuilds its selector maps.
+ */
+ void MarkOriginsDirty(OriginFlags aChangedOrigins);
+
+ /**
+ * Note that the stylist needs a style flush due to style sheet changes.
+ */
+ void SetStylistStyleSheetsDirty();
+
+ void SetStylistShadowDOMStyleSheetsDirty();
+
+ bool StylistNeedsUpdate() const {
+ return mStylistState != StylistState::NotDirty;
+ }
+
+ /**
+ * Update the stylist as needed to ensure style data is up-to-date.
+ *
+ * This should only be called if StylistNeedsUpdate returns true.
+ */
+ void UpdateStylist();
+
+ void RunPostTraversalTasks();
+
+ void PrependSheetOfType(Origin, StyleSheet*);
+ void AppendSheetOfType(Origin, StyleSheet*);
+ void InsertSheetOfType(Origin, StyleSheet*, StyleSheet* aBeforeSheet);
+ void RemoveSheetOfType(Origin, StyleSheet*);
+
+ const nsPresContext* GetPresContext() const {
+ return const_cast<ServoStyleSet*>(this)->GetPresContext();
+ }
+
+ /**
+ * Return the associated pres context if we're the master style set and we
+ * have an associated pres shell.
+ */
+ nsPresContext* GetPresContext();
+
+ // The owner document of this style set. Never null, and always outlives the
+ // StyleSet.
+ dom::Document* mDocument;
+ UniquePtr<StylePerDocumentStyleData> mRawData;
+
+ // Map from raw Servo style rule to Gecko's wrapper object.
+ // Constructed lazily when requested by devtools.
+ UniquePtr<ServoStyleRuleMap> mStyleRuleMap;
+ uint64_t mUserFontSetUpdateGeneration = 0;
+
+ // Tasks to perform after a traversal, back on the main thread.
+ //
+ // These are similar to Servo's SequentialTasks, except that they are
+ // posted by C++ code running on style worker threads.
+ nsTArray<PostTraversalTask> mPostTraversalTasks;
+
+ // Stores pointers to our cached ComputedStyles for non-inheriting anonymous
+ // boxes.
+ EnumeratedArray<nsCSSAnonBoxes::NonInheriting,
+ nsCSSAnonBoxes::NonInheriting::_Count, RefPtr<ComputedStyle>>
+ mNonInheritingComputedStyles;
+
+ public:
+ void PutCachedAnonymousContentStyles(
+ AnonymousContentKey aKey, nsTArray<RefPtr<ComputedStyle>>&& aStyles) {
+ auto index = static_cast<size_t>(aKey);
+
+ MOZ_ASSERT(mCachedAnonymousContentStyles.Length() + aStyles.Length() < 256,
+ "(index, length) pairs must be bigger");
+ MOZ_ASSERT(mCachedAnonymousContentStyleIndexes[index].second == 0,
+ "shouldn't need to overwrite existing cached styles");
+ MOZ_ASSERT(!aStyles.IsEmpty(), "should have some styles to cache");
+
+ mCachedAnonymousContentStyleIndexes[index] = std::make_pair(
+ mCachedAnonymousContentStyles.Length(), aStyles.Length());
+ mCachedAnonymousContentStyles.AppendElements(std::move(aStyles));
+ }
+
+ void GetCachedAnonymousContentStyles(
+ AnonymousContentKey aKey, nsTArray<RefPtr<ComputedStyle>>& aStyles) {
+ auto index = static_cast<size_t>(aKey);
+ auto loc = mCachedAnonymousContentStyleIndexes[index];
+ aStyles.AppendElements(mCachedAnonymousContentStyles.Elements() + loc.first,
+ loc.second);
+ }
+
+ void RegisterProperty(const dom::PropertyDefinition&, ErrorResult&);
+
+ private:
+ // Map of AnonymousContentKey values to an (index, length) pair pointing into
+ // mCachedAnonymousContentStyles.
+ //
+ // We assert that the index and length values fit into uint8_ts.
+ Array<std::pair<uint8_t, uint8_t>, 1 << sizeof(AnonymousContentKey) * 8>
+ mCachedAnonymousContentStyleIndexes;
+
+ // Stores cached ComputedStyles for certain native anonymous content.
+ nsTArray<RefPtr<ComputedStyle>> mCachedAnonymousContentStyles;
+
+ StylistState mStylistState = StylistState::NotDirty;
+ bool mAuthorStyleDisabled = false;
+ bool mNeedsRestyleAfterEnsureUniqueInner = false;
+};
+
+class UACacheReporter final : public nsIMemoryReporter {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ private:
+ ~UACacheReporter() = default;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoStyleSet_h
diff --git a/layout/style/ServoStyleSetInlines.h b/layout/style/ServoStyleSetInlines.h
new file mode 100644
index 0000000000..9e8eab2652
--- /dev/null
+++ b/layout/style/ServoStyleSetInlines.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 mozilla_ServoStyleSetInlines_h
+#define mozilla_ServoStyleSetInlines_h
+
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla {
+
+nscoord ServoStyleSet::EvaluateSourceSizeList(
+ const StyleSourceSizeList* aSourceSizeList) const {
+ return Servo_SourceSizeList_Evaluate(mRawData.get(), aSourceSizeList);
+}
+
+already_AddRefed<ComputedStyle> ServoStyleSet::ResolveServoStyle(
+ const dom::Element& aElement) {
+ return Servo_ResolveStyle(&aElement).Consume();
+}
+
+} // namespace mozilla
+
+#endif // mozilla_ServoStyleSetInlines_h
diff --git a/layout/style/ServoTraversalStatistics.h b/layout/style/ServoTraversalStatistics.h
new file mode 100644
index 0000000000..abb95d19fe
--- /dev/null
+++ b/layout/style/ServoTraversalStatistics.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ServoTraversalStatistics_h
+#define mozilla_ServoTraversalStatistics_h
+
+#include <inttypes.h>
+
+namespace mozilla {
+
+// Traversal statistics for Servo traversal.
+//
+// See style::context::PerThreadTraversalStatistics for
+// meaning of these fields.
+struct ServoTraversalStatistics {
+ uint32_t mElementsTraversed = 0;
+ uint32_t mElementsStyled = 0;
+ uint32_t mElementsMatched = 0;
+ uint32_t mStylesShared = 0;
+ uint32_t mStylesReused = 0;
+
+ static bool sActive;
+ static ServoTraversalStatistics sSingleton;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoTraversalStatistics_h
diff --git a/layout/style/ServoTypes.h b/layout/style/ServoTypes.h
new file mode 100644
index 0000000000..b767de8aca
--- /dev/null
+++ b/layout/style/ServoTypes.h
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* types defined to pass values through Gecko_* and Servo_* FFI functions */
+
+#ifndef mozilla_ServoTypes_h
+#define mozilla_ServoTypes_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/TypedEnumBits.h"
+#include "nsCSSPropertyID.h"
+#include "nsCoord.h"
+#include "X11UndefineNone.h"
+
+namespace mozilla {
+struct StyleLockedFontFaceRule;
+enum class StyleOrigin : uint8_t;
+struct LangGroupFontPrefs;
+} // namespace mozilla
+
+// used for associating origin with specific @font-face rules
+struct nsFontFaceRuleContainer {
+ RefPtr<mozilla::StyleLockedFontFaceRule> mRule;
+ mozilla::StyleOrigin mOrigin;
+};
+
+namespace mozilla {
+
+// Indicates whether the Servo style system should expect the style on an
+// element to have already been resolved (i.e. via a parallel traversal), or
+// whether it may be lazily computed.
+enum class LazyComputeBehavior {
+ Allow,
+ Assert,
+};
+
+// Various flags for the servo traversal.
+enum class ServoTraversalFlags : uint32_t {
+ Empty = 0,
+ // Perform animation processing but not regular styling.
+ AnimationOnly = 1 << 0,
+ // Traverses as normal mode but tries to update all CSS animations.
+ ForCSSRuleChanges = 1 << 1,
+ // The final animation-only traversal, which shouldn't really care about other
+ // style changes anymore.
+ FinalAnimationTraversal = 1 << 2,
+ // Allows the traversal to run in parallel if there are sufficient cores on
+ // the machine.
+ ParallelTraversal = 1 << 7,
+ // Flush throttled animations. By default, we only update throttled animations
+ // when we have other non-throttled work to do. With this flag, we
+ // unconditionally tick and process them.
+ FlushThrottledAnimations = 1 << 8,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoTraversalFlags)
+
+// Indicates which rules should be included when performing selecting matching
+// on an element. DefaultOnly is used to exclude all rules except for those
+// that come from UA style sheets, and is used to implemented
+// getDefaultComputedStyle.
+enum class StyleRuleInclusion {
+ All,
+ DefaultOnly,
+};
+
+// Represents which tasks are performed in a SequentialTask of UpdateAnimations.
+enum class UpdateAnimationsTasks : uint8_t {
+ CSSAnimations = 1 << 0,
+ CSSTransitions = 1 << 1,
+ EffectProperties = 1 << 2,
+ CascadeResults = 1 << 3,
+ DisplayChangedFromNone = 1 << 4,
+ ScrollTimelines = 1 << 5,
+ ViewTimelines = 1 << 6,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(UpdateAnimationsTasks)
+
+// The kind of style we're generating when requesting Servo to give us an
+// inherited style.
+enum class InheritTarget {
+ // We're requesting a text style.
+ Text,
+ // We're requesting a first-letter continuation frame style.
+ FirstLetterContinuation,
+ // We're requesting a style for a placeholder frame.
+ PlaceholderFrame,
+};
+
+// Represents values for interaction media features.
+// https://drafts.csswg.org/mediaqueries-4/#mf-interaction
+enum class PointerCapabilities : uint8_t {
+ None = 0,
+ Coarse = 1 << 0,
+ Fine = 1 << 1,
+ Hover = 1 << 2,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PointerCapabilities)
+
+// These measurements are obtained for both the UA cache and the Stylist, but
+// not all the fields are used in both cases.
+class ServoStyleSetSizes {
+ public:
+ size_t mRuleTree; // Stylist-only
+ size_t mPrecomputedPseudos; // UA cache-only
+ size_t mElementAndPseudosMaps; // Used for both
+ size_t mInvalidationMap; // Used for both
+ size_t mRevalidationSelectors; // Used for both
+ size_t mOther; // Used for both
+
+ ServoStyleSetSizes()
+ : mRuleTree(0),
+ mPrecomputedPseudos(0),
+ mElementAndPseudosMaps(0),
+ mInvalidationMap(0),
+ mRevalidationSelectors(0),
+ mOther(0) {}
+};
+
+// A callback that can be sent via FFI which will be invoked _right before_
+// being mutated, and at most once.
+struct DeclarationBlockMutationClosure {
+ // The callback function. The first argument is `data`, the second is the
+ // property id that changed.
+ void (*function)(void*, nsCSSPropertyID) = nullptr;
+ void* data = nullptr;
+};
+
+struct MediumFeaturesChangedResult {
+ bool mAffectsDocumentRules;
+ bool mAffectsNonDocumentRules;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoTypes_h
diff --git a/layout/style/ServoUtils.h b/layout/style/ServoUtils.h
new file mode 100644
index 0000000000..5c8df84fec
--- /dev/null
+++ b/layout/style/ServoUtils.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* some utilities for Gecko to use for Servo initialization and assertions */
+
+#ifndef mozilla_ServoUtils_h
+#define mozilla_ServoUtils_h
+
+#include "mozilla/Assertions.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla {
+
+// Defined in GeckoBindings.cpp.
+void InitializeServo();
+void ShutdownServo();
+void AssertIsMainThreadOrServoFontMetricsLocked();
+
+class ServoStyleSet;
+extern ServoStyleSet* sInServoTraversal;
+inline bool IsInServoTraversal() {
+ // The callers of this function are generally main-thread-only _except_
+ // for potentially running during the Servo traversal, in which case they may
+ // take special paths that avoid writing to caches and the like. In order
+ // to allow those callers to branch efficiently without checking TLS, we
+ // maintain this static boolean. However, the danger is that those callers
+ // are generally unprepared to deal with non-Servo-but-also-non-main-thread
+ // callers, and are likely to take the main-thread codepath if this function
+ // returns false. So we assert against other non-main-thread callers here.
+ MOZ_ASSERT(sInServoTraversal || NS_IsMainThread());
+ return sInServoTraversal;
+}
+} // namespace mozilla
+
+#endif // mozilla_ServoUtils_h
diff --git a/layout/style/ShadowParts.cpp b/layout/style/ShadowParts.cpp
new file mode 100644
index 0000000000..adb4d932f6
--- /dev/null
+++ b/layout/style/ShadowParts.cpp
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ShadowParts.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+static bool IsSpace(char16_t aChar) {
+ return nsContentUtils::IsHTMLWhitespace(aChar);
+};
+
+using SingleMapping = std::pair<RefPtr<nsAtom>, RefPtr<nsAtom>>;
+
+// https://drafts.csswg.org/css-shadow-parts/#parsing-mapping
+//
+// Returns null on both tokens to signal an error.
+static SingleMapping ParseSingleMapping(const nsAString& aString) {
+ const char16_t* c = aString.BeginReading();
+ const char16_t* end = aString.EndReading();
+
+ const auto CollectASequenceOfSpaces = [&c, end]() {
+ while (c != end && IsSpace(*c)) {
+ ++c;
+ }
+ };
+
+ const auto CollectToken = [&c, end]() -> RefPtr<nsAtom> {
+ const char16_t* t = c;
+ while (c != end && !IsSpace(*c) && *c != ':') {
+ ++c;
+ }
+ if (c == t) {
+ return nullptr;
+ }
+ return NS_AtomizeMainThread(Substring(t, c));
+ };
+
+ // Steps 1 and 2 are variable declarations.
+ //
+ // 3. Collect a sequence of code points that are space characters.
+ CollectASequenceOfSpaces();
+
+ // 4. Collect a sequence of code points that are not space characters or
+ // U+003A COLON characters, and call the result first token.
+ RefPtr<nsAtom> firstToken = CollectToken();
+
+ // 5. If first token is empty then return error.
+ if (!firstToken) {
+ return {nullptr, nullptr};
+ }
+
+ // 6. Collect a sequence of code points that are space characters.
+ CollectASequenceOfSpaces();
+
+ // 7. If the end of the input has been reached, return the pair first
+ // token/first token.
+ if (c == end) {
+ return {firstToken, firstToken};
+ }
+
+ // 8. If character at position is not a U+003A COLON character, return error.
+ if (*c != ':') {
+ return {nullptr, nullptr};
+ }
+
+ // 9. Consume the U+003A COLON character.
+ ++c;
+
+ // 10. Collect a sequence of code points that are space characters.
+ CollectASequenceOfSpaces();
+
+ // 11. Collect a sequence of code points that are not space characters or
+ // U+003A COLON characters. and let second token be the result.
+ RefPtr<nsAtom> secondToken = CollectToken();
+
+ // 12. If second token is empty then return error.
+ if (!secondToken) {
+ return {nullptr, nullptr};
+ }
+
+ // 13. Collect a sequence of code points that are space characters.
+ CollectASequenceOfSpaces();
+
+ // 14. If position is not past the end of input then return error.
+ if (c != end) {
+ return {nullptr, nullptr};
+ }
+
+ // 15. Return the pair first token/second token.
+ return {std::move(firstToken), std::move(secondToken)};
+}
+
+// https://drafts.csswg.org/css-shadow-parts/#parsing-mapping-list
+ShadowParts ShadowParts::Parse(const nsAString& aString) {
+ ShadowParts parts;
+
+ for (const auto& substring : aString.Split(',')) {
+ auto mapping = ParseSingleMapping(substring);
+ if (!mapping.first) {
+ MOZ_ASSERT(!mapping.second);
+ continue;
+ }
+ nsAtom* second = mapping.second.get();
+ parts.mMappings.GetOrInsertNew(mapping.first)
+ ->AppendElement(std::move(mapping.second));
+ parts.mReverseMappings.InsertOrUpdate(second, std::move(mapping.first));
+ }
+
+ return parts;
+}
+
+#ifdef DEBUG
+void ShadowParts::Dump() const {
+ if (mMappings.IsEmpty()) {
+ printf(" (empty)\n");
+ return;
+ }
+ for (auto& entry : mMappings) {
+ nsAutoCString key;
+ entry.GetKey()->ToUTF8String(key);
+ printf(" %s: ", key.get());
+
+ bool first = true;
+ for (nsAtom* part : *entry.GetData()) {
+ if (!first) {
+ printf(", ");
+ }
+ first = false;
+ nsAutoCString value;
+ part->ToUTF8String(value);
+ printf("%s", value.get());
+ }
+ printf("\n");
+ }
+}
+#endif
+} // namespace mozilla
diff --git a/layout/style/ShadowParts.h b/layout/style/ShadowParts.h
new file mode 100644
index 0000000000..fe15305461
--- /dev/null
+++ b/layout/style/ShadowParts.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ShadowParts_h
+#define mozilla_ShadowParts_h
+
+#include "nsAtomHashKeys.h"
+#include "nsTHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+
+class ShadowParts final {
+ public:
+ ShadowParts(ShadowParts&&) = default;
+ ShadowParts(const ShadowParts&) = delete;
+ static ShadowParts Parse(const nsAString&);
+
+ using PartList = AutoTArray<RefPtr<nsAtom>, 1>;
+
+ PartList* Get(nsAtom* aName) const { return mMappings.Get(aName); }
+
+ nsAtom* GetReverse(nsAtom* aName) const {
+ return mReverseMappings.GetWeak(aName);
+ }
+
+#ifdef DEBUG
+ void Dump() const;
+#endif
+
+ private:
+ ShadowParts() = default;
+
+ // TODO(emilio): If the two hashtables take a lot of memory we should consider
+ // just using an nsTArray<Pair<>> or something.
+ nsClassHashtable<nsAtomHashKey, PartList> mMappings;
+ nsRefPtrHashtable<nsAtomHashKey, nsAtom> mReverseMappings;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ShadowParts_h
diff --git a/layout/style/SharedStyleSheetCache.cpp b/layout/style/SharedStyleSheetCache.cpp
new file mode 100644
index 0000000000..96c65efde7
--- /dev/null
+++ b/layout/style/SharedStyleSheetCache.cpp
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "SharedStyleSheetCache.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/css/SheetLoadData.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/ServoBindings.h"
+#include "nsContentUtils.h"
+#include "nsXULPrototypeCache.h"
+
+extern mozilla::LazyLogModule sCssLoaderLog;
+
+#define LOG(...) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(SharedStyleSheetCache, nsIMemoryReporter)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(SharedStyleSheetCacheMallocSizeOf)
+
+SharedStyleSheetCache::SharedStyleSheetCache() = default;
+
+void SharedStyleSheetCache::Init() { RegisterWeakMemoryReporter(this); }
+
+SharedStyleSheetCache::~SharedStyleSheetCache() {
+ UnregisterWeakMemoryReporter(this);
+}
+
+void SharedStyleSheetCache::LoadCompleted(SharedStyleSheetCache* aCache,
+ StyleSheetLoadData& aData,
+ nsresult aStatus) {
+ // If aStatus is a failure we need to mark this data failed. We also need to
+ // mark any ancestors of a failing data as failed and any sibling of a
+ // failing data as failed. Note that SheetComplete is never called on a
+ // SheetLoadData that is the mNext of some other SheetLoadData.
+ nsresult cancelledStatus = aStatus;
+ if (NS_FAILED(aStatus)) {
+ css::Loader::MarkLoadTreeFailed(aData);
+ } else {
+ cancelledStatus = NS_BINDING_ABORTED;
+ css::SheetLoadData* data = &aData;
+ do {
+ if (data->IsCancelled()) {
+ // We only need to mark loads for this loader as cancelled, so as to not
+ // fire error events in unrelated documents.
+ css::Loader::MarkLoadTreeFailed(*data, data->mLoader);
+ }
+ } while ((data = data->mNext));
+ }
+
+ // 8 is probably big enough for all our common cases. It's not likely that
+ // imports will nest more than 8 deep, and multiple sheets with the same URI
+ // are rare.
+ AutoTArray<RefPtr<css::SheetLoadData>, 8> datasToNotify;
+ LoadCompletedInternal(aCache, aData, datasToNotify);
+
+ // Now it's safe to go ahead and notify observers
+ for (RefPtr<css::SheetLoadData>& data : datasToNotify) {
+ auto status = data->IsCancelled() ? cancelledStatus : aStatus;
+ data->mLoader->NotifyObservers(*data, status);
+ }
+}
+
+void SharedStyleSheetCache::InsertIfNeeded(css::SheetLoadData& aData) {
+ MOZ_ASSERT(aData.mLoader->IsDocumentAssociated(),
+ "We only cache document-associated sheets");
+ LOG("SharedStyleSheetCache::InsertIfNeeded");
+ // If we ever start doing this for failed loads, we'll need to adjust the
+ // PostLoadEvent code that thinks anything already complete must have loaded
+ // succesfully.
+ if (aData.mLoadFailed) {
+ LOG(" Load failed, bailing");
+ return;
+ }
+
+ // If this sheet came from the cache already, there's no need to override
+ // anything.
+ if (aData.mSheetAlreadyComplete) {
+ LOG(" Sheet came from the cache, bailing");
+ return;
+ }
+
+ if (!aData.mURI) {
+ LOG(" Inline or constructable style sheet, bailing");
+ // Inline sheet caching happens in Loader::mInlineSheets, where we still
+ // have the input text available.
+ // Constructable sheets are not worth caching, they're always unique.
+ return;
+ }
+
+ LOG(" Putting style sheet in shared cache: %s",
+ aData.mURI->GetSpecOrDefault().get());
+ Insert(aData);
+}
+
+void SharedStyleSheetCache::LoadCompletedInternal(
+ SharedStyleSheetCache* aCache, css::SheetLoadData& aData,
+ nsTArray<RefPtr<css::SheetLoadData>>& aDatasToNotify) {
+ if (aCache) {
+ aCache->LoadCompleted(aData);
+ }
+
+ // Go through and deal with the whole linked list.
+ auto* data = &aData;
+ do {
+ MOZ_RELEASE_ASSERT(!data->mSheetCompleteCalled);
+ data->mSheetCompleteCalled = true;
+
+ if (!data->mSheetAlreadyComplete) {
+ // If mSheetAlreadyComplete, then the sheet could well be modified between
+ // when we posted the async call to SheetComplete and now, since the sheet
+ // was page-accessible during that whole time.
+
+ // HasForcedUniqueInner() is okay if the sheet is constructed, because
+ // constructed sheets are always unique and they may be set to complete
+ // multiple times if their rules are replaced via Replace()
+ MOZ_ASSERT(data->mSheet->IsConstructed() ||
+ !data->mSheet->HasForcedUniqueInner(),
+ "should not get a forced unique inner during parsing");
+ // Insert the sheet into the tree now the sheet has loaded, but only if
+ // the sheet is still relevant, and if this is a top-level sheet.
+ const bool needInsertIntoTree = [&] {
+ if (!data->mLoader->GetDocument()) {
+ // Not a document load, nothing to do.
+ return false;
+ }
+ if (data->IsPreload()) {
+ // Preloads are not supposed to be observable.
+ return false;
+ }
+ if (data->mSheet->IsConstructed()) {
+ // Constructable sheets are not in the regular stylesheet tree.
+ return false;
+ }
+ if (data->mIsChildSheet) {
+ // A child sheet, those will get exposed from the parent, no need to
+ // insert them into the tree.
+ return false;
+ }
+ if (data->mHadOwnerNode != !!data->mSheet->GetOwnerNode()) {
+ // The sheet was already removed from the tree and is no longer the
+ // current sheet of the owning node, we can bail.
+ return false;
+ }
+ return true;
+ }();
+
+ if (needInsertIntoTree) {
+ data->mLoader->InsertSheetInTree(*data->mSheet);
+ }
+ data->mSheet->SetComplete();
+ } else if (data->mSheet->IsApplicable()) {
+ if (dom::Document* doc = data->mLoader->GetDocument()) {
+ // We post these events for devtools, even though the applicable state
+ // has not actually changed, to make the cache not observable.
+ doc->PostStyleSheetApplicableStateChangeEvent(*data->mSheet);
+ }
+ }
+ aDatasToNotify.AppendElement(data);
+
+ NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
+ "Broken pending child count on our parent");
+
+ // If we have a parent, our parent is no longer being parsed, and
+ // we are the last pending child, then our load completion
+ // completes the parent too. Note that the parent _can_ still be
+ // being parsed (eg if the child (us) failed to open the channel
+ // or some such).
+ if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
+ !data->mParentData->mIsBeingParsed) {
+ LoadCompletedInternal(aCache, *data->mParentData, aDatasToNotify);
+ }
+
+ data = data->mNext;
+ } while (data);
+
+ if (aCache) {
+ aCache->InsertIfNeeded(aData);
+ }
+}
+
+NS_IMETHODIMP
+SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/document-shared",
+ KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(SharedStyleSheetCacheMallocSizeOf),
+ "Memory used for SharedStyleSheetCache to share style "
+ "sheets across documents (not to be confused with "
+ "GlobalStyleSheetCache)");
+ return NS_OK;
+}
+
+void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal,
+ const nsACString* aBaseDomain) {
+ using ContentParent = dom::ContentParent;
+
+ if (XRE_IsParentProcess()) {
+ auto forPrincipal = aForPrincipal ? Some(RefPtr(aForPrincipal)) : Nothing();
+ auto baseDomain = aBaseDomain ? Some(nsCString(*aBaseDomain)) : Nothing();
+
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ Unused << cp->SendClearStyleSheetCache(forPrincipal, baseDomain);
+ }
+ }
+
+ if (sInstance) {
+ sInstance->ClearInProcess(aForPrincipal, aBaseDomain);
+ }
+}
+
+} // namespace mozilla
+
+#undef LOG
diff --git a/layout/style/SharedStyleSheetCache.h b/layout/style/SharedStyleSheetCache.h
new file mode 100644
index 0000000000..c5043944f5
--- /dev/null
+++ b/layout/style/SharedStyleSheetCache.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_SharedStyleSheetCache_h__
+#define mozilla_SharedStyleSheetCache_h__
+
+// The shared style sheet cache is a cache that allows us to share sheets across
+// documents.
+//
+// It's generally a singleton, but it is different from GlobalStyleSheetCache in
+// the sense that:
+//
+// * It needs to be cycle-collectable, as it can keep alive style sheets from
+// various documents.
+//
+// * It is conceptually a singleton, but given its cycle-collectable nature, we
+// might re-create it.
+
+#include "mozilla/SharedSubResourceCache.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/Loader.h"
+
+namespace mozilla {
+
+class StyleSheet;
+class SheetLoadDataHashKey;
+
+namespace css {
+class SheetLoadData;
+class Loader;
+} // namespace css
+
+struct SharedStyleSheetCacheTraits {
+ using Loader = css::Loader;
+ using Key = SheetLoadDataHashKey;
+ using Value = StyleSheet;
+ using LoadingValue = css::SheetLoadData;
+
+ static SheetLoadDataHashKey KeyFromLoadingValue(const LoadingValue& aValue) {
+ return SheetLoadDataHashKey(aValue);
+ }
+};
+
+class SharedStyleSheetCache final
+ : public SharedSubResourceCache<SharedStyleSheetCacheTraits,
+ SharedStyleSheetCache>,
+ public nsIMemoryReporter {
+ public:
+ using Base = SharedSubResourceCache<SharedStyleSheetCacheTraits,
+ SharedStyleSheetCache>;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ SharedStyleSheetCache();
+ void Init();
+
+ // This has to be static because it's also called for loaders that don't have
+ // a sheet cache (loaders that are not owned by a document).
+ static void LoadCompleted(SharedStyleSheetCache*, css::SheetLoadData&,
+ nsresult);
+ using Base::LoadCompleted;
+ static void LoadCompletedInternal(SharedStyleSheetCache*, css::SheetLoadData&,
+ nsTArray<RefPtr<css::SheetLoadData>>&);
+ static void Clear(nsIPrincipal* aForPrincipal = nullptr,
+ const nsACString* aBaseDomain = nullptr);
+
+ protected:
+ void InsertIfNeeded(css::SheetLoadData&);
+
+ ~SharedStyleSheetCache();
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/SharedSubResourceCache.h b/layout/style/SharedSubResourceCache.h
new file mode 100644
index 0000000000..32919b8863
--- /dev/null
+++ b/layout/style/SharedSubResourceCache.h
@@ -0,0 +1,507 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_SharedSubResourceCache_h__
+#define mozilla_SharedSubResourceCache_h__
+
+// A cache that allows us to share subresources across documents. In order to
+// use it you need to provide some types, mainly:
+//
+// * Loader, which implements LoaderPrincipal() and allows you to key per
+// principal. The idea is that this would be the
+// {CSS,Script,Image}Loader object.
+//
+// * Key (self explanatory). We might want to introduce a common key to
+// share the cache partitioning logic.
+//
+// * Value, which represents the final cached value. This is expected to
+// be a StyleSheet / Stencil / imgRequestProxy.
+//
+// * LoadingValue, which must inherit from
+// SharedSubResourceCacheLoadingValueBase (which contains the linked
+// list and the state that the cache manages). It also must provide a
+// ValueForCache() and ExpirationTime() members. For style, this is the
+// SheetLoadData.
+
+#include "mozilla/PrincipalHashKey.h"
+#include "mozilla/WeakPtr.h"
+#include "nsTHashMap.h"
+#include "nsIMemoryReporter.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+enum class CachedSubResourceState {
+ Miss,
+ Loading,
+ Pending,
+ Complete,
+};
+
+template <typename Derived>
+struct SharedSubResourceCacheLoadingValueBase {
+ // Whether we're in the "loading" hash table.
+ RefPtr<Derived> mNext;
+
+ virtual bool IsLoading() const = 0;
+ virtual bool IsCancelled() const = 0;
+ virtual bool IsSyncLoad() const = 0;
+
+ virtual void StartLoading() = 0;
+ virtual void SetLoadCompleted() = 0;
+ virtual void Cancel() = 0;
+
+ ~SharedSubResourceCacheLoadingValueBase() {
+ // Do this iteratively to avoid blowing up the stack.
+ RefPtr<Derived> next = std::move(mNext);
+ while (next) {
+ next = std::move(next->mNext);
+ }
+ }
+};
+
+template <typename Traits, typename Derived>
+class SharedSubResourceCache {
+ private:
+ using Loader = typename Traits::Loader;
+ using Key = typename Traits::Key;
+ using Value = typename Traits::Value;
+ using LoadingValue = typename Traits::LoadingValue;
+ static Key KeyFromLoadingValue(const LoadingValue& aValue) {
+ return Traits::KeyFromLoadingValue(aValue);
+ }
+
+ const Derived& AsDerived() const {
+ return *static_cast<const Derived*>(this);
+ }
+ Derived& AsDerived() { return *static_cast<Derived*>(this); }
+
+ public:
+ SharedSubResourceCache(const SharedSubResourceCache&) = delete;
+ SharedSubResourceCache(SharedSubResourceCache&&) = delete;
+ SharedSubResourceCache() = default;
+
+ static already_AddRefed<Derived> Get() {
+ static_assert(
+ std::is_base_of_v<SharedSubResourceCacheLoadingValueBase<LoadingValue>,
+ LoadingValue>);
+
+ if (sInstance) {
+ return do_AddRef(sInstance);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!sInstance);
+ RefPtr<Derived> cache = new Derived();
+ cache->Init();
+ sInstance = cache.get();
+ return cache.forget();
+ }
+
+ public:
+ struct Result {
+ Value* mCompleteValue = nullptr;
+ LoadingValue* mLoadingOrPendingValue = nullptr;
+ CachedSubResourceState mState = CachedSubResourceState::Miss;
+ };
+
+ Result Lookup(Loader&, const Key&, bool aSyncLoad);
+
+ // Tries to coalesce with an already existing load. The sheet state must be
+ // the one that Lookup returned, if it returned a sheet.
+ //
+ // TODO(emilio): Maybe try to merge this with the lookup? Most consumers could
+ // have a data there already.
+ [[nodiscard]] bool CoalesceLoad(const Key&, LoadingValue& aNewLoad,
+ CachedSubResourceState aExistingLoadState);
+
+ size_t SizeOfIncludingThis(MallocSizeOf) const;
+
+ // Puts the load into the "loading" set.
+ void LoadStarted(const Key&, LoadingValue&);
+
+ // Removes the load from the "loading" set if there.
+ void LoadCompleted(LoadingValue&);
+
+ // Inserts a value into the cache.
+ void Insert(LoadingValue&);
+
+ // Puts a load into the "pending" set.
+ void DeferLoad(const Key&, LoadingValue&);
+
+ template <typename Callback>
+ void StartPendingLoadsForLoader(Loader&, const Callback& aShouldStartLoad);
+ void CancelLoadsForLoader(Loader&);
+
+ // Register a loader into the cache. This has the effect of keeping alive all
+ // subresources for the origin of the loader's document until UnregisterLoader
+ // is called.
+ void RegisterLoader(Loader&);
+
+ // Unregister a loader from the cache.
+ //
+ // If this is the loader for the last document of a given origin, then all the
+ // subresources for that document will be removed from the cache. This needs
+ // to be called when the document goes away, or when its principal changes.
+ void UnregisterLoader(Loader&);
+
+ void ClearInProcess(nsIPrincipal* aForPrincipal = nullptr,
+ const nsACString* aBaseDomain = nullptr);
+
+ protected:
+ void CancelPendingLoadsForLoader(Loader&);
+
+ ~SharedSubResourceCache() {
+ MOZ_DIAGNOSTIC_ASSERT(sInstance == this);
+ sInstance = nullptr;
+ }
+
+ struct CompleteSubResource {
+ RefPtr<Value> mResource;
+ uint32_t mExpirationTime = 0;
+ bool mWasSyncLoad = false;
+
+ inline bool Expired() const;
+ };
+
+ void WillStartPendingLoad(LoadingValue&);
+
+ nsTHashMap<Key, CompleteSubResource> mComplete;
+ nsRefPtrHashtable<Key, LoadingValue> mPending;
+ // The SheetLoadData pointers in mLoadingDatas below are weak references that
+ // get cleaned up when StreamLoader::OnStopRequest gets called.
+ //
+ // Note that we hold on to all sheet loads, even if in the end they happen not
+ // to be cacheable.
+ nsTHashMap<Key, WeakPtr<LoadingValue>> mLoading;
+
+ // An origin-to-number-of-registered-documents count, in order to manage cache
+ // eviction as described in RegisterLoader / UnregisterLoader.
+ nsTHashMap<PrincipalHashKey, uint32_t> mLoaderPrincipalRefCnt;
+
+ protected:
+ inline static Derived* sInstance;
+};
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::ClearInProcess(
+ nsIPrincipal* aForPrincipal, const nsACString* aBaseDomain) {
+ if (!aForPrincipal && !aBaseDomain) {
+ mComplete.Clear();
+ return;
+ }
+
+ for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
+ const bool shouldRemove = [&] {
+ if (aForPrincipal && iter.Key().Principal()->Equals(aForPrincipal)) {
+ return true;
+ }
+ if (!aBaseDomain) {
+ return false;
+ }
+ // Clear by baseDomain.
+ nsIPrincipal* partitionPrincipal = iter.Key().PartitionPrincipal();
+
+ // Clear entries with matching base domain. This includes entries
+ // which are partitioned under other top level sites (= have a
+ // partitionKey set).
+ nsAutoCString principalBaseDomain;
+ nsresult rv = partitionPrincipal->GetBaseDomain(principalBaseDomain);
+ if (NS_SUCCEEDED(rv) && principalBaseDomain.Equals(*aBaseDomain)) {
+ return true;
+ }
+
+ // Clear entries partitioned under aBaseDomain.
+ return StoragePrincipalHelper::PartitionKeyHasBaseDomain(
+ partitionPrincipal->OriginAttributesRef().mPartitionKey,
+ *aBaseDomain);
+ }();
+
+ if (shouldRemove) {
+ iter.Remove();
+ }
+ }
+}
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::RegisterLoader(Loader& aLoader) {
+ mLoaderPrincipalRefCnt.LookupOrInsert(aLoader.LoaderPrincipal(), 0) += 1;
+}
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::UnregisterLoader(
+ Loader& aLoader) {
+ nsIPrincipal* prin = aLoader.LoaderPrincipal();
+ auto lookup = mLoaderPrincipalRefCnt.Lookup(prin);
+ MOZ_RELEASE_ASSERT(lookup);
+ MOZ_RELEASE_ASSERT(lookup.Data());
+ if (!--lookup.Data()) {
+ lookup.Remove();
+ // TODO(emilio): Do this off a timer or something maybe.
+ for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Key().LoaderPrincipal()->Equals(prin)) {
+ iter.Remove();
+ }
+ }
+ }
+}
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::CancelPendingLoadsForLoader(
+ Loader& aLoader) {
+ AutoTArray<RefPtr<LoadingValue>, 10> arr;
+
+ for (auto iter = mPending.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<LoadingValue>& first = iter.Data();
+ LoadingValue* prev = nullptr;
+ LoadingValue* current = iter.Data();
+ do {
+ if (&current->Loader() != &aLoader) {
+ prev = current;
+ current = current->mNext;
+ continue;
+ }
+ // Detach the load from the list, mark it as cancelled, and then below
+ // call SheetComplete on it.
+ RefPtr<LoadingValue> strong =
+ prev ? std::move(prev->mNext) : std::move(first);
+ MOZ_ASSERT(strong == current);
+ if (prev) {
+ prev->mNext = std::move(strong->mNext);
+ current = prev->mNext;
+ } else {
+ first = std::move(strong->mNext);
+ current = first;
+ }
+ arr.AppendElement(std::move(strong));
+ } while (current);
+
+ if (!first) {
+ iter.Remove();
+ }
+ }
+
+ for (auto& loading : arr) {
+ loading->DidCancelLoad();
+ }
+}
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::WillStartPendingLoad(
+ LoadingValue& aData) {
+ LoadingValue* curr = &aData;
+ do {
+ curr->Loader().WillStartPendingLoad();
+ } while ((curr = curr->mNext));
+}
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::CancelLoadsForLoader(
+ Loader& aLoader) {
+ CancelPendingLoadsForLoader(aLoader);
+
+ // We can't stop in-progress loads because some other loader may care about
+ // them.
+ for (LoadingValue* data : mLoading.Values()) {
+ MOZ_DIAGNOSTIC_ASSERT(data,
+ "We weren't properly notified and the load was "
+ "incorrectly dropped on the floor");
+ for (; data; data = data->mNext) {
+ if (&data->Loader() == &aLoader) {
+ data->Cancel();
+ MOZ_ASSERT(data->IsCancelled());
+ }
+ }
+ }
+}
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::DeferLoad(const Key& aKey,
+ LoadingValue& aValue) {
+ MOZ_ASSERT(KeyFromLoadingValue(aValue).KeyEquals(aKey));
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.mNext, "Should only defer loads once");
+
+ mPending.InsertOrUpdate(aKey, RefPtr{&aValue});
+}
+
+template <typename Traits, typename Derived>
+template <typename Callback>
+void SharedSubResourceCache<Traits, Derived>::StartPendingLoadsForLoader(
+ Loader& aLoader, const Callback& aShouldStartLoad) {
+ AutoTArray<RefPtr<LoadingValue>, 10> arr;
+
+ for (auto iter = mPending.Iter(); !iter.Done(); iter.Next()) {
+ bool startIt = false;
+ {
+ LoadingValue* data = iter.Data();
+ do {
+ if (&data->Loader() == &aLoader) {
+ if (aShouldStartLoad(*data)) {
+ startIt = true;
+ break;
+ }
+ }
+ } while ((data = data->mNext));
+ }
+ if (startIt) {
+ arr.AppendElement(std::move(iter.Data()));
+ iter.Remove();
+ }
+ }
+ for (auto& data : arr) {
+ WillStartPendingLoad(*data);
+ data->StartPendingLoad();
+ }
+}
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::Insert(LoadingValue& aValue) {
+ auto key = KeyFromLoadingValue(aValue);
+#ifdef DEBUG
+ // We only expect a complete entry to be overriding when:
+ // * It's expired.
+ // * We're explicitly bypassing the cache.
+ // * Our entry is a sync load that was completed after aValue started loading
+ // async.
+ for (const auto& entry : mComplete) {
+ if (key.KeyEquals(entry.GetKey())) {
+ MOZ_ASSERT(entry.GetData().Expired() ||
+ aValue.Loader().ShouldBypassCache() ||
+ (entry.GetData().mWasSyncLoad && !aValue.IsSyncLoad()),
+ "Overriding existing complete entry?");
+ }
+ }
+#endif
+
+ // TODO(emilio): Use counters!
+ mComplete.InsertOrUpdate(
+ key, CompleteSubResource{aValue.ValueForCache(), aValue.ExpirationTime(),
+ aValue.IsSyncLoad()});
+}
+
+template <typename Traits, typename Derived>
+bool SharedSubResourceCache<Traits, Derived>::CoalesceLoad(
+ const Key& aKey, LoadingValue& aNewLoad,
+ CachedSubResourceState aExistingLoadState) {
+ MOZ_ASSERT(KeyFromLoadingValue(aNewLoad).KeyEquals(aKey));
+ // TODO(emilio): If aExistingLoadState is inconvenient, we could get rid of it
+ // by paying two hash lookups...
+ LoadingValue* existingLoad = nullptr;
+ if (aExistingLoadState == CachedSubResourceState::Loading) {
+ existingLoad = mLoading.Get(aKey);
+ MOZ_ASSERT(existingLoad, "Caller lied about the state");
+ } else if (aExistingLoadState == CachedSubResourceState::Pending) {
+ existingLoad = mPending.GetWeak(aKey);
+ MOZ_ASSERT(existingLoad, "Caller lied about the state");
+ }
+
+ if (!existingLoad) {
+ return false;
+ }
+
+ if (aExistingLoadState == CachedSubResourceState::Pending &&
+ !aNewLoad.ShouldDefer()) {
+ // Kick the load off; someone cares about it right away
+ RefPtr<LoadingValue> removedLoad;
+ mPending.Remove(aKey, getter_AddRefs(removedLoad));
+ MOZ_ASSERT(removedLoad == existingLoad, "Bad loading table");
+
+ WillStartPendingLoad(*removedLoad);
+
+ // We insert to the front instead of the back, to keep the invariant that
+ // the front sheet always is the one that triggers the load.
+ aNewLoad.mNext = std::move(removedLoad);
+ return false;
+ }
+
+ LoadingValue* data = existingLoad;
+ while (data->mNext) {
+ data = data->mNext;
+ }
+ data->mNext = &aNewLoad;
+ return true;
+}
+
+template <typename Traits, typename Derived>
+auto SharedSubResourceCache<Traits, Derived>::Lookup(Loader& aLoader,
+ const Key& aKey,
+ bool aSyncLoad) -> Result {
+ // Now complete sheets.
+ if (auto lookup = mComplete.Lookup(aKey)) {
+ const CompleteSubResource& completeSubResource = lookup.Data();
+ if ((!aLoader.ShouldBypassCache() && !completeSubResource.Expired()) ||
+ aLoader.HasLoaded(aKey)) {
+ return {completeSubResource.mResource.get(), nullptr,
+ CachedSubResourceState::Complete};
+ }
+ }
+
+ if (aSyncLoad) {
+ return {};
+ }
+
+ if (LoadingValue* data = mLoading.Get(aKey)) {
+ return {nullptr, data, CachedSubResourceState::Loading};
+ }
+
+ if (LoadingValue* data = mPending.GetWeak(aKey)) {
+ return {nullptr, data, CachedSubResourceState::Pending};
+ }
+
+ return {};
+}
+
+template <typename Traits, typename Derived>
+size_t SharedSubResourceCache<Traits, Derived>::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(&AsDerived());
+
+ n += mComplete.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& data : mComplete.Values()) {
+ n += data.mResource->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::LoadStarted(
+ const Key& aKey, LoadingValue& aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsLoading(), "Already loading? How?");
+ MOZ_DIAGNOSTIC_ASSERT(KeyFromLoadingValue(aValue).KeyEquals(aKey));
+ MOZ_DIAGNOSTIC_ASSERT(!mLoading.Contains(aKey), "Load not coalesced?");
+ aValue.StartLoading();
+ MOZ_ASSERT(aValue.IsLoading(), "Check that StartLoading is effectful.");
+ mLoading.InsertOrUpdate(aKey, &aValue);
+}
+
+template <typename Traits, typename Derived>
+bool SharedSubResourceCache<Traits, Derived>::CompleteSubResource::Expired()
+ const {
+ return mExpirationTime &&
+ mExpirationTime <= nsContentUtils::SecondsFromPRTime(PR_Now());
+}
+
+template <typename Traits, typename Derived>
+void SharedSubResourceCache<Traits, Derived>::LoadCompleted(
+ LoadingValue& aValue) {
+ if (!aValue.IsLoading()) {
+ return;
+ }
+ auto key = KeyFromLoadingValue(aValue);
+ Maybe<LoadingValue*> value = mLoading.Extract(key);
+ MOZ_DIAGNOSTIC_ASSERT(value);
+ MOZ_DIAGNOSTIC_ASSERT(value.value() == &aValue);
+ Unused << value;
+ aValue.SetLoadCompleted();
+ MOZ_ASSERT(!aValue.IsLoading(), "Check that SetLoadCompleted is effectful.");
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/SheetLoadData.h b/layout/style/SheetLoadData.h
new file mode 100644
index 0000000000..c817e4c1f3
--- /dev/null
+++ b/layout/style/SheetLoadData.h
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_css_SheetLoadData_h
+#define mozilla_css_SheetLoadData_h
+
+#include "mozilla/css/Loader.h"
+#include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/PreloaderBase.h"
+#include "mozilla/SharedSubResourceCache.h"
+#include "mozilla/NotNull.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace dom {
+enum class FetchPriority : uint8_t;
+} // namespace dom
+class AsyncEventDispatcher;
+class StyleSheet;
+} // namespace mozilla
+class nsICSSLoaderObserver;
+class nsINode;
+class nsIPrincipal;
+class nsIURI;
+class nsIReferrerInfo;
+
+namespace mozilla::css {
+
+/*********************************************
+ * Data needed to properly load a stylesheet *
+ *********************************************/
+
+static_assert(eAuthorSheetFeatures == 0 && eUserSheetFeatures == 1 &&
+ eAgentSheetFeatures == 2,
+ "sheet parsing mode constants won't fit "
+ "in SheetLoadData::mParsingMode");
+
+enum class SyncLoad : bool { No, Yes };
+
+class SheetLoadData final
+ : public PreloaderBase,
+ public SharedSubResourceCacheLoadingValueBase<SheetLoadData> {
+ using MediaMatched = dom::LinkStyle::MediaMatched;
+ using IsAlternate = dom::LinkStyle::IsAlternate;
+ using UseSystemPrincipal = css::Loader::UseSystemPrincipal;
+
+ protected:
+ virtual ~SheetLoadData();
+
+ public:
+ static void PrioritizeAsPreload(nsIChannel* aChannel);
+
+ // If this is a deferred load, start it now.
+ void StartPendingLoad();
+
+ // Data for loading a sheet linked from a document
+ SheetLoadData(css::Loader*, const nsAString& aTitle, nsIURI*, StyleSheet*,
+ SyncLoad, nsINode* aOwningNode, IsAlternate, MediaMatched,
+ StylePreloadKind, nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo*,
+ const nsAString& aNonce, dom::FetchPriority aFetchPriority);
+
+ // Data for loading a sheet linked from an @import rule
+ SheetLoadData(css::Loader*, nsIURI*, StyleSheet*, SheetLoadData* aParentData,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo*);
+
+ // Data for loading a non-document sheet
+ SheetLoadData(css::Loader*, nsIURI*, StyleSheet*, SyncLoad,
+ UseSystemPrincipal, StylePreloadKind,
+ const Encoding* aPreloadEncoding,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo*,
+ const nsAString& aNonce, dom::FetchPriority aFetchPriority);
+
+ nsIReferrerInfo* ReferrerInfo() const { return mReferrerInfo; }
+
+ const nsString& Nonce() const { return mNonce; }
+
+ already_AddRefed<AsyncEventDispatcher> PrepareLoadEventIfNeeded();
+
+ NotNull<const Encoding*> DetermineNonBOMEncoding(const nsACString& aSegment,
+ nsIChannel*) const;
+
+ // The caller may have the bytes for the stylesheet split across two strings,
+ // so aBytes1 and aBytes2 refer to those pieces.
+ nsresult VerifySheetReadyToParse(nsresult aStatus, const nsACString& aBytes1,
+ const nsACString& aBytes2,
+ nsIChannel* aChannel);
+
+ NS_DECL_ISUPPORTS
+
+ css::Loader& Loader() { return *mLoader; }
+
+ void DidCancelLoad() { mIsCancelled = true; }
+
+ // Hold a ref to the CSSLoader so we can call back to it to let it
+ // know the load finished
+ const RefPtr<css::Loader> mLoader;
+
+ // Title needed to pull datas out of the pending datas table when
+ // the preferred title is changed
+ const nsString mTitle;
+
+ // The encoding we decided to use for the sheet
+ const Encoding* mEncoding;
+
+ // URI we're loading. Null for inline or constructable sheets.
+ nsCOMPtr<nsIURI> mURI;
+
+ // The sheet we're loading data for
+ const RefPtr<StyleSheet> mSheet;
+
+ // Load data for the sheet that @import-ed us if we were @import-ed
+ // during the parse
+ const RefPtr<SheetLoadData> mParentData;
+
+ // The expiration time of the channel that has loaded this data, if
+ // applicable.
+ uint32_t mExpirationTime = 0;
+
+ // Number of sheets we @import-ed that are still loading
+ uint32_t mPendingChildren;
+
+ // mSyncLoad is true when the load needs to be synchronous.
+ // For LoadSheetSync, <link> to chrome stylesheets in UA Widgets,
+ // and children of sync loads.
+ const bool mSyncLoad : 1;
+
+ // mIsNonDocumentSheet is true if the load was triggered by LoadSheetSync or
+ // LoadSheet or an @import from such a sheet. Non-document sheet loads can
+ // proceed even if we have no document.
+ const bool mIsNonDocumentSheet : 1;
+
+ // Whether this stylesheet is for a child sheet load. This is necessary
+ // because the sheet could be detached mid-load by CSSOM.
+ const bool mIsChildSheet : 1;
+
+ // mIsBeingParsed is true if this stylesheet is currently being parsed.
+ bool mIsBeingParsed : 1;
+
+ // mIsLoading is set to true when a sheet load is initiated. This field is
+ // also used by the SharedSubResourceCache to avoid having multiple loads for
+ // the same resource.
+ bool mIsLoading : 1;
+
+ // mIsCancelled is set to true when a sheet load is stopped by
+ // Stop() or StopLoadingSheet() (which was removed in Bug 556446).
+ // SheetLoadData::OnStreamComplete() checks this to avoid parsing
+ // sheets that have been cancelled and such.
+ bool mIsCancelled : 1;
+
+ // mMustNotify is true if the load data is being loaded async and the original
+ // function call that started the load has returned.
+ //
+ // This applies only to observer notifications; load/error events are fired
+ // for any SheetLoadData that has a non-null owner node (though mMustNotify is
+ // used to avoid an event loop round-trip in that case).
+ bool mMustNotify : 1;
+
+ // Whether we had an owner node at the point of creation. This allows
+ // differentiating between "Link" header stylesheets and LinkStyle-owned
+ // stylesheets.
+ const bool mHadOwnerNode : 1;
+
+ // mWasAlternate is true if the sheet was an alternate
+ // (https://html.spec.whatwg.org/#rel-alternate) when the load data was
+ // created.
+ const bool mWasAlternate : 1;
+
+ // mMediaMatched is true if the sheet matched its medialist when the load data
+ // was created.
+ const bool mMediaMatched : 1;
+
+ // mUseSystemPrincipal is true if the system principal should be used for
+ // this sheet, no matter what the channel principal is. Only true for sync
+ // loads.
+ const bool mUseSystemPrincipal : 1;
+
+ // If true, this SheetLoadData is being used as a way to handle
+ // async observer notification for an already-complete sheet.
+ bool mSheetAlreadyComplete : 1;
+
+ // If true, the sheet is being loaded cross-origin without CORS permissions.
+ // This is completely normal and CORS isn't needed for such loads. This
+ // flag is simply useful in determining whether to set mBlockResourceTiming
+ // for a child sheet.
+ bool mIsCrossOriginNoCORS : 1;
+
+ // If this flag is true, LoadSheet will call SetReportResourceTiming(false)
+ // on the timedChannel. This is to mark resources that are loaded by a
+ // cross-origin stylesheet with a no-cors policy.
+ // https://www.w3.org/TR/resource-timing/#processing-model
+ bool mBlockResourceTiming : 1;
+
+ // Boolean flag indicating whether the load has failed. This will be set
+ // to true if this load, or the load of any descendant import, fails.
+ bool mLoadFailed : 1;
+
+ // Whether this is a preload, and which kind of preload it is.
+ //
+ // TODO(emilio): This can become a bitfield once we build with a GCC version
+ // that has the fix for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414,
+ // which causes a false positive warning here.
+ const StylePreloadKind mPreloadKind;
+
+ nsINode* GetRequestingNode() const;
+
+ // The observer that wishes to be notified of load completion
+ nsCOMPtr<nsICSSLoaderObserver> mObserver;
+
+ // The principal that identifies who started loading us.
+ const nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+
+ // Referrer info of the load.
+ const nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+
+ // The cryptographic nonce of the load used for CSP checks.
+ const nsString mNonce;
+
+ const dom::FetchPriority mFetchPriority;
+
+ // The encoding guessed from attributes and the document character set.
+ const NotNull<const Encoding*> mGuessedEncoding;
+
+ // The quirks mode of the loader at the time the load was triggered.
+ const nsCompatibility mCompatMode;
+
+ // Whether SheetComplete was called.
+ bool mSheetCompleteCalled = false;
+
+ // Whether we intentionally are not calling SheetComplete because nobody is
+ // listening for the load.
+ bool mIntentionallyDropped = false;
+
+ bool ShouldDefer() const { return mWasAlternate || !mMediaMatched; }
+
+ RefPtr<StyleSheet> ValueForCache() const;
+ uint32_t ExpirationTime() const { return mExpirationTime; }
+
+ // If there are no child sheets outstanding, mark us as complete.
+ // Otherwise, the children are holding strong refs to the data
+ // and will call SheetComplete() on it when they complete.
+ void SheetFinishedParsingAsync() {
+ MOZ_ASSERT(mIsBeingParsed);
+ mIsBeingParsed = false;
+ if (!mPendingChildren) {
+ mLoader->SheetComplete(*this, NS_OK);
+ }
+ }
+
+ bool IsPreload() const { return mPreloadKind != StylePreloadKind::None; }
+ bool IsLinkRelPreload() const { return css::IsLinkRelPreload(mPreloadKind); }
+
+ bool BlocksLoadEvent() const {
+ const auto& root = RootLoadData();
+ return !root.IsLinkRelPreload() && !root.IsSyncLoad();
+ }
+
+ bool IsSyncLoad() const override { return mSyncLoad; }
+ bool IsLoading() const override { return mIsLoading; }
+ bool IsCancelled() const override { return mIsCancelled; }
+
+ void StartLoading() override { mIsLoading = true; }
+ void SetLoadCompleted() override { mIsLoading = false; }
+ void Cancel() override { mIsCancelled = true; }
+
+ private:
+ const SheetLoadData& RootLoadData() const {
+ const auto* top = this;
+ while (top->mParentData) {
+ top = top->mParentData;
+ }
+ return *top;
+ }
+};
+
+using SheetLoadDataHolder = nsMainThreadPtrHolder<SheetLoadData>;
+
+} // namespace mozilla::css
+
+#endif // mozilla_css_SheetLoadData_h
diff --git a/layout/style/SheetParsingMode.h b/layout/style/SheetParsingMode.h
new file mode 100644
index 0000000000..24656778a7
--- /dev/null
+++ b/layout/style/SheetParsingMode.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_css_SheetParsingMode_h
+#define mozilla_css_SheetParsingMode_h
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace css {
+
+/**
+ * Enum defining the mode in which a sheet is to be parsed. This is
+ * usually, but not always, the same as the cascade level at which the
+ * sheet will apply (see nsStyleSet.h). Most of the Loader APIs only
+ * support loading of author sheets.
+ *
+ * Author sheets are the normal case: styles embedded in or linked
+ * from HTML pages. They are also the most restricted.
+ *
+ * User sheets can do anything author sheets can do, and also get
+ * access to a few CSS extensions that are not yet suitable for
+ * exposure on the public Web, but are very useful for expressing
+ * user style overrides, such as @-moz-document rules.
+ *
+ * XXX: eUserSheetFeatures was added in bug 1035091, but some patches in
+ * that bug never landed to use this enum value. Currently, all the features
+ * in user sheet are also available in author sheet.
+ *
+ * Agent sheets have access to all author- and user-sheet features
+ * plus more extensions that are necessary for internal use but,
+ * again, not yet suitable for exposure on the public Web. Some of
+ * these are outright unsafe to expose; in particular, incorrect
+ * styling of anonymous box pseudo-elements can violate layout
+ * invariants.
+ */
+enum SheetParsingMode : uint8_t {
+ eAuthorSheetFeatures = 0,
+ eUserSheetFeatures,
+ eAgentSheetFeatures,
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif // mozilla_css_SheetParsingMode_h
diff --git a/layout/style/StreamLoader.cpp b/layout/style/StreamLoader.cpp
new file mode 100644
index 0000000000..3e8bd37d76
--- /dev/null
+++ b/layout/style/StreamLoader.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/css/StreamLoader.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/TaskQueue.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIInputStream.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIStreamTransportService.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::css {
+
+StreamLoader::StreamLoader(SheetLoadData& aSheetLoadData)
+ : mSheetLoadData(&aSheetLoadData), mStatus(NS_OK) {}
+
+StreamLoader::~StreamLoader() {
+#ifdef NIGHTLY_BUILD
+ MOZ_RELEASE_ASSERT(mOnStopRequestCalled || mChannelOpenFailed);
+#endif
+}
+
+NS_IMPL_ISUPPORTS(StreamLoader, nsIStreamListener,
+ nsIThreadRetargetableStreamListener)
+
+/* nsIRequestObserver implementation */
+NS_IMETHODIMP
+StreamLoader::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(aRequest);
+ mSheetLoadData->NotifyStart(aRequest);
+
+ // It's kinda bad to let Web content send a number that results
+ // in a potentially large allocation directly, but efficiency of
+ // compression bombs is so great that it doesn't make much sense
+ // to require a site to send one before going ahead and allocating.
+ if (nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest)) {
+ int64_t length;
+ nsresult rv = channel->GetContentLength(&length);
+ if (NS_SUCCEEDED(rv) && length > 0) {
+ CheckedInt<nsACString::size_type> checkedLength(length);
+ if (!checkedLength.isValid()) {
+ return (mStatus = NS_ERROR_OUT_OF_MEMORY);
+ }
+ if (!mBytes.SetCapacity(checkedLength.value(), fallible)) {
+ return (mStatus = NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ }
+ if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) {
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ RefPtr queue =
+ TaskQueue::Create(sts.forget(), "css::StreamLoader Delivery Queue");
+ rr->RetargetDeliveryTo(queue);
+ }
+
+ mSheetLoadData->mExpirationTime = [&] {
+ auto info = nsContentUtils::GetSubresourceCacheValidationInfo(
+ aRequest, mSheetLoadData->mURI);
+
+ // For now, we never cache entries that we have to revalidate, or whose
+ // channel don't support caching.
+ if (info.mMustRevalidate || !info.mExpirationTime) {
+ return nsContentUtils::SecondsFromPRTime(PR_Now()) - 1;
+ }
+ return *info.mExpirationTime;
+ }();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StreamLoader::CheckListenerChain() { return NS_OK; }
+
+NS_IMETHODIMP
+StreamLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+#ifdef NIGHTLY_BUILD
+ MOZ_RELEASE_ASSERT(!mOnStopRequestCalled);
+ mOnStopRequestCalled = true;
+#endif
+
+ nsresult rv = mStatus;
+ // Decoded data
+ nsCString utf8String;
+ {
+ // Hold the nsStringBuffer for the bytes from the stack to ensure release
+ // no matter which return branch is taken.
+ nsCString bytes = std::move(mBytes);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+ if (NS_FAILED(mStatus)) {
+ mSheetLoadData->VerifySheetReadyToParse(mStatus, ""_ns, ""_ns, channel);
+ return mStatus;
+ }
+
+ rv = mSheetLoadData->VerifySheetReadyToParse(aStatus, mBOMBytes, bytes,
+ channel);
+ if (rv != NS_OK_PARSE_SHEET) {
+ return rv;
+ }
+
+ // BOM detection generally happens during the write callback, but that won't
+ // have happened if fewer than three bytes were received.
+ if (mEncodingFromBOM.isNothing()) {
+ HandleBOM();
+ MOZ_ASSERT(mEncodingFromBOM.isSome());
+ }
+
+ // The BOM handling has happened, but we still may not have an encoding if
+ // there was no BOM. Ensure we have one.
+ const Encoding* encoding = mEncodingFromBOM.value();
+ if (!encoding) {
+ // No BOM
+ encoding = mSheetLoadData->DetermineNonBOMEncoding(bytes, channel);
+ }
+ mSheetLoadData->mEncoding = encoding;
+
+ size_t validated = 0;
+ if (encoding == UTF_8_ENCODING) {
+ validated = Encoding::UTF8ValidUpTo(bytes);
+ }
+
+ if (validated == bytes.Length()) {
+ // Either this is UTF-8 and all valid, or it's not UTF-8 but is an empty
+ // string. This assumes that an empty string in any encoding decodes to
+ // empty string, which seems like a plausible assumption.
+ utf8String = std::move(bytes);
+ } else {
+ rv = encoding->DecodeWithoutBOMHandling(bytes, utf8String, validated);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } // run destructor for `bytes`
+
+ // For reasons I don't understand, factoring the below lines into
+ // a method on SheetLoadData resulted in a linker error. Hence,
+ // accessing fields of mSheetLoadData from here.
+ mSheetLoadData->mLoader->ParseSheet(utf8String, *mSheetLoadData,
+ Loader::AllowAsyncParse::Yes);
+
+ return NS_OK;
+}
+
+/* nsIStreamListener implementation */
+NS_IMETHODIMP
+StreamLoader::OnDataAvailable(nsIRequest*, nsIInputStream* aInputStream,
+ uint64_t, uint32_t aCount) {
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+ uint32_t dummy;
+ return aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy);
+}
+
+NS_IMETHODIMP
+StreamLoader::OnDataFinished(nsresult aStatus) { return NS_OK; }
+
+void StreamLoader::HandleBOM() {
+ MOZ_ASSERT(mEncodingFromBOM.isNothing());
+ MOZ_ASSERT(mBytes.IsEmpty());
+
+ auto [encoding, bomLength] = Encoding::ForBOM(mBOMBytes);
+ mEncodingFromBOM.emplace(encoding); // Null means no BOM.
+
+ // BOMs are three bytes at most, but may be fewer. Copy over anything
+ // that wasn't part of the BOM to mBytes. Note that we need to track
+ // any BOM bytes as well for SRI handling.
+ mBytes.Append(Substring(mBOMBytes, bomLength));
+ mBOMBytes.Truncate(bomLength);
+}
+
+nsresult StreamLoader::WriteSegmentFun(nsIInputStream*, void* aClosure,
+ const char* aSegment, uint32_t,
+ uint32_t aCount, uint32_t* aWriteCount) {
+ *aWriteCount = 0;
+ StreamLoader* self = static_cast<StreamLoader*>(aClosure);
+ if (NS_FAILED(self->mStatus)) {
+ return self->mStatus;
+ }
+
+ // If we haven't done BOM detection yet, divert bytes into the special buffer.
+ if (self->mEncodingFromBOM.isNothing()) {
+ size_t bytesToCopy = std::min<size_t>(3 - self->mBOMBytes.Length(), aCount);
+ self->mBOMBytes.Append(aSegment, bytesToCopy);
+ aSegment += bytesToCopy;
+ *aWriteCount += bytesToCopy;
+ aCount -= bytesToCopy;
+
+ if (self->mBOMBytes.Length() == 3) {
+ self->HandleBOM();
+ } else {
+ return NS_OK;
+ }
+ }
+
+ if (!self->mBytes.Append(aSegment, aCount, fallible)) {
+ self->mBytes.Truncate();
+ return (self->mStatus = NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ *aWriteCount += aCount;
+ return NS_OK;
+}
+
+} // namespace mozilla::css
diff --git a/layout/style/StreamLoader.h b/layout/style/StreamLoader.h
new file mode 100644
index 0000000000..a34117625a
--- /dev/null
+++ b/layout/style/StreamLoader.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_css_StreamLoader_h
+#define mozilla_css_StreamLoader_h
+
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsString.h"
+#include "mozilla/css/SheetLoadData.h"
+#include "mozilla/Assertions.h"
+
+class nsIInputStream;
+
+namespace mozilla::css {
+
+class StreamLoader : public nsIThreadRetargetableStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ explicit StreamLoader(SheetLoadData&);
+
+ void ChannelOpenFailed(nsresult rv) {
+#ifdef NIGHTLY_BUILD
+ mChannelOpenFailed = true;
+#endif
+ }
+
+ private:
+ virtual ~StreamLoader();
+
+ /**
+ * callback method used for ReadSegments
+ */
+ static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, uint32_t,
+ uint32_t, uint32_t*);
+
+ void HandleBOM();
+
+ RefPtr<SheetLoadData> mSheetLoadData;
+ nsresult mStatus;
+ Maybe<const Encoding*> mEncodingFromBOM;
+
+ // We store the initial three bytes of the stream into mBOMBytes, and then
+ // use that buffer to detect a BOM. We then shift any non-BOM bytes into
+ // mBytes, and store all subsequent data in that buffer.
+ nsCString mBytes;
+ nsAutoCStringN<3> mBOMBytes;
+
+#ifdef NIGHTLY_BUILD
+ bool mChannelOpenFailed = false;
+ bool mOnStopRequestCalled = false;
+#endif
+};
+
+} // namespace mozilla::css
+
+#endif // mozilla_css_StreamLoader_h
diff --git a/layout/style/StyleAnimationValue.cpp b/layout/style/StyleAnimationValue.cpp
new file mode 100644
index 0000000000..b87fb5f21b
--- /dev/null
+++ b/layout/style/StyleAnimationValue.cpp
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Utilities for animation of computed style values */
+
+#include "mozilla/StyleAnimationValue.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ServoStyleSet.h"
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMArray.h"
+#include "nsString.h"
+#include "mozilla/ComputedStyle.h"
+#include "nsComputedDOMStyle.h"
+#include "nsCSSPseudoElements.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Likely.h"
+#include "mozilla/ServoBindings.h" // StyleLockedDeclarationBlock
+#include "mozilla/ServoCSSParser.h"
+#include "gfxMatrix.h"
+#include "gfxQuaternion.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFrame.h"
+#include "gfx2DGlue.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/layers/LayersMessages.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+bool AnimationValue::operator==(const AnimationValue& aOther) const {
+ if (mServo && aOther.mServo) {
+ return Servo_AnimationValue_DeepEqual(mServo, aOther.mServo);
+ }
+ if (!mServo && !aOther.mServo) {
+ return true;
+ }
+ return false;
+}
+
+bool AnimationValue::operator!=(const AnimationValue& aOther) const {
+ return !operator==(aOther);
+}
+
+float AnimationValue::GetOpacity() const {
+ MOZ_ASSERT(mServo);
+ return Servo_AnimationValue_GetOpacity(mServo);
+}
+
+nscolor AnimationValue::GetColor(nscolor aForegroundColor) const {
+ MOZ_ASSERT(mServo);
+ return Servo_AnimationValue_GetColor(mServo, aForegroundColor);
+}
+
+bool AnimationValue::IsCurrentColor() const {
+ MOZ_ASSERT(mServo);
+ return Servo_AnimationValue_IsCurrentColor(mServo);
+}
+
+const StyleTranslate& AnimationValue::GetTranslateProperty() const {
+ MOZ_ASSERT(mServo);
+ return *Servo_AnimationValue_GetTranslate(mServo);
+}
+
+const StyleRotate& AnimationValue::GetRotateProperty() const {
+ MOZ_ASSERT(mServo);
+ return *Servo_AnimationValue_GetRotate(mServo);
+}
+
+const StyleScale& AnimationValue::GetScaleProperty() const {
+ MOZ_ASSERT(mServo);
+ return *Servo_AnimationValue_GetScale(mServo);
+}
+
+const StyleTransform& AnimationValue::GetTransformProperty() const {
+ MOZ_ASSERT(mServo);
+ return *Servo_AnimationValue_GetTransform(mServo);
+}
+
+void AnimationValue::GetOffsetPathProperty(StyleOffsetPath& aOffsetPath) const {
+ MOZ_ASSERT(mServo);
+ Servo_AnimationValue_GetOffsetPath(mServo, &aOffsetPath);
+}
+
+const mozilla::LengthPercentage& AnimationValue::GetOffsetDistanceProperty()
+ const {
+ MOZ_ASSERT(mServo);
+ return *Servo_AnimationValue_GetOffsetDistance(mServo);
+}
+
+const mozilla::StyleOffsetRotate& AnimationValue::GetOffsetRotateProperty()
+ const {
+ MOZ_ASSERT(mServo);
+ return *Servo_AnimationValue_GetOffsetRotate(mServo);
+}
+
+const mozilla::StylePositionOrAuto& AnimationValue::GetOffsetAnchorProperty()
+ const {
+ MOZ_ASSERT(mServo);
+ return *Servo_AnimationValue_GetOffsetAnchor(mServo);
+}
+
+const mozilla::StyleOffsetPosition& AnimationValue::GetOffsetPositionProperty()
+ const {
+ MOZ_ASSERT(mServo);
+ return *Servo_AnimationValue_GetOffsetPosition(mServo);
+}
+
+bool AnimationValue::IsOffsetPathUrl() const {
+ return mServo && Servo_AnimationValue_IsOffsetPathUrl(mServo);
+}
+
+MatrixScales AnimationValue::GetScaleValue(const nsIFrame* aFrame) const {
+ using namespace nsStyleTransformMatrix;
+
+ AnimatedPropertyID property(eCSSProperty_UNKNOWN);
+ Servo_AnimationValue_GetPropertyId(mServo, &property);
+ switch (property.mID) {
+ case eCSSProperty_scale: {
+ const StyleScale& scale = GetScaleProperty();
+ return scale.IsNone()
+ ? MatrixScales()
+ : MatrixScales(scale.AsScale()._0, scale.AsScale()._1);
+ }
+ case eCSSProperty_rotate:
+ case eCSSProperty_translate:
+ return MatrixScales();
+ case eCSSProperty_transform:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Should only need to check in transform properties");
+ return MatrixScales();
+ }
+
+ TransformReferenceBox refBox(aFrame);
+ Matrix4x4 t =
+ ReadTransforms(StyleTranslate::None(), StyleRotate::None(),
+ StyleScale::None(), nullptr, GetTransformProperty(),
+ refBox, aFrame->PresContext()->AppUnitsPerDevPixel());
+ Matrix transform2d;
+ bool canDraw2D = t.CanDraw2D(&transform2d);
+ if (!canDraw2D) {
+ return MatrixScales(0, 0);
+ }
+ return transform2d.ScaleFactors();
+}
+
+void AnimationValue::SerializeSpecifiedValue(
+ const AnimatedPropertyID& aProperty,
+ const StylePerDocumentStyleData* aRawData, nsACString& aString) const {
+ MOZ_ASSERT(mServo);
+ Servo_AnimationValue_Serialize(mServo, &aProperty, aRawData, &aString);
+}
+
+bool AnimationValue::IsInterpolableWith(const AnimatedPropertyID& aProperty,
+ const AnimationValue& aToValue) const {
+ if (IsNull() || aToValue.IsNull()) {
+ return false;
+ }
+
+ MOZ_ASSERT(mServo);
+ MOZ_ASSERT(aToValue.mServo);
+ return Servo_AnimationValues_IsInterpolable(mServo, aToValue.mServo);
+}
+
+double AnimationValue::ComputeDistance(const AnimationValue& aOther) const {
+ if (IsNull() || aOther.IsNull()) {
+ return 0.0;
+ }
+
+ MOZ_ASSERT(mServo);
+ MOZ_ASSERT(aOther.mServo);
+
+ double distance =
+ Servo_AnimationValues_ComputeDistance(mServo, aOther.mServo);
+ return distance < 0.0 ? 0.0 : distance;
+}
+
+/* static */
+AnimationValue AnimationValue::FromString(AnimatedPropertyID& aProperty,
+ const nsACString& aValue,
+ Element* aElement) {
+ MOZ_ASSERT(aElement);
+
+ AnimationValue result;
+
+ nsCOMPtr<Document> doc = aElement->GetComposedDoc();
+ if (!doc) {
+ return result;
+ }
+
+ RefPtr<PresShell> presShell = doc->GetPresShell();
+ if (!presShell) {
+ return result;
+ }
+
+ // GetComputedStyle() flushes style, so we shouldn't assume that any
+ // non-owning references we have are still valid.
+ RefPtr<const ComputedStyle> computedStyle =
+ nsComputedDOMStyle::GetComputedStyle(aElement);
+ MOZ_ASSERT(computedStyle);
+
+ RefPtr<StyleLockedDeclarationBlock> declarations =
+ ServoCSSParser::ParseProperty(aProperty, aValue,
+ ServoCSSParser::GetParsingEnvironment(doc),
+ StyleParsingMode::DEFAULT);
+
+ if (!declarations) {
+ return result;
+ }
+
+ result.mServo = presShell->StyleSet()->ComputeAnimationValue(
+ aElement, declarations, computedStyle);
+ return result;
+}
+
+/* static */
+already_AddRefed<StyleAnimationValue> AnimationValue::FromAnimatable(
+ nsCSSPropertyID aProperty, const layers::Animatable& aAnimatable) {
+ switch (aAnimatable.type()) {
+ case layers::Animatable::Tnull_t:
+ break;
+ case layers::Animatable::TStyleTransform: {
+ const StyleTransform& transform = aAnimatable.get_StyleTransform();
+ MOZ_ASSERT(!transform.HasPercent(),
+ "Received transform operations should have been resolved.");
+ return Servo_AnimationValue_Transform(&transform).Consume();
+ }
+ case layers::Animatable::Tfloat:
+ return Servo_AnimationValue_Opacity(aAnimatable.get_float()).Consume();
+ case layers::Animatable::Tnscolor:
+ return Servo_AnimationValue_Color(aProperty, aAnimatable.get_nscolor())
+ .Consume();
+ case layers::Animatable::TStyleRotate:
+ return Servo_AnimationValue_Rotate(&aAnimatable.get_StyleRotate())
+ .Consume();
+ case layers::Animatable::TStyleScale:
+ return Servo_AnimationValue_Scale(&aAnimatable.get_StyleScale())
+ .Consume();
+ case layers::Animatable::TStyleTranslate:
+ MOZ_ASSERT(
+ aAnimatable.get_StyleTranslate().IsNone() ||
+ (!aAnimatable.get_StyleTranslate()
+ .AsTranslate()
+ ._0.HasPercent() &&
+ !aAnimatable.get_StyleTranslate().AsTranslate()._1.HasPercent()),
+ "Should have been resolved already");
+ return Servo_AnimationValue_Translate(&aAnimatable.get_StyleTranslate())
+ .Consume();
+ case layers::Animatable::TStyleOffsetPath:
+ return Servo_AnimationValue_OffsetPath(&aAnimatable.get_StyleOffsetPath())
+ .Consume();
+ case layers::Animatable::TLengthPercentage:
+ return Servo_AnimationValue_OffsetDistance(
+ &aAnimatable.get_LengthPercentage())
+ .Consume();
+ case layers::Animatable::TStyleOffsetRotate:
+ return Servo_AnimationValue_OffsetRotate(
+ &aAnimatable.get_StyleOffsetRotate())
+ .Consume();
+ case layers::Animatable::TStylePositionOrAuto:
+ return Servo_AnimationValue_OffsetAnchor(
+ &aAnimatable.get_StylePositionOrAuto())
+ .Consume();
+ case layers::Animatable::TStyleOffsetPosition:
+ return Servo_AnimationValue_OffsetPosition(
+ &aAnimatable.get_StyleOffsetPosition())
+ .Consume();
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported type");
+ }
+ return nullptr;
+}
diff --git a/layout/style/StyleAnimationValue.h b/layout/style/StyleAnimationValue.h
new file mode 100644
index 0000000000..04dc48dd90
--- /dev/null
+++ b/layout/style/StyleAnimationValue.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Utilities for animation of computed style values */
+
+#ifndef mozilla_StyleAnimationValue_h_
+#define mozilla_StyleAnimationValue_h_
+
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/ServoStyleConsts.h" // Servo_AnimationValue_Dump
+#include "mozilla/DbgMacro.h"
+#include "mozilla/AnimatedPropertyID.h"
+#include "nsStringFwd.h"
+#include "nsStringBuffer.h"
+#include "nsCoord.h"
+#include "nsColor.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSValue.h"
+#include "nsStyleConsts.h"
+#include "nsStyleTransformMatrix.h"
+
+class nsIFrame;
+class gfx3DMatrix;
+
+namespace mozilla {
+
+namespace css {
+class StyleRule;
+} // namespace css
+
+namespace dom {
+class Element;
+} // namespace dom
+
+namespace layers {
+class Animatable;
+} // namespace layers
+
+enum class PseudoStyleType : uint8_t;
+struct PropertyStyleAnimationValuePair;
+
+struct AnimationValue {
+ explicit AnimationValue(const RefPtr<StyleAnimationValue>& aValue)
+ : mServo(aValue) {}
+ AnimationValue() = default;
+
+ AnimationValue(const AnimationValue& aOther) = default;
+ AnimationValue(AnimationValue&& aOther) = default;
+
+ AnimationValue& operator=(const AnimationValue& aOther) = default;
+ AnimationValue& operator=(AnimationValue&& aOther) = default;
+
+ bool operator==(const AnimationValue& aOther) const;
+ bool operator!=(const AnimationValue& aOther) const;
+
+ bool IsNull() const { return !mServo; }
+
+ float GetOpacity() const;
+
+ // Returns nscolor value in this AnimationValue.
+ // Currently only background-color is supported.
+ nscolor GetColor(nscolor aForegroundColor) const;
+
+ // Returns true if this AnimationValue is current-color.
+ // Currently only background-color is supported.
+ bool IsCurrentColor() const;
+
+ // Return a transform list for the transform property.
+ const mozilla::StyleTransform& GetTransformProperty() const;
+ const mozilla::StyleScale& GetScaleProperty() const;
+ const mozilla::StyleTranslate& GetTranslateProperty() const;
+ const mozilla::StyleRotate& GetRotateProperty() const;
+
+ // Motion path properties.
+ // Note: This clones the StyleOffsetPath object from its AnimatedValue, so
+ // this may be expensive if the path is a complex SVG path or polygon. The
+ // caller should be aware of this performance impact.
+ void GetOffsetPathProperty(StyleOffsetPath& aOffsetPath) const;
+ const mozilla::LengthPercentage& GetOffsetDistanceProperty() const;
+ const mozilla::StyleOffsetRotate& GetOffsetRotateProperty() const;
+ const mozilla::StylePositionOrAuto& GetOffsetAnchorProperty() const;
+ const mozilla::StyleOffsetPosition& GetOffsetPositionProperty() const;
+ bool IsOffsetPathUrl() const;
+
+ // Return the scale for mServo, which is calculated with reference to aFrame.
+ mozilla::gfx::MatrixScales GetScaleValue(const nsIFrame* aFrame) const;
+
+ // Uncompute this AnimationValue and then serialize it.
+ void SerializeSpecifiedValue(const AnimatedPropertyID& aProperty,
+ const StylePerDocumentStyleData* aRawData,
+ nsACString& aString) const;
+
+ // Check if |*this| and |aToValue| can be interpolated.
+ bool IsInterpolableWith(const AnimatedPropertyID& aProperty,
+ const AnimationValue& aToValue) const;
+
+ // Compute the distance between *this and aOther.
+ double ComputeDistance(const AnimationValue& aOther) const;
+
+ // Create an AnimaitonValue from a string. This method flushes style, so we
+ // should use this carefully. Now, it is only used by
+ // nsDOMWindowUtils::ComputeAnimationDistance.
+ static AnimationValue FromString(AnimatedPropertyID& aProperty,
+ const nsACString& aValue,
+ dom::Element* aElement);
+
+ // Create an already_AddRefed<StyleAnimationValue> from a
+ // layers::Animatable. Basically, this function should return AnimationValue,
+ // but it seems the caller, AnimationHelper, only needs
+ // StyleAnimationValue, so we return its already_AddRefed<> to avoid
+ // adding/removing a redundant ref-count.
+ static already_AddRefed<StyleAnimationValue> FromAnimatable(
+ nsCSSPropertyID aProperty, const layers::Animatable& aAnimatable);
+
+ RefPtr<StyleAnimationValue> mServo;
+};
+
+inline std::ostream& operator<<(std::ostream& aOut,
+ const AnimationValue& aValue) {
+ MOZ_ASSERT(aValue.mServo);
+ nsAutoCString s;
+ Servo_AnimationValue_Dump(aValue.mServo, &s);
+ return aOut << s;
+}
+
+struct PropertyStyleAnimationValuePair {
+ AnimatedPropertyID mProperty;
+ AnimationValue mValue;
+};
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/StyleColor.cpp b/layout/style/StyleColor.cpp
new file mode 100644
index 0000000000..df58cf093d
--- /dev/null
+++ b/layout/style/StyleColor.cpp
@@ -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/. */
+
+#include "mozilla/StyleColorInlines.h"
+
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsIFrame.h"
+#include "nsStyleStruct.h"
+
+namespace mozilla {
+
+template <>
+bool StyleColor::MaybeTransparent() const {
+ // We know that the color is opaque when it's a numeric color with
+ // alpha == 1.0.
+ return !IsAbsolute() || AsAbsolute().alpha != 1.0f;
+}
+
+template <>
+StyleAbsoluteColor StyleColor::ResolveColor(
+ const StyleAbsoluteColor& aForegroundColor) const {
+ if (IsAbsolute()) {
+ return AsAbsolute();
+ }
+
+ if (IsCurrentColor()) {
+ return aForegroundColor;
+ }
+
+ MOZ_ASSERT(IsColorMix(), "should be the only type left at this point.");
+ return Servo_ResolveColor(this, &aForegroundColor);
+}
+
+template <>
+nscolor StyleColor::CalcColor(nscolor aColor) const {
+ return ResolveColor(StyleAbsoluteColor::FromColor(aColor)).ToColor();
+}
+
+template <>
+nscolor StyleColor::CalcColor(
+ const StyleAbsoluteColor& aForegroundColor) const {
+ return ResolveColor(aForegroundColor).ToColor();
+}
+
+template <>
+nscolor StyleColor::CalcColor(const ComputedStyle& aStyle) const {
+ return ResolveColor(aStyle.StyleText()->mColor).ToColor();
+}
+
+template <>
+nscolor StyleColor::CalcColor(const nsIFrame* aFrame) const {
+ return ResolveColor(aFrame->StyleText()->mColor).ToColor();
+}
+
+StyleAbsoluteColor StyleAbsoluteColor::ToColorSpace(
+ StyleColorSpace aColorSpace) const {
+ return Servo_ConvertColorSpace(this, aColorSpace);
+}
+
+nscolor StyleAbsoluteColor::ToColor() const {
+ auto srgb = ToColorSpace(StyleColorSpace::Srgb);
+
+ // TODO(tlouw): Needs gamut mapping here. Right now we just hard clip the
+ // components to [0..1], which will yield invalid colors.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1626624
+ auto red = std::clamp(srgb.components._0, 0.0f, 1.0f);
+ auto green = std::clamp(srgb.components._1, 0.0f, 1.0f);
+ auto blue = std::clamp(srgb.components._2, 0.0f, 1.0f);
+
+ return NS_RGBA(nsStyleUtil::FloatToColorComponent(red),
+ nsStyleUtil::FloatToColorComponent(green),
+ nsStyleUtil::FloatToColorComponent(blue),
+ nsStyleUtil::FloatToColorComponent(srgb.alpha));
+}
+
+} // namespace mozilla
diff --git a/layout/style/StyleColorInlines.h b/layout/style/StyleColorInlines.h
new file mode 100644
index 0000000000..b7cc9f1ad6
--- /dev/null
+++ b/layout/style/StyleColorInlines.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 functions for StyleColor (aka values::computed::Color) */
+
+#ifndef mozilla_StyleColorInlines_h_
+#define mozilla_StyleColorInlines_h_
+
+#include "nsColor.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "nsStyleUtil.h"
+
+namespace mozilla {
+
+inline StyleAbsoluteColor StyleAbsoluteColor::FromColor(nscolor aColor) {
+ return StyleAbsoluteColor::SrgbLegacy(
+ NS_GET_R(aColor) / 255.0f, NS_GET_G(aColor) / 255.0f,
+ NS_GET_B(aColor) / 255.0f, NS_GET_A(aColor) / 255.0f);
+}
+
+// static
+inline StyleAbsoluteColor StyleAbsoluteColor::SrgbLegacy(float red, float green,
+ float blue,
+ float alpha) {
+ const auto ToLegacyComponent = [](float aF) {
+ if (MOZ_UNLIKELY(!std::isfinite(aF))) {
+ return 0.0f;
+ }
+ return aF;
+ };
+
+ return StyleAbsoluteColor{
+ StyleColorComponents{ToLegacyComponent(red), ToLegacyComponent(green),
+ ToLegacyComponent(blue)},
+ alpha, StyleColorSpace::Srgb, StyleColorFlags::IS_LEGACY_SRGB};
+}
+
+template <>
+inline StyleColor StyleColor::FromColor(nscolor aColor) {
+ return StyleColor::Absolute(StyleAbsoluteColor::FromColor(aColor));
+}
+
+template <>
+inline StyleColor StyleColor::Transparent() {
+ return StyleColor::Absolute(StyleAbsoluteColor::TRANSPARENT_BLACK);
+}
+
+template <>
+inline StyleColor StyleColor::Black() {
+ return StyleColor::Absolute(StyleAbsoluteColor::BLACK);
+}
+
+template <>
+inline StyleColor StyleColor::White() {
+ return StyleColor::Absolute(StyleAbsoluteColor::WHITE);
+}
+
+template <>
+StyleAbsoluteColor StyleColor::ResolveColor(const StyleAbsoluteColor&) const;
+
+template <>
+nscolor StyleColor::CalcColor(const StyleAbsoluteColor&) const;
+
+template <>
+nscolor StyleColor::CalcColor(nscolor) const;
+
+template <>
+nscolor StyleColor::CalcColor(const ComputedStyle&) const;
+
+template <>
+nscolor StyleColor::CalcColor(const nsIFrame*) const;
+
+} // namespace mozilla
+
+#endif // mozilla_StyleColor_h_
diff --git a/layout/style/StylePreloadKind.h b/layout/style/StylePreloadKind.h
new file mode 100644
index 0000000000..154e9cda63
--- /dev/null
+++ b/layout/style/StylePreloadKind.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_css_StylePreloadKind_h
+#define mozilla_css_StylePreloadKind_h
+
+#include <stdint.h>
+
+namespace mozilla::css {
+
+enum class StylePreloadKind : uint8_t {
+ // Not a preload.
+ None,
+ // An speculative load from the parser for a <link rel="stylesheet"> or
+ // @import stylesheet.
+ FromParser,
+ // A preload (speculative or not) for a <link rel="preload" as="style">
+ // element.
+ FromLinkRelPreloadElement,
+ // A preload for a "Link" rel=preload response header.
+ FromLinkRelPreloadHeader,
+};
+
+inline bool IsLinkRelPreload(StylePreloadKind aKind) {
+ return aKind == StylePreloadKind::FromLinkRelPreloadElement ||
+ aKind == StylePreloadKind::FromLinkRelPreloadHeader;
+}
+
+} // namespace mozilla::css
+
+#endif // mozilla_css_StylePreloadKind_h
diff --git a/layout/style/StyleSheet.cpp b/layout/style/StyleSheet.cpp
new file mode 100644
index 0000000000..494618d879
--- /dev/null
+++ b/layout/style/StyleSheet.cpp
@@ -0,0 +1,1484 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/StyleSheet.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/css/ErrorReporter.h"
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/dom/CSSImportRule.h"
+#include "mozilla/dom/CSSRuleList.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/FetchPriority.h"
+#include "mozilla/dom/MediaList.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/ShadowRootBinding.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoCSSRuleList.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/SheetLoadData.h"
+
+#include "mozAutoDocUpdate.h"
+#include "SheetLoadData.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+StyleSheet::StyleSheet(css::SheetParsingMode aParsingMode, CORSMode aCORSMode,
+ const dom::SRIMetadata& aIntegrity)
+ : mParentSheet(nullptr),
+ mConstructorDocument(nullptr),
+ mDocumentOrShadowRoot(nullptr),
+ mParsingMode(aParsingMode),
+ mState(static_cast<State>(0)),
+ mInner(new StyleSheetInfo(aCORSMode, aIntegrity, aParsingMode)) {
+ mInner->AddSheet(this);
+}
+
+StyleSheet::StyleSheet(const StyleSheet& aCopy, StyleSheet* aParentSheetToUse,
+ dom::DocumentOrShadowRoot* aDocOrShadowRootToUse,
+ dom::Document* aConstructorDocToUse)
+ : mParentSheet(aParentSheetToUse),
+ mConstructorDocument(aConstructorDocToUse),
+ mTitle(aCopy.mTitle),
+ mDocumentOrShadowRoot(aDocOrShadowRootToUse),
+ mParsingMode(aCopy.mParsingMode),
+ mState(aCopy.mState),
+ // Shallow copy, but concrete subclasses will fix up.
+ mInner(aCopy.mInner) {
+ MOZ_ASSERT(!aConstructorDocToUse || aCopy.IsConstructed());
+ MOZ_ASSERT(!aConstructorDocToUse || !aDocOrShadowRootToUse,
+ "Should never have both of these together.");
+ MOZ_ASSERT(mInner, "Should only copy StyleSheets with an mInner.");
+ mInner->AddSheet(this);
+ // CSSOM's been there, force full copy now.
+ if (HasForcedUniqueInner()) {
+ MOZ_ASSERT(IsComplete(),
+ "Why have rules been accessed on an incomplete sheet?");
+ EnsureUniqueInner();
+ // But CSSOM hasn't been on _this_ stylesheet yet, so no need to clone
+ // ourselves.
+ mState &= ~(State::ForcedUniqueInner | State::ModifiedRules |
+ State::ModifiedRulesForDevtools);
+ }
+
+ if (aCopy.mMedia) {
+ // XXX This is wrong; we should be keeping @import rules and
+ // sheets in sync!
+ mMedia = aCopy.mMedia->Clone();
+ }
+}
+
+/* static */
+// https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-cssstylesheet
+already_AddRefed<StyleSheet> StyleSheet::Constructor(
+ const dom::GlobalObject& aGlobal, const dom::CSSStyleSheetInit& aOptions,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+
+ if (!window) {
+ aRv.ThrowNotSupportedError("Not supported when there is no document");
+ return nullptr;
+ }
+
+ Document* constructorDocument = window->GetExtantDoc();
+ if (!constructorDocument) {
+ aRv.ThrowNotSupportedError("Not supported when there is no document");
+ return nullptr;
+ }
+
+ // 1. Construct a sheet and set its properties (see spec).
+ auto sheet =
+ MakeRefPtr<StyleSheet>(css::SheetParsingMode::eAuthorSheetFeatures,
+ CORSMode::CORS_NONE, dom::SRIMetadata());
+
+ // baseURL not yet in the spec. Implemented based on the following discussion:
+ // https://github.com/WICG/construct-stylesheets/issues/95#issuecomment-594217180
+ RefPtr<nsIURI> baseURI;
+ if (!aOptions.mBaseURL.WasPassed()) {
+ baseURI = constructorDocument->GetBaseURI();
+ } else {
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aOptions.mBaseURL.Value(),
+ nullptr, constructorDocument->GetBaseURI());
+ if (NS_FAILED(rv)) {
+ aRv.ThrowNotAllowedError(
+ "Constructed style sheets must have a valid base URL");
+ return nullptr;
+ }
+ }
+
+ nsIURI* sheetURI = constructorDocument->GetDocumentURI();
+ nsIURI* originalURI = nullptr;
+ sheet->SetURIs(sheetURI, originalURI, baseURI);
+
+ sheet->SetPrincipal(constructorDocument->NodePrincipal());
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*constructorDocument);
+ sheet->SetReferrerInfo(referrerInfo);
+ sheet->mConstructorDocument = constructorDocument;
+
+ // 2. Set the sheet's media according to aOptions.
+ if (aOptions.mMedia.IsUTF8String()) {
+ sheet->SetMedia(MediaList::Create(aOptions.mMedia.GetAsUTF8String()));
+ } else {
+ sheet->SetMedia(aOptions.mMedia.GetAsMediaList()->Clone());
+ }
+
+ // 3. Set the sheet's disabled flag according to aOptions.
+ sheet->SetDisabled(aOptions.mDisabled);
+ sheet->SetComplete();
+
+ // 4. Return sheet.
+ return sheet.forget();
+}
+
+StyleSheet::~StyleSheet() {
+ MOZ_ASSERT(!mInner, "Inner should have been dropped in LastRelease");
+}
+
+bool StyleSheet::HasRules() const {
+ return Servo_StyleSheet_HasRules(Inner().mContents);
+}
+
+Document* StyleSheet::GetAssociatedDocument() const {
+ auto* associated = GetAssociatedDocumentOrShadowRoot();
+ return associated ? associated->AsNode().OwnerDoc() : nullptr;
+}
+
+dom::DocumentOrShadowRoot* StyleSheet::GetAssociatedDocumentOrShadowRoot()
+ const {
+ const StyleSheet& outer = OutermostSheet();
+ if (outer.mDocumentOrShadowRoot) {
+ return outer.mDocumentOrShadowRoot;
+ }
+ if (outer.IsConstructed()) {
+ return outer.mConstructorDocument;
+ }
+ return nullptr;
+}
+
+void StyleSheet::UpdateRelevantGlobal() {
+ if (mRelevantGlobal || !IsComplete()) {
+ return;
+ }
+ if (Document* doc = GetAssociatedDocument()) {
+ mRelevantGlobal = doc->GetScopeObject();
+ }
+}
+
+Document* StyleSheet::GetKeptAliveByDocument() const {
+ const StyleSheet& outer = OutermostSheet();
+ if (outer.mDocumentOrShadowRoot) {
+ return outer.mDocumentOrShadowRoot->AsNode().GetComposedDoc();
+ }
+ if (outer.IsConstructed()) {
+ for (DocumentOrShadowRoot* adopter : outer.mAdopters) {
+ MOZ_ASSERT(adopter->AsNode().OwnerDoc() == outer.mConstructorDocument);
+ if (adopter->AsNode().IsInComposedDoc()) {
+ return outer.mConstructorDocument.get();
+ }
+ }
+ }
+ return nullptr;
+}
+
+void StyleSheet::LastRelease() {
+ MOZ_DIAGNOSTIC_ASSERT(mAdopters.IsEmpty(),
+ "Should have no adopters at time of destruction.");
+
+ if (mInner) {
+ MOZ_ASSERT(mInner->mSheets.Contains(this), "Our mInner should include us.");
+ mInner->RemoveSheet(this);
+ mInner = nullptr;
+ }
+
+ DropMedia();
+ DropRuleList();
+}
+
+void StyleSheet::UnlinkInner() {
+ if (!mInner) {
+ return;
+ }
+
+ // We can only have a cycle through our inner if we have a unique inner,
+ // because otherwise there are no JS wrappers for anything in the inner.
+ if (mInner->mSheets.Length() != 1) {
+ mInner->RemoveSheet(this);
+ mInner = nullptr;
+ return;
+ }
+
+ for (StyleSheet* child : ChildSheets()) {
+ MOZ_ASSERT(child->mParentSheet == this, "We have a unique inner!");
+ child->mParentSheet = nullptr;
+ }
+ Inner().mChildren.Clear();
+}
+
+void StyleSheet::TraverseInner(nsCycleCollectionTraversalCallback& cb) {
+ if (!mInner) {
+ return;
+ }
+
+ for (StyleSheet* child : ChildSheets()) {
+ if (child->mParentSheet == this) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "child sheet");
+ cb.NoteXPCOMChild(child);
+ }
+ }
+}
+
+// QueryInterface implementation for StyleSheet
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StyleSheet)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StyleSheet)
+// We want to disconnect from our inner as soon as our refcount drops to zero,
+// without waiting for async deletion by the cycle collector. Otherwise we
+// might end up cloning the inner if someone mutates another sheet that shares
+// it with us, even though there is only one such sheet and we're about to go
+// away. This situation arises easily with sheet preloading.
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(StyleSheet, LastRelease())
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(StyleSheet)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StyleSheet)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMedia)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelevantGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConstructorDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReplacePromise)
+ tmp->TraverseInner(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StyleSheet)
+ tmp->DropMedia();
+ tmp->UnlinkInner();
+ tmp->DropRuleList();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelevantGlobal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mConstructorDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mReplacePromise)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+dom::CSSStyleSheetParsingMode StyleSheet::ParsingModeDOM() {
+#define CHECK_MODE(X, Y) \
+ static_assert( \
+ static_cast<int>(X) == static_cast<int>(Y), \
+ "mozilla::dom::CSSStyleSheetParsingMode and " \
+ "mozilla::css::SheetParsingMode should have identical values");
+
+ CHECK_MODE(dom::CSSStyleSheetParsingMode::Agent, css::eAgentSheetFeatures);
+ CHECK_MODE(dom::CSSStyleSheetParsingMode::User, css::eUserSheetFeatures);
+ CHECK_MODE(dom::CSSStyleSheetParsingMode::Author, css::eAuthorSheetFeatures);
+
+#undef CHECK_MODE
+
+ return static_cast<dom::CSSStyleSheetParsingMode>(mParsingMode);
+}
+
+void StyleSheet::SetComplete() {
+ // HasForcedUniqueInner() is okay if the sheet is constructed, because
+ // constructed sheets are always unique and they may be set to complete
+ // multiple times if their rules are replaced via Replace()
+ MOZ_ASSERT(IsConstructed() || !HasForcedUniqueInner(),
+ "Can't complete a sheet that's already been forced unique.");
+ MOZ_ASSERT(!IsComplete(), "Already complete?");
+ mState |= State::Complete;
+
+ UpdateRelevantGlobal();
+
+ if (!Disabled()) {
+ ApplicableStateChanged(true);
+ }
+ MaybeResolveReplacePromise();
+}
+
+void StyleSheet::ApplicableStateChanged(bool aApplicable) {
+ MOZ_ASSERT(aApplicable == IsApplicable());
+ Document* docToPostEvent = nullptr;
+ auto Notify = [&](DocumentOrShadowRoot& target) {
+ nsINode& node = target.AsNode();
+ if (ShadowRoot* shadow = ShadowRoot::FromNode(node)) {
+ shadow->StyleSheetApplicableStateChanged(*this);
+ MOZ_ASSERT(!docToPostEvent || !shadow->IsInComposedDoc() ||
+ docToPostEvent == shadow->GetComposedDoc());
+ if (!docToPostEvent) {
+ docToPostEvent = shadow->GetComposedDoc();
+ }
+ } else {
+ Document* doc = node.AsDocument();
+ MOZ_ASSERT(!docToPostEvent || docToPostEvent == doc);
+ doc->StyleSheetApplicableStateChanged(*this);
+ docToPostEvent = doc;
+ }
+ };
+
+ const StyleSheet& sheet = OutermostSheet();
+ if (sheet.mDocumentOrShadowRoot) {
+ Notify(*sheet.mDocumentOrShadowRoot);
+ }
+
+ if (sheet.mConstructorDocument) {
+ Notify(*sheet.mConstructorDocument);
+ }
+
+ for (DocumentOrShadowRoot* adopter : sheet.mAdopters) {
+ MOZ_ASSERT(adopter, "adopters should never be null");
+ if (adopter != sheet.mConstructorDocument) {
+ Notify(*adopter);
+ }
+ }
+
+ if (docToPostEvent) {
+ docToPostEvent->PostStyleSheetApplicableStateChangeEvent(*this);
+ }
+}
+
+void StyleSheet::SetDisabled(bool aDisabled) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ if (aDisabled == Disabled()) {
+ return;
+ }
+
+ if (aDisabled) {
+ mState |= State::Disabled;
+ } else {
+ mState &= ~State::Disabled;
+ }
+
+ if (IsComplete()) {
+ ApplicableStateChanged(!aDisabled);
+ }
+}
+
+void StyleSheet::SetURLExtraData() {
+ Inner().mURLData =
+ new URLExtraData(GetBaseURI(), GetReferrerInfo(), Principal());
+}
+
+nsISupports* StyleSheet::GetRelevantGlobal() const {
+ const StyleSheet& outer = OutermostSheet();
+ return outer.mRelevantGlobal;
+}
+
+StyleSheetInfo::StyleSheetInfo(CORSMode aCORSMode,
+ const SRIMetadata& aIntegrity,
+ css::SheetParsingMode aParsingMode)
+ : mPrincipal(NullPrincipal::CreateWithoutOriginAttributes()),
+ mCORSMode(aCORSMode),
+ mReferrerInfo(new ReferrerInfo(nullptr)),
+ mIntegrity(aIntegrity),
+ mContents(Servo_StyleSheet_Empty(aParsingMode).Consume()),
+ mURLData(URLExtraData::Dummy()) {
+ if (!mPrincipal) {
+ MOZ_CRASH("NullPrincipal::Init failed");
+ }
+ MOZ_COUNT_CTOR(StyleSheetInfo);
+}
+
+StyleSheetInfo::StyleSheetInfo(StyleSheetInfo& aCopy, StyleSheet* aPrimarySheet)
+ : mSheetURI(aCopy.mSheetURI),
+ mOriginalSheetURI(aCopy.mOriginalSheetURI),
+ mBaseURI(aCopy.mBaseURI),
+ mPrincipal(aCopy.mPrincipal),
+ mCORSMode(aCopy.mCORSMode),
+ mReferrerInfo(aCopy.mReferrerInfo),
+ mIntegrity(aCopy.mIntegrity),
+ // We don't rebuild the child because we're making a copy without
+ // children.
+ mSourceMapURL(aCopy.mSourceMapURL),
+ mContents(Servo_StyleSheet_Clone(aCopy.mContents.get(), aPrimarySheet)
+ .Consume()),
+ mURLData(aCopy.mURLData)
+#ifdef DEBUG
+ ,
+ mPrincipalSet(aCopy.mPrincipalSet)
+#endif
+{
+ AddSheet(aPrimarySheet);
+
+ // Our child list is fixed up by our parent.
+ MOZ_COUNT_CTOR(StyleSheetInfo);
+}
+
+StyleSheetInfo::~StyleSheetInfo() { MOZ_COUNT_DTOR(StyleSheetInfo); }
+
+StyleSheetInfo* StyleSheetInfo::CloneFor(StyleSheet* aPrimarySheet) {
+ return new StyleSheetInfo(*this, aPrimarySheet);
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(ServoStyleSheetMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoStyleSheetMallocEnclosingSizeOf)
+
+size_t StyleSheetInfo::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ n += Servo_StyleSheet_SizeOfIncludingThis(
+ ServoStyleSheetMallocSizeOf, ServoStyleSheetMallocEnclosingSizeOf,
+ mContents);
+
+ return n;
+}
+
+void StyleSheetInfo::AddSheet(StyleSheet* aSheet) {
+ mSheets.AppendElement(aSheet);
+}
+
+void StyleSheetInfo::RemoveSheet(StyleSheet* aSheet) {
+ // Fix up the parent pointer in children lists.
+ StyleSheet* newParent =
+ aSheet == mSheets[0] ? mSheets.SafeElementAt(1) : mSheets[0];
+ for (StyleSheet* child : mChildren) {
+ MOZ_ASSERT(child->mParentSheet);
+ MOZ_ASSERT(child->mParentSheet->mInner == this);
+ if (child->mParentSheet == aSheet) {
+ child->mParentSheet = newParent;
+ }
+ }
+
+ if (1 == mSheets.Length()) {
+ NS_ASSERTION(aSheet == mSheets.ElementAt(0), "bad parent");
+ delete this;
+ return;
+ }
+
+ mSheets.RemoveElement(aSheet);
+}
+
+void StyleSheet::GetType(nsAString& aType) { aType.AssignLiteral("text/css"); }
+
+void StyleSheet::GetHref(nsAString& aHref, ErrorResult& aRv) {
+ if (nsIURI* sheetURI = Inner().mOriginalSheetURI) {
+ nsAutoCString str;
+ nsresult rv = sheetURI->GetSpec(str);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ CopyUTF8toUTF16(str, aHref);
+ } else {
+ SetDOMStringToNull(aHref);
+ }
+}
+
+void StyleSheet::GetTitle(nsAString& aTitle) {
+ // From https://drafts.csswg.org/cssom/#dom-stylesheet-title:
+ //
+ // The title attribute must return the title or null if title is the empty
+ // string.
+ //
+ if (!mTitle.IsEmpty()) {
+ aTitle.Assign(mTitle);
+ } else {
+ SetDOMStringToNull(aTitle);
+ }
+}
+
+void StyleSheet::WillDirty() {
+ MOZ_ASSERT(!IsReadOnly());
+
+ if (IsComplete()) {
+ EnsureUniqueInner();
+ }
+}
+
+void StyleSheet::AddStyleSet(ServoStyleSet* aStyleSet) {
+ MOZ_DIAGNOSTIC_ASSERT(!mStyleSets.Contains(aStyleSet),
+ "style set already registered");
+ mStyleSets.AppendElement(aStyleSet);
+}
+
+void StyleSheet::DropStyleSet(ServoStyleSet* aStyleSet) {
+ bool found = mStyleSets.RemoveElement(aStyleSet);
+ MOZ_DIAGNOSTIC_ASSERT(found, "didn't find style set");
+#ifndef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ Unused << found;
+#endif
+}
+
+// NOTE(emilio): Composed doc and containing shadow root are set in child sheets
+// too, so no need to do it for each ancestor.
+#define NOTIFY(function_, args_) \
+ do { \
+ StyleSheet* current = this; \
+ do { \
+ for (ServoStyleSet * set : current->mStyleSets) { \
+ set->function_ args_; \
+ } \
+ if (auto* docOrShadow = current->mDocumentOrShadowRoot) { \
+ if (auto* shadow = ShadowRoot::FromNode(docOrShadow->AsNode())) { \
+ shadow->function_ args_; \
+ } else { \
+ docOrShadow->AsNode().AsDocument()->function_ args_; \
+ } \
+ } \
+ for (auto* adopter : mAdopters) { \
+ if (auto* shadow = ShadowRoot::FromNode(adopter->AsNode())) { \
+ shadow->function_ args_; \
+ } else { \
+ adopter->AsNode().AsDocument()->function_ args_; \
+ } \
+ } \
+ current = current->mParentSheet; \
+ } while (current); \
+ } while (0)
+
+void StyleSheet::EnsureUniqueInner() {
+ MOZ_ASSERT(mInner->mSheets.Length() != 0, "unexpected number of outers");
+
+ if (IsReadOnly()) {
+ // Sheets that can't be modified don't need a unique inner.
+ return;
+ }
+
+ mState |= State::ForcedUniqueInner;
+
+ if (HasUniqueInner()) {
+ // already unique
+ return;
+ }
+
+ StyleSheetInfo* clone = mInner->CloneFor(this);
+ MOZ_ASSERT(clone);
+
+ mInner->RemoveSheet(this);
+ mInner = clone;
+
+ // Fixup the child lists and parent links in the Servo sheet. This is done
+ // here instead of in StyleSheetInner::CloneFor, because it's just more
+ // convenient to do so instead.
+ FixUpAfterInnerClone();
+
+ // let our containing style sets know that if we call
+ // nsPresContext::EnsureSafeToHandOutCSSRules we will need to restyle the
+ // document
+ NOTIFY(SheetCloned, (*this));
+}
+
+// WebIDL CSSStyleSheet API
+
+dom::CSSRuleList* StyleSheet::GetCssRules(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!AreRulesAvailable(aSubjectPrincipal, aRv)) {
+ return nullptr;
+ }
+ return GetCssRulesInternal();
+}
+
+void StyleSheet::GetSourceMapURL(nsACString& aSourceMapURL) {
+ if (!mInner->mSourceMapURL.IsEmpty()) {
+ aSourceMapURL = mInner->mSourceMapURL;
+ return;
+ }
+ Servo_StyleSheet_GetSourceMapURL(mInner->mContents, &aSourceMapURL);
+}
+
+void StyleSheet::SetSourceMapURL(nsCString&& aSourceMapURL) {
+ mInner->mSourceMapURL = std::move(aSourceMapURL);
+}
+
+void StyleSheet::GetSourceURL(nsACString& aSourceURL) {
+ Servo_StyleSheet_GetSourceURL(mInner->mContents, &aSourceURL);
+}
+
+css::Rule* StyleSheet::GetDOMOwnerRule() const { return GetOwnerRule(); }
+
+// https://drafts.csswg.org/cssom/#dom-cssstylesheet-insertrule
+// https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-insertrule
+uint32_t StyleSheet::InsertRule(const nsACString& aRule, uint32_t aIndex,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (IsReadOnly() || !AreRulesAvailable(aSubjectPrincipal, aRv)) {
+ return 0;
+ }
+
+ if (ModificationDisallowed()) {
+ aRv.ThrowNotAllowedError(
+ "This method can only be called on "
+ "modifiable style sheets");
+ return 0;
+ }
+
+ return InsertRuleInternal(aRule, aIndex, aRv);
+}
+
+// https://drafts.csswg.org/cssom/#dom-cssstylesheet-deleterule
+// https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-deleterule
+void StyleSheet::DeleteRule(uint32_t aIndex, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (IsReadOnly() || !AreRulesAvailable(aSubjectPrincipal, aRv)) {
+ return;
+ }
+
+ if (ModificationDisallowed()) {
+ return aRv.ThrowNotAllowedError(
+ "This method can only be called on "
+ "modifiable style sheets");
+ }
+
+ return DeleteRuleInternal(aIndex, aRv);
+}
+
+int32_t StyleSheet::AddRule(const nsACString& aSelector,
+ const nsACString& aBlock,
+ const Optional<uint32_t>& aIndex,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ if (IsReadOnly() || !AreRulesAvailable(aSubjectPrincipal, aRv)) {
+ return -1;
+ }
+
+ nsAutoCString rule;
+ rule.Append(aSelector);
+ rule.AppendLiteral(" { ");
+ if (!aBlock.IsEmpty()) {
+ rule.Append(aBlock);
+ rule.Append(' ');
+ }
+ rule.Append('}');
+
+ auto index =
+ aIndex.WasPassed() ? aIndex.Value() : GetCssRulesInternal()->Length();
+
+ InsertRuleInternal(rule, index, aRv);
+ // Always return -1.
+ return -1;
+}
+
+void StyleSheet::MaybeResolveReplacePromise() {
+ MOZ_ASSERT(!!mReplacePromise == ModificationDisallowed());
+ if (!mReplacePromise) {
+ return;
+ }
+
+ SetModificationDisallowed(false);
+ mReplacePromise->MaybeResolve(this);
+ mReplacePromise = nullptr;
+}
+
+void StyleSheet::MaybeRejectReplacePromise() {
+ MOZ_ASSERT(!!mReplacePromise == ModificationDisallowed());
+ if (!mReplacePromise) {
+ return;
+ }
+
+ SetModificationDisallowed(false);
+ mReplacePromise->MaybeRejectWithNetworkError(
+ "@import style sheet load failed");
+ mReplacePromise = nullptr;
+}
+
+// https://drafts.csswg.org/cssom/#dom-cssstylesheet-replace
+already_AddRefed<dom::Promise> StyleSheet::Replace(const nsACString& aText,
+ ErrorResult& aRv) {
+ nsIGlobalObject* globalObject = nullptr;
+ const StyleSheet& outer = OutermostSheet();
+ if (outer.mRelevantGlobal) {
+ globalObject = outer.mRelevantGlobal;
+ } else if (Document* doc = outer.GetAssociatedDocument()) {
+ globalObject = doc->GetScopeObject();
+ }
+
+ RefPtr<dom::Promise> promise = dom::Promise::Create(globalObject, aRv);
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 1 and 4 are variable declarations
+
+ // 2.1 Check if sheet is constructed, else reject promise.
+ if (!IsConstructed()) {
+ promise->MaybeRejectWithNotAllowedError(
+ "This method can only be called on "
+ "constructed style sheets");
+ return promise.forget();
+ }
+
+ // 2.2 Check if sheet is modifiable, else throw.
+ if (ModificationDisallowed()) {
+ promise->MaybeRejectWithNotAllowedError(
+ "This method can only be called on "
+ "modifiable style sheets");
+ return promise.forget();
+ }
+
+ // 3. Disallow modifications until finished.
+ SetModificationDisallowed(true);
+
+ // TODO(emilio, 1642227): Should constructable stylesheets notify global
+ // observers (i.e., set mMustNotify to true)?
+ auto* loader = mConstructorDocument->CSSLoader();
+ auto loadData = MakeRefPtr<css::SheetLoadData>(
+ loader, /* aURI = */ nullptr, this, css::SyncLoad::No,
+ css::Loader::UseSystemPrincipal::No, css::StylePreloadKind::None,
+ /* aPreloadEncoding */ nullptr, /* aObserver */ nullptr,
+ mConstructorDocument->NodePrincipal(), GetReferrerInfo(),
+ /* aNonce */ u""_ns, FetchPriority::Auto);
+
+ // In parallel
+ // 5.1 Parse aText into rules.
+ // 5.2 Load import rules, throw NetworkError if failed.
+ // 5.3 Set sheet's rules to new rules.
+ nsISerialEventTarget* target = GetMainThreadSerialEventTarget();
+ loadData->mIsBeingParsed = true;
+ MOZ_ASSERT(!mReplacePromise);
+ mReplacePromise = promise;
+ ParseSheet(*loader, aText, *loadData)
+ ->Then(
+ target, __func__,
+ [loadData] { loadData->SheetFinishedParsingAsync(); },
+ [] { MOZ_CRASH("This MozPromise should never be rejected."); });
+
+ // 6. Return the promise
+ return promise.forget();
+}
+
+// https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-replacesync
+void StyleSheet::ReplaceSync(const nsACString& aText, ErrorResult& aRv) {
+ // Step 1 is a variable declaration
+
+ // 2.1 Check if sheet is constructed, else throw.
+ if (!IsConstructed()) {
+ return aRv.ThrowNotAllowedError(
+ "Can only be called on constructed style sheets");
+ }
+
+ // 2.2 Check if sheet is modifiable, else throw.
+ if (ModificationDisallowed()) {
+ return aRv.ThrowNotAllowedError(
+ "Can only be called on modifiable style sheets");
+ }
+
+ // 3. Parse aText into rules.
+ // 4. If rules contain @imports, skip them and continue parsing.
+ auto* loader = mConstructorDocument->CSSLoader();
+ SetURLExtraData();
+ RefPtr<const StyleStylesheetContents> rawContent =
+ Servo_StyleSheet_FromUTF8Bytes(
+ loader, this,
+ /* load_data = */ nullptr, &aText, mParsingMode, URLData(),
+ mConstructorDocument->GetCompatibilityMode(),
+ /* reusable_sheets = */ nullptr,
+ mConstructorDocument->GetStyleUseCounters(),
+ StyleAllowImportRules::No, StyleSanitizationKind::None,
+ /* sanitized_output = */ nullptr)
+ .Consume();
+
+ // 5. Set sheet's rules to the new rules.
+ Inner().mContents = std::move(rawContent);
+ FixUpRuleListAfterContentsChangeIfNeeded();
+ RuleChanged(nullptr, StyleRuleChangeKind::Generic);
+}
+
+nsresult StyleSheet::DeleteRuleFromGroup(css::GroupRule* aGroup,
+ uint32_t aIndex) {
+ NS_ENSURE_ARG_POINTER(aGroup);
+ NS_ASSERTION(IsComplete(), "No deleting from an incomplete sheet!");
+ RefPtr<css::Rule> rule = aGroup->GetStyleRuleAt(aIndex);
+ NS_ENSURE_TRUE(rule, NS_ERROR_ILLEGAL_VALUE);
+
+ // check that the rule actually belongs to this sheet!
+ if (this != rule->GetStyleSheet()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (IsReadOnly()) {
+ return NS_OK;
+ }
+
+ WillDirty();
+
+ nsresult result = aGroup->DeleteStyleRuleAt(aIndex);
+ NS_ENSURE_SUCCESS(result, result);
+
+ rule->DropReferences();
+
+ RuleRemoved(*rule);
+ return NS_OK;
+}
+
+void StyleSheet::RuleAdded(css::Rule& aRule) {
+ SetModifiedRules();
+ NOTIFY(RuleAdded, (*this, aRule));
+}
+
+void StyleSheet::RuleRemoved(css::Rule& aRule) {
+ SetModifiedRules();
+ NOTIFY(RuleRemoved, (*this, aRule));
+}
+
+void StyleSheet::RuleChanged(css::Rule* aRule, StyleRuleChangeKind aKind) {
+ MOZ_ASSERT(!aRule || HasUniqueInner(),
+ "Shouldn't have mutated a shared sheet");
+ SetModifiedRules();
+ NOTIFY(RuleChanged, (*this, aRule, aKind));
+}
+
+// nsICSSLoaderObserver implementation
+NS_IMETHODIMP
+StyleSheet::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) {
+ if (!aSheet->GetParentSheet()) {
+ return NS_OK; // ignore if sheet has been detached already
+ }
+ MOZ_ASSERT(this == aSheet->GetParentSheet(),
+ "We are being notified of a sheet load for a sheet that is not "
+ "our child!");
+ if (NS_FAILED(aStatus)) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aSheet->GetOwnerRule());
+ NOTIFY(ImportRuleLoaded, (*aSheet->GetOwnerRule(), *aSheet));
+ return NS_OK;
+}
+
+#undef NOTIFY
+
+nsresult StyleSheet::InsertRuleIntoGroup(const nsACString& aRule,
+ css::GroupRule* aGroup,
+ uint32_t aIndex) {
+ NS_ASSERTION(IsComplete(), "No inserting into an incomplete sheet!");
+ // check that the group actually belongs to this sheet!
+ if (this != aGroup->GetStyleSheet()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (IsReadOnly()) {
+ return NS_OK;
+ }
+
+ if (ModificationDisallowed()) {
+ return NS_ERROR_DOM_NOT_ALLOWED_ERR;
+ }
+
+ WillDirty();
+
+ nsresult result = InsertRuleIntoGroupInternal(aRule, aGroup, aIndex);
+ NS_ENSURE_SUCCESS(result, result);
+ RuleAdded(*aGroup->GetStyleRuleAt(aIndex));
+ return NS_OK;
+}
+
+uint64_t StyleSheet::FindOwningWindowInnerID() const {
+ uint64_t windowID = 0;
+ if (Document* doc = GetAssociatedDocument()) {
+ windowID = doc->InnerWindowID();
+ }
+
+ if (windowID == 0 && mOwningNode) {
+ windowID = mOwningNode->OwnerDoc()->InnerWindowID();
+ }
+
+ RefPtr<css::Rule> ownerRule;
+ if (windowID == 0 && (ownerRule = GetDOMOwnerRule())) {
+ RefPtr<StyleSheet> sheet = ownerRule->GetStyleSheet();
+ if (sheet) {
+ windowID = sheet->FindOwningWindowInnerID();
+ }
+ }
+
+ if (windowID == 0 && mParentSheet) {
+ windowID = mParentSheet->FindOwningWindowInnerID();
+ }
+
+ return windowID;
+}
+
+void StyleSheet::RemoveFromParent() {
+ if (!mParentSheet) {
+ return;
+ }
+
+ MOZ_ASSERT(mParentSheet->ChildSheets().Contains(this));
+ mParentSheet->Inner().mChildren.RemoveElement(this);
+ mParentSheet = nullptr;
+}
+
+void StyleSheet::SubjectSubsumesInnerPrincipal(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ StyleSheetInfo& info = Inner();
+
+ if (aSubjectPrincipal.Subsumes(info.mPrincipal)) {
+ return;
+ }
+
+ // Allow access only if CORS mode is not NONE and the security flag
+ // is not turned off.
+ if (GetCORSMode() == CORS_NONE && !nsContentUtils::BypassCSSOMOriginCheck()) {
+ aRv.ThrowSecurityError("Not allowed to access cross-origin stylesheet");
+ return;
+ }
+
+ // Now make sure we set the principal of our inner to the subjectPrincipal.
+ // We do this because we're in a situation where the caller would not normally
+ // be able to access the sheet, but the sheet has opted in to being read.
+ // Unfortunately, that means it's also opted in to being _edited_, and if the
+ // caller now makes edits to the sheet we want the resulting resource loads,
+ // if any, to look as if they are coming from the caller's principal, not the
+ // original sheet principal.
+ //
+ // That means we need a unique inner, of course. But we don't want to do that
+ // if we're not complete yet. Luckily, all the callers of this method throw
+ // anyway if not complete, so we can just do that here too.
+ if (!IsComplete()) {
+ aRv.ThrowInvalidAccessError(
+ "Not allowed to access still-loading stylesheet");
+ return;
+ }
+
+ WillDirty();
+
+ info.mPrincipal = &aSubjectPrincipal;
+}
+
+bool StyleSheet::AreRulesAvailable(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ // Rules are not available on incomplete sheets.
+ if (!IsComplete()) {
+ aRv.ThrowInvalidAccessError(
+ "Can't access rules of still-loading style sheet");
+ return false;
+ }
+ //-- Security check: Only scripts whose principal subsumes that of the
+ // style sheet can access rule collections.
+ SubjectSubsumesInnerPrincipal(aSubjectPrincipal, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return false;
+ }
+ return true;
+}
+
+void StyleSheet::SetAssociatedDocumentOrShadowRoot(
+ DocumentOrShadowRoot* aDocOrShadowRoot) {
+ MOZ_ASSERT(!IsConstructed());
+ MOZ_ASSERT(!mParentSheet || !aDocOrShadowRoot,
+ "Shouldn't be set on child sheets");
+
+ // not ref counted
+ mDocumentOrShadowRoot = aDocOrShadowRoot;
+ UpdateRelevantGlobal();
+}
+
+void StyleSheet::AppendStyleSheet(StyleSheet& aSheet) {
+ WillDirty();
+ AppendStyleSheetSilently(aSheet);
+}
+
+void StyleSheet::AppendStyleSheetSilently(StyleSheet& aSheet) {
+ MOZ_ASSERT(!IsReadOnly());
+
+ Inner().mChildren.AppendElement(&aSheet);
+
+ // This is not reference counted. Our parent tells us when
+ // it's going away.
+ aSheet.mParentSheet = this;
+}
+
+size_t StyleSheet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ n += aMallocSizeOf(this);
+
+ // We want to measure the inner with only one of the children, and it makes
+ // sense for it to be the latest as it is the most likely to be reachable.
+ if (Inner().mSheets.LastElement() == this) {
+ n += Inner().SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mTitle
+ // - mMedia
+ // - mStyleSets
+ // - mRuleList
+
+ return n;
+}
+
+#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
+void StyleSheet::List(FILE* aOut, int32_t aIndent) {
+ for (StyleSheet* child : ChildSheets()) {
+ child->List(aOut, aIndent);
+ }
+
+ nsCString line;
+ for (int i = 0; i < aIndent; ++i) {
+ line.AppendLiteral(" ");
+ }
+
+ line.AppendLiteral("/* ");
+
+ nsCString url;
+ GetSheetURI()->GetSpec(url);
+ if (url.IsEmpty()) {
+ line.AppendLiteral("(no URL)");
+ } else {
+ line.Append(url);
+ }
+
+ line.AppendLiteral(" (");
+
+ switch (GetOrigin()) {
+ case StyleOrigin::UserAgent:
+ line.AppendLiteral("User Agent");
+ break;
+ case StyleOrigin::User:
+ line.AppendLiteral("User");
+ break;
+ case StyleOrigin::Author:
+ line.AppendLiteral("Author");
+ break;
+ }
+
+ if (mMedia) {
+ nsAutoCString buffer;
+ mMedia->GetText(buffer);
+
+ if (!buffer.IsEmpty()) {
+ line.AppendLiteral(", ");
+ line.Append(buffer);
+ }
+ }
+
+ line.AppendLiteral(") */");
+
+ fprintf_stderr(aOut, "%s\n\n", line.get());
+
+ nsCString newlineIndent;
+ newlineIndent.Append('\n');
+ for (int i = 0; i < aIndent; ++i) {
+ newlineIndent.AppendLiteral(" ");
+ }
+
+ ServoCSSRuleList* ruleList = GetCssRulesInternal();
+ for (uint32_t i = 0, len = ruleList->Length(); i < len; ++i) {
+ css::Rule* rule = ruleList->GetRule(i);
+
+ nsAutoCString cssText;
+ rule->GetCssText(cssText);
+ cssText.ReplaceSubstring("\n"_ns, newlineIndent);
+ fprintf_stderr(aOut, "%s\n", cssText.get());
+ }
+
+ if (ruleList->Length() != 0) {
+ fprintf_stderr(aOut, "\n");
+ }
+}
+#endif
+
+void StyleSheet::SetMedia(already_AddRefed<dom::MediaList> aMedia) {
+ mMedia = aMedia;
+ if (mMedia) {
+ mMedia->SetStyleSheet(this);
+ }
+}
+
+void StyleSheet::DropMedia() {
+ if (mMedia) {
+ mMedia->SetStyleSheet(nullptr);
+ mMedia = nullptr;
+ }
+}
+
+dom::MediaList* StyleSheet::Media() {
+ if (!mMedia) {
+ mMedia = dom::MediaList::Create(EmptyCString());
+ mMedia->SetStyleSheet(this);
+ }
+
+ return mMedia;
+}
+
+// nsWrapperCache
+
+JSObject* StyleSheet::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::CSSStyleSheet_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void StyleSheet::FixUpRuleListAfterContentsChangeIfNeeded(bool aFromClone) {
+ if (!mRuleList) {
+ return;
+ }
+
+ RefPtr<StyleLockedCssRules> rules =
+ Servo_StyleSheet_GetRules(Inner().mContents.get()).Consume();
+ mRuleList->SetRawContents(std::move(rules), aFromClone);
+}
+
+void StyleSheet::FixUpAfterInnerClone() {
+ MOZ_ASSERT(Inner().mSheets.Length() == 1, "Should've just cloned");
+ MOZ_ASSERT(Inner().mSheets[0] == this);
+ MOZ_ASSERT(Inner().mChildren.IsEmpty());
+
+ FixUpRuleListAfterContentsChangeIfNeeded(/* aFromClone = */ true);
+
+ RefPtr<StyleLockedCssRules> rules =
+ Servo_StyleSheet_GetRules(Inner().mContents.get()).Consume();
+ uint32_t index = 0;
+ while (true) {
+ uint32_t line, column; // Actually unused.
+ RefPtr<StyleLockedImportRule> import =
+ Servo_CssRules_GetImportRuleAt(rules, index, &line, &column).Consume();
+ if (!import) {
+ // Note that only @charset rules come before @import rules, and @charset
+ // rules are parsed but skipped, so we can stop iterating as soon as we
+ // find something that isn't an @import rule.
+ break;
+ }
+ auto* sheet = const_cast<StyleSheet*>(Servo_ImportRule_GetSheet(import));
+ MOZ_ASSERT(sheet);
+ AppendStyleSheetSilently(*sheet);
+ index++;
+ }
+}
+
+already_AddRefed<StyleSheet> StyleSheet::CreateEmptyChildSheet(
+ already_AddRefed<dom::MediaList> aMediaList) const {
+ RefPtr<StyleSheet> child =
+ new StyleSheet(ParsingMode(), CORSMode::CORS_NONE, SRIMetadata());
+
+ child->mMedia = aMediaList;
+ return child.forget();
+}
+
+// We disable parallel stylesheet parsing if the browser is recording CSS errors
+// (which parallel parsing can't handle).
+static bool AllowParallelParse(css::Loader& aLoader, URLExtraData* aUrlData) {
+ Document* doc = aLoader.GetDocument();
+ if (doc && css::ErrorReporter::ShouldReportErrors(*doc)) {
+ return false;
+ }
+ // Otherwise we can parse in parallel.
+ return true;
+}
+
+RefPtr<StyleSheetParsePromise> StyleSheet::ParseSheet(
+ css::Loader& aLoader, const nsACString& aBytes,
+ css::SheetLoadData& aLoadData) {
+ MOZ_ASSERT(mParsePromise.IsEmpty());
+ RefPtr<StyleSheetParsePromise> p = mParsePromise.Ensure(__func__);
+ if (!aLoadData.ShouldDefer()) {
+ mParsePromise.SetTaskPriority(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING,
+ __func__);
+ }
+ SetURLExtraData();
+
+ // @import rules are disallowed due to this decision:
+ // https://github.com/WICG/construct-stylesheets/issues/119#issuecomment-588352418
+ // We may allow @import rules again in the future.
+ auto allowImportRules = SelfOrAncestorIsConstructed()
+ ? StyleAllowImportRules::No
+ : StyleAllowImportRules::Yes;
+ URLExtraData* urlData = URLData();
+ const bool shouldRecordCounters =
+ aLoader.GetDocument() && aLoader.GetDocument()->GetStyleUseCounters() &&
+ !urlData->ChromeRulesEnabled();
+ if (!AllowParallelParse(aLoader, urlData)) {
+ UniquePtr<StyleUseCounters> counters;
+ if (shouldRecordCounters) {
+ counters.reset(Servo_UseCounters_Create());
+ }
+
+ RefPtr<StyleStylesheetContents> contents =
+ Servo_StyleSheet_FromUTF8Bytes(
+ &aLoader, this, &aLoadData, &aBytes, mParsingMode, urlData,
+ aLoadData.mCompatMode,
+ /* reusable_sheets = */ nullptr, counters.get(), allowImportRules,
+ StyleSanitizationKind::None,
+ /* sanitized_output = */ nullptr)
+ .Consume();
+ FinishAsyncParse(contents.forget(), std::move(counters));
+ } else {
+ auto holder = MakeRefPtr<css::SheetLoadDataHolder>(__func__, &aLoadData);
+ Servo_StyleSheet_FromUTF8BytesAsync(holder, urlData, &aBytes, mParsingMode,
+ aLoadData.mCompatMode,
+ shouldRecordCounters, allowImportRules);
+ }
+
+ return p;
+}
+
+void StyleSheet::FinishAsyncParse(
+ already_AddRefed<StyleStylesheetContents> aSheetContents,
+ UniquePtr<StyleUseCounters> aUseCounters) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mParsePromise.IsEmpty());
+ Inner().mContents = aSheetContents;
+ Inner().mUseCounters = std::move(aUseCounters);
+ FixUpRuleListAfterContentsChangeIfNeeded();
+ mParsePromise.Resolve(true, __func__);
+}
+
+void StyleSheet::ParseSheetSync(
+ css::Loader* aLoader, const nsACString& aBytes,
+ css::SheetLoadData* aLoadData,
+ css::LoaderReusableStyleSheets* aReusableSheets) {
+ const nsCompatibility compatMode = [&] {
+ if (aLoadData) {
+ return aLoadData->mCompatMode;
+ }
+ if (aLoader) {
+ return aLoader->CompatMode(css::StylePreloadKind::None);
+ }
+ return eCompatibility_FullStandards;
+ }();
+
+ SetURLExtraData();
+
+ URLExtraData* urlData = URLData();
+ const StyleUseCounters* useCounters =
+ aLoader && aLoader->GetDocument() && !urlData->ChromeRulesEnabled()
+ ? aLoader->GetDocument()->GetStyleUseCounters()
+ : nullptr;
+
+ auto allowImportRules = SelfOrAncestorIsConstructed()
+ ? StyleAllowImportRules::No
+ : StyleAllowImportRules::Yes;
+
+ Inner().mContents = Servo_StyleSheet_FromUTF8Bytes(
+ aLoader, this, aLoadData, &aBytes, mParsingMode,
+ urlData, compatMode, aReusableSheets, useCounters,
+ allowImportRules, StyleSanitizationKind::None,
+ /* sanitized_output = */ nullptr)
+ .Consume();
+}
+
+void StyleSheet::ReparseSheet(const nsACString& aInput, ErrorResult& aRv) {
+ if (!IsComplete()) {
+ return aRv.ThrowInvalidAccessError("Cannot reparse still-loading sheet");
+ }
+
+ // Allowing to modify UA sheets is dangerous (in the sense that C++ code
+ // relies on rules in those sheets), plus they're probably going to be shared
+ // across processes in which case this is directly a no-go.
+ if (IsReadOnly()) {
+ return;
+ }
+
+ // Hold strong ref to the CSSLoader in case the document update
+ // kills the document
+ RefPtr<css::Loader> loader;
+ if (Document* doc = GetAssociatedDocument()) {
+ loader = doc->CSSLoader();
+ NS_ASSERTION(loader, "Document with no CSS loader!");
+ } else {
+ loader = new css::Loader;
+ }
+
+ WillDirty();
+
+ // cache child sheets to reuse
+ css::LoaderReusableStyleSheets reusableSheets;
+ for (StyleSheet* child : ChildSheets()) {
+ if (child->GetOriginalURI()) {
+ reusableSheets.AddReusableSheet(child);
+ }
+ }
+
+ // Clean up child sheets list.
+ for (StyleSheet* child : ChildSheets()) {
+ child->mParentSheet = nullptr;
+ }
+ Inner().mChildren.Clear();
+
+ // Notify to the stylesets about the old rules going away.
+ {
+ ServoCSSRuleList* ruleList = GetCssRulesInternal();
+ MOZ_ASSERT(ruleList);
+
+ uint32_t ruleCount = ruleList->Length();
+ for (uint32_t i = 0; i < ruleCount; ++i) {
+ css::Rule* rule = ruleList->GetRule(i);
+ MOZ_ASSERT(rule);
+ RuleRemoved(*rule);
+ }
+
+ // We need to clear the rule list here (rather than after parsing) because
+ // ParseSheetSync may reuse child sheets, which would cause us to end up
+ // with a wrong mChilden array.
+ ruleList->SetRawContents(nullptr, /* aFromClone = */ false);
+ }
+
+ ParseSheetSync(loader, aInput, /* aLoadData = */ nullptr, &reusableSheets);
+
+ FixUpRuleListAfterContentsChangeIfNeeded();
+
+ // Notify the stylesets about the new rules.
+ {
+ // Get the rule list (which will need to be regenerated after ParseSheet).
+ ServoCSSRuleList* ruleList = GetCssRulesInternal();
+ MOZ_ASSERT(ruleList);
+
+ uint32_t ruleCount = ruleList->Length();
+ for (uint32_t i = 0; i < ruleCount; ++i) {
+ css::Rule* rule = ruleList->GetRule(i);
+ MOZ_ASSERT(rule);
+ RuleAdded(*rule);
+ }
+ }
+
+ // Our rules are no longer considered modified for devtools.
+ mState &= ~State::ModifiedRulesForDevtools;
+}
+
+void StyleSheet::DropRuleList() {
+ if (mRuleList) {
+ mRuleList->DropReferences();
+ mRuleList = nullptr;
+ }
+}
+
+already_AddRefed<StyleSheet> StyleSheet::Clone(
+ StyleSheet* aCloneParent,
+ dom::DocumentOrShadowRoot* aCloneDocumentOrShadowRoot) const {
+ MOZ_ASSERT(!IsConstructed(),
+ "Cannot create a non-constructed sheet from a constructed sheet");
+ RefPtr<StyleSheet> clone =
+ new StyleSheet(*this, aCloneParent, aCloneDocumentOrShadowRoot,
+ /* aConstructorDocToUse */ nullptr);
+ return clone.forget();
+}
+
+already_AddRefed<StyleSheet> StyleSheet::CloneAdoptedSheet(
+ Document& aConstructorDocument) const {
+ MOZ_ASSERT(IsConstructed(),
+ "Cannot create a constructed sheet from a non-constructed sheet");
+ MOZ_ASSERT(aConstructorDocument.IsStaticDocument(),
+ "Should never clone adopted sheets for a non-static document");
+ RefPtr<StyleSheet> clone = new StyleSheet(*this,
+ /* aParentSheetToUse */ nullptr,
+ /* aDocOrShadowRootToUse */ nullptr,
+ &aConstructorDocument);
+ return clone.forget();
+}
+
+ServoCSSRuleList* StyleSheet::GetCssRulesInternal() {
+ if (!mRuleList) {
+ // TODO(emilio): This should go away, but we need to fix the CC setup for
+ // @import rules first, see bug 1719963.
+ EnsureUniqueInner();
+
+ RefPtr<StyleLockedCssRules> rawRules =
+ Servo_StyleSheet_GetRules(Inner().mContents).Consume();
+ MOZ_ASSERT(rawRules);
+ mRuleList = new ServoCSSRuleList(rawRules.forget(), this, nullptr);
+ }
+ return mRuleList;
+}
+
+uint32_t StyleSheet::InsertRuleInternal(const nsACString& aRule,
+ uint32_t aIndex, ErrorResult& aRv) {
+ MOZ_ASSERT(!IsReadOnly());
+ MOZ_ASSERT(!ModificationDisallowed());
+
+ // Ensure mRuleList is constructed.
+ GetCssRulesInternal();
+
+ aRv = mRuleList->InsertRule(aRule, aIndex);
+ if (aRv.Failed()) {
+ return 0;
+ }
+
+ // XXX We may not want to get the rule when stylesheet change event
+ // is not enabled.
+ css::Rule* rule = mRuleList->GetRule(aIndex);
+ RuleAdded(*rule);
+
+ return aIndex;
+}
+
+void StyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv) {
+ MOZ_ASSERT(!IsReadOnly());
+ MOZ_ASSERT(!ModificationDisallowed());
+
+ // Ensure mRuleList is constructed.
+ GetCssRulesInternal();
+ if (aIndex >= mRuleList->Length()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Cannot delete rule at index %u"
+ " because the number of rules is only %u",
+ aIndex, mRuleList->Length()));
+ return;
+ }
+
+ // Hold a strong ref to the rule so it doesn't die when we remove it
+ // from the list. XXX We may not want to hold it if stylesheet change
+ // event is not enabled.
+ RefPtr<css::Rule> rule = mRuleList->GetRule(aIndex);
+ aRv = mRuleList->DeleteRule(aIndex);
+ if (!aRv.Failed()) {
+ RuleRemoved(*rule);
+ }
+}
+
+nsresult StyleSheet::InsertRuleIntoGroupInternal(const nsACString& aRule,
+ css::GroupRule* aGroup,
+ uint32_t aIndex) {
+ MOZ_ASSERT(!IsReadOnly());
+
+ ServoCSSRuleList* rules = aGroup->CssRules();
+ MOZ_ASSERT(rules && rules->GetParentRule() == aGroup);
+ return rules->InsertRule(aRule, aIndex);
+}
+
+StyleOrigin StyleSheet::GetOrigin() const {
+ return Servo_StyleSheet_GetOrigin(Inner().mContents);
+}
+
+void StyleSheet::SetSharedContents(const StyleLockedCssRules* aSharedRules) {
+ MOZ_ASSERT(!IsComplete());
+
+ SetURLExtraData();
+
+ Inner().mContents =
+ Servo_StyleSheet_FromSharedData(URLData(), aSharedRules).Consume();
+}
+
+const StyleLockedCssRules* StyleSheet::ToShared(
+ StyleSharedMemoryBuilder* aBuilder, nsCString& aErrorMessage) {
+ // Assert some things we assume when creating a StyleSheet using shared
+ // memory.
+ MOZ_ASSERT(GetReferrerInfo()->ReferrerPolicy() == ReferrerPolicy::_empty);
+ MOZ_ASSERT(GetReferrerInfo()->GetSendReferrer());
+ MOZ_ASSERT(!nsCOMPtr<nsIURI>(GetReferrerInfo()->GetComputedReferrer()));
+ MOZ_ASSERT(GetCORSMode() == CORS_NONE);
+ MOZ_ASSERT(Inner().mIntegrity.IsEmpty());
+ MOZ_ASSERT(Principal()->IsSystemPrincipal());
+
+ const StyleLockedCssRules* rules = Servo_SharedMemoryBuilder_AddStylesheet(
+ aBuilder, Inner().mContents, &aErrorMessage);
+
+#ifdef DEBUG
+ if (!rules) {
+ // Print the ToShmem error message so that developers know what to fix.
+ printf_stderr("%s\n", aErrorMessage.get());
+ MOZ_CRASH("UA style sheet contents failed shared memory requirements");
+ }
+#endif
+
+ return rules;
+}
+
+bool StyleSheet::IsReadOnly() const {
+ return IsComplete() && GetOrigin() == StyleOrigin::UserAgent;
+}
+
+} // namespace mozilla
diff --git a/layout/style/StyleSheet.h b/layout/style/StyleSheet.h
new file mode 100644
index 0000000000..1dbcad1e01
--- /dev/null
+++ b/layout/style/StyleSheet.h
@@ -0,0 +1,615 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_StyleSheet_h
+#define mozilla_StyleSheet_h
+
+#include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/dom/CSSStyleSheetBinding.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/ServoTypes.h"
+#include "mozilla/StyleSheetInfo.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsIPrincipal.h"
+#include "nsWrapperCache.h"
+#include "nsStringFwd.h"
+
+class nsIGlobalObject;
+class nsINode;
+class nsIPrincipal;
+struct StyleLockedCssRules;
+class nsIReferrerInfo;
+
+namespace mozilla {
+
+class ServoCSSRuleList;
+class ServoStyleSet;
+
+using StyleSheetParsePromise = MozPromise</* Dummy */ bool,
+ /* Dummy */ bool,
+ /* IsExclusive = */ true>;
+
+enum class StyleRuleChangeKind : uint32_t;
+
+namespace css {
+class GroupRule;
+class Loader;
+class LoaderReusableStyleSheets;
+class Rule;
+class SheetLoadData;
+} // namespace css
+
+namespace dom {
+class CSSImportRule;
+class CSSRuleList;
+class DocumentOrShadowRoot;
+class MediaList;
+class ShadowRoot;
+struct CSSStyleSheetInit;
+} // namespace dom
+
+enum class StyleSheetState : uint8_t {
+ // Whether the sheet is disabled. Sheets can be made disabled via CSSOM, or
+ // via alternate links and such.
+ Disabled = 1 << 0,
+ // Whether the sheet is complete. The sheet is complete if it's finished
+ // loading. See StyleSheet::SetComplete.
+ Complete = 1 << 1,
+ // Whether we've forced a unique inner. StyleSheet objects share an 'inner'
+ // StyleSheetInfo object if they share URL, CORS mode, etc.
+ //
+ // See the Loader's `mCompleteSheets` and `mLoadingSheets`.
+ ForcedUniqueInner = 1 << 2,
+ // Whether this stylesheet has suffered any modification to the rules via
+ // CSSOM.
+ ModifiedRules = 1 << 3,
+ // Same flag, but devtools clears it in some specific situations.
+ //
+ // Used to control whether devtools shows the rule in its authored form or
+ // not.
+ ModifiedRulesForDevtools = 1 << 4,
+ // Whether modifications to the sheet are currently disallowed.
+ // This flag is set during the async Replace() function to ensure
+ // that the sheet is not modified until the promise is resolved.
+ ModificationDisallowed = 1 << 5,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StyleSheetState)
+
+class StyleSheet final : public nsICSSLoaderObserver, public nsWrapperCache {
+ StyleSheet(const StyleSheet& aCopy, StyleSheet* aParentSheetToUse,
+ dom::DocumentOrShadowRoot* aDocOrShadowRootToUse,
+ dom::Document* aConstructorDocToUse);
+
+ virtual ~StyleSheet();
+
+ using State = StyleSheetState;
+
+ public:
+ StyleSheet(css::SheetParsingMode aParsingMode, CORSMode aCORSMode,
+ const dom::SRIMetadata& aIntegrity);
+
+ static already_AddRefed<StyleSheet> Constructor(const dom::GlobalObject&,
+ const dom::CSSStyleSheetInit&,
+ ErrorResult&);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(StyleSheet)
+
+ already_AddRefed<StyleSheet> CreateEmptyChildSheet(
+ already_AddRefed<dom::MediaList> aMediaList) const;
+
+ bool HasRules() const;
+
+ // Parses a stylesheet. The load data argument corresponds to the
+ // SheetLoadData for this stylesheet.
+ // NOTE: ParseSheet can run synchronously or asynchronously
+ // based on the result of `AllowParallelParse`
+ RefPtr<StyleSheetParsePromise> ParseSheet(css::Loader&,
+ const nsACString& aBytes,
+ css::SheetLoadData&);
+
+ // Common code that needs to be called after servo finishes parsing. This is
+ // shared between the parallel and sequential paths.
+ void FinishAsyncParse(already_AddRefed<StyleStylesheetContents>,
+ UniquePtr<StyleUseCounters>);
+
+ // Similar to `ParseSheet`, but guarantees that
+ // parsing will be performed synchronously.
+ // NOTE: ParseSheet can still run synchronously.
+ // This is not a strict alternative.
+ //
+ // The load data may be null sometimes.
+ void ParseSheetSync(
+ css::Loader* aLoader, const nsACString& aBytes,
+ css::SheetLoadData* aLoadData,
+ css::LoaderReusableStyleSheets* aReusableSheets = nullptr);
+
+ void ReparseSheet(const nsACString& aInput, ErrorResult& aRv);
+
+ const StyleStylesheetContents* RawContents() const {
+ return Inner().mContents;
+ }
+
+ const StyleUseCounters* GetStyleUseCounters() const {
+ return Inner().mUseCounters.get();
+ }
+
+ URLExtraData* URLData() const { return Inner().mURLData; }
+
+ // nsICSSLoaderObserver interface
+ NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) final;
+
+ // Internal GetCssRules methods which do not have security check and
+ // completeness check.
+ ServoCSSRuleList* GetCssRulesInternal();
+
+ // Returns the stylesheet's Servo origin as a StyleOrigin value.
+ StyleOrigin GetOrigin() const;
+
+ void SetOwningNode(nsINode* aOwningNode) { mOwningNode = aOwningNode; }
+
+ css::SheetParsingMode ParsingMode() const { return mParsingMode; }
+ dom::CSSStyleSheetParsingMode ParsingModeDOM();
+
+ /**
+ * Whether the sheet is complete.
+ */
+ bool IsComplete() const { return bool(mState & State::Complete); }
+
+ void SetComplete();
+
+ void SetEnabled(bool aEnabled) { SetDisabled(!aEnabled); }
+
+ // Whether the sheet is for an inline <style> element.
+ bool IsInline() const { return !GetOriginalURI(); }
+
+ nsIURI* GetSheetURI() const { return Inner().mSheetURI; }
+
+ /**
+ * Get the URI this sheet was originally loaded from, if any. Can return null.
+ */
+ nsIURI* GetOriginalURI() const { return Inner().mOriginalSheetURI; }
+
+ nsIURI* GetBaseURI() const { return Inner().mBaseURI; }
+
+ /**
+ * SetURIs must be called on all sheets before parsing into them.
+ * SetURIs may only be called while the sheet is 1) incomplete and 2)
+ * has no rules in it.
+ *
+ * FIXME(emilio): Can we pass this down when constructing the sheet instead?
+ */
+ inline void SetURIs(nsIURI* aSheetURI, nsIURI* aOriginalSheetURI,
+ nsIURI* aBaseURI);
+
+ /**
+ * Whether the sheet is applicable. A sheet that is not applicable
+ * should never be inserted into a style set. A sheet may not be
+ * applicable for a variety of reasons including being disabled and
+ * being incomplete.
+ */
+ bool IsApplicable() const { return !Disabled() && IsComplete(); }
+
+ already_AddRefed<StyleSheet> Clone(
+ StyleSheet* aCloneParent,
+ dom::DocumentOrShadowRoot* aCloneDocumentOrShadowRoot) const;
+
+ /**
+ * Creates a clone of the adopted style sheet as though it were constructed
+ * by aConstructorDocument. This should only be used for printing.
+ */
+ already_AddRefed<StyleSheet> CloneAdoptedSheet(
+ dom::Document& aConstructorDocument) const;
+
+ bool HasForcedUniqueInner() const {
+ return bool(mState & State::ForcedUniqueInner);
+ }
+
+ bool HasModifiedRules() const { return bool(mState & State::ModifiedRules); }
+
+ bool HasModifiedRulesForDevtools() const {
+ return bool(mState & State::ModifiedRulesForDevtools);
+ }
+
+ bool HasUniqueInner() const { return Inner().mSheets.Length() == 1; }
+
+ void AssertHasUniqueInner() const { MOZ_ASSERT(HasUniqueInner()); }
+
+ void EnsureUniqueInner();
+
+ // Returns the DocumentOrShadowRoot* that owns us, if any.
+ //
+ // TODO(emilio): Maybe rename to GetOwner*() or such? Might be
+ // confusing with nsINode::OwnerDoc and such.
+ dom::DocumentOrShadowRoot* GetAssociatedDocumentOrShadowRoot() const;
+
+ // Whether this stylesheet is kept alive by the associated or constructor
+ // document somehow, and thus at least has the same lifetime as
+ // GetAssociatedDocument().
+ dom::Document* GetKeptAliveByDocument() const;
+
+ // If this is a constructed style sheet, return mConstructorDocument.
+ // Otherwise return the document we're associated to,
+ // via mDocumentOrShadowRoot.
+ //
+ // Non-null iff GetAssociatedDocumentOrShadowRoot is non-null.
+ dom::Document* GetAssociatedDocument() const;
+
+ void SetAssociatedDocumentOrShadowRoot(dom::DocumentOrShadowRoot*);
+ void ClearAssociatedDocumentOrShadowRoot() {
+ SetAssociatedDocumentOrShadowRoot(nullptr);
+ }
+
+ nsINode* GetOwnerNode() const { return mOwningNode; }
+
+ nsINode* GetOwnerNodeOfOutermostSheet() const {
+ return OutermostSheet().GetOwnerNode();
+ }
+
+ StyleSheet* GetParentSheet() const { return mParentSheet; }
+
+ void AddReferencingRule(dom::CSSImportRule& aRule) {
+ MOZ_ASSERT(!mReferencingRules.Contains(&aRule));
+ mReferencingRules.AppendElement(&aRule);
+ }
+
+ void RemoveReferencingRule(dom::CSSImportRule& aRule) {
+ MOZ_ASSERT(mReferencingRules.Contains(&aRule));
+ mReferencingRules.RemoveElement(&aRule);
+ }
+
+ // Note that when exposed to script, this should always have a <= 1 length.
+ // CSSImportRule::GetStyleSheetForBindings takes care of that.
+ dom::CSSImportRule* GetOwnerRule() const {
+ return mReferencingRules.SafeElementAt(0);
+ }
+
+ void AppendStyleSheet(StyleSheet&);
+
+ // Append a stylesheet to the child list without calling WillDirty.
+ void AppendStyleSheetSilently(StyleSheet&);
+
+ const nsTArray<RefPtr<StyleSheet>>& ChildSheets() const {
+#ifdef DEBUG
+ for (StyleSheet* child : Inner().mChildren) {
+ MOZ_ASSERT(child->GetParentSheet());
+ MOZ_ASSERT(child->GetParentSheet()->mInner == mInner);
+ }
+#endif
+ return Inner().mChildren;
+ }
+
+ // Principal() never returns a null pointer.
+ nsIPrincipal* Principal() const { return Inner().mPrincipal; }
+
+ /**
+ * SetPrincipal should be called on all sheets before parsing into them.
+ * This can only be called once with a non-null principal.
+ *
+ * Calling this with a null pointer is allowed and is treated as a no-op.
+ *
+ * FIXME(emilio): Can we get this at construction time instead?
+ */
+ void SetPrincipal(nsIPrincipal* aPrincipal) {
+ StyleSheetInfo& info = Inner();
+ MOZ_ASSERT(!info.mPrincipalSet, "Should only set principal once");
+ if (aPrincipal) {
+ info.mPrincipal = aPrincipal;
+#ifdef DEBUG
+ info.mPrincipalSet = true;
+#endif
+ }
+ }
+
+ void SetTitle(const nsAString& aTitle) { mTitle = aTitle; }
+ void SetMedia(already_AddRefed<dom::MediaList> aMedia);
+
+ // Get this style sheet's CORS mode
+ CORSMode GetCORSMode() const { return Inner().mCORSMode; }
+
+ // Get this style sheet's ReferrerInfo
+ nsIReferrerInfo* GetReferrerInfo() const { return Inner().mReferrerInfo; }
+
+ // Set this style sheet's ReferrerInfo
+ void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ Inner().mReferrerInfo = aReferrerInfo;
+ }
+
+ // Get this style sheet's integrity metadata
+ void GetIntegrity(dom::SRIMetadata& aResult) const {
+ aResult = Inner().mIntegrity;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
+ void List(FILE* aOut = stdout, int32_t aIndex = 0);
+#endif
+
+ // WebIDL StyleSheet API
+ void GetType(nsAString& aType);
+ void GetHref(nsAString& aHref, ErrorResult& aRv);
+ // GetOwnerNode is defined above.
+ StyleSheet* GetParentStyleSheet() const { return GetParentSheet(); }
+ void GetTitle(nsAString& aTitle);
+ dom::MediaList* Media();
+ bool Disabled() const { return bool(mState & State::Disabled); }
+ void SetDisabled(bool aDisabled);
+
+ void GetSourceMapURL(nsACString&);
+ void SetSourceMapURL(nsCString&&);
+ void GetSourceURL(nsACString& aSourceURL);
+
+ // WebIDL CSSStyleSheet API
+ // Can't be inline because we can't include ImportRule here. And can't be
+ // called GetOwnerRule because that would be ambiguous with the ImportRule
+ // version.
+ css::Rule* GetDOMOwnerRule() const;
+ dom::CSSRuleList* GetCssRules(nsIPrincipal& aSubjectPrincipal, ErrorResult&);
+ uint32_t InsertRule(const nsACString& aRule, uint32_t aIndex,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
+ void DeleteRule(uint32_t aIndex, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+ int32_t AddRule(const nsACString& aSelector, const nsACString& aBlock,
+ const dom::Optional<uint32_t>& aIndex,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
+ already_AddRefed<dom::Promise> Replace(const nsACString& aText, ErrorResult&);
+ void ReplaceSync(const nsACString& aText, ErrorResult&);
+ bool ModificationDisallowed() const {
+ return bool(mState & State::ModificationDisallowed);
+ }
+
+ // Called before and after the asynchronous Replace() function
+ // to disable/re-enable modification while there is a pending promise.
+ void SetModificationDisallowed(bool aDisallowed) {
+ MOZ_ASSERT(IsConstructed());
+ MOZ_ASSERT(!IsReadOnly());
+ if (aDisallowed) {
+ mState |= State::ModificationDisallowed;
+ // Sheet will be re-set to complete when its rules are replaced
+ mState &= ~State::Complete;
+ if (!Disabled()) {
+ ApplicableStateChanged(false);
+ }
+ } else {
+ mState &= ~State::ModificationDisallowed;
+ }
+ }
+
+ // True if the sheet was created through the Constructable StyleSheets API
+ bool IsConstructed() const { return !!mConstructorDocument; }
+
+ // True if any of this sheet's ancestors were created through the
+ // Constructable StyleSheets API
+ bool SelfOrAncestorIsConstructed() const {
+ return OutermostSheet().IsConstructed();
+ }
+
+ // Ture if the sheet's constructor document matches the given document
+ bool ConstructorDocumentMatches(const dom::Document& aDocument) const {
+ return mConstructorDocument == &aDocument;
+ }
+
+ // Add a document or shadow root to the list of adopters.
+ // Adopters will be notified when styles are changed.
+ void AddAdopter(dom::DocumentOrShadowRoot& aAdopter) {
+ MOZ_ASSERT(IsConstructed());
+ MOZ_ASSERT(!mAdopters.Contains(&aAdopter));
+ mAdopters.AppendElement(&aAdopter);
+ }
+
+ // Remove a document or shadow root from the list of adopters.
+ void RemoveAdopter(dom::DocumentOrShadowRoot& aAdopter) {
+ // Cannot assert IsConstructed() because this can run after unlink.
+ mAdopters.RemoveElement(&aAdopter);
+ }
+
+ const nsTArray<dom::DocumentOrShadowRoot*>& SelfOrAncestorAdopters() const {
+ return OutermostSheet().mAdopters;
+ }
+
+ // WebIDL miscellaneous bits
+ inline dom::ParentObject GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ // Changes to sheets should be after a WillDirty call.
+ void WillDirty();
+
+ // Called when a rule changes from CSSOM.
+ //
+ // FIXME(emilio): This shouldn't allow null, but MediaList doesn't know about
+ // its owning media rule, plus it's used for the stylesheet media itself.
+ void RuleChanged(css::Rule*, StyleRuleChangeKind);
+
+ void AddStyleSet(ServoStyleSet* aStyleSet);
+ void DropStyleSet(ServoStyleSet* aStyleSet);
+
+ nsresult DeleteRuleFromGroup(css::GroupRule* aGroup, uint32_t aIndex);
+ nsresult InsertRuleIntoGroup(const nsACString& aRule, css::GroupRule* aGroup,
+ uint32_t aIndex);
+
+ // Find the ID of the owner inner window.
+ uint64_t FindOwningWindowInnerID() const;
+
+ // Copy the contents of this style sheet into the shared memory buffer managed
+ // by aBuilder. Returns the pointer into the buffer that the sheet contents
+ // were stored at. (The returned pointer is to an Arc<Locked<Rules>> value,
+ // or null, with a filled in aErrorMessage, on failure.)
+ const StyleLockedCssRules* ToShared(StyleSharedMemoryBuilder* aBuilder,
+ nsCString& aErrorMessage);
+
+ // Sets the contents of this style sheet to the specified aSharedRules
+ // pointer, which must be a pointer somewhere in the aSharedMemory buffer
+ // as previously returned by a ToShared() call.
+ void SetSharedContents(const StyleLockedCssRules* aSharedRules);
+
+ // Whether this style sheet should not allow any modifications.
+ //
+ // This is true for any User Agent sheets once they are complete.
+ bool IsReadOnly() const;
+
+ // Removes a stylesheet from its parent sheet child list, if any.
+ void RemoveFromParent();
+
+ // Resolves mReplacePromise with this sheet.
+ void MaybeResolveReplacePromise();
+
+ // Rejects mReplacePromise with a NetworkError.
+ void MaybeRejectReplacePromise();
+
+ // Gets the relevant global if exists.
+ nsISupports* GetRelevantGlobal() const;
+
+ private:
+ void SetModifiedRules() {
+ mState |= State::ModifiedRules | State::ModifiedRulesForDevtools;
+ }
+
+ const StyleSheet& OutermostSheet() const {
+ const auto* current = this;
+ while (current->mParentSheet) {
+ MOZ_ASSERT(!current->mDocumentOrShadowRoot,
+ "Shouldn't be set on child sheets");
+ MOZ_ASSERT(!current->mConstructorDocument,
+ "Shouldn't be set on child sheets");
+ current = current->mParentSheet;
+ }
+ return *current;
+ }
+
+ StyleSheetInfo& Inner() {
+ MOZ_ASSERT(mInner);
+ return *mInner;
+ }
+
+ const StyleSheetInfo& Inner() const {
+ MOZ_ASSERT(mInner);
+ return *mInner;
+ }
+
+ // Check if the rules are available for read and write.
+ // It does the security check as well as whether the rules have been
+ // completely loaded. aRv will have an exception set if this function
+ // returns false.
+ bool AreRulesAvailable(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
+
+ void SetURLExtraData();
+
+ protected:
+ // Internal methods which do not have security check and completeness check.
+ uint32_t InsertRuleInternal(const nsACString& aRule, uint32_t aIndex,
+ ErrorResult&);
+ void DeleteRuleInternal(uint32_t aIndex, ErrorResult&);
+ nsresult InsertRuleIntoGroupInternal(const nsACString& aRule,
+ css::GroupRule* aGroup, uint32_t aIndex);
+
+ // Take the recently cloned sheets from the `@import` rules, and reparent them
+ // correctly to `aPrimarySheet`.
+ void FixUpAfterInnerClone();
+
+ // aFromClone says whether this comes from a clone of the stylesheet (and thus
+ // we should also fix up the wrappers for the individual rules in the rule
+ // lists).
+ void FixUpRuleListAfterContentsChangeIfNeeded(bool aFromClone = false);
+
+ void DropRuleList();
+
+ // Called when a rule is removed from the sheet from CSSOM.
+ void RuleAdded(css::Rule&);
+
+ // Called when a rule is added to the sheet from CSSOM.
+ void RuleRemoved(css::Rule&);
+
+ // Called when a stylesheet is cloned.
+ void StyleSheetCloned(StyleSheet&);
+
+ // Notifies that the applicable state changed.
+ // aApplicable is the value that we expect to get from IsApplicable().
+ // assertion will fail if the expectation does not match reality.
+ void ApplicableStateChanged(bool aApplicable);
+
+ void LastRelease();
+
+ // Return success if the subject principal subsumes the principal of our
+ // inner, error otherwise. This will also succeed if access is allowed by
+ // CORS. In that case, it will set the principal of the inner to the
+ // subject principal.
+ void SubjectSubsumesInnerPrincipal(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ // Drop our reference to mMedia
+ void DropMedia();
+ // Set our relevant global if needed.
+ void UpdateRelevantGlobal();
+ // Unlink our inner, if needed, for cycle collection.
+ void UnlinkInner();
+ // Traverse our inner, if needed, for cycle collection
+ void TraverseInner(nsCycleCollectionTraversalCallback&);
+
+ // Return whether the given @import rule has pending child sheet.
+ static bool RuleHasPendingChildSheet(css::Rule* aRule);
+
+ StyleSheet* mParentSheet; // weak ref
+
+ // A pointer to the sheet's relevant global object. This is populated when the
+ // sheet gets an associated document and is complete.
+ //
+ // This is required for the sheet to be able to create a promise.
+ // https://html.spec.whatwg.org/#concept-relevant-everything
+ nsCOMPtr<nsIGlobalObject> mRelevantGlobal;
+
+ RefPtr<dom::Document> mConstructorDocument;
+
+ // Will be set in the Replace() function and resolved/rejected by the
+ // sheet once its rules have been replaced and the sheet is complete again.
+ RefPtr<dom::Promise> mReplacePromise;
+
+ nsString mTitle;
+
+ // weak ref; parents maintain this for their children
+ dom::DocumentOrShadowRoot* mDocumentOrShadowRoot;
+ nsINode* mOwningNode = nullptr; // weak ref
+ nsTArray<dom::CSSImportRule*> mReferencingRules; // weak ref
+
+ RefPtr<dom::MediaList> mMedia;
+
+ // mParsingMode controls access to nonstandard style constructs that
+ // are not safe for use on the public Web but necessary in UA sheets
+ // and/or useful in user sheets.
+ //
+ // FIXME(emilio): Given we store the parsed contents in the Inner, this should
+ // probably also move there.
+ css::SheetParsingMode mParsingMode;
+
+ State mState;
+
+ // Core information we get from parsed sheets, which are shared amongst
+ // StyleSheet clones.
+ //
+ // Always nonnull until LastRelease().
+ StyleSheetInfo* mInner;
+
+ nsTArray<ServoStyleSet*> mStyleSets;
+
+ RefPtr<ServoCSSRuleList> mRuleList;
+
+ MozPromiseHolder<StyleSheetParsePromise> mParsePromise;
+
+ nsTArray<dom::DocumentOrShadowRoot*> mAdopters;
+
+ // Make StyleSheetInfo and subclasses into friends so they can use
+ // ChildSheetListBuilder.
+ friend struct StyleSheetInfo;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_StyleSheet_h
diff --git a/layout/style/StyleSheetInfo.h b/layout/style/StyleSheetInfo.h
new file mode 100644
index 0000000000..f7ba20b2b9
--- /dev/null
+++ b/layout/style/StyleSheetInfo.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StyleSheetInfo_h
+#define mozilla_StyleSheetInfo_h
+
+#include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/CORSMode.h"
+
+#include "nsIReferrerInfo.h"
+
+class nsIPrincipal;
+class nsIURI;
+
+namespace mozilla {
+class StyleSheet;
+struct StyleUseCounters;
+struct StyleStylesheetContents;
+struct URLExtraData;
+
+/**
+ * Struct for data common to CSSStyleSheetInner and ServoStyleSheet.
+ */
+struct StyleSheetInfo final {
+ using ReferrerPolicy = dom::ReferrerPolicy;
+
+ StyleSheetInfo(CORSMode aCORSMode, const dom::SRIMetadata& aIntegrity,
+ css::SheetParsingMode aParsingMode);
+
+ // FIXME(emilio): aCopy should be const.
+ StyleSheetInfo(StyleSheetInfo& aCopy, StyleSheet* aPrimarySheet);
+
+ ~StyleSheetInfo();
+
+ StyleSheetInfo* CloneFor(StyleSheet* aPrimarySheet);
+
+ void AddSheet(StyleSheet* aSheet);
+ void RemoveSheet(StyleSheet* aSheet);
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ // FIXME(emilio): most of this struct should be const, then we can remove the
+ // duplication with the UrlExtraData member and such.
+ nsCOMPtr<nsIURI> mSheetURI; // for error reports, etc.
+ nsCOMPtr<nsIURI> mOriginalSheetURI; // for GetHref. Can be null.
+ nsCOMPtr<nsIURI> mBaseURI; // for resolving relative URIs
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ const CORSMode mCORSMode;
+ // The ReferrerInfo of a stylesheet is used for its child sheets and loads
+ // come from this stylesheet, so it is stored here.
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ dom::SRIMetadata mIntegrity;
+
+ // Pointer to the list of child sheets. This is all fundamentally broken,
+ // because each of the child sheets has a unique parent... We can only hope
+ // (and currently this is the case) that any time page JS can get its hands on
+ // a child sheet that means we've already ensured unique infos throughout its
+ // parent chain and things are good.
+ nsTArray<RefPtr<StyleSheet>> mChildren;
+
+ AutoTArray<StyleSheet*, 8> mSheets;
+
+ // If a SourceMap or X-SourceMap response header is seen, this is
+ // the value. If both are seen, SourceMap is preferred. If neither
+ // is seen, this will be an empty string.
+ nsCString mSourceMapURL;
+
+ RefPtr<const StyleStylesheetContents> mContents;
+
+ UniquePtr<StyleUseCounters> mUseCounters;
+
+ // XXX We already have mSheetURI, mBaseURI, and mPrincipal.
+ //
+ // Can we somehow replace them with URLExtraData directly? The issue
+ // is currently URLExtraData is immutable, but URIs in StyleSheetInfo
+ // seems to be mutable, so we probably cannot set them altogether.
+ // Also, this is mostly a duplicate reference of the same url data
+ // inside RawServoStyleSheet. We may want to just use that instead.
+ RefPtr<URLExtraData> mURLData;
+
+#ifdef DEBUG
+ bool mPrincipalSet = false;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // mozilla_StyleSheetInfo_h
diff --git a/layout/style/StyleSheetInlines.h b/layout/style/StyleSheetInlines.h
new file mode 100644
index 0000000000..54cbba855f
--- /dev/null
+++ b/layout/style/StyleSheetInlines.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_StyleSheetInlines_h
+#define mozilla_StyleSheetInlines_h
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/StyleSheet.h"
+#include "nsIGlobalObject.h"
+#include "nsINode.h"
+
+namespace mozilla {
+
+void StyleSheet::SetURIs(nsIURI* aSheetURI, nsIURI* aOriginalSheetURI,
+ nsIURI* aBaseURI) {
+ MOZ_ASSERT(aSheetURI && aBaseURI, "null ptr");
+ MOZ_ASSERT(!HasRules() && !IsComplete(),
+ "Can't call SetURIs on sheets that are complete or have rules");
+ StyleSheetInfo& info = Inner();
+ info.mSheetURI = aSheetURI;
+ info.mOriginalSheetURI = aOriginalSheetURI;
+ info.mBaseURI = aBaseURI;
+}
+
+dom::ParentObject StyleSheet::GetParentObject() const {
+ if (mRelevantGlobal) {
+ return dom::ParentObject(mRelevantGlobal);
+ }
+ if (IsConstructed()) {
+ return dom::ParentObject(mConstructorDocument.get());
+ }
+ if (mOwningNode) {
+ return dom::ParentObject(mOwningNode);
+ }
+ return dom::ParentObject(mParentSheet);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_StyleSheetInlines_h
diff --git a/layout/style/TimelineCollection.cpp b/layout/style/TimelineCollection.cpp
new file mode 100644
index 0000000000..d4b0815433
--- /dev/null
+++ b/layout/style/TimelineCollection.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TimelineCollection.h"
+
+#include "mozilla/ElementAnimationData.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScrollTimeline.h"
+#include "mozilla/dom/ViewTimeline.h"
+#include <type_traits>
+
+namespace mozilla {
+
+template <class TimelineType>
+TimelineCollection<TimelineType>::~TimelineCollection() {
+ MOZ_COUNT_DTOR(TimelineCollection);
+
+ // FIXME: Bug 1774060. We have to restyle the animations which use the
+ // timelines associtated with this TimelineCollection.
+
+ LinkedListElement<SelfType>::remove();
+}
+
+template <class TimelineType>
+void TimelineCollection<TimelineType>::Destroy() {
+ auto* data = mElement.GetAnimationData();
+ MOZ_ASSERT(data);
+ if constexpr (std::is_same_v<TimelineType, dom::ScrollTimeline>) {
+ MOZ_ASSERT(data->GetScrollTimelineCollection(mPseudo) == this);
+ data->ClearScrollTimelineCollectionFor(mPseudo);
+ } else if constexpr (std::is_same_v<TimelineType, dom::ViewTimeline>) {
+ MOZ_ASSERT(data->GetViewTimelineCollection(mPseudo) == this);
+ data->ClearViewTimelineCollectionFor(mPseudo);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unsupported TimelienType");
+ }
+}
+
+template <class TimelineType>
+/* static */ TimelineCollection<TimelineType>*
+TimelineCollection<TimelineType>::Get(const dom::Element* aElement,
+ const PseudoStyleType aPseudoType) {
+ MOZ_ASSERT(aElement);
+ auto* data = aElement->GetAnimationData();
+ if (!data) {
+ return nullptr;
+ }
+
+ if constexpr (std::is_same_v<TimelineType, dom::ScrollTimeline>) {
+ return data->GetScrollTimelineCollection(aPseudoType);
+ }
+
+ if constexpr (std::is_same_v<TimelineType, dom::ViewTimeline>) {
+ return data->GetViewTimelineCollection(aPseudoType);
+ }
+
+ return nullptr;
+}
+
+// Explicit class instantiations
+template class TimelineCollection<dom::ScrollTimeline>;
+template class TimelineCollection<dom::ViewTimeline>;
+
+} // namespace mozilla
diff --git a/layout/style/TimelineCollection.h b/layout/style/TimelineCollection.h
new file mode 100644
index 0000000000..21e0b2b6bc
--- /dev/null
+++ b/layout/style/TimelineCollection.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TimelineCollection_h
+#define mozilla_TimelineCollection_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "nsAtomHashKeys.h"
+#include "nsTHashMap.h"
+
+class nsAtom;
+
+namespace mozilla {
+namespace dom {
+class Element;
+}
+enum class PseudoStyleType : uint8_t;
+
+// The collection of ScrollTimeline or ViewTimeline. We use the template class
+// to share the implementation for these two timeline types.
+template <class TimelineType>
+class TimelineCollection final
+ : public LinkedListElement<TimelineCollection<TimelineType>> {
+ public:
+ using SelfType = TimelineCollection<TimelineType>;
+ using TimelineMap = nsTHashMap<RefPtr<nsAtom>, RefPtr<TimelineType>>;
+
+ TimelineCollection(dom::Element& aElement, PseudoStyleType aPseudoType)
+ : mElement(aElement), mPseudo(aPseudoType) {
+ MOZ_COUNT_CTOR(TimelineCollection);
+ }
+
+ ~TimelineCollection();
+
+ already_AddRefed<TimelineType> Lookup(nsAtom* aName) const {
+ return mTimelines.Get(aName).forget();
+ }
+
+ already_AddRefed<TimelineType> Extract(nsAtom* aName) {
+ Maybe<RefPtr<TimelineType>> timeline = mTimelines.Extract(aName);
+ return timeline ? timeline->forget() : nullptr;
+ }
+
+ void Swap(TimelineMap& aValue) { mTimelines.SwapElements(aValue); }
+
+ void Destroy();
+
+ // Get the collection of timelines for the given |aElement| and or create it
+ // if it does not already exist.
+ static TimelineCollection* Get(const dom::Element* aElement,
+ PseudoStyleType aPseudoType);
+ const TimelineMap& Timelines() const { return mTimelines; }
+
+ private:
+ // The element. Weak reference is fine since it owns us.
+ dom::Element& mElement;
+ const PseudoStyleType mPseudo;
+
+ TimelineMap mTimelines;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TimelineCollection_h
diff --git a/layout/style/TimelineManager.cpp b/layout/style/TimelineManager.cpp
new file mode 100644
index 0000000000..4adc03df17
--- /dev/null
+++ b/layout/style/TimelineManager.cpp
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TimelineManager.h"
+
+#include "mozilla/ElementAnimationData.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScrollTimeline.h"
+#include "mozilla/dom/ViewTimeline.h"
+#include "nsPresContext.h"
+
+namespace mozilla {
+using dom::Element;
+using dom::ScrollTimeline;
+using dom::ViewTimeline;
+
+template <typename TimelineType>
+void TryDestroyTimeline(Element* aElement, PseudoStyleType aPseudoType) {
+ auto* collection =
+ TimelineCollection<TimelineType>::Get(aElement, aPseudoType);
+ if (!collection) {
+ return;
+ }
+ collection->Destroy();
+}
+
+void TimelineManager::UpdateTimelines(Element* aElement,
+ PseudoStyleType aPseudoType,
+ const ComputedStyle* aComputedStyle,
+ ProgressTimelineType aType) {
+ MOZ_ASSERT(
+ aElement->IsInComposedDoc(),
+ "No need to update timelines that are not attached to the document tree");
+
+ // If we are in a display:none subtree we will have no computed values.
+ // However, if we are on the root of display:none subtree, the computed values
+ // might not have been cleared yet. In either case, since CSS animations
+ // should not run in display:none subtrees, so we don't need timeline, either.
+ const bool shouldDestroyTimelines =
+ !aComputedStyle ||
+ aComputedStyle->StyleDisplay()->mDisplay == StyleDisplay::None;
+
+ switch (aType) {
+ case ProgressTimelineType::Scroll:
+ if (shouldDestroyTimelines) {
+ TryDestroyTimeline<ScrollTimeline>(aElement, aPseudoType);
+ return;
+ }
+ DoUpdateTimelines<StyleScrollTimeline, ScrollTimeline>(
+ mPresContext, aElement, aPseudoType,
+ aComputedStyle->StyleUIReset()->mScrollTimelines,
+ aComputedStyle->StyleUIReset()->mScrollTimelineNameCount);
+ break;
+
+ case ProgressTimelineType::View:
+ if (shouldDestroyTimelines) {
+ TryDestroyTimeline<ViewTimeline>(aElement, aPseudoType);
+ return;
+ }
+ DoUpdateTimelines<StyleViewTimeline, ViewTimeline>(
+ mPresContext, aElement, aPseudoType,
+ aComputedStyle->StyleUIReset()->mViewTimelines,
+ aComputedStyle->StyleUIReset()->mViewTimelineNameCount);
+ break;
+ }
+}
+
+template <typename TimelineType>
+static already_AddRefed<TimelineType> PopExistingTimeline(
+ nsAtom* aName, TimelineCollection<TimelineType>* aCollection) {
+ if (!aCollection) {
+ return nullptr;
+ }
+ return aCollection->Extract(aName);
+}
+
+template <typename StyleType, typename TimelineType>
+static auto BuildTimelines(nsPresContext* aPresContext, Element* aElement,
+ PseudoStyleType aPseudoType,
+ const nsStyleAutoArray<StyleType>& aTimelines,
+ size_t aTimelineCount,
+ TimelineCollection<TimelineType>* aCollection) {
+ typename TimelineCollection<TimelineType>::TimelineMap result;
+ // If multiple timelines are attempting to modify the same property, then the
+ // timeline closest to the end of the list of names wins.
+ // The spec doesn't mention this specifically for scroll-timeline-name and
+ // view-timeline-name, so we follow the same rule with animation-name.
+ for (size_t idx = 0; idx < aTimelineCount; ++idx) {
+ const StyleType& timeline = aTimelines[idx];
+ if (timeline.GetName() == nsGkAtoms::_empty) {
+ continue;
+ }
+
+ RefPtr<TimelineType> dest =
+ PopExistingTimeline(timeline.GetName(), aCollection);
+ if (dest) {
+ dest->ReplacePropertiesWith(aElement, aPseudoType, timeline);
+ } else {
+ dest = TimelineType::MakeNamed(aPresContext->Document(), aElement,
+ aPseudoType, timeline);
+ }
+ MOZ_ASSERT(dest);
+
+ // Override the previous one if it is duplicated.
+ Unused << result.InsertOrUpdate(timeline.GetName(), dest);
+ }
+ return result;
+}
+
+template <typename TimelineType>
+static TimelineCollection<TimelineType>& EnsureTimelineCollection(
+ Element& aElement, PseudoStyleType aPseudoType);
+
+template <>
+ScrollTimelineCollection& EnsureTimelineCollection<ScrollTimeline>(
+ Element& aElement, PseudoStyleType aPseudoType) {
+ return aElement.EnsureAnimationData().EnsureScrollTimelineCollection(
+ aElement, aPseudoType);
+}
+
+template <>
+ViewTimelineCollection& EnsureTimelineCollection<ViewTimeline>(
+ Element& aElement, PseudoStyleType aPseudoType) {
+ return aElement.EnsureAnimationData().EnsureViewTimelineCollection(
+ aElement, aPseudoType);
+}
+
+template <typename StyleType, typename TimelineType>
+void TimelineManager::DoUpdateTimelines(
+ nsPresContext* aPresContext, Element* aElement, PseudoStyleType aPseudoType,
+ const nsStyleAutoArray<StyleType>& aStyleTimelines, size_t aTimelineCount) {
+ auto* collection =
+ TimelineCollection<TimelineType>::Get(aElement, aPseudoType);
+ if (!collection && aTimelineCount == 1 &&
+ aStyleTimelines[0].GetName() == nsGkAtoms::_empty) {
+ return;
+ }
+
+ // We create a new timeline list based on its computed style and the existing
+ // timelines.
+ auto newTimelines = BuildTimelines<StyleType, TimelineType>(
+ aPresContext, aElement, aPseudoType, aStyleTimelines, aTimelineCount,
+ collection);
+
+ if (newTimelines.IsEmpty()) {
+ if (collection) {
+ collection->Destroy();
+ }
+ return;
+ }
+
+ if (!collection) {
+ collection =
+ &EnsureTimelineCollection<TimelineType>(*aElement, aPseudoType);
+ if (!collection->isInList()) {
+ AddTimelineCollection(collection);
+ }
+ }
+
+ // Replace unused timeline with new ones.
+ collection->Swap(newTimelines);
+
+ // FIXME: Bug 1774060. We may have to restyle the animations which use the
+ // dropped timelines. Or rely on restyling the subtree and the following
+ // siblings when mutating {scroll|view}-timeline-name.
+}
+
+void TimelineManager::UpdateHiddenByContentVisibilityForAnimations() {
+ for (auto* scrollTimelineCollection : mScrollTimelineCollections) {
+ for (ScrollTimeline* timeline :
+ scrollTimelineCollection->Timelines().Values()) {
+ timeline->UpdateHiddenByContentVisibility();
+ }
+ }
+
+ for (auto* viewTimelineCollection : mViewTimelineCollections) {
+ for (ViewTimeline* timeline :
+ viewTimelineCollection->Timelines().Values()) {
+ timeline->UpdateHiddenByContentVisibility();
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/style/TimelineManager.h b/layout/style/TimelineManager.h
new file mode 100644
index 0000000000..3fc31a719c
--- /dev/null
+++ b/layout/style/TimelineManager.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_TimelineManager_h
+#define mozilla_TimelineManager_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/TimelineCollection.h"
+#include "nsStyleAutoArray.h"
+
+class nsPresContext;
+
+namespace mozilla {
+class ComputedStyle;
+enum class PseudoStyleType : uint8_t;
+
+namespace dom {
+class Element;
+class ScrollTimeline;
+class ViewTimeline;
+} // namespace dom
+
+class TimelineManager {
+ public:
+ explicit TimelineManager(nsPresContext* aPresContext)
+ : mPresContext(aPresContext) {}
+
+ ~TimelineManager() {
+ MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
+ }
+
+ void Disconnect() {
+ while (auto* head = mScrollTimelineCollections.getFirst()) {
+ head->Destroy();
+ }
+ while (auto* head = mViewTimelineCollections.getFirst()) {
+ head->Destroy();
+ }
+
+ mPresContext = nullptr;
+ }
+
+ enum class ProgressTimelineType : uint8_t {
+ Scroll,
+ View,
+ };
+ void UpdateTimelines(dom::Element* aElement, PseudoStyleType aPseudoType,
+ const ComputedStyle* aComputedStyle,
+ ProgressTimelineType aType);
+
+ void UpdateHiddenByContentVisibilityForAnimations();
+
+ private:
+ template <typename StyleType, typename TimelineType>
+ void DoUpdateTimelines(nsPresContext* aPresContext, dom::Element* aElement,
+ PseudoStyleType aPseudoType,
+ const nsStyleAutoArray<StyleType>& aStyleTimelines,
+ size_t aTimelineCount);
+
+ template <typename T>
+ void AddTimelineCollection(TimelineCollection<T>* aCollection);
+
+ LinkedList<TimelineCollection<dom::ScrollTimeline>>
+ mScrollTimelineCollections;
+ LinkedList<TimelineCollection<dom::ViewTimeline>> mViewTimelineCollections;
+ nsPresContext* mPresContext;
+};
+
+template <>
+inline void TimelineManager::AddTimelineCollection(
+ TimelineCollection<dom::ScrollTimeline>* aCollection) {
+ mScrollTimelineCollections.insertBack(aCollection);
+}
+
+template <>
+inline void TimelineManager::AddTimelineCollection(
+ TimelineCollection<dom::ViewTimeline>* aCollection) {
+ mViewTimelineCollections.insertBack(aCollection);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_TimelineManager_h
diff --git a/layout/style/TopLevelImageDocument.css b/layout/style/TopLevelImageDocument.css
new file mode 100644
index 0000000000..f03295f673
--- /dev/null
+++ b/layout/style/TopLevelImageDocument.css
@@ -0,0 +1,49 @@
+/* -*- 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 CSS stylesheet defines the rules to be applied to ImageDocuments that
+ are top level (e.g. not iframes).
+*/
+
+@media not print {
+ :root {
+ /* The font color here was chosen to be readable over the corresponding
+ backgrounds. This is important in case this ImageDocument is for an
+ image that happens to be corrupt, in which case we'll display a textual
+ error message over the background, instead of the image itself. */
+ color: #eee;
+ /* The background-attachment is fixed to stop an ugly white gutter
+ from appearing when the document is overscrolled. */
+ background: url("chrome://global/skin/media/imagedoc-darknoise.png") fixed;
+ }
+
+ img.transparent {
+ color: #222;
+ background: hsl(0,0%,90%) url("chrome://global/skin/media/imagedoc-lightnoise.png");
+ }
+
+ img {
+ text-align: center;
+ position: absolute;
+ inset: 0;
+ margin: auto;
+ }
+
+ img.overflowingVertical {
+ /* If we're overflowing vertically, we need to set margin-top to
+ 0. Otherwise we'll end up trying to vertically center, and end
+ up cutting off the top part of the image. */
+ margin-top: 0;
+ }
+
+ .completeRotation {
+ transition: transform 0.3s ease 0s;
+ }
+}
+
+img {
+ image-orientation: from-image;
+}
diff --git a/layout/style/TopLevelVideoDocument.css b/layout/style/TopLevelVideoDocument.css
new file mode 100644
index 0000000000..2c1f903106
--- /dev/null
+++ b/layout/style/TopLevelVideoDocument.css
@@ -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/. */
+
+/*
+ This CSS stylesheet defines the rules to be applied to VideoDocuments that
+ are top level (e.g. not iframes).
+*/
+
+:root {
+ background-color: black;
+ /* Fill the viewport height, so that our '-moz-user-focus' styling will
+ disregard clicks in the whole background area (so the video element
+ doesn't inadvertently lose focus from a stray click on the background). */
+ height: 100%;
+ -moz-user-focus: ignore;
+}
+
+video {
+ position: absolute;
+ inset: 0;
+ margin: auto;
+ max-width: 100%;
+ max-height: 100%;
+ user-select: none;
+ -moz-user-focus: normal;
+}
+
+video:focus {
+ outline-style: none;
+}
diff --git a/layout/style/URLExtraData.cpp b/layout/style/URLExtraData.cpp
new file mode 100644
index 0000000000..730576cad3
--- /dev/null
+++ b/layout/style/URLExtraData.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* thread-safe container of information for resolving url values */
+
+#include "mozilla/URLExtraData.h"
+
+#include "mozilla/NullPrincipal.h"
+#include "nsAboutProtocolUtils.h"
+#include "ReferrerInfo.h"
+
+namespace mozilla {
+
+StaticRefPtr<URLExtraData> URLExtraData::sDummy;
+StaticRefPtr<URLExtraData> URLExtraData::sDummyChrome;
+
+/* static */
+void URLExtraData::Init() {
+ RefPtr<nsIURI> baseURI = NullPrincipal::CreateURI();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(nullptr);
+ sDummy = new URLExtraData(do_AddRef(baseURI), do_AddRef(referrerInfo),
+ NullPrincipal::CreateWithoutOriginAttributes());
+
+ sDummyChrome =
+ new URLExtraData(baseURI.forget(), referrerInfo.forget(),
+ NullPrincipal::CreateWithoutOriginAttributes());
+ sDummyChrome->mChromeRulesEnabled = true;
+}
+
+bool URLExtraData::ChromeRulesEnabled(nsIURI* aURI) {
+ if (!aURI) {
+ return false;
+ }
+ return aURI->SchemeIs("chrome") || aURI->SchemeIs("resource") ||
+ (aURI->SchemeIs("about") && !NS_IsContentAccessibleAboutURI(aURI));
+}
+
+/* static */
+void URLExtraData::Shutdown() {
+ sDummy = nullptr;
+ sDummyChrome = nullptr;
+}
+
+URLExtraData::~URLExtraData() = default;
+
+StaticRefPtr<URLExtraData>
+ URLExtraData::sShared[size_t(UserAgentStyleSheetID::Count)];
+
+} // namespace mozilla
diff --git a/layout/style/URLExtraData.h b/layout/style/URLExtraData.h
new file mode 100644
index 0000000000..c63cbc4ca1
--- /dev/null
+++ b/layout/style/URLExtraData.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* thread-safe container of information for resolving url values */
+
+#ifndef mozilla_URLExtraData_h
+#define mozilla_URLExtraData_h
+
+#include <utility>
+
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UserAgentStyleSheetID.h"
+#include "nsCOMPtr.h"
+#include "nsIPrincipal.h"
+#include "nsIReferrerInfo.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+
+struct URLExtraData {
+ static bool ChromeRulesEnabled(nsIURI* aURI);
+
+ URLExtraData(already_AddRefed<nsIURI> aBaseURI,
+ already_AddRefed<nsIReferrerInfo> aReferrerInfo,
+ already_AddRefed<nsIPrincipal> aPrincipal)
+ : mBaseURI(std::move(aBaseURI)),
+ mReferrerInfo(std::move(aReferrerInfo)),
+ mPrincipal(std::move(aPrincipal)) {
+ MOZ_ASSERT(mBaseURI);
+ MOZ_ASSERT(mPrincipal);
+ MOZ_ASSERT(mReferrerInfo);
+ // When we hold the URI data of a style sheet, referrer is always
+ // equal to the sheet URI.
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetOriginalReferrer();
+ mChromeRulesEnabled = ChromeRulesEnabled(referrer);
+ }
+
+ URLExtraData(nsIURI* aBaseURI, nsIReferrerInfo* aReferrerInfo,
+ nsIPrincipal* aPrincipal)
+ : URLExtraData(do_AddRef(aBaseURI), do_AddRef(aReferrerInfo),
+ do_AddRef(aPrincipal)) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLExtraData)
+
+ nsIURI* BaseURI() const { return mBaseURI; }
+ nsIReferrerInfo* ReferrerInfo() const { return mReferrerInfo; }
+ nsIPrincipal* Principal() const { return mPrincipal; }
+
+ bool ChromeRulesEnabled() const { return mChromeRulesEnabled; }
+
+ static URLExtraData* Dummy() {
+ MOZ_ASSERT(sDummy);
+ return sDummy;
+ }
+
+ static URLExtraData* DummyChrome() {
+ MOZ_ASSERT(sDummyChrome);
+ return sDummyChrome;
+ }
+
+ static void Init();
+ static void Shutdown();
+
+ // URLExtraData objects that shared style sheets use a sheet ID index to
+ // refer to.
+ static StaticRefPtr<URLExtraData>
+ sShared[size_t(UserAgentStyleSheetID::Count)];
+
+ private:
+ ~URLExtraData();
+
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ bool mChromeRulesEnabled;
+
+ static StaticRefPtr<URLExtraData> sDummy;
+ static StaticRefPtr<URLExtraData> sDummyChrome;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_URLExtraData_h
diff --git a/layout/style/UserAgentStyleSheetID.h b/layout/style/UserAgentStyleSheetID.h
new file mode 100644
index 0000000000..58de5cc607
--- /dev/null
+++ b/layout/style/UserAgentStyleSheetID.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* an identifier for User Agent style sheets */
+
+#ifndef mozilla_UserAgentStyleSheetID_h
+#define mozilla_UserAgentStyleSheetID_h
+
+namespace mozilla {
+
+enum class UserAgentStyleSheetID : uint8_t {
+#define STYLE_SHEET(identifier_, url_, shared_) identifier_,
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+ Count
+};
+
+} // namespace mozilla
+
+#endif // mozilla_UserAgentStyleSheetID_h
diff --git a/layout/style/UserAgentStyleSheetList.h b/layout/style/UserAgentStyleSheetList.h
new file mode 100644
index 0000000000..18de1d0a26
--- /dev/null
+++ b/layout/style/UserAgentStyleSheetList.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* list of user agent style sheets that GlobalStyleSheetCache manages */
+
+/*
+ * STYLE_SHEET(identifier_, url_, shared_)
+ *
+ * identifier_
+ * An identifier for the style sheet, suitable for use as an enum class value.
+ *
+ * url_
+ * The URL of the style sheet.
+ *
+ * shared_
+ * A boolean indicating whether the sheet can be safely placed in shared
+ * memory.
+ */
+
+STYLE_SHEET(ContentEditable, "resource://gre/res/contenteditable.css", true)
+STYLE_SHEET(CounterStyles, "resource://gre-resources/counterstyles.css", true)
+STYLE_SHEET(DesignMode, "resource://gre/res/designmode.css", true)
+STYLE_SHEET(Forms, "resource://gre-resources/forms.css", true)
+STYLE_SHEET(HTML, "resource://gre-resources/html.css", true)
+STYLE_SHEET(MathML, "resource://gre-resources/mathml.css", true)
+STYLE_SHEET(NoFrames, "resource://gre-resources/noframes.css", true)
+STYLE_SHEET(Quirk, "resource://gre-resources/quirk.css", true)
+STYLE_SHEET(Scrollbars, "resource://gre-resources/scrollbars.css", true)
+STYLE_SHEET(SVG, "resource://gre/res/svg.css", true)
+STYLE_SHEET(UA, "resource://gre-resources/ua.css", true)
+STYLE_SHEET(XUL, "chrome://global/content/xul.css", false)
diff --git a/layout/style/contenteditable.css b/layout/style/contenteditable.css
new file mode 100644
index 0000000000..a2e2ca8712
--- /dev/null
+++ b/layout/style/contenteditable.css
@@ -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/. */
+
+@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
+
+/* Scroll-anchoring shouldn't work in any editable and scrollable elements when
+ user inserts something.
+*/
+*|*:read-write:focus,
+*|*:root:read-write {
+ overflow-anchor: none;
+}
+
+*|*::-moz-canvas {
+ cursor: text;
+}
+
+/* Use default arrow over objects with size that
+ are selected when clicked on.
+ Override the browser's pointer cursor over links
+*/
+
+img:read-write, img:read-write[usemap], area:read-write,
+object:read-write, object:read-write[usemap],
+applet:read-write, hr:read-write, button:read-write,
+select:read-write,
+a:read-write:link img, a:read-write:visited img,
+a:read-write:active img, a:read-write:-moz-only-whitespace[name] {
+ cursor: default;
+}
+
+*|*:any-link:read-write {
+ cursor: text;
+}
+
+/* Prevent clicking on links from going to link */
+a:link:read-write img, a:visited:read-write img,
+a:active:read-write img {
+ -moz-user-input: none;
+}
+
+/* We suppress user/author's prefs for link underline,
+ so we must set explicitly. This isn't good!
+*/
+a:link:read-write {
+ color: -moz-hyperlinktext;
+}
+
+/* Allow double-clicks on these widgets to open properties dialogs
+ XXX except when the widget has disabled attribute */
+*|*:read-write > input:read-only,
+*|*:read-write > button:read-only,
+*|*:read-write > textarea:read-only {
+ user-select: all;
+ -moz-user-input: auto !important;
+}
+
+/* XXX Still need a better way of blocking other events to these widgets */
+select:read-write,
+*|*:read-write > input:disabled,
+*|*:read-write > input[type="checkbox"],
+*|*:read-write > input[type="radio"],
+*|*:read-write > input[type="file"],
+input[contenteditable="true"]:disabled,
+input[contenteditable="true"][type="checkbox"],
+input[contenteditable="true"][type="radio"],
+input[contenteditable="true"][type="file"] {
+ user-select: all;
+ -moz-user-input: none !important;
+}
+
+*|*:read-write > input[type="hidden"],
+input[contenteditable="true"][type="hidden"] {
+ border: 1px solid black !important;
+ visibility: visible !important;
+}
+
+*|*::-moz-display-comboboxcontrol-frame {
+ user-select: text;
+}
+
+option:read-write {
+ user-select: text;
+}
+
+/* the following rules are for Image Resizing */
+
+span[\_moz_anonclass="mozResizer"] {
+ width: 5px;
+ height: 5px;
+ position: absolute;
+ border: 1px black solid;
+ background-color: white;
+ user-select: none;
+ z-index: 2147483646; /* max value -1 for this property */
+}
+
+/* we can't use :active below */
+span[\_moz_anonclass="mozResizer"][\_moz_activated],
+span[\_moz_anonclass="mozResizer"]:hover {
+ background-color: black;
+}
+
+span[\_moz_anonclass="mozResizer"].hidden,
+span[\_moz_anonclass="mozResizingShadow"].hidden,
+img[\_moz_anonclass="mozResizingShadow"].hidden,
+span[\_moz_anonclass="mozGrabber"].hidden,
+span[\_moz_anonclass="mozResizingInfo"].hidden,
+a[\_moz_anonclass="mozTableRemoveRow"].hidden,
+a[\_moz_anonclass="mozTableRemoveColumn"].hidden {
+ display: none !important;
+}
+
+span[\_moz_anonclass="mozResizer"][anonlocation="nw"] {
+ cursor: nw-resize;
+}
+span[\_moz_anonclass="mozResizer"][anonlocation="n"] {
+ cursor: n-resize;
+}
+span[\_moz_anonclass="mozResizer"][anonlocation="ne"] {
+ cursor: ne-resize;
+}
+span[\_moz_anonclass="mozResizer"][anonlocation="w"] {
+ cursor: w-resize;
+}
+span[\_moz_anonclass="mozResizer"][anonlocation="e"] {
+ cursor: e-resize;
+}
+span[\_moz_anonclass="mozResizer"][anonlocation="sw"] {
+ cursor: sw-resize;
+}
+span[\_moz_anonclass="mozResizer"][anonlocation="s"] {
+ cursor: s-resize;
+}
+span[\_moz_anonclass="mozResizer"][anonlocation="se"] {
+ cursor: se-resize;
+}
+
+span[\_moz_anonclass="mozResizingShadow"],
+img[\_moz_anonclass="mozResizingShadow"] {
+ outline: thin dashed black;
+ user-select: none;
+ opacity: 0.5;
+ position: absolute;
+ z-index: 2147483647; /* max value for this property */
+}
+
+span[\_moz_anonclass="mozResizingInfo"] {
+ font-family: sans-serif;
+ font-size: x-small;
+ color: black;
+ background-color: #d0d0d0;
+ border: ridge 2px #d0d0d0;
+ padding: 2px;
+ position: absolute;
+ z-index: 2147483647; /* max value for this property */
+}
+
+img[\_moz_resizing] {
+ outline: thin solid black;
+}
+
+*[\_moz_abspos] {
+ outline: silver ridge 2px;
+ z-index: 2147483645 !important; /* max value -2 for this property */
+}
+*[\_moz_abspos="white"] {
+ background-color: white !important;
+}
+*[\_moz_abspos="black"] {
+ background-color: black !important;
+}
+
+span[\_moz_anonclass="mozGrabber"] {
+ outline: ridge 2px silver;
+ padding: 2px;
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ background-image: url("resource://gre/res/grabber.gif");
+ background-repeat: no-repeat;
+ background-position: center center;
+ user-select: none;
+ cursor: move;
+}
+
+/* INLINE TABLE EDITING */
+
+a[\_moz_anonclass="mozTableAddColumnBefore"] {
+ position: absolute;
+ z-index: 2147483647; /* max value for this property */
+ text-decoration: none !important;
+ border: none 0px !important;
+ width: 4px;
+ height: 8px;
+ background-image: url("resource://gre/res/table-add-column-before.gif");
+ background-repeat: no-repeat;
+ background-position: center center;
+ user-select: none;
+}
+
+a[\_moz_anonclass="mozTableAddColumnBefore"]:hover {
+ background-image: url("resource://gre/res/table-add-column-before-hover.gif");
+}
+
+a[\_moz_anonclass="mozTableAddColumnBefore"]:active {
+ background-image: url("resource://gre/res/table-add-column-before-active.gif");
+}
+
+a[\_moz_anonclass="mozTableAddColumnAfter"] {
+ position: absolute;
+ z-index: 2147483647; /* max value for this property */
+ text-decoration: none !important;
+ border: none 0px !important;
+ width: 4px;
+ height: 8px;
+ background-image: url("resource://gre/res/table-add-column-after.gif");
+ background-repeat: no-repeat;
+ background-position: center center;
+ user-select: none;
+}
+
+a[\_moz_anonclass="mozTableAddColumnAfter"]:hover {
+ background-image: url("resource://gre/res/table-add-column-after-hover.gif");
+}
+
+a[\_moz_anonclass="mozTableAddColumnAfter"]:active {
+ background-image: url("resource://gre/res/table-add-column-after-active.gif");
+}
+
+a[\_moz_anonclass="mozTableRemoveColumn"] {
+ position: absolute;
+ z-index: 2147483647; /* max value for this property */
+ text-decoration: none !important;
+ border: none 0px !important;
+ width: 8px;
+ height: 8px;
+ background-image: url("resource://gre/res/table-remove-column.gif");
+ background-repeat: no-repeat;
+ background-position: center center;
+ user-select: none;
+}
+
+a[\_moz_anonclass="mozTableRemoveColumn"]:hover {
+ background-image: url("resource://gre/res/table-remove-column-hover.gif");
+}
+
+a[\_moz_anonclass="mozTableRemoveColumn"]:active {
+ background-image: url("resource://gre/res/table-remove-column-active.gif");
+}
+
+a[\_moz_anonclass="mozTableAddRowBefore"] {
+ position: absolute;
+ z-index: 2147483647; /* max value for this property */
+ text-decoration: none !important;
+ border: none 0px !important;
+ width: 8px;
+ height: 4px;
+ background-image: url("resource://gre/res/table-add-row-before.gif");
+ background-repeat: no-repeat;
+ background-position: center center;
+ user-select: none;
+}
+
+a[\_moz_anonclass="mozTableAddRowBefore"]:hover {
+ background-image: url("resource://gre/res/table-add-row-before-hover.gif");
+}
+
+a[\_moz_anonclass="mozTableAddRowBefore"]:active {
+ background-image: url("resource://gre/res/table-add-row-before-active.gif");
+}
+
+a[\_moz_anonclass="mozTableAddRowAfter"] {
+ position: absolute;
+ z-index: 2147483647; /* max value for this property */
+ text-decoration: none !important;
+ border: none 0px !important;
+ width: 8px;
+ height: 4px;
+ background-image: url("resource://gre/res/table-add-row-after.gif");
+ background-repeat: no-repeat;
+ background-position: center center;
+ user-select: none;
+}
+
+a[\_moz_anonclass="mozTableAddRowAfter"]:hover {
+ background-image: url("resource://gre/res/table-add-row-after-hover.gif");
+}
+
+a[\_moz_anonclass="mozTableAddRowAfter"]:active {
+ background-image: url("resource://gre/res/table-add-row-after-active.gif");
+}
+
+a[\_moz_anonclass="mozTableRemoveRow"] {
+ position: absolute;
+ z-index: 2147483647; /* max value for this property */
+ text-decoration: none !important;
+ border: none 0px !important;
+ width: 8px;
+ height: 8px;
+ background-image: url("resource://gre/res/table-remove-row.gif");
+ background-repeat: no-repeat;
+ background-position: center center;
+ user-select: none;
+}
+
+a[\_moz_anonclass="mozTableRemoveRow"]:hover {
+ background-image: url("resource://gre/res/table-remove-row-hover.gif");
+}
+
+a[\_moz_anonclass="mozTableRemoveRow"]:active {
+ background-image: url("resource://gre/res/table-remove-row-active.gif");
+}
diff --git a/layout/style/crashtests/1017798-1.css b/layout/style/crashtests/1017798-1.css
new file mode 100644
index 0000000000..feb77d9dcf
--- /dev/null
+++ b/layout/style/crashtests/1017798-1.css
@@ -0,0 +1,84 @@
+/* ----------------------------------
+ * SWITCHES
+ * ---------------------------------- */
+
+label.pack-switch {
+ display: inline-block;
+ vertical-align: middle;
+ width: 100%;
+ height: 5rem;
+ position: relative;
+ background: none;
+}
+
+label.pack-switch span {
+ float: left;
+ font-size: 1.8rem;
+ color: #333;
+ padding: 1rem 0 0;
+ height: 6rem;
+ line-height: 3rem;
+ box-sizing: border-box;
+ display: block;
+}
+
+label.pack-switch input {
+ margin: 0;
+ opacity: 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+label.pack-switch input:checked ~ span:after {
+ background-position: center bottom;
+}
+
+/* ----------------------------------
+ * ON/OFF SWITCHES
+ * ---------------------------------- */
+
+label.pack-switch input ~ span:after {
+ content: '';
+ position: absolute;
+ right: 0;
+ top: 50%;
+ width: 6rem;
+ margin: -1.4rem 0 0;
+ height: 2.7rem;
+ border-radius: 1.35rem;
+ overflow: hidden;
+ background: #e6e6e6 url(images/background_off.png) no-repeat -3.2rem 0 / 9.2rem 2.7rem;
+ transition: background 0.2s ease;
+}
+
+/* switch: 'ON' state */
+label.pack-switch input:checked ~ span:after {
+ background: #e6e6e6 url(images/background.png) no-repeat 0 0 / 9.2rem 2.7rem;
+}
+
+/* switch: disabled state */
+label.pack-switch input:disabled ~ span:after {
+ opacity: 0.4;
+}
+
+label.pack-switch input.uninit ~ span:after {
+ transition: none;
+}
+
+/******************************************************************************
+ * Right-To-Left tweaks
+ */
+html[dir="rtl"] label.pack-switch input {
+ left: auto;
+ right: 0;
+}
+
+html[dir="rtl"] label.pack-switch input ~ span:after {
+ left: 0;
+ right: auto;
+}
+
+html[dir="rtl"] label.pack-switch input ~ span:after {
+ background-position: 0;
+}
diff --git a/layout/style/crashtests/1017798-1.html b/layout/style/crashtests/1017798-1.html
new file mode 100644
index 0000000000..9ca89fdf98
--- /dev/null
+++ b/layout/style/crashtests/1017798-1.html
@@ -0,0 +1,126 @@
+
+<!DOCTYPE html>
+<!--
+This is a slightly minimised, modified and self-contained version of
+gaia_switch/examples/index.html from the Gaia repository.
+-->
+<script>
+'use strict';
+
+(function(exports) {
+
+ /**
+ * ComponentUtils is a utility which allows us to use web components earlier
+ * than we should be able to by polyfilling and fixing platform deficiencies.
+ */
+ var ComponentUtils = {
+
+ /**
+ * Injects a style.css into both the shadow root and outside the shadow
+ * root so we can style projected content. Bug 992249.
+ */
+ style: function(baseUrl) {
+ var style = document.createElement('style');
+ style.setAttribute('scoped', '');
+ var url = baseUrl + '1017798-1.css';
+ style.innerHTML = '@import url(' + url + ');';
+
+ this.appendChild(style);
+
+ if (!this.shadowRoot) {
+ return;
+ }
+
+ // The setTimeout is necessary to avoid missing @import styles
+ // when appending two stylesheets. Bug 1003294.
+ setTimeout(() => {
+ this.shadowRoot.appendChild(style.cloneNode(true));
+ });
+ }
+
+ };
+
+ exports.ComponentUtils = ComponentUtils;
+
+}(window));
+</script>
+<script>
+'use strict';
+/* global ComponentUtils */
+
+window.GaiaSwitch = (function(win) {
+ // Extend from the HTMLElement prototype
+ class GaiaSwitch extends HTMLElement {
+ connectedCallback() {
+ var shadow = this.attachShadow({ mode: "open" });
+ this._template = template.content.cloneNode(true);
+ this._input = this._template.querySelector('input[type="checkbox"]');
+
+ var checked = this.getAttribute('checked');
+ if (checked !== null) {
+ this._input.checked = true;
+ }
+
+ shadow.appendChild(this._template);
+
+ ComponentUtils.style.call(this, '');
+ }
+ };
+
+
+ /**
+ * Proxy the checked property to the input element.
+ */
+ Object.defineProperty( GaiaSwitch.prototype, 'checked', {
+ get: function() {
+ return this._input.checked;
+ },
+ set: function(value) {
+ this._input.checked = value;
+ }
+ });
+
+ /**
+ * Proxy the name property to the input element.
+ */
+ Object.defineProperty( GaiaSwitch.prototype, 'name', {
+ get: function() {
+ return this.getAttribute('name');
+ },
+ set: function(value) {
+ this.setAttribute('name', value);
+ }
+ });
+
+ // HACK: Create a <template> in memory at runtime.
+ // When the custom-element is created we clone
+ // this template and inject into the shadow-root.
+ // Prior to this we would have had to copy/paste
+ // the template into the <head> of every app that
+ // wanted to use <gaia-switch>, this would make
+ // markup changes complicated, and could lead to
+ // things getting out of sync. This is a short-term
+ // hack until we can import entire custom-elements
+ // using HTML Imports (bug 877072).
+ var template = document.createElement('template');
+ template.innerHTML = '<label id="switch-label" class="pack-switch">' +
+ '<input type="checkbox">' +
+ '<span><slot></slot></span>' +
+ '</label>';
+
+ // Register and return the constructor
+ win.customElements.define('gaia-switch', GaiaSwitch);
+ return GaiaSwitch;
+})(window);
+</script>
+<body>
+<section>
+ <gaia-switch>
+ <label>With a label</label>
+ </gaia-switch>
+</section>
+<script>
+window.onload = function() {
+ document.querySelector('gaia-switch').checked = true;
+};
+</script>
diff --git a/layout/style/crashtests/1028514-1.html b/layout/style/crashtests/1028514-1.html
new file mode 100644
index 0000000000..bbe7f3ba50
--- /dev/null
+++ b/layout/style/crashtests/1028514-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.documentElement.style.animation = "137438953471s bounce";
+ document.documentElement.offsetHeight;
+ document.documentElement.style.animationIterationCount = "infinite";
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/105619-1.html b/layout/style/crashtests/105619-1.html
new file mode 100644
index 0000000000..27746f29ad
--- /dev/null
+++ b/layout/style/crashtests/105619-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <title>International Herald Tribune</title>
+
+ <script type="text/javascript">
+
+ function displayFix() {
+ document.getElementById("bodyNode").style.display = "block";
+ }
+
+ </script>
+ <style type="text/css">
+
+ #clippingsContainer {overflow:auto;}
+ #menuSearch {position:absolute;}
+
+ </style>
+
+</head>
+
+<body id="bodyNode" onload="displayFix()">
+ <div>
+ <div id="menuSearch"><input type="text"></div>
+ <div id="clippingsContainer"></div>
+ </div>
+
+ <table>
+ <tr><td></td><td></td><td></td></tr>
+ <tr><td></td><td></td><td></td></tr>
+ </table>
+
+</body></html>
diff --git a/layout/style/crashtests/1066089-1.html b/layout/style/crashtests/1066089-1.html
new file mode 100644
index 0000000000..019a20a7f8
--- /dev/null
+++ b/layout/style/crashtests/1066089-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf8">
+<style>
+ @counter-style triangle { symbols: a; }
+ @counter-style disc { system: extends triangle; }
+</style>
+<script>
+ function crash() {
+ var styleNode = document.createElement("style");
+ styleNode.textContent =
+ "@counter-style triangle { symbols: b; } " +
+ "@counter-style disc { system: extends triangle; } " +
+ "ul {}";
+ document.getElementsByTagName("head")[0].appendChild(styleNode);
+ }
+</script>
+</head>
+<body onload="crash()">
+ <ul><li>Don't technically need any text here, but here's some anyway.
diff --git a/layout/style/crashtests/1074651-1.html b/layout/style/crashtests/1074651-1.html
new file mode 100644
index 0000000000..b76855cc1d
--- /dev/null
+++ b/layout/style/crashtests/1074651-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html style="transition-duration: 500000000000000000ms">
+<body onload="document.documentElement.style.strokeWidth = '17px';"></body>
+</html>
diff --git a/layout/style/crashtests/1089463-1.html b/layout/style/crashtests/1089463-1.html
new file mode 100644
index 0000000000..8efb3f6674
--- /dev/null
+++ b/layout/style/crashtests/1089463-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<div></div>
+<script>
+window.onload = function() {
+ var div = document.querySelector("div");
+ var shadow = div.attachShadow({ mode: "open" });
+ shadow.innerHTML = "<p style='display: none'><span><i>x</i></span></p>";
+ var p = shadow.lastChild;
+ var span = p.firstChild;
+ var i = span.firstChild;
+
+ span.style.color = 'blue';
+ p.remove();
+
+ document.body.offsetTop;
+
+ shadow.appendChild(p);
+ i.style.color = 'red';
+};
+</script>
diff --git a/layout/style/crashtests/1135534.html b/layout/style/crashtests/1135534.html
new file mode 100644
index 0000000000..920ec9c7bf
--- /dev/null
+++ b/layout/style/crashtests/1135534.html
@@ -0,0 +1 @@
+<ruby><rtc style="border-image: url(whatever); border-style: solid;"></ruby>
diff --git a/layout/style/crashtests/1136010-1.html b/layout/style/crashtests/1136010-1.html
new file mode 100644
index 0000000000..bdf63f9c08
--- /dev/null
+++ b/layout/style/crashtests/1136010-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+body { text-transform: uppercase; width: 200px; height: 200px; background-color: white; }
+#a, #b { font-size: 24px; }
+</style>
+<div id=a><div id=b><span>x</span><span>y</span></div></div>
+<script>
+document.body.offsetTop;
+var a = document.getElementById("a");
+var b = document.getElementById("b");
+a.style.fontSize = "24px";
+b.style.fontSize = "24px";
+document.body.offsetTop;
+b.style.fontSize = "36px";
+document.body.offsetTop;
+</script>
diff --git a/layout/style/crashtests/1146101-1.html b/layout/style/crashtests/1146101-1.html
new file mode 100644
index 0000000000..e3f8f2aa3f
--- /dev/null
+++ b/layout/style/crashtests/1146101-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script>
+function boom()
+{
+ document.getElementsByTagName("tbody")[0].style.position = "absolute";
+ document.getElementsByTagName("table")[0].style.color = "green";
+}
+</script>
+<body onload="boom();">
+<table><tbody></tbody></table>
diff --git a/layout/style/crashtests/1153693-1.html b/layout/style/crashtests/1153693-1.html
new file mode 100644
index 0000000000..8035f1b218
--- /dev/null
+++ b/layout/style/crashtests/1153693-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+.a { clip-path: url(z); }
+#x { clip-path: inherit; }
+
+</style>
+</head>
+
+<body>
+ <div class="a">
+ <div class="a" id="x"></div>
+ </div>
+ <script>
+ getComputedStyle(document.getElementById("x"), "").clipPath;
+ </script>
+</body>
+
+</html>
diff --git a/layout/style/crashtests/1156969.svg b/layout/style/crashtests/1156969.svg
new file mode 100644
index 0000000000..723fec12d1
--- /dev/null
+++ b/layout/style/crashtests/1156969.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <circle id="x" r="10" style="transition-duration: 1ms;" />
+ <script>
+ window.addEventListener("load", function() {
+ document.getElementById("x").style.transform = "matrix(1, 2, 3, 4, 5, 6%)";
+ }, false)
+ </script>
+</svg>
diff --git a/layout/style/crashtests/1161320-1.html b/layout/style/crashtests/1161320-1.html
new file mode 100644
index 0000000000..3cb3a7b45e
--- /dev/null
+++ b/layout/style/crashtests/1161320-1.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<head>
+<meta charset=utf-8>
+<style>
+@keyframes a { }
+body {
+ animation-name: a;
+}
+</style>
+
+<script>
+function boom()
+{
+ var body = document.body;
+ body.style.animationPlayState = 'paused';
+ window.getComputedStyle(body).animationPlayState;
+ body.style.animationPlayState = 'running';
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="setTimeout(boom, 100);"></body>
+</html>
diff --git a/layout/style/crashtests/1161320-2.html b/layout/style/crashtests/1161320-2.html
new file mode 100644
index 0000000000..71db694d01
--- /dev/null
+++ b/layout/style/crashtests/1161320-2.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<head>
+<meta charset=utf-8>
+<style>
+@keyframes a { }
+body {
+ animation: a 100s;
+}
+</style>
+
+<script>
+function boom()
+{
+ var anim = document.body.getAnimations()[0];
+ anim.finish();
+ anim.pause();
+ anim.play();
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="setTimeout(boom, 100);"></body>
+</html>
diff --git a/layout/style/crashtests/1161366-1.html b/layout/style/crashtests/1161366-1.html
new file mode 100644
index 0000000000..d4eacccdc9
--- /dev/null
+++ b/layout/style/crashtests/1161366-1.html
@@ -0,0 +1,7 @@
+<script>
+var f = new FontFace("x", "url(x.ttf)", { unicodeRange: "U+0041" });
+f.load();
+document.fonts.add(f);
+f = new FontFace("x", "url(x.ttf)", { unicodeRange: "U+0042" });
+f.load();
+</script>
diff --git a/layout/style/crashtests/1163446-1.html b/layout/style/crashtests/1163446-1.html
new file mode 100644
index 0000000000..a3ca0c44d5
--- /dev/null
+++ b/layout/style/crashtests/1163446-1.html
@@ -0,0 +1,4 @@
+<script>
+// Will leak with bug 1161413 patches and without bug 1163446 fix.
+new FontFace("x", new ArrayBuffer(0));
+</script>
diff --git a/layout/style/crashtests/1164813-1.html b/layout/style/crashtests/1164813-1.html
new file mode 100644
index 0000000000..ee5a60ffd6
--- /dev/null
+++ b/layout/style/crashtests/1164813-1.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html class="reftest-wait">
+<style>
+#parent.hidden {
+ display: none;
+}
+.icon {
+ opacity: 0;
+ transition: opacity 0.5s;
+}
+.icon.shrink {
+ animation: shrink 1s;
+}
+@keyframes shrink {
+ to { transform: scale(0); }
+}
+</style>
+<div id="parent">
+ <div class="icon">Searching</div>
+</div>
+<script>
+var icon = document.querySelector('.icon');
+getComputedStyle(icon).opacity;
+icon.style.opacity = 1;
+icon.classList.add('shrink');
+setTimeout(function() {
+ document.getElementById('parent').classList.add('hidden');
+ setTimeout(function() {
+ document.documentElement.removeAttribute('class');
+ }, 500);
+}, 500);
+</script>
+</html>
diff --git a/layout/style/crashtests/1167782-1.html b/layout/style/crashtests/1167782-1.html
new file mode 100644
index 0000000000..4b41ea3983
--- /dev/null
+++ b/layout/style/crashtests/1167782-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<body>
+<script>
+ var d = window.getComputedStyle(document.body, "::-moz-color-swatch").display;
+</script>
+</body>
+</html>
diff --git a/layout/style/crashtests/1186768-1.xhtml b/layout/style/crashtests/1186768-1.xhtml
new file mode 100644
index 0000000000..22608557df
--- /dev/null
+++ b/layout/style/crashtests/1186768-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <mfrac xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>
+ <div xmlns="http://www.w3.org/1999/xhtml" style="font-family: monospace; font-size: 1.17em;"></div>
+ </mi>
+ <mi/>
+ </mfrac>
+ </body>
+</html>
diff --git a/layout/style/crashtests/1200568-1.html b/layout/style/crashtests/1200568-1.html
new file mode 100644
index 0000000000..e2dc9c09df
--- /dev/null
+++ b/layout/style/crashtests/1200568-1.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+<head>
+<style>
+.anim { animation: anim 2s infinite linear }
+@keyframes anim { }
+</style>
+</head>
+<body>
+<script>
+var i = document.createElement('i');
+i.setAttribute('class', 'anim');
+getComputedStyle(i).display;
+</script>
+</body>
+</html>
diff --git a/layout/style/crashtests/1206105-1.html b/layout/style/crashtests/1206105-1.html
new file mode 100644
index 0000000000..88af39e532
--- /dev/null
+++ b/layout/style/crashtests/1206105-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<title>crashtest, bug 1206105</title>
+<style>
+*:nth-child(-n-2147483647) {}
+</style>
+<body>
diff --git a/layout/style/crashtests/1223688-1.html b/layout/style/crashtests/1223688-1.html
new file mode 100644
index 0000000000..70f9d85795
--- /dev/null
+++ b/layout/style/crashtests/1223688-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom() {
+ CSS.supports('');
+
+ var style = document.createElement("style");
+ var tn = document.createTextNode("* { border: var(--b); }");
+ style.appendChild(tn);
+ document.body.appendChild(style);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/1223694-1.html b/layout/style/crashtests/1223694-1.html
new file mode 100644
index 0000000000..c4589884f7
--- /dev/null
+++ b/layout/style/crashtests/1223694-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var sheet = document.createElement("style");
+ sheet.scoped = true;
+ document.documentElement.appendChild(sheet);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/1226400-1.html b/layout/style/crashtests/1226400-1.html
new file mode 100644
index 0000000000..dea15642c7
--- /dev/null
+++ b/layout/style/crashtests/1226400-1.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>FontFaceSet::Load crasher</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+<style type="text/css">
+
+body {
+ margin: 50px;
+}
+
+p {
+ margin: 0;
+ font-size: 300%;
+}
+
+</style>
+
+</head>
+<body>
+
+<p>This may crash on load...</p>
+
+<script>
+var scriptText = `
+ var fontFaceSet = document.fonts;
+ var link = document.createElement("link");
+ link.onerror = link.onload = function() {
+ fontFaceSet.load("12px foo");
+ }
+ link.rel = "stylesheet";
+ link.href = "data:text/css,";
+ document.body.appendChild(link);
+`;
+
+var styleText = `
+ @font-face {
+ font-family: foo;
+ src: url("data:text/ttf,");
+ }
+`;
+
+var ifr = document.createElement("iframe");
+document.body.appendChild(ifr);
+var style = ifr.contentDocument.createElement("style");
+style.textContent = styleText;
+ifr.contentDocument.body.appendChild(style);
+var script = ifr.contentDocument.createElement("script");
+script.textContent = scriptText;
+ifr.contentDocument.body.appendChild(script);
+ifr.remove();
+</script>
+</body>
+</html>
diff --git a/layout/style/crashtests/1227498.html b/layout/style/crashtests/1227498.html
new file mode 100644
index 0000000000..3ccc263fd5
--- /dev/null
+++ b/layout/style/crashtests/1227498.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ addScopedSheet("#x { }");
+}
+
+function addScopedSheet(text)
+{
+ var sheet = document.createElementNS("http://www.w3.org/1999/xhtml", "style");
+ sheet.appendChild(document.createTextNode(text));
+ sheet.scoped = true;
+ document.documentElement.appendChild(sheet);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+ <embed type="application/x-missing-plugin"></embed>
+</body>
+</html>
diff --git a/layout/style/crashtests/1227501-1.html b/layout/style/crashtests/1227501-1.html
new file mode 100644
index 0000000000..03383813d0
--- /dev/null
+++ b/layout/style/crashtests/1227501-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+* { will-change: --t; }
+</style>
+</head>
+</html>
diff --git a/layout/style/crashtests/1228789-1.html b/layout/style/crashtests/1228789-1.html
new file mode 100644
index 0000000000..b8e7ffda61
--- /dev/null
+++ b/layout/style/crashtests/1228789-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="border-image-source: -webkit-gradient();"></body>
+</html>
diff --git a/layout/style/crashtests/1230408-1.html b/layout/style/crashtests/1230408-1.html
new file mode 100644
index 0000000000..0a2588c8ae
--- /dev/null
+++ b/layout/style/crashtests/1230408-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<style>
+body { width: 1px; }
+body:first-letter { }
+</style>
+<body>
+<rb>C</rb>
+</body>
diff --git a/layout/style/crashtests/1233135-1.html b/layout/style/crashtests/1233135-1.html
new file mode 100644
index 0000000000..58a82f3049
--- /dev/null
+++ b/layout/style/crashtests/1233135-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+ fieldset, input, select {
+ display: ruby;
+ }
+</style>
+<fieldset></fieldset>
+<input type="color">
+<input type="file">
+<input type="number">
+<input type="range">
+<select></select>
+<select size="2"></select>
diff --git a/layout/style/crashtests/1233135-2.html b/layout/style/crashtests/1233135-2.html
new file mode 100644
index 0000000000..03e356ae1f
--- /dev/null
+++ b/layout/style/crashtests/1233135-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<ruby>
+ <fieldset></fieldset>
+ <input type="color">
+ <input type="file">
+ <input type="number">
+ <input type="range">
+ <select></select>
+ <select size="2"></select>
+ <span style="display: block; overflow: scroll"></span>
+</ruby>
diff --git a/layout/style/crashtests/1236398.xhtml b/layout/style/crashtests/1236398.xhtml
new file mode 100644
index 0000000000..c1739fac72
--- /dev/null
+++ b/layout/style/crashtests/1236398.xhtml
@@ -0,0 +1,71 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/Math/DTD/mathml2/xhtml-math11-f.dtd" [ <!ENTITY mathml "http://www.w3.org/1998/Math/MathML"> ]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-type" content="text/xml; charset=utf-8" />
+ <meta http-equiv="expires" content="0" />
+ <title>Crashing Firefox 3.0.2</title>
+ </head>
+ <body>
+ <table class="site" cellpadding="0" cellspacing="2">
+ <tr>
+ <td class="title" colspan="2">
+ <div class="title"></div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="content" width="100%" height="100%">
+
+
+
+<div class="headline">Text</div>
+
+
+
+<div class="navigation">
+ <a href="/forum/">Text</a>
+
+
+ / <a href="#">Link</a>
+
+
+
+ / <a href="#">Link</a>
+
+</div>
+
+
+<table class="foren" cellspacing="0" cellpadding="0" width="100%" >
+ <col width="200" class="author" />
+ <tfoot><tr><td colspan="2"></td></tr></tfoot>
+ <tbody>
+
+<tr class="message">
+ <td class="text">
+ <div class="body">[x<i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i><i>?</i>]</div>
+ </td>
+</tr>
+
+
+
+<tr class="message">
+ <td class="text">
+ <div class="body"><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b>.</b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></div>
+ </td>
+</tr>
+
+<tr class="message">
+ <td class="author">
+ </td>
+
+ <td class="text">
+<div class="body"><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><i><b><b><i><b><i><b><i><b><i><b></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></i></b></b></i></b></i></b></i></b></i></div>
+ </td>
+</tr>
+</tbody>
+</table>
+</td>
+</tr>
+</table>
+</body>
+</html>
diff --git a/layout/style/crashtests/1238660-1.html b/layout/style/crashtests/1238660-1.html
new file mode 100644
index 0000000000..2323d2cfa5
--- /dev/null
+++ b/layout/style/crashtests/1238660-1.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Bug 1238660</title>
+ </head>
+ <body>
+ <span>
+ <div>
+ <span></span>
+ </div>
+ </span>
+ <style>
+ span {
+ animation: anim 0.1s 0.1s;
+ }
+ @keyframes anim { to { opacity:1 } }
+ </style>
+ </body>
+</html>
diff --git a/layout/style/crashtests/1245260-1.html b/layout/style/crashtests/1245260-1.html
new file mode 100644
index 0000000000..6f2dda9955
--- /dev/null
+++ b/layout/style/crashtests/1245260-1.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>Bug 1245260</title>
+</head>
+<body>
+<style>
+body {
+ color: blue;
+}
+
+div {
+ transition: text-emphasis-color 0.1s;
+}
+
+.hide {
+ display: none;
+}
+
+a {
+ color: red;
+ transition: text-emphasis-color 0.1s;
+}
+
+span {
+ transition: text-emphasis-color 0.1s;
+}
+
+@font-face {
+ font-family: "icon-fonts";
+ src: url(x);
+}
+</style>
+
+<div>
+<span><a>Shows</a></span>
+<span><a>Video</a></span>
+<span><a>Schedule</a></span>
+<span><a>Topics</a></span>
+<span><a>Games</a></span>
+<span><a>Shop</a></span>
+<span><a>This Day In History</a></span>
+<span><a>Ask History</a></span>
+<span><a>History Lists</a></span>
+<span><a>Hungry History</a></span>
+<span><a>Speeches &amp; Audio</a></span>
+<span class="hide"><a>Sign In</a></span>
+<span><a class="hide">Sign Out</a></span>
+</div>
+<script/>
+</body>
+</html>
diff --git a/layout/style/crashtests/1247865-1.html b/layout/style/crashtests/1247865-1.html
new file mode 100644
index 0000000000..b7ec8ba60a
--- /dev/null
+++ b/layout/style/crashtests/1247865-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ .nav { display: table }
+ .nav:after { content: " " }
+ </style>
+</head>
+<body>
+ <div style="font-size: 1rem"></div>
+ <div class="nav">
+ </div>
+ <script>
+ document.documentElement.style.fontSize = "10px";
+ document.documentElement.offsetHeight;
+ document.documentElement.style.fontSize = "15px";
+ </script>
+</body>
+</html>
diff --git a/layout/style/crashtests/1250791.html b/layout/style/crashtests/1250791.html
new file mode 100644
index 0000000000..8fb0a16431
--- /dev/null
+++ b/layout/style/crashtests/1250791.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <div style="font-size: calc(-1e9%);">
+ <div style="font-size: 1e900%;"></div>
+ </div>
+ </body>
+</html>
diff --git a/layout/style/crashtests/1264396-1.html b/layout/style/crashtests/1264396-1.html
new file mode 100644
index 0000000000..abba4de3b5
--- /dev/null
+++ b/layout/style/crashtests/1264396-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8" />
+<style>
+@keyframes bug {
+ from {display:none}
+ to {display:inline-block}
+}
+body {
+ animation-name: bug;
+ animation-duration: 1s;
+}
+</style>
+</html>
diff --git a/layout/style/crashtests/1264949.html b/layout/style/crashtests/1264949.html
new file mode 100644
index 0000000000..780bff97a3
--- /dev/null
+++ b/layout/style/crashtests/1264949.html
@@ -0,0 +1,23 @@
+<!doctype HTML>
+<html>
+ <head>
+ <style>
+ div {
+ background-image: linear-gradient(green, red);
+ background-clip: text;
+ width: 150px;
+ color:transparent;
+ overflow: hidden;
+ opacity:0.5;
+ text-overflow: ellipsis;
+ }
+ </style>
+ </head>
+ <body>
+ <div>
+ <span stype="font-size: 30px;">
+ ALongLongLongLongLongLongLongLongLongLongLongLongString
+ </span>
+ </div>
+ </body>
+</html>
diff --git a/layout/style/crashtests/1265611-1.html b/layout/style/crashtests/1265611-1.html
new file mode 100644
index 0000000000..df5467ffa3
--- /dev/null
+++ b/layout/style/crashtests/1265611-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!--
+ NOTE: The comment below is no longer quite true - in particular, we've
+ removed the 'layout.css.prefixes.webkit' pref entirely, so this test doesn't
+ exercise the same codepath that it did when the below comment was written. We
+ might as well keep this crashtest, though, even if it traverses a different
+ codepath now.
+-->
+<!--
+ This test relies on triggering a transition on the 'color' property which,
+ at least when this test was written, would trigger a transition on the
+ -webkit-text-fill-color property since its default value is 'currentcolor'.
+
+ However, in crashtests.list we turn off layout.css.prefixes.webkit so
+ we should not trigger a transition on -webkit-text-fill-color.
+ [Alert: This is no longer accurate, per NOTE above.]
+
+ This test exercises some code that, prior to this bug, would fail because we
+ would initially create the transition on -webkit-text-fill-color (because we
+ forgot to check if it was enabled or not) and then we would call other
+ methods that *do* check for the enabled-ness of the property leaving us
+ in an unexpected state.
+-->
+<body style="transition: all 4s" onload="document.body.style.color = 'green';"></body>
diff --git a/layout/style/crashtests/1270795.html b/layout/style/crashtests/1270795.html
new file mode 100644
index 0000000000..c4262078ed
--- /dev/null
+++ b/layout/style/crashtests/1270795.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <table style="background-image: linear-gradient(black, white); background-clip: text;">
+ <tr>
+ <td>Table 1</td>
+ </tr>
+ </table>
+ <table style="background-color: red; background-clip: text;">
+ <tr>
+ <td>Table 2</td>
+ </tr>
+ </table>
+ </body>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1275026.html b/layout/style/crashtests/1275026.html
new file mode 100644
index 0000000000..7960d2889a
--- /dev/null
+++ b/layout/style/crashtests/1275026.html
@@ -0,0 +1,4 @@
+<!doctype HTML>
+<html style="background: linear-gradient(to right, red, blue); background-clip: text;">
+<body></body>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1277908-1.html b/layout/style/crashtests/1277908-1.html
new file mode 100644
index 0000000000..c8dae69068
--- /dev/null
+++ b/layout/style/crashtests/1277908-1.html
@@ -0,0 +1,26 @@
+<script>
+window.onload = function () {
+ var root = document.documentElement; while(root.firstChild) { root.firstChild.remove(); }
+ var a = document.createElementNS("http://www.w3.org/1999/xhtml", "link");
+ a.setAttributeNS(null, "href", "mailto:");
+ root.appendChild(a);
+ var b = document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+ var c = document.createElementNS("http://www.w3.org/1999/xhtml", "p");
+ root.appendChild(b);
+ root.animate([{"opacity":1},
+ {"opacity":-64},
+ {"opacity":1024},
+ {"opacity":32},
+ {"opacity":3},
+ {"opacity":1024},
+ {"opacity":0.19310025712314532},
+ {"opacity":512}],
+ {"duration":1,"fill":"backwards"});
+ a.style.maskType = "alpha, luminance";
+ c.animate({}, 1);
+ root.style.position = "fixed";
+ b.getAnimations();
+ a.style.perspectiveOrigin = "1rem bottom";
+ root.style.position = "static";
+};
+</script>
diff --git a/layout/style/crashtests/1277908-2.html b/layout/style/crashtests/1277908-2.html
new file mode 100644
index 0000000000..4c5266826a
--- /dev/null
+++ b/layout/style/crashtests/1277908-2.html
@@ -0,0 +1,19 @@
+<script>
+function start() {
+ o28=document.createElement('a');
+ o28.href='javascript:x()';
+ o115=document.createElement('tr');
+ o116=document.createElement('th');
+ o116.innerHTML="<style>{}\n*{ display: table;> </style><style>@keyframes key8 { from{ left; background-position-x: 128vw}to{}label}\n*{ animation-name: key8; animation-duration: 0.001s";
+ document.documentElement.appendChild(o28);
+ document.documentElement.appendChild(o115);
+ document.documentElement.appendChild(o116);
+ o213=document.createElement('input');
+ o115.appendChild(o213);
+ o216=document.createElement('style');
+ o217=document.createTextNode("*{ text-shadow: 196608rem -3px");
+ o216.appendChild(o217);
+ o213.appendChild(o216);
+}
+</script>
+<body onload="start()"></body>
diff --git a/layout/style/crashtests/1278463-1.html b/layout/style/crashtests/1278463-1.html
new file mode 100644
index 0000000000..da3b976d88
--- /dev/null
+++ b/layout/style/crashtests/1278463-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+ @keyframes c {
+ 50% {
+ stroke-dasharray: context-value;
+ }
+ }
+
+ div {
+ animation-name: c;
+ }
+
+</style>
+</head>
+<body>
+<div></div>
+</body>
+</html>
diff --git a/layout/style/crashtests/1279819-1.html b/layout/style/crashtests/1279819-1.html
new file mode 100644
index 0000000000..1794551739
--- /dev/null
+++ b/layout/style/crashtests/1279819-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+function start() {
+ o0=window.document;
+ o1=window.document.documentElement;
+ o1.hidden^=true;
+ document.replaceChild(o0.documentElement,document.documentElement);
+ o132=o0.createElement('form');
+ o0.documentElement.appendChild(o132);
+ o132.innerHTML=
+"<svg id><style id>@font-face{ font-family: font7; src: url('eot');}{}\n"+
+"*{ font-size: 85rem!important; all: initial;<style>@keyframes{{}}*{ animation-name: key12; animation-duration: 0.001s}{}\n"+
+"@keyframes key12{ from{ font: larger Helvetica";
+ document.documentElement.classList.remove("reftest-wait");
+}
+window.setTimeout("start()",4);
+</script>
diff --git a/layout/style/crashtests/1282076-1.html b/layout/style/crashtests/1282076-1.html
new file mode 100644
index 0000000000..d5d1f0c74b
--- /dev/null
+++ b/layout/style/crashtests/1282076-1.html
@@ -0,0 +1,51 @@
+<script>
+function start() {
+ o0=document;
+ o24=document.createElement('table');
+ o35=window;
+ o60=document.createElement('input');
+ o24.appendChild(o60);
+ o62=document.body;
+ o66=document.createElement('input');
+ o62.appendChild(o66);
+ o60.innerHTML="<svg><color-profile><script><rect><animateColor><style><style>*{ all: unset<script><style>div<style>";
+ o93=o60.querySelectorAll('*')[5];
+ o97=o60.querySelectorAll('*')[9];
+ document.body.appendChild(o24);
+ o305=document.createTextNode("{}:first-line{");
+ o93.appendChild(o305);
+ o318=(new DOMParser()).parseFromString('','text/html');
+ o320=o318.all[1];
+ o355=document.createElement('style');
+ o356=document.createTextNode("@keyframes key2{ from{ opacity: 0}}#id2{ animation-name: key2; animation-duration: 0.01s");
+ o355.appendChild(o356);
+ o97.appendChild(o355);
+ o66.style.display='list-item';
+ o473=document.createElement('script');
+ o24.appendChild(o473);
+ o577=document.createElement('style');
+ o320.appendChild(o577);
+ o577.style.position='fixed';
+ document.replaceChild(o318.documentElement,document.documentElement);
+ o908=(new DOMParser()).parseFromString('','text/html');
+ o911=o908.all[2];
+ o911.style.display='inline';
+ o577.id='id2';
+ o1202=document.createElement('table');
+ document.body=o911;
+ document.body.appendChild(o1202);
+ document.replaceChild(o0.documentElement,document.documentElement);
+ o1232=o473.parentNode;
+ o1233=o1232.parentNode;
+ document.body=o1233;
+ o35.scrollByLines(1);
+ o577.style.position='absolute';
+ setTimeout(f2, 4);
+}
+
+function f2() {
+ o0.designMode='on';
+ o0.execCommand('insertparagraph',false,null);
+}
+</script>
+<body onload="start()"></body>
diff --git a/layout/style/crashtests/1282076-2.html b/layout/style/crashtests/1282076-2.html
new file mode 100644
index 0000000000..1c6f986396
--- /dev/null
+++ b/layout/style/crashtests/1282076-2.html
@@ -0,0 +1,46 @@
+<script>
+function start() {
+ o0=document;
+ o24=document.createElement('table');
+ o35=window;
+ o60=document.createElement('input');
+ o24.appendChild(o60);
+ o62=document.body;
+ o66=document.createElement('input');
+ o62.appendChild(o66);
+ o60.innerHTML="<svg><color-profile><script><rect><animateColor><style><style>*{ all: unset<script>";
+ o93=o60.querySelectorAll('*')[5];
+ document.body.appendChild(o24);
+ o305=document.createTextNode("{}:first-line{");
+ o93.appendChild(o305);
+ o318=(new DOMParser()).parseFromString('','text/html');
+ o320=o318.all[1];
+ o66.style.display='list-item';
+ o473=document.createElement('script');
+ o24.appendChild(o473);
+ o577=document.createElement('style');
+ o320.appendChild(o577);
+ o577.style.position='fixed';
+ document.replaceChild(o318.documentElement,document.documentElement);
+ o908=(new DOMParser()).parseFromString('','text/html');
+ o911=o908.all[2];
+ o911.style.display='inline';
+ o577.animate({ opacity: [0, 1] }, 100);
+ o1202=document.createElement('table');
+ document.body=o911;
+ document.body.appendChild(o1202);
+ document.replaceChild(o0.documentElement,document.documentElement);
+ o1232=o473.parentNode;
+ o1233=o1232.parentNode;
+ document.body=o1233;
+ o35.scrollByLines(1);
+ o577.style.position='absolute';
+ setTimeout(f2, 4);
+}
+
+function f2() {
+ o0.designMode='on';
+ o0.execCommand('insertparagraph',false,null);
+}
+</script>
+<body onload="start()"></body>
diff --git a/layout/style/crashtests/1290994-1.html b/layout/style/crashtests/1290994-1.html
new file mode 100644
index 0000000000..d9d99a5b06
--- /dev/null
+++ b/layout/style/crashtests/1290994-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<script>
+window.onload=function(){
+ var a = document.createElement("div");
+ document.documentElement.appendChild(a);
+ a.animate([{borderLeftColor:"black"},
+ {borderLeftColor:"hsl(0,0e309%,0%)"}]);
+};
+</script>
+</html>
diff --git a/layout/style/crashtests/1290994-2.html b/layout/style/crashtests/1290994-2.html
new file mode 100644
index 0000000000..e592b84eee
--- /dev/null
+++ b/layout/style/crashtests/1290994-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<script>
+window.onload=function(){
+ var a = document.createElement("div");
+ document.documentElement.appendChild(a);
+ a.animate([{color:"rgb(0,0,0)"},
+ {color:"rgb(0e309%,0%,0%)"}]);
+};
+</script>
+</html>
diff --git a/layout/style/crashtests/1290994-3.html b/layout/style/crashtests/1290994-3.html
new file mode 100644
index 0000000000..76589f5a69
--- /dev/null
+++ b/layout/style/crashtests/1290994-3.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<script>
+window.onload=function(){
+ var a = document.createElement("div");
+ document.documentElement.appendChild(a);
+ a.animate([{background: "-webkit-gradient(radial, 1 2, 8, 3 4, 9, from(lime))"},
+ {background: "-webkit-gradient(radial, 0e309 2, 8, 3 4, 9, from(lime))"}]);
+};
+</script>
+</html>
diff --git a/layout/style/crashtests/1290994-4.html b/layout/style/crashtests/1290994-4.html
new file mode 100644
index 0000000000..4278856d0c
--- /dev/null
+++ b/layout/style/crashtests/1290994-4.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<style>
+@keyframes anim {
+ 0e309% {}
+}
+</style>
+</html>
diff --git a/layout/style/crashtests/1314531.html b/layout/style/crashtests/1314531.html
new file mode 100644
index 0000000000..8e804643fd
--- /dev/null
+++ b/layout/style/crashtests/1314531.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<style>::-moz-tree-row:hover {}</style>
diff --git a/layout/style/crashtests/1315889-1.html b/layout/style/crashtests/1315889-1.html
new file mode 100644
index 0000000000..29186fac1c
--- /dev/null
+++ b/layout/style/crashtests/1315889-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+.x { color: blue; }
+</style>
+<div class=x>hello</div>
+<script>
+document.body.offsetWidth;
+var x = document.querySelector(".x");
+x.className = "";
+x.remove();
+document.body.offsetWidth;
+</script>
diff --git a/layout/style/crashtests/1315894-1.html b/layout/style/crashtests/1315894-1.html
new file mode 100644
index 0000000000..e0192460ff
--- /dev/null
+++ b/layout/style/crashtests/1315894-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<div style="display: none"><span>x</span></div>
+<script>
+document.body.offsetWidth;
+document.querySelector("div").style.display = "inline";
+document.body.offsetWidth;
+document.querySelector("span").style.color = "blue";
+document.body.offsetWidth;
+</script>
diff --git a/layout/style/crashtests/1319072-1.html b/layout/style/crashtests/1319072-1.html
new file mode 100644
index 0000000000..f6c1330559
--- /dev/null
+++ b/layout/style/crashtests/1319072-1.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Interpolation of decomposed matrices</title>
+<style>
+#target {
+ width: 100px; height: 100px;
+ background: blue;
+ animation: anim 0.1s cubic-bezier(0,1.5,1,1.5);
+}
+@keyframes anim {
+ from { transform: matrix(1, 0, 0, 1, 100, 200); }
+ to { transform: matrix(1, 0, 0, 1, 200, 100); }
+}
+</style>
+<div id="target"></div>
+<script>
+document.getElementById("target").addEventListener("animationend", () => {
+ document.documentElement.classList.remove("reftest-wait");
+});
+</script>
diff --git a/layout/style/crashtests/1320423-1.html b/layout/style/crashtests/1320423-1.html
new file mode 100644
index 0000000000..9769455e75
--- /dev/null
+++ b/layout/style/crashtests/1320423-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<style>
+#target {
+ cursor: url(file:///somewhere/cursor.png), pointer;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: yellow;
+}
+#target:hover {
+ background: green;
+}
+</style>
+<div id=target></div>
+<script>
+var target = document.getElementById("target");
+var x = window.outerWidth / 2, y = window.outerHeight / 2;
+SpecialPowers.DOMWindowUtils.sendMouseEvent("mouseover", x, y, 0, 0, 0);
+</script>
diff --git a/layout/style/crashtests/1321357-1.html b/layout/style/crashtests/1321357-1.html
new file mode 100644
index 0000000000..7b3a3c39c0
--- /dev/null
+++ b/layout/style/crashtests/1321357-1.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<body onload="document.getElementById('containerA').pauseAnimations()">
+ <svg id="containerA">
+ <animate id="ia" end="50s"></animate>
+ <animate begin="60s" end="ic.end"></animate>
+ </svg>
+ <svg>
+ <animate id="ic" end="ia.end"></animate>
+ </svg>
+</body>
+</html>
diff --git a/layout/style/crashtests/1328535-1.html b/layout/style/crashtests/1328535-1.html
new file mode 100644
index 0000000000..3738380978
--- /dev/null
+++ b/layout/style/crashtests/1328535-1.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<style>
+@keyframes anim {
+ from { box-shadow: none; }
+ to { box-shadow: rgba(120, 120, 120, 0.5) 10px 10px 10px 0px; }
+}
+#target {
+ width: 100px; height: 100px;
+ /*
+ * Use negative delay to shift to the point that the cubic-bezier function
+ * produces a value out of range of [0, 1].
+ */
+ animation: anim 1s -0.02s cubic-bezier(0, -0.5, 0, 0) paused;
+}
+</style>
+<div id="target"></div>
diff --git a/layout/style/crashtests/1331272.html b/layout/style/crashtests/1331272.html
new file mode 100644
index 0000000000..6ee9f022f7
--- /dev/null
+++ b/layout/style/crashtests/1331272.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<style>
+ div::before {
+ content: "PASS";
+ display: none;
+ }
+ .foo::before {
+ display: none;
+ }
+</style>
+<div></div>
+<script>
+window.onload = function() {
+ document.querySelector('div').className = "foo";
+}
+</script>
diff --git a/layout/style/crashtests/1332550.html b/layout/style/crashtests/1332550.html
new file mode 100644
index 0000000000..4299450a58
--- /dev/null
+++ b/layout/style/crashtests/1332550.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<style>
+ @keyframes test {
+ 0% {
+ }
+ }
+</style>
+<script>
+ var sheet = document.styleSheets[0];
+ var rule = sheet.cssRules[0];
+ var keyframe = rule.cssRules[0];
+ rule.deleteRule("0%");
+ sheet.deleteRule(0);
+ rule = null;
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ keyframe.parentRule;
+</script>
diff --git a/layout/style/crashtests/1333001-1.css b/layout/style/crashtests/1333001-1.css
new file mode 100644
index 0000000000..ee2d477b79
--- /dev/null
+++ b/layout/style/crashtests/1333001-1.css
@@ -0,0 +1 @@
+@import url(chrome://foo);
diff --git a/layout/style/crashtests/1333001-1.html b/layout/style/crashtests/1333001-1.html
new file mode 100644
index 0000000000..2fb577b7bd
--- /dev/null
+++ b/layout/style/crashtests/1333001-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel=stylesheet href=1333001-1.css type=text/css>
+<link rel=stylesheet href=1333001-1.css type=text/css>
+<body onload="f()">
+<script>
+function f() {
+ document.styleSheets[1].cssRules[0].media;
+}
+</script>
diff --git a/layout/style/crashtests/1340248.html b/layout/style/crashtests/1340248.html
new file mode 100644
index 0000000000..04e7ebaada
--- /dev/null
+++ b/layout/style/crashtests/1340248.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+ o1 = document.createElement('style');
+ o1.textContent = "* { position: absolute; }";
+ o1.className = 'c2';
+ document.head.appendChild(o1);
+ document.styleSheets[0].insertRule("*::first-line { border-inline-end-style: solid; }", 0);
+ document.styleSheets[0].insertRule("* { display: table-row ; }", 0);
+ document.styleSheets[0].insertRule(".c2::first-line { border-style: none ; }", 0);
+ </script>
+ </head>
+</html>
diff --git a/layout/style/crashtests/1340344.html b/layout/style/crashtests/1340344.html
new file mode 100644
index 0000000000..5d0a3c85fd
--- /dev/null
+++ b/layout/style/crashtests/1340344.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+window.onload = function(){
+ var anim =
+ document.documentElement.animate([{ "color": "hsla(6e147grad,16%,183.379675555%,0.0210463770007)" }]);
+ anim.ready.then(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+};
+</script>
+</head>
+</html>
diff --git a/layout/style/crashtests/1342316-1.html b/layout/style/crashtests/1342316-1.html
new file mode 100644
index 0000000000..091c6e8261
--- /dev/null
+++ b/layout/style/crashtests/1342316-1.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>calc() in translate3d as base style of transform animation</title>
+<style>
+#target {
+ width: 100px; height: 100px;
+ background: blue;
+ animation: anim 1s;
+ transform: translate3d(100px, calc(10px + 30%), 10px);
+}
+@keyframes anim {
+ to { transform: translate3d(0px, 0px, 0px); }
+}
+</style>
+<div id="target"></div>
+<script>
+document.getElementById("target").addEventListener("animationstart", () => {
+ document.documentElement.classList.remove("reftest-wait");
+});
+</script>
diff --git a/layout/style/crashtests/1344210.html b/layout/style/crashtests/1344210.html
new file mode 100644
index 0000000000..f6b39eb082
--- /dev/null
+++ b/layout/style/crashtests/1344210.html
@@ -0,0 +1,22 @@
+<html>
+ <head>
+ <script>
+ o1 = document.createElement('style');
+ o2 = document.createElement('style');
+ o3 = document.createElement('style');
+ o4 = document.implementation.createHTMLDocument().documentElement;
+ document.replaceChild(o4, document.documentElement);
+ document.head.appendChild(o1);
+ document.head.appendChild(o2);
+ document.head.appendChild(o3);
+ o1.scrollLeft = 2;
+ document.styleSheets[0].insertRule("*:first-line { border-inline-start-color: green; }", 0);
+ document.styleSheets[0].insertRule("* {float: left ; }", 0);
+ window.find('foobar', true, false, true, true, true, false);
+ document.styleSheets[2].insertRule("*:first-line { border: 10px solid;", 0);
+ window.find('foobar', true, true, true, false, false, false);
+ o3.parentNode.removeChild(o3);
+ </script>
+ </head>
+ <body></body>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1353312.html b/layout/style/crashtests/1353312.html
new file mode 100644
index 0000000000..56adf9ed6d
--- /dev/null
+++ b/layout/style/crashtests/1353312.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<style>
+ body { float: left }
+ div::first-line {
+ border-inline-start: 0;
+ }
+ div.boom::first-line {
+ border: 0;
+ }
+</style>
+
+<div>x</div>
+<div class="boom">x</div>
+<div>x</div>
diff --git a/layout/style/crashtests/1356601-1.html b/layout/style/crashtests/1356601-1.html
new file mode 100644
index 0000000000..4fc28bd58f
--- /dev/null
+++ b/layout/style/crashtests/1356601-1.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<style>
+div::first-line {
+ --bar: left;
+}
+span {
+ animation: var(--bar) 5s infinite alternate;
+}
+@keyframes left {
+ from {left: 0;}
+ to {left: 30px;}
+}
+</style>
+<div>
+ <span>Crash</span>
+</div>
+</html>
diff --git a/layout/style/crashtests/1364139-1.html b/layout/style/crashtests/1364139-1.html
new file mode 100644
index 0000000000..d33093d018
--- /dev/null
+++ b/layout/style/crashtests/1364139-1.html
@@ -0,0 +1,20 @@
+<html>
+ <head>
+ <script>
+ try { o1 = document.createElement('area'); } catch(e) { };
+ try { o2 = document.createElement('article'); } catch(e) { };
+ try { o3 = document.createElement('iframe'); } catch(e) { };
+ try { o4 = document.createElement('style'); } catch(e) { };
+ try { o1.className = 'c3'; } catch(e) { };
+ try { o2.className = 'c3'; } catch(e) { };
+ try { o2.innerText = "\uE4C9"; } catch(e) { };
+ try { document.documentElement.appendChild(o1); } catch(e) { };
+ try { document.documentElement.appendChild(o2); } catch(e) { };
+ try { document.documentElement.appendChild(o3); } catch(e) { };
+ try { document.documentElement.appendChild(o4); } catch(e) { };
+ try { document.styleSheets[0].insertRule("*::first-letter, .c3 ~ .c3::first-line { float: right; font-variant-alternates: annotation(\\62 lah); }", 0); } catch(e) { };
+ try { o3.src = "data:text/html,"; } catch(e) { };
+ try { document.styleSheets[0].insertRule(".c3:last-child:nth-of-type(+3n) { }", 0); } catch(e) { };
+ </script>
+ </head>
+</html>
diff --git a/layout/style/crashtests/1371450-1.html b/layout/style/crashtests/1371450-1.html
new file mode 100644
index 0000000000..199f326bb5
--- /dev/null
+++ b/layout/style/crashtests/1371450-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title></title>
+<style>
+@keyframes anim {
+ to { transform: rotate(360deg); }
+}
+#animation {
+ animation: anim 3s infinite;
+}
+.red {
+ color: red;
+}
+</style>
+<div><span id="target">text</span></div>
+<div id="animation">animation</div>
+<script>
+window.addEventListener('load', () => {
+ var target = document.getElementById('target');
+ target.classList.add('red');
+ setTimeout(() => {
+ target.classList.remove('red');
+ SpecialPowers.getDOMWindowUtils(window)
+ .sendMouseEvent("mousemove", 100, 100, 1,
+ 0, 1, 0);
+ setTimeout(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ }, 0);
+ }, 0);
+ SpecialPowers.getDOMWindowUtils(window)
+ .sendMouseEvent("mousemove", 100, 100, 1,
+ 0, 1, 0);
+});
+</script>
diff --git a/layout/style/crashtests/1374175-1.html b/layout/style/crashtests/1374175-1.html
new file mode 100644
index 0000000000..e534f77719
--- /dev/null
+++ b/layout/style/crashtests/1374175-1.html
@@ -0,0 +1,12 @@
+<style>
+@keyframes anim {
+ from { color: red }
+}
+input {
+ animation: anim 1s;
+}
+</style>
+<script>
+ input=document.createElement('input');
+ document.documentElement.appendChild(input);
+</script>
diff --git a/layout/style/crashtests/1375812-1.html b/layout/style/crashtests/1375812-1.html
new file mode 100644
index 0000000000..e51fc60faf
--- /dev/null
+++ b/layout/style/crashtests/1375812-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Bug 1375812 - Interpolation between interpolatematrix and none tranform
+</title>
+<meta charset="UTF-8">
+<style>
+ #target {
+ transition: all 10s linear;
+ transform: translateX(100px);
+ }
+</style>
+<script>
+ function go() {
+ var div = document.getElementById('target');
+ div.style.setProperty("transform", "rotate(60deg)", "");
+ window.getComputedStyle(div).transform;
+ div.style.setProperty("transform", "none", "");
+ }
+</script>
+</head>
+<body onload="go()">
+<div id="target"></div>
+</body>
+</html>
diff --git a/layout/style/crashtests/1377053-1.html b/layout/style/crashtests/1377053-1.html
new file mode 100644
index 0000000000..829a36128d
--- /dev/null
+++ b/layout/style/crashtests/1377053-1.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<style>
+div {
+ animation: t 1s;
+}
+@keyframes t {
+ to { stroke-dasharray: none; }
+}
+</style>
+<div></div>
diff --git a/layout/style/crashtests/1377256-1-helper.html b/layout/style/crashtests/1377256-1-helper.html
new file mode 100644
index 0000000000..67c7dd05b4
--- /dev/null
+++ b/layout/style/crashtests/1377256-1-helper.html
@@ -0,0 +1,13 @@
+<script>
+document.addEventListener("DOMContentLoaded", function(){
+ window.getSelection().modify('extend','backward','character');
+ let n=document.getElementById('a');
+ n.parentNode.removeChild(n);
+ setTimeout(function(){window.parent.finish(); }, 0);
+});
+</script>
+<body id='a'>
+<table contenteditable='true'>
+>
+<th
+</html>
diff --git a/layout/style/crashtests/1377256-1.html b/layout/style/crashtests/1377256-1.html
new file mode 100644
index 0000000000..187803f6f2
--- /dev/null
+++ b/layout/style/crashtests/1377256-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<iframe src="1377256-1-helper.html"></iframe>
+<script>
+function finish() {
+ document.querySelector("iframe").remove();
+ document.documentElement.className = "";
+}
+</script>
diff --git a/layout/style/crashtests/1378064-1.html b/layout/style/crashtests/1378064-1.html
new file mode 100644
index 0000000000..aaf9bb3b7d
--- /dev/null
+++ b/layout/style/crashtests/1378064-1.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title></title>
+<style>
+@keyframes anim {
+ to { transform: rotate(360deg); }
+}
+span {
+ color: black;
+ animation: anim 3s infinite;
+}
+span.red {
+ color: red;
+}
+</style>
+<div>
+<span id="target">text</span>
+<span>text</span>
+</div>
+<script>
+window.addEventListener('load', () => {
+ var target = document.getElementById('target');
+ target.classList.add('red');
+ requestAnimationFrame(() => {
+ target.classList.remove('red');
+ SpecialPowers.getDOMWindowUtils(window)
+ .sendMouseEvent("mousemove", 100, 100, 1,
+ 0, 1, 0);
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+ });
+
+ SpecialPowers.getDOMWindowUtils(window)
+ .sendMouseEvent("mousemove", 100, 100, 1,
+ 0, 1, 0);
+});
+</script>
diff --git a/layout/style/crashtests/1378814.html b/layout/style/crashtests/1378814.html
new file mode 100644
index 0000000000..4177063408
--- /dev/null
+++ b/layout/style/crashtests/1378814.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+getComputedStyle(document.documentElement, '::first-letter').display;
+</script>
diff --git a/layout/style/crashtests/1380800.html b/layout/style/crashtests/1380800.html
new file mode 100644
index 0000000000..a01740cd83
--- /dev/null
+++ b/layout/style/crashtests/1380800.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<style></style>
+<script>
+document.styleSheets[0].deleteRule(0);
+</script>
diff --git a/layout/style/crashtests/1381420-1.html b/layout/style/crashtests/1381420-1.html
new file mode 100644
index 0000000000..e4d3df8d48
--- /dev/null
+++ b/layout/style/crashtests/1381420-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title></title>
+<style>
+@keyframes anim {
+ from { transform: scale(1); }
+ to { transform: rotate(0deg); }
+}
+#target {
+ animation: anim 3s infinite;
+ background-color: blue;
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div>
+<div id="target"></div>
+<details id="details" open>
+ <summary>Summary</summary>
+ <p>detail description</p>
+</details>
+</div>
+<script>
+window.addEventListener('load', () => {
+ requestAnimationFrame(() => {
+ details.open = false;
+ SpecialPowers.getDOMWindowUtils(window)
+ .sendMouseEvent("mousemove", 100, 100, 1,
+ 0, 1, 0);
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+ });
+});
+</script>
diff --git a/layout/style/crashtests/1381682.html b/layout/style/crashtests/1381682.html
new file mode 100644
index 0000000000..3db2e8a4d9
--- /dev/null
+++ b/layout/style/crashtests/1381682.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<style>
+.foo :not(.bar) + baz {
+ color: red;
+}
+
+.descendant::before {
+ content: "";
+}
+</style>
+<div class="foo">
+ <div class="descendant">
+ </div>
+</div>
+<script>
+ document.body.offsetTop;
+ document.querySelector('.foo').classList.remove('foo');
+</script>
diff --git a/layout/style/crashtests/1382672.html b/layout/style/crashtests/1382672.html
new file mode 100644
index 0000000000..86526e840e
--- /dev/null
+++ b/layout/style/crashtests/1382672.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+window.onload = () => {
+ document.documentElement.animate([ { "font": "menu" }, { "font": "message-box" } ])
+}
+</script>
+</head>
+</html>
diff --git a/layout/style/crashtests/1382710.html b/layout/style/crashtests/1382710.html
new file mode 100644
index 0000000000..46c7720d79
--- /dev/null
+++ b/layout/style/crashtests/1382710.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<script>
+document.documentElement.appendChild(document.createElement("table"))
+</script>
+</head>
+<body></body>
+</html>
diff --git a/layout/style/crashtests/1383001-2.html b/layout/style/crashtests/1383001-2.html
new file mode 100644
index 0000000000..5fba0184b4
--- /dev/null
+++ b/layout/style/crashtests/1383001-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+window.onload = () => {
+ a = document.createElement("p")
+ document.documentElement.appendChild(a)
+ a.animate([])
+ document.designMode = 'on'
+ document.documentElement.appendChild(document.createElement("div"))
+}
+</script>
+</head>
+</html>
diff --git a/layout/style/crashtests/1383001.html b/layout/style/crashtests/1383001.html
new file mode 100644
index 0000000000..e963c847f6
--- /dev/null
+++ b/layout/style/crashtests/1383001.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<div id="test" contenteditable>
+ <div id="first"></div>
+</div>
+<script>
+document.body.offsetTop;
+let anim = document.createElement('div');
+anim.animate({ color: ['red', 'green' ] }, 1000);
+first.appendChild(anim);
+
+let child = document.createElement('span');
+child.innerText = 'text';
+
+requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ anim.appendChild(child);
+ test.appendChild(anim);
+ document.documentElement.className = "";
+ });
+});
+</script>
+</html>
diff --git a/layout/style/crashtests/1383319.html b/layout/style/crashtests/1383319.html
new file mode 100644
index 0000000000..960b539383
--- /dev/null
+++ b/layout/style/crashtests/1383319.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<style>
+ @keyframes anim {
+ to { background-color: green; }
+ }
+ .foo {
+ width: 100px;
+ height: 100px;
+ background: red;
+ animation: anim 10s ease;
+ }
+</style>
+<div id="test" contenteditable>
+</div>
+<script>
+document.documentElement.offsetTop;
+test.innerHTML = '<div class="foo">Some random element</div>';
+</script>
diff --git a/layout/style/crashtests/1383493-1.html b/layout/style/crashtests/1383493-1.html
new file mode 100644
index 0000000000..12a04818ad
--- /dev/null
+++ b/layout/style/crashtests/1383493-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+document.documentElement.animate([{ "transform": "matrix3d(195.311324955,58.3749549585,-94.0753846053,1.0,33.6874926451,148.902450737,122.351964713,-50.0063005423,1.0,6238.29,-236.4116913,-63.5213475335,106.192024542,57.7281969546,268.082053869,39.5424976348)" }],
+ { duration: 3000, iterationComposite: "accumulate", iterationStart: 70.6806439384 })
+</script>
+</head>
+</html>
diff --git a/layout/style/crashtests/1383589-1.html b/layout/style/crashtests/1383589-1.html
new file mode 100644
index 0000000000..16c4f456ad
--- /dev/null
+++ b/layout/style/crashtests/1383589-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+window.addEventListener('load', () => {
+ document.documentElement.animate([{ transform: 'none' }], 10000);
+ requestAnimationFrame(() => {
+ SpecialPowers.getDOMWindowUtils(window)
+ .sendMouseEvent("mousemove", 100, 100, 1, 0, 1, 0);
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove('reftest-wait');
+ });
+ });
+});
+</script>
diff --git a/layout/style/crashtests/1383975.html b/layout/style/crashtests/1383975.html
new file mode 100644
index 0000000000..5cf5ee7574
--- /dev/null
+++ b/layout/style/crashtests/1383975.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+color: url(9
+</style>
+</head>
+</html>
diff --git a/layout/style/crashtests/1383981-2.html b/layout/style/crashtests/1383981-2.html
new file mode 100644
index 0000000000..410e5ecff4
--- /dev/null
+++ b/layout/style/crashtests/1383981-2.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<style>
+div { padding: 1px; }
+.test div { padding: 2px; }
+.test div ~ div { padding: 3px; }
+.test div ~ div ~ div { background: orange; }
+.test div ~ div ~ div ~ div { background: white; }
+.test div ~ div ~ div ~ div ~ div { background: red; }
+</style>
+<body>
+<script>
+let root = document.createElement('div');
+for (let i = 0; i < 1000; ++i) {
+ let div = root.appendChild(document.createElement('div'));
+ div.appendChild(document.createTextNode(i));
+}
+document.body.appendChild(root);
+document.body.offsetTop;
+root.className = 'test';
+</script>
+</body>
diff --git a/layout/style/crashtests/1383981-3.html b/layout/style/crashtests/1383981-3.html
new file mode 100644
index 0000000000..91891d3d3d
--- /dev/null
+++ b/layout/style/crashtests/1383981-3.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<style>
+div { padding: 1px; }
+.test div { padding: 2px; }
+.test div div { padding: 3px; }
+.test div div div { background: orange; }
+.test div div div div { background: white; }
+.test div div div div div { background: red; }
+</style>
+<body>
+<script>
+let root = document.createElement('div');
+let p = root;
+for (let i = 0; i < 1000; ++i) {
+ p = p.appendChild(document.createElement('div'));
+ p.appendChild(document.createTextNode(i));
+}
+document.body.appendChild(root);
+
+// Flush styles.
+document.body.offsetTop;
+
+// Add 20 more top-level siblings to ensure that the style traversal goes
+// parallel before the deep tree is processed.
+//
+// Note that we need to make these children of the <html> element, not the
+// <body> element, because invalidations get processed by the parent when
+// enqueuing children, so the _parent_ needs to be at a level in the DOM
+// with enough dirty siblings to trigger a switch to parallel mode.
+for (let i = 0; i < 20; ++i) {
+ document.documentElement.appendChild(document.createElement('div'));
+}
+
+root.className = 'test';
+</script>
+</body>
diff --git a/layout/style/crashtests/1383981.html b/layout/style/crashtests/1383981.html
new file mode 100644
index 0000000000..94bb9700a8
--- /dev/null
+++ b/layout/style/crashtests/1383981.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<style>
+div { padding: 1px; }
+.test div { padding: 2px; }
+.test div div { padding: 3px; }
+.test div div div { background: orange; }
+.test div div div div { background: white; }
+.test div div div div div { background: red; }
+</style>
+<body>
+<script>
+let root = document.createElement('div');
+let p = root;
+for (let i = 0; i < 1000; ++i) {
+ p = p.appendChild(document.createElement('div'));
+ p.appendChild(document.createTextNode(i));
+}
+document.body.appendChild(root);
+document.body.offsetTop;
+root.className = 'test';
+</script>
+</body>
diff --git a/layout/style/crashtests/1384232.html b/layout/style/crashtests/1384232.html
new file mode 100644
index 0000000000..df9df64e81
--- /dev/null
+++ b/layout/style/crashtests/1384232.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<script>
+root = document.createElement("div")
+document.documentElement.appendChild(root)
+document.documentElement.appendChild(document.createElement("thead"))
+document.documentElement.getBoundingClientRect()
+root.appendChild(document.createElement("textarea"))
+</script>
diff --git a/layout/style/crashtests/1384824-1.html b/layout/style/crashtests/1384824-1.html
new file mode 100644
index 0000000000..6dcf485f0e
--- /dev/null
+++ b/layout/style/crashtests/1384824-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<style>
+ textarea {
+ min-height: 100px;
+ }
+</style>
+<div>
+ <iframe src="about:blank"></iframe>
+</div>
+<script>
+ let div = document.querySelector('div');
+ let iframe = document.querySelector('iframe');
+ iframe.onload = function() {
+ let doc = iframe.contentDocument;
+ let e = doc.createElement('textarea');
+ doc.body.appendChild(e);
+ setTimeout(function() {
+ getComputedStyle(e).minHeight;
+ div.style.display = 'none';
+ setTimeout(function() {
+ getComputedStyle(e).minHeight;
+ document.documentElement.className = "";
+ }, 0);
+ }, 0);
+ };
+</script>
diff --git a/layout/style/crashtests/1384824-2.html b/layout/style/crashtests/1384824-2.html
new file mode 100644
index 0000000000..65e6f29fd1
--- /dev/null
+++ b/layout/style/crashtests/1384824-2.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<style>
+ textarea {
+ min-height: 100px;
+ }
+</style>
+<div>
+ <iframe src="about:blank"></iframe>
+</div>
+<script>
+ let div = document.querySelector('div');
+ let iframe = document.querySelector('iframe');
+ iframe.onload = function() {
+ let doc = iframe.contentDocument;
+ let e = doc.createElement('textarea');
+ doc.body.appendChild(e);
+ setTimeout(function() {
+ var cs = getComputedStyle(e);
+ cs.minHeight;
+ div.style.display = 'none';
+ setTimeout(function() {
+ div.style.display = 'block';
+ setTimeout(function() {
+ cs.minHeight;
+ document.documentElement.className = "";
+ }, 0);
+ }, 0);
+ }, 0);
+ };
+</script>
diff --git a/layout/style/crashtests/1386773.html b/layout/style/crashtests/1386773.html
new file mode 100644
index 0000000000..b6267ca344
--- /dev/null
+++ b/layout/style/crashtests/1386773.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=UTF-8>
+<style>
+::-moz-selection{}
+</style>
+<script>
+window.onload = () => {
+ r = document.createRange()
+ document.getSelection().addRange(r)
+ r.setEndAfter(document.body)
+}
+</script>
+</head>
+<body>A</body>
+</html>
diff --git a/layout/style/crashtests/1387481-1-iframe.html b/layout/style/crashtests/1387481-1-iframe.html
new file mode 100644
index 0000000000..87fde769c5
--- /dev/null
+++ b/layout/style/crashtests/1387481-1-iframe.html
@@ -0,0 +1,26 @@
+<html>
+ <head>
+ <script id='x'>
+ try { o1 = document.getElementById('x') } catch(e) { }
+ try { o2 = document.createElement('table') } catch(e) { }
+ try { o3 = document.createElement('td') } catch(e) { }
+ try { o5 = document.createElement('input') } catch(e) { }
+ try { o6 = document.createTextNode('{\r\u2044/=+=\u06F0\u2029\u06F9a \uDC1D0\n-\v') } catch(e) { }
+ try { o7 = new Image(0.167398293512524, 0.2503646329685738) } catch(e) { }
+ try { o1.appendChild(o2) } catch(e) { }
+ try { o2.appendChild(o3) } catch(e) { }
+ try { o3.appendChild(o6) } catch(e) { }
+ try { o8 = document.createRange() } catch(e) { }
+ try { document.documentElement.appendChild(o7) } catch(e) { }
+ try { o7.outerHTML = '<select contenteditable="true"></select>' } catch(e) { }
+ try { document.documentElement.appendChild(o5) } catch(e) { }
+ try { o9 = window.getSelection() } catch(e) { }
+ try { o5.select() } catch(e) { }
+ try { document.replaceChild(document.documentElement, document.documentElement) } catch(e) { }
+ try { document.designMode = 'on' } catch(e) { }
+ try { o8.selectNode(o6) } catch(e) { }
+ try { o9.addRange(o8) } catch(e) { }
+ try { document.execCommand('unlink', false, null) } catch(e) { }
+ </script>
+ </head>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1387481-1.html b/layout/style/crashtests/1387481-1.html
new file mode 100644
index 0000000000..98783d1666
--- /dev/null
+++ b/layout/style/crashtests/1387481-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<iframe id="f" src="1387481-1-iframe.html" onload="step()"></iframe>
+<script>
+var reloads = 3;
+function step() {
+ if (--reloads) {
+ f.contentWindow.location.reload();
+ } else {
+ document.documentElement.className = "";
+ }
+}
+</script>
diff --git a/layout/style/crashtests/1387499.html b/layout/style/crashtests/1387499.html
new file mode 100644
index 0000000000..86afa3e84f
--- /dev/null
+++ b/layout/style/crashtests/1387499.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <script>
+ try { o1 = document.createElement('code') } catch(e) { }
+ try { o2 = document.createElement('iframe') } catch(e) { }
+ try { document.documentElement.appendChild(o2) } catch(e) { }
+ try { o3 = o2.getSVGDocument() } catch(e) { }
+ try { o4 = document.caretPositionFromPoint(1, 1) } catch(e) { }
+ try { o3.head.appendChild(o1); } catch(e) { }
+ try { document.replaceChild(document.documentElement, document.documentElement); } catch(e) { }
+ try { document.replaceChild(o3.firstElementChild, document.documentElement); } catch(e) { }
+ window.close()
+ </script>
+ </head>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1388234.html b/layout/style/crashtests/1388234.html
new file mode 100644
index 0000000000..db4d52a5ef
--- /dev/null
+++ b/layout/style/crashtests/1388234.html
@@ -0,0 +1,9 @@
+<style></style>
+<script>
+ try { o1 = document.createElement('th') } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { document.styleSheets[0].insertRule("* { }", 0); } catch(e) { }
+ try { document.documentElement.getBoundingClientRect() } catch(e) { }
+ try { document.styleSheets[0].insertRule("* { unicode-bidi: bidi-override}", 0); } catch(e) { }
+ try { o1.rowSpan = 32 } catch(e) { }
+</script>
diff --git a/layout/style/crashtests/1389645.html b/layout/style/crashtests/1389645.html
new file mode 100644
index 0000000000..85d3314478
--- /dev/null
+++ b/layout/style/crashtests/1389645.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<script>
+window.onload = () => {
+ a = document.createElement("x")
+ document.documentElement.appendChild(a)
+ b = document.createElement("span")
+ document.documentElement.appendChild(b)
+ document.documentElement.getBoundingClientRect()
+ a.appendChild(document.createElement('div'))
+ b.innerText = "\uFDDE\r\u0301\n"
+}
+</script>
diff --git a/layout/style/crashtests/1390726.html b/layout/style/crashtests/1390726.html
new file mode 100644
index 0000000000..308c56627b
--- /dev/null
+++ b/layout/style/crashtests/1390726.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+window.onload = () => {
+ a = document.createElement("frameset")
+ document.documentElement.appendChild(a)
+ b = document.createElement("f")
+ b.setAttribute("class", "x")
+ a.appendChild(b)
+ c = document.createElement("t")
+ document.documentElement.appendChild(c)
+ d = document.createElement('m')
+ d.setAttribute("class", "x")
+ c.appendChild(d)
+ f = document.createElement('d')
+ d.appendChild(f)
+ g = document.createElement('n')
+ f.appendChild(g)
+ setTimeout(() => {
+ g.setAttribute("class", "")
+ sheet = document.createElement("style")
+ sheet.appendChild(document.createTextNode(".x:-moz-broken{"))
+ document.head.appendChild(sheet)
+ document.documentElement.className = "";
+ }, 500)
+}
+</script>
diff --git a/layout/style/crashtests/1391577.html b/layout/style/crashtests/1391577.html
new file mode 100644
index 0000000000..b0e3211c65
--- /dev/null
+++ b/layout/style/crashtests/1391577.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<style>
+.foo input[type="range"]::-moz-range-thumb:hover {
+ color: red;
+}
+</style>
+<div>
+ <input type="range"></input>
+</div>
+<script>
+onload = function() {
+ document.querySelector('div').classList.add('foo');
+}
+</script>
diff --git a/layout/style/crashtests/1393189.html b/layout/style/crashtests/1393189.html
new file mode 100644
index 0000000000..5a0bfc7244
--- /dev/null
+++ b/layout/style/crashtests/1393189.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+@keyframes x {
+ 0% {
+ list-style-type: c;
+ }
+}
+div {
+ animation-name: x;
+ list-style-type: t;
+}
+</style>
+<div></div>
diff --git a/layout/style/crashtests/1393580.html b/layout/style/crashtests/1393580.html
new file mode 100644
index 0000000000..4a59016308
--- /dev/null
+++ b/layout/style/crashtests/1393580.html
@@ -0,0 +1,10 @@
+<style>
+* {
+ scroll-snap-points-x: repeat(calc(1px));
+}
+</style>
+<script>
+window.onload = () => {
+ document.documentElement.animate([{"scrollSnapPointsX": "repeat(1px)"}])
+}
+</script>
diff --git a/layout/style/crashtests/1393791.html b/layout/style/crashtests/1393791.html
new file mode 100644
index 0000000000..ad045f9a7c
--- /dev/null
+++ b/layout/style/crashtests/1393791.html
@@ -0,0 +1,12 @@
+<script>
+window.onload = () => {
+ a = document.createElement("p")
+ document.documentElement.appendChild(a)
+ b = document.createElement("caption")
+ document.documentElement.appendChild(b)
+ a.insertAdjacentHTML("afterEnd", "<audio controls></audio><iframe>");
+ document.documentElement.getBoundingClientRect()
+ document.documentElement.appendChild(document.createElement("tr"))
+ b.style.borderCollapse = "collapse"
+}
+</script>
diff --git a/layout/style/crashtests/1395719.html b/layout/style/crashtests/1395719.html
new file mode 100644
index 0000000000..5cb4aa0c7a
--- /dev/null
+++ b/layout/style/crashtests/1395719.html
@@ -0,0 +1,19 @@
+<script>
+o = []
+window.onload = () => {
+ a = document.createElement("body");
+ b = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math");
+ b.setAttributeNS(null, "display", "block");
+ c = document.createElementNS("http://www.w3.org/1998/Math/MathML", "munderover");
+ d = document.createTextNode("\n ");
+ c.appendChild(d);
+ c.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "mo"));
+ b.appendChild(c);
+ a.appendChild(b);
+ document.documentElement.appendChild(a);
+ a.style.display = "inline";
+ setTimeout(() => {
+ d.data = "\u934F" + d.data;
+ }, 100)
+}
+</script>
diff --git a/layout/style/crashtests/1395725.html b/layout/style/crashtests/1395725.html
new file mode 100644
index 0000000000..0ff8b4d10c
--- /dev/null
+++ b/layout/style/crashtests/1395725.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABQCAYAAADvCdDvAAAAZElEQVR4nO3RQQ0AIBDAMPybBgu8YJe0DpatxbX9wO/GUQyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAGQ4uFciajNXnVwAAAABJRU5ErkJggg==" usemap="#map">
+<map name="map"><area></map>
+<script>
+window.onload = () => {
+ let d = document.querySelector('area');
+ setTimeout(() => {
+ d.appendChild(document.createElement('div'));
+ document.body.offsetHeight;
+ document.documentElement.className = '';
+ }, 100);
+}
+</script>
+</html>
diff --git a/layout/style/crashtests/1396041.html b/layout/style/crashtests/1396041.html
new file mode 100644
index 0000000000..4bca993486
--- /dev/null
+++ b/layout/style/crashtests/1396041.html
@@ -0,0 +1,9 @@
+<script>
+document.documentElement.appendChild(document.createElement("meter"))
+a = document.createElement("textarea")
+document.documentElement.appendChild(a)
+b = document.createElement("style")
+b.appendChild(document.createTextNode("*::-moz-meter-bar { text-indent: calc(50%); scroll-behavior: smooth; transition-duration: 250ms; }"))
+a.select()
+document.head.appendChild(b)
+</script>
diff --git a/layout/style/crashtests/1397091.html b/layout/style/crashtests/1397091.html
new file mode 100644
index 0000000000..54dfb4fd51
--- /dev/null
+++ b/layout/style/crashtests/1397091.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<div id="wrapper" style="display: flex">
+ Some anon flex item.
+ <div id="item">
+ Foo bar.
+ <span> Baz</span>
+ </div>
+</div>
+<script>
+ document.body.offsetTop;
+ item.style.color = "red";
+ wrapper.firstChild.textContent = "";
+</script>
diff --git a/layout/style/crashtests/1397363-1.html b/layout/style/crashtests/1397363-1.html
new file mode 100644
index 0000000000..5ccbfa3176
--- /dev/null
+++ b/layout/style/crashtests/1397363-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+a = document.documentElement.animate([{ "clip": "rect(632vw,0,103.9vmax,5ex)" }],
+ { duration:96.2272536276,
+ iterations:Number.POSITIVE_INFINITY,
+ iterationComposite:"accumulate" });
+a.playbackRate = 3000;
+</script>
+</head>
+</html>
diff --git a/layout/style/crashtests/1397439-1.html b/layout/style/crashtests/1397439-1.html
new file mode 100644
index 0000000000..b617f8e0ed
--- /dev/null
+++ b/layout/style/crashtests/1397439-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<math>
+<mstyle scriptlevel=101>
+<mstyle scriptlevel=-204>
+</math>
+
diff --git a/layout/style/crashtests/1398479.html b/layout/style/crashtests/1398479.html
new file mode 100644
index 0000000000..85d49274a2
--- /dev/null
+++ b/layout/style/crashtests/1398479.html
@@ -0,0 +1 @@
+<table align="center" hspace="1">
diff --git a/layout/style/crashtests/1398581.html b/layout/style/crashtests/1398581.html
new file mode 100644
index 0000000000..e056ab44d5
--- /dev/null
+++ b/layout/style/crashtests/1398581.html
@@ -0,0 +1,17 @@
+<script>
+function start() {
+ o3=document.createElement('div');
+ document.body.appendChild(o3);
+ o14=document.createElement('style');
+ document.documentElement.appendChild(o14);
+ o18=document.createElement('style');
+ o14.appendChild(o18);
+ s4=unescape('%u06A10');
+ o3.appendChild(document.createTextNode(s4));
+ o59=document.createTextNode("{}:first-letter{ all: inherit;'x'}\n*{ float: left}:first-line{");
+ o18['before'](o18,-1,o59);
+ document.documentElement.offsetHeight;
+ o3.appendChild(document.createTextNode("x"));
+}
+</script>
+<body onload="start()"></body>
diff --git a/layout/style/crashtests/1399006.html b/layout/style/crashtests/1399006.html
new file mode 100644
index 0000000000..20e9e79869
--- /dev/null
+++ b/layout/style/crashtests/1399006.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<script>
+window.onload=function(){
+ let n=document.getElementById('a');
+ n.parentNode.removeChild(n);
+ document.designMode='on';
+}
+</script>
+<wbr/>
+<script></script>
+<img space='-155.002502498'/>
+<blockquote style="display:ruby-text-container">
+<mark id='a'></mark>
+</blockquote>
+<video></video>
+<area/>
+<section></section>
+<img/>
+<noscript></noscript>
+<bdo></bdo>
+<select></select>
+<select></select>
+<option></option>
+<figure style="border-image:stretch url(data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=) 27% fill / / 297.456633622ex"></figure>
+<wbr/>
+<pre></pre>
+<table>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1399546.html b/layout/style/crashtests/1399546.html
new file mode 100644
index 0000000000..883ee9fd65
--- /dev/null
+++ b/layout/style/crashtests/1399546.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<script>
+var d = document
+var de = d.documentElement
+de.appendChild(d.createElement("x"))
+de.appendChild(d.createElement("x"))
+de.appendChild(d.createElement("x"))
+de.appendChild(d.createElement("body"))
+
+// Should be enough of them in order for styling to be paralelizable.
+for (var i = 0; i < 100; ++i)
+ de.appendChild(d.createElement("x"))
+
+var t = d.createElement("x")
+de.appendChild(t)
+t.appendChild(d.createElement("table"))
+</script>
diff --git a/layout/style/crashtests/1400035.html b/layout/style/crashtests/1400035.html
new file mode 100644
index 0000000000..fb7a56ffbb
--- /dev/null
+++ b/layout/style/crashtests/1400035.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html id="landing-page-home">
+<style>
+@keyframes opacity {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+#circle {
+ animation: opacity 1s infinite;
+}
+</style>
+<svg width="100" height="100">
+ <mask id="mask">
+ <circle id="circle" cx="50" cy="50" r="50" fill="red"/>
+ </mask>
+ <rect class="rect" x="0" y="0" width="100" height="100" fill="blue" mask="url(#mask)"/>
+</svg>
+</html>
diff --git a/layout/style/crashtests/1400325.html b/layout/style/crashtests/1400325.html
new file mode 100644
index 0000000000..39e33a53eb
--- /dev/null
+++ b/layout/style/crashtests/1400325.html
@@ -0,0 +1,6 @@
+<style>
+* {
+ padding-right: 2em;
+ font-size: calc(1pt + 5% + 0% + 4em);
+}
+</style>
diff --git a/layout/style/crashtests/1400926.html b/layout/style/crashtests/1400926.html
new file mode 100644
index 0000000000..f49030759e
--- /dev/null
+++ b/layout/style/crashtests/1400926.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+@keyframes anim {
+ 0% { background-color: black; }
+ 100% { background-color: yellow; }
+}
+</style>
+<script>
+document.styleSheets[0].cssRules[0].cssRules[0].style.setProperty('background-color', 'red', 'important');
+</script>
diff --git a/layout/style/crashtests/1400936-1.html b/layout/style/crashtests/1400936-1.html
new file mode 100644
index 0000000000..3af80a73d3
--- /dev/null
+++ b/layout/style/crashtests/1400936-1.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<style>
+fieldset:invalid {
+ border: 10px solid red;
+}
+fieldset:valid {
+ border: 10px solid green;
+}
+input.foo {
+ color: green;
+}
+</style>
+<div>
+ <fieldset id="fieldset">
+ <div id="ancestor">
+ <input type="text" id="requiredInput" required>
+ <input type="text" id="other">
+ </div>
+ </fieldset>
+</div>
+<script>
+window.onload = function() {
+ other.classList.add('foo');
+ ancestor.remove();
+};
+</script>
diff --git a/layout/style/crashtests/1400936-2.html b/layout/style/crashtests/1400936-2.html
new file mode 100644
index 0000000000..b8cdc873da
--- /dev/null
+++ b/layout/style/crashtests/1400936-2.html
@@ -0,0 +1,12 @@
+<script>
+document.addEventListener("DOMContentLoaded", function(){
+ document.getElementById('a').appendChild(document.createElement('rtc'));
+ let e = document.getElementById('b');
+ document.getElementById('c').appendChild(e.parentNode.removeChild(e));
+});
+</script>
+<body dir='auto'>
+<abbr id='b'>
+&#x79C;
+<meter id='c'>
+<keygen id='a'>
diff --git a/layout/style/crashtests/1401256.html b/layout/style/crashtests/1401256.html
new file mode 100644
index 0000000000..da0a579504
--- /dev/null
+++ b/layout/style/crashtests/1401256.html
@@ -0,0 +1,5 @@
+<script>
+ let o1 = document.createElement('p');
+ document.documentElement.appendChild(o1);
+ o1.animate({'minWidth':['max-content']});
+</script>
diff --git a/layout/style/crashtests/1401706.html b/layout/style/crashtests/1401706.html
new file mode 100644
index 0000000000..7443acd8c9
--- /dev/null
+++ b/layout/style/crashtests/1401706.html
@@ -0,0 +1,10 @@
+<style></style>
+<script>
+ try { o1 = document.createElement('r') } catch(e) { }
+ try { o2 = document.createElement('input') } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { document.documentElement.appendChild(o2) } catch(e) { }
+ try { document.styleSheets[0].insertRule('*, :first-line { float:left }', 0); } catch(e) { }
+ try { o1.getClientRects() } catch(e) { }
+ try { o2.type = 'url' } catch(e) { }
+</script>
diff --git a/layout/style/crashtests/1401801.html b/layout/style/crashtests/1401801.html
new file mode 100644
index 0000000000..1dedd1fff1
--- /dev/null
+++ b/layout/style/crashtests/1401801.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<style>
+#test {
+ display: none;
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="test"></div>
+<script>
+ test.animate({ backgroundColor: [ 'red', 'blue' ] },
+ { duration: 1000,
+ iterations: Infinity });
+
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ document.styleSheets[0].cssRules[0].style.setProperty('display', 'block');
+ test.getBoundingClientRect();
+ document.documentElement.classList.remove('reftest-wait');
+ });
+ });
+</script>
+</html>
diff --git a/layout/style/crashtests/1401825.html b/layout/style/crashtests/1401825.html
new file mode 100644
index 0000000000..d42aaeefa3
--- /dev/null
+++ b/layout/style/crashtests/1401825.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<style>
+::marker {
+ -moz-appearance: button;
+}
+</style>
+<li></li>
diff --git a/layout/style/crashtests/1402218-1.html b/layout/style/crashtests/1402218-1.html
new file mode 100644
index 0000000000..597c1c4d5e
--- /dev/null
+++ b/layout/style/crashtests/1402218-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<style>
+#wrapper::first-line {}
+</style>
+<body>
+<div id="wrapper">
+ <div id="test"></div>
+</div>
+<script>
+ document.querySelector('#test').style.position = 'absolute';
+ document.body.offsetHeight;
+ document.querySelector('#wrapper').style.color = 'green';
+ document.querySelector('#test').style.position = '';
+</script>
+</body>
diff --git a/layout/style/crashtests/1402366.html b/layout/style/crashtests/1402366.html
new file mode 100644
index 0000000000..9cffd08f04
--- /dev/null
+++ b/layout/style/crashtests/1402366.html
@@ -0,0 +1,10 @@
+<script>
+document.documentElement.appendChild(document.createElement('x'))
+a = document.createElement('x')
+document.documentElement.appendChild(a)
+new Range().getClientRects()
+document.documentElement.appendChild(document.createElement('tr'))
+b = document.createElement('input')
+b.type = 'date'
+document.documentElement.insertBefore(b, a)
+</script>
diff --git a/layout/style/crashtests/1402419.html b/layout/style/crashtests/1402419.html
new file mode 100644
index 0000000000..052de43834
--- /dev/null
+++ b/layout/style/crashtests/1402419.html
@@ -0,0 +1,3 @@
+<style>
+html { color: -8192e17%; }
+</style>
diff --git a/layout/style/crashtests/1402472.html b/layout/style/crashtests/1402472.html
new file mode 100644
index 0000000000..e2842a928b
--- /dev/null
+++ b/layout/style/crashtests/1402472.html
@@ -0,0 +1,13 @@
+<style>
+* { display: contents; }
+</style>
+<script>
+a = document.createElement('map')
+document.documentElement.appendChild(a)
+a.appendChild(document.head)
+document.documentElement.getBoundingClientRect()
+document.designMode = "on"
+document.designMode = "off"
+a.appendChild(document.createElement('frame'))
+document.designMode = "on"
+</script>
diff --git a/layout/style/crashtests/1403028.html b/layout/style/crashtests/1403028.html
new file mode 100644
index 0000000000..485a08efde
--- /dev/null
+++ b/layout/style/crashtests/1403028.html
@@ -0,0 +1,9 @@
+<style>:invalid { color: red; }</style>
+<form id="theForm"></form>
+<button id="button" form="theForm">
+<script>
+window.onload = function() {
+ button.setCustomValidity("foo");
+ document.body.remove();
+}
+</script>
diff --git a/layout/style/crashtests/1403433.html b/layout/style/crashtests/1403433.html
new file mode 100644
index 0000000000..58ef5d8099
--- /dev/null
+++ b/layout/style/crashtests/1403433.html
@@ -0,0 +1,6 @@
+<style>
+#b { -webkit-filter: url(#a); }
+</style>
+<iframe id="a" height="0" src="data:v;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAA5NtZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0OCByMjY0MyA1YzY1NzA0IC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTEgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAvWWIhAAh/9PWYQ7q+jvvWOfBgvpv0eIYkqWiQW6SsLQx8ByoouBLEC9HBQTAXOJh/wFnteOP+NH5Er2DeHrP4kxvjj4nXKG9Zm/FycSAdlzoMDOFc4CmXmCL51Dj+zekurxKazOLwXVd7f/rOQpa9+iPXYTZsRw+WFFNokI8saLT7Mt03UvGxwdAYkwe7UmwPZacue5goP6rQhBgGMjgK21nSHZWUcz5Y6Ec/wdCPp0Sxx/h6UsSneF9hINuvwAAAAhBmiJsQx92QAAAAAgBnkF5DH/EgQAAAzRtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAZAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACXnRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAZAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAIAAAACAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAGQAAAQAAAEAAAAAAdZtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAADwAAAAGAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAGBbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABQXN0YmwAAACVc3RzZAAAAAAAAAABAAAAhWF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAIAAgAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAvYXZjQwFkAAr/4QAWZ2QACqzZSWhAAAADAEAAAA8DxIllgAEABmjr48siwAAAABhzdHRzAAAAAAAAAAEAAAADAAACAAAAABRzdHNzAAAAAAAAAAEAAAABAAAAKGN0dHMAAAAAAAAAAwAAAAEAAAQAAAAAAQAABgAAAAABAAACAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAwAAAAEAAAAgc3RzegAAAAAAAAAAAAAAAwAAA3MAAAAMAAAADAAAABRzdGNvAAAAAAAAAAEAAAAwAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDEnow">
+</iframe>
+<table id="b">
diff --git a/layout/style/crashtests/1403465.html b/layout/style/crashtests/1403465.html
new file mode 100644
index 0000000000..924392757b
--- /dev/null
+++ b/layout/style/crashtests/1403465.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <math class="hidden">
+ <mi>x</mi>
+ <mo>=</mo>
+ </math>
+<script>
+window.onload = function() {
+ let s = document.createElement("style");
+ s.textContent = `
+ body {
+ line-height: 1.42857143;
+ }
+
+ .hidden {
+ display: none;
+ }
+ `;
+ document.body.appendChild(s);
+};
+</script>
+</body>
+</html>
diff --git a/layout/style/crashtests/1403592.html b/layout/style/crashtests/1403592.html
new file mode 100644
index 0000000000..184096c11c
--- /dev/null
+++ b/layout/style/crashtests/1403592.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<style></style>
+<script>
+function boom() {
+ a = document.createElement("x")
+ a.style.overflow = "o"
+ document.styleSheets[0].insertRule("i {}", 0)
+ setTimeout(function() {
+ document.documentElement.className = "";
+ }, 0);
+}
+setTimeout(boom, 0)
+</script>
+</head>
+<i id=id0 style="margin: 15ch">
+<svg><animate xlink:href=#id0 attributeName=width to></svg>
+</html>
diff --git a/layout/style/crashtests/1403615.html b/layout/style/crashtests/1403615.html
new file mode 100644
index 0000000000..ab2c27f7b8
--- /dev/null
+++ b/layout/style/crashtests/1403615.html
@@ -0,0 +1,24 @@
+<html class="reftest-wait">
+<script>
+window.onload = () => {
+ a = document.createElement("body")
+ b = document.createElement("span")
+ a.appendChild(b)
+ c = document.createElement("div")
+ d = document.createElement("div")
+ c.appendChild(d)
+ a.appendChild(c)
+ document.documentElement.appendChild(a)
+ setTimeout(() => {
+ a.style.display = "table-column-group"
+ d.appendChild(document.createTextNode("\u05D2"))
+ setTimeout(() => {
+ d.appendChild(document.createElement("span"))
+ b.style.zIndex = "1073741824"
+ document.documentElement.offsetTop;
+ document.documentElement.className = "";
+ }, 0)
+ }, 0)
+}
+</script>
+</html>
diff --git a/layout/style/crashtests/1403712.html b/layout/style/crashtests/1403712.html
new file mode 100644
index 0000000000..1166a11964
--- /dev/null
+++ b/layout/style/crashtests/1403712.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<style>
+ :indeterminate { color: red; }
+</style>
+<div id="container">
+ <fieldset>
+ <label>
+ <input name="layout" type="radio">Foo
+ </label>
+ <label>
+ <input name="layout" type="radio">Bar
+ </label>
+ <label>
+ <input name="layout" type="radio">Baz
+ </label>
+ <label>
+ <input name="layout" type="radio">Buz
+ </label>
+ </fieldset>
+</div>
+<script>
+container.querySelector('input').checked = true;
+document.body.offsetTop;
+container.remove();
+document.body.offsetTop;
+</script>
diff --git a/layout/style/crashtests/1404057.html b/layout/style/crashtests/1404057.html
new file mode 100644
index 0000000000..4c4a33ce49
--- /dev/null
+++ b/layout/style/crashtests/1404057.html
@@ -0,0 +1,6 @@
+<html>
+ <title>Testcase, bug 143862</title>
+ <head>
+<svg><text><t style='all:initial'>aaa</t></text></svg>
+ </head>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1404180-1.html b/layout/style/crashtests/1404180-1.html
new file mode 100644
index 0000000000..079d6800f5
--- /dev/null
+++ b/layout/style/crashtests/1404180-1.html
@@ -0,0 +1,22 @@
+<script>
+function jsfuzzer() {
+ try { htmlvar00016.appendChild(htmlvar00017); } catch(e) { }
+ try { htmlvar00016.form.setAttribute("novalidate", "novalidate"); } catch(e) { }
+ try { htmlvar00017.appendChild(htmlvar00036); } catch(e) { }
+ try { svgvar00007.appendChild(htmlvar00008); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<form id="htmlvar00007">
+<legend id="htmlvar00008">
+<output id="htmlvar00016"></output>
+</legend>
+<link id="htmlvar00017"></link>
+<svg>
+<path id="svgvar00006">
+<animateTransform id="svgvar00007"/>
+</path>
+<use xlink:href="#svgvar00006">
+<table id="htmlvar00036">
+<th>
+<output form="htmlvar00007">
diff --git a/layout/style/crashtests/1404316.html b/layout/style/crashtests/1404316.html
new file mode 100644
index 0000000000..4ac2f16701
--- /dev/null
+++ b/layout/style/crashtests/1404316.html
@@ -0,0 +1,20 @@
+<html contenteditable="">
+ <style></style>
+ <script>
+ try { o1 = document.getSelection() } catch(e) { }
+ try { o2 = document.createElement('table') } catch(e) { }
+ try { o3 = document.createElement('r') } catch(e) { }
+ try { o4 = document.createElement('l') } catch(e) { }
+ try { o5 = document.createElement('k') } catch(e) { }
+ try { o6 = document.createElement('u') } catch(e) { }
+ try { document.documentElement.appendChild(o2) } catch(e) { }
+ try { document.documentElement.appendChild(o3) } catch(e) { }
+ try { document.documentElement.appendChild(o4) } catch(e) { }
+ try { document.documentElement.appendChild(o5) } catch(e) { }
+ try { document.designMode = "on"; } catch(e) { }
+ try { document.designMode = "off"; } catch(e) { }
+ try { o1.selectAllChildren(o2) } catch(e) { }
+ try { document.styleSheets[0].insertRule("e{", 0); } catch(e) { }
+ try { document.documentElement.appendChild(o6) } catch(e) { }
+ </script>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1404324-1.html b/layout/style/crashtests/1404324-1.html
new file mode 100644
index 0000000000..574a5437cb
--- /dev/null
+++ b/layout/style/crashtests/1404324-1.html
@@ -0,0 +1,12 @@
+<style></style>
+<script>
+document.documentElement.className = 'c1'
+o1 = document.createElement('form')
+o2 = document.createElement('e')
+o1.className = 'c2'
+document.documentElement.appendChild(o1)
+document.documentElement.appendChild(o2)
+document.styleSheets[0].insertRule('.c1:first-line, .c2 { position:fixed', 0);
+document.documentElement.getBoundingClientRect()
+document.styleSheets[0].cssRules[0].style.position = 'relative'
+</script>
diff --git a/layout/style/crashtests/1404324-2.html b/layout/style/crashtests/1404324-2.html
new file mode 100644
index 0000000000..797347d5c0
--- /dev/null
+++ b/layout/style/crashtests/1404324-2.html
@@ -0,0 +1,10 @@
+<style>
+ del, *::first-line {
+ position: absolute;
+ }
+</style>
+<del></del>
+<script>
+ document.documentElement.offsetTop;
+ document.styleSheets[0].cssRules[0].style.position = 'sticky'
+</script>
diff --git a/layout/style/crashtests/1404324-3.html b/layout/style/crashtests/1404324-3.html
new file mode 100644
index 0000000000..3b06f12a2b
--- /dev/null
+++ b/layout/style/crashtests/1404324-3.html
@@ -0,0 +1,14 @@
+<style>
+del {
+ position: absolute;
+}
+
+body::first-line {
+ color: red;
+}
+</style>
+<del></del>
+<script>
+ document.documentElement.offsetTop;
+ document.styleSheets[0].cssRules[0].style.position = 'sticky'
+</script>
diff --git a/layout/style/crashtests/1405880.html b/layout/style/crashtests/1405880.html
new file mode 100644
index 0000000000..aad53507a7
--- /dev/null
+++ b/layout/style/crashtests/1405880.html
@@ -0,0 +1,21 @@
+<style>
+input:invalid {}
+</style>
+<script>
+function eventhandler1() {
+try { htmlvar00005.select(); } catch(e) { }
+try { htmlvar00009.appendChild(htmlvar00004); } catch(e) { }
+try { htmlvar00013.innerText = ""; } catch(e) { }
+}
+function eventhandler2() {
+try { htmlvar00011.addEventListener("DOMSubtreeModified", eventhandler1); } catch(e) { }
+try { htmlvar00011.style.setProperty("text-transform", "full-width"); } catch(e) { }
+}
+</script>
+<marquee id="htmlvar00004">
+<input id="htmlvar00005" required="">
+</marquee>
+<form id="htmlvar00009">
+<legend id="htmlvar00011">
+<q id="htmlvar00013">
+<image onload="eventhandler2()" src="data:;base64,R0lGODlhIAAAAAAAACH5BAAAAAAALAAAAAgACAAAAO">
diff --git a/layout/style/crashtests/1406222-1.html b/layout/style/crashtests/1406222-1.html
new file mode 100644
index 0000000000..218a3da095
--- /dev/null
+++ b/layout/style/crashtests/1406222-1.html
@@ -0,0 +1,25 @@
+<script>
+window.addEventListener("load", function(event) {
+ setTimeout(window.close, 1000);
+});
+</script>
+<!-- a -->
+<style id="s1">
+:not(video) { position: fixed; }
+a::first-line {}
+*, .class3 { columns: 0px; }
+</style>
+<script>
+function jsfuzzer() {
+try { s1.appendChild(htmlvar00009); } catch(e) { }
+try { htmlvar00001.scrollIntoView(true); } catch(e) { }
+try { htmlvar00003.href = undefined; } catch(e) { }
+try { document.createEvent("1"); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<shadow id="htmlvar00001">
+<a id="htmlvar00003">
+</a>
+<a id="htmlvar00009">
+<!-- a -->
diff --git a/layout/style/crashtests/1406222-2.html b/layout/style/crashtests/1406222-2.html
new file mode 100644
index 0000000000..3ae655d972
--- /dev/null
+++ b/layout/style/crashtests/1406222-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<style>
+a { display: block; }
+a { columns: 0px; }
+a::first-line {}
+</style>
+<script>
+onload = function() {
+ document.body.offsetWidth;
+ document.body.style.color = "green";
+ document.body.offsetWidth;
+ document.querySelector("a").href = "Something";
+}
+</script>
+<a>Some text</a>
diff --git a/layout/style/crashtests/1409183.html b/layout/style/crashtests/1409183.html
new file mode 100644
index 0000000000..7fe21032cd
--- /dev/null
+++ b/layout/style/crashtests/1409183.html
@@ -0,0 +1,15 @@
+<script>
+function jsfuzzer() {
+try { htmlvar00043.defaultChecked = true; } catch(e) { }
+try { svgvar00002.appendChild(htmlvar00040); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<table>
+<svg id="svgvar00001">
+<title id="svgvar00002">Title</title>
+</table>
+<svg>
+<use xlink:href="#svgvar00001"><body xmlns="">
+<label id="htmlvar00040">
+<input id="htmlvar00043" name="x" type="radio">
diff --git a/layout/style/crashtests/1409502.html b/layout/style/crashtests/1409502.html
new file mode 100644
index 0000000000..33c5d38728
--- /dev/null
+++ b/layout/style/crashtests/1409502.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <style>
+ HTML:first-line, DIV { position:fixed }
+ </style>
+ <script>
+ try { o1 = document.createElement('DIV') } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { document.documentElement.getBoundingClientRect() } catch(e) { }
+ try { document.styleSheets[0].cssRules[0].style['position'] = 'relative' } catch(e) { }
+ </script>
+ </head>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1409931.html b/layout/style/crashtests/1409931.html
new file mode 100644
index 0000000000..00a8272f94
--- /dev/null
+++ b/layout/style/crashtests/1409931.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<map id="htmlvar00005">
+<area></area>
+<input
+ id="htmlvar00019"
+ type="image"
+ usemap="#htmlvar00005"
+ src="data:image/gif;base64,R0lGODlhIAAgAPIBAGbMzP///wAAADOZZpn/zAAAAAAAAAAAACH5BAAAAAAALAAAAAAgACAAAAOLGLrc/k7ISau9S5DNu/8fICgaYJ5oqqbDGJRrLAMtScw468J5Xr+3nm8XFM5+PGMMWYwxcMyZ40iULQaDhSzqDGBNisGyuhUDrmNb72pWcaXhtpsM/27pVi8UX96rcQpDf3V+QD12d4NKK2+Lc4qOKI2RJ5OUNHyXSDRYnZ6foKAuLxelphMQqaoPCQA7">
+<script>
+onload = () => {
+ document.body.offsetTop;
+ document.querySelector('area').style.color = "red";
+}
+</script>
diff --git a/layout/style/crashtests/1410226-1.html b/layout/style/crashtests/1410226-1.html
new file mode 100644
index 0000000000..0ced6eefed
--- /dev/null
+++ b/layout/style/crashtests/1410226-1.html
@@ -0,0 +1,8 @@
+<style>
+ * { display: contents; }
+</style>
+<details>
+</details>
+<marquee>
+<ul>
+<canvas>
diff --git a/layout/style/crashtests/1410226-2.html b/layout/style/crashtests/1410226-2.html
new file mode 100644
index 0000000000..c02e585512
--- /dev/null
+++ b/layout/style/crashtests/1410226-2.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<style>
+ * { display: contents; }
+</style>
+<marquee>
+<div id="target">
+</div>
+</marquee>
+<script>
+document.body.offsetTop;
+let div = document.createElement('div');
+div.style.display = "block";
+div.appendChild(document.createTextNode('where am I'));
+target.appendChild(div);
+document.body.style.color = "red";
+</script>
diff --git a/layout/style/crashtests/1411008.html b/layout/style/crashtests/1411008.html
new file mode 100644
index 0000000000..9621a559ee
--- /dev/null
+++ b/layout/style/crashtests/1411008.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<script>
+ try { o1 = document.createElement('c') } catch(e) { }
+ try { o2 = document.createElement('p') } catch(e) { }
+ try { o3 = document.createElement('progress') } catch(e) { }
+ try { o4 = document.createElement('legend') } catch(e) { }
+ try { o5 = document.createElement('m') } catch(e) { }
+ try { o6 = new Range() } catch(e) { }
+ try { o7 = window.getSelection() } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { document.documentElement.appendChild(o2) } catch(e) { }
+ try { document.documentElement.appendChild(o3) } catch(e) { }
+ try { o1.scrollTopMax } catch(e) { }
+ try { o2.outerHTML = '<b contenteditable="">' } catch(e) { }
+ try { o7.modify('move', 'right', 'line') } catch(e) { }
+ try { o1.prepend(o4) } catch(e) { }
+ try { document.documentElement.appendChild(o5) } catch(e) { }
+ try { o5.scrollLeft = 8 } catch(e) { }
+ try { document.designMode = 'on'; } catch(e) { }
+ try { o7.addRange(o6); } catch(e) { }
+ try { document.execCommand('inserttext') } catch(e) { }
+</script>
diff --git a/layout/style/crashtests/1411143.html b/layout/style/crashtests/1411143.html
new file mode 100644
index 0000000000..adf3f2c042
--- /dev/null
+++ b/layout/style/crashtests/1411143.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<link rel="stylesheet" href="data:image/png,">
+<script>
+window.onload = function() {
+ document.styleSheets[0].insertRule("* { marker: url(#a); }", 0);
+};
+</script>
diff --git a/layout/style/crashtests/1411478.html b/layout/style/crashtests/1411478.html
new file mode 100644
index 0000000000..fcbf640186
--- /dev/null
+++ b/layout/style/crashtests/1411478.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+ document.write('<frameset><frame hidden>');
+ document.documentElement.offsetHeight;
+ document.querySelector('frame').appendChild(document.createElement('div'));
+</script>
diff --git a/layout/style/crashtests/1413288.html b/layout/style/crashtests/1413288.html
new file mode 100644
index 0000000000..cab30aee5b
--- /dev/null
+++ b/layout/style/crashtests/1413288.html
@@ -0,0 +1,17 @@
+<style>
+:root { column-width: 0px; }
+:not(article) { padding: 2 0px; }
+</style>
+<script>
+function jsfuzzer() {
+try { htmlvar00007.setAttribute("align", "LEFT"); } catch(e) { }
+try { svgvar00016.getStartPositionOfChar(0); } catch(e) { }
+try { htmlvar00016.style.setProperty("column-gap", "0"); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<iframe id="htmlvar00007"></iframe>
+<details>
+<summary id="htmlvar00016">)
+<svg>
+<tspan id="svgvar00016" />
diff --git a/layout/style/crashtests/1413361.html b/layout/style/crashtests/1413361.html
new file mode 100644
index 0000000000..c7bd075f23
--- /dev/null
+++ b/layout/style/crashtests/1413361.html
@@ -0,0 +1,8 @@
+<script>
+function go() {
+ window.getSelection().selectAllChildren(document.body.firstChild);
+ document.createElement("body").aLink = "-moz-mac-menuselect";
+ window.getSelection().getRangeAt(0).getClientRects();
+}
+</script>
+<body onload=go()>
diff --git a/layout/style/crashtests/1413670.html b/layout/style/crashtests/1413670.html
new file mode 100644
index 0000000000..958485c0ef
--- /dev/null
+++ b/layout/style/crashtests/1413670.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">
+<script>
+setTimeout(function(){
+ document.designMode='on';
+ window.frames[0].document.body.appendChild(document.getElementById('a'));
+ document.documentElement.className = "";
+}, 0);
+</script>
+<iframe></iframe>
+<base href=''/>
+<body id='a'>
+<kbd contenteditable='true'>
+<li contenteditable='false'>
+</li>
+<label>
+<a href=''>
+</html>
diff --git a/layout/style/crashtests/1415353.html b/layout/style/crashtests/1415353.html
new file mode 100644
index 0000000000..e44f72cde6
--- /dev/null
+++ b/layout/style/crashtests/1415353.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<textarea id="htmlvar00003" maxlength="0">
+ <!-- Text nodes around me are important -->
+</textarea>
+<script>
+document.body.offsetTop;
+document.querySelector('textarea').attachShadow({ mode: "open" });
+</script>
diff --git a/layout/style/crashtests/1418059.html b/layout/style/crashtests/1418059.html
new file mode 100644
index 0000000000..a799e162a9
--- /dev/null
+++ b/layout/style/crashtests/1418059.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<style>
+@keyframes anim {
+ to { transform: rotate(360deg); }
+}
+.document-ready .animation::after {
+ display: none;
+}
+.animation::after {
+ content: "";
+ animation: anim 1s infinite;
+}
+</style>
+<div class="animation"></div>
+<script>
+window.addEventListener('load', () => {
+ document.documentElement.classList.add('document-ready');
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove('reftest-wait');
+ });
+});
+</script>
+</html>
diff --git a/layout/style/crashtests/1418867.html b/layout/style/crashtests/1418867.html
new file mode 100644
index 0000000000..4317f812c2
--- /dev/null
+++ b/layout/style/crashtests/1418867.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<style>
+@keyframes anim {
+ to { transform: rotate(360deg); }
+}
+.document-ready div::after {
+ display: none;
+}
+div::after {
+ content: "";
+}
+.animation::after {
+ animation: anim 1s infinite;
+}
+</style>
+<div id="target"></div>
+<script>
+window.addEventListener('load', () => {
+ target.classList.add('animation');
+ const psuedo = document.getAnimations()[0].effect.target;
+ target.classList.remove('animation');
+
+ psuedo.animate([ { transform: 'rotate(360deg)' } ], 1000);
+ requestAnimationFrame(() => {
+ document.documentElement.classList.add('document-ready');
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove('reftest-wait');
+ });
+ });
+});
+</script>
+</html>
diff --git a/layout/style/crashtests/1419554.html b/layout/style/crashtests/1419554.html
new file mode 100644
index 0000000000..3a9b917c19
--- /dev/null
+++ b/layout/style/crashtests/1419554.html
@@ -0,0 +1,9 @@
+<script>
+function go() {
+ a.attachShadow({ mode: "open" });
+ a.appendChild(b);
+}
+</script>
+<body onload=go()>
+<div id="b"></div>
+<div id="a"></div>
diff --git a/layout/style/crashtests/1426312.html b/layout/style/crashtests/1426312.html
new file mode 100644
index 0000000000..4a99d1eec0
--- /dev/null
+++ b/layout/style/crashtests/1426312.html
@@ -0,0 +1,4 @@
+<style>
+ @keyframes a { }
+ * { animation: a -7s }
+</style>
diff --git a/layout/style/crashtests/1439793.html b/layout/style/crashtests/1439793.html
new file mode 100644
index 0000000000..1667978a63
--- /dev/null
+++ b/layout/style/crashtests/1439793.html
@@ -0,0 +1,10 @@
+<script>
+ o1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mfenced")
+ document.documentElement.appendChild(o1);
+ o3 = document.createElementNS("http://www.w3.org/1999/xhtml", "style");
+ o3.appendChild(document.createTextNode("[s=\"\"]{"));
+ (document.head || document.children[0]).appendChild(o3);
+ o4 = o3.sheet;
+ window.scrollBy(0.41979783887602806, 16);
+ o4.insertRule("::-moz-math-anonymous,g{scroll-behavior:smooth", 1);
+</script>
diff --git a/layout/style/crashtests/1445682.html b/layout/style/crashtests/1445682.html
new file mode 100644
index 0000000000..b79fd1d89b
--- /dev/null
+++ b/layout/style/crashtests/1445682.html
@@ -0,0 +1,16 @@
+<script>
+ function go() {
+ let host = document.createElement('div');
+ let style_1 = document.createElement('style');
+ let style_2 = document.createElement('style');
+ let otherElement = document.createElement('div');
+ let shadowRoot = host.attachShadow({mode: "open"});
+ style_1.title = 'x';
+ style_2.title = 'y';
+ document.head.appendChild(style_1);
+ shadowRoot.appendChild(style_2);
+ document.documentElement.appendChild(otherElement);
+ otherElement.before('', host, '');
+ }
+ window.addEventListener('load', go)
+</script>
diff --git a/layout/style/crashtests/1449243.html b/layout/style/crashtests/1449243.html
new file mode 100644
index 0000000000..ad07f572a8
--- /dev/null
+++ b/layout/style/crashtests/1449243.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<div id="host"></div>
+<script>
+ host.attachShadow({ mode: 'open' }).innerHTML = `
+ <style>
+ div { color: green }
+ </style>
+ <div></div>
+ `;
+ document.body.offsetTop;
+ host.shadowRoot.styleSheets[0].insertRule("[foo] { color: green }", 0);
+ host.shadowRoot.querySelector("div").setAttribute("foo", "bar");
+</script>
diff --git a/layout/style/crashtests/1450691.html b/layout/style/crashtests/1450691.html
new file mode 100644
index 0000000000..2c83abbdbb
--- /dev/null
+++ b/layout/style/crashtests/1450691.html
@@ -0,0 +1,12 @@
+<script>
+ o0 = document.createElement('iframe');
+ o1 = document.createElement('iframe');
+ document.documentElement.appendChild(o0);
+ o2 = o0.getSVGDocument();
+ o3 = o2.childNodes[0];
+ o3.appendChild(o1);
+ o3.scrollHeight;
+ o3.hidden = true;
+ o4 = window.getComputedStyle(o1.contentDocument.childNodes[0].childNodes[1], ":z");
+ o4.length;
+</script>
diff --git a/layout/style/crashtests/1453206.html b/layout/style/crashtests/1453206.html
new file mode 100644
index 0000000000..da63711bde
--- /dev/null
+++ b/layout/style/crashtests/1453206.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<div id="host"></div>
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ let shadow = host.attachShadow({ mode: 'open' });
+ shadow.appendChild(document.createElement('marquee'));
+});
+</script>
diff --git a/layout/style/crashtests/1454140.html b/layout/style/crashtests/1454140.html
new file mode 100644
index 0000000000..8e388d43f9
--- /dev/null
+++ b/layout/style/crashtests/1454140.html
@@ -0,0 +1,4 @@
+<!-- A -->
+<table background="
+#"><base href=Y:
+<!-- A -->
diff --git a/layout/style/crashtests/1455108.html b/layout/style/crashtests/1455108.html
new file mode 100644
index 0000000000..29b1467797
--- /dev/null
+++ b/layout/style/crashtests/1455108.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<style>
+ div::first-line {
+ color: yellow:
+ }
+ .foo span {
+ display: none;
+ }
+ .foo::first-line {
+ color: red;
+ }
+</style>
+<div><span>Yo, I'm a first-line<span> really</span></span>
+<script>
+ document.body.offsetTop;
+ document.querySelector('div').classList.add('foo');
+</script>
diff --git a/layout/style/crashtests/1457288.html b/layout/style/crashtests/1457288.html
new file mode 100644
index 0000000000..43ca9c365e
--- /dev/null
+++ b/layout/style/crashtests/1457288.html
@@ -0,0 +1,9 @@
+<script>
+function start() {
+ o234=document.createElement('style');
+ o235=document.createTextNode("*{ shape-outside: ellipse(1% 10%); shape-margin: 1048576rem; float: right}");
+ o234.appendChild(o235);
+ document.documentElement.appendChild(o234);
+}
+</script>
+<body onload="start()"></body>
diff --git a/layout/style/crashtests/1457985.html b/layout/style/crashtests/1457985.html
new file mode 100644
index 0000000000..9772908c50
--- /dev/null
+++ b/layout/style/crashtests/1457985.html
@@ -0,0 +1,2 @@
+<body style="content:attr(a) open-quote">
+<header style="all:inherit">
diff --git a/layout/style/crashtests/1468640.html b/layout/style/crashtests/1468640.html
new file mode 100644
index 0000000000..4e9f3bad1e
--- /dev/null
+++ b/layout/style/crashtests/1468640.html
@@ -0,0 +1,10 @@
+<script>
+function go() {
+ a.attachShadow({mode: "closed"}).innerHTML = b.outerHTML;
+ a.setAttribute("o", "");
+}
+</script>
+<body onload=go()>
+<meter id="b">
+<style></style>
+<div id="a">
diff --git a/layout/style/crashtests/1469076.html b/layout/style/crashtests/1469076.html
new file mode 100644
index 0000000000..0270316382
--- /dev/null
+++ b/layout/style/crashtests/1469076.html
@@ -0,0 +1,12 @@
+<style>
+* { display: contents }
+</style>
+<script>
+function go() {
+ a.parentElement.animate({"ping": [0, 1]}, 0.281207776578);
+}
+</script>
+<body onload=go()>
+></br>
+<shadow>
+<details id="a">
diff --git a/layout/style/crashtests/1475003.html b/layout/style/crashtests/1475003.html
new file mode 100644
index 0000000000..6cdfefbe62
--- /dev/null
+++ b/layout/style/crashtests/1475003.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<head>
+<script>
+function go() {
+ window.requestAnimationFrame(eh);
+}
+function eh() {
+ a.appendChild(b);
+ d.innerHTML = c.outerHTML;
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload=go()>
+<audio id="a" onerror="eh()" src="x"></audio>
+<data id="b">
+<li id="c">
+<svg>
+<set attributeName="overflow" to="hidden"/>
+<li id="d">
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/1479681.html b/layout/style/crashtests/1479681.html
new file mode 100644
index 0000000000..1d4c6c612d
--- /dev/null
+++ b/layout/style/crashtests/1479681.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<style>
+ div {
+ color: red;
+ transition: margin 1s ease, all 2s;
+ }
+</style>
+<div>Should turn Green</div>
+<script>
+onload = function() {
+ document.querySelector('div').style.color = "green";
+ document.body.offsetTop;
+}
+</script>
diff --git a/layout/style/crashtests/1488817.html b/layout/style/crashtests/1488817.html
new file mode 100644
index 0000000000..7613927185
--- /dev/null
+++ b/layout/style/crashtests/1488817.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+<style id='style'></style>
+<script>
+function start() {
+ o1 = document.createElement('figcaption')
+ o1.innerText = 'ᾂ﷞ 󠈂7阘𝅨ᷱ𝉗٩m󠙅,︥𖕬שּׁ︦ n-፝󠿸𫮨𯚪҅҈h"٪۰󠗽錐󠇪҈שּׁ𛜑𝔡L‭\r\n埤സ&\n<<=󠺁킻&︠‬e󠔥\b\r>𯉸󠕯'
+ document.documentElement.appendChild(o1)
+ document.getElementById('style').textContent = `
+ FIGCAPTION::before, FIGCAPTION {
+ content: counters(\\46, \'.\')
+ }
+ HTML {
+ -webkit-transition-delay: 1s, 250ms
+ }
+ FIGCAPTION::first-letter, * {
+ fill: currentColor
+ }
+ FIGCAPTION::first-line {}`
+ document.body.offsetTop;
+ document.replaceChild(document.documentElement, document.documentElement)
+}
+
+document.addEventListener('DOMContentLoaded', start)
+</script>
+<body></body>
+</html>
diff --git a/layout/style/crashtests/1490012.html b/layout/style/crashtests/1490012.html
new file mode 100644
index 0000000000..fcd951ccc4
--- /dev/null
+++ b/layout/style/crashtests/1490012.html
@@ -0,0 +1,11 @@
+<script>
+function go() {
+ b.appendChild(document.createElement("iframe"));
+ a.scroll({left: -1, top: 1});
+ window[0].matchMedia("").addListener(function(){});
+ b.open = false;
+}
+</script>
+<body onload=go()>
+<image id="a"></image>
+<details id="b" open="true">
diff --git a/layout/style/crashtests/1502893.html b/layout/style/crashtests/1502893.html
new file mode 100644
index 0000000000..eed2d75f5e
--- /dev/null
+++ b/layout/style/crashtests/1502893.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<style>
+ #animate_0[onrepeat=''] {}
+</style>
+<animate id="animate_0" />
+<animate id="animate_1" />
+<script>
+ window.CustomElement0 = class extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({
+ mode: 'open'
+ })
+ }
+ connectedCallback() {
+ this.shadowRoot.prepend(o2)
+ this.setAttribute('contenteditable', 'true')
+ }
+ }
+
+ customElements.define('custom-element-0', CustomElement0)
+ o1 = document.createElement('custom-element-0')
+ o2 = document.getElementById('animate_0')
+ o3 = document.getElementById('animate_1')
+ document.documentElement.appendChild(o1)
+ document.replaceChild(document.documentElement, document.documentElement)
+ o1.shadowRoot.prepend(o3)
+ o3.offsetTop;
+</script>
diff --git a/layout/style/crashtests/1507674.html b/layout/style/crashtests/1507674.html
new file mode 100644
index 0000000000..be247b781c
--- /dev/null
+++ b/layout/style/crashtests/1507674.html
@@ -0,0 +1,16 @@
+<style>
+menuitem { -webkit-filter: url(#a) }
+</style>
+<menuitem>A
+</menu>
+<svg id="b" style="-webkit-text-stroke-width: 1px;">
+<text id="a">
+<menu style="background-image: url();">
+<menuitem id="c">
+<script>
+c.offsetTop;
+a.setAttribute("exponent", "1");
+c.offsetTop;
+b.setAttribute("text-decoration", "line-through");
+c.offsetTop;
+</script>
diff --git a/layout/style/crashtests/1509989.html b/layout/style/crashtests/1509989.html
new file mode 100644
index 0000000000..1eacaddd96
--- /dev/null
+++ b/layout/style/crashtests/1509989.html
@@ -0,0 +1,11 @@
+<script>
+function go() {
+ window.getSelection().getRangeAt(0).insertNode(a);
+}
+</script>
+<body onload=go()>
+<dl>
+<dd id="a">
+<video>
+</dd>
+<input type="number" autofocus="">
diff --git a/layout/style/crashtests/1514086.html b/layout/style/crashtests/1514086.html
new file mode 100644
index 0000000000..dc3f81e2ac
--- /dev/null
+++ b/layout/style/crashtests/1514086.html
@@ -0,0 +1,13 @@
+<script>
+let o = [];
+window.onload = function(){
+ o[0] = document.createElement("font");
+ document.body.appendChild(o[0]);
+ o[1] = document.createElement("embed");
+ o[0].appendChild(o[1]);
+ o[2] = document.createElement("dialog");
+ o[1].appendChild(o[2]);
+ o[2].appendChild(document.createElement("s"));
+ o[2].animate([{"all": "unset"}, {}], 3);
+}
+</script>
diff --git a/layout/style/crashtests/1533783.html b/layout/style/crashtests/1533783.html
new file mode 100644
index 0000000000..1b793c6f4e
--- /dev/null
+++ b/layout/style/crashtests/1533783.html
@@ -0,0 +1,10 @@
+<script>
+ function start() {
+ const style = document.createElement('style')
+ document.head.appendChild(style)
+ const sheet_1 = style.sheet
+ document.head.appendChild(style)
+ sheet_1.insertRule('@import url()', 0)
+ }
+ document.addEventListener('DOMContentLoaded', start)
+</script>
diff --git a/layout/style/crashtests/1533891.html b/layout/style/crashtests/1533891.html
new file mode 100644
index 0000000000..da135c66eb
--- /dev/null
+++ b/layout/style/crashtests/1533891.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+ <style>
+ * {
+ border-image-source: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7);
+ }
+ pre::first-line { }
+ </style>
+ <head>
+<body>
+<pre><data>]j% BAVxE</command>
+</body>
+</html>
diff --git a/layout/style/crashtests/1533968.html b/layout/style/crashtests/1533968.html
new file mode 100644
index 0000000000..1297e3b485
--- /dev/null
+++ b/layout/style/crashtests/1533968.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+ <style>
+ #th {
+ width: 100px;
+ height: 100px;
+ transition-duration: 1s;
+ }
+ </style>
+ <script>
+ function start () {
+ var div = document.getElementById('th');
+ div.style.backgroundColor = 'green';
+ }
+ </script>
+</head>
+<body onload="start()">
+<table>
+ <tr style="background-color: blue;"><th id="th"></th></tr>
+</table>
+</body>
+</html>
diff --git a/layout/style/crashtests/1545177.html b/layout/style/crashtests/1545177.html
new file mode 100644
index 0000000000..476bb4c77e
--- /dev/null
+++ b/layout/style/crashtests/1545177.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <script>
+ function start () {
+ const font_1 = new FontFace('m', 'url()', {})
+ const font_2 = new FontFace('', '')
+ document.fonts.add(font_1)
+ font_1.family = 'f'
+ document.fonts.add(font_2)
+ }
+
+ window.addEventListener('load', start)
+ </script>
+</head>
+</html>
diff --git a/layout/style/crashtests/1546255.html b/layout/style/crashtests/1546255.html
new file mode 100644
index 0000000000..ca5c1933c6
--- /dev/null
+++ b/layout/style/crashtests/1546255.html
@@ -0,0 +1,29 @@
+<style id="id_0">
+ @import url('data:;base64,QGN7IGJ9CkRBTVAsIEhUTUwgey');
+</style>
+<script>
+ function start () {
+ style = document.createElement('style')
+ document.head.appendChild(style)
+
+ const o0 = document.getElementById('id_0')
+ const o1 = document.getElementById('id_1')
+
+ const range = new Range()
+ range.selectNode(o1)
+
+ style.sheet.insertRule('@namespace\'', 0)
+ for (let i = 0; i < 7; i++) {
+ range.insertNode(o0)
+ }
+
+ const xhr = new XMLHttpRequest()
+ xhr.open('G', '', false)
+ xhr.send()
+
+ SpecialPowers.InspectorUtils.getCSSStyleRules(document.documentElement)
+ }
+
+ window.addEventListener('load', start)
+</script>
+<li id="id_1">
diff --git a/layout/style/crashtests/1552911.html b/layout/style/crashtests/1552911.html
new file mode 100644
index 0000000000..0e8fcb8db7
--- /dev/null
+++ b/layout/style/crashtests/1552911.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <script>
+ function start() {
+ const o1 =
+ document.createElementNS('http://www.w3.org/1999/xhtml', 'slot');
+ const observer = new ResizeObserverEntry(o1);
+ typeof observer.borderBoxSize;
+ typeof observer.contentBoxSize;
+ }
+
+ window.addEventListener('load', start);
+ </script>
+</head>
+</html>
diff --git a/layout/style/crashtests/1562361.html b/layout/style/crashtests/1562361.html
new file mode 100644
index 0000000000..6f79560341
--- /dev/null
+++ b/layout/style/crashtests/1562361.html
@@ -0,0 +1,15 @@
+<script>
+window.onload = function() {
+ a.setAttribute("width", "0")
+ b.select()
+ document.documentElement.style.display="none"
+ document.documentElement.getBoundingClientRect()
+ document.documentElement.style.display=""
+
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ document.body.offsetTop;
+}
+</script>
+<input id="b" type="number">
+<iframe id="a" height="0">
diff --git a/layout/style/crashtests/1566684.html b/layout/style/crashtests/1566684.html
new file mode 100644
index 0000000000..328ec46a6b
--- /dev/null
+++ b/layout/style/crashtests/1566684.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<style>
+ x { }
+</style>
+<script>
+ var sheet = document.styleSheets[0];
+ var rule = sheet.cssRules[0];
+ var decl = rule.style;
+ rule.expando = 5;
+ decl.expando = 6;
+ sheet.deleteRule(0);
+ rule = decl = null;
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ SpecialPowers.forceGC();
+</script>
diff --git a/layout/style/crashtests/1579788.html b/layout/style/crashtests/1579788.html
new file mode 100644
index 0000000000..1379062d06
--- /dev/null
+++ b/layout/style/crashtests/1579788.html
@@ -0,0 +1,15 @@
+<script>
+function start() {
+ document.addEventListener('DOMAttrModified', function() {
+ b.scroll()
+ b.setAttribute('dir', 'ltr')
+ b.dir = ''
+ })
+ a.setAttribute('d', '')
+}
+document.addEventListener('DOMContentLoaded', start)
+</script>
+<form dir="rtl">
+<object id="a">
+<input value="X" multiple pattern="[]">
+<p id="b">
diff --git a/layout/style/crashtests/1580307.html b/layout/style/crashtests/1580307.html
new file mode 100644
index 0000000000..0d2c56d0ff
--- /dev/null
+++ b/layout/style/crashtests/1580307.html
@@ -0,0 +1 @@
+<legend style="justify-items: left legacy; overflow-y: hidden">
diff --git a/layout/style/crashtests/1581579.html b/layout/style/crashtests/1581579.html
new file mode 100644
index 0000000000..96e94a0771
--- /dev/null
+++ b/layout/style/crashtests/1581579.html
@@ -0,0 +1,3 @@
+<div dir="RTL">
+<textarea style="resize: vertical" dir="auto"></textarea>
+<textarea></textarea>
diff --git a/layout/style/crashtests/1586444.html b/layout/style/crashtests/1586444.html
new file mode 100644
index 0000000000..9ae041c7a1
--- /dev/null
+++ b/layout/style/crashtests/1586444.html
@@ -0,0 +1,15 @@
+<style>
+#a {
+ overflow: auto auto;
+ display: -webkit-box;
+}
+</style>
+<script>
+window.onload = () => {
+ a.align = "ABSMIDDLE"
+ document.documentElement.style.display="none"
+ document.documentElement.getBoundingClientRect()
+ document.documentElement.style.display=""
+}
+</script>
+<object id="a">
diff --git a/layout/style/crashtests/1593766.html b/layout/style/crashtests/1593766.html
new file mode 100644
index 0000000000..5fe3dd0be2
--- /dev/null
+++ b/layout/style/crashtests/1593766.html
@@ -0,0 +1,3 @@
+<script>
+ typeof (window.paintWorklet || CSS.paintWorklet);
+</script>
diff --git a/layout/style/crashtests/1594949.html b/layout/style/crashtests/1594949.html
new file mode 100644
index 0000000000..df332a05f0
--- /dev/null
+++ b/layout/style/crashtests/1594949.html
@@ -0,0 +1,13 @@
+<script>
+window.onload = () => {
+ a.style.setProperty("scale", "3")
+ a.clientTop
+ a.style.setProperty("offset", "path('M1 6')0deg")
+
+ b.style.setProperty("offset", "path('M100 6')0deg")
+}
+</script>
+<svg>
+ <text id="a"></text>
+ <foreignObject id="b"></foreignObject>
+</svg>
diff --git a/layout/style/crashtests/1594960.html b/layout/style/crashtests/1594960.html
new file mode 100644
index 0000000000..48cf0e4e41
--- /dev/null
+++ b/layout/style/crashtests/1594960.html
@@ -0,0 +1,15 @@
+<style>
+.x {
+ transform: scale(1, 1) rotate(50deg);
+ opacity: 0.2;
+ transition: transform 0.96s, opacity 1s;
+ border: solid medium;
+ offset-path: path('M0 1 1 0');
+}
+</style>
+<script>
+window.onload = () => {
+ a.setAttribute("class", "x")
+}
+</script>
+<ol id="a">
diff --git a/layout/style/crashtests/1599286.html b/layout/style/crashtests/1599286.html
new file mode 100644
index 0000000000..9e08d6f204
--- /dev/null
+++ b/layout/style/crashtests/1599286.html
@@ -0,0 +1,2 @@
+<rt>
+<ul id="a" style="overflow: auto" noresize>
diff --git a/layout/style/crashtests/1609786.html b/layout/style/crashtests/1609786.html
new file mode 100644
index 0000000000..1545085b76
--- /dev/null
+++ b/layout/style/crashtests/1609786.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <style>
+ * {
+ offset-path: path(' ') ! important;
+ }
+ </style>
+</head>
+</html>
diff --git a/layout/style/crashtests/1616407.html b/layout/style/crashtests/1616407.html
new file mode 100644
index 0000000000..a4fc4fa033
--- /dev/null
+++ b/layout/style/crashtests/1616407.html
@@ -0,0 +1,7 @@
+<script>
+ async function start() {
+ var a = new CSSStyleSheet({ })
+ document.adoptedStyleSheets = [new CSSStyleSheet({ }), a, a]
+ }
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
diff --git a/layout/style/crashtests/1616433.html b/layout/style/crashtests/1616433.html
new file mode 100644
index 0000000000..7d7049bec0
--- /dev/null
+++ b/layout/style/crashtests/1616433.html
@@ -0,0 +1,9 @@
+<script>
+ window.addEventListener('load', () => {
+ var a = document.createElementNS('http://www.w3.org/1999/xhtml', 'div')
+ var b = new CSSStyleSheet({'disabled': true})
+ var c = a.attachShadow({'mode': 'closed'})
+ c.adoptedStyleSheets = [new CSSStyleSheet(), b, b]
+ b.disabled = false
+ })
+</script> \ No newline at end of file
diff --git a/layout/style/crashtests/1639533.html b/layout/style/crashtests/1639533.html
new file mode 100644
index 0000000000..5bf2118ffe
--- /dev/null
+++ b/layout/style/crashtests/1639533.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<style>
+.tweak ::-moz-range-thumb:is(:hover) {
+ background: red;
+}
+</style>
+<div>
+ <input type=range>
+</div>
+<script>
+ onload = function() {
+ document.querySelector("div").getBoundingClientRect();
+ document.querySelector("div").classList.add("tweak");
+ }
+</script>
diff --git a/layout/style/crashtests/1640040.html b/layout/style/crashtests/1640040.html
new file mode 100644
index 0000000000..a1bbb4e2af
--- /dev/null
+++ b/layout/style/crashtests/1640040.html
@@ -0,0 +1,11 @@
+<script>
+window.onload = () => {
+ b.type = ""
+ document.getSelection().selectAllChildren(a)
+ b.placeholder = ""
+ b.setRangeText(String.fromCodePoint(814688))
+}
+</script>
+<menu spellcheck="true">
+<input id="b" autofocus="" type="button">
+<menu id="a">
diff --git a/layout/style/crashtests/1806189-1.html b/layout/style/crashtests/1806189-1.html
new file mode 100644
index 0000000000..9a2d5f718a
--- /dev/null
+++ b/layout/style/crashtests/1806189-1.html
@@ -0,0 +1,15 @@
+<ins id="a">
+<li contenteditable="true">
+<shadow id="b">
+<script>
+window.onload = function() {
+ var c = window.getSelection();
+ a.addEventListener("DOMNodeRemoved", function() {
+ document.execCommand("insertHorizontalRule", false);
+ document.execCommand("justifyCenter", false);
+ });
+ c.setPosition(b);
+ document.execCommand("insertText", false, "1");
+}
+</script>
+<option>
diff --git a/layout/style/crashtests/1821416.html b/layout/style/crashtests/1821416.html
new file mode 100644
index 0000000000..127a97a65e
--- /dev/null
+++ b/layout/style/crashtests/1821416.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<style>
+*, *::after {
+ scroll-timeline: tl;
+}
+</style>
+<script>
+window.addEventListener("load", () => {
+ let a = document.createElement("ul")
+ let b = document.createElement("li")
+ let c = document.createElement("div")
+ let d = document.createElement("del")
+ d.appendChild(document.createElement("q"))
+ c.appendChild(d)
+ b.appendChild(c)
+ a.appendChild(b)
+ document.documentElement.appendChild(a);
+ a.scrollBy(32767, 256);
+ a.type = "square";
+})
+</script>
diff --git a/layout/style/crashtests/1872309.html b/layout/style/crashtests/1872309.html
new file mode 100644
index 0000000000..d39afd0672
--- /dev/null
+++ b/layout/style/crashtests/1872309.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+document.addEventListener("DOMContentLoaded", async () => {
+ let a = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas")
+ document.documentElement.appendChild(a)
+ for (let i = 0; i < 10; i++) {
+ a.animate(
+ {"rotate": ["-45155266.58grad", "1739581432.45grad 41467 1000 -25020", "none"]},
+ {"composite": "add", "delay": 10, "iterationStart": 0.25}
+ )
+ }
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove('reftest-wait');
+ });
+})
+</script>
+</html>
diff --git a/layout/style/crashtests/187671-1.html b/layout/style/crashtests/187671-1.html
new file mode 100644
index 0000000000..7395c23b42
--- /dev/null
+++ b/layout/style/crashtests/187671-1.html
@@ -0,0 +1,12 @@
+<a href="http://mozillazine.org/">Link text</a>
+
+<SPAN><p>Paragraph text</p></span>
+
+
+
+<!-- The styles have to be after the content. (In the original, the styles were loaded using javascript, with the style sheet loaded depending on the browser.) -->
+
+<style>
+a {font-size: 13px }
+span {position: relative;}
+</style>
diff --git a/layout/style/crashtests/192408-1.html b/layout/style/crashtests/192408-1.html
new file mode 100644
index 0000000000..bb75e44012
--- /dev/null
+++ b/layout/style/crashtests/192408-1.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <title>bug 192408</title>
+ </head>
+ <body>
+ <span>
+ <embed src="foo.mid"></embed>
+ <script>
+ document;
+ </script>
+ <center></center>
+ <script src="bar.js"></script>
+ </span>
+ </body>
+</html>
diff --git a/layout/style/crashtests/285727-1.html b/layout/style/crashtests/285727-1.html
new file mode 100644
index 0000000000..db1d28d8d4
--- /dev/null
+++ b/layout/style/crashtests/285727-1.html
@@ -0,0 +1,13 @@
+<BODY STYLE="display:table-row;">
+<SPAN>
+1
+</SPAN>
+<FORM>
+<DIV STYLE="float:left;">
+1
+<FONT>
+1
+</FONT>
+</DIV>
+</FORM>
+</BODY>
diff --git a/layout/style/crashtests/286707-1.html b/layout/style/crashtests/286707-1.html
new file mode 100644
index 0000000000..7485a9644f
--- /dev/null
+++ b/layout/style/crashtests/286707-1.html
@@ -0,0 +1,2 @@
+<html><body><p style="font-size:1px">hi<sup>1
+
diff --git a/layout/style/crashtests/317561-1.html b/layout/style/crashtests/317561-1.html
new file mode 100644
index 0000000000..01335a17ca
--- /dev/null
+++ b/layout/style/crashtests/317561-1.html
@@ -0,0 +1,104 @@
+<!doctype html>
+<html>
+<head>
+<style type="text/css">
+div:not([id^='img']) {
+ height: 20px; width: 20px; border: 1px solid #000;
+}
+#img1 {position:absolute; left:180px; top:17px;}
+#img2 {position:absolute; left:-72px; top:58px;}
+#img3 {position:absolute; left:2px; top:0px;}
+#img4 {position:absolute; left:25px; top:-3px;}
+#img5 {position:absolute; left:1px; top:-3px;}
+#img6 {position:absolute; left:27px; top:-1px;}
+#img7 {position:absolute; left:41px; top:14px;}
+#img8 {position:absolute; left:17px; top:3px;}
+#img9 {position:absolute; left:50px; top:42px;}
+#img11 {position:absolute; left:-265px; top:113px;}
+#img12 {position:absolute; left:25px; top:-57px;}
+#img13 {position:absolute; left:17px; top:-4px;}
+#img14 {position:absolute; left:8px; top:-13px;}
+#img15 {position:absolute; left:38px; top:0px;}
+#img16 {position:absolute; left:37px; top:8px;}
+#img17 {position:absolute; left:11px; top:12px;}
+#img18 {position:absolute; left:4px; top:12px;}
+#img19 {position:absolute; left:-7px; top:13px;}
+#img20 {position:absolute; left:17px; top:13px;}
+#img21 {position:absolute; left:7px; top:12px;}
+#img22 {position:absolute; left:32px; top:6px;}
+#img23 {position:absolute; left:20px; top:10px;}
+#img24 {position:absolute; left:22px; top:10px;}
+#img25 {position:absolute; left:81px; top:-27px;}
+#img26 {position:absolute; left:9px; top:-19px;}
+#img27 {position:absolute; left:0px; top:-15px;}
+#img28 {position:absolute; left:19px; top:63px;}
+#img29 {position:absolute; left:-68px; top:0px;}
+#img32 {position:absolute; left:42px; top:121px;}
+#img33 {position:absolute; left:21px; top:1px;}
+#img34 {position:absolute; left:29px; top:2px;}
+#img35 {position:absolute; left:25px; top:1px;}
+#img36 {position:absolute; left:-115px; top:16px;}
+#img37 {position:absolute; left:11px; top:-8px;}
+#img38 {position:absolute; left:-16px; top:-15px;}
+#img39 {position:absolute; left:-18px; top:-1px;}
+#img40 {position:absolute; left:-28px; top:-8px;}
+#img41 {position:absolute; left:-6px; top:-6px;}
+#img42 {position:absolute; left:-19px; top:-12px;}
+#img43 {position:absolute; left:-26px; top:-9px;}
+#img44 {position:absolute; left:1px; top:-23px;}
+#img45 {position:absolute; left:-10px; top:-22px;}
+#img46 {position:absolute; left:-59px; top:-39px;}
+#img47 {position:absolute; left:-28px; top:0px;}
+#img48 {position:absolute; left:-13px; top:4px;}
+#img49 {position:absolute; left:-21px; top:4px;}
+</style>
+</head>
+<body>
+<div id="img1"><div></div>
+<div id="img2"><div></div>
+<div id="img3"><div></div>
+<div id="img4"><div></div>
+<div id="img5"><div></div>
+<div id="img6"><div></div>
+<div id="img7"><div></div>
+<div id="img8"><div></div>
+<div id="img9"><div></div>
+<div id="img11"><div></div>
+<div id="img12"><div></div>
+<div id="img13"><div></div>
+<div id="img14"><div></div>
+<div id="img15"><div></div>
+<div id="img16"><div></div>
+<div id="img17"><div></div>
+<div id="img18"><div></div>
+<div id="img19"><div></div>
+<div id="img20"><div></div>
+<div id="img21"><div></div>
+<div id="img22"><div></div>
+<div id="img23"><div></div>
+<div id="img24"><div></div>
+<div id="img25"><div></div>
+<div id="img26"><div></div>
+<div id="img27"><div></div>
+<div id="img28"><div></div>
+<div id="img29"><div></div>
+<div id="img32"><div></div>
+<div id="img33"><div></div>
+<div id="img34"><div></div>
+<div id="img35"><div></div>
+<div id="img36"><div></div>
+<div id="img37"><div></div>
+<div id="img38"><div></div>
+<div id="img39"><div></div>
+<div id="img40"><div></div>
+<div id="img41"><div></div>
+<div id="img42"><div></div>
+<div id="img43"><div></div>
+<div id="img44"><div></div>
+<div id="img45"><div></div>
+<div id="img46"><div></div>
+<div id="img47"><div></div>
+<div id="img48"><div></div>
+<div id="img49"><div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
+</body>
+</html>
diff --git a/layout/style/crashtests/330998-1.html b/layout/style/crashtests/330998-1.html
new file mode 100644
index 0000000000..9696bc4202
--- /dev/null
+++ b/layout/style/crashtests/330998-1.html
@@ -0,0 +1,30 @@
+<html>
+
+<head>
+
+<script>
+
+function init()
+{
+ var form1 = document.getElementById("form1");
+ var tr1 = document.getElementById("tr1");
+
+ tr1.appendChild(form1);
+ tr1.removeChild(form1);
+}
+
+window.addEventListener("load", init);
+
+</script>
+
+</head>
+
+<body>
+
+<table><tr id="tr1"><td></td></tr></table>
+
+<form id="form1"><span style="float:left"></span></form>
+
+</body>
+
+</html>
diff --git a/layout/style/crashtests/363950.html b/layout/style/crashtests/363950.html
new file mode 100644
index 0000000000..a0d04810c6
--- /dev/null
+++ b/layout/style/crashtests/363950.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<title>Testcase bug 363950 - crash [@ nsComputedDOMStyle::GetMarginWidthCoordFor]</title>
+</head>
+<body>
+This page should not crash Mozilla
+<script>
+var properties = ['margin-left','margin-right','margin-top','padding-bottom','padding-left','padding-right','padding-top'];
+
+function removestyles(i, j){
+if (j>=properties.length)
+ j = 0;
+document.defaultView.getComputedStyle(document.getElementsByTagName('head')[0]).getPropertyValue(properties[j]);
+j++;
+setTimeout(removestyles,50,j);
+}
+setTimeout(removestyles,500,0,0);
+</script>
+</body>
+</html>
diff --git a/layout/style/crashtests/368175-1.html b/layout/style/crashtests/368175-1.html
new file mode 100644
index 0000000000..5352ae4d46
--- /dev/null
+++ b/layout/style/crashtests/368175-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+<body>
+<div style="width: 300px; height: 200px; overflow: auto">
+ <div style='direction: rtl; column-width:1em; column-gap:1em; width: 500px;'>
+ <p>滾滾長江東逝水,浪花淘盡英雄。是非成敗轉頭空:青山依舊在,幾度夕陽紅。</p>
+ <p style='direction: rtl; column-width:1em; column-gap:1em;'>
+ 滾滾長江東逝水,浪花淘盡英雄。是非成敗轉頭空:青山依舊在,幾度夕陽紅。</p>
+ </div>
+</div>
+</body>
+</html>
diff --git a/layout/style/crashtests/368740.html b/layout/style/crashtests/368740.html
new file mode 100644
index 0000000000..b7a0d7735d
--- /dev/null
+++ b/layout/style/crashtests/368740.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+<title>Testcase bug - Crash [@ nsIFrame::IsThemed] when trying to get the computed style of a button in iframe</title>
+
+<style>
+iframe {
+width: 800px;
+height: 400px;
+}
+</style>
+</head>
+<body>
+This page should not crash Mozilla<br>
+<iframe id="content" src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%3E%0A%3Cbutton%3E%3C/button%3E%0A%3C/body%3E%3C/html%3E"></iframe>
+
+<script>
+function getComputedStyles(){
+var x=document.getElementById('content').contentDocument.getElementsByTagName('button')[0];
+var style = document.defaultView.getComputedStyle(x).getPropertyValue('border-left-width');
+}
+setTimeout(getComputedStyles,300);
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/379788-1.html b/layout/style/crashtests/379788-1.html
new file mode 100644
index 0000000000..40e38a7ccd
--- /dev/null
+++ b/layout/style/crashtests/379788-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('p2').style.position = 'fixed';">
+
+<p style="right: 1em; bottom: 1em; position: fixed;">Foo</p>
+<p id="p2">Bar</p>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/383979-1.xhtml b/layout/style/crashtests/383979-1.xhtml
new file mode 100644
index 0000000000..9a3f8e6735
--- /dev/null
+++ b/layout/style/crashtests/383979-1.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("div1").appendChild(document.getElementById("div2"));
+}
+
+</script>
+
+<style type="text/css">
+
+#s1, #s2 {
+ font: 8pt arial;
+}
+
+#div1 #s1, #div1 #s2 {
+ font-size: 20pt;
+}
+
+</style>
+</head>
+
+<body onload="boom();">
+
+<div id="div1"></div>
+<div id="div2"><span id="s1"><span id="s2">foo</span></span></div>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/383979-2.html b/layout/style/crashtests/383979-2.html
new file mode 100644
index 0000000000..a2cc055a6d
--- /dev/null
+++ b/layout/style/crashtests/383979-2.html
@@ -0,0 +1,35 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function run()
+{
+ var a = getComputedStyle(document.getElementById("s1"), "").listStyleType;
+ var b = getComputedStyle(document.getElementById("s3"), "").listStyleType;
+}
+
+</script>
+
+<style type="text/css">
+
+body { display: none } /* so we control the order of the ComputeListData calls */
+
+#s1, #s2, #s3 {
+ list-style-image: none;
+ list-style-position: outside;
+ list-style-type: disc;
+}
+
+#s2, #s3 {
+ list-style-type: disc;
+}
+
+</style>
+</head>
+
+<body onload="run();">
+
+<div id="s1"><div id="s2"><div id="s3"></div></div></div>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/386939-1.html b/layout/style/crashtests/386939-1.html
new file mode 100644
index 0000000000..345581d100
--- /dev/null
+++ b/layout/style/crashtests/386939-1.html
@@ -0,0 +1,24 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function rM(n)
+{
+ n.remove();
+}
+
+function boom()
+{
+ rM(document.getElementById("f1"));
+ rM(document.getElementById("f2"));
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<frameset rows="170,*" onload="setTimeout(boom, 30);">
+<frame src="data:text/html,frame1" id="f1">
+<frame src="data:text/html,frame2" id="f2">
+</frameset>
+
+</html>
diff --git a/layout/style/crashtests/391034-1.xhtml b/layout/style/crashtests/391034-1.xhtml
new file mode 100644
index 0000000000..315796d285
--- /dev/null
+++ b/layout/style/crashtests/391034-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ var p = document.getElementById("p");
+ window.getComputedStyle(p, null).getPropertyValue("right");
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<p id="p" style="position: relative; left: 3ch;">foo</p>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/397022-1.html b/layout/style/crashtests/397022-1.html
new file mode 100644
index 0000000000..ececc3fa5f
--- /dev/null
+++ b/layout/style/crashtests/397022-1.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<style>
+
+div:before { content: counter(c); }
+
+.b:before { content: "x"; }
+
+</style>
+</head>
+
+<body onload="document.getElementById('v').setAttribute('class', 'b');">
+
+<div id="v"></div>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/399289-1.svg b/layout/style/crashtests/399289-1.svg
new file mode 100644
index 0000000000..583de2c241
--- /dev/null
+++ b/layout/style/crashtests/399289-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="url(#x)">
+ <rect fill="url(#y)" />
+</svg>
diff --git a/layout/style/crashtests/404470-1.html b/layout/style/crashtests/404470-1.html
new file mode 100644
index 0000000000..03cac228f4
--- /dev/null
+++ b/layout/style/crashtests/404470-1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ iframe.setAttribute("src", 'data:application/xhtml+xml,<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><script>window.parent.document.body.style.display="inline"; window.parent.document.body.offsetWidth;</' + 'script></window>');
+}
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/411603-1.html b/layout/style/crashtests/411603-1.html
new file mode 100644
index 0000000000..596565fbc3
--- /dev/null
+++ b/layout/style/crashtests/411603-1.html
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+</head>
+<body onload="document.getElementById('ms').setAttribute('scriptminsize', '9em');">
+<math:mstyle id="ms" /><span />
+</body>
+</html>
diff --git a/layout/style/crashtests/412588-1.html b/layout/style/crashtests/412588-1.html
new file mode 100644
index 0000000000..561aed564d
--- /dev/null
+++ b/layout/style/crashtests/412588-1.html
@@ -0,0 +1,5 @@
+<html><head>
+<link rel="stylesheet" href="data:text/css;charset=utf-8,@media%20print%20%7B%7B%7D%0Aa%20%7B%20%7D%0Aa%7Bfont-size%3A%20200%25%3B%7D" type="text/css">
+<style>
+</style>
+</head><body></body></html> \ No newline at end of file
diff --git a/layout/style/crashtests/413274-1.xhtml b/layout/style/crashtests/413274-1.xhtml
new file mode 100644
index 0000000000..19d8fab0fd
--- /dev/null
+++ b/layout/style/crashtests/413274-1.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mstyle scriptsizemultiplier="8205" scriptlevel="15">
+ <mroot>
+ <mrow/>
+ <mrow>
+ <span xmlns="http://www.w3.org/1999/xhtml"/>
+ </mrow>
+ </mroot>
+ </mstyle>
+</math>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/416461-1.xhtml b/layout/style/crashtests/416461-1.xhtml
new file mode 100644
index 0000000000..1624dbc29c
--- /dev/null
+++ b/layout/style/crashtests/416461-1.xhtml
@@ -0,0 +1,6 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <wizard>
+ <msqrt xmlns="http://www.w3.org/1998/Math/MathML"/>
+ </wizard>
+ <menupopup style="display: inline; order: 2147483646;"/>
+</window>
diff --git a/layout/style/crashtests/418007-1.xhtml b/layout/style/crashtests/418007-1.xhtml
new file mode 100644
index 0000000000..f07a693444
--- /dev/null
+++ b/layout/style/crashtests/418007-1.xhtml
@@ -0,0 +1,24 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body>
+
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <ms fontsize="8179em">
+ <span xmlns="http://www.w3.org/1999/xhtml">
+ <span>
+ <td>
+ <mfrac xmlns="http://www.w3.org/1998/Math/MathML">
+ <mfrac>
+ <mrow/>
+ <mrow/>
+ </mfrac>
+ <mrow/>
+ </mfrac>
+ </td>
+ </span>
+ </span>
+ </ms>
+</math>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/431705-1.xhtml b/layout/style/crashtests/431705-1.xhtml
new file mode 100644
index 0000000000..8b64d4b3b2
--- /dev/null
+++ b/layout/style/crashtests/431705-1.xhtml
@@ -0,0 +1,6 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <span xmlns="http://www.w3.org/1999/xhtml" style="-moz-box-ordinal-group: 2;">
+ <mtext xmlns="http://www.w3.org/1998/Math/MathML" style="display: block;"/>
+ </span>
+ <div xmlns="http://www.w3.org/1999/xhtml" style="overflow: auto;"/>
+</window> \ No newline at end of file
diff --git a/layout/style/crashtests/432561-1.html b/layout/style/crashtests/432561-1.html
new file mode 100644
index 0000000000..81bb082e4f
--- /dev/null
+++ b/layout/style/crashtests/432561-1.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<title>Testcase, Bug 432561</title>
+</head>
+<body>
+<script>
+var str = '{';
+for (var i=0;i<22;i++)
+ str+=str;
+document.write('<style type="text/css">div '+str+'</style>');
+str = '{{[('
+for (var i=0;i<20;i++)
+ str+=str;
+document.write('<style type="text/css">div '+str+'</style>');
+</script>
+</body>
+</html>
diff --git a/layout/style/crashtests/437170-1.html b/layout/style/crashtests/437170-1.html
new file mode 100644
index 0000000000..af6fa1d662
--- /dev/null
+++ b/layout/style/crashtests/437170-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+html img { color: blue; }
+</style>
+<script>
+
+function boom()
+{
+ var r = document.createRange();
+ r.selectNodeContents(document.documentElement);
+ r.cloneContents();
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<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" onload="window.getComputedStyle(this, null).getPropertyValue('color');">
+
+</body>
+</html>
diff --git a/layout/style/crashtests/437532-1.html b/layout/style/crashtests/437532-1.html
new file mode 100644
index 0000000000..52eefa530f
--- /dev/null
+++ b/layout/style/crashtests/437532-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/csS">
+
+a\[href$=".pdf"\] { }
+
+</style>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/style/crashtests/439184-1.html b/layout/style/crashtests/439184-1.html
new file mode 100644
index 0000000000..f22660726c
--- /dev/null
+++ b/layout/style/crashtests/439184-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US">
+<head>
+ <title>Testcase, bug 439184</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" id="style">
+
+ </style>
+ <script type="text/javascript">
+
+ var styleText = "p { color: green; }";
+
+ // We want to end up with a million rules or so, so double this text
+ // 20 times to make it 2^20 rules:
+ for (var i = 0; i < 20; ++i) {
+ styleText += styleText;
+ }
+
+ document.getElementById("style").firstChild.data = styleText;
+
+ </script>
+</head>
+<body>
+
+<p>This should be green.</p>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/444237-1.html b/layout/style/crashtests/444237-1.html
new file mode 100644
index 0000000000..7eac32ed8e
--- /dev/null
+++ b/layout/style/crashtests/444237-1.html
@@ -0,0 +1 @@
+<input style="box-shadow: initial;">
diff --git a/layout/style/crashtests/444848-1.html b/layout/style/crashtests/444848-1.html
new file mode 100644
index 0000000000..d2c75d5766
--- /dev/null
+++ b/layout/style/crashtests/444848-1.html
@@ -0,0 +1,9 @@
+<style>
+ [^=foo
+</style>
+<style>
+ [*=bar
+</style>
+<style>
+ [$=baz
+</style>
diff --git a/layout/style/crashtests/447776-1.html b/layout/style/crashtests/447776-1.html
new file mode 100644
index 0000000000..dde700fae1
--- /dev/null
+++ b/layout/style/crashtests/447776-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Hang with zero width and word-wrap</title>
+</head><body>
+<div style="width: 0px; word-wrap: break-word;">abc</div>
+</body></html>
diff --git a/layout/style/crashtests/447783-1.html b/layout/style/crashtests/447783-1.html
new file mode 100644
index 0000000000..1af063f16b
--- /dev/null
+++ b/layout/style/crashtests/447783-1.html
@@ -0,0 +1,8 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Hang with column-count and word-wrap</title>
+</head><body>
+<div style="border: 1px solid black; word-wrap: normal; column-count: 2; width: 110px;">
+a<span style="word-wrap: break-word;">abcde</span>
+</div>
+</body></html>
diff --git a/layout/style/crashtests/448161-1.html b/layout/style/crashtests/448161-1.html
new file mode 100644
index 0000000000..ef200462b5
--- /dev/null
+++ b/layout/style/crashtests/448161-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var r = document.createRange();
+ r.setStart(document.body, 0);
+ r.setEnd(document.getElementById("g"), 0);
+ r.deleteContents();
+
+ // Give spell-check a chance to run
+ setTimeout(function() { document.documentElement.className = ""; },
+ 50);
+}
+
+</script>
+</head>
+
+<body onload="boom();" contenteditable="true"><span><span contenteditable="true"><a href="http://www.mozilla.org/">5</a></span></span><span id="g"></span></body>
+
+</html>
diff --git a/layout/style/crashtests/448161-2.html b/layout/style/crashtests/448161-2.html
new file mode 100644
index 0000000000..0dd8a8b530
--- /dev/null
+++ b/layout/style/crashtests/448161-2.html
@@ -0,0 +1,9 @@
+<html>
+ <body>
+ <script>
+ var node = document.createElement("a");
+ node.href = "http://www.mozilla.org";
+ document.defaultView.getComputedStyle(node).color
+ </script>
+ </body>
+</html>
diff --git a/layout/style/crashtests/452150-1.xhtml b/layout/style/crashtests/452150-1.xhtml
new file mode 100644
index 0000000000..4ff411c6de
--- /dev/null
+++ b/layout/style/crashtests/452150-1.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head></head>
+<body>
+<p><m:mo fontsize="268435456em"><m:mstyle scriptlevel="30"><m:mstyle scriptlevel="15"><span/></m:mstyle></m:mstyle></m:mo></p>
+</body>
+</html>
diff --git a/layout/style/crashtests/456196.html b/layout/style/crashtests/456196.html
new file mode 100644
index 0000000000..dd4b013b93
--- /dev/null
+++ b/layout/style/crashtests/456196.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<title>Crash [@ nsCSSValueList::~nsCSSValueList] with adding a lot of values in css property</title>
+</head>
+<body>
+<div style="border: 1px solid black; width: 100px; height: 100px;"></div>
+<script>
+function forceFree() {
+ var str = ' rotate(1deg)';
+ for(var i=0;i<17;i++) {str += str;}
+ document.getElementsByTagName('div')[0].style.transform = str;
+}
+setTimeout(forceFree,100);
+</script>
+</body>
+</html>
diff --git a/layout/style/crashtests/460209-1.html b/layout/style/crashtests/460209-1.html
new file mode 100644
index 0000000000..d78235738a
--- /dev/null
+++ b/layout/style/crashtests/460209-1.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<style>
+@import "404.css" s x
+</style>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/style/crashtests/460217-1.html b/layout/style/crashtests/460217-1.html
new file mode 100644
index 0000000000..e5918ad6ed
--- /dev/null
+++ b/layout/style/crashtests/460217-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@font-face
+{
+}
+@font-face
+{ font-family: 1;
+}
+</style>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/style/crashtests/460323-1.html b/layout/style/crashtests/460323-1.html
new file mode 100644
index 0000000000..0bae55aaf1
--- /dev/null
+++ b/layout/style/crashtests/460323-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script>
+ var expectedLoads = 3;
+ function load_done() {
+ --expectedLoads;
+ if (expectedLoads == 0) {
+ document.documentElement.className = "";
+ }
+ }
+ function addLink() {
+ var l = document.createElement("link");
+ l.rel = "stylesheet";
+ l.type = "text/css";
+ l.href = "data:text/css,some { random: data }";
+ l.onload = load_done;
+ document.getElementsByTagName("head")[0].appendChild(l);
+ }
+ function doIt() {
+ document.styleSheets[0].insertRule('a {}', 0)
+ addLink();
+ addLink();
+ }
+ addLink();
+ </script>
+ </head>
+ <body onload="doIt()">
+ <body>
+</body>
diff --git a/layout/style/crashtests/466845-1.html b/layout/style/crashtests/466845-1.html
new file mode 100644
index 0000000000..49b6b02b76
--- /dev/null
+++ b/layout/style/crashtests/466845-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>Crash [@ nsViewManager::CreateView] with ::first-line position: absolute and transform</title>
+<style>
+#a::first-line { transform: translate(50px);}
+</style>
+</head>
+<body style="position: absolute;">
+<span style="position: absolute;" id="a">
+<span style="transform: translate(50px);">&#1593; &#1593; &#1593;
+</span>
+</span>
+</body>
+</html>
diff --git a/layout/style/crashtests/469432-1.xhtml b/layout/style/crashtests/469432-1.xhtml
new file mode 100644
index 0000000000..9b11a88c42
--- /dev/null
+++ b/layout/style/crashtests/469432-1.xhtml
@@ -0,0 +1,8 @@
+<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">
+
+<xul:menuitem>
+<select/>
+<xul:tooltip/>
+<mathml:msup/>
+</xul:menuitem>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/472195-1.html b/layout/style/crashtests/472195-1.html
new file mode 100644
index 0000000000..0eff97e46d
--- /dev/null
+++ b/layout/style/crashtests/472195-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Copy of 473892-1.html for bug 472195</title>
+<style type="text/css">
+
+@media (min-width: 5rem) { body { color: green; } }
+
+</style>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/style/crashtests/472237-1.html b/layout/style/crashtests/472237-1.html
new file mode 100644
index 0000000000..0d0e273db4
--- /dev/null
+++ b/layout/style/crashtests/472237-1.html
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+
+@font-face {
+ font-family: "Fontin-Sans SC";
+ /* the font url below is correct but won't be accessed due to cross-site restrictions */
+ src: url(../../reftests/fonts/markA.ttf) format("opentype");
+}
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("r").style.fontFamily = "'Fontin-Sans SC'";
+ document.documentElement.offsetHeight;
+ document.removeChild(document.documentElement);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div id="r">R</div></body>
+</html>
diff --git a/layout/style/crashtests/473720-1.html b/layout/style/crashtests/473720-1.html
new file mode 100644
index 0000000000..a29316181f
--- /dev/null
+++ b/layout/style/crashtests/473720-1.html
@@ -0,0 +1,15 @@
+<html><head><style>
+/* Recovery from an unparseable recognized @-rule is not the same thing
+ as recovery from an unrecognized @-rule. */
+
+@charset # { }
+@import # { }
+@namespace # { }
+@media # { }
+@-moz-document # { }
+@font-face # { }
+@page # { }
+@-non-mozilla # { }
+@nonstandard # { }
+
+</style></head></html>
diff --git a/layout/style/crashtests/473892-1.html b/layout/style/crashtests/473892-1.html
new file mode 100644
index 0000000000..362e38a6d9
--- /dev/null
+++ b/layout/style/crashtests/473892-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+@media (width: 5ex) { }
+
+</style>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/style/crashtests/473914-1.html b/layout/style/crashtests/473914-1.html
new file mode 100644
index 0000000000..47a7a98986
--- /dev/null
+++ b/layout/style/crashtests/473914-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style id="s"></style>
+<script type="text/javascript">
+
+// Duplicates the string 2^n times
+function exp(s, n)
+{
+ for (var i = 0; i < n; ++i)
+ s += s;
+ return s;
+}
+
+var stylesheet = exp("/**/", 20);
+document.getElementById("s").textContent = stylesheet;
+
+</script>
+</head>
+<body>
+<div></div>
+</body>
+</html>
diff --git a/layout/style/crashtests/474377-1.xhtml b/layout/style/crashtests/474377-1.xhtml
new file mode 100644
index 0000000000..519a753cfa
--- /dev/null
+++ b/layout/style/crashtests/474377-1.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+
+#q:after {
+ content: 'A';
+}
+
+</style>
+</head>
+<body>
+
+<mrow xmlns="http://www.w3.org/1998/Math/MathML"></mrow>
+<span id="q"><span><div></div></span></span>
+
+</body>
+</html>
+
diff --git a/layout/style/crashtests/478321-1.xhtml b/layout/style/crashtests/478321-1.xhtml
new file mode 100644
index 0000000000..654e11c106
--- /dev/null
+++ b/layout/style/crashtests/478321-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="display: table; float: left; line-height: 1rem;"/>
diff --git a/layout/style/crashtests/481557.html b/layout/style/crashtests/481557.html
new file mode 100644
index 0000000000..cdc9bb88cc
--- /dev/null
+++ b/layout/style/crashtests/481557.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body onload="document.queryCommandValue('justifycenter');">
+
+<table contenteditable="true"><tbody><tr><td align="char"></td></tr></tbody></table>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/495269-1.html b/layout/style/crashtests/495269-1.html
new file mode 100644
index 0000000000..40090edc8c
--- /dev/null
+++ b/layout/style/crashtests/495269-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" href="data:text/css,@font-face {}';">
+ <link rel="stylesheet" href="data:text/css,@font-face {}';">
+ <script>
+ // Force a unique inner for the second linked sheet
+ document.styleSheets[1].cssRules[0];
+ </script>
+ </head>
+</html>
+
diff --git a/layout/style/crashtests/495269-2.html b/layout/style/crashtests/495269-2.html
new file mode 100644
index 0000000000..8deca08e81
--- /dev/null
+++ b/layout/style/crashtests/495269-2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" href="data:text/css,@-moz-document domain(example.com) {}';">
+ <link rel="stylesheet" href="data:text/css,@-moz-document domain(example.com) {}';">
+ <script>
+ // Force a unique inner for the second linked sheet
+ document.styleSheets[1].cssRules[0];
+ </script>
+ </head>
+</html>
+
diff --git a/layout/style/crashtests/498036-1.html b/layout/style/crashtests/498036-1.html
new file mode 100644
index 0000000000..0128be7491
--- /dev/null
+++ b/layout/style/crashtests/498036-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<!-- bogus assertion when one stylesheet contains two @import rules
+ specifying malformed URIs -->
+<html>
+<head>
+<style type="text/css">
+
+@import 'data:css';
+@import 'data:css';
+
+</style>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/style/crashtests/509155-1.html b/layout/style/crashtests/509155-1.html
new file mode 100644
index 0000000000..d211373326
--- /dev/null
+++ b/layout/style/crashtests/509155-1.html
@@ -0,0 +1,4 @@
+<html style="outline-color: inherit;">
+<head></head>
+<body></body>
+</html>
diff --git a/layout/style/crashtests/509156-1.html b/layout/style/crashtests/509156-1.html
new file mode 100644
index 0000000000..f75776a5d3
--- /dev/null
+++ b/layout/style/crashtests/509156-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html>
+<head></head>
+<body><div style="text-align: -moz-right;"><div style="display: table;"></div></div></body>
+</html>
diff --git a/layout/style/crashtests/509569-1.html b/layout/style/crashtests/509569-1.html
new file mode 100644
index 0000000000..41a3cc5594
--- /dev/null
+++ b/layout/style/crashtests/509569-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html style="border-inline-start: inherit; border: none"><body></body></html>
diff --git a/layout/style/crashtests/512851-1.xhtml b/layout/style/crashtests/512851-1.xhtml
new file mode 100644
index 0000000000..d772390cd1
--- /dev/null
+++ b/layout/style/crashtests/512851-1.xhtml
@@ -0,0 +1,23 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<style type="text/css">
+mover { font-size: 326590449211mm; }
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("r").setAttribute("accentunder", "4");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<mover xmlns="http://www.w3.org/1998/Math/MathML" id="mover"><munderover id="r"><merror/><msqrt/></munderover></mover>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/524252-1.html b/layout/style/crashtests/524252-1.html
new file mode 100644
index 0000000000..4ea708bc39
--- /dev/null
+++ b/layout/style/crashtests/524252-1.html
@@ -0,0 +1,10 @@
+<html>
+<head><script>
+function boom()
+{
+ var f = document.getElementById("f");
+ window.getComputedStyle(f).getPropertyValue("text-decoration");
+}
+</script></head>
+<body onload="boom();"><font id="f" color="black">a</font></body>
+</html>
diff --git a/layout/style/crashtests/536789-1.html b/layout/style/crashtests/536789-1.html
new file mode 100644
index 0000000000..86fcb344f7
--- /dev/null
+++ b/layout/style/crashtests/536789-1.html
@@ -0,0 +1,11 @@
+<!-- Must be in quirks mode -->
+<html>
+ <body>
+ <script>
+ var docEl = document.documentElement;
+ var b = document.body;
+ docEl.removeChild(b);
+ docEl.appendChild(document.createElement("table"));
+ docEl.offsetWidth;
+ </script>
+</html>
diff --git a/layout/style/crashtests/539613-1.xhtml b/layout/style/crashtests/539613-1.xhtml
new file mode 100644
index 0000000000..386d7e1277
--- /dev/null
+++ b/layout/style/crashtests/539613-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<body>
+<m:ms fontsize="90071992547409pc"><span><m:mfrac><m:mtext fontsize="625%"><m:mfrac>f<m:mrow><m:mstyle scriptlevel="3"><m:msup/></m:mstyle></m:mrow></m:mfrac></m:mtext><m:malignmark/></m:mfrac></span></m:ms>
+</body>
+</html>
diff --git a/layout/style/crashtests/558943-1.xhtml b/layout/style/crashtests/558943-1.xhtml
new file mode 100644
index 0000000000..e3d978fd34
--- /dev/null
+++ b/layout/style/crashtests/558943-1.xhtml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<style>
+.x:first-line { }
+.y { }
+</style>
+</head>
+<body onload="setTimeout(function(){ document.documentElement.className = 'y'; }, 0)">
+<td class="x"><a href="#">Link</a></td>
+</body>
+</html>
diff --git a/layout/style/crashtests/559491.html b/layout/style/crashtests/559491.html
new file mode 100644
index 0000000000..19126e5603
--- /dev/null
+++ b/layout/style/crashtests/559491.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ for (var i = 0; i < 200; ++i) {
+ //dump(i + "\n");
+ r1 = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
+ r1.setAttributeNS(null, "href", "404");
+ r1.style.color = "green";
+ r2 = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ r2.style.color = "red";
+ document.removeChild(document.documentElement);
+ document.appendChild(r1);
+ document.removeChild(document.documentElement);
+ document.appendChild(r2);
+ document.removeChild(document.documentElement);
+ document.appendChild(r1);
+ document.documentElement.offsetHeight;
+ }
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/565248-1.html b/layout/style/crashtests/565248-1.html
new file mode 100644
index 0000000000..18c8add08e
--- /dev/null
+++ b/layout/style/crashtests/565248-1.html
@@ -0,0 +1,2 @@
+<html><body style="font-size: 18014398509481984%"></body></html>
+
diff --git a/layout/style/crashtests/571105-1.xhtml b/layout/style/crashtests/571105-1.xhtml
new file mode 100644
index 0000000000..4dce42dc8e
--- /dev/null
+++ b/layout/style/crashtests/571105-1.xhtml
@@ -0,0 +1 @@
+<link xmlns="http://www.w3.org/1999/xhtml" href="http://www.mozilla.org/"/> \ No newline at end of file
diff --git a/layout/style/crashtests/573127-1.html b/layout/style/crashtests/573127-1.html
new file mode 100644
index 0000000000..72fbaf63ef
--- /dev/null
+++ b/layout/style/crashtests/573127-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var mspace = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mspace");
+ var emptyset = document.createElementNS("http://www.w3.org/1998/Math/MathML", "emptyset");
+ emptyset.setAttributeNS(null, "mathvariant", "3");
+ mspace.appendChild(emptyset);
+ document.body.appendChild(mspace);
+ emptyset.removeAttribute('mathvariant');
+}
+
+</script>
+</head>
+
+<body onload="boom()"></body>
+</html>
diff --git a/layout/style/crashtests/575464-1.html b/layout/style/crashtests/575464-1.html
new file mode 100644
index 0000000000..e77649ed74
--- /dev/null
+++ b/layout/style/crashtests/575464-1.html
@@ -0,0 +1 @@
+<html><body style="font-size: 1823190rem;"><big><big><span style="text-shadow: 0pt 0pt 0.2em rgb(136, 255, 119);"></span></big></big></body></html>
diff --git a/layout/style/crashtests/580685.html b/layout/style/crashtests/580685.html
new file mode 100644
index 0000000000..2a9115cb60
--- /dev/null
+++ b/layout/style/crashtests/580685.html
@@ -0,0 +1,10 @@
+<html>
+<head></head>
+<body style="outline-offset: 0.1rem; ">
+<script>
+var body = document.body;
+document.removeChild(document.documentElement);
+var compstyle = window.getComputedStyle(body).getPropertyValue('outline-offset');
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/585185-1.html b/layout/style/crashtests/585185-1.html
new file mode 100644
index 0000000000..17eec73b36
--- /dev/null
+++ b/layout/style/crashtests/585185-1.html
@@ -0,0 +1 @@
+<a style="font: -2px Verdana;">
diff --git a/layout/style/crashtests/588627-1.html b/layout/style/crashtests/588627-1.html
new file mode 100644
index 0000000000..510a20dd58
--- /dev/null
+++ b/layout/style/crashtests/588627-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="column-rule: 137438953471mozmm groove transparent"></body>
+</html> \ No newline at end of file
diff --git a/layout/style/crashtests/592698-1.html b/layout/style/crashtests/592698-1.html
new file mode 100644
index 0000000000..b7570f6e4c
--- /dev/null
+++ b/layout/style/crashtests/592698-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <iframe id="x"
+ srcdoc="<div id='a'>aaa"></iframe>
+
+ <script>
+ window.onload = function() {
+ window.frames[0].document.getElementById("a").setAttribute("style",
+ 'transition-property: color;' +
+ 'transition-duration: 10s;' +
+ 'transition-property: color;' +
+ 'transition-duration: 10s; ' +
+ 'color: red;');
+
+ // And start the transition
+ window.frames[0].document.documentElement.getBoundingClientRect();
+
+ // Now kill off the presshell
+ var frame = document.getElementById("x");
+ frame.style.display = "none";
+ document.documentElement.getBoundingClientRect();
+
+ // And wait for the refresh driver to fire
+ setTimeout(function() {
+ document.documentElement.className = "";
+ }, 100);
+ }
+ </script>
+</html>
diff --git a/layout/style/crashtests/601437-1.html b/layout/style/crashtests/601437-1.html
new file mode 100644
index 0000000000..8b5efd6cca
--- /dev/null
+++ b/layout/style/crashtests/601437-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link id="s" rel="stylesheet" href="data:text/css,@font-face { font-family: 'F'; src: url('file:///404/'); }">
+</head>
+<body onload="document.getElementById('s').sheet.media.appendMedium('x');"></body>
+</html>
diff --git a/layout/style/crashtests/601439-1.html b/layout/style/crashtests/601439-1.html
new file mode 100644
index 0000000000..e76e4520e9
--- /dev/null
+++ b/layout/style/crashtests/601439-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+
+var elt = document.createElement("span");
+elt.setAttribute("style", "color: red ! important;");
+elt.style.getPropertyPriority("foo");
+
+</script>
diff --git a/layout/style/crashtests/605689-1.html b/layout/style/crashtests/605689-1.html
new file mode 100644
index 0000000000..be02ac2e71
--- /dev/null
+++ b/layout/style/crashtests/605689-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script>
+
+function boom()
+{
+ var r = document.documentElement;
+ r.style.display = "table-cell";
+ r.style.transitionProperty = "x";
+ window.getComputedStyle(r).transitionProperty;
+}
+
+</script>
+<body onload="boom();"></body>
diff --git a/layout/style/crashtests/611922-1.html b/layout/style/crashtests/611922-1.html
new file mode 100644
index 0000000000..f6affa0de1
--- /dev/null
+++ b/layout/style/crashtests/611922-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <body onload="setTimeout(boom, 100)">
+ <a id="x" href=""><span>This link starts out visited</span></a>
+ <script>
+ function boom() {
+ document.getElementById("x").removeAttribute("href");
+ document.body.offsetWidth;
+ document.documentElement.className = "";
+ }
+ </script>
+ </body>
+</body>
diff --git a/layout/style/crashtests/612213.html b/layout/style/crashtests/612213.html
new file mode 100644
index 0000000000..a97b21bca5
--- /dev/null
+++ b/layout/style/crashtests/612213.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ var v = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
+ document.body.appendChild(v);
+ var s = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ s.setAttribute("style", "filter: url(#g);");
+ document.body.appendChild(s);
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body id="g" onload="setTimeout(boom, 0);"></body></html>
diff --git a/layout/style/crashtests/621596-1.html b/layout/style/crashtests/621596-1.html
new file mode 100644
index 0000000000..95bb498bf7
--- /dev/null
+++ b/layout/style/crashtests/621596-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.style.columnWidth = "1px";
+ document.documentElement.style.MozOutlineRadiusBottomleft = "100%";
+ document.documentElement.style.padding = "2251799813685249em";
+ window.getComputedStyle(document.documentElement).MozOutlineRadiusBottomleft;
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/622314-1.xhtml b/layout/style/crashtests/622314-1.xhtml
new file mode 100644
index 0000000000..4daf23169f
--- /dev/null
+++ b/layout/style/crashtests/622314-1.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <div>
+ <body link="orange" style="position: absolute;"></body>
+ <body link="yellow" style="position: absolute;"></body>
+
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+ <span style="display: inline;"></span>
+
+ </div>
+</html>
diff --git a/layout/style/crashtests/635153.html b/layout/style/crashtests/635153.html
new file mode 100644
index 0000000000..50f05878a1
--- /dev/null
+++ b/layout/style/crashtests/635153.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var r = document.documentElement;
+ document.__proto__ = document.createTextNode("text");
+ r.style.counterReset = "a";
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/637242.xhtml b/layout/style/crashtests/637242.xhtml
new file mode 100644
index 0000000000..a8d99a7325
--- /dev/null
+++ b/layout/style/crashtests/637242.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style id="style">p { color: red; }</style>
+<script>
+<![CDATA[
+
+function boom()
+{
+ var styleText = "p { color: green; }";
+
+ // Make 2^17 rules
+ for (var i = 0; i < 17; ++i) {
+ styleText += styleText;
+ }
+
+ document.getElementById("style").firstChild.data = styleText;
+
+ document.body.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "mrow"));
+}
+
+]]>
+</script>
+</head>
+
+<body onload="boom();"><p>This should be green</p></body>
+
+</html>
diff --git a/layout/style/crashtests/645142.html b/layout/style/crashtests/645142.html
new file mode 100644
index 0000000000..290355c187
--- /dev/null
+++ b/layout/style/crashtests/645142.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html><html>
+<head><title>Testcase for bug 645142</title></head>
+<body>
+<div style="font-size: 18446744073709552000mozmm"></div>
+<div style="font-size: 18446744073709552000px"></div>
+<div style="font-size: 18446744073709552000pc"></div>
+<div style="font-size: 18446744073709552000pt"></div>
+<div style="font-size: 18446744073709552000in"></div>
+<div style="font-size: 18446744073709552000mm"></div>
+<div style="font-size: 18446744073709552000cm"></div>
+</body></html>
diff --git a/layout/style/crashtests/652976-1.svg b/layout/style/crashtests/652976-1.svg
new file mode 100644
index 0000000000..1ca6ee28ec
--- /dev/null
+++ b/layout/style/crashtests/652976-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+window.addEventListener("load", function() {
+ setTimeout(function() {
+ document.getElementById("a").style.MozAnimationName = "a";
+ }, 0);
+}, false);
+</script>
+<rect id="a"><animate attributeName="fill" by="#AAF573"/></rect>
+</svg>
diff --git a/layout/style/crashtests/653675.html b/layout/style/crashtests/653675.html
new file mode 100644
index 0000000000..ad41881aac
--- /dev/null
+++ b/layout/style/crashtests/653675.html
@@ -0,0 +1 @@
+<body style="transform: rotate(520327040619807469187028288680706778649deg); position:fixed;"> <body text=black>
diff --git a/layout/style/crashtests/665209-1.html b/layout/style/crashtests/665209-1.html
new file mode 100644
index 0000000000..30e8055ebb
--- /dev/null
+++ b/layout/style/crashtests/665209-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ var w = '<div xmlns="http://www.w3.org/1999/xhtml" style="content: url(#);" />';
+ var v = 'url("data:image/svg+xml,' + encodeURIComponent(w) + '")';
+ document.documentElement.style.content = v;
+ document.documentElement.className = "";
+}
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/671799-1.html b/layout/style/crashtests/671799-1.html
new file mode 100644
index 0000000000..cc89495b36
--- /dev/null
+++ b/layout/style/crashtests/671799-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe src="data:text/html,<style>@font-face { font-family: 'x'; src: url(x.ttf); } :root { font-family: 'x'; }</style>"></iframe>
+</body>
+</html>
diff --git a/layout/style/crashtests/671799-2.html b/layout/style/crashtests/671799-2.html
new file mode 100644
index 0000000000..a8398680e0
--- /dev/null
+++ b/layout/style/crashtests/671799-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+ font-family: foo;
+ src: url("http://spaces in hostname/");
+}
+body {
+ font-family: foo, monospace;
+}
+</style>
+</head>
+<body>
+foo bar
+</body>
+</html>
diff --git a/layout/style/crashtests/690990-1.html b/layout/style/crashtests/690990-1.html
new file mode 100644
index 0000000000..19520e4f90
--- /dev/null
+++ b/layout/style/crashtests/690990-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+
+<link id="e" href="data:text/css,.ref { background-color: green; }" rel="stylesheet">
+
+<script>
+
+function boom()
+{
+ document.documentElement.appendChild(document.getElementById("e"));
+ document.styleSheets[0].cssRules[0];
+ // Remove reftest-wait async so we give the SheetComplete a chance to run
+ setTimeout(function() { document.documentElement.className = ""; }, 0);
+}
+
+</script>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/style/crashtests/694775.html b/layout/style/crashtests/694775.html
new file mode 100644
index 0000000000..2952d5e6e8
--- /dev/null
+++ b/layout/style/crashtests/694775.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+
+document.documentElement.style.direction = "rtl";
+document.__proto__ = null;
+
+</script>
diff --git a/layout/style/crashtests/696188-1.html b/layout/style/crashtests/696188-1.html
new file mode 100644
index 0000000000..7442743cfc
--- /dev/null
+++ b/layout/style/crashtests/696188-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<script>
+
+function boom()
+{
+ var e = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ document.body.appendChild(e);
+ e.setAttribute("style", "transform: rotate3d(2, 3, 4, 45deg) scale(10);");
+ e.offsetHeight;
+ e.setAttribute("style", "transition-duration: 1ms;");
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/696869-1.html b/layout/style/crashtests/696869-1.html
new file mode 100644
index 0000000000..b7f3dccd25
--- /dev/null
+++ b/layout/style/crashtests/696869-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<body style="transition: 4000000000000000s" onload="document.body.style.color = 'green';"></body> \ No newline at end of file
diff --git a/layout/style/crashtests/700116.html b/layout/style/crashtests/700116.html
new file mode 100644
index 0000000000..de35ce7029
--- /dev/null
+++ b/layout/style/crashtests/700116.html
@@ -0,0 +1,5 @@
+<head>
+ <script>
+ document.write('<link rel="stylesheet" href="#"><link rel="alternate stylesheet" title="x" href="data:text/css,"><link rel="stylesheet" title="x" href="data:text/css,">');
+ </script>
+</head>
diff --git a/layout/style/crashtests/729126-1.html b/layout/style/crashtests/729126-1.html
new file mode 100644
index 0000000000..a5c50abd0b
--- /dev/null
+++ b/layout/style/crashtests/729126-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body style="transition-duration: 1ms"></body>
+<script>
+var body = document.body;
+/* flush */ getComputedStyle(body, "").background;
+body.style.background = 'url(none.png), repeat';
+/* flush */ getComputedStyle(body, "").background;
+</script>
+</html>
diff --git a/layout/style/crashtests/729126-2.html b/layout/style/crashtests/729126-2.html
new file mode 100644
index 0000000000..63533ee302
--- /dev/null
+++ b/layout/style/crashtests/729126-2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body style="background-size: cover; transition-duration: 1ms"></body>
+<script>
+var body = document.body;
+/* flush */ getComputedStyle(body, "").backgroundSize;
+body.style.backgroundSize = 'contain';
+/* flush */ getComputedStyle(body, "").backgroundSize;
+</script>
+</html>
diff --git a/layout/style/crashtests/786108-1.html b/layout/style/crashtests/786108-1.html
new file mode 100644
index 0000000000..2962e71177
--- /dev/null
+++ b/layout/style/crashtests/786108-1.html
@@ -0,0 +1,22 @@
+<html>
+ <head></head>
+ <body></body>
+ <script type="text/javascript">
+ // Detect severe performance and memory issues when large amounts of errors
+ // are reported from CSS embedded in a file with a long data URI. Addressed
+ // by 786108; should finish quickly with that patch and run for a very long
+ // time otherwise.
+
+ var img = new Array;
+ img.push('<img src="data:image/svg+xml,');
+ img.push(encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300px" height="300px">'));
+
+ for (var i = 0 ; i < 10000 ; i++)
+ img.push(encodeURIComponent('<circle cx="0" cy="0" r="1" style="xxx-invalid-property: 0;"/>'));
+
+ img.push(encodeURIComponent('</svg>'));
+ img.push('">');
+
+ document.getElementsByTagName('body')[0].innerHTML = img.join('');
+ </script>
+</html>
diff --git a/layout/style/crashtests/786108-2.html b/layout/style/crashtests/786108-2.html
new file mode 100644
index 0000000000..1b2892040b
--- /dev/null
+++ b/layout/style/crashtests/786108-2.html
@@ -0,0 +1,23 @@
+<html>
+ <head></head>
+ <body></body>
+ <script type="text/javascript">
+ // Detect severe performance and memory issues when large amounts of errors
+ // are reported from CSS embedded in a file with a long data URI. Addressed
+ // by 786108; should finish quickly with that patch and run for a very long
+ // time otherwise. This version is designed for slow / memory constrained
+ // platforms like Android.
+
+ var img = new Array;
+ img.push('<img src="data:image/svg+xml,');
+ img.push(encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300px" height="300px">'));
+
+ for (var i = 0 ; i < 2500 ; i++)
+ img.push(encodeURIComponent('<circle cx="0" cy="0" r="1" style="xxx-invalid-property: 0;"/>'));
+
+ img.push(encodeURIComponent('</svg>'));
+ img.push('">');
+
+ document.getElementsByTagName('body')[0].innerHTML = img.join('');
+ </script>
+</html>
diff --git a/layout/style/crashtests/788836.html b/layout/style/crashtests/788836.html
new file mode 100644
index 0000000000..938c216753
--- /dev/null
+++ b/layout/style/crashtests/788836.html
@@ -0,0 +1,3 @@
+<style>@\</style>
+<style>@\
+</style>
diff --git a/layout/style/crashtests/806310-1.html b/layout/style/crashtests/806310-1.html
new file mode 100644
index 0000000000..505b410769
--- /dev/null
+++ b/layout/style/crashtests/806310-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html lang="en-US" style="font: caption; font-size: 1rem;">
+<body></body>
+</html>
diff --git a/layout/style/crashtests/809762.html b/layout/style/crashtests/809762.html
new file mode 100644
index 0000000000..ad06540efd
--- /dev/null
+++ b/layout/style/crashtests/809762.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var ss = document.getElementsByTagName('style')[0];
+ var styleText = '@font-face { font-family: "MarkA"; src: url(markA.ttf); } :root { font-family: "MarkA"; }';
+
+ ss.firstChild.data = styleText;
+ setTimeout(function() {
+ ss.firstChild.data = styleText + " ";
+ }, 0);
+}
+
+</script>
+<style>
+</style>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/812824.html b/layout/style/crashtests/812824.html
new file mode 100644
index 0000000000..5367b6c308
--- /dev/null
+++ b/layout/style/crashtests/812824.html
@@ -0,0 +1 @@
+<html style="border: inherit;"><div></div><style>html, div { border-image-source: url('border.png'); }</style></html>
diff --git a/layout/style/crashtests/822766-1.html b/layout/style/crashtests/822766-1.html
new file mode 100644
index 0000000000..21c298ceb8
--- /dev/null
+++ b/layout/style/crashtests/822766-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+@keyframes togreen {
+ 100% {
+ color: green;
+ }
+}
+
+.a:after {
+ animation-name: togreen;
+ animation-duration: 10s;
+}
+
+</style>
+<script>
+
+function boom()
+{
+ document.documentElement.setAttribute("class", "a");
+ document.documentElement.offsetHeight;
+ document.documentElement.appendChild(document.createElement("span"));
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/822877.html b/layout/style/crashtests/822877.html
new file mode 100644
index 0000000000..8dfa192e49
--- /dev/null
+++ b/layout/style/crashtests/822877.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.style.transitionDelay = "10s";
+ document.documentElement.style.borderBottomLeftRadius = "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999%";
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/827213-1.html b/layout/style/crashtests/827213-1.html
new file mode 100644
index 0000000000..16cc88a20f
--- /dev/null
+++ b/layout/style/crashtests/827213-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<div style="height: 100px; background-image: repeating-radial-gradient(red -999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999%, blue 100%)">
+</div>
+
+</body>
+</html>
diff --git a/layout/style/crashtests/827220.html b/layout/style/crashtests/827220.html
new file mode 100644
index 0000000000..7c1717a1b6
--- /dev/null
+++ b/layout/style/crashtests/827220.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html style="font-size: 8em; clip: rect(auto, 3em, 0, 0); transition-timing-function: cubic-bezier(1, 1152921504606847000, 1, 1); transition-duration: 1s">
+<body onload="document.documentElement.style.fontSize = '';">
+</body>
+</html>
diff --git a/layout/style/crashtests/827591-1.html b/layout/style/crashtests/827591-1.html
new file mode 100644
index 0000000000..a0ab100913
--- /dev/null
+++ b/layout/style/crashtests/827591-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+@page {}
+
+</style>
+<script>
+
+function boom()
+{
+ document.styleSheets[0].cssRules[0].style.paddingLeft = "initial";
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/829817.html b/layout/style/crashtests/829817.html
new file mode 100644
index 0000000000..a81fb6b79d
--- /dev/null
+++ b/layout/style/crashtests/829817.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+@page {}
+
+</style>
+<script>
+
+function boom()
+{
+ // This shouldn't cause a shutdown leak.
+ document.styleSheets[0].cssRules[0].style.someExpando = "set an expando to preserve the wrapper";
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/842134.html b/layout/style/crashtests/842134.html
new file mode 100644
index 0000000000..f5bab1214b
--- /dev/null
+++ b/layout/style/crashtests/842134.html
@@ -0,0 +1 @@
+<!doctype html><style>body { marker: url(#m) url(#m); }</style>
diff --git a/layout/style/crashtests/861489-1.html b/layout/style/crashtests/861489-1.html
new file mode 100644
index 0000000000..bb394cef5b
--- /dev/null
+++ b/layout/style/crashtests/861489-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+@keyframes anim {
+ 20% {
+ color: green;
+ }
+}
+
+a {
+ animation-name: anim;
+ animation-duration: 40s;
+}
+
+</style>
+<style>
+
+:link { background: red ! important; }
+
+</style>
+</head>
+
+<body onload="document.documentElement.style.border = 'initial';">
+<div><a href="data:text/html,unlikely to be visited"></a></div>
+</body>
+</html>
diff --git a/layout/style/crashtests/862113.html b/layout/style/crashtests/862113.html
new file mode 100644
index 0000000000..319132b783
--- /dev/null
+++ b/layout/style/crashtests/862113.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ window.getComputedStyle(document.documentElement, ":foo");
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/867487.html b/layout/style/crashtests/867487.html
new file mode 100644
index 0000000000..a259690d14
--- /dev/null
+++ b/layout/style/crashtests/867487.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="data:text/css,">
+<script>
+
+function boom()
+{
+ var s = document.styleSheets[0];
+ var n = s.ownerNode;
+ var p = n.parentNode;
+
+ s.insertRule("#a { }", 0);
+
+ for (var i = 0; i < 3; ++i) {
+ p.removeChild(n);
+ p.appendChild(n);
+ }
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/873222.html b/layout/style/crashtests/873222.html
new file mode 100644
index 0000000000..1ffcc623ae
--- /dev/null
+++ b/layout/style/crashtests/873222.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var r = document.documentElement;
+ r.style.font = "170% fantasy";
+ r.style.fontSynthesis = "none";
+ r.getAttribute("style");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/873260-1.html b/layout/style/crashtests/873260-1.html
new file mode 100644
index 0000000000..9461ef41ad
--- /dev/null
+++ b/layout/style/crashtests/873260-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var r = document.documentElement;
+ r.style.transitionDelay = "60s";
+ r.style.borderRadius = "9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999%";
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/873260-2.html b/layout/style/crashtests/873260-2.html
new file mode 100644
index 0000000000..9634389198
--- /dev/null
+++ b/layout/style/crashtests/873260-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.documentElement.style.transitionDelay = "1ms";
+ document.documentElement.style.marginRight = "calc(-999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999*-140737488355327px + 1%)";
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/style/crashtests/880862.html b/layout/style/crashtests/880862.html
new file mode 100644
index 0000000000..d89e24d90b
--- /dev/null
+++ b/layout/style/crashtests/880862.html
@@ -0,0 +1,28 @@
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ // This file tests if we create a shutdown leak.
+ document.getElementById("c").getContext("2d").fillText("x", 0, 0);
+ document.styleSheets[0].cssRules[0].style.whatever = "create an expando to preserve the wrapper";
+}
+
+</script>
+
+<style>
+
+@font-face {
+ font-family: missing;
+ src: local(missing);
+}
+
+</style>
+</head>
+
+<body onload="boom();">
+<canvas id="c"></canvas>
+</body>
+</html>
diff --git a/layout/style/crashtests/894245-1.html b/layout/style/crashtests/894245-1.html
new file mode 100644
index 0000000000..b04f0fae04
--- /dev/null
+++ b/layout/style/crashtests/894245-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<div style="background-color: -moz-mac-defaultbuttontext"></div>
+<div style="background-color: -moz-mac-focusring"></div>
+<div style="background-color: -moz-win-communicationstext"></div>
diff --git a/layout/style/crashtests/915440.html b/layout/style/crashtests/915440.html
new file mode 100644
index 0000000000..f3a291f1fe
--- /dev/null
+++ b/layout/style/crashtests/915440.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<body>
+<iframe src="data:text/html,<!DOCTYPE HTML><style>@font-face { font-family: 'a'; src: url('not-found') format('woff'); }</style>"></iframe>
+</body>
diff --git a/layout/style/crashtests/927734-1.html b/layout/style/crashtests/927734-1.html
new file mode 100644
index 0000000000..bd99f9b9ee
--- /dev/null
+++ b/layout/style/crashtests/927734-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<table>
+ <tr>
+ <td>
+ <style scoped></style>
+ <span></span>
+ </td>
+ </tr>
+</table>
+<style>div {}</style>
diff --git a/layout/style/crashtests/930270-1.html b/layout/style/crashtests/930270-1.html
new file mode 100644
index 0000000000..cbdf5a9e38
--- /dev/null
+++ b/layout/style/crashtests/930270-1.html
@@ -0,0 +1,6 @@
+<nobr>
+<form>
+<style scoped></style>
+<input required="required">
+<button>
+<nobr>
diff --git a/layout/style/crashtests/930270-2.html b/layout/style/crashtests/930270-2.html
new file mode 100644
index 0000000000..0cd1dc99c6
--- /dev/null
+++ b/layout/style/crashtests/930270-2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<body>
+<style scoped>span { color: red; }</style>
+<div><span></span></div>
+<script>
+var div = document.querySelector("div");
+div.remove();
+getComputedStyle(div.firstChild, "").color;
+</script>
diff --git a/layout/style/crashtests/945048-1.html b/layout/style/crashtests/945048-1.html
new file mode 100644
index 0000000000..753efb66f8
--- /dev/null
+++ b/layout/style/crashtests/945048-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<style>
+button::-moz-focus-inner:active { }
+</style>
+<button>hello</button>
diff --git a/layout/style/crashtests/972199-1.html b/layout/style/crashtests/972199-1.html
new file mode 100644
index 0000000000..a4e0de0d81
--- /dev/null
+++ b/layout/style/crashtests/972199-1.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ div {
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div></div>
+<script type="application/javascript">
+
+window.addEventListener("load", function() {
+ document.querySelector("div").setAttribute("style",
+ "animation: 100s 300s anim linear");
+ advance_clock(200000);
+ advance_clock(300000);
+
+ Promise.resolve().then(function() {
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ }).then(function() {
+ document.documentElement.className = "";
+ });
+});
+
+function advance_clock(milliseconds) {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
+}
+</script>
+</html>
diff --git a/layout/style/crashtests/989965-1.html b/layout/style/crashtests/989965-1.html
new file mode 100644
index 0000000000..a2879d973f
--- /dev/null
+++ b/layout/style/crashtests/989965-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<body>
+<style>
+::placeholder { color: red; }
+::placeholder:focus { color: green; }
+</style>
+<script>
+window.getComputedStyle(document.body, "::placeholder").color;
+</script>
diff --git a/layout/style/crashtests/992333-1.html b/layout/style/crashtests/992333-1.html
new file mode 100644
index 0000000000..86a1d57d73
--- /dev/null
+++ b/layout/style/crashtests/992333-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+p { --variable: value; transition: 1s --variable; }
+</style>
+<p>Hello.</p>
+<script>
+window.onload = function() {
+ document.querySelector("p").style.color = "green";
+};
+</script>
diff --git a/layout/style/crashtests/blue-32x32.png b/layout/style/crashtests/blue-32x32.png
new file mode 100644
index 0000000000..deefd19b2a
--- /dev/null
+++ b/layout/style/crashtests/blue-32x32.png
Binary files differ
diff --git a/layout/style/crashtests/border-image-visited-link.html b/layout/style/crashtests/border-image-visited-link.html
new file mode 100644
index 0000000000..b6e3ae5d78
--- /dev/null
+++ b/layout/style/crashtests/border-image-visited-link.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<title>border-image on link with visited styles</title>
+<style>
+
+:link { color: blue }
+:visited { color: purple }
+:link, :visited { border: medium solid; border-image: url(blue-32x32.png) 4 4 4 4; }
+
+</style>
+<a href="http://example.com/">test</a>
diff --git a/layout/style/crashtests/content-only-on-link-before.html b/layout/style/crashtests/content-only-on-link-before.html
new file mode 100644
index 0000000000..949e13542f
--- /dev/null
+++ b/layout/style/crashtests/content-only-on-link-before.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<style type="text/css">
+:link::before { content: "link: " }
+</style>
+<a href="http://www.example.com/">example</a>
diff --git a/layout/style/crashtests/content-only-on-visited-before.html b/layout/style/crashtests/content-only-on-visited-before.html
new file mode 100644
index 0000000000..4496bb86df
--- /dev/null
+++ b/layout/style/crashtests/content-only-on-visited-before.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<style type="text/css">
+:visited::before { content: "link: " }
+</style>
+<a href="http://www.example.com/">example</a>
diff --git a/layout/style/crashtests/crashtests.list b/layout/style/crashtests/crashtests.list
new file mode 100644
index 0000000000..3e7d1e2669
--- /dev/null
+++ b/layout/style/crashtests/crashtests.list
@@ -0,0 +1,325 @@
+load 105619-1.html
+load 187671-1.html
+load 192408-1.html
+load 285727-1.html
+load 286707-1.html
+load 317561-1.html
+load 330998-1.html
+load 363950.html
+load 368175-1.html
+load 368740.html
+load 379788-1.html
+load 383979-1.xhtml
+load 383979-2.html
+load 386939-1.html
+load 391034-1.xhtml
+load 397022-1.html
+load 399289-1.svg
+load 404470-1.html
+load 411603-1.html
+load 412588-1.html
+load 413274-1.xhtml
+skip-if(Android) load chrome://reftest/content/crashtests/layout/style/crashtests/416461-1.xhtml
+load 418007-1.xhtml
+load chrome://reftest/content/crashtests/layout/style/crashtests/431705-1.xhtml
+load 432561-1.html
+load 437170-1.html
+load 437532-1.html
+skip-if(ThreadSanitizer) load 439184-1.html
+load 444237-1.html
+load 444848-1.html
+load 447776-1.html
+load 447783-1.html
+load 448161-1.html
+load 448161-2.html
+load 452150-1.xhtml
+load 456196.html
+load 460209-1.html
+load 460217-1.html
+load 460323-1.html
+load 466845-1.html
+load 469432-1.xhtml
+load 472195-1.html
+load 472237-1.html # will fail, test for leak (474704)
+HTTP(..) load 472237-1.html
+load 473720-1.html
+load 473892-1.html
+load 473914-1.html
+load 474377-1.xhtml
+load 478321-1.xhtml
+load 481557.html
+load 495269-1.html
+load 495269-2.html
+load 498036-1.html
+load 509155-1.html
+load 509156-1.html
+load 509569-1.html
+load 512851-1.xhtml
+load 524252-1.html
+load 536789-1.html
+load 539613-1.xhtml
+load 558943-1.xhtml
+load 559491.html
+load 565248-1.html
+load 571105-1.xhtml
+load 573127-1.html
+load 575464-1.html
+load 580685.html
+load 585185-1.html
+load 588627-1.html
+load 592698-1.html
+load 601437-1.html
+load 601439-1.html
+load 605689-1.html
+load 611922-1.html
+load 612213.html
+load 621596-1.html
+load 622314-1.xhtml
+load 635153.html
+load 637242.xhtml
+load 645142.html
+load 652976-1.svg
+load 653675.html
+load 665209-1.html
+load 671799-1.html
+load 671799-2.html
+load 690990-1.html
+load 694775.html
+load 696188-1.html
+load 696869-1.html
+load 700116.html
+load 729126-1.html
+load 729126-2.html
+load 786108-1.html
+load 786108-2.html
+load 788836.html
+load 806310-1.html
+load 809762.html
+load 812824.html
+load 822766-1.html
+load 822877.html
+load 827213-1.html
+load 827220.html
+load 827591-1.html
+load 829817.html
+load 842134.html
+load 861489-1.html
+load 862113.html
+load 867487.html
+load 873222.html
+load 873260-1.html
+load 873260-2.html
+load 880862.html
+load 894245-1.html
+load 915440.html
+load 927734-1.html
+load 930270-1.html
+load 930270-2.html
+load 945048-1.html
+load 972199-1.html
+load 989965-1.html
+load 992333-1.html
+load 1017798-1.html
+load 1028514-1.html
+load 1066089-1.html
+load 1074651-1.html
+load 1135534.html
+load 1089463-1.html
+load 1136010-1.html
+load 1146101-1.html
+load 1153693-1.html
+load 1156969.svg
+load 1161320-1.html
+load 1161320-2.html
+load 1161366-1.html
+load 1163446-1.html
+load 1164813-1.html
+load 1167782-1.html
+load 1186768-1.xhtml
+load 1200568-1.html
+load 1206105-1.html
+load 1223688-1.html
+load 1223694-1.html
+load 1226400-1.html
+load 1227498.html
+load 1227501-1.html
+load 1228789-1.html
+load 1230408-1.html
+load 1233135-1.html
+load 1233135-2.html
+load 1236398.xhtml
+load 1238660-1.html
+load 1245260-1.html
+load 1247865-1.html
+load 1250791.html
+load 1264396-1.html
+load 1264949.html
+load 1265611-1.html
+load 1270795.html
+load 1275026.html
+load 1278463-1.html
+load 1277908-1.html
+load 1277908-2.html
+load 1282076-1.html
+load 1282076-2.html
+load 1290994-1.html
+load 1290994-2.html
+load 1290994-3.html
+load 1290994-4.html
+load 1314531.html
+load 1315889-1.html
+load 1315894-1.html
+pref(widget.windows.window_occlusion_tracking.enabled,false) skip-if(wayland) load 1319072-1.html # Bug 1819154, wayland: bug 1856389
+HTTP load 1320423-1.html
+load 1321357-1.html
+load 1328535-1.html
+load 1331272.html
+load 1332550.html
+HTTP load 1333001-1.html
+load 1340248.html
+skip-if(wayland) load 1340344.html # wayland: bug 1856389
+skip-if(wayland) load 1342316-1.html # wayland: bug 1856389
+load 1344210.html
+load 1353312.html
+load 1356601-1.html
+load 1364139-1.html
+load 1371450-1.html
+load 1374175-1.html
+load 1375812-1.html
+load 1377053-1.html
+load 1377256-1.html
+skip-if(wayland) load 1378064-1.html # wayland: bug 1856389
+load 1378814.html
+load 1380800.html
+load link-transition-before.html
+skip-if(wayland) load 1381420-1.html # wayland: bug 1856389
+load 1381682.html
+load 1382672.html
+load 1382710.html
+pref(dom.animations-api.compositing.enabled,true) load 1383493-1.html
+skip-if(wayland) load 1383001.html # wayland: bug 1856389
+load 1383001-2.html
+load 1383319.html
+skip-if(wayland) load 1383589-1.html # wayland: bug 1856389
+load 1383975.html
+load border-image-visited-link.html
+load content-only-on-link-before.html
+load content-only-on-visited-before.html
+load font-face-truncated-src.html
+load large_border_image_width.html
+load link-transition-before.html
+load long-url-list-stack-overflow.html #Bug 1525117
+skip-if(wayland) load scale-on-block-continuation.html # wayland: bug 1856389
+skip-if(AddressSanitizer) skip-if(ThreadSanitizer) load 1383981.html # Very sensitive to stack size.
+skip-if(AddressSanitizer) skip-if(ThreadSanitizer) load 1383981-2.html
+skip-if(AddressSanitizer) skip-if(ThreadSanitizer) load 1383981-3.html
+load 1384824-1.html
+load 1384824-2.html
+load 1386773.html
+load 1387481-1.html
+load 1387499.html
+load 1388234.html
+load 1391577.html
+load 1393189.html
+load 1393580.html
+load 1389645.html
+load 1390726.html
+load 1393791.html
+load 1384232.html
+load 1395725.html
+load 1396041.html
+pref(dom.animations-api.compositing.enabled,true) load 1397363-1.html
+load 1397439-1.html
+load 1395719.html
+load 1397091.html
+load 1398479.html
+load 1398581.html
+load 1399006.html
+load 1399546.html
+load 1400035.html
+load 1400325.html
+load 1400926.html
+load 1400936-1.html
+load 1400936-2.html
+load 1401256.html
+load 1401706.html
+skip-if(wayland) load 1401801.html # wayland: bug 1856389
+load 1401825.html
+load 1402218-1.html
+load 1402366.html
+load 1402419.html
+load 1402472.html
+load 1403028.html
+load 1403433.html
+load 1403465.html
+load 1403592.html
+load 1403615.html
+load 1403712.html
+load 1404180-1.html
+load 1404316.html
+load 1406222-1.html
+load 1406222-2.html
+load 1404324-1.html
+load 1404324-2.html
+load 1404324-3.html
+load 1404057.html
+load 1405880.html
+load 1409502.html
+load 1409931.html
+load 1410226-1.html
+load 1410226-2.html
+load 1411008.html
+load 1411143.html
+load 1411478.html
+load 1413288.html
+load 1413361.html
+load 1413670.html
+load 1415353.html
+skip-if(wayland) load 1418059.html # wayland: bug 1856389
+skip-if(wayland) load 1418867.html # wayland: bug 1856389
+load 1419554.html
+load 1426312.html
+load 1439793.html
+load 1409183.html
+load 1445682.html
+load 1449243.html
+load 1450691.html
+load 1453206.html
+load 1454140.html
+load 1455108.html
+load 1457288.html
+load 1457985.html
+load 1468640.html
+load 1469076.html
+load 1475003.html
+load 1479681.html
+load 1488817.html
+load 1490012.html
+load 1502893.html
+load 1507674.html
+load 1509989.html
+load 1514086.html
+load 1533783.html
+load 1533891.html
+pref(gfx.omta.background-color,true) load 1533968.html
+load 1545177.html
+skip-if(geckoview) skip-if(Android) load 1546255.html # Bug 1563020 for GV+WR & Bug 1553971
+load 1552911.html
+load 1562361.html
+load 1566684.html
+load 1579788.html
+load 1580307.html
+load 1581579.html
+skip-if(release_or_beta) pref(dom.paintWorklet.enabled,true) load 1593766.html # bug 1581896
+load 1594949.html
+pref(layout.css.individual-transform.enabled,true) load 1594960.html
+load 1586444.html
+load 1599286.html
+load 1609786.html
+load 1616433.html
+load 1616407.html
+load 1639533.html
+pref(layout.accessiblecaret.enabled,true) load 1640040.html
+load 1806189-1.html
+pref(layout.css.scroll-driven-animations.enabled,true) load 1821416.html
+pref(layout.css.individual-transform.enabled,true) pref(dom.animations-api.compositing.enabled,true) load 1872309.html
diff --git a/layout/style/crashtests/font-face-truncated-src.html b/layout/style/crashtests/font-face-truncated-src.html
new file mode 100644
index 0000000000..c3d7dbda5b
--- /dev/null
+++ b/layout/style/crashtests/font-face-truncated-src.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<style>@font-face { src:</style>
diff --git a/layout/style/crashtests/large_border_image_width.html b/layout/style/crashtests/large_border_image_width.html
new file mode 100644
index 0000000000..d80b734654
--- /dev/null
+++ b/layout/style/crashtests/large_border_image_width.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div style="border: 10px solid transparent; border-image-source: linear-gradient(90deg, blue 25px, green 25px); border-image-width: 5464618830153;"></div>
+</body>
+</html>
+
diff --git a/layout/style/crashtests/link-transition-before.html b/layout/style/crashtests/link-transition-before.html
new file mode 100644
index 0000000000..6a8b7e409e
--- /dev/null
+++ b/layout/style/crashtests/link-transition-before.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<style type="text/css">
+a {
+ border-bottom: 1px solid transparent;
+ transition: all 2s linear;
+}
+a.start {
+ border-bottom: 1px solid #000000;
+}
+/* Can be anything, just need to ensure pseudos cascade */
+:before {
+ color: blue;
+}
+</style>
+<a href="http://www.example.com/">example</a>
+<script>
+let a0 = document.querySelectorAll("a")[0];
+a0.classList.add("start");
+setTimeout(() => {
+ a0.classList.remove("start");
+ setTimeout(() => {
+ a0.classList.add("start");
+ document.documentElement.removeAttribute("class");
+ }, 0);
+}, 0);
+</script> \ No newline at end of file
diff --git a/layout/style/crashtests/long-url-list-stack-overflow.html b/layout/style/crashtests/long-url-list-stack-overflow.html
new file mode 100644
index 0000000000..899e858df5
--- /dev/null
+++ b/layout/style/crashtests/long-url-list-stack-overflow.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style id="s"></style>
+<script type="text/javascript">
+
+// Duplicates the string 2^n times
+function exp(s, n)
+{
+ for (var i = 0; i < n; ++i)
+ s += s;
+ return s;
+}
+
+var stylesheet = "@-moz-document url(http://www.w3.org/)" + exp(", url-prefix(file:///)", 20) + " { }";
+document.getElementById("s").textContent = stylesheet;
+
+</script>
+</head>
+<body>
+<div></div>
+</body>
+</html>
diff --git a/layout/style/crashtests/scale-on-block-continuation.html b/layout/style/crashtests/scale-on-block-continuation.html
new file mode 100644
index 0000000000..e8f27e607d
--- /dev/null
+++ b/layout/style/crashtests/scale-on-block-continuation.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset=utf-8>
+<title>Test for applying a scale animation to a block continuation</title>
+<!--
+ Transform animations involve special frame handling since the animations are
+ applied to the primary frame but are stored on the style frame (a distinction
+ that only exists for display:table content).
+
+ This test exists to ensure that in the case of tranform animations on block
+ continuations we don't come unstuck since this is another case where there are
+ multiple frames with transform animations, but only one primary frame.
+
+ We choose to animate scale in this test because there are a number of methods
+ that take special interest in scale animations because of their impact on
+ prerendering. By using a scale animation we check that none of those code
+ paths trip up on block continuations.
+-->
+<style>
+.container {
+ /* This sizing should hopefully mean that the second paragraph is split across
+ two columns. */
+ column-width: 20em;
+ width: 42em;
+}
+#two {
+ animation: scale 1s infinite alternate;
+}
+@keyframes scale {
+ to { transform: scale(1.3); }
+}
+</style>
+<div class=container>
+ <p>Neque et soluta consectetur. Quia quo magnam ipsa modi. Aspernatur necessitatibus consequatur facere voluptates rerum omnis iusto earum. Beatae quisquam odio est. Deleniti distinctio doloribus veniam similique voluptas est aut. Dignissimos dignissimos voluptas illo odit.</p>
+ <p id=two>Laborum quae reprehenderit alias. Incidunt aliquam non sint non eaque et itaque aut. Est quaerat neque explicabo id voluptas reiciendis. Et animi odio eligendi ipsa repudiandae quam iure. Commodi asperiores sapiente dolorem assumenda debitis. Soluta quisquam porro fugiat fugiat sapiente et excepturi rem.</p>
+ <p>Ipsam eligendi neque perspiciatis est aut ea nihil. Eum sit ipsa sunt aut voluptatem optio. Qui quae autem aspernatur. Et perspiciatis alias voluptatem.</p>
+</div>
+<script>
+two.addEventListener('animationstart', () => {
+ document.documentElement.removeAttribute('class');
+});
+</script>
+</html>
diff --git a/layout/style/designmode.css b/layout/style/designmode.css
new file mode 100644
index 0000000000..194586ee1d
--- /dev/null
+++ b/layout/style/designmode.css
@@ -0,0 +1,8 @@
+/* -*- 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/. */
+
+*|* {
+ -moz-user-modify: read-write;
+}
diff --git a/layout/style/extra-bindgen-flags.in b/layout/style/extra-bindgen-flags.in
new file mode 100644
index 0000000000..862f2f7c45
--- /dev/null
+++ b/layout/style/extra-bindgen-flags.in
@@ -0,0 +1 @@
+@BINDGEN_SYSTEM_FLAGS@ @NSPR_CFLAGS@ @MOZ_PIXMAN_CFLAGS@ @MOZ_ICU_CFLAGS@
diff --git a/layout/style/jar.mn b/layout/style/jar.mn
new file mode 100644
index 0000000000..4467a9d79e
--- /dev/null
+++ b/layout/style/jar.mn
@@ -0,0 +1,35 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+ res/ua.css (res/ua.css)
+ res/html.css (res/html.css)
+ res/quirk.css (res/quirk.css)
+ res/counterstyles.css (res/counterstyles.css)
+ res/noframes.css (res/noframes.css)
+ res/scrollbars.css (res/scrollbars.css)
+ res/forms.css (res/forms.css)
+ #ifdef ANDROID
+ res/accessiblecaret-normal.svg (res/accessiblecaret-normal.svg)
+ res/accessiblecaret-tilt-left.svg (res/accessiblecaret-tilt-left.svg)
+ res/accessiblecaret-tilt-right.svg (res/accessiblecaret-tilt-right.svg)
+ #else
+ res/accessiblecaret-normal@1x.png (res/accessiblecaret-normal@1x.png)
+ res/accessiblecaret-normal@1.5x.png (res/accessiblecaret-normal@1.5x.png)
+ res/accessiblecaret-normal@2x.png (res/accessiblecaret-normal@2x.png)
+ res/accessiblecaret-normal@2.25x.png (res/accessiblecaret-normal@2.25x.png)
+ res/accessiblecaret-tilt-left@1x.png (res/accessiblecaret-tilt-left@1x.png)
+ res/accessiblecaret-tilt-left@1.5x.png (res/accessiblecaret-tilt-left@1.5x.png)
+ res/accessiblecaret-tilt-left@2x.png (res/accessiblecaret-tilt-left@2x.png)
+ res/accessiblecaret-tilt-left@2.25x.png (res/accessiblecaret-tilt-left@2.25x.png)
+ res/accessiblecaret-tilt-right@1x.png (res/accessiblecaret-tilt-right@1x.png)
+ res/accessiblecaret-tilt-right@1.5x.png (res/accessiblecaret-tilt-right@1.5x.png)
+ res/accessiblecaret-tilt-right@2x.png (res/accessiblecaret-tilt-right@2x.png)
+ res/accessiblecaret-tilt-right@2.25x.png (res/accessiblecaret-tilt-right@2.25x.png)
+ #endif
+ res/password.svg (res/password.svg)
+ res/password-hide.svg (res/password-hide.svg)
+
+% resource gre-resources %res/
+% resource content-accessible resource://gre/contentaccessible/ contentaccessible=yes
diff --git a/layout/style/moz.build b/layout/style/moz.build
new file mode 100644
index 0000000000..a14ab6a7ac
--- /dev/null
+++ b/layout/style/moz.build
@@ -0,0 +1,353 @@
+# -*- 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("**"):
+ BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
+
+with Files("nsComputedDOMStyle.*"):
+ BUG_COMPONENT = ("Core", "DOM: CSS Object Model")
+
+with Files("nsROCSSPrimitiveValue.*"):
+ BUG_COMPONENT = ("Core", "DOM: CSS Object Model")
+
+with Files("CSSRuleList.*"):
+ BUG_COMPONENT = ("Core", "DOM: CSS Object Model")
+
+with Files("nsDOM*"):
+ BUG_COMPONENT = ("Core", "DOM: CSS Object Model")
+
+with Files("AnimationCollection.*"):
+ BUG_COMPONENT = ("Core", "CSS Transitions and Animations")
+
+with Files("AnimatedPropertyID*"):
+ BUG_COMPONENT = ("Core", "CSS Transitions and Animations")
+
+with Files("AnimationCommon.*"):
+ BUG_COMPONENT = ("Core", "CSS Transitions and Animations")
+
+with Files("nsAnimationManager.*"):
+ BUG_COMPONENT = ("Core", "CSS Transitions and Animations")
+
+with Files("nsTransitionManager.*"):
+ BUG_COMPONENT = ("Core", "CSS Transitions and Animations")
+
+with Files("StyleAnimationValue.*"):
+ BUG_COMPONENT = ("Core", "CSS Transitions and Animations")
+
+TEST_DIRS += ["test"]
+
+EXPORTS += [
+ "!nsCSSPropertyID.h",
+ "AnimationCommon.h",
+ "CounterStyleManager.h",
+ "nsAnimationManager.h",
+ "nsComputedDOMStyle.h",
+ "nsCSSAnonBoxes.h",
+ "nsCSSAnonBoxList.h",
+ "nsCSSCounterDescList.h",
+ "nsCSSFontDescList.h",
+ "nsCSSPropertyIDSet.h",
+ "nsCSSProps.h",
+ "nsCSSPseudoElementList.h",
+ "nsCSSPseudoElements.h",
+ "nsCSSValue.h",
+ "nsDOMCSSAttrDeclaration.h",
+ "nsDOMCSSDeclaration.h",
+ "nsICSSDeclaration.h",
+ "nsICSSLoaderObserver.h",
+ "nsStyleAutoArray.h",
+ "nsStyleConsts.h",
+ "nsStyleStruct.h",
+ "nsStyleStructFwd.h",
+ "nsStyleStructInlines.h",
+ "nsStyleStructList.h",
+ "nsStyleTransformMatrix.h",
+ "nsStyleUtil.h",
+]
+
+EXPORTS.mozilla += [
+ "!ServoCSSPropList.h",
+ "AnimatedPropertyID.h",
+ "AnimatedPropertyIDSet.h",
+ "AnimationCollection.h",
+ "AttributeStyles.h",
+ "CachedInheritingStyles.h",
+ "ComputedStyle.h",
+ "ComputedStyleInlines.h",
+ "CSSEnabledState.h",
+ "CSSPropFlags.h",
+ "DeclarationBlock.h",
+ "DocumentStyleRootIterator.h",
+ "FontLoaderUtils.h",
+ "FontPreloader.h",
+ "GeckoBindings.h",
+ "GlobalStyleSheetCache.h",
+ "ImportScanner.h",
+ "LayerAnimationInfo.h",
+ "MappedDeclarationsBuilder.h",
+ "MediaFeatureChange.h",
+ "PostTraversalTask.h",
+ "PreferenceSheet.h",
+ "PreloadedStyleSheet.h",
+ "PseudoStyleType.h",
+ "RustCell.h",
+ "ServoBindings.h",
+ "ServoBindingTypes.h",
+ "ServoBoxedTypeList.h",
+ "ServoComputedData.h",
+ "ServoCSSParser.h",
+ "ServoCSSRuleList.h",
+ "ServoElementSnapshot.h",
+ "ServoElementSnapshotTable.h",
+ "ServoLockedArcTypeList.h",
+ "ServoStyleConstsForwards.h",
+ "ServoStyleConstsInlines.h",
+ "ServoStyleSet.h",
+ "ServoStyleSetInlines.h",
+ "ServoTraversalStatistics.h",
+ "ServoTypes.h",
+ "ServoUtils.h",
+ "ShadowParts.h",
+ "SharedStyleSheetCache.h",
+ "SharedSubResourceCache.h",
+ "StyleAnimationValue.h",
+ "StyleColorInlines.h",
+ "StyleSheet.h",
+ "StyleSheetInfo.h",
+ "StyleSheetInlines.h",
+ "TimelineCollection.h",
+ "TimelineManager.h",
+ "URLExtraData.h",
+ "UserAgentStyleSheetID.h",
+ "UserAgentStyleSheetList.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "CSS.h",
+ "CSSContainerRule.h",
+ "CSSCounterStyleRule.h",
+ "CSSFontFaceRule.h",
+ "CSSFontFeatureValuesRule.h",
+ "CSSFontPaletteValuesRule.h",
+ "CSSImportRule.h",
+ "CSSKeyframeRule.h",
+ "CSSKeyframesRule.h",
+ "CSSLayerBlockRule.h",
+ "CSSLayerStatementRule.h",
+ "CSSMediaRule.h",
+ "CSSMozDocumentRule.h",
+ "CSSNamespaceRule.h",
+ "CSSPageRule.h",
+ "CSSPropertyRule.h",
+ "CSSRuleList.h",
+ "CSSStyleRule.h",
+ "CSSSupportsRule.h",
+ "CSSValue.h",
+ "FontFace.h",
+ "FontFaceImpl.h",
+ "FontFaceSet.h",
+ "FontFaceSetDocumentImpl.h",
+ "FontFaceSetImpl.h",
+ "FontFaceSetIterator.h",
+ "FontFaceSetWorkerImpl.h",
+ "MediaList.h",
+ "MediaQueryList.h",
+ "PaintWorkletGlobalScope.h",
+]
+
+EXPORTS.mozilla.css += [
+ "DocumentMatchingFunction.h",
+ "ErrorReporter.h",
+ "GroupRule.h",
+ "ImageLoader.h",
+ "Loader.h",
+ "Rule.h",
+ "SheetLoadData.h",
+ "SheetParsingMode.h",
+ "StreamLoader.h",
+ "StylePreloadKind.h",
+]
+
+UNIFIED_SOURCES += [
+ "AnimationCollection.cpp",
+ "AttributeStyles.cpp",
+ "CachedInheritingStyles.cpp",
+ "ComputedStyle.cpp",
+ "CounterStyleManager.cpp",
+ "CSS.cpp",
+ "CSSContainerRule.cpp",
+ "CSSCounterStyleRule.cpp",
+ "CSSFontFaceRule.cpp",
+ "CSSFontFeatureValuesRule.cpp",
+ "CSSFontPaletteValuesRule.cpp",
+ "CSSImportRule.cpp",
+ "CSSKeyframeRule.cpp",
+ "CSSKeyframesRule.cpp",
+ "CSSLayerBlockRule.cpp",
+ "CSSLayerStatementRule.cpp",
+ "CSSMediaRule.cpp",
+ "CSSMozDocumentRule.cpp",
+ "CSSNamespaceRule.cpp",
+ "CSSPageRule.cpp",
+ "CSSPropertyRule.cpp",
+ "CSSRuleList.cpp",
+ "CSSStyleRule.cpp",
+ "CSSSupportsRule.cpp",
+ "DeclarationBlock.cpp",
+ "DocumentStyleRootIterator.cpp",
+ "ErrorReporter.cpp",
+ "FontFace.cpp",
+ "FontFaceImpl.cpp",
+ "FontFaceSet.cpp",
+ "FontFaceSetDocumentImpl.cpp",
+ "FontFaceSetImpl.cpp",
+ "FontFaceSetIterator.cpp",
+ "FontFaceSetWorkerImpl.cpp",
+ "FontLoaderUtils.cpp",
+ "FontPreloader.cpp",
+ "GeckoBindings.cpp",
+ "GlobalStyleSheetCache.cpp",
+ "GroupRule.cpp",
+ "ImageLoader.cpp",
+ "ImportScanner.cpp",
+ "LayerAnimationInfo.cpp",
+ "Loader.cpp",
+ "MappedDeclarationsBuilder.cpp",
+ "MediaList.cpp",
+ "MediaQueryList.cpp",
+ "nsAnimationManager.cpp",
+ "nsComputedDOMStyle.cpp",
+ "nsCSSAnonBoxes.cpp",
+ "nsCSSProps.cpp",
+ "nsCSSPseudoElements.cpp",
+ "nsCSSValue.cpp",
+ "nsDOMCSSAttrDeclaration.cpp",
+ "nsDOMCSSDeclaration.cpp",
+ "nsDOMCSSValueList.cpp",
+ "nsFontFaceLoader.cpp",
+ "nsFontFaceUtils.cpp",
+ "nsICSSDeclaration.cpp",
+ "nsMediaFeatures.cpp",
+ "nsROCSSPrimitiveValue.cpp",
+ "nsStyleStruct.cpp",
+ "nsStyleTransformMatrix.cpp",
+ "nsStyleUtil.cpp",
+ "nsTransitionManager.cpp",
+ "PaintWorkletGlobalScope.cpp",
+ "PaintWorkletImpl.cpp",
+ "PostTraversalTask.cpp",
+ "PreferenceSheet.cpp",
+ "PreloadedStyleSheet.cpp",
+ "PseudoStyleType.cpp",
+ "Rule.cpp",
+ "ServoCSSParser.cpp",
+ "ServoCSSRuleList.cpp",
+ "ServoElementSnapshot.cpp",
+ "ServoStyleSet.cpp",
+ "ShadowParts.cpp",
+ "SharedStyleSheetCache.cpp",
+ "StreamLoader.cpp",
+ "StyleAnimationValue.cpp",
+ "StyleColor.cpp",
+ "StyleSheet.cpp",
+ "TimelineCollection.cpp",
+ "TimelineManager.cpp",
+ "URLExtraData.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "../base",
+ "../generic",
+ "../xul",
+ "/dom/base",
+ "/dom/html",
+ "/dom/xul",
+ "/image",
+]
+
+JAR_MANIFESTS += ["jar.mn"]
+
+RESOURCE_FILES += [
+ "contenteditable.css",
+ "designmode.css",
+]
+
+CONTENT_ACCESSIBLE_FILES += [
+ "ImageDocument.css",
+ "res/accessiblecaret.css",
+ "res/details.css",
+ "res/plaintext.css",
+ "res/searchfield-cancel.svg",
+ "res/viewsource.css",
+ "TopLevelImageDocument.css",
+ "TopLevelVideoDocument.css",
+]
+
+
+GeneratedFile(
+ "nsCSSPropertyID.h",
+ script="GenerateCSSPropertyID.py",
+ entry_point="generate",
+ inputs=["nsCSSPropertyID.h.in", "!ServoCSSPropList.py"],
+)
+GeneratedFile(
+ "ServoCSSPropList.h",
+ script="GenerateServoCSSPropList.py",
+ entry_point="generate_header",
+ inputs=["!ServoCSSPropList.py"],
+)
+GeneratedFile(
+ "ServoCSSPropList.py",
+ script="GenerateServoCSSPropList.py",
+ entry_point="generate_data",
+ inputs=["ServoCSSPropList.mako.py"],
+)
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ EXPORTS.mozilla += [
+ "!CompositorAnimatableProperties.h",
+ "!CountedUnknownProperties.h",
+ "!ServoStyleConsts.h",
+ ]
+
+ GeneratedFile(
+ "CompositorAnimatableProperties.h",
+ script="GenerateCompositorAnimatableProperties.py",
+ entry_point="generate",
+ inputs=["!ServoCSSPropList.py"],
+ )
+ GeneratedFile(
+ "CountedUnknownProperties.h",
+ script="GenerateCountedUnknownProperties.py",
+ entry_point="generate",
+ inputs=[
+ "/servo/components/style/properties/counted_unknown_properties.py",
+ ],
+ )
+ GeneratedFile(
+ "nsComputedDOMStyleGenerated.inc",
+ script="GenerateComputedDOMStyleGenerated.py",
+ entry_point="generate",
+ inputs=["!ServoCSSPropList.py"],
+ )
+ GeneratedFile(
+ "nsCSSPropsGenerated.inc",
+ script="GenerateCSSPropsGenerated.py",
+ entry_point="generate",
+ inputs=["!ServoCSSPropList.py"],
+ )
+ CbindgenHeader(
+ "ServoStyleConsts.h",
+ inputs=["/servo/ports/geckolib", "/servo/components/style"],
+ )
+
+ CONFIGURE_SUBST_FILES += [
+ "extra-bindgen-flags",
+ ]
diff --git a/layout/style/nsAnimationManager.cpp b/layout/style/nsAnimationManager.cpp
new file mode 100644
index 0000000000..978755cae2
--- /dev/null
+++ b/layout/style/nsAnimationManager.cpp
@@ -0,0 +1,467 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsAnimationManager.h"
+#include "nsINode.h"
+#include "nsTransitionManager.h"
+
+#include "mozilla/AnimationEventDispatcher.h"
+#include "mozilla/AnimationUtils.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/ElementAnimationData.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/TimelineCollection.h"
+#include "mozilla/dom/AnimationEffect.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/dom/MutationObservers.h"
+#include "mozilla/dom/ScrollTimeline.h"
+#include "mozilla/dom/ViewTimeline.h"
+
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+#include "nsStyleChangeList.h"
+#include "nsLayoutUtils.h"
+#include "nsIFrame.h"
+#include "mozilla/dom/Document.h"
+#include "nsDOMMutationObserver.h"
+#include "nsRFPService.h"
+#include <algorithm> // std::stable_sort
+#include <math.h>
+
+using namespace mozilla;
+using namespace mozilla::css;
+using mozilla::dom::Animation;
+using mozilla::dom::AnimationPlayState;
+using mozilla::dom::CSSAnimation;
+using mozilla::dom::Element;
+using mozilla::dom::KeyframeEffect;
+using mozilla::dom::MutationObservers;
+using mozilla::dom::ScrollTimeline;
+using mozilla::dom::ViewTimeline;
+
+////////////////////////// nsAnimationManager ////////////////////////////
+
+// Find the matching animation by |aName| in the old list
+// of animations and remove the matched animation from the list.
+static already_AddRefed<CSSAnimation> PopExistingAnimation(
+ const nsAtom* aName,
+ nsAnimationManager::CSSAnimationCollection* aCollection) {
+ if (!aCollection) {
+ return nullptr;
+ }
+
+ // Animations are stored in reverse order to how they appear in the
+ // animation-name property. However, we want to match animations beginning
+ // from the end of the animation-name list, so we iterate *forwards*
+ // through the collection.
+ for (size_t idx = 0, length = aCollection->mAnimations.Length();
+ idx != length; ++idx) {
+ CSSAnimation* cssAnim = aCollection->mAnimations[idx];
+ if (cssAnim->AnimationName() == aName) {
+ RefPtr<CSSAnimation> match = cssAnim;
+ aCollection->mAnimations.RemoveElementAt(idx);
+ return match.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+class MOZ_STACK_CLASS ServoCSSAnimationBuilder final {
+ public:
+ explicit ServoCSSAnimationBuilder(const ComputedStyle* aComputedStyle)
+ : mComputedStyle(aComputedStyle) {
+ MOZ_ASSERT(aComputedStyle);
+ }
+
+ bool BuildKeyframes(const Element& aElement, nsPresContext* aPresContext,
+ nsAtom* aName,
+ const StyleComputedTimingFunction& aTimingFunction,
+ nsTArray<Keyframe>& aKeyframes) {
+ return aPresContext->StyleSet()->GetKeyframesForName(
+ aElement, *mComputedStyle, aName, aTimingFunction, aKeyframes);
+ }
+ void SetKeyframes(KeyframeEffect& aEffect, nsTArray<Keyframe>&& aKeyframes,
+ const dom::AnimationTimeline* aTimeline) {
+ aEffect.SetKeyframes(std::move(aKeyframes), mComputedStyle, aTimeline);
+ }
+
+ // Currently all the animation building code in this file is based on
+ // assumption that creating and removing animations should *not* trigger
+ // additional restyles since those changes will be handled within the same
+ // restyle.
+ //
+ // While that is true for the Gecko style backend, it is not true for the
+ // Servo style backend where we want restyles to be triggered so that we
+ // perform a second animation restyle where we will incorporate the changes
+ // arising from creating and removing animations.
+ //
+ // Fortunately, our attempts to avoid posting extra restyles as part of the
+ // processing here are imperfect and most of the time we happen to post
+ // them anyway. Occasionally, however, we don't. For example, we don't post
+ // a restyle when we create a new animation whose an animation index matches
+ // the default value it was given already (which is typically only true when
+ // the CSSAnimation we create is the first Animation created in a particular
+ // content process).
+ //
+ // As a result, when we are using the Servo backend, whenever we have an added
+ // or removed animation we need to explicitly trigger a restyle.
+ //
+ // This code should eventually disappear along with the Gecko style backend
+ // and we should simply call Play() / Pause() / Cancel() etc. which will
+ // post the required restyles.
+ void NotifyNewOrRemovedAnimation(const Animation& aAnimation) {
+ dom::AnimationEffect* effect = aAnimation.GetEffect();
+ if (!effect) {
+ return;
+ }
+
+ KeyframeEffect* keyframeEffect = effect->AsKeyframeEffect();
+ if (!keyframeEffect) {
+ return;
+ }
+
+ keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Standard);
+ }
+
+ private:
+ const ComputedStyle* mComputedStyle;
+};
+
+static void UpdateOldAnimationPropertiesWithNew(
+ CSSAnimation& aOld, TimingParams&& aNewTiming,
+ nsTArray<Keyframe>&& aNewKeyframes, bool aNewIsStylePaused,
+ CSSAnimationProperties aOverriddenProperties,
+ ServoCSSAnimationBuilder& aBuilder, dom::AnimationTimeline* aTimeline,
+ dom::CompositeOperation aNewComposite) {
+ bool animationChanged = false;
+
+ // Update the old from the new so we can keep the original object
+ // identity (and any expando properties attached to it).
+ if (aOld.GetEffect()) {
+ dom::AnimationEffect* oldEffect = aOld.GetEffect();
+
+ // Copy across the changes that are not overridden
+ TimingParams updatedTiming = oldEffect->SpecifiedTiming();
+ if (~aOverriddenProperties & CSSAnimationProperties::Duration) {
+ updatedTiming.SetDuration(aNewTiming.Duration());
+ }
+ if (~aOverriddenProperties & CSSAnimationProperties::IterationCount) {
+ updatedTiming.SetIterations(aNewTiming.Iterations());
+ }
+ if (~aOverriddenProperties & CSSAnimationProperties::Direction) {
+ updatedTiming.SetDirection(aNewTiming.Direction());
+ }
+ if (~aOverriddenProperties & CSSAnimationProperties::Delay) {
+ updatedTiming.SetDelay(aNewTiming.Delay());
+ }
+ if (~aOverriddenProperties & CSSAnimationProperties::FillMode) {
+ updatedTiming.SetFill(aNewTiming.Fill());
+ }
+
+ animationChanged = oldEffect->SpecifiedTiming() != updatedTiming;
+ oldEffect->SetSpecifiedTiming(std::move(updatedTiming));
+
+ if (KeyframeEffect* oldKeyframeEffect = oldEffect->AsKeyframeEffect()) {
+ if (~aOverriddenProperties & CSSAnimationProperties::Keyframes) {
+ aBuilder.SetKeyframes(*oldKeyframeEffect, std::move(aNewKeyframes),
+ aTimeline);
+ }
+
+ if (~aOverriddenProperties & CSSAnimationProperties::Composition) {
+ animationChanged = oldKeyframeEffect->Composite() != aNewComposite;
+ oldKeyframeEffect->SetCompositeFromStyle(aNewComposite);
+ }
+ }
+ }
+
+ // Checking pointers should be enough. If both are scroll-timeline, we reuse
+ // the scroll-timeline object if their scrollers and axes are the same.
+ if (aOld.GetTimeline() != aTimeline) {
+ aOld.SetTimeline(aTimeline);
+ animationChanged = true;
+ }
+
+ // Handle changes in play state. If the animation is idle, however,
+ // changes to animation-play-state should *not* restart it.
+ if (aOld.PlayState() != AnimationPlayState::Idle &&
+ ~aOverriddenProperties & CSSAnimationProperties::PlayState) {
+ bool wasPaused = aOld.PlayState() == AnimationPlayState::Paused;
+ if (!wasPaused && aNewIsStylePaused) {
+ aOld.PauseFromStyle();
+ animationChanged = true;
+ } else if (wasPaused && !aNewIsStylePaused) {
+ aOld.PlayFromStyle();
+ animationChanged = true;
+ }
+ }
+
+ // Updating the effect timing above might already have caused the
+ // animation to become irrelevant so only add a changed record if
+ // the animation is still relevant.
+ if (animationChanged && aOld.IsRelevant()) {
+ MutationObservers::NotifyAnimationChanged(&aOld);
+ }
+}
+
+static already_AddRefed<dom::AnimationTimeline> GetNamedProgressTimeline(
+ dom::Document* aDocument, const NonOwningAnimationTarget& aTarget,
+ nsAtom* aName) {
+ // A named progress timeline is referenceable in animation-timeline by:
+ // 1. the declaring element itself
+ // 2. that element’s descendants
+ // 3. that element’s following siblings and their descendants
+ // https://drafts.csswg.org/scroll-animations-1/#timeline-scope
+ // FIXME: Bug 1823500. Reduce default scoping to ancestors only.
+ for (Element* curr = AnimationUtils::GetElementForRestyle(
+ aTarget.mElement, aTarget.mPseudoType);
+ curr; curr = curr->GetParentElement()) {
+ // If multiple elements have declared the same timeline name, the matching
+ // timeline is the one declared on the nearest element in tree order, which
+ // considers siblings closer than parents.
+ // Note: This is fine for parallel traversal because we update animations by
+ // SequentialTask.
+ for (Element* e = curr; e; e = e->GetPreviousElementSibling()) {
+ // In case of a name conflict on the same element, scroll progress
+ // timelines take precedence over view progress timelines.
+ const auto [element, pseudoType] =
+ AnimationUtils::GetElementPseudoPair(e);
+ if (auto* collection =
+ TimelineCollection<ScrollTimeline>::Get(element, pseudoType)) {
+ if (RefPtr<ScrollTimeline> timeline = collection->Lookup(aName)) {
+ return timeline.forget();
+ }
+ }
+
+ if (auto* collection =
+ TimelineCollection<ViewTimeline>::Get(element, pseudoType)) {
+ if (RefPtr<ViewTimeline> timeline = collection->Lookup(aName)) {
+ return timeline.forget();
+ }
+ }
+ }
+ }
+
+ // If we cannot find a matched scroll-timeline-name, this animation is not
+ // associated with a timeline.
+ // https://drafts.csswg.org/css-animations-2/#valdef-animation-timeline-custom-ident
+ return nullptr;
+}
+
+static already_AddRefed<dom::AnimationTimeline> GetTimeline(
+ const StyleAnimationTimeline& aStyleTimeline, nsPresContext* aPresContext,
+ const NonOwningAnimationTarget& aTarget) {
+ switch (aStyleTimeline.tag) {
+ case StyleAnimationTimeline::Tag::Timeline: {
+ // Check scroll-timeline-name property or view-timeline-property.
+ nsAtom* name = aStyleTimeline.AsTimeline().AsAtom();
+ return name != nsGkAtoms::_empty
+ ? GetNamedProgressTimeline(aPresContext->Document(), aTarget,
+ name)
+ : nullptr;
+ }
+ case StyleAnimationTimeline::Tag::Scroll: {
+ const auto& scroll = aStyleTimeline.AsScroll();
+ return ScrollTimeline::MakeAnonymous(aPresContext->Document(), aTarget,
+ scroll.axis, scroll.scroller);
+ }
+ case StyleAnimationTimeline::Tag::View: {
+ const auto& view = aStyleTimeline.AsView();
+ return ViewTimeline::MakeAnonymous(aPresContext->Document(), aTarget,
+ view.axis, view.inset);
+ }
+ case StyleAnimationTimeline::Tag::Auto:
+ return do_AddRef(aTarget.mElement->OwnerDoc()->Timeline());
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown animation-timeline value?");
+ return nullptr;
+}
+
+// Returns a new animation set up with given StyleAnimation.
+// Or returns an existing animation matching StyleAnimation's name updated
+// with the new StyleAnimation.
+static already_AddRefed<CSSAnimation> BuildAnimation(
+ nsPresContext* aPresContext, const NonOwningAnimationTarget& aTarget,
+ const nsStyleUIReset& aStyle, uint32_t animIdx,
+ ServoCSSAnimationBuilder& aBuilder,
+ nsAnimationManager::CSSAnimationCollection* aCollection) {
+ MOZ_ASSERT(aPresContext);
+
+ nsAtom* animationName = aStyle.GetAnimationName(animIdx);
+ nsTArray<Keyframe> keyframes;
+ if (!aBuilder.BuildKeyframes(*aTarget.mElement, aPresContext, animationName,
+ aStyle.GetAnimationTimingFunction(animIdx),
+ keyframes)) {
+ return nullptr;
+ }
+
+ TimingParams timing = TimingParamsFromCSSParams(
+ aStyle.GetAnimationDuration(animIdx).ToMilliseconds(),
+ aStyle.GetAnimationDelay(animIdx).ToMilliseconds(),
+ aStyle.GetAnimationIterationCount(animIdx),
+ aStyle.GetAnimationDirection(animIdx),
+ aStyle.GetAnimationFillMode(animIdx));
+
+ bool isStylePaused =
+ aStyle.GetAnimationPlayState(animIdx) == StyleAnimationPlayState::Paused;
+
+ RefPtr<dom::AnimationTimeline> timeline =
+ GetTimeline(aStyle.GetTimeline(animIdx), aPresContext, aTarget);
+
+ // Find the matching animation with animation name in the old list
+ // of animations and remove the matched animation from the list.
+ RefPtr<CSSAnimation> oldAnim =
+ PopExistingAnimation(animationName, aCollection);
+
+ const auto composition = StyleToDom(aStyle.GetAnimationComposition(animIdx));
+ if (oldAnim) {
+ // Copy over the start times and (if still paused) pause starts
+ // for each animation (matching on name only) that was also in the
+ // old list of animations.
+ // This means that we honor dynamic changes, which isn't what the
+ // spec says to do, but WebKit seems to honor at least some of
+ // them. See
+ // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
+ // In order to honor what the spec said, we'd copy more data over.
+ UpdateOldAnimationPropertiesWithNew(
+ *oldAnim, std::move(timing), std::move(keyframes), isStylePaused,
+ oldAnim->GetOverriddenProperties(), aBuilder, timeline, composition);
+ return oldAnim.forget();
+ }
+
+ KeyframeEffectParams effectOptions(composition);
+ RefPtr<KeyframeEffect> effect = new dom::CSSAnimationKeyframeEffect(
+ aPresContext->Document(),
+ OwningAnimationTarget(aTarget.mElement, aTarget.mPseudoType),
+ std::move(timing), effectOptions);
+
+ aBuilder.SetKeyframes(*effect, std::move(keyframes), timeline);
+
+ RefPtr<CSSAnimation> animation = new CSSAnimation(
+ aPresContext->Document()->GetScopeObject(), animationName);
+ animation->SetOwningElement(
+ OwningElementRef(*aTarget.mElement, aTarget.mPseudoType));
+
+ animation->SetTimelineNoUpdate(timeline);
+ animation->SetEffectNoUpdate(effect);
+
+ if (isStylePaused) {
+ animation->PauseFromStyle();
+ } else {
+ animation->PlayFromStyle();
+ }
+
+ aBuilder.NotifyNewOrRemovedAnimation(*animation);
+
+ return animation.forget();
+}
+
+static nsAnimationManager::OwningCSSAnimationPtrArray BuildAnimations(
+ nsPresContext* aPresContext, const NonOwningAnimationTarget& aTarget,
+ const nsStyleUIReset& aStyle, ServoCSSAnimationBuilder& aBuilder,
+ nsAnimationManager::CSSAnimationCollection* aCollection,
+ nsTHashSet<RefPtr<nsAtom>>& aReferencedAnimations) {
+ nsAnimationManager::OwningCSSAnimationPtrArray result;
+
+ for (size_t animIdx = aStyle.mAnimationNameCount; animIdx-- != 0;) {
+ nsAtom* name = aStyle.GetAnimationName(animIdx);
+ // CSS Animations whose animation-name does not match a @keyframes rule do
+ // not generate animation events. This includes when the animation-name is
+ // "none" which is represented by an empty name in the StyleAnimation.
+ // Since such animations neither affect style nor dispatch events, we do
+ // not generate a corresponding CSSAnimation for them.
+ if (name == nsGkAtoms::_empty) {
+ continue;
+ }
+
+ aReferencedAnimations.Insert(name);
+ RefPtr<CSSAnimation> dest = BuildAnimation(aPresContext, aTarget, aStyle,
+ animIdx, aBuilder, aCollection);
+ if (!dest) {
+ continue;
+ }
+
+ dest->SetAnimationIndex(static_cast<uint64_t>(animIdx));
+ result.AppendElement(dest);
+ }
+ return result;
+}
+
+void nsAnimationManager::UpdateAnimations(dom::Element* aElement,
+ PseudoStyleType aPseudoType,
+ const ComputedStyle* aComputedStyle) {
+ MOZ_ASSERT(mPresContext->IsDynamic(),
+ "Should not update animations for print or print preview");
+ MOZ_ASSERT(aElement->IsInComposedDoc(),
+ "Should not update animations that are not attached to the "
+ "document tree");
+
+ if (!aComputedStyle ||
+ aComputedStyle->StyleDisplay()->mDisplay == StyleDisplay::None) {
+ // If we are in a display:none subtree we will have no computed values.
+ // However, if we are on the root of display:none subtree, the computed
+ // values might not have been cleared yet.
+ // In either case, since CSS animations should not run in display:none
+ // subtrees we should stop (actually, destroy) any animations on this
+ // element here.
+ StopAnimationsForElement(aElement, aPseudoType);
+ return;
+ }
+
+ NonOwningAnimationTarget target(aElement, aPseudoType);
+ ServoCSSAnimationBuilder builder(aComputedStyle);
+
+ DoUpdateAnimations(target, *aComputedStyle->StyleUIReset(), builder);
+}
+
+void nsAnimationManager::DoUpdateAnimations(
+ const NonOwningAnimationTarget& aTarget, const nsStyleUIReset& aStyle,
+ ServoCSSAnimationBuilder& aBuilder) {
+ // Everything that causes our animation data to change triggers a
+ // style change, which in turn triggers a non-animation restyle.
+ // Likewise, when we initially construct frames, we're not in a
+ // style change, but also not in an animation restyle.
+
+ auto* collection =
+ CSSAnimationCollection::Get(aTarget.mElement, aTarget.mPseudoType);
+ if (!collection && aStyle.mAnimationNameCount == 1 &&
+ aStyle.mAnimations[0].GetName() == nsGkAtoms::_empty) {
+ return;
+ }
+
+ nsAutoAnimationMutationBatch mb(aTarget.mElement->OwnerDoc());
+
+ // Build the updated animations list, extracting matching animations from
+ // the existing collection as we go.
+ OwningCSSAnimationPtrArray newAnimations =
+ BuildAnimations(mPresContext, aTarget, aStyle, aBuilder, collection,
+ mMaybeReferencedAnimations);
+
+ if (newAnimations.IsEmpty()) {
+ if (collection) {
+ collection->Destroy();
+ }
+ return;
+ }
+
+ if (!collection) {
+ collection =
+ &aTarget.mElement->EnsureAnimationData().EnsureAnimationCollection(
+ *aTarget.mElement, aTarget.mPseudoType);
+ if (!collection->isInList()) {
+ AddElementCollection(collection);
+ }
+ }
+ collection->mAnimations.SwapElements(newAnimations);
+
+ // Cancel removed animations
+ for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0;) {
+ aBuilder.NotifyNewOrRemovedAnimation(*newAnimations[newAnimIdx]);
+ newAnimations[newAnimIdx]->CancelFromStyle(PostRestyleMode::IfNeeded);
+ }
+}
diff --git a/layout/style/nsAnimationManager.h b/layout/style/nsAnimationManager.h
new file mode 100644
index 0000000000..f37fa0a7d1
--- /dev/null
+++ b/layout/style/nsAnimationManager.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsAnimationManager_h_
+#define nsAnimationManager_h_
+
+#include "mozilla/Attributes.h"
+#include "AnimationCommon.h"
+#include "mozilla/dom/CSSAnimation.h"
+#include "mozilla/Keyframe.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsISupportsImpl.h"
+#include "nsTHashSet.h"
+
+class ServoCSSAnimationBuilder;
+
+struct nsStyleUIReset;
+
+namespace mozilla {
+class ComputedStyle;
+
+enum class PseudoStyleType : uint8_t;
+struct NonOwningAnimationTarget;
+
+} /* namespace mozilla */
+
+class nsAnimationManager final
+ : public mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation> {
+ public:
+ explicit nsAnimationManager(nsPresContext* aPresContext)
+ : mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation>(
+ aPresContext) {}
+
+ typedef mozilla::AnimationCollection<mozilla::dom::CSSAnimation>
+ CSSAnimationCollection;
+ typedef nsTArray<RefPtr<mozilla::dom::CSSAnimation>>
+ OwningCSSAnimationPtrArray;
+
+ ~nsAnimationManager() override = default;
+
+ /**
+ * This function does the same thing as the above UpdateAnimations()
+ * but with servo's computed values.
+ */
+ void UpdateAnimations(mozilla::dom::Element* aElement,
+ mozilla::PseudoStyleType aPseudoType,
+ const mozilla::ComputedStyle* aComputedValues);
+
+ // Utility function to walk through |aIter| to find the Keyframe with
+ // matching offset and timing function but stopping as soon as the offset
+ // differs from |aOffset| (i.e. it assumes a sorted iterator).
+ //
+ // If a matching Keyframe is found,
+ // Returns true and sets |aIndex| to the index of the matching Keyframe
+ // within |aIter|.
+ //
+ // If no matching Keyframe is found,
+ // Returns false and sets |aIndex| to the index in the iterator of the
+ // first Keyframe with an offset differing to |aOffset| or, if the end
+ // of the iterator is reached, sets |aIndex| to the index after the last
+ // Keyframe.
+ template <class IterType>
+ static bool FindMatchingKeyframe(
+ IterType&& aIter, double aOffset,
+ const mozilla::StyleComputedTimingFunction& aTimingFunctionToMatch,
+ mozilla::dom::CompositeOperationOrAuto aCompositionToMatch,
+ size_t& aIndex) {
+ aIndex = 0;
+ for (mozilla::Keyframe& keyframe : aIter) {
+ if (keyframe.mOffset.value() != aOffset) {
+ break;
+ }
+ const bool matches = [&] {
+ if (keyframe.mComposite != aCompositionToMatch) {
+ return false;
+ }
+ return keyframe.mTimingFunction
+ ? *keyframe.mTimingFunction == aTimingFunctionToMatch
+ : aTimingFunctionToMatch.IsLinearKeyword();
+ }();
+ if (matches) {
+ return true;
+ }
+ ++aIndex;
+ }
+ return false;
+ }
+
+ bool AnimationMayBeReferenced(nsAtom* aName) const {
+ return mMaybeReferencedAnimations.Contains(aName);
+ }
+
+ private:
+ // This includes all animation names referenced regardless of whether a
+ // corresponding `@keyframes` rule is available.
+ //
+ // It may contain names which are no longer referenced, but it should always
+ // contain names which are currently referenced, so that it is usable for
+ // style invalidation.
+ nsTHashSet<RefPtr<nsAtom>> mMaybeReferencedAnimations;
+
+ void DoUpdateAnimations(const mozilla::NonOwningAnimationTarget& aTarget,
+ const nsStyleUIReset& aStyle,
+ ServoCSSAnimationBuilder& aBuilder);
+};
+
+#endif /* !defined(nsAnimationManager_h_) */
diff --git a/layout/style/nsCSSAnonBoxList.h b/layout/style/nsCSSAnonBoxList.h
new file mode 100644
index 0000000000..891b34814b
--- /dev/null
+++ b/layout/style/nsCSSAnonBoxList.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* atom list for CSS anonymous boxes */
+
+/*
+ * This file contains the list of nsAtoms and their values for CSS
+ * pseudo-element-ish things used internally for anonymous boxes. It is
+ * designed to be used as inline input to nsCSSAnonBoxes.cpp *only* through the
+ * magic of C preprocessing. All entries must be enclosed in the macros
+ * CSS_ANON_BOX, CSS_WRAPPER_ANON_BOX, or CSS_NON_INHERITING_ANON_BOX which will
+ * have cruel and unusual things done to it. The entries should be kept in some
+ * sort of logical order.
+ *
+ * The first argument to
+ * CSS_ANON_BOX/CSS_WRAPPER_ANON_BOX/CSS_NON_INHERITING_ANON_BOX is the C++
+ * identifier of the atom.
+ *
+ * The second argument is the string value of the atom.
+ *
+ * CSS_NON_INHERITING_ANON_BOX is used for anon boxes that never inherit style
+ * from anything. This means all their property values are the initial values
+ * of those properties. These ones must come first! Code relies on this.
+ * If this macro is not defined, it will default to CSS_ANON_BOX.
+ *
+ * CSS_WRAPPER_ANON_BOX is used for anon boxes that are used as wrappers around
+ * other frames during frametree fixup (e.g. table anonymous boxes, ruby
+ * anonymous boxes, anonymous flex item blocks, etc). These are also inheriting
+ * anon boxes, just like CSS_ANON_BOX. If this macro is not defined, it will
+ * default to CSS_ANON_BOX.
+ */
+
+// OUTPUT_CLASS=nsCSSAnonBoxes
+// MACRO_NAME=CSS_ANON_BOX/CSS_NON_INHERITING_ANON_BOX/CSS_WRAPPER_ANON_BOX
+
+#ifndef CSS_NON_INHERITING_ANON_BOX
+# ifdef DEFINED_CSS_NON_INHERITING_ANON_BOX
+# error "Recursive includes of nsCSSAnonBoxList.h?"
+# endif /* DEFINED_CSS_NON_INHERITING_ANON_BOX */
+# define CSS_NON_INHERITING_ANON_BOX(name_, value_) CSS_ANON_BOX(name_, value_)
+# define DEFINED_CSS_NON_INHERITING_ANON_BOX
+#endif /* CSS_NON_INHERITING_ANON_BOX */
+
+#ifndef CSS_WRAPPER_ANON_BOX
+# ifdef DEFINED_CSS_WRAPPER_ANON_BOX
+# error "Recursive includes of nsCSSAnonBoxList.h?"
+# endif /* DEFINED_CSS_WRAPPER_ANON_BOX */
+# define CSS_WRAPPER_ANON_BOX(name_, value_) CSS_ANON_BOX(name_, value_)
+# define DEFINED_CSS_WRAPPER_ANON_BOX
+#endif /* CSS_WRAPPER_ANON_BOX */
+
+//---------------------------------------------------------------------------
+// Non-inheriting ones, which must come first
+//---------------------------------------------------------------------------
+
+// Placeholder frames for out of flows. Note that :-moz-placeholder is used for
+// the pseudo-element that represents the placeholder text in <input
+// placeholder="foo">, so we need a different string here.
+CSS_NON_INHERITING_ANON_BOX(oofPlaceholder, ":-moz-oof-placeholder")
+
+// Framesets
+CSS_NON_INHERITING_ANON_BOX(horizontalFramesetBorder, ":-moz-hframeset-border")
+CSS_NON_INHERITING_ANON_BOX(verticalFramesetBorder, ":-moz-vframeset-border")
+
+CSS_NON_INHERITING_ANON_BOX(framesetBlank, ":-moz-frameset-blank")
+
+CSS_NON_INHERITING_ANON_BOX(tableColGroup, ":-moz-table-column-group")
+CSS_NON_INHERITING_ANON_BOX(tableCol, ":-moz-table-column")
+
+CSS_NON_INHERITING_ANON_BOX(page, ":-moz-page")
+CSS_NON_INHERITING_ANON_BOX(pageBreak, ":-moz-page-break")
+CSS_NON_INHERITING_ANON_BOX(pageContent, ":-moz-page-content")
+CSS_NON_INHERITING_ANON_BOX(printedSheet, ":-moz-printed-sheet")
+
+// Applies to blocks that wrap contiguous runs of "column-span: all"
+// elements in multi-column subtrees, or the wrappers themselves, all the
+// way up to the column set wrappers.
+CSS_NON_INHERITING_ANON_BOX(columnSpanWrapper, ":-moz-column-span-wrapper")
+
+//---------------------------------------------------------------------------
+// Other ones
+//---------------------------------------------------------------------------
+
+// ::-moz-text, ::-moz-oof-placeholder, and ::-moz-first-letter-continuation are
+// non-elements which no rule will match.
+CSS_ANON_BOX(mozText, ":-moz-text")
+// nsFirstLetterFrames for content outside the ::first-letter.
+CSS_ANON_BOX(firstLetterContinuation, ":-moz-first-letter-continuation")
+
+CSS_ANON_BOX(mozBlockInsideInlineWrapper, ":-moz-block-inside-inline-wrapper")
+CSS_WRAPPER_ANON_BOX(mozMathMLAnonymousBlock, ":-moz-mathml-anonymous-block")
+
+CSS_ANON_BOX(mozLineFrame, ":-moz-line-frame")
+
+CSS_ANON_BOX(buttonContent, ":-moz-button-content")
+CSS_ANON_BOX(cellContent, ":-moz-cell-content")
+CSS_ANON_BOX(dropDownList, ":-moz-dropdown-list")
+CSS_ANON_BOX(fieldsetContent, ":-moz-fieldset-content")
+CSS_ANON_BOX(mozDisplayComboboxControlFrame, ":-moz-display-comboboxcontrol-frame")
+CSS_ANON_BOX(htmlCanvasContent, ":-moz-html-canvas-content")
+
+CSS_WRAPPER_ANON_BOX(inlineTable, ":-moz-inline-table")
+CSS_WRAPPER_ANON_BOX(table, ":-moz-table")
+CSS_WRAPPER_ANON_BOX(tableCell, ":-moz-table-cell")
+CSS_ANON_BOX(tableWrapper, ":-moz-table-wrapper")
+CSS_WRAPPER_ANON_BOX(tableRowGroup, ":-moz-table-row-group")
+CSS_WRAPPER_ANON_BOX(tableRow, ":-moz-table-row")
+
+CSS_ANON_BOX(canvas, ":-moz-canvas")
+CSS_ANON_BOX(pageSequence, ":-moz-page-sequence")
+CSS_ANON_BOX(scrolledContent, ":-moz-scrolled-content")
+CSS_ANON_BOX(scrolledCanvas, ":-moz-scrolled-canvas")
+
+// A column set is a set of columns inside of ColumnSetWrapperFrame, which
+// applies to nsColumnSetFrame. It doesn't contain any column-span elements.
+CSS_ANON_BOX(columnSet, ":-moz-column-set")
+
+// Applies to each column block inside of a column set.
+CSS_ANON_BOX(columnContent, ":-moz-column-content")
+
+CSS_ANON_BOX(viewport, ":-moz-viewport")
+CSS_ANON_BOX(viewportScroll, ":-moz-viewport-scroll")
+
+// Inside a flex/grid/-moz-box container, a contiguous run of text gets wrapped
+// in an anonymous block, which is then treated as a flex item.
+CSS_WRAPPER_ANON_BOX(anonymousItem, ":-moz-anonymous-item")
+
+CSS_ANON_BOX(blockRubyContent, ":-moz-block-ruby-content")
+CSS_WRAPPER_ANON_BOX(ruby, ":-moz-ruby")
+CSS_WRAPPER_ANON_BOX(rubyBase, ":-moz-ruby-base")
+CSS_WRAPPER_ANON_BOX(rubyBaseContainer, ":-moz-ruby-base-container")
+CSS_WRAPPER_ANON_BOX(rubyText, ":-moz-ruby-text")
+CSS_WRAPPER_ANON_BOX(rubyTextContainer, ":-moz-ruby-text-container")
+
+CSS_ANON_BOX(mozTreeColumn, ":-moz-tree-column")
+CSS_ANON_BOX(mozTreeRow, ":-moz-tree-row")
+CSS_ANON_BOX(mozTreeSeparator, ":-moz-tree-separator")
+CSS_ANON_BOX(mozTreeCell, ":-moz-tree-cell")
+CSS_ANON_BOX(mozTreeIndentation, ":-moz-tree-indentation")
+CSS_ANON_BOX(mozTreeLine, ":-moz-tree-line")
+CSS_ANON_BOX(mozTreeTwisty, ":-moz-tree-twisty")
+CSS_ANON_BOX(mozTreeImage, ":-moz-tree-image")
+CSS_ANON_BOX(mozTreeCellText, ":-moz-tree-cell-text")
+CSS_ANON_BOX(mozTreeCheckbox, ":-moz-tree-checkbox")
+CSS_ANON_BOX(mozTreeDropFeedback, ":-moz-tree-drop-feedback")
+
+CSS_ANON_BOX(mozSVGMarkerAnonChild, ":-moz-svg-marker-anon-child")
+CSS_ANON_BOX(mozSVGOuterSVGAnonChild, ":-moz-svg-outer-svg-anon-child")
+CSS_ANON_BOX(mozSVGForeignContent, ":-moz-svg-foreign-content")
+CSS_ANON_BOX(mozSVGText, ":-moz-svg-text")
+
+#ifdef DEFINED_CSS_NON_INHERITING_ANON_BOX
+# undef DEFINED_CSS_NON_INHERITING_ANON_BOX
+# undef CSS_NON_INHERITING_ANON_BOX
+#endif /* DEFINED_CSS_NON_INHERITING_ANON_BOX */
+
+#ifdef DEFINED_CSS_WRAPPER_ANON_BOX
+# undef DEFINED_CSS_WRAPPER_ANON_BOX
+# undef CSS_WRAPPER_ANON_BOX
+#endif /* DEFINED_CSS_NON_INHERITING_ANON_BOX */
diff --git a/layout/style/nsCSSAnonBoxes.cpp b/layout/style/nsCSSAnonBoxes.cpp
new file mode 100644
index 0000000000..db9f183927
--- /dev/null
+++ b/layout/style/nsCSSAnonBoxes.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* atom list for CSS anonymous boxes */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCSSAnonBoxes.h"
+#include "nsGkAtomConsts.h"
+#include "nsStaticAtomUtils.h"
+
+using namespace mozilla;
+
+/* static */
+bool nsCSSAnonBoxes::IsTreePseudoElement(nsAtom* aPseudo) {
+ return StringBeginsWith(nsDependentAtomString(aPseudo), u":-moz-tree-"_ns);
+}
+
+#ifdef DEBUG
+/* static */
+nsStaticAtom* nsCSSAnonBoxes::GetAtomBase() {
+ return const_cast<nsStaticAtom*>(
+ nsGkAtoms::GetAtomByIndex(kAtomIndex_AnonBoxes));
+}
+
+/* static */
+void nsCSSAnonBoxes::AssertAtoms() {
+ nsStaticAtom* base = GetAtomBase();
+ size_t index = 0;
+# define CSS_ANON_BOX(name_, value_) \
+ { \
+ RefPtr<nsAtom> atom = NS_Atomize(value_); \
+ MOZ_ASSERT(atom == nsGkAtoms::AnonBox_##name_, \
+ "Static atom for " #name_ " has incorrect value"); \
+ MOZ_ASSERT(atom == &base[index], \
+ "Static atom for " #name_ " not at expected index"); \
+ ++index; \
+ }
+# include "nsCSSAnonBoxList.h"
+# undef CSS_ANON_BOX
+}
+#endif
diff --git a/layout/style/nsCSSAnonBoxes.h b/layout/style/nsCSSAnonBoxes.h
new file mode 100644
index 0000000000..78539b8d10
--- /dev/null
+++ b/layout/style/nsCSSAnonBoxes.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* atom list for CSS anonymous boxes */
+
+#ifndef nsCSSAnonBoxes_h___
+#define nsCSSAnonBoxes_h___
+
+#include "nsAtom.h"
+#include "nsGkAtoms.h"
+#include "mozilla/PseudoStyleType.h"
+
+class nsCSSAnonBoxes {
+ using PseudoStyleType = mozilla::PseudoStyleType;
+ using PseudoStyle = mozilla::PseudoStyle;
+
+ public:
+ static bool IsTreePseudoElement(nsAtom* aPseudo);
+ static bool IsNonElement(PseudoStyleType aPseudo) {
+ return aPseudo == PseudoStyleType::mozText ||
+ aPseudo == PseudoStyleType::oofPlaceholder ||
+ aPseudo == PseudoStyleType::firstLetterContinuation;
+ }
+
+ enum class NonInheriting : uint8_t {
+#define CSS_ANON_BOX(_name, _value) /* nothing */
+#define CSS_NON_INHERITING_ANON_BOX(_name, _value) _name,
+#include "nsCSSAnonBoxList.h"
+#undef CSS_NON_INHERITING_ANON_BOX
+#undef CSS_ANON_BOX
+ _Count
+ };
+
+ // Get the NonInheriting type for a given pseudo tag. The pseudo tag must
+ // test true for IsNonInheritingAnonBox.
+ static NonInheriting NonInheritingTypeForPseudoType(PseudoStyleType aType) {
+ MOZ_ASSERT(PseudoStyle::IsNonInheritingAnonBox(aType));
+ static_assert(sizeof(PseudoStyleType) == sizeof(uint8_t), "");
+ return static_cast<NonInheriting>(
+ static_cast<uint8_t>(aType) -
+ static_cast<uint8_t>(PseudoStyleType::NonInheritingAnonBoxesStart));
+ }
+
+#ifdef DEBUG
+ static nsStaticAtom* GetAtomBase();
+ static void AssertAtoms();
+#endif
+
+// Alias nsCSSAnonBoxes::foo() to nsGkAtoms::AnonBox_foo.
+#define CSS_ANON_BOX(name_, value_) \
+ static nsCSSAnonBoxPseudoStaticAtom* name_() { \
+ return const_cast<nsCSSAnonBoxPseudoStaticAtom*>( \
+ static_cast<const nsCSSAnonBoxPseudoStaticAtom*>( \
+ nsGkAtoms::AnonBox_##name_)); \
+ }
+#include "nsCSSAnonBoxList.h"
+#undef CSS_ANON_BOX
+};
+
+#endif /* nsCSSAnonBoxes_h___ */
diff --git a/layout/style/nsCSSCounterDescList.h b/layout/style/nsCSSCounterDescList.h
new file mode 100644
index 0000000000..bad70d1be0
--- /dev/null
+++ b/layout/style/nsCSSCounterDescList.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+CSS_COUNTER_DESC(system, System)
+CSS_COUNTER_DESC(symbols, Symbols)
+CSS_COUNTER_DESC(additive-symbols, AdditiveSymbols)
+CSS_COUNTER_DESC(negative, Negative)
+CSS_COUNTER_DESC(prefix, Prefix)
+CSS_COUNTER_DESC(suffix, Suffix)
+CSS_COUNTER_DESC(range, Range)
+CSS_COUNTER_DESC(pad, Pad)
+CSS_COUNTER_DESC(fallback, Fallback)
+CSS_COUNTER_DESC(speak-as, SpeakAs)
diff --git a/layout/style/nsCSSFontDescList.h b/layout/style/nsCSSFontDescList.h
new file mode 100644
index 0000000000..a82856e4b7
--- /dev/null
+++ b/layout/style/nsCSSFontDescList.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+CSS_FONT_DESC(font-family, Family)
+CSS_FONT_DESC(font-style, Style)
+CSS_FONT_DESC(font-weight, Weight)
+CSS_FONT_DESC(font-stretch, Stretch)
+CSS_FONT_DESC(src, Src)
+CSS_FONT_DESC(unicode-range, UnicodeRange)
+CSS_FONT_DESC(font-feature-settings, FontFeatureSettings)
+CSS_FONT_DESC(font-variation-settings, FontVariationSettings)
+CSS_FONT_DESC(font-language-override, FontLanguageOverride)
+CSS_FONT_DESC(font-display, Display)
+CSS_FONT_DESC(ascent-override, AscentOverride)
+CSS_FONT_DESC(descent-override, DescentOverride)
+CSS_FONT_DESC(line-gap-override, LineGapOverride)
+CSS_FONT_DESC(size-adjust, SizeAdjust)
diff --git a/layout/style/nsCSSPropertyID.h.in b/layout/style/nsCSSPropertyID.h.in
new file mode 100644
index 0000000000..4c8f951fee
--- /dev/null
+++ b/layout/style/nsCSSPropertyID.h.in
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 types for CSS properties and their values */
+
+#ifndef nsCSSPropertyID_h___
+#define nsCSSPropertyID_h___
+
+#include <nsHashKeys.h>
+
+/*
+ Declare the enum list using the magic of preprocessing
+ enum values are "eCSSProperty_foo" (where foo is the property)
+
+ To change the list of properties, see ServoCSSPropList.h
+
+ */
+enum nsCSSPropertyID : int32_t {
+ eCSSProperty_UNKNOWN = -1,
+
+$property_ids
+
+ // Some of the values below could probably overlap with each other
+ // if we had a need for them to do so.
+
+ // Extra value to represent custom properties (--*).
+ eCSSPropertyExtra_variable,
+};
+
+// MOZ_DBG support is defined in nsCSSProps.h since it depends on
+// nsCSSProps::GetStringValue
+
+const nsCSSPropertyID
+ eCSSProperty_COUNT_no_shorthands = $longhand_count;
+const nsCSSPropertyID
+ eCSSProperty_COUNT = $shorthand_count;
+const nsCSSPropertyID
+ eCSSProperty_COUNT_with_aliases = eCSSPropertyExtra_variable;
+
+namespace mozilla {
+
+template<>
+inline PLDHashNumber
+Hash<nsCSSPropertyID>(const nsCSSPropertyID& aValue)
+{
+ return uint32_t(aValue);
+}
+
+} // namespace mozilla
+
+// The "descriptors" that can appear in a @font-face rule.
+// They have the syntax of properties but different value rules.
+enum nsCSSFontDesc {
+ eCSSFontDesc_UNKNOWN = -1,
+#define CSS_FONT_DESC(name_, method_) eCSSFontDesc_##method_,
+#include "nsCSSFontDescList.h"
+#undef CSS_FONT_DESC
+ eCSSFontDesc_COUNT
+};
+
+// The "descriptors" that can appear in a @counter-style rule.
+// They have the syntax of properties but different value rules.
+enum nsCSSCounterDesc {
+ eCSSCounterDesc_UNKNOWN = -1,
+#define CSS_COUNTER_DESC(name_, method_) eCSSCounterDesc_##method_,
+#include "nsCSSCounterDescList.h"
+#undef CSS_COUNTER_DESC
+ eCSSCounterDesc_COUNT
+};
+
+namespace mozilla {
+
+// FIXME: The underlying type of this enum should be uint8_t, but we can't do
+// that because of https://bugs.llvm.org/show_bug.cgi?id=44228.
+enum class CountedUnknownProperty : uint32_t {
+#define COUNTED_UNKNOWN_PROPERTY(name_, method_) method_,
+#include "mozilla/CountedUnknownProperties.h"
+#undef COUNTED_UNKNOWN_PROPERTY
+ Count,
+};
+
+} // namespace mozilla
+
+#endif /* nsCSSPropertyID_h___ */
diff --git a/layout/style/nsCSSPropertyIDSet.h b/layout/style/nsCSSPropertyIDSet.h
new file mode 100644
index 0000000000..18488815ee
--- /dev/null
+++ b/layout/style/nsCSSPropertyIDSet.h
@@ -0,0 +1,304 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* bit vectors for sets of CSS properties */
+
+#ifndef nsCSSPropertyIDSet_h__
+#define nsCSSPropertyIDSet_h__
+
+#include <initializer_list>
+#include <limits.h> // for CHAR_BIT
+#include <ostream>
+
+#include "mozilla/ArrayUtils.h"
+// For COMPOSITOR_ANIMATABLE_PROPERTY_LIST and
+// COMPOSITOR_ANIMATABLE_PROPERTY_LIST_LENGTH
+#include "mozilla/AnimatedPropertyID.h"
+#include "mozilla/CompositorAnimatableProperties.h"
+#include "nsCSSProps.h" // For operator<< for nsCSSPropertyID
+#include "nsCSSPropertyID.h"
+
+/**
+ * nsCSSPropertyIDSet maintains a set of non-shorthand CSS properties. In
+ * other words, for each longhand CSS property we support, it has a bit
+ * for whether that property is in the set.
+ */
+class nsCSSPropertyIDSet {
+ public:
+ constexpr nsCSSPropertyIDSet() : mProperties{0} {}
+ // auto-generated copy-constructor OK
+
+ explicit constexpr nsCSSPropertyIDSet(
+ std::initializer_list<nsCSSPropertyID> aProperties)
+ : mProperties{0} {
+ for (auto property : aProperties) {
+ size_t p = property;
+ mProperties[p / kBitsInChunk] |= property_set_type(1)
+ << (p % kBitsInChunk);
+ }
+ }
+
+ void AssertInSetRange(nsCSSPropertyID aProperty) const {
+ MOZ_DIAGNOSTIC_ASSERT(
+ 0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
+ "out of bounds");
+ }
+
+ // Conversion of aProperty to |size_t| after AssertInSetRange
+ // lets the compiler generate significantly tighter code.
+
+ void AddProperty(nsCSSPropertyID aProperty) {
+ AssertInSetRange(aProperty);
+ size_t p = aProperty;
+ mProperties[p / kBitsInChunk] |= property_set_type(1) << (p % kBitsInChunk);
+ }
+
+ void RemoveProperty(nsCSSPropertyID aProperty) {
+ AssertInSetRange(aProperty);
+ size_t p = aProperty;
+ mProperties[p / kBitsInChunk] &=
+ ~(property_set_type(1) << (p % kBitsInChunk));
+ }
+
+ bool HasProperty(const mozilla::AnimatedPropertyID& aProperty) const {
+ return !aProperty.IsCustom() && HasProperty(aProperty.mID);
+ }
+
+ bool HasProperty(nsCSSPropertyID aProperty) const {
+ AssertInSetRange(aProperty);
+ size_t p = aProperty;
+ return (mProperties[p / kBitsInChunk] &
+ (property_set_type(1) << (p % kBitsInChunk))) != 0;
+ }
+
+ // Returns an nsCSSPropertyIDSet including all properties that can be run
+ // on the compositor.
+ static constexpr nsCSSPropertyIDSet CompositorAnimatables() {
+ return nsCSSPropertyIDSet(COMPOSITOR_ANIMATABLE_PROPERTY_LIST);
+ }
+
+ static constexpr size_t CompositorAnimatableCount() {
+ return COMPOSITOR_ANIMATABLE_PROPERTY_LIST_LENGTH;
+ }
+
+ static constexpr size_t CompositorAnimatableDisplayItemCount() {
+ // We have 3 individual transforms and 5 motion path properties, and they
+ // also use DisplayItemType::TYPE_TRANSFORM.
+ return COMPOSITOR_ANIMATABLE_PROPERTY_LIST_LENGTH - 8;
+ }
+
+ static constexpr nsCSSPropertyIDSet CSSTransformProperties() {
+ return nsCSSPropertyIDSet{eCSSProperty_transform, eCSSProperty_translate,
+ eCSSProperty_rotate, eCSSProperty_scale};
+ }
+
+ static constexpr nsCSSPropertyIDSet MotionPathProperties() {
+ return nsCSSPropertyIDSet{
+ eCSSProperty_offset_path, eCSSProperty_offset_distance,
+ eCSSProperty_offset_rotate, eCSSProperty_offset_anchor,
+ eCSSProperty_offset_position};
+ }
+
+ static constexpr nsCSSPropertyIDSet TransformLikeProperties() {
+ return nsCSSPropertyIDSet{
+ eCSSProperty_transform, eCSSProperty_translate,
+ eCSSProperty_rotate, eCSSProperty_scale,
+ eCSSProperty_offset_path, eCSSProperty_offset_distance,
+ eCSSProperty_offset_rotate, eCSSProperty_offset_anchor,
+ eCSSProperty_offset_position};
+ }
+
+ static constexpr nsCSSPropertyIDSet OpacityProperties() {
+ return nsCSSPropertyIDSet{eCSSProperty_opacity};
+ }
+
+ bool Intersects(const nsCSSPropertyIDSet& aOther) const {
+ for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
+ if (mProperties[i] & aOther.mProperties[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void Empty() { memset(mProperties, 0, sizeof(mProperties)); }
+
+ void AssertIsEmpty(const char* aText) const {
+ for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
+ NS_ASSERTION(mProperties[i] == 0, aText);
+ }
+ }
+
+ bool Equals(const nsCSSPropertyIDSet& aOther) const {
+ return mozilla::ArrayEqual(mProperties, aOther.mProperties);
+ }
+
+ bool IsEmpty() const {
+ for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
+ if (mProperties[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool IsSubsetOf(const nsCSSPropertyIDSet& aOther) const {
+ return this->Intersect(aOther).Equals(*this);
+ }
+
+ // Return a new nsCSSPropertyIDSet which is the inverse of this set.
+ nsCSSPropertyIDSet Inverse() const {
+ nsCSSPropertyIDSet result;
+ for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
+ result.mProperties[i] = ~mProperties[i];
+ }
+ return result;
+ }
+
+ // Returns a new nsCSSPropertyIDSet with all properties that are both in
+ // this set and |aOther|.
+ nsCSSPropertyIDSet Intersect(const nsCSSPropertyIDSet& aOther) const {
+ nsCSSPropertyIDSet result;
+ for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
+ result.mProperties[i] = mProperties[i] & aOther.mProperties[i];
+ }
+ return result;
+ }
+
+ // Return a new nsCSSPropertyIDSet with all properties that are in either
+ // this set or |aOther| but not both.
+ nsCSSPropertyIDSet Xor(const nsCSSPropertyIDSet& aOther) const {
+ nsCSSPropertyIDSet result;
+ for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
+ result.mProperties[i] = mProperties[i] ^ aOther.mProperties[i];
+ }
+ return result;
+ }
+
+ nsCSSPropertyIDSet& operator|=(const nsCSSPropertyIDSet& aOther) {
+ for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
+ mProperties[i] |= aOther.mProperties[i];
+ }
+ return *this;
+ }
+
+ private:
+ typedef unsigned long property_set_type;
+
+ public:
+ // number of bits in |property_set_type|.
+ static const size_t kBitsInChunk = sizeof(property_set_type) * CHAR_BIT;
+ // number of |property_set_type|s in the set
+ static const size_t kChunkCount =
+ (eCSSProperty_COUNT_no_shorthands + kBitsInChunk - 1) / kBitsInChunk;
+
+ /*
+ * For fast enumeration of all the bits that are set, callers can
+ * check each chunk against zero (since in normal cases few bits are
+ * likely to be set).
+ */
+ bool HasPropertyInChunk(size_t aChunk) const {
+ return mProperties[aChunk] != 0;
+ }
+ bool HasPropertyAt(size_t aChunk, size_t aBit) const {
+ return (mProperties[aChunk] & (property_set_type(1) << aBit)) != 0;
+ }
+ static nsCSSPropertyID CSSPropertyAt(size_t aChunk, size_t aBit) {
+ return nsCSSPropertyID(aChunk * kBitsInChunk + aBit);
+ }
+
+ // Iterator for use in range-based for loops
+ class Iterator {
+ public:
+ Iterator(Iterator&& aOther)
+ : mPropertySet(aOther.mPropertySet),
+ mChunk(aOther.mChunk),
+ mBit(aOther.mBit) {}
+
+ static Iterator BeginIterator(const nsCSSPropertyIDSet& aPropertySet) {
+ Iterator result(aPropertySet);
+
+ // Search for the first property.
+ // Unsigned integer overflow is defined so the following is safe.
+ result.mBit = -1;
+ ++result;
+
+ return result;
+ }
+
+ static Iterator EndIterator(const nsCSSPropertyIDSet& aPropertySet) {
+ Iterator result(aPropertySet);
+ result.mChunk = kChunkCount;
+ result.mBit = 0;
+ return result;
+ }
+
+ bool operator!=(const Iterator& aOther) const {
+ return mChunk != aOther.mChunk || mBit != aOther.mBit;
+ }
+
+ Iterator& operator++() {
+ MOZ_ASSERT(mChunk < kChunkCount, "Should not iterate beyond end");
+
+ do {
+ mBit++;
+ } while (mBit < kBitsInChunk &&
+ !mPropertySet.HasPropertyAt(mChunk, mBit));
+ if (mBit != kBitsInChunk) {
+ return *this;
+ }
+
+ do {
+ mChunk++;
+ } while (mChunk < kChunkCount &&
+ !mPropertySet.HasPropertyInChunk(mChunk));
+ mBit = 0;
+ if (mChunk != kChunkCount) {
+ while (mBit < kBitsInChunk &&
+ !mPropertySet.HasPropertyAt(mChunk, mBit)) {
+ mBit++;
+ }
+ }
+
+ return *this;
+ }
+
+ nsCSSPropertyID operator*() {
+ MOZ_ASSERT(mChunk < kChunkCount, "Should not dereference beyond end");
+ return nsCSSPropertyIDSet::CSSPropertyAt(mChunk, mBit);
+ }
+
+ private:
+ explicit Iterator(const nsCSSPropertyIDSet& aPropertySet)
+ : mPropertySet(aPropertySet) {}
+
+ Iterator() = delete;
+ Iterator(const Iterator&) = delete;
+ Iterator& operator=(const Iterator&) = delete;
+ Iterator& operator=(const Iterator&&) = delete;
+
+ const nsCSSPropertyIDSet& mPropertySet;
+ size_t mChunk = 0;
+ size_t mBit = 0;
+ };
+
+ Iterator begin() const { return Iterator::BeginIterator(*this); }
+ Iterator end() const { return Iterator::EndIterator(*this); }
+
+ private:
+ property_set_type mProperties[kChunkCount];
+};
+
+// MOZ_DBG support
+
+inline std::ostream& operator<<(std::ostream& aOut,
+ const nsCSSPropertyIDSet& aPropertySet) {
+ AutoTArray<nsCSSPropertyID, 16> properties;
+ for (nsCSSPropertyID property : aPropertySet) {
+ properties.AppendElement(property);
+ }
+ return aOut << properties;
+}
+
+#endif /* !defined(nsCSSPropertyIDSet_h__) */
diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp
new file mode 100644
index 0000000000..5f2f275d6e
--- /dev/null
+++ b/layout/style/nsCSSProps.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * methods for dealing with CSS properties and tables of the keyword
+ * values they accept
+ */
+
+#include "nsCSSProps.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Casting.h"
+
+#include "gfxPlatform.h"
+#include "nsLayoutUtils.h"
+#include "nsIWidget.h"
+#include "nsStyleConsts.h" // For system widget appearance types
+
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/AnimationEffectBinding.h" // for PlaybackDirection
+#include "mozilla/gfx/gfxVars.h" // for UseWebRender
+#include "mozilla/gfx/gfxVarReceiver.h"
+#include "mozilla/LookAndFeel.h" // for system colors
+
+#include "nsString.h"
+#include "nsStaticNameTable.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+
+using namespace mozilla;
+
+static StaticAutoPtr<nsStaticCaseInsensitiveNameTable> gFontDescTable;
+static StaticAutoPtr<nsStaticCaseInsensitiveNameTable> gCounterDescTable;
+static StaticAutoPtr<nsTHashMap<nsCStringHashKey, nsCSSPropertyID>>
+ gPropertyIDLNameTable;
+
+static constexpr const char* const kCSSRawFontDescs[] = {
+#define CSS_FONT_DESC(name_, method_) #name_,
+#include "nsCSSFontDescList.h"
+#undef CSS_FONT_DESC
+};
+
+static constexpr const char* const kCSSRawCounterDescs[] = {
+#define CSS_COUNTER_DESC(name_, method_) #name_,
+#include "nsCSSCounterDescList.h"
+#undef CSS_COUNTER_DESC
+};
+
+static constexpr CSSPropFlags kFlagsTable[eCSSProperty_COUNT_with_aliases] = {
+#define CSS_PROP_LONGHAND(name_, id_, method_, flags_, ...) flags_,
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, ...) flags_,
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, flags_, ...) flags_,
+#include "mozilla/ServoCSSPropList.h"
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LONGHAND
+};
+
+static nsStaticCaseInsensitiveNameTable* CreateStaticTable(
+ const char* const aRawTable[], int32_t aLength) {
+ auto* table = new nsStaticCaseInsensitiveNameTable(aRawTable, aLength);
+#ifdef DEBUG
+ // Partially verify the entries.
+ for (int32_t index = 0; index < aLength; ++index) {
+ nsAutoCString temp(aRawTable[index]);
+ MOZ_ASSERT(-1 == temp.FindChar('_'),
+ "underscore char in case insensitive name table");
+ }
+#endif
+ return table;
+}
+
+void nsCSSProps::RecomputeEnabledState(const char* aPref, void*) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ DebugOnly<bool> foundPref = false;
+ for (const PropertyPref* pref = kPropertyPrefTable;
+ pref->mPropID != eCSSProperty_UNKNOWN; pref++) {
+ if (!aPref || !strcmp(aPref, pref->mPref)) {
+ foundPref = true;
+#ifdef FUZZING
+ gPropertyEnabled[pref->mPropID] = true;
+#else
+ gPropertyEnabled[pref->mPropID] = Preferences::GetBool(pref->mPref);
+ if (pref->mPropID == eCSSProperty_backdrop_filter) {
+ gPropertyEnabled[pref->mPropID] &=
+ gfx::gfxVars::GetAllowBackdropFilterOrDefault();
+ }
+#endif
+ }
+ }
+ MOZ_ASSERT(foundPref);
+}
+
+void nsCSSProps::Init() {
+ MOZ_ASSERT(!gFontDescTable, "pre existing array!");
+ MOZ_ASSERT(!gCounterDescTable, "pre existing array!");
+ MOZ_ASSERT(!gPropertyIDLNameTable, "pre existing array!");
+
+ gFontDescTable = CreateStaticTable(kCSSRawFontDescs, eCSSFontDesc_COUNT);
+ gCounterDescTable =
+ CreateStaticTable(kCSSRawCounterDescs, eCSSCounterDesc_COUNT);
+
+ gPropertyIDLNameTable = new nsTHashMap<nsCStringHashKey, nsCSSPropertyID>;
+ for (nsCSSPropertyID p = nsCSSPropertyID(0);
+ size_t(p) < ArrayLength(kIDLNameTable); p = nsCSSPropertyID(p + 1)) {
+ if (kIDLNameTable[p]) {
+ gPropertyIDLNameTable->InsertOrUpdate(
+ nsDependentCString(kIDLNameTable[p]), p);
+ }
+ }
+
+ ClearOnShutdown(&gFontDescTable);
+ ClearOnShutdown(&gCounterDescTable);
+ ClearOnShutdown(&gPropertyIDLNameTable);
+
+ for (const PropertyPref* pref = kPropertyPrefTable;
+ pref->mPropID != eCSSProperty_UNKNOWN; pref++) {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1472523
+ // We need to use nsCString instead of substring because the preference
+ // callback code stores them. Using AssignLiteral prevents any
+ // unnecessary allocations.
+ nsCString prefName;
+ prefName.AssignLiteral(pref->mPref, strlen(pref->mPref));
+ Preferences::RegisterCallback(nsCSSProps::RecomputeEnabledState, prefName);
+ }
+ RecomputeEnabledState(/* aPrefName = */ nullptr);
+}
+
+/* static */
+bool nsCSSProps::IsCustomPropertyName(const nsACString& aProperty) {
+ return aProperty.Length() >= CSS_CUSTOM_NAME_PREFIX_LENGTH &&
+ StringBeginsWith(aProperty, "--"_ns);
+}
+
+nsCSSPropertyID nsCSSProps::LookupPropertyByIDLName(
+ const nsACString& aPropertyIDLName, EnabledState aEnabled) {
+ MOZ_ASSERT(gPropertyIDLNameTable, "no lookup table, needs addref");
+ nsCSSPropertyID res;
+ if (!gPropertyIDLNameTable->Get(aPropertyIDLName, &res)) {
+ return eCSSProperty_UNKNOWN;
+ }
+ MOZ_ASSERT(res < eCSSProperty_COUNT);
+ if (!IsEnabled(res, aEnabled)) {
+ return eCSSProperty_UNKNOWN;
+ }
+ return res;
+}
+
+nsCSSFontDesc nsCSSProps::LookupFontDesc(const nsACString& aFontDesc) {
+ MOZ_ASSERT(gFontDescTable, "no lookup table, needs addref");
+ nsCSSFontDesc which = nsCSSFontDesc(gFontDescTable->Lookup(aFontDesc));
+
+ return which;
+}
+
+static constexpr auto sDescNullStr = ""_ns;
+
+const nsCString& nsCSSProps::GetStringValue(nsCSSFontDesc aFontDescID) {
+ MOZ_ASSERT(gFontDescTable, "no lookup table, needs addref");
+ if (gFontDescTable) {
+ return gFontDescTable->GetStringValue(int32_t(aFontDescID));
+ }
+ return sDescNullStr;
+}
+
+const nsCString& nsCSSProps::GetStringValue(nsCSSCounterDesc aCounterDescID) {
+ MOZ_ASSERT(gCounterDescTable, "no lookup table, needs addref");
+ if (gCounterDescTable) {
+ return gCounterDescTable->GetStringValue(int32_t(aCounterDescID));
+ }
+ return sDescNullStr;
+}
+
+CSSPropFlags nsCSSProps::PropFlags(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_with_aliases,
+ "out of range");
+ return kFlagsTable[aProperty];
+}
+
+/* static */
+bool nsCSSProps::gPropertyEnabled[eCSSProperty_COUNT_with_aliases] = {
+// If the property has any "ENABLED_IN" flag set, it is disabled by
+// default. Note that, if a property has pref, whatever its default
+// value is, it will later be changed in nsCSSProps::AddRefTable().
+// If the property has "ENABLED_IN" flags but doesn't have a pref,
+// it is an internal property which is disabled elsewhere.
+#define IS_ENABLED_BY_DEFAULT(flags_) \
+ (!((flags_) & (CSSPropFlags::EnabledMask | CSSPropFlags::Inaccessible)))
+
+#define CSS_PROP_LONGHAND(name_, id_, method_, flags_, ...) \
+ IS_ENABLED_BY_DEFAULT(flags_),
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, ...) \
+ IS_ENABLED_BY_DEFAULT(flags_),
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, flags_, ...) \
+ IS_ENABLED_BY_DEFAULT(flags_),
+#include "mozilla/ServoCSSPropList.h"
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LONGHAND
+
+#undef IS_ENABLED_BY_DEFAULT
+};
+
+/**
+ * A singleton class to register as a receiver for gfxVars.
+ * Updates the state of backdrop-filter's pref if the gfx
+ * backdrop filter var changes state.
+ */
+class nsCSSPropsGfxVarReceiver final : public gfx::gfxVarReceiver {
+ constexpr nsCSSPropsGfxVarReceiver() = default;
+
+ // Backdrop filter's last known enabled state.
+ static bool sLastKnownAllowBackdropFilter;
+ static nsCSSPropsGfxVarReceiver sInstance;
+
+ public:
+ static gfx::gfxVarReceiver& GetInstance() { return sInstance; }
+
+ void OnVarChanged(const gfx::GfxVarUpdate&) override {
+ bool enabled = gfx::gfxVars::AllowBackdropFilter();
+ if (sLastKnownAllowBackdropFilter != enabled) {
+ sLastKnownAllowBackdropFilter = enabled;
+ nsCSSProps::RecomputeEnabledState(
+ StaticPrefs::GetPrefName_layout_css_backdrop_filter_enabled());
+ }
+ }
+};
+
+/* static */
+nsCSSPropsGfxVarReceiver nsCSSPropsGfxVarReceiver::sInstance =
+ nsCSSPropsGfxVarReceiver();
+
+/* static */
+bool nsCSSPropsGfxVarReceiver::sLastKnownAllowBackdropFilter = true;
+
+/* static */
+gfx::gfxVarReceiver& nsCSSProps::GfxVarReceiver() {
+ return nsCSSPropsGfxVarReceiver::GetInstance();
+}
+
+#include "nsCSSPropsGenerated.inc"
diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h
new file mode 100644
index 0000000000..70cf78e6d5
--- /dev/null
+++ b/layout/style/nsCSSProps.h
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * methods for dealing with CSS properties and tables of the keyword
+ * values they accept
+ */
+
+#ifndef nsCSSProps_h___
+#define nsCSSProps_h___
+
+#include <ostream>
+
+#include "nsString.h"
+#include "nsCSSPropertyID.h"
+#include "nsStyleStructFwd.h"
+#include "mozilla/UseCounter.h"
+#include "mozilla/CSSEnabledState.h"
+#include "mozilla/CSSPropFlags.h"
+#include "mozilla/Preferences.h"
+
+// Length of the "--" prefix on custom names (such as custom property names,
+// and, in the future, custom media query names).
+#define CSS_CUSTOM_NAME_PREFIX_LENGTH 2
+
+namespace mozilla {
+class ComputedStyle;
+namespace gfx {
+class gfxVarReceiver;
+}
+} // namespace mozilla
+
+extern "C" {
+nsCSSPropertyID Servo_ResolveLogicalProperty(nsCSSPropertyID,
+ const mozilla::ComputedStyle*);
+nsCSSPropertyID Servo_Property_LookupEnabledForAllContent(const nsACString*);
+const uint8_t* Servo_Property_GetName(nsCSSPropertyID, uint32_t* aLength);
+}
+
+class nsCSSProps {
+ public:
+ using EnabledState = mozilla::CSSEnabledState;
+ using Flags = mozilla::CSSPropFlags;
+
+ static void Init();
+
+ // Looks up the property with name aProperty and returns its corresponding
+ // nsCSSPropertyID value. If aProperty is the name of a custom property,
+ // then eCSSPropertyExtra_variable will be returned.
+ //
+ // This only returns properties enabled for all content, and resolves aliases
+ // to return the aliased property.
+ static nsCSSPropertyID LookupProperty(const nsACString& aProperty) {
+ return Servo_Property_LookupEnabledForAllContent(&aProperty);
+ }
+
+ // As above, but looked up using a property's IDL name.
+ // eCSSPropertyExtra_variable won't be returned from this method.
+ static nsCSSPropertyID LookupPropertyByIDLName(
+ const nsACString& aPropertyIDLName, EnabledState aEnabled);
+
+ // Returns whether aProperty is a custom property name, i.e. begins with
+ // "--". This assumes that the CSS Variables pref has been enabled.
+ static bool IsCustomPropertyName(const nsACString& aProperty);
+
+ static bool IsShorthand(nsCSSPropertyID aProperty) {
+ if (aProperty == eCSSPropertyExtra_variable) {
+ return false;
+ }
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
+ "out of range");
+ return aProperty >= eCSSProperty_COUNT_no_shorthands;
+ }
+
+ // Same but for @font-face descriptors
+ static nsCSSFontDesc LookupFontDesc(const nsACString&);
+
+ // The relevant invariants are asserted in Document.cpp
+ static mozilla::UseCounter UseCounterFor(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_with_aliases,
+ "out of range");
+ return mozilla::UseCounter(size_t(mozilla::eUseCounter_FirstCSSProperty) +
+ size_t(aProperty));
+ }
+
+ // Given a property enum, get the string value
+ //
+ // This string is static.
+ static nsDependentCSubstring GetStringValue(nsCSSPropertyID aProperty) {
+ uint32_t len;
+ const uint8_t* chars = Servo_Property_GetName(aProperty, &len);
+ return nsDependentCSubstring(reinterpret_cast<const char*>(chars), len);
+ }
+
+ static const nsCString& GetStringValue(nsCSSFontDesc aFontDesc);
+ static const nsCString& GetStringValue(nsCSSCounterDesc aCounterDesc);
+
+ static Flags PropFlags(nsCSSPropertyID);
+ static bool PropHasFlags(nsCSSPropertyID aProperty, Flags aFlags) {
+ return (PropFlags(aProperty) & aFlags) == aFlags;
+ }
+
+ static nsCSSPropertyID Physicalize(nsCSSPropertyID aProperty,
+ const mozilla::ComputedStyle& aStyle) {
+ MOZ_ASSERT(!IsShorthand(aProperty));
+ if (PropHasFlags(aProperty, Flags::IsLogical)) {
+ return Servo_ResolveLogicalProperty(aProperty, &aStyle);
+ }
+ return aProperty;
+ }
+
+ private:
+ // A table for shorthand properties. The appropriate index is the
+ // property ID minus eCSSProperty_COUNT_no_shorthands.
+ static const nsCSSPropertyID* const
+ kSubpropertyTable[eCSSProperty_COUNT - eCSSProperty_COUNT_no_shorthands];
+
+ public:
+ /**
+ * Returns true if the backdrop-filter pref and the gfx blocklist are enabled.
+ */
+ static bool IsBackdropFilterAvailable(JSContext*, JSObject*) {
+ return IsEnabled(eCSSProperty_backdrop_filter, EnabledState::ForAllContent);
+ }
+
+ /**
+ * Recoumputes the enabled state of a pref. If aPrefName is nullptr,
+ * recomputes the state of all prefs in gPropertyEnabled.
+ * aClosure is the pref callback closure data, which is not used.
+ */
+ static void RecomputeEnabledState(const char* aPrefName,
+ void* aClosure = nullptr);
+
+ /**
+ * Retrieve a singleton receiver to register with gfxVars
+ */
+ static mozilla::gfx::gfxVarReceiver& GfxVarReceiver();
+
+ static const nsCSSPropertyID* SubpropertyEntryFor(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(eCSSProperty_COUNT_no_shorthands <= aProperty &&
+ aProperty < eCSSProperty_COUNT,
+ "out of range");
+ return kSubpropertyTable[aProperty - eCSSProperty_COUNT_no_shorthands];
+ }
+
+ private:
+ static bool gPropertyEnabled[eCSSProperty_COUNT_with_aliases];
+ // Defined in the generated nsCSSPropsGenerated.inc.
+ static const char* const kIDLNameTable[eCSSProperty_COUNT];
+ static const int32_t kIDLNameSortPositionTable[eCSSProperty_COUNT];
+
+ public:
+ /**
+ * Returns the IDL name of the specified property, which must be a
+ * longhand, logical or shorthand property. The IDL name is the property
+ * name with any hyphen-lowercase character pairs replaced by an
+ * uppercase character:
+ * https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
+ *
+ * As a special case, the string "cssFloat" is returned for the float
+ * property. nullptr is returned for internal properties.
+ */
+ static const char* PropertyIDLName(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
+ "out of range");
+ return kIDLNameTable[aProperty];
+ }
+
+ /**
+ * Returns the position of the specified property in a list of all
+ * properties sorted by their IDL name.
+ */
+ static int32_t PropertyIDLNameSortPosition(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
+ "out of range");
+ return kIDLNameSortPositionTable[aProperty];
+ }
+
+ static bool IsEnabled(nsCSSPropertyID aProperty, EnabledState aEnabled) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_with_aliases,
+ "out of range");
+ // In the child process, assert that we're not trying to parse stylesheets
+ // before we've gotten all our prefs.
+ MOZ_ASSERT_IF(!XRE_IsParentProcess(),
+ mozilla::Preferences::ArePrefsInitedInContentProcess());
+ if (gPropertyEnabled[aProperty]) {
+ return true;
+ }
+ if (aEnabled == EnabledState::IgnoreEnabledState) {
+ return true;
+ }
+ if ((aEnabled & EnabledState::InUASheets) &&
+ PropHasFlags(aProperty, Flags::EnabledInUASheets)) {
+ return true;
+ }
+ if ((aEnabled & EnabledState::InChrome) &&
+ PropHasFlags(aProperty, Flags::EnabledInChrome)) {
+ return true;
+ }
+ return false;
+ }
+
+ struct PropertyPref {
+ nsCSSPropertyID mPropID;
+ const char* mPref;
+ };
+ static const PropertyPref kPropertyPrefTable[];
+
+// Storing the enabledstate_ value in an nsCSSPropertyID variable is a small
+// hack to avoid needing a separate variable declaration for its real type
+// (CSSEnabledState), which would then require using a block and
+// therefore a pair of macros by consumers for the start and end of the loop.
+#define CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(it_, prop_, enabledstate_) \
+ for (const nsCSSPropertyID * \
+ it_ = nsCSSProps::SubpropertyEntryFor(prop_), \
+ es_ = (nsCSSPropertyID)((enabledstate_) | CSSEnabledState(0)); \
+ *it_ != eCSSProperty_UNKNOWN; ++it_) \
+ if (nsCSSProps::IsEnabled(*it_, (mozilla::CSSEnabledState)es_))
+};
+
+// MOZ_DBG support for nsCSSPropertyID
+
+inline std::ostream& operator<<(std::ostream& aOut, nsCSSPropertyID aProperty) {
+ return aOut << nsCSSProps::GetStringValue(aProperty);
+}
+
+#endif /* nsCSSProps_h___ */
diff --git a/layout/style/nsCSSPseudoElementList.h b/layout/style/nsCSSPseudoElementList.h
new file mode 100644
index 0000000000..9514dd6c4b
--- /dev/null
+++ b/layout/style/nsCSSPseudoElementList.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* list of CSS pseudo-elements */
+
+/*
+ * This file contains the list of support CSS pseudo-elements and some flags.
+ * It is designed to be used as inline input to nsCSSPseudoElements.cpp *only*
+ * through the magic of C preprocessing. All entries must be enclosed either
+ * in the macro CSS_PSEUDO_ELEMENT; these macros will have cruel and unusual
+ * things done to them. The entries should be kept in some sort of logical
+ * order.
+ *
+ * Code including this file MUST define CSS_PSEUDO_ELEMENT, which takes
+ * three parameters:
+ * name_ : The C++ identifier used for the atom (which will be a member
+ * of nsCSSPseudoElements)
+ * value_ : The pseudo-element as a string, with single-colon syntax,
+ * used as the string value of the atom.
+ * flags_ : A bitfield containing flags defined in nsCSSPseudoElements.h
+ *
+ * A corresponding atom must also be defined in StaticAtoms.py with a name of
+ * "PseudoElement_<name_>" and whose value matches the definition in this file.
+ */
+
+// OUTPUT_CLASS=nsCSSPseudoElements
+// MACRO_NAME=CSS_PSEUDO_ELEMENT
+
+CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2 |
+ CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM)
+CSS_PSEUDO_ELEMENT(before, ":before", CSS_PSEUDO_ELEMENT_IS_CSS2 |
+ CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM)
+CSS_PSEUDO_ELEMENT(marker, ":marker", 0)
+
+CSS_PSEUDO_ELEMENT(backdrop, ":backdrop", 0)
+
+CSS_PSEUDO_ELEMENT(cue, ":cue", CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC |
+ CSS_PSEUDO_ELEMENT_SUPPORTS_STYLE_ATTRIBUTE)
+
+CSS_PSEUDO_ELEMENT(firstLetter, ":first-letter",
+ CSS_PSEUDO_ELEMENT_IS_CSS2 |
+ CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS)
+CSS_PSEUDO_ELEMENT(firstLine, ":first-line",
+ CSS_PSEUDO_ELEMENT_IS_CSS2 |
+ CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS)
+CSS_PSEUDO_ELEMENT(highlight, ":highlight", 0)
+
+CSS_PSEUDO_ELEMENT(selection, ":selection",
+ CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS)
+
+// XXXbz should we really allow random content to style these? Maybe
+// use our flags to prevent that?
+CSS_PSEUDO_ELEMENT(mozFocusInner, ":-moz-focus-inner", 0)
+
+// HTML5 Forms pseudo elements
+CSS_PSEUDO_ELEMENT(mozNumberSpinBox, ":-moz-number-spin-box",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
+CSS_PSEUDO_ELEMENT(mozNumberSpinUp, ":-moz-number-spin-up",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
+CSS_PSEUDO_ELEMENT(mozNumberSpinDown, ":-moz-number-spin-down",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
+CSS_PSEUDO_ELEMENT(mozSearchClearButton, ":-moz-search-clear-button",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
+CSS_PSEUDO_ELEMENT(mozProgressBar, ":-moz-progress-bar",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE)
+CSS_PSEUDO_ELEMENT(mozRangeTrack, ":-moz-range-track",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE)
+CSS_PSEUDO_ELEMENT(mozRangeProgress, ":-moz-range-progress",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE)
+CSS_PSEUDO_ELEMENT(mozRangeThumb, ":-moz-range-thumb",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE)
+CSS_PSEUDO_ELEMENT(mozMeterBar, ":-moz-meter-bar",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE)
+CSS_PSEUDO_ELEMENT(placeholder, ":placeholder",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE)
+CSS_PSEUDO_ELEMENT(mozColorSwatch, ":-moz-color-swatch",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_STYLE_ATTRIBUTE |
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE)
+// The root of the text value anonymous content inside an <input> or <textarea>.
+CSS_PSEUDO_ELEMENT(mozTextControlEditingRoot, ":-moz-text-control-editing-root",
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS)
+// The element that shows the autofill value.
+CSS_PSEUDO_ELEMENT(mozTextControlPreview, ":-moz-text-control-preview",
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS)
+// The Reveal Password button for <input type=password>.
+CSS_PSEUDO_ELEMENT(mozReveal, ":-moz-reveal",
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS)
+
+// The button in an <input type=file>
+CSS_PSEUDO_ELEMENT(fileSelectorButton, ":file-selector-button",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE)
+
+// Standard progress/meter/range pseudo-elements.
+CSS_PSEUDO_ELEMENT(sliderTrack, ":slider-track",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
+CSS_PSEUDO_ELEMENT(sliderThumb, ":slider-thumb",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
+CSS_PSEUDO_ELEMENT(sliderFill, ":slider-fill",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
diff --git a/layout/style/nsCSSPseudoElements.cpp b/layout/style/nsCSSPseudoElements.cpp
new file mode 100644
index 0000000000..48b4d5c1a7
--- /dev/null
+++ b/layout/style/nsCSSPseudoElements.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* atom list for CSS pseudo-elements */
+
+#include "nsCSSPseudoElements.h"
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCSSAnonBoxes.h"
+#include "nsDOMString.h"
+#include "nsGkAtomConsts.h"
+#include "nsStaticAtomUtils.h"
+#include "nsStringFwd.h"
+
+using namespace mozilla;
+
+// Flags data for each of the pseudo-elements.
+/* static */ const uint32_t nsCSSPseudoElements::kPseudoElementFlags[] = {
+#define CSS_PSEUDO_ELEMENT(name_, value_, flags_) flags_,
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+};
+
+/* static */
+nsStaticAtom* nsCSSPseudoElements::GetAtomBase() {
+ return const_cast<nsStaticAtom*>(
+ nsGkAtoms::GetAtomByIndex(kAtomIndex_PseudoElements));
+}
+
+/* static */
+nsAtom* nsCSSPseudoElements::GetPseudoAtom(Type aType) {
+ MOZ_ASSERT(PseudoStyle::IsPseudoElement(aType), "Unexpected type");
+ size_t index = kAtomIndex_PseudoElements + static_cast<size_t>(aType);
+ return nsGkAtoms::GetAtomByIndex(index);
+}
+
+/* static */
+std::tuple<mozilla::Maybe<PseudoStyleType>, RefPtr<nsAtom>>
+nsCSSPseudoElements::ParsePseudoElement(const nsAString& aPseudoElement,
+ CSSEnabledState aEnabledState) {
+ if (DOMStringIsNull(aPseudoElement) || aPseudoElement.IsEmpty()) {
+ return {Some(PseudoStyleType::NotPseudo), nullptr};
+ }
+
+ if (aPseudoElement.First() != char16_t(':')) {
+ return {};
+ }
+
+ // deal with two-colon forms of aPseudoElt
+ const char16_t* start = aPseudoElement.BeginReading();
+ const char16_t* end = aPseudoElement.EndReading();
+ NS_ASSERTION(start != end, "aPseudoElement is not empty!");
+ ++start;
+ bool haveTwoColons = true;
+ if (start == end || *start != char16_t(':')) {
+ --start;
+ haveTwoColons = false;
+ }
+
+ // XXX jjaschke: this parsing algorithm should be replaced by the css parser
+ // for correct handling of all edge cases. See Bug 1845712.
+ const int32_t parameterPosition = aPseudoElement.FindChar('(');
+ const bool hasParameter = parameterPosition != kNotFound;
+ if (hasParameter) {
+ end = start + parameterPosition - 1;
+ }
+ RefPtr<nsAtom> pseudo = NS_Atomize(Substring(start, end));
+ MOZ_ASSERT(pseudo);
+
+ Maybe<uint32_t> index = nsStaticAtomUtils::Lookup(pseudo, GetAtomBase(),
+ kAtomCount_PseudoElements);
+ if (index.isNothing()) {
+ return {};
+ }
+ auto type = static_cast<Type>(*index);
+ RefPtr<nsAtom> functionalPseudoParameter;
+ if (hasParameter) {
+ if (type != PseudoStyleType::highlight) {
+ return {};
+ }
+ functionalPseudoParameter =
+ [&aPseudoElement, parameterPosition]() -> already_AddRefed<nsAtom> {
+ const char16_t* start = aPseudoElement.BeginReading();
+ const char16_t* end = aPseudoElement.EndReading();
+ start += parameterPosition + 1;
+ --end;
+ if (*end != ')') {
+ return nullptr;
+ }
+ return NS_Atomize(Substring(start, end));
+ }();
+ }
+
+ if (!haveTwoColons &&
+ !PseudoElementHasFlags(type, CSS_PSEUDO_ELEMENT_IS_CSS2)) {
+ return {};
+ }
+ if (IsEnabled(type, aEnabledState)) {
+ return {Some(type), functionalPseudoParameter};
+ }
+ return {};
+}
+
+/* static */
+mozilla::Maybe<PseudoStyleType> nsCSSPseudoElements::GetPseudoType(
+ const nsAString& aPseudoElement, CSSEnabledState aEnabledState) {
+ auto [pseudoType, _] = ParsePseudoElement(aPseudoElement, aEnabledState);
+ return pseudoType;
+}
+
+/* static */
+bool nsCSSPseudoElements::PseudoElementSupportsUserActionState(
+ const Type aType) {
+ return PseudoElementHasFlags(aType,
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE);
+}
+
+/* static */
+nsString nsCSSPseudoElements::PseudoTypeAsString(Type aPseudoType) {
+ switch (aPseudoType) {
+ case PseudoStyleType::before:
+ return u"::before"_ns;
+ case PseudoStyleType::after:
+ return u"::after"_ns;
+ case PseudoStyleType::marker:
+ return u"::marker"_ns;
+ default:
+ MOZ_ASSERT(aPseudoType == PseudoStyleType::NotPseudo,
+ "Unexpected pseudo type");
+ return u""_ns;
+ }
+}
+
+#ifdef DEBUG
+/* static */
+void nsCSSPseudoElements::AssertAtoms() {
+ nsStaticAtom* base = GetAtomBase();
+# define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
+ { \
+ RefPtr<nsAtom> atom = NS_Atomize(value_); \
+ size_t index = static_cast<size_t>(PseudoStyleType::name_); \
+ MOZ_ASSERT(atom == nsGkAtoms::PseudoElement_##name_, \
+ "Static atom for " #name_ " has incorrect value"); \
+ MOZ_ASSERT(atom == &base[index], \
+ "Static atom for " #name_ " not at expected index"); \
+ }
+# include "nsCSSPseudoElementList.h"
+# undef CSS_PSEUDO_ELEMENT
+}
+#endif
diff --git a/layout/style/nsCSSPseudoElements.h b/layout/style/nsCSSPseudoElements.h
new file mode 100644
index 0000000000..9866fbfb7e
--- /dev/null
+++ b/layout/style/nsCSSPseudoElements.h
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* atom list for CSS pseudo-elements */
+
+#ifndef nsCSSPseudoElements_h___
+#define nsCSSPseudoElements_h___
+
+#include "nsGkAtoms.h"
+#include "mozilla/CSSEnabledState.h"
+#include "mozilla/PseudoStyleType.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+// Is this pseudo-element a CSS2 pseudo-element that can be specified
+// with the single colon syntax (in addition to the double-colon syntax,
+// which can be used for all pseudo-elements)?
+//
+// Note: We also rely on this for IsEagerlyCascadedInServo.
+#define CSS_PSEUDO_ELEMENT_IS_CSS2 (1 << 0)
+// Is this pseudo-element a pseudo-element that can contain other
+// elements?
+// (Currently pseudo-elements are either leaves of the tree (relative to
+// real elements) or they contain other elements in a non-tree-like
+// manner (i.e., like incorrectly-nested start and end tags). It's
+// possible that in the future there might be container pseudo-elements
+// that form a properly nested tree structure. If that happens, we
+// should probably split this flag into two.)
+#define CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS (1 << 1)
+// Flag to add the ability to take into account style attribute set for the
+// pseudo element (by default it's ignored).
+#define CSS_PSEUDO_ELEMENT_SUPPORTS_STYLE_ATTRIBUTE (1 << 2)
+// Flag that indicate the pseudo-element supports a user action pseudo-class
+// following it, such as :active or :hover. This would normally correspond
+// to whether the pseudo-element is tree-like, but we don't support these
+// pseudo-classes on ::before and ::after generated content yet. See
+// http://dev.w3.org/csswg/selectors4/#pseudo-elements.
+#define CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE (1 << 3)
+// Should this pseudo-element be enabled only for UA sheets?
+#define CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS (1 << 4)
+// Should this pseudo-element be enabled only for UA sheets and chrome
+// stylesheets?
+#define CSS_PSEUDO_ELEMENT_ENABLED_IN_CHROME (1 << 5)
+
+#define CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME \
+ (CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS | \
+ CSS_PSEUDO_ELEMENT_ENABLED_IN_CHROME)
+
+// Can we use the ChromeOnly document.createElement(..., { pseudo: "::foo" })
+// API for creating pseudo-implementing native anonymous content in JS with this
+// pseudo-element?
+#define CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC (1 << 6)
+// Does this pseudo-element act like an item for containers (such as flex and
+// grid containers) and thus needs parent display-based style fixup?
+#define CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM (1 << 7)
+
+class nsCSSPseudoElements {
+ typedef mozilla::PseudoStyleType Type;
+ typedef mozilla::CSSEnabledState EnabledState;
+
+ public:
+ static bool IsEagerlyCascadedInServo(const Type aType) {
+ return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_IS_CSS2);
+ }
+
+ public:
+#ifdef DEBUG
+ static void AssertAtoms();
+#endif
+
+// Alias nsCSSPseudoElements::foo() to nsGkAtoms::foo.
+#define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
+ static nsCSSPseudoElementStaticAtom* name_() { \
+ return const_cast<nsCSSPseudoElementStaticAtom*>( \
+ static_cast<const nsCSSPseudoElementStaticAtom*>( \
+ nsGkAtoms::PseudoElement_##name_)); \
+ }
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+
+ // Returns an empty tuple for a syntactically invalid pseudo-element, and
+ // NotPseudo for the empty / null string.
+ // The second element of the tuple (functional pseudo parameter) is currently
+ // only used for `::highlight()` pseudos and is `nullptr` otherwise.
+ static std::tuple<mozilla::Maybe<Type>, RefPtr<nsAtom>> ParsePseudoElement(
+ const nsAString& aPseudoElement,
+ EnabledState = EnabledState::ForAllContent);
+
+ // Returns Nothing() for a syntactically invalid pseudo-element, and NotPseudo
+ // for the empty / null string.
+ static mozilla::Maybe<Type> GetPseudoType(
+ const nsAString& aPseudoElement,
+ EnabledState = EnabledState::ForAllContent);
+
+ // Get the atom for a given Type. aType must be <
+ // PseudoType::CSSPseudoElementsEnd.
+ // This only ever returns static atoms, so it's fine to return a raw pointer.
+ static nsAtom* GetPseudoAtom(Type aType);
+
+ // Get the atom for a given pseudo-element string (e.g. "::before"). This can
+ // return dynamic atoms, for unrecognized pseudo-elements.
+ static already_AddRefed<nsAtom> GetPseudoAtom(
+ const nsAString& aPseudoElement);
+
+ static bool PseudoElementContainsElements(const Type aType) {
+ return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS);
+ }
+
+ static bool PseudoElementSupportsStyleAttribute(const Type aType) {
+ MOZ_ASSERT(aType < Type::CSSPseudoElementsEnd);
+ return PseudoElementHasFlags(aType,
+ CSS_PSEUDO_ELEMENT_SUPPORTS_STYLE_ATTRIBUTE);
+ }
+
+ static bool PseudoElementSupportsUserActionState(const Type aType);
+
+ static bool PseudoElementIsJSCreatedNAC(Type aType) {
+ return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC);
+ }
+
+ static bool PseudoElementIsFlexOrGridItem(const Type aType) {
+ return PseudoElementHasFlags(aType,
+ CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM);
+ }
+
+ static bool EnabledInContent(Type aType) {
+ switch (aType) {
+ case Type::highlight:
+ return mozilla::StaticPrefs::dom_customHighlightAPI_enabled();
+ case Type::sliderTrack:
+ case Type::sliderThumb:
+ case Type::sliderFill:
+ return mozilla::StaticPrefs::layout_css_modern_range_pseudos_enabled();
+ default:
+ return !PseudoElementHasAnyFlag(
+ aType, CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME);
+ }
+ }
+
+ static bool IsEnabled(Type aType, EnabledState aEnabledState) {
+ if (EnabledInContent(aType)) {
+ return true;
+ }
+
+ if ((aEnabledState & EnabledState::InUASheets) &&
+ PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS)) {
+ return true;
+ }
+
+ if ((aEnabledState & EnabledState::InChrome) &&
+ PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_ENABLED_IN_CHROME)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ static nsString PseudoTypeAsString(Type aPseudoType);
+
+ private:
+ // Does the given pseudo-element have all of the flags given?
+ static bool PseudoElementHasFlags(const Type aType, uint32_t aFlags) {
+ MOZ_ASSERT(aType < Type::CSSPseudoElementsEnd);
+ return (kPseudoElementFlags[size_t(aType)] & aFlags) == aFlags;
+ }
+
+ static bool PseudoElementHasAnyFlag(const Type aType, uint32_t aFlags) {
+ MOZ_ASSERT(aType < Type::CSSPseudoElementsEnd);
+ return (kPseudoElementFlags[size_t(aType)] & aFlags) != 0;
+ }
+
+ static nsStaticAtom* GetAtomBase();
+
+ static const uint32_t kPseudoElementFlags[size_t(Type::CSSPseudoElementsEnd)];
+};
+
+#endif /* nsCSSPseudoElements_h___ */
diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp
new file mode 100644
index 0000000000..5d815c25f2
--- /dev/null
+++ b/layout/style/nsCSSValue.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* representation of simple property values within CSS declarations */
+
+#include "nsCSSValue.h"
+
+#include "mozilla/CORSMode.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoTypes.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/ImageLoader.h"
+#include "gfxFontConstants.h"
+#include "imgRequestProxy.h"
+#include "mozilla/dom/Document.h"
+#include "nsCSSProps.h"
+#include "nsNetUtil.h"
+#include "nsPresContext.h"
+#include "nsStyleUtil.h"
+#include "nsDeviceContext.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+
+nsCSSValue::nsCSSValue(float aValue, nsCSSUnit aUnit) : mUnit(aUnit) {
+ MOZ_ASSERT(eCSSUnit_Null == aUnit, "not a float value");
+ mValue = aValue;
+ MOZ_ASSERT(!std::isnan(mValue));
+}
+
+nsCSSValue::nsCSSValue(const nsCSSValue& aCopy) : mUnit(aCopy.mUnit) {
+ if (eCSSUnit_Null != mUnit) {
+ mValue = aCopy.mValue;
+ MOZ_ASSERT(!std::isnan(mValue));
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unknown unit");
+ }
+}
+
+nsCSSValue& nsCSSValue::operator=(const nsCSSValue& aCopy) {
+ if (this != &aCopy) {
+ this->~nsCSSValue();
+ new (this) nsCSSValue(aCopy);
+ }
+ return *this;
+}
+
+nsCSSValue& nsCSSValue::operator=(nsCSSValue&& aOther) {
+ MOZ_ASSERT(this != &aOther, "Self assigment with rvalue reference");
+
+ Reset();
+ mUnit = aOther.mUnit;
+ mValue = aOther.mValue;
+ aOther.mUnit = eCSSUnit_Null;
+
+ return *this;
+}
+
+bool nsCSSValue::operator==(const nsCSSValue& aOther) const {
+ if (mUnit != aOther.mUnit) {
+ return false;
+ }
+ return mValue == aOther.mValue;
+}
+
+nscoord nsCSSValue::GetPixelLength() const {
+ MOZ_ASSERT(IsPixelLengthUnit(), "not a fixed length unit");
+
+ double scaleFactor;
+ switch (mUnit) {
+ case eCSSUnit_Pixel:
+ return nsPresContext::CSSPixelsToAppUnits(mValue);
+ case eCSSUnit_Pica:
+ scaleFactor = 16.0;
+ break;
+ case eCSSUnit_Point:
+ scaleFactor = 4 / 3.0;
+ break;
+ case eCSSUnit_Inch:
+ scaleFactor = 96.0;
+ break;
+ case eCSSUnit_Millimeter:
+ scaleFactor = 96 / 25.4;
+ break;
+ case eCSSUnit_Centimeter:
+ scaleFactor = 96 / 2.54;
+ break;
+ case eCSSUnit_Quarter:
+ scaleFactor = 96 / 101.6;
+ break;
+ default:
+ NS_ERROR("should never get here");
+ return 0;
+ }
+ return nsPresContext::CSSPixelsToAppUnits(float(mValue * scaleFactor));
+}
+
+void nsCSSValue::SetPercentValue(float aValue) {
+ Reset();
+ mUnit = eCSSUnit_Percent;
+ mValue = aValue;
+ MOZ_ASSERT(!std::isnan(mValue));
+}
+
+void nsCSSValue::SetFloatValue(float aValue, nsCSSUnit aUnit) {
+ MOZ_ASSERT(IsFloatUnit(aUnit), "not a float value");
+ Reset();
+ if (IsFloatUnit(aUnit)) {
+ mUnit = aUnit;
+ mValue = aValue;
+ MOZ_ASSERT(!std::isnan(mValue));
+ }
+}
diff --git a/layout/style/nsCSSValue.h b/layout/style/nsCSSValue.h
new file mode 100644
index 0000000000..bc8914bbc1
--- /dev/null
+++ b/layout/style/nsCSSValue.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* representation of simple property values within CSS declarations */
+
+#ifndef nsCSSValue_h___
+#define nsCSSValue_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/EnumTypeTraits.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/URLExtraData.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCoord.h"
+#include "nsTArray.h"
+
+#include <type_traits>
+
+// XXX Avoid including this here by moving function bodies to the cpp file
+#include "mozilla/FloatingPoint.h"
+
+class imgRequestProxy;
+class nsAtom;
+class nsIContent;
+
+class nsIPrincipal;
+class nsIURI;
+class nsPresContext;
+template <class T>
+class nsPtrHashKey;
+struct RawServoCssUrlData;
+
+namespace mozilla {
+class CSSStyleSheet;
+} // namespace mozilla
+
+// Forward declaration copied here since ServoBindings.h #includes nsCSSValue.h.
+extern "C" {
+mozilla::URLExtraData* Servo_CssUrlData_GetExtraData(const RawServoCssUrlData*);
+bool Servo_CssUrlData_IsLocalRef(const RawServoCssUrlData* url);
+}
+
+enum nsCSSUnit : uint32_t {
+ eCSSUnit_Null = 0, // (n/a) null unit, value is not specified
+
+ eCSSUnit_Percent = 100, // (1.0 == 100%) value is percentage of
+ // something
+ eCSSUnit_Number = 101, // value is numeric (usually multiplier,
+ // different behavior than percent)
+
+ // Font relative measure
+ eCSSUnit_EM = 800, // == current font size
+ eCSSUnit_XHeight = 801, // distance from top of lower case x to
+ // baseline
+ eCSSUnit_Char = 802, // number of characters, used for width with
+ // monospace font
+ eCSSUnit_RootEM = 803, // == root element font size
+ eCSSUnit_Ideographic = 804, // == CJK water ideograph width
+ eCSSUnit_CapHeight = 805, // == Capital letter height
+
+ // Screen relative measure
+ eCSSUnit_Point = 900, // 4/3 of a CSS pixel
+ eCSSUnit_Inch = 901, // 96 CSS pixels
+ eCSSUnit_Millimeter = 902, // 96/25.4 CSS pixels
+ eCSSUnit_Centimeter = 903, // 96/2.54 CSS pixels
+ eCSSUnit_Pica = 904, // 12 points == 16 CSS pixls
+ eCSSUnit_Quarter = 905, // 96/101.6 CSS pixels
+ eCSSUnit_Pixel = 906, // CSS pixel unit
+
+ // Viewport percentage lengths
+ eCSSUnit_VW = 950,
+ eCSSUnit_VH = 951,
+ eCSSUnit_VMin = 952,
+ eCSSUnit_VMax = 953,
+};
+
+struct nsCSSValuePair;
+struct nsCSSValuePair_heap;
+struct nsCSSValueList;
+struct nsCSSValueList_heap;
+struct nsCSSValueSharedList;
+struct nsCSSValuePairList;
+struct nsCSSValuePairList_heap;
+
+class nsCSSValue {
+ public:
+ explicit nsCSSValue() : mUnit(eCSSUnit_Null) {}
+
+ nsCSSValue(float aValue, nsCSSUnit aUnit);
+ nsCSSValue(const nsCSSValue& aCopy);
+ nsCSSValue(nsCSSValue&& aOther) : mUnit(aOther.mUnit), mValue(aOther.mValue) {
+ aOther.mUnit = eCSSUnit_Null;
+ }
+
+ nsCSSValue& operator=(const nsCSSValue& aCopy);
+ nsCSSValue& operator=(nsCSSValue&& aCopy);
+ bool operator==(const nsCSSValue& aOther) const;
+
+ bool operator!=(const nsCSSValue& aOther) const { return !(*this == aOther); }
+
+ nsCSSUnit GetUnit() const { return mUnit; }
+ bool IsLengthUnit() const {
+ return eCSSUnit_EM <= mUnit && mUnit <= eCSSUnit_Pixel;
+ }
+ /**
+ * A "pixel" length unit is a some multiple of CSS pixels.
+ */
+ static bool IsPixelLengthUnit(nsCSSUnit aUnit) {
+ return eCSSUnit_Point <= aUnit && aUnit <= eCSSUnit_Pixel;
+ }
+ bool IsPixelLengthUnit() const { return IsPixelLengthUnit(mUnit); }
+ static bool IsPercentLengthUnit(nsCSSUnit aUnit) {
+ return aUnit == eCSSUnit_Percent;
+ }
+ static bool IsFloatUnit(nsCSSUnit aUnit) { return eCSSUnit_Number <= aUnit; }
+
+ float GetPercentValue() const {
+ MOZ_ASSERT(mUnit == eCSSUnit_Percent, "not a percent value");
+ return mValue;
+ }
+
+ float GetFloatValue() const {
+ MOZ_ASSERT(eCSSUnit_Number <= mUnit, "not a float value");
+ MOZ_ASSERT(!std::isnan(mValue));
+ return mValue;
+ }
+
+ nscoord GetPixelLength() const;
+
+ void Reset() { mUnit = eCSSUnit_Null; }
+ ~nsCSSValue() { Reset(); }
+
+ public:
+ void SetPercentValue(float aValue);
+ void SetFloatValue(float aValue, nsCSSUnit aUnit);
+
+ protected:
+ nsCSSUnit mUnit;
+ float mValue;
+};
+
+#endif /* nsCSSValue_h___ */
diff --git a/layout/style/nsCSSVisitedDependentPropList.h b/layout/style/nsCSSVisitedDependentPropList.h
new file mode 100644
index 0000000000..e6a688369c
--- /dev/null
+++ b/layout/style/nsCSSVisitedDependentPropList.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 style struct's member variables which can be visited-dependent */
+
+/* This file contains the list of all style structs and fields that can
+ * be visited-dependent. Each entry is defined as a STYLE_STRUCT macro
+ * with the following parameters:
+ * - 'name_' the name of the style struct
+ * - 'fields_' the list of member variables in the style struct that can
+ * be visited-dependent
+ *
+ * Users of this list should define a macro STYLE_STRUCT(name_, fields_)
+ * before including this file.
+ *
+ * Note that, currently, there is a restriction that all fields in a
+ * each entry must have the same type, otherwise you need two entries.
+ */
+
+STYLE_STRUCT(Background, (mBackgroundColor))
+STYLE_STRUCT(Border, (mBorderTopColor,
+ mBorderRightColor,
+ mBorderBottomColor,
+ mBorderLeftColor))
+STYLE_STRUCT(Outline, (mOutlineColor))
+STYLE_STRUCT(Column, (mColumnRuleColor))
+STYLE_STRUCT(Text, (mColor))
+STYLE_STRUCT(Text, (mTextEmphasisColor,
+ mWebkitTextFillColor,
+ mWebkitTextStrokeColor))
+STYLE_STRUCT(TextReset, (mTextDecorationColor))
+STYLE_STRUCT(SVG, (mFill, mStroke))
+STYLE_STRUCT(UI, (mCaretColor))
diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp
new file mode 100644
index 0000000000..3b25ff9726
--- /dev/null
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -0,0 +1,2434 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object returned from element.getComputedStyle() */
+
+#include "nsComputedDOMStyle.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+#include "nsError.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "mozilla/ComputedStyle.h"
+#include "nsIScrollableFrame.h"
+#include "nsContentUtils.h"
+#include "nsDocShell.h"
+#include "nsIContent.h"
+#include "nsStyleConsts.h"
+
+#include "nsDOMCSSValueList.h"
+#include "nsFlexContainerFrame.h"
+#include "nsGridContainerFrame.h"
+#include "nsGkAtoms.h"
+#include "mozilla/ReflowInput.h"
+#include "nsStyleUtil.h"
+#include "nsStyleStructInlines.h"
+#include "nsROCSSPrimitiveValue.h"
+
+#include "nsPresContext.h"
+#include "mozilla/dom/Document.h"
+
+#include "nsCSSProps.h"
+#include "nsCSSPseudoElements.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ViewportFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsStyleTransformMatrix.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "prtime.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/AppUnits.h"
+#include <algorithm>
+#include "mozilla/ComputedStyleInlines.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/*
+ * This is the implementation of the readonly CSSStyleDeclaration that is
+ * returned by the getComputedStyle() function.
+ */
+
+already_AddRefed<nsComputedDOMStyle> NS_NewComputedDOMStyle(
+ dom::Element* aElement, const nsAString& aPseudoElt, Document* aDocument,
+ nsComputedDOMStyle::StyleType aStyleType, mozilla::ErrorResult&) {
+ auto [pseudo, functionalPseudoParameter] =
+ nsCSSPseudoElements::ParsePseudoElement(aPseudoElt,
+ CSSEnabledState::ForAllContent);
+ auto returnEmpty = nsComputedDOMStyle::AlwaysReturnEmptyStyle::No;
+ if (!pseudo) {
+ if (!aPseudoElt.IsEmpty() && aPseudoElt.First() == u':') {
+ returnEmpty = nsComputedDOMStyle::AlwaysReturnEmptyStyle::Yes;
+ }
+ pseudo.emplace(PseudoStyleType::NotPseudo);
+ }
+ RefPtr<nsComputedDOMStyle> computedStyle =
+ new nsComputedDOMStyle(aElement, *pseudo, functionalPseudoParameter,
+ aDocument, aStyleType, returnEmpty);
+ return computedStyle.forget();
+}
+
+static nsDOMCSSValueList* GetROCSSValueList(bool aCommaDelimited) {
+ return new nsDOMCSSValueList(aCommaDelimited);
+}
+
+static const Element* GetRenderedElement(const Element* aElement,
+ PseudoStyleType aPseudo) {
+ if (aPseudo == PseudoStyleType::NotPseudo) {
+ return aElement;
+ }
+ if (aPseudo == PseudoStyleType::before) {
+ return nsLayoutUtils::GetBeforePseudo(aElement);
+ }
+ if (aPseudo == PseudoStyleType::after) {
+ return nsLayoutUtils::GetAfterPseudo(aElement);
+ }
+ if (aPseudo == PseudoStyleType::marker) {
+ return nsLayoutUtils::GetMarkerPseudo(aElement);
+ }
+ return nullptr;
+}
+
+// Whether aDocument needs to restyle for aElement
+static bool ElementNeedsRestyle(Element* aElement, PseudoStyleType aPseudo,
+ bool aMayNeedToFlushLayout) {
+ const Document* doc = aElement->GetComposedDoc();
+ if (!doc) {
+ // If the element is out of the document we don't return styles for it, so
+ // nothing to do.
+ return false;
+ }
+
+ PresShell* presShell = doc->GetPresShell();
+ if (!presShell) {
+ // If there's no pres-shell we'll just either mint a new style from our
+ // caller document, or return no styles, so nothing to do (unless our owner
+ // element needs to get restyled, which could cause us to gain a pres shell,
+ // but the caller checks that).
+ return false;
+ }
+
+ // Unfortunately we don't know if the sheet change affects mElement or not, so
+ // just assume it will and that we need to flush normally.
+ ServoStyleSet* styleSet = presShell->StyleSet();
+ if (styleSet->StyleSheetsHaveChanged()) {
+ return true;
+ }
+
+ nsPresContext* presContext = presShell->GetPresContext();
+ MOZ_ASSERT(presContext);
+
+ // Pending media query updates can definitely change style on the element. For
+ // example, if you change the zoom factor and then call getComputedStyle, you
+ // should be able to observe the style with the new media queries.
+ //
+ // TODO(emilio): Does this need to also check the user font set? (it affects
+ // ch / ex units).
+ if (presContext->HasPendingMediaQueryUpdates()) {
+ // So gotta flush.
+ return true;
+ }
+
+ // If the pseudo-element is animating, make sure to flush.
+ if (aElement->MayHaveAnimations() && aPseudo != PseudoStyleType::NotPseudo &&
+ AnimationUtils::IsSupportedPseudoForAnimations(aPseudo)) {
+ if (EffectSet::Get(aElement, aPseudo)) {
+ return true;
+ }
+ }
+
+ // For Servo, we need to process the restyle-hint-invalidations first, to
+ // expand LaterSiblings hint, so that we can look whether ancestors need
+ // restyling.
+ RestyleManager* restyleManager = presContext->RestyleManager();
+ restyleManager->ProcessAllPendingAttributeAndStateInvalidations();
+
+ if (!presContext->EffectCompositor()->HasPendingStyleUpdates() &&
+ !doc->GetServoRestyleRoot()) {
+ return false;
+ }
+
+ // If there's a pseudo, we need to prefer that element, as the pseudo itself
+ // may have explicit restyles.
+ const Element* styledElement = GetRenderedElement(aElement, aPseudo);
+ // Try to skip the restyle otherwise.
+ return Servo_HasPendingRestyleAncestor(
+ styledElement ? styledElement : aElement, aMayNeedToFlushLayout);
+}
+
+/**
+ * An object that represents the ordered set of properties that are exposed on
+ * an nsComputedDOMStyle object and how their computed values can be obtained.
+ */
+struct ComputedStyleMap {
+ friend class nsComputedDOMStyle;
+
+ struct Entry {
+ // Create a pointer-to-member-function type.
+ using ComputeMethod = already_AddRefed<CSSValue> (nsComputedDOMStyle::*)();
+
+ nsCSSPropertyID mProperty;
+
+ // Whether the property can ever be exposed in getComputedStyle(). For
+ // example, @page descriptors implemented as CSS properties or other
+ // internal properties, would have this flag set to `false`.
+ bool mCanBeExposed = false;
+
+ ComputeMethod mGetter = nullptr;
+
+ bool IsEnumerable() const {
+ return IsEnabled() && !nsCSSProps::IsShorthand(mProperty);
+ }
+
+ bool IsEnabled() const {
+ if (!mCanBeExposed ||
+ !nsCSSProps::IsEnabled(mProperty, CSSEnabledState::ForAllContent)) {
+ return false;
+ }
+ if (nsCSSProps::IsShorthand(mProperty) &&
+ !StaticPrefs::layout_css_computed_style_shorthands()) {
+ return nsCSSProps::PropHasFlags(
+ mProperty, CSSPropFlags::ShorthandUnconditionallyExposedOnGetCS);
+ }
+ return true;
+ }
+ };
+
+ // This generated file includes definition of kEntries which is typed
+ // Entry[] and used below, so this #include has to be put here.
+#include "nsComputedDOMStyleGenerated.inc"
+
+ /**
+ * Returns the number of properties that should be exposed on an
+ * nsComputedDOMStyle, ecxluding any disabled properties.
+ */
+ uint32_t Length() {
+ Update();
+ return mEnumerablePropertyCount;
+ }
+
+ /**
+ * Returns the property at the given index in the list of properties
+ * that should be exposed on an nsComputedDOMStyle, excluding any
+ * disabled properties.
+ */
+ nsCSSPropertyID PropertyAt(uint32_t aIndex) {
+ Update();
+ return kEntries[EntryIndex(aIndex)].mProperty;
+ }
+
+ /**
+ * Searches for and returns the computed style map entry for the given
+ * property, or nullptr if the property is not exposed on nsComputedDOMStyle
+ * or is currently disabled.
+ */
+ const Entry* FindEntryForProperty(nsCSSPropertyID aPropID) {
+ if (size_t(aPropID) >= ArrayLength(kEntryIndices)) {
+ MOZ_ASSERT(aPropID == eCSSProperty_UNKNOWN);
+ return nullptr;
+ }
+ MOZ_ASSERT(kEntryIndices[aPropID] < ArrayLength(kEntries));
+ const auto& entry = kEntries[kEntryIndices[aPropID]];
+ if (!entry.IsEnabled()) {
+ return nullptr;
+ }
+ return &entry;
+ }
+
+ /**
+ * Records that mIndexMap needs updating, due to prefs changing that could
+ * affect the set of properties exposed on an nsComputedDOMStyle.
+ */
+ void MarkDirty() { mEnumerablePropertyCount = 0; }
+
+ // The member variables are public so that we can use an initializer in
+ // nsComputedDOMStyle::GetComputedStyleMap. Use the member functions
+ // above to get information from this object.
+
+ /**
+ * The number of properties that should be exposed on an nsComputedDOMStyle.
+ * This will be less than eComputedStyleProperty_COUNT if some property
+ * prefs are disabled. A value of 0 indicates that it and mIndexMap are out
+ * of date.
+ */
+ uint32_t mEnumerablePropertyCount = 0;
+
+ /**
+ * A map of indexes on the nsComputedDOMStyle object to indexes into kEntries.
+ */
+ uint32_t mIndexMap[ArrayLength(kEntries)];
+
+ private:
+ /**
+ * Returns whether mEnumerablePropertyCount and mIndexMap are out of date.
+ */
+ bool IsDirty() { return mEnumerablePropertyCount == 0; }
+
+ /**
+ * Updates mEnumerablePropertyCount and mIndexMap to take into account
+ * properties whose prefs are currently disabled.
+ */
+ void Update();
+
+ /**
+ * Maps an nsComputedDOMStyle indexed getter index to an index into kEntries.
+ */
+ uint32_t EntryIndex(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mEnumerablePropertyCount);
+ return mIndexMap[aIndex];
+ }
+};
+
+constexpr ComputedStyleMap::Entry
+ ComputedStyleMap::kEntries[ArrayLength(kEntries)];
+
+constexpr size_t ComputedStyleMap::kEntryIndices[ArrayLength(kEntries)];
+
+void ComputedStyleMap::Update() {
+ if (!IsDirty()) {
+ return;
+ }
+
+ uint32_t index = 0;
+ for (uint32_t i = 0; i < ArrayLength(kEntries); i++) {
+ if (kEntries[i].IsEnumerable()) {
+ mIndexMap[index++] = i;
+ }
+ }
+ mEnumerablePropertyCount = index;
+}
+
+nsComputedDOMStyle::nsComputedDOMStyle(dom::Element* aElement,
+ PseudoStyleType aPseudo,
+ nsAtom* aFunctionalPseudoParameter,
+ Document* aDocument,
+ StyleType aStyleType,
+ AlwaysReturnEmptyStyle aAlwaysEmpty)
+ : mDocumentWeak(nullptr),
+ mOuterFrame(nullptr),
+ mInnerFrame(nullptr),
+ mPresShell(nullptr),
+ mPseudo(aPseudo),
+ mFunctionalPseudoParameter(aFunctionalPseudoParameter),
+ mStyleType(aStyleType),
+ mAlwaysReturnEmpty(aAlwaysEmpty) {
+ MOZ_ASSERT(aElement);
+ MOZ_ASSERT(aDocument);
+ // TODO(emilio, bug 548397, https://github.com/w3c/csswg-drafts/issues/2403):
+ // Should use aElement->OwnerDoc() instead.
+ mDocumentWeak = aDocument;
+ mElement = aElement;
+ SetEnabledCallbacks(nsIMutationObserver::kParentChainChanged);
+}
+
+nsComputedDOMStyle::~nsComputedDOMStyle() {
+ MOZ_ASSERT(!mResolvedComputedStyle,
+ "Should have called ClearComputedStyle() during last release.");
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsComputedDOMStyle)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsComputedDOMStyle)
+ tmp->ClearComputedStyle(); // remove observer before clearing mElement
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsComputedDOMStyle)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// We can skip the nsComputedDOMStyle if it has no wrapper and its
+// element is skippable, because it will have no outgoing edges, so
+// it can't be part of a cycle.
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsComputedDOMStyle)
+ if (!tmp->GetWrapperPreserveColor()) {
+ return !tmp->mElement ||
+ mozilla::dom::FragmentOrElement::CanSkip(tmp->mElement, true);
+ }
+ return tmp->HasKnownLiveWrapper();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsComputedDOMStyle)
+ if (!tmp->GetWrapperPreserveColor()) {
+ return !tmp->mElement ||
+ mozilla::dom::FragmentOrElement::CanSkipInCC(tmp->mElement);
+ }
+ return tmp->HasKnownLiveWrapper();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsComputedDOMStyle)
+ if (!tmp->GetWrapperPreserveColor()) {
+ return !tmp->mElement ||
+ mozilla::dom::FragmentOrElement::CanSkipThis(tmp->mElement);
+ }
+ return tmp->HasKnownLiveWrapper();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+// QueryInterface implementation for nsComputedDOMStyle
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsComputedDOMStyle)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsComputedDOMStyle)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsComputedDOMStyle,
+ ClearComputedStyle())
+
+void nsComputedDOMStyle::GetPropertyValue(const nsCSSPropertyID aPropID,
+ nsACString& aValue) {
+ return GetPropertyValue(aPropID, EmptyCString(), aValue);
+}
+
+void nsComputedDOMStyle::SetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsACString& aValue,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv) {
+ aRv.ThrowNoModificationAllowedError(nsPrintfCString(
+ "Can't set value for property '%s' in computed style",
+ PromiseFlatCString(nsCSSProps::GetStringValue(aPropID)).get()));
+}
+
+void nsComputedDOMStyle::GetCssText(nsACString& aCssText) {
+ aCssText.Truncate();
+}
+
+void nsComputedDOMStyle::SetCssText(const nsACString& aCssText,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv) {
+ aRv.ThrowNoModificationAllowedError("Can't set cssText on computed style");
+}
+
+uint32_t nsComputedDOMStyle::Length() {
+ // Make sure we have up to date style so that we can include custom
+ // properties.
+ UpdateCurrentStyleSources(eCSSPropertyExtra_variable);
+ if (!mComputedStyle) {
+ return 0;
+ }
+
+ uint32_t length = GetComputedStyleMap()->Length() +
+ Servo_GetCustomPropertiesCount(mComputedStyle);
+
+ ClearCurrentStyleSources();
+
+ return length;
+}
+
+css::Rule* nsComputedDOMStyle::GetParentRule() { return nullptr; }
+
+void nsComputedDOMStyle::GetPropertyValue(const nsACString& aPropertyName,
+ nsACString& aReturn) {
+ nsCSSPropertyID prop = nsCSSProps::LookupProperty(aPropertyName);
+ GetPropertyValue(prop, aPropertyName, aReturn);
+}
+
+void nsComputedDOMStyle::GetPropertyValue(
+ nsCSSPropertyID aPropID, const nsACString& aMaybeCustomPropertyName,
+ nsACString& aReturn) {
+ MOZ_ASSERT(aReturn.IsEmpty());
+
+ const ComputedStyleMap::Entry* entry = nullptr;
+ if (aPropID != eCSSPropertyExtra_variable) {
+ entry = GetComputedStyleMap()->FindEntryForProperty(aPropID);
+ if (!entry) {
+ return;
+ }
+ }
+
+ UpdateCurrentStyleSources(aPropID);
+ if (!mComputedStyle) {
+ return;
+ }
+
+ auto cleanup = mozilla::MakeScopeExit([&] { ClearCurrentStyleSources(); });
+
+ if (!entry) {
+ MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aMaybeCustomPropertyName));
+ const nsACString& name =
+ Substring(aMaybeCustomPropertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
+ Servo_GetCustomPropertyValue(
+ mComputedStyle, mPresShell->StyleSet()->RawData(), &name, &aReturn);
+ return;
+ }
+
+ if (nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::IsLogical)) {
+ MOZ_ASSERT(entry);
+ MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter);
+
+ DebugOnly<nsCSSPropertyID> logicalProp = aPropID;
+
+ aPropID = Servo_ResolveLogicalProperty(aPropID, mComputedStyle);
+ entry = GetComputedStyleMap()->FindEntryForProperty(aPropID);
+
+ MOZ_ASSERT(NeedsToFlushLayout(logicalProp) == NeedsToFlushLayout(aPropID),
+ "Logical and physical property don't agree on whether layout is "
+ "needed");
+ }
+
+ if (!nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::SerializedByServo)) {
+ if (RefPtr<CSSValue> value = (this->*entry->mGetter)()) {
+ nsAutoString text;
+ value->GetCssText(text);
+ CopyUTF16toUTF8(text, aReturn);
+ }
+ return;
+ }
+
+ MOZ_ASSERT(entry->mGetter == &nsComputedDOMStyle::DummyGetter);
+ Servo_GetResolvedValue(mComputedStyle, aPropID,
+ mPresShell->StyleSet()->RawData(), mElement, &aReturn);
+}
+
+/* static */
+already_AddRefed<const ComputedStyle> nsComputedDOMStyle::GetComputedStyle(
+ Element* aElement, PseudoStyleType aPseudo,
+ nsAtom* aFunctionalPseudoParameter, StyleType aStyleType) {
+ if (Document* doc = aElement->GetComposedDoc()) {
+ doc->FlushPendingNotifications(FlushType::Style);
+ }
+ return GetComputedStyleNoFlush(aElement, aPseudo, aFunctionalPseudoParameter,
+ aStyleType);
+}
+
+/**
+ * The following function checks whether we need to explicitly resolve the style
+ * again, even though we have a style coming from the frame.
+ *
+ * This basically checks whether the style is or may be under a ::first-line or
+ * ::first-letter frame, in which case we can't return the frame style, and we
+ * need to resolve it. See bug 505515.
+ */
+static bool MustReresolveStyle(const ComputedStyle* aStyle) {
+ MOZ_ASSERT(aStyle);
+
+ // TODO(emilio): We may want to avoid re-resolving pseudo-element styles
+ // more often.
+ return aStyle->HasPseudoElementData() && !aStyle->IsPseudoElement();
+}
+
+static bool IsInFlatTree(const Element& aElement) {
+ const auto* topmost = &aElement;
+ while (true) {
+ if (topmost->HasServoData()) {
+ // If we have styled this element then we know it's in the flat tree.
+ return true;
+ }
+ const Element* parent = topmost->GetFlattenedTreeParentElement();
+ if (!parent) {
+ break;
+ }
+ topmost = parent;
+ }
+ auto* root = topmost->GetFlattenedTreeParentNode();
+ return root && root->IsDocument();
+}
+
+already_AddRefed<const ComputedStyle>
+nsComputedDOMStyle::DoGetComputedStyleNoFlush(
+ const Element* aElement, PseudoStyleType aPseudo,
+ nsAtom* aFunctionalPseudoParameter, PresShell* aPresShell,
+ StyleType aStyleType) {
+ MOZ_ASSERT(aElement, "NULL element");
+
+ // If the content has a pres shell, we must use it. Otherwise we'd
+ // potentially mix rule trees by using the wrong pres shell's style
+ // set. Using the pres shell from the content also means that any
+ // content that's actually *in* a document will get the style from the
+ // correct document.
+ PresShell* presShell = nsContentUtils::GetPresShellForContent(aElement);
+ bool inDocWithShell = true;
+ if (!presShell) {
+ inDocWithShell = false;
+ presShell = aPresShell;
+ if (!presShell) {
+ return nullptr;
+ }
+ }
+
+ MOZ_ASSERT(aPseudo == PseudoStyleType::NotPseudo ||
+ PseudoStyle::IsPseudoElement(aPseudo));
+ if (!aElement->IsInComposedDoc()) {
+ // Don't return styles for disconnected elements, that makes no sense. This
+ // can only happen with a non-null presShell for cross-document calls.
+ return nullptr;
+ }
+
+ if (!IsInFlatTree(*aElement)) {
+ return nullptr;
+ }
+
+ // XXX the !aElement->IsHTMLElement(nsGkAtoms::area)
+ // check is needed due to bug 135040 (to avoid using
+ // mPrimaryFrame). Remove it once that's fixed.
+ if (inDocWithShell && aStyleType == StyleType::All &&
+ !aElement->IsHTMLElement(nsGkAtoms::area)) {
+ if (const Element* element = GetRenderedElement(aElement, aPseudo)) {
+ if (element->HasServoData()) {
+ const ComputedStyle* result =
+ Servo_Element_GetMaybeOutOfDateStyle(element);
+ return do_AddRef(result);
+ }
+ }
+ }
+
+ // No frame has been created, or we have a pseudo, or we're looking
+ // for the default style, so resolve the style ourselves.
+ ServoStyleSet* styleSet = presShell->StyleSet();
+
+ StyleRuleInclusion rules = aStyleType == StyleType::DefaultOnly
+ ? StyleRuleInclusion::DefaultOnly
+ : StyleRuleInclusion::All;
+ RefPtr<ComputedStyle> result = styleSet->ResolveStyleLazily(
+ *aElement, aPseudo, aFunctionalPseudoParameter, rules);
+ return result.forget();
+}
+
+already_AddRefed<const ComputedStyle>
+nsComputedDOMStyle::GetUnanimatedComputedStyleNoFlush(
+ Element* aElement, PseudoStyleType aPseudo,
+ nsAtom* aFunctionalPseudoParameter) {
+ RefPtr<const ComputedStyle> style =
+ GetComputedStyleNoFlush(aElement, aPseudo, aFunctionalPseudoParameter);
+ if (!style) {
+ return nullptr;
+ }
+
+ PresShell* presShell = aElement->OwnerDoc()->GetPresShell();
+ MOZ_ASSERT(presShell,
+ "How in the world did we get a style a few lines above?");
+
+ Element* elementOrPseudoElement =
+ AnimationUtils::GetElementForRestyle(aElement, aPseudo);
+ if (!elementOrPseudoElement) {
+ return nullptr;
+ }
+
+ return presShell->StyleSet()->GetBaseContextForElement(elementOrPseudoElement,
+ style);
+}
+
+nsMargin nsComputedDOMStyle::GetAdjustedValuesForBoxSizing() {
+ // We want the width/height of whatever parts 'width' or 'height' controls,
+ // which can be different depending on the value of the 'box-sizing' property.
+ const nsStylePosition* stylePos = StylePosition();
+
+ nsMargin adjustment;
+ if (stylePos->mBoxSizing == StyleBoxSizing::Border) {
+ adjustment = mInnerFrame->GetUsedBorderAndPadding();
+ }
+
+ return adjustment;
+}
+
+static void AddImageURL(nsIURI& aURI, nsTArray<nsCString>& aURLs) {
+ nsCString spec;
+ nsresult rv = aURI.GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ aURLs.AppendElement(std::move(spec));
+}
+
+static void AddImageURL(const StyleComputedUrl& aURL,
+ nsTArray<nsCString>& aURLs) {
+ if (aURL.IsLocalRef()) {
+ return;
+ }
+
+ if (nsIURI* uri = aURL.GetURI()) {
+ AddImageURL(*uri, aURLs);
+ }
+}
+
+static void AddImageURL(const StyleImage& aImage, nsTArray<nsCString>& aURLs) {
+ if (auto* urlValue = aImage.GetImageRequestURLValue()) {
+ AddImageURL(*urlValue, aURLs);
+ }
+}
+
+static void AddImageURL(const StyleShapeOutside& aShapeOutside,
+ nsTArray<nsCString>& aURLs) {
+ if (aShapeOutside.IsImage()) {
+ AddImageURL(aShapeOutside.AsImage(), aURLs);
+ }
+}
+
+static void AddImageURL(const StyleClipPath& aClipPath,
+ nsTArray<nsCString>& aURLs) {
+ if (aClipPath.IsUrl()) {
+ AddImageURL(aClipPath.AsUrl(), aURLs);
+ }
+}
+
+static void AddImageURLs(const nsStyleImageLayers& aLayers,
+ nsTArray<nsCString>& aURLs) {
+ for (auto i : IntegerRange(aLayers.mLayers.Length())) {
+ AddImageURL(aLayers.mLayers[i].mImage, aURLs);
+ }
+}
+
+static void CollectImageURLsForProperty(nsCSSPropertyID aProp,
+ const ComputedStyle& aStyle,
+ nsTArray<nsCString>& aURLs) {
+ if (nsCSSProps::IsShorthand(aProp)) {
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProp,
+ CSSEnabledState::ForAllContent) {
+ CollectImageURLsForProperty(*p, aStyle, aURLs);
+ }
+ return;
+ }
+
+ switch (aProp) {
+ case eCSSProperty_cursor:
+ for (auto& image : aStyle.StyleUI()->Cursor().images.AsSpan()) {
+ AddImageURL(image.image, aURLs);
+ }
+ break;
+ case eCSSProperty_background_image:
+ AddImageURLs(aStyle.StyleBackground()->mImage, aURLs);
+ break;
+ case eCSSProperty_mask_clip:
+ AddImageURLs(aStyle.StyleSVGReset()->mMask, aURLs);
+ break;
+ case eCSSProperty_list_style_image: {
+ const auto& image = aStyle.StyleList()->mListStyleImage;
+ if (image.IsUrl()) {
+ AddImageURL(image.AsUrl(), aURLs);
+ }
+ break;
+ }
+ case eCSSProperty_border_image_source:
+ AddImageURL(aStyle.StyleBorder()->mBorderImageSource, aURLs);
+ break;
+ case eCSSProperty_clip_path:
+ AddImageURL(aStyle.StyleSVGReset()->mClipPath, aURLs);
+ break;
+ case eCSSProperty_shape_outside:
+ AddImageURL(aStyle.StyleDisplay()->mShapeOutside, aURLs);
+ break;
+ default:
+ break;
+ }
+}
+
+void nsComputedDOMStyle::GetCSSImageURLs(const nsACString& aPropertyName,
+ nsTArray<nsCString>& aImageURLs,
+ mozilla::ErrorResult& aRv) {
+ nsCSSPropertyID prop = nsCSSProps::LookupProperty(aPropertyName);
+ if (prop == eCSSProperty_UNKNOWN) {
+ // Note: not using nsPrintfCString here in case aPropertyName contains
+ // nulls.
+ aRv.ThrowSyntaxError("Invalid property name '"_ns + aPropertyName + "'"_ns);
+ return;
+ }
+
+ UpdateCurrentStyleSources(prop);
+
+ if (!mComputedStyle) {
+ return;
+ }
+
+ CollectImageURLsForProperty(prop, *mComputedStyle, aImageURLs);
+ ClearCurrentStyleSources();
+}
+
+// nsDOMCSSDeclaration abstract methods which should never be called
+// on a nsComputedDOMStyle object, but must be defined to avoid
+// compile errors.
+DeclarationBlock* nsComputedDOMStyle::GetOrCreateCSSDeclaration(
+ Operation aOperation, DeclarationBlock** aCreated) {
+ MOZ_CRASH("called nsComputedDOMStyle::GetCSSDeclaration");
+}
+
+nsresult nsComputedDOMStyle::SetCSSDeclaration(DeclarationBlock*,
+ MutationClosureData*) {
+ MOZ_CRASH("called nsComputedDOMStyle::SetCSSDeclaration");
+}
+
+Document* nsComputedDOMStyle::DocToUpdate() {
+ MOZ_CRASH("called nsComputedDOMStyle::DocToUpdate");
+}
+
+nsDOMCSSDeclaration::ParsingEnvironment
+nsComputedDOMStyle::GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal) const {
+ MOZ_CRASH("called nsComputedDOMStyle::GetParsingEnvironment");
+}
+
+void nsComputedDOMStyle::ClearComputedStyle() {
+ if (mResolvedComputedStyle) {
+ mResolvedComputedStyle = false;
+ mElement->RemoveMutationObserver(this);
+ }
+ mComputedStyle = nullptr;
+}
+
+void nsComputedDOMStyle::SetResolvedComputedStyle(
+ RefPtr<const ComputedStyle>&& aContext, uint64_t aGeneration) {
+ if (!mResolvedComputedStyle) {
+ mResolvedComputedStyle = true;
+ mElement->AddMutationObserver(this);
+ }
+ mComputedStyle = aContext;
+ mComputedStyleGeneration = aGeneration;
+ mPresShellId = mPresShell->GetPresShellId();
+}
+
+void nsComputedDOMStyle::SetFrameComputedStyle(mozilla::ComputedStyle* aStyle,
+ uint64_t aGeneration) {
+ ClearComputedStyle();
+ mComputedStyle = aStyle;
+ mComputedStyleGeneration = aGeneration;
+ mPresShellId = mPresShell->GetPresShellId();
+}
+
+static bool MayNeedToFlushLayout(nsCSSPropertyID aPropID) {
+ switch (aPropID) {
+ case eCSSProperty_width:
+ case eCSSProperty_height:
+ case eCSSProperty_block_size:
+ case eCSSProperty_inline_size:
+ case eCSSProperty_line_height:
+ case eCSSProperty_grid_template_rows:
+ case eCSSProperty_grid_template_columns:
+ case eCSSProperty_perspective_origin:
+ case eCSSProperty_transform_origin:
+ case eCSSProperty_transform:
+ case eCSSProperty_border_top_width:
+ case eCSSProperty_border_bottom_width:
+ case eCSSProperty_border_left_width:
+ case eCSSProperty_border_right_width:
+ case eCSSProperty_border_block_start_width:
+ case eCSSProperty_border_block_end_width:
+ case eCSSProperty_border_inline_start_width:
+ case eCSSProperty_border_inline_end_width:
+ case eCSSProperty_top:
+ case eCSSProperty_right:
+ case eCSSProperty_bottom:
+ case eCSSProperty_left:
+ case eCSSProperty_inset_block_start:
+ case eCSSProperty_inset_block_end:
+ case eCSSProperty_inset_inline_start:
+ case eCSSProperty_inset_inline_end:
+ case eCSSProperty_padding_top:
+ case eCSSProperty_padding_right:
+ case eCSSProperty_padding_bottom:
+ case eCSSProperty_padding_left:
+ case eCSSProperty_padding_block_start:
+ case eCSSProperty_padding_block_end:
+ case eCSSProperty_padding_inline_start:
+ case eCSSProperty_padding_inline_end:
+ case eCSSProperty_margin_top:
+ case eCSSProperty_margin_right:
+ case eCSSProperty_margin_bottom:
+ case eCSSProperty_margin_left:
+ case eCSSProperty_margin_block_start:
+ case eCSSProperty_margin_block_end:
+ case eCSSProperty_margin_inline_start:
+ case eCSSProperty_margin_inline_end:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool nsComputedDOMStyle::NeedsToFlushStyle(nsCSSPropertyID aPropID) const {
+ bool mayNeedToFlushLayout = MayNeedToFlushLayout(aPropID);
+
+ // We always compute styles from the element's owner document.
+ if (ElementNeedsRestyle(mElement, mPseudo, mayNeedToFlushLayout)) {
+ return true;
+ }
+
+ Document* doc = mElement->OwnerDoc();
+ // If parent document is there, also needs to check if there is some change
+ // that needs to flush this document (e.g. size change for iframe).
+ while (doc->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) {
+ if (Element* element = doc->GetEmbedderElement()) {
+ if (ElementNeedsRestyle(element, PseudoStyleType::NotPseudo,
+ mayNeedToFlushLayout)) {
+ return true;
+ }
+ }
+
+ doc = doc->GetInProcessParentDocument();
+ }
+
+ return false;
+}
+
+static bool IsNonReplacedInline(nsIFrame* aFrame) {
+ // FIXME: this should be IsInlineInsideStyle() since width/height
+ // doesn't apply to ruby boxes.
+ return aFrame->StyleDisplay()->IsInlineFlow() && !aFrame->IsReplaced() &&
+ !aFrame->IsFieldSetFrame() && !aFrame->IsBlockFrame() &&
+ !aFrame->IsScrollFrame() && !aFrame->IsColumnSetWrapperFrame();
+}
+
+static Side SideForPaddingOrMarginOrInsetProperty(nsCSSPropertyID aPropID) {
+ switch (aPropID) {
+ case eCSSProperty_top:
+ case eCSSProperty_margin_top:
+ case eCSSProperty_padding_top:
+ return eSideTop;
+ case eCSSProperty_right:
+ case eCSSProperty_margin_right:
+ case eCSSProperty_padding_right:
+ return eSideRight;
+ case eCSSProperty_bottom:
+ case eCSSProperty_margin_bottom:
+ case eCSSProperty_padding_bottom:
+ return eSideBottom;
+ case eCSSProperty_left:
+ case eCSSProperty_margin_left:
+ case eCSSProperty_padding_left:
+ return eSideLeft;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected property");
+ return eSideTop;
+ }
+}
+
+static bool PaddingNeedsUsedValue(const LengthPercentage& aValue,
+ const ComputedStyle& aStyle) {
+ return !aValue.ConvertsToLength() || aStyle.StyleDisplay()->HasAppearance();
+}
+
+bool nsComputedDOMStyle::NeedsToFlushLayout(nsCSSPropertyID aPropID) const {
+ MOZ_ASSERT(aPropID != eCSSProperty_UNKNOWN);
+ if (aPropID == eCSSPropertyExtra_variable) {
+ return false;
+ }
+ nsIFrame* outerFrame = GetOuterFrame();
+ if (!outerFrame) {
+ return false;
+ }
+ nsIFrame* frame = nsLayoutUtils::GetStyleFrame(outerFrame);
+ auto* style = frame->Style();
+ if (nsCSSProps::PropHasFlags(aPropID, CSSPropFlags::IsLogical)) {
+ aPropID = Servo_ResolveLogicalProperty(aPropID, style);
+ }
+
+ switch (aPropID) {
+ case eCSSProperty_width:
+ case eCSSProperty_height:
+ return !IsNonReplacedInline(frame);
+ case eCSSProperty_line_height:
+ return frame->StyleFont()->mLineHeight.IsMozBlockHeight();
+ case eCSSProperty_grid_template_rows:
+ case eCSSProperty_grid_template_columns:
+ return !!nsGridContainerFrame::GetGridContainerFrame(frame);
+ case eCSSProperty_perspective_origin:
+ return style->StyleDisplay()->mPerspectiveOrigin.HasPercent();
+ case eCSSProperty_transform_origin:
+ return style->StyleDisplay()->mTransformOrigin.HasPercent();
+ case eCSSProperty_transform:
+ return style->StyleDisplay()->mTransform.HasPercent();
+ case eCSSProperty_border_top_width:
+ case eCSSProperty_border_bottom_width:
+ case eCSSProperty_border_left_width:
+ case eCSSProperty_border_right_width:
+ // FIXME(emilio): This should return false per spec (bug 1551000), but
+ // themed borders don't make that easy, so for now flush for that case.
+ //
+ // TODO(emilio): If we make GetUsedBorder() stop returning 0 for an
+ // unreflowed frame, or something of that sort, then we can stop flushing
+ // layout for themed frames.
+ return style->StyleDisplay()->HasAppearance();
+ case eCSSProperty_top:
+ case eCSSProperty_right:
+ case eCSSProperty_bottom:
+ case eCSSProperty_left:
+ // Doing better than this is actually hard.
+ return style->StyleDisplay()->mPosition != StylePositionProperty::Static;
+ case eCSSProperty_padding_top:
+ case eCSSProperty_padding_right:
+ case eCSSProperty_padding_bottom:
+ case eCSSProperty_padding_left: {
+ Side side = SideForPaddingOrMarginOrInsetProperty(aPropID);
+ // Theming can override used padding, sigh.
+ //
+ // TODO(emilio): If we make GetUsedPadding() stop returning 0 for an
+ // unreflowed frame, or something of that sort, then we can stop flushing
+ // layout for themed frames.
+ return PaddingNeedsUsedValue(style->StylePadding()->mPadding.Get(side),
+ *style);
+ }
+ case eCSSProperty_margin_top:
+ case eCSSProperty_margin_right:
+ case eCSSProperty_margin_bottom:
+ case eCSSProperty_margin_left: {
+ // NOTE(emilio): This is dubious, but matches other browsers.
+ // See https://github.com/w3c/csswg-drafts/issues/2328
+ Side side = SideForPaddingOrMarginOrInsetProperty(aPropID);
+ return !style->StyleMargin()->mMargin.Get(side).ConvertsToLength();
+ }
+ default:
+ return false;
+ }
+}
+
+void nsComputedDOMStyle::Flush(Document& aDocument, FlushType aFlushType) {
+ MOZ_ASSERT(mElement->IsInComposedDoc());
+ MOZ_ASSERT(mDocumentWeak == &aDocument);
+
+ if (MOZ_UNLIKELY(&aDocument != mElement->OwnerDoc())) {
+ aDocument.FlushPendingNotifications(aFlushType);
+ }
+ // This performs the flush, and also guarantees that content-visibility:
+ // hidden elements get laid out, if needed.
+ mElement->GetPrimaryFrame(aFlushType);
+}
+
+nsIFrame* nsComputedDOMStyle::GetOuterFrame() const {
+ if (mPseudo == PseudoStyleType::NotPseudo) {
+ return mElement->GetPrimaryFrame();
+ }
+ nsAtom* property = nullptr;
+ if (mPseudo == PseudoStyleType::before) {
+ property = nsGkAtoms::beforePseudoProperty;
+ } else if (mPseudo == PseudoStyleType::after) {
+ property = nsGkAtoms::afterPseudoProperty;
+ } else if (mPseudo == PseudoStyleType::marker) {
+ property = nsGkAtoms::markerPseudoProperty;
+ }
+ if (!property) {
+ return nullptr;
+ }
+ auto* pseudo = static_cast<Element*>(mElement->GetProperty(property));
+ return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
+}
+
+void nsComputedDOMStyle::UpdateCurrentStyleSources(nsCSSPropertyID aPropID) {
+ nsCOMPtr<Document> document(mDocumentWeak);
+ if (!document) {
+ ClearComputedStyle();
+ return;
+ }
+
+ // We don't return styles for disconnected elements anymore, so don't go
+ // through the trouble of flushing or what not.
+ //
+ // TODO(emilio): We may want to return earlier for elements outside of the
+ // flat tree too: https://github.com/w3c/csswg-drafts/issues/1964
+ if (!mElement->IsInComposedDoc()) {
+ ClearComputedStyle();
+ return;
+ }
+
+ if (mAlwaysReturnEmpty == AlwaysReturnEmptyStyle::Yes) {
+ ClearComputedStyle();
+ return;
+ }
+
+ DebugOnly<bool> didFlush = false;
+ if (NeedsToFlushStyle(aPropID)) {
+ didFlush = true;
+ // We look at the frame in NeedsToFlushLayout, so flush frames, not only
+ // styles.
+ Flush(*document, FlushType::Frames);
+ }
+
+ if (NeedsToFlushLayout(aPropID)) {
+ MOZ_ASSERT(MayNeedToFlushLayout(aPropID));
+ didFlush = true;
+ Flush(*document, FlushType::Layout);
+#ifdef DEBUG
+ mFlushedPendingReflows = true;
+#endif
+ } else {
+#ifdef DEBUG
+ mFlushedPendingReflows = false;
+#endif
+ }
+
+ mPresShell = document->GetPresShell();
+ if (!mPresShell || !mPresShell->GetPresContext()) {
+ ClearComputedStyle();
+ return;
+ }
+
+ // We need to use GetUndisplayedRestyleGeneration instead of
+ // GetRestyleGeneration, because the caching of mComputedStyle is an
+ // optimization that is useful only for displayed elements.
+ // For undisplayed elements we need to take into account any DOM changes that
+ // might cause a restyle, because Servo will not increase the generation for
+ // undisplayed elements.
+ uint64_t currentGeneration =
+ mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration();
+
+ if (mComputedStyle && mComputedStyleGeneration == currentGeneration &&
+ mPresShellId == mPresShell->GetPresShellId()) {
+ // Our cached style is still valid.
+ return;
+ }
+
+ mComputedStyle = nullptr;
+
+ // XXX the !mElement->IsHTMLElement(nsGkAtoms::area) check is needed due to
+ // bug 135040 (to avoid using mPrimaryFrame). Remove it once that's fixed.
+ if (mStyleType == StyleType::All &&
+ !mElement->IsHTMLElement(nsGkAtoms::area)) {
+ mOuterFrame = GetOuterFrame();
+ mInnerFrame = mOuterFrame;
+ if (mOuterFrame) {
+ mInnerFrame = nsLayoutUtils::GetStyleFrame(mOuterFrame);
+ SetFrameComputedStyle(mInnerFrame->Style(), currentGeneration);
+ NS_ASSERTION(mComputedStyle, "Frame without style?");
+ }
+ }
+
+ if (!mComputedStyle || MustReresolveStyle(mComputedStyle)) {
+ PresShell* presShellForContent = mElement->OwnerDoc()->GetPresShell();
+ // Need to resolve a style.
+ RefPtr<const ComputedStyle> resolvedComputedStyle =
+ DoGetComputedStyleNoFlush(
+ mElement, mPseudo, mFunctionalPseudoParameter,
+ presShellForContent ? presShellForContent : mPresShell, mStyleType);
+ if (!resolvedComputedStyle) {
+ ClearComputedStyle();
+ return;
+ }
+
+ // No need to re-get the generation, even though GetComputedStyle
+ // will flush, since we flushed style at the top of this function.
+ // We don't need to check this if we only flushed the parent.
+ NS_ASSERTION(
+ !didFlush ||
+ currentGeneration ==
+ mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration(),
+ "why should we have flushed style again?");
+
+ SetResolvedComputedStyle(std::move(resolvedComputedStyle),
+ currentGeneration);
+ NS_ASSERTION(mPseudo != PseudoStyleType::NotPseudo ||
+ !mComputedStyle->HasPseudoElementData(),
+ "should not have pseudo-element data");
+ }
+
+ // mExposeVisitedStyle is set to true only by testing APIs that
+ // require chrome privilege.
+ MOZ_ASSERT(!mExposeVisitedStyle || nsContentUtils::IsCallerChrome(),
+ "mExposeVisitedStyle set incorrectly");
+ if (mExposeVisitedStyle && mComputedStyle->RelevantLinkVisited()) {
+ if (const auto* styleIfVisited = mComputedStyle->GetStyleIfVisited()) {
+ mComputedStyle = styleIfVisited;
+ }
+ }
+}
+
+void nsComputedDOMStyle::ClearCurrentStyleSources() {
+ // Release the current style if we got it off the frame.
+ //
+ // For a style we resolved, keep it around so that we can re-use it next time
+ // this object is queried, but not if it-s a re-resolved style because we were
+ // inside a pseudo-element.
+ if (!mResolvedComputedStyle || mOuterFrame) {
+ ClearComputedStyle();
+ }
+
+ mOuterFrame = nullptr;
+ mInnerFrame = nullptr;
+ mPresShell = nullptr;
+}
+
+void nsComputedDOMStyle::RemoveProperty(const nsACString& aPropertyName,
+ nsACString& aReturn, ErrorResult& aRv) {
+ // Note: not using nsPrintfCString here in case aPropertyName contains
+ // nulls.
+ aRv.ThrowNoModificationAllowedError("Can't remove property '"_ns +
+ aPropertyName +
+ "' from computed style"_ns);
+}
+
+void nsComputedDOMStyle::GetPropertyPriority(const nsACString& aPropertyName,
+ nsACString& aReturn) {
+ aReturn.Truncate();
+}
+
+void nsComputedDOMStyle::SetProperty(const nsACString& aPropertyName,
+ const nsACString& aValue,
+ const nsACString& aPriority,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv) {
+ // Note: not using nsPrintfCString here in case aPropertyName contains
+ // nulls.
+ aRv.ThrowNoModificationAllowedError("Can't set value for property '"_ns +
+ aPropertyName + "' in computed style"_ns);
+}
+
+void nsComputedDOMStyle::IndexedGetter(uint32_t aIndex, bool& aFound,
+ nsACString& aPropName) {
+ ComputedStyleMap* map = GetComputedStyleMap();
+ uint32_t length = map->Length();
+
+ if (aIndex < length) {
+ aFound = true;
+ aPropName.Assign(nsCSSProps::GetStringValue(map->PropertyAt(aIndex)));
+ return;
+ }
+
+ // Custom properties are exposed with indexed properties just after all
+ // of the built-in properties.
+ UpdateCurrentStyleSources(eCSSPropertyExtra_variable);
+ if (!mComputedStyle) {
+ aFound = false;
+ return;
+ }
+
+ uint32_t count = Servo_GetCustomPropertiesCount(mComputedStyle);
+
+ const uint32_t index = aIndex - length;
+ if (index < count) {
+ aFound = true;
+ aPropName.AssignLiteral("--");
+ if (nsAtom* atom = Servo_GetCustomPropertyNameAt(mComputedStyle, index)) {
+ aPropName.Append(nsAtomCString(atom));
+ }
+ } else {
+ aFound = false;
+ }
+
+ ClearCurrentStyleSources();
+}
+
+// Property getters...
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBottom() {
+ return GetOffsetWidthFor(eSideBottom);
+}
+
+static Position MaybeResolvePositionForTransform(const LengthPercentage& aX,
+ const LengthPercentage& aY,
+ nsIFrame* aInnerFrame) {
+ if (!aInnerFrame) {
+ return {aX, aY};
+ }
+ nsStyleTransformMatrix::TransformReferenceBox refBox(aInnerFrame);
+ CSSPoint p = nsStyleTransformMatrix::Convert2DPosition(aX, aY, refBox);
+ return {LengthPercentage::FromPixels(p.x), LengthPercentage::FromPixels(p.y)};
+}
+
+/* Convert the stored representation into a list of two values and then hand
+ * it back.
+ */
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetTransformOrigin() {
+ /* We need to build up a list of two values. We'll call them
+ * width and height.
+ */
+
+ /* Store things as a value list */
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ /* Now, get the values. */
+ const auto& origin = StyleDisplay()->mTransformOrigin;
+
+ RefPtr<nsROCSSPrimitiveValue> width = new nsROCSSPrimitiveValue;
+ auto position = MaybeResolvePositionForTransform(
+ origin.horizontal, origin.vertical, mInnerFrame);
+ SetValueToPosition(position, valueList);
+ if (!origin.depth.IsZero()) {
+ RefPtr<nsROCSSPrimitiveValue> depth = new nsROCSSPrimitiveValue;
+ depth->SetPixels(origin.depth.ToCSSPixels());
+ valueList->AppendCSSValue(depth.forget());
+ }
+ return valueList.forget();
+}
+
+/* Convert the stored representation into a list of two values and then hand
+ * it back.
+ */
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPerspectiveOrigin() {
+ /* We need to build up a list of two values. We'll call them
+ * width and height.
+ */
+
+ /* Store things as a value list */
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ /* Now, get the values. */
+ const auto& origin = StyleDisplay()->mPerspectiveOrigin;
+
+ auto position = MaybeResolvePositionForTransform(
+ origin.horizontal, origin.vertical, mInnerFrame);
+ SetValueToPosition(position, valueList);
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetTransform() {
+ const nsStyleDisplay* display = StyleDisplay();
+ return GetTransformValue(display->mTransform);
+}
+
+/* static */
+already_AddRefed<nsROCSSPrimitiveValue> nsComputedDOMStyle::MatrixToCSSValue(
+ const mozilla::gfx::Matrix4x4& matrix) {
+ bool is3D = !matrix.Is2D();
+
+ nsAutoString resultString(u"matrix"_ns);
+ if (is3D) {
+ resultString.AppendLiteral("3d");
+ }
+
+ resultString.Append('(');
+ resultString.AppendFloat(matrix._11);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._12);
+ resultString.AppendLiteral(", ");
+ if (is3D) {
+ resultString.AppendFloat(matrix._13);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._14);
+ resultString.AppendLiteral(", ");
+ }
+ resultString.AppendFloat(matrix._21);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._22);
+ resultString.AppendLiteral(", ");
+ if (is3D) {
+ resultString.AppendFloat(matrix._23);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._24);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._31);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._32);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._33);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._34);
+ resultString.AppendLiteral(", ");
+ }
+ resultString.AppendFloat(matrix._41);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._42);
+ if (is3D) {
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._43);
+ resultString.AppendLiteral(", ");
+ resultString.AppendFloat(matrix._44);
+ }
+ resultString.Append(')');
+
+ /* Create a value to hold our result. */
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ val->SetString(resultString);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMozOsxFontSmoothing() {
+ if (nsContentUtils::ShouldResistFingerprinting(
+ mPresShell->GetPresContext()->GetDocShell(),
+ RFPTarget::DOMStyleOsxFontSmoothing)) {
+ return nullptr;
+ }
+
+ nsAutoCString result;
+ mComputedStyle->GetComputedPropertyValue(eCSSProperty__moz_osx_font_smoothing,
+ result);
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetString(result);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetImageLayerPosition(
+ const nsStyleImageLayers& aLayers) {
+ if (aLayers.mPositionXCount != aLayers.mPositionYCount) {
+ // No value to return. We can't express this combination of
+ // values as a shorthand.
+ return nullptr;
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+ for (uint32_t i = 0, i_end = aLayers.mPositionXCount; i < i_end; ++i) {
+ RefPtr<nsDOMCSSValueList> itemList = GetROCSSValueList(false);
+
+ SetValueToPosition(aLayers.mLayers[i].mPosition, itemList);
+ valueList->AppendCSSValue(itemList.forget());
+ }
+
+ return valueList.forget();
+}
+
+void nsComputedDOMStyle::SetValueToPosition(const Position& aPosition,
+ nsDOMCSSValueList* aValueList) {
+ RefPtr<nsROCSSPrimitiveValue> valX = new nsROCSSPrimitiveValue;
+ SetValueToLengthPercentage(valX, aPosition.horizontal, false);
+ aValueList->AppendCSSValue(valX.forget());
+
+ RefPtr<nsROCSSPrimitiveValue> valY = new nsROCSSPrimitiveValue;
+ SetValueToLengthPercentage(valY, aPosition.vertical, false);
+ aValueList->AppendCSSValue(valY.forget());
+}
+
+enum class Brackets { No, Yes };
+
+static void AppendGridLineNames(nsACString& aResult,
+ Span<const StyleCustomIdent> aLineNames,
+ Brackets aBrackets) {
+ if (aLineNames.IsEmpty()) {
+ if (aBrackets == Brackets::Yes) {
+ aResult.AppendLiteral("[]");
+ }
+ return;
+ }
+ uint32_t numLines = aLineNames.Length();
+ if (aBrackets == Brackets::Yes) {
+ aResult.Append('[');
+ }
+ for (uint32_t i = 0;;) {
+ // TODO: Maybe use servo to do this and avoid the silly utf16->utf8 dance?
+ nsAutoString name;
+ nsStyleUtil::AppendEscapedCSSIdent(
+ nsDependentAtomString(aLineNames[i].AsAtom()), name);
+ AppendUTF16toUTF8(name, aResult);
+
+ if (++i == numLines) {
+ break;
+ }
+ aResult.Append(' ');
+ }
+ if (aBrackets == Brackets::Yes) {
+ aResult.Append(']');
+ }
+}
+
+static void AppendGridLineNames(nsDOMCSSValueList* aValueList,
+ Span<const StyleCustomIdent> aLineNames,
+ bool aSuppressEmptyList = true) {
+ if (aLineNames.IsEmpty() && aSuppressEmptyList) {
+ return;
+ }
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoCString lineNamesString;
+ AppendGridLineNames(lineNamesString, aLineNames, Brackets::Yes);
+ val->SetString(lineNamesString);
+ aValueList->AppendCSSValue(val.forget());
+}
+
+static void AppendGridLineNames(nsDOMCSSValueList* aValueList,
+ Span<const StyleCustomIdent> aLineNames1,
+ Span<const StyleCustomIdent> aLineNames2) {
+ if (aLineNames1.IsEmpty() && aLineNames2.IsEmpty()) {
+ return;
+ }
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoCString lineNamesString;
+ lineNamesString.Assign('[');
+ if (!aLineNames1.IsEmpty()) {
+ AppendGridLineNames(lineNamesString, aLineNames1, Brackets::No);
+ }
+ if (!aLineNames2.IsEmpty()) {
+ if (!aLineNames1.IsEmpty()) {
+ lineNamesString.Append(' ');
+ }
+ AppendGridLineNames(lineNamesString, aLineNames2, Brackets::No);
+ }
+ lineNamesString.Append(']');
+ val->SetString(lineNamesString);
+ aValueList->AppendCSSValue(val.forget());
+}
+
+void nsComputedDOMStyle::SetValueToTrackBreadth(
+ nsROCSSPrimitiveValue* aValue, const StyleTrackBreadth& aBreadth) {
+ using Tag = StyleTrackBreadth::Tag;
+ switch (aBreadth.tag) {
+ case Tag::MinContent:
+ return aValue->SetString("min-content");
+ case Tag::MaxContent:
+ return aValue->SetString("max-content");
+ case Tag::Auto:
+ return aValue->SetString("auto");
+ case Tag::Breadth:
+ return SetValueToLengthPercentage(aValue, aBreadth.AsBreadth(), true);
+ case Tag::Fr: {
+ nsAutoString tmpStr;
+ nsStyleUtil::AppendCSSNumber(aBreadth.AsFr(), tmpStr);
+ tmpStr.AppendLiteral("fr");
+ return aValue->SetString(tmpStr);
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown breadth value");
+ return;
+ }
+}
+
+already_AddRefed<nsROCSSPrimitiveValue> nsComputedDOMStyle::GetGridTrackBreadth(
+ const StyleTrackBreadth& aBreadth) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToTrackBreadth(val, aBreadth);
+ return val.forget();
+}
+
+already_AddRefed<nsROCSSPrimitiveValue> nsComputedDOMStyle::GetGridTrackSize(
+ const StyleTrackSize& aTrackSize) {
+ if (aTrackSize.IsFitContent()) {
+ // A fit-content() function.
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ MOZ_ASSERT(aTrackSize.AsFitContent().IsBreadth(),
+ "unexpected unit for fit-content() argument value");
+ SetValueFromFitContentFunction(val, aTrackSize.AsFitContent().AsBreadth());
+ return val.forget();
+ }
+
+ if (aTrackSize.IsBreadth()) {
+ return GetGridTrackBreadth(aTrackSize.AsBreadth());
+ }
+
+ MOZ_ASSERT(aTrackSize.IsMinmax());
+ const auto& min = aTrackSize.AsMinmax()._0;
+ const auto& max = aTrackSize.AsMinmax()._1;
+ if (min == max) {
+ return GetGridTrackBreadth(min);
+ }
+
+ // minmax(auto, <flex>) is equivalent to (and is our internal representation
+ // of) <flex>, and both compute to <flex>
+ if (min.IsAuto() && max.IsFr()) {
+ return GetGridTrackBreadth(max);
+ }
+
+ nsAutoString argumentStr, minmaxStr;
+ minmaxStr.AppendLiteral("minmax(");
+
+ {
+ RefPtr<nsROCSSPrimitiveValue> argValue = GetGridTrackBreadth(min);
+ argValue->GetCssText(argumentStr);
+ minmaxStr.Append(argumentStr);
+ argumentStr.Truncate();
+ }
+
+ minmaxStr.AppendLiteral(", ");
+
+ {
+ RefPtr<nsROCSSPrimitiveValue> argValue = GetGridTrackBreadth(max);
+ argValue->GetCssText(argumentStr);
+ minmaxStr.Append(argumentStr);
+ }
+
+ minmaxStr.Append(char16_t(')'));
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetString(minmaxStr);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::GetGridTemplateColumnsRows(
+ const StyleGridTemplateComponent& aTrackList,
+ const ComputedGridTrackInfo& aTrackInfo) {
+ if (aTrackInfo.mIsMasonry) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetString("masonry");
+ return val.forget();
+ }
+
+ if (aTrackInfo.mIsSubgrid) {
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ RefPtr<nsROCSSPrimitiveValue> subgridKeyword = new nsROCSSPrimitiveValue;
+ subgridKeyword->SetString("subgrid");
+ valueList->AppendCSSValue(subgridKeyword.forget());
+ for (const auto& lineNames : aTrackInfo.mResolvedLineNames) {
+ AppendGridLineNames(valueList, lineNames, /*aSuppressEmptyList*/ false);
+ }
+ uint32_t line = aTrackInfo.mResolvedLineNames.Length();
+ uint32_t lastLine = aTrackInfo.mNumExplicitTracks + 1;
+ const Span<const StyleCustomIdent> empty;
+ for (; line < lastLine; ++line) {
+ AppendGridLineNames(valueList, empty, /*aSuppressEmptyList*/ false);
+ }
+ return valueList.forget();
+ }
+
+ const bool serializeImplicit =
+ StaticPrefs::layout_css_serialize_grid_implicit_tracks();
+
+ const nsTArray<nscoord>& trackSizes = aTrackInfo.mSizes;
+ const uint32_t numExplicitTracks = aTrackInfo.mNumExplicitTracks;
+ const uint32_t numLeadingImplicitTracks =
+ aTrackInfo.mNumLeadingImplicitTracks;
+ uint32_t numSizes = trackSizes.Length();
+ MOZ_ASSERT(numSizes >= numLeadingImplicitTracks + numExplicitTracks);
+
+ const bool hasTracksToSerialize =
+ serializeImplicit ? !!numSizes : !!numExplicitTracks;
+ const bool hasRepeatAuto = aTrackList.HasRepeatAuto();
+ if (!hasTracksToSerialize && !hasRepeatAuto) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetString("none");
+ return val.forget();
+ }
+
+ // We've done layout on the grid and have resolved the sizes of its tracks,
+ // so we'll return those sizes here. The grid spec says we MAY use
+ // repeat(<positive-integer>, Npx) here for consecutive tracks with the same
+ // size, but that doesn't seem worth doing since even for repeat(auto-*)
+ // the resolved size might differ for the repeated tracks.
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ // Add any leading implicit tracks.
+ if (serializeImplicit) {
+ for (uint32_t i = 0; i < numLeadingImplicitTracks; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(trackSizes[i]);
+ valueList->AppendCSSValue(val.forget());
+ }
+ }
+
+ if (hasRepeatAuto) {
+ const auto* const autoRepeatValue = aTrackList.GetRepeatAutoValue();
+ const auto repeatLineNames = autoRepeatValue->line_names.AsSpan();
+ MOZ_ASSERT(repeatLineNames.Length() >= 2);
+ // Number of tracks inside the repeat, not including any repetitions.
+ // Check that if we have truncated the number of tracks due to overflowing
+ // the maximum track limit then we also truncate this repeat count.
+ MOZ_ASSERT(repeatLineNames.Length() ==
+ autoRepeatValue->track_sizes.len + 1);
+ // If we have truncated the first repetition of repeat tracks, then we
+ // can't index using autoRepeatValue->track_sizes.len, and
+ // aTrackInfo.mRemovedRepeatTracks.Length() will account for all repeat
+ // tracks that haven't been truncated.
+ const uint32_t numRepeatTracks =
+ std::min(aTrackInfo.mRemovedRepeatTracks.Length(),
+ autoRepeatValue->track_sizes.len);
+ MOZ_ASSERT(repeatLineNames.Length() >= numRepeatTracks + 1);
+ // The total of all tracks in all repetitions of the repeat.
+ const uint32_t totalNumRepeatTracks =
+ aTrackInfo.mRemovedRepeatTracks.Length();
+ const uint32_t repeatStart = aTrackInfo.mRepeatFirstTrack;
+ // We need to skip over any track sizes which were resolved to 0 by
+ // collapsed tracks. Keep track of the iteration separately.
+ const auto explicitTrackSizeBegin =
+ trackSizes.cbegin() + numLeadingImplicitTracks;
+ const auto explicitTrackSizeEnd =
+ explicitTrackSizeBegin + numExplicitTracks;
+ auto trackSizeIter = explicitTrackSizeBegin;
+ // Write any leading explicit tracks before the repeat.
+ for (uint32_t i = 0; i < repeatStart; i++) {
+ AppendGridLineNames(valueList, aTrackInfo.mResolvedLineNames[i]);
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(*trackSizeIter++);
+ valueList->AppendCSSValue(val.forget());
+ }
+ auto lineNameIter = aTrackInfo.mResolvedLineNames.cbegin() + repeatStart;
+ // Write the track names at the start of the repeat, including the names
+ // at the end of the last non-repeat track. Unlike all later repeat line
+ // name lists, this one needs the resolved line name which includes both
+ // the last non-repeat line names and the leading repeat line names.
+ AppendGridLineNames(valueList, *lineNameIter++);
+ {
+ // Write out the first repeat value, checking for size zero (removed
+ // track).
+ const nscoord firstRepeatTrackSize =
+ (!aTrackInfo.mRemovedRepeatTracks[0]) ? *trackSizeIter++ : 0;
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(firstRepeatTrackSize);
+ valueList->AppendCSSValue(val.forget());
+ }
+ // Write the line names and track sizes inside the repeat, checking for
+ // removed tracks (size 0).
+ for (uint32_t i = 1; i < totalNumRepeatTracks; i++) {
+ const uint32_t repeatIndex = i % numRepeatTracks;
+ // If we are rolling over from one repetition to the next, include track
+ // names from both the end of the previous repeat and the start of the
+ // next.
+ if (repeatIndex == 0) {
+ AppendGridLineNames(valueList,
+ repeatLineNames[numRepeatTracks].AsSpan(),
+ repeatLineNames[0].AsSpan());
+ } else {
+ AppendGridLineNames(valueList, repeatLineNames[repeatIndex].AsSpan());
+ }
+ MOZ_ASSERT(aTrackInfo.mRemovedRepeatTracks[i] ||
+ trackSizeIter != explicitTrackSizeEnd);
+ const nscoord repeatTrackSize =
+ (!aTrackInfo.mRemovedRepeatTracks[i]) ? *trackSizeIter++ : 0;
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(repeatTrackSize);
+ valueList->AppendCSSValue(val.forget());
+ }
+ // The resolved line names include a single repetition of the auto-repeat
+ // line names. Skip over those.
+ lineNameIter += numRepeatTracks - 1;
+ // Write out any more tracks after the repeat.
+ while (trackSizeIter != explicitTrackSizeEnd) {
+ AppendGridLineNames(valueList, *lineNameIter++);
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(*trackSizeIter++);
+ valueList->AppendCSSValue(val.forget());
+ }
+ // Write the final trailing line name.
+ AppendGridLineNames(valueList, *lineNameIter++);
+ } else if (numExplicitTracks > 0) {
+ // If there are explicit tracks but no repeat tracks, just serialize those.
+ for (uint32_t i = 0;; i++) {
+ AppendGridLineNames(valueList, aTrackInfo.mResolvedLineNames[i]);
+ if (i == numExplicitTracks) {
+ break;
+ }
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(trackSizes[i + numLeadingImplicitTracks]);
+ valueList->AppendCSSValue(val.forget());
+ }
+ }
+ // Add any trailing implicit tracks.
+ if (serializeImplicit) {
+ for (uint32_t i = numLeadingImplicitTracks + numExplicitTracks;
+ i < numSizes; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(trackSizes[i]);
+ valueList->AppendCSSValue(val.forget());
+ }
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetGridTemplateColumns() {
+ nsGridContainerFrame* gridFrame =
+ nsGridContainerFrame::GetGridFrameWithComputedInfo(mInnerFrame);
+ if (!gridFrame) {
+ // The element doesn't have a box - return the computed value.
+ // https://drafts.csswg.org/css-grid/#resolved-track-list
+ nsAutoCString string;
+ mComputedStyle->GetComputedPropertyValue(eCSSProperty_grid_template_columns,
+ string);
+ RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
+ value->SetString(string);
+ return value.forget();
+ }
+
+ // GetGridFrameWithComputedInfo() above ensures that this returns non-null:
+ const ComputedGridTrackInfo* info = gridFrame->GetComputedTemplateColumns();
+ return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateColumns,
+ *info);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetGridTemplateRows() {
+ nsGridContainerFrame* gridFrame =
+ nsGridContainerFrame::GetGridFrameWithComputedInfo(mInnerFrame);
+ if (!gridFrame) {
+ // The element doesn't have a box - return the computed value.
+ // https://drafts.csswg.org/css-grid/#resolved-track-list
+ nsAutoCString string;
+ mComputedStyle->GetComputedPropertyValue(eCSSProperty_grid_template_rows,
+ string);
+ RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
+ value->SetString(string);
+ return value.forget();
+ }
+
+ // GetGridFrameWithComputedInfo() above ensures that this returns non-null:
+ const ComputedGridTrackInfo* info = gridFrame->GetComputedTemplateRows();
+ return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateRows, *info);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPaddingTop() {
+ return GetPaddingWidthFor(eSideTop);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPaddingBottom() {
+ return GetPaddingWidthFor(eSideBottom);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPaddingLeft() {
+ return GetPaddingWidthFor(eSideLeft);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetPaddingRight() {
+ return GetPaddingWidthFor(eSideRight);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderSpacing() {
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ RefPtr<nsROCSSPrimitiveValue> xSpacing = new nsROCSSPrimitiveValue;
+ RefPtr<nsROCSSPrimitiveValue> ySpacing = new nsROCSSPrimitiveValue;
+
+ const nsStyleTableBorder* border = StyleTableBorder();
+ xSpacing->SetAppUnits(border->mBorderSpacingCol);
+ ySpacing->SetAppUnits(border->mBorderSpacingRow);
+
+ valueList->AppendCSSValue(xSpacing.forget());
+ valueList->AppendCSSValue(ySpacing.forget());
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderTopWidth() {
+ return GetBorderWidthFor(eSideTop);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderBottomWidth() {
+ return GetBorderWidthFor(eSideBottom);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderLeftWidth() {
+ return GetBorderWidthFor(eSideLeft);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetBorderRightWidth() {
+ return GetBorderWidthFor(eSideRight);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMarginTop() {
+ return GetMarginFor(eSideTop);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMarginBottom() {
+ return GetMarginFor(eSideBottom);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMarginLeft() {
+ return GetMarginFor(eSideLeft);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMarginRight() {
+ return GetMarginFor(eSideRight);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetHeight() {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ if (mInnerFrame && !IsNonReplacedInline(mInnerFrame)) {
+ AssertFlushedPendingReflows();
+ nsMargin adjustedValues = GetAdjustedValuesForBoxSizing();
+ val->SetAppUnits(mInnerFrame->GetContentRect().height +
+ adjustedValues.TopBottom());
+ } else {
+ SetValueToSize(val, StylePosition()->mHeight);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetWidth() {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ if (mInnerFrame && !IsNonReplacedInline(mInnerFrame)) {
+ AssertFlushedPendingReflows();
+ nsMargin adjustedValues = GetAdjustedValuesForBoxSizing();
+ val->SetAppUnits(mInnerFrame->GetContentRect().width +
+ adjustedValues.LeftRight());
+ } else {
+ SetValueToSize(val, StylePosition()->mWidth);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMaxHeight() {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToMaxSize(val, StylePosition()->mMaxHeight);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMaxWidth() {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToMaxSize(val, StylePosition()->mMaxWidth);
+ return val.forget();
+}
+
+/**
+ * This function indicates whether we should return "auto" as the
+ * getComputedStyle() result for the (default) "min-width: auto" and
+ * "min-height: auto" CSS values.
+ *
+ * As of this writing, the CSS Sizing draft spec says this "auto" value
+ * *always* computes to itself. However, for now, we only make it compute to
+ * itself for grid and flex items (the containers where "auto" has special
+ * significance), because those are the only areas where the CSSWG has actually
+ * resolved on this "computes-to-itself" behavior. For elements in other sorts
+ * of containers, this function returns false, which will make us resolve
+ * "auto" to 0.
+ */
+bool nsComputedDOMStyle::ShouldHonorMinSizeAutoInAxis(PhysicalAxis aAxis) {
+ return mOuterFrame && mOuterFrame->IsFlexOrGridItem();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMinHeight() {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ StyleSize minHeight = StylePosition()->mMinHeight;
+
+ if (minHeight.IsAuto() && !ShouldHonorMinSizeAutoInAxis(eAxisVertical)) {
+ minHeight = StyleSize::LengthPercentage(LengthPercentage::Zero());
+ }
+
+ SetValueToSize(val, minHeight);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMinWidth() {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ StyleSize minWidth = StylePosition()->mMinWidth;
+
+ if (minWidth.IsAuto() && !ShouldHonorMinSizeAutoInAxis(eAxisHorizontal)) {
+ minWidth = StyleSize::LengthPercentage(LengthPercentage::Zero());
+ }
+
+ SetValueToSize(val, minWidth);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetLeft() {
+ return GetOffsetWidthFor(eSideLeft);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetRight() {
+ return GetOffsetWidthFor(eSideRight);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetTop() {
+ return GetOffsetWidthFor(eSideTop);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::GetOffsetWidthFor(
+ mozilla::Side aSide) {
+ const nsStyleDisplay* display = StyleDisplay();
+
+ mozilla::StylePositionProperty position = display->mPosition;
+ if (!mOuterFrame) {
+ // GetNonStaticPositionOffset or GetAbsoluteOffset don't handle elements
+ // without frames in any sensible way. GetStaticOffset, however, is perfect
+ // for that case.
+ position = StylePositionProperty::Static;
+ }
+
+ switch (position) {
+ case StylePositionProperty::Static:
+ return GetStaticOffset(aSide);
+ case StylePositionProperty::Sticky:
+ return GetNonStaticPositionOffset(
+ aSide, false, &nsComputedDOMStyle::GetScrollFrameContentWidth,
+ &nsComputedDOMStyle::GetScrollFrameContentHeight);
+ case StylePositionProperty::Absolute:
+ case StylePositionProperty::Fixed:
+ return GetAbsoluteOffset(aSide);
+ case StylePositionProperty::Relative:
+ return GetNonStaticPositionOffset(
+ aSide, true, &nsComputedDOMStyle::GetCBContentWidth,
+ &nsComputedDOMStyle::GetCBContentHeight);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid position");
+ return nullptr;
+ }
+}
+
+static_assert(eSideTop == 0 && eSideRight == 1 && eSideBottom == 2 &&
+ eSideLeft == 3,
+ "box side constants not as expected for NS_OPPOSITE_SIDE");
+#define NS_OPPOSITE_SIDE(s_) mozilla::Side(((s_) + 2) & 3)
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::GetNonStaticPositionOffset(
+ mozilla::Side aSide, bool aResolveAuto, PercentageBaseGetter aWidthGetter,
+ PercentageBaseGetter aHeightGetter) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStylePosition* positionData = StylePosition();
+ int32_t sign = 1;
+ LengthPercentageOrAuto coord = positionData->mOffset.Get(aSide);
+
+ if (coord.IsAuto()) {
+ if (!aResolveAuto) {
+ val->SetString("auto");
+ return val.forget();
+ }
+ coord = positionData->mOffset.Get(NS_OPPOSITE_SIDE(aSide));
+ sign = -1;
+ }
+ if (!coord.IsLengthPercentage()) {
+ val->SetPixels(0.0f);
+ return val.forget();
+ }
+
+ auto& lp = coord.AsLengthPercentage();
+ if (lp.ConvertsToLength()) {
+ val->SetPixels(sign * lp.ToLengthInCSSPixels());
+ return val.forget();
+ }
+
+ PercentageBaseGetter baseGetter = (aSide == eSideLeft || aSide == eSideRight)
+ ? aWidthGetter
+ : aHeightGetter;
+ nscoord percentageBase;
+ if (!(this->*baseGetter)(percentageBase)) {
+ val->SetPixels(0.0f);
+ return val.forget();
+ }
+ nscoord result = lp.Resolve(percentageBase);
+ val->SetAppUnits(sign * result);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::GetAbsoluteOffset(
+ mozilla::Side aSide) {
+ const auto& offset = StylePosition()->mOffset;
+ const auto& coord = offset.Get(aSide);
+ const auto& oppositeCoord = offset.Get(NS_OPPOSITE_SIDE(aSide));
+
+ if (coord.IsAuto() || oppositeCoord.IsAuto()) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(GetUsedAbsoluteOffset(aSide));
+ return val.forget();
+ }
+
+ return GetNonStaticPositionOffset(
+ aSide, false, &nsComputedDOMStyle::GetCBPaddingRectWidth,
+ &nsComputedDOMStyle::GetCBPaddingRectHeight);
+}
+
+nscoord nsComputedDOMStyle::GetUsedAbsoluteOffset(mozilla::Side aSide) {
+ MOZ_ASSERT(mOuterFrame, "need a frame, so we can call GetContainingBlock()");
+
+ nsIFrame* container = mOuterFrame->GetContainingBlock();
+ nsMargin margin = mOuterFrame->GetUsedMargin();
+ nsMargin border = container->GetUsedBorder();
+ nsMargin scrollbarSizes(0, 0, 0, 0);
+ nsRect rect = mOuterFrame->GetRect();
+ nsRect containerRect = container->GetRect();
+
+ if (container->IsViewportFrame()) {
+ // For absolutely positioned frames scrollbars are taken into
+ // account by virtue of getting a containing block that does
+ // _not_ include the scrollbars. For fixed positioned frames,
+ // the containing block is the viewport, which _does_ include
+ // scrollbars. We have to do some extra work.
+ // the first child in the default frame list is what we want
+ nsIFrame* scrollingChild = container->PrincipalChildList().FirstChild();
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(scrollingChild);
+ if (scrollFrame) {
+ scrollbarSizes = scrollFrame->GetActualScrollbarSizes();
+ }
+
+ // The viewport size might have been expanded by the visual viewport or
+ // the minimum-scale size.
+ const ViewportFrame* viewportFrame = do_QueryFrame(container);
+ MOZ_ASSERT(viewportFrame);
+ containerRect.SizeTo(
+ viewportFrame->AdjustViewportSizeForFixedPosition(containerRect));
+ } else if (container->IsGridContainerFrame() &&
+ mOuterFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ containerRect = nsGridContainerFrame::GridItemCB(mOuterFrame);
+ rect.MoveBy(-containerRect.x, -containerRect.y);
+ }
+
+ nscoord offset = 0;
+ switch (aSide) {
+ case eSideTop:
+ offset = rect.y - margin.top - border.top - scrollbarSizes.top;
+
+ break;
+ case eSideRight:
+ offset = containerRect.width - rect.width - rect.x - margin.right -
+ border.right - scrollbarSizes.right;
+
+ break;
+ case eSideBottom:
+ offset = containerRect.height - rect.height - rect.y - margin.bottom -
+ border.bottom - scrollbarSizes.bottom;
+
+ break;
+ case eSideLeft:
+ offset = rect.x - margin.left - border.left - scrollbarSizes.left;
+
+ break;
+ default:
+ NS_ERROR("Invalid side");
+ break;
+ }
+
+ return offset;
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::GetStaticOffset(
+ mozilla::Side aSide) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToLengthPercentageOrAuto(val, StylePosition()->mOffset.Get(aSide),
+ false);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::GetPaddingWidthFor(
+ mozilla::Side aSide) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ auto& padding = StylePadding()->mPadding.Get(aSide);
+ if (!mInnerFrame || !PaddingNeedsUsedValue(padding, *mComputedStyle)) {
+ SetValueToLengthPercentage(val, padding, true);
+ } else {
+ AssertFlushedPendingReflows();
+ val->SetAppUnits(mInnerFrame->GetUsedPadding().Side(aSide));
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::GetBorderWidthFor(
+ mozilla::Side aSide) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ nscoord width;
+ if (mInnerFrame && mComputedStyle->StyleDisplay()->HasAppearance()) {
+ AssertFlushedPendingReflows();
+ width = mInnerFrame->GetUsedBorder().Side(aSide);
+ } else {
+ width = StyleBorder()->GetComputedBorderWidth(aSide);
+ }
+ val->SetAppUnits(width);
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::GetMarginFor(Side aSide) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ auto& margin = StyleMargin()->mMargin.Get(aSide);
+ if (!mInnerFrame || margin.ConvertsToLength()) {
+ SetValueToLengthPercentageOrAuto(val, margin, false);
+ } else {
+ AssertFlushedPendingReflows();
+
+ // For tables, GetUsedMargin always returns an empty margin, so we
+ // should read the margin from the table wrapper frame instead.
+ val->SetAppUnits(mOuterFrame->GetUsedMargin().Side(aSide));
+ NS_ASSERTION(mOuterFrame == mInnerFrame ||
+ mInnerFrame->GetUsedMargin() == nsMargin(0, 0, 0, 0),
+ "Inner tables must have zero margins");
+ }
+
+ return val.forget();
+}
+
+static void SetValueToExtremumLength(nsROCSSPrimitiveValue* aValue,
+ nsIFrame::ExtremumLength aSize) {
+ switch (aSize) {
+ case nsIFrame::ExtremumLength::MaxContent:
+ return aValue->SetString("max-content");
+ case nsIFrame::ExtremumLength::MinContent:
+ return aValue->SetString("min-content");
+ case nsIFrame::ExtremumLength::MozAvailable:
+ return aValue->SetString("-moz-available");
+ case nsIFrame::ExtremumLength::FitContent:
+ return aValue->SetString("fit-content");
+ case nsIFrame::ExtremumLength::FitContentFunction:
+ MOZ_ASSERT_UNREACHABLE("fit-content() should be handled separately");
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown extremum length?");
+}
+
+void nsComputedDOMStyle::SetValueFromFitContentFunction(
+ nsROCSSPrimitiveValue* aValue, const LengthPercentage& aLength) {
+ nsAutoString argumentStr;
+ SetValueToLengthPercentage(aValue, aLength, true);
+ aValue->GetCssText(argumentStr);
+
+ nsAutoString fitContentStr;
+ fitContentStr.AppendLiteral("fit-content(");
+ fitContentStr.Append(argumentStr);
+ fitContentStr.Append(u')');
+ aValue->SetString(fitContentStr);
+}
+
+void nsComputedDOMStyle::SetValueToSize(nsROCSSPrimitiveValue* aValue,
+ const StyleSize& aSize) {
+ if (aSize.IsAuto()) {
+ return aValue->SetString("auto");
+ }
+ if (aSize.IsFitContentFunction()) {
+ return SetValueFromFitContentFunction(aValue, aSize.AsFitContentFunction());
+ }
+ if (auto length = nsIFrame::ToExtremumLength(aSize)) {
+ return SetValueToExtremumLength(aValue, *length);
+ }
+ MOZ_ASSERT(aSize.IsLengthPercentage());
+ SetValueToLengthPercentage(aValue, aSize.AsLengthPercentage(), true);
+}
+
+void nsComputedDOMStyle::SetValueToMaxSize(nsROCSSPrimitiveValue* aValue,
+ const StyleMaxSize& aSize) {
+ if (aSize.IsNone()) {
+ return aValue->SetString("none");
+ }
+ if (aSize.IsFitContentFunction()) {
+ return SetValueFromFitContentFunction(aValue, aSize.AsFitContentFunction());
+ }
+ if (auto length = nsIFrame::ToExtremumLength(aSize)) {
+ return SetValueToExtremumLength(aValue, *length);
+ }
+ MOZ_ASSERT(aSize.IsLengthPercentage());
+ SetValueToLengthPercentage(aValue, aSize.AsLengthPercentage(), true);
+}
+
+void nsComputedDOMStyle::SetValueToLengthPercentageOrAuto(
+ nsROCSSPrimitiveValue* aValue, const LengthPercentageOrAuto& aSize,
+ bool aClampNegativeCalc) {
+ if (aSize.IsAuto()) {
+ return aValue->SetString("auto");
+ }
+ SetValueToLengthPercentage(aValue, aSize.AsLengthPercentage(),
+ aClampNegativeCalc);
+}
+
+void nsComputedDOMStyle::SetValueToLengthPercentage(
+ nsROCSSPrimitiveValue* aValue, const mozilla::LengthPercentage& aLength,
+ bool aClampNegativeCalc) {
+ if (aLength.ConvertsToLength()) {
+ CSSCoord length = aLength.ToLengthInCSSPixels();
+ if (aClampNegativeCalc) {
+ length = std::max(float(length), 0.0f);
+ }
+ return aValue->SetPixels(length);
+ }
+ if (aLength.ConvertsToPercentage()) {
+ float result = aLength.ToPercentage();
+ if (aClampNegativeCalc) {
+ result = std::max(result, 0.0f);
+ }
+ return aValue->SetPercent(result);
+ }
+
+ nsAutoCString result;
+ Servo_LengthPercentage_ToCss(&aLength, &result);
+ aValue->SetString(result);
+}
+
+bool nsComputedDOMStyle::GetCBContentWidth(nscoord& aWidth) {
+ if (!mOuterFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ aWidth = mOuterFrame->GetContainingBlock()->GetContentRect().width;
+ return true;
+}
+
+bool nsComputedDOMStyle::GetCBContentHeight(nscoord& aHeight) {
+ if (!mOuterFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ aHeight = mOuterFrame->GetContainingBlock()->GetContentRect().height;
+ return true;
+}
+
+bool nsComputedDOMStyle::GetCBPaddingRectWidth(nscoord& aWidth) {
+ if (!mOuterFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ aWidth = mOuterFrame->GetContainingBlock()->GetPaddingRect().width;
+ return true;
+}
+
+bool nsComputedDOMStyle::GetCBPaddingRectHeight(nscoord& aHeight) {
+ if (!mOuterFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ aHeight = mOuterFrame->GetContainingBlock()->GetPaddingRect().height;
+ return true;
+}
+
+bool nsComputedDOMStyle::GetScrollFrameContentWidth(nscoord& aWidth) {
+ if (!mOuterFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ nsIScrollableFrame* scrollableFrame =
+ nsLayoutUtils::GetNearestScrollableFrame(
+ mOuterFrame->GetParent(),
+ nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+
+ if (!scrollableFrame) {
+ return false;
+ }
+ aWidth =
+ scrollableFrame->GetScrolledFrame()->GetContentRectRelativeToSelf().width;
+ return true;
+}
+
+bool nsComputedDOMStyle::GetScrollFrameContentHeight(nscoord& aHeight) {
+ if (!mOuterFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ nsIScrollableFrame* scrollableFrame =
+ nsLayoutUtils::GetNearestScrollableFrame(
+ mOuterFrame->GetParent(),
+ nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+
+ if (!scrollableFrame) {
+ return false;
+ }
+ aHeight = scrollableFrame->GetScrolledFrame()
+ ->GetContentRectRelativeToSelf()
+ .height;
+ return true;
+}
+
+bool nsComputedDOMStyle::GetFrameBorderRectWidth(nscoord& aWidth) {
+ if (!mInnerFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ aWidth = mInnerFrame->GetSize().width;
+ return true;
+}
+
+bool nsComputedDOMStyle::GetFrameBorderRectHeight(nscoord& aHeight) {
+ if (!mInnerFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ aHeight = mInnerFrame->GetSize().height;
+ return true;
+}
+
+/* If the property is "none", hand back "none" wrapped in a value.
+ * Otherwise, compute the aggregate transform matrix and hands it back in a
+ * "matrix" wrapper.
+ */
+already_AddRefed<CSSValue> nsComputedDOMStyle::GetTransformValue(
+ const StyleTransform& aTransform) {
+ /* If there are no transforms, then we should construct a single-element
+ * entry and hand it back.
+ */
+ if (aTransform.IsNone()) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetString("none");
+ return val.forget();
+ }
+
+ /* Otherwise, we need to compute the current value of the transform matrix,
+ * store it in a string, and hand it back to the caller.
+ */
+
+ /* Use the inner frame for the reference box. If we don't have an inner
+ * frame we use empty dimensions to allow us to continue (and percentage
+ * values in the transform will simply give broken results).
+ * TODO: There is no good way for us to represent the case where there's no
+ * frame, which is problematic. The reason is that when we have percentage
+ * transforms, there are a total of four stored matrix entries that influence
+ * the transform based on the size of the element. However, this poses a
+ * problem, because only two of these values can be explicitly referenced
+ * using the named transforms. Until a real solution is found, we'll just
+ * use this approach.
+ */
+ nsStyleTransformMatrix::TransformReferenceBox refBox(mInnerFrame, nsRect());
+ gfx::Matrix4x4 matrix = nsStyleTransformMatrix::ReadTransforms(
+ aTransform, refBox, float(mozilla::AppUnitsPerCSSPixel()));
+
+ return MatrixToCSSValue(matrix);
+}
+
+already_AddRefed<CSSValue> nsComputedDOMStyle::DummyGetter() {
+ MOZ_CRASH("DummyGetter is not supposed to be invoked");
+}
+
+static void MarkComputedStyleMapDirty(const char* aPref, void* aMap) {
+ static_cast<ComputedStyleMap*>(aMap)->MarkDirty();
+}
+
+void nsComputedDOMStyle::ParentChainChanged(nsIContent* aContent) {
+ NS_ASSERTION(mElement == aContent, "didn't we register mElement?");
+ NS_ASSERTION(mResolvedComputedStyle,
+ "should have only registered an observer when "
+ "mResolvedComputedStyle is true");
+
+ ClearComputedStyle();
+}
+
+/* static */
+ComputedStyleMap* nsComputedDOMStyle::GetComputedStyleMap() {
+ static ComputedStyleMap map{};
+ return &map;
+}
+
+static StaticAutoPtr<nsTArray<const char*>> gCallbackPrefs;
+
+/* static */
+void nsComputedDOMStyle::RegisterPrefChangeCallbacks() {
+ // Note that this will register callbacks for all properties with prefs, not
+ // just those that are implemented on computed style objects, as it's not
+ // easy to grab specific property data from ServoCSSPropList.h based on the
+ // entries iterated in nsComputedDOMStylePropertyList.h.
+
+ AutoTArray<const char*, 64> prefs;
+ for (const auto* p = nsCSSProps::kPropertyPrefTable;
+ p->mPropID != eCSSProperty_UNKNOWN; p++) {
+ // Many properties are controlled by the same preference, so de-duplicate
+ // them before adding observers.
+ //
+ // Note: This is done by pointer comparison, which works because the mPref
+ // members are string literals from the same same translation unit, and are
+ // therefore de-duplicated by the compiler. On the off chance that we wind
+ // up with some duplicates with different pointers, though, it's not a bit
+ // deal.
+ if (!prefs.ContainsSorted(p->mPref)) {
+ prefs.InsertElementSorted(p->mPref);
+ }
+ }
+
+ prefs.AppendElement(
+ StaticPrefs::GetPrefName_layout_css_computed_style_shorthands());
+
+ prefs.AppendElement(nullptr);
+
+ MOZ_ASSERT(!gCallbackPrefs);
+ gCallbackPrefs = new nsTArray<const char*>(std::move(prefs));
+
+ Preferences::RegisterCallbacks(MarkComputedStyleMapDirty,
+ gCallbackPrefs->Elements(),
+ GetComputedStyleMap());
+}
+
+/* static */
+void nsComputedDOMStyle::UnregisterPrefChangeCallbacks() {
+ if (!gCallbackPrefs) {
+ return;
+ }
+
+ Preferences::UnregisterCallbacks(MarkComputedStyleMapDirty,
+ gCallbackPrefs->Elements(),
+ GetComputedStyleMap());
+ gCallbackPrefs = nullptr;
+}
diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h
new file mode 100644
index 0000000000..4a6fec785d
--- /dev/null
+++ b/layout/style/nsComputedDOMStyle.h
@@ -0,0 +1,400 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object returned from element.getComputedStyle() */
+
+#ifndef nsComputedDOMStyle_h__
+#define nsComputedDOMStyle_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/StyleColorInlines.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nscore.h"
+#include "nsDOMCSSDeclaration.h"
+#include "mozilla/ComputedStyle.h"
+#include "nsIWeakReferenceUtils.h"
+#include "mozilla/gfx/Types.h"
+#include "nsCoord.h"
+#include "nsColor.h"
+#include "nsStubMutationObserver.h"
+#include "nsStyleStruct.h"
+#include "mozilla/WritingModes.h"
+
+// XXX Avoid including this here by moving function bodies to the cpp file
+#include "mozilla/dom/Element.h"
+
+namespace mozilla {
+enum class FlushType : uint8_t;
+enum class PseudoStyleType : uint8_t;
+
+namespace dom {
+class DocGroup;
+class Element;
+} // namespace dom
+class PresShell;
+struct ComputedGridTrackInfo;
+} // namespace mozilla
+
+struct ComputedStyleMap;
+struct nsCSSKTableEntry;
+class nsIFrame;
+class nsDOMCSSValueList;
+struct nsMargin;
+class nsROCSSPrimitiveValue;
+class nsStyleGradient;
+
+class nsComputedDOMStyle final : public nsDOMCSSDeclaration,
+ public nsStubMutationObserver {
+ private:
+ // Convenience typedefs:
+ template <typename T>
+ using Span = mozilla::Span<T>;
+ using KTableEntry = nsCSSKTableEntry;
+ using CSSValue = mozilla::dom::CSSValue;
+ using StyleGeometryBox = mozilla::StyleGeometryBox;
+ using Element = mozilla::dom::Element;
+ using Document = mozilla::dom::Document;
+ using PseudoStyleType = mozilla::PseudoStyleType;
+ using LengthPercentage = mozilla::LengthPercentage;
+ using LengthPercentageOrAuto = mozilla::LengthPercentageOrAuto;
+ using ComputedStyle = mozilla::ComputedStyle;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS_AMBIGUOUS(
+ nsComputedDOMStyle, nsICSSDeclaration)
+
+ NS_DECL_NSIDOMCSSSTYLEDECLARATION_HELPER
+
+ void GetPropertyValue(const nsCSSPropertyID aPropID,
+ nsACString& aValue) override;
+ void SetPropertyValue(const nsCSSPropertyID aPropID, const nsACString& aValue,
+ nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& aRv) override;
+
+ void IndexedGetter(uint32_t aIndex, bool& aFound,
+ nsACString& aPropName) final;
+
+ enum class StyleType : uint8_t {
+ DefaultOnly, // Only includes UA and user sheets
+ All // Includes all stylesheets
+ };
+
+ // In some cases, for legacy reasons, we forcefully return an empty style.
+ enum class AlwaysReturnEmptyStyle : bool { No, Yes };
+
+ nsComputedDOMStyle(Element*, PseudoStyleType,
+ nsAtom* aFunctionalPseudoParameter, Document*, StyleType,
+ AlwaysReturnEmptyStyle = AlwaysReturnEmptyStyle::No);
+
+ nsINode* GetAssociatedNode() const override { return mElement; }
+ nsINode* GetParentObject() const override { return mElement; }
+
+ static already_AddRefed<const ComputedStyle> GetComputedStyle(
+ Element* aElement, PseudoStyleType = PseudoStyleType::NotPseudo,
+ nsAtom* aFunctionalPseudoParameter = nullptr, StyleType = StyleType::All);
+
+ static already_AddRefed<const ComputedStyle> GetComputedStyleNoFlush(
+ const Element* aElement,
+ PseudoStyleType aPseudo = PseudoStyleType::NotPseudo,
+ nsAtom* aFunctionalPseudoParameter = nullptr,
+ StyleType aStyleType = StyleType::All) {
+ return DoGetComputedStyleNoFlush(
+ aElement, aPseudo, aFunctionalPseudoParameter,
+ nsContentUtils::GetPresShellForContent(aElement), aStyleType);
+ }
+
+ static already_AddRefed<const ComputedStyle>
+ GetUnanimatedComputedStyleNoFlush(
+ Element*, PseudoStyleType = PseudoStyleType::NotPseudo,
+ nsAtom* aFunctionalPseudoParameter = nullptr);
+
+ // Helper for nsDOMWindowUtils::GetVisitedDependentComputedStyle
+ void SetExposeVisitedStyle(bool aExpose) {
+ NS_ASSERTION(aExpose != mExposeVisitedStyle, "should always be changing");
+ mExposeVisitedStyle = aExpose;
+ }
+
+ void GetCSSImageURLs(const nsACString& aPropertyName,
+ nsTArray<nsCString>& aImageURLs,
+ mozilla::ErrorResult& aRv) final;
+
+ // nsDOMCSSDeclaration abstract methods which should never be called
+ // on a nsComputedDOMStyle object, but must be defined to avoid
+ // compile errors.
+ mozilla::DeclarationBlock* GetOrCreateCSSDeclaration(
+ Operation aOperation, mozilla::DeclarationBlock** aCreated) final;
+ virtual nsresult SetCSSDeclaration(mozilla::DeclarationBlock*,
+ mozilla::MutationClosureData*) override;
+ virtual mozilla::dom::Document* DocToUpdate() override;
+
+ nsDOMCSSDeclaration::ParsingEnvironment GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal) const final;
+
+ static already_AddRefed<nsROCSSPrimitiveValue> MatrixToCSSValue(
+ const mozilla::gfx::Matrix4x4& aMatrix);
+
+ static void RegisterPrefChangeCallbacks();
+ static void UnregisterPrefChangeCallbacks();
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
+
+ private:
+ void GetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsACString& aMaybeCustomPropertyNme,
+ nsACString& aValue);
+ using nsDOMCSSDeclaration::GetPropertyValue;
+
+ virtual ~nsComputedDOMStyle();
+
+ void AssertFlushedPendingReflows() {
+ NS_ASSERTION(mFlushedPendingReflows,
+ "property getter should have been marked layout-dependent");
+ }
+
+ nsMargin GetAdjustedValuesForBoxSizing();
+
+ // This indicates error by leaving mComputedStyle null.
+ void UpdateCurrentStyleSources(nsCSSPropertyID);
+ void ClearCurrentStyleSources();
+
+ // Helper functions called by UpdateCurrentStyleSources.
+ void ClearComputedStyle();
+ void SetResolvedComputedStyle(RefPtr<const ComputedStyle>&& aContext,
+ uint64_t aGeneration);
+ void SetFrameComputedStyle(ComputedStyle* aStyle, uint64_t aGeneration);
+
+ static already_AddRefed<const ComputedStyle> DoGetComputedStyleNoFlush(
+ const Element*, PseudoStyleType, nsAtom* aFunctionalPseudoParameter,
+ mozilla::PresShell*, StyleType);
+
+#define STYLE_STRUCT(name_) \
+ const nsStyle##name_* Style##name_() { \
+ return mComputedStyle->Style##name_(); \
+ }
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ /**
+ * A method to get a percentage base for a percentage value. Returns true
+ * if a percentage base value was determined, false otherwise.
+ */
+ typedef bool (nsComputedDOMStyle::*PercentageBaseGetter)(nscoord&);
+
+ already_AddRefed<CSSValue> GetOffsetWidthFor(mozilla::Side);
+ already_AddRefed<CSSValue> GetAbsoluteOffset(mozilla::Side);
+ nscoord GetUsedAbsoluteOffset(mozilla::Side);
+ already_AddRefed<CSSValue> GetNonStaticPositionOffset(
+ mozilla::Side aSide, bool aResolveAuto, PercentageBaseGetter aWidthGetter,
+ PercentageBaseGetter aHeightGetter);
+
+ already_AddRefed<CSSValue> GetStaticOffset(mozilla::Side aSide);
+
+ already_AddRefed<CSSValue> GetPaddingWidthFor(mozilla::Side aSide);
+
+ already_AddRefed<CSSValue> GetBorderWidthFor(mozilla::Side aSide);
+
+ already_AddRefed<CSSValue> GetMarginFor(mozilla::Side aSide);
+
+ already_AddRefed<CSSValue> GetTransformValue(const mozilla::StyleTransform&);
+
+ already_AddRefed<nsROCSSPrimitiveValue> GetGridTrackSize(
+ const mozilla::StyleTrackSize&);
+ already_AddRefed<nsROCSSPrimitiveValue> GetGridTrackBreadth(
+ const mozilla::StyleTrackBreadth&);
+ void SetValueToTrackBreadth(nsROCSSPrimitiveValue*,
+ const mozilla::StyleTrackBreadth&);
+ already_AddRefed<CSSValue> GetGridTemplateColumnsRows(
+ const mozilla::StyleGridTemplateComponent& aTrackList,
+ const mozilla::ComputedGridTrackInfo& aTrackInfo);
+
+ bool GetLineHeightCoord(nscoord& aCoord);
+
+ bool ShouldHonorMinSizeAutoInAxis(mozilla::PhysicalAxis aAxis);
+
+ /* Properties queryable as CSSValues.
+ * To avoid a name conflict with nsIDOM*CSS2Properties, these are all
+ * DoGetXXX instead of GetXXX.
+ */
+
+ /* Box properties */
+
+ already_AddRefed<CSSValue> DoGetWidth();
+ already_AddRefed<CSSValue> DoGetHeight();
+ already_AddRefed<CSSValue> DoGetMaxHeight();
+ already_AddRefed<CSSValue> DoGetMaxWidth();
+ already_AddRefed<CSSValue> DoGetMinHeight();
+ already_AddRefed<CSSValue> DoGetMinWidth();
+ already_AddRefed<CSSValue> DoGetLeft();
+ already_AddRefed<CSSValue> DoGetTop();
+ already_AddRefed<CSSValue> DoGetRight();
+ already_AddRefed<CSSValue> DoGetBottom();
+
+ /* Font properties */
+ already_AddRefed<CSSValue> DoGetMozOsxFontSmoothing();
+
+ /* Grid properties */
+ already_AddRefed<CSSValue> DoGetGridTemplateColumns();
+ already_AddRefed<CSSValue> DoGetGridTemplateRows();
+
+ /* StyleImageLayer properties */
+ already_AddRefed<CSSValue> DoGetImageLayerPosition(
+ const nsStyleImageLayers& aLayers);
+
+ /* Padding properties */
+ already_AddRefed<CSSValue> DoGetPaddingTop();
+ already_AddRefed<CSSValue> DoGetPaddingBottom();
+ already_AddRefed<CSSValue> DoGetPaddingLeft();
+ already_AddRefed<CSSValue> DoGetPaddingRight();
+
+ /* Table Properties */
+ already_AddRefed<CSSValue> DoGetBorderSpacing();
+
+ /* Border Properties */
+ already_AddRefed<CSSValue> DoGetBorderTopWidth();
+ already_AddRefed<CSSValue> DoGetBorderBottomWidth();
+ already_AddRefed<CSSValue> DoGetBorderLeftWidth();
+ already_AddRefed<CSSValue> DoGetBorderRightWidth();
+
+ /* Margin Properties */
+ already_AddRefed<CSSValue> DoGetMarginTop();
+ already_AddRefed<CSSValue> DoGetMarginBottom();
+ already_AddRefed<CSSValue> DoGetMarginLeft();
+ already_AddRefed<CSSValue> DoGetMarginRight();
+
+ /* Display properties */
+ already_AddRefed<CSSValue> DoGetTransform();
+ already_AddRefed<CSSValue> DoGetTransformOrigin();
+ already_AddRefed<CSSValue> DoGetPerspectiveOrigin();
+
+ // For working around a MSVC bug. See related comment in
+ // GenerateComputedDOMStyleGenerated.py.
+ already_AddRefed<CSSValue> DummyGetter();
+
+ /* Helper functions */
+ void SetValueToPosition(const mozilla::Position& aPosition,
+ nsDOMCSSValueList* aValueList);
+
+ void SetValueFromFitContentFunction(nsROCSSPrimitiveValue* aValue,
+ const mozilla::LengthPercentage&);
+
+ void SetValueToSize(nsROCSSPrimitiveValue* aValue, const mozilla::StyleSize&);
+
+ void SetValueToLengthPercentageOrAuto(nsROCSSPrimitiveValue* aValue,
+ const LengthPercentageOrAuto&,
+ bool aClampNegativeCalc);
+
+ void SetValueToLengthPercentage(nsROCSSPrimitiveValue* aValue,
+ const LengthPercentage&,
+ bool aClampNegativeCalc);
+
+ void SetValueToMaxSize(nsROCSSPrimitiveValue* aValue,
+ const mozilla::StyleMaxSize&);
+
+ bool GetCBContentWidth(nscoord& aWidth);
+ bool GetCBContentHeight(nscoord& aHeight);
+ bool GetCBPaddingRectWidth(nscoord& aWidth);
+ bool GetCBPaddingRectHeight(nscoord& aHeight);
+ bool GetScrollFrameContentWidth(nscoord& aWidth);
+ bool GetScrollFrameContentHeight(nscoord& aHeight);
+ bool GetFrameBorderRectWidth(nscoord& aWidth);
+ bool GetFrameBorderRectHeight(nscoord& aHeight);
+
+ // Find out if we can safely skip flushing (i.e. pending restyles do not
+ // affect our element).
+ bool NeedsToFlushStyle(nsCSSPropertyID) const;
+ // Find out if we need to flush layout of the document, depending on the
+ // property that was requested.
+ bool NeedsToFlushLayout(nsCSSPropertyID) const;
+ // Flushes the given document, which must be our document, and potentially the
+ // mElement's document.
+ void Flush(Document&, mozilla::FlushType);
+ nsIFrame* GetOuterFrame() const;
+
+ static ComputedStyleMap* GetComputedStyleMap();
+
+ // We don't really have a good immutable representation of "presentation".
+ // Given the way GetComputedStyle is currently used, we should just grab the
+ // presshell, if any, from the document.
+ mozilla::WeakPtr<mozilla::dom::Document> mDocumentWeak;
+ RefPtr<Element> mElement;
+
+ /**
+ * Strong reference to the ComputedStyle we access data from. This can be
+ * either a ComputedStyle we resolved ourselves or a ComputedStyle we got
+ * from our frame.
+ *
+ * If we got the ComputedStyle from the frame, we clear out mComputedStyle
+ * in ClearCurrentStyleSources. If we resolved one ourselves, then
+ * ClearCurrentStyleSources leaves it in mComputedStyle for use the next
+ * time this nsComputedDOMStyle object is queried. UpdateCurrentStyleSources
+ * in this case will check that the ComputedStyle is still valid to be used,
+ * by checking whether flush styles results in any restyles having been
+ * processed.
+ */
+ RefPtr<const ComputedStyle> mComputedStyle;
+
+ /*
+ * While computing style data, the primary frame for mContent --- named
+ * "outer" because we should use it to compute positioning data. Null
+ * otherwise.
+ */
+ nsIFrame* mOuterFrame;
+ /*
+ * While computing style data, the "inner frame" for mContent --- the frame
+ * which we should use to compute margin, border, padding and content data.
+ * Null otherwise.
+ */
+ nsIFrame* mInnerFrame;
+ /*
+ * While computing style data, the presshell we're working with. Null
+ * otherwise.
+ */
+ mozilla::PresShell* mPresShell;
+
+ PseudoStyleType mPseudo;
+ RefPtr<nsAtom> mFunctionalPseudoParameter;
+
+ /* The kind of styles we should be returning. */
+ StyleType mStyleType;
+
+ /* Whether for legacy reasons we return an empty style (when an unknown
+ * pseudo-element is specified) */
+ AlwaysReturnEmptyStyle mAlwaysReturnEmpty;
+
+ /**
+ * The nsComputedDOMStyle generation at the time we last resolved a style
+ * context and stored it in mComputedStyle, and the pres shell we got the
+ * style from. Should only be used together.
+ */
+ uint64_t mComputedStyleGeneration = 0;
+
+ uint32_t mPresShellId = 0;
+
+ bool mExposeVisitedStyle = false;
+
+ /**
+ * Whether we resolved a ComputedStyle last time we called
+ * UpdateCurrentStyleSources. Initially false.
+ */
+ bool mResolvedComputedStyle = false;
+
+#ifdef DEBUG
+ bool mFlushedPendingReflows = false;
+#endif
+
+ friend struct ComputedStyleMap;
+};
+
+already_AddRefed<nsComputedDOMStyle> NS_NewComputedDOMStyle(
+ mozilla::dom::Element*, const nsAString& aPseudoElt,
+ mozilla::dom::Document*, nsComputedDOMStyle::StyleType,
+ mozilla::ErrorResult&);
+
+#endif /* nsComputedDOMStyle_h__ */
diff --git a/layout/style/nsDOMCSSAttrDeclaration.cpp b/layout/style/nsDOMCSSAttrDeclaration.cpp
new file mode 100644
index 0000000000..1381b22d6c
--- /dev/null
+++ b/layout/style/nsDOMCSSAttrDeclaration.cpp
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object for element.style */
+
+#include "nsDOMCSSAttrDeclaration.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/SVGElement.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "mozilla/layers/ScrollLinkedEffectDetector.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/SMILCSSValueType.h"
+#include "mozilla/SMILValue.h"
+#include "mozAutoDocUpdate.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsIFrame.h"
+#include "ActiveLayerTracker.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsDOMCSSAttributeDeclaration::nsDOMCSSAttributeDeclaration(Element* aElement,
+ bool aIsSMILOverride)
+ : mElement(aElement), mIsSMILOverride(aIsSMILOverride) {
+ NS_ASSERTION(aElement, "Inline style for a NULL element?");
+}
+
+nsDOMCSSAttributeDeclaration::~nsDOMCSSAttributeDeclaration() = default;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMCSSAttributeDeclaration, mElement)
+
+// mElement holds a strong ref to us, so if it's going to be
+// skipped, the attribute declaration can't be part of a garbage
+// cycle.
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMCSSAttributeDeclaration)
+ if (tmp->mElement && Element::CanSkip(tmp->mElement, true)) {
+ if (tmp->PreservingWrapper()) {
+ tmp->MarkWrapperLive();
+ }
+ return true;
+ }
+ return tmp->HasKnownLiveWrapper();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMCSSAttributeDeclaration)
+ return tmp->HasKnownLiveWrapper() ||
+ (tmp->mElement && Element::CanSkipInCC(tmp->mElement));
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMCSSAttributeDeclaration)
+ return tmp->HasKnownLiveWrapper() ||
+ (tmp->mElement && Element::CanSkipThis(tmp->mElement));
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCSSAttributeDeclaration)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCSSAttributeDeclaration)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCSSAttributeDeclaration)
+
+nsresult nsDOMCSSAttributeDeclaration::SetCSSDeclaration(
+ DeclarationBlock* aDecl, MutationClosureData* aClosureData) {
+ NS_ASSERTION(mElement, "Must have Element to set the declaration!");
+
+ // Whenever changing element.style values, aClosureData must be non-null.
+ // SMIL doesn't update Element's attribute values, so closure data isn't
+ // needed.
+ MOZ_ASSERT_IF(!mIsSMILOverride, aClosureData);
+
+ // The closure needs to have been called by now, otherwise we shouldn't be
+ // getting here when the attribute hasn't changed.
+ MOZ_ASSERT_IF(aClosureData && aClosureData->mShouldBeCalled,
+ aClosureData->mWasCalled);
+
+ aDecl->SetDirty();
+ if (mIsSMILOverride) {
+ mElement->SetSMILOverrideStyleDeclaration(*aDecl);
+ return NS_OK;
+ }
+ return mElement->SetInlineStyleDeclaration(*aDecl, *aClosureData);
+}
+
+Document* nsDOMCSSAttributeDeclaration::DocToUpdate() {
+ // We need OwnerDoc() rather than GetUncomposedDoc() because it might
+ // be the BeginUpdate call that inserts mElement into the document.
+ return mElement->OwnerDoc();
+}
+
+DeclarationBlock* nsDOMCSSAttributeDeclaration::GetOrCreateCSSDeclaration(
+ Operation aOperation, DeclarationBlock** aCreated) {
+ MOZ_ASSERT(aOperation != Operation::Modify || aCreated);
+
+ if (!mElement) return nullptr;
+
+ DeclarationBlock* declaration;
+ if (mIsSMILOverride) {
+ declaration = mElement->GetSMILOverrideStyleDeclaration();
+ } else {
+ declaration = mElement->GetInlineStyleDeclaration();
+ }
+
+ if (declaration) {
+ return declaration;
+ }
+
+ if (aOperation != Operation::Modify) {
+ return nullptr;
+ }
+
+ // cannot fail
+ RefPtr<DeclarationBlock> decl = new DeclarationBlock();
+ // Mark the declaration dirty so that it can be reused by the caller.
+ // Normally SetDirty is called later in SetCSSDeclaration.
+ decl->SetDirty();
+#ifdef DEBUG
+ RefPtr<DeclarationBlock> mutableDecl = decl->EnsureMutable();
+ MOZ_ASSERT(mutableDecl == decl);
+#endif
+ decl.swap(*aCreated);
+ return *aCreated;
+}
+
+nsDOMCSSDeclaration::ParsingEnvironment
+nsDOMCSSAttributeDeclaration::GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal) const {
+ return {
+ mElement->GetURLDataForStyleAttr(aSubjectPrincipal),
+ mElement->OwnerDoc()->GetCompatibilityMode(),
+ mElement->OwnerDoc()->CSSLoader(),
+ };
+}
+
+template <typename SetterFunc>
+nsresult nsDOMCSSAttributeDeclaration::SetSMILValueHelper(SetterFunc aFunc) {
+ MOZ_ASSERT(mIsSMILOverride);
+
+ // No need to do the ActiveLayerTracker / ScrollLinkedEffectDetector bits,
+ // since we're in a SMIL animation anyway, no need to try to detect we're a
+ // scripted animation.
+ RefPtr<DeclarationBlock> created;
+ DeclarationBlock* olddecl =
+ GetOrCreateCSSDeclaration(Operation::Modify, getter_AddRefs(created));
+ if (!olddecl) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mozAutoDocUpdate autoUpdate(DocToUpdate(), true);
+ RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
+
+ bool changed = aFunc(*decl);
+
+ if (changed) {
+ // We can pass nullptr as the latter param, since this is
+ // mIsSMILOverride == true case.
+ SetCSSDeclaration(decl, nullptr);
+ }
+ return NS_OK;
+}
+
+nsresult nsDOMCSSAttributeDeclaration::SetSMILValue(
+ const nsCSSPropertyID /*aPropID*/, const SMILValue& aValue) {
+ MOZ_ASSERT(aValue.mType == &SMILCSSValueType::sSingleton,
+ "We should only try setting a CSS value type");
+ return SetSMILValueHelper([&aValue](DeclarationBlock& aDecl) {
+ return SMILCSSValueType::SetPropertyValues(aValue, aDecl);
+ });
+}
+
+nsresult nsDOMCSSAttributeDeclaration::SetSMILValue(
+ const nsCSSPropertyID aPropID, const SVGAnimatedLength& aLength) {
+ return SetSMILValueHelper([aPropID, &aLength](DeclarationBlock& aDecl) {
+ MOZ_ASSERT(aDecl.IsMutable());
+ return SVGElement::UpdateDeclarationBlockFromLength(
+ *aDecl.Raw(), aPropID, aLength, SVGElement::ValToUse::Anim);
+ });
+}
+
+nsresult nsDOMCSSAttributeDeclaration::SetSMILValue(
+ const nsCSSPropertyID /*aPropID*/, const SVGAnimatedPathSegList& aPath) {
+ return SetSMILValueHelper([&aPath](DeclarationBlock& aDecl) {
+ MOZ_ASSERT(aDecl.IsMutable());
+ return SVGElement::UpdateDeclarationBlockFromPath(
+ *aDecl.Raw(), aPath, SVGElement::ValToUse::Anim);
+ });
+}
+
+// Scripted modifications to style.opacity or style.transform (or other
+// transform-like properties, e.g. style.translate, style.rotate, style.scale)
+// could immediately force us into the animated state if heuristics suggest
+// this is a scripted animation.
+//
+// FIXME: This is missing the margin shorthand and the logical versions of
+// the margin properties, see bug 1266287.
+static bool IsActiveLayerProperty(nsCSSPropertyID aPropID) {
+ switch (aPropID) {
+ case eCSSProperty_opacity:
+ case eCSSProperty_transform:
+ case eCSSProperty_translate:
+ case eCSSProperty_rotate:
+ case eCSSProperty_scale:
+ case eCSSProperty_offset_path:
+ case eCSSProperty_offset_distance:
+ case eCSSProperty_offset_rotate:
+ case eCSSProperty_offset_anchor:
+ case eCSSProperty_offset_position:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void nsDOMCSSAttributeDeclaration::SetPropertyValue(
+ const nsCSSPropertyID aPropID, const nsACString& aValue,
+ nsIPrincipal* aSubjectPrincipal, ErrorResult& aRv) {
+ nsDOMCSSDeclaration::SetPropertyValue(aPropID, aValue, aSubjectPrincipal,
+ aRv);
+}
+
+static bool IsScrollLinkedEffectiveProperty(const nsCSSPropertyID aPropID) {
+ switch (aPropID) {
+ case eCSSProperty_background_position:
+ case eCSSProperty_background_position_x:
+ case eCSSProperty_background_position_y:
+ case eCSSProperty_transform:
+ case eCSSProperty_translate:
+ case eCSSProperty_rotate:
+ case eCSSProperty_scale:
+ case eCSSProperty_offset_path:
+ case eCSSProperty_offset_distance:
+ case eCSSProperty_offset_rotate:
+ case eCSSProperty_offset_anchor:
+ case eCSSProperty_offset_position:
+ case eCSSProperty_top:
+ case eCSSProperty_left:
+ case eCSSProperty_bottom:
+ case eCSSProperty_right:
+ case eCSSProperty_margin:
+ case eCSSProperty_margin_top:
+ case eCSSProperty_margin_left:
+ case eCSSProperty_margin_bottom:
+ case eCSSProperty_margin_right:
+ case eCSSProperty_margin_inline_start:
+ case eCSSProperty_margin_inline_end:
+ case eCSSProperty_margin_block_start:
+ case eCSSProperty_margin_block_end:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void nsDOMCSSAttributeDeclaration::MutationClosureFunction(
+ void* aData, nsCSSPropertyID aPropID) {
+ auto* data = static_cast<MutationClosureData*>(aData);
+ MOZ_ASSERT(
+ data->mShouldBeCalled,
+ "Did we pass a non-null closure to the style system unnecessarily?");
+ if (data->mWasCalled) {
+ return;
+ }
+ if (IsScrollLinkedEffectiveProperty(aPropID)) {
+ mozilla::layers::ScrollLinkedEffectDetector::PositioningPropertyMutated();
+ }
+ if (IsActiveLayerProperty(aPropID)) {
+ if (nsIFrame* frame = data->mElement->GetPrimaryFrame()) {
+ ActiveLayerTracker::NotifyInlineStyleRuleModified(frame, aPropID);
+ }
+ }
+
+ data->mWasCalled = true;
+ data->mElement->InlineStyleDeclarationWillChange(*data);
+}
diff --git a/layout/style/nsDOMCSSAttrDeclaration.h b/layout/style/nsDOMCSSAttrDeclaration.h
new file mode 100644
index 0000000000..ebae280bf0
--- /dev/null
+++ b/layout/style/nsDOMCSSAttrDeclaration.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object for element.style */
+
+#ifndef nsDOMCSSAttributeDeclaration_h
+#define nsDOMCSSAttributeDeclaration_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/ServoTypes.h"
+#include "nsDOMCSSDeclaration.h"
+
+struct RawServoUnlockedDeclarationBlock;
+
+namespace mozilla {
+
+class SMILValue;
+class SVGAnimatedLength;
+class SVGAnimatedPathSegList;
+
+namespace dom {
+class DomGroup;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class nsDOMCSSAttributeDeclaration final : public nsDOMCSSDeclaration {
+ public:
+ typedef mozilla::dom::Element Element;
+ typedef mozilla::SMILValue SMILValue;
+ typedef mozilla::SVGAnimatedLength SVGAnimatedLength;
+ nsDOMCSSAttributeDeclaration(Element* aContent, bool aIsSMILOverride);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS_AMBIGUOUS(
+ nsDOMCSSAttributeDeclaration, nsICSSDeclaration)
+
+ mozilla::DeclarationBlock* GetOrCreateCSSDeclaration(
+ Operation aOperation, mozilla::DeclarationBlock** aCreated) final;
+
+ nsDOMCSSDeclaration::ParsingEnvironment GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal) const final;
+
+ mozilla::css::Rule* GetParentRule() override { return nullptr; }
+
+ nsINode* GetAssociatedNode() const override { return mElement; }
+ nsINode* GetParentObject() const override { return mElement; }
+
+ nsresult SetSMILValue(const nsCSSPropertyID aPropID, const SMILValue& aValue);
+ nsresult SetSMILValue(const nsCSSPropertyID aPropID,
+ const SVGAnimatedLength& aLength);
+ nsresult SetSMILValue(const nsCSSPropertyID,
+ const mozilla::SVGAnimatedPathSegList& aPath);
+ void ClearSMILValue(const nsCSSPropertyID aPropID) {
+ // Put empty string in override style for our property
+ SetPropertyValue(aPropID, ""_ns, nullptr, mozilla::IgnoreErrors());
+ }
+
+ void SetPropertyValue(const nsCSSPropertyID aPropID, const nsACString& aValue,
+ nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& aRv) override;
+
+ static void MutationClosureFunction(void* aData, nsCSSPropertyID);
+
+ void GetPropertyChangeClosure(
+ mozilla::DeclarationBlockMutationClosure* aClosure,
+ mozilla::MutationClosureData* aClosureData) final {
+ if (!mIsSMILOverride) {
+ aClosure->function = MutationClosureFunction;
+ aClosure->data = aClosureData;
+ aClosureData->mShouldBeCalled = true;
+ aClosureData->mElement = mElement;
+ }
+ }
+
+ protected:
+ ~nsDOMCSSAttributeDeclaration();
+
+ nsresult SetCSSDeclaration(
+ mozilla::DeclarationBlock* aDecl,
+ mozilla::MutationClosureData* aClosureData) override;
+ mozilla::dom::Document* DocToUpdate() override;
+
+ RefPtr<Element> mElement;
+
+ /* If true, this indicates that this nsDOMCSSAttributeDeclaration
+ * should interact with mContent's SMIL override style rule (rather
+ * than the inline style rule).
+ */
+ const bool mIsSMILOverride;
+
+ private:
+ template <typename SetterFunc>
+ nsresult SetSMILValueHelper(SetterFunc aFunc);
+};
+
+#endif /* nsDOMCSSAttributeDeclaration_h */
diff --git a/layout/style/nsDOMCSSDeclaration.cpp b/layout/style/nsDOMCSSDeclaration.cpp
new file mode 100644
index 0000000000..4203748df6
--- /dev/null
+++ b/layout/style/nsDOMCSSDeclaration.cpp
@@ -0,0 +1,373 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for DOM objects for element.style and cssStyleRule.style */
+
+#include "nsDOMCSSDeclaration.h"
+
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Rule.h"
+#include "mozilla/dom/CSS2PropertiesBinding.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "nsCSSProps.h"
+#include "nsCOMPtr.h"
+#include "mozAutoDocUpdate.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsQueryObject.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsDOMCSSDeclaration::~nsDOMCSSDeclaration() = default;
+
+/* virtual */
+JSObject* nsDOMCSSDeclaration::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CSS2Properties_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsDOMCSSDeclaration, nsICSSDeclaration)
+
+void nsDOMCSSDeclaration::GetPropertyValue(const nsCSSPropertyID aPropID,
+ nsACString& aValue) {
+ MOZ_ASSERT(aPropID != eCSSProperty_UNKNOWN,
+ "Should never pass eCSSProperty_UNKNOWN around");
+ MOZ_ASSERT(aValue.IsEmpty());
+
+ if (DeclarationBlock* decl =
+ GetOrCreateCSSDeclaration(Operation::Read, nullptr)) {
+ decl->GetPropertyValueByID(aPropID, aValue);
+ }
+}
+
+void nsDOMCSSDeclaration::SetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsACString& aValue,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ if (aValue.IsEmpty()) {
+ // If the new value of the property is an empty string we remove the
+ // property.
+ return RemovePropertyInternal(aPropID, aRv);
+ }
+
+ aRv = ParsePropertyValue(aPropID, aValue, false, aSubjectPrincipal);
+}
+
+void nsDOMCSSDeclaration::GetCssText(nsACString& aCssText) {
+ MOZ_ASSERT(aCssText.IsEmpty());
+
+ if (auto* decl = GetOrCreateCSSDeclaration(Operation::Read, nullptr)) {
+ decl->ToString(aCssText);
+ }
+}
+
+void nsDOMCSSDeclaration::SetCssText(const nsACString& aCssText,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ // We don't need to *do* anything with the old declaration, but we need
+ // to ensure that it exists, or else SetCSSDeclaration may crash.
+ RefPtr<DeclarationBlock> created;
+ DeclarationBlock* olddecl =
+ GetOrCreateCSSDeclaration(Operation::Modify, getter_AddRefs(created));
+ if (!olddecl) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
+ // Attribute setting code, which leads in turn to BeginUpdate. We
+ // need to start the update now so that the old rule doesn't get used
+ // between when we mutate the declaration and when we set the new
+ // rule (see stack in bug 209575).
+ mozAutoDocUpdate autoUpdate(DocToUpdate(), true);
+ DeclarationBlockMutationClosure closure = {};
+ MutationClosureData closureData;
+ GetPropertyChangeClosure(&closure, &closureData);
+
+ ParsingEnvironment servoEnv = GetParsingEnvironment(aSubjectPrincipal);
+ if (!servoEnv.mUrlExtraData) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ // Need to special case closure calling here, since parsing css text
+ // doesn't modify any existing declaration and that is why the callback isn't
+ // called implicitly.
+ if (closure.function && !closureData.mWasCalled) {
+ closure.function(&closureData, eCSSProperty_UNKNOWN);
+ }
+
+ RefPtr<DeclarationBlock> newdecl = DeclarationBlock::FromCssText(
+ aCssText, servoEnv.mUrlExtraData, servoEnv.mCompatMode, servoEnv.mLoader,
+ servoEnv.mRuleType);
+
+ aRv = SetCSSDeclaration(newdecl, &closureData);
+}
+
+uint32_t nsDOMCSSDeclaration::Length() {
+ DeclarationBlock* decl = GetOrCreateCSSDeclaration(Operation::Read, nullptr);
+
+ if (decl) {
+ return decl->Count();
+ }
+
+ return 0;
+}
+
+void nsDOMCSSDeclaration::IndexedGetter(uint32_t aIndex, bool& aFound,
+ nsACString& aPropName) {
+ DeclarationBlock* decl = GetOrCreateCSSDeclaration(Operation::Read, nullptr);
+ aFound = decl && decl->GetNthProperty(aIndex, aPropName);
+}
+
+void nsDOMCSSDeclaration::GetPropertyValue(const nsACString& aPropertyName,
+ nsACString& aReturn) {
+ MOZ_ASSERT(aReturn.IsEmpty());
+ if (auto* decl = GetOrCreateCSSDeclaration(Operation::Read, nullptr)) {
+ decl->GetPropertyValue(aPropertyName, aReturn);
+ }
+}
+
+void nsDOMCSSDeclaration::GetPropertyPriority(const nsACString& aPropertyName,
+ nsACString& aPriority) {
+ MOZ_ASSERT(aPriority.IsEmpty());
+ DeclarationBlock* decl = GetOrCreateCSSDeclaration(Operation::Read, nullptr);
+ if (decl && decl->GetPropertyIsImportant(aPropertyName)) {
+ aPriority.AssignLiteral("important");
+ }
+}
+
+void nsDOMCSSDeclaration::SetProperty(const nsACString& aPropertyName,
+ const nsACString& aValue,
+ const nsACString& aPriority,
+ nsIPrincipal* aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ if (aValue.IsEmpty()) {
+ // If the new value of the property is an empty string we remove the
+ // property.
+ // XXX this ignores the priority string, should it?
+ return RemovePropertyInternal(aPropertyName, aRv);
+ }
+
+ // In the common (and fast) cases we can use the property id
+ nsCSSPropertyID propID = nsCSSProps::LookupProperty(aPropertyName);
+ if (propID == eCSSProperty_UNKNOWN) {
+ return;
+ }
+
+ bool important;
+ if (aPriority.IsEmpty()) {
+ important = false;
+ } else if (aPriority.LowerCaseEqualsASCII("important")) {
+ important = true;
+ } else {
+ // XXX silent failure?
+ return;
+ }
+
+ if (propID == eCSSPropertyExtra_variable) {
+ aRv = ParseCustomPropertyValue(aPropertyName, aValue, important,
+ aSubjectPrincipal);
+ return;
+ }
+ aRv = ParsePropertyValue(propID, aValue, important, aSubjectPrincipal);
+}
+
+void nsDOMCSSDeclaration::RemoveProperty(const nsACString& aPropertyName,
+ nsACString& aReturn,
+ ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return;
+ }
+ GetPropertyValue(aPropertyName, aReturn);
+ RemovePropertyInternal(aPropertyName, aRv);
+}
+
+/* static */ nsDOMCSSDeclaration::ParsingEnvironment
+nsDOMCSSDeclaration::GetParsingEnvironmentForRule(const css::Rule* aRule,
+ StyleCssRuleType aRuleType) {
+ if (!aRule) {
+ return {};
+ }
+
+ MOZ_ASSERT(aRule->Type() == aRuleType);
+
+ StyleSheet* sheet = aRule->GetStyleSheet();
+ if (!sheet) {
+ return {};
+ }
+
+ if (Document* document = sheet->GetAssociatedDocument()) {
+ return {
+ sheet->URLData(),
+ document->GetCompatibilityMode(),
+ document->CSSLoader(),
+ aRuleType,
+ };
+ }
+
+ return {
+ sheet->URLData(),
+ eCompatibility_FullStandards,
+ nullptr,
+ aRuleType,
+ };
+}
+
+template <typename Func>
+nsresult nsDOMCSSDeclaration::ModifyDeclaration(
+ nsIPrincipal* aSubjectPrincipal, MutationClosureData* aClosureData,
+ Func aFunc) {
+ RefPtr<DeclarationBlock> created;
+ DeclarationBlock* olddecl =
+ GetOrCreateCSSDeclaration(Operation::Modify, getter_AddRefs(created));
+ if (!olddecl) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
+ // Attribute setting code, which leads in turn to BeginUpdate. We
+ // need to start the update now so that the old rule doesn't get used
+ // between when we mutate the declaration and when we set the new
+ // rule (see stack in bug 209575).
+ mozAutoDocUpdate autoUpdate(DocToUpdate(), true);
+ RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
+
+ bool changed;
+ ParsingEnvironment servoEnv = GetParsingEnvironment(aSubjectPrincipal);
+ if (!servoEnv.mUrlExtraData) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ changed = aFunc(decl, servoEnv);
+
+ if (!changed) {
+ // Parsing failed -- but we don't throw an exception for that.
+ return NS_OK;
+ }
+
+ return SetCSSDeclaration(decl, aClosureData);
+}
+
+nsresult nsDOMCSSDeclaration::ParsePropertyValue(
+ const nsCSSPropertyID aPropID, const nsACString& aPropValue,
+ bool aIsImportant, nsIPrincipal* aSubjectPrincipal) {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR_RELEVANT_FOR_JS(LAYOUT_CSSParsing);
+
+ if (IsReadOnly()) {
+ return NS_OK;
+ }
+
+ DeclarationBlockMutationClosure closure = {};
+ MutationClosureData closureData;
+ GetPropertyChangeClosure(&closure, &closureData);
+
+ return ModifyDeclaration(
+ aSubjectPrincipal, &closureData,
+ [&](DeclarationBlock* decl, ParsingEnvironment& env) {
+ return Servo_DeclarationBlock_SetPropertyById(
+ decl->Raw(), aPropID, &aPropValue, aIsImportant, env.mUrlExtraData,
+ StyleParsingMode::DEFAULT, env.mCompatMode, env.mLoader,
+ env.mRuleType, closure);
+ });
+}
+
+nsresult nsDOMCSSDeclaration::ParseCustomPropertyValue(
+ const nsACString& aPropertyName, const nsACString& aPropValue,
+ bool aIsImportant, nsIPrincipal* aSubjectPrincipal) {
+ MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aPropertyName));
+
+ if (IsReadOnly()) {
+ return NS_OK;
+ }
+
+ DeclarationBlockMutationClosure closure = {};
+ MutationClosureData closureData;
+ GetPropertyChangeClosure(&closure, &closureData);
+
+ return ModifyDeclaration(
+ aSubjectPrincipal, &closureData,
+ [&](DeclarationBlock* decl, ParsingEnvironment& env) {
+ return Servo_DeclarationBlock_SetProperty(
+ decl->Raw(), &aPropertyName, &aPropValue, aIsImportant,
+ env.mUrlExtraData, StyleParsingMode::DEFAULT, env.mCompatMode,
+ env.mLoader, env.mRuleType, closure);
+ });
+}
+
+void nsDOMCSSDeclaration::RemovePropertyInternal(nsCSSPropertyID aPropID,
+ ErrorResult& aRv) {
+ DeclarationBlock* olddecl =
+ GetOrCreateCSSDeclaration(Operation::RemoveProperty, nullptr);
+ if (IsReadOnly()) {
+ return;
+ }
+
+ if (!olddecl) {
+ return; // no decl, so nothing to remove
+ }
+
+ // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
+ // Attribute setting code, which leads in turn to BeginUpdate. We
+ // need to start the update now so that the old rule doesn't get used
+ // between when we mutate the declaration and when we set the new
+ // rule (see stack in bug 209575).
+ mozAutoDocUpdate autoUpdate(DocToUpdate(), true);
+
+ DeclarationBlockMutationClosure closure = {};
+ MutationClosureData closureData;
+ GetPropertyChangeClosure(&closure, &closureData);
+
+ RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
+ if (!decl->RemovePropertyByID(aPropID, closure)) {
+ return;
+ }
+ aRv = SetCSSDeclaration(decl, &closureData);
+}
+
+void nsDOMCSSDeclaration::RemovePropertyInternal(
+ const nsACString& aPropertyName, ErrorResult& aRv) {
+ if (IsReadOnly()) {
+ return;
+ }
+
+ DeclarationBlock* olddecl =
+ GetOrCreateCSSDeclaration(Operation::RemoveProperty, nullptr);
+ if (!olddecl) {
+ return; // no decl, so nothing to remove
+ }
+
+ // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
+ // Attribute setting code, which leads in turn to BeginUpdate. We
+ // need to start the update now so that the old rule doesn't get used
+ // between when we mutate the declaration and when we set the new
+ // rule (see stack in bug 209575).
+ mozAutoDocUpdate autoUpdate(DocToUpdate(), true);
+
+ DeclarationBlockMutationClosure closure = {};
+ MutationClosureData closureData;
+ GetPropertyChangeClosure(&closure, &closureData);
+
+ RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
+ if (!decl->RemoveProperty(aPropertyName, closure)) {
+ return;
+ }
+ aRv = SetCSSDeclaration(decl, &closureData);
+}
diff --git a/layout/style/nsDOMCSSDeclaration.h b/layout/style/nsDOMCSSDeclaration.h
new file mode 100644
index 0000000000..28810c280a
--- /dev/null
+++ b/layout/style/nsDOMCSSDeclaration.h
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for DOM objects for element.style and cssStyleRule.style */
+
+#ifndef nsDOMCSSDeclaration_h___
+#define nsDOMCSSDeclaration_h___
+
+#include "nsICSSDeclaration.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/URLExtraData.h"
+#include "nsAttrValue.h"
+#include "nsCOMPtr.h"
+#include "nsCompatibility.h"
+
+class nsIPrincipal;
+struct JSContext;
+class JSObject;
+
+namespace mozilla {
+enum class StyleCssRuleType : uint8_t;
+class DeclarationBlock;
+struct DeclarationBlockMutationClosure;
+namespace css {
+class Loader;
+class Rule;
+} // namespace css
+namespace dom {
+class Document;
+class Element;
+} // namespace dom
+
+struct MutationClosureData {
+ MutationClosureData() = default;
+
+ mozilla::dom::Element* mElement = nullptr;
+ Maybe<nsAttrValue> mOldValue;
+ uint8_t mModType = 0;
+ bool mWasCalled = false;
+ bool mShouldBeCalled = false;
+};
+
+} // namespace mozilla
+
+class nsDOMCSSDeclaration : public nsICSSDeclaration {
+ public:
+ // Only implement QueryInterface; subclasses have the responsibility
+ // of implementing AddRef/Release.
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+
+ // Declare addref and release so they can be called on us, but don't
+ // implement them. Our subclasses must handle their own
+ // refcounting.
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() override = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release() override = 0;
+
+ /**
+ * Method analogous to CSSStyleDeclaration::GetPropertyValue,
+ * which obeys all the same restrictions.
+ */
+ virtual void GetPropertyValue(const nsCSSPropertyID aPropID,
+ nsACString& aValue);
+
+ /**
+ * Method analogous to CSSStyleDeclaration::SetProperty. This
+ * method does NOT allow setting a priority (the priority will
+ * always be set to default priority).
+ */
+ virtual void SetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsACString& aValue,
+ nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& aRv);
+
+ // Require subclasses to implement |GetParentRule|.
+ // NS_DECL_NSIDOMCSSSTYLEDECLARATION
+ void GetCssText(nsACString& aCssText) override;
+ void SetCssText(const nsACString& aCssText, nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& aRv) override;
+ void GetPropertyValue(const nsACString& propertyName,
+ nsACString& _retval) override;
+ void RemoveProperty(const nsACString& propertyName, nsACString& _retval,
+ mozilla::ErrorResult& aRv) override;
+ void GetPropertyPriority(const nsACString& propertyName,
+ nsACString& aPriority) override;
+ void SetProperty(const nsACString& propertyName, const nsACString& value,
+ const nsACString& priority, nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& aRv) override;
+ uint32_t Length() override;
+
+ // WebIDL interface for CSS2Properties
+ virtual void IndexedGetter(uint32_t aIndex, bool& aFound,
+ nsACString& aPropName) override;
+
+ JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
+
+ // Information needed to parse a declaration for Servo side.
+ // Put this in public so other Servo parsing functions can reuse this.
+ struct MOZ_STACK_CLASS ParsingEnvironment {
+ RefPtr<mozilla::URLExtraData> mUrlExtraData;
+ nsCompatibility mCompatMode = eCompatibility_FullStandards;
+ mozilla::css::Loader* mLoader = nullptr;
+ mozilla::StyleCssRuleType mRuleType{1 /* Style */};
+ };
+
+ protected:
+ // The reason for calling GetOrCreateCSSDeclaration.
+ enum class Operation {
+ // We are calling GetOrCreateCSSDeclaration so that we can read from it.
+ // Does not allocate a new declaration if we don't have one yet; returns
+ // nullptr in this case.
+ Read,
+
+ // We are calling GetOrCreateCSSDeclaration so that we can set a property on
+ // it or re-parse the whole declaration. Allocates a new declaration if we
+ // don't have one yet. A nullptr return value indicates an error allocating
+ // the declaration.
+ Modify,
+
+ // We are calling GetOrCreateCSSDeclaration so that we can remove a property
+ // from it. Does not allocate a new declaration if we don't have one yet;
+ // returns nullptr in this case.
+ RemoveProperty,
+ };
+
+ // If aOperation is Modify, aCreated must be non-null and the call may set it
+ // to point to the newly created object.
+ virtual mozilla::DeclarationBlock* GetOrCreateCSSDeclaration(
+ Operation aOperation, mozilla::DeclarationBlock** aCreated) = 0;
+
+ virtual nsresult SetCSSDeclaration(
+ mozilla::DeclarationBlock* aDecl,
+ mozilla::MutationClosureData* aClosureData) = 0;
+ // Document that we must call BeginUpdate/EndUpdate on around the
+ // calls to SetCSSDeclaration and the style rule mutation that leads
+ // to it.
+ virtual mozilla::dom::Document* DocToUpdate() = 0;
+
+ // mUrlExtraData returns URL data for parsing url values in
+ // CSS. Returns nullptr on failure. If mUrlExtraData is nullptr,
+ // mCompatMode may not be set to anything meaningful.
+ // If aSubjectPrincipal is passed, it should be the subject principal of the
+ // scripted caller that initiated the parser.
+ virtual ParsingEnvironment GetParsingEnvironment(
+ nsIPrincipal* aSubjectPrincipal = nullptr) const = 0;
+
+ // An implementation for GetParsingEnvironment for callers wrapping a
+ // css::Rule.
+ //
+ // The RuleType argument is just to avoid a virtual call, since all callers
+ // know it statically. Should be equal to aRule->Type().
+ static ParsingEnvironment GetParsingEnvironmentForRule(
+ const mozilla::css::Rule* aRule, mozilla::StyleCssRuleType);
+
+ nsresult ParsePropertyValue(const nsCSSPropertyID aPropID,
+ const nsACString& aPropValue, bool aIsImportant,
+ nsIPrincipal* aSubjectPrincipal);
+
+ nsresult ParseCustomPropertyValue(const nsACString& aPropertyName,
+ const nsACString& aPropValue,
+ bool aIsImportant,
+ nsIPrincipal* aSubjectPrincipal);
+
+ void RemovePropertyInternal(nsCSSPropertyID aPropID,
+ mozilla::ErrorResult& aRv);
+ void RemovePropertyInternal(const nsACString& aPropert,
+ mozilla::ErrorResult& aRv);
+
+ virtual void GetPropertyChangeClosure(
+ mozilla::DeclarationBlockMutationClosure* aClosure,
+ mozilla::MutationClosureData* aClosureData) {}
+
+ protected:
+ virtual ~nsDOMCSSDeclaration();
+
+ private:
+ template <typename ServoFunc>
+ inline nsresult ModifyDeclaration(nsIPrincipal* aSubjectPrincipal,
+ mozilla::MutationClosureData* aClosureData,
+ ServoFunc aServoFunc);
+};
+
+#endif // nsDOMCSSDeclaration_h___
diff --git a/layout/style/nsDOMCSSValueList.cpp b/layout/style/nsDOMCSSValueList.cpp
new file mode 100644
index 0000000000..6cee83e509
--- /dev/null
+++ b/layout/style/nsDOMCSSValueList.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object representing lists of values in DOM computed style */
+
+#include "nsDOMCSSValueList.h"
+
+#include <utility>
+
+#include "mozilla/ErrorResult.h"
+#include "nsString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsDOMCSSValueList::nsDOMCSSValueList(bool aCommaDelimited)
+ : CSSValue(), mCommaDelimited(aCommaDelimited) {}
+
+nsDOMCSSValueList::~nsDOMCSSValueList() = default;
+
+void nsDOMCSSValueList::AppendCSSValue(already_AddRefed<CSSValue> aValue) {
+ RefPtr<CSSValue> val = aValue;
+ mCSSValues.AppendElement(std::move(val));
+}
+
+void nsDOMCSSValueList::GetCssText(nsAString& aCssText) {
+ aCssText.Truncate();
+
+ uint32_t count = mCSSValues.Length();
+
+ nsAutoString separator;
+ if (mCommaDelimited) {
+ separator.AssignLiteral(", ");
+ } else {
+ separator.Assign(char16_t(' '));
+ }
+
+ nsAutoString tmpStr;
+ for (uint32_t i = 0; i < count; ++i) {
+ CSSValue* cssValue = mCSSValues[i];
+ NS_ASSERTION(cssValue,
+ "Eek! Someone filled the value list with null CSSValues!");
+ if (cssValue) {
+ cssValue->GetCssText(tmpStr);
+ if (tmpStr.IsEmpty()) {
+#ifdef DEBUG_caillon
+ NS_ERROR("Eek! An empty CSSValue! Bad!");
+#endif
+ continue;
+ }
+ // If this isn't the first item in the list, then
+ // it's ok to append a separator.
+ if (!aCssText.IsEmpty()) {
+ aCssText.Append(separator);
+ }
+ aCssText.Append(tmpStr);
+ }
+ }
+}
diff --git a/layout/style/nsDOMCSSValueList.h b/layout/style/nsDOMCSSValueList.h
new file mode 100644
index 0000000000..ecab14e225
--- /dev/null
+++ b/layout/style/nsDOMCSSValueList.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object representing lists of values in DOM computed style */
+
+#ifndef nsDOMCSSValueList_h___
+#define nsDOMCSSValueList_h___
+
+#include "CSSValue.h"
+#include "nsTArray.h"
+
+class nsDOMCSSValueList final : public mozilla::dom::CSSValue {
+ public:
+ // nsDOMCSSValueList
+ explicit nsDOMCSSValueList(bool aCommaDelimited);
+
+ /**
+ * Adds a value to this list.
+ */
+ void AppendCSSValue(already_AddRefed<CSSValue> aValue);
+
+ uint16_t CssValueType() const final { return CSS_VALUE_LIST; }
+
+ uint32_t Length() const { return mCSSValues.Length(); }
+ void GetCssText(nsAString&) final;
+
+ protected:
+ virtual ~nsDOMCSSValueList();
+
+ bool mCommaDelimited; // some value lists use a comma as the delimiter, some
+ // just use spaces.
+
+ nsTArray<RefPtr<CSSValue>> mCSSValues;
+};
+
+#endif /* nsDOMCSSValueList_h___ */
diff --git a/layout/style/nsFontFaceLoader.cpp b/layout/style/nsFontFaceLoader.cpp
new file mode 100644
index 0000000000..4986f2348f
--- /dev/null
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -0,0 +1,379 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* code for loading in @font-face defined font data */
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Logging.h"
+
+#include "nsFontFaceLoader.h"
+
+#include "nsError.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "FontFaceSet.h"
+#include "nsPresContext.h"
+#include "nsIHttpChannel.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsContentPolicyUtils.h"
+#include "nsNetCID.h"
+
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define LOG(args) \
+ MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug)
+
+static uint32_t GetFallbackDelay() {
+ return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000);
+}
+
+static uint32_t GetShortFallbackDelay() {
+ return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay_short",
+ 100);
+}
+
+nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry* aUserFontEntry,
+ uint32_t aSrcIndex,
+ FontFaceSetImpl* aFontFaceSet,
+ nsIChannel* aChannel)
+ : mUserFontEntry(aUserFontEntry),
+ mFontFaceSet(aFontFaceSet),
+ mChannel(aChannel),
+ mStreamLoader(nullptr),
+ mSrcIndex(aSrcIndex) {
+ MOZ_ASSERT(mFontFaceSet,
+ "We should get a valid FontFaceSet from the caller!");
+
+ const gfxFontFaceSrc& src = aUserFontEntry->SourceAt(mSrcIndex);
+ MOZ_ASSERT(src.mSourceType == gfxFontFaceSrc::eSourceType_URL);
+
+ mFontURI = src.mURI->get();
+ mStartTime = TimeStamp::Now();
+
+ // We add an explicit load block rather than just rely on the network
+ // request's block, since we need to do some OMT work after the load
+ // is finished before we unblock load.
+ auto* doc = mFontFaceSet->GetDocument();
+ if (doc) {
+ doc->BlockOnload();
+ }
+}
+
+nsFontFaceLoader::~nsFontFaceLoader() {
+ MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
+ MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
+ if (mUserFontEntry) {
+ mUserFontEntry->mLoader = nullptr;
+ }
+ if (mLoadTimer) {
+ mLoadTimer->Cancel();
+ mLoadTimer = nullptr;
+ }
+ if (mFontFaceSet) {
+ mFontFaceSet->RemoveLoader(this);
+ auto* doc = mFontFaceSet->GetDocument();
+ if (doc) {
+ doc->UnblockOnload(false);
+ }
+ }
+}
+
+void nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader) {
+ int32_t loadTimeout;
+ StyleFontDisplay fontDisplay = GetFontDisplay();
+ if (fontDisplay == StyleFontDisplay::Auto ||
+ fontDisplay == StyleFontDisplay::Block) {
+ loadTimeout = GetFallbackDelay();
+ } else {
+ loadTimeout = GetShortFallbackDelay();
+ }
+
+ if (loadTimeout > 0) {
+ NS_NewTimerWithFuncCallback(getter_AddRefs(mLoadTimer), LoadTimerCallback,
+ static_cast<void*>(this), loadTimeout,
+ nsITimer::TYPE_ONE_SHOT, "LoadTimerCallback",
+ GetMainThreadSerialEventTarget());
+ } else {
+ mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+ }
+ mStreamLoader = aStreamLoader;
+}
+
+/* static */
+void nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure) {
+ nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure);
+
+ MOZ_DIAGNOSTIC_ASSERT(!loader->mInLoadTimerCallback);
+ MOZ_DIAGNOSTIC_ASSERT(!loader->mInStreamComplete);
+ AutoRestore<bool> scope{loader->mInLoadTimerCallback};
+ loader->mInLoadTimerCallback = true;
+
+ if (!loader->mFontFaceSet) {
+ // We've been canceled
+ return;
+ }
+
+ gfxUserFontEntry* ufe = loader->mUserFontEntry.get();
+ StyleFontDisplay fontDisplay = loader->GetFontDisplay();
+
+ // Depending upon the value of the font-display descriptor for the font,
+ // their may be one or two timeouts associated with each font. The
+ // LOADING_SLOWLY state indicates that the fallback font is shown. The
+ // LOADING_TIMED_OUT state indicates that the fallback font is shown *and* the
+ // downloaded font resource will not replace the fallback font when the load
+ // completes.
+
+ bool updateUserFontSet = true;
+ switch (fontDisplay) {
+ case StyleFontDisplay::Auto:
+ case StyleFontDisplay::Block:
+ // If the entry is loading, check whether it's >75% done; if so,
+ // we allow another timeout period before showing a fallback font.
+ if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
+ int64_t contentLength;
+ uint32_t numBytesRead;
+ if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) &&
+ contentLength > 0 && contentLength < UINT32_MAX &&
+ NS_SUCCEEDED(
+ loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) &&
+ numBytesRead > 3 * (uint32_t(contentLength) >> 2)) {
+ // More than 3/4 the data has been downloaded, so allow 50% extra
+ // time and hope the remainder will arrive before the additional
+ // time expires.
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_ALMOST_DONE;
+ uint32_t delay;
+ loader->mLoadTimer->GetDelay(&delay);
+ loader->mLoadTimer->InitWithNamedFuncCallback(
+ LoadTimerCallback, static_cast<void*>(loader), delay >> 1,
+ nsITimer::TYPE_ONE_SHOT, "nsFontFaceLoader::LoadTimerCallback");
+ updateUserFontSet = false;
+ LOG(("userfonts (%p) 75%% done, resetting timer\n", loader));
+ }
+ }
+ if (updateUserFontSet) {
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+ }
+ break;
+ case StyleFontDisplay::Swap:
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+ break;
+ case StyleFontDisplay::Fallback: {
+ if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+ } else {
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
+ updateUserFontSet = false;
+ }
+ break;
+ }
+ case StyleFontDisplay::Optional:
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("strange font-display value");
+ break;
+ }
+
+ // If the font is not 75% loaded, or if we've already timed out once
+ // before, we mark this entry as "loading slowly", so the fallback
+ // font will be used in the meantime, and tell the context to refresh.
+ if (updateUserFontSet) {
+ nsTArray<RefPtr<gfxUserFontSet>> fontSets;
+ ufe->GetUserFontSets(fontSets);
+ for (gfxUserFontSet* fontSet : fontSets) {
+ nsPresContext* ctx = FontFaceSetImpl::GetPresContextFor(fontSet);
+ if (ctx) {
+ fontSet->IncrementGeneration();
+ ctx->UserFontSetUpdated(ufe);
+ LOG(("userfonts (%p) timeout reflow for pres context %p display %d\n",
+ loader, ctx, static_cast<int>(fontDisplay)));
+ }
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver, nsIRequestObserver)
+
+// nsIStreamLoaderObserver
+NS_IMETHODIMP
+nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* aContext, nsresult aStatus,
+ uint32_t aStringLen,
+ const uint8_t* aString) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
+ MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
+
+ AutoRestore<bool> scope{mInStreamComplete};
+ mInStreamComplete = true;
+
+ DropChannel();
+
+ if (mLoadTimer) {
+ mLoadTimer->Cancel();
+ mLoadTimer = nullptr;
+ }
+
+ if (!mFontFaceSet) {
+ // We've been canceled
+ return aStatus;
+ }
+
+ TimeStamp doneTime = TimeStamp::Now();
+ TimeDuration downloadTime = doneTime - mStartTime;
+ uint32_t downloadTimeMS = uint32_t(downloadTime.ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::WEBFONT_DOWNLOAD_TIME, downloadTimeMS);
+
+ if (GetFontDisplay() == StyleFontDisplay::Fallback) {
+ uint32_t loadTimeout = GetFallbackDelay();
+ if (downloadTimeMS > loadTimeout &&
+ (mUserFontEntry->mFontDataLoadingState ==
+ gfxUserFontEntry::LOADING_SLOWLY)) {
+ mUserFontEntry->mFontDataLoadingState =
+ gfxUserFontEntry::LOADING_TIMED_OUT;
+ }
+ }
+
+ if (LOG_ENABLED()) {
+ if (NS_SUCCEEDED(aStatus)) {
+ LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
+ this, mFontURI->GetSpecOrDefault().get(), downloadTimeMS));
+ } else {
+ LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8" PRIx32
+ "\n",
+ this, mFontURI->GetSpecOrDefault().get(),
+ static_cast<uint32_t>(aStatus)));
+ }
+ }
+
+ if (NS_SUCCEEDED(aStatus)) {
+ // for HTTP requests, check whether the request _actually_ succeeded;
+ // the "request status" in aStatus does not necessarily indicate this,
+ // because HTTP responses such as 404 (Not Found) will still result in
+ // a success code and potentially an HTML error page from the server
+ // as the resulting data. We don't want to use that as a font.
+ nsCOMPtr<nsIRequest> request;
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ aLoader->GetRequest(getter_AddRefs(request));
+ httpChannel = do_QueryInterface(request);
+ if (httpChannel) {
+ bool succeeded;
+ nsresult rv = httpChannel->GetRequestSucceeded(&succeeded);
+ if (NS_SUCCEEDED(rv) && !succeeded) {
+ aStatus = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ }
+
+ mFontFaceSet->RecordFontLoadDone(aStringLen, doneTime);
+
+ // The userFontEntry is responsible for freeing the downloaded data
+ // (aString) when finished with it; the pointer is no longer valid
+ // after FontDataDownloadComplete returns.
+ // This is called even in the case of a failed download (HTTP 404, etc),
+ // as there may still be data to be freed (e.g. an error page),
+ // and we need to load the next source.
+
+ // FontDataDownloadComplete will load the platform font on a worker thread,
+ // and will call FontLoadComplete when it has finished its work.
+ mUserFontEntry->FontDataDownloadComplete(mSrcIndex, aString, aStringLen,
+ aStatus, this);
+ return NS_SUCCESS_ADOPTED_DATA;
+}
+
+nsresult nsFontFaceLoader::FontLoadComplete() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mFontFaceSet) {
+ // We've been canceled
+ return NS_OK;
+ }
+
+ // when new font loaded, need to reflow
+ nsTArray<RefPtr<gfxUserFontSet>> fontSets;
+ mUserFontEntry->GetUserFontSets(fontSets);
+ for (gfxUserFontSet* fontSet : fontSets) {
+ nsPresContext* ctx = FontFaceSetImpl::GetPresContextFor(fontSet);
+ if (ctx) {
+ // Update layout for the presence of the new font. Since this is
+ // asynchronous, reflows will coalesce.
+ ctx->UserFontSetUpdated(mUserFontEntry);
+ LOG(("userfonts (%p) reflow for pres context %p\n", this, ctx));
+ }
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet);
+ mFontFaceSet->RemoveLoader(this);
+ auto* doc = mFontFaceSet->GetDocument();
+ if (doc) {
+ doc->UnblockOnload(false);
+ }
+ mFontFaceSet = nullptr;
+
+ return NS_OK;
+}
+
+// nsIRequestObserver
+NS_IMETHODIMP
+nsFontFaceLoader::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIThreadRetargetableRequest> req = do_QueryInterface(aRequest);
+ if (req) {
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ RefPtr<TaskQueue> queue =
+ TaskQueue::Create(sts.forget(), "nsFontFaceLoader STS Delivery Queue");
+ Unused << NS_WARN_IF(NS_FAILED(req->RetargetDeliveryTo(queue)));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFontFaceLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ MOZ_ASSERT(NS_IsMainThread());
+ DropChannel();
+ return NS_OK;
+}
+
+void nsFontFaceLoader::Cancel() {
+ MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
+ MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
+ MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet);
+
+ mUserFontEntry->LoadCanceled();
+ mUserFontEntry = nullptr;
+ auto* doc = mFontFaceSet->GetDocument();
+ if (doc) {
+ doc->UnblockOnload(false);
+ }
+ mFontFaceSet = nullptr;
+ if (mLoadTimer) {
+ mLoadTimer->Cancel();
+ mLoadTimer = nullptr;
+ }
+ if (nsCOMPtr<nsIChannel> channel = std::move(mChannel)) {
+ channel->CancelWithReason(NS_BINDING_ABORTED,
+ "nsFontFaceLoader::OnStopRequest"_ns);
+ }
+}
+
+StyleFontDisplay nsFontFaceLoader::GetFontDisplay() {
+ return mUserFontEntry->GetFontDisplay();
+}
+
+#undef LOG
+#undef LOG_ENABLED
diff --git a/layout/style/nsFontFaceLoader.h b/layout/style/nsFontFaceLoader.h
new file mode 100644
index 0000000000..42a1052167
--- /dev/null
+++ b/layout/style/nsFontFaceLoader.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* code for loading in @font-face defined font data */
+
+#ifndef nsFontFaceLoader_h_
+#define nsFontFaceLoader_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/FontFaceSetImpl.h"
+#include "nsCOMPtr.h"
+#include "nsIFontLoadCompleteCallback.h"
+#include "nsIStreamLoader.h"
+#include "nsIChannel.h"
+#include "nsIRequestObserver.h"
+#include "gfxUserFontSet.h"
+#include "nsHashKeys.h"
+#include "nsTHashtable.h"
+
+class nsIPrincipal;
+class nsITimer;
+
+class nsFontFaceLoader final : public nsIStreamLoaderObserver,
+ public nsIRequestObserver,
+ public nsIFontLoadCompleteCallback {
+ public:
+ nsFontFaceLoader(gfxUserFontEntry* aUserFontEntry, uint32_t aSrcIndex,
+ mozilla::dom::FontFaceSetImpl* aFontFaceSet,
+ nsIChannel* aChannel);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // cancel the load and remove its reference to mFontFaceSet
+ void Cancel();
+
+ void DropChannel() { mChannel = nullptr; }
+
+ void StartedLoading(nsIStreamLoader* aStreamLoader);
+
+ static void LoadTimerCallback(nsITimer* aTimer, void* aClosure);
+
+ gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; }
+
+ // Called by the gfxUserFontEntry once it has finished the platform font
+ // loading.
+ NS_IMETHODIMP FontLoadComplete() final;
+
+ protected:
+ virtual ~nsFontFaceLoader();
+
+ // helper method for determining the font-display value
+ mozilla::StyleFontDisplay GetFontDisplay();
+
+ private:
+ RefPtr<gfxUserFontEntry> mUserFontEntry;
+ nsCOMPtr<nsIURI> mFontURI;
+ // Cleared in FontFaceSet::~FontFaceSet, and on cancelation and such too.
+ mozilla::dom::FontFaceSetImpl* MOZ_NON_OWNING_REF mFontFaceSet;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsITimer> mLoadTimer;
+ mozilla::TimeStamp mStartTime;
+ nsIStreamLoader* mStreamLoader;
+ uint32_t mSrcIndex;
+ bool mInStreamComplete = false;
+ bool mInLoadTimerCallback = false;
+};
+
+#endif /* !defined(nsFontFaceLoader_h_) */
diff --git a/layout/style/nsFontFaceUtils.cpp b/layout/style/nsFontFaceUtils.cpp
new file mode 100644
index 0000000000..fdf5095436
--- /dev/null
+++ b/layout/style/nsFontFaceUtils.cpp
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsFontFaceUtils.h"
+
+#include "gfxTextRun.h"
+#include "gfxUserFontSet.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RestyleManager.h"
+#include "nsFontMetrics.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTArray.h"
+
+using namespace mozilla;
+
+enum class FontUsageKind {
+ // The frame did not use the given font.
+ None = 0,
+ // The frame uses the given font, but doesn't use font-metric-dependent units,
+ // which means that its style doesn't depend on this font.
+ Frame = 1 << 0,
+ // The frame uses has some font-metric-dependent units on this font.
+ // This means that its style depends on this font, and we need to restyle the
+ // element the frame came from.
+ FontMetrics = 1 << 1,
+
+ Max = Frame | FontMetrics,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(FontUsageKind);
+
+static bool IsFontReferenced(const ComputedStyle& aStyle,
+ const nsAString& aFamilyName) {
+ for (const auto& family :
+ aStyle.StyleFont()->mFont.family.families.list.AsSpan()) {
+ if (family.IsNamedFamily(aFamilyName)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static FontUsageKind StyleFontUsage(nsIFrame* aFrame, ComputedStyle* aStyle,
+ nsPresContext* aPresContext,
+ const gfxUserFontEntry* aFont,
+ const nsAString& aFamilyName,
+ bool aIsExtraStyle) {
+ MOZ_ASSERT(NS_ConvertUTF8toUTF16(aFont->FamilyName()) == aFamilyName);
+
+ auto FontIsUsed = [&aFont, &aPresContext,
+ &aFamilyName](ComputedStyle* aStyle) {
+ if (!IsFontReferenced(*aStyle, aFamilyName)) {
+ return false;
+ }
+
+ // family name is in the fontlist, check to see if the font group
+ // associated with the frame includes the specific userfont.
+ //
+ // TODO(emilio): Is this check really useful? I guess it's useful for
+ // different font faces of the same font?
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForComputedStyle(
+ aStyle, aPresContext, 1.0f);
+ return fm->GetThebesFontGroup()->ContainsUserFont(aFont);
+ };
+
+ auto usage = FontUsageKind::None;
+
+ if (FontIsUsed(aStyle)) {
+ usage |= FontUsageKind::Frame;
+ if (aStyle->DependsOnSelfFontMetrics()) {
+ usage |= FontUsageKind::FontMetrics;
+ }
+ }
+
+ if (aStyle->DependsOnInheritedFontMetrics() &&
+ !(usage & FontUsageKind::FontMetrics)) {
+ ComputedStyle* parentStyle = nullptr;
+ if (aIsExtraStyle) {
+ parentStyle = aFrame->Style();
+ } else {
+ nsIFrame* provider = nullptr;
+ parentStyle = aFrame->GetParentComputedStyle(&provider);
+ }
+
+ if (parentStyle && FontIsUsed(parentStyle)) {
+ usage |= FontUsageKind::FontMetrics;
+ }
+ }
+
+ return usage;
+}
+
+static FontUsageKind FrameFontUsage(nsIFrame* aFrame,
+ nsPresContext* aPresContext,
+ const gfxUserFontEntry* aFont,
+ const nsAString& aFamilyName) {
+ // check the style of the frame
+ FontUsageKind kind = StyleFontUsage(aFrame, aFrame->Style(), aPresContext,
+ aFont, aFamilyName, /* extra = */ false);
+ if (kind == FontUsageKind::Max) {
+ return kind;
+ }
+
+ // check additional styles
+ int32_t contextIndex = 0;
+ for (ComputedStyle* extraContext;
+ (extraContext = aFrame->GetAdditionalComputedStyle(contextIndex));
+ ++contextIndex) {
+ kind |= StyleFontUsage(aFrame, extraContext, aPresContext, aFont,
+ aFamilyName, /* extra = */ true);
+ if (kind == FontUsageKind::Max) {
+ break;
+ }
+ }
+
+ return kind;
+}
+
+// TODO(emilio): Can we use the restyle-hint machinery instead of this?
+static void ScheduleReflow(PresShell* aPresShell, nsIFrame* aFrame) {
+ nsIFrame* f = aFrame;
+ if (f->IsSVGFrame() || f->IsInSVGTextSubtree()) {
+ // SVG frames (and the non-SVG descendants of an SVGTextFrame) need special
+ // reflow handling. We need to search upwards for the first displayed
+ // SVGOuterSVGFrame or non-SVG frame, which is the frame we can call
+ // FrameNeedsReflow on. (This logic is based on
+ // SVGUtils::ScheduleReflowSVG and
+ // SVGTextFrame::ScheduleReflowSVGNonDisplayText.)
+ if (f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ while (f) {
+ if (!f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ if (f->IsSubtreeDirty()) {
+ // This is a displayed frame, so if it is already dirty, we
+ // will be reflowed soon anyway. No need to call
+ // FrameNeedsReflow again, then.
+ return;
+ }
+ if (!f->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
+ !f->IsInSVGTextSubtree()) {
+ break;
+ }
+ f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ f = f->GetParent();
+ }
+ MOZ_ASSERT(f, "should have found an ancestor frame to reflow");
+ }
+ }
+
+ aPresShell->FrameNeedsReflow(f, IntrinsicDirty::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+}
+
+enum class ReflowAlreadyScheduled {
+ No,
+ Yes,
+};
+
+/* static */
+void nsFontFaceUtils::MarkDirtyForFontChange(nsIFrame* aSubtreeRoot,
+ const gfxUserFontEntry* aFont) {
+ MOZ_ASSERT(aFont);
+ AutoTArray<nsIFrame*, 4> subtrees;
+ subtrees.AppendElement(aSubtreeRoot);
+
+ nsPresContext* pc = aSubtreeRoot->PresContext();
+ PresShell* presShell = pc->PresShell();
+
+ const bool usesMetricsFromStyle = pc->StyleSet()->UsesFontMetrics();
+
+ // StyleSingleFontFamily::IsNamedFamily expects a UTF-16 string. Convert it
+ // once here rather than on each call.
+ NS_ConvertUTF8toUTF16 familyName(aFont->FamilyName());
+
+ // check descendants, iterating over subtrees that may include
+ // additional subtrees associated with placeholders
+ do {
+ nsIFrame* subtreeRoot = subtrees.PopLastElement();
+
+ // Check all descendants to see if they use the font
+ AutoTArray<std::pair<nsIFrame*, ReflowAlreadyScheduled>, 32> stack;
+ stack.AppendElement(
+ std::make_pair(subtreeRoot, ReflowAlreadyScheduled::No));
+
+ do {
+ auto pair = stack.PopLastElement();
+ nsIFrame* f = pair.first;
+ ReflowAlreadyScheduled alreadyScheduled = pair.second;
+
+ // if this frame uses the font, mark its descendants dirty
+ // and skip checking its children
+ FontUsageKind kind = FrameFontUsage(f, pc, aFont, familyName);
+ if (kind != FontUsageKind::None) {
+ if ((kind & FontUsageKind::Frame) &&
+ alreadyScheduled == ReflowAlreadyScheduled::No) {
+ ScheduleReflow(presShell, f);
+ alreadyScheduled = ReflowAlreadyScheduled::Yes;
+ }
+ if (kind & FontUsageKind::FontMetrics) {
+ MOZ_ASSERT(f->GetContent() && f->GetContent()->IsElement(),
+ "How could we target a non-element with selectors?");
+ f->PresContext()->RestyleManager()->PostRestyleEvent(
+ dom::Element::FromNode(f->GetContent()),
+ RestyleHint::RECASCADE_SELF, nsChangeHint(0));
+ }
+ }
+
+ if (alreadyScheduled == ReflowAlreadyScheduled::No ||
+ usesMetricsFromStyle) {
+ if (f->IsPlaceholderFrame()) {
+ nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
+ if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
+ // We have another distinct subtree we need to mark.
+ subtrees.AppendElement(oof);
+ }
+ }
+
+ for (const auto& childList : f->ChildLists()) {
+ for (nsIFrame* kid : childList.mList) {
+ stack.AppendElement(std::make_pair(kid, alreadyScheduled));
+ }
+ }
+ }
+ } while (!stack.IsEmpty());
+ } while (!subtrees.IsEmpty());
+}
diff --git a/layout/style/nsFontFaceUtils.h b/layout/style/nsFontFaceUtils.h
new file mode 100644
index 0000000000..d753ea3ee2
--- /dev/null
+++ b/layout/style/nsFontFaceUtils.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* helper utilities for working with downloadable fonts */
+
+#ifndef nsFontFaceUtils_h_
+#define nsFontFaceUtils_h_
+
+class gfxUserFontEntry;
+class nsIFrame;
+
+class nsFontFaceUtils {
+ public:
+ // mark dirty frames affected by a downloadable font
+ static void MarkDirtyForFontChange(nsIFrame* aSubtreeRoot,
+ const gfxUserFontEntry* aFont);
+};
+
+#endif /* !defined(nsFontFaceUtils_h_) */
diff --git a/layout/style/nsICSSDeclaration.cpp b/layout/style/nsICSSDeclaration.cpp
new file mode 100644
index 0000000000..2c0f2d813a
--- /dev/null
+++ b/layout/style/nsICSSDeclaration.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Implementation of non-inline bits of nsICSSDeclaration. */
+
+#include "nsICSSDeclaration.h"
+
+#include "nsINode.h"
+#include "mozilla/css/Rule.h"
+
+using mozilla::dom::DocGroup;
+
+DocGroup* nsICSSDeclaration::GetDocGroup() {
+ nsINode* parentNode = GetAssociatedNode();
+ if (!parentNode) {
+ return nullptr;
+ }
+
+ return parentNode->GetDocGroup();
+}
+
+bool nsICSSDeclaration::IsReadOnly() {
+ mozilla::css::Rule* rule = GetParentRule();
+ return rule && rule->IsReadOnly();
+}
diff --git a/layout/style/nsICSSDeclaration.h b/layout/style/nsICSSDeclaration.h
new file mode 100644
index 0000000000..1750fd75c3
--- /dev/null
+++ b/layout/style/nsICSSDeclaration.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * interface for accessing style declarations using enums instead of strings,
+ * for internal use
+ */
+
+#ifndef nsICSSDeclaration_h__
+#define nsICSSDeclaration_h__
+
+/**
+ * This interface provides access to methods analogous to those of
+ * CSSStyleDeclaration; the difference is that these use nsCSSPropertyID
+ * enums for the prop names instead of using strings.
+ */
+
+#include "mozilla/Attributes.h"
+#include "nsCSSPropertyID.h"
+#include "mozilla/dom/CSSValue.h"
+#include "mozilla/ErrorResult.h"
+#include "nsWrapperCache.h"
+#include "nsStringFwd.h"
+#include "nsCOMPtr.h"
+
+class nsINode;
+class nsIPrincipal;
+namespace mozilla {
+class ErrorResult;
+
+namespace css {
+class Rule;
+} // namespace css
+namespace dom {
+class DocGroup;
+} // namespace dom
+} // namespace mozilla
+
+// dbeabbfa-6cb3-4f5c-aec2-dd558d9d681f
+#define NS_ICSSDECLARATION_IID \
+ { \
+ 0xdbeabbfa, 0x6cb3, 0x4f5c, { \
+ 0xae, 0xc2, 0xdd, 0x55, 0x8d, 0x9d, 0x68, 0x1f \
+ } \
+ }
+
+class nsICSSDeclaration : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICSSDECLARATION_IID)
+
+ virtual nsINode* GetAssociatedNode() const = 0;
+ virtual nsISupports* GetParentObject() const = 0;
+
+ mozilla::dom::DocGroup* GetDocGroup();
+
+ virtual void GetPropertyValue(const nsACString& aPropName,
+ nsACString& aValue) = 0;
+ virtual void RemoveProperty(const nsACString& aPropertyName,
+ nsACString& aValue,
+ mozilla::ErrorResult& aRv) = 0;
+ virtual void SetProperty(const nsACString& aPropertyName,
+ const nsACString& aValue,
+ const nsACString& aPriority,
+ nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& aRv) = 0;
+ // For C++ callers.
+ void SetProperty(const nsACString& aPropertyName, const nsACString& aValue,
+ const nsACString& aPriority, mozilla::ErrorResult& aRv) {
+ SetProperty(aPropertyName, aValue, aPriority, nullptr, aRv);
+ }
+
+ void Item(uint32_t aIndex, nsACString& aReturn) {
+ bool found;
+ IndexedGetter(aIndex, found, aReturn);
+ if (!found) {
+ aReturn.Truncate();
+ }
+ }
+
+ virtual void GetCSSImageURLs(const nsACString& aPropertyName,
+ nsTArray<nsCString>& aImageURLs,
+ mozilla::ErrorResult& aRv) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ // WebIDL interface for CSSStyleDeclaration
+ virtual void SetCssText(const nsACString& aString,
+ nsIPrincipal* aSubjectPrincipal,
+ mozilla::ErrorResult& rv) = 0;
+ virtual void GetCssText(nsACString& aString) = 0;
+ virtual uint32_t Length() = 0;
+
+ // The actual implementation of the Item method and the WebIDL indexed getter
+ virtual void IndexedGetter(uint32_t aIndex, bool& aFound,
+ nsACString& aPropName) = 0;
+
+ virtual void GetPropertyPriority(const nsACString& aPropName,
+ nsACString& aPriority) = 0;
+ virtual mozilla::css::Rule* GetParentRule() = 0;
+
+ protected:
+ bool IsReadOnly();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICSSDeclaration, NS_ICSSDECLARATION_IID)
+
+#define NS_DECL_NSIDOMCSSSTYLEDECLARATION_HELPER \
+ void GetCssText(nsACString& aCssText) override; \
+ void SetCssText(const nsACString& aCssText, nsIPrincipal* aSubjectPrincipal, \
+ mozilla::ErrorResult& aRv) override; \
+ void GetPropertyValue(const nsACString& aPropertyName, nsACString& aValue) \
+ override; \
+ void RemoveProperty(const nsACString& aPropertyName, nsACString& aValue, \
+ mozilla::ErrorResult& aRv) override; \
+ void GetPropertyPriority(const nsACString& aPropertyName, \
+ nsACString& aPriority) override; \
+ void SetProperty(const nsACString& aPropertyName, const nsACString& aValue, \
+ const nsACString& aPriority, \
+ nsIPrincipal* aSubjectPrincipal, mozilla::ErrorResult& aRv) \
+ override; \
+ uint32_t Length() override; \
+ mozilla::css::Rule* GetParentRule() override;
+
+#endif // nsICSSDeclaration_h__
diff --git a/layout/style/nsICSSLoaderObserver.h b/layout/style/nsICSSLoaderObserver.h
new file mode 100644
index 0000000000..1f0d5b5130
--- /dev/null
+++ b/layout/style/nsICSSLoaderObserver.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* internal interface for observing CSS style sheet loads */
+
+#ifndef nsICSSLoaderObserver_h___
+#define nsICSSLoaderObserver_h___
+
+#include "nsISupports.h"
+
+#define NS_ICSSLOADEROBSERVER_IID \
+ { \
+ 0xf51fbf2c, 0xfe4b, 0x4a15, { \
+ 0xaf, 0x7e, 0x5e, 0x20, 0x64, 0x5f, 0xaf, 0x58 \
+ } \
+ }
+
+namespace mozilla {
+class StyleSheet;
+}
+
+class nsICSSLoaderObserver : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICSSLOADEROBSERVER_IID)
+
+ /**
+ * StyleSheetLoaded is called after aSheet is marked complete and before any
+ * load events associated with aSheet are fired.
+ * @param aSheet the sheet that was loaded. Guaranteed to always be
+ * non-null, even if aStatus indicates failure.
+ * @param aWasDeferred whether the sheet load was deferred, due to it being an
+ * alternate sheet, or having a non-matching media list.
+ * @param aStatus is a success code if the sheet loaded successfully and a
+ * failure code otherwise. Note that successful load of aSheet
+ * doesn't indicate anything about whether the data actually parsed
+ * as CSS, and doesn't indicate anything about the status of any child
+ * sheets of aSheet.
+ */
+ NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICSSLoaderObserver, NS_ICSSLOADEROBSERVER_IID)
+
+#endif // nsICSSLoaderObserver_h___
diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp
new file mode 100644
index 0000000000..c99eecb486
--- /dev/null
+++ b/layout/style/nsMediaFeatures.cpp
@@ -0,0 +1,407 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 features that media queries can test */
+
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsCSSProps.h"
+#include "nsCSSValue.h"
+#include "mozilla/LookAndFeel.h"
+#include "nsDeviceContext.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIPrintSettings.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/BrowsingContextBinding.h"
+#include "mozilla/dom/ScreenBinding.h"
+#include "nsIWidget.h"
+#include "nsContentUtils.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/GeckoBindings.h"
+#include "PreferenceSheet.h"
+#include "nsGlobalWindowOuter.h"
+#ifdef XP_WIN
+# include "mozilla/WindowsVersion.h"
+#endif
+
+using namespace mozilla;
+using mozilla::dom::DisplayMode;
+using mozilla::dom::Document;
+
+// A helper for four features below
+static nsSize GetSize(const Document& aDocument) {
+ nsPresContext* pc = aDocument.GetPresContext();
+
+ // Per spec, return a 0x0 viewport if we're not being rendered. See:
+ //
+ // * https://github.com/w3c/csswg-drafts/issues/571
+ // * https://github.com/whatwg/html/issues/1813
+ //
+ if (!pc) {
+ return {};
+ }
+
+ if (pc->IsRootPaginatedDocument()) {
+ // We want the page size, including unprintable areas and margins.
+ //
+ // FIXME(emilio, bug 1414600): Not quite!
+ return pc->GetPageSize();
+ }
+
+ return pc->GetVisibleArea().Size();
+}
+
+// A helper for three features below.
+static nsSize GetDeviceSize(const Document& aDocument) {
+ if (aDocument.ShouldResistFingerprinting(RFPTarget::CSSDeviceSize)) {
+ return GetSize(aDocument);
+ }
+
+ // Media queries in documents in an RDM pane should use the simulated
+ // device size.
+ Maybe<CSSIntSize> deviceSize =
+ nsGlobalWindowOuter::GetRDMDeviceSize(aDocument);
+ if (deviceSize.isSome()) {
+ return CSSPixel::ToAppUnits(deviceSize.value());
+ }
+
+ nsPresContext* pc = aDocument.GetPresContext();
+ // NOTE(emilio): We should probably figure out how to return an appropriate
+ // device size here, though in a multi-screen world that makes no sense
+ // really.
+ if (!pc) {
+ return {};
+ }
+
+ if (pc->IsRootPaginatedDocument()) {
+ // We want the page size, including unprintable areas and margins.
+ // XXX The spec actually says we want the "page sheet size", but
+ // how is that different?
+ return pc->GetPageSize();
+ }
+
+ nsSize size;
+ pc->DeviceContext()->GetDeviceSurfaceDimensions(size.width, size.height);
+ return size;
+}
+
+bool Gecko_MediaFeatures_IsResourceDocument(const Document* aDocument) {
+ return aDocument->IsResourceDoc();
+}
+
+bool Gecko_MediaFeatures_UseOverlayScrollbars(const Document* aDocument) {
+ nsPresContext* pc = aDocument->GetPresContext();
+ return pc && pc->UseOverlayScrollbars();
+}
+
+static nsDeviceContext* GetDeviceContextFor(const Document* aDocument) {
+ nsPresContext* pc = aDocument->GetPresContext();
+ if (!pc) {
+ return nullptr;
+ }
+
+ // It would be nice to call nsLayoutUtils::GetDeviceContextForScreenInfo here,
+ // except for two things: (1) it can flush, and flushing is bad here, and (2)
+ // it doesn't really get us consistency in multi-monitor situations *anyway*.
+ return pc->DeviceContext();
+}
+
+void Gecko_MediaFeatures_GetDeviceSize(const Document* aDocument,
+ nscoord* aWidth, nscoord* aHeight) {
+ nsSize size = GetDeviceSize(*aDocument);
+ *aWidth = size.width;
+ *aHeight = size.height;
+}
+
+int32_t Gecko_MediaFeatures_GetMonochromeBitsPerPixel(
+ const Document* aDocument) {
+ // The default bits per pixel for a monochrome device. We could propagate this
+ // further to nsIPrintSettings, but Gecko doesn't actually know this value
+ // from the hardware, so it seems silly to do so.
+ static constexpr int32_t kDefaultMonochromeBpp = 8;
+
+ nsPresContext* pc = aDocument->GetPresContext();
+ if (!pc) {
+ return 0;
+ }
+ nsIPrintSettings* ps = pc->GetPrintSettings();
+ if (!ps) {
+ return 0;
+ }
+ bool color = true;
+ ps->GetPrintInColor(&color);
+ return color ? 0 : kDefaultMonochromeBpp;
+}
+
+dom::ScreenColorGamut Gecko_MediaFeatures_ColorGamut(
+ const Document* aDocument) {
+ auto colorGamut = dom::ScreenColorGamut::Srgb;
+ if (!aDocument->ShouldResistFingerprinting(RFPTarget::CSSColorInfo)) {
+ if (auto* dx = GetDeviceContextFor(aDocument)) {
+ colorGamut = dx->GetColorGamut();
+ }
+ }
+ return colorGamut;
+}
+
+int32_t Gecko_MediaFeatures_GetColorDepth(const Document* aDocument) {
+ if (Gecko_MediaFeatures_GetMonochromeBitsPerPixel(aDocument) != 0) {
+ // If we're a monochrome device, then the color depth is zero.
+ return 0;
+ }
+
+ // Use depth of 24 when resisting fingerprinting, or when we're not being
+ // rendered.
+ int32_t depth = 24;
+
+ if (!aDocument->ShouldResistFingerprinting(RFPTarget::CSSColorInfo)) {
+ if (nsDeviceContext* dx = GetDeviceContextFor(aDocument)) {
+ depth = dx->GetDepth();
+ }
+ }
+
+ // The spec says to use bits *per color component*, so divide by 3,
+ // and round down, since the spec says to use the smallest when the
+ // color components differ.
+ return depth / 3;
+}
+
+float Gecko_MediaFeatures_GetResolution(const Document* aDocument) {
+ // We're returning resolution in terms of device pixels per css pixel, since
+ // that is the preferred unit for media queries of resolution. This avoids
+ // introducing precision error from conversion to and from less-used
+ // physical units like inches.
+ nsPresContext* pc = aDocument->GetPresContext();
+ if (!pc) {
+ return 1.;
+ }
+
+ if (pc->GetOverrideDPPX() > 0.) {
+ return pc->GetOverrideDPPX();
+ }
+
+ if (aDocument->ShouldResistFingerprinting(RFPTarget::CSSResolution)) {
+ return pc->DeviceContext()->GetFullZoom();
+ }
+ // Get the actual device pixel ratio, which also takes zoom into account.
+ return float(AppUnitsPerCSSPixel()) /
+ pc->DeviceContext()->AppUnitsPerDevPixel();
+}
+
+static const Document* TopDocument(const Document* aDocument) {
+ const Document* current = aDocument;
+ while (const Document* parent = current->GetInProcessParentDocument()) {
+ current = parent;
+ }
+ return current;
+}
+
+StyleDisplayMode Gecko_MediaFeatures_GetDisplayMode(const Document* aDocument) {
+ const Document* rootDocument = TopDocument(aDocument);
+
+ nsCOMPtr<nsISupports> container = rootDocument->GetContainer();
+ if (nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container)) {
+ nsCOMPtr<nsIWidget> mainWidget;
+ baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+ if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Fullscreen) {
+ return StyleDisplayMode::Fullscreen;
+ }
+ }
+
+ static_assert(static_cast<int32_t>(DisplayMode::Browser) ==
+ static_cast<int32_t>(StyleDisplayMode::Browser) &&
+ static_cast<int32_t>(DisplayMode::Minimal_ui) ==
+ static_cast<int32_t>(StyleDisplayMode::MinimalUi) &&
+ static_cast<int32_t>(DisplayMode::Standalone) ==
+ static_cast<int32_t>(StyleDisplayMode::Standalone) &&
+ static_cast<int32_t>(DisplayMode::Fullscreen) ==
+ static_cast<int32_t>(StyleDisplayMode::Fullscreen),
+ "DisplayMode must mach nsStyleConsts.h");
+
+ dom::BrowsingContext* browsingContext = aDocument->GetBrowsingContext();
+ if (!browsingContext) {
+ return StyleDisplayMode::Browser;
+ }
+ return static_cast<StyleDisplayMode>(browsingContext->DisplayMode());
+}
+
+bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) {
+ switch (aPlatform) {
+#if defined(XP_WIN)
+ case StylePlatform::Windows:
+ return true;
+#elif defined(ANDROID)
+ case StylePlatform::Android:
+ return true;
+#elif defined(MOZ_WIDGET_GTK)
+ case StylePlatform::Linux:
+ return true;
+#elif defined(XP_MACOSX)
+ case StylePlatform::Macos:
+ return true;
+#else
+# error "Unknown platform?"
+#endif
+ default:
+ return false;
+ }
+}
+
+bool Gecko_MediaFeatures_PrefersReducedMotion(const Document* aDocument) {
+ if (aDocument->ShouldResistFingerprinting(
+ RFPTarget::CSSPrefersReducedMotion)) {
+ return false;
+ }
+ return LookAndFeel::GetInt(LookAndFeel::IntID::PrefersReducedMotion, 0) == 1;
+}
+
+bool Gecko_MediaFeatures_PrefersReducedTransparency(const Document* aDocument) {
+ if (aDocument->ShouldResistFingerprinting(
+ RFPTarget::CSSPrefersReducedTransparency)) {
+ return false;
+ }
+ return LookAndFeel::GetInt(LookAndFeel::IntID::PrefersReducedTransparency,
+ 0) == 1;
+}
+
+StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme(
+ const Document* aDocument, bool aUseContent) {
+ auto scheme = aUseContent ? PreferenceSheet::ContentPrefs().mColorScheme
+ : aDocument->PreferredColorScheme();
+ return scheme == ColorScheme::Dark ? StylePrefersColorScheme::Dark
+ : StylePrefersColorScheme::Light;
+}
+
+// Neither Linux, Windows, nor Mac have a way to indicate that low contrast is
+// preferred so we use the presence of an accessibility theme or forced colors
+// as a signal.
+StylePrefersContrast Gecko_MediaFeatures_PrefersContrast(
+ const Document* aDocument) {
+ if (aDocument->ShouldResistFingerprinting(RFPTarget::CSSPrefersContrast)) {
+ return StylePrefersContrast::NoPreference;
+ }
+ const auto& prefs = PreferenceSheet::PrefsFor(*aDocument);
+ if (!prefs.mUseAccessibilityTheme && prefs.mUseDocumentColors) {
+ return StylePrefersContrast::NoPreference;
+ }
+ const auto& colors = prefs.ColorsFor(ColorScheme::Light);
+ float ratio = RelativeLuminanceUtils::ContrastRatio(colors.mDefaultBackground,
+ colors.mDefault);
+ // https://www.w3.org/TR/WCAG21/#contrast-minimum
+ if (ratio < 4.5f) {
+ return StylePrefersContrast::Less;
+ }
+ // https://www.w3.org/TR/WCAG21/#contrast-enhanced
+ if (ratio >= 7.0f) {
+ return StylePrefersContrast::More;
+ }
+ return StylePrefersContrast::Custom;
+}
+
+bool Gecko_MediaFeatures_InvertedColors(const Document* aDocument) {
+ if (aDocument->ShouldResistFingerprinting(RFPTarget::CSSInvertedColors)) {
+ return false;
+ }
+ return LookAndFeel::GetInt(LookAndFeel::IntID::InvertedColors, 0) == 1;
+}
+
+StyleScripting Gecko_MediaFeatures_Scripting(const Document* aDocument) {
+ const auto* doc = aDocument;
+ if (aDocument->IsStaticDocument()) {
+ doc = aDocument->GetOriginalDocument();
+ }
+
+ return doc->IsScriptEnabled() ? StyleScripting::Enabled
+ : StyleScripting::None;
+}
+
+StyleDynamicRange Gecko_MediaFeatures_DynamicRange(const Document* aDocument) {
+ // Bug 1759772: Once HDR color is available, update each platform
+ // LookAndFeel implementation to return StyleDynamicRange::High when
+ // appropriate.
+ return StyleDynamicRange::Standard;
+}
+
+StyleDynamicRange Gecko_MediaFeatures_VideoDynamicRange(
+ const Document* aDocument) {
+ if (aDocument->ShouldResistFingerprinting(RFPTarget::CSSVideoDynamicRange)) {
+ return StyleDynamicRange::Standard;
+ }
+ // video-dynamic-range: high has 3 requirements:
+ // 1) high peak brightness
+ // 2) high contrast ratio
+ // 3) color depth > 24
+ // We check the color depth requirement before asking the LookAndFeel
+ // if it is HDR capable.
+ if (nsDeviceContext* dx = GetDeviceContextFor(aDocument)) {
+ if (dx->GetDepth() > 24 &&
+ LookAndFeel::GetInt(LookAndFeel::IntID::VideoDynamicRange)) {
+ return StyleDynamicRange::High;
+ }
+ }
+
+ return StyleDynamicRange::Standard;
+}
+
+static PointerCapabilities GetPointerCapabilities(const Document* aDocument,
+ LookAndFeel::IntID aID) {
+ MOZ_ASSERT(aID == LookAndFeel::IntID::PrimaryPointerCapabilities ||
+ aID == LookAndFeel::IntID::AllPointerCapabilities);
+ MOZ_ASSERT(aDocument);
+
+ if (dom::BrowsingContext* bc = aDocument->GetBrowsingContext()) {
+ // The touch-events-override happens only for the Responsive Design Mode so
+ // that we don't need to care about ResistFingerprinting.
+ if (bc->TouchEventsOverride() == dom::TouchEventsOverride::Enabled) {
+ return PointerCapabilities::Coarse;
+ }
+ }
+
+ // The default value for Desktop is mouse-type pointer, and for Android
+ // a coarse pointer.
+ const PointerCapabilities kDefaultCapabilities =
+#ifdef ANDROID
+ PointerCapabilities::Coarse;
+#else
+ PointerCapabilities::Fine | PointerCapabilities::Hover;
+#endif
+ if (aDocument->ShouldResistFingerprinting(
+ RFPTarget::CSSPointerCapabilities)) {
+ return kDefaultCapabilities;
+ }
+
+ int32_t intValue;
+ nsresult rv = LookAndFeel::GetInt(aID, &intValue);
+ if (NS_FAILED(rv)) {
+ return kDefaultCapabilities;
+ }
+
+ return static_cast<PointerCapabilities>(intValue);
+}
+
+PointerCapabilities Gecko_MediaFeatures_PrimaryPointerCapabilities(
+ const Document* aDocument) {
+ return GetPointerCapabilities(aDocument,
+ LookAndFeel::IntID::PrimaryPointerCapabilities);
+}
+
+PointerCapabilities Gecko_MediaFeatures_AllPointerCapabilities(
+ const Document* aDocument) {
+ return GetPointerCapabilities(aDocument,
+ LookAndFeel::IntID::AllPointerCapabilities);
+}
+
+StyleGtkThemeFamily Gecko_MediaFeatures_GtkThemeFamily() {
+ static_assert(int32_t(StyleGtkThemeFamily::Unknown) == 0);
+ return StyleGtkThemeFamily(
+ LookAndFeel::GetInt(LookAndFeel::IntID::GTKThemeFamily));
+}
diff --git a/layout/style/nsROCSSPrimitiveValue.cpp b/layout/style/nsROCSSPrimitiveValue.cpp
new file mode 100644
index 0000000000..84911d209b
--- /dev/null
+++ b/layout/style/nsROCSSPrimitiveValue.cpp
@@ -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/. */
+
+/* DOM object representing values in DOM computed style */
+
+#include "nsROCSSPrimitiveValue.h"
+
+#include "mozilla/ErrorResult.h"
+#include "nsPresContext.h"
+#include "nsStyleUtil.h"
+#include "nsError.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsROCSSPrimitiveValue::nsROCSSPrimitiveValue() : CSSValue(), mType(CSS_PX) {
+ mValue.mFloat = 0.0f;
+}
+
+nsROCSSPrimitiveValue::~nsROCSSPrimitiveValue() { Reset(); }
+
+void nsROCSSPrimitiveValue::GetCssText(nsAString& aCssText) {
+ nsAutoString tmpStr;
+ aCssText.Truncate();
+
+ switch (mType) {
+ case CSS_PX: {
+ nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr);
+ tmpStr.AppendLiteral("px");
+ break;
+ }
+ case CSS_STRING: {
+ tmpStr.Append(mValue.mString);
+ break;
+ }
+ case CSS_PERCENTAGE: {
+ nsStyleUtil::AppendCSSNumber(mValue.mFloat * 100, tmpStr);
+ tmpStr.Append(char16_t('%'));
+ break;
+ }
+ case CSS_NUMBER: {
+ nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr);
+ break;
+ }
+ case CSS_NUMBER_INT32: {
+ tmpStr.AppendInt(mValue.mInt32);
+ break;
+ }
+ case CSS_NUMBER_UINT32: {
+ tmpStr.AppendInt(mValue.mUint32);
+ break;
+ }
+ case CSS_DEG: {
+ nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr);
+ tmpStr.AppendLiteral("deg");
+ break;
+ }
+ case CSS_S: {
+ nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr);
+ tmpStr.Append('s');
+ break;
+ }
+ }
+
+ aCssText.Assign(tmpStr);
+}
+
+uint16_t nsROCSSPrimitiveValue::CssValueType() const {
+ return CSSValue::CSS_PRIMITIVE_VALUE;
+}
+
+void nsROCSSPrimitiveValue::SetNumber(float aValue) {
+ Reset();
+ mValue.mFloat = aValue;
+ mType = CSS_NUMBER;
+}
+
+void nsROCSSPrimitiveValue::SetNumber(int32_t aValue) {
+ Reset();
+ mValue.mInt32 = aValue;
+ mType = CSS_NUMBER_INT32;
+}
+
+void nsROCSSPrimitiveValue::SetNumber(uint32_t aValue) {
+ Reset();
+ mValue.mUint32 = aValue;
+ mType = CSS_NUMBER_UINT32;
+}
+
+void nsROCSSPrimitiveValue::SetPercent(float aValue) {
+ Reset();
+ mValue.mFloat = aValue;
+ mType = CSS_PERCENTAGE;
+}
+
+void nsROCSSPrimitiveValue::SetDegree(float aValue) {
+ Reset();
+ mValue.mFloat = aValue;
+ mType = CSS_DEG;
+}
+
+void nsROCSSPrimitiveValue::SetAppUnits(nscoord aValue) {
+ SetPixels(nsPresContext::AppUnitsToFloatCSSPixels(aValue));
+}
+
+void nsROCSSPrimitiveValue::SetPixels(float aValue) {
+ Reset();
+ mValue.mFloat = aValue;
+ mType = CSS_PX;
+}
+
+void nsROCSSPrimitiveValue::SetAppUnits(float aValue) {
+ SetAppUnits(NSToCoordRound(aValue));
+}
+
+void nsROCSSPrimitiveValue::SetString(const nsACString& aString) {
+ Reset();
+ mValue.mString = ToNewUnicode(aString, mozilla::fallible);
+ if (mValue.mString) {
+ mType = CSS_STRING;
+ } else {
+ // XXXcaa We should probably let the caller know we are out of memory
+ mType = CSS_UNKNOWN;
+ }
+}
+
+void nsROCSSPrimitiveValue::SetString(const nsAString& aString) {
+ Reset();
+ mValue.mString = ToNewUnicode(aString, mozilla::fallible);
+ if (mValue.mString) {
+ mType = CSS_STRING;
+ } else {
+ // XXXcaa We should probably let the caller know we are out of memory
+ mType = CSS_UNKNOWN;
+ }
+}
+
+void nsROCSSPrimitiveValue::SetTime(float aValue) {
+ Reset();
+ mValue.mFloat = aValue;
+ mType = CSS_S;
+}
+
+void nsROCSSPrimitiveValue::Reset() {
+ switch (mType) {
+ case CSS_STRING:
+ NS_ASSERTION(mValue.mString, "Null string should never happen");
+ free(mValue.mString);
+ mValue.mString = nullptr;
+ break;
+ }
+
+ mType = CSS_UNKNOWN;
+}
+
+uint16_t nsROCSSPrimitiveValue::PrimitiveType() {
+ // New value types were introduced but not added to CSS OM.
+ // Return CSS_UNKNOWN to avoid exposing CSS_TURN to content.
+ if (mType > CSS_RGBCOLOR) {
+ if (mType == CSS_NUMBER_INT32 || mType == CSS_NUMBER_UINT32) {
+ return CSS_NUMBER;
+ }
+ return CSS_UNKNOWN;
+ }
+ return mType;
+}
diff --git a/layout/style/nsROCSSPrimitiveValue.h b/layout/style/nsROCSSPrimitiveValue.h
new file mode 100644
index 0000000000..d782a23469
--- /dev/null
+++ b/layout/style/nsROCSSPrimitiveValue.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object representing values in DOM computed style */
+
+#ifndef nsROCSSPrimitiveValue_h___
+#define nsROCSSPrimitiveValue_h___
+
+#include "CSSValue.h"
+#include "nsCoord.h"
+#include "nsString.h"
+
+/**
+ * Read-only CSS primitive value - a DOM object representing values in DOM
+ * computed style.
+ */
+class nsROCSSPrimitiveValue final : public mozilla::dom::CSSValue {
+ public:
+ enum : uint16_t {
+ CSS_UNKNOWN,
+ CSS_NUMBER,
+ CSS_PERCENTAGE,
+ CSS_PX,
+ CSS_DEG,
+ CSS_S,
+ CSS_STRING,
+ CSS_RGBCOLOR,
+ CSS_NUMBER_INT32,
+ CSS_NUMBER_UINT32,
+ };
+
+ // CSSValue
+ void GetCssText(nsAString&) final;
+ uint16_t CssValueType() const final;
+
+ // CSSPrimitiveValue
+ uint16_t PrimitiveType();
+
+ // nsROCSSPrimitiveValue
+ nsROCSSPrimitiveValue();
+
+ void SetNumber(float aValue);
+ void SetNumber(int32_t aValue);
+ void SetNumber(uint32_t aValue);
+ void SetPercent(float aValue);
+ void SetDegree(float aValue);
+ void SetPixels(float aValue);
+ void SetAppUnits(nscoord aValue);
+ void SetAppUnits(float aValue);
+ void SetString(const nsACString& aString);
+ void SetString(const nsAString& aString);
+
+ template <size_t N>
+ void SetString(const char (&aString)[N]) {
+ SetString(nsLiteralCString(aString));
+ }
+
+ void SetTime(float aValue);
+ void Reset();
+
+ virtual ~nsROCSSPrimitiveValue();
+
+ protected:
+ uint16_t mType;
+
+ union {
+ float mFloat;
+ int32_t mInt32;
+ uint32_t mUint32;
+ char16_t* mString;
+ } mValue;
+};
+
+inline nsROCSSPrimitiveValue* mozilla::dom::CSSValue::AsPrimitiveValue() {
+ return CssValueType() == mozilla::dom::CSSValue::CSS_PRIMITIVE_VALUE
+ ? static_cast<nsROCSSPrimitiveValue*>(this)
+ : nullptr;
+}
+
+#endif /* nsROCSSPrimitiveValue_h___ */
diff --git a/layout/style/nsStyleAutoArray.h b/layout/style/nsStyleAutoArray.h
new file mode 100644
index 0000000000..368b7c6a7b
--- /dev/null
+++ b/layout/style/nsStyleAutoArray.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/. */
+
+#ifndef nsStyleAutoArray_h_
+#define nsStyleAutoArray_h_
+
+#include "nsTArray.h"
+#include "mozilla/Assertions.h"
+
+/**
+ * An array of objects, similar to AutoTArray<T,1> but which is memmovable. It
+ * always has length >= 1.
+ */
+template <typename T>
+class nsStyleAutoArray {
+ public:
+ // This constructor places a single element in mFirstElement.
+ enum WithSingleInitialElement { WITH_SINGLE_INITIAL_ELEMENT };
+ explicit nsStyleAutoArray(WithSingleInitialElement) {}
+
+ nsStyleAutoArray(const nsStyleAutoArray&) = delete;
+ nsStyleAutoArray& operator=(const nsStyleAutoArray&) = delete;
+
+ nsStyleAutoArray(nsStyleAutoArray&&) = default;
+ nsStyleAutoArray& operator=(nsStyleAutoArray&&) = default;
+
+ bool Assign(const nsStyleAutoArray& aOther, mozilla::fallible_t) {
+ mFirstElement = aOther.mFirstElement;
+ return mOtherElements.Assign(aOther.mOtherElements, mozilla::fallible);
+ }
+
+ nsStyleAutoArray Clone() const {
+ nsStyleAutoArray res(WITH_SINGLE_INITIAL_ELEMENT);
+ res.mFirstElement = mFirstElement;
+ res.mOtherElements = mOtherElements.Clone();
+ return res;
+ }
+
+ bool operator==(const nsStyleAutoArray& aOther) const {
+ return Length() == aOther.Length() &&
+ mFirstElement == aOther.mFirstElement &&
+ mOtherElements == aOther.mOtherElements;
+ }
+ bool operator!=(const nsStyleAutoArray& aOther) const {
+ return !(*this == aOther);
+ }
+
+ size_t Length() const { return mOtherElements.Length() + 1; }
+ const T& operator[](size_t aIndex) const {
+ return aIndex == 0 ? mFirstElement : mOtherElements[aIndex - 1];
+ }
+ T& operator[](size_t aIndex) {
+ return aIndex == 0 ? mFirstElement : mOtherElements[aIndex - 1];
+ }
+
+ void EnsureLengthAtLeast(size_t aMinLen) {
+ if (aMinLen > 0) {
+ mOtherElements.EnsureLengthAtLeast(aMinLen - 1);
+ }
+ }
+
+ void SetLengthNonZero(size_t aNewLen) {
+ MOZ_ASSERT(aNewLen > 0);
+ mOtherElements.SetLength(aNewLen - 1);
+ }
+
+ void TruncateLengthNonZero(size_t aNewLen) {
+ MOZ_ASSERT(aNewLen > 0);
+ MOZ_ASSERT(aNewLen <= Length());
+ mOtherElements.TruncateLength(aNewLen - 1);
+ }
+
+ private:
+ T mFirstElement;
+ nsTArray<T> mOtherElements;
+};
+
+#endif /* nsStyleAutoArray_h_ */
diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h
new file mode 100644
index 0000000000..99433c9027
--- /dev/null
+++ b/layout/style/nsStyleConsts.h
@@ -0,0 +1,595 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* constants used in the style struct data provided by ComputedStyle */
+
+#ifndef nsStyleConsts_h___
+#define nsStyleConsts_h___
+
+#include <inttypes.h>
+
+#include "X11UndefineNone.h"
+
+#include "gfxFontConstants.h"
+#include "mozilla/ServoStyleConsts.h"
+
+// XXX fold this into ComputedStyle and group by nsStyleXXX struct
+
+namespace mozilla {
+
+// box-align
+enum class StyleBoxAlign : uint8_t {
+ Stretch,
+ Start,
+ Center,
+ Baseline,
+ End,
+};
+
+// box-decoration-break
+enum class StyleBoxDecorationBreak : uint8_t {
+ Slice,
+ Clone,
+};
+
+// box-direction
+enum class StyleBoxDirection : uint8_t {
+ Normal,
+ Reverse,
+};
+
+// box-orient
+enum class StyleBoxOrient : uint8_t {
+ Horizontal,
+ Vertical,
+};
+
+// box-pack
+enum class StyleBoxPack : uint8_t {
+ Start,
+ Center,
+ End,
+ Justify,
+};
+
+// box-sizing
+enum class StyleBoxSizing : uint8_t { Content, Border };
+
+// box-shadow
+enum class StyleBoxShadowType : uint8_t {
+ Inset,
+};
+
+enum class StyleColumnFill : uint8_t {
+ Balance,
+ Auto,
+};
+
+enum class StyleColumnSpan : uint8_t {
+ None,
+ All,
+};
+
+// Define geometry box for clip-path's reference-box, background-clip,
+// background-origin, mask-clip, mask-origin, shape-box and transform-box.
+enum class StyleGeometryBox : uint8_t {
+ ContentBox, // Used by everything, except transform-box.
+ PaddingBox, // Used by everything, except transform-box.
+ BorderBox,
+ MarginBox, // XXX Bug 1260094 comment 9.
+ // Although margin-box is required by mask-origin and mask-clip,
+ // we do not implement that due to lack of support in other
+ // browsers. clip-path reference-box only.
+ FillBox, // Used by everything, except shape-box.
+ StrokeBox, // mask-clip, mask-origin and clip-path reference-box only.
+ ViewBox, // Used by everything, except shape-box.
+ NoClip, // mask-clip only.
+ Text, // background-clip only.
+ NoBox, // Depending on which kind of element this style value applied on,
+ // the default value of a reference-box can be different.
+ // For an HTML element, the default value of reference-box is
+ // border-box; for an SVG element, the default value is fill-box.
+ // Since we can not determine the default value at parsing time,
+ // set it as NoBox so that we make a decision later.
+ // clip-path reference-box only.
+ MozAlmostPadding = 127 // A magic value that we use for our "pretend that
+ // background-clip is 'padding' when we have a solid
+ // border" optimization. This isn't actually equal
+ // to StyleGeometryBox::Padding because using that
+ // causes antialiasing seams between the background
+ // and border.
+ // background-clip only.
+};
+
+// float-edge
+enum class StyleFloatEdge : uint8_t {
+ ContentBox,
+ MarginBox,
+};
+
+// Hyphens
+enum class StyleHyphens : uint8_t {
+ None,
+ Manual,
+ Auto,
+};
+
+// image-orientation
+enum class StyleImageOrientation : uint8_t {
+ None,
+ FromImage,
+};
+
+// scrollbar-width
+enum class StyleScrollbarWidth : uint8_t {
+ Auto,
+ Thin,
+ None,
+};
+
+// Shape source type
+enum class StyleShapeSourceType : uint8_t {
+ None,
+ Image, // shape-outside / clip-path only, and clip-path only uses it for
+ // <url>s
+ Shape,
+ Box,
+ Path, // SVG path function
+};
+
+// user-focus
+enum class StyleUserFocus : uint8_t {
+ None,
+ Ignore,
+ Normal,
+};
+
+// user-input
+enum class StyleUserInput : uint8_t {
+ None,
+ Auto,
+};
+
+// user-modify
+enum class StyleUserModify : uint8_t {
+ ReadOnly,
+ ReadWrite,
+ WriteOnly,
+};
+
+// -moz-inert
+enum class StyleInert : uint8_t {
+ None,
+ Inert,
+};
+
+// -moz-window-dragging
+enum class StyleWindowDragging : uint8_t {
+ Default,
+ Drag,
+ NoDrag,
+};
+
+// orient
+enum class StyleOrient : uint8_t {
+ Inline,
+ Block,
+ Horizontal,
+ Vertical,
+};
+
+// See nsStyleImageLayers
+enum class StyleImageLayerAttachment : uint8_t { Scroll, Fixed, Local };
+
+// See nsStyleImageLayers
+enum class StyleImageLayerRepeat : uint8_t {
+ NoRepeat = 0x00,
+ RepeatX,
+ RepeatY,
+ Repeat,
+ Space,
+ Round
+};
+
+// Mask mode
+enum class StyleMaskMode : uint8_t { Alpha = 0, Luminance, MatchSource };
+
+// See nsStyleTable
+enum class StyleBorderCollapse : uint8_t { Collapse, Separate };
+
+// border-image-repeat
+enum class StyleBorderImageRepeat : uint8_t { Stretch, Repeat, Round, Space };
+
+// See nsStyleVisibility
+enum class StyleDirection : uint8_t { Ltr, Rtl };
+
+// See nsStyleVisibility
+// NOTE: WritingModes.h depends on the particular values used here.
+
+// Single-bit flag, used in combination with VerticalLR and RL to specify
+// the corresponding Sideways* modes.
+// (To avoid ambiguity, this bit must be high enough such that no other
+// values here accidentally use it in their binary representation.)
+static constexpr uint8_t kWritingModeSidewaysMask = 4;
+
+enum class StyleWritingModeProperty : uint8_t {
+ HorizontalTb = 0,
+ VerticalRl = 1,
+ // HorizontalBT = 2, // hypothetical
+ VerticalLr = 3,
+ SidewaysRl = VerticalRl | kWritingModeSidewaysMask,
+ SidewaysLr = VerticalLr | kWritingModeSidewaysMask,
+};
+
+// See nsStylePosition
+enum class StyleFlexDirection : uint8_t {
+ Row,
+ RowReverse,
+ Column,
+ ColumnReverse,
+};
+
+// See nsStylePosition
+enum class StyleFlexWrap : uint8_t {
+ Nowrap,
+ Wrap,
+ WrapReverse,
+};
+
+// CSS Grid <track-breadth> keywords
+enum class StyleGridTrackBreadth : uint8_t {
+ MaxContent = 1,
+ MinContent = 2,
+};
+
+// defaults per MathML spec
+static constexpr float kMathMLDefaultScriptSizeMultiplier{0.71f};
+static constexpr float kMathMLDefaultScriptMinSizePt{8.f};
+
+// See nsStyleFont
+enum class StyleMathVariant : uint8_t {
+ None = 0,
+ Normal = 1,
+ Bold = 2,
+ Italic = 3,
+ BoldItalic = 4,
+ Script = 5,
+ BoldScript = 6,
+ Fraktur = 7,
+ DoubleStruck = 8,
+ BoldFraktur = 9,
+ SansSerif = 10,
+ BoldSansSerif = 11,
+ SansSerifItalic = 12,
+ SansSerifBoldItalic = 13,
+ Monospace = 14,
+ Initial = 15,
+ Tailed = 16,
+ Looped = 17,
+ Stretched = 18,
+};
+
+// See nsStyleFont::mMathStyle
+enum class StyleMathStyle : uint8_t { Compact = 0, Normal = 1 };
+
+// See nsStyleDisplay.mPosition
+enum class StylePositionProperty : uint8_t {
+ Static,
+ Relative,
+ Absolute,
+ Fixed,
+ Sticky,
+};
+
+enum class FrameBorderProperty : uint8_t { Yes, No, One, Zero };
+
+enum class ScrollingAttribute : uint8_t {
+ Yes,
+ No,
+ On,
+ Off,
+ Scroll,
+ Noscroll,
+ Auto
+};
+
+// See nsStyleList
+enum class ListStyle : uint8_t {
+ Custom = 255, // for @counter-style
+ None = 0,
+ Decimal,
+ Disc,
+ Circle,
+ Square,
+ DisclosureClosed,
+ DisclosureOpen,
+ Hebrew,
+ JapaneseInformal,
+ JapaneseFormal,
+ KoreanHangulFormal,
+ KoreanHanjaInformal,
+ KoreanHanjaFormal,
+ SimpChineseInformal,
+ SimpChineseFormal,
+ TradChineseInformal,
+ TradChineseFormal,
+ EthiopicNumeric,
+ // These styles are handled as custom styles defined in counterstyles.css.
+ // They are preserved here only for html attribute map.
+ LowerRoman = 100,
+ UpperRoman,
+ LowerAlpha,
+ UpperAlpha
+};
+
+// See nsStyleList
+enum class StyleListStylePosition : uint8_t { Inside, Outside };
+
+// See nsStyleVisibility
+enum class StylePointerEvents : uint8_t {
+ None,
+ Visiblepainted,
+ Visiblefill,
+ Visiblestroke,
+ Visible,
+ Painted,
+ Fill,
+ Stroke,
+ All,
+ Auto,
+};
+
+enum class StyleIsolation : uint8_t {
+ Auto,
+ Isolate,
+};
+
+// See nsStylePosition.mObjectFit
+enum class StyleObjectFit : uint8_t {
+ Fill,
+ Contain,
+ Cover,
+ None,
+ ScaleDown,
+};
+
+// See nsStyleText
+enum class StyleTextDecorationStyle : uint8_t {
+ None, // not in CSS spec, mapped to -moz-none
+ Dotted,
+ Dashed,
+ Solid,
+ Double,
+ Wavy,
+ Sentinel = Wavy
+};
+
+// See nsStyleText
+enum class StyleTextSecurity : uint8_t {
+ None,
+ Circle,
+ Disc,
+ Square,
+};
+
+// See nsStyleDisplay
+enum class StyleTopLayer : uint8_t {
+ None,
+ Top,
+};
+
+// See nsStyleVisibility
+enum class StyleVisibility : uint8_t {
+ Hidden,
+ Visible,
+ Collapse,
+};
+
+// See nsStyleText
+enum class StyleWhiteSpaceCollapse : uint8_t {
+ Collapse = 0,
+ // TODO: Discard not yet supported
+ Preserve,
+ PreserveBreaks,
+ PreserveSpaces,
+ BreakSpaces,
+};
+
+// See nsStyleText
+enum class StyleTextWrapMode : uint8_t {
+ Wrap = 0,
+ Nowrap,
+};
+
+// See nsStyleText
+// TODO: this will become StyleTextWrapStyle when we turn text-wrap
+// (see https://bugzilla.mozilla.org/show_bug.cgi?id=1758391) and
+// white-space (https://bugzilla.mozilla.org/show_bug.cgi?id=1852478)
+// into shorthands.
+enum class StyleTextWrapStyle : uint8_t {
+ Auto = 0,
+ Stable,
+ Balance,
+};
+
+// ruby-align, see nsStyleText
+enum class StyleRubyAlign : uint8_t {
+ Start,
+ Center,
+ SpaceBetween,
+ SpaceAround,
+};
+
+// See nsStyleText
+enum class StyleTextSizeAdjust : uint8_t {
+ None,
+ Auto,
+};
+
+// See nsStyleVisibility
+enum class StyleTextOrientation : uint8_t {
+ Mixed,
+ Upright,
+ Sideways,
+};
+
+// Whether flexbox visibility: collapse items use legacy -moz-box behavior or
+// not.
+enum class StyleMozBoxCollapse : uint8_t {
+ Flex,
+ Legacy,
+};
+
+// See nsStyleText
+enum class StyleTextCombineUpright : uint8_t {
+ None,
+ All,
+};
+
+// See nsStyleText
+enum class StyleUnicodeBidi : uint8_t {
+ Normal,
+ Embed,
+ Isolate,
+ BidiOverride,
+ IsolateOverride,
+ Plaintext
+};
+
+enum class StyleTableLayout : uint8_t {
+ Auto,
+ Fixed,
+};
+
+enum class StyleEmptyCells : uint8_t {
+ Hide,
+ Show,
+};
+
+// See nsStyleUIReset
+enum class StyleImeMode : uint8_t {
+ Auto,
+ Normal,
+ Active,
+ Disabled,
+ Inactive,
+};
+
+// See nsStyleSVG
+
+// -moz-window-shadow
+enum class StyleWindowShadow : uint8_t {
+ Auto,
+ None,
+};
+
+// dominant-baseline
+enum class StyleDominantBaseline : uint8_t {
+ Auto,
+ Ideographic,
+ Alphabetic,
+ Hanging,
+ Mathematical,
+ Central,
+ Middle,
+ TextAfterEdge,
+ TextBeforeEdge,
+};
+
+// mask-type
+enum class StyleMaskType : uint8_t {
+ Luminance,
+ Alpha,
+};
+
+// shape-rendering
+enum class StyleShapeRendering : uint8_t {
+ Auto,
+ Optimizespeed,
+ Crispedges,
+ Geometricprecision,
+};
+
+// stroke-linecap
+enum class StyleStrokeLinecap : uint8_t {
+ Butt,
+ Round,
+ Square,
+};
+
+// stroke-linejoin
+enum class StyleStrokeLinejoin : uint8_t {
+ Miter,
+ Round,
+ Bevel,
+};
+
+// text-anchor
+enum class StyleTextAnchor : uint8_t {
+ Start,
+ Middle,
+ End,
+};
+
+// text-rendering
+enum class StyleTextRendering : uint8_t {
+ Auto,
+ Optimizespeed,
+ Optimizelegibility,
+ Geometricprecision,
+};
+
+// color-interpolation and color-interpolation-filters
+enum class StyleColorInterpolation : uint8_t {
+ Auto = 0,
+ Srgb = 1,
+ Linearrgb = 2,
+};
+
+// vector-effect
+enum class StyleVectorEffect : uint8_t { None = 0, NonScalingStroke = 1 };
+
+// 3d Transforms - Backface visibility
+enum class StyleBackfaceVisibility : uint8_t { Hidden = 0, Visible = 1 };
+
+// blending
+enum class StyleBlend : uint8_t {
+ Normal = 0,
+ Multiply,
+ Screen,
+ Overlay,
+ Darken,
+ Lighten,
+ ColorDodge,
+ ColorBurn,
+ HardLight,
+ SoftLight,
+ Difference,
+ Exclusion,
+ Hue,
+ Saturation,
+ Color,
+ Luminosity,
+ PlusLighter,
+};
+
+// composite
+enum class StyleMaskComposite : uint8_t {
+ Add = 0,
+ Subtract,
+ Intersect,
+ Exclude
+};
+
+// scroll-behavior
+enum class StyleScrollBehavior : uint8_t {
+ Auto,
+ Smooth,
+};
+
+} // namespace mozilla
+
+#endif /* nsStyleConsts_h___ */
diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp
new file mode 100644
index 0000000000..123a1b3304
--- /dev/null
+++ b/layout/style/nsStyleStruct.cpp
@@ -0,0 +1,3604 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * structs that contain the data provided by ComputedStyle, the
+ * internal API for computed style data for an element
+ */
+
+#include "nsStyleStruct.h"
+#include "nsStyleStructInlines.h"
+#include "nsStyleConsts.h"
+#include "nsString.h"
+#include "nsPresContext.h"
+#include "nsIWidget.h"
+#include "nsCRTGlue.h"
+#include "nsCSSProps.h"
+#include "nsDeviceContext.h"
+#include "nsStyleUtil.h"
+#include "nsIURIMutator.h"
+
+#include "nsCOMPtr.h"
+
+#include "nsBidiUtils.h"
+#include "nsLayoutUtils.h"
+
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "CounterStyleManager.h"
+
+#include "mozilla/dom/AnimationEffectBinding.h" // for PlaybackDirection
+#include "mozilla/dom/BaseKeyframeTypesBinding.h" // for CompositeOperation
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/GeckoBindings.h"
+#include "mozilla/PreferenceSheet.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPresData.h"
+#include "mozilla/Likely.h"
+#include "nsIURI.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include <algorithm>
+#include "ImageLoader.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static const nscoord kMediumBorderWidth = nsPresContext::CSSPixelsToAppUnits(3);
+
+// We set the size limit of style structs to 504 bytes so that when they
+// are allocated by Servo side with Arc, the total size doesn't exceed
+// 512 bytes, which minimizes allocator slop.
+static constexpr size_t kStyleStructSizeLimit = 504;
+
+template <typename Struct, size_t Actual, size_t Limit>
+struct AssertSizeIsLessThan {
+ static_assert(Actual == sizeof(Struct), "Bogus invocation");
+ static_assert(Actual <= Limit,
+ "Style struct became larger than the size limit");
+ static constexpr bool instantiate = true;
+};
+
+#define STYLE_STRUCT(name_) \
+ static_assert(AssertSizeIsLessThan<nsStyle##name_, sizeof(nsStyle##name_), \
+ kStyleStructSizeLimit>::instantiate, \
+ "");
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+bool StyleCssUrlData::operator==(const StyleCssUrlData& aOther) const {
+ // This very intentionally avoids comparing LoadData and such.
+ const auto& extra = extra_data.get();
+ const auto& otherExtra = aOther.extra_data.get();
+ if (extra.BaseURI() != otherExtra.BaseURI() ||
+ extra.Principal() != otherExtra.Principal() ||
+ cors_mode != aOther.cors_mode) {
+ // NOTE(emilio): This does pointer comparison, but it's what URLValue used
+ // to do. That's ok though since this is only used for style struct diffing.
+ return false;
+ }
+ return serialization == aOther.serialization;
+}
+
+StyleLoadData::~StyleLoadData() { Gecko_LoadData_Drop(this); }
+
+already_AddRefed<nsIURI> StyleComputedUrl::ResolveLocalRef(
+ nsIURI* aBase) const {
+ nsCOMPtr<nsIURI> result = GetURI();
+ if (result && IsLocalRef()) {
+ nsCString ref;
+ result->GetRef(ref);
+
+ nsresult rv = NS_MutateURI(aBase).SetRef(ref).Finalize(result);
+
+ if (NS_FAILED(rv)) {
+ // If setting the ref failed, just return the original URI.
+ result = aBase;
+ }
+ }
+ return result.forget();
+}
+
+already_AddRefed<nsIURI> StyleComputedUrl::ResolveLocalRef(
+ const nsIContent* aContent) const {
+ return ResolveLocalRef(aContent->GetBaseURI());
+}
+
+void StyleComputedUrl::ResolveImage(Document& aDocument,
+ const StyleComputedUrl* aOldImage) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ StyleLoadData& data = LoadData();
+
+ MOZ_ASSERT(!(data.flags & StyleLoadDataFlags::TRIED_TO_RESOLVE_IMAGE));
+
+ data.flags |= StyleLoadDataFlags::TRIED_TO_RESOLVE_IMAGE;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO(emilio, bug 1440442): This is a hackaround to avoid flickering due the
+ // lack of non-http image caching in imagelib (bug 1406134), which causes
+ // stuff like bug 1439285. Cleanest fix if that doesn't get fixed is bug
+ // 1440305, but that seems too risky, and a lot of work to do before 60.
+ //
+ // Once that's fixed, the "old style" argument to TriggerImageLoads can go
+ // away, and same for mSharedCount in the image loader and so on.
+ const bool reuseProxy = nsContentUtils::IsChromeDoc(&aDocument) &&
+ aOldImage && aOldImage->IsImageResolved() &&
+ *this == *aOldImage;
+
+ RefPtr<imgRequestProxy> request;
+ if (reuseProxy) {
+ request = aOldImage->LoadData().resolved_image;
+ if (request) {
+ css::ImageLoader::NoteSharedLoad(request);
+ }
+ } else {
+ request = css::ImageLoader::LoadImage(*this, aDocument);
+ }
+
+ if (!request) {
+ return;
+ }
+
+ data.resolved_image = request.forget().take();
+
+ // Boost priority now that we know the image is present in the ComputedStyle
+ // of some frame.
+ data.resolved_image->BoostPriority(imgIRequest::CATEGORY_FRAME_STYLE);
+}
+
+/**
+ * Runnable to release the image request's mRequestProxy
+ * and mImageTracker on the main thread, and to perform
+ * any necessary unlocking and untracking of the image.
+ */
+class StyleImageRequestCleanupTask final : public mozilla::Runnable {
+ public:
+ explicit StyleImageRequestCleanupTask(StyleLoadData& aData)
+ : mozilla::Runnable("StyleImageRequestCleanupTask"),
+ mRequestProxy(dont_AddRef(aData.resolved_image)) {
+ MOZ_ASSERT(mRequestProxy);
+ aData.resolved_image = nullptr;
+ }
+
+ NS_IMETHOD Run() final {
+ MOZ_ASSERT(NS_IsMainThread());
+ css::ImageLoader::UnloadImage(mRequestProxy);
+ return NS_OK;
+ }
+
+ protected:
+ virtual ~StyleImageRequestCleanupTask() {
+ MOZ_ASSERT(!mRequestProxy || NS_IsMainThread(),
+ "mRequestProxy destructor need to run on the main thread!");
+ }
+
+ private:
+ // Since we always dispatch this runnable to the main thread, these will be
+ // released on the main thread when the runnable itself is released.
+ RefPtr<imgRequestProxy> mRequestProxy;
+};
+
+// This is defined here for parallelism with LoadURI.
+void Gecko_LoadData_Drop(StyleLoadData* aData) {
+ if (aData->resolved_image) {
+ // We want to dispatch this async to prevent reentrancy issues, as
+ // imgRequestProxy going away can destroy documents, etc, see bug 1677555.
+ auto task = MakeRefPtr<StyleImageRequestCleanupTask>(*aData);
+ SchedulerGroup::Dispatch(task.forget());
+ }
+
+ // URIs are safe to refcount from any thread.
+ NS_IF_RELEASE(aData->resolved_uri);
+}
+
+// --------------------
+// nsStyleFont
+//
+nsStyleFont::nsStyleFont(const nsStyleFont& aSrc)
+ : mFont(aSrc.mFont),
+ mSize(aSrc.mSize),
+ mFontSizeFactor(aSrc.mFontSizeFactor),
+ mFontSizeOffset(aSrc.mFontSizeOffset),
+ mFontSizeKeyword(aSrc.mFontSizeKeyword),
+ mFontPalette(aSrc.mFontPalette),
+ mMathDepth(aSrc.mMathDepth),
+ mLineHeight(aSrc.mLineHeight),
+ mMathVariant(aSrc.mMathVariant),
+ mMathStyle(aSrc.mMathStyle),
+ mMinFontSizeRatio(aSrc.mMinFontSizeRatio),
+ mExplicitLanguage(aSrc.mExplicitLanguage),
+ mXTextScale(aSrc.mXTextScale),
+ mScriptUnconstrainedSize(aSrc.mScriptUnconstrainedSize),
+ mScriptMinSize(aSrc.mScriptMinSize),
+ mLanguage(aSrc.mLanguage) {
+ MOZ_COUNT_CTOR(nsStyleFont);
+}
+
+static StyleXTextScale InitialTextScale(const Document& aDoc) {
+ if (nsContentUtils::IsChromeDoc(&aDoc) ||
+ nsContentUtils::IsPDFJS(aDoc.NodePrincipal())) {
+ return StyleXTextScale::ZoomOnly;
+ }
+ return StyleXTextScale::All;
+}
+
+nsStyleFont::nsStyleFont(const Document& aDocument)
+ : mFont(*aDocument.GetFontPrefsForLang(nullptr)->GetDefaultFont(
+ StyleGenericFontFamily::None)),
+ mSize(ZoomText(aDocument, mFont.size)),
+ mFontSizeFactor(1.0),
+ mFontSizeOffset{0},
+ mFontSizeKeyword(StyleFontSizeKeyword::Medium),
+ mFontPalette(StyleFontPalette::Normal()),
+ mMathDepth(0),
+ mLineHeight(StyleLineHeight::Normal()),
+ mMathVariant(StyleMathVariant::None),
+ mMathStyle(StyleMathStyle::Normal),
+ mXTextScale(InitialTextScale(aDocument)),
+ mScriptUnconstrainedSize(mSize),
+ mScriptMinSize(Length::FromPixels(
+ CSSPixel::FromPoints(kMathMLDefaultScriptMinSizePt))),
+ mLanguage(aDocument.GetLanguageForStyle()) {
+ MOZ_COUNT_CTOR(nsStyleFont);
+ MOZ_ASSERT(NS_IsMainThread());
+ mFont.family.is_initial = true;
+ mFont.size = mSize;
+ if (MinFontSizeEnabled()) {
+ const Length minimumFontSize =
+ aDocument.GetFontPrefsForLang(mLanguage)->mMinimumFontSize;
+ mFont.size = Length::FromPixels(
+ std::max(mSize.ToCSSPixels(), minimumFontSize.ToCSSPixels()));
+ }
+}
+
+nsChangeHint nsStyleFont::CalcDifference(const nsStyleFont& aNewData) const {
+ MOZ_ASSERT(mXTextScale == aNewData.mXTextScale,
+ "expected -x-text-scale to be the same on both nsStyleFonts");
+ if (mSize != aNewData.mSize || mLanguage != aNewData.mLanguage ||
+ mExplicitLanguage != aNewData.mExplicitLanguage ||
+ mMathVariant != aNewData.mMathVariant ||
+ mMathStyle != aNewData.mMathStyle ||
+ mMinFontSizeRatio != aNewData.mMinFontSizeRatio ||
+ mLineHeight != aNewData.mLineHeight) {
+ return NS_STYLE_HINT_REFLOW;
+ }
+
+ switch (mFont.CalcDifference(aNewData.mFont)) {
+ case nsFont::MaxDifference::eLayoutAffecting:
+ return NS_STYLE_HINT_REFLOW;
+
+ case nsFont::MaxDifference::eVisual:
+ return NS_STYLE_HINT_VISUAL;
+
+ case nsFont::MaxDifference::eNone:
+ break;
+ }
+
+ if (mFontPalette != aNewData.mFontPalette) {
+ return NS_STYLE_HINT_VISUAL;
+ }
+
+ // XXX Should any of these cause a non-nsChangeHint_NeutralChange change?
+ if (mMathDepth != aNewData.mMathDepth ||
+ mScriptUnconstrainedSize != aNewData.mScriptUnconstrainedSize ||
+ mScriptMinSize != aNewData.mScriptMinSize) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ return nsChangeHint(0);
+}
+
+Length nsStyleFont::ZoomText(const Document& aDocument, Length aSize) {
+ if (auto* pc = aDocument.GetPresContext()) {
+ aSize.ScaleBy(pc->TextZoom());
+ }
+ return aSize;
+}
+
+template <typename T>
+static StyleRect<T> StyleRectWithAllSides(const T& aSide) {
+ return {aSide, aSide, aSide, aSide};
+}
+
+nsStyleMargin::nsStyleMargin()
+ : mMargin(StyleRectWithAllSides(
+ LengthPercentageOrAuto::LengthPercentage(LengthPercentage::Zero()))),
+ mScrollMargin(StyleRectWithAllSides(StyleLength{0.})),
+ mOverflowClipMargin(StyleLength::Zero()) {
+ MOZ_COUNT_CTOR(nsStyleMargin);
+}
+
+nsStyleMargin::nsStyleMargin(const nsStyleMargin& aSrc)
+ : mMargin(aSrc.mMargin),
+ mScrollMargin(aSrc.mScrollMargin),
+ mOverflowClipMargin(aSrc.mOverflowClipMargin) {
+ MOZ_COUNT_CTOR(nsStyleMargin);
+}
+
+nsChangeHint nsStyleMargin::CalcDifference(
+ const nsStyleMargin& aNewData) const {
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (mMargin != aNewData.mMargin) {
+ // Margin differences can't affect descendant intrinsic sizes and
+ // don't need to force children to reflow.
+ hint |= nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ if (mScrollMargin != aNewData.mScrollMargin) {
+ // FIXME: Bug 1530253 Support re-snapping when scroll-margin changes.
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ if (mOverflowClipMargin != aNewData.mOverflowClipMargin) {
+ hint |= nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
+ }
+
+ return hint;
+}
+
+nsStylePadding::nsStylePadding()
+ : mPadding(StyleRectWithAllSides(LengthPercentage::Zero())),
+ mScrollPadding(StyleRectWithAllSides(LengthPercentageOrAuto::Auto())) {
+ MOZ_COUNT_CTOR(nsStylePadding);
+}
+
+nsStylePadding::nsStylePadding(const nsStylePadding& aSrc)
+ : mPadding(aSrc.mPadding), mScrollPadding(aSrc.mScrollPadding) {
+ MOZ_COUNT_CTOR(nsStylePadding);
+}
+
+nsChangeHint nsStylePadding::CalcDifference(
+ const nsStylePadding& aNewData) const {
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (mPadding != aNewData.mPadding) {
+ // Padding differences can't affect descendant intrinsic sizes, but do need
+ // to force children to reflow so that we can reposition them, since their
+ // offsets are from our frame bounds but our content rect's position within
+ // those bounds is moving.
+ // FIXME: It would be good to return a weaker hint here that doesn't
+ // force reflow of all descendants, but the hint would need to force
+ // reflow of the frame's children (see how
+ // ReflowInput::InitResizeFlags initializes the inline-resize flag).
+ hint |= NS_STYLE_HINT_REFLOW & ~nsChangeHint_ClearDescendantIntrinsics;
+ }
+
+ if (mScrollPadding != aNewData.mScrollPadding) {
+ // FIXME: Bug 1530253 Support re-snapping when scroll-padding changes.
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ return hint;
+}
+
+static inline BorderRadius ZeroBorderRadius() {
+ auto zero = LengthPercentage::Zero();
+ return {{{zero, zero}}, {{zero, zero}}, {{zero, zero}}, {{zero, zero}}};
+}
+
+nsStyleBorder::nsStyleBorder()
+ : mBorderRadius(ZeroBorderRadius()),
+ mBorderImageSource(StyleImage::None()),
+ mBorderImageWidth(
+ StyleRectWithAllSides(StyleBorderImageSideWidth::Number(1.))),
+ mBorderImageOutset(
+ StyleRectWithAllSides(StyleNonNegativeLengthOrNumber::Number(0.))),
+ mBorderImageSlice(
+ {StyleRectWithAllSides(StyleNumberOrPercentage::Percentage({1.})),
+ false}),
+ mBorderImageRepeatH(StyleBorderImageRepeat::Stretch),
+ mBorderImageRepeatV(StyleBorderImageRepeat::Stretch),
+ mFloatEdge(StyleFloatEdge::ContentBox),
+ mBoxDecorationBreak(StyleBoxDecorationBreak::Slice),
+ mBorderTopColor(StyleColor::CurrentColor()),
+ mBorderRightColor(StyleColor::CurrentColor()),
+ mBorderBottomColor(StyleColor::CurrentColor()),
+ mBorderLeftColor(StyleColor::CurrentColor()),
+ mComputedBorder(0, 0, 0, 0) {
+ MOZ_COUNT_CTOR(nsStyleBorder);
+
+ nscoord medium = kMediumBorderWidth;
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ mBorder.Side(side) = medium;
+ mBorderStyle[side] = StyleBorderStyle::None;
+ }
+}
+
+nsStyleBorder::nsStyleBorder(const nsStyleBorder& aSrc)
+ : mBorderRadius(aSrc.mBorderRadius),
+ mBorderImageSource(aSrc.mBorderImageSource),
+ mBorderImageWidth(aSrc.mBorderImageWidth),
+ mBorderImageOutset(aSrc.mBorderImageOutset),
+ mBorderImageSlice(aSrc.mBorderImageSlice),
+ mBorderImageRepeatH(aSrc.mBorderImageRepeatH),
+ mBorderImageRepeatV(aSrc.mBorderImageRepeatV),
+ mFloatEdge(aSrc.mFloatEdge),
+ mBoxDecorationBreak(aSrc.mBoxDecorationBreak),
+ mBorderTopColor(aSrc.mBorderTopColor),
+ mBorderRightColor(aSrc.mBorderRightColor),
+ mBorderBottomColor(aSrc.mBorderBottomColor),
+ mBorderLeftColor(aSrc.mBorderLeftColor),
+ mComputedBorder(aSrc.mComputedBorder),
+ mBorder(aSrc.mBorder) {
+ MOZ_COUNT_CTOR(nsStyleBorder);
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ mBorderStyle[side] = aSrc.mBorderStyle[side];
+ }
+}
+
+void nsStyleBorder::TriggerImageLoads(Document& aDocument,
+ const nsStyleBorder* aOldStyle) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mBorderImageSource.ResolveImage(
+ aDocument, aOldStyle ? &aOldStyle->mBorderImageSource : nullptr);
+}
+
+nsMargin nsStyleBorder::GetImageOutset() const {
+ // We don't check whether there is a border-image (which is OK since
+ // the initial values yields 0 outset) so that we don't have to
+ // reflow to update overflow areas when an image loads.
+ nsMargin outset;
+ for (const auto s : mozilla::AllPhysicalSides()) {
+ const auto& coord = mBorderImageOutset.Get(s);
+ nscoord value;
+ if (coord.IsLength()) {
+ value = coord.AsLength().ToAppUnits();
+ } else {
+ MOZ_ASSERT(coord.IsNumber());
+ value = coord.AsNumber() * mComputedBorder.Side(s);
+ }
+ outset.Side(s) = value;
+ }
+ return outset;
+}
+
+nsChangeHint nsStyleBorder::CalcDifference(
+ const nsStyleBorder& aNewData) const {
+ // FIXME: XXXbz: As in nsStylePadding::CalcDifference, many of these
+ // differences should not need to clear descendant intrinsics.
+ // FIXME: It would be good to return a weaker hint for the
+ // GetComputedBorder() differences (and perhaps others) that doesn't
+ // force reflow of all descendants, but the hint would need to force
+ // reflow of the frame's children (see how
+ // ReflowInput::InitResizeFlags initializes the inline-resize flag).
+ if (GetComputedBorder() != aNewData.GetComputedBorder() ||
+ mFloatEdge != aNewData.mFloatEdge ||
+ mBorderImageOutset != aNewData.mBorderImageOutset ||
+ mBoxDecorationBreak != aNewData.mBoxDecorationBreak) {
+ return NS_STYLE_HINT_REFLOW;
+ }
+
+ for (const auto ix : mozilla::AllPhysicalSides()) {
+ // See the explanation in nsChangeHint.h of
+ // nsChangeHint_BorderStyleNoneChange .
+ // Furthermore, even though we know *this* side is 0 width, just
+ // assume a repaint hint for some other change rather than bother
+ // tracking this result through the rest of the function.
+ if (HasVisibleStyle(ix) != aNewData.HasVisibleStyle(ix)) {
+ return nsChangeHint_RepaintFrame | nsChangeHint_BorderStyleNoneChange;
+ }
+ }
+
+ // Note that mBorderStyle stores not only the border style but also
+ // color-related flags. Given that we've already done an mComputedBorder
+ // comparison, border-style differences can only lead to a repaint hint. So
+ // it's OK to just compare the values directly -- if either the actual
+ // style or the color flags differ we want to repaint.
+ for (const auto ix : mozilla::AllPhysicalSides()) {
+ if (mBorderStyle[ix] != aNewData.mBorderStyle[ix] ||
+ BorderColorFor(ix) != aNewData.BorderColorFor(ix)) {
+ return nsChangeHint_RepaintFrame;
+ }
+ }
+
+ // Note that border radius also controls the outline radius if the
+ // layout.css.outline-follows-border-radius.enabled pref is set. Any
+ // optimizations here should apply to both.
+ if (mBorderRadius != aNewData.mBorderRadius) {
+ return nsChangeHint_RepaintFrame;
+ }
+
+ // Loading status of the border image can be accessed in main thread only
+ // while CalcDifference might be executed on a background thread. As a
+ // result, we have to check mBorderImage* fields even before border image was
+ // actually loaded.
+ if (!mBorderImageSource.IsNone() || !aNewData.mBorderImageSource.IsNone()) {
+ if (mBorderImageSource != aNewData.mBorderImageSource ||
+ mBorderImageRepeatH != aNewData.mBorderImageRepeatH ||
+ mBorderImageRepeatV != aNewData.mBorderImageRepeatV ||
+ mBorderImageSlice != aNewData.mBorderImageSlice ||
+ mBorderImageWidth != aNewData.mBorderImageWidth) {
+ return nsChangeHint_RepaintFrame;
+ }
+ }
+
+ // mBorder is the specified border value. Changes to this don't
+ // need any change processing, since we operate on the computed
+ // border values instead.
+ if (mBorder != aNewData.mBorder) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ // mBorderImage* fields are checked only when border-image is not 'none'.
+ if (mBorderImageSource != aNewData.mBorderImageSource ||
+ mBorderImageRepeatH != aNewData.mBorderImageRepeatH ||
+ mBorderImageRepeatV != aNewData.mBorderImageRepeatV ||
+ mBorderImageSlice != aNewData.mBorderImageSlice ||
+ mBorderImageWidth != aNewData.mBorderImageWidth) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ return nsChangeHint(0);
+}
+
+nsStyleOutline::nsStyleOutline()
+ : mOutlineWidth(kMediumBorderWidth),
+ mOutlineOffset({0.0f}),
+ mOutlineColor(StyleColor::CurrentColor()),
+ mOutlineStyle(StyleOutlineStyle::BorderStyle(StyleBorderStyle::None)),
+ mActualOutlineWidth(0) {
+ MOZ_COUNT_CTOR(nsStyleOutline);
+}
+
+nsStyleOutline::nsStyleOutline(const nsStyleOutline& aSrc)
+ : mOutlineWidth(aSrc.mOutlineWidth),
+ mOutlineOffset(aSrc.mOutlineOffset),
+ mOutlineColor(aSrc.mOutlineColor),
+ mOutlineStyle(aSrc.mOutlineStyle),
+ mActualOutlineWidth(aSrc.mActualOutlineWidth) {
+ MOZ_COUNT_CTOR(nsStyleOutline);
+}
+
+nsChangeHint nsStyleOutline::CalcDifference(
+ const nsStyleOutline& aNewData) const {
+ const bool shouldPaintOutline = ShouldPaintOutline();
+ // We need the explicit 'outline-style: auto' check because
+ // 'outline-style: auto' effectively also changes 'outline-width'.
+ if (shouldPaintOutline != aNewData.ShouldPaintOutline() ||
+ mActualOutlineWidth != aNewData.mActualOutlineWidth ||
+ mOutlineStyle.IsAuto() != aNewData.mOutlineStyle.IsAuto() ||
+ (shouldPaintOutline && mOutlineOffset != aNewData.mOutlineOffset)) {
+ return nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint |
+ nsChangeHint_RepaintFrame;
+ }
+
+ if (mOutlineStyle != aNewData.mOutlineStyle ||
+ mOutlineColor != aNewData.mOutlineColor) {
+ return shouldPaintOutline ? nsChangeHint_RepaintFrame
+ : nsChangeHint_NeutralChange;
+ }
+
+ if (mOutlineWidth != aNewData.mOutlineWidth ||
+ mOutlineOffset != aNewData.mOutlineOffset) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ return nsChangeHint(0);
+}
+
+nsSize nsStyleOutline::EffectiveOffsetFor(const nsRect& aRect) const {
+ const nscoord offset = mOutlineOffset.ToAppUnits();
+
+ if (offset >= 0) {
+ // Fast path for non-negative offset values
+ return nsSize(offset, offset);
+ }
+
+ return nsSize(std::max(offset, -(aRect.Width() / 2)),
+ std::max(offset, -(aRect.Height() / 2)));
+}
+
+// --------------------
+// nsStyleList
+//
+nsStyleList::nsStyleList()
+ : mListStylePosition(StyleListStylePosition::Outside),
+ mQuotes(StyleQuotes::Auto()),
+ mListStyleImage(StyleImage::None()) {
+ MOZ_COUNT_CTOR(nsStyleList);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mCounterStyle = nsGkAtoms::disc;
+}
+
+nsStyleList::nsStyleList(const nsStyleList& aSource)
+ : mListStylePosition(aSource.mListStylePosition),
+ mCounterStyle(aSource.mCounterStyle),
+ mQuotes(aSource.mQuotes),
+ mListStyleImage(aSource.mListStyleImage) {
+ MOZ_COUNT_CTOR(nsStyleList);
+}
+
+void nsStyleList::TriggerImageLoads(Document& aDocument,
+ const nsStyleList* aOldStyle) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mListStyleImage.ResolveImage(
+ aDocument, aOldStyle ? &aOldStyle->mListStyleImage : nullptr);
+}
+
+nsChangeHint nsStyleList::CalcDifference(const nsStyleList& aNewData,
+ const ComputedStyle& aOldStyle) const {
+ // If the quotes implementation is ever going to change we might not need
+ // a framechange here and a reflow should be sufficient. See bug 35768.
+ if (mQuotes != aNewData.mQuotes) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ nsChangeHint hint = nsChangeHint(0);
+ // Only elements whose display value is list-item can be affected by
+ // list-style-{position,type,image}. This also relies on that when the display
+ // value changes from something else to list-item, that change itself would
+ // cause ReconstructFrame.
+ if (mListStylePosition != aNewData.mListStylePosition ||
+ mCounterStyle != aNewData.mCounterStyle ||
+ mListStyleImage != aNewData.mListStyleImage) {
+ if (aOldStyle.StyleDisplay()->IsListItem()) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ // list-style-image may affect nsImageFrame for XUL elements, but that is
+ // dealt with explicitly in nsImageFrame::DidSetComputedStyle.
+ hint = nsChangeHint_NeutralChange;
+ }
+ return hint;
+}
+
+already_AddRefed<nsIURI> nsStyleList::GetListStyleImageURI() const {
+ if (!mListStyleImage.IsUrl()) {
+ return nullptr;
+ }
+
+ return do_AddRef(mListStyleImage.AsUrl().GetURI());
+}
+
+// --------------------
+// nsStyleXUL
+//
+nsStyleXUL::nsStyleXUL()
+ : mBoxFlex(0.0f),
+ mBoxOrdinal(1),
+ mBoxAlign(StyleBoxAlign::Stretch),
+ mBoxDirection(StyleBoxDirection::Normal),
+ mBoxOrient(StyleBoxOrient::Horizontal),
+ mBoxPack(StyleBoxPack::Start) {
+ MOZ_COUNT_CTOR(nsStyleXUL);
+}
+
+nsStyleXUL::nsStyleXUL(const nsStyleXUL& aSource)
+ : mBoxFlex(aSource.mBoxFlex),
+ mBoxOrdinal(aSource.mBoxOrdinal),
+ mBoxAlign(aSource.mBoxAlign),
+ mBoxDirection(aSource.mBoxDirection),
+ mBoxOrient(aSource.mBoxOrient),
+ mBoxPack(aSource.mBoxPack) {
+ MOZ_COUNT_CTOR(nsStyleXUL);
+}
+
+nsChangeHint nsStyleXUL::CalcDifference(const nsStyleXUL& aNewData) const {
+ if (mBoxAlign == aNewData.mBoxAlign &&
+ mBoxDirection == aNewData.mBoxDirection &&
+ mBoxFlex == aNewData.mBoxFlex && mBoxOrient == aNewData.mBoxOrient &&
+ mBoxPack == aNewData.mBoxPack && mBoxOrdinal == aNewData.mBoxOrdinal) {
+ return nsChangeHint(0);
+ }
+ if (mBoxOrdinal != aNewData.mBoxOrdinal) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ return NS_STYLE_HINT_REFLOW;
+}
+
+// --------------------
+// nsStyleColumn
+//
+/* static */ const uint32_t nsStyleColumn::kMaxColumnCount;
+/* static */ const uint32_t nsStyleColumn::kColumnCountAuto;
+
+nsStyleColumn::nsStyleColumn()
+ : mColumnWidth(LengthOrAuto::Auto()),
+ mColumnRuleColor(StyleColor::CurrentColor()),
+ mColumnRuleStyle(StyleBorderStyle::None),
+ mColumnRuleWidth(kMediumBorderWidth),
+ mActualColumnRuleWidth(0) {
+ MOZ_COUNT_CTOR(nsStyleColumn);
+}
+
+nsStyleColumn::nsStyleColumn(const nsStyleColumn& aSource)
+ : mColumnCount(aSource.mColumnCount),
+ mColumnWidth(aSource.mColumnWidth),
+ mColumnRuleColor(aSource.mColumnRuleColor),
+ mColumnRuleStyle(aSource.mColumnRuleStyle),
+ mColumnFill(aSource.mColumnFill),
+ mColumnSpan(aSource.mColumnSpan),
+ mColumnRuleWidth(aSource.mColumnRuleWidth),
+ mActualColumnRuleWidth(aSource.mActualColumnRuleWidth) {
+ MOZ_COUNT_CTOR(nsStyleColumn);
+}
+
+nsChangeHint nsStyleColumn::CalcDifference(
+ const nsStyleColumn& aNewData) const {
+ if (mColumnWidth.IsAuto() != aNewData.mColumnWidth.IsAuto() ||
+ mColumnCount != aNewData.mColumnCount ||
+ mColumnSpan != aNewData.mColumnSpan) {
+ // We force column count changes to do a reframe, because it's tricky to
+ // handle some edge cases where the column count gets smaller and content
+ // overflows.
+ // XXX not ideal
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ if (mColumnWidth != aNewData.mColumnWidth ||
+ mColumnFill != aNewData.mColumnFill) {
+ return NS_STYLE_HINT_REFLOW;
+ }
+
+ if (mActualColumnRuleWidth != aNewData.mActualColumnRuleWidth ||
+ mColumnRuleStyle != aNewData.mColumnRuleStyle ||
+ mColumnRuleColor != aNewData.mColumnRuleColor) {
+ return NS_STYLE_HINT_VISUAL;
+ }
+
+ if (mColumnRuleWidth != aNewData.mColumnRuleWidth) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ return nsChangeHint(0);
+}
+
+using SVGPaintFallback = StyleGenericSVGPaintFallback<StyleColor>;
+
+// --------------------
+// nsStyleSVG
+//
+nsStyleSVG::nsStyleSVG()
+ : mFill{StyleSVGPaintKind::Color(StyleColor::Black()),
+ SVGPaintFallback::Unset()},
+ mStroke{StyleSVGPaintKind::None(), SVGPaintFallback::Unset()},
+ mMarkerEnd(StyleUrlOrNone::None()),
+ mMarkerMid(StyleUrlOrNone::None()),
+ mMarkerStart(StyleUrlOrNone::None()),
+ mMozContextProperties{{}, {0}},
+ mStrokeDasharray(StyleSVGStrokeDashArray::Values({})),
+ mStrokeDashoffset(
+ StyleSVGLength::LengthPercentage(LengthPercentage::Zero())),
+ mStrokeWidth(
+ StyleSVGWidth::LengthPercentage(LengthPercentage::FromPixels(1.0f))),
+ mFillOpacity(StyleSVGOpacity::Opacity(1.0f)),
+ mStrokeMiterlimit(4.0f),
+ mStrokeOpacity(StyleSVGOpacity::Opacity(1.0f)),
+ mClipRule(StyleFillRule::Nonzero),
+ mColorInterpolation(StyleColorInterpolation::Srgb),
+ mColorInterpolationFilters(StyleColorInterpolation::Linearrgb),
+ mFillRule(StyleFillRule::Nonzero),
+ mPaintOrder(0),
+ mShapeRendering(StyleShapeRendering::Auto),
+ mStrokeLinecap(StyleStrokeLinecap::Butt),
+ mStrokeLinejoin(StyleStrokeLinejoin::Miter),
+ mDominantBaseline(StyleDominantBaseline::Auto),
+ mTextAnchor(StyleTextAnchor::Start) {
+ MOZ_COUNT_CTOR(nsStyleSVG);
+}
+
+nsStyleSVG::nsStyleSVG(const nsStyleSVG& aSource)
+ : mFill(aSource.mFill),
+ mStroke(aSource.mStroke),
+ mMarkerEnd(aSource.mMarkerEnd),
+ mMarkerMid(aSource.mMarkerMid),
+ mMarkerStart(aSource.mMarkerStart),
+ mMozContextProperties(aSource.mMozContextProperties),
+ mStrokeDasharray(aSource.mStrokeDasharray),
+ mStrokeDashoffset(aSource.mStrokeDashoffset),
+ mStrokeWidth(aSource.mStrokeWidth),
+ mFillOpacity(aSource.mFillOpacity),
+ mStrokeMiterlimit(aSource.mStrokeMiterlimit),
+ mStrokeOpacity(aSource.mStrokeOpacity),
+ mClipRule(aSource.mClipRule),
+ mColorInterpolation(aSource.mColorInterpolation),
+ mColorInterpolationFilters(aSource.mColorInterpolationFilters),
+ mFillRule(aSource.mFillRule),
+ mPaintOrder(aSource.mPaintOrder),
+ mShapeRendering(aSource.mShapeRendering),
+ mStrokeLinecap(aSource.mStrokeLinecap),
+ mStrokeLinejoin(aSource.mStrokeLinejoin),
+ mDominantBaseline(aSource.mDominantBaseline),
+ mTextAnchor(aSource.mTextAnchor) {
+ MOZ_COUNT_CTOR(nsStyleSVG);
+}
+
+static bool PaintURIChanged(const StyleSVGPaint& aPaint1,
+ const StyleSVGPaint& aPaint2) {
+ if (aPaint1.kind.IsPaintServer() != aPaint2.kind.IsPaintServer()) {
+ return true;
+ }
+ return aPaint1.kind.IsPaintServer() &&
+ aPaint1.kind.AsPaintServer() != aPaint2.kind.AsPaintServer();
+}
+
+nsChangeHint nsStyleSVG::CalcDifference(const nsStyleSVG& aNewData) const {
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (mMarkerEnd != aNewData.mMarkerEnd || mMarkerMid != aNewData.mMarkerMid ||
+ mMarkerStart != aNewData.mMarkerStart) {
+ // Markers currently contribute to SVGGeometryFrame::mRect,
+ // so we need a reflow as well as a repaint. No intrinsic sizes need
+ // to change, so nsChangeHint_NeedReflow is sufficient.
+ return nsChangeHint_UpdateEffects | nsChangeHint_NeedReflow |
+ nsChangeHint_RepaintFrame;
+ }
+
+ if (mFill != aNewData.mFill || mStroke != aNewData.mStroke ||
+ mFillOpacity != aNewData.mFillOpacity ||
+ mStrokeOpacity != aNewData.mStrokeOpacity) {
+ hint |= nsChangeHint_RepaintFrame;
+ if (HasStroke() != aNewData.HasStroke() ||
+ (!HasStroke() && HasFill() != aNewData.HasFill())) {
+ // Frame bounds and overflow rects depend on whether we "have" fill or
+ // stroke. Whether we have stroke or not just changed, or else we have no
+ // stroke (in which case whether we have fill or not is significant to
+ // frame bounds) and whether we have fill or not just changed. In either
+ // case we need to reflow so the frame rect is updated.
+ // XXXperf this is a waste on non SVGGeometryFrames.
+ hint |= nsChangeHint_NeedReflow;
+ }
+ if (PaintURIChanged(mFill, aNewData.mFill) ||
+ PaintURIChanged(mStroke, aNewData.mStroke)) {
+ hint |= nsChangeHint_UpdateEffects;
+ }
+ }
+
+ // Stroke currently contributes to SVGGeometryFrame::mRect, so
+ // we need a reflow here. No intrinsic sizes need to change, so
+ // nsChangeHint_NeedReflow is sufficient.
+ // Note that stroke-dashoffset does not affect SVGGeometryFrame::mRect.
+ // text-anchor and dominant-baseline changes also require a reflow since
+ // they change frames' rects.
+ if (mStrokeWidth != aNewData.mStrokeWidth ||
+ mStrokeMiterlimit != aNewData.mStrokeMiterlimit ||
+ mStrokeLinecap != aNewData.mStrokeLinecap ||
+ mStrokeLinejoin != aNewData.mStrokeLinejoin ||
+ mDominantBaseline != aNewData.mDominantBaseline ||
+ mTextAnchor != aNewData.mTextAnchor) {
+ return hint | nsChangeHint_NeedReflow | nsChangeHint_RepaintFrame;
+ }
+
+ if (hint & nsChangeHint_RepaintFrame) {
+ return hint; // we don't add anything else below
+ }
+
+ if (mStrokeDashoffset != aNewData.mStrokeDashoffset ||
+ mClipRule != aNewData.mClipRule ||
+ mColorInterpolation != aNewData.mColorInterpolation ||
+ mColorInterpolationFilters != aNewData.mColorInterpolationFilters ||
+ mFillRule != aNewData.mFillRule || mPaintOrder != aNewData.mPaintOrder ||
+ mShapeRendering != aNewData.mShapeRendering ||
+ mStrokeDasharray != aNewData.mStrokeDasharray ||
+ mMozContextProperties.bits != aNewData.mMozContextProperties.bits) {
+ return hint | nsChangeHint_RepaintFrame;
+ }
+
+ if (!hint) {
+ if (mMozContextProperties.idents != aNewData.mMozContextProperties.idents) {
+ hint = nsChangeHint_NeutralChange;
+ }
+ }
+
+ return hint;
+}
+
+// --------------------
+// nsStyleSVGReset
+//
+nsStyleSVGReset::nsStyleSVGReset()
+ : mX(LengthPercentage::Zero()),
+ mY(LengthPercentage::Zero()),
+ mCx(LengthPercentage::Zero()),
+ mCy(LengthPercentage::Zero()),
+ mRx(NonNegativeLengthPercentageOrAuto::Auto()),
+ mRy(NonNegativeLengthPercentageOrAuto::Auto()),
+ mR(NonNegativeLengthPercentage::Zero()),
+ mMask(nsStyleImageLayers::LayerType::Mask),
+ mClipPath(StyleClipPath::None()),
+ mStopColor(StyleColor::Black()),
+ mFloodColor(StyleColor::Black()),
+ mLightingColor(StyleColor::White()),
+ mStopOpacity(1.0f),
+ mFloodOpacity(1.0f),
+ mVectorEffect(StyleVectorEffect::None),
+ mMaskType(StyleMaskType::Luminance),
+ mD(StyleDProperty::None()) {
+ MOZ_COUNT_CTOR(nsStyleSVGReset);
+}
+
+nsStyleSVGReset::nsStyleSVGReset(const nsStyleSVGReset& aSource)
+ : mX(aSource.mX),
+ mY(aSource.mY),
+ mCx(aSource.mCx),
+ mCy(aSource.mCy),
+ mRx(aSource.mRx),
+ mRy(aSource.mRy),
+ mR(aSource.mR),
+ mMask(aSource.mMask),
+ mClipPath(aSource.mClipPath),
+ mStopColor(aSource.mStopColor),
+ mFloodColor(aSource.mFloodColor),
+ mLightingColor(aSource.mLightingColor),
+ mStopOpacity(aSource.mStopOpacity),
+ mFloodOpacity(aSource.mFloodOpacity),
+ mVectorEffect(aSource.mVectorEffect),
+ mMaskType(aSource.mMaskType),
+ mD(aSource.mD) {
+ MOZ_COUNT_CTOR(nsStyleSVGReset);
+}
+
+void nsStyleSVGReset::TriggerImageLoads(Document& aDocument,
+ const nsStyleSVGReset* aOldStyle) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // NOTE(emilio): we intentionally don't call TriggerImageLoads for clip-path.
+
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, mMask) {
+ auto& image = mMask.mLayers[i].mImage;
+ if (!image.IsImageRequestType()) {
+ continue;
+ }
+ const auto* url = image.GetImageRequestURLValue();
+ // If the url is a local ref, it must be a <mask-resource>, so we don't
+ // need to resolve the style image.
+ if (url->IsLocalRef()) {
+ continue;
+ }
+#if 0
+ // XXX The old style system also checks whether this is a reference to
+ // the current document with reference, but it doesn't seem to be a
+ // behavior mentioned anywhere, so we comment out the code for now.
+ nsIURI* docURI = aPresContext->Document()->GetDocumentURI();
+ if (url->EqualsExceptRef(docURI)) {
+ continue;
+ }
+#endif
+ // Otherwise, we may need the image even if it has a reference, in case
+ // the referenced element isn't a valid SVG <mask> element.
+ const auto* oldImage = (aOldStyle && aOldStyle->mMask.mLayers.Length() > i)
+ ? &aOldStyle->mMask.mLayers[i].mImage
+ : nullptr;
+
+ image.ResolveImage(aDocument, oldImage);
+ }
+}
+
+nsChangeHint nsStyleSVGReset::CalcDifference(
+ const nsStyleSVGReset& aNewData) const {
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (mX != aNewData.mX || mY != aNewData.mY || mCx != aNewData.mCx ||
+ mCy != aNewData.mCy || mR != aNewData.mR || mRx != aNewData.mRx ||
+ mRy != aNewData.mRy || mD != aNewData.mD) {
+ hint |= nsChangeHint_InvalidateRenderingObservers | nsChangeHint_NeedReflow;
+ }
+
+ if (mClipPath != aNewData.mClipPath) {
+ hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
+ }
+
+ if (mVectorEffect != aNewData.mVectorEffect) {
+ // Stroke currently affects SVGGeometryFrame::mRect, and
+ // vector-effect affect stroke. As a result we need to reflow if
+ // vector-effect changes in order to have SVGGeometryFrame::
+ // ReflowSVG called to update its mRect. No intrinsic sizes need
+ // to change so nsChangeHint_NeedReflow is sufficient.
+ hint |= nsChangeHint_NeedReflow | nsChangeHint_RepaintFrame;
+ } else if (mStopColor != aNewData.mStopColor ||
+ mFloodColor != aNewData.mFloodColor ||
+ mLightingColor != aNewData.mLightingColor ||
+ mStopOpacity != aNewData.mStopOpacity ||
+ mFloodOpacity != aNewData.mFloodOpacity ||
+ mMaskType != aNewData.mMaskType || mD != aNewData.mD) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ hint |=
+ mMask.CalcDifference(aNewData.mMask, nsStyleImageLayers::LayerType::Mask);
+
+ return hint;
+}
+
+bool nsStyleSVGReset::HasMask() const {
+ for (uint32_t i = 0; i < mMask.mImageCount; i++) {
+ if (!mMask.mLayers[i].mImage.IsNone()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// --------------------
+// nsStylePage
+//
+
+nsStylePage::nsStylePage(const nsStylePage& aSrc)
+ : mSize(aSrc.mSize),
+ mPage(aSrc.mPage),
+ mPageOrientation(aSrc.mPageOrientation) {
+ MOZ_COUNT_CTOR(nsStylePage);
+}
+
+nsChangeHint nsStylePage::CalcDifference(const nsStylePage& aNewData) const {
+ // Page rule styling only matters when printing or using print preview.
+ if (aNewData.mSize != mSize || aNewData.mPage != mPage ||
+ aNewData.mPageOrientation != mPageOrientation) {
+ return nsChangeHint_NeutralChange;
+ }
+ return nsChangeHint_Empty;
+}
+
+// --------------------
+// nsStylePosition
+//
+nsStylePosition::nsStylePosition()
+ : mObjectPosition(Position::FromPercentage(0.5f)),
+ mOffset(StyleRectWithAllSides(LengthPercentageOrAuto::Auto())),
+ mWidth(StyleSize::Auto()),
+ mMinWidth(StyleSize::Auto()),
+ mMaxWidth(StyleMaxSize::None()),
+ mHeight(StyleSize::Auto()),
+ mMinHeight(StyleSize::Auto()),
+ mMaxHeight(StyleMaxSize::None()),
+ mFlexBasis(StyleFlexBasis::Size(StyleSize::Auto())),
+ mAspectRatio(StyleAspectRatio::Auto()),
+ mGridAutoFlow(StyleGridAutoFlow::ROW),
+ mMasonryAutoFlow(
+ {StyleMasonryPlacement::Pack, StyleMasonryItemOrder::DefiniteFirst}),
+ mAlignContent({StyleAlignFlags::NORMAL}),
+ mAlignItems({StyleAlignFlags::NORMAL}),
+ mAlignSelf({StyleAlignFlags::AUTO}),
+ mJustifyContent({StyleAlignFlags::NORMAL}),
+ mJustifyItems({{StyleAlignFlags::LEGACY}, {StyleAlignFlags::NORMAL}}),
+ mJustifySelf({StyleAlignFlags::AUTO}),
+ mFlexDirection(StyleFlexDirection::Row),
+ mFlexWrap(StyleFlexWrap::Nowrap),
+ mObjectFit(StyleObjectFit::Fill),
+ mBoxSizing(StyleBoxSizing::Content),
+ mOrder(0),
+ mFlexGrow(0.0f),
+ mFlexShrink(1.0f),
+ mZIndex(StyleZIndex::Auto()),
+ mGridTemplateColumns(StyleGridTemplateComponent::None()),
+ mGridTemplateRows(StyleGridTemplateComponent::None()),
+ mGridTemplateAreas(StyleGridTemplateAreas::None()),
+ mColumnGap(NonNegativeLengthPercentageOrNormal::Normal()),
+ mRowGap(NonNegativeLengthPercentageOrNormal::Normal()),
+ mContainIntrinsicWidth(StyleContainIntrinsicSize::None()),
+ mContainIntrinsicHeight(StyleContainIntrinsicSize::None()) {
+ MOZ_COUNT_CTOR(nsStylePosition);
+
+ // The initial value of grid-auto-columns and grid-auto-rows is 'auto',
+ // which computes to 'minmax(auto, auto)'.
+
+ // Other members get their default constructors
+ // which initialize them to representations of their respective initial value.
+ // mGridTemplate{Rows,Columns}: false and empty arrays for 'none'
+ // mGrid{Column,Row}{Start,End}: false/0/empty values for 'auto'
+}
+
+nsStylePosition::nsStylePosition(const nsStylePosition& aSource)
+ : mAlignTracks(aSource.mAlignTracks),
+ mJustifyTracks(aSource.mJustifyTracks),
+ mObjectPosition(aSource.mObjectPosition),
+ mOffset(aSource.mOffset),
+ mWidth(aSource.mWidth),
+ mMinWidth(aSource.mMinWidth),
+ mMaxWidth(aSource.mMaxWidth),
+ mHeight(aSource.mHeight),
+ mMinHeight(aSource.mMinHeight),
+ mMaxHeight(aSource.mMaxHeight),
+ mFlexBasis(aSource.mFlexBasis),
+ mGridAutoColumns(aSource.mGridAutoColumns),
+ mGridAutoRows(aSource.mGridAutoRows),
+ mAspectRatio(aSource.mAspectRatio),
+ mGridAutoFlow(aSource.mGridAutoFlow),
+ mMasonryAutoFlow(aSource.mMasonryAutoFlow),
+ mAlignContent(aSource.mAlignContent),
+ mAlignItems(aSource.mAlignItems),
+ mAlignSelf(aSource.mAlignSelf),
+ mJustifyContent(aSource.mJustifyContent),
+ mJustifyItems(aSource.mJustifyItems),
+ mJustifySelf(aSource.mJustifySelf),
+ mFlexDirection(aSource.mFlexDirection),
+ mFlexWrap(aSource.mFlexWrap),
+ mObjectFit(aSource.mObjectFit),
+ mBoxSizing(aSource.mBoxSizing),
+ mOrder(aSource.mOrder),
+ mFlexGrow(aSource.mFlexGrow),
+ mFlexShrink(aSource.mFlexShrink),
+ mZIndex(aSource.mZIndex),
+ mGridTemplateColumns(aSource.mGridTemplateColumns),
+ mGridTemplateRows(aSource.mGridTemplateRows),
+ mGridTemplateAreas(aSource.mGridTemplateAreas),
+ mGridColumnStart(aSource.mGridColumnStart),
+ mGridColumnEnd(aSource.mGridColumnEnd),
+ mGridRowStart(aSource.mGridRowStart),
+ mGridRowEnd(aSource.mGridRowEnd),
+ mColumnGap(aSource.mColumnGap),
+ mRowGap(aSource.mRowGap),
+ mContainIntrinsicWidth(aSource.mContainIntrinsicWidth),
+ mContainIntrinsicHeight(aSource.mContainIntrinsicHeight) {
+ MOZ_COUNT_CTOR(nsStylePosition);
+}
+
+static bool IsAutonessEqual(const StyleRect<LengthPercentageOrAuto>& aSides1,
+ const StyleRect<LengthPercentageOrAuto>& aSides2) {
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ if (aSides1.Get(side).IsAuto() != aSides2.Get(side).IsAuto()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+nsChangeHint nsStylePosition::CalcDifference(
+ const nsStylePosition& aNewData, const ComputedStyle& aOldStyle) const {
+ if (mGridTemplateColumns.IsMasonry() !=
+ aNewData.mGridTemplateColumns.IsMasonry() ||
+ mGridTemplateRows.IsMasonry() != aNewData.mGridTemplateRows.IsMasonry()) {
+ // XXXmats this could be optimized to AllReflowHints with a bit of work,
+ // but I'll assume this is a very rare use case in practice. (bug 1623886)
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ nsChangeHint hint = nsChangeHint(0);
+
+ // Changes to "z-index" require a repaint.
+ if (mZIndex != aNewData.mZIndex) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ // Changes to "object-fit" & "object-position" require a repaint. They
+ // may also require a reflow, if we have a nsSubDocumentFrame, so that we
+ // can adjust the size & position of the subdocument.
+ if (mObjectFit != aNewData.mObjectFit ||
+ mObjectPosition != aNewData.mObjectPosition) {
+ hint |= nsChangeHint_RepaintFrame | nsChangeHint_NeedReflow;
+ }
+
+ if (mContainIntrinsicWidth != aNewData.mContainIntrinsicWidth ||
+ mContainIntrinsicHeight != aNewData.mContainIntrinsicHeight) {
+ hint |= NS_STYLE_HINT_REFLOW;
+ }
+
+ if (mOrder != aNewData.mOrder) {
+ // "order" impacts both layout order and stacking order, so we need both a
+ // reflow and a repaint when it changes. (Technically, we only need a
+ // reflow if we're in a multi-line flexbox (which we can't be sure about,
+ // since that's determined by styling on our parent) -- there, "order" can
+ // affect which flex line we end up on, & hence can affect our sizing by
+ // changing the group of flex items we're competing with for space.)
+ return hint | nsChangeHint_RepaintFrame | nsChangeHint_AllReflowHints;
+ }
+
+ if (mBoxSizing != aNewData.mBoxSizing) {
+ // Can affect both widths and heights; just a bad scene.
+ return hint | nsChangeHint_AllReflowHints;
+ }
+
+ if (mAlignItems != aNewData.mAlignItems ||
+ mAlignSelf != aNewData.mAlignSelf ||
+ mJustifyTracks != aNewData.mJustifyTracks ||
+ mAlignTracks != aNewData.mAlignTracks) {
+ return hint | nsChangeHint_AllReflowHints;
+ }
+
+ // Properties that apply to flex items:
+ // XXXdholbert These should probably be more targeted (bug 819536)
+ if (mFlexBasis != aNewData.mFlexBasis || mFlexGrow != aNewData.mFlexGrow ||
+ mFlexShrink != aNewData.mFlexShrink) {
+ return hint | nsChangeHint_AllReflowHints;
+ }
+
+ // Properties that apply to flex containers:
+ // - flex-direction can swap a flex container between vertical & horizontal.
+ // - flex-wrap changes whether a flex container's children are wrapped, which
+ // impacts their sizing/positioning and hence impacts the container's size.
+ if (mFlexDirection != aNewData.mFlexDirection ||
+ mFlexWrap != aNewData.mFlexWrap) {
+ return hint | nsChangeHint_AllReflowHints;
+ }
+
+ // Properties that apply to grid containers:
+ // FIXME: only for grid containers
+ // (ie. 'display: grid' or 'display: inline-grid')
+ if (mGridTemplateColumns != aNewData.mGridTemplateColumns ||
+ mGridTemplateRows != aNewData.mGridTemplateRows ||
+ mGridTemplateAreas != aNewData.mGridTemplateAreas ||
+ mGridAutoColumns != aNewData.mGridAutoColumns ||
+ mGridAutoRows != aNewData.mGridAutoRows ||
+ mGridAutoFlow != aNewData.mGridAutoFlow ||
+ mMasonryAutoFlow != aNewData.mMasonryAutoFlow) {
+ return hint | nsChangeHint_AllReflowHints;
+ }
+
+ // Properties that apply to grid items:
+ // FIXME: only for grid items
+ // (ie. parent frame is 'display: grid' or 'display: inline-grid')
+ if (mGridColumnStart != aNewData.mGridColumnStart ||
+ mGridColumnEnd != aNewData.mGridColumnEnd ||
+ mGridRowStart != aNewData.mGridRowStart ||
+ mGridRowEnd != aNewData.mGridRowEnd ||
+ mColumnGap != aNewData.mColumnGap || mRowGap != aNewData.mRowGap) {
+ return hint | nsChangeHint_AllReflowHints;
+ }
+
+ // Changing 'justify-content/items/self' might affect the positioning,
+ // but it won't affect any sizing.
+ if (mJustifyContent != aNewData.mJustifyContent ||
+ mJustifyItems.computed != aNewData.mJustifyItems.computed ||
+ mJustifySelf != aNewData.mJustifySelf) {
+ hint |= nsChangeHint_NeedReflow;
+ }
+
+ // No need to do anything if specified justify-items changes, as long as the
+ // computed one (tested above) is unchanged.
+ if (mJustifyItems.specified != aNewData.mJustifyItems.specified) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ // 'align-content' doesn't apply to a single-line flexbox but we don't know
+ // if we're a flex container at this point so we can't optimize for that.
+ if (mAlignContent != aNewData.mAlignContent) {
+ hint |= nsChangeHint_NeedReflow;
+ }
+
+ bool widthChanged = mWidth != aNewData.mWidth ||
+ mMinWidth != aNewData.mMinWidth ||
+ mMaxWidth != aNewData.mMaxWidth;
+ bool heightChanged = mHeight != aNewData.mHeight ||
+ mMinHeight != aNewData.mMinHeight ||
+ mMaxHeight != aNewData.mMaxHeight;
+
+ if (widthChanged || heightChanged) {
+ // It doesn't matter whether we're looking at the old or new visibility
+ // struct, since a change between vertical and horizontal writing-mode will
+ // cause a reframe.
+ const bool isVertical = aOldStyle.StyleVisibility()->mWritingMode !=
+ StyleWritingModeProperty::HorizontalTb;
+ if (isVertical ? widthChanged : heightChanged) {
+ hint |= nsChangeHint_ReflowHintsForBSizeChange;
+ }
+ if (isVertical ? heightChanged : widthChanged) {
+ hint |= nsChangeHint_ReflowHintsForISizeChange;
+ }
+ }
+
+ if (mAspectRatio != aNewData.mAspectRatio) {
+ hint |= nsChangeHint_ReflowHintsForISizeChange |
+ nsChangeHint_ReflowHintsForBSizeChange;
+ }
+
+ // If any of the offsets have changed, then return the respective hints
+ // so that we would hopefully be able to avoid reflowing.
+ // Note that it is possible that we'll need to reflow when processing
+ // restyles, but we don't have enough information to make a good decision
+ // right now.
+ // Don't try to handle changes between "auto" and non-auto efficiently;
+ // that's tricky to do and will hardly ever be able to avoid a reflow.
+ if (mOffset != aNewData.mOffset) {
+ if (IsAutonessEqual(mOffset, aNewData.mOffset)) {
+ hint |=
+ nsChangeHint_RecomputePosition | nsChangeHint_UpdateParentOverflow;
+ } else {
+ hint |=
+ nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition;
+ }
+ }
+ return hint;
+}
+
+const StyleContainIntrinsicSize& nsStylePosition::ContainIntrinsicBSize(
+ const WritingMode& aWM) const {
+ return aWM.IsVertical() ? mContainIntrinsicWidth : mContainIntrinsicHeight;
+}
+
+const StyleContainIntrinsicSize& nsStylePosition::ContainIntrinsicISize(
+ const WritingMode& aWM) const {
+ return aWM.IsVertical() ? mContainIntrinsicHeight : mContainIntrinsicWidth;
+}
+
+StyleAlignSelf nsStylePosition::UsedAlignSelf(
+ const ComputedStyle* aParent) const {
+ if (mAlignSelf._0 != StyleAlignFlags::AUTO) {
+ return mAlignSelf;
+ }
+ if (MOZ_LIKELY(aParent)) {
+ auto parentAlignItems = aParent->StylePosition()->mAlignItems;
+ MOZ_ASSERT(!(parentAlignItems._0 & StyleAlignFlags::LEGACY),
+ "align-items can't have 'legacy'");
+ return {parentAlignItems._0};
+ }
+ return {StyleAlignFlags::NORMAL};
+}
+
+StyleJustifySelf nsStylePosition::UsedJustifySelf(
+ const ComputedStyle* aParent) const {
+ if (mJustifySelf._0 != StyleAlignFlags::AUTO) {
+ return mJustifySelf;
+ }
+ if (MOZ_LIKELY(aParent)) {
+ const auto& inheritedJustifyItems =
+ aParent->StylePosition()->mJustifyItems.computed;
+ return {inheritedJustifyItems._0 & ~StyleAlignFlags::LEGACY};
+ }
+ return {StyleAlignFlags::NORMAL};
+}
+
+// --------------------
+// nsStyleTable
+//
+
+nsStyleTable::nsStyleTable()
+ : mLayoutStrategy(StyleTableLayout::Auto), mXSpan(1) {
+ MOZ_COUNT_CTOR(nsStyleTable);
+}
+
+nsStyleTable::nsStyleTable(const nsStyleTable& aSource)
+ : mLayoutStrategy(aSource.mLayoutStrategy), mXSpan(aSource.mXSpan) {
+ MOZ_COUNT_CTOR(nsStyleTable);
+}
+
+nsChangeHint nsStyleTable::CalcDifference(const nsStyleTable& aNewData) const {
+ if (mXSpan != aNewData.mXSpan ||
+ mLayoutStrategy != aNewData.mLayoutStrategy) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ return nsChangeHint(0);
+}
+
+// -----------------------
+// nsStyleTableBorder
+
+nsStyleTableBorder::nsStyleTableBorder()
+ : mBorderSpacingCol(0),
+ mBorderSpacingRow(0),
+ mBorderCollapse(StyleBorderCollapse::Separate),
+ mCaptionSide(StyleCaptionSide::Top),
+ mEmptyCells(StyleEmptyCells::Show) {
+ MOZ_COUNT_CTOR(nsStyleTableBorder);
+}
+
+nsStyleTableBorder::nsStyleTableBorder(const nsStyleTableBorder& aSource)
+ : mBorderSpacingCol(aSource.mBorderSpacingCol),
+ mBorderSpacingRow(aSource.mBorderSpacingRow),
+ mBorderCollapse(aSource.mBorderCollapse),
+ mCaptionSide(aSource.mCaptionSide),
+ mEmptyCells(aSource.mEmptyCells) {
+ MOZ_COUNT_CTOR(nsStyleTableBorder);
+}
+
+nsChangeHint nsStyleTableBorder::CalcDifference(
+ const nsStyleTableBorder& aNewData) const {
+ // Border-collapse changes need a reframe, because we use a different frame
+ // class for table cells in the collapsed border model. This is used to
+ // conserve memory when using the separated border model (collapsed borders
+ // require extra state to be stored).
+ if (mBorderCollapse != aNewData.mBorderCollapse) {
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ if (mCaptionSide == aNewData.mCaptionSide &&
+ mBorderSpacingCol == aNewData.mBorderSpacingCol &&
+ mBorderSpacingRow == aNewData.mBorderSpacingRow) {
+ if (mEmptyCells == aNewData.mEmptyCells) {
+ return nsChangeHint(0);
+ }
+ return NS_STYLE_HINT_VISUAL;
+ }
+ return NS_STYLE_HINT_REFLOW;
+}
+
+template <typename T>
+static bool GradientItemsAreOpaque(
+ Span<const StyleGenericGradientItem<StyleColor, T>> aItems) {
+ for (auto& stop : aItems) {
+ if (stop.IsInterpolationHint()) {
+ continue;
+ }
+
+ auto& color = stop.IsSimpleColorStop() ? stop.AsSimpleColorStop()
+ : stop.AsComplexColorStop().color;
+ if (color.MaybeTransparent()) {
+ // We don't know the foreground color here, so if it's being used
+ // we must assume it might be transparent.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <>
+bool StyleGradient::IsOpaque() const {
+ if (IsLinear()) {
+ return GradientItemsAreOpaque(AsLinear().items.AsSpan());
+ }
+ if (IsRadial()) {
+ return GradientItemsAreOpaque(AsRadial().items.AsSpan());
+ }
+ return GradientItemsAreOpaque(AsConic().items.AsSpan());
+}
+
+template <>
+bool StyleImage::IsOpaque() const {
+ if (IsImageSet()) {
+ return FinalImage().IsOpaque();
+ }
+
+ if (!IsComplete()) {
+ return false;
+ }
+
+ if (IsGradient()) {
+ return AsGradient()->IsOpaque();
+ }
+
+ if (IsElement()) {
+ return false;
+ }
+
+ MOZ_ASSERT(IsImageRequestType(), "unexpected image type");
+ MOZ_ASSERT(GetImageRequest(), "should've returned earlier above");
+
+ nsCOMPtr<imgIContainer> imageContainer;
+ GetImageRequest()->GetImage(getter_AddRefs(imageContainer));
+ MOZ_ASSERT(imageContainer, "IsComplete() said image container is ready");
+
+ return imageContainer->WillDrawOpaqueNow();
+}
+
+template <>
+bool StyleImage::IsComplete() const {
+ switch (tag) {
+ case Tag::None:
+ return false;
+ case Tag::Gradient:
+ case Tag::Element:
+ return true;
+ case Tag::Url: {
+ if (!IsResolved()) {
+ return false;
+ }
+ imgRequestProxy* req = GetImageRequest();
+ if (!req) {
+ return false;
+ }
+ uint32_t status = imgIRequest::STATUS_ERROR;
+ return NS_SUCCEEDED(req->GetImageStatus(&status)) &&
+ (status & imgIRequest::STATUS_SIZE_AVAILABLE) &&
+ (status & imgIRequest::STATUS_FRAME_COMPLETE);
+ }
+ case Tag::ImageSet:
+ return FinalImage().IsComplete();
+ // Bug 546052 cross-fade not yet implemented.
+ case Tag::CrossFade:
+ return true;
+ }
+ MOZ_ASSERT_UNREACHABLE("unexpected image type");
+ return false;
+}
+
+template <>
+bool StyleImage::IsSizeAvailable() const {
+ switch (tag) {
+ case Tag::None:
+ return false;
+ case Tag::Gradient:
+ case Tag::Element:
+ return true;
+ case Tag::Url: {
+ imgRequestProxy* req = GetImageRequest();
+ if (!req) {
+ return false;
+ }
+ uint32_t status = imgIRequest::STATUS_ERROR;
+ return NS_SUCCEEDED(req->GetImageStatus(&status)) &&
+ !(status & imgIRequest::STATUS_ERROR) &&
+ (status & imgIRequest::STATUS_SIZE_AVAILABLE);
+ }
+ case Tag::ImageSet:
+ return FinalImage().IsSizeAvailable();
+ case Tag::CrossFade:
+ // TODO: Bug 546052 cross-fade not yet implemented.
+ return true;
+ }
+ MOZ_ASSERT_UNREACHABLE("unexpected image type");
+ return false;
+}
+
+template <>
+void StyleImage::ResolveImage(Document& aDoc, const StyleImage* aOld) {
+ if (IsResolved()) {
+ return;
+ }
+ const auto* old = aOld ? aOld->GetImageRequestURLValue() : nullptr;
+ const auto* url = GetImageRequestURLValue();
+ // We could avoid this const_cast generating more code but it's not really
+ // worth it.
+ const_cast<StyleComputedImageUrl*>(url)->ResolveImage(aDoc, old);
+}
+
+template <>
+ImageResolution StyleImage::GetResolution(const ComputedStyle& aStyle) const {
+ ImageResolution resolution;
+ if (imgRequestProxy* request = GetImageRequest()) {
+ RefPtr<imgIContainer> image;
+ request->GetImage(getter_AddRefs(image));
+ if (image) {
+ resolution = image->GetResolution();
+ }
+ }
+ if (IsImageSet()) {
+ const auto& set = *AsImageSet();
+ auto items = set.items.AsSpan();
+ if (MOZ_LIKELY(set.selected_index < items.Length())) {
+ float r = items[set.selected_index].resolution._0;
+ resolution.ScaleBy(r);
+ }
+ }
+ if (aStyle.EffectiveZoom() != StyleZoom::ONE) {
+ resolution.ScaleBy(1.0f / aStyle.EffectiveZoom().ToFloat());
+ }
+ return resolution;
+}
+
+// --------------------
+// nsStyleImageLayers
+//
+
+const nsCSSPropertyID nsStyleImageLayers::kBackgroundLayerTable[] = {
+ eCSSProperty_background, // shorthand
+ eCSSProperty_background_color, // color
+ eCSSProperty_background_image, // image
+ eCSSProperty_background_repeat, // repeat
+ eCSSProperty_background_position_x, // positionX
+ eCSSProperty_background_position_y, // positionY
+ eCSSProperty_background_clip, // clip
+ eCSSProperty_background_origin, // origin
+ eCSSProperty_background_size, // size
+ eCSSProperty_background_attachment, // attachment
+ eCSSProperty_UNKNOWN, // maskMode
+ eCSSProperty_UNKNOWN // composite
+};
+
+const nsCSSPropertyID nsStyleImageLayers::kMaskLayerTable[] = {
+ eCSSProperty_mask, // shorthand
+ eCSSProperty_UNKNOWN, // color
+ eCSSProperty_mask_image, // image
+ eCSSProperty_mask_repeat, // repeat
+ eCSSProperty_mask_position_x, // positionX
+ eCSSProperty_mask_position_y, // positionY
+ eCSSProperty_mask_clip, // clip
+ eCSSProperty_mask_origin, // origin
+ eCSSProperty_mask_size, // size
+ eCSSProperty_UNKNOWN, // attachment
+ eCSSProperty_mask_mode, // maskMode
+ eCSSProperty_mask_composite // composite
+};
+
+nsStyleImageLayers::nsStyleImageLayers(nsStyleImageLayers::LayerType aType)
+ : mAttachmentCount(1),
+ mClipCount(1),
+ mOriginCount(1),
+ mRepeatCount(1),
+ mPositionXCount(1),
+ mPositionYCount(1),
+ mImageCount(1),
+ mSizeCount(1),
+ mMaskModeCount(1),
+ mBlendModeCount(1),
+ mCompositeCount(1),
+ mLayers(nsStyleAutoArray<Layer>::WITH_SINGLE_INITIAL_ELEMENT) {
+ // Ensure first layer is initialized as specified layer type
+ mLayers[0].Initialize(aType);
+}
+
+nsStyleImageLayers::nsStyleImageLayers(const nsStyleImageLayers& aSource)
+ : mAttachmentCount(aSource.mAttachmentCount),
+ mClipCount(aSource.mClipCount),
+ mOriginCount(aSource.mOriginCount),
+ mRepeatCount(aSource.mRepeatCount),
+ mPositionXCount(aSource.mPositionXCount),
+ mPositionYCount(aSource.mPositionYCount),
+ mImageCount(aSource.mImageCount),
+ mSizeCount(aSource.mSizeCount),
+ mMaskModeCount(aSource.mMaskModeCount),
+ mBlendModeCount(aSource.mBlendModeCount),
+ mCompositeCount(aSource.mCompositeCount),
+ mLayers(aSource.mLayers.Clone()) {}
+
+static bool AnyLayerIsElementImage(const nsStyleImageLayers& aLayers) {
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, aLayers) {
+ if (aLayers.mLayers[i].mImage.FinalImage().IsElement()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsChangeHint nsStyleImageLayers::CalcDifference(
+ const nsStyleImageLayers& aNewLayers, LayerType aType) const {
+ nsChangeHint hint = nsChangeHint(0);
+
+ // If the number of visible images changes, then it's easy-peasy.
+ if (mImageCount != aNewLayers.mImageCount) {
+ hint |= nsChangeHint_RepaintFrame;
+ if (aType == nsStyleImageLayers::LayerType::Mask ||
+ AnyLayerIsElementImage(*this) || AnyLayerIsElementImage(aNewLayers)) {
+ hint |= nsChangeHint_UpdateEffects;
+ }
+ return hint;
+ }
+
+ const nsStyleImageLayers& moreLayers =
+ mLayers.Length() > aNewLayers.mLayers.Length() ? *this : aNewLayers;
+ const nsStyleImageLayers& lessLayers =
+ mLayers.Length() > aNewLayers.mLayers.Length() ? aNewLayers : *this;
+
+ for (size_t i = 0; i < moreLayers.mLayers.Length(); ++i) {
+ const Layer& moreLayersLayer = moreLayers.mLayers[i];
+ if (i < moreLayers.mImageCount) {
+ // This is a visible image we're diffing, we may need to repaint.
+ const Layer& lessLayersLayer = lessLayers.mLayers[i];
+ nsChangeHint layerDifference =
+ moreLayersLayer.CalcDifference(lessLayersLayer);
+ if (layerDifference &&
+ (moreLayersLayer.mImage.FinalImage().IsElement() ||
+ lessLayersLayer.mImage.FinalImage().IsElement())) {
+ layerDifference |=
+ nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
+ }
+ hint |= layerDifference;
+ continue;
+ }
+ if (hint) {
+ // If they're different by now, we're done.
+ return hint;
+ }
+ if (i >= lessLayers.mLayers.Length()) {
+ // The layer count differs, we know some property has changed, but if we
+ // got here we know it won't affect rendering.
+ return nsChangeHint_NeutralChange;
+ }
+
+ const Layer& lessLayersLayer = lessLayers.mLayers[i];
+ MOZ_ASSERT(moreLayersLayer.mImage.IsNone());
+ MOZ_ASSERT(lessLayersLayer.mImage.IsNone());
+ if (moreLayersLayer.CalcDifference(lessLayersLayer)) {
+ // We don't care about the difference returned, we know it's not visible,
+ // but if something changed, then we need to return the neutral change.
+ return nsChangeHint_NeutralChange;
+ }
+ }
+
+ if (hint) {
+ // If they're different by now, we're done.
+ return hint;
+ }
+
+ // We could have same layers and values, but different count still.
+ if (mAttachmentCount != aNewLayers.mAttachmentCount ||
+ mBlendModeCount != aNewLayers.mBlendModeCount ||
+ mClipCount != aNewLayers.mClipCount ||
+ mCompositeCount != aNewLayers.mCompositeCount ||
+ mMaskModeCount != aNewLayers.mMaskModeCount ||
+ mOriginCount != aNewLayers.mOriginCount ||
+ mRepeatCount != aNewLayers.mRepeatCount ||
+ mPositionXCount != aNewLayers.mPositionXCount ||
+ mPositionYCount != aNewLayers.mPositionYCount ||
+ mSizeCount != aNewLayers.mSizeCount) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ return hint;
+}
+
+nsStyleImageLayers& nsStyleImageLayers::operator=(
+ const nsStyleImageLayers& aOther) {
+ mAttachmentCount = aOther.mAttachmentCount;
+ mClipCount = aOther.mClipCount;
+ mOriginCount = aOther.mOriginCount;
+ mRepeatCount = aOther.mRepeatCount;
+ mPositionXCount = aOther.mPositionXCount;
+ mPositionYCount = aOther.mPositionYCount;
+ mImageCount = aOther.mImageCount;
+ mSizeCount = aOther.mSizeCount;
+ mMaskModeCount = aOther.mMaskModeCount;
+ mBlendModeCount = aOther.mBlendModeCount;
+ mCompositeCount = aOther.mCompositeCount;
+ mLayers = aOther.mLayers.Clone();
+
+ return *this;
+}
+
+bool nsStyleImageLayers::operator==(const nsStyleImageLayers& aOther) const {
+ if (mAttachmentCount != aOther.mAttachmentCount ||
+ mClipCount != aOther.mClipCount || mOriginCount != aOther.mOriginCount ||
+ mRepeatCount != aOther.mRepeatCount ||
+ mPositionXCount != aOther.mPositionXCount ||
+ mPositionYCount != aOther.mPositionYCount ||
+ mImageCount != aOther.mImageCount || mSizeCount != aOther.mSizeCount ||
+ mMaskModeCount != aOther.mMaskModeCount ||
+ mBlendModeCount != aOther.mBlendModeCount) {
+ return false;
+ }
+
+ if (mLayers.Length() != aOther.mLayers.Length()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < mLayers.Length(); i++) {
+ if (mLayers[i].mPosition != aOther.mLayers[i].mPosition ||
+ mLayers[i].mImage != aOther.mLayers[i].mImage ||
+ mLayers[i].mSize != aOther.mLayers[i].mSize ||
+ mLayers[i].mClip != aOther.mLayers[i].mClip ||
+ mLayers[i].mOrigin != aOther.mLayers[i].mOrigin ||
+ mLayers[i].mAttachment != aOther.mLayers[i].mAttachment ||
+ mLayers[i].mBlendMode != aOther.mLayers[i].mBlendMode ||
+ mLayers[i].mComposite != aOther.mLayers[i].mComposite ||
+ mLayers[i].mMaskMode != aOther.mLayers[i].mMaskMode ||
+ mLayers[i].mRepeat != aOther.mLayers[i].mRepeat) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool SizeDependsOnPositioningAreaSize(const StyleBackgroundSize& aSize,
+ const StyleImage& aImage) {
+ MOZ_ASSERT(!aImage.IsNone(), "caller should have handled this");
+ MOZ_ASSERT(!aImage.IsImageSet(), "caller should have handled this");
+
+ // Contain and cover straightforwardly depend on frame size.
+ if (aSize.IsCover() || aSize.IsContain()) {
+ return true;
+ }
+
+ MOZ_ASSERT(aSize.IsExplicitSize());
+ const auto& size = aSize.AsExplicitSize();
+
+ // If either dimension contains a non-zero percentage, rendering for that
+ // dimension straightforwardly depends on frame size.
+ if (size.width.HasPercent() || size.height.HasPercent()) {
+ return true;
+ }
+
+ // If both dimensions are fixed lengths, there's no dependency.
+ if (!size.width.IsAuto() && !size.height.IsAuto()) {
+ return false;
+ }
+
+ // Gradient rendering depends on frame size when auto is involved because
+ // gradients have no intrinsic ratio or dimensions, and therefore the relevant
+ // dimension is "treat[ed] as 100%".
+ if (aImage.IsGradient()) {
+ return true;
+ }
+
+ // XXX Element rendering for auto or fixed length doesn't depend on frame size
+ // according to the spec. However, we don't implement the spec yet, so
+ // for now we bail and say element() plus auto affects ultimate size.
+ if (aImage.IsElement()) {
+ return true;
+ }
+
+ MOZ_ASSERT(aImage.IsImageRequestType(), "Missed some image");
+ if (auto* request = aImage.GetImageRequest()) {
+ nsCOMPtr<imgIContainer> imgContainer;
+ request->GetImage(getter_AddRefs(imgContainer));
+ if (imgContainer) {
+ CSSIntSize imageSize;
+ AspectRatio imageRatio;
+ bool hasWidth, hasHeight;
+ // We could bother getting the right resolution here but it doesn't matter
+ // since we ignore `imageSize`.
+ nsLayoutUtils::ComputeSizeForDrawing(imgContainer, ImageResolution(),
+ imageSize, imageRatio, hasWidth,
+ hasHeight);
+
+ // If the image has a fixed width and height, rendering never depends on
+ // the frame size.
+ if (hasWidth && hasHeight) {
+ return false;
+ }
+
+ // If the image has an intrinsic ratio, rendering will depend on frame
+ // size when background-size is all auto.
+ if (imageRatio) {
+ return size.width.IsAuto() == size.height.IsAuto();
+ }
+
+ // Otherwise, rendering depends on frame size when the image dimensions
+ // and background-size don't complement each other.
+ return !(hasWidth && size.width.IsLengthPercentage()) &&
+ !(hasHeight && size.height.IsLengthPercentage());
+ }
+ }
+
+ // Passed the gauntlet: no dependency.
+ return false;
+}
+
+nsStyleImageLayers::Layer::Layer()
+ : mImage(StyleImage::None()),
+ mSize(StyleBackgroundSize::ExplicitSize(LengthPercentageOrAuto::Auto(),
+ LengthPercentageOrAuto::Auto())),
+
+ mClip(StyleGeometryBox::BorderBox),
+ mAttachment(StyleImageLayerAttachment::Scroll),
+ mBlendMode(StyleBlend::Normal),
+ mComposite(StyleMaskComposite::Add),
+ mMaskMode(StyleMaskMode::MatchSource) {}
+
+nsStyleImageLayers::Layer::~Layer() = default;
+
+void nsStyleImageLayers::Layer::Initialize(
+ nsStyleImageLayers::LayerType aType) {
+ mPosition = Position::FromPercentage(0.);
+
+ if (aType == LayerType::Background) {
+ mOrigin = StyleGeometryBox::PaddingBox;
+ } else {
+ MOZ_ASSERT(aType == LayerType::Mask, "unsupported layer type.");
+ mOrigin = StyleGeometryBox::BorderBox;
+ }
+}
+
+bool nsStyleImageLayers::Layer::
+ RenderingMightDependOnPositioningAreaSizeChange() const {
+ // Do we even have an image?
+ if (mImage.IsNone()) {
+ return false;
+ }
+
+ return mPosition.DependsOnPositioningAreaSize() ||
+ SizeDependsOnPositioningAreaSize(mSize, mImage.FinalImage()) ||
+ mRepeat.DependsOnPositioningAreaSize();
+}
+
+bool nsStyleImageLayers::Layer::operator==(const Layer& aOther) const {
+ return mAttachment == aOther.mAttachment && mClip == aOther.mClip &&
+ mOrigin == aOther.mOrigin && mRepeat == aOther.mRepeat &&
+ mBlendMode == aOther.mBlendMode && mPosition == aOther.mPosition &&
+ mSize == aOther.mSize && mImage == aOther.mImage &&
+ mMaskMode == aOther.mMaskMode && mComposite == aOther.mComposite;
+}
+
+template <class ComputedValueItem>
+static void FillImageLayerList(
+ nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
+ ComputedValueItem nsStyleImageLayers::Layer::*aResultLocation,
+ uint32_t aItemCount, uint32_t aFillCount) {
+ MOZ_ASSERT(aFillCount <= aLayers.Length(), "unexpected array length");
+ for (uint32_t sourceLayer = 0, destLayer = aItemCount; destLayer < aFillCount;
+ ++sourceLayer, ++destLayer) {
+ aLayers[destLayer].*aResultLocation = aLayers[sourceLayer].*aResultLocation;
+ }
+}
+
+// The same as FillImageLayerList, but for values stored in
+// layer.mPosition.*aResultLocation instead of layer.*aResultLocation.
+static void FillImageLayerPositionCoordList(
+ nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
+ LengthPercentage Position::*aResultLocation, uint32_t aItemCount,
+ uint32_t aFillCount) {
+ MOZ_ASSERT(aFillCount <= aLayers.Length(), "unexpected array length");
+ for (uint32_t sourceLayer = 0, destLayer = aItemCount; destLayer < aFillCount;
+ ++sourceLayer, ++destLayer) {
+ aLayers[destLayer].mPosition.*aResultLocation =
+ aLayers[sourceLayer].mPosition.*aResultLocation;
+ }
+}
+
+void nsStyleImageLayers::FillAllLayers(uint32_t aMaxItemCount) {
+ // Delete any extra items. We need to keep layers in which any
+ // property was specified.
+ mLayers.TruncateLengthNonZero(aMaxItemCount);
+
+ uint32_t fillCount = mImageCount;
+ FillImageLayerList(mLayers, &Layer::mImage, mImageCount, fillCount);
+ FillImageLayerList(mLayers, &Layer::mRepeat, mRepeatCount, fillCount);
+ FillImageLayerList(mLayers, &Layer::mAttachment, mAttachmentCount, fillCount);
+ FillImageLayerList(mLayers, &Layer::mClip, mClipCount, fillCount);
+ FillImageLayerList(mLayers, &Layer::mBlendMode, mBlendModeCount, fillCount);
+ FillImageLayerList(mLayers, &Layer::mOrigin, mOriginCount, fillCount);
+ FillImageLayerPositionCoordList(mLayers, &Position::horizontal,
+ mPositionXCount, fillCount);
+ FillImageLayerPositionCoordList(mLayers, &Position::vertical, mPositionYCount,
+ fillCount);
+ FillImageLayerList(mLayers, &Layer::mSize, mSizeCount, fillCount);
+ FillImageLayerList(mLayers, &Layer::mMaskMode, mMaskModeCount, fillCount);
+ FillImageLayerList(mLayers, &Layer::mComposite, mCompositeCount, fillCount);
+}
+
+static bool UrlValuesEqual(const StyleImage& aImage,
+ const StyleImage& aOtherImage) {
+ const auto* url = aImage.GetImageRequestURLValue();
+ const auto* other = aOtherImage.GetImageRequestURLValue();
+ return url == other || (url && other && *url == *other);
+}
+
+nsChangeHint nsStyleImageLayers::Layer::CalcDifference(
+ const nsStyleImageLayers::Layer& aNewLayer) const {
+ nsChangeHint hint = nsChangeHint(0);
+ if (!UrlValuesEqual(mImage, aNewLayer.mImage)) {
+ hint |= nsChangeHint_RepaintFrame | nsChangeHint_UpdateEffects;
+ } else if (mAttachment != aNewLayer.mAttachment || mClip != aNewLayer.mClip ||
+ mOrigin != aNewLayer.mOrigin || mRepeat != aNewLayer.mRepeat ||
+ mBlendMode != aNewLayer.mBlendMode || mSize != aNewLayer.mSize ||
+ mImage != aNewLayer.mImage || mMaskMode != aNewLayer.mMaskMode ||
+ mComposite != aNewLayer.mComposite) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ if (mPosition != aNewLayer.mPosition) {
+ hint |= nsChangeHint_UpdateBackgroundPosition;
+ }
+
+ return hint;
+}
+
+// --------------------
+// nsStyleBackground
+//
+
+nsStyleBackground::nsStyleBackground()
+ : mImage(nsStyleImageLayers::LayerType::Background),
+ mBackgroundColor(StyleColor::Transparent()) {
+ MOZ_COUNT_CTOR(nsStyleBackground);
+}
+
+nsStyleBackground::nsStyleBackground(const nsStyleBackground& aSource)
+ : mImage(aSource.mImage), mBackgroundColor(aSource.mBackgroundColor) {
+ MOZ_COUNT_CTOR(nsStyleBackground);
+}
+
+void nsStyleBackground::TriggerImageLoads(Document& aDocument,
+ const nsStyleBackground* aOldStyle) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mImage.ResolveImages(aDocument, aOldStyle ? &aOldStyle->mImage : nullptr);
+}
+
+nsChangeHint nsStyleBackground::CalcDifference(
+ const nsStyleBackground& aNewData) const {
+ nsChangeHint hint = nsChangeHint(0);
+ if (mBackgroundColor != aNewData.mBackgroundColor) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ hint |= mImage.CalcDifference(aNewData.mImage,
+ nsStyleImageLayers::LayerType::Background);
+
+ return hint;
+}
+
+bool nsStyleBackground::HasFixedBackground(nsIFrame* aFrame) const {
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, mImage) {
+ const nsStyleImageLayers::Layer& layer = mImage.mLayers[i];
+ if (layer.mAttachment == StyleImageLayerAttachment::Fixed &&
+ !layer.mImage.IsNone() && !nsLayoutUtils::IsTransformed(aFrame)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nscolor nsStyleBackground::BackgroundColor(const nsIFrame* aFrame) const {
+ return mBackgroundColor.CalcColor(aFrame);
+}
+
+nscolor nsStyleBackground::BackgroundColor(const ComputedStyle* aStyle) const {
+ return mBackgroundColor.CalcColor(*aStyle);
+}
+
+bool nsStyleBackground::IsTransparent(const nsIFrame* aFrame) const {
+ return IsTransparent(aFrame->Style());
+}
+
+bool nsStyleBackground::IsTransparent(const ComputedStyle* aStyle) const {
+ return BottomLayer().mImage.IsNone() && mImage.mImageCount == 1 &&
+ NS_GET_A(BackgroundColor(aStyle)) == 0;
+}
+
+StyleTransition::StyleTransition(const StyleTransition& aCopy) = default;
+
+bool StyleTransition::operator==(const StyleTransition& aOther) const {
+ return mTimingFunction == aOther.mTimingFunction &&
+ mDuration == aOther.mDuration && mDelay == aOther.mDelay &&
+ mProperty == aOther.mProperty;
+}
+
+StyleAnimation::StyleAnimation(const StyleAnimation& aCopy) = default;
+
+bool StyleAnimation::operator==(const StyleAnimation& aOther) const {
+ return mTimingFunction == aOther.mTimingFunction &&
+ mDuration == aOther.mDuration && mDelay == aOther.mDelay &&
+ mName == aOther.mName && mDirection == aOther.mDirection &&
+ mFillMode == aOther.mFillMode && mPlayState == aOther.mPlayState &&
+ mIterationCount == aOther.mIterationCount &&
+ mComposition == aOther.mComposition && mTimeline == aOther.mTimeline;
+}
+
+// --------------------
+// nsStyleDisplay
+//
+nsStyleDisplay::nsStyleDisplay()
+ : mDisplay(StyleDisplay::Inline),
+ mOriginalDisplay(StyleDisplay::Inline),
+ mContentVisibility(StyleContentVisibility::Visible),
+ mContainerType(StyleContainerType::Normal),
+ mAppearance(StyleAppearance::None),
+ mContain(StyleContain::NONE),
+ mEffectiveContainment(StyleContain::NONE),
+ mDefaultAppearance(StyleAppearance::None),
+ mPosition(StylePositionProperty::Static),
+ mFloat(StyleFloat::None),
+ mClear(StyleClear::None),
+ mBreakInside(StyleBreakWithin::Auto),
+ mBreakBefore(StyleBreakBetween::Auto),
+ mBreakAfter(StyleBreakBetween::Auto),
+ mOverflowX(StyleOverflow::Visible),
+ mOverflowY(StyleOverflow::Visible),
+ mOverflowClipBoxBlock(StyleOverflowClipBox::PaddingBox),
+ mOverflowClipBoxInline(StyleOverflowClipBox::PaddingBox),
+ mScrollbarGutter(StyleScrollbarGutter::AUTO),
+ mResize(StyleResize::None),
+ mOrient(StyleOrient::Inline),
+ mIsolation(StyleIsolation::Auto),
+ mTopLayer(StyleTopLayer::None),
+ mTouchAction(StyleTouchAction::AUTO),
+ mScrollBehavior(StyleScrollBehavior::Auto),
+ mOverscrollBehaviorX(StyleOverscrollBehavior::Auto),
+ mOverscrollBehaviorY(StyleOverscrollBehavior::Auto),
+ mOverflowAnchor(StyleOverflowAnchor::Auto),
+ mScrollSnapAlign{StyleScrollSnapAlignKeyword::None,
+ StyleScrollSnapAlignKeyword::None},
+ mScrollSnapStop{StyleScrollSnapStop::Normal},
+ mScrollSnapType{StyleScrollSnapAxis::Both,
+ StyleScrollSnapStrictness::None},
+ mBackfaceVisibility(StyleBackfaceVisibility::Visible),
+ mTransformStyle(StyleTransformStyle::Flat),
+ mTransformBox(StyleTransformBox::ViewBox),
+ mRotate(StyleRotate::None()),
+ mTranslate(StyleTranslate::None()),
+ mScale(StyleScale::None()),
+ mWillChange{{}, {0}},
+ mOffsetPath(StyleOffsetPath::None()),
+ mOffsetDistance(LengthPercentage::Zero()),
+ mOffsetRotate{true, StyleAngle{0.0}},
+ mOffsetAnchor(StylePositionOrAuto::Auto()),
+ mOffsetPosition(StyleOffsetPosition::Normal()),
+ mTransformOrigin{LengthPercentage::FromPercentage(0.5),
+ LengthPercentage::FromPercentage(0.5),
+ {0.}},
+ mChildPerspective(StylePerspective::None()),
+ mPerspectiveOrigin(Position::FromPercentage(0.5f)),
+ mVerticalAlign(
+ StyleVerticalAlign::Keyword(StyleVerticalAlignKeyword::Baseline)),
+ mBaselineSource(StyleBaselineSource::Auto),
+ mWebkitLineClamp(0),
+ mShapeMargin(LengthPercentage::Zero()),
+ mShapeOutside(StyleShapeOutside::None()) {
+ MOZ_COUNT_CTOR(nsStyleDisplay);
+}
+
+nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource)
+ : mDisplay(aSource.mDisplay),
+ mOriginalDisplay(aSource.mOriginalDisplay),
+ mContentVisibility(aSource.mContentVisibility),
+ mContainerType(aSource.mContainerType),
+ mAppearance(aSource.mAppearance),
+ mContain(aSource.mContain),
+ mEffectiveContainment(aSource.mEffectiveContainment),
+ mDefaultAppearance(aSource.mDefaultAppearance),
+ mPosition(aSource.mPosition),
+ mFloat(aSource.mFloat),
+ mClear(aSource.mClear),
+ mBreakInside(aSource.mBreakInside),
+ mBreakBefore(aSource.mBreakBefore),
+ mBreakAfter(aSource.mBreakAfter),
+ mOverflowX(aSource.mOverflowX),
+ mOverflowY(aSource.mOverflowY),
+ mOverflowClipBoxBlock(aSource.mOverflowClipBoxBlock),
+ mOverflowClipBoxInline(aSource.mOverflowClipBoxInline),
+ mScrollbarGutter(aSource.mScrollbarGutter),
+ mResize(aSource.mResize),
+ mOrient(aSource.mOrient),
+ mIsolation(aSource.mIsolation),
+ mTopLayer(aSource.mTopLayer),
+ mTouchAction(aSource.mTouchAction),
+ mScrollBehavior(aSource.mScrollBehavior),
+ mOverscrollBehaviorX(aSource.mOverscrollBehaviorX),
+ mOverscrollBehaviorY(aSource.mOverscrollBehaviorY),
+ mOverflowAnchor(aSource.mOverflowAnchor),
+ mScrollSnapAlign(aSource.mScrollSnapAlign),
+ mScrollSnapStop(aSource.mScrollSnapStop),
+ mScrollSnapType(aSource.mScrollSnapType),
+ mBackfaceVisibility(aSource.mBackfaceVisibility),
+ mTransformStyle(aSource.mTransformStyle),
+ mTransformBox(aSource.mTransformBox),
+ mTransform(aSource.mTransform),
+ mRotate(aSource.mRotate),
+ mTranslate(aSource.mTranslate),
+ mScale(aSource.mScale),
+ mContainerName(aSource.mContainerName),
+ mWillChange(aSource.mWillChange),
+ mOffsetPath(aSource.mOffsetPath),
+ mOffsetDistance(aSource.mOffsetDistance),
+ mOffsetRotate(aSource.mOffsetRotate),
+ mOffsetAnchor(aSource.mOffsetAnchor),
+ mOffsetPosition(aSource.mOffsetPosition),
+ mTransformOrigin(aSource.mTransformOrigin),
+ mChildPerspective(aSource.mChildPerspective),
+ mPerspectiveOrigin(aSource.mPerspectiveOrigin),
+ mVerticalAlign(aSource.mVerticalAlign),
+ mBaselineSource(aSource.mBaselineSource),
+ mWebkitLineClamp(aSource.mWebkitLineClamp),
+ mShapeImageThreshold(aSource.mShapeImageThreshold),
+ mShapeMargin(aSource.mShapeMargin),
+ mShapeOutside(aSource.mShapeOutside) {
+ MOZ_COUNT_CTOR(nsStyleDisplay);
+}
+
+void nsStyleDisplay::TriggerImageLoads(Document& aDocument,
+ const nsStyleDisplay* aOldStyle) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mShapeOutside.IsImage()) {
+ const auto* old = aOldStyle && aOldStyle->mShapeOutside.IsImage()
+ ? &aOldStyle->mShapeOutside.AsImage()
+ : nullptr;
+ // Const-cast is ugly but legit, we could avoid it by generating mut-casts
+ // with cbindgen.
+ const_cast<StyleImage&>(mShapeOutside.AsImage())
+ .ResolveImage(aDocument, old);
+ }
+}
+
+template <typename TransformLike>
+static inline nsChangeHint CompareTransformValues(
+ const TransformLike& aOldTransform, const TransformLike& aNewTransform) {
+ nsChangeHint result = nsChangeHint(0);
+
+ // Note: If we add a new change hint for transform changes here, we have to
+ // modify KeyframeEffect::CalculateCumulativeChangeHint too!
+ if (aOldTransform != aNewTransform) {
+ result |= nsChangeHint_UpdateTransformLayer;
+ if (!aOldTransform.IsNone() && !aNewTransform.IsNone()) {
+ result |= nsChangeHint_UpdatePostTransformOverflow;
+ } else {
+ result |= nsChangeHint_UpdateOverflow;
+ }
+ }
+
+ return result;
+}
+
+static inline nsChangeHint CompareMotionValues(
+ const nsStyleDisplay& aDisplay, const nsStyleDisplay& aNewDisplay) {
+ if (aDisplay.mOffsetPath == aNewDisplay.mOffsetPath) {
+ if (aDisplay.mOffsetDistance == aNewDisplay.mOffsetDistance &&
+ aDisplay.mOffsetRotate == aNewDisplay.mOffsetRotate &&
+ aDisplay.mOffsetAnchor == aNewDisplay.mOffsetAnchor &&
+ aDisplay.mOffsetPosition == aNewDisplay.mOffsetPosition) {
+ return nsChangeHint(0);
+ }
+
+ // No motion path transform is applied.
+ if (aDisplay.mOffsetPath.IsNone()) {
+ return nsChangeHint_NeutralChange;
+ }
+ }
+
+ // TODO: Bug 1482737: This probably doesn't need to UpdateOverflow
+ // (or UpdateTransformLayer) if there's already a transform.
+ // Set the same hints as what we use for transform because motion path is
+ // a kind of transform and will be combined with other transforms.
+ nsChangeHint result = nsChangeHint_UpdateTransformLayer;
+ if (!aDisplay.mOffsetPath.IsNone() && !aNewDisplay.mOffsetPath.IsNone()) {
+ result |= nsChangeHint_UpdatePostTransformOverflow;
+ } else {
+ result |= nsChangeHint_UpdateOverflow;
+ }
+ return result;
+}
+
+static bool ScrollbarGenerationChanged(const nsStyleDisplay& aOld,
+ const nsStyleDisplay& aNew) {
+ auto changed = [](StyleOverflow aOld, StyleOverflow aNew) {
+ return aOld != aNew &&
+ (aOld == StyleOverflow::Hidden || aNew == StyleOverflow::Hidden);
+ };
+ return changed(aOld.mOverflowX, aNew.mOverflowX) ||
+ changed(aOld.mOverflowY, aNew.mOverflowY);
+}
+
+static bool AppearanceValueAffectsFrames(StyleAppearance aAppearance,
+ StyleAppearance aDefaultAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Textfield:
+ // This is for <input type=number/search> where we allow authors to
+ // specify a |-moz-appearance:textfield| to get a control without buttons.
+ // We need to reframe since this affects the spinbox creation in
+ // nsNumber/SearchControlFrame::CreateAnonymousContent.
+ return aDefaultAppearance == StyleAppearance::NumberInput ||
+ aDefaultAppearance == StyleAppearance::Searchfield;
+ case StyleAppearance::Menulist:
+ // This affects the menulist button creation.
+ return aDefaultAppearance == StyleAppearance::Menulist;
+ default:
+ return false;
+ }
+}
+
+nsChangeHint nsStyleDisplay::CalcDifference(
+ const nsStyleDisplay& aNewData, const ComputedStyle& aOldStyle) const {
+ if (mDisplay != aNewData.mDisplay ||
+ (mFloat == StyleFloat::None) != (aNewData.mFloat == StyleFloat::None) ||
+ mTopLayer != aNewData.mTopLayer || mResize != aNewData.mResize) {
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ auto oldAppearance = EffectiveAppearance();
+ auto newAppearance = aNewData.EffectiveAppearance();
+ if (oldAppearance != newAppearance) {
+ // Changes to the relevant default appearance changes in
+ // AppearanceValueRequiresFrameReconstruction require reconstruction on
+ // their own, so we can just pick either the new or the old.
+ if (AppearanceValueAffectsFrames(oldAppearance, mDefaultAppearance) ||
+ AppearanceValueAffectsFrames(newAppearance, mDefaultAppearance)) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ }
+
+ auto hint = nsChangeHint(0);
+ const auto containmentDiff =
+ mEffectiveContainment ^ aNewData.mEffectiveContainment;
+ if (containmentDiff) {
+ if (containmentDiff & StyleContain::STYLE) {
+ // Style containment affects counters so we need to re-frame.
+ return nsChangeHint_ReconstructFrame;
+ }
+ if (containmentDiff & (StyleContain::PAINT | StyleContain::LAYOUT)) {
+ // Paint and layout containment boxes are absolutely/fixed positioning
+ // containers.
+ hint |= nsChangeHint_UpdateContainingBlock;
+ }
+ // The other container types only need a reflow.
+ hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
+ }
+ if (mPosition != aNewData.mPosition) {
+ if (IsAbsolutelyPositionedStyle() ||
+ aNewData.IsAbsolutelyPositionedStyle()) {
+ // This changes our parent relationship on the frame tree and / or needs
+ // to create a placeholder, so gotta reframe. There are some cases (when
+ // switching from fixed to absolute or viceversa, if our containing block
+ // happens to remain the same, i.e., if it has a transform or such) where
+ // this wouldn't really be needed (though we'd still need to move the
+ // frame from one child list to another). In any case we don't have a hand
+ // to that information from here, and it doesn't seem like a case worth
+ // optimizing for.
+ return nsChangeHint_ReconstructFrame;
+ }
+ // We start or stop being a containing block for abspos descendants. This
+ // also causes painting to change, as we'd become a pseudo-stacking context.
+ if (IsRelativelyOrStickyPositionedStyle() !=
+ aNewData.IsRelativelyOrStickyPositionedStyle()) {
+ hint |= nsChangeHint_UpdateContainingBlock | nsChangeHint_RepaintFrame;
+ }
+ if (IsPositionForcingStackingContext() !=
+ aNewData.IsPositionForcingStackingContext()) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ // On top of that: if the above ends up not reframing, we need a reflow to
+ // compute our relative, static or sticky position.
+ hint |= nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition;
+ }
+
+ if (mScrollSnapAlign != aNewData.mScrollSnapAlign ||
+ mScrollSnapType != aNewData.mScrollSnapType ||
+ mScrollSnapStop != aNewData.mScrollSnapStop) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ if (mScrollBehavior != aNewData.mScrollBehavior) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ if (mOverflowX != aNewData.mOverflowX || mOverflowY != aNewData.mOverflowY) {
+ const bool isScrollable = IsScrollableOverflow();
+ if (isScrollable != aNewData.IsScrollableOverflow()) {
+ // We may need to construct or destroy a scroll frame as a result of this
+ // change. If we don't, we still need to update our overflow in some cases
+ // (like svg:foreignObject), which ignore the scrollable-ness of our
+ // overflow.
+ hint |= nsChangeHint_ScrollbarChange | nsChangeHint_UpdateOverflow |
+ nsChangeHint_RepaintFrame;
+ } else if (isScrollable) {
+ if (ScrollbarGenerationChanged(*this, aNewData)) {
+ // We might need to reframe in the case of hidden -> non-hidden case
+ // though, since nsHTMLScrollFrame::CreateAnonymousContent avoids
+ // creating scrollbars altogether for overflow: hidden. That seems it
+ // could create some interesting perf cliffs...
+ hint |= nsChangeHint_ScrollbarChange;
+ } else {
+ // Otherwise, for changes where both overflow values are scrollable,
+ // means that scrollbars may appear or disappear. We need to reflow,
+ // since reflow is what determines which scrollbars if any are visible.
+ hint |= nsChangeHint_ReflowHintsForScrollbarChange;
+ }
+ } else {
+ // Otherwise this is a change between 'visible' and 'clip'.
+ // Here only whether we have a 'clip' changes, so just repaint and
+ // update our overflow areas in that case.
+ hint |= nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
+ }
+ }
+
+ if (mScrollbarGutter != aNewData.mScrollbarGutter) {
+ if (IsScrollableOverflow() || aOldStyle.IsRootElementStyle()) {
+ // Changing scrollbar-gutter affects available inline-size of a inner
+ // scrolled frame, so we need a reflow for scrollbar change. Note that the
+ // root is always scrollable in HTML, even if its style doesn't say so.
+ hint |= nsChangeHint_ReflowHintsForScrollbarChange;
+ } else {
+ // scrollbar-gutter only applies to scroll containers.
+ hint |= nsChangeHint_NeutralChange;
+ }
+ }
+
+ if (mFloat != aNewData.mFloat) {
+ // Changing which side we're floating on (float:none was handled above).
+ hint |= nsChangeHint_ReflowHintsForFloatAreaChange;
+ }
+
+ if (mShapeOutside != aNewData.mShapeOutside ||
+ mShapeMargin != aNewData.mShapeMargin ||
+ mShapeImageThreshold != aNewData.mShapeImageThreshold) {
+ if (aNewData.mFloat != StyleFloat::None) {
+ // If we are floating, and our shape-outside, shape-margin, or
+ // shape-image-threshold are changed, our descendants are not impacted,
+ // but our ancestor and siblings are.
+ hint |= nsChangeHint_ReflowHintsForFloatAreaChange;
+ } else {
+ // shape-outside or shape-margin or shape-image-threshold changed,
+ // but we don't need to reflow because we're not floating.
+ hint |= nsChangeHint_NeutralChange;
+ }
+ }
+
+ if (mWebkitLineClamp != aNewData.mWebkitLineClamp ||
+ mVerticalAlign != aNewData.mVerticalAlign ||
+ mBaselineSource != aNewData.mBaselineSource) {
+ // XXX Can this just be AllReflowHints + RepaintFrame, and be included in
+ // the block below?
+ hint |= NS_STYLE_HINT_REFLOW;
+ }
+
+ // XXX the following is conservative, for now: changing float breaking
+ // shouldn't necessarily require a repaint, reflow should suffice.
+ //
+ // FIXME(emilio): We definitely change the frame tree in nsCSSFrameConstructor
+ // based on break-before / break-after... Shouldn't that reframe?
+ if (mClear != aNewData.mClear || mBreakInside != aNewData.mBreakInside ||
+ mBreakBefore != aNewData.mBreakBefore ||
+ mBreakAfter != aNewData.mBreakAfter ||
+ mAppearance != aNewData.mAppearance ||
+ mDefaultAppearance != aNewData.mDefaultAppearance ||
+ mOrient != aNewData.mOrient ||
+ mOverflowClipBoxBlock != aNewData.mOverflowClipBoxBlock ||
+ mOverflowClipBoxInline != aNewData.mOverflowClipBoxInline) {
+ hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
+ }
+
+ if (mIsolation != aNewData.mIsolation) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ /* If we've added or removed the transform property, we need to reconstruct
+ * the frame to add or remove the view object, and also to handle abs-pos and
+ * fixed-pos containers.
+ */
+ if (HasTransformStyle() != aNewData.HasTransformStyle()) {
+ hint |= nsChangeHint_ComprehensiveAddOrRemoveTransform;
+ } else {
+ /* Otherwise, if we've kept the property lying around and we already had a
+ * transform, we need to see whether or not we've changed the transform.
+ * If so, we need to recompute its overflow rect (which probably changed
+ * if the transform changed) and to redraw within the bounds of that new
+ * overflow rect.
+ *
+ * If the property isn't present in either style struct, we still do the
+ * comparisons but turn all the resulting change hints into
+ * nsChangeHint_NeutralChange.
+ */
+ nsChangeHint transformHint = CalcTransformPropertyDifference(aNewData);
+
+ if (transformHint) {
+ if (HasTransformStyle()) {
+ hint |= transformHint;
+ } else {
+ hint |= nsChangeHint_NeutralChange;
+ }
+ }
+ }
+
+ if (HasPerspectiveStyle() != aNewData.HasPerspectiveStyle()) {
+ // A change from/to being a containing block for position:fixed.
+ hint |= nsChangeHint_UpdateContainingBlock | nsChangeHint_UpdateOverflow |
+ nsChangeHint_RepaintFrame;
+ } else if (mChildPerspective != aNewData.mChildPerspective) {
+ hint |= nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
+ }
+
+ // Note that the HasTransformStyle() != aNewData.HasTransformStyle()
+ // test above handles relevant changes in the StyleWillChangeBit_TRANSFORM
+ // bit, which in turn handles frame reconstruction for changes in the
+ // containing block of fixed-positioned elements.
+ auto willChangeBitsChanged = mWillChange.bits ^ aNewData.mWillChange.bits;
+
+ if (willChangeBitsChanged &
+ (StyleWillChangeBits::STACKING_CONTEXT_UNCONDITIONAL |
+ StyleWillChangeBits::SCROLL | StyleWillChangeBits::OPACITY |
+ StyleWillChangeBits::PERSPECTIVE | StyleWillChangeBits::TRANSFORM |
+ StyleWillChangeBits::Z_INDEX)) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ if (willChangeBitsChanged &
+ (StyleWillChangeBits::FIXPOS_CB_NON_SVG | StyleWillChangeBits::TRANSFORM |
+ StyleWillChangeBits::PERSPECTIVE | StyleWillChangeBits::POSITION |
+ StyleWillChangeBits::CONTAIN)) {
+ hint |= nsChangeHint_UpdateContainingBlock;
+ }
+
+ // If touch-action is changed, we need to regenerate the event regions on
+ // the layers and send it over to the compositor for APZ to handle.
+ if (mTouchAction != aNewData.mTouchAction) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ // If overscroll-behavior has changed, the changes are picked up
+ // during a repaint.
+ if (mOverscrollBehaviorX != aNewData.mOverscrollBehaviorX ||
+ mOverscrollBehaviorY != aNewData.mOverscrollBehaviorY) {
+ hint |= nsChangeHint_SchedulePaint;
+ }
+
+ if (mOriginalDisplay != aNewData.mOriginalDisplay) {
+ // Our hypothetical box position may have changed.
+ //
+ // Note that it doesn't matter if we look at the old or the new struct,
+ // since a change on whether we need a hypothetical position would trigger
+ // reflow anyway.
+ if (IsAbsolutelyPositionedStyle() &&
+ aOldStyle.StylePosition()->NeedsHypotheticalPositionIfAbsPos()) {
+ hint |=
+ nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition;
+ } else {
+ hint |= nsChangeHint_NeutralChange;
+ }
+ }
+
+ // Note: Our current behavior for handling changes to the
+ // transition-duration, transition-delay, and transition-timing-function
+ // properties is to do nothing. In other words, the transition
+ // property that matters is what it is when the transition begins, and
+ // we don't stop a transition later because the transition property
+ // changed.
+ // We do handle changes to transition-property, but we don't need to
+ // bother with anything here, since the transition manager is notified
+ // of any ComputedStyle change anyway.
+
+ // Note: Likewise, for animation-*, the animation manager gets
+ // notified about every new ComputedStyle constructed, and it uses
+ // that opportunity to handle dynamic changes appropriately.
+
+ // But we still need to return nsChangeHint_NeutralChange for these
+ // properties, since some data did change in the style struct.
+
+ // TODO(emilio): Figure out change hints for container-name, maybe it needs to
+ // be handled by the style system as a special-case (since it changes
+ // container-query selection on descendants).
+ // container-type / contain / content-visibility are handled by the
+ // mEffectiveContainment check.
+ if (!hint && (mWillChange != aNewData.mWillChange ||
+ mOverflowAnchor != aNewData.mOverflowAnchor ||
+ mContentVisibility != aNewData.mContentVisibility ||
+ mContainerType != aNewData.mContainerType ||
+ mContain != aNewData.mContain ||
+ mContainerName != aNewData.mContainerName)) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ return hint;
+}
+
+nsChangeHint nsStyleDisplay::CalcTransformPropertyDifference(
+ const nsStyleDisplay& aNewData) const {
+ nsChangeHint transformHint = nsChangeHint(0);
+
+ transformHint |= CompareTransformValues(mTransform, aNewData.mTransform);
+ transformHint |= CompareTransformValues(mRotate, aNewData.mRotate);
+ transformHint |= CompareTransformValues(mTranslate, aNewData.mTranslate);
+ transformHint |= CompareTransformValues(mScale, aNewData.mScale);
+ transformHint |= CompareMotionValues(*this, aNewData);
+
+ if (mTransformOrigin != aNewData.mTransformOrigin) {
+ transformHint |= nsChangeHint_UpdateTransformLayer |
+ nsChangeHint_UpdatePostTransformOverflow;
+ }
+
+ if (mPerspectiveOrigin != aNewData.mPerspectiveOrigin ||
+ mTransformStyle != aNewData.mTransformStyle ||
+ mTransformBox != aNewData.mTransformBox) {
+ transformHint |= nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
+ }
+
+ if (mBackfaceVisibility != aNewData.mBackfaceVisibility) {
+ transformHint |= nsChangeHint_RepaintFrame;
+ }
+
+ return transformHint;
+}
+
+// --------------------
+// nsStyleVisibility
+//
+
+nsStyleVisibility::nsStyleVisibility(const Document& aDocument)
+ : mDirection(aDocument.GetBidiOptions() == IBMBIDI_TEXTDIRECTION_RTL
+ ? StyleDirection::Rtl
+ : StyleDirection::Ltr),
+ mVisible(StyleVisibility::Visible),
+ mImageRendering(StyleImageRendering::Auto),
+ mWritingMode(StyleWritingModeProperty::HorizontalTb),
+ mTextOrientation(StyleTextOrientation::Mixed),
+ mMozBoxCollapse(StyleMozBoxCollapse::Flex),
+ mPrintColorAdjust(StylePrintColorAdjust::Economy),
+ mImageOrientation(StyleImageOrientation::FromImage) {
+ MOZ_COUNT_CTOR(nsStyleVisibility);
+}
+
+nsStyleVisibility::nsStyleVisibility(const nsStyleVisibility& aSource)
+ : mDirection(aSource.mDirection),
+ mVisible(aSource.mVisible),
+ mImageRendering(aSource.mImageRendering),
+ mWritingMode(aSource.mWritingMode),
+ mTextOrientation(aSource.mTextOrientation),
+ mMozBoxCollapse(aSource.mMozBoxCollapse),
+ mPrintColorAdjust(aSource.mPrintColorAdjust),
+ mImageOrientation(aSource.mImageOrientation) {
+ MOZ_COUNT_CTOR(nsStyleVisibility);
+}
+
+nsChangeHint nsStyleVisibility::CalcDifference(
+ const nsStyleVisibility& aNewData) const {
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (mDirection != aNewData.mDirection ||
+ mWritingMode != aNewData.mWritingMode) {
+ // It's important that a change in mWritingMode results in frame
+ // reconstruction, because it may affect intrinsic size (see
+ // nsSubDocumentFrame::GetIntrinsicISize/BSize).
+ // Also, the used writing-mode value is now a field on nsIFrame and some
+ // classes (e.g. table rows/cells) copy their value from an ancestor.
+ return nsChangeHint_ReconstructFrame;
+ }
+ if (mImageOrientation != aNewData.mImageOrientation) {
+ hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
+ }
+ if (mVisible != aNewData.mVisible) {
+ if (mVisible == StyleVisibility::Visible ||
+ aNewData.mVisible == StyleVisibility::Visible) {
+ hint |= nsChangeHint_VisibilityChange;
+ }
+ if (mVisible == StyleVisibility::Collapse ||
+ aNewData.mVisible == StyleVisibility::Collapse) {
+ hint |= NS_STYLE_HINT_REFLOW;
+ } else {
+ hint |= NS_STYLE_HINT_VISUAL;
+ }
+ }
+ if (mTextOrientation != aNewData.mTextOrientation ||
+ mMozBoxCollapse != aNewData.mMozBoxCollapse) {
+ hint |= NS_STYLE_HINT_REFLOW;
+ }
+ if (mImageRendering != aNewData.mImageRendering) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ if (mPrintColorAdjust != aNewData.mPrintColorAdjust) {
+ // color-adjust only affects media where dynamic changes can't happen.
+ hint |= nsChangeHint_NeutralChange;
+ }
+ return hint;
+}
+
+StyleImageOrientation nsStyleVisibility::UsedImageOrientation(
+ imgIRequest* aRequest, StyleImageOrientation aOrientation) {
+ if (aOrientation == StyleImageOrientation::FromImage || !aRequest) {
+ return aOrientation;
+ }
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ aRequest->GetTriggeringPrincipal();
+
+ // If the request was for a blob, the request may not have a triggering
+ // principal and we should use the input orientation.
+ if (!triggeringPrincipal) {
+ return aOrientation;
+ }
+
+ nsCOMPtr<nsIURI> uri = aRequest->GetURI();
+ // If the image request is a data uri, then treat the request as a
+ // same origin request.
+ bool isSameOrigin =
+ uri->SchemeIs("data") || triggeringPrincipal->IsSameOrigin(uri);
+
+ // If the image request is a cross-origin request that does not use CORS,
+ // do not enforce the image orientation found in the style. Use the image
+ // orientation found in the exif data.
+ if (!isSameOrigin && !nsLayoutUtils::ImageRequestUsesCORS(aRequest)) {
+ return StyleImageOrientation::FromImage;
+ }
+
+ return aOrientation;
+}
+
+//-----------------------
+// nsStyleContent
+//
+
+nsStyleContent::nsStyleContent() : mContent(StyleContent::Normal()) {
+ MOZ_COUNT_CTOR(nsStyleContent);
+}
+
+nsStyleContent::nsStyleContent(const nsStyleContent& aSource)
+ : mContent(aSource.mContent),
+ mCounterIncrement(aSource.mCounterIncrement),
+ mCounterReset(aSource.mCounterReset),
+ mCounterSet(aSource.mCounterSet) {
+ MOZ_COUNT_CTOR(nsStyleContent);
+}
+
+nsChangeHint nsStyleContent::CalcDifference(
+ const nsStyleContent& aNewData) const {
+ // Unfortunately we need to reframe even if the content lengths are the same;
+ // a simple reflow will not pick up different text or different image URLs,
+ // since we set all that up in the CSSFrameConstructor
+ if (mContent != aNewData.mContent ||
+ mCounterIncrement != aNewData.mCounterIncrement ||
+ mCounterReset != aNewData.mCounterReset ||
+ mCounterSet != aNewData.mCounterSet) {
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ return nsChangeHint(0);
+}
+
+void nsStyleContent::TriggerImageLoads(Document& aDoc,
+ const nsStyleContent* aOld) {
+ if (!mContent.IsItems()) {
+ return;
+ }
+
+ Span<const StyleContentItem> oldItems;
+ if (aOld && aOld->mContent.IsItems()) {
+ oldItems = aOld->mContent.AsItems().AsSpan();
+ }
+
+ auto items = mContent.AsItems().AsSpan();
+
+ for (size_t i = 0; i < items.Length(); ++i) {
+ const auto& item = items[i];
+ if (!item.IsImage()) {
+ continue;
+ }
+ const auto& image = item.AsImage();
+ const auto* oldImage = i < oldItems.Length() && oldItems[i].IsImage()
+ ? &oldItems[i].AsImage()
+ : nullptr;
+ const_cast<StyleImage&>(image).ResolveImage(aDoc, oldImage);
+ }
+}
+
+// --------------------
+// nsStyleTextReset
+//
+
+nsStyleTextReset::nsStyleTextReset()
+ : mTextDecorationLine(StyleTextDecorationLine::NONE),
+ mTextDecorationStyle(StyleTextDecorationStyle::Solid),
+ mUnicodeBidi(StyleUnicodeBidi::Normal),
+ mInitialLetterSink(0),
+ mInitialLetterSize(0.0f),
+ mTextDecorationColor(StyleColor::CurrentColor()),
+ mTextDecorationThickness(StyleTextDecorationLength::Auto()) {
+ MOZ_COUNT_CTOR(nsStyleTextReset);
+}
+
+nsStyleTextReset::nsStyleTextReset(const nsStyleTextReset& aSource)
+ : mTextOverflow(aSource.mTextOverflow),
+ mTextDecorationLine(aSource.mTextDecorationLine),
+ mTextDecorationStyle(aSource.mTextDecorationStyle),
+ mUnicodeBidi(aSource.mUnicodeBidi),
+ mInitialLetterSink(aSource.mInitialLetterSink),
+ mInitialLetterSize(aSource.mInitialLetterSize),
+ mTextDecorationColor(aSource.mTextDecorationColor),
+ mTextDecorationThickness(aSource.mTextDecorationThickness) {
+ MOZ_COUNT_CTOR(nsStyleTextReset);
+}
+
+nsChangeHint nsStyleTextReset::CalcDifference(
+ const nsStyleTextReset& aNewData) const {
+ if (mUnicodeBidi != aNewData.mUnicodeBidi ||
+ mInitialLetterSink != aNewData.mInitialLetterSink ||
+ mInitialLetterSize != aNewData.mInitialLetterSize) {
+ return NS_STYLE_HINT_REFLOW;
+ }
+
+ if (mTextDecorationLine != aNewData.mTextDecorationLine ||
+ mTextDecorationStyle != aNewData.mTextDecorationStyle ||
+ mTextDecorationThickness != aNewData.mTextDecorationThickness) {
+ // Changes to our text-decoration line can impact our overflow area &
+ // also our descendants' overflow areas (particularly for text-frame
+ // descendants). So, we update those areas & trigger a repaint.
+ return nsChangeHint_RepaintFrame | nsChangeHint_UpdateSubtreeOverflow |
+ nsChangeHint_SchedulePaint;
+ }
+
+ // Repaint for decoration color changes
+ if (mTextDecorationColor != aNewData.mTextDecorationColor) {
+ return nsChangeHint_RepaintFrame;
+ }
+
+ if (mTextOverflow != aNewData.mTextOverflow) {
+ return nsChangeHint_RepaintFrame;
+ }
+
+ return nsChangeHint(0);
+}
+
+// --------------------
+// nsStyleText
+//
+
+static StyleAbsoluteColor DefaultColor(const Document& aDocument) {
+ return StyleAbsoluteColor::FromColor(
+ PreferenceSheet::PrefsFor(aDocument)
+ .ColorsFor(aDocument.DefaultColorScheme())
+ .mDefault);
+}
+
+nsStyleText::nsStyleText(const Document& aDocument)
+ : mColor(DefaultColor(aDocument)),
+ mForcedColorAdjust(StyleForcedColorAdjust::Auto),
+ mTextTransform(StyleTextTransform::None()),
+ mTextAlign(StyleTextAlign::Start),
+ mTextAlignLast(StyleTextAlignLast::Auto),
+ mTextJustify(StyleTextJustify::Auto),
+ mHyphens(StyleHyphens::Manual),
+ mRubyAlign(StyleRubyAlign::SpaceAround),
+ mRubyPosition(StyleRubyPosition::AlternateOver),
+ mTextSizeAdjust(StyleTextSizeAdjust::Auto),
+ mTextCombineUpright(StyleTextCombineUpright::None),
+ mMozControlCharacterVisibility(
+ StaticPrefs::layout_css_control_characters_visible()
+ ? StyleMozControlCharacterVisibility::Visible
+ : StyleMozControlCharacterVisibility::Hidden),
+ mTextRendering(StyleTextRendering::Auto),
+ mTextEmphasisColor(StyleColor::CurrentColor()),
+ mWebkitTextFillColor(StyleColor::CurrentColor()),
+ mWebkitTextStrokeColor(StyleColor::CurrentColor()),
+ mTabSize(StyleNonNegativeLengthOrNumber::Number(8.f)),
+ mWordSpacing(LengthPercentage::Zero()),
+ mLetterSpacing({0.}),
+ mTextUnderlineOffset(LengthPercentageOrAuto::Auto()),
+ mTextDecorationSkipInk(StyleTextDecorationSkipInk::Auto),
+ mTextUnderlinePosition(StyleTextUnderlinePosition::AUTO),
+ mWebkitTextStrokeWidth(0),
+ mTextEmphasisStyle(StyleTextEmphasisStyle::None()) {
+ MOZ_COUNT_CTOR(nsStyleText);
+ RefPtr<nsAtom> language = aDocument.GetContentLanguageAsAtomForStyle();
+ mTextEmphasisPosition =
+ language && nsStyleUtil::MatchesLanguagePrefix(language, u"zh")
+ ? StyleTextEmphasisPosition::UNDER
+ : StyleTextEmphasisPosition::OVER;
+}
+
+nsStyleText::nsStyleText(const nsStyleText& aSource)
+ : mColor(aSource.mColor),
+ mForcedColorAdjust(aSource.mForcedColorAdjust),
+ mTextTransform(aSource.mTextTransform),
+ mTextAlign(aSource.mTextAlign),
+ mTextAlignLast(aSource.mTextAlignLast),
+ mTextJustify(aSource.mTextJustify),
+ mWhiteSpaceCollapse(aSource.mWhiteSpaceCollapse),
+ mTextWrapMode(aSource.mTextWrapMode),
+ mLineBreak(aSource.mLineBreak),
+ mWordBreak(aSource.mWordBreak),
+ mOverflowWrap(aSource.mOverflowWrap),
+ mHyphens(aSource.mHyphens),
+ mRubyAlign(aSource.mRubyAlign),
+ mRubyPosition(aSource.mRubyPosition),
+ mTextSizeAdjust(aSource.mTextSizeAdjust),
+ mTextCombineUpright(aSource.mTextCombineUpright),
+ mMozControlCharacterVisibility(aSource.mMozControlCharacterVisibility),
+ mTextEmphasisPosition(aSource.mTextEmphasisPosition),
+ mTextRendering(aSource.mTextRendering),
+ mTextEmphasisColor(aSource.mTextEmphasisColor),
+ mWebkitTextFillColor(aSource.mWebkitTextFillColor),
+ mWebkitTextStrokeColor(aSource.mWebkitTextStrokeColor),
+ mTabSize(aSource.mTabSize),
+ mWordSpacing(aSource.mWordSpacing),
+ mLetterSpacing(aSource.mLetterSpacing),
+ mTextIndent(aSource.mTextIndent),
+ mTextUnderlineOffset(aSource.mTextUnderlineOffset),
+ mTextDecorationSkipInk(aSource.mTextDecorationSkipInk),
+ mTextUnderlinePosition(aSource.mTextUnderlinePosition),
+ mWebkitTextStrokeWidth(aSource.mWebkitTextStrokeWidth),
+ mTextShadow(aSource.mTextShadow),
+ mTextEmphasisStyle(aSource.mTextEmphasisStyle),
+ mHyphenateCharacter(aSource.mHyphenateCharacter),
+ mWebkitTextSecurity(aSource.mWebkitTextSecurity),
+ mTextWrapStyle(aSource.mTextWrapStyle) {
+ MOZ_COUNT_CTOR(nsStyleText);
+}
+
+nsChangeHint nsStyleText::CalcDifference(const nsStyleText& aNewData) const {
+ if (WhiteSpaceOrNewlineIsSignificant() !=
+ aNewData.WhiteSpaceOrNewlineIsSignificant()) {
+ // This may require construction of suppressed text frames
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ if (mTextCombineUpright != aNewData.mTextCombineUpright ||
+ mMozControlCharacterVisibility !=
+ aNewData.mMozControlCharacterVisibility) {
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ if ((mTextAlign != aNewData.mTextAlign) ||
+ (mTextAlignLast != aNewData.mTextAlignLast) ||
+ (mTextTransform != aNewData.mTextTransform) ||
+ (mWhiteSpaceCollapse != aNewData.mWhiteSpaceCollapse) ||
+ (mTextWrapMode != aNewData.mTextWrapMode) ||
+ (mLineBreak != aNewData.mLineBreak) ||
+ (mWordBreak != aNewData.mWordBreak) ||
+ (mOverflowWrap != aNewData.mOverflowWrap) ||
+ (mHyphens != aNewData.mHyphens) || (mRubyAlign != aNewData.mRubyAlign) ||
+ (mRubyPosition != aNewData.mRubyPosition) ||
+ (mTextSizeAdjust != aNewData.mTextSizeAdjust) ||
+ (mLetterSpacing != aNewData.mLetterSpacing) ||
+ (mTextIndent != aNewData.mTextIndent) ||
+ (mTextJustify != aNewData.mTextJustify) ||
+ (mWordSpacing != aNewData.mWordSpacing) ||
+ (mTabSize != aNewData.mTabSize) ||
+ (mHyphenateCharacter != aNewData.mHyphenateCharacter) ||
+ (mWebkitTextSecurity != aNewData.mWebkitTextSecurity) ||
+ (mTextWrapStyle != aNewData.mTextWrapStyle)) {
+ return NS_STYLE_HINT_REFLOW;
+ }
+
+ if (HasEffectiveTextEmphasis() != aNewData.HasEffectiveTextEmphasis() ||
+ (HasEffectiveTextEmphasis() &&
+ mTextEmphasisPosition != aNewData.mTextEmphasisPosition)) {
+ // Text emphasis position change could affect line height calculation.
+ return nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
+ }
+
+ nsChangeHint hint = nsChangeHint(0);
+
+ // text-rendering changes require a reflow since they change SVG
+ // frames' rects.
+ if (mTextRendering != aNewData.mTextRendering) {
+ hint |= nsChangeHint_NeedReflow | nsChangeHint_RepaintFrame;
+ }
+
+ if (mTextShadow != aNewData.mTextShadow ||
+ mTextEmphasisStyle != aNewData.mTextEmphasisStyle ||
+ mWebkitTextStrokeWidth != aNewData.mWebkitTextStrokeWidth ||
+ mTextUnderlineOffset != aNewData.mTextUnderlineOffset ||
+ mTextDecorationSkipInk != aNewData.mTextDecorationSkipInk ||
+ mTextUnderlinePosition != aNewData.mTextUnderlinePosition) {
+ hint |= nsChangeHint_UpdateSubtreeOverflow | nsChangeHint_SchedulePaint |
+ nsChangeHint_RepaintFrame;
+
+ // We don't add any other hints below.
+ return hint;
+ }
+
+ if (mColor != aNewData.mColor) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ if (mTextEmphasisColor != aNewData.mTextEmphasisColor ||
+ mWebkitTextFillColor != aNewData.mWebkitTextFillColor ||
+ mWebkitTextStrokeColor != aNewData.mWebkitTextStrokeColor) {
+ hint |= nsChangeHint_SchedulePaint | nsChangeHint_RepaintFrame;
+ }
+
+ if (hint) {
+ return hint;
+ }
+
+ if (mTextEmphasisPosition != aNewData.mTextEmphasisPosition ||
+ mForcedColorAdjust != aNewData.mForcedColorAdjust) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ return nsChangeHint(0);
+}
+
+LogicalSide nsStyleText::TextEmphasisSide(WritingMode aWM) const {
+ bool noLeftBit = !(mTextEmphasisPosition & StyleTextEmphasisPosition::LEFT);
+ DebugOnly<bool> noRightBit =
+ !(mTextEmphasisPosition & StyleTextEmphasisPosition::RIGHT);
+ bool noOverBit = !(mTextEmphasisPosition & StyleTextEmphasisPosition::OVER);
+ DebugOnly<bool> noUnderBit =
+ !(mTextEmphasisPosition & StyleTextEmphasisPosition::UNDER);
+
+ MOZ_ASSERT((noOverBit != noUnderBit) &&
+ ((noLeftBit != noRightBit) || noRightBit));
+ mozilla::Side side = aWM.IsVertical() ? (noLeftBit ? eSideRight : eSideLeft)
+ : (noOverBit ? eSideBottom : eSideTop);
+ LogicalSide result = aWM.LogicalSideForPhysicalSide(side);
+ MOZ_ASSERT(IsBlock(result));
+ return result;
+}
+
+//-----------------------
+// nsStyleUI
+//
+
+nsStyleUI::nsStyleUI()
+ : mInert(StyleInert::None),
+ mMozTheme(StyleMozTheme::Auto),
+ mUserInput(StyleUserInput::Auto),
+ mUserModify(StyleUserModify::ReadOnly),
+ mUserFocus(StyleUserFocus::Normal),
+ mPointerEvents(StylePointerEvents::Auto),
+ mCursor{{}, StyleCursorKind::Auto},
+ mAccentColor(StyleColorOrAuto::Auto()),
+ mCaretColor(StyleColorOrAuto::Auto()),
+ mScrollbarColor(StyleScrollbarColor::Auto()),
+ mColorScheme(StyleColorScheme{{}, {}}) {
+ MOZ_COUNT_CTOR(nsStyleUI);
+}
+
+nsStyleUI::nsStyleUI(const nsStyleUI& aSource)
+ : mInert(aSource.mInert),
+ mMozTheme(aSource.mMozTheme),
+ mUserInput(aSource.mUserInput),
+ mUserModify(aSource.mUserModify),
+ mUserFocus(aSource.mUserFocus),
+ mPointerEvents(aSource.mPointerEvents),
+ mCursor(aSource.mCursor),
+ mAccentColor(aSource.mAccentColor),
+ mCaretColor(aSource.mCaretColor),
+ mScrollbarColor(aSource.mScrollbarColor),
+ mColorScheme(aSource.mColorScheme) {
+ MOZ_COUNT_CTOR(nsStyleUI);
+}
+
+void nsStyleUI::TriggerImageLoads(Document& aDocument,
+ const nsStyleUI* aOldStyle) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto cursorImages = mCursor.images.AsSpan();
+ auto oldCursorImages = aOldStyle ? aOldStyle->mCursor.images.AsSpan()
+ : Span<const StyleCursorImage>();
+ for (size_t i = 0; i < cursorImages.Length(); ++i) {
+ const auto& cursor = cursorImages[i];
+ const auto* oldCursorImage =
+ oldCursorImages.Length() > i ? &oldCursorImages[i].image : nullptr;
+ const_cast<StyleCursorImage&>(cursor).image.ResolveImage(aDocument,
+ oldCursorImage);
+ }
+}
+
+nsChangeHint nsStyleUI::CalcDifference(const nsStyleUI& aNewData) const {
+ // SVGGeometryFrame's mRect depends on stroke _and_ on the value of
+ // pointer-events. See SVGGeometryFrame::ReflowSVG's use of GetHitTestFlags.
+ // (Only a reflow, no visual change.)
+ //
+ // pointer-events changes can change event regions overrides on layers and
+ // so needs a repaint.
+ const auto kPointerEventsHint =
+ nsChangeHint_NeedReflow | nsChangeHint_SchedulePaint;
+
+ nsChangeHint hint = nsChangeHint(0);
+ if (mCursor != aNewData.mCursor) {
+ hint |= nsChangeHint_UpdateCursor;
+ }
+
+ if (mPointerEvents != aNewData.mPointerEvents) {
+ hint |= kPointerEventsHint;
+ }
+
+ if (mUserModify != aNewData.mUserModify) {
+ hint |= NS_STYLE_HINT_VISUAL;
+ }
+
+ if (mInert != aNewData.mInert) {
+ // inert affects pointer-events, user-modify, user-select, user-focus and
+ // -moz-user-input, do the union of all them (minus
+ // nsChangeHint_NeutralChange which isn't needed if there's any other hint).
+ hint |= NS_STYLE_HINT_VISUAL | kPointerEventsHint;
+ }
+
+ if (mUserFocus != aNewData.mUserFocus || mUserInput != aNewData.mUserInput) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ if (mCaretColor != aNewData.mCaretColor ||
+ mAccentColor != aNewData.mAccentColor ||
+ mScrollbarColor != aNewData.mScrollbarColor ||
+ mMozTheme != aNewData.mMozTheme ||
+ mColorScheme != aNewData.mColorScheme) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ return hint;
+}
+
+//-----------------------
+// nsStyleUIReset
+//
+
+nsStyleUIReset::nsStyleUIReset()
+ : mUserSelect(StyleUserSelect::Auto),
+ mScrollbarWidth(StyleScrollbarWidth::Auto),
+ mMozForceBrokenImageIcon(false),
+ mMozSubtreeHiddenOnlyVisually(false),
+ mIMEMode(StyleImeMode::Auto),
+ mWindowDragging(StyleWindowDragging::Default),
+ mWindowShadow(StyleWindowShadow::Auto),
+ mWindowOpacity(1.0),
+ mMozWindowInputRegionMargin(StyleLength::Zero()),
+ mWindowTransformOrigin{LengthPercentage::FromPercentage(0.5),
+ LengthPercentage::FromPercentage(0.5),
+ {0.}},
+ mTransitions(
+ nsStyleAutoArray<StyleTransition>::WITH_SINGLE_INITIAL_ELEMENT),
+ mTransitionTimingFunctionCount(1),
+ mTransitionDurationCount(1),
+ mTransitionDelayCount(1),
+ mTransitionPropertyCount(1),
+ mAnimations(
+ nsStyleAutoArray<StyleAnimation>::WITH_SINGLE_INITIAL_ELEMENT),
+ mAnimationTimingFunctionCount(1),
+ mAnimationDurationCount(1),
+ mAnimationDelayCount(1),
+ mAnimationNameCount(1),
+ mAnimationDirectionCount(1),
+ mAnimationFillModeCount(1),
+ mAnimationPlayStateCount(1),
+ mAnimationIterationCountCount(1),
+ mAnimationCompositionCount(1),
+ mAnimationTimelineCount(1),
+ mScrollTimelines(
+ nsStyleAutoArray<StyleScrollTimeline>::WITH_SINGLE_INITIAL_ELEMENT),
+ mScrollTimelineNameCount(1),
+ mScrollTimelineAxisCount(1),
+ mViewTimelines(
+ nsStyleAutoArray<StyleViewTimeline>::WITH_SINGLE_INITIAL_ELEMENT),
+ mViewTimelineNameCount(1),
+ mViewTimelineAxisCount(1),
+ mViewTimelineInsetCount(1) {
+ MOZ_COUNT_CTOR(nsStyleUIReset);
+}
+
+nsStyleUIReset::nsStyleUIReset(const nsStyleUIReset& aSource)
+ : mUserSelect(aSource.mUserSelect),
+ mScrollbarWidth(aSource.mScrollbarWidth),
+ mMozForceBrokenImageIcon(aSource.mMozForceBrokenImageIcon),
+ mMozSubtreeHiddenOnlyVisually(aSource.mMozSubtreeHiddenOnlyVisually),
+ mIMEMode(aSource.mIMEMode),
+ mWindowDragging(aSource.mWindowDragging),
+ mWindowShadow(aSource.mWindowShadow),
+ mWindowOpacity(aSource.mWindowOpacity),
+ mMozWindowInputRegionMargin(aSource.mMozWindowInputRegionMargin),
+ mMozWindowTransform(aSource.mMozWindowTransform),
+ mWindowTransformOrigin(aSource.mWindowTransformOrigin),
+ mTransitions(aSource.mTransitions.Clone()),
+ mTransitionTimingFunctionCount(aSource.mTransitionTimingFunctionCount),
+ mTransitionDurationCount(aSource.mTransitionDurationCount),
+ mTransitionDelayCount(aSource.mTransitionDelayCount),
+ mTransitionPropertyCount(aSource.mTransitionPropertyCount),
+ mAnimations(aSource.mAnimations.Clone()),
+ mAnimationTimingFunctionCount(aSource.mAnimationTimingFunctionCount),
+ mAnimationDurationCount(aSource.mAnimationDurationCount),
+ mAnimationDelayCount(aSource.mAnimationDelayCount),
+ mAnimationNameCount(aSource.mAnimationNameCount),
+ mAnimationDirectionCount(aSource.mAnimationDirectionCount),
+ mAnimationFillModeCount(aSource.mAnimationFillModeCount),
+ mAnimationPlayStateCount(aSource.mAnimationPlayStateCount),
+ mAnimationIterationCountCount(aSource.mAnimationIterationCountCount),
+ mAnimationCompositionCount(aSource.mAnimationCompositionCount),
+ mAnimationTimelineCount(aSource.mAnimationTimelineCount),
+ mScrollTimelines(aSource.mScrollTimelines.Clone()),
+ mScrollTimelineNameCount(aSource.mScrollTimelineNameCount),
+ mScrollTimelineAxisCount(aSource.mScrollTimelineAxisCount),
+ mViewTimelines(aSource.mViewTimelines.Clone()),
+ mViewTimelineNameCount(aSource.mViewTimelineNameCount),
+ mViewTimelineAxisCount(aSource.mViewTimelineAxisCount),
+ mViewTimelineInsetCount(aSource.mViewTimelineInsetCount) {
+ MOZ_COUNT_CTOR(nsStyleUIReset);
+}
+
+nsChangeHint nsStyleUIReset::CalcDifference(
+ const nsStyleUIReset& aNewData) const {
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (mMozForceBrokenImageIcon != aNewData.mMozForceBrokenImageIcon) {
+ hint |= nsChangeHint_ReconstructFrame;
+ }
+ if (mMozSubtreeHiddenOnlyVisually != aNewData.mMozSubtreeHiddenOnlyVisually) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ if (mScrollbarWidth != aNewData.mScrollbarWidth) {
+ // For scrollbar-width change, we need some special handling similar
+ // to overflow properties. Specifically, we may need to reconstruct
+ // the scrollbar or force reflow of the viewport scrollbar.
+ hint |= nsChangeHint_ScrollbarChange;
+ }
+ if (mWindowShadow != aNewData.mWindowShadow) {
+ // We really need just an nsChangeHint_SyncFrameView, except
+ // on an ancestor of the frame, so we get that by doing a
+ // reflow.
+ hint |= NS_STYLE_HINT_REFLOW;
+ }
+ if (mUserSelect != aNewData.mUserSelect) {
+ hint |= NS_STYLE_HINT_VISUAL;
+ }
+
+ if (mWindowDragging != aNewData.mWindowDragging) {
+ hint |= nsChangeHint_SchedulePaint;
+ }
+
+ if (!hint &&
+ (mTransitions != aNewData.mTransitions ||
+ mTransitionTimingFunctionCount !=
+ aNewData.mTransitionTimingFunctionCount ||
+ mTransitionDurationCount != aNewData.mTransitionDurationCount ||
+ mTransitionDelayCount != aNewData.mTransitionDelayCount ||
+ mTransitionPropertyCount != aNewData.mTransitionPropertyCount ||
+ mAnimations != aNewData.mAnimations ||
+ mAnimationTimingFunctionCount !=
+ aNewData.mAnimationTimingFunctionCount ||
+ mAnimationDurationCount != aNewData.mAnimationDurationCount ||
+ mAnimationDelayCount != aNewData.mAnimationDelayCount ||
+ mAnimationNameCount != aNewData.mAnimationNameCount ||
+ mAnimationDirectionCount != aNewData.mAnimationDirectionCount ||
+ mAnimationFillModeCount != aNewData.mAnimationFillModeCount ||
+ mAnimationPlayStateCount != aNewData.mAnimationPlayStateCount ||
+ mAnimationIterationCountCount !=
+ aNewData.mAnimationIterationCountCount ||
+ mAnimationCompositionCount != aNewData.mAnimationCompositionCount ||
+ mAnimationTimelineCount != aNewData.mAnimationTimelineCount ||
+ mIMEMode != aNewData.mIMEMode ||
+ mWindowOpacity != aNewData.mWindowOpacity ||
+ mMozWindowInputRegionMargin != aNewData.mMozWindowInputRegionMargin ||
+ mMozWindowTransform != aNewData.mMozWindowTransform ||
+ mScrollTimelines != aNewData.mScrollTimelines ||
+ mScrollTimelineNameCount != aNewData.mScrollTimelineNameCount ||
+ mScrollTimelineAxisCount != aNewData.mScrollTimelineAxisCount ||
+ mViewTimelines != aNewData.mViewTimelines ||
+ mViewTimelineNameCount != aNewData.mViewTimelineNameCount ||
+ mViewTimelineAxisCount != aNewData.mViewTimelineAxisCount ||
+ mViewTimelineInsetCount != aNewData.mViewTimelineInsetCount)) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ return hint;
+}
+
+StyleScrollbarWidth nsStyleUIReset::ScrollbarWidth() const {
+ if (MOZ_UNLIKELY(StaticPrefs::layout_css_scrollbar_width_thin_disabled())) {
+ if (mScrollbarWidth == StyleScrollbarWidth::Thin) {
+ return StyleScrollbarWidth::Auto;
+ }
+ }
+ return mScrollbarWidth;
+}
+
+//-----------------------
+// nsStyleEffects
+//
+
+nsStyleEffects::nsStyleEffects()
+ : mClip(StyleClipRectOrAuto::Auto()),
+ mOpacity(1.0f),
+ mMixBlendMode(StyleBlend::Normal) {
+ MOZ_COUNT_CTOR(nsStyleEffects);
+}
+
+nsStyleEffects::nsStyleEffects(const nsStyleEffects& aSource)
+ : mFilters(aSource.mFilters),
+ mBoxShadow(aSource.mBoxShadow),
+ mBackdropFilters(aSource.mBackdropFilters),
+ mClip(aSource.mClip),
+ mOpacity(aSource.mOpacity),
+ mMixBlendMode(aSource.mMixBlendMode) {
+ MOZ_COUNT_CTOR(nsStyleEffects);
+}
+
+static bool AnyAutonessChanged(const StyleClipRectOrAuto& aOld,
+ const StyleClipRectOrAuto& aNew) {
+ if (aOld.IsAuto() != aNew.IsAuto()) {
+ return true;
+ }
+ if (aOld.IsAuto()) {
+ return false;
+ }
+ const auto& oldRect = aOld.AsRect();
+ const auto& newRect = aNew.AsRect();
+ return oldRect.top.IsAuto() != newRect.top.IsAuto() ||
+ oldRect.right.IsAuto() != newRect.right.IsAuto() ||
+ oldRect.bottom.IsAuto() != newRect.bottom.IsAuto() ||
+ oldRect.left.IsAuto() != newRect.left.IsAuto();
+}
+
+nsChangeHint nsStyleEffects::CalcDifference(
+ const nsStyleEffects& aNewData) const {
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (mBoxShadow != aNewData.mBoxShadow) {
+ // Update overflow regions & trigger DLBI to be sure it's noticed.
+ // Also request a repaint, since it's possible that only the color
+ // of the shadow is changing (and UpdateOverflow/SchedulePaint won't
+ // repaint for that, since they won't know what needs invalidating.)
+ hint |= nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint |
+ nsChangeHint_RepaintFrame;
+ }
+
+ if (AnyAutonessChanged(mClip, aNewData.mClip)) {
+ hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
+ } else if (mClip != aNewData.mClip) {
+ // If the clip has changed, we just need to update overflow areas. DLBI
+ // will handle the invalidation.
+ hint |= nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint;
+ }
+
+ if (mOpacity != aNewData.mOpacity) {
+ hint |= nsChangeHint_UpdateOpacityLayer;
+
+ // If we're going from the optimized >=0.99 opacity value to 1.0 or back,
+ // then repaint the frame because DLBI will not catch the invalidation.
+ // Otherwise, just update the opacity layer.
+ if ((mOpacity >= 0.99f && mOpacity < 1.0f && aNewData.mOpacity == 1.0f) ||
+ (aNewData.mOpacity >= 0.99f && aNewData.mOpacity < 1.0f &&
+ mOpacity == 1.0f)) {
+ hint |= nsChangeHint_RepaintFrame;
+ } else {
+ if ((mOpacity == 1.0f) != (aNewData.mOpacity == 1.0f)) {
+ hint |= nsChangeHint_UpdateUsesOpacity;
+ }
+ }
+ }
+
+ if (HasFilters() != aNewData.HasFilters()) {
+ // A change from/to being a containing block for position:fixed.
+ hint |= nsChangeHint_UpdateContainingBlock;
+ }
+
+ if (mFilters != aNewData.mFilters) {
+ hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame |
+ nsChangeHint_UpdateOverflow;
+ }
+
+ if (mMixBlendMode != aNewData.mMixBlendMode) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ if (HasBackdropFilters() != aNewData.HasBackdropFilters()) {
+ // A change from/to being a containing block for position:fixed.
+ hint |= nsChangeHint_UpdateContainingBlock;
+ }
+
+ if (mBackdropFilters != aNewData.mBackdropFilters) {
+ hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
+ }
+
+ return hint;
+}
+
+static bool TransformOperationHasPercent(const StyleTransformOperation& aOp) {
+ switch (aOp.tag) {
+ case StyleTransformOperation::Tag::TranslateX:
+ return aOp.AsTranslateX().HasPercent();
+ case StyleTransformOperation::Tag::TranslateY:
+ return aOp.AsTranslateY().HasPercent();
+ case StyleTransformOperation::Tag::TranslateZ:
+ return false;
+ case StyleTransformOperation::Tag::Translate3D: {
+ const auto& translate = aOp.AsTranslate3D();
+ // NOTE(emilio): z translation is a `<length>`, so can't have percentages.
+ return translate._0.HasPercent() || translate._1.HasPercent();
+ }
+ case StyleTransformOperation::Tag::Translate: {
+ const auto& translate = aOp.AsTranslate();
+ return translate._0.HasPercent() || translate._1.HasPercent();
+ }
+ case StyleTransformOperation::Tag::AccumulateMatrix: {
+ const auto& accum = aOp.AsAccumulateMatrix();
+ return accum.from_list.HasPercent() || accum.to_list.HasPercent();
+ }
+ case StyleTransformOperation::Tag::InterpolateMatrix: {
+ const auto& interpolate = aOp.AsInterpolateMatrix();
+ return interpolate.from_list.HasPercent() ||
+ interpolate.to_list.HasPercent();
+ }
+ case StyleTransformOperation::Tag::Perspective:
+ case StyleTransformOperation::Tag::RotateX:
+ case StyleTransformOperation::Tag::RotateY:
+ case StyleTransformOperation::Tag::RotateZ:
+ case StyleTransformOperation::Tag::Rotate:
+ case StyleTransformOperation::Tag::Rotate3D:
+ case StyleTransformOperation::Tag::SkewX:
+ case StyleTransformOperation::Tag::SkewY:
+ case StyleTransformOperation::Tag::Skew:
+ case StyleTransformOperation::Tag::ScaleX:
+ case StyleTransformOperation::Tag::ScaleY:
+ case StyleTransformOperation::Tag::ScaleZ:
+ case StyleTransformOperation::Tag::Scale:
+ case StyleTransformOperation::Tag::Scale3D:
+ case StyleTransformOperation::Tag::Matrix:
+ case StyleTransformOperation::Tag::Matrix3D:
+ return false;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown transform operation");
+ return false;
+ }
+}
+
+template <>
+bool StyleTransform::HasPercent() const {
+ for (const auto& op : Operations()) {
+ if (TransformOperationHasPercent(op)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <>
+void StyleCalcNode::ScaleLengthsBy(float aScale) {
+ auto ScaleNode = [aScale](const StyleCalcNode& aNode) {
+ // This const_cast could be removed by generating more mut-casts, if
+ // needed.
+ const_cast<StyleCalcNode&>(aNode).ScaleLengthsBy(aScale);
+ };
+
+ switch (tag) {
+ case Tag::Leaf: {
+ const auto& leaf = AsLeaf();
+ if (leaf.IsLength()) {
+ // This const_cast could be removed by generating more mut-casts, if
+ // needed.
+ const_cast<Length&>(leaf.AsLength()).ScaleBy(aScale);
+ }
+ break;
+ }
+ case Tag::Clamp: {
+ const auto& clamp = AsClamp();
+ ScaleNode(*clamp.min);
+ ScaleNode(*clamp.center);
+ ScaleNode(*clamp.max);
+ break;
+ }
+ case Tag::Round: {
+ const auto& round = AsRound();
+ ScaleNode(*round.value);
+ ScaleNode(*round.step);
+ break;
+ }
+ case Tag::ModRem: {
+ const auto& modRem = AsModRem();
+ ScaleNode(*modRem.dividend);
+ ScaleNode(*modRem.divisor);
+ break;
+ }
+ case Tag::MinMax: {
+ for (const auto& child : AsMinMax()._0.AsSpan()) {
+ ScaleNode(child);
+ }
+ break;
+ }
+ case Tag::Sum: {
+ for (const auto& child : AsSum().AsSpan()) {
+ ScaleNode(child);
+ }
+ break;
+ }
+ case Tag::Product: {
+ for (const auto& child : AsProduct().AsSpan()) {
+ ScaleNode(child);
+ }
+ break;
+ }
+ case Tag::Negate: {
+ const auto& negate = AsNegate();
+ ScaleNode(*negate);
+ break;
+ }
+ case Tag::Invert: {
+ const auto& invert = AsInvert();
+ ScaleNode(*invert);
+ break;
+ }
+ case Tag::Hypot: {
+ for (const auto& child : AsHypot().AsSpan()) {
+ ScaleNode(child);
+ }
+ break;
+ }
+ case Tag::Abs: {
+ const auto& abs = AsAbs();
+ ScaleNode(*abs);
+ break;
+ }
+ case Tag::Sign: {
+ const auto& sign = AsSign();
+ ScaleNode(*sign);
+ break;
+ }
+ }
+}
+
+nscoord StyleCalcLengthPercentage::Resolve(nscoord aBasis,
+ CoordRounder aRounder) const {
+ CSSCoord result = ResolveToCSSPixels(CSSPixel::FromAppUnits(aBasis));
+ return aRounder(result * AppUnitsPerCSSPixel());
+}
+
+bool nsStyleDisplay::PrecludesSizeContainmentOrContentVisibilityWithFrame(
+ const nsIFrame& aFrame) const {
+ // The spec says that in the case of SVG, the contain property only applies
+ // to <svg> elements that have an associated CSS layout box.
+ // https://drafts.csswg.org/css-contain/#contain-property
+ // Internal SVG elements do not use the standard CSS box model, and wouldn't
+ // be affected by size containment. By disabling it we prevent them from
+ // becoming query containers for size features.
+ if (aFrame.HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ return true;
+ }
+
+ // Note: The spec for size containment says it should have no effect
+ // - on non-atomic, inline-level boxes.
+ // - on internal ruby boxes.
+ // - if inner display type is table.
+ // - on internal table boxes.
+ // https://drafts.csswg.org/css-contain/#containment-size
+ bool isNonReplacedInline = aFrame.IsLineParticipant() && !aFrame.IsReplaced();
+ return isNonReplacedInline || IsInternalRubyDisplayType() ||
+ DisplayInside() == mozilla::StyleDisplayInside::Table ||
+ IsInnerTableStyle();
+}
+
+ContainSizeAxes nsStyleDisplay::GetContainSizeAxes(
+ const nsIFrame& aFrame) const {
+ // Short circuit for no containment whatsoever
+ if (MOZ_LIKELY(!mEffectiveContainment)) {
+ return ContainSizeAxes(false, false);
+ }
+
+ if (PrecludesSizeContainmentOrContentVisibilityWithFrame(aFrame)) {
+ return ContainSizeAxes(false, false);
+ }
+
+ // https://drafts.csswg.org/css-contain-2/#content-visibility
+ // If this content skips its content via content-visibility, it always has
+ // size containment.
+ if (MOZ_LIKELY(!(mEffectiveContainment & StyleContain::SIZE)) &&
+ MOZ_UNLIKELY(aFrame.HidesContent())) {
+ return ContainSizeAxes(true, true);
+ }
+
+ return ContainSizeAxes(
+ static_cast<bool>(mEffectiveContainment & StyleContain::INLINE_SIZE),
+ static_cast<bool>(mEffectiveContainment & StyleContain::BLOCK_SIZE));
+}
+
+StyleContentVisibility nsStyleDisplay::ContentVisibility(
+ const nsIFrame& aFrame) const {
+ if (MOZ_LIKELY(mContentVisibility == StyleContentVisibility::Visible)) {
+ return StyleContentVisibility::Visible;
+ }
+ // content-visibility applies to elements for which size containment applies.
+ // https://drafts.csswg.org/css-contain/#content-visibility
+ if (PrecludesSizeContainmentOrContentVisibilityWithFrame(aFrame)) {
+ return StyleContentVisibility::Visible;
+ }
+ return mContentVisibility;
+}
+
+static nscoord Resolve(const StyleContainIntrinsicSize& aSize,
+ nscoord aNoneValue, const nsIFrame& aFrame,
+ LogicalAxis aAxis) {
+ if (aSize.IsNone()) {
+ return aNoneValue;
+ }
+ if (aSize.IsLength()) {
+ return aSize.AsLength().ToAppUnits();
+ }
+ MOZ_ASSERT(aSize.HasAuto());
+ if (const auto* element = Element::FromNodeOrNull(aFrame.GetContent())) {
+ Maybe<float> lastSize = aAxis == eLogicalAxisBlock
+ ? element->GetLastRememberedBSize()
+ : element->GetLastRememberedISize();
+ if (lastSize && aFrame.HidesContent()) {
+ return CSSPixel::ToAppUnits(*lastSize);
+ }
+ }
+ if (aSize.IsAutoNone()) {
+ return aNoneValue;
+ }
+ return aSize.AsAutoLength().ToAppUnits();
+}
+
+Maybe<nscoord> ContainSizeAxes::ContainIntrinsicBSize(
+ const nsIFrame& aFrame, nscoord aNoneValue) const {
+ if (!mBContained) {
+ return Nothing();
+ }
+ const StyleContainIntrinsicSize& bSize =
+ aFrame.StylePosition()->ContainIntrinsicBSize(aFrame.GetWritingMode());
+ return Some(Resolve(bSize, aNoneValue, aFrame, eLogicalAxisBlock));
+}
+
+Maybe<nscoord> ContainSizeAxes::ContainIntrinsicISize(
+ const nsIFrame& aFrame, nscoord aNoneValue) const {
+ if (!mIContained) {
+ return Nothing();
+ }
+ const StyleContainIntrinsicSize& iSize =
+ aFrame.StylePosition()->ContainIntrinsicISize(aFrame.GetWritingMode());
+ return Some(Resolve(iSize, aNoneValue, aFrame, eLogicalAxisInline));
+}
+
+nsSize ContainSizeAxes::ContainSize(const nsSize& aUncontainedSize,
+ const nsIFrame& aFrame) const {
+ if (!IsAny()) {
+ return aUncontainedSize;
+ }
+ if (aFrame.GetWritingMode().IsVertical()) {
+ return nsSize(
+ ContainIntrinsicBSize(aFrame).valueOr(aUncontainedSize.Width()),
+ ContainIntrinsicISize(aFrame).valueOr(aUncontainedSize.Height()));
+ }
+ return nsSize(
+ ContainIntrinsicISize(aFrame).valueOr(aUncontainedSize.Width()),
+ ContainIntrinsicBSize(aFrame).valueOr(aUncontainedSize.Height()));
+}
+
+IntrinsicSize ContainSizeAxes::ContainIntrinsicSize(
+ const IntrinsicSize& aUncontainedSize, const nsIFrame& aFrame) const {
+ if (!IsAny()) {
+ return aUncontainedSize;
+ }
+ IntrinsicSize result(aUncontainedSize);
+ const bool isVerticalWM = aFrame.GetWritingMode().IsVertical();
+ if (Maybe<nscoord> containBSize = ContainIntrinsicBSize(aFrame)) {
+ (isVerticalWM ? result.width : result.height) = containBSize;
+ }
+ if (Maybe<nscoord> containISize = ContainIntrinsicISize(aFrame)) {
+ (isVerticalWM ? result.height : result.width) = containISize;
+ }
+ return result;
+}
diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h
new file mode 100644
index 0000000000..b2d68a5976
--- /dev/null
+++ b/layout/style/nsStyleStruct.h
@@ -0,0 +1,2085 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * structs that contain the data provided by ComputedStyle, the
+ * internal API for computed style data for an element
+ */
+
+#ifndef nsStyleStruct_h___
+#define nsStyleStruct_h___
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowButtonType.h"
+#include "nsColor.h"
+#include "nsCoord.h"
+#include "nsMargin.h"
+#include "nsFont.h"
+#include "nsStyleAutoArray.h"
+#include "nsStyleConsts.h"
+#include "nsChangeHint.h"
+#include "nsTArray.h"
+#include "imgIContainer.h"
+#include "imgRequestProxy.h"
+#include "CounterStyleManager.h"
+#include <cstddef> // offsetof()
+#include "X11UndefineNone.h"
+
+class nsIFrame;
+class nsIURI;
+class nsTextFrame;
+struct nsStyleDisplay;
+struct nsStyleVisibility;
+namespace mozilla {
+class ComputedStyle;
+struct IntrinsicSize;
+
+} // namespace mozilla
+
+namespace mozilla::dom {
+enum class CompositeOperation : uint8_t;
+} // namespace mozilla::dom
+
+namespace mozilla {
+
+using Position = StylePosition;
+
+template <>
+inline bool StylePosition::HasPercent() const {
+ return horizontal.HasPercent() || vertical.HasPercent();
+}
+
+/**
+ * True if the effective background image position described by this depends on
+ * the size of the corresponding frame.
+ */
+template <>
+inline bool StylePosition::DependsOnPositioningAreaSize() const {
+ return HasPercent();
+}
+
+template <>
+inline Position Position::FromPercentage(float aPercent) {
+ return {LengthPercentage::FromPercentage(aPercent),
+ LengthPercentage::FromPercentage(aPercent)};
+}
+
+/**
+ * Convenience struct for querying if a given box has size-containment in
+ * either axis.
+ */
+struct ContainSizeAxes {
+ ContainSizeAxes(bool aIContained, bool aBContained)
+ : mIContained(aIContained), mBContained(aBContained) {}
+
+ bool IsBoth() const { return mIContained && mBContained; }
+ bool IsAny() const { return mIContained || mBContained; }
+
+ bool operator==(const ContainSizeAxes& aOther) const {
+ return mIContained == aOther.mIContained &&
+ mBContained == aOther.mBContained;
+ }
+
+ /**
+ * Return a contained size from an uncontained size.
+ */
+ nsSize ContainSize(const nsSize& aUncontainedSize,
+ const nsIFrame& aFrame) const;
+ IntrinsicSize ContainIntrinsicSize(const IntrinsicSize& aUncontainedSize,
+ const nsIFrame& aFrame) const;
+ Maybe<nscoord> ContainIntrinsicBSize(const nsIFrame& aFrame,
+ nscoord aNoneValue = 0) const;
+ Maybe<nscoord> ContainIntrinsicISize(const nsIFrame& aFrame,
+ nscoord aNoneValue = 0) const;
+
+ const bool mIContained;
+ const bool mBContained;
+};
+
+} // namespace mozilla
+
+#define STYLE_STRUCT(name_) \
+ name_(const name_&); \
+ MOZ_COUNTED_DTOR(name_); \
+ void MarkLeaked() const { MOZ_COUNT_DTOR(name_); } \
+ nsChangeHint CalcDifference(const name_&) const;
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleFont {
+ STYLE_STRUCT(nsStyleFont)
+ explicit nsStyleFont(const mozilla::dom::Document&);
+
+ /**
+ * Return a given size multiplied by the current text zoom factor (in
+ * aPresContext).
+ *
+ * The size is allowed to be negative, but the caller is expected to deal with
+ * negative results.
+ */
+ static mozilla::Length ZoomText(const mozilla::dom::Document&,
+ mozilla::Length);
+
+ nsAtom* GetFontPaletteAtom() const { return mFontPalette._0.AsAtom(); }
+
+ nsFont mFont;
+
+ // Our "computed size". Can be different from mFont.size which is our "actual
+ // size" and is enforced to be >= the user's preferred min-size. mFont.size
+ // should be used for display purposes while mSize is the value to return in
+ // getComputedStyle() for example.
+ mozilla::NonNegativeLength mSize;
+
+ // In stylo these three track whether the size is keyword-derived
+ // and if so if it has been modified by a factor/offset
+ float mFontSizeFactor;
+ mozilla::Length mFontSizeOffset;
+ mozilla::StyleFontSizeKeyword mFontSizeKeyword;
+ mozilla::StyleFontPalette mFontPalette;
+
+ // math-depth support (used for MathML scriptlevel)
+ int8_t mMathDepth;
+ mozilla::StyleLineHeight mLineHeight;
+ // MathML mathvariant support
+ mozilla::StyleMathVariant mMathVariant;
+ // math-style support (used for MathML displaystyle)
+ mozilla::StyleMathStyle mMathStyle;
+
+ // allow different min font-size for certain cases
+ uint8_t mMinFontSizeRatio = 100; // percent * 100
+
+ // Was mLanguage set based on a lang attribute in the document?
+ bool mExplicitLanguage = false;
+
+ mozilla::StyleXTextScale mXTextScale;
+
+ bool MinFontSizeEnabled() const {
+ return mXTextScale == mozilla::StyleXTextScale::All;
+ }
+
+ // The value mSize would have had if scriptminsize had never been applied
+ mozilla::NonNegativeLength mScriptUnconstrainedSize;
+ mozilla::Length mScriptMinSize;
+ RefPtr<nsAtom> mLanguage;
+};
+
+struct nsStyleImageLayers {
+ enum class LayerType : uint8_t { Background = 0, Mask };
+
+ explicit nsStyleImageLayers(LayerType aType);
+ nsStyleImageLayers(const nsStyleImageLayers& aSource);
+
+ struct Repeat {
+ mozilla::StyleImageLayerRepeat mXRepeat =
+ mozilla::StyleImageLayerRepeat::Repeat;
+ mozilla::StyleImageLayerRepeat mYRepeat =
+ mozilla::StyleImageLayerRepeat::Repeat;
+
+ // Initialize nothing
+ Repeat() = default;
+
+ bool IsInitialValue() const {
+ return mXRepeat == mozilla::StyleImageLayerRepeat::Repeat &&
+ mYRepeat == mozilla::StyleImageLayerRepeat::Repeat;
+ }
+
+ bool DependsOnPositioningAreaSize() const {
+ return mXRepeat == mozilla::StyleImageLayerRepeat::Space ||
+ mYRepeat == mozilla::StyleImageLayerRepeat::Space;
+ }
+
+ bool operator==(const Repeat& aOther) const {
+ return mXRepeat == aOther.mXRepeat && mYRepeat == aOther.mYRepeat;
+ }
+ bool operator!=(const Repeat& aOther) const { return !(*this == aOther); }
+ };
+
+ struct Layer {
+ using StyleGeometryBox = mozilla::StyleGeometryBox;
+ using StyleImageLayerAttachment = mozilla::StyleImageLayerAttachment;
+ using StyleBackgroundSize = mozilla::StyleBackgroundSize;
+
+ mozilla::StyleImage mImage;
+ mozilla::Position mPosition;
+ StyleBackgroundSize mSize;
+ StyleGeometryBox mClip;
+ MOZ_INIT_OUTSIDE_CTOR StyleGeometryBox mOrigin;
+
+ // This property is used for background layer only.
+ // For a mask layer, it should always be the initial value, which is
+ // StyleImageLayerAttachment::Scroll.
+ StyleImageLayerAttachment mAttachment;
+
+ // This property is used for background layer only.
+ // For a mask layer, it should always be the initial value, which is
+ // StyleBlend::Normal.
+ mozilla::StyleBlend mBlendMode;
+
+ // This property is used for mask layer only.
+ // For a background layer, it should always be the initial value, which is
+ // StyleMaskComposite::Add.
+ mozilla::StyleMaskComposite mComposite;
+
+ // mask-only property. This property is used for mask layer only. For a
+ // background layer, it should always be the initial value, which is
+ // StyleMaskMode::MatchSource.
+ mozilla::StyleMaskMode mMaskMode;
+
+ Repeat mRepeat;
+
+ // This constructor does not initialize mRepeat or mOrigin and Initialize()
+ // must be called to do that.
+ Layer();
+ ~Layer();
+
+ // Initialize mRepeat and mOrigin by specified layer type
+ void Initialize(LayerType aType);
+
+ void ResolveImage(mozilla::dom::Document& aDocument,
+ const Layer* aOldLayer) {
+ mImage.ResolveImage(aDocument, aOldLayer ? &aOldLayer->mImage : nullptr);
+ }
+
+ // True if the rendering of this layer might change when the size
+ // of the background positioning area changes. This is true for any
+ // non-solid-color background whose position or size depends on
+ // the size of the positioning area. It's also true for SVG images
+ // whose root <svg> node has a viewBox.
+ bool RenderingMightDependOnPositioningAreaSizeChange() const;
+
+ // Compute the change hint required by changes in just this layer.
+ nsChangeHint CalcDifference(const Layer& aNewLayer) const;
+
+ // An equality operator that compares the images using URL-equality
+ // rather than pointer-equality.
+ bool operator==(const Layer& aOther) const;
+ bool operator!=(const Layer& aOther) const { return !(*this == aOther); }
+ };
+
+ // The (positive) number of computed values of each property, since
+ // the lengths of the lists are independent.
+ uint32_t mAttachmentCount;
+ uint32_t mClipCount;
+ uint32_t mOriginCount;
+ uint32_t mRepeatCount;
+ uint32_t mPositionXCount;
+ uint32_t mPositionYCount;
+ uint32_t mImageCount;
+ uint32_t mSizeCount;
+ uint32_t mMaskModeCount;
+ uint32_t mBlendModeCount;
+ uint32_t mCompositeCount;
+
+ // Layers are stored in an array, matching the top-to-bottom order in
+ // which they are specified in CSS. The number of layers to be used
+ // should come from the background-image property. We create
+ // additional |Layer| objects for *any* property, not just
+ // background-image. This means that the bottommost layer that
+ // callers in layout care about (which is also the one whose
+ // background-clip applies to the background-color) may not be last
+ // layer. In layers below the bottom layer, properties will be
+ // uninitialized unless their count, above, indicates that they are
+ // present.
+ nsStyleAutoArray<Layer> mLayers;
+
+ const Layer& BottomLayer() const { return mLayers[mImageCount - 1]; }
+
+ void ResolveImages(mozilla::dom::Document& aDocument,
+ const nsStyleImageLayers* aOldLayers) {
+ for (uint32_t i = 0; i < mImageCount; ++i) {
+ const Layer* oldLayer = (aOldLayers && aOldLayers->mLayers.Length() > i)
+ ? &aOldLayers->mLayers[i]
+ : nullptr;
+ mLayers[i].ResolveImage(aDocument, oldLayer);
+ }
+ }
+
+ // Fill unspecified layers by cycling through their values
+ // till they all are of length aMaxItemCount
+ void FillAllLayers(uint32_t aMaxItemCount);
+
+ nsChangeHint CalcDifference(const nsStyleImageLayers& aNewLayers,
+ nsStyleImageLayers::LayerType aType) const;
+
+ nsStyleImageLayers& operator=(const nsStyleImageLayers& aOther);
+ nsStyleImageLayers& operator=(nsStyleImageLayers&& aOther) = default;
+ bool operator==(const nsStyleImageLayers& aOther) const;
+
+ static const nsCSSPropertyID kBackgroundLayerTable[];
+ static const nsCSSPropertyID kMaskLayerTable[];
+
+#define NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(var_, layers_) \
+ for (uint32_t var_ = (layers_).mImageCount; (var_)-- != 0;)
+#define NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(var_, layers_, \
+ start_, count_) \
+ NS_ASSERTION( \
+ (int32_t)(start_) >= 0 && (uint32_t)(start_) < (layers_).mImageCount, \
+ "Invalid layer start!"); \
+ NS_ASSERTION((count_) > 0 && (count_) <= (start_) + 1, \
+ "Invalid layer range!"); \
+ for (uint32_t var_ = (start_) + 1; \
+ (var_)-- != (uint32_t)((start_) + 1 - (count_));)
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleBackground {
+ STYLE_STRUCT(nsStyleBackground)
+ nsStyleBackground();
+ void TriggerImageLoads(mozilla::dom::Document&, const nsStyleBackground*);
+
+ // Return the background color as nscolor.
+ nscolor BackgroundColor(const nsIFrame* aFrame) const;
+ nscolor BackgroundColor(const mozilla::ComputedStyle* aStyle) const;
+
+ // True if this background is completely transparent.
+ bool IsTransparent(const nsIFrame* aFrame) const;
+ bool IsTransparent(const mozilla::ComputedStyle* aStyle) const;
+
+ // We have to take slower codepaths for fixed background attachment,
+ // but we don't want to do that when there's no image.
+ // Not inline because it uses an nsCOMPtr<imgIRequest>
+ // FIXME: Should be in nsStyleStructInlines.h.
+ bool HasFixedBackground(nsIFrame* aFrame) const;
+
+ // Checks to see if this has a non-empty image with "local" attachment.
+ // This is defined in nsStyleStructInlines.h.
+ inline bool HasLocalBackground() const;
+
+ const nsStyleImageLayers::Layer& BottomLayer() const {
+ return mImage.BottomLayer();
+ }
+
+ nsStyleImageLayers mImage;
+ mozilla::StyleColor mBackgroundColor;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleMargin {
+ STYLE_STRUCT(nsStyleMargin)
+ nsStyleMargin();
+
+ bool GetMargin(nsMargin& aMargin) const {
+ bool convertsToLength = mMargin.All(
+ [](const auto& aLength) { return aLength.ConvertsToLength(); });
+
+ if (!convertsToLength) {
+ return false;
+ }
+
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ aMargin.Side(side) = mMargin.Get(side).AsLengthPercentage().ToLength();
+ }
+ return true;
+ }
+
+ nsMargin GetScrollMargin() const {
+ return nsMargin(mScrollMargin.Get(mozilla::eSideTop).ToAppUnits(),
+ mScrollMargin.Get(mozilla::eSideRight).ToAppUnits(),
+ mScrollMargin.Get(mozilla::eSideBottom).ToAppUnits(),
+ mScrollMargin.Get(mozilla::eSideLeft).ToAppUnits());
+ }
+
+ // Return true if either the start or end side in the axis is 'auto'.
+ // (defined in WritingModes.h since we need the full WritingMode type)
+ inline bool HasBlockAxisAuto(mozilla::WritingMode aWM) const;
+ inline bool HasInlineAxisAuto(mozilla::WritingMode aWM) const;
+ inline bool HasAuto(mozilla::LogicalAxis, mozilla::WritingMode) const;
+
+ mozilla::StyleRect<mozilla::LengthPercentageOrAuto> mMargin;
+ mozilla::StyleRect<mozilla::StyleLength> mScrollMargin;
+ // TODO: Add support for overflow-clip-margin: <visual-box> and maybe
+ // per-axis/side clipping, see https://github.com/w3c/csswg-drafts/issues/7245
+ mozilla::StyleLength mOverflowClipMargin;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePadding {
+ STYLE_STRUCT(nsStylePadding)
+ nsStylePadding();
+
+ mozilla::StyleRect<mozilla::NonNegativeLengthPercentage> mPadding;
+ mozilla::StyleRect<mozilla::NonNegativeLengthPercentageOrAuto> mScrollPadding;
+
+ inline bool IsWidthDependent() const {
+ return !mPadding.All(
+ [](const auto& aLength) { return aLength.ConvertsToLength(); });
+ }
+
+ bool GetPadding(nsMargin& aPadding) const {
+ if (IsWidthDependent()) {
+ return false;
+ }
+
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ // Clamp negative calc() to 0.
+ aPadding.Side(side) = std::max(mPadding.Get(side).ToLength(), 0);
+ }
+ return true;
+ }
+};
+
+// Border widths are rounded to the nearest-below integer number of pixels,
+// but values between zero and one device pixels are always rounded up to
+// one device pixel.
+#define NS_ROUND_BORDER_TO_PIXELS(l, tpp) \
+ ((l) == 0) ? 0 : std::max((tpp), (l) / (tpp) * (tpp))
+
+// Returns if the given border style type is visible or not
+static bool IsVisibleBorderStyle(mozilla::StyleBorderStyle aStyle) {
+ return (aStyle != mozilla::StyleBorderStyle::None &&
+ aStyle != mozilla::StyleBorderStyle::Hidden);
+}
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleBorder {
+ STYLE_STRUCT(nsStyleBorder)
+ nsStyleBorder();
+ void TriggerImageLoads(mozilla::dom::Document&, const nsStyleBorder*);
+
+ // Return whether aStyle is a visible style. Invisible styles cause
+ // the relevant computed border width to be 0.
+ // Note that this does *not* consider the effects of 'border-image':
+ // if border-style is none, but there is a loaded border image,
+ // HasVisibleStyle will be false even though there *is* a border.
+ bool HasVisibleStyle(mozilla::Side aSide) const {
+ return IsVisibleBorderStyle(mBorderStyle[aSide]);
+ }
+
+ // aBorderWidth is in twips
+ void SetBorderWidth(mozilla::Side aSide, nscoord aBorderWidth,
+ nscoord aAppUnitsPerDevPixel) {
+ nscoord roundedWidth =
+ NS_ROUND_BORDER_TO_PIXELS(aBorderWidth, aAppUnitsPerDevPixel);
+ mBorder.Side(aSide) = roundedWidth;
+ if (HasVisibleStyle(aSide)) {
+ mComputedBorder.Side(aSide) = roundedWidth;
+ }
+ }
+
+ // Get the computed border (plus rounding). This does consider the
+ // effects of 'border-style: none', but does not consider
+ // 'border-image'.
+ const nsMargin& GetComputedBorder() const { return mComputedBorder; }
+
+ bool HasBorder() const {
+ return mComputedBorder != nsMargin(0, 0, 0, 0) ||
+ !mBorderImageSource.IsNone();
+ }
+
+ // Get the actual border width for a particular side, in appunits. Note that
+ // this is zero if and only if there is no border to be painted for this
+ // side. That is, this value takes into account the border style and the
+ // value is rounded to the nearest device pixel by NS_ROUND_BORDER_TO_PIXELS.
+ nscoord GetComputedBorderWidth(mozilla::Side aSide) const {
+ return GetComputedBorder().Side(aSide);
+ }
+
+ mozilla::StyleBorderStyle GetBorderStyle(mozilla::Side aSide) const {
+ NS_ASSERTION(aSide <= mozilla::eSideLeft, "bad side");
+ return mBorderStyle[aSide];
+ }
+
+ void SetBorderStyle(mozilla::Side aSide, mozilla::StyleBorderStyle aStyle) {
+ NS_ASSERTION(aSide <= mozilla::eSideLeft, "bad side");
+ mBorderStyle[aSide] = aStyle;
+ mComputedBorder.Side(aSide) =
+ (HasVisibleStyle(aSide) ? mBorder.Side(aSide) : 0);
+ }
+
+ inline bool IsBorderImageSizeAvailable() const {
+ return mBorderImageSource.IsSizeAvailable();
+ }
+
+ nsMargin GetImageOutset() const;
+
+ imgIRequest* GetBorderImageRequest() const {
+ return mBorderImageSource.GetImageRequest();
+ }
+
+ public:
+ mozilla::StyleBorderRadius mBorderRadius; // coord, percent
+ mozilla::StyleImage mBorderImageSource;
+ mozilla::StyleBorderImageWidth mBorderImageWidth;
+ mozilla::StyleNonNegativeLengthOrNumberRect mBorderImageOutset;
+ mozilla::StyleBorderImageSlice mBorderImageSlice; // factor, percent
+ mozilla::StyleBorderImageRepeat mBorderImageRepeatH;
+ mozilla::StyleBorderImageRepeat mBorderImageRepeatV;
+ mozilla::StyleFloatEdge mFloatEdge;
+ mozilla::StyleBoxDecorationBreak mBoxDecorationBreak;
+
+ protected:
+ mozilla::StyleBorderStyle mBorderStyle[4]; // StyleBorderStyle::*
+
+ public:
+ // the colors to use for a simple border.
+ // not used for -moz-border-colors
+ mozilla::StyleColor mBorderTopColor;
+ mozilla::StyleColor mBorderRightColor;
+ mozilla::StyleColor mBorderBottomColor;
+ mozilla::StyleColor mBorderLeftColor;
+
+ mozilla::StyleColor& BorderColorFor(mozilla::Side aSide) {
+ switch (aSide) {
+ case mozilla::eSideTop:
+ return mBorderTopColor;
+ case mozilla::eSideRight:
+ return mBorderRightColor;
+ case mozilla::eSideBottom:
+ return mBorderBottomColor;
+ case mozilla::eSideLeft:
+ return mBorderLeftColor;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown side");
+ return mBorderTopColor;
+ }
+
+ const mozilla::StyleColor& BorderColorFor(mozilla::Side aSide) const {
+ switch (aSide) {
+ case mozilla::eSideTop:
+ return mBorderTopColor;
+ case mozilla::eSideRight:
+ return mBorderRightColor;
+ case mozilla::eSideBottom:
+ return mBorderBottomColor;
+ case mozilla::eSideLeft:
+ return mBorderLeftColor;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown side");
+ return mBorderTopColor;
+ }
+
+ static mozilla::StyleColor nsStyleBorder::*BorderColorFieldFor(
+ mozilla::Side aSide) {
+ switch (aSide) {
+ case mozilla::eSideTop:
+ return &nsStyleBorder::mBorderTopColor;
+ case mozilla::eSideRight:
+ return &nsStyleBorder::mBorderRightColor;
+ case mozilla::eSideBottom:
+ return &nsStyleBorder::mBorderBottomColor;
+ case mozilla::eSideLeft:
+ return &nsStyleBorder::mBorderLeftColor;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown side");
+ return nullptr;
+ }
+
+ nsStyleBorder& operator=(const nsStyleBorder&) = delete;
+
+ protected:
+ // mComputedBorder holds the CSS2.1 computed border-width values.
+ // In particular, these widths take into account the border-style
+ // for the relevant side, and the values are rounded to the nearest
+ // device pixel (which is not part of the definition of computed
+ // values). The presence or absence of a border-image does not
+ // affect border-width values.
+ nsMargin mComputedBorder;
+
+ // mBorder holds the nscoord values for the border widths as they
+ // would be if all the border-style values were visible (not hidden
+ // or none). This member exists so that when we create structs
+ // using the copy constructor during style resolution the new
+ // structs will know what the specified values of the border were in
+ // case they have more specific rules setting the border style.
+ //
+ // Note that this isn't quite the CSS specified value, since this
+ // has had the enumerated border widths converted to lengths, and
+ // all lengths converted to twips. But it's not quite the computed
+ // value either. The values are rounded to the nearest device pixel.
+ nsMargin mBorder;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleOutline {
+ STYLE_STRUCT(nsStyleOutline)
+ nsStyleOutline();
+
+ // This is the specified value of outline-width, but with length values
+ // computed to absolute. mActualOutlineWidth stores the outline-width
+ // value used by layout. (We must store mOutlineWidth for the same
+ // style struct resolution reasons that we do nsStyleBorder::mBorder;
+ // see that field's comment.)
+ nscoord mOutlineWidth;
+ mozilla::Length mOutlineOffset;
+ mozilla::StyleColor mOutlineColor;
+ mozilla::StyleOutlineStyle mOutlineStyle;
+
+ nscoord GetOutlineWidth() const { return mActualOutlineWidth; }
+
+ bool ShouldPaintOutline() const {
+ if (mOutlineStyle.IsAuto()) {
+ return true;
+ }
+ if (GetOutlineWidth() > 0) {
+ MOZ_ASSERT(
+ mOutlineStyle.AsBorderStyle() != mozilla::StyleBorderStyle::None,
+ "outline-style: none implies outline-width of zero");
+ return true;
+ }
+ return false;
+ }
+
+ nsSize EffectiveOffsetFor(const nsRect& aRect) const;
+
+ protected:
+ // The actual value of outline-width is the computed value (an absolute
+ // length, forced to zero when outline-style is none) rounded to device
+ // pixels. This is the value used by layout.
+ nscoord mActualOutlineWidth;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleList {
+ STYLE_STRUCT(nsStyleList)
+ nsStyleList();
+
+ void TriggerImageLoads(mozilla::dom::Document&, const nsStyleList*);
+
+ nsStyleList& operator=(const nsStyleList& aOther) = delete;
+ nsChangeHint CalcDifference(const nsStyleList& aNewData,
+ const mozilla::ComputedStyle& aOldStyle) const;
+
+ already_AddRefed<nsIURI> GetListStyleImageURI() const;
+
+ mozilla::StyleListStylePosition mListStylePosition;
+
+ mozilla::CounterStylePtr mCounterStyle;
+ mozilla::StyleQuotes mQuotes;
+ mozilla::StyleImage mListStyleImage;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePage {
+ STYLE_STRUCT(nsStylePage)
+ MOZ_COUNTED_DEFAULT_CTOR(nsStylePage)
+
+ using StylePageOrientation = mozilla::StylePageOrientation;
+ using StylePageSize = mozilla::StylePageSize;
+ using StylePageName = mozilla::StylePageName;
+
+ // page-size property.
+ StylePageSize mSize = StylePageSize::Auto();
+ // page-name property.
+ StylePageName mPage = StylePageName::Auto();
+ // page-orientation property.
+ StylePageOrientation mPageOrientation = StylePageOrientation::Upright;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePosition {
+ STYLE_STRUCT(nsStylePosition)
+ nsStylePosition();
+
+ using LengthPercentageOrAuto = mozilla::LengthPercentageOrAuto;
+ using Position = mozilla::Position;
+ template <typename T>
+ using StyleRect = mozilla::StyleRect<T>;
+ using StyleSize = mozilla::StyleSize;
+ using StyleMaxSize = mozilla::StyleMaxSize;
+ using WritingMode = mozilla::WritingMode;
+ using LogicalAxis = mozilla::LogicalAxis;
+ using StyleImplicitGridTracks = mozilla::StyleImplicitGridTracks;
+ using ComputedStyle = mozilla::ComputedStyle;
+ using StyleAlignSelf = mozilla::StyleAlignSelf;
+ using StyleJustifySelf = mozilla::StyleJustifySelf;
+
+ nsChangeHint CalcDifference(const nsStylePosition& aNewData,
+ const ComputedStyle& aOldStyle) const;
+
+ // Returns whether we need to compute an hypothetical position if we were
+ // absolutely positioned.
+ bool NeedsHypotheticalPositionIfAbsPos() const {
+ return (mOffset.Get(mozilla::eSideRight).IsAuto() &&
+ mOffset.Get(mozilla::eSideLeft).IsAuto()) ||
+ (mOffset.Get(mozilla::eSideTop).IsAuto() &&
+ mOffset.Get(mozilla::eSideBottom).IsAuto());
+ }
+
+ const mozilla::StyleContainIntrinsicSize& ContainIntrinsicBSize(
+ const WritingMode& aWM) const;
+ const mozilla::StyleContainIntrinsicSize& ContainIntrinsicISize(
+ const WritingMode& aWM) const;
+
+ /**
+ * Return the used value for 'align-self' given our parent ComputedStyle
+ * (or null for the root).
+ */
+ StyleAlignSelf UsedAlignSelf(const ComputedStyle*) const;
+
+ /**
+ * Return the used value for 'justify-self' given our parent ComputedStyle
+ * aParent (or null for the root).
+ */
+ StyleJustifySelf UsedJustifySelf(const ComputedStyle*) const;
+
+ /**
+ * Return the used value for 'justify/align-self' in aAxis given our parent
+ * ComputedStyle aParent (or null for the root).
+ * (defined in WritingModes.h since we need the full WritingMode type)
+ */
+ inline mozilla::StyleAlignFlags UsedSelfAlignment(
+ LogicalAxis aAxis, const mozilla::ComputedStyle* aParent) const;
+
+ /**
+ * Return the used value for 'justify/align-content' in aAxis.
+ * (defined in WritingModes.h since we need the full WritingMode type)
+ */
+ inline mozilla::StyleContentDistribution UsedContentAlignment(
+ LogicalAxis aAxis) const;
+
+ /**
+ * Return the used value for 'align-tracks'/'justify-tracks' for a track
+ * in the given axis.
+ * (defined in WritingModes.h since we need the full LogicalAxis type)
+ */
+ inline mozilla::StyleContentDistribution UsedTracksAlignment(
+ LogicalAxis aAxis, uint32_t aIndex) const;
+
+ // Each entry has the same encoding as *-content, see below.
+ mozilla::StyleAlignTracks mAlignTracks;
+ mozilla::StyleJustifyTracks mJustifyTracks;
+
+ Position mObjectPosition;
+ StyleRect<LengthPercentageOrAuto> mOffset;
+ StyleSize mWidth;
+ StyleSize mMinWidth;
+ StyleMaxSize mMaxWidth;
+ StyleSize mHeight;
+ StyleSize mMinHeight;
+ StyleMaxSize mMaxHeight;
+ mozilla::StyleFlexBasis mFlexBasis;
+ StyleImplicitGridTracks mGridAutoColumns;
+ StyleImplicitGridTracks mGridAutoRows;
+ mozilla::StyleAspectRatio mAspectRatio;
+ mozilla::StyleGridAutoFlow mGridAutoFlow;
+ mozilla::StyleMasonryAutoFlow mMasonryAutoFlow;
+
+ mozilla::StyleAlignContent mAlignContent;
+ mozilla::StyleAlignItems mAlignItems;
+ mozilla::StyleAlignSelf mAlignSelf;
+ mozilla::StyleJustifyContent mJustifyContent;
+ mozilla::StyleComputedJustifyItems mJustifyItems;
+ mozilla::StyleJustifySelf mJustifySelf;
+ mozilla::StyleFlexDirection mFlexDirection;
+ mozilla::StyleFlexWrap mFlexWrap;
+ mozilla::StyleObjectFit mObjectFit;
+ mozilla::StyleBoxSizing mBoxSizing;
+ int32_t mOrder;
+ float mFlexGrow;
+ float mFlexShrink;
+ mozilla::StyleZIndex mZIndex;
+
+ mozilla::StyleGridTemplateComponent mGridTemplateColumns;
+ mozilla::StyleGridTemplateComponent mGridTemplateRows;
+ mozilla::StyleGridTemplateAreas mGridTemplateAreas;
+
+ mozilla::StyleGridLine mGridColumnStart;
+ mozilla::StyleGridLine mGridColumnEnd;
+ mozilla::StyleGridLine mGridRowStart;
+ mozilla::StyleGridLine mGridRowEnd;
+ mozilla::NonNegativeLengthPercentageOrNormal mColumnGap;
+ mozilla::NonNegativeLengthPercentageOrNormal mRowGap;
+
+ mozilla::StyleContainIntrinsicSize mContainIntrinsicWidth;
+ mozilla::StyleContainIntrinsicSize mContainIntrinsicHeight;
+
+ // Logical-coordinate accessors for width and height properties,
+ // given a WritingMode value. The definitions of these methods are
+ // found in WritingModes.h (after the WritingMode class is fully
+ // declared).
+ inline const StyleSize& ISize(WritingMode) const;
+ inline const StyleSize& MinISize(WritingMode) const;
+ inline const StyleMaxSize& MaxISize(WritingMode) const;
+ inline const StyleSize& BSize(WritingMode) const;
+ inline const StyleSize& MinBSize(WritingMode) const;
+ inline const StyleMaxSize& MaxBSize(WritingMode) const;
+ inline const StyleSize& Size(LogicalAxis, WritingMode) const;
+ inline const StyleSize& MinSize(LogicalAxis, WritingMode) const;
+ inline const StyleMaxSize& MaxSize(LogicalAxis, WritingMode) const;
+ inline bool ISizeDependsOnContainer(WritingMode) const;
+ inline bool MinISizeDependsOnContainer(WritingMode) const;
+ inline bool MaxISizeDependsOnContainer(WritingMode) const;
+ inline bool BSizeDependsOnContainer(WritingMode) const;
+ inline bool MinBSizeDependsOnContainer(WritingMode) const;
+ inline bool MaxBSizeDependsOnContainer(WritingMode) const;
+
+ private:
+ template <typename SizeOrMaxSize>
+ static bool ISizeCoordDependsOnContainer(const SizeOrMaxSize& aCoord) {
+ if (aCoord.IsLengthPercentage()) {
+ return aCoord.AsLengthPercentage().HasPercent();
+ }
+ return aCoord.IsFitContent() || aCoord.IsMozAvailable();
+ }
+
+ template <typename SizeOrMaxSize>
+ static bool BSizeCoordDependsOnContainer(const SizeOrMaxSize& aCoord) {
+ return aCoord.IsLengthPercentage() &&
+ aCoord.AsLengthPercentage().HasPercent();
+ }
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTextReset {
+ STYLE_STRUCT(nsStyleTextReset)
+ nsStyleTextReset();
+
+ // Note the difference between this and
+ // ComputedStyle::HasTextDecorationLines.
+ bool HasTextDecorationLines() const {
+ return mTextDecorationLine != mozilla::StyleTextDecorationLine::NONE &&
+ mTextDecorationLine !=
+ mozilla::StyleTextDecorationLine::COLOR_OVERRIDE;
+ }
+
+ mozilla::StyleTextOverflow mTextOverflow;
+
+ mozilla::StyleTextDecorationLine mTextDecorationLine;
+ mozilla::StyleTextDecorationStyle mTextDecorationStyle;
+ mozilla::StyleUnicodeBidi mUnicodeBidi;
+ nscoord mInitialLetterSink; // 0 means normal
+ float mInitialLetterSize; // 0.0f means normal
+ mozilla::StyleColor mTextDecorationColor;
+ mozilla::StyleTextDecorationLength mTextDecorationThickness;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleText {
+ STYLE_STRUCT(nsStyleText)
+ explicit nsStyleText(const mozilla::dom::Document&);
+
+ mozilla::StyleAbsoluteColor mColor;
+ mozilla::StyleForcedColorAdjust mForcedColorAdjust;
+ mozilla::StyleTextTransform mTextTransform;
+ mozilla::StyleTextAlign mTextAlign;
+ mozilla::StyleTextAlignLast mTextAlignLast;
+ mozilla::StyleTextJustify mTextJustify;
+ mozilla::StyleWhiteSpaceCollapse mWhiteSpaceCollapse =
+ mozilla::StyleWhiteSpaceCollapse::Collapse;
+ mozilla::StyleTextWrapMode mTextWrapMode = mozilla::StyleTextWrapMode::Wrap;
+ mozilla::StyleLineBreak mLineBreak = mozilla::StyleLineBreak::Auto;
+
+ private:
+ mozilla::StyleWordBreak mWordBreak = mozilla::StyleWordBreak::Normal;
+ mozilla::StyleOverflowWrap mOverflowWrap = mozilla::StyleOverflowWrap::Normal;
+
+ public:
+ mozilla::StyleHyphens mHyphens;
+ mozilla::StyleRubyAlign mRubyAlign;
+ mozilla::StyleRubyPosition mRubyPosition;
+ mozilla::StyleTextSizeAdjust mTextSizeAdjust;
+ mozilla::StyleTextCombineUpright mTextCombineUpright;
+ mozilla::StyleMozControlCharacterVisibility mMozControlCharacterVisibility;
+ mozilla::StyleTextEmphasisPosition mTextEmphasisPosition;
+ mozilla::StyleTextRendering mTextRendering;
+ mozilla::StyleColor mTextEmphasisColor;
+ mozilla::StyleColor mWebkitTextFillColor;
+ mozilla::StyleColor mWebkitTextStrokeColor;
+
+ mozilla::StyleNonNegativeLengthOrNumber mTabSize;
+ mozilla::LengthPercentage mWordSpacing;
+ mozilla::StyleLetterSpacing mLetterSpacing;
+ mozilla::StyleTextIndent mTextIndent;
+
+ mozilla::LengthPercentageOrAuto mTextUnderlineOffset;
+ mozilla::StyleTextDecorationSkipInk mTextDecorationSkipInk;
+ mozilla::StyleTextUnderlinePosition mTextUnderlinePosition;
+
+ mozilla::StyleAu mWebkitTextStrokeWidth;
+
+ mozilla::StyleArcSlice<mozilla::StyleSimpleShadow> mTextShadow;
+ mozilla::StyleTextEmphasisStyle mTextEmphasisStyle;
+
+ mozilla::StyleHyphenateCharacter mHyphenateCharacter =
+ mozilla::StyleHyphenateCharacter::Auto();
+
+ mozilla::StyleTextSecurity mWebkitTextSecurity =
+ mozilla::StyleTextSecurity::None;
+
+ mozilla::StyleTextWrapStyle mTextWrapStyle =
+ mozilla::StyleTextWrapStyle::Auto;
+
+ char16_t TextSecurityMaskChar() const {
+ switch (mWebkitTextSecurity) {
+ case mozilla::StyleTextSecurity::None:
+ return 0;
+ case mozilla::StyleTextSecurity::Circle:
+ return 0x25E6;
+ case mozilla::StyleTextSecurity::Disc:
+ return 0x2022;
+ case mozilla::StyleTextSecurity::Square:
+ return 0x25A0;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown StyleTextSecurity value!");
+ return 0;
+ }
+ }
+
+ mozilla::StyleWordBreak EffectiveWordBreak() const {
+ if (mWordBreak == mozilla::StyleWordBreak::BreakWord) {
+ return mozilla::StyleWordBreak::Normal;
+ }
+ return mWordBreak;
+ }
+
+ mozilla::StyleOverflowWrap EffectiveOverflowWrap() const {
+ if (mWordBreak == mozilla::StyleWordBreak::BreakWord) {
+ return mozilla::StyleOverflowWrap::Anywhere;
+ }
+ return mOverflowWrap;
+ }
+
+ bool WhiteSpaceIsSignificant() const {
+ return mWhiteSpaceCollapse != mozilla::StyleWhiteSpaceCollapse::Collapse &&
+ mWhiteSpaceCollapse !=
+ mozilla::StyleWhiteSpaceCollapse::PreserveBreaks;
+ }
+
+ bool WhiteSpaceCanHangOrVisuallyCollapse() const {
+ // This was originally expressed in nsTextFrame in terms of:
+ // mWhiteSpace != StyleWhiteSpace::BreakSpaces &&
+ // WhiteSpaceCanWrapStyle() &&
+ // WhiteSpaceIsSignificant()
+ // which simplifies to:
+ return mTextWrapMode == mozilla::StyleTextWrapMode::Wrap &&
+ mWhiteSpaceCollapse != mozilla::StyleWhiteSpaceCollapse::BreakSpaces;
+ }
+
+ bool NewlineIsSignificantStyle() const {
+ return mWhiteSpaceCollapse == mozilla::StyleWhiteSpaceCollapse::Preserve ||
+ mWhiteSpaceCollapse ==
+ mozilla::StyleWhiteSpaceCollapse::PreserveBreaks ||
+ mWhiteSpaceCollapse == mozilla::StyleWhiteSpaceCollapse::BreakSpaces;
+ }
+
+ bool WhiteSpaceOrNewlineIsSignificant() const {
+ return NewlineIsSignificantStyle() || WhiteSpaceIsSignificant();
+ }
+
+ bool TabIsSignificant() const {
+ return mWhiteSpaceCollapse == mozilla::StyleWhiteSpaceCollapse::Preserve ||
+ mWhiteSpaceCollapse == mozilla::StyleWhiteSpaceCollapse::BreakSpaces;
+ }
+
+ bool WhiteSpaceCanWrapStyle() const {
+ return mTextWrapMode == mozilla::StyleTextWrapMode::Wrap;
+ }
+
+ bool WordCanWrapStyle() const {
+ if (!WhiteSpaceCanWrapStyle()) {
+ return false;
+ }
+ auto owrap = EffectiveOverflowWrap();
+ return owrap == mozilla::StyleOverflowWrap::BreakWord ||
+ owrap == mozilla::StyleOverflowWrap::Anywhere;
+ }
+
+ bool HasEffectiveTextEmphasis() const {
+ if (mTextEmphasisStyle.IsNone()) {
+ return false;
+ }
+ if (mTextEmphasisStyle.IsString() &&
+ mTextEmphasisStyle.AsString().AsString().IsEmpty()) {
+ return false;
+ }
+ return true;
+ }
+
+ mozilla::StyleTextAlign TextAlignForLastLine() const {
+ switch (mTextAlignLast) {
+ case mozilla::StyleTextAlignLast::Auto:
+ // 'text-align-last: auto' is equivalent to the value of the
+ // 'text-align' property except when 'text-align' is set to 'justify',
+ // in which case it is 'justify' when 'text-justify' is 'distribute' and
+ // 'start' otherwise.
+ //
+ // XXX: the code below will have to change when we implement
+ // text-justify
+ if (mTextAlign == mozilla::StyleTextAlign::Justify) {
+ return mozilla::StyleTextAlign::Start;
+ }
+ return mTextAlign;
+ case mozilla::StyleTextAlignLast::Center:
+ return mozilla::StyleTextAlign::Center;
+ case mozilla::StyleTextAlignLast::Start:
+ return mozilla::StyleTextAlign::Start;
+ case mozilla::StyleTextAlignLast::End:
+ return mozilla::StyleTextAlign::End;
+ case mozilla::StyleTextAlignLast::Left:
+ return mozilla::StyleTextAlign::Left;
+ case mozilla::StyleTextAlignLast::Right:
+ return mozilla::StyleTextAlign::Right;
+ case mozilla::StyleTextAlignLast::Justify:
+ return mozilla::StyleTextAlign::Justify;
+ }
+ return mozilla::StyleTextAlign::Start;
+ }
+
+ bool HasWebkitTextStroke() const { return mWebkitTextStrokeWidth > 0; }
+
+ bool HasTextShadow() const { return !mTextShadow.IsEmpty(); }
+
+ // The aContextFrame argument on each of these is the frame this
+ // style struct is for. If the frame is for SVG text or inside ruby,
+ // the return value will be massaged to be something that makes sense
+ // for those cases.
+ inline bool NewlineIsSignificant(const nsTextFrame* aContextFrame) const;
+ inline bool WhiteSpaceCanWrap(const nsIFrame* aContextFrame) const;
+ inline bool WordCanWrap(const nsIFrame* aContextFrame) const;
+
+ mozilla::LogicalSide TextEmphasisSide(mozilla::WritingMode aWM) const;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleVisibility {
+ STYLE_STRUCT(nsStyleVisibility)
+ explicit nsStyleVisibility(const mozilla::dom::Document&);
+
+ bool IsVisible() const {
+ return mVisible == mozilla::StyleVisibility::Visible;
+ }
+
+ bool IsCollapse() const {
+ return mVisible == mozilla::StyleVisibility::Collapse;
+ }
+
+ bool IsVisibleOrCollapsed() const {
+ return mVisible == mozilla::StyleVisibility::Visible ||
+ mVisible == mozilla::StyleVisibility::Collapse;
+ }
+
+ bool UseLegacyCollapseBehavior() const {
+ return mMozBoxCollapse == mozilla::StyleMozBoxCollapse::Legacy;
+ }
+
+ /**
+ * Given an image request, returns the orientation that should be used
+ * on the image. The returned orientation may differ from the style
+ * struct's orientation member value, if the image request is not of the
+ * same origin.
+ *
+ * @param aRequest The image request used to determine if same origin.
+ */
+ mozilla::StyleImageOrientation UsedImageOrientation(
+ imgIRequest* aRequest) const {
+ return UsedImageOrientation(aRequest, mImageOrientation);
+ }
+
+ /**
+ * Given an image request and an orientation, returns the orientation
+ * that should be used on the image. The returned orientation may differ
+ * from the input orientation if the image request is not of the same
+ * origin.
+ *
+ * @param aRequest The image request used to determine if same origin.
+ * @param aOrientation The input orientation.
+ */
+ static mozilla::StyleImageOrientation UsedImageOrientation(
+ imgIRequest* aRequest, mozilla::StyleImageOrientation aOrientation);
+
+ mozilla::StyleDirection mDirection;
+ mozilla::StyleVisibility mVisible;
+ mozilla::StyleImageRendering mImageRendering;
+ mozilla::StyleWritingModeProperty mWritingMode;
+ mozilla::StyleTextOrientation mTextOrientation;
+ mozilla::StyleMozBoxCollapse mMozBoxCollapse;
+ mozilla::StylePrintColorAdjust mPrintColorAdjust;
+
+ private:
+ mozilla::StyleImageOrientation mImageOrientation;
+};
+
+namespace mozilla {
+
+inline StyleTextTransform StyleTextTransform::None() {
+ return StyleTextTransform{StyleTextTransformCase::None,
+ StyleTextTransformOther()};
+}
+
+inline bool StyleTextTransform::IsNone() const { return *this == None(); }
+
+// Note that IsAuto() does not exclude the possibility that `left` or `right`
+// is set; it refers only to behavior in horizontal typographic mode.
+inline bool StyleTextUnderlinePosition::IsAuto() const {
+ return !(*this & (StyleTextUnderlinePosition::FROM_FONT |
+ StyleTextUnderlinePosition::UNDER));
+}
+inline bool StyleTextUnderlinePosition::IsFromFont() const {
+ return bool(*this & StyleTextUnderlinePosition::FROM_FONT);
+}
+inline bool StyleTextUnderlinePosition::IsUnder() const {
+ return bool(*this & StyleTextUnderlinePosition::UNDER);
+}
+inline bool StyleTextUnderlinePosition::IsLeft() const {
+ return bool(*this & StyleTextUnderlinePosition::LEFT);
+}
+inline bool StyleTextUnderlinePosition::IsRight() const {
+ return bool(*this & StyleTextUnderlinePosition::RIGHT);
+}
+
+struct StyleTransition {
+ StyleTransition() = default;
+ explicit StyleTransition(const StyleTransition& aCopy);
+ const StyleComputedTimingFunction& GetTimingFunction() const {
+ return mTimingFunction;
+ }
+ const StyleTime& GetDelay() const { return mDelay; }
+ const StyleTime& GetDuration() const { return mDuration; }
+ const StyleTransitionProperty& GetProperty() const { return mProperty; }
+
+ bool operator==(const StyleTransition& aOther) const;
+ bool operator!=(const StyleTransition& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ StyleComputedTimingFunction mTimingFunction{
+ StyleComputedTimingFunction::Keyword(StyleTimingKeyword::Ease)};
+ StyleTime mDuration{0.0};
+ StyleTime mDelay{0.0};
+ StyleTransitionProperty mProperty{StyleTransitionProperty::NonCustom(
+ StyleNonCustomPropertyId{uint16_t(eCSSProperty_all)})};
+};
+
+struct StyleAnimation {
+ StyleAnimation() = default;
+ explicit StyleAnimation(const StyleAnimation& aCopy);
+
+ const StyleComputedTimingFunction& GetTimingFunction() const {
+ return mTimingFunction;
+ }
+ const StyleTime& GetDelay() const { return mDelay; }
+ const StyleTime& GetDuration() const { return mDuration; }
+ nsAtom* GetName() const { return mName._0.AsAtom(); }
+ StyleAnimationDirection GetDirection() const { return mDirection; }
+ StyleAnimationFillMode GetFillMode() const { return mFillMode; }
+ StyleAnimationPlayState GetPlayState() const { return mPlayState; }
+ float GetIterationCount() const { return mIterationCount._0; }
+ StyleAnimationComposition GetComposition() const { return mComposition; }
+ const StyleAnimationTimeline& GetTimeline() const { return mTimeline; }
+
+ bool operator==(const StyleAnimation& aOther) const;
+ bool operator!=(const StyleAnimation& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ StyleComputedTimingFunction mTimingFunction{
+ StyleComputedTimingFunction::Keyword(StyleTimingKeyword::Ease)};
+ StyleTime mDuration{0.0f};
+ StyleTime mDelay{0.0f};
+ StyleAnimationName mName;
+ StyleAnimationDirection mDirection = StyleAnimationDirection::Normal;
+ StyleAnimationFillMode mFillMode = StyleAnimationFillMode::None;
+ StyleAnimationPlayState mPlayState = StyleAnimationPlayState::Running;
+ StyleAnimationIterationCount mIterationCount{1.0f};
+ StyleAnimationComposition mComposition = StyleAnimationComposition::Replace;
+ StyleAnimationTimeline mTimeline{StyleAnimationTimeline::Auto()};
+};
+
+struct StyleScrollTimeline {
+ StyleScrollTimeline() = default;
+ explicit StyleScrollTimeline(const StyleScrollTimeline& aCopy) = default;
+
+ nsAtom* GetName() const { return mName._0.AsAtom(); }
+ StyleScrollAxis GetAxis() const { return mAxis; }
+
+ bool operator==(const StyleScrollTimeline& aOther) const {
+ return mName == aOther.mName && mAxis == aOther.mAxis;
+ }
+ bool operator!=(const StyleScrollTimeline& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ StyleScrollTimelineName mName;
+ StyleScrollAxis mAxis = StyleScrollAxis::Block;
+};
+
+struct StyleViewTimeline {
+ StyleViewTimeline() = default;
+ explicit StyleViewTimeline(const StyleViewTimeline& aCopy) = default;
+
+ nsAtom* GetName() const { return mName._0.AsAtom(); }
+ StyleScrollAxis GetAxis() const { return mAxis; }
+ const StyleViewTimelineInset& GetInset() const { return mInset; }
+
+ bool operator==(const StyleViewTimeline& aOther) const {
+ return mName == aOther.mName && mAxis == aOther.mAxis &&
+ mInset == aOther.mInset;
+ }
+ bool operator!=(const StyleViewTimeline& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ StyleScrollTimelineName mName;
+ StyleScrollAxis mAxis = StyleScrollAxis::Block;
+ StyleViewTimelineInset mInset;
+};
+
+} // namespace mozilla
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay {
+ STYLE_STRUCT(nsStyleDisplay)
+ nsStyleDisplay();
+ void TriggerImageLoads(mozilla::dom::Document&, const nsStyleDisplay*);
+
+ using StyleContain = mozilla::StyleContain;
+ using StyleContentVisibility = mozilla::StyleContentVisibility;
+
+ nsChangeHint CalcDifference(const nsStyleDisplay& aNewData,
+ const mozilla::ComputedStyle& aOldStyle) const;
+
+ nsChangeHint CalcTransformPropertyDifference(
+ const nsStyleDisplay& aNewData) const;
+
+ mozilla::StyleDisplay mDisplay;
+ // Saved mDisplay for position:absolute/fixed and float:left/right; otherwise
+ // equal to mDisplay.
+ mozilla::StyleDisplay mOriginalDisplay;
+ // Equal to mContain plus any implicit containment from mContentVisibility and
+ // mContainerType.
+ mozilla::StyleContentVisibility mContentVisibility;
+ mozilla::StyleContainerType mContainerType;
+
+ bool IsQueryContainer() const {
+ return mContainerType != mozilla::StyleContainerType::Normal;
+ }
+
+ private:
+ mozilla::StyleAppearance mAppearance;
+ mozilla::StyleContain mContain;
+ // Equal to mContain plus any implicit containment from mContentVisibility and
+ // mContainerType.
+ mozilla::StyleContain mEffectiveContainment;
+
+ public:
+ mozilla::StyleAppearance mDefaultAppearance;
+ mozilla::StylePositionProperty mPosition;
+
+ mozilla::StyleFloat mFloat;
+ mozilla::StyleClear mClear;
+ mozilla::StyleBreakWithin mBreakInside;
+ mozilla::StyleBreakBetween mBreakBefore;
+ mozilla::StyleBreakBetween mBreakAfter;
+ mozilla::StyleOverflow mOverflowX;
+ mozilla::StyleOverflow mOverflowY;
+ mozilla::StyleOverflowClipBox mOverflowClipBoxBlock;
+ mozilla::StyleOverflowClipBox mOverflowClipBoxInline;
+ mozilla::StyleScrollbarGutter mScrollbarGutter;
+ mozilla::StyleResize mResize;
+ mozilla::StyleOrient mOrient;
+ mozilla::StyleIsolation mIsolation;
+ mozilla::StyleTopLayer mTopLayer;
+
+ mozilla::StyleTouchAction mTouchAction;
+ mozilla::StyleScrollBehavior mScrollBehavior;
+ mozilla::StyleOverscrollBehavior mOverscrollBehaviorX;
+ mozilla::StyleOverscrollBehavior mOverscrollBehaviorY;
+ mozilla::StyleOverflowAnchor mOverflowAnchor;
+ mozilla::StyleScrollSnapAlign mScrollSnapAlign;
+ mozilla::StyleScrollSnapStop mScrollSnapStop;
+ mozilla::StyleScrollSnapType mScrollSnapType;
+
+ mozilla::StyleBackfaceVisibility mBackfaceVisibility;
+ mozilla::StyleTransformStyle mTransformStyle;
+ mozilla::StyleTransformBox mTransformBox;
+
+ mozilla::StyleTransform mTransform;
+ mozilla::StyleRotate mRotate;
+
+ mozilla::StyleTranslate mTranslate;
+ mozilla::StyleScale mScale;
+
+ mozilla::StyleContainerName mContainerName;
+ mozilla::StyleWillChange mWillChange;
+
+ mozilla::StyleOffsetPath mOffsetPath;
+ mozilla::LengthPercentage mOffsetDistance;
+ mozilla::StyleOffsetRotate mOffsetRotate;
+ mozilla::StylePositionOrAuto mOffsetAnchor;
+ mozilla::StyleOffsetPosition mOffsetPosition;
+
+ mozilla::StyleTransformOrigin mTransformOrigin;
+ mozilla::StylePerspective mChildPerspective;
+ mozilla::Position mPerspectiveOrigin;
+
+ mozilla::StyleVerticalAlign mVerticalAlign;
+ mozilla::StyleBaselineSource mBaselineSource;
+
+ mozilla::StyleLineClamp mWebkitLineClamp;
+
+ // The threshold used for extracting a shape from shape-outside: <image>.
+ float mShapeImageThreshold = 0.0f;
+
+ mozilla::StyleZoom mZoom = mozilla::StyleZoom::ONE;
+
+ // The margin around a shape-outside: <image>.
+ mozilla::NonNegativeLengthPercentage mShapeMargin;
+
+ mozilla::StyleShapeOutside mShapeOutside;
+
+ mozilla::Maybe<mozilla::WindowButtonType> GetWindowButtonType() const {
+ if (MOZ_LIKELY(mDefaultAppearance == mozilla::StyleAppearance::None)) {
+ return mozilla::Nothing();
+ }
+ switch (mDefaultAppearance) {
+ case mozilla::StyleAppearance::MozWindowButtonMaximize:
+ case mozilla::StyleAppearance::MozWindowButtonRestore:
+ return Some(mozilla::WindowButtonType::Maximize);
+ case mozilla::StyleAppearance::MozWindowButtonMinimize:
+ return Some(mozilla::WindowButtonType::Minimize);
+ case mozilla::StyleAppearance::MozWindowButtonClose:
+ return Some(mozilla::WindowButtonType::Close);
+ default:
+ return mozilla::Nothing();
+ }
+ }
+
+ bool HasAppearance() const {
+ return EffectiveAppearance() != mozilla::StyleAppearance::None;
+ }
+
+ mozilla::StyleAppearance EffectiveAppearance() const {
+ if (MOZ_LIKELY(mAppearance == mozilla::StyleAppearance::None)) {
+ return mAppearance;
+ }
+ switch (mAppearance) {
+ case mozilla::StyleAppearance::Auto:
+ case mozilla::StyleAppearance::Button:
+ case mozilla::StyleAppearance::Searchfield:
+ case mozilla::StyleAppearance::Textarea:
+ case mozilla::StyleAppearance::Checkbox:
+ case mozilla::StyleAppearance::Radio:
+ case mozilla::StyleAppearance::Menulist:
+ case mozilla::StyleAppearance::Listbox:
+ case mozilla::StyleAppearance::Meter:
+ case mozilla::StyleAppearance::ProgressBar:
+ // These are all the values that behave like `auto`.
+ return mDefaultAppearance;
+ case mozilla::StyleAppearance::Textfield:
+ // `appearance: textfield` should behave like `auto` on all elements
+ // except <input type=search> elements, which we identify using the
+ // internal -moz-default-appearance property. (In the browser chrome
+ // we have some other elements that set `-moz-default-appearance:
+ // searchfield`, but not in content documents.)
+ if (mDefaultAppearance == mozilla::StyleAppearance::Searchfield) {
+ return mAppearance;
+ }
+ // We also need to support `appearance: textfield` on <input
+ // type=number>, since that is the only way in Gecko to disable the
+ // spinners.
+ if (mDefaultAppearance == mozilla::StyleAppearance::NumberInput) {
+ return mAppearance;
+ }
+ return mDefaultAppearance;
+ case mozilla::StyleAppearance::MenulistButton:
+ // `appearance: menulist-button` should behave like `auto` on all
+ // elements except for drop down selects, but since we have very little
+ // difference between menulist and menulist-button handling, we don't
+ // bother.
+ return mDefaultAppearance;
+ default:
+ return mAppearance;
+ }
+ }
+
+ mozilla::StyleDisplayOutside DisplayOutside() const {
+ return mDisplay.Outside();
+ }
+ mozilla::StyleDisplayInside DisplayInside() const {
+ return mDisplay.Inside();
+ }
+ bool IsListItem() const { return mDisplay.IsListItem(); }
+ bool IsInlineFlow() const { return mDisplay.IsInlineFlow(); }
+
+ bool IsInlineInsideStyle() const { return mDisplay.IsInlineInside(); }
+
+ bool IsBlockOutsideStyle() const {
+ return DisplayOutside() == mozilla::StyleDisplayOutside::Block;
+ }
+
+ bool IsInlineOutsideStyle() const { return mDisplay.IsInlineOutside(); }
+
+ bool IsOriginalDisplayInlineOutside() const {
+ return mOriginalDisplay.IsInlineOutside();
+ }
+
+ bool IsInnerTableStyle() const { return mDisplay.IsInternalTable(); }
+
+ bool IsInternalTableStyleExceptCell() const {
+ return mDisplay.IsInternalTableExceptCell();
+ }
+
+ bool IsFloatingStyle() const { return mozilla::StyleFloat::None != mFloat; }
+
+ bool IsPositionedStyle() const {
+ return mPosition != mozilla::StylePositionProperty::Static ||
+ (mWillChange.bits & mozilla::StyleWillChangeBits::POSITION);
+ }
+
+ bool IsAbsolutelyPositionedStyle() const {
+ return mozilla::StylePositionProperty::Absolute == mPosition ||
+ mozilla::StylePositionProperty::Fixed == mPosition;
+ }
+
+ bool IsRelativelyOrStickyPositionedStyle() const {
+ return mozilla::StylePositionProperty::Relative == mPosition ||
+ mozilla::StylePositionProperty::Sticky == mPosition;
+ }
+ bool IsRelativelyPositionedStyle() const {
+ return mozilla::StylePositionProperty::Relative == mPosition;
+ }
+ bool IsStickyPositionedStyle() const {
+ return mozilla::StylePositionProperty::Sticky == mPosition;
+ }
+ bool IsPositionForcingStackingContext() const {
+ return mozilla::StylePositionProperty::Sticky == mPosition ||
+ mozilla::StylePositionProperty::Fixed == mPosition;
+ }
+
+ bool IsRubyDisplayType() const { return mDisplay.IsRuby(); }
+
+ bool IsInternalRubyDisplayType() const { return mDisplay.IsInternalRuby(); }
+
+ bool IsOutOfFlowStyle() const {
+ return (IsAbsolutelyPositionedStyle() || IsFloatingStyle());
+ }
+
+ bool IsScrollableOverflow() const {
+ // Visible and Clip can be combined but not with other values,
+ // so checking mOverflowX is enough.
+ return mOverflowX != mozilla::StyleOverflow::Visible &&
+ mOverflowX != mozilla::StyleOverflow::Clip;
+ }
+
+ bool OverflowIsVisibleInBothAxis() const {
+ return mOverflowX == mozilla::StyleOverflow::Visible &&
+ mOverflowY == mozilla::StyleOverflow::Visible;
+ }
+
+ bool IsContainPaint() const {
+ // Short circuit for no containment whatsoever
+ if (!mEffectiveContainment) {
+ return false;
+ }
+ return (mEffectiveContainment & StyleContain::PAINT) &&
+ !IsInternalRubyDisplayType() && !IsInternalTableStyleExceptCell();
+ }
+
+ bool IsContainLayout() const {
+ // Short circuit for no containment whatsoever
+ if (!mEffectiveContainment) {
+ return false;
+ }
+ // Note: The spec for layout containment says it should
+ // have no effect on non-atomic, inline-level boxes. We
+ // don't check for these here because we don't know
+ // what type of element is involved. Callers are
+ // responsible for checking if the box in question is
+ // non-atomic and inline-level, and creating an
+ // exemption as necessary.
+ return (mEffectiveContainment & StyleContain::LAYOUT) &&
+ !IsInternalRubyDisplayType() && !IsInternalTableStyleExceptCell();
+ }
+
+ bool IsContainStyle() const {
+ return !!(mEffectiveContainment & StyleContain::STYLE);
+ }
+
+ bool IsContainAny() const { return !!mEffectiveContainment; }
+
+ // This is similar to PrecludesSizeContainmentOrContentVisibility, but also
+ // takes into account whether or not the given frame is a non-atomic,
+ // inline-level box.
+ bool PrecludesSizeContainmentOrContentVisibilityWithFrame(
+ const nsIFrame&) const;
+
+ StyleContentVisibility ContentVisibility(const nsIFrame&) const;
+
+ /* Returns whether the element has the transform property or a related
+ * property. */
+ bool HasTransformStyle() const {
+ return HasTransformProperty() || HasIndividualTransform() ||
+ mTransformStyle == mozilla::StyleTransformStyle::Preserve3d ||
+ (mWillChange.bits & mozilla::StyleWillChangeBits::TRANSFORM) ||
+ !mOffsetPath.IsNone();
+ }
+
+ bool HasTransformProperty() const { return !mTransform._0.IsEmpty(); }
+
+ bool HasIndividualTransform() const {
+ return !mRotate.IsNone() || !mTranslate.IsNone() || !mScale.IsNone();
+ }
+
+ bool HasPerspectiveStyle() const { return !mChildPerspective.IsNone(); }
+
+ bool BackfaceIsHidden() const {
+ return mBackfaceVisibility == mozilla::StyleBackfaceVisibility::Hidden;
+ }
+
+ // FIXME(emilio): This should be more fine-grained on each caller to
+ // BreakBefore() / BreakAfter().
+ static bool ShouldBreak(mozilla::StyleBreakBetween aBreak) {
+ switch (aBreak) {
+ case mozilla::StyleBreakBetween::Left:
+ case mozilla::StyleBreakBetween::Right:
+ case mozilla::StyleBreakBetween::Page:
+ case mozilla::StyleBreakBetween::Always:
+ return true;
+ case mozilla::StyleBreakBetween::Auto:
+ case mozilla::StyleBreakBetween::Avoid:
+ return false;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown break kind");
+ return false;
+ }
+ }
+
+ bool BreakBefore() const { return ShouldBreak(mBreakBefore); }
+
+ bool BreakAfter() const { return ShouldBreak(mBreakAfter); }
+
+ // These are defined in nsStyleStructInlines.h.
+
+ // The aContextFrame argument on each of these is the frame this
+ // style struct is for. If the frame is for SVG text, the return
+ // value will be massaged to be something that makes sense for
+ // SVG text.
+ inline bool IsBlockOutside(const nsIFrame* aContextFrame) const;
+ inline bool IsInlineOutside(const nsIFrame* aContextFrame) const;
+ inline mozilla::StyleDisplay GetDisplay(const nsIFrame* aContextFrame) const;
+ inline bool IsFloating(const nsIFrame* aContextFrame) const;
+ inline bool IsRelativelyOrStickyPositioned(
+ const nsIFrame* aContextFrame) const;
+
+ // Note: In general, you'd want to call IsRelativelyOrStickyPositioned()
+ // unless you want to deal with "position:relative" and "position:sticky"
+ // differently.
+ inline bool IsRelativelyPositioned(const nsIFrame* aContextFrame) const;
+ inline bool IsStickyPositioned(const nsIFrame* aContextFrame) const;
+
+ inline bool IsAbsolutelyPositioned(const nsIFrame* aContextFrame) const;
+
+ // These methods are defined in nsStyleStructInlines.h.
+
+ /**
+ * Returns true when the element has the transform property
+ * or a related property, and supports CSS transforms.
+ * aContextFrame is the frame for which this is the nsStyleDisplay.
+ */
+ inline bool HasTransform(const nsIFrame* aContextFrame) const;
+
+ /**
+ * Returns true when the element has the perspective property,
+ * and supports CSS transforms. aContextFrame is the frame for
+ * which this is the nsStyleDisplay.
+ */
+ inline bool HasPerspective(const nsIFrame* aContextFrame) const;
+
+ inline bool
+ IsFixedPosContainingBlockForContainLayoutAndPaintSupportingFrames() const;
+ inline bool IsFixedPosContainingBlockForTransformSupportingFrames() const;
+
+ mozilla::ContainSizeAxes GetContainSizeAxes(const nsIFrame& aFrame) const;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTable {
+ STYLE_STRUCT(nsStyleTable)
+ nsStyleTable();
+
+ mozilla::StyleTableLayout mLayoutStrategy;
+ int32_t mXSpan; // The number of columns spanned by a colgroup or col
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTableBorder {
+ STYLE_STRUCT(nsStyleTableBorder)
+ nsStyleTableBorder();
+
+ nscoord mBorderSpacingCol;
+ nscoord mBorderSpacingRow;
+ mozilla::StyleBorderCollapse mBorderCollapse;
+ mozilla::StyleCaptionSide mCaptionSide;
+ mozilla::StyleEmptyCells mEmptyCells;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleContent {
+ STYLE_STRUCT(nsStyleContent)
+ nsStyleContent();
+ void TriggerImageLoads(mozilla::dom::Document&, const nsStyleContent*);
+
+ using CounterPair = mozilla::StyleGenericCounterPair<int32_t>;
+
+ size_t ContentCount() const {
+ return mContent.IsItems() ? mContent.AsItems().Length() : 0;
+ }
+
+ const mozilla::StyleContentItem& ContentAt(size_t aIndex) const {
+ return mContent.AsItems().AsSpan()[aIndex];
+ }
+
+ mozilla::StyleContent mContent;
+ mozilla::StyleCounterIncrement mCounterIncrement;
+ mozilla::StyleCounterReset mCounterReset;
+ mozilla::StyleCounterSet mCounterSet;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleUIReset {
+ STYLE_STRUCT(nsStyleUIReset)
+ nsStyleUIReset();
+
+ private:
+ mozilla::StyleUserSelect mUserSelect; // Use ComputedStyle::UserSelect()
+ mozilla::StyleScrollbarWidth mScrollbarWidth; // Use ScrollbarWidth()
+
+ public:
+ mozilla::StyleUserSelect ComputedUserSelect() const { return mUserSelect; }
+
+ mozilla::StyleScrollbarWidth ScrollbarWidth() const;
+
+ const mozilla::StyleTransitionProperty& GetTransitionProperty(
+ uint32_t aIndex) const {
+ return mTransitions[aIndex % mTransitionPropertyCount].GetProperty();
+ }
+ const mozilla::StyleTime& GetTransitionDelay(uint32_t aIndex) const {
+ return mTransitions[aIndex % mTransitionDelayCount].GetDelay();
+ }
+ const mozilla::StyleTime& GetTransitionDuration(uint32_t aIndex) const {
+ return mTransitions[aIndex % mTransitionDurationCount].GetDuration();
+ }
+ const mozilla::StyleComputedTimingFunction& GetTransitionTimingFunction(
+ uint32_t aIndex) const {
+ return mTransitions[aIndex % mTransitionTimingFunctionCount]
+ .GetTimingFunction();
+ }
+ mozilla::StyleTime GetTransitionCombinedDuration(uint32_t aIndex) const {
+ // https://drafts.csswg.org/css-transitions/#transition-combined-duration
+ return {std::max(GetTransitionDuration(aIndex).seconds, 0.0f) +
+ GetTransitionDelay(aIndex).seconds};
+ }
+
+ nsAtom* GetAnimationName(uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationNameCount].GetName();
+ }
+ const mozilla::StyleTime& GetAnimationDelay(uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationDelayCount].GetDelay();
+ }
+ const mozilla::StyleTime& GetAnimationDuration(uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationDurationCount].GetDuration();
+ }
+ mozilla::StyleAnimationDirection GetAnimationDirection(
+ uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationDirectionCount].GetDirection();
+ }
+ mozilla::StyleAnimationFillMode GetAnimationFillMode(uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationFillModeCount].GetFillMode();
+ }
+ mozilla::StyleAnimationPlayState GetAnimationPlayState(
+ uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationPlayStateCount].GetPlayState();
+ }
+ float GetAnimationIterationCount(uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationIterationCountCount]
+ .GetIterationCount();
+ }
+ const mozilla::StyleComputedTimingFunction& GetAnimationTimingFunction(
+ uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationTimingFunctionCount]
+ .GetTimingFunction();
+ }
+ mozilla::StyleAnimationComposition GetAnimationComposition(
+ uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationCompositionCount].GetComposition();
+ }
+ const mozilla::StyleAnimationTimeline& GetTimeline(uint32_t aIndex) const {
+ return mAnimations[aIndex % mAnimationTimelineCount].GetTimeline();
+ }
+
+ mozilla::StyleBoolInteger mMozForceBrokenImageIcon;
+ mozilla::StyleBoolInteger mMozSubtreeHiddenOnlyVisually;
+ mozilla::StyleImeMode mIMEMode;
+ mozilla::StyleWindowDragging mWindowDragging;
+ mozilla::StyleWindowShadow mWindowShadow;
+ float mWindowOpacity;
+ // The margin of the window region that should be transparent to events.
+ mozilla::StyleLength mMozWindowInputRegionMargin;
+ mozilla::StyleTransform mMozWindowTransform;
+ mozilla::StyleTransformOrigin mWindowTransformOrigin;
+
+ nsStyleAutoArray<mozilla::StyleTransition> mTransitions;
+ // The number of elements in mTransitions that are not from repeating
+ // a list due to another property being longer.
+ uint32_t mTransitionTimingFunctionCount;
+ uint32_t mTransitionDurationCount;
+ uint32_t mTransitionDelayCount;
+ uint32_t mTransitionPropertyCount;
+ nsStyleAutoArray<mozilla::StyleAnimation> mAnimations;
+ // The number of elements in mAnimations that are not from repeating
+ // a list due to another property being longer.
+ uint32_t mAnimationTimingFunctionCount;
+ uint32_t mAnimationDurationCount;
+ uint32_t mAnimationDelayCount;
+ uint32_t mAnimationNameCount;
+ uint32_t mAnimationDirectionCount;
+ uint32_t mAnimationFillModeCount;
+ uint32_t mAnimationPlayStateCount;
+ uint32_t mAnimationIterationCountCount;
+ uint32_t mAnimationCompositionCount;
+ uint32_t mAnimationTimelineCount;
+
+ nsStyleAutoArray<mozilla::StyleScrollTimeline> mScrollTimelines;
+ uint32_t mScrollTimelineNameCount;
+ uint32_t mScrollTimelineAxisCount;
+
+ nsStyleAutoArray<mozilla::StyleViewTimeline> mViewTimelines;
+ uint32_t mViewTimelineNameCount;
+ uint32_t mViewTimelineAxisCount;
+ uint32_t mViewTimelineInsetCount;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleUI {
+ STYLE_STRUCT(nsStyleUI)
+ nsStyleUI();
+ void TriggerImageLoads(mozilla::dom::Document&, const nsStyleUI*);
+
+ mozilla::StyleInert mInert;
+ mozilla::StyleMozTheme mMozTheme;
+
+ private:
+ mozilla::StyleUserInput mUserInput;
+ mozilla::StyleUserModify mUserModify;
+ mozilla::StyleUserFocus mUserFocus;
+ mozilla::StylePointerEvents mPointerEvents;
+ mozilla::StyleCursor mCursor;
+
+ public:
+ bool IsInert() const { return mInert == mozilla::StyleInert::Inert; }
+
+ mozilla::StyleUserInput UserInput() const {
+ return IsInert() ? mozilla::StyleUserInput::None : mUserInput;
+ }
+
+ mozilla::StyleUserModify UserModify() const {
+ return IsInert() ? mozilla::StyleUserModify::ReadOnly : mUserModify;
+ }
+
+ mozilla::StyleUserFocus UserFocus() const {
+ return IsInert() ? mozilla::StyleUserFocus::None : mUserFocus;
+ }
+
+ // This is likely not the getter you want (you probably want
+ // ComputedStyle::PointerEvents().
+ mozilla::StylePointerEvents ComputedPointerEvents() const {
+ return mPointerEvents;
+ }
+
+ const mozilla::StyleCursor& Cursor() const {
+ static mozilla::StyleCursor sAuto{{}, mozilla::StyleCursorKind::Auto};
+ return IsInert() ? sAuto : mCursor;
+ }
+
+ mozilla::StyleColorOrAuto mAccentColor;
+ mozilla::StyleCaretColor mCaretColor;
+ mozilla::StyleScrollbarColor mScrollbarColor;
+ mozilla::StyleColorScheme mColorScheme;
+
+ bool HasCustomScrollbars() const { return !mScrollbarColor.IsAuto(); }
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleXUL {
+ STYLE_STRUCT(nsStyleXUL)
+ nsStyleXUL();
+
+ float mBoxFlex;
+ int32_t mBoxOrdinal;
+ mozilla::StyleBoxAlign mBoxAlign;
+ mozilla::StyleBoxDirection mBoxDirection;
+ mozilla::StyleBoxOrient mBoxOrient;
+ mozilla::StyleBoxPack mBoxPack;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleColumn {
+ STYLE_STRUCT(nsStyleColumn)
+ nsStyleColumn();
+
+ // This is the maximum number of columns we can process. It's used in
+ // nsColumnSetFrame.
+ static const uint32_t kMaxColumnCount = 1000;
+
+ // This represents the value of column-count: auto.
+ static const uint32_t kColumnCountAuto = 0;
+
+ uint32_t mColumnCount = kColumnCountAuto;
+ mozilla::NonNegativeLengthOrAuto mColumnWidth;
+
+ mozilla::StyleColor mColumnRuleColor;
+ mozilla::StyleBorderStyle mColumnRuleStyle; // StyleborderStyle::*
+ mozilla::StyleColumnFill mColumnFill = mozilla::StyleColumnFill::Balance;
+ mozilla::StyleColumnSpan mColumnSpan = mozilla::StyleColumnSpan::None;
+
+ nscoord GetColumnRuleWidth() const { return mActualColumnRuleWidth; }
+
+ bool IsColumnContainerStyle() const {
+ return mColumnCount != kColumnCountAuto || !mColumnWidth.IsAuto();
+ }
+
+ bool IsColumnSpanStyle() const {
+ return mColumnSpan == mozilla::StyleColumnSpan::All;
+ }
+
+ protected:
+ // This is the specified value of column-rule-width, but with length values
+ // computed to absolute. mActualColumnRuleWidth stores the column-rule-width
+ // value used by layout. (We must store mColumnRuleWidth for the same
+ // style struct resolution reasons that we do nsStyleBorder::mBorder;
+ // see that field's comment.)
+ nscoord mColumnRuleWidth;
+ // The actual value of column-rule-width is the computed value (an absolute
+ // length, forced to zero when column-rule-style is none) rounded to device
+ // pixels. This is the value used by layout.
+ nscoord mActualColumnRuleWidth;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleSVG {
+ STYLE_STRUCT(nsStyleSVG)
+ nsStyleSVG();
+
+ mozilla::StyleSVGPaint mFill;
+ mozilla::StyleSVGPaint mStroke;
+ mozilla::StyleUrlOrNone mMarkerEnd;
+ mozilla::StyleUrlOrNone mMarkerMid;
+ mozilla::StyleUrlOrNone mMarkerStart;
+ mozilla::StyleMozContextProperties mMozContextProperties;
+
+ mozilla::StyleSVGStrokeDashArray mStrokeDasharray;
+ mozilla::StyleSVGLength mStrokeDashoffset;
+ mozilla::StyleSVGWidth mStrokeWidth;
+
+ mozilla::StyleSVGOpacity mFillOpacity;
+ float mStrokeMiterlimit;
+ mozilla::StyleSVGOpacity mStrokeOpacity;
+
+ mozilla::StyleFillRule mClipRule;
+ mozilla::StyleColorInterpolation mColorInterpolation;
+ mozilla::StyleColorInterpolation mColorInterpolationFilters;
+ mozilla::StyleFillRule mFillRule;
+ mozilla::StyleSVGPaintOrder mPaintOrder;
+ mozilla::StyleShapeRendering mShapeRendering;
+ mozilla::StyleStrokeLinecap mStrokeLinecap;
+ mozilla::StyleStrokeLinejoin mStrokeLinejoin;
+ mozilla::StyleDominantBaseline mDominantBaseline;
+ mozilla::StyleTextAnchor mTextAnchor;
+
+ /// Returns true if style has been set to expose the computed values of
+ /// certain properties (such as 'fill') to the contents of any linked images.
+ bool ExposesContextProperties() const {
+ return bool(mMozContextProperties.bits);
+ }
+
+ bool HasMarker() const {
+ return mMarkerStart.IsUrl() || mMarkerMid.IsUrl() || mMarkerEnd.IsUrl();
+ }
+
+ /**
+ * Returns true if the stroke is not "none" and the stroke-opacity is greater
+ * than zero (or a context-dependent value).
+ *
+ * This ignores stroke-widths as that depends on the context.
+ */
+ bool HasStroke() const {
+ if (mStroke.kind.IsNone()) {
+ return false;
+ }
+ return !mStrokeOpacity.IsOpacity() || mStrokeOpacity.AsOpacity() > 0;
+ }
+
+ /**
+ * Returns true if the fill is not "none" and the fill-opacity is greater
+ * than zero (or a context-dependent value).
+ */
+ bool HasFill() const {
+ if (mFill.kind.IsNone()) {
+ return false;
+ }
+ return !mFillOpacity.IsOpacity() || mFillOpacity.AsOpacity() > 0;
+ }
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleSVGReset {
+ STYLE_STRUCT(nsStyleSVGReset)
+ nsStyleSVGReset();
+ void TriggerImageLoads(mozilla::dom::Document&, const nsStyleSVGReset*);
+
+ bool HasClipPath() const { return !mClipPath.IsNone(); }
+
+ bool HasMask() const;
+
+ bool HasNonScalingStroke() const {
+ return mVectorEffect == mozilla::StyleVectorEffect::NonScalingStroke;
+ }
+
+ // geometry properties
+ mozilla::LengthPercentage mX;
+ mozilla::LengthPercentage mY;
+ mozilla::LengthPercentage mCx;
+ mozilla::LengthPercentage mCy;
+ mozilla::NonNegativeLengthPercentageOrAuto mRx;
+ mozilla::NonNegativeLengthPercentageOrAuto mRy;
+ mozilla::NonNegativeLengthPercentage mR;
+
+ nsStyleImageLayers mMask;
+ mozilla::StyleClipPath mClipPath;
+ mozilla::StyleColor mStopColor;
+ mozilla::StyleColor mFloodColor;
+ mozilla::StyleColor mLightingColor;
+
+ float mStopOpacity;
+ float mFloodOpacity;
+
+ mozilla::StyleVectorEffect mVectorEffect;
+ mozilla::StyleMaskType mMaskType;
+
+ mozilla::StyleDProperty mD;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleEffects {
+ STYLE_STRUCT(nsStyleEffects)
+ nsStyleEffects();
+
+ bool HasFilters() const { return !mFilters.IsEmpty(); }
+
+ bool HasBackdropFilters() const { return !mBackdropFilters.IsEmpty(); }
+
+ bool HasBoxShadowWithInset(bool aInset) const {
+ for (const auto& shadow : mBoxShadow.AsSpan()) {
+ if (shadow.inset == aInset) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool HasMixBlendMode() const {
+ return mMixBlendMode != mozilla::StyleBlend::Normal;
+ }
+
+ bool IsOpaque() const { return mOpacity >= 1.0f; }
+
+ bool IsTransparent() const { return mOpacity == 0.0f; }
+
+ mozilla::StyleOwnedSlice<mozilla::StyleFilter> mFilters;
+ mozilla::StyleOwnedSlice<mozilla::StyleBoxShadow> mBoxShadow;
+ mozilla::StyleOwnedSlice<mozilla::StyleFilter> mBackdropFilters;
+ mozilla::StyleClipRectOrAuto mClip; // offsets from UL border edge
+ float mOpacity;
+ mozilla::StyleBlend mMixBlendMode;
+};
+
+#undef STYLE_STRUCT
+
+#define STATIC_ASSERT_TYPE_LAYOUTS_MATCH(T1, T2) \
+ static_assert(sizeof(T1) == sizeof(T2), \
+ "Size mismatch between " #T1 " and " #T2); \
+ static_assert(alignof(T1) == alignof(T2), \
+ "Align mismatch between " #T1 " and " #T2);
+
+#define STATIC_ASSERT_FIELD_OFFSET_MATCHES(T1, T2, field) \
+ static_assert(offsetof(T1, field) == offsetof(T2, field), \
+ "Field offset mismatch of " #field " between " #T1 \
+ " and " #T2);
+
+/**
+ * These *_Simple types are used to map Gecko types to layout-equivalent but
+ * simpler Rust types, to aid Rust binding generation.
+ *
+ * If something in this types or the assertions below needs to change, ask
+ * bholley, heycam or emilio before!
+ *
+ * <div rustbindgen="true" replaces="nsPoint">
+ */
+struct nsPoint_Simple {
+ nscoord x, y;
+};
+
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsPoint, nsPoint_Simple);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsPoint, nsPoint_Simple, x);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsPoint, nsPoint_Simple, y);
+
+/**
+ * <div rustbindgen="true" replaces="nsMargin">
+ */
+struct nsMargin_Simple {
+ nscoord top, right, bottom, left;
+};
+
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsMargin, nsMargin_Simple);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsMargin, nsMargin_Simple, top);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsMargin, nsMargin_Simple, right);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsMargin, nsMargin_Simple, bottom);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsMargin, nsMargin_Simple, left);
+
+/**
+ * <div rustbindgen="true" replaces="nsRect">
+ */
+struct nsRect_Simple {
+ nscoord x, y, width, height;
+};
+
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsRect, nsRect_Simple);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsRect, nsRect_Simple, x);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsRect, nsRect_Simple, y);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsRect, nsRect_Simple, width);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsRect, nsRect_Simple, height);
+
+/**
+ * <div rustbindgen="true" replaces="nsSize">
+ */
+struct nsSize_Simple {
+ nscoord width, height;
+};
+
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsSize, nsSize_Simple);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsSize, nsSize_Simple, width);
+STATIC_ASSERT_FIELD_OFFSET_MATCHES(nsSize, nsSize_Simple, height);
+
+/**
+ * <div rustbindgen="true" replaces="mozilla::UniquePtr">
+ *
+ * TODO(Emilio): This is a workaround and we should be able to get rid of this
+ * one.
+ */
+template <typename T>
+struct UniquePtr_Simple {
+ T* mPtr;
+};
+
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(mozilla::UniquePtr<int>,
+ UniquePtr_Simple<int>);
+
+/**
+ * <div rustbindgen replaces="nsTArray"></div>
+ */
+template <typename T>
+class nsTArray_Simple {
+ protected:
+ T* mBuffer;
+
+ public:
+ ~nsTArray_Simple() {
+ // The existence of a user-provided, and therefore non-trivial, destructor
+ // here prevents bindgen from deriving the Clone trait via a simple memory
+ // copy.
+ }
+};
+
+/**
+ * <div rustbindgen replaces="CopyableTArray"></div>
+ */
+template <typename T>
+class CopyableTArray_Simple : public nsTArray_Simple<T> {};
+
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsTArray<nsStyleImageLayers::Layer>,
+ nsTArray_Simple<nsStyleImageLayers::Layer>);
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsTArray<mozilla::StyleTransition>,
+ nsTArray_Simple<mozilla::StyleTransition>);
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsTArray<mozilla::StyleAnimation>,
+ nsTArray_Simple<mozilla::StyleAnimation>);
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsTArray<mozilla::StyleViewTimeline>,
+ nsTArray_Simple<mozilla::StyleViewTimeline>);
+
+#endif /* nsStyleStruct_h___ */
diff --git a/layout/style/nsStyleStructFwd.h b/layout/style/nsStyleStructFwd.h
new file mode 100644
index 0000000000..5187a764b9
--- /dev/null
+++ b/layout/style/nsStyleStructFwd.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Forward declarations to avoid including all of nsStyleStruct.h.
+ */
+
+#ifndef nsStyleStructFwd_h_
+#define nsStyleStructFwd_h_
+
+namespace mozilla {
+
+enum class StyleStructID : uint32_t {
+/*
+ * Define the constants eStyleStruct_Font, etc.
+ *
+ * The C++ standard, section 7.2, guarantees that enums begin with 0 and
+ * increase by 1.
+ *
+ * Note that we rely on the inherited structs being before the rest in
+ * ComputedStyle.
+ */
+#define STYLE_STRUCT_INHERITED(name) name,
+#define STYLE_STRUCT_RESET(name)
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+
+#define STYLE_STRUCT_RESET(name) name,
+#define STYLE_STRUCT_INHERITED(name)
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+};
+
+struct StyleStructConstants {
+ static const uint32_t kStyleStructCount =
+#define STYLE_STRUCT_RESET(name) 1 +
+#define STYLE_STRUCT_INHERITED(name) 1 +
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+ 0;
+
+ static const uint32_t kInheritedStyleStructCount =
+#define STYLE_STRUCT_RESET(name)
+#define STYLE_STRUCT_INHERITED(name) 1 +
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+ 0;
+
+ static const uint32_t kResetStyleStructCount =
+#define STYLE_STRUCT_RESET(name) 1 +
+#define STYLE_STRUCT_INHERITED(name)
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+ 0;
+
+ static_assert(kStyleStructCount <= 32, "Bitmasks must be bigger!");
+
+ static const uint32_t kAllStructsMask = (1 << kStyleStructCount) - 1;
+ static const uint32_t kInheritedStructsMask =
+ (1 << kInheritedStyleStructCount) - 1;
+ static const uint32_t kResetStructsMask =
+ kAllStructsMask & (~kInheritedStructsMask);
+
+ static uint32_t BitFor(StyleStructID aID) {
+ return 1 << static_cast<uint32_t>(aID);
+ }
+};
+
+} // namespace mozilla
+
+#endif /* nsStyleStructFwd_h_ */
diff --git a/layout/style/nsStyleStructInlines.h b/layout/style/nsStyleStructInlines.h
new file mode 100644
index 0000000000..885be379ee
--- /dev/null
+++ b/layout/style/nsStyleStructInlines.h
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsStyleStruct.h, except that they
+ * require more headers.
+ */
+
+#ifndef nsStyleStructInlines_h_
+#define nsStyleStructInlines_h_
+
+#include "nsIFrame.h"
+#include "nsStyleStruct.h"
+#include "nsIContent.h" // for GetParent()
+#include "nsTextFrame.h" // for nsTextFrame::ShouldSuppressLineBreak
+
+bool nsStyleText::NewlineIsSignificant(const nsTextFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
+ return NewlineIsSignificantStyle() &&
+ !aContextFrame->ShouldSuppressLineBreak() &&
+ !aContextFrame->Style()->IsTextCombined();
+}
+
+bool nsStyleText::WhiteSpaceCanWrap(const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
+ return WhiteSpaceCanWrapStyle() && !aContextFrame->IsInSVGTextSubtree() &&
+ !aContextFrame->Style()->IsTextCombined();
+}
+
+bool nsStyleText::WordCanWrap(const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
+ return WordCanWrapStyle() && !aContextFrame->IsInSVGTextSubtree();
+}
+
+bool nsStyleDisplay::IsBlockOutside(const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this,
+ "unexpected aContextFrame");
+ if (aContextFrame->IsInSVGTextSubtree()) {
+ return aContextFrame->IsBlockFrame();
+ }
+ return IsBlockOutsideStyle();
+}
+
+bool nsStyleDisplay::IsInlineOutside(const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this,
+ "unexpected aContextFrame");
+ if (aContextFrame->IsInSVGTextSubtree()) {
+ return !aContextFrame->IsBlockFrame();
+ }
+ return IsInlineOutsideStyle();
+}
+
+mozilla::StyleDisplay nsStyleDisplay::GetDisplay(
+ const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this,
+ "unexpected aContextFrame");
+ if (aContextFrame->IsInSVGTextSubtree() &&
+ mDisplay != mozilla::StyleDisplay::None) {
+ return aContextFrame->IsBlockFrame() ? mozilla::StyleDisplay::Block
+ : mozilla::StyleDisplay::Inline;
+ }
+ return mDisplay;
+}
+
+bool nsStyleDisplay::IsFloating(const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this,
+ "unexpected aContextFrame");
+ return IsFloatingStyle() && !aContextFrame->IsInSVGTextSubtree();
+}
+
+// If you change this function, also change the corresponding block in
+// nsCSSFrameConstructor::ConstructFrameFromItemInternal that references
+// this function in comments.
+bool nsStyleDisplay::HasTransform(const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this,
+ "unexpected aContextFrame");
+ return HasTransformStyle() && aContextFrame->SupportsCSSTransforms();
+}
+
+bool nsStyleDisplay::HasPerspective(const nsIFrame* aContextFrame) const {
+ MOZ_ASSERT(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
+ return HasPerspectiveStyle() && aContextFrame->SupportsCSSTransforms();
+}
+
+bool nsStyleDisplay::
+ IsFixedPosContainingBlockForContainLayoutAndPaintSupportingFrames() const {
+ return IsContainPaint() || IsContainLayout() ||
+ mWillChange.bits & mozilla::StyleWillChangeBits::CONTAIN;
+}
+
+bool nsStyleDisplay::IsFixedPosContainingBlockForTransformSupportingFrames()
+ const {
+ // NOTE: Any CSS properties that influence the output of this function
+ // should also look at mWillChange as necessary.
+ return HasTransformStyle() || HasPerspectiveStyle() ||
+ mWillChange.bits & mozilla::StyleWillChangeBits::PERSPECTIVE;
+}
+
+bool nsStyleDisplay::IsRelativelyOrStickyPositioned(
+ const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this,
+ "unexpected aContextFrame");
+ return IsRelativelyOrStickyPositionedStyle() &&
+ !aContextFrame->IsInSVGTextSubtree();
+}
+
+bool nsStyleDisplay::IsRelativelyPositioned(
+ const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this,
+ "unexpected aContextFrame");
+ return IsRelativelyPositionedStyle() && !aContextFrame->IsInSVGTextSubtree();
+}
+
+bool nsStyleDisplay::IsStickyPositioned(const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this,
+ "unexpected aContextFrame");
+ return IsStickyPositionedStyle() && !aContextFrame->IsInSVGTextSubtree();
+}
+
+bool nsStyleDisplay::IsAbsolutelyPositioned(
+ const nsIFrame* aContextFrame) const {
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this,
+ "unexpected aContextFrame");
+ return IsAbsolutelyPositionedStyle() && !aContextFrame->IsInSVGTextSubtree();
+}
+
+bool nsStyleBackground::HasLocalBackground() const {
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, mImage) {
+ const nsStyleImageLayers::Layer& layer = mImage.mLayers[i];
+ if (!layer.mImage.IsNone() &&
+ layer.mAttachment == mozilla::StyleImageLayerAttachment::Local) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#endif /* !defined(nsStyleStructInlines_h_) */
diff --git a/layout/style/nsStyleStructList.h b/layout/style/nsStyleStructList.h
new file mode 100644
index 0000000000..880e0c8da0
--- /dev/null
+++ b/layout/style/nsStyleStructList.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// IWYU pragma: private, include "nsStyleStructFwd.h"
+
+/*
+ * list of structs that contain the data provided by ComputedStyle, the
+ * internal API for computed style data for an element
+ */
+
+/*
+ * This file is intended to be used by different parts of the code, with
+ * the STYLE_STRUCT macro (or the STYLE_STRUCT_INHERITED and
+ * STYLE_STRUCT_RESET pair of macros) defined in different ways.
+ */
+
+#ifndef STYLE_STRUCT_INHERITED
+#define STYLE_STRUCT_INHERITED(name) STYLE_STRUCT(name)
+#define UNDEF_STYLE_STRUCT_INHERITED
+#endif
+
+#ifndef STYLE_STRUCT_RESET
+#define STYLE_STRUCT_RESET(name) STYLE_STRUCT(name)
+#define UNDEF_STYLE_STRUCT_RESET
+#endif
+
+// The inherited structs are listed before the Reset structs.
+// nsStyleStructID assumes this is the case, and callers other than
+// nsStyleStructFwd.h that want the structs in id-order just define
+// STYLE_STRUCT rather than including the file twice.
+
+STYLE_STRUCT_INHERITED(Font)
+STYLE_STRUCT_INHERITED(List)
+STYLE_STRUCT_INHERITED(Text)
+STYLE_STRUCT_INHERITED(Visibility)
+STYLE_STRUCT_INHERITED(UI)
+STYLE_STRUCT_INHERITED(TableBorder)
+STYLE_STRUCT_INHERITED(SVG)
+
+STYLE_STRUCT_RESET(Background)
+STYLE_STRUCT_RESET(Position)
+STYLE_STRUCT_RESET(TextReset)
+STYLE_STRUCT_RESET(Display)
+STYLE_STRUCT_RESET(Content)
+STYLE_STRUCT_RESET(UIReset)
+STYLE_STRUCT_RESET(Table)
+STYLE_STRUCT_RESET(Margin)
+STYLE_STRUCT_RESET(Padding)
+STYLE_STRUCT_RESET(Border)
+STYLE_STRUCT_RESET(Outline)
+STYLE_STRUCT_RESET(XUL)
+STYLE_STRUCT_RESET(SVGReset)
+STYLE_STRUCT_RESET(Column)
+STYLE_STRUCT_RESET(Effects)
+STYLE_STRUCT_RESET(Page)
+
+#ifdef UNDEF_STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_INHERITED
+#undef UNDEF_STYLE_STRUCT_INHERITED
+#endif
+
+#ifdef UNDEF_STYLE_STRUCT_RESET
+#undef STYLE_STRUCT_RESET
+#undef UNDEF_STYLE_STRUCT_RESET
+#endif
diff --git a/layout/style/nsStyleTransformMatrix.cpp b/layout/style/nsStyleTransformMatrix.cpp
new file mode 100644
index 0000000000..593bcbb39d
--- /dev/null
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -0,0 +1,653 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 used for intermediate representations of the transform and
+ * transform-like properties.
+ */
+
+#include "nsStyleTransformMatrix.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "mozilla/MotionPathUtils.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/SVGUtils.h"
+#include "gfxMatrix.h"
+#include "gfxQuaternion.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+namespace nsStyleTransformMatrix {
+
+/* Note on floating point precision: The transform matrix is an array
+ * of single precision 'float's, and so are most of the input values
+ * we get from the style system, but intermediate calculations
+ * involving angles need to be done in 'double'.
+ */
+
+// Define UNIFIED_CONTINUATIONS here and in nsDisplayList.cpp
+// 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.
+// #define UNIFIED_CONTINUATIONS
+
+static nsRect GetSVGBox(const nsIFrame* aFrame) {
+ auto computeViewBox = [&]() {
+ // Percentages in transforms resolve against the width/height of the
+ // nearest viewport (or its viewBox if one is applied), and the
+ // transform is relative to {0,0} in current user space.
+ CSSSize size = CSSSize::FromUnknownSize(SVGUtils::GetContextSize(aFrame));
+ return nsRect(-aFrame->GetPosition(), CSSPixel::ToAppUnits(size));
+ };
+
+ auto transformBox = aFrame->StyleDisplay()->mTransformBox;
+ if ((transformBox == StyleTransformBox::StrokeBox ||
+ transformBox == StyleTransformBox::BorderBox) &&
+ aFrame->StyleSVGReset()->HasNonScalingStroke()) {
+ // To calculate stroke bounds for an element with `non-scaling-stroke` we
+ // need to resolve its transform to its outer-svg, but to resolve that
+ // transform when it has `transform-box:stroke-box` (or `border-box`)
+ // may require its stroke bounds. There's no ideal way to break this
+ // cyclical dependency, but we break it by converting to
+ // `transform-box:fill-box` here.
+ // https://github.com/w3c/csswg-drafts/issues/9640
+ transformBox = StyleTransformBox::FillBox;
+ }
+
+ // For SVG elements without associated CSS layout box, the used value for
+ // content-box is fill-box and for border-box is stroke-box.
+ // https://drafts.csswg.org/css-transforms-1/#transform-box
+ switch (transformBox) {
+ case StyleTransformBox::ContentBox:
+ case StyleTransformBox::FillBox: {
+ // Percentages in transforms resolve against the SVG bbox, and the
+ // transform is relative to the top-left of the SVG bbox.
+ nsRect bboxInAppUnits = nsLayoutUtils::ComputeSVGReferenceRect(
+ const_cast<nsIFrame*>(aFrame), StyleGeometryBox::FillBox);
+ // The mRect of an SVG nsIFrame is its user space bounds *including*
+ // stroke and markers, whereas bboxInAppUnits is its user space bounds
+ // including fill only. We need to note the offset of the reference box
+ // from the frame's mRect in mX/mY.
+ return {bboxInAppUnits.x - aFrame->GetPosition().x,
+ bboxInAppUnits.y - aFrame->GetPosition().y, bboxInAppUnits.width,
+ bboxInAppUnits.height};
+ }
+ case StyleTransformBox::BorderBox:
+ if (!StaticPrefs::layout_css_transform_box_content_stroke_enabled()) {
+ // If stroke-box is disabled, we shouldn't use it and fall back to
+ // view-box.
+ return computeViewBox();
+ }
+ [[fallthrough]];
+ case StyleTransformBox::StrokeBox: {
+ // We are using SVGUtils::PathExtentsToMaxStrokeExtents() to compute the
+ // bbox contribution for stroke box (if it doesn't have simple bounds),
+ // so the |strokeBox| here may be larger than the author's expectation.
+ // Using Moz2D to compute the tighter bounding box is another way but it
+ // has some potential issues (see SVGGeometryFrame::GetBBoxContribution()
+ // for more details), and its result depends on the drawing backend. So
+ // for now we still rely on our default calcuclation for SVG geometry
+ // frame reflow code. At least this works for the shape elements which
+ // have simple bounds.
+ // FIXME: Bug 1849054. We may have to update
+ // SVGGeometryFrame::GetBBoxContribution() to get tighter stroke bounds.
+ nsRect strokeBox = nsLayoutUtils::ComputeSVGReferenceRect(
+ const_cast<nsIFrame*>(aFrame), StyleGeometryBox::StrokeBox);
+ // The |nsIFrame::mRect| includes markers, so we have to compute the
+ // offsets without markers.
+ return nsRect{strokeBox.x - aFrame->GetPosition().x,
+ strokeBox.y - aFrame->GetPosition().y, strokeBox.width,
+ strokeBox.height};
+ }
+ case StyleTransformBox::ViewBox:
+ return computeViewBox();
+ }
+
+ MOZ_ASSERT_UNREACHABLE("All transform box should be handled.");
+ return {};
+}
+
+void TransformReferenceBox::EnsureDimensionsAreCached() {
+ if (mIsCached) {
+ return;
+ }
+
+ MOZ_ASSERT(mFrame);
+
+ mIsCached = true;
+
+ if (mFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ mBox = GetSVGBox(mFrame);
+ return;
+ }
+
+ // For elements with associated CSS layout box, the used value for fill-box is
+ // content-box and for stroke-box and view-box is border-box.
+ // https://drafts.csswg.org/css-transforms-1/#transform-box
+ switch (mFrame->StyleDisplay()->mTransformBox) {
+ case StyleTransformBox::FillBox:
+ case StyleTransformBox::ContentBox: {
+ mBox = mFrame->GetContentRectRelativeToSelf();
+ return;
+ }
+ case StyleTransformBox::StrokeBox:
+ // TODO: Implement this in the following patches.
+ return;
+ case StyleTransformBox::ViewBox:
+ case StyleTransformBox::BorderBox: {
+ // If UNIFIED_CONTINUATIONS is not defined, this is simply the frame's
+ // bounding rectangle, translated to the origin. Otherwise, it is the
+ // smallest rectangle containing a frame and all of its continuations. For
+ // example, if there is a <span> element with several continuations split
+ // over several lines, this function will return the rectangle containing
+ // all of those continuations.
+
+ nsRect rect;
+
+#ifndef UNIFIED_CONTINUATIONS
+ rect = mFrame->GetRect();
+#else
+ // Iterate the continuation list, unioning together the bounding rects:
+ for (const nsIFrame* currFrame = mFrame->FirstContinuation();
+ currFrame != nullptr; currFrame = currFrame->GetNextContinuation()) {
+ // Get the frame rect in local coordinates, then translate back to the
+ // original coordinates:
+ rect.UnionRect(result, nsRect(currFrame->GetOffsetTo(mFrame),
+ currFrame->GetSize()));
+ }
+#endif
+
+ mBox = {0, 0, rect.Width(), rect.Height()};
+ return;
+ }
+ }
+}
+
+float ProcessTranslatePart(
+ const LengthPercentage& aValue, TransformReferenceBox* aRefBox,
+ TransformReferenceBox::DimensionGetter aDimensionGetter) {
+ return aValue.ResolveToCSSPixelsWith([&] {
+ return aRefBox && !aRefBox->IsEmpty()
+ ? CSSPixel::FromAppUnits((aRefBox->*aDimensionGetter)())
+ : CSSCoord(0);
+ });
+}
+
+/**
+ * Helper functions to process all the transformation function types.
+ *
+ * These take a matrix parameter to accumulate the current matrix.
+ */
+
+/* Helper function to process a matrix entry. */
+static void ProcessMatrix(Matrix4x4& aMatrix,
+ const StyleTransformOperation& aOp) {
+ const auto& matrix = aOp.AsMatrix();
+ gfxMatrix result;
+
+ result._11 = matrix.a;
+ result._12 = matrix.b;
+ result._21 = matrix.c;
+ result._22 = matrix.d;
+ result._31 = matrix.e;
+ result._32 = matrix.f;
+
+ aMatrix = result * aMatrix;
+}
+
+static void ProcessMatrix3D(Matrix4x4& aMatrix,
+ const StyleTransformOperation& aOp) {
+ Matrix4x4 temp;
+
+ const auto& matrix = aOp.AsMatrix3D();
+
+ temp._11 = matrix.m11;
+ temp._12 = matrix.m12;
+ temp._13 = matrix.m13;
+ temp._14 = matrix.m14;
+ temp._21 = matrix.m21;
+ temp._22 = matrix.m22;
+ temp._23 = matrix.m23;
+ temp._24 = matrix.m24;
+ temp._31 = matrix.m31;
+ temp._32 = matrix.m32;
+ temp._33 = matrix.m33;
+ temp._34 = matrix.m34;
+
+ temp._41 = matrix.m41;
+ temp._42 = matrix.m42;
+ temp._43 = matrix.m43;
+ temp._44 = matrix.m44;
+
+ aMatrix = temp * aMatrix;
+}
+
+// For accumulation for transform functions, |aOne| corresponds to |aB| and
+// |aTwo| corresponds to |aA| for StyleAnimationValue::Accumulate().
+class Accumulate {
+ public:
+ template <typename T>
+ static T operate(const T& aOne, const T& aTwo, double aCoeff) {
+ return aOne + aTwo * aCoeff;
+ }
+
+ static Point4D operateForPerspective(const Point4D& aOne, const Point4D& aTwo,
+ double aCoeff) {
+ return (aOne - Point4D(0, 0, 0, 1)) +
+ (aTwo - Point4D(0, 0, 0, 1)) * aCoeff + Point4D(0, 0, 0, 1);
+ }
+ static Point3D operateForScale(const Point3D& aOne, const Point3D& aTwo,
+ double aCoeff) {
+ // For scale, the identify element is 1, see AddTransformScale in
+ // StyleAnimationValue.cpp.
+ return (aOne - Point3D(1, 1, 1)) + (aTwo - Point3D(1, 1, 1)) * aCoeff +
+ Point3D(1, 1, 1);
+ }
+
+ static Matrix4x4 operateForRotate(const gfxQuaternion& aOne,
+ const gfxQuaternion& aTwo, double aCoeff) {
+ if (aCoeff == 0.0) {
+ return aOne.ToMatrix();
+ }
+
+ double theta = acos(mozilla::clamped(aTwo.w, -1.0, 1.0));
+ double scale = (theta != 0.0) ? 1.0 / sin(theta) : 0.0;
+ theta *= aCoeff;
+ scale *= sin(theta);
+
+ gfxQuaternion result = gfxQuaternion(scale * aTwo.x, scale * aTwo.y,
+ scale * aTwo.z, cos(theta)) *
+ aOne;
+ return result.ToMatrix();
+ }
+
+ static Matrix4x4 operateForFallback(const Matrix4x4& aMatrix1,
+ const Matrix4x4& aMatrix2,
+ double aProgress) {
+ return aMatrix1;
+ }
+
+ static Matrix4x4 operateByServo(const Matrix4x4& aMatrix1,
+ const Matrix4x4& aMatrix2, double aCount) {
+ Matrix4x4 result;
+ Servo_MatrixTransform_Operate(MatrixTransformOperator::Accumulate,
+ &aMatrix1.components, &aMatrix2.components,
+ aCount, &result.components);
+ return result;
+ }
+};
+
+class Interpolate {
+ public:
+ template <typename T>
+ static T operate(const T& aOne, const T& aTwo, double aCoeff) {
+ return aOne + (aTwo - aOne) * aCoeff;
+ }
+
+ static Point4D operateForPerspective(const Point4D& aOne, const Point4D& aTwo,
+ double aCoeff) {
+ return aOne + (aTwo - aOne) * aCoeff;
+ }
+
+ static Point3D operateForScale(const Point3D& aOne, const Point3D& aTwo,
+ double aCoeff) {
+ return aOne + (aTwo - aOne) * aCoeff;
+ }
+
+ static Matrix4x4 operateForRotate(const gfxQuaternion& aOne,
+ const gfxQuaternion& aTwo, double aCoeff) {
+ return aOne.Slerp(aTwo, aCoeff).ToMatrix();
+ }
+
+ static Matrix4x4 operateForFallback(const Matrix4x4& aMatrix1,
+ const Matrix4x4& aMatrix2,
+ double aProgress) {
+ return aProgress < 0.5 ? aMatrix1 : aMatrix2;
+ }
+
+ static Matrix4x4 operateByServo(const Matrix4x4& aMatrix1,
+ const Matrix4x4& aMatrix2, double aProgress) {
+ Matrix4x4 result;
+ Servo_MatrixTransform_Operate(MatrixTransformOperator::Interpolate,
+ &aMatrix1.components, &aMatrix2.components,
+ aProgress, &result.components);
+ return result;
+ }
+};
+
+template <typename Operator>
+static void ProcessMatrixOperator(Matrix4x4& aMatrix,
+ const StyleTransform& aFrom,
+ const StyleTransform& aTo, float aProgress,
+ TransformReferenceBox& aRefBox) {
+ float appUnitPerCSSPixel = AppUnitsPerCSSPixel();
+ Matrix4x4 matrix1 = ReadTransforms(aFrom, aRefBox, appUnitPerCSSPixel);
+ Matrix4x4 matrix2 = ReadTransforms(aTo, aRefBox, appUnitPerCSSPixel);
+ aMatrix = Operator::operateByServo(matrix1, matrix2, aProgress) * aMatrix;
+}
+
+/* Helper function to process two matrices that we need to interpolate between
+ */
+void ProcessInterpolateMatrix(Matrix4x4& aMatrix,
+ const StyleTransformOperation& aOp,
+ TransformReferenceBox& aRefBox) {
+ const auto& args = aOp.AsInterpolateMatrix();
+ ProcessMatrixOperator<Interpolate>(aMatrix, args.from_list, args.to_list,
+ args.progress._0, aRefBox);
+}
+
+void ProcessAccumulateMatrix(Matrix4x4& aMatrix,
+ const StyleTransformOperation& aOp,
+ TransformReferenceBox& aRefBox) {
+ const auto& args = aOp.AsAccumulateMatrix();
+ ProcessMatrixOperator<Accumulate>(aMatrix, args.from_list, args.to_list,
+ args.count, aRefBox);
+}
+
+/* Helper function to process a translatex function. */
+static void ProcessTranslateX(Matrix4x4& aMatrix,
+ const LengthPercentage& aLength,
+ TransformReferenceBox& aRefBox) {
+ Point3D temp;
+ temp.x =
+ ProcessTranslatePart(aLength, &aRefBox, &TransformReferenceBox::Width);
+ aMatrix.PreTranslate(temp);
+}
+
+/* Helper function to process a translatey function. */
+static void ProcessTranslateY(Matrix4x4& aMatrix,
+ const LengthPercentage& aLength,
+ TransformReferenceBox& aRefBox) {
+ Point3D temp;
+ temp.y =
+ ProcessTranslatePart(aLength, &aRefBox, &TransformReferenceBox::Height);
+ aMatrix.PreTranslate(temp);
+}
+
+static void ProcessTranslateZ(Matrix4x4& aMatrix, const Length& aLength) {
+ Point3D temp;
+ temp.z = aLength.ToCSSPixels();
+ aMatrix.PreTranslate(temp);
+}
+
+/* Helper function to process a translate function. */
+static void ProcessTranslate(Matrix4x4& aMatrix, const LengthPercentage& aX,
+ const LengthPercentage& aY,
+ TransformReferenceBox& aRefBox) {
+ Point3D temp;
+ temp.x = ProcessTranslatePart(aX, &aRefBox, &TransformReferenceBox::Width);
+ temp.y = ProcessTranslatePart(aY, &aRefBox, &TransformReferenceBox::Height);
+ aMatrix.PreTranslate(temp);
+}
+
+static void ProcessTranslate3D(Matrix4x4& aMatrix, const LengthPercentage& aX,
+ const LengthPercentage& aY, const Length& aZ,
+ TransformReferenceBox& aRefBox) {
+ Point3D temp;
+
+ temp.x = ProcessTranslatePart(aX, &aRefBox, &TransformReferenceBox::Width);
+ temp.y = ProcessTranslatePart(aY, &aRefBox, &TransformReferenceBox::Height);
+ temp.z = aZ.ToCSSPixels();
+
+ aMatrix.PreTranslate(temp);
+}
+
+/* Helper function to set up a scale matrix. */
+static void ProcessScaleHelper(Matrix4x4& aMatrix, float aXScale, float aYScale,
+ float aZScale) {
+ aMatrix.PreScale(aXScale, aYScale, aZScale);
+}
+
+static void ProcessScale3D(Matrix4x4& aMatrix,
+ const StyleTransformOperation& aOp) {
+ const auto& scale = aOp.AsScale3D();
+ ProcessScaleHelper(aMatrix, scale._0, scale._1, scale._2);
+}
+
+/* Helper function that, given a set of angles, constructs the appropriate
+ * skew matrix.
+ */
+static void ProcessSkewHelper(Matrix4x4& aMatrix, const StyleAngle& aXAngle,
+ const StyleAngle& aYAngle) {
+ aMatrix.SkewXY(aXAngle.ToRadians(), aYAngle.ToRadians());
+}
+
+static void ProcessRotate3D(Matrix4x4& aMatrix, float aX, float aY, float aZ,
+ const StyleAngle& aAngle) {
+ Matrix4x4 temp;
+ temp.SetRotateAxisAngle(aX, aY, aZ, aAngle.ToRadians());
+ aMatrix = temp * aMatrix;
+}
+
+static void ProcessPerspective(
+ Matrix4x4& aMatrix,
+ const StyleGenericPerspectiveFunction<Length>& aPerspective) {
+ if (aPerspective.IsNone()) {
+ return;
+ }
+ float p = aPerspective.AsLength().ToCSSPixels();
+ if (!std::isinf(p)) {
+ aMatrix.Perspective(std::max(p, 1.0f));
+ }
+}
+
+static void MatrixForTransformFunction(Matrix4x4& aMatrix,
+ const StyleTransformOperation& aOp,
+ TransformReferenceBox& aRefBox) {
+ /* Get the keyword for the transform. */
+ switch (aOp.tag) {
+ case StyleTransformOperation::Tag::TranslateX:
+ ProcessTranslateX(aMatrix, aOp.AsTranslateX(), aRefBox);
+ break;
+ case StyleTransformOperation::Tag::TranslateY:
+ ProcessTranslateY(aMatrix, aOp.AsTranslateY(), aRefBox);
+ break;
+ case StyleTransformOperation::Tag::TranslateZ:
+ ProcessTranslateZ(aMatrix, aOp.AsTranslateZ());
+ break;
+ case StyleTransformOperation::Tag::Translate:
+ ProcessTranslate(aMatrix, aOp.AsTranslate()._0, aOp.AsTranslate()._1,
+ aRefBox);
+ break;
+ case StyleTransformOperation::Tag::Translate3D:
+ return ProcessTranslate3D(aMatrix, aOp.AsTranslate3D()._0,
+ aOp.AsTranslate3D()._1, aOp.AsTranslate3D()._2,
+ aRefBox);
+ break;
+ case StyleTransformOperation::Tag::ScaleX:
+ ProcessScaleHelper(aMatrix, aOp.AsScaleX(), 1.0f, 1.0f);
+ break;
+ case StyleTransformOperation::Tag::ScaleY:
+ ProcessScaleHelper(aMatrix, 1.0f, aOp.AsScaleY(), 1.0f);
+ break;
+ case StyleTransformOperation::Tag::ScaleZ:
+ ProcessScaleHelper(aMatrix, 1.0f, 1.0f, aOp.AsScaleZ());
+ break;
+ case StyleTransformOperation::Tag::Scale:
+ ProcessScaleHelper(aMatrix, aOp.AsScale()._0, aOp.AsScale()._1, 1.0f);
+ break;
+ case StyleTransformOperation::Tag::Scale3D:
+ ProcessScale3D(aMatrix, aOp);
+ break;
+ case StyleTransformOperation::Tag::SkewX:
+ ProcessSkewHelper(aMatrix, aOp.AsSkewX(), StyleAngle::Zero());
+ break;
+ case StyleTransformOperation::Tag::SkewY:
+ ProcessSkewHelper(aMatrix, StyleAngle::Zero(), aOp.AsSkewY());
+ break;
+ case StyleTransformOperation::Tag::Skew:
+ ProcessSkewHelper(aMatrix, aOp.AsSkew()._0, aOp.AsSkew()._1);
+ break;
+ case StyleTransformOperation::Tag::RotateX:
+ aMatrix.RotateX(aOp.AsRotateX().ToRadians());
+ break;
+ case StyleTransformOperation::Tag::RotateY:
+ aMatrix.RotateY(aOp.AsRotateY().ToRadians());
+ break;
+ case StyleTransformOperation::Tag::RotateZ:
+ aMatrix.RotateZ(aOp.AsRotateZ().ToRadians());
+ break;
+ case StyleTransformOperation::Tag::Rotate:
+ aMatrix.RotateZ(aOp.AsRotate().ToRadians());
+ break;
+ case StyleTransformOperation::Tag::Rotate3D:
+ ProcessRotate3D(aMatrix, aOp.AsRotate3D()._0, aOp.AsRotate3D()._1,
+ aOp.AsRotate3D()._2, aOp.AsRotate3D()._3);
+ break;
+ case StyleTransformOperation::Tag::Matrix:
+ ProcessMatrix(aMatrix, aOp);
+ break;
+ case StyleTransformOperation::Tag::Matrix3D:
+ ProcessMatrix3D(aMatrix, aOp);
+ break;
+ case StyleTransformOperation::Tag::InterpolateMatrix:
+ ProcessInterpolateMatrix(aMatrix, aOp, aRefBox);
+ break;
+ case StyleTransformOperation::Tag::AccumulateMatrix:
+ ProcessAccumulateMatrix(aMatrix, aOp, aRefBox);
+ break;
+ case StyleTransformOperation::Tag::Perspective:
+ ProcessPerspective(aMatrix, aOp.AsPerspective());
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown transform function!");
+ }
+}
+
+Matrix4x4 ReadTransforms(const StyleTransform& aTransform,
+ TransformReferenceBox& aRefBox,
+ float aAppUnitsPerMatrixUnit) {
+ Matrix4x4 result;
+
+ for (const StyleTransformOperation& op : aTransform.Operations()) {
+ MatrixForTransformFunction(result, op, aRefBox);
+ }
+
+ float scale = float(AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
+ result.PreScale(1 / scale, 1 / scale, 1 / scale);
+ result.PostScale(scale, scale, scale);
+
+ return result;
+}
+
+static void ProcessTranslate(Matrix4x4& aMatrix,
+ const StyleTranslate& aTranslate,
+ TransformReferenceBox& aRefBox) {
+ switch (aTranslate.tag) {
+ case StyleTranslate::Tag::None:
+ return;
+ case StyleTranslate::Tag::Translate:
+ return ProcessTranslate3D(aMatrix, aTranslate.AsTranslate()._0,
+ aTranslate.AsTranslate()._1,
+ aTranslate.AsTranslate()._2, aRefBox);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Huh?");
+ }
+}
+
+static void ProcessRotate(Matrix4x4& aMatrix, const StyleRotate& aRotate) {
+ switch (aRotate.tag) {
+ case StyleRotate::Tag::None:
+ return;
+ case StyleRotate::Tag::Rotate:
+ aMatrix.RotateZ(aRotate.AsRotate().ToRadians());
+ return;
+ case StyleRotate::Tag::Rotate3D:
+ return ProcessRotate3D(aMatrix, aRotate.AsRotate3D()._0,
+ aRotate.AsRotate3D()._1, aRotate.AsRotate3D()._2,
+ aRotate.AsRotate3D()._3);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Huh?");
+ }
+}
+
+static void ProcessScale(Matrix4x4& aMatrix, const StyleScale& aScale) {
+ switch (aScale.tag) {
+ case StyleScale::Tag::None:
+ return;
+ case StyleScale::Tag::Scale:
+ return ProcessScaleHelper(aMatrix, aScale.AsScale()._0,
+ aScale.AsScale()._1, aScale.AsScale()._2);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Huh?");
+ }
+}
+
+Matrix4x4 ReadTransforms(const StyleTranslate& aTranslate,
+ const StyleRotate& aRotate, const StyleScale& aScale,
+ const ResolvedMotionPathData* aMotion,
+ const StyleTransform& aTransform,
+ TransformReferenceBox& aRefBox,
+ float aAppUnitsPerMatrixUnit) {
+ Matrix4x4 result;
+
+ ProcessTranslate(result, aTranslate, aRefBox);
+ ProcessRotate(result, aRotate);
+ ProcessScale(result, aScale);
+
+ if (aMotion) {
+ // Create the equivalent translate and rotate function, according to the
+ // order in spec. We combine the translate and then the rotate.
+ // https://drafts.fxtf.org/motion-1/#calculating-path-transform
+ //
+ // Besides, we have to shift the object by the delta between anchor-point
+ // and transform-origin, to make sure we rotate the object according to
+ // anchor-point.
+ result.PreTranslate(aMotion->mTranslate.x + aMotion->mShift.x,
+ aMotion->mTranslate.y + aMotion->mShift.y, 0.0);
+ if (aMotion->mRotate != 0.0) {
+ result.RotateZ(aMotion->mRotate);
+ }
+ // Shift the origin back to transform-origin.
+ result.PreTranslate(-aMotion->mShift.x, -aMotion->mShift.y, 0.0);
+ }
+
+ for (const StyleTransformOperation& op : aTransform.Operations()) {
+ MatrixForTransformFunction(result, op, aRefBox);
+ }
+
+ float scale = float(AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
+ result.PreScale(1 / scale, 1 / scale, 1 / scale);
+ result.PostScale(scale, scale, scale);
+
+ return result;
+}
+
+mozilla::CSSPoint Convert2DPosition(const mozilla::LengthPercentage& aX,
+ const mozilla::LengthPercentage& aY,
+ const CSSSize& aSize) {
+ return {
+ aX.ResolveToCSSPixels(aSize.width),
+ aY.ResolveToCSSPixels(aSize.height),
+ };
+}
+
+CSSPoint Convert2DPosition(const LengthPercentage& aX,
+ const LengthPercentage& aY,
+ TransformReferenceBox& aRefBox) {
+ return {
+ aX.ResolveToCSSPixelsWith(
+ [&] { return CSSPixel::FromAppUnits(aRefBox.Width()); }),
+ aY.ResolveToCSSPixelsWith(
+ [&] { return CSSPixel::FromAppUnits(aRefBox.Height()); }),
+ };
+}
+
+Point Convert2DPosition(const LengthPercentage& aX, const LengthPercentage& aY,
+ TransformReferenceBox& aRefBox,
+ int32_t aAppUnitsPerPixel) {
+ float scale = mozilla::AppUnitsPerCSSPixel() / float(aAppUnitsPerPixel);
+ CSSPoint p = Convert2DPosition(aX, aY, aRefBox);
+ return {p.x * scale, p.y * scale};
+}
+
+} // namespace nsStyleTransformMatrix
diff --git a/layout/style/nsStyleTransformMatrix.h b/layout/style/nsStyleTransformMatrix.h
new file mode 100644
index 0000000000..57e2742aa3
--- /dev/null
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 representing three matrices that can be used for style transforms.
+ */
+
+#ifndef nsStyleTransformMatrix_h_
+#define nsStyleTransformMatrix_h_
+
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "nsSize.h"
+#include "Units.h" // for CSSPoint
+#include <limits>
+
+class nsIFrame;
+class nsPresContext;
+struct gfxQuaternion;
+struct nsRect;
+
+namespace mozilla {
+struct ResolvedMotionPathData;
+} // namespace mozilla
+
+/**
+ * A helper to generate gfxMatrixes from css transform functions.
+ */
+namespace nsStyleTransformMatrix {
+// The operator passed to Servo backend.
+enum class MatrixTransformOperator : uint8_t { Interpolate, Accumulate };
+
+/**
+ * This class provides on-demand access to the 'reference box' for CSS
+ * transforms (needed to resolve percentage values in 'transform',
+ * 'transform-origin', etc.):
+ *
+ * http://dev.w3.org/csswg/css-transforms/#reference-box
+ *
+ * This class helps us to avoid calculating the reference box unless and
+ * until it is actually needed. This is important for performance when
+ * transforms are applied to SVG elements since the reference box for SVG is
+ * much more expensive to calculate (than for elements with a CSS layout box
+ * where we can use the nsIFrame's cached mRect), much more common (than on
+ * HTML), and yet very rarely have percentage values that require the
+ * reference box to be resolved. We also don't want to cause SVG frames to
+ * cache lots of ObjectBoundingBoxProperty objects that aren't needed.
+ *
+ * If UNIFIED_CONTINUATIONS (experimental, and currently broke) is defined,
+ * we consider the reference box for non-SVG frames to be the smallest
+ * rectangle containing a frame and all of its continuations. For example,
+ * if there is a <span> element with several continuations split over
+ * several lines, this function will return the rectangle containing all of
+ * those continuations. (This behavior is not currently in a spec.)
+ */
+class MOZ_STACK_CLASS TransformReferenceBox final {
+ public:
+ typedef nscoord (TransformReferenceBox::*DimensionGetter)();
+
+ TransformReferenceBox() = default;
+
+ explicit TransformReferenceBox(const nsIFrame* aFrame) : mFrame(aFrame) {
+ MOZ_ASSERT(mFrame);
+ }
+
+ TransformReferenceBox(const nsIFrame* aFrame,
+ const nsRect& aFallbackDimensions) {
+ mFrame = aFrame;
+ if (!mFrame) {
+ Init(aFallbackDimensions);
+ }
+ }
+
+ void Init(const nsIFrame* aFrame) {
+ MOZ_ASSERT(!mFrame && !mIsCached);
+ mFrame = aFrame;
+ }
+
+ void Init(const nsRect& aDimensions) {
+ MOZ_ASSERT(!mFrame && !mIsCached);
+ mBox = aDimensions;
+ mIsCached = true;
+ }
+
+ /**
+ * The offset of the reference box from the nsIFrame's TopLeft(). This
+ * is non-zero only in the case of SVG content. If we can successfully
+ * implement UNIFIED_CONTINUATIONS at some point in the future then it
+ * may also be non-zero for non-SVG content.
+ */
+ nscoord X() {
+ EnsureDimensionsAreCached();
+ return mBox.X();
+ }
+ nscoord Y() {
+ EnsureDimensionsAreCached();
+ return mBox.Y();
+ }
+
+ /**
+ * The size of the reference box.
+ */
+ nscoord Width() {
+ EnsureDimensionsAreCached();
+ return mBox.Width();
+ }
+ nscoord Height() {
+ EnsureDimensionsAreCached();
+ return mBox.Height();
+ }
+
+ bool IsEmpty() { return !mFrame; }
+
+ private:
+ // We don't really need to prevent copying, but since none of our consumers
+ // currently need to copy, preventing copying may allow us to catch some
+ // cases where we use pass-by-value instead of pass-by-reference.
+ TransformReferenceBox(const TransformReferenceBox&) = delete;
+
+ void EnsureDimensionsAreCached();
+
+ const nsIFrame* mFrame = nullptr;
+ nsRect mBox;
+ bool mIsCached = false;
+};
+
+float ProcessTranslatePart(
+ const mozilla::LengthPercentage& aValue, TransformReferenceBox* aRefBox,
+ TransformReferenceBox::DimensionGetter aDimensionGetter = nullptr);
+
+void ProcessInterpolateMatrix(mozilla::gfx::Matrix4x4& aMatrix,
+ const mozilla::StyleTransformOperation& aOp,
+ TransformReferenceBox& aBounds);
+
+void ProcessAccumulateMatrix(mozilla::gfx::Matrix4x4& aMatrix,
+ const mozilla::StyleTransformOperation& aOp,
+ TransformReferenceBox& aBounds);
+
+/**
+ * Given a StyleTransform containing transform functions, returns a matrix
+ * containing the value of those functions.
+ *
+ * @param aList the transform operation list.
+ * @param aBounds The frame's bounding rectangle.
+ * @param aAppUnitsPerMatrixUnit The number of app units per device pixel.
+ */
+mozilla::gfx::Matrix4x4 ReadTransforms(const mozilla::StyleTransform& aList,
+ TransformReferenceBox& aBounds,
+ float aAppUnitsPerMatrixUnit);
+
+// Generate the gfx::Matrix for CSS Transform Module Level 2.
+// https://drafts.csswg.org/css-transforms-2/#ctm
+mozilla::gfx::Matrix4x4 ReadTransforms(
+ const mozilla::StyleTranslate&, const mozilla::StyleRotate&,
+ const mozilla::StyleScale&, const mozilla::ResolvedMotionPathData* aMotion,
+ const mozilla::StyleTransform&, TransformReferenceBox& aRefBox,
+ float aAppUnitsPerMatrixUnit);
+
+/**
+ * Given the x and y values, compute the 2d position with respect to the given
+ * a reference box size that these values describe, in CSS pixels.
+ */
+mozilla::CSSPoint Convert2DPosition(const mozilla::LengthPercentage& aX,
+ const mozilla::LengthPercentage& aY,
+ const mozilla::CSSSize& aSize);
+
+/**
+ * Given the x and y values, compute the 2d position with respect to the given
+ * TransformReferenceBox that these values describe, in CSS pixels.
+ */
+mozilla::CSSPoint Convert2DPosition(const mozilla::LengthPercentage& aX,
+ const mozilla::LengthPercentage& aY,
+ TransformReferenceBox& aRefBox);
+
+/**
+ * Given the x and y values, compute the 2d position with respect to the given
+ * TransformReferenceBox that these values describe, in device pixels.
+ */
+mozilla::gfx::Point Convert2DPosition(const mozilla::LengthPercentage& aX,
+ const mozilla::LengthPercentage& aY,
+ TransformReferenceBox& aRefBox,
+ int32_t aAppUnitsPerDevPixel);
+
+} // namespace nsStyleTransformMatrix
+
+#endif
diff --git a/layout/style/nsStyleUtil.cpp b/layout/style/nsStyleUtil.cpp
new file mode 100644
index 0000000000..d2a45dd4c2
--- /dev/null
+++ b/layout/style/nsStyleUtil.cpp
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStyleUtil.h"
+#include "nsStyleConsts.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/ExpandedPrincipal.h"
+#include "mozilla/intl/MozLocaleBindings.h"
+#include "mozilla/intl/oxilangtag_ffi_generated.h"
+#include "mozilla/TextUtils.h"
+#include "nsIContent.h"
+#include "nsCSSProps.h"
+#include "nsContentUtils.h"
+#include "nsROCSSPrimitiveValue.h"
+#include "nsStyleStruct.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsLayoutUtils.h"
+#include "nsPrintfCString.h"
+#include <cctype>
+
+using namespace mozilla;
+
+//------------------------------------------------------------------------------
+// Font Algorithm Code
+//------------------------------------------------------------------------------
+
+// Compare two language strings
+bool nsStyleUtil::DashMatchCompare(const nsAString& aAttributeValue,
+ const nsAString& aSelectorValue,
+ const nsStringComparator& aComparator) {
+ bool result;
+ uint32_t selectorLen = aSelectorValue.Length();
+ uint32_t attributeLen = aAttributeValue.Length();
+ if (selectorLen > attributeLen) {
+ result = false;
+ } else {
+ nsAString::const_iterator iter;
+ if (selectorLen != attributeLen &&
+ *aAttributeValue.BeginReading(iter).advance(selectorLen) !=
+ char16_t('-')) {
+ // to match, the aAttributeValue must have a dash after the end of
+ // the aSelectorValue's text (unless the aSelectorValue and the
+ // aAttributeValue have the same text)
+ result = false;
+ } else {
+ result = StringBeginsWith(aAttributeValue, aSelectorValue, aComparator);
+ }
+ }
+ return result;
+}
+
+bool nsStyleUtil::LangTagCompare(const nsACString& aAttributeValue,
+ const nsACString& aSelectorValue) {
+ if (aAttributeValue.IsEmpty() || aSelectorValue.IsEmpty()) {
+ return false;
+ }
+
+ class MOZ_RAII AutoLangTag final {
+ public:
+ AutoLangTag() = delete;
+ AutoLangTag(const AutoLangTag& aOther) = delete;
+ explicit AutoLangTag(const nsACString& aLangTag) {
+ mLangTag = intl::ffi::lang_tag_new(&aLangTag);
+ }
+
+ ~AutoLangTag() {
+ if (mLangTag) {
+ intl::ffi::lang_tag_destroy(mLangTag);
+ }
+ }
+
+ bool IsValid() const { return mLangTag; }
+ operator intl::ffi::LangTag*() const { return mLangTag; }
+
+ void Reset(const nsACString& aLangTag) {
+ if (mLangTag) {
+ intl::ffi::lang_tag_destroy(mLangTag);
+ }
+ mLangTag = intl::ffi::lang_tag_new(&aLangTag);
+ }
+
+ private:
+ intl::ffi::LangTag* mLangTag = nullptr;
+ };
+
+ AutoLangTag langAttr(aAttributeValue);
+
+ // Non-BCP47 extension: recognize '_' as an alternative subtag delimiter.
+ nsAutoCString attrTemp;
+ if (!langAttr.IsValid()) {
+ if (aAttributeValue.Contains('_')) {
+ attrTemp = aAttributeValue;
+ attrTemp.ReplaceChar('_', '-');
+ langAttr.Reset(attrTemp);
+ }
+ }
+
+ if (!langAttr.IsValid()) {
+ return false;
+ }
+
+ return intl::ffi::lang_tag_matches(langAttr, &aSelectorValue);
+}
+
+bool nsStyleUtil::ValueIncludes(const nsAString& aValueList,
+ const nsAString& aValue,
+ const nsStringComparator& aComparator) {
+ const char16_t *p = aValueList.BeginReading(),
+ *p_end = aValueList.EndReading();
+
+ while (p < p_end) {
+ // skip leading space
+ while (p != p_end && nsContentUtils::IsHTMLWhitespace(*p)) ++p;
+
+ const char16_t* val_start = p;
+
+ // look for space or end
+ while (p != p_end && !nsContentUtils::IsHTMLWhitespace(*p)) ++p;
+
+ const char16_t* val_end = p;
+
+ if (val_start < val_end &&
+ aValue.Equals(Substring(val_start, val_end), aComparator))
+ return true;
+
+ ++p; // we know the next character is not whitespace
+ }
+ return false;
+}
+
+void nsStyleUtil::AppendEscapedCSSString(const nsAString& aString,
+ nsAString& aReturn,
+ char16_t quoteChar) {
+ MOZ_ASSERT(quoteChar == '\'' || quoteChar == '"',
+ "CSS strings must be quoted with ' or \"");
+
+ aReturn.Append(quoteChar);
+
+ const char16_t* in = aString.BeginReading();
+ const char16_t* const end = aString.EndReading();
+ for (; in != end; in++) {
+ if (*in < 0x20 || *in == 0x7F) {
+ // Escape U+0000 through U+001F and U+007F numerically.
+ aReturn.AppendPrintf("\\%x ", *in);
+ } else {
+ if (*in == '"' || *in == '\'' || *in == '\\') {
+ // Escape backslash and quote characters symbolically.
+ // It's not technically necessary to escape the quote
+ // character that isn't being used to delimit the string,
+ // but we do it anyway because that makes testing simpler.
+ aReturn.Append(char16_t('\\'));
+ }
+ aReturn.Append(*in);
+ }
+ }
+
+ aReturn.Append(quoteChar);
+}
+
+/* static */
+void nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent,
+ nsAString& aReturn) {
+ // The relevant parts of the CSS grammar are:
+ // ident ([-]?{nmstart}|[-][-]){nmchar}*
+ // nmstart [_a-z]|{nonascii}|{escape}
+ // nmchar [_a-z0-9-]|{nonascii}|{escape}
+ // nonascii [^\0-\177]
+ // escape {unicode}|\\[^\n\r\f0-9a-f]
+ // unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
+ // from http://www.w3.org/TR/CSS21/syndata.html#tokenization but
+ // modified for idents by
+ // http://dev.w3.org/csswg/cssom/#serialize-an-identifier and
+ // http://dev.w3.org/csswg/css-syntax/#would-start-an-identifier
+
+ const char16_t* in = aIdent.BeginReading();
+ const char16_t* const end = aIdent.EndReading();
+
+ if (in == end) return;
+
+ // A leading dash does not need to be escaped as long as it is not the
+ // *only* character in the identifier.
+ if (*in == '-') {
+ if (in + 1 == end) {
+ aReturn.Append(char16_t('\\'));
+ aReturn.Append(char16_t('-'));
+ return;
+ }
+
+ aReturn.Append(char16_t('-'));
+ ++in;
+ }
+
+ // Escape a digit at the start (including after a dash),
+ // numerically. If we didn't escape it numerically, it would get
+ // interpreted as a numeric escape for the wrong character.
+ if (in != end && ('0' <= *in && *in <= '9')) {
+ aReturn.AppendPrintf("\\%x ", *in);
+ ++in;
+ }
+
+ for (; in != end; ++in) {
+ char16_t ch = *in;
+ if (ch == 0x00) {
+ aReturn.Append(char16_t(0xFFFD));
+ } else if (ch < 0x20 || 0x7F == ch) {
+ // Escape U+0000 through U+001F and U+007F numerically.
+ aReturn.AppendPrintf("\\%x ", *in);
+ } else {
+ // Escape ASCII non-identifier printables as a backslash plus
+ // the character.
+ if (ch < 0x7F && ch != '_' && ch != '-' && (ch < '0' || '9' < ch) &&
+ (ch < 'A' || 'Z' < ch) && (ch < 'a' || 'z' < ch)) {
+ aReturn.Append(char16_t('\\'));
+ }
+ aReturn.Append(ch);
+ }
+ }
+}
+
+/* static */
+float nsStyleUtil::ColorComponentToFloat(uint8_t aAlpha) {
+ // Alpha values are expressed as decimals, so we should convert
+ // back, using as few decimal places as possible for
+ // round-tripping.
+ // First try two decimal places:
+ float rounded = NS_roundf(float(aAlpha) * 100.0f / 255.0f) / 100.0f;
+ if (FloatToColorComponent(rounded) != aAlpha) {
+ // Use three decimal places.
+ rounded = NS_roundf(float(aAlpha) * 1000.0f / 255.0f) / 1000.0f;
+ }
+ return rounded;
+}
+
+/* static */
+void nsStyleUtil::GetSerializedColorValue(nscolor aColor,
+ nsAString& aSerializedColor) {
+ MOZ_ASSERT(aSerializedColor.IsEmpty());
+
+ const bool hasAlpha = NS_GET_A(aColor) != 255;
+ if (hasAlpha) {
+ aSerializedColor.AppendLiteral("rgba(");
+ } else {
+ aSerializedColor.AppendLiteral("rgb(");
+ }
+ aSerializedColor.AppendInt(NS_GET_R(aColor));
+ aSerializedColor.AppendLiteral(", ");
+ aSerializedColor.AppendInt(NS_GET_G(aColor));
+ aSerializedColor.AppendLiteral(", ");
+ aSerializedColor.AppendInt(NS_GET_B(aColor));
+ if (hasAlpha) {
+ aSerializedColor.AppendLiteral(", ");
+ float alpha = nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor));
+ nsStyleUtil::AppendCSSNumber(alpha, aSerializedColor);
+ }
+ aSerializedColor.AppendLiteral(")");
+}
+
+/* static */
+bool nsStyleUtil::IsSignificantChild(nsIContent* aChild,
+ bool aWhitespaceIsSignificant) {
+ bool isText = aChild->IsText();
+
+ if (!isText && !aChild->IsComment() && !aChild->IsProcessingInstruction()) {
+ return true;
+ }
+
+ return isText && aChild->TextLength() != 0 &&
+ (aWhitespaceIsSignificant || !aChild->TextIsOnlyWhitespace());
+}
+
+/* static */
+bool nsStyleUtil::ThreadSafeIsSignificantChild(const nsIContent* aChild,
+ bool aWhitespaceIsSignificant) {
+ bool isText = aChild->IsText();
+
+ if (!isText && !aChild->IsComment() && !aChild->IsProcessingInstruction()) {
+ return true;
+ }
+
+ return isText && aChild->TextLength() != 0 &&
+ (aWhitespaceIsSignificant ||
+ !aChild->ThreadSafeTextIsOnlyWhitespace());
+}
+
+// For a replaced element whose concrete object size is no larger than the
+// element's content-box, this method checks whether the given
+// "object-position" coordinate might cause overflow in its dimension.
+static bool ObjectPositionCoordMightCauseOverflow(
+ const LengthPercentage& aCoord) {
+ // Any nonzero length in "object-position" can push us to overflow
+ // (particularly if our concrete object size is exactly the same size as the
+ // replaced element's content-box).
+ if (!aCoord.ConvertsToPercentage()) {
+ return !aCoord.ConvertsToLength() || aCoord.ToLengthInCSSPixels() != 0.0f;
+ }
+
+ // Percentages are interpreted as a fraction of the extra space. So,
+ // percentages in the 0-100% range are safe, but values outside of that
+ // range could cause overflow.
+ float percentage = aCoord.ToPercentage();
+ return percentage < 0.0f || percentage > 1.0f;
+}
+
+/* static */
+bool nsStyleUtil::ObjectPropsMightCauseOverflow(
+ const nsStylePosition* aStylePos) {
+ auto objectFit = aStylePos->mObjectFit;
+
+ // "object-fit: cover" & "object-fit: none" can give us a render rect that's
+ // larger than our container element's content-box.
+ if (objectFit == StyleObjectFit::Cover || objectFit == StyleObjectFit::None) {
+ return true;
+ }
+ // (All other object-fit values produce a concrete object size that's no
+ // larger than the constraint region.)
+
+ // Check each of our "object-position" coords to see if it could cause
+ // overflow in its dimension:
+ const Position& objectPosistion = aStylePos->mObjectPosition;
+ if (ObjectPositionCoordMightCauseOverflow(objectPosistion.horizontal) ||
+ ObjectPositionCoordMightCauseOverflow(objectPosistion.vertical)) {
+ return true;
+ }
+
+ return false;
+}
+
+/* static */
+bool nsStyleUtil::CSPAllowsInlineStyle(
+ dom::Element* aElement, dom::Document* aDocument,
+ nsIPrincipal* aTriggeringPrincipal, uint32_t aLineNumber,
+ uint32_t aColumnNumber, const nsAString& aStyleText, nsresult* aRv) {
+ nsresult rv;
+
+ if (aRv) {
+ *aRv = NS_OK;
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ if (aTriggeringPrincipal && BasePrincipal::Cast(aTriggeringPrincipal)
+ ->OverridesCSP(aDocument->NodePrincipal())) {
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aTriggeringPrincipal);
+ if (ep) {
+ csp = ep->GetCsp();
+ }
+ } else {
+ csp = aDocument->GetCsp();
+ }
+
+ if (!csp) {
+ // No CSP --> the style is allowed
+ return true;
+ }
+
+ // Hack to allow Devtools to edit inline styles
+ if (csp->GetSkipAllowInlineStyleCheck()) {
+ return true;
+ }
+
+ bool isStyleElement = false;
+ // Query the nonce.
+ nsAutoString nonce;
+ if (aElement && aElement->NodeInfo()->NameAtom() == nsGkAtoms::style) {
+ isStyleElement = true;
+ nsString* cspNonce =
+ static_cast<nsString*>(aElement->GetProperty(nsGkAtoms::nonce));
+ if (cspNonce) {
+ nonce = *cspNonce;
+ }
+ }
+
+ bool allowInlineStyle = true;
+ rv = csp->GetAllowsInline(
+ isStyleElement ? nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE
+ : nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE,
+ !isStyleElement /* aHasUnsafeHash */, nonce,
+ false, // aParserCreated only applies to scripts
+ aElement, nullptr, // nsICSPEventListener
+ aStyleText, aLineNumber, aColumnNumber, &allowInlineStyle);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return allowInlineStyle;
+}
diff --git a/layout/style/nsStyleUtil.h b/layout/style/nsStyleUtil.h
new file mode 100644
index 0000000000..990996fdbc
--- /dev/null
+++ b/layout/style/nsStyleUtil.h
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsStyleUtil_h___
+#define nsStyleUtil_h___
+
+#include "nsCoord.h"
+#include "nsCSSPropertyID.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsStringFwd.h"
+#include "nsCRT.h"
+#include "nsColor.h"
+#include "nsGkAtoms.h"
+
+class nsCSSValue;
+class nsIContent;
+class nsIPrincipal;
+class nsIURI;
+struct gfxFontFeature;
+struct nsCSSKTableEntry;
+struct nsCSSValueList;
+struct nsStylePosition;
+
+namespace mozilla {
+namespace dom {
+class Document;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+// Style utility functions
+class nsStyleUtil {
+ public:
+ static bool DashMatchCompare(const nsAString& aAttributeValue,
+ const nsAString& aSelectorValue,
+ const nsStringComparator& aComparator);
+
+ static bool LangTagCompare(const nsACString& aAttributeValue,
+ const nsACString& aSelectorValue);
+
+ static bool ValueIncludes(const nsAString& aValueList,
+ const nsAString& aValue,
+ const nsStringComparator& aComparator);
+
+ // Append a quoted (with 'quoteChar') and escaped version of aString
+ // to aResult. 'quoteChar' must be ' or ".
+ static void AppendEscapedCSSString(const nsAString& aString,
+ nsAString& aResult,
+ char16_t quoteChar = '"');
+
+ // Append the identifier given by |aIdent| to |aResult|, with
+ // appropriate escaping so that it can be reparsed to the same
+ // identifier. An exception is if aIdent contains U+0000, which
+ // will be escaped as U+FFFD and then reparsed back to U+FFFD.
+ static void AppendEscapedCSSIdent(const nsAString& aIdent,
+ nsAString& aResult);
+
+ public:
+ static void AppendCSSNumber(float aNumber, nsAString& aResult) {
+ aResult.AppendFloat(aNumber);
+ }
+
+ /*
+ * Convert an author-provided floating point number to an integer (0
+ * ... 255) appropriate for use in the alpha component of a color.
+ */
+ static uint8_t FloatToColorComponent(float aAlpha) {
+ NS_ASSERTION(0.0 <= aAlpha && aAlpha <= 1.0, "out of range");
+ return static_cast<uint8_t>(NSToIntRound(aAlpha * 255));
+ }
+
+ /*
+ * Convert the alpha component of an nscolor (0 ... 255) to the
+ * floating point number with the least accurate *decimal*
+ * representation that is converted to that color.
+ *
+ * Should be used only by serialization code.
+ */
+ static float ColorComponentToFloat(uint8_t aAlpha);
+
+ /**
+ * GetSerializedColorValue() computes serialized color value of aColor and
+ * returns it with aSerializedColor.
+ * https://drafts.csswg.org/cssom/#serialize-a-css-component-value
+ */
+ static void GetSerializedColorValue(nscolor aColor,
+ nsAString& aSerializedColor);
+
+ /*
+ * Does this child count as significant for selector matching?
+ */
+ static bool IsSignificantChild(nsIContent* aChild,
+ bool aWhitespaceIsSignificant);
+
+ /*
+ * Thread-safe version of IsSignificantChild()
+ */
+ static bool ThreadSafeIsSignificantChild(const nsIContent* aChild,
+ bool aWhitespaceIsSignificant);
+ /**
+ * Returns true if our object-fit & object-position properties might cause
+ * a replaced element's contents to overflow its content-box (requiring
+ * clipping), or false if we can be sure that this won't happen.
+ *
+ * This lets us optimize by skipping clipping when we can tell it's
+ * unnecessary (particularly with the default values of these properties).
+ *
+ * @param aStylePos The nsStylePosition whose object-fit & object-position
+ * properties should be checked for potential overflow.
+ * @return false if we can be sure that the object-fit & object-position
+ * properties on 'aStylePos' cannot cause a replaced element's
+ * contents to overflow its content-box. Otherwise (if overflow is
+ * is possible), returns true.
+ */
+ static bool ObjectPropsMightCauseOverflow(const nsStylePosition* aStylePos);
+
+ /*
+ * Does the document have a CSP that blocks the application of
+ * inline styles? Returns false if application of the style should
+ * be blocked.
+ *
+ * @param aContent
+ * The <style> element that the caller wants to know whether to honor.
+ * Included to check the nonce attribute if one is provided. Allowed to
+ * be null, if this is for something other than a <style> element (in
+ * which case nonces won't be checked).
+ * @param aDocument
+ * The document containing the inline style (for querying the CSP);
+ * @param aTriggeringPrincipal
+ * The principal of the scripted caller which added the inline
+ * stylesheet, or null if no scripted caller can be identified.
+ * @param aLineNumber
+ * Line number of inline style element in the containing document (for
+ * reporting violations)
+ * @param aColumnNumber
+ * Column number of inline style element in the containing document (for
+ * reporting violations)
+ * @param aStyleText
+ * Contents of the inline style element (for reporting violations)
+ * @param aRv
+ * Return error code in case of failure
+ * @return
+ * Does CSP allow application of the specified inline style?
+ */
+ static bool CSPAllowsInlineStyle(mozilla::dom::Element* aContent,
+ mozilla::dom::Document* aDocument,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aLineNumber, uint32_t aColumnNumber,
+ const nsAString& aStyleText, nsresult* aRv);
+
+ template <size_t N>
+ static bool MatchesLanguagePrefix(const char16_t* aLang, size_t aLen,
+ const char16_t (&aPrefix)[N]) {
+ return !NS_strncmp(aLang, aPrefix, N - 1) &&
+ (aLen == N - 1 || aLang[N - 1] == '-');
+ }
+
+ template <size_t N>
+ static bool MatchesLanguagePrefix(const nsAtom* aLang,
+ const char16_t (&aPrefix)[N]) {
+ MOZ_ASSERT(aLang);
+ return MatchesLanguagePrefix(aLang->GetUTF16String(), aLang->GetLength(),
+ aPrefix);
+ }
+
+ template <size_t N>
+ static bool MatchesLanguagePrefix(const nsAString& aLang,
+ const char16_t (&aPrefix)[N]) {
+ return MatchesLanguagePrefix(aLang.Data(), aLang.Length(), aPrefix);
+ }
+};
+
+#endif /* nsStyleUtil_h___ */
diff --git a/layout/style/nsTransitionManager.cpp b/layout/style/nsTransitionManager.cpp
new file mode 100644
index 0000000000..ad3809c37f
--- /dev/null
+++ b/layout/style/nsTransitionManager.cpp
@@ -0,0 +1,497 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Code to start and animate CSS transitions. */
+
+#include "nsTransitionManager.h"
+#include "mozilla/dom/Document.h"
+#include "nsAnimationManager.h"
+
+#include "nsIContent.h"
+#include "AnimatedPropertyID.h"
+#include "AnimatedPropertyIDSet.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsCSSPropertyIDSet.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/ElementAnimationData.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/dom/Element.h"
+#include "nsIFrame.h"
+#include "nsCSSProps.h"
+#include "nsCSSPseudoElements.h"
+#include "nsDisplayList.h"
+#include "nsRFPService.h"
+#include "nsStyleChangeList.h"
+#include "mozilla/RestyleManager.h"
+
+using mozilla::dom::CSSTransition;
+using mozilla::dom::DocumentTimeline;
+using mozilla::dom::KeyframeEffect;
+
+using namespace mozilla;
+using namespace mozilla::css;
+
+bool nsTransitionManager::UpdateTransitions(dom::Element* aElement,
+ PseudoStyleType aPseudoType,
+ const ComputedStyle& aOldStyle,
+ const ComputedStyle& aNewStyle) {
+ if (mPresContext->Medium() == nsGkAtoms::print) {
+ // For print or print preview, ignore transitions.
+ return false;
+ }
+
+ MOZ_ASSERT(mPresContext->IsDynamic());
+ if (aNewStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {
+ StopAnimationsForElement(aElement, aPseudoType);
+ return false;
+ }
+
+ auto* collection = CSSTransitionCollection::Get(aElement, aPseudoType);
+ return DoUpdateTransitions(*aNewStyle.StyleUIReset(), aElement, aPseudoType,
+ collection, aOldStyle, aNewStyle);
+}
+
+// This function expands the shorthands and "all" keyword specified in
+// transition-property, and then execute |aHandler| on the expanded longhand.
+// |aHandler| should be a lamda function which accepts nsCSSPropertyID.
+template <typename T>
+static void ExpandTransitionProperty(const StyleTransitionProperty& aProperty,
+ T aHandler) {
+ switch (aProperty.tag) {
+ case StyleTransitionProperty::Tag::Unsupported:
+ break;
+ case StyleTransitionProperty::Tag::Custom: {
+ AnimatedPropertyID property(aProperty.AsCustom().AsAtom());
+ aHandler(property);
+ break;
+ }
+ case StyleTransitionProperty::Tag::NonCustom: {
+ nsCSSPropertyID id = nsCSSPropertyID(aProperty.AsNonCustom()._0);
+ if (nsCSSProps::IsShorthand(id)) {
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, id,
+ CSSEnabledState::ForAllContent) {
+ AnimatedPropertyID property(*subprop);
+ aHandler(property);
+ }
+ } else {
+ AnimatedPropertyID property(id);
+ aHandler(property);
+ }
+ break;
+ }
+ }
+}
+
+bool nsTransitionManager::DoUpdateTransitions(
+ const nsStyleUIReset& aStyle, dom::Element* aElement,
+ PseudoStyleType aPseudoType, CSSTransitionCollection*& aElementTransitions,
+ const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle) {
+ MOZ_ASSERT(!aElementTransitions || &aElementTransitions->mElement == aElement,
+ "Element mismatch");
+
+ // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
+ // I'll consider only the transitions from the number of items in
+ // 'transition-property' on down, and later ones will override earlier
+ // ones (tracked using |propertiesChecked|).
+ bool startedAny = false;
+ AnimatedPropertyIDSet propertiesChecked;
+ for (uint32_t i = aStyle.mTransitionPropertyCount; i--;) {
+ const float delay = aStyle.GetTransitionDelay(i).ToMilliseconds();
+
+ // The spec says a negative duration is treated as zero.
+ const float duration =
+ std::max(aStyle.GetTransitionDuration(i).ToMilliseconds(), 0.0f);
+
+ // If the combined duration of this transition is 0 or less we won't start a
+ // transition, we can avoid even looking at transition-property if we're the
+ // last one.
+ if (i == 0 && delay + duration <= 0.0f) {
+ continue;
+ }
+
+ ExpandTransitionProperty(aStyle.GetTransitionProperty(i),
+ [&](const AnimatedPropertyID& aProperty) {
+ // We might have something to transition. See if
+ // any of the properties in question changed and
+ // are animatable.
+ startedAny |= ConsiderInitiatingTransition(
+ aProperty, aStyle, i, delay, duration,
+ aElement, aPseudoType, aElementTransitions,
+ aOldStyle, aNewStyle, propertiesChecked);
+ });
+ }
+
+ // Stop any transitions for properties that are no longer in
+ // 'transition-property', including finished transitions.
+ // Also stop any transitions (and remove any finished transitions)
+ // for properties that just changed (and are still in the set of
+ // properties to transition), but for which we didn't just start the
+ // transition. This can happen delay and duration are both zero, or
+ // because the new value is not interpolable.
+ if (aElementTransitions) {
+ const bool checkProperties = !aStyle.GetTransitionProperty(0).IsAll();
+ AnimatedPropertyIDSet allTransitionProperties;
+ if (checkProperties) {
+ for (uint32_t i = aStyle.mTransitionPropertyCount; i-- != 0;) {
+ ExpandTransitionProperty(aStyle.GetTransitionProperty(i),
+ [&](const AnimatedPropertyID& aProperty) {
+ allTransitionProperties.AddProperty(
+ aProperty.ToPhysical(aNewStyle));
+ });
+ }
+ }
+
+ OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
+ size_t i = animations.Length();
+ MOZ_ASSERT(i != 0, "empty transitions list?");
+ AnimationValue currentValue;
+ do {
+ --i;
+ CSSTransition* anim = animations[i];
+ const AnimatedPropertyID& property = anim->TransitionProperty();
+ if (
+ // Properties no longer in `transition-property`.
+ (checkProperties && !allTransitionProperties.HasProperty(property)) ||
+ // Properties whose computed values changed but for which we did not
+ // start a new transition (because delay and duration are both zero,
+ // or because the new value is not interpolable); a new transition
+ // would have anim->ToValue() matching currentValue.
+ !Servo_ComputedValues_TransitionValueMatches(
+ &aNewStyle, &property, anim->ToValue().mServo.get())) {
+ // Stop the transition.
+ DoCancelTransition(aElement, aPseudoType, aElementTransitions, i);
+ }
+ } while (i != 0);
+ }
+
+ return startedAny;
+}
+
+static Keyframe& AppendKeyframe(double aOffset,
+ const AnimatedPropertyID& aProperty,
+ AnimationValue&& aValue,
+ nsTArray<Keyframe>& aKeyframes) {
+ Keyframe& frame = *aKeyframes.AppendElement();
+ frame.mOffset.emplace(aOffset);
+ MOZ_ASSERT(aValue.mServo);
+ RefPtr<StyleLockedDeclarationBlock> decl =
+ Servo_AnimationValue_Uncompute(aValue.mServo).Consume();
+ frame.mPropertyValues.AppendElement(
+ PropertyValuePair(aProperty, std::move(decl)));
+ return frame;
+}
+
+static nsTArray<Keyframe> GetTransitionKeyframes(
+ const AnimatedPropertyID& aProperty, AnimationValue&& aStartValue,
+ AnimationValue&& aEndValue) {
+ nsTArray<Keyframe> keyframes(2);
+
+ AppendKeyframe(0.0, aProperty, std::move(aStartValue), keyframes);
+ AppendKeyframe(1.0, aProperty, std::move(aEndValue), keyframes);
+
+ return keyframes;
+}
+
+static Maybe<CSSTransition::ReplacedTransitionProperties>
+GetReplacedTransitionProperties(const CSSTransition* aTransition,
+ const DocumentTimeline* aTimelineToMatch) {
+ Maybe<CSSTransition::ReplacedTransitionProperties> result;
+
+ // Transition needs to be currently running on the compositor to be
+ // replaceable.
+ if (!aTransition || !aTransition->HasCurrentEffect() ||
+ !aTransition->IsRunningOnCompositor() ||
+ aTransition->GetStartTime().IsNull()) {
+ return result;
+ }
+
+ // Transition needs to be running on the same timeline.
+ if (aTransition->GetTimeline() != aTimelineToMatch) {
+ return result;
+ }
+
+ // The transition needs to have a keyframe effect.
+ const KeyframeEffect* keyframeEffect =
+ aTransition->GetEffect() ? aTransition->GetEffect()->AsKeyframeEffect()
+ : nullptr;
+ if (!keyframeEffect) {
+ return result;
+ }
+
+ // The keyframe effect needs to be a simple transition of the original
+ // transition property (i.e. not replaced with something else).
+ if (keyframeEffect->Properties().Length() != 1 ||
+ keyframeEffect->Properties()[0].mSegments.Length() != 1 ||
+ keyframeEffect->Properties()[0].mProperty !=
+ aTransition->TransitionProperty()) {
+ return result;
+ }
+
+ const AnimationPropertySegment& segment =
+ keyframeEffect->Properties()[0].mSegments[0];
+
+ result.emplace(CSSTransition::ReplacedTransitionProperties(
+ {aTransition->GetStartTime().Value(), aTransition->PlaybackRate(),
+ keyframeEffect->SpecifiedTiming(), segment.mTimingFunction,
+ segment.mFromValue, segment.mToValue}));
+
+ return result;
+}
+
+bool nsTransitionManager::ConsiderInitiatingTransition(
+ const AnimatedPropertyID& aProperty, const nsStyleUIReset& aStyle,
+ uint32_t aTransitionIndex, float aDelay, float aDuration,
+ dom::Element* aElement, PseudoStyleType aPseudoType,
+ CSSTransitionCollection*& aElementTransitions,
+ const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle,
+ AnimatedPropertyIDSet& aPropertiesChecked) {
+ // IsShorthand itself will assert if aProperty is not a property.
+ MOZ_ASSERT(aProperty.IsCustom() || !nsCSSProps::IsShorthand(aProperty.mID),
+ "property out of range");
+ NS_ASSERTION(
+ !aElementTransitions || &aElementTransitions->mElement == aElement,
+ "Element mismatch");
+
+ AnimatedPropertyID property = aProperty.ToPhysical(aNewStyle);
+
+ // A later item in transition-property already specified a transition for
+ // this property, so we ignore this one.
+ //
+ // See http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
+ if (aPropertiesChecked.HasProperty(property)) {
+ return false;
+ }
+
+ aPropertiesChecked.AddProperty(property);
+
+ if (aDuration + aDelay <= 0.0f) {
+ return false;
+ }
+
+ size_t currentIndex = nsTArray<KeyframeEffect>::NoIndex;
+ const auto* oldTransition = [&]() -> const CSSTransition* {
+ if (!aElementTransitions) {
+ return nullptr;
+ }
+ const OwningCSSTransitionPtrArray& animations =
+ aElementTransitions->mAnimations;
+ for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
+ if (animations[i]->TransitionProperty() == property) {
+ currentIndex = i;
+ return animations[i];
+ }
+ }
+ return nullptr;
+ }();
+
+ AnimationValue startValue, endValue;
+ const StyleShouldTransitionResult result =
+ Servo_ComputedValues_ShouldTransition(
+ &aOldStyle, &aNewStyle, &property,
+ oldTransition ? oldTransition->ToValue().mServo.get() : nullptr,
+ &startValue.mServo, &endValue.mServo);
+
+ // If we got a style change that changed the value to the endpoint
+ // of the currently running transition, we don't want to interrupt
+ // its timing function.
+ // This needs to be before the !shouldAnimate && haveCurrentTransition
+ // case below because we might be close enough to the end of the
+ // transition that the current value rounds to the final value. In
+ // this case, we'll end up with shouldAnimate as false (because
+ // there's no value change), but we need to return early here rather
+ // than cancel the running transition because shouldAnimate is false!
+ //
+ // Likewise, if we got a style change that changed the value to the
+ // endpoint of our finished transition, we also don't want to start
+ // a new transition for the reasons described in
+ // https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .
+ if (result.old_transition_value_matches) {
+ // GetAnimationRule already called RestyleForAnimation.
+ return false;
+ }
+
+ if (!result.should_animate) {
+ if (oldTransition) {
+ // We're in the middle of a transition, and just got a non-transition
+ // style change to something that we can't animate. This might happen
+ // because we got a non-transition style change changing to the current
+ // in-progress value (which is particularly easy to cause when we're
+ // currently in the 'transition-delay'). It also might happen because we
+ // just got a style change to a value that can't be interpolated.
+ DoCancelTransition(aElement, aPseudoType, aElementTransitions,
+ currentIndex);
+ }
+ return false;
+ }
+
+ AnimationValue startForReversingTest = startValue;
+ double reversePortion = 1.0;
+
+ // If the new transition reverses an existing one, we'll need to
+ // handle the timing differently.
+ if (oldTransition && oldTransition->HasCurrentEffect() &&
+ oldTransition->StartForReversingTest() == endValue) {
+ // Compute the appropriate negative transition-delay such that right
+ // now we'd end up at the current position.
+ double valuePortion =
+ oldTransition->CurrentValuePortion() * oldTransition->ReversePortion() +
+ (1.0 - oldTransition->ReversePortion());
+ // A timing function with negative y1 (or y2!) might make
+ // valuePortion negative. In this case, we still want to apply our
+ // reversing logic based on relative distances, not make duration
+ // negative.
+ if (valuePortion < 0.0) {
+ valuePortion = -valuePortion;
+ }
+ // A timing function with y2 (or y1!) greater than one might
+ // advance past its terminal value. It's probably a good idea to
+ // clamp valuePortion to be at most one to preserve the invariant
+ // that a transition will complete within at most its specified
+ // time.
+ if (valuePortion > 1.0) {
+ valuePortion = 1.0;
+ }
+
+ // Negative delays are essentially part of the transition
+ // function, so reduce them along with the duration, but don't
+ // reduce positive delays.
+ if (aDelay < 0.0f && std::isfinite(aDelay)) {
+ aDelay *= valuePortion;
+ }
+
+ if (std::isfinite(aDuration)) {
+ aDuration *= valuePortion;
+ }
+
+ startForReversingTest = oldTransition->ToValue();
+ reversePortion = valuePortion;
+ }
+
+ TimingParams timing = TimingParamsFromCSSParams(
+ aDuration, aDelay, 1.0 /* iteration count */,
+ StyleAnimationDirection::Normal, StyleAnimationFillMode::Backwards);
+
+ const StyleComputedTimingFunction& tf =
+ aStyle.GetTransitionTimingFunction(aTransitionIndex);
+ if (!tf.IsLinearKeyword()) {
+ timing.SetTimingFunction(Some(tf));
+ }
+
+ RefPtr<CSSTransition> transition = DoCreateTransition(
+ property, aElement, aPseudoType, aNewStyle, aElementTransitions,
+ std::move(timing), std::move(startValue), std::move(endValue),
+ std::move(startForReversingTest), reversePortion);
+ if (!transition) {
+ return false;
+ }
+
+ OwningCSSTransitionPtrArray& transitions = aElementTransitions->mAnimations;
+#ifdef DEBUG
+ for (size_t i = 0, i_end = transitions.Length(); i < i_end; ++i) {
+ MOZ_ASSERT(
+ i == currentIndex || transitions[i]->TransitionProperty() != property,
+ "duplicate transitions for property");
+ }
+#endif
+ if (oldTransition) {
+ // If this new transition is replacing an existing transition that is
+ // running on the compositor, we store select parameters from the replaced
+ // transition so that later, once all scripts have run, we can update the
+ // start value of the transition using TimeStamp::Now(). This allows us to
+ // avoid a large jump when starting a new transition when the main thread
+ // lags behind the compositor.
+ const dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
+ auto replacedTransitionProperties =
+ GetReplacedTransitionProperties(oldTransition, timeline);
+ if (replacedTransitionProperties) {
+ transition->SetReplacedTransition(
+ std::move(replacedTransitionProperties.ref()));
+ }
+
+ transitions[currentIndex]->CancelFromStyle(PostRestyleMode::IfNeeded);
+ oldTransition = nullptr; // Clear pointer so it doesn't dangle
+ transitions[currentIndex] = transition;
+ } else {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ transitions.AppendElement(transition);
+ }
+
+ if (auto* effectSet = EffectSet::Get(aElement, aPseudoType)) {
+ effectSet->UpdateAnimationGeneration(mPresContext);
+ }
+
+ return true;
+}
+
+already_AddRefed<CSSTransition> nsTransitionManager::DoCreateTransition(
+ const AnimatedPropertyID& aProperty, dom::Element* aElement,
+ PseudoStyleType aPseudoType, const mozilla::ComputedStyle& aNewStyle,
+ CSSTransitionCollection*& aElementTransitions, TimingParams&& aTiming,
+ AnimationValue&& aStartValue, AnimationValue&& aEndValue,
+ AnimationValue&& aStartForReversingTest, double aReversePortion) {
+ dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
+ KeyframeEffectParams effectOptions;
+ RefPtr<KeyframeEffect> keyframeEffect = new KeyframeEffect(
+ aElement->OwnerDoc(), OwningAnimationTarget(aElement, aPseudoType),
+ std::move(aTiming), effectOptions);
+
+ keyframeEffect->SetKeyframes(
+ GetTransitionKeyframes(aProperty, std::move(aStartValue),
+ std::move(aEndValue)),
+ &aNewStyle, timeline);
+
+ if (NS_WARN_IF(MOZ_UNLIKELY(!keyframeEffect->IsValidTransition()))) {
+ return nullptr;
+ }
+
+ RefPtr<CSSTransition> animation =
+ new CSSTransition(mPresContext->Document()->GetScopeObject(), aProperty);
+ animation->SetOwningElement(OwningElementRef(*aElement, aPseudoType));
+ animation->SetTimelineNoUpdate(timeline);
+ animation->SetCreationSequence(
+ mPresContext->RestyleManager()->GetAnimationGeneration());
+ animation->SetEffectFromStyle(keyframeEffect);
+ animation->SetReverseParameters(std::move(aStartForReversingTest),
+ aReversePortion);
+ animation->PlayFromStyle();
+
+ if (!aElementTransitions) {
+ aElementTransitions =
+ &aElement->EnsureAnimationData().EnsureTransitionCollection(
+ *aElement, aPseudoType);
+ if (!aElementTransitions->isInList()) {
+ AddElementCollection(aElementTransitions);
+ }
+ }
+ return animation.forget();
+}
+
+void nsTransitionManager::DoCancelTransition(
+ dom::Element* aElement, PseudoStyleType aPseudoType,
+ CSSTransitionCollection*& aElementTransitions, size_t aIndex) {
+ MOZ_ASSERT(aElementTransitions);
+ OwningCSSTransitionPtrArray& transitions = aElementTransitions->mAnimations;
+ CSSTransition* transition = transitions[aIndex];
+
+ if (transition->HasCurrentEffect()) {
+ if (auto* effectSet = EffectSet::Get(aElement, aPseudoType)) {
+ effectSet->UpdateAnimationGeneration(mPresContext);
+ }
+ }
+ transition->CancelFromStyle(PostRestyleMode::IfNeeded);
+ transitions.RemoveElementAt(aIndex);
+
+ if (transitions.IsEmpty()) {
+ aElementTransitions->Destroy();
+ // |aElementTransitions| is now a dangling pointer!
+ aElementTransitions = nullptr;
+ }
+}
diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h
new file mode 100644
index 0000000000..e87a0109a2
--- /dev/null
+++ b/layout/style/nsTransitionManager.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 start and animate CSS transitions. */
+
+#ifndef nsTransitionManager_h_
+#define nsTransitionManager_h_
+
+#include "mozilla/dom/CSSTransition.h"
+#include "AnimationCommon.h"
+#include "nsISupportsImpl.h"
+
+class nsPresContext;
+class nsCSSPropertyIDSet;
+struct nsStyleUIReset;
+
+namespace mozilla {
+class AnimatedPropertyIDSet;
+class ComputedStyle;
+enum class PseudoStyleType : uint8_t;
+} // namespace mozilla
+
+class nsTransitionManager final
+ : public mozilla::CommonAnimationManager<mozilla::dom::CSSTransition> {
+ public:
+ explicit nsTransitionManager(nsPresContext* aPresContext)
+ : mozilla::CommonAnimationManager<mozilla::dom::CSSTransition>(
+ aPresContext) {}
+
+ ~nsTransitionManager() final = default;
+
+ typedef mozilla::AnimationCollection<mozilla::dom::CSSTransition>
+ CSSTransitionCollection;
+
+ /**
+ * Update transitions for stylo.
+ */
+ bool UpdateTransitions(mozilla::dom::Element* aElement,
+ mozilla::PseudoStyleType aPseudoType,
+ const mozilla::ComputedStyle& aOldStyle,
+ const mozilla::ComputedStyle& aNewStyle);
+
+ protected:
+ typedef nsTArray<RefPtr<mozilla::dom::CSSTransition>>
+ OwningCSSTransitionPtrArray;
+
+ // Update transitions. This will start new transitions,
+ // replace existing transitions, and stop existing transitions
+ // as needed. aDisp and aElement must be non-null.
+ // aElementTransitions is the collection of current transitions, and it
+ // could be a nullptr if we don't have any transitions.
+ bool DoUpdateTransitions(const nsStyleUIReset& aStyle,
+ mozilla::dom::Element* aElement,
+ mozilla::PseudoStyleType aPseudoType,
+ CSSTransitionCollection*& aElementTransitions,
+ const mozilla::ComputedStyle& aOldStyle,
+ const mozilla::ComputedStyle& aNewStyle);
+
+ // Returns whether the transition actually started.
+ bool ConsiderInitiatingTransition(
+ const mozilla::AnimatedPropertyID&, const nsStyleUIReset& aStyle,
+ uint32_t aTransitionIndex, float aDelay, float aDuration,
+ mozilla::dom::Element* aElement, mozilla::PseudoStyleType aPseudoType,
+ CSSTransitionCollection*& aElementTransitions,
+ const mozilla::ComputedStyle& aOldStyle,
+ const mozilla::ComputedStyle& aNewStyle,
+ mozilla::AnimatedPropertyIDSet& aPropertiesChecked);
+
+ already_AddRefed<mozilla::dom::CSSTransition> DoCreateTransition(
+ const mozilla::AnimatedPropertyID& aProperty,
+ mozilla::dom::Element* aElement, mozilla::PseudoStyleType aPseudoType,
+ const mozilla::ComputedStyle& aNewStyle,
+ CSSTransitionCollection*& aElementTransitions,
+ mozilla::TimingParams&& aTiming, mozilla::AnimationValue&& aStartValue,
+ mozilla::AnimationValue&& aEndValue,
+ mozilla::AnimationValue&& aStartForReversingTest, double aReversePortion);
+
+ void DoCancelTransition(mozilla::dom::Element* aElement,
+ mozilla::PseudoStyleType aPseudoType,
+ CSSTransitionCollection*& aElementTransitions,
+ size_t aIndex);
+};
+
+#endif /* !defined(nsTransitionManager_h_) */
diff --git a/layout/style/res/Mozilla_Bullet.bf b/layout/style/res/Mozilla_Bullet.bf
new file mode 100644
index 0000000000..de8a7e2c29
--- /dev/null
+++ b/layout/style/res/Mozilla_Bullet.bf
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<font>
+<format>2.2</format>
+
+<postscript_name>Mozilla Bullet</postscript_name>
+<name>Bullet</name>
+<subfamily>Regular</subfamily>
+<bold>false</bold>
+<italic>false</italic>
+<full_name>Mozilla Bullet</full_name>
+<unique_identifier>Mozilla Bullet</unique_identifier>
+<version>Version 1.0</version>
+<description></description>
+<copyright></copyright>
+<license></license>
+<license_url></license_url>
+<weight>400</weight>
+<units_per_em>1024</units_per_em>
+<trademark></trademark>
+<manufacturer>Mozilla</manufacturer>
+<designer>Mats Palmgren</designer>
+<vendor_url></vendor_url>
+<designer_url></designer_url>
+
+<horizontal>
+ <top_limit>84.0000000000</top_limit>
+ <top_position>66.0000000000</top_position>
+ <x-height>62.0000000000</x-height>
+ <base_line>4.0000000000</base_line>
+ <bottom_position>0.0000000000</bottom_position>
+ <bottom_limit>0.0000000000</bottom_limit>
+ <custom_guide label="center">130.5577907913</custom_guide>
+ <custom_guide label="middle">34.0000000000</custom_guide>
+</horizontal>
+
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="4.0000"/>
+<grid width="1.0000"/>
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="4.0000"/>
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="4.0000"/>
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="4.0000"/>
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="4.0000"/>
+<grid width="1.0000"/>
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="4.0000"/>
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="4.0000"/>
+<grid width="1.0000"/>
+<grid width="2.0000"/>
+<grid width="4.0000"/>
+<background scale="1" />
+
+<collection unicode="U+20">
+ <selected id="0"/>
+ <glyph id="0" left="0" right="40">
+ </glyph>
+</collection>
+
+<collection unicode="U+2022">
+ <selected id="1"/>
+ <glyph id="1" left="0" right="56.054687999999999">
+ <layer name= "Layer" visible="true">
+ <path data="S 39.8340571477,21.8512253642 Q 37.5859284387,19.6030966550 34.3912193480,18.3015483276 Q 31.1965114690,17.0000000000 28.0018023784,17.0000000000 Q 24.8070932877,17.0000000000 21.6123841971,18.3015483276 Q 18.4176751064,19.6030966550 16.1695476089,21.8512253642 Q 13.8030966551,24.0993528616 12.5015483276,27.2940619523 Q 11.2000000000,30.4887710429 11.2000000000,33.6834789219 Q 11.2000000000,36.8781880126 12.5015483276,40.0728971032 Q 13.8030966551,43.2676061939 16.1695476089,45.5157349029 Q 18.4176751064,47.7638624004 21.6123841971,49.1837329728 Q 24.8070932877,50.4852813003 28.0018023784,50.4852813003 Q 31.1965114690,50.4852813003 34.3912193480,49.1837329728 Q 37.5859284387,47.7638624004 39.8340571477,45.5157349029 Q 42.0821846453,43.2676061939 43.3837329728,40.0728971032 Q 44.6852813003,36.8781880126 44.6852813003,33.6834789219 Q 44.6852813003,30.4887710429 43.3837329728,27.2940619523 Q 42.0821846453,24.0993528616 39.8340571477,21.8512253642" />
+ </layer>
+ </glyph>
+</collection>
+
+<collection unicode="U+25aa">
+ <selected id="1"/>
+ <glyph id="1" left="0" right="56.054687999999999">
+ <layer name= "Layer" visible="true">
+ <path data="B 12.0000000000,49.8262327578 M 43.9762327578,49.8262327578 M 43.9762327578,17.8500000000 M 12.0000000000,17.8500000000 M 12.0000000000,49.8262327578" />
+ </layer>
+ </glyph>
+</collection>
+
+<collection unicode="U+25b8">
+ <selected id="0"/>
+ <glyph id="0" left="-24" right="24">
+ <layer name= "Layer" visible="true">
+ <path data="S -22.3181299324,34.0373543359 L -22.3181299324,57.9301041570 L 25.4673697100,34.0373543359 L -22.3181299324,10.1446045147 L -22.3181299324,34.0373543359" />
+ </layer>
+ </glyph>
+</collection>
+
+<collection unicode="U+25be">
+ <selected id="1"/>
+ <glyph id="0" left="-28" right="28">
+ <layer name= "Layer" visible="true">
+ </layer>
+ </glyph>
+ <glyph id="1" left="-24" right="24">
+ <layer name= "Layer" visible="true">
+ <path data="S -0.0072501788,57.9301041570 L 23.8854996423,57.9301041570 L -0.0072501788,10.1446045146 L -23.9000000000,57.9301041570 L -0.0072501788,57.9301041570" />
+ </layer>
+ </glyph>
+</collection>
+
+<collection unicode="U+25c2">
+ <selected id="1"/>
+ <glyph id="0" left="-28" right="28">
+ <layer name= "Layer" visible="true">
+ </layer>
+ </glyph>
+ <glyph id="1" left="-24" right="24">
+ <layer name= "Layer" visible="true">
+ <path data="S 22.1854996424,34.0373543358 L 22.1854996424,10.1446045147 L -25.6000000000,34.0373543358 L 22.1854996424,57.9301041570 L 22.1854996424,34.0373543358" />
+ </layer>
+ </glyph>
+</collection>
+
+<collection unicode="U+25e6">
+ <selected id="0"/>
+ <glyph id="0" left="0" right="56.054687999999999">
+ <layer name= "Layer" visible="true">
+ <path stroke="2.3999999999999999" data="B 40.0833461228,23.2212841662 C 37.0522303130,20.1901683563 32.4889602320,18.3000000000 28.2023151445,18.3000000000 T C 23.9156700571,18.3000000000 19.3523999761,20.1901683563 16.3212841662,23.2212841662 T C 13.2901683563,26.2523999761 11.4000000000,30.8156700570 11.4000000000,35.1023151445 T C 11.4000000000,39.3889602320 13.2901683564,43.9522303130 16.3212841662,46.9833461228 T C 19.3523999761,50.0144619327 23.9156700571,51.9046302890 28.2023151445,51.9046302890 T C 32.4889602320,51.9046302890 37.0522303129,50.0144619326 40.0833461228,46.9833461228 T C 43.1144619327,43.9522303130 45.0046302890,39.3889602320 45.0046302890,35.1023151445 T C 45.0046302890,30.8156700570 43.1144619327,26.2523999760 40.0833461228,23.2212841662 T" />
+ </layer>
+ </glyph>
+</collection>
+
+
+
+
+</font>
diff --git a/layout/style/res/Mozilla_Bullet.ttf b/layout/style/res/Mozilla_Bullet.ttf
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/layout/style/res/Mozilla_Bullet.ttf
diff --git a/layout/style/res/Mozilla_Bullet.woff2 b/layout/style/res/Mozilla_Bullet.woff2
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/layout/style/res/Mozilla_Bullet.woff2
diff --git a/layout/style/res/accessiblecaret-normal.svg b/layout/style/res/accessiblecaret-normal.svg
new file mode 100644
index 0000000000..add588ead1
--- /dev/null
+++ b/layout/style/res/accessiblecaret-normal.svg
@@ -0,0 +1,10 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg width="66px" height="80px" viewBox="0 0 66 80" xmlns="http://www.w3.org/2000/svg">
+ <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g transform="translate(-332.000000, -1230.000000)" fill="AccentColor" opacity="0.95">
+ <path d="M342,1253 L365,1230 L388,1253 C401.220659,1266.37181 401.223493,1287.34993 388,1300 C375.446886,1313.2341 354.555948,1313.23694 342,1300 C328.779341,1287.35277 328.776507,1266.37465 342,1253 Z"></path>
+ </g>
+ </g>
+</svg>
diff --git a/layout/style/res/accessiblecaret-normal@1.5x.png b/layout/style/res/accessiblecaret-normal@1.5x.png
new file mode 100644
index 0000000000..6e2eb80dd3
--- /dev/null
+++ b/layout/style/res/accessiblecaret-normal@1.5x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-normal@1x.png b/layout/style/res/accessiblecaret-normal@1x.png
new file mode 100644
index 0000000000..7410946c66
--- /dev/null
+++ b/layout/style/res/accessiblecaret-normal@1x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-normal@2.25x.png b/layout/style/res/accessiblecaret-normal@2.25x.png
new file mode 100644
index 0000000000..cbe570c2ed
--- /dev/null
+++ b/layout/style/res/accessiblecaret-normal@2.25x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-normal@2x.png b/layout/style/res/accessiblecaret-normal@2x.png
new file mode 100644
index 0000000000..fd5034b880
--- /dev/null
+++ b/layout/style/res/accessiblecaret-normal@2x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-tilt-left.svg b/layout/style/res/accessiblecaret-tilt-left.svg
new file mode 100644
index 0000000000..f5d238dc09
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-left.svg
@@ -0,0 +1,10 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg width="66px" height="66px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
+ <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g transform="translate(-564.000000, -1723.000000)" fill="AccentColor" opacity="0.95">
+ <path d="M564,1756 C564,1737.7746 578.770596,1723 597,1723 L630,1723 L630,1756 C630,1774.2254 615.229404,1789 597,1789 C578.774603,1789 564,1774.2294 564,1756 L564,1756 Z"></path>
+ </g>
+ </g>
+</svg>
diff --git a/layout/style/res/accessiblecaret-tilt-left@1.5x.png b/layout/style/res/accessiblecaret-tilt-left@1.5x.png
new file mode 100644
index 0000000000..7176191193
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-left@1.5x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-tilt-left@1x.png b/layout/style/res/accessiblecaret-tilt-left@1x.png
new file mode 100644
index 0000000000..09fdb0286d
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-left@1x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-tilt-left@2.25x.png b/layout/style/res/accessiblecaret-tilt-left@2.25x.png
new file mode 100644
index 0000000000..4b66547c2b
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-left@2.25x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-tilt-left@2x.png b/layout/style/res/accessiblecaret-tilt-left@2x.png
new file mode 100644
index 0000000000..d32fed2173
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-left@2x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-tilt-right.svg b/layout/style/res/accessiblecaret-tilt-right.svg
new file mode 100644
index 0000000000..72f5b41cf9
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-right.svg
@@ -0,0 +1,10 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg width="66px" height="66px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
+ <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g transform="translate(-690.000000, -1723.000000)" fill="AccentColor" opacity="0.95">
+ <path d="M723,1723 C741.225397,1723 756,1737.7706 756,1756 C756,1774.2254 741.229404,1789 723,1789 C704.774603,1789 690,1774.2294 690,1756 L690,1723 L723,1723 Z"></path>
+ </g>
+ </g>
+</svg>
diff --git a/layout/style/res/accessiblecaret-tilt-right@1.5x.png b/layout/style/res/accessiblecaret-tilt-right@1.5x.png
new file mode 100644
index 0000000000..cc7524ec8d
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-right@1.5x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-tilt-right@1x.png b/layout/style/res/accessiblecaret-tilt-right@1x.png
new file mode 100644
index 0000000000..2712284d1e
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-right@1x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-tilt-right@2.25x.png b/layout/style/res/accessiblecaret-tilt-right@2.25x.png
new file mode 100644
index 0000000000..c50c1b5e2f
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-right@2.25x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret-tilt-right@2x.png b/layout/style/res/accessiblecaret-tilt-right@2x.png
new file mode 100644
index 0000000000..d4a4138071
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-right@2x.png
Binary files differ
diff --git a/layout/style/res/accessiblecaret.css b/layout/style/res/accessiblecaret.css
new file mode 100644
index 0000000000..a994910a56
--- /dev/null
+++ b/layout/style/res/accessiblecaret.css
@@ -0,0 +1,108 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+:host {
+ /* Add transition effect to make caret size changing smoother. */
+ transition-property: width, height, margin-left;
+
+ position: absolute;
+ z-index: 2147483647;
+}
+
+#image,
+#text-overlay {
+ width: 100%;
+
+ /* Override this property in moz-custom-content-container to make dummy touch
+ * listener work. */
+ pointer-events: auto;
+}
+
+#image {
+ background-position: center top;
+ background-size: 100%;
+ background-repeat: no-repeat;
+ background-origin: content-box;
+}
+
+:host(.normal) #image {
+ background-image: image-set(
+ url("resource://gre-resources/accessiblecaret-normal@1x.png"),
+ url("resource://gre-resources/accessiblecaret-normal@1.5x.png") 1.5x,
+ url("resource://gre-resources/accessiblecaret-normal@2x.png") 2x,
+ url("resource://gre-resources/accessiblecaret-normal@2.25x.png") 2.25x
+ );
+}
+
+:host(.left) #image,
+:host(.left) #text-overlay {
+ margin-left: -39%;
+}
+
+:host(.left) > #image {
+ background-image: image-set(
+ url("resource://gre-resources/accessiblecaret-tilt-left@1x.png"),
+ url("resource://gre-resources/accessiblecaret-tilt-left@1.5x.png") 1.5x,
+ url("resource://gre-resources/accessiblecaret-tilt-left@2x.png") 2x,
+ url("resource://gre-resources/accessiblecaret-tilt-left@2.25x.png") 2.25x
+ );
+}
+
+:host(.right) #image,
+:host(.right) #text-overlay {
+ margin-left: 41%;
+}
+
+:host(.right) #image {
+ background-image: image-set(
+ url("resource://gre-resources/accessiblecaret-tilt-right@1x.png"),
+ url("resource://gre-resources/accessiblecaret-tilt-right@1.5x.png") 1.5x,
+ url("resource://gre-resources/accessiblecaret-tilt-right@2x.png") 2x,
+ url("resource://gre-resources/accessiblecaret-tilt-right@2.25x.png") 2.25x
+ );
+}
+
+:host(.none) {
+ display: none;
+}
+
+:host(.hidden) {
+ visibility: hidden;
+}
+
+@media (-moz-platform: android) {
+ #image,
+ #text-overlay {
+ /* border: 0.1px solid red; */ /* Uncomment border to see the touch target. */
+ padding-left: 59%; /* Enlarge the touch area. ((48-22)/2)px / 22px ~= 59% */
+ padding-right: 59%; /* Enlarge the touch area. */
+ margin-left: -59%;
+ }
+
+ #image {
+ padding-bottom: 59%; /* Enlarge the touch area. */
+ }
+
+ :host(.normal) #image {
+ background-image: url("resource://gre-resources/accessiblecaret-normal.svg");
+ }
+
+ :host(.left) #image,
+ :host(.left) #text-overlay {
+ margin-left: -109%;
+ }
+
+ :host(.left) #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-left.svg");
+ }
+
+ :host(.right) #image,
+ :host(.right) #text-overlay {
+ margin-left: -12%;
+ }
+
+ :host(.right) #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-right.svg");
+ }
+}
diff --git a/layout/style/res/counterstyles.css b/layout/style/res/counterstyles.css
new file mode 100644
index 0000000000..982ff0e185
--- /dev/null
+++ b/layout/style/res/counterstyles.css
@@ -0,0 +1,365 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Defined in CSS Counter Styles Level 3 */
+
+/* 6 Simple Predefined Counter Styles */
+
+/* 6.1 Numeric */
+
+@counter-style decimal-leading-zero {
+ system: extends decimal;
+ pad: 2 '0';
+}
+
+@counter-style arabic-indic {
+ system: numeric;
+ symbols: '\660' '\661' '\662' '\663' '\664' '\665' '\666' '\667' '\668' '\669';
+}
+
+@counter-style armenian {
+ system: additive;
+ range: 1 9999;
+ additive-symbols: 9000 '\554', 8000 '\553', 7000 '\552', 6000 '\551', 5000 '\550', 4000 '\54F', 3000 '\54E', 2000 '\54D', 1000 '\54C', 900 '\54B', 800 '\54A', 700 '\549', 600 '\548', 500 '\547', 400 '\546', 300 '\545', 200 '\544', 100 '\543', 90 '\542', 80 '\541', 70 '\540', 60 '\53F', 50 '\53E', 40 '\53D', 30 '\53C', 20 '\53B', 10 '\53A', 9 '\539', 8 '\538', 7 '\537', 6 '\536', 5 '\535', 4 '\534', 3 '\533', 2 '\532', 1 '\531';
+}
+
+@counter-style upper-armenian {
+ system: additive;
+ range: 1 9999;
+ additive-symbols: 9000 '\554', 8000 '\553', 7000 '\552', 6000 '\551', 5000 '\550', 4000 '\54F', 3000 '\54E', 2000 '\54D', 1000 '\54C', 900 '\54B', 800 '\54A', 700 '\549', 600 '\548', 500 '\547', 400 '\546', 300 '\545', 200 '\544', 100 '\543', 90 '\542', 80 '\541', 70 '\540', 60 '\53F', 50 '\53E', 40 '\53D', 30 '\53C', 20 '\53B', 10 '\53A', 9 '\539', 8 '\538', 7 '\537', 6 '\536', 5 '\535', 4 '\534', 3 '\533', 2 '\532', 1 '\531';
+}
+
+@counter-style lower-armenian {
+ system: additive;
+ range: 1 9999;
+ additive-symbols: 9000 '\584', 8000 '\583', 7000 '\582', 6000 '\581', 5000 '\580', 4000 '\57F', 3000 '\57E', 2000 '\57D', 1000 '\57C', 900 '\57B', 800 '\57A', 700 '\579', 600 '\578', 500 '\577', 400 '\576', 300 '\575', 200 '\574', 100 '\573', 90 '\572', 80 '\571', 70 '\570', 60 '\56F', 50 '\56E', 40 '\56D', 30 '\56C', 20 '\56B', 10 '\56A', 9 '\569', 8 '\568', 7 '\567', 6 '\566', 5 '\565', 4 '\564', 3 '\563', 2 '\562', 1 '\561';
+}
+
+@counter-style bengali {
+ system: numeric;
+ symbols: '\9E6' '\9E7' '\9E8' '\9E9' '\9EA' '\9EB' '\9EC' '\9ED' '\9EE' '\9EF';
+}
+
+@counter-style cambodian {
+ system: extends khmer;
+}
+
+@counter-style khmer {
+ system: numeric;
+ symbols: '\17E0' '\17E1' '\17E2' '\17E3' '\17E4' '\17E5' '\17E6' '\17E7' '\17E8' '\17E9';
+}
+
+@counter-style cjk-decimal {
+ system: numeric;
+ range: 0 infinite;
+ symbols: '\3007' '\4E00' '\4E8C' '\4E09' '\56DB' '\4E94' '\516D' '\4E03' '\516B' '\4E5D';
+ suffix: '\3001';
+}
+
+@counter-style devanagari {
+ system: numeric;
+ symbols: '\966' '\967' '\968' '\969' '\96A' '\96B' '\96C' '\96D' '\96E' '\96F';
+}
+
+@counter-style georgian {
+ system: additive;
+ range: 1 19999;
+ additive-symbols: 10000 '\10F5', 9000 '\10F0', 8000 '\10EF', 7000 '\10F4', 6000 '\10EE', 5000 '\10ED', 4000 '\10EC', 3000 '\10EB', 2000 '\10EA', 1000 '\10E9', 900 '\10E8', 800 '\10E7', 700 '\10E6', 600 '\10E5', 500 '\10E4', 400 '\10F3', 300 '\10E2', 200 '\10E1', 100 '\10E0', 90 '\10DF', 80 '\10DE', 70 '\10DD', 60 '\10F2', 50 '\10DC', 40 '\10DB', 30 '\10DA', 20 '\10D9', 10 '\10D8', 9 '\10D7', 8 '\10F1', 7 '\10D6', 6 '\10D5', 5 '\10D4', 4 '\10D3', 3 '\10D2', 2 '\10D1', 1 '\10D0';
+}
+
+@counter-style gujarati {
+ system: numeric;
+ symbols: '\AE6' '\AE7' '\AE8' '\AE9' '\AEA' '\AEB' '\AEC' '\AED' '\AEE' '\AEF';
+}
+
+@counter-style gurmukhi {
+ system: numeric;
+ symbols: '\A66' '\A67' '\A68' '\A69' '\A6A' '\A6B' '\A6C' '\A6D' '\A6E' '\A6F';
+}
+
+/* hebrew is not included because our builtin algorithm can generate a wider
+ * range of number in this style than what the spec defines. */
+
+@counter-style kannada {
+ system: numeric;
+ symbols: '\CE6' '\CE7' '\CE8' '\CE9' '\CEA' '\CEB' '\CEC' '\CED' '\CEE' '\CEF';
+}
+
+@counter-style lao {
+ system: numeric;
+ symbols: '\ED0' '\ED1' '\ED2' '\ED3' '\ED4' '\ED5' '\ED6' '\ED7' '\ED8' '\ED9';
+}
+
+@counter-style malayalam {
+ system: numeric;
+ symbols: '\D66' '\D67' '\D68' '\D69' '\D6A' '\D6B' '\D6C' '\D6D' '\D6E' '\D6F';
+}
+
+@counter-style mongolian {
+ system: numeric;
+ symbols: '\1810' '\1811' '\1812' '\1813' '\1814' '\1815' '\1816' '\1817' '\1818' '\1819';
+}
+
+@counter-style myanmar {
+ system: numeric;
+ symbols: '\1040' '\1041' '\1042' '\1043' '\1044' '\1045' '\1046' '\1047' '\1048' '\1049';
+}
+
+@counter-style oriya {
+ system: numeric;
+ symbols: '\B66' '\B67' '\B68' '\B69' '\B6A' '\B6B' '\B6C' '\B6D' '\B6E' '\B6F';
+}
+
+@counter-style persian {
+ system: numeric;
+ symbols: '\6F0' '\6F1' '\6F2' '\6F3' '\6F4' '\6F5' '\6F6' '\6F7' '\6F8' '\6F9';
+}
+
+@counter-style lower-roman {
+ system: additive;
+ range: 1 3999;
+ additive-symbols: 1000 'm', 900 'cm', 500 'd', 400 'cd', 100 'c', 90 'xc', 50 'l', 40 'xl', 10 'x', 9 'ix', 5 'v', 4 'iv', 1 'i';
+}
+
+@counter-style upper-roman {
+ system: additive;
+ range: 1 3999;
+ additive-symbols: 1000 'M', 900 'CM', 500 'D', 400 'CD', 100 'C', 90 'XC', 50 'L', 40 'XL', 10 'X', 9 'IX', 5 'V', 4 'IV', 1 'I';
+}
+
+@counter-style tamil {
+ system: numeric;
+ symbols: '\BE6' '\BE7' '\BE8' '\BE9' '\BEA' '\BEB' '\BEC' '\BED' '\BEE' '\BEF';
+}
+
+@counter-style telugu {
+ system: numeric;
+ symbols: '\C66' '\C67' '\C68' '\C69' '\C6A' '\C6B' '\C6C' '\C6D' '\C6E' '\C6F';
+}
+
+@counter-style thai {
+ system: numeric;
+ symbols: '\E50' '\E51' '\E52' '\E53' '\E54' '\E55' '\E56' '\E57' '\E58' '\E59';
+}
+
+@counter-style tibetan {
+ system: numeric;
+ symbols: '\F20' '\F21' '\F22' '\F23' '\F24' '\F25' '\F26' '\F27' '\F28' '\F29';
+}
+
+/* 6.2 Alphabetic */
+
+@counter-style lower-alpha {
+ system: alphabetic;
+ symbols: 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z';
+}
+
+@counter-style lower-latin {
+ system: extends lower-alpha;
+}
+
+@counter-style upper-alpha {
+ system: alphabetic;
+ symbols: 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z';
+}
+
+@counter-style upper-latin {
+ system: extends upper-alpha;
+}
+
+@counter-style cjk-heavenly-stem {
+ system: fixed;
+ symbols: '\7532' '\4E59' '\4E19' '\4E01' '\620A' '\5DF1' '\5E9A' '\8F9B' '\58EC' '\7678';
+ fallback: cjk-decimal;
+ suffix: '\3001';
+}
+
+@counter-style cjk-earthly-branch {
+ system: fixed;
+ symbols: '\5B50' '\4E11' '\5BC5' '\536F' '\8FB0' '\5DF3' '\5348' '\672A' '\7533' '\9149' '\620C' '\4EA5';
+ fallback: cjk-decimal;
+ suffix: '\3001';
+}
+
+@counter-style lower-greek {
+ system: alphabetic;
+ symbols: '\3B1' '\3B2' '\3B3' '\3B4' '\3B5' '\3B6' '\3B7' '\3B8' '\3B9' '\3BA' '\3BB' '\3BC' '\3BD' '\3BE' '\3BF' '\3C0' '\3C1' '\3C3' '\3C4' '\3C5' '\3C6' '\3C7' '\3C8' '\3C9';
+}
+
+@counter-style hiragana {
+ system: alphabetic;
+ symbols: '\3042' '\3044' '\3046' '\3048' '\304A' '\304B' '\304D' '\304F' '\3051' '\3053' '\3055' '\3057' '\3059' '\305B' '\305D' '\305F' '\3061' '\3064' '\3066' '\3068' '\306A' '\306B' '\306C' '\306D' '\306E' '\306F' '\3072' '\3075' '\3078' '\307B' '\307E' '\307F' '\3080' '\3081' '\3082' '\3084' '\3086' '\3088' '\3089' '\308A' '\308B' '\308C' '\308D' '\308F' '\3090' '\3091' '\3092' '\3093';
+ suffix: '\3001';
+}
+
+@counter-style hiragana-iroha {
+ system: alphabetic;
+ symbols: '\3044' '\308D' '\306F' '\306B' '\307B' '\3078' '\3068' '\3061' '\308A' '\306C' '\308B' '\3092' '\308F' '\304B' '\3088' '\305F' '\308C' '\305D' '\3064' '\306D' '\306A' '\3089' '\3080' '\3046' '\3090' '\306E' '\304A' '\304F' '\3084' '\307E' '\3051' '\3075' '\3053' '\3048' '\3066' '\3042' '\3055' '\304D' '\3086' '\3081' '\307F' '\3057' '\3091' '\3072' '\3082' '\305B' '\3059';
+ suffix: '\3001';
+}
+
+@counter-style katakana {
+ system: alphabetic;
+ symbols: '\30A2' '\30A4' '\30A6' '\30A8' '\30AA' '\30AB' '\30AD' '\30AF' '\30B1' '\30B3' '\30B5' '\30B7' '\30B9' '\30BB' '\30BD' '\30BF' '\30C1' '\30C4' '\30C6' '\30C8' '\30CA' '\30CB' '\30CC' '\30CD' '\30CE' '\30CF' '\30D2' '\30D5' '\30D8' '\30DB' '\30DE' '\30DF' '\30E0' '\30E1' '\30E2' '\30E4' '\30E6' '\30E8' '\30E9' '\30EA' '\30EB' '\30EC' '\30ED' '\30EF' '\30F0' '\30F1' '\30F2' '\30F3';
+ suffix: '\3001';
+}
+
+@counter-style katakana-iroha {
+ system: alphabetic;
+ symbols: '\30A4' '\30ED' '\30CF' '\30CB' '\30DB' '\30D8' '\30C8' '\30C1' '\30EA' '\30CC' '\30EB' '\30F2' '\30EF' '\30AB' '\30E8' '\30BF' '\30EC' '\30BD' '\30C4' '\30CD' '\30CA' '\30E9' '\30E0' '\30A6' '\30F0' '\30CE' '\30AA' '\30AF' '\30E4' '\30DE' '\30B1' '\30D5' '\30B3' '\30A8' '\30C6' '\30A2' '\30B5' '\30AD' '\30E6' '\30E1' '\30DF' '\30B7' '\30F1' '\30D2' '\30E2' '\30BB' '\30B9';
+ suffix: '\3001';
+}
+
+/* 6.3 Symbolic */
+
+/* symbolic counter styles are not included because they will be drew directly
+ * by the program instead of use alternative symbols defined in the spec */
+
+/* 7 Complex Predefined Counter Styles */
+
+/* only alias is included as other complex counter styles will be generated by
+ * specific algorithms to support the extended range. */
+
+@counter-style cjk-ideographic {
+ system: extends trad-chinese-informal;
+}
+
+/* Mozilla-specific counter styles */
+
+/* Numeric */
+
+@counter-style -moz-arabic-indic {
+ system: extends arabic-indic;
+}
+
+@counter-style -moz-persian {
+ system: extends persian;
+}
+
+@counter-style -moz-urdu {
+ system: extends persian;
+}
+
+@counter-style -moz-devanagari {
+ system: extends devanagari;
+}
+
+@counter-style -moz-bengali {
+ system: extends bengali;
+}
+
+@counter-style -moz-gurmukhi {
+ system: extends gurmukhi;
+}
+
+@counter-style -moz-gujarati {
+ system: extends gujarati;
+}
+
+@counter-style -moz-oriya {
+ system: extends oriya;
+}
+
+@counter-style -moz-tamil {
+ system: extends tamil;
+}
+
+@counter-style -moz-telugu {
+ system: extends telugu;
+}
+
+@counter-style -moz-kannada {
+ system: extends kannada;
+}
+
+@counter-style -moz-malayalam {
+ system: extends malayalam;
+}
+
+@counter-style -moz-thai {
+ system: extends thai;
+}
+
+@counter-style -moz-lao {
+ system: extends lao;
+}
+
+@counter-style -moz-myanmar {
+ system: extends myanmar;
+}
+
+@counter-style -moz-khmer {
+ system: extends khmer;
+}
+
+/* Alphabetic */
+
+@counter-style -moz-cjk-heavenly-stem {
+ system: extends cjk-heavenly-stem;
+}
+@counter-style -moz-cjk-earthly-branch {
+ system: extends cjk-earthly-branch;
+}
+
+@counter-style -moz-hangul {
+ system: alphabetic;
+ symbols: '\AC00' '\B098' '\B2E4' '\B77C' '\B9C8' '\BC14' '\C0AC' '\C544' '\C790' '\CC28' '\CE74' '\D0C0' '\D30C' '\D558';
+ suffix: ',';
+}
+@counter-style -moz-hangul-consonant {
+ system: alphabetic;
+ symbols: '\3131' '\3134' '\3137' '\3139' '\3141' '\3142' '\3145' '\3147' '\3148' '\314A' '\314B' '\314C' '\314D' '\314E';
+ suffix: ',';
+}
+
+/* Ge'ez set of Ethiopic ordered list. There are other locale-dependent sets.
+ * For the time being, let's implement two Ge'ez sets only
+ * per Momoi san's suggestion in bug 102252.
+ * For details, refer to http://www.ethiopic.org/Collation/OrderedLists.html. */
+@counter-style -moz-ethiopic-halehame {
+ system: alphabetic;
+ symbols: '\1200' '\1208' '\1210' '\1218' '\1220' '\1228' '\1230' '\1240' '\1260' '\1270' '\1280' '\1290' '\12A0' '\12A8' '\12C8' '\12D0' '\12D8' '\12E8' '\12F0' '\1308' '\1320' '\1330' '\1338' '\1340' '\1348' '\1350';
+}
+@counter-style -moz-ethiopic-halehame-am {
+ system: alphabetic;
+ symbols: '\1200' '\1208' '\1210' '\1218' '\1220' '\1228' '\1230' '\1238' '\1240' '\1260' '\1270' '\1278' '\1280' '\1290' '\1298' '\12A0' '\12A8' '\12B8' '\12C8' '\12D0' '\12D8' '\12E0' '\12E8' '\12F0' '\1300' '\1308' '\1320' '\1328' '\1330' '\1338' '\1340' '\1348' '\1350';
+}
+@counter-style -moz-ethiopic-halehame-ti-er {
+ system: alphabetic;
+ symbols: '\1200' '\1208' '\1210' '\1218' '\1228' '\1230' '\1238' '\1240' '\1250' '\1260' '\1270' '\1278' '\1290' '\1298' '\12A0' '\12A8' '\12B8' '\12C8' '\12D0' '\12D8' '\12E0' '\12E8' '\12F0' '\1300' '\1308' '\1320' '\1328' '\1330' '\1338' '\1348' '\1350';
+}
+@counter-style -moz-ethiopic-halehame-ti-et {
+ system: alphabetic;
+ symbols: '\1200' '\1208' '\1210' '\1218' '\1220' '\1228' '\1230' '\1238' '\1240' '\1250' '\1260' '\1270' '\1278' '\1280' '\1290' '\1298' '\12A0' '\12A8' '\12B8' '\12C8' '\12D0' '\12D8' '\12E0' '\12E8' '\12F0' '\1300' '\1308' '\1320' '\1328' '\1330' '\1338' '\1340' '\1348' '\1350';
+}
+
+/* Alias */
+
+@counter-style -moz-trad-chinese-informal {
+ system: extends trad-chinese-informal;
+}
+
+@counter-style -moz-trad-chinese-formal {
+ system: extends trad-chinese-formal;
+}
+
+@counter-style -moz-simp-chinese-informal {
+ system: extends simp-chinese-informal;
+}
+
+@counter-style -moz-simp-chinese-formal {
+ system: extends simp-chinese-formal;
+}
+
+@counter-style -moz-japanese-informal {
+ system: extends japanese-informal;
+}
+
+@counter-style -moz-japanese-formal {
+ system: extends japanese-formal;
+}
+
+@counter-style -moz-ethiopic-numeric {
+ system: extends ethiopic-numeric;
+}
diff --git a/layout/style/res/details.css b/layout/style/res/details.css
new file mode 100644
index 0000000000..593e03d9d3
--- /dev/null
+++ b/layout/style/res/details.css
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+slot:not([name]) {
+ display: none;
+}
+:host([open]) slot:not([name]) {
+ display: revert;
+}
+
+/* See the comment around the summary styles in html.css, these rules should
+ * match */
+summary {
+ display: list-item;
+ counter-increment: list-item 0;
+ list-style: disclosure-closed inside;
+}
+:host([open]) summary {
+ list-style-type: disclosure-open;
+}
diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css
new file mode 100644
index 0000000000..9fb833aa74
--- /dev/null
+++ b/layout/style/res/forms.css
@@ -0,0 +1,932 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ Styles for old GFX form widgets
+ **/
+
+
+@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
+
+*|*::-moz-fieldset-content {
+ display: block; /* StyleAdjuster::adjust_for_fieldset_content overrides this in some cases */
+ unicode-bidi: inherit;
+ text-overflow: inherit;
+ overflow: inherit;
+ overflow-clip-box: inherit;
+ resize: inherit;
+ /* Need to inherit border-radius too, so when the fieldset has rounded
+ borders we don't leak out the corners for hit-testing purposes. */
+ border-radius: inherit;
+ padding: inherit;
+ box-decoration-break: inherit;
+ block-size: 100%; /* Need this so percentage block-sizes of kids work right */
+ /* Please keep the declarations below in sync with ::-moz-scrolled-content in
+ ua.css and ::-moz-button-content below. */
+ content: inherit;
+ /* Multicol container */
+ column-count: inherit;
+ column-width: inherit;
+ column-gap: inherit;
+ column-rule: inherit;
+ column-fill: inherit;
+ /* Flex container */
+ flex-direction: inherit;
+ flex-wrap: inherit;
+ /* -webkit-box container (aliased from -webkit versions to -moz versions) */
+ -moz-box-orient: inherit;
+ -moz-box-direction: inherit;
+ -moz-box-pack: inherit;
+ -moz-box-align: inherit;
+ /* Grid container */
+ grid-auto-columns: inherit;
+ grid-auto-rows: inherit;
+ grid-auto-flow: inherit;
+ grid-column-gap: inherit;
+ grid-row-gap: inherit;
+ grid-template-areas: inherit;
+ grid-template-columns: inherit;
+ grid-template-rows: inherit;
+ /* CSS Align */
+ align-content: inherit;
+ align-items: inherit;
+ justify-content: inherit;
+ justify-items: inherit;
+}
+
+/* Miscellaneous form elements */
+
+legend {
+ display: block;
+ padding-inline: 2px;
+}
+
+fieldset {
+ display: block;
+ margin-inline: 2px;
+ padding-block: 0.35em 0.625em;
+ padding-inline: 0.75em;
+ border: 2px groove ThreeDFace;
+ min-inline-size: min-content;
+}
+
+label {
+ cursor: default;
+}
+
+/* Default inputs, text inputs, and selects */
+
+/* Note: Values in nsNativeTheme IsWidgetStyled function
+ need to match textfield background/border values here */
+
+input {
+ display: inline-block;
+ appearance: auto;
+ -moz-default-appearance: textfield;
+ /* The sum of border and padding on block-start and block-end
+ must be the same here, for buttons, and for <select> */
+ padding-block: 1px;
+ padding-inline: 2px;
+ border: 2px inset ButtonBorder;
+ background-color: Field;
+ color: FieldText;
+ font: -moz-field;
+ text-rendering: optimizeLegibility;
+ cursor: text;
+ overflow-clip-box: padding-box content-box;
+}
+
+textarea {
+ display: inline-block;
+ appearance: auto;
+ -moz-default-appearance: textarea;
+ margin-block: 1px;
+ border: 2px inset ButtonBorder;
+ padding: 2px;
+ background-color: Field;
+ color: FieldText;
+ font: medium -moz-fixed;
+ text-rendering: optimizeLegibility;
+ vertical-align: text-bottom;
+ cursor: text;
+ resize: both;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+/* A few properties that we don't want to inherit by default: */
+input, textarea, select, button, ::file-selector-button {
+ text-align: initial;
+ text-indent: initial;
+ text-shadow: initial;
+ text-transform: initial;
+ word-spacing: initial;
+ letter-spacing: initial;
+ /* Note that line-height is also reset for all these, via the font shorthand */
+}
+
+::placeholder,
+::-moz-text-control-editing-root,
+::-moz-text-control-preview {
+ overflow: auto;
+ border: 0;
+ /* This is necessary to make overflow-clip-box work */
+ padding: inherit;
+ margin: 0;
+ text-decoration: inherit;
+ display: inline-block;
+ ime-mode: inherit;
+ resize: inherit;
+ scrollbar-width: inherit;
+ -moz-control-character-visibility: visible;
+ overflow-clip-box: inherit;
+ visibility: hidden;
+}
+
+::placeholder,
+::-moz-text-control-preview {
+ /*
+ * Changing display to inline can leads to broken behaviour and will assert.
+ */
+ display: inline-block;
+
+ /*
+ * Changing resize would display a broken behaviour and will assert.
+ */
+ resize: none;
+
+ overflow: hidden;
+
+ /*
+ * The placeholder or preview should be ignored by pointer / selection / etc.
+ * Otherwise, we might have some unexpected behavior like the resize handle
+ * not being selectable.
+ */
+ pointer-events: none;
+ user-select: none;
+}
+
+::-moz-text-control-preview {
+ font-family: system-ui;
+}
+
+::placeholder {
+ opacity: 0.54;
+}
+
+:not(:-moz-autofill-preview)::-moz-text-control-editing-root,
+:placeholder-shown:not(:autofill)::placeholder,
+:autofill::-moz-text-control-preview {
+ visibility: inherit;
+}
+
+input::placeholder,
+input::-moz-text-control-editing-root,
+input::-moz-text-control-preview {
+ word-wrap: normal;
+ white-space: pre;
+ /* Make the line-height equal to the available height */
+ line-height: -moz-block-height !important;
+}
+
+input[type=password]::-moz-text-control-editing-root,
+input[type=password]::-moz-text-control-preview {
+ /*
+ * In password fields, any character should be put same direction. Otherwise,
+ * caret position at typing tells everybody whether the character is an RTL
+ * or an LTR character. Unfortunately, this makes odd rendering when bidi
+ * text is unmasked.
+ */
+ unicode-bidi: bidi-override;
+}
+
+textarea::-moz-text-control-editing-root {
+ scroll-behavior: inherit;
+ overscroll-behavior: inherit;
+ /* StyleAdjuster makes sure that the overflow value ends up being scrollable */
+ overflow: inherit;
+}
+
+input:read-write,
+textarea:read-write {
+ -moz-user-modify: read-write !important;
+}
+
+select {
+ margin: 0;
+ border-color: ButtonBorder;
+ font: -moz-list;
+ white-space: nowrap !important;
+ word-wrap: normal !important;
+ cursor: default;
+ box-sizing: border-box;
+ user-select: none;
+ border-width: 2px;
+ border-style: inset;
+ overflow: clip;
+ /* No text-decoration reaching inside, by default */
+ display: inline-block;
+ page-break-inside: avoid;
+ overflow-clip-box: padding-box !important; /* bug 992447 */
+ padding-block: 1px;
+
+ /* Set some styles for drop down selects. These are overridden below for
+ * list box selects. */
+ padding-inline: 4px;
+ background-color: -moz-Combobox;
+ color: -moz-ComboboxText;
+ vertical-align: baseline;
+ appearance: auto;
+ -moz-default-appearance: menulist;
+}
+
+select:-moz-select-list-box {
+ overflow-inline: hidden;
+ overflow-block: scroll;
+ padding-inline: 0;
+ background-color: Field;
+ color: FieldText;
+ vertical-align: text-bottom;
+ appearance: auto;
+ -moz-default-appearance: listbox;
+}
+
+@media (-moz-platform: macos) {
+ select:-moz-select-list-box {
+ scrollbar-width: thin;
+ }
+}
+
+select > button {
+ inline-size: 12px;
+ white-space: nowrap;
+ position: static;
+ appearance: auto;
+ -moz-default-appearance: -moz-menulist-arrow-button;
+
+ /* Make sure to size correctly if the combobox has a non-auto height. */
+ block-size: 100%;
+ box-sizing: border-box;
+
+ /* Draw the arrow in the select's color */
+ color: inherit;
+
+ /*
+ Make sure to align properly with the display frame. Note that we
+ want the baseline of the combobox to match the baseline of the
+ display frame, so the dropmarker is what gets the vertical-align.
+ */
+ vertical-align: top;
+}
+
+*|*::-moz-display-comboboxcontrol-frame {
+ content: inherit;
+ overflow: clip;
+ color: unset;
+ white-space: nowrap;
+ text-align: unset;
+ user-select: none;
+ /* Make sure to size correctly if the combobox has a non-auto block-size. */
+ block-size: 100%;
+ /* Try to always display our own text */
+ min-inline-size: max-content;
+ box-sizing: border-box;
+ line-height: -moz-block-height;
+}
+
+option[label]::before {
+ content: attr(label);
+}
+
+select:-moz-select-list-box option,
+select:-moz-select-list-box optgroup {
+ line-height: normal !important;
+}
+
+option {
+ display: block;
+ float: none !important;
+ position: static !important;
+ /* This makes sure that it is a containing block for positioned descendants. */
+ will-change: -moz-fixed-pos-containing-block !important;
+
+ min-block-size: 1em;
+ padding-block: 2px;
+ user-select: none;
+ /*
+ * Note that the "UA !important" tests in
+ * layout/style/test/test_animations.html depend on this rule, because
+ * they need some UA !important rule to test. If this changes, use a
+ * different one there.
+ */
+ white-space: nowrap !important;
+ word-wrap: normal !important;
+}
+
+select > option {
+ padding-inline: 4px;
+}
+
+option:checked {
+ background-color: -moz-cellhighlight;
+ color: -moz-cellhighlighttext;
+}
+
+select:-moz-select-list-box:focus option:checked {
+ background-color: SelectedItem !important;
+ color: SelectedItemText !important;
+}
+
+optgroup {
+ display: block;
+ float: none !important;
+ position: static !important;
+ font-style: italic;
+ font-weight: bold;
+ font-size: unset;
+ user-select: none;
+ white-space: nowrap !important;
+ word-wrap: normal !important;
+}
+
+optgroup > option {
+ padding-inline-start: 20px;
+ font-style: normal;
+ font-weight: normal;
+}
+
+optgroup:before {
+ display: block;
+ content: "\200b" attr(label);
+}
+
+@media (-moz-platform: android) {
+ /* These elements are handled by the prompt module. */
+ select option,
+ select optgroup {
+ pointer-events: none;
+ }
+}
+
+*|*::-moz-dropdown-list {
+ content: inherit;
+ z-index: 2147483647;
+ background-color: inherit;
+ user-select: none;
+ position: static !important;
+ float: none !important;
+
+ /*
+ * We can't change the padding here, because that would affect our
+ * intrinsic inline-size, since we scroll. But at the same time, we want
+ * to make sure that our inline-start border+padding matches the inline-start
+ * border+padding of a combobox so that our scrollbar will line up
+ * with the dropmarker. So set our inline-start border to 2px.
+ */
+ border: 1px outset black !important;
+ border-inline-start-width: 2px !important;
+}
+
+input:disabled,
+textarea:disabled,
+option:disabled,
+optgroup:disabled,
+select:disabled {
+ color: GrayText;
+ background-color: -moz-DisabledField;
+ cursor: unset;
+}
+
+input:disabled,
+textarea:disabled {
+ cursor: default;
+}
+
+option:disabled,
+optgroup:disabled {
+ background-color: transparent;
+}
+
+/* hidden inputs */
+input[type=hidden] {
+ appearance: none;
+ -moz-default-appearance: none;
+ display: none !important;
+ padding: unset;
+ border: 0;
+ cursor: auto;
+ -moz-user-focus: ignore;
+}
+
+/* image buttons */
+input[type=image] {
+ appearance: none;
+ -moz-default-appearance: none;
+ padding: unset;
+ border: none;
+ background-color: transparent;
+ font-family: sans-serif;
+ font-size: small;
+ cursor: pointer;
+}
+
+input[type=image]:disabled {
+ cursor: unset;
+}
+
+/* colored part of the color selector button */
+::-moz-color-swatch {
+ width: 100%;
+ height: 100%;
+ min-width: 3px;
+ min-height: 3px;
+ margin-inline: auto;
+ box-sizing: border-box;
+ border: 1px solid grey;
+ display: block;
+}
+
+/* radio buttons */
+input[type=radio] {
+ appearance: auto;
+ -moz-default-appearance: radio;
+ margin-block: 3px 0;
+ margin-inline: 5px 3px;
+}
+
+/* check boxes */
+input[type=checkbox] {
+ appearance: auto;
+ -moz-default-appearance: checkbox;
+ margin-block: 3px;
+ margin-inline: 4px 3px;
+}
+
+/* Common features of radio buttons and check boxes */
+
+input[type=radio],
+input[type=checkbox] {
+ box-sizing: border-box;
+ cursor: default;
+ /* unset some values from the general 'input' rule above: */
+ padding: unset;
+ border: unset;
+ background-color: unset;
+ color: unset;
+}
+
+input:is([type=radio], [type=checkbox]):is(:disabled, :disabled:active, :disabled:hover:active) {
+ cursor: unset;
+}
+
+input[type=search] {
+ box-sizing: border-box;
+}
+
+/* buttons */
+
+/* Note: Values in nsNativeTheme IsWidgetStyled function
+ need to match button background/border values here */
+
+/* Non text-related properties for buttons: these ones are shared with
+ input[type=color] */
+button,
+::file-selector-button,
+input:is([type=color], [type=reset], [type=button], [type=submit]) {
+ appearance: auto;
+ -moz-default-appearance: button;
+ /* The sum of border and padding on block-start and block-end
+ must be the same here, for text inputs, and for <select>.
+ Note -moz-focus-inner padding does not affect button size. */
+ padding-block: 1px;
+ padding-inline: 8px;
+ border: 2px outset ButtonBorder;
+ background-color: ButtonFace;
+ cursor: default;
+ box-sizing: border-box;
+ user-select: none;
+ overflow-clip-box: padding-box;
+}
+
+/* Text-related properties for buttons: these ones are not shared with
+ input[type=color] */
+button,
+::file-selector-button,
+input:is([type=reset], [type=button], [type=submit]) {
+ color: ButtonText;
+ font: -moz-button;
+ white-space: pre;
+ text-align: center;
+ padding-inline: 4px;
+}
+
+input[type=color] {
+ inline-size: 64px;
+ block-size: 32px;
+ padding: 4px;
+}
+
+/* https://github.com/whatwg/html/issues/9976 */
+input:not([type=image i], [type=range i], [type=checkbox i], [type=radio i]) {
+ overflow: clip !important;
+ overflow-clip-margin: 0px !important;
+}
+
+button,
+::file-selector-button {
+ /* Buttons should lay out like "normal" html, mostly */
+ white-space: unset;
+ /* But no text-decoration reaching inside, by default */
+ display: inline-block;
+}
+
+*|*::-moz-button-content {
+ display: block;
+ /* Please keep the declarations below in sync with ::-moz-scrolled-content in
+ ua.css and ::-moz-fieldset-content above. */
+ content: inherit;
+ /* Multicol container */
+ column-count: inherit;
+ column-width: inherit;
+ column-gap: inherit;
+ column-rule: inherit;
+ column-fill: inherit;
+ /* Flex container */
+ flex-direction: inherit;
+ flex-wrap: inherit;
+ /* -webkit-box container (aliased from -webkit versions to -moz versions) */
+ -moz-box-orient: inherit;
+ -moz-box-direction: inherit;
+ -moz-box-pack: inherit;
+ -moz-box-align: inherit;
+ /* Grid container */
+ grid-auto-columns: inherit;
+ grid-auto-rows: inherit;
+ grid-auto-flow: inherit;
+ grid-column-gap: inherit;
+ grid-row-gap: inherit;
+ grid-template-areas: inherit;
+ grid-template-columns: inherit;
+ grid-template-rows: inherit;
+ /* CSS Align */
+ align-content: inherit;
+ align-items: inherit;
+ justify-content: inherit;
+ justify-items: inherit;
+}
+
+::file-selector-button:hover,
+button:hover,
+input:is([type=reset], [type=button], [type=submit], [type=color]):hover {
+ color: -moz-buttonhovertext;
+ background-color: -moz-buttonhoverface;
+}
+
+::file-selector-button:active:hover,
+button:active:hover,
+input:is([type=reset], [type=button], [type=submit], [type=color]):active:hover {
+ border-style: inset;
+ color: -moz-buttonactivetext;
+ background-color: -moz-buttonactiveface;
+}
+
+::-moz-focus-inner {
+ /* Note this padding only affects the -moz-focus-inner ring, not the button itself */
+ padding-block: 0;
+ padding-inline: 2px;
+ border: 1px dotted transparent;
+}
+
+:focus-visible::-moz-focus-inner {
+ border-color: currentColor;
+}
+
+:is(:disabled, :disabled:active)::file-selector-button,
+button:is(:disabled, :disabled:active),
+input:is([type=reset], [type=button], [type=submit], [type=color]):is(:disabled, :disabled:active),
+select:is(:disabled, :disabled:active) > button {
+ border-style: outset;
+ cursor: unset;
+}
+
+:is(:disabled, :disabled:active)::file-selector-button,
+button:is(:disabled, :disabled:active),
+input:is([type=reset], [type=button], [type=submit]):is(:disabled, :disabled:active),
+select:is(:disabled, :disabled:active) > button {
+ color: GrayText;
+ background-color: -moz-ButtonDisabledFace;
+}
+
+/* file selector */
+input[type=file] {
+ white-space: nowrap !important;
+ overflow-clip-box: padding-box;
+ color: unset;
+
+ /* Revert rules which apply on all inputs. */
+ appearance: none;
+ -moz-default-appearance: none;
+ cursor: default;
+
+ border: none;
+ background-color: transparent;
+ padding: unset;
+}
+
+input[type=file] > label {
+ display: inline-block;
+ min-inline-size: 12em;
+ text-align: match-parent;
+
+ color: unset;
+ font-size: unset;
+ letter-spacing: unset;
+
+ user-select: none;
+ unicode-bidi: plaintext;
+}
+
+/* button part of file selector */
+::file-selector-button {
+ font-size: unset;
+ letter-spacing: unset;
+ cursor: unset;
+ margin-inline-end: 5px;
+}
+
+ /*
+ * Make form controls inherit 'unicode-bidi' transparently as required by
+ * their various anonymous descendants and pseudo-elements:
+ *
+ * <textarea> and <input type=text>:
+ * inherit into the scroll frame with pseudo ::-moz-text-control-editing-root
+ * which is a (direct or indirect) child of the text control.
+ *
+ * Buttons (either <button>, <input type=submit>, <input type=button>
+ * or <input type=reset>)
+ * inherit into the ':-moz-button-content' pseudo-element.
+ *
+ * <select>:
+ * inherit into the ':-moz-display-comboboxcontrol-frame' pseudo-element and
+ * the <optgroup>'s ':before' pseudo-element, which is where the label of
+ * the <optgroup> gets displayed. The <option>s don't use anonymous boxes,
+ * so they need no special rules.
+ */
+::placeholder,
+::-moz-text-control-editing-root,
+*|*::-moz-button-content,
+*|*::-moz-display-comboboxcontrol-frame,
+optgroup:before {
+ unicode-bidi: inherit;
+ text-overflow: inherit;
+}
+
+progress {
+ appearance: auto;
+ -moz-default-appearance: progress-bar;
+ display: inline-block;
+ vertical-align: -0.2em;
+
+ /* Default style in case of there is appearance: none; */
+ border: 1px solid ThreeDShadow;
+ border-right-color: ThreeDHighlight;
+ border-bottom-color: ThreeDHighlight;
+ /* #e6e6e6 is a light gray. */
+ background-color: #e6e6e6;
+ overflow: clip;
+}
+
+progress::-moz-progress-bar,
+progress::slider-fill {
+ /* Prevent styling that would change the type of frame we construct. */
+ display: inline-block !important;
+ float: none !important;
+ position: static !important;
+ overflow: visible !important;
+ box-sizing: border-box !important;
+
+ appearance: auto;
+ -moz-default-appearance: progresschunk;
+ height: 100%;
+ width: 100%;
+
+ /* Default style in case of there is appearance: none; */
+ background-color: #0064b4; /* blue */
+}
+
+meter {
+ appearance: auto;
+ -moz-default-appearance: meter;
+ display: inline-block;
+ vertical-align: -0.2em;
+ background: linear-gradient(#e6e6e6, #e6e6e6, #eeeeee 20%, #cccccc 45%, #cccccc 55%);
+ overflow: clip;
+}
+
+meter::-moz-meter-bar,
+meter::slider-fill {
+ /* Block styles that would change the type of frame we construct. */
+ display: inline-block !important;
+ float: none !important;
+ position: static !important;
+ overflow: visible !important;
+
+ appearance: auto;
+ -moz-default-appearance: meterchunk;
+ height: 100%;
+ width: 100%;
+}
+
+meter:-moz-meter-optimum::-moz-meter-bar,
+meter:-moz-meter-optimum::slider-fill {
+ /* green. */
+ background: linear-gradient(#ad7, #ad7, #cea 20%, #7a3 45%, #7a3 55%);
+}
+meter:-moz-meter-sub-optimum::-moz-meter-bar,
+meter:-moz-meter-sub-optimum::slider-fill {
+ /* orange. */
+ background: linear-gradient(#fe7, #fe7, #ffc 20%, #db3 45%, #db3 55%);
+}
+meter:-moz-meter-sub-sub-optimum::-moz-meter-bar,
+meter:-moz-meter-sub-sub-optimum::slider-fill {
+ /* red. */
+ background: linear-gradient(#f77, #f77, #fcc 20%, #d44 45%, #d44 55%);
+}
+
+input[type=range] {
+ appearance: auto;
+ -moz-default-appearance: range;
+ margin: 2px;
+ /* Override some rules that apply on all input types: */
+ cursor: default;
+ padding: unset;
+ border: unset;
+ /* Prevent nsIFrame::HandlePress setting mouse capture to this element. */
+ user-select: none !important;
+}
+
+/**
+ * Layout handles positioning of this pseudo-element specially (so that content
+ * authors can concentrate on styling the thumb without worrying about the
+ * logic to position it). Specifically the 'margin', 'top' and 'left'
+ * properties are ignored.
+ *
+ * If content authors want to have a vertical range, they will also need to
+ * set the width/height of this pseudo-element.
+ *
+ * TODO(emilio, bug 1663819): Losen these restrictions once these
+ * pseudo-elements are better spec'd out.
+ */
+input[type=range]::-moz-range-track,
+input[type=range]::slider-track {
+ /* Prevent styling that would change the type of frame we construct. */
+ display: block !important;
+ float: none !important;
+ position: static !important;
+ writing-mode: unset !important;
+ direction: unset !important;
+ block-size: 0.2em; /* same as inline-size below */
+ /* Prevent nsIFrame::HandlePress setting mouse capture to this element. */
+ user-select: none !important;
+}
+
+input[type=range][orient=vertical]::-moz-range-track,
+input[type=range][orient=vertical]::slider-track {
+ inline-size: 0.2em; /* same as block-size above */
+ block-size: 100%;
+}
+
+/**
+ * Layout handles positioning of this pseudo-element specially (so that content
+ * authors can concentrate on styling this pseudo-element without worrying
+ * about the logic to position it). Specifically the 'margin', 'top' and 'left'
+ * properties are ignored. Additionally, if the range is horizontal, the width
+ * property is ignored, and if the range range is vertical, the height property
+ * is ignored.
+ */
+input[type=range]::-moz-range-progress,
+input[type=range]::slider-fill {
+ /* Prevent styling that would change the type of frame we construct. */
+ display: block !important;
+ float: none !important;
+ position: static !important;
+ writing-mode: unset !important;
+ direction: unset !important;
+ /* Since one of width/height will be ignored, this just sets the "other"
+ dimension. */
+ width: 0.2em;
+ height: 0.2em;
+ /* Prevent nsIFrame::HandlePress setting mouse capture to this element. */
+ user-select: none !important;
+}
+
+/**
+ * Layout handles positioning of this pseudo-element specially (so that content
+ * authors can concentrate on styling the thumb without worrying about the
+ * logic to position it). Specifically the 'margin', 'top' and 'left'
+ * properties are ignored.
+ */
+input[type=range]::-moz-range-thumb,
+input[type=range]::slider-thumb {
+ /* Native theming is atomic for range. Set appearance on the range
+ * to get rid of it. The thumb's appearance is fixed.
+ */
+ appearance: auto !important;
+ -moz-default-appearance: range-thumb !important;
+ /* Prevent styling that would change the type of frame we construct. */
+ display: block !important;
+ float: none !important;
+ position: static !important;
+ writing-mode: unset !important;
+ direction: unset !important;
+
+ width: 1em;
+ height: 1em;
+ border: 0.1em solid #999;
+ border-radius: 0.5em;
+ background-color: #F0F0F0;
+ /* Prevent nsIFrame::HandlePress setting mouse capture to this element. */
+ user-select: none !important;
+}
+
+input[type=number] {
+ appearance: auto;
+ -moz-default-appearance: number-input;
+}
+
+input[type=number]::-moz-number-spin-box {
+ writing-mode: horizontal-tb;
+ display: flex;
+ flex-direction: column;
+ width: max-content;
+ align-self: center;
+ justify-content: center;
+ /* Don't allow the spin buttons to create overflow */
+ max-height: 100%;
+ max-width: 100%;
+ overflow: clip;
+}
+
+input[type=number]::-moz-number-spin-up,
+input[type=number]::-moz-number-spin-down {
+ writing-mode: horizontal-tb;
+ appearance: auto;
+ -moz-default-appearance: spinner-upbutton;
+ display: block; /* bug 926670 */
+ flex-grow: 1;
+ cursor: default;
+}
+
+input[type=number]::-moz-number-spin-down {
+ -moz-default-appearance: spinner-downbutton;
+}
+
+input::-moz-search-clear-button,
+input::-moz-reveal {
+ display: block;
+ cursor: default;
+ width: 1em;
+ height: 1em;
+ max-height: 100%;
+ max-width: 100%;
+ margin-inline-start: 1px;
+ background-image: url("resource://content-accessible/searchfield-cancel.svg");
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+}
+
+input::-moz-reveal {
+ background-image: url("resource://gre-resources/password.svg");
+ -moz-context-properties: fill;
+ fill: currentColor;
+}
+
+input:-moz-revealed::-moz-reveal {
+ background-image: url("resource://gre-resources/password-hide.svg");
+}
+
+input:-moz-value-empty::-moz-reveal,
+input:-moz-value-empty::-moz-search-clear-button {
+ visibility: hidden;
+}
+
+input:is([type=date], [type=time], [type=datetime-local]) {
+ font-family: -moz-fixed;
+ cursor: default;
+}
+
+input:is([type=date], [type=time], [type=datetime-local]):is(:disabled, :read-only) {
+ color: GrayText;
+}
+
+input:autofill, select:autofill {
+ /* The idea behind using background-image instead of plain background-color
+ * is that it's less likely to be overridden by the page. */
+ background-image: linear-gradient(-moz-autofill-background, -moz-autofill-background);
+}
+
+input:-moz-autofill-preview, select:-moz-autofill-preview {
+ color: GrayText;
+}
diff --git a/layout/style/res/html.css b/layout/style/res/html.css
new file mode 100644
index 0000000000..383aa35f7b
--- /dev/null
+++ b/layout/style/res/html.css
@@ -0,0 +1,927 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
+@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);
+
+@font-face {
+ font-family: -moz-bullet-font;
+ src: url("data:font/woff2;base64,d09GMgABAAAAAARYAAwAAAAACqAAAAQFAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cBmAAgwIRCAqKDIcYATYCJAMsCxgABCAFg2AHIBtDCFGUrM0MyM/DGJyndUclpZwnU8QPQ6wsFOHO+/yHNvV9IEqSQgsVJW2p+uZkohKbVExPEz/JUW7zk6rzYH9723UJ/4DzBII8CSzwgFMOOE4KW2rjAmh4qQWSiid60f7Y9FsXScfUpeluagFqTTQYyoQaQSubXil0lHRsBehAI9KofG/6ogF9/Hl6lBd920LmeTxj8JXBfFWKwYAY3QsAAXD17ZAB4M6Ocv/nsZlkaTl0gLipewIQEMB2d9lKDAT1ZjP/F7jmWsQ3wn8AZvZFgIIC7ESh4AP1iFb4nPnfWyJgcQArpUWqW9W96j71kHr/L9a/dNRbj+8/ujXLWCMMFYVYiXoEaZQ4YOC/E7Dkz3JEjaokxAP6i3hIUjv9fwYyE6I3yIozKkaGN63fP36aeLJmxlRZwZiFziPrMVvOlnCF98SQJfGO/Xllv/FnWAcEjrMqaMCPwVmktCDO5zzxcXqeKS+QFxYnvJLFVzKZAFcKnlZRwHEPVM3mXxNt2gdZEpkKaIAG9gbgDTU5SOYDcmX7Rr61evEA1U+1/3SMb6FFtUqh55nYleE95j2zPNxzdlnGoR5xu/PxQKzb7bFaFLvl79Z54Lx9OH+g0/PPYlcsNo/nwP2V2vsHUHsV4KqEsd+e2r6Fu2v7abkfvsb65b41uwvRvqewH25kTntqolbYsEr5DAqrX97a0jrBLjFRExVHR2dH1Za6unyZfCVZHqQP0BtYjuUYlg9J6SurcrTEiOr26m5xcuTW4Sp3ljkUYWJ9+/iVmzbzPpcuXnGeqU3U7IKjz/qeKSSCltyhuSoxkqqTZX+DXt/OMmAikC8SzVD0jg3dbhIE0eUqc04UFFrxCSXzFxgHclzRMPe0ocOjiKRoSZISE0OtpkBjECdxkkRM1KiaJV0ed0V5umBP1wyUPL2ZBGLSDh5itIHahHRF0CY5nZMGib6T3cOmDvcTSZKuPiFJFpPRGNTIOq7jl5OjDiGIoo6qEE0QtfQJ3VYUD2RZvbm5uWWCWOgR4HXAk5GxLEO0izhLdvPTfpTV3xeOnOfT96uR53CdPrPq+P9HZvzO4QCMmDr7lTcNZorT6iMOYgI+IxClMzeCspeoLt5ABGSpta6gRZmQSnzk9e9jmOAYKC7RsyGMYaB6MpbNKMiRq2U6vI0yPZuTMiODFytMrGEBi7pjGgkAMoaNLGOFSoYceaZMx1+XTC/UNpmRzY1nmwT7AIcK+fK5lYNjz893lwMTuXlD2AKl8Md+24AxitRaLH/MUMqd9z862owy/bZTuZUqs1iRQop0KdLgZwPc9JS0lprnkef1gitBCRhjgV+vjGK8BfIV8CrlVghj1vIyZfyC/AJvqbsQYAEA/v+IAQA=");
+}
+
+/* bidi */
+
+:-moz-has-dir-attr {
+ unicode-bidi: isolate;
+}
+:-moz-dir-attr-rtl {
+ direction: rtl;
+}
+:-moz-dir-attr-ltr {
+ direction: ltr;
+}
+
+:-moz-dir-attr-like-auto:dir(ltr) { direction: ltr; }
+:-moz-dir-attr-like-auto:dir(rtl) { direction: rtl; }
+
+/* https://html.spec.whatwg.org/#bidi-rendering */
+input[type=tel]:dir(ltr) {
+ direction: ltr;
+}
+
+/* To ensure http://www.w3.org/TR/REC-html40/struct/dirlang.html#style-bidi:
+ *
+ * "When a block element that does not have a dir attribute is transformed to
+ * the style of an inline element by a style sheet, the resulting presentation
+ * should be equivalent, in terms of bidirectional formatting, to the
+ * formatting obtained by explicitly adding a dir attribute (assigned the
+ * inherited value) to the transformed element."
+ *
+ * and the rules in http://dev.w3.org/html5/spec/rendering.html#rendering
+ */
+
+address,
+article,
+aside,
+blockquote,
+body,
+caption,
+center,
+col,
+colgroup,
+dd,
+dir,
+div,
+dl,
+dt,
+fieldset,
+figcaption,
+figure,
+footer,
+form,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+header,
+hgroup,
+hr,
+html,
+legend,
+li,
+listing,
+main,
+marquee,
+menu,
+nav,
+noframes,
+ol,
+p,
+plaintext,
+pre,
+search,
+section,
+summary,
+table,
+tbody,
+td,
+tfoot,
+th,
+thead,
+tr,
+ul,
+xmp {
+ unicode-bidi: isolate;
+}
+
+bdi, output {
+ unicode-bidi: isolate;
+}
+/* We need the "bdo:-moz-has-dir-attr" bit because "bdo" has lower
+ specificity than the ":-moz-has-dir-attr" selector above. */
+bdo, bdo:-moz-has-dir-attr {
+ unicode-bidi: isolate-override;
+}
+textarea:-moz-dir-attr-like-auto,
+pre:-moz-dir-attr-like-auto {
+ unicode-bidi: plaintext;
+}
+
+/* blocks */
+
+article,
+aside,
+details,
+div,
+dt,
+figcaption,
+footer,
+form,
+header,
+hgroup,
+html,
+main,
+nav,
+search,
+section,
+summary {
+ display: block;
+}
+
+body {
+ display: block;
+ margin: 8px;
+}
+
+p, dl, multicol {
+ display: block;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+}
+
+dd {
+ display: block;
+ margin-inline-start: 40px;
+}
+
+blockquote, figure {
+ display: block;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+ margin-inline-start: 40px;
+ margin-inline-end: 40px;
+}
+
+address {
+ display: block;
+ font-style: italic;
+}
+
+center {
+ display: block;
+ text-align: -moz-center;
+}
+
+h1 {
+ display: block;
+ font-size: 2em;
+ font-weight: bold;
+ margin-block-start: .67em;
+ margin-block-end: .67em;
+}
+
+h2,
+:is(article, aside, nav, section)
+h1 {
+ display: block;
+ font-size: 1.5em;
+ font-weight: bold;
+ margin-block-start: .83em;
+ margin-block-end: .83em;
+}
+
+h3,
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+h1 {
+ display: block;
+ font-size: 1.17em;
+ font-weight: bold;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+}
+
+h4,
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+h1 {
+ display: block;
+ font-size: 1.00em;
+ font-weight: bold;
+ margin-block-start: 1.33em;
+ margin-block-end: 1.33em;
+}
+
+h5,
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+h1 {
+ display: block;
+ font-size: 0.83em;
+ font-weight: bold;
+ margin-block-start: 1.67em;
+ margin-block-end: 1.67em;
+}
+
+h6,
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+:is(article, aside, nav, section)
+h1 {
+ display: block;
+ font-size: 0.67em;
+ font-weight: bold;
+ margin-block-start: 2.33em;
+ margin-block-end: 2.33em;
+}
+
+listing {
+ display: block;
+ font-family: -moz-fixed;
+ font-size: medium;
+ white-space: pre;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+}
+
+xmp, pre, plaintext {
+ display: block;
+ font-family: -moz-fixed;
+ white-space: pre;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+}
+
+/* tables */
+
+table {
+ display: table;
+ border-spacing: 2px;
+ border-collapse: separate;
+ /* XXXldb do we want this if we're border-collapse:collapse ? */
+ box-sizing: border-box;
+ text-indent: 0;
+}
+
+table[align="left"] {
+ float: left;
+}
+
+table[align="right"] {
+ float: right;
+ text-align: start;
+}
+
+
+/* border collapse rules */
+
+ /* Set hidden if we have 'frame' or 'rules' attribute.
+ Set it on all sides when we do so there's more consistency
+ in what authors should expect */
+
+ /* Put this first so 'border' and 'frame' rules can override it. */
+table[rules] {
+ border-width: thin;
+ border-style: hidden;
+}
+
+ /* 'border' before 'frame' so 'frame' overrides
+ A border with a given value should, of course, pass that value
+ as the border-width in pixels -> attr mapping */
+
+ /* :-moz-table-border-nonzero is like [border]:not([border="0"]) except it
+ also checks for other zero-like values according to HTML attribute
+ parsing rules */
+table:-moz-table-border-nonzero {
+ border-width: thin;
+ border-style: outset;
+}
+
+table[frame] {
+ border: thin hidden;
+}
+
+/* specificity must beat table:-moz-table-border-nonzero rule above */
+table[frame="void"] { border-style: hidden; }
+table[frame="above"] { border-style: outset hidden hidden hidden; }
+table[frame="below"] { border-style: hidden hidden outset hidden; }
+table[frame="lhs"] { border-style: hidden hidden hidden outset; }
+table[frame="rhs"] { border-style: hidden outset hidden hidden; }
+table[frame="hsides"] { border-style: outset hidden; }
+table[frame="vsides"] { border-style: hidden outset; }
+table[frame="box"],
+table[frame="border"] { border-style: outset; }
+
+
+/* Internal Table Borders */
+
+ /* 'border' cell borders first */
+
+table:-moz-table-border-nonzero > * > tr > td,
+table:-moz-table-border-nonzero > * > tr > th,
+table:-moz-table-border-nonzero > * > td,
+table:-moz-table-border-nonzero > * > th,
+table:-moz-table-border-nonzero > td,
+table:-moz-table-border-nonzero > th
+{
+ border-width: thin;
+ border-style: inset;
+}
+
+/* collapse only if rules are really specified */
+table[rules]:not([rules="none"], [rules=""]) {
+ border-collapse: collapse;
+}
+
+/* only specified rules override 'border' settings
+ (increased specificity to achieve this) */
+table[rules]:not([rules=""])> tr > td,
+table[rules]:not([rules=""])> * > tr > td,
+table[rules]:not([rules=""])> tr > th,
+table[rules]:not([rules=""])> * > tr > th,
+table[rules]:not([rules=""])> td,
+table[rules]:not([rules=""])> th
+{
+ border-width: thin;
+ border-style: none;
+}
+
+
+table[rules][rules="none"] > tr > td,
+table[rules][rules="none"] > * > tr > td,
+table[rules][rules="none"] > tr > th,
+table[rules][rules="none"] > * > tr > th,
+table[rules][rules="none"] > td,
+table[rules][rules="none"] > th
+{
+ border-width: thin;
+ border-style: none;
+}
+
+table[rules][rules="all"] > tr > td,
+table[rules][rules="all"] > * > tr > td,
+table[rules][rules="all"] > tr > th,
+table[rules][rules="all"] > * > tr > th,
+table[rules][rules="all"] > td,
+table[rules][rules="all"] > th
+{
+ border-width: thin;
+ border-style: solid;
+}
+
+table[rules][rules="rows"] > tr,
+table[rules][rules="rows"] > * > tr {
+ border-block-start-width: thin;
+ border-block-end-width: thin;
+ border-block-start-style: solid;
+ border-block-end-style: solid;
+}
+
+
+table[rules][rules="cols"] > tr > td,
+table[rules][rules="cols"] > * > tr > td,
+table[rules][rules="cols"] > tr > th,
+table[rules][rules="cols"] > * > tr > th {
+ border-inline-start-width: thin;
+ border-inline-end-width: thin;
+ border-inline-start-style: solid;
+ border-inline-end-style: solid;
+}
+
+table[rules][rules="groups"] > colgroup {
+ border-inline-start-width: thin;
+ border-inline-end-width: thin;
+ border-inline-start-style: solid;
+ border-inline-end-style: solid;
+}
+table[rules][rules="groups"] > tfoot,
+table[rules][rules="groups"] > thead,
+table[rules][rules="groups"] > tbody {
+ border-block-start-width: thin;
+ border-block-end-width: thin;
+ border-block-start-style: solid;
+ border-block-end-style: solid;
+}
+
+
+/* caption inherits from table not table-outer */
+caption {
+ display: table-caption;
+ text-align: center;
+}
+
+table[align="center"] > caption {
+ margin-inline-start: auto;
+ margin-inline-end: auto;
+}
+
+table[align="center"] > caption[align="left"]:dir(ltr) {
+ margin-inline-end: 0;
+}
+table[align="center"] > caption[align="left"]:dir(rtl) {
+ margin-inline-start: 0;
+}
+
+table[align="center"] > caption[align="right"]:dir(ltr) {
+ margin-inline-start: 0;
+}
+table[align="center"] > caption[align="right"]:dir(rtl) {
+ margin-inline-end: 0;
+}
+
+tr {
+ display: table-row;
+ vertical-align: inherit;
+}
+
+col {
+ display: table-column;
+}
+
+colgroup {
+ display: table-column-group;
+}
+
+tbody {
+ display: table-row-group;
+ vertical-align: middle;
+}
+
+thead {
+ display: table-header-group;
+ vertical-align: middle;
+}
+
+tfoot {
+ display: table-footer-group;
+ vertical-align: middle;
+}
+
+/* for XHTML tables without tbody */
+table > tr {
+ vertical-align: middle;
+}
+
+td {
+ display: table-cell;
+ vertical-align: inherit;
+ text-align: unset;
+ padding: 1px;
+}
+
+th {
+ display: table-cell;
+ vertical-align: inherit;
+ font-weight: bold;
+ padding: 1px;
+ text-align: -moz-center-or-inherit;
+}
+
+:is(tr, tbody, thead, tfoot, table) > form:-moz-is-html {
+ /* Important: don't show these forms in HTML */
+ display: none !important;
+}
+
+table[bordercolor] > tbody,
+table[bordercolor] > thead,
+table[bordercolor] > tfoot,
+table[bordercolor] > col,
+table[bordercolor] > colgroup,
+table[bordercolor] > tr,
+table[bordercolor] > * > tr,
+table[bordercolor] > tr > td,
+table[bordercolor] > * > tr > td,
+table[bordercolor] > tr > th,
+table[bordercolor] > * > tr > th {
+ border-color: inherit;
+}
+
+/* inlines */
+
+q:before {
+ content: open-quote;
+}
+
+q:after {
+ content: close-quote;
+}
+
+b, strong {
+ font-weight: bolder;
+}
+
+i, cite, em, var, dfn {
+ font-style: italic;
+}
+
+tt, code, kbd, samp {
+ font-family: -moz-fixed;
+}
+
+u, ins {
+ text-decoration: underline;
+}
+
+s, strike, del {
+ text-decoration: line-through;
+}
+
+big {
+ font-size: larger;
+}
+
+small {
+ font-size: smaller;
+}
+
+sub {
+ vertical-align: sub;
+ font-size: smaller;
+}
+
+sup {
+ vertical-align: super;
+ font-size: smaller;
+}
+
+nobr {
+ white-space: nowrap;
+}
+
+mark {
+ background: Mark;
+ color: MarkText;
+}
+
+/* titles */
+abbr[title], acronym[title] {
+ text-decoration: dotted underline;
+}
+
+/* lists */
+
+ul, menu, dir {
+ display: block;
+ list-style-type: disc;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+ padding-inline-start: 40px;
+}
+
+ul, ol, menu {
+ counter-reset: list-item;
+}
+
+ol {
+ display: block;
+ list-style-type: decimal;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+ padding-inline-start: 40px;
+}
+
+li {
+ display: list-item;
+ text-align: match-parent;
+}
+
+/* nested lists have no top/bottom margins */
+:is(ul, ol, dir, menu, dl) ul,
+:is(ul, ol, dir, menu, dl) ol,
+:is(ul, ol, dir, menu, dl) dir,
+:is(ul, ol, dir, menu, dl) menu,
+:is(ul, ol, dir, menu, dl) dl {
+ margin-block-start: 0;
+ margin-block-end: 0;
+}
+
+/* 2 deep unordered lists use a circle */
+:is(ol, ul, menu, dir) ul,
+:is(ol, ul, menu, dir) menu,
+:is(ol, ul, menu, dir) dir {
+ list-style-type: circle;
+}
+
+/* 3 deep (or more) unordered lists use a square */
+:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) ul,
+:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) menu,
+:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) dir {
+ list-style-type: square;
+}
+
+
+/* leafs */
+
+/* <hr> noshade and color attributes are handled completely by
+ * HTMLHRElement::MapAttributesIntoRule.
+ * https://html.spec.whatwg.org/#the-hr-element-2
+ */
+hr {
+ color: gray;
+ border-width: 1px;
+ border-style: inset;
+ margin-block-start: 0.5em;
+ margin-block-end: 0.5em;
+ margin-inline-start: auto;
+ margin-inline-end: auto;
+ overflow: hidden;
+
+ /* FIXME: This is not really per spec */
+ display: block;
+}
+
+hr[size="1"] {
+ border-style: solid none none none;
+}
+
+/* Note that we only intend for the alt content to show up if the image is
+ * broken. But non-broken images/inputs will have a replaced box, and thus we
+ * won't we don't generate the pseudo-element anyways. This prevents
+ * unnecessary reframing when images become broken / non-broken. */
+input[type=image]::before,
+img::before {
+ content: -moz-alt-content !important;
+ unicode-bidi: isolate;
+}
+
+img[usemap], object[usemap] {
+ color: blue;
+}
+
+frameset {
+ display: block ! important;
+ overflow: clip;
+ position: static ! important;
+ float: none ! important;
+ border: none ! important;
+}
+
+frame {
+ border-radius: 0 ! important;
+}
+
+iframe {
+ border: 2px inset;
+}
+
+spacer {
+ position: static ! important;
+ float: none ! important;
+}
+
+canvas {
+ user-select: none;
+}
+
+iframe:focus-visible,
+body:focus-visible,
+html:focus-visible {
+ /* These elements historically don't show outlines when focused by default.
+ * We could consider changing that if needed. */
+ outline-style: none;
+}
+
+/* hidden elements: https://html.spec.whatwg.org/#hidden-elements
+ *
+ * Exceptions:
+ *
+ * * area declaration needs to be !important, see below / bug 135040. That's
+ * hacky and broken.
+ *
+ * * [hidden] is implemented as a presentation attribute to avoid a global
+ * selector in a UA sheet.
+ */
+base, basefont, datalist, head, link, meta, noembed,
+noframes, param, rp, script, style, template, title {
+ display: none;
+}
+
+area {
+ /* Don't give it frames other than its imageframe */
+ display: none ! important;
+}
+
+iframe:fullscreen {
+ /* iframes in full-screen mode don't show a border. */
+ border: none !important;
+ padding: unset !important;
+}
+
+/* Details and summary
+ * https://html.spec.whatwg.org/#the-details-and-summary-elements
+ *
+ * Note that these rules need to be duplicated in details.css for the anonymous
+ * summary, which wouldn't match otherwise.
+ *
+ * The spec here says something different, see
+ * https://github.com/whatwg/html/issues/8610
+ */
+details > summary:first-of-type {
+ display: list-item;
+ counter-increment: list-item 0;
+ list-style: disclosure-closed inside;
+}
+details[open] > summary:first-of-type {
+ list-style-type: disclosure-open;
+}
+
+/* media elements */
+video {
+ object-fit: contain;
+}
+
+video > img:-moz-native-anonymous {
+ /* Video poster images should render with the video element's "object-fit" &
+ "object-position" properties */
+ object-fit: inherit !important;
+ object-position: inherit !important;
+}
+
+audio:not([controls]) {
+ display: none !important;
+}
+
+audio[controls] {
+ /* This ensures that intrinsic sizing can reliably shrinkwrap our
+ controls (which are also always horizontal) and produce a
+ reasonable intrinsic size from them. */
+ writing-mode: horizontal-tb !important;
+}
+
+*|*::-moz-html-canvas-content {
+ display: block !important;
+ /* we want to be an absolute and fixed container */
+ transform: translate(0) !important;
+}
+
+video > .caption-box {
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
+
+/**
+ * The pseudo element won't inherit CSS styles from its direct parent, `::cue`
+ * would actually inherit styles from video because it's video's pseudo element.
+ * Therefore, we have to explicitly set some styles which are already defined
+ * in its parent element in vtt.jsm.
+ */
+::cue {
+ color: rgba(255, 255, 255, 1);
+ white-space: pre-line;
+ background-color: rgba(0, 0, 0, 0.8);
+ font: 10px sans-serif;
+ overflow-wrap: break-word;
+ /* TODO : enable unicode-bidi, right now enable it would cause incorrect
+ display direction, maybe related with bug 1558431. */
+}
+
+/* <dialog> element styles */
+
+dialog {
+ position: absolute;
+ display: block;
+ inset-inline-start: 0;
+ inset-inline-end: 0;
+ margin: auto;
+ border-width: initial;
+ border-style: solid;
+ border-color: initial;
+ border-image: initial;
+ padding: 1em;
+ background-color: Canvas;
+ color: CanvasText;
+ width: -moz-fit-content;
+ height: -moz-fit-content;
+}
+
+dialog:not([open]) {
+ display: none;
+}
+
+dialog:modal {
+ -moz-top-layer: top !important;
+ position: fixed;
+ overflow: auto;
+ visibility: visible;
+ inset-block-start: 0;
+ inset-block-end: 0;
+ max-width: calc(100% - 6px - 2em);
+ max-height: calc(100% - 6px - 2em);
+}
+
+/* https://html.spec.whatwg.org/#flow-content-3 */
+dialog::backdrop {
+ background: rgba(0, 0, 0, 0.1);
+}
+
+marquee {
+ inline-size: -moz-available;
+ display: inline-block;
+ vertical-align: text-bottom;
+ text-align: start;
+}
+
+marquee:is([direction="up"], [direction="down"]) {
+ block-size: 200px;
+}
+
+/* Ruby */
+
+ruby {
+ display: ruby;
+}
+rb {
+ display: ruby-base;
+ white-space: nowrap;
+}
+rt {
+ display: ruby-text;
+}
+rtc {
+ display: ruby-text-container;
+}
+rtc, rt {
+ white-space: nowrap;
+ font-size: 50%;
+ -moz-min-font-size-ratio: 50%;
+ line-height: 1;
+}
+@media not (-moz-platform: windows) {
+ rtc, rt {
+ /* The widely-used Windows font Meiryo doesn't work fine with this
+ * setting, so disable this on Windows. We should re-enable it once
+ * Microsoft fixes this issue. See bug 1164279. */
+ font-variant-east-asian: ruby;
+ }
+}
+rtc, rt {
+ text-emphasis: none;
+}
+rtc:lang(zh), rt:lang(zh) {
+ ruby-align: center;
+}
+rtc:lang(zh-TW), rt:lang(zh-TW) {
+ font-size: 30%; /* bopomofo */
+ -moz-min-font-size-ratio: 30%;
+}
+rtc > rt {
+ font-size: unset;
+}
+ruby, rb, rt, rtc {
+ unicode-bidi: isolate;
+}
+
+/* Shadow DOM v1
+ * https://drafts.csswg.org/css-scoping/#slots-in-shadow-tree */
+slot {
+ display: contents;
+}
+
+/* Hide noscript elements if scripting is enabled */
+@media (scripting) {
+ noscript {
+ display: none !important;
+ }
+}
+
+@media print {
+ input, textarea, select, button, details {
+ -moz-user-input: none !important;
+ pointer-events: none !important;
+ }
+}
+
+/* Popover UA style, https://html.spec.whatwg.org/#flow-content-3 */
+/* stylelint-disable-next-line media-query-no-invalid */
+@media (-moz-bool-pref: "dom.element.popover.enabled") {
+ [popover]:not(:popover-open):not(dialog[open]) {
+ display:none;
+ }
+
+ dialog:popover-open {
+ display:block;
+ }
+
+ [popover] {
+ position: fixed;
+ inset: 0;
+ width: fit-content;
+ height: fit-content;
+ margin: auto;
+ border: solid;
+ padding: 0.25em;
+ overflow: auto;
+ color: CanvasText;
+ background-color: Canvas;
+ }
+
+ :popover-open {
+ -moz-top-layer: top;
+ }
+
+ :popover-open::backdrop {
+ position: fixed;
+ inset: 0;
+ pointer-events: none !important;
+ background-color: transparent;
+ }
+}
diff --git a/layout/style/res/noframes.css b/layout/style/res/noframes.css
new file mode 100644
index 0000000000..4d1adfdc1f
--- /dev/null
+++ b/layout/style/res/noframes.css
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 sheet is added to the style set for documents with frames disabled */
+
+noframes {
+ display: block;
+}
+
+frame, frameset, iframe {
+ display: none !important;
+}
diff --git a/layout/style/res/password-hide.svg b/layout/style/res/password-hide.svg
new file mode 100644
index 0000000000..74a63e3ed9
--- /dev/null
+++ b/layout/style/res/password-hide.svg
@@ -0,0 +1,7 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
+ <path d="M3.067 1.183a.626.626 0 0 0-.885.885l1.306 1.306A8.885 8.885 0 0 0 0 7.595l0 .809C1.325 11.756 4.507 14 8 14c1.687 0 3.294-.535 4.66-1.455l2.273 2.273a.626.626 0 0 0 .884-.886L3.067 1.183zm3.759 5.528 2.463 2.463c-.32.352-.777.576-1.289.576-.965 0-1.75-.785-1.75-1.75 0-.512.225-.969.576-1.289zM8 12.75c-3.013 0-5.669-1.856-6.83-4.75a7.573 7.573 0 0 1 3.201-3.745l1.577 1.577A2.958 2.958 0 0 0 5 8c0 1.654 1.346 3 3 3 .858 0 1.624-.367 2.168-.948l1.613 1.613A7.118 7.118 0 0 1 8 12.75z"/>
+ <path d="M8 2c-.687 0-1.356.11-2.007.275l1.049 1.049A7.06 7.06 0 0 1 8 3.25c3.013 0 5.669 1.856 6.83 4.75a7.925 7.925 0 0 1-1.141 1.971l.863.863A9.017 9.017 0 0 0 16 8.404l0-.809C14.675 4.244 11.493 2 8 2z"/>
+</svg>
diff --git a/layout/style/res/password.svg b/layout/style/res/password.svg
new file mode 100644
index 0000000000..0e9aa3f36f
--- /dev/null
+++ b/layout/style/res/password.svg
@@ -0,0 +1,7 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
+ <path d="M16 7.595C14.675 4.244 11.493 2 8 2S1.325 4.244 0 7.595l0 .809C1.325 11.756 4.507 14 8 14s6.675-2.244 8-5.595l0-.81zM8 12.75c-3.013 0-5.669-1.856-6.83-4.75C2.331 5.106 4.987 3.25 8 3.25S13.669 5.106 14.83 8c-1.161 2.894-3.817 4.75-6.83 4.75z"/>
+ <path d="M8 11c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3-1.346 3-3 3zm0-4.75c-.965 0-1.75.785-1.75 1.75S7.035 9.75 8 9.75 9.75 8.965 9.75 8 8.965 6.25 8 6.25z"/>
+</svg>
diff --git a/layout/style/res/plaintext.css b/layout/style/res/plaintext.css
new file mode 100644
index 0000000000..04d9b500b1
--- /dev/null
+++ b/layout/style/res/plaintext.css
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pre {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ -moz-control-character-visibility: visible;
+}
+
+.nowrap pre {
+ white-space: pre;
+}
+
+/* Make text go with the rules of dir=auto, but allow it to be overriden if 'Switch Text Direction' is triggered */
+html:not([dir]) pre { /* Not a UA sheet, so doesn't use :-moz-has-dir-attr */
+ unicode-bidi: plaintext;
+}
+
+@-moz-document unobservable-document() {
+ :root {
+ color-scheme: light dark;
+ }
+}
+
+/* NOTE(emilio): For some reason some pages, mainly bing.com, load a bunch of
+ * scripts in zero-size <object> elements, see bug 1548449.
+ *
+ * Line-breaking such documents is useless and pretty expensive, so only render
+ * them if there's a viewport. Sigh.
+ */
+@media (width: 0) or (height: 0) {
+ :root {
+ display: none;
+ }
+}
diff --git a/layout/style/res/quirk.css b/layout/style/res/quirk.css
new file mode 100644
index 0000000000..6e74839ee3
--- /dev/null
+++ b/layout/style/res/quirk.css
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
+
+
+/* Quirk: make orphaned LIs have inside bullet (b=1049) */
+
+/* force inside position for orphaned lis */
+li {
+ list-style-position: inside;
+}
+
+/* restore outside position for lists inside LIs */
+li :is(ul, ol, dir, menu) {
+ list-style-position: outside;
+}
+
+/* undo previous two rules for properly nested lists */
+:is(ul, ol, dir, menu) :is(ul, ol, dir, menu, li) {
+ list-style-position: unset;
+}
+
+
+/* Quirk: ensure that we get proper padding if the very first
+ * node in an LI is another UL or OL. This is an ugly way to
+ * fix the problem, because it extends the LI up into what
+ * would otherwise appear to be the ULs space. (b=38832) */
+
+/* Note: this fix will fail once we implement marker box
+ * alignment correctly. */
+li > ul:-moz-first-node,
+li > ol:-moz-first-node {
+ padding-block-start: 1em;
+}
+
+
+table {
+ text-align: start;
+ white-space: normal; /* compatible with IE & spec */
+ line-height: normal;
+
+ /* Quirk: cut off all font inheritance in tables except for family. */
+ font-size: initial;
+ font-weight: initial;
+ font-style: initial;
+ font-variant: initial;
+}
+
+
+/* Quirk: collapse top margin of BODY and TD and bottom margin of TD */
+
+/*
+ * While it may seem simpler to use :-moz-first-node and :-moz-last-node without
+ * tags, it's slower, since we have to do the :-moz-first-node or :-moz-last-node
+ * check on every single element in the document. If we list all the
+ * element names for which the UA stylesheet specifies a margin, the
+ * selectors will be hashed in the selector maps and things will be much more
+ * efficient.
+ */
+:is(body, td, th) > :is(p, dl, multicol, blockquote, h1, h2, h3, h4, h5, h6, listing, plaintext, xmp, pre, ul, menu, dir, ol):-moz-first-node {
+ margin-block-start: 0;
+}
+
+td > p:-moz-last-node, th > p:-moz-last-node {
+ margin-block-end: 0;
+}
+
+/* Similar as above, but for empty elements
+ * collapse the bottom or top margins of empty elements
+ * - see bug 97361
+ */
+:is(body, td, th) > :is(p, dl, multicol, blockquote, h1, h2, h3, h4, h5, h6, listing, plaintext, xmp, pre, ul, menu, dir, ol):-moz-only-whitespace:-moz-first-node {
+ margin-block-end: 0;
+}
+
+:is(td, th) > :is(p, dl, multicol, blockquote, h1, h2, h3, h4, h5, h6, listing, plaintext, xmp, pre, ul, menu, dir, ol):-moz-only-whitespace:-moz-last-node {
+ margin-block-start: 0;
+}
+
+/* Quirk: Make floated images have a margin (b=58899) */
+img[align=left] {
+ margin-right: 3px;
+}
+
+img[align=right] {
+ margin-left: 3px;
+}
+
+/*
+ * Quirk: Use border-box box sizing for text inputs, password inputs, and
+ * textareas. (b=184478 on why we use content-box sizing in standards mode)
+ */
+
+/* Note that all other <input>s already use border-box
+ sizing, so we're ok with this selector */
+input:not([type=image]), textarea {
+ box-sizing: border-box;
+}
+
+/* Quirk: give form margin for compat (b=41806) */
+form {
+ margin-block-end: 1em;
+}
diff --git a/layout/style/res/scrollbars.css b/layout/style/res/scrollbars.css
new file mode 100644
index 0000000000..a4f2611888
--- /dev/null
+++ b/layout/style/res/scrollbars.css
@@ -0,0 +1,234 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* Rules required for style caching of anonymous content scrollbar parts */
+/* stylelint-disable-next-line media-query-no-invalid */
+@media (-moz-bool-pref: "layout.css.cached-scrollbar-styles.enabled") {
+ :is(scrollcorner, resizer, scrollbar, scrollbarbutton, slider):where(:-moz-native-anonymous) {
+ /* All scrollbar parts must not inherit any properties from the scrollable
+ * element (except for visibility and pointer-events), for the anonymous
+ * content style caching system to work.
+ */
+ all: initial;
+ visibility: inherit;
+ pointer-events: inherit;
+
+ /* These properties are not included in 'all'. */
+ -moz-context-properties: initial;
+ -moz-control-character-visibility: initial;
+ -moz-min-font-size-ratio: initial;
+ -moz-box-collapse: initial;
+ -moz-theme: initial;
+
+ /* We don't want zoom on our ancestors to affect our styles. */
+ zoom: document;
+
+ math-depth: initial;
+ /* As long as inert implies pointer-events: none as it does now, we're
+ * good. */
+ -moz-inert: initial;
+
+ /* direction: initial is not sufficient, since its initial value can depend
+ * on the document's language. But we specify ltr explicitly below */
+
+ /* Similarly for font properties, whose initial values depend on the
+ * document's language. Scrollbar parts don't have any text or rely on
+ * font metrics.
+ */
+ font: 16px sans-serif;
+
+ /* The initial value of justify-items is `legacy`, which makes it depend on
+ * the parent style.
+ *
+ * Reset it to something else.
+ */
+ justify-items: start;
+
+ /* Avoid `object > *` rule in html.css from setting a useless, non-initial
+ * value of vertical-align.
+ */
+ vertical-align: initial !important;
+ }
+
+ /* There are other rules that set the cursor on the scrollbar, expecting them
+ * to inherit into its children. Explicitly inherit it, overriding the
+ * 'all: initial;' declaration above.
+ */
+ :is(scrollbarbutton, slider, thumb):where(:-moz-native-anonymous) {
+ cursor: inherit;
+ }
+}
+
+scrollbar, scrollbarbutton, scrollcorner, slider, thumb, resizer {
+ /* We need a display value that doesn't get blockified to preserve the
+ * scrollbar sizing asserts. In practice it doesn't matter since these get
+ * special frames */
+ display: block;
+ box-sizing: border-box;
+
+ /* Our scrollbar layout uses physical coordinates, we wouldn't want an
+ * horizontal scrollbar to flip in rtl for example. */
+ direction: ltr;
+ writing-mode: initial;
+
+ -moz-user-focus: ignore;
+ /* Prevent -moz-user-modify declaration from designmode.css having an effect. */
+ -moz-user-modify: initial;
+ user-select: none;
+}
+
+
+/********** resizer **********/
+
+resizer {
+ position: relative;
+ z-index: 2147483647;
+
+ background: url("chrome://global/skin/icons/resizer.svg") no-repeat;
+ background-size: 100% 100%;
+ cursor: se-resize;
+ width: 15px;
+ height: 15px;
+}
+
+resizer[dir="bottom"][flip],
+resizer[dir="bottomleft"] {
+ transform: scaleX(-1);
+}
+
+resizer[dir="bottomleft"] {
+ cursor: sw-resize;
+}
+
+resizer[dir="top"],
+resizer[dir="bottom"] {
+ cursor: ns-resize;
+}
+
+resizer[dir="left"] {
+ transform: scaleX(-1);
+}
+
+resizer[dir="left"],
+resizer[dir="right"] {
+ cursor: ew-resize;
+}
+
+resizer[dir="topleft"] {
+ cursor: nw-resize;
+}
+
+resizer[dir="topright"] {
+ cursor: ne-resize;
+}
+
+thumb {
+ appearance: auto;
+ -moz-default-appearance: scrollbarthumb-horizontal;
+}
+
+thumb[orient="vertical"] {
+ -moz-default-appearance: scrollbarthumb-vertical;
+}
+
+scrollbar[disabled] thumb {
+ visibility: hidden;
+}
+
+@media (-moz-platform: android) {
+ scrollbar, resizer, scrollcorner {
+ pointer-events: none;
+ }
+}
+
+scrollbar {
+ appearance: auto;
+ -moz-default-appearance: scrollbar-horizontal;
+ cursor: default;
+}
+
+scrollbar[orient="vertical"] {
+ -moz-default-appearance: scrollbar-vertical;
+}
+
+scrollbar[root] {
+ position: relative;
+ z-index: 2147483647; /* largest positive value of a signed 32-bit integer */
+}
+
+@media (-moz-overlay-scrollbars) {
+ scrollbar {
+ opacity: 1;
+ will-change: opacity;
+ transition-property: opacity;
+ transition-duration: env(-moz-overlay-scrollbar-fade-duration);
+ }
+ scrollbar:not([active]),
+ scrollbar[disabled] {
+ pointer-events: none;
+ opacity: 0;
+ }
+ scrollcorner {
+ pointer-events: none;
+ }
+}
+
+slider {
+ appearance: auto;
+ -moz-default-appearance: scrollbartrack-horizontal;
+}
+
+slider[orient="vertical"] {
+ -moz-default-appearance: scrollbartrack-vertical;
+}
+
+scrollbarbutton {
+ appearance: auto;
+ -moz-default-appearance: scrollbarbutton-right;
+}
+
+scrollbar[orient="vertical"] > scrollbarbutton {
+ -moz-default-appearance: scrollbarbutton-down;
+}
+
+scrollbarbutton[type="decrement"] {
+ -moz-default-appearance: scrollbarbutton-left;
+}
+
+scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"] {
+ -moz-default-appearance: scrollbarbutton-up;
+}
+
+scrollcorner {
+ appearance: auto;
+ -moz-default-appearance: scrollcorner;
+ width: 16px;
+ cursor: default;
+}
+
+@media (-moz-scrollbar-start-backward: 0) {
+ scrollbarbutton[sbattr="scrollbar-up-top"] {
+ display: none;
+ }
+}
+
+@media (-moz-scrollbar-start-forward: 0) {
+ scrollbarbutton[sbattr="scrollbar-down-top"] {
+ display: none;
+ }
+}
+
+@media (-moz-scrollbar-end-backward: 0) {
+ scrollbarbutton[sbattr="scrollbar-up-bottom"] {
+ display: none;
+ }
+}
+
+@media (-moz-scrollbar-end-forward: 0) {
+ scrollbarbutton[sbattr="scrollbar-down-bottom"] {
+ display: none;
+ }
+}
diff --git a/layout/style/res/searchfield-cancel.svg b/layout/style/res/searchfield-cancel.svg
new file mode 100644
index 0000000000..2cff2f08bb
--- /dev/null
+++ b/layout/style/res/searchfield-cancel.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <style>
+ circle {
+ fill: #808080;
+ }
+
+ line {
+ stroke: #fff;
+ stroke-width: 1.5px;
+ }
+ </style>
+
+ <circle cx="7" cy="7" r="7" />
+ <line x1="4" y1="4" x2="10" y2="10" />
+ <line x1="10" y1="4" x2="4" y2="10" />
+</svg> \ No newline at end of file
diff --git a/layout/style/res/ua.css b/layout/style/res/ua.css
new file mode 100644
index 0000000000..b0bc905d96
--- /dev/null
+++ b/layout/style/res/ua.css
@@ -0,0 +1,455 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace parsererror url(http://www.mozilla.org/newlayout/xml/parsererror.xml);
+@namespace html url(http://www.w3.org/1999/xhtml);
+@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);
+
+/* magic -- some of these rules are important to keep pages from overriding
+ them
+*/
+
+/* Tables */
+
+*|*::-moz-table {
+ display: table;
+ box-sizing: border-box; /* XXX do we really want this? */
+}
+
+*|*::-moz-inline-table {
+ display: inline-table;
+ box-sizing: border-box; /* XXX do we really want this? */
+}
+
+*|*::-moz-table-wrapper {
+ /* The inherited properties here need to be safe to have on both the
+ * table and the table wrapper, generally because code ignores them
+ * for the table. */
+ display: inherit; /* table or inline-table */
+ -moz-top-layer: inherit;
+ margin: inherit;
+ float: inherit;
+ clear: inherit;
+ position: inherit;
+ top: inherit;
+ right: inherit;
+ bottom: inherit;
+ left: inherit;
+ z-index: inherit;
+ page-break-before: inherit;
+ page-break-after: inherit;
+ page-break-inside: inherit;
+ vertical-align: inherit; /* needed for inline-table */
+ line-height: inherit; /* needed for vertical-align on inline-table */
+ /* Bug 722777 */
+ transform: inherit;
+ transform-origin: inherit;
+ /* Bug 724750 */
+ backface-visibility: inherit;
+ clip: inherit;
+ /* Other transform-related properties */
+ /* transform-style: inherit; Bug 1560704 */
+ rotate: inherit;
+ scale: inherit;
+ translate: inherit;
+ /* When the table wrapper is a Flex/Grid item we need these: */
+ align-self: inherit;
+ justify-self: inherit;
+ grid-column-start: inherit;
+ grid-column-end: inherit;
+ grid-row-start: inherit;
+ grid-row-end: inherit;
+ order: inherit;
+ outline: inherit;
+ outline-offset: inherit;
+ column-span: inherit; /* needed if <table> has "column-span:all" */
+ contain: inherit; /* needed if table has 'contain:layout' or 'paint' */
+ container: inherit; /* Bug 1805588 */
+ scroll-margin: inherit; /* Bug 1633192 */
+}
+
+*|*::-moz-table-row {
+ display: table-row;
+}
+
+/* The ::-moz-table-column pseudo-element is for extra columns at the end
+ of a table. */
+*|*::-moz-table-column {
+ display: table-column;
+ /* Make sure anonymous columns don't interfere with hit testing. Basically,
+ * they should pretend as much as possible to not exist (since in the spec
+ * they do not exist).
+ *
+ * Please make sure to not reintroduce
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1403293 if you change this
+ * bit!
+ */
+ visibility: hidden;
+}
+
+*|*::-moz-table-column-group {
+ display: table-column-group;
+ /* Make sure anonymous colgroups don't interfere with hit testing. Basically,
+ * they should pretend as much as possible to not exist (since in the spec
+ * they do not exist).
+ *
+ * Please make sure to not reintroduce
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1403293 if you change this
+ * bit!
+ */
+ visibility: hidden;
+}
+
+*|*::-moz-table-row-group {
+ display: table-row-group;
+}
+
+*|*::-moz-table-cell {
+ display: table-cell;
+ white-space: inherit;
+}
+
+/* Ruby */
+*|*::-moz-ruby {
+ display: ruby;
+ unicode-bidi: isolate;
+}
+*|*::-moz-ruby-base {
+ display: ruby-base;
+ unicode-bidi: isolate;
+}
+*|*::-moz-ruby-text {
+ display: ruby-text;
+ unicode-bidi: isolate;
+}
+*|*::-moz-ruby-base-container {
+ display: ruby-base-container;
+ unicode-bidi: isolate;
+}
+*|*::-moz-ruby-text-container {
+ display: ruby-text-container;
+ unicode-bidi: isolate;
+}
+
+/* https://drafts.csswg.org/css-lists-3/#ua-stylesheet */
+::marker {
+ text-align: end;
+ text-transform: none;
+ unicode-bidi: isolate;
+ font-variant-numeric: tabular-nums;
+ white-space: pre;
+}
+
+/* SVG documents don't always load this file but they do have links.
+ * If you change the link rules, consider carefully whether to make
+ * the same changes to svg.css.
+ */
+
+/* Links and focusable content */
+
+:any-link {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+:link {
+ color: LinkText;
+}
+
+:any-link:active {
+ color: ActiveText;
+}
+
+:visited {
+ color: VisitedText;
+}
+
+/* stylelint-disable-next-line media-query-no-invalid */
+@media (-moz-bool-pref: "layout.css.always_underline_links") {
+ :any-link {
+ text-decoration: underline !important;
+ }
+}
+
+:focus-visible {
+ outline: 1px auto;
+}
+
+/* Inert subtrees */
+:-moz-inert {
+ -moz-inert: inert;
+}
+
+/* Miscellaneous */
+
+*|*::-moz-cell-content {
+ display: block;
+ unicode-bidi: inherit;
+ text-overflow: inherit;
+ overflow: inherit;
+ overflow-clip-box: inherit;
+ resize: inherit;
+ padding: inherit;
+ box-decoration-break: inherit;
+}
+
+*|*::-moz-block-inside-inline-wrapper {
+ display: block;
+ /* we currently inherit from the inline that is split */
+ position: inherit; /* static or relative or sticky */
+ outline: inherit;
+ outline-offset: inherit;
+ clip-path: inherit;
+ filter: inherit;
+ mask: inherit;
+ opacity: inherit;
+ text-decoration: inherit;
+ overflow-clip-box: inherit;
+ unicode-bidi: inherit;
+ user-select: inherit;
+ text-overflow: inherit;
+ /* The properties below here don't apply if our position is static,
+ and we do want them to have an effect if it's not, so it's fine
+ to always inherit them. */
+ top: inherit;
+ left: inherit;
+ bottom: inherit;
+ right: inherit;
+ z-index: inherit;
+}
+
+*|*::-moz-scrolled-content,
+*|*::-moz-scrolled-canvas {
+ /* e.g., text inputs, select boxes */
+ padding: inherit;
+ /* The display doesn't affect the kind of frame constructed here. This just
+ affects auto-width sizing of the block we create. */
+ display: block;
+ /* make unicode-bidi inherit, otherwise it has no effect on text inputs and
+ blocks with overflow: scroll; */
+ unicode-bidi: inherit;
+ text-overflow: inherit;
+ /* Please keep the declarations below in sync with ::-moz-fieldset-content /
+ ::-moz-button-content in forms.css */
+ content: inherit;
+ /* Multicol container */
+ column-count: inherit;
+ column-width: inherit;
+ column-gap: inherit;
+ column-rule: inherit;
+ column-fill: inherit;
+ /* Flex container */
+ flex-direction: inherit;
+ flex-wrap: inherit;
+ /* -webkit-box container (aliased from -webkit versions to -moz versions) */
+ -moz-box-orient: inherit;
+ -moz-box-direction: inherit;
+ -moz-box-pack: inherit;
+ -moz-box-align: inherit;
+ -webkit-line-clamp: inherit;
+ /* Grid container */
+ grid-auto-columns: inherit;
+ grid-auto-rows: inherit;
+ grid-auto-flow: inherit;
+ grid-column-gap: inherit;
+ grid-row-gap: inherit;
+ grid-template-areas: inherit;
+ grid-template-columns: inherit;
+ grid-template-rows: inherit;
+ /* CSS Align */
+ align-content: inherit;
+ align-items: inherit;
+ justify-content: inherit;
+ justify-items: inherit;
+ /* Do not change these. nsCSSFrameConstructor depends on them to create a good
+ frame tree. */
+ overflow-clip-box: inherit;
+}
+
+*|*::-moz-viewport, *|*::-moz-viewport-scroll, *|*::-moz-canvas, *|*::-moz-scrolled-canvas {
+ display: block;
+ background-color: inherit;
+}
+
+*|*::-moz-viewport-scroll {
+ overflow: auto;
+}
+
+*|*::-moz-column-set,
+*|*::-moz-column-content {
+ /* the column boxes inside a column-flowed block */
+ /* make unicode-bidi inherit, otherwise it has no effect on column boxes */
+ unicode-bidi: inherit;
+ text-overflow: inherit;
+ /* Both -moz-column-set and -moz-column-content need to be blocks. */
+ display: block;
+}
+
+*|*::-moz-column-set {
+ /* Inherit from ColumnSetWrapperFrame so that nsColumnSetFrame is aware of
+ them.*/
+ columns: inherit;
+ column-gap: inherit;
+ column-rule: inherit;
+ column-fill: inherit;
+}
+
+*|*::-moz-column-span-wrapper {
+ /* As a result of the discussion in
+ * https://github.com/w3c/csswg-drafts/issues/1072, most of the styles
+ * currently applied to ::-moz-block-inside-inline-wrapper should not
+ * apply here. */
+ display: block;
+ column-span: all;
+}
+
+*|*::-moz-anonymous-item {
+ /* Anonymous blocks that wrap contiguous runs of text
+ * inside of a flex / grid / -moz-box container. */
+ display: block;
+}
+
+*|*::-moz-page-sequence {
+ /* Collection of pages in print/print preview. Visual styles may only appear
+ * in print preview. */
+ display: block;
+ background: #606060 linear-gradient(#606060, #8a8a8a) fixed;
+ print-color-adjust: exact;
+ /* We always fill the available space in both directions */
+ height: 100%;
+ width: 100%;
+}
+
+*|*::-moz-printed-sheet {
+ /* Individual sheet of paper in print/print preview. Visual styles may only
+ * appear in print preview. */
+ display: block;
+ background: white;
+ print-color-adjust: exact;
+ box-shadow: 5px 5px 8px #202020;
+ box-decoration-break: clone;
+ /* TODO: Remove margin if viewport is small or something? */
+ margin: 0.125in 0.25in;
+}
+
+@media (monochrome) and (-moz-print-preview) {
+ *|*::-moz-page {
+ filter: grayscale(1);
+ }
+}
+
+*|*::-moz-page-content {
+ display: block;
+ margin: auto;
+}
+
+*|*::-moz-page-break {
+ display: block;
+}
+
+/* Printing */
+
+@media print {
+ * {
+ cursor: default !important;
+ }
+}
+
+:fullscreen:not(:root) {
+ -moz-top-layer: top !important;
+ position: fixed !important;
+ inset: 0 !important;
+ width: 100% !important;
+ height: 100% !important;
+ margin: 0 !important;
+ min-width: 0 !important;
+ max-width: none !important;
+ min-height: 0 !important;
+ max-height: none !important;
+ box-sizing: border-box !important;
+ object-fit: contain;
+ transform: none !important;
+}
+
+/* This pseudo-class is used to remove the inertness for the topmost modal
+ * element in top layer.
+ *
+ * Avoid doing this if the element is explicitly inert though. */
+:-moz-topmost-modal:not(html|*[inert]) {
+ -moz-inert: none;
+ /* Topmost modal elements need to be selectable even though ancestors are
+ * inert, but allow users to override this if they want to. */
+ user-select: text;
+}
+
+/**
+ * Ensure we recompute the default color for the root based on its
+ * computed color-scheme. This matches other browsers.
+ *
+ * For the default background, we look at the root
+ * element style frame in
+ * PresShell::GetDefaultBackgroundColorToDraw, however we
+ * can't make the initial style (the style the root element
+ * inherits from) depend on the root element's style, as that
+ * is trivially cyclic.
+ */
+:root {
+ color: CanvasText;
+}
+
+::backdrop {
+ -moz-top-layer: top !important;
+ display: block;
+ position: fixed;
+ inset: 0;
+ /* This prevents undesired interactions with the selection code. */
+ user-select: none;
+}
+
+:fullscreen:not(:root)::backdrop {
+ background: black;
+}
+
+/* XML parse error reporting */
+
+parsererror|parsererror {
+ display: block;
+ font-family: sans-serif;
+ font-weight: bold;
+ white-space: pre;
+ margin: 1em;
+ padding: 1em;
+ border-width: thin;
+ border-style: inset;
+ border-color: red;
+ font-size: 14pt;
+ background-color: lightyellow;
+ color: black;
+}
+
+parsererror|sourcetext {
+ display: block;
+ white-space: pre;
+ font-family: -moz-fixed;
+ margin-top: 2em;
+ margin-bottom: 1em;
+ color: red;
+ font-weight: bold;
+ font-size: 12pt;
+}
+
+/* Custom content container in the CanvasFrame, positioned on top of everything
+ everything else, not reacting to pointer events. */
+.moz-custom-content-container:-moz-native-anonymous {
+ pointer-events: none;
+ user-select: none;
+ -moz-top-layer: top;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ /* Initial direction depends on the document, make sure to reset it */
+ direction: ltr;
+}
diff --git a/layout/style/res/viewsource.css b/layout/style/res/viewsource.css
new file mode 100644
index 0000000000..6bcf73faa6
--- /dev/null
+++ b/layout/style/res/viewsource.css
@@ -0,0 +1,116 @@
+@charset "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/. */
+
+@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
+
+*|*:root {
+ color-scheme: light dark;
+ direction: ltr;
+ -moz-control-character-visibility: visible;
+ height: 100%;
+}
+#viewsource {
+ font-family: -moz-fixed;
+ font-weight: normal;
+ white-space: pre;
+ counter-reset: line;
+ height: 100%;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 8px;
+}
+#viewsource.wrap {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+pre {
+ font: inherit;
+ color: inherit;
+ white-space: inherit;
+ margin: 0 0 0 5ch;
+}
+pre[id]:before,
+span[id]:before {
+ content: counter(line) " ";
+ counter-increment: line;
+ user-select: none;
+ display: inline-block;
+ width: 5ch;
+ margin: 0 0 0 -5ch;
+ text-align: right;
+ color: #ccc;
+ font-weight: normal;
+ font-style: normal;
+}
+.highlight .start-tag,
+.highlight .end-tag {
+ color: purple;
+ font-weight: bold;
+}
+.highlight .comment {
+ color: green;
+ font-style: italic;
+}
+.highlight .cdata {
+ color: #CC0066;
+}
+.highlight .doctype,
+.highlight .markupdeclaration {
+ color: steelblue;
+ font-style: italic;
+}
+.highlight .pi {
+ color: orchid;
+ font-style: italic;
+}
+.highlight .entity {
+ color: #FF4500;
+ font-weight: normal;
+}
+.highlight .text {
+ font-weight: normal;
+}
+.highlight .attribute-name {
+ font-weight: bold;
+}
+.highlight .attribute-value {
+ color: blue;
+ font-weight: normal;
+}
+span:not(.error),
+a:not(.error) {
+ unicode-bidi: embed;
+}
+span[id] {
+ unicode-bidi: isolate;
+}
+.highlight .error {
+ color: revert;
+ font-weight: bold;
+ background-color: rgba(231, 116, 113, 0.3);
+ text-decoration: underline wavy red 0.5px;
+}
+@media (prefers-color-scheme: dark) {
+ .highlight .start-tag,
+ .highlight .end-tag {
+ color: #f55e5e;
+ }
+ .highlight .comment {
+ color: lightgreen;
+ }
+ .highlight .cdata {
+ color: #f068ac;
+ }
+ .highlight .doctype,
+ .highlight .markupdeclaration {
+ color: lightgray;
+ }
+ .highlight .entity {
+ color: #f18a65;
+ }
+ .highlight .attribute-value {
+ color: #97bbff;
+ }
+}
diff --git a/layout/style/test/Ahem.ttf b/layout/style/test/Ahem.ttf
new file mode 100644
index 0000000000..ac81cb0316
--- /dev/null
+++ b/layout/style/test/Ahem.ttf
Binary files differ
diff --git a/layout/style/test/BitPattern.woff b/layout/style/test/BitPattern.woff
new file mode 100644
index 0000000000..e4e8244057
--- /dev/null
+++ b/layout/style/test/BitPattern.woff
Binary files differ
diff --git a/layout/style/test/ListCSSProperties.cpp b/layout/style/test/ListCSSProperties.cpp
new file mode 100644
index 0000000000..13ac6ae93d
--- /dev/null
+++ b/layout/style/test/ListCSSProperties.cpp
@@ -0,0 +1,178 @@
+/* vim: set shiftwidth=4 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/. */
+
+/* build (from code) lists of all supported CSS properties */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "mozilla/ArrayUtils.h"
+
+// Do not consider properties not valid in style rules
+#define CSS_PROP_LIST_EXCLUDE_NOT_IN_STYLE
+
+// Need an extra level of macro nesting to force expansion of method_
+// params before they get pasted.
+#define STRINGIFY_METHOD(method_) #method_
+
+struct PropertyInfo {
+ const char* propName;
+ const char* domName;
+ const char* pref;
+};
+
+const PropertyInfo gLonghandProperties[] = {
+
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_
+#define CSS_PROP_LONGHAND(name_, id_, method_, flags_, pref_, ...) \
+ {#name_, STRINGIFY_METHOD(method_), pref_},
+
+#include "mozilla/ServoCSSPropList.h"
+
+#undef CSS_PROP_LONGHAND
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+
+};
+
+/*
+ * These are the properties for which domName in the above list should
+ * be used. They're in the same order as the above list, with some
+ * items skipped.
+ */
+const char* gLonghandPropertiesWithDOMProp[] = {
+
+#define CSS_PROP_LIST_EXCLUDE_INTERNAL
+#define CSS_PROP_LONGHAND(name_, ...) #name_,
+
+#include "mozilla/ServoCSSPropList.h"
+
+#undef CSS_PROP_LONGHAND
+#undef CSS_PROP_LIST_EXCLUDE_INTERNAL
+
+};
+
+const PropertyInfo gShorthandProperties[] = {
+
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+ {#name_, STRINGIFY_METHOD(method_), pref_},
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, flags_, pref_) \
+ {#name_, #method_, pref_},
+
+#include "mozilla/ServoCSSPropList.h"
+
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+
+};
+
+/* see gLonghandPropertiesWithDOMProp */
+const char* gShorthandPropertiesWithDOMProp[] = {
+
+#define CSS_PROP_LIST_EXCLUDE_INTERNAL
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) #name_,
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, flags_, pref_) #name_,
+
+#include "mozilla/ServoCSSPropList.h"
+
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LIST_EXCLUDE_INTERNAL
+
+};
+
+#undef STRINGIFY_METHOD
+
+const char* gInaccessibleProperties[] = {
+ // Don't print the properties that aren't accepted by the parser, per
+ // CSSParserImpl::ParseProperty
+ "-x-cols",
+ "-x-lang",
+ "-x-span",
+ "-x-text-scale",
+ "-moz-default-appearance",
+ "-moz-theme",
+ "-moz-inert",
+ "-moz-script-level", // parsed by UA sheets only
+ "-moz-math-variant",
+ "-moz-math-display", // parsed by UA sheets only
+ "-moz-top-layer", // parsed by UA sheets only
+ "-moz-min-font-size-ratio", // parsed by UA sheets only
+ "-moz-box-collapse", // chrome-only internal properties
+ "-moz-subtree-hidden-only-visually", // chrome-only internal properties
+ "-moz-user-focus", // chrome-only internal properties
+ "-moz-window-input-region-margin", // chrome-only internal properties
+ "-moz-window-opacity", // chrome-only internal properties
+ "-moz-window-transform", // chrome-only internal properties
+ "-moz-window-transform-origin", // chrome-only internal properties
+ "-moz-window-shadow", // chrome-only internal properties
+};
+
+inline int is_inaccessible(const char* aPropName) {
+ for (unsigned j = 0; j < MOZ_ARRAY_LENGTH(gInaccessibleProperties); ++j) {
+ if (strcmp(aPropName, gInaccessibleProperties[j]) == 0) return 1;
+ }
+ return 0;
+}
+
+void print_array(const char* aName, const PropertyInfo* aProps,
+ unsigned aPropsLength, const char* const* aDOMProps,
+ unsigned aDOMPropsLength) {
+ printf("var %s = [\n", aName);
+
+ int first = 1;
+ unsigned j = 0; // index into DOM prop list
+ for (unsigned i = 0; i < aPropsLength; ++i) {
+ const PropertyInfo* p = aProps + i;
+
+ if (is_inaccessible(p->propName))
+ // inaccessible properties never have DOM props, so don't
+ // worry about incrementing j. The assertion below will
+ // catch if they do.
+ continue;
+
+ if (first)
+ first = 0;
+ else
+ printf(",\n");
+
+ printf("\t{ name: \"%s\", prop: ", p->propName);
+ if (j >= aDOMPropsLength || strcmp(p->propName, aDOMProps[j]) != 0)
+ printf("null");
+ else {
+ ++j;
+ if (strncmp(p->domName, "Moz", 3) == 0)
+ printf("\"%s\"", p->domName);
+ else
+ // lowercase the first letter
+ printf("\"%c%s\"", p->domName[0] + 32, p->domName + 1);
+ }
+ if (p->pref[0]) {
+ printf(", pref: \"%s\"", p->pref);
+ }
+ printf(" }");
+ }
+
+ if (j != aDOMPropsLength) {
+ fprintf(stderr, "Assertion failure %s:%d\n", __FILE__, __LINE__);
+ fprintf(stderr, "j==%d, aDOMPropsLength == %d\n", j, aDOMPropsLength);
+ exit(1);
+ }
+
+ printf("\n];\n\n");
+}
+
+int main() {
+ print_array("gLonghandProperties", gLonghandProperties,
+ MOZ_ARRAY_LENGTH(gLonghandProperties),
+ gLonghandPropertiesWithDOMProp,
+ MOZ_ARRAY_LENGTH(gLonghandPropertiesWithDOMProp));
+ print_array("gShorthandProperties", gShorthandProperties,
+ MOZ_ARRAY_LENGTH(gShorthandProperties),
+ gShorthandPropertiesWithDOMProp,
+ MOZ_ARRAY_LENGTH(gShorthandPropertiesWithDOMProp));
+ return 0;
+}
diff --git a/layout/style/test/ParseCSS.cpp b/layout/style/test/ParseCSS.cpp
new file mode 100644
index 0000000000..04e37d48e2
--- /dev/null
+++ b/layout/style/test/ParseCSS.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=8:et:sw=4:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file is meant to be used with |#define CSS_REPORT_PARSE_ERRORS|
+ * in mozilla/dom/html/style/src/nsCSSScanner.h uncommented, and the
+ * |#ifdef DEBUG| block in nsCSSScanner::OutputError (in
+ * nsCSSScanner.cpp in the same directory) used (even if not a debug
+ * build).
+ */
+
+#include "nsXPCOM.h"
+#include "nsCOMPtr.h"
+
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+
+#include "nsContentCID.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+
+using namespace mozilla;
+
+static already_AddRefed<nsIURI> FileToURI(const char* aFilename,
+ nsresult* aRv = 0) {
+ nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, aRv));
+ NS_ENSURE_TRUE(lf, nullptr);
+ // XXX Handle relative paths somehow.
+ lf->InitWithNativePath(nsDependentCString(aFilename));
+
+ nsIURI* uri = nullptr;
+ nsresult rv = NS_NewFileURI(&uri, lf);
+ if (aRv) *aRv = rv;
+ return uri;
+}
+
+static int ParseCSSFile(nsIURI* aSheetURI) {
+ RefPtr<mozilla::css::Loader> = new mozilla::css::Loader();
+ RefPtr<CSSStyleSheet> sheet;
+ loader->LoadSheetSync(aSheetURI, getter_AddRefs(sheet));
+ NS_ASSERTION(sheet, "sheet load failed");
+ /* This can happen if the file can't be found (e.g. you
+ * ask for a relative path and xpcom/io rejects it)
+ */
+ if (!sheet) return -1;
+ bool complete;
+ sheet->GetComplete(complete);
+ NS_ASSERTION(complete, "synchronous load did not complete");
+ if (!complete) return -2;
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ if (argc < 2) {
+ fprintf(stderr, "%s [FILE]...\n", argv[0]);
+ }
+ nsresult rv = NS_InitXPCOM(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) return (int)rv;
+
+ int res = 0;
+ for (int i = 1; i < argc; ++i) {
+ const char* filename = argv[i];
+
+ printf("\nParsing %s.\n", filename);
+
+ nsCOMPtr<nsIURI> uri = FileToURI(filename, &rv);
+ if (rv == NS_ERROR_OUT_OF_MEMORY) {
+ fprintf(stderr, "Out of memory.\n");
+ return 1;
+ }
+ if (uri) res = ParseCSSFile(uri);
+ }
+
+ NS_ShutdownXPCOM(nullptr);
+
+ return res;
+}
diff --git a/layout/style/test/additional_sheets_helper.html b/layout/style/test/additional_sheets_helper.html
new file mode 100644
index 0000000000..306ddbf5bd
--- /dev/null
+++ b/layout/style/test/additional_sheets_helper.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+ some text
+</body>
+</html>
diff --git a/layout/style/test/animation_utils.js b/layout/style/test/animation_utils.js
new file mode 100644
index 0000000000..6f7ededcd4
--- /dev/null
+++ b/layout/style/test/animation_utils.js
@@ -0,0 +1,935 @@
+//----------------------------------------------------------------------
+//
+// Common testing functions
+//
+//----------------------------------------------------------------------
+
+function advance_clock(milliseconds) {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
+}
+
+// Test-element creation/destruction and event checking
+(function () {
+ var gElem;
+ var gEventsReceived = [];
+
+ function new_div(style) {
+ return new_element("div", style);
+ }
+
+ // Creates a new |tagname| element with inline style |style| and appends
+ // it as a child of the element with ID 'display'.
+ // The element will also be given the class 'target' which can be used
+ // for additional styling.
+ function new_element(tagname, style) {
+ if (gElem) {
+ ok(false, "test author forgot to call done_div/done_elem");
+ }
+ if (typeof style != "string") {
+ ok(false, "test author forgot to pass argument");
+ }
+ if (!document.getElementById("display")) {
+ ok(false, "no 'display' element to append to");
+ }
+ gElem = document.createElement(tagname);
+ gElem.setAttribute("style", style);
+ gElem.classList.add("target");
+ document.getElementById("display").appendChild(gElem);
+ return [gElem, getComputedStyle(gElem, "")];
+ }
+
+ function listen() {
+ if (!gElem) {
+ ok(false, "test author forgot to call new_div before listen");
+ }
+ gEventsReceived = [];
+ function listener(event) {
+ gEventsReceived.push(event);
+ }
+ gElem.addEventListener("animationstart", listener);
+ gElem.addEventListener("animationiteration", listener);
+ gElem.addEventListener("animationend", listener);
+ }
+
+ function check_events(eventsExpected, desc) {
+ // This function checks that the list of eventsExpected matches
+ // the received events -- but it only checks the properties that
+ // are present on eventsExpected.
+ is(
+ gEventsReceived.length,
+ eventsExpected.length,
+ "number of events received for " + desc
+ );
+ for (
+ var i = 0,
+ i_end = Math.min(eventsExpected.length, gEventsReceived.length);
+ i != i_end;
+ ++i
+ ) {
+ var exp = eventsExpected[i];
+ var rec = gEventsReceived[i];
+ for (var prop in exp) {
+ if (prop == "elapsedTime") {
+ // Allow floating point error.
+ ok(
+ Math.abs(rec.elapsedTime - exp.elapsedTime) < 0.000002,
+ "events[" +
+ i +
+ "]." +
+ prop +
+ " for " +
+ desc +
+ " received=" +
+ rec.elapsedTime +
+ " expected=" +
+ exp.elapsedTime
+ );
+ } else {
+ is(
+ rec[prop],
+ exp[prop],
+ "events[" + i + "]." + prop + " for " + desc
+ );
+ }
+ }
+ }
+ for (var i = eventsExpected.length; i < gEventsReceived.length; ++i) {
+ ok(false, "unexpected " + gEventsReceived[i].type + " event for " + desc);
+ }
+ gEventsReceived = [];
+ }
+
+ function done_element() {
+ if (!gElem) {
+ ok(
+ false,
+ "test author called done_element/done_div without matching" +
+ " call to new_element/new_div"
+ );
+ }
+ gElem.remove();
+ gElem = null;
+ if (gEventsReceived.length) {
+ ok(false, "caller should have called check_events");
+ }
+ }
+
+ [new_div, new_element, listen, check_events, done_element].forEach(function (
+ fn
+ ) {
+ window[fn.name] = fn;
+ });
+ window.done_div = done_element;
+})();
+
+function px_to_num(str) {
+ return Number(String(str).match(/^([\d.]+)px$/)[1]);
+}
+
+function bezier(x1, y1, x2, y2) {
+ // Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1).
+ function x_for_t(t) {
+ var omt = 1 - t;
+ return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t;
+ }
+ function y_for_t(t) {
+ var omt = 1 - t;
+ return 3 * omt * omt * t * y1 + 3 * omt * t * t * y2 + t * t * t;
+ }
+ function t_for_x(x) {
+ // Binary subdivision.
+ var mint = 0,
+ maxt = 1;
+ for (var i = 0; i < 30; ++i) {
+ var guesst = (mint + maxt) / 2;
+ var guessx = x_for_t(guesst);
+ if (x < guessx) {
+ maxt = guesst;
+ } else {
+ mint = guesst;
+ }
+ }
+ return (mint + maxt) / 2;
+ }
+ return function bezier_closure(x) {
+ if (x == 0) {
+ return 0;
+ }
+ if (x == 1) {
+ return 1;
+ }
+ return y_for_t(t_for_x(x));
+ };
+}
+
+function step_end(nsteps) {
+ return function step_end_closure(x) {
+ return Math.floor(x * nsteps) / nsteps;
+ };
+}
+
+function step_start(nsteps) {
+ var stepend = step_end(nsteps);
+ return function step_start_closure(x) {
+ return 1.0 - stepend(1.0 - x);
+ };
+}
+
+var gTF = {
+ ease: bezier(0.25, 0.1, 0.25, 1),
+ linear: function (x) {
+ return x;
+ },
+ ease_in: bezier(0.42, 0, 1, 1),
+ ease_out: bezier(0, 0, 0.58, 1),
+ ease_in_out: bezier(0.42, 0, 0.58, 1),
+ step_start: step_start(1),
+ step_end: step_end(1),
+};
+
+function is_approx(float1, float2, error, desc) {
+ ok(
+ Math.abs(float1 - float2) < error,
+ desc + ": " + float1 + " and " + float2 + " should be within " + error
+ );
+}
+
+function findKeyframesRule(name) {
+ for (var i = 0; i < document.styleSheets.length; i++) {
+ var match = [].find.call(document.styleSheets[i].cssRules, function (rule) {
+ return rule.type == CSSRule.KEYFRAMES_RULE && rule.name == name;
+ });
+ if (match) {
+ return match;
+ }
+ }
+ return undefined;
+}
+
+// Checks if off-main thread animation (OMTA) is available, and if it is, runs
+// the provided callback function. If OMTA is not available or is not
+// functioning correctly, the second callback, aOnSkip, is run instead.
+//
+// This function also does an internal test to verify that OMTA is working at
+// all so that if OMTA is not functioning correctly when it is expected to
+// function only a single failure is produced.
+//
+// Since this function relies on various asynchronous operations, the caller is
+// responsible for calling SimpleTest.waitForExplicitFinish() before calling
+// this and SimpleTest.finish() within aTestFunction and aOnSkip.
+//
+// specialPowersForPrefs exists because some SpecialPowers objects apparently
+// can get prefs and some can't; callers that would normally have one of the
+// latter but can get their hands on one of the former can pass it in
+// explicitly.
+function runOMTATest(aTestFunction, aOnSkip, specialPowersForPrefs) {
+ const OMTAPrefKey = "layers.offmainthreadcomposition.async-animations";
+ var utils = SpecialPowers.DOMWindowUtils;
+ if (!specialPowersForPrefs) {
+ specialPowersForPrefs = SpecialPowers;
+ }
+ var expectOMTA =
+ utils.layerManagerRemote &&
+ // ^ Off-main thread animation cannot be used if off-main
+ // thread composition (OMTC) is not available
+ specialPowersForPrefs.getBoolPref(OMTAPrefKey);
+
+ isOMTAWorking()
+ .then(function (isWorking) {
+ if (expectOMTA) {
+ if (isWorking) {
+ aTestFunction();
+ } else {
+ // We only call this when we know it will fail as otherwise in the
+ // regular success case we will end up inflating the "passed tests"
+ // count by 1
+ ok(isWorking, "OMTA should work");
+ aOnSkip();
+ }
+ } else {
+ todo(
+ isWorking,
+ "OMTA should ideally work, though we don't expect it to work on " +
+ "this platform/configuration"
+ );
+ aOnSkip();
+ }
+ })
+ .catch(function (err) {
+ ok(false, err);
+ aOnSkip();
+ });
+
+ function isOMTAWorking() {
+ // Create keyframes rule
+ const animationName = "a6ce3091ed85"; // Random name to avoid clashes
+ var ruleText =
+ "@keyframes " +
+ animationName +
+ " { from { opacity: 0.5 } to { opacity: 0.5 } }";
+ var style = document.createElement("style");
+ style.appendChild(document.createTextNode(ruleText));
+ document.head.appendChild(style);
+
+ // Create animation target
+ var div = document.createElement("div");
+ document.body.appendChild(div);
+
+ // Give the target geometry so it is eligible for layerization
+ div.style.width = "100px";
+ div.style.height = "100px";
+ div.style.backgroundColor = "white";
+
+ // Common clean up code
+ var cleanUp = function () {
+ div.remove();
+ style.remove();
+ if (utils.isTestControllingRefreshes) {
+ utils.restoreNormalRefresh();
+ }
+ };
+
+ return waitForDocumentLoad()
+ .then(loadPaintListener)
+ .then(function () {
+ // Put refresh driver under test control and flush all pending style,
+ // layout and paint to avoid the situation that waitForPaintsFlush()
+ // receives unexpected MozAfterpaint event for those pending
+ // notifications.
+ utils.advanceTimeAndRefresh(0);
+ return waitForPaintsFlushed();
+ })
+ .then(function () {
+ div.style.animation = animationName + " 10s";
+
+ return waitForPaintsFlushed();
+ })
+ .then(function () {
+ var opacity = utils.getOMTAStyle(div, "opacity");
+ cleanUp();
+ return Promise.resolve(opacity == 0.5);
+ })
+ .catch(function (err) {
+ cleanUp();
+ return Promise.reject(err);
+ });
+ }
+
+ function waitForDocumentLoad() {
+ return new Promise(function (resolve, reject) {
+ if (document.readyState === "complete") {
+ resolve();
+ } else {
+ window.addEventListener("load", resolve);
+ }
+ });
+ }
+
+ function loadPaintListener() {
+ return new Promise(function (resolve, reject) {
+ if (typeof window.waitForAllPaints !== "function") {
+ var script = document.createElement("script");
+ script.onload = resolve;
+ script.onerror = function () {
+ reject(new Error("Failed to load paint listener"));
+ };
+ script.src = "/tests/SimpleTest/paint_listener.js";
+ var firstScript = document.scripts[0];
+ firstScript.parentNode.insertBefore(script, firstScript);
+ } else {
+ resolve();
+ }
+ });
+ }
+}
+
+// Common architecture for setting up a series of asynchronous animation tests
+//
+// Usage example:
+//
+// addAsyncAnimTest(function *() {
+// .. do work ..
+// yield functionThatReturnsAPromise();
+// .. do work ..
+// });
+// runAllAsyncAnimTests().then(SimpleTest.finish());
+//
+(function () {
+ var tests = [];
+
+ window.addAsyncAnimTest = function (generator) {
+ tests.push(generator);
+ };
+
+ // Returns a promise when all tests have run
+ window.runAllAsyncAnimTests = function (aOnAbort) {
+ // runAsyncAnimTest returns a Promise that is resolved when the
+ // test is finished so we can chain them together
+ return tests.reduce(function (sequence, test) {
+ return sequence.then(function () {
+ return runAsyncAnimTest(test, aOnAbort);
+ });
+ }, Promise.resolve() /* the start of the sequence */);
+ };
+
+ // Takes a generator function that represents a test case. Each point in the
+ // test case that waits asynchronously for some result yields a Promise that
+ // is resolved when the asynchronous action has completed. By chaining these
+ // intermediate results together we run the test to completion.
+ //
+ // This method itself returns a Promise that is resolved when the generator
+ // function has completed.
+ //
+ // This arrangement is based on add_task() which is currently only available
+ // in mochitest-chrome (bug 872229). If add_task becomes available in
+ // mochitest-plain, we can remove this function and use add_task instead.
+ function runAsyncAnimTest(aTestFunc, aOnAbort) {
+ var generator;
+
+ function step(arg) {
+ var next;
+ try {
+ next = generator.next(arg);
+ } catch (e) {
+ return Promise.reject(e);
+ }
+ if (next.done) {
+ return Promise.resolve(next.value);
+ }
+ return Promise.resolve(next.value).then(step, function (err) {
+ throw err;
+ });
+ }
+
+ // Put refresh driver under test control
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
+
+ // Run test
+ var promise = aTestFunc();
+ if (!promise.then) {
+ generator = promise;
+ promise = step();
+ }
+ return promise
+ .catch(function (err) {
+ ok(false, err.message);
+ if (typeof aOnAbort == "function") {
+ aOnAbort();
+ }
+ })
+ .then(function () {
+ // Restore clock
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ });
+ }
+})();
+
+//----------------------------------------------------------------------
+//
+// Helper functions for testing animated values on the compositor
+//
+//----------------------------------------------------------------------
+
+const RunningOn = {
+ MainThread: 0,
+ Compositor: 1,
+ Either: 2,
+ TodoMainThread: 3,
+ TodoCompositor: 4,
+};
+
+const ExpectComparisonTo = {
+ Pass: 1,
+ Fail: 2,
+};
+
+(function () {
+ window.omta_todo_is = function (
+ elem,
+ property,
+ expected,
+ runningOn,
+ desc,
+ pseudo
+ ) {
+ return omta_is_approx(
+ elem,
+ property,
+ expected,
+ 0,
+ runningOn,
+ desc,
+ ExpectComparisonTo.Fail,
+ pseudo
+ );
+ };
+
+ window.omta_is = function (
+ elem,
+ property,
+ expected,
+ runningOn,
+ desc,
+ pseudo
+ ) {
+ return omta_is_approx(
+ elem,
+ property,
+ expected,
+ 0,
+ runningOn,
+ desc,
+ ExpectComparisonTo.Pass,
+ pseudo
+ );
+ };
+
+ // Many callers of this method will pass 'undefined' for
+ // expectedComparisonResult.
+ window.omta_is_approx = function (
+ elem,
+ property,
+ expected,
+ tolerance,
+ runningOn,
+ desc,
+ expectedComparisonResult,
+ pseudo
+ ) {
+ // Check input
+ // FIXME: Auto generate this array.
+ const omtaProperties = [
+ "transform",
+ "translate",
+ "rotate",
+ "scale",
+ "offset-path",
+ "offset-distance",
+ "offset-rotate",
+ "offset-anchor",
+ "offset-position",
+ "opacity",
+ "background-color",
+ ];
+ if (!omtaProperties.includes(property)) {
+ ok(false, property + " is not an OMTA property");
+ return;
+ }
+ var normalize;
+ var compare;
+ var normalizedToString = JSON.stringify;
+ switch (property) {
+ case "offset-path":
+ case "offset-distance":
+ case "offset-rotate":
+ case "offset-anchor":
+ case "offset-position":
+ case "translate":
+ case "rotate":
+ case "scale":
+ if (runningOn == RunningOn.MainThread) {
+ normalize = value => value;
+ compare = function (a, b, error) {
+ return a == b;
+ };
+ break;
+ }
+ // fall through
+ case "transform":
+ normalize = convertTo3dMatrix;
+ compare = matricesRoughlyEqual;
+ normalizedToString = convert3dMatrixToString;
+ break;
+ case "opacity":
+ normalize = parseFloat;
+ compare = function (a, b, error) {
+ return Math.abs(a - b) <= error;
+ };
+ break;
+ default:
+ normalize = value => value;
+ compare = function (a, b, error) {
+ return a == b;
+ };
+ break;
+ }
+
+ if (!!expected.compositorValue) {
+ const originalNormalize = normalize;
+ normalize = value =>
+ !!value.compositorValue
+ ? originalNormalize(value.compositorValue)
+ : originalNormalize(value);
+ }
+
+ // Get actual values
+ var compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(
+ elem,
+ property,
+ pseudo
+ );
+ var computedStr = window.getComputedStyle(elem, pseudo)[property];
+
+ // Prepare expected value
+ var expectedValue = normalize(expected);
+ if (expectedValue === null) {
+ ok(
+ false,
+ desc +
+ ": test author should provide a valid 'expected' value" +
+ " - got " +
+ expected.toString()
+ );
+ return;
+ }
+
+ // Check expected value appears in the right place
+ var actualStr;
+ switch (runningOn) {
+ case RunningOn.Either:
+ runningOn =
+ compositorStr !== "" ? RunningOn.Compositor : RunningOn.MainThread;
+ actualStr = compositorStr !== "" ? compositorStr : computedStr;
+ break;
+
+ case RunningOn.Compositor:
+ if (compositorStr === "") {
+ ok(false, desc + ": should be animating on compositor");
+ return;
+ }
+ actualStr = compositorStr;
+ break;
+
+ case RunningOn.TodoMainThread:
+ todo(
+ compositorStr === "",
+ desc + ": should NOT be animating on compositor"
+ );
+ actualStr = compositorStr === "" ? computedStr : compositorStr;
+ break;
+
+ case RunningOn.TodoCompositor:
+ todo(
+ compositorStr !== "",
+ desc + ": should be animating on compositor"
+ );
+ actualStr = compositorStr !== "" ? computedStr : compositorStr;
+ break;
+
+ default:
+ if (compositorStr !== "") {
+ ok(false, desc + ": should NOT be animating on compositor");
+ return;
+ }
+ actualStr = computedStr;
+ break;
+ }
+
+ var okOrTodo =
+ expectedComparisonResult == ExpectComparisonTo.Fail ? todo : ok;
+
+ // Compare animated value with expected
+ var actualValue = normalize(actualStr);
+ // Note: the actualStr should be empty string when using todoCompositor, so
+ // actualValue is null in this case. However, compare() should handle null
+ // well.
+ okOrTodo(
+ compare(expectedValue, actualValue, tolerance),
+ desc +
+ " - got " +
+ actualStr +
+ ", expected " +
+ normalizedToString(expectedValue)
+ );
+
+ // For transform-like properties, if we have multiple transform-like
+ // properties, the OMTA value and getComputedStyle() must be different,
+ // so use this flag to skip the following tests.
+ // FIXME: Putting this property on the expected value is a little bit odd.
+ // It's not really a product of the expected value, but rather the kind of
+ // test we're running. That said, the omta_is, omta_todo_is etc. methods are
+ // already pretty complex and adding another parameter would probably
+ // complicate things too much so this is fine for now. If we extend these
+ // functions any more, though, we should probably reconsider this API.
+ if (expected.usesMultipleProperties) {
+ return;
+ }
+
+ if (typeof expected.computed !== "undefined") {
+ // For some tests we specify a separate computed value for comparing
+ // with getComputedStyle.
+ //
+ // In particular, we do this for the individual transform functions since
+ // the form returned from getComputedStyle() reflects the individual
+ // properties (e.g. 'translate: 100px') while the form we read back from
+ // the compositor represents the combined result of all the transform
+ // properties as a single transform matrix (e.g. [0, 0, 0, 0, 100, 0]).
+ //
+ // Despite the fact that we can't directly compare the OMTA value against
+ // the getComputedStyle value in this case, it is still worth checking the
+ // result of getComputedStyle since it will help to alert us if some
+ // discrepancy arises between the way we calculate values on the main
+ // thread and compositor.
+ okOrTodo(
+ computedStr == expected.computed,
+ desc + ": Computed style should be equal to " + expected.computed
+ );
+ } else if (actualStr === compositorStr) {
+ // For compositor animations do an additional check that they match
+ // the value calculated on the main thread
+ var computedValue = normalize(computedStr);
+ if (computedValue === null) {
+ ok(
+ false,
+ desc +
+ ": test framework should parse computed style" +
+ " - got " +
+ computedStr
+ );
+ return;
+ }
+ okOrTodo(
+ compare(computedValue, actualValue, 0.0),
+ desc +
+ ": OMTA style and computed style should be equal" +
+ " - OMTA " +
+ actualStr +
+ ", computed " +
+ computedStr
+ );
+ }
+ };
+
+ window.matricesRoughlyEqual = function (a, b, tolerance) {
+ // Error handle if a or b is invalid.
+ if (!a || !b) {
+ return false;
+ }
+
+ tolerance = tolerance || 0.00011;
+ for (var i = 0; i < 4; i++) {
+ for (var j = 0; j < 4; j++) {
+ var diff = Math.abs(a[i][j] - b[i][j]);
+ if (diff > tolerance || isNaN(diff)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ // Converts something representing an transform into a 3d matrix in
+ // column-major order.
+ // The following are supported:
+ // "matrix(...)"
+ // "matrix3d(...)"
+ // [ 1, 0, 0, ... ]
+ // { a: 1, ty: 23 } etc.
+ window.convertTo3dMatrix = function (matrixLike) {
+ if (typeof matrixLike == "string") {
+ return convertStringTo3dMatrix(matrixLike);
+ } else if (Array.isArray(matrixLike)) {
+ return convertArrayTo3dMatrix(matrixLike);
+ } else if (typeof matrixLike == "object") {
+ return convertObjectTo3dMatrix(matrixLike);
+ }
+ return null;
+ };
+
+ // In future most of these methods should be able to be replaced
+ // with DOMMatrix
+ window.isInvertible = function (matrix) {
+ return getDeterminant(matrix) != 0;
+ };
+
+ // Converts strings of the format "matrix(...)" and "matrix3d(...)" to a 3d
+ // matrix
+ function convertStringTo3dMatrix(str) {
+ if (str == "none") {
+ return convertArrayTo3dMatrix([1, 0, 0, 1, 0, 0]);
+ }
+ var result = str.match("^matrix(3d)?\\(");
+ if (result === null) {
+ return null;
+ }
+
+ return convertArrayTo3dMatrix(
+ str
+ .substring(result[0].length, str.length - 1)
+ .split(",")
+ .map(function (component) {
+ return Number(component);
+ })
+ );
+ }
+
+ // Takes an array of numbers of length 6 (2d matrix) or 16 (3d matrix)
+ // representing a matrix specified in column-major order and returns a 3d
+ // matrix represented as an array of arrays
+ function convertArrayTo3dMatrix(array) {
+ if (array.length == 6) {
+ return convertObjectTo3dMatrix({
+ a: array[0],
+ b: array[1],
+ c: array[2],
+ d: array[3],
+ e: array[4],
+ f: array[5],
+ });
+ } else if (array.length == 16) {
+ return [
+ array.slice(0, 4),
+ array.slice(4, 8),
+ array.slice(8, 12),
+ array.slice(12, 16),
+ ];
+ }
+ return null;
+ }
+
+ // Return the first defined value in args.
+ function defined(...args) {
+ return args.find(arg => typeof arg !== "undefined");
+ }
+
+ // Takes an object of the form { a: 1.1, e: 23 } and builds up a 3d matrix
+ // with unspecified values filled in with identity values.
+ function convertObjectTo3dMatrix(obj) {
+ return [
+ [
+ defined(obj.a, obj.sx, obj.m11, 1),
+ obj.b || obj.m12 || 0,
+ obj.m13 || 0,
+ obj.m14 || 0,
+ ],
+ [
+ obj.c || obj.m21 || 0,
+ defined(obj.d, obj.sy, obj.m22, 1),
+ obj.m23 || 0,
+ obj.m24 || 0,
+ ],
+ [obj.m31 || 0, obj.m32 || 0, defined(obj.sz, obj.m33, 1), obj.m34 || 0],
+ [
+ obj.e || obj.tx || obj.m41 || 0,
+ obj.f || obj.ty || obj.m42 || 0,
+ obj.tz || obj.m43 || 0,
+ defined(obj.m44, 1),
+ ],
+ ];
+ }
+
+ function convert3dMatrixToString(matrix) {
+ if (is2d(matrix)) {
+ return (
+ "matrix(" +
+ [
+ matrix[0][0],
+ matrix[0][1],
+ matrix[1][0],
+ matrix[1][1],
+ matrix[3][0],
+ matrix[3][1],
+ ].join(", ") +
+ ")"
+ );
+ }
+ return (
+ "matrix3d(" +
+ matrix
+ .reduce(function (outer, inner) {
+ return outer.concat(inner);
+ })
+ .join(", ") +
+ ")"
+ );
+ }
+
+ function is2d(matrix) {
+ return (
+ matrix[0][2] === 0 &&
+ matrix[0][3] === 0 &&
+ matrix[1][2] === 0 &&
+ matrix[1][3] === 0 &&
+ matrix[2][0] === 0 &&
+ matrix[2][1] === 0 &&
+ matrix[2][2] === 1 &&
+ matrix[2][3] === 0 &&
+ matrix[3][2] === 0 &&
+ matrix[3][3] === 1
+ );
+ }
+
+ function getDeterminant(matrix) {
+ if (is2d(matrix)) {
+ return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
+ }
+
+ return (
+ matrix[0][3] * matrix[1][2] * matrix[2][1] * matrix[3][0] -
+ matrix[0][2] * matrix[1][3] * matrix[2][1] * matrix[3][0] -
+ matrix[0][3] * matrix[1][1] * matrix[2][2] * matrix[3][0] +
+ matrix[0][1] * matrix[1][3] * matrix[2][2] * matrix[3][0] +
+ matrix[0][2] * matrix[1][1] * matrix[2][3] * matrix[3][0] -
+ matrix[0][1] * matrix[1][2] * matrix[2][3] * matrix[3][0] -
+ matrix[0][3] * matrix[1][2] * matrix[2][0] * matrix[3][1] +
+ matrix[0][2] * matrix[1][3] * matrix[2][0] * matrix[3][1] +
+ matrix[0][3] * matrix[1][0] * matrix[2][2] * matrix[3][1] -
+ matrix[0][0] * matrix[1][3] * matrix[2][2] * matrix[3][1] -
+ matrix[0][2] * matrix[1][0] * matrix[2][3] * matrix[3][1] +
+ matrix[0][0] * matrix[1][2] * matrix[2][3] * matrix[3][1] +
+ matrix[0][3] * matrix[1][1] * matrix[2][0] * matrix[3][2] -
+ matrix[0][1] * matrix[1][3] * matrix[2][0] * matrix[3][2] -
+ matrix[0][3] * matrix[1][0] * matrix[2][1] * matrix[3][2] +
+ matrix[0][0] * matrix[1][3] * matrix[2][1] * matrix[3][2] +
+ matrix[0][1] * matrix[1][0] * matrix[2][3] * matrix[3][2] -
+ matrix[0][0] * matrix[1][1] * matrix[2][3] * matrix[3][2] -
+ matrix[0][2] * matrix[1][1] * matrix[2][0] * matrix[3][3] +
+ matrix[0][1] * matrix[1][2] * matrix[2][0] * matrix[3][3] +
+ matrix[0][2] * matrix[1][0] * matrix[2][1] * matrix[3][3] -
+ matrix[0][0] * matrix[1][2] * matrix[2][1] * matrix[3][3] -
+ matrix[0][1] * matrix[1][0] * matrix[2][2] * matrix[3][3] +
+ matrix[0][0] * matrix[1][1] * matrix[2][2] * matrix[3][3]
+ );
+ }
+})();
+
+//----------------------------------------------------------------------
+//
+// Promise wrappers for paint_listener.js
+//
+//----------------------------------------------------------------------
+
+// Returns a Promise that resolves once all paints have completed
+function waitForPaints() {
+ return new Promise(function (resolve, reject) {
+ waitForAllPaints(resolve);
+ });
+}
+
+// As with waitForPaints but also flushes pending style changes before waiting
+function waitForPaintsFlushed() {
+ return new Promise(function (resolve, reject) {
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+function waitForVisitedLinkColoring(visitedLink, waitProperty, waitValue) {
+ function checkLink(resolve) {
+ if (
+ SpecialPowers.DOMWindowUtils.getVisitedDependentComputedStyle(
+ visitedLink,
+ "",
+ waitProperty
+ ) == waitValue
+ ) {
+ // Our link has been styled as visited. Resolve.
+ resolve(true);
+ } else {
+ // Our link is not yet styled as visited. Poll for completion.
+ setTimeout(checkLink, 0, resolve);
+ }
+ }
+ return new Promise(function (resolve, reject) {
+ checkLink(resolve);
+ });
+}
diff --git a/layout/style/test/browser.toml b/layout/style/test/browser.toml
new file mode 100644
index 0000000000..7c16123562
--- /dev/null
+++ b/layout/style/test/browser.toml
@@ -0,0 +1,19 @@
+[DEFAULT]
+support-files = [
+ "bug453896_iframe.html",
+ "media_queries_iframe.html",
+ "mapped.css",
+ "mapped.css^headers^",
+ "mapped2.css",
+ "mapped2.css^headers^",
+ "sourcemap_css.html"
+]
+
+["browser_bug453896.js"]
+
+["browser_sourcemap.js"]
+
+["browser_sourcemap_comment.js"]
+
+["browser_sourceurl_comment.js"]
+
diff --git a/layout/style/test/browser_bug453896.js b/layout/style/test/browser_bug453896.js
new file mode 100644
index 0000000000..6b8e180c38
--- /dev/null
+++ b/layout/style/test/browser_bug453896.js
@@ -0,0 +1,16 @@
+add_task(async function () {
+ let uri = getRootDirectory(gTestPath) + "bug453896_iframe.html";
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: uri,
+ },
+ function (browser) {
+ return SpecialPowers.spawn(browser, [], async function () {
+ var fake_window = { ok: ok };
+ content.wrappedJSObject.run(fake_window);
+ });
+ }
+ );
+});
diff --git a/layout/style/test/browser_sourcemap.js b/layout/style/test/browser_sourcemap.js
new file mode 100644
index 0000000000..56ad067818
--- /dev/null
+++ b/layout/style/test/browser_sourcemap.js
@@ -0,0 +1,41 @@
+add_task(async function () {
+ let uri = "http://example.com/browser/layout/style/test/sourcemap_css.html";
+ info(`URI is ${uri}`);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: uri,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], function () {
+ let seenSheets = 0;
+
+ for (let i = 0; i < content.document.styleSheets.length; ++i) {
+ let sheet = content.document.styleSheets[i];
+
+ info(`Checking ${sheet.href}`);
+ if (/mapped\.css/.test(sheet.href)) {
+ is(
+ sheet.sourceMapURL,
+ "mapped.css.map",
+ "X-SourceMap header took effect"
+ );
+ seenSheets |= 1;
+ } else if (/mapped2\.css/.test(sheet.href)) {
+ is(
+ sheet.sourceMapURL,
+ "mapped2.css.map",
+ "SourceMap header took effect"
+ );
+ seenSheets |= 2;
+ } else {
+ ok(false, "sheet does not have source map URL");
+ }
+ }
+
+ is(seenSheets, 3, "seen all source-mapped sheets");
+ });
+ }
+ );
+});
diff --git a/layout/style/test/browser_sourcemap_comment.js b/layout/style/test/browser_sourcemap_comment.js
new file mode 100644
index 0000000000..e0bbff8de4
--- /dev/null
+++ b/layout/style/test/browser_sourcemap_comment.js
@@ -0,0 +1,47 @@
+add_task(async function () {
+ // Test text and expected results.
+ let test_cases = [
+ ["/*# sourceMappingURL=here*/", "here"],
+ ["/*# sourceMappingURL=here */", "here"],
+ ["/*@ sourceMappingURL=here*/", "here"],
+ ["/*@ sourceMappingURL=there*/ /*# sourceMappingURL=here*/", "here"],
+ ["/*# sourceMappingURL=here there */", "here"],
+
+ ["/*# sourceMappingURL= here */", ""],
+ ["/*# sourceMappingURL=*/", ""],
+ ["/*# sourceMappingUR=here */", ""],
+ ["/*! sourceMappingURL=here */", ""],
+ ["/*# sourceMappingURL = here */", ""],
+ ["/* # sourceMappingURL=here */", ""],
+ ];
+
+ let page = "<!DOCTYPE HTML>\n<html>\n<head>\n";
+ for (let i = 0; i < test_cases.length; ++i) {
+ page += `<style type="text/css"> #x${i} { color: red; }${test_cases[i][0]}</style>\n`;
+ }
+ page += "</head><body>some text</body></html>";
+
+ let uri = "data:text/html;base64," + btoa(page);
+ info(`URI is ${uri}`);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: uri,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [test_cases], function (tests) {
+ for (let i = 0; i < content.document.styleSheets.length; ++i) {
+ let sheet = content.document.styleSheets[i];
+
+ info(`Checking sheet #${i}`);
+ is(
+ sheet.sourceMapURL,
+ tests[i][1],
+ `correct source map for sheet ${i}`
+ );
+ }
+ });
+ }
+ );
+});
diff --git a/layout/style/test/browser_sourceurl_comment.js b/layout/style/test/browser_sourceurl_comment.js
new file mode 100644
index 0000000000..9baf6c9ce5
--- /dev/null
+++ b/layout/style/test/browser_sourceurl_comment.js
@@ -0,0 +1,43 @@
+add_task(async function () {
+ // Test text and expected results.
+ let test_cases = [
+ ["/*# sourceURL=here*/", "here"],
+ ["/*# sourceURL=here */", "here"],
+ ["/*@ sourceURL=here*/", "here"],
+ ["/*@ sourceURL=there*/ /*# sourceURL=here*/", "here"],
+ ["/*# sourceURL=here there */", "here"],
+
+ ["/*# sourceURL= here */", ""],
+ ["/*# sourceURL=*/", ""],
+ ["/*# sourceUR=here */", ""],
+ ["/*! sourceURL=here */", ""],
+ ["/*# sourceURL = here */", ""],
+ ["/* # sourceURL=here */", ""],
+ ];
+
+ let page = "<!DOCTYPE HTML>\n<html>\n<head>\n";
+ for (let i = 0; i < test_cases.length; ++i) {
+ page += `<style type="text/css"> #x${i} { color: red; }${test_cases[i][0]}</style>\n`;
+ }
+ page += "</head><body>some text</body></html>";
+
+ let uri = "data:text/html;base64," + btoa(page);
+ info(`URI is ${uri}`);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: uri,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [test_cases], function (tests) {
+ for (let i = 0; i < content.document.styleSheets.length; ++i) {
+ let sheet = content.document.styleSheets[i];
+
+ info(`Checking sheet #${i}`);
+ is(sheet.sourceURL, tests[i][1], `correct source URL for sheet ${i}`);
+ }
+ });
+ }
+ );
+});
diff --git a/layout/style/test/bug1382568-iframe.html b/layout/style/test/bug1382568-iframe.html
new file mode 100644
index 0000000000..b1448703e5
--- /dev/null
+++ b/layout/style/test/bug1382568-iframe.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<iframe src="http://example.com/doesnt-matter-because-it-gets-blocked-due-to-mixed-content"></iframe>
+<script>
+window.addEventListener('load', function(){
+ window[0].document.body.innerText;
+ window.parent.postMessage({ result: "ok" }, "*");
+});
+</script>
diff --git a/layout/style/test/bug1729861.js b/layout/style/test/bug1729861.js
new file mode 100644
index 0000000000..dbfa060ab1
--- /dev/null
+++ b/layout/style/test/bug1729861.js
@@ -0,0 +1,81 @@
+// # Bug 1729861
+
+// Expected values. Format: [name, pref_off_value, pref_on_value]
+var expected_values = [
+ [
+ "device-aspect-ratio",
+ screen.width + "/" + screen.height,
+ window.innerWidth + "/" + window.innerHeight,
+ ],
+ ["device-height", screen.height + "px", window.innerHeight + "px"],
+ ["device-width", screen.width + "px", window.innerWidth + "px"],
+];
+
+// Create a line containing a CSS media query and a rule to set the bg color.
+var mediaQueryCSSLine = function (key, val, color) {
+ return (
+ "@media (" +
+ key +
+ ": " +
+ val +
+ ") { #" +
+ key +
+ " { background-color: " +
+ color +
+ "; } }\n"
+ );
+};
+
+var green = "rgb(0, 128, 0)";
+var blue = "rgb(0, 0, 255)";
+
+// Set a pref value asynchronously, returning a promise that resolves
+// when it succeeds.
+var pushPref = function (key, value) {
+ return SpecialPowers.pushPrefEnv({ set: [[key, value]] });
+};
+
+// Set the resistFingerprinting pref to the given value, and then check that the background
+// color has been updated properly as a result of re-evaluating the media queries.
+var checkColorForPref = async function (setting, testDivs, expectedColor) {
+ await pushPref("privacy.resistFingerprinting", setting);
+ for (let div of testDivs) {
+ let color = window.getComputedStyle(div).backgroundColor;
+ is(color, expectedColor, "background for '" + div.id + "' is " + color);
+ }
+};
+
+var test = async function () {
+ // If the "off" and "on" expected values are the same, we can't actually
+ // test anything here. (Might this be the case on mobile?)
+ let skipTest = false;
+ for (let [key, offVal, onVal] of expected_values) {
+ if (offVal == onVal) {
+ todo(false, "Unable to test because '" + key + "' is invariant");
+ return;
+ }
+ }
+
+ let css =
+ ".test { margin: 1em; padding: 1em; color: white; width: max-content; font: 2em monospace }\n";
+
+ // Create a test element for each of the media queries we're checking, and append the matching
+ // "on" and "off" media queries to the CSS.
+ let testDivs = [];
+ for (let [key, offVal, onVal] of expected_values) {
+ let div = document.createElement("div");
+ div.textContent = key;
+ div.setAttribute("class", "test");
+ div.setAttribute("id", key);
+ testDivs.push(div);
+ document.getElementById("display").appendChild(div);
+ css += mediaQueryCSSLine(key, onVal, "green");
+ css += mediaQueryCSSLine(key, offVal, "blue");
+ }
+ document.getElementById("test-css").textContent = css;
+
+ // Check that the test elements change color as expected when we flip the resistFingerprinting pref.
+ await checkColorForPref(true, testDivs, green);
+ await checkColorForPref(false, testDivs, blue);
+ await checkColorForPref(true, testDivs, green);
+};
diff --git a/layout/style/test/bug453896_iframe.html b/layout/style/test/bug453896_iframe.html
new file mode 100644
index 0000000000..e5414cc0df
--- /dev/null
+++ b/layout/style/test/bug453896_iframe.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US">
+<head>
+ <title>Bug 453896 Test middle frame</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <script type="application/javascript">
+
+function run(test_window)
+{
+ var subdoc = document.getElementById("subdoc").contentDocument;
+ var subwin = document.getElementById("subdoc").contentWindow;
+ var style = subdoc.getElementById("style");
+ var iframe_style = document.getElementById("subdoc").style;
+ var body_cs = subdoc.defaultView.getComputedStyle(subdoc.body);
+
+ function query_applies(q) {
+ style.setAttribute("media", q);
+ return body_cs.getPropertyValue("text-decoration-line") == "underline";
+ }
+
+ function should_apply(q) {
+ test_window.ok(query_applies(q), q + " should apply");
+ }
+
+ function should_not_apply(q) {
+ test_window.ok(!query_applies(q), q + " should not apply");
+ }
+
+ // in this test, assume the common underlying implementation is correct
+ let width_val = 157; // pick two not-too-round numbers
+ let height_val = 182;
+ iframe_style.width = width_val + "px";
+ iframe_style.height = height_val + "px";
+ for (let [feature, value] of
+ Object.entries({ "width": width_val, "height": height_val })) {
+ should_apply("all and (" + feature + ": " + value + "px)");
+ should_not_apply("all and (" + feature + ": " + (value + 1) + "px)");
+ should_not_apply("all and (" + feature + ": " + (value - 1) + "px)");
+ }
+
+ iframe_style.width = "0";
+ should_apply("all and (height)");
+ should_not_apply("all and (width)");
+ iframe_style.height = "0";
+ should_not_apply("all and (height)");
+ should_not_apply("all and (width)");
+ should_apply("all and (device-height)");
+ should_apply("all and (device-width)");
+ iframe_style.width = width_val + "px";
+ should_not_apply("all and (height)");
+ should_apply("all and (width)");
+ iframe_style.height = height_val + "px";
+ should_apply("all and (height)");
+ should_apply("all and (width)");
+}
+
+ </script>
+</head>
+<body>
+
+<iframe id="subdoc" src="media_queries_iframe.html"></iframe>
+
+</body>
+</html>
diff --git a/layout/style/test/bug517224.sjs b/layout/style/test/bug517224.sjs
new file mode 100644
index 0000000000..bd1fe4f336
--- /dev/null
+++ b/layout/style/test/bug517224.sjs
@@ -0,0 +1,27 @@
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ switch (request.queryString) {
+ case "reset":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ setState("imageloaded", "");
+ break;
+ case "image":
+ setState("imageloaded", "imageloaded");
+ response.setStatusLine("1.1", 302, "Found");
+ // redirect to a solid blue image
+ response.setHeader(
+ "Location",
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12NgYPgPAAEDAQDZqt2zAAAAAElFTkSuQmCC"
+ );
+ response.setHeader("Content-Type", "text/plain", false);
+ break;
+ case "result":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ var state = getState("imageloaded");
+ response.write(
+ "is('" + state + "', '', 'image should not have been loaded')\n"
+ );
+ response.write("SimpleTest.finish()");
+ break;
+ }
+}
diff --git a/layout/style/test/bug732209-css.sjs b/layout/style/test/bug732209-css.sjs
new file mode 100644
index 0000000000..a572cf3942
--- /dev/null
+++ b/layout/style/test/bug732209-css.sjs
@@ -0,0 +1,30 @@
+function handleRequest(request, response) {
+ // First item will be the ID; other items are optional
+ var query = request.queryString.split(/&/);
+
+ response.setHeader("Content-Type", "text/css", false);
+
+ if (query.indexOf("cors-anonymous") != -1) {
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ } else if (
+ query.indexOf("cors-credentials") != -1 &&
+ request.hasHeader("Origin")
+ ) {
+ response.setHeader(
+ "Access-Control-Allow-Origin",
+ request.getHeader("Origin"),
+ false
+ );
+ response.setHeader("Access-Control-Allow-Credentials", "true", false);
+ }
+
+ response.write(
+ "#" +
+ query[0] +
+ " { color: green !important }" +
+ "\n" +
+ "#" +
+ query[0] +
+ ".reverse { color: red !important }"
+ );
+}
diff --git a/layout/style/test/ccd-quirks.html b/layout/style/test/ccd-quirks.html
new file mode 100644
index 0000000000..570b5d5a1b
--- /dev/null
+++ b/layout/style/test/ccd-quirks.html
@@ -0,0 +1,129 @@
+<!-- Intentionally in quirks mode. -->
+<html><head>
+<!-- baseline -->
+<style>
+body, html { margin: 0; padding: 0; overflow: hidden }
+div {
+ width: 60px;
+ height: 20px;
+ position: relative;
+}
+p {
+ position: absolute;
+ top: 2px; left: 2px;
+ width: 16px;
+ height: 16px;
+ margin: 0;
+ padding: 0;
+}
+p + p { left: 22px }
+
+#IA1i, #IA1l, #IA2i, #IA2l, #IB1i, #IB1l, #IB2i, #IB2l,
+#IC1i, #IC1l, #IC2i, #IC2l, #ID1i, #ID1l, #ID2i, #ID2l,
+#JA1i, #JA1l, #JA2i, #JA2l, #JD1i, #JD1l, #JD2i, #JD2l
+ { background-color: red }
+
+#JB1i, #JB1l, #JC1i, #JC1l,
+#JB2i, #JB2l, #JC2i, #JC2l
+ { background-color: lime }
+
+#IA3i, #IA3l, #IB3i, #IB3l, #IC3i, #IC3l, #ID3i, #ID3l,
+#JA3i, #JA3l, #JB3i, #JB3l, #JC3i, #JC3l, #JD3i, #JD3l
+ { background-color: lime }
+</style>
+
+<!-- @import rules -->
+<style>
+@import url("ccd.sjs?IA1iq");
+@import url("ccd.sjs?IA2iq");
+@import url("ccd.sjs?IA3iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB1iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB2iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB3iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3iq");
+@import url("ccd.sjs?JA1iq");
+@import url("ccd.sjs?JA2iq");
+@import url("ccd.sjs?JA3iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB1iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB2iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB3iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3iq");
+</style>
+
+<!-- link directives -->
+<link rel="stylesheet" href="ccd.sjs?IA1lq">
+<link rel="stylesheet" href="ccd.sjs?IA2lq">
+<link rel="stylesheet" href="ccd.sjs?IA3lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB1lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB2lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB3lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3lq">
+<link rel="stylesheet" href="ccd.sjs?JA1lq">
+<link rel="stylesheet" href="ccd.sjs?JA2lq">
+<link rel="stylesheet" href="ccd.sjs?JA3lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB1lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB2lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB3lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3lq">
+
+</head><body>
+<div></div>
+<div></div>
+<div><p id="IA1i"></p><p id="IA1l"></p></div>
+<div><p id="IA2i"></p><p id="IA2l"></p></div>
+<div><p id="IA3i"></p><p id="IA3l"></p></div>
+<div></div>
+<div><p id="IB1i"></p><p id="IB1l"></p></div>
+<div><p id="IB2i"></p><p id="IB2l"></p></div>
+<div><p id="IB3i"></p><p id="IB3l"></p></div>
+<div></div>
+<div><p id="IC1i"></p><p id="IC1l"></p></div>
+<div><p id="IC2i"></p><p id="IC2l"></p></div>
+<div><p id="IC3i"></p><p id="IC3l"></p></div>
+<div></div>
+<div><p id="ID1i"></p><p id="ID1l"></p></div>
+<div><p id="ID2i"></p><p id="ID2l"></p></div>
+<div><p id="ID3i"></p><p id="ID3l"></p></div>
+<div></div>
+<div></div>
+<div><p id="JA1i"></p><p id="JA1l"></p></div>
+<div><p id="JA2i"></p><p id="JA2l"></p></div>
+<div><p id="JA3i"></p><p id="JA3l"></p></div>
+<div></div>
+<div><p id="JB1i"></p><p id="JB1l"></p></div>
+<div><p id="JB2i"></p><p id="JB2l"></p></div>
+<div><p id="JB3i"></p><p id="JB3l"></p></div>
+<div></div>
+<div><p id="JC1i"></p><p id="JC1l"></p></div>
+<div><p id="JC2i"></p><p id="JC2l"></p></div>
+<div><p id="JC3i"></p><p id="JC3l"></p></div>
+<div></div>
+<div><p id="JD1i"></p><p id="JD1l"></p></div>
+<div><p id="JD2i"></p><p id="JD2l"></p></div>
+<div><p id="JD3i"></p><p id="JD3l"></p></div>
+<script>
+ window.onload = function() {
+ window.parent.quirksLoaded()
+ }
+</script>
+</body></html>
diff --git a/layout/style/test/ccd-standards.html b/layout/style/test/ccd-standards.html
new file mode 100644
index 0000000000..693402df4c
--- /dev/null
+++ b/layout/style/test/ccd-standards.html
@@ -0,0 +1,128 @@
+<!doctype html>
+<html><head>
+<!-- baseline -->
+<style>
+body, html { margin: 0; padding: 0; overflow: hidden }
+div {
+ width: 60px;
+ height: 20px;
+ position: relative;
+}
+p {
+ position: absolute;
+ top: 2px; left: 2px;
+ width: 16px;
+ height: 16px;
+ margin: 0;
+ padding: 0;
+}
+p + p { left: 22px }
+
+#IA1i, #IA1l, #IA2i, #IA2l, #IB1i, #IB1l, #IB2i, #IB2l,
+#IC1i, #IC1l, #IC2i, #IC2l, #ID1i, #ID1l, #ID2i, #ID2l
+ { background-color: red }
+
+#JA1i, #JA1l, #JA2i, #JA2l, #JB1i, #JB1l, #JB2i, #JB2l,
+#JC1i, #JC1l, #JC2i, #JC2l, #JD1i, #JD1l, #JD2i, #JD2l
+ { background-color: lime }
+
+#IA3i, #IA3l, #IB3i, #IB3l, #IC3i, #IC3l, #ID3i, #ID3l,
+#JA3i, #JA3l, #JB3i, #JB3l, #JC3i, #JC3l, #JD3i, #JD3l
+ { background-color: lime }
+</style>
+
+<!-- @import rules -->
+<style>
+@import url("ccd.sjs?IA1is");
+@import url("ccd.sjs?IA2is");
+@import url("ccd.sjs?IA3is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB1is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB2is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB3is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3is");
+@import url("ccd.sjs?JA1is");
+@import url("ccd.sjs?JA2is");
+@import url("ccd.sjs?JA3is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB1is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB2is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB3is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3is");
+</style>
+
+<!-- link directives -->
+<link rel="stylesheet" href="ccd.sjs?IA1ls">
+<link rel="stylesheet" href="ccd.sjs?IA2ls">
+<link rel="stylesheet" href="ccd.sjs?IA3ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB1ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB2ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB3ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3ls">
+<link rel="stylesheet" href="ccd.sjs?JA1ls">
+<link rel="stylesheet" href="ccd.sjs?JA2ls">
+<link rel="stylesheet" href="ccd.sjs?JA3ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB1ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB2ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB3ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3ls">
+
+</head><body>
+<div></div>
+<div></div>
+<div><p id="IA1i"></p><p id="IA1l"></p></div>
+<div><p id="IA2i"></p><p id="IA2l"></p></div>
+<div><p id="IA3i"></p><p id="IA3l"></p></div>
+<div></div>
+<div><p id="IB1i"></p><p id="IB1l"></p></div>
+<div><p id="IB2i"></p><p id="IB2l"></p></div>
+<div><p id="IB3i"></p><p id="IB3l"></p></div>
+<div></div>
+<div><p id="IC1i"></p><p id="IC1l"></p></div>
+<div><p id="IC2i"></p><p id="IC2l"></p></div>
+<div><p id="IC3i"></p><p id="IC3l"></p></div>
+<div></div>
+<div><p id="ID1i"></p><p id="ID1l"></p></div>
+<div><p id="ID2i"></p><p id="ID2l"></p></div>
+<div><p id="ID3i"></p><p id="ID3l"></p></div>
+<div></div>
+<div></div>
+<div><p id="JA1i"></p><p id="JA1l"></p></div>
+<div><p id="JA2i"></p><p id="JA2l"></p></div>
+<div><p id="JA3i"></p><p id="JA3l"></p></div>
+<div></div>
+<div><p id="JB1i"></p><p id="JB1l"></p></div>
+<div><p id="JB2i"></p><p id="JB2l"></p></div>
+<div><p id="JB3i"></p><p id="JB3l"></p></div>
+<div></div>
+<div><p id="JC1i"></p><p id="JC1l"></p></div>
+<div><p id="JC2i"></p><p id="JC2l"></p></div>
+<div><p id="JC3i"></p><p id="JC3l"></p></div>
+<div></div>
+<div><p id="JD1i"></p><p id="JD1l"></p></div>
+<div><p id="JD2i"></p><p id="JD2l"></p></div>
+<div><p id="JD3i"></p><p id="JD3l"></p></div>
+<script>
+ window.onload = function() {
+ window.parent.standardsLoaded();
+ }
+</script>
+</body></html>
diff --git a/layout/style/test/ccd.sjs b/layout/style/test/ccd.sjs
new file mode 100644
index 0000000000..22a4294d59
--- /dev/null
+++ b/layout/style/test/ccd.sjs
@@ -0,0 +1,71 @@
+const DEBUG_all_valid = false;
+const DEBUG_all_stub = false;
+
+function handleRequest(request, response) {
+ // Decode the query string to know what test we're doing.
+
+ // character 1: 'I' = text/css response, 'J' = text/html response
+ let responseCSS = request.queryString[0] == "I";
+
+ // character 2: redirection type - we only care about whether we're
+ // ultimately same-origin with the requesting document ('A', 'D') or
+ // not ('B', 'C').
+ let sameOrigin =
+ request.queryString[1] == "A" || request.queryString[1] == "D";
+
+ // character 3: '1' = syntactically valid, '2' = invalid, '3' = http error
+ let malformed = request.queryString[2] == "2";
+ let httpError = request.queryString[2] == "3";
+
+ // character 4: loaded with <link> or @import (no action required)
+
+ // character 5: loading document mode: 'q' = quirks, 's' = standards
+ let quirksMode = request.queryString[4] == "q";
+
+ // Our response contains a CSS rule that selects an element whose
+ // ID is the first four characters of the query string.
+ let selector = "#" + request.queryString.substring(0, 4);
+
+ // "Malformed" responses wrap the CSS rule in the construct
+ // <html>{} ... </html>
+ // This mimics what the CSS parser might see if an actual HTML
+ // document were fed to it. Because CSS parsers recover from
+ // errors by skipping tokens until they find something
+ // recognizable, a style rule appearing where I wrote '...' above
+ // will be honored!
+ let leader = malformed ? "<html>{}" : "";
+ let trailer = malformed ? "</html>" : "";
+
+ // Standards mode documents will ignore the style sheet if it is being
+ // served as text/html (regardless of its contents). Quirks mode
+ // documents will ignore the style sheet if it is being served as
+ // text/html _and_ it is not same-origin. Regardless, style sheets
+ // are ignored if they come as the body of an HTTP error response.
+ //
+ // Style sheets that should be ignored paint the element red; those
+ // that should be honored paint it lime.
+ let color =
+ (responseCSS || (quirksMode && sameOrigin)) && !httpError ? "lime" : "red";
+
+ // For debugging the test itself, we have the capacity to make every style
+ // sheet well-formed, or every style sheet do nothing.
+ if (DEBUG_all_valid) {
+ // In this mode, every test chip should turn blue.
+ response.setHeader("Content-Type", "text/css");
+ response.write(selector + "{background-color:blue}\n");
+ } else if (DEBUG_all_stub) {
+ // In this mode, every test chip for a case where the true test
+ // sheet would be honored, should turn red.
+ response.setHeader("Content-Type", "text/css");
+ response.write(selector + "{}\n");
+ } else {
+ // Normal operation.
+ if (httpError) {
+ response.setStatusLine(request.httpVersion, 500, "Internal Server Error");
+ }
+ response.setHeader("Content-Type", responseCSS ? "text/css" : "text/html");
+ response.write(
+ leader + selector + "{background-color:" + color + "}" + trailer + "\n"
+ );
+ }
+}
diff --git a/layout/style/test/chrome/bug418986-2.js b/layout/style/test/chrome/bug418986-2.js
new file mode 100644
index 0000000000..6d2af235c3
--- /dev/null
+++ b/layout/style/test/chrome/bug418986-2.js
@@ -0,0 +1,318 @@
+// # Bug 418986, part 2.
+
+const is_chrome_window = window.location.protocol === "chrome:";
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+// Expected values. Format: [name, pref_off_value, pref_on_value]
+// If pref_*_value is an array with two values, then we will match
+// any value in between those two values. If a value is null, then
+// we skip the media query.
+var expected_values = [
+ ["color", null, 8],
+ ["color-index", null, 0],
+ ["aspect-ratio", null, window.innerWidth + "/" + window.innerHeight],
+ [
+ "device-aspect-ratio",
+ screen.width + "/" + screen.height,
+ window.innerWidth + "/" + window.innerHeight,
+ ],
+ ["device-height", screen.height + "px", window.innerHeight + "px"],
+ ["device-width", screen.width + "px", window.innerWidth + "px"],
+ ["grid", null, 0],
+ ["height", window.innerHeight + "px", window.innerHeight + "px"],
+ ["monochrome", null, 0],
+ // Square is defined as portrait:
+ [
+ "orientation",
+ null,
+ window.innerWidth > window.innerHeight ? "landscape" : "portrait",
+ ],
+ ["resolution", null, "96dpi"],
+ [
+ "resolution",
+ [
+ 0.999 * window.devicePixelRatio + "dppx",
+ 1.001 * window.devicePixelRatio + "dppx",
+ ],
+ "1dppx",
+ ],
+ ["width", window.innerWidth + "px", window.innerWidth + "px"],
+ ["-moz-device-pixel-ratio", window.devicePixelRatio, 1],
+ [
+ "-moz-device-orientation",
+ screen.width > screen.height ? "landscape" : "portrait",
+ window.innerWidth > window.innerHeight ? "landscape" : "portrait",
+ ],
+];
+
+// These media queries return value 0 or 1 when the pref is off.
+// When the pref is on, they should not match.
+var suppressed_toggles = [
+ // Not available on most OSs.
+ "-moz-scrollbar-end-backward",
+ "-moz-scrollbar-end-forward",
+ "-moz-scrollbar-start-backward",
+ "-moz-scrollbar-start-forward",
+ "-moz-gtk-csd-available",
+ "-moz-gtk-csd-minimize-button",
+ "-moz-gtk-csd-maximize-button",
+ "-moz-gtk-csd-close-button",
+ "-moz-gtk-csd-reversed-placement",
+];
+
+var toggles_enabled_in_content = [];
+
+// Read the current OS.
+var OS = SpecialPowers.Services.appinfo.OS;
+
+// __keyValMatches(key, val)__.
+// Runs a media query and returns true if key matches to val.
+var keyValMatches = (key, val) =>
+ matchMedia("(" + key + ":" + val + ")").matches;
+
+// __testMatch(key, val)__.
+// Attempts to run a media query match for the given key and value.
+// If value is an array of two elements [min max], then matches any
+// value in-between.
+var testMatch = function (key, val) {
+ if (val === null) {
+ return;
+ } else if (Array.isArray(val)) {
+ ok(
+ keyValMatches("min-" + key, val[0]) &&
+ keyValMatches("max-" + key, val[1]),
+ "Expected " + key + " between " + val[0] + " and " + val[1]
+ );
+ } else {
+ ok(keyValMatches(key, val), "Expected " + key + ":" + val);
+ }
+};
+
+// __testToggles(resisting)__.
+// Test whether we are able to match the "toggle" media queries.
+var testToggles = function (resisting) {
+ suppressed_toggles.forEach(function (key) {
+ var exists = keyValMatches(key, 0) || keyValMatches(key, 1);
+ if (!toggles_enabled_in_content.includes(key) && !is_chrome_window) {
+ ok(!exists, key + " should not exist.");
+ } else {
+ ok(exists, key + " should exist.");
+ if (resisting) {
+ ok(
+ keyValMatches(key, 0) && !keyValMatches(key, 1),
+ "Should always match as false"
+ );
+ }
+ }
+ });
+};
+
+// __generateHtmlLines(resisting)__.
+// Create a series of div elements that look like:
+// `<div class='spoof' id='resolution'>resolution</div>`,
+// where each line corresponds to a different media query.
+var generateHtmlLines = function (resisting) {
+ let fragment = document.createDocumentFragment();
+ expected_values.forEach(function ([key, offVal, onVal]) {
+ let val = resisting ? onVal : offVal;
+ if (val) {
+ let div = document.createElementNS(HTML_NS, "div");
+ div.setAttribute("class", "spoof");
+ div.setAttribute("id", key);
+ div.textContent = key;
+ fragment.appendChild(div);
+ }
+ });
+ suppressed_toggles.forEach(function (key) {
+ let div = document.createElementNS(HTML_NS, "div");
+ div.setAttribute("class", "suppress");
+ div.setAttribute("id", key);
+ div.textContent = key;
+ fragment.appendChild(div);
+ });
+ return fragment;
+};
+
+// __cssLine__.
+// Creates a line of css that looks something like
+// `@media (resolution: 1ppx) { .spoof#resolution { background-color: green; } }`.
+var cssLine = function (query, clazz, id, color) {
+ return (
+ "@media " +
+ query +
+ " { ." +
+ clazz +
+ "#" +
+ id +
+ " { background-color: " +
+ color +
+ "; } }\n"
+ );
+};
+
+// __constructQuery(key, val)__.
+// Creates a CSS media query from key and val. If key is an array of
+// two elements, constructs a range query (using min- and max-).
+var constructQuery = function (key, val) {
+ return Array.isArray(val)
+ ? "(min-" + key + ": " + val[0] + ") and (max-" + key + ": " + val[1] + ")"
+ : "(" + key + ": " + val + ")";
+};
+
+// __mediaQueryCSSLine(key, val, color)__.
+// Creates a line containing a CSS media query and a CSS expression.
+var mediaQueryCSSLine = function (key, val, color) {
+ if (val === null) {
+ return "";
+ }
+ return cssLine(constructQuery(key, val), "spoof", key, color);
+};
+
+// __suppressedMediaQueryCSSLine(key, color)__.
+// Creates a CSS line that matches the existence of a
+// media query that is supposed to be suppressed.
+var suppressedMediaQueryCSSLine = function (key, color, suppressed) {
+ let query = "(" + key + ": 0), (" + key + ": 1)";
+ return cssLine(query, "suppress", key, color);
+};
+
+// __generateCSSLines(resisting)__.
+// Creates a series of lines of CSS, each of which corresponds to
+// a different media query. If the query produces a match to the
+// expected value, then the element will be colored green.
+var generateCSSLines = function (resisting) {
+ let lines = ".spoof { background-color: red;}\n";
+ expected_values.forEach(function ([key, offVal, onVal]) {
+ lines += mediaQueryCSSLine(key, resisting ? onVal : offVal, "green");
+ });
+ lines +=
+ ".suppress { background-color: " + (resisting ? "green" : "red") + ";}\n";
+ suppressed_toggles.forEach(function (key) {
+ if (
+ !toggles_enabled_in_content.includes(key) &&
+ !resisting &&
+ !is_chrome_window
+ ) {
+ lines += "#" + key + " { background-color: green; }\n";
+ } else {
+ lines += suppressedMediaQueryCSSLine(key, "green");
+ }
+ });
+ return lines;
+};
+
+// __green__.
+// Returns the computed color style corresponding to green.
+var green = "rgb(0, 128, 0)";
+
+// __testCSS(resisting)__.
+// Creates a series of divs and CSS using media queries to set their
+// background color. If all media queries match as expected, then
+// all divs should have a green background color.
+var testCSS = function (resisting) {
+ document.getElementById("display").appendChild(generateHtmlLines(resisting));
+ document.getElementById("test-css").textContent = generateCSSLines(resisting);
+ let cssTestDivs = document.querySelectorAll(".spoof,.suppress");
+ for (let div of cssTestDivs) {
+ let color = window.getComputedStyle(div).backgroundColor;
+ ok(color === green, "CSS for '" + div.id + "'");
+ }
+};
+
+// __testOSXFontSmoothing(resisting)__.
+// When fingerprinting resistance is enabled, the `getComputedStyle`
+// should always return `undefined` for `MozOSXFontSmoothing`.
+var testOSXFontSmoothing = function (resisting) {
+ let div = document.createElementNS(HTML_NS, "div");
+ div.style.MozOsxFontSmoothing = "unset";
+ document.documentElement.appendChild(div);
+ let readBack = window.getComputedStyle(div).MozOsxFontSmoothing;
+ div.remove();
+ let smoothingPref = SpecialPowers.getBoolPref(
+ "layout.css.osx-font-smoothing.enabled",
+ false
+ );
+ is(
+ readBack,
+ resisting ? "" : smoothingPref ? "auto" : "",
+ "-moz-osx-font-smoothing"
+ );
+};
+
+// __sleep(timeoutMs)__.
+// Returns a promise that resolves after the given timeout.
+var sleep = function (timeoutMs) {
+ return new Promise(function (resolve, reject) {
+ window.setTimeout(resolve);
+ });
+};
+
+// __testMediaQueriesInPictureElements(resisting)__.
+// Test to see if media queries are properly spoofed in picture elements
+// when we are resisting fingerprinting.
+var testMediaQueriesInPictureElements = async function (resisting) {
+ const MATCH = "/tests/layout/style/test/chrome/match.png";
+ let container = document.getElementById("pictures");
+ let testImages = [];
+ for (let [key, offVal, onVal] of expected_values) {
+ let expected = resisting ? onVal : offVal;
+ if (expected) {
+ let picture = document.createElementNS(HTML_NS, "picture");
+ let query = constructQuery(key, expected);
+ ok(matchMedia(query).matches, `${query} should match`);
+
+ let source = document.createElementNS(HTML_NS, "source");
+ source.setAttribute("srcset", MATCH);
+ source.setAttribute("media", query);
+
+ let image = document.createElementNS(HTML_NS, "img");
+ image.setAttribute("title", key + ":" + expected);
+ image.setAttribute("class", "testImage");
+ image.setAttribute("src", "/tests/layout/style/test/chrome/mismatch.png");
+ image.setAttribute("alt", key);
+
+ testImages.push(image);
+
+ picture.appendChild(source);
+ picture.appendChild(image);
+ container.appendChild(picture);
+ }
+ }
+ const matchURI = new URL(MATCH, document.baseURI).href;
+ await sleep(0);
+ for (let testImage of testImages) {
+ is(
+ testImage.currentSrc,
+ matchURI,
+ "Media query '" + testImage.title + "' in picture should match."
+ );
+ }
+};
+
+// __pushPref(key, value)__.
+// Set a pref value asynchronously, returning a promise that resolves
+// when it succeeds.
+var pushPref = function (key, value) {
+ return new Promise(function (resolve, reject) {
+ SpecialPowers.pushPrefEnv({ set: [[key, value]] }, resolve);
+ });
+};
+
+// __test(isContent)__.
+// Run all tests.
+var test = async function (isContent) {
+ for (prefValue of [false, true]) {
+ await pushPref("privacy.resistFingerprinting", prefValue);
+ let resisting = prefValue && isContent;
+ expected_values.forEach(function ([key, offVal, onVal]) {
+ testMatch(key, resisting ? onVal : offVal);
+ });
+ testToggles(resisting);
+ testCSS(resisting);
+ if (OS === "Darwin") {
+ testOSXFontSmoothing(resisting);
+ }
+ await testMediaQueriesInPictureElements(resisting);
+ }
+};
diff --git a/layout/style/test/chrome/bug535806-css.css b/layout/style/test/chrome/bug535806-css.css
new file mode 100644
index 0000000000..bda339f776
--- /dev/null
+++ b/layout/style/test/chrome/bug535806-css.css
@@ -0,0 +1 @@
+fooBar[fooBar] { color: green; }
diff --git a/layout/style/test/chrome/bug535806-html.html b/layout/style/test/chrome/bug535806-html.html
new file mode 100644
index 0000000000..e4395da3f3
--- /dev/null
+++ b/layout/style/test/chrome/bug535806-html.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="bug535806-css.css">
+ </head>
+ <body onload="window.parent.wrappedJSObject.htmlLoaded()">
+ </body>
+</html>
diff --git a/layout/style/test/chrome/bug535806-xul.xhtml b/layout/style/test/chrome/bug535806-xul.xhtml
new file mode 100644
index 0000000000..3d9a82b91e
--- /dev/null
+++ b/layout/style/test/chrome/bug535806-xul.xhtml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="data:text/css,fooBar{color:red;}"?>
+<?xml-stylesheet type="text/css" href="bug535806-css.css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="window.parent.wrappedJSObject.xulLoaded()">
+ <fooBar fooBar="" id="s"/>
+</window>
diff --git a/layout/style/test/chrome/chrome-only-media-queries.js b/layout/style/test/chrome/chrome-only-media-queries.js
new file mode 100644
index 0000000000..aaf313a526
--- /dev/null
+++ b/layout/style/test/chrome/chrome-only-media-queries.js
@@ -0,0 +1,34 @@
+const CHROME_ONLY_TOGGLES = [
+ "-moz-is-glyph",
+ "-moz-print-preview",
+ "-moz-scrollbar-start-backward",
+ "-moz-scrollbar-start-forward",
+ "-moz-scrollbar-end-backward",
+ "-moz-scrollbar-end-forward",
+ "-moz-overlay-scrollbars",
+ "-moz-mac-big-sur-theme",
+ "-moz-menubar-drag",
+ "-moz-windows-accent-color-in-titlebar",
+ "-moz-swipe-animation-enabled",
+ "-moz-gtk-csd-available",
+ "-moz-gtk-csd-minimize-button",
+ "-moz-gtk-csd-maximize-button",
+ "-moz-gtk-csd-close-button",
+ "-moz-gtk-csd-reversed-placement",
+ "-moz-panel-animations",
+];
+
+// Non-parseable queries can be tested directly in
+// `test_chrome_only_media_queries.html`.
+const CHROME_ONLY_QUERIES = [
+ "(-moz-platform: linux)",
+ "(-moz-platform: windows)",
+ "(-moz-platform: macos)",
+ "(-moz-platform: android)",
+ "(-moz-content-prefers-color-scheme: dark)",
+ "(-moz-content-prefers-color-scheme: light)",
+ "(-moz-gtk-theme-family: unknown)",
+ "(-moz-gtk-theme-family: adwaita)",
+ "(-moz-gtk-theme-family: breeze)",
+ "(-moz-gtk-theme-family: yaru)",
+];
diff --git a/layout/style/test/chrome/chrome.toml b/layout/style/test/chrome/chrome.toml
new file mode 100644
index 0000000000..8c4c6045d8
--- /dev/null
+++ b/layout/style/test/chrome/chrome.toml
@@ -0,0 +1,51 @@
+[DEFAULT]
+skip-if = ["os == 'android'"]
+support-files = [
+ "bug418986-2.js",
+ "bug535806-css.css",
+ "bug535806-html.html",
+ "bug535806-xul.xhtml",
+ "hover_helper.html",
+ "match.png",
+ "mismatch.png",
+]
+
+["test_bug418986-2.xhtml"]
+
+["test_bug511909.html"]
+
+["test_bug535806.xhtml"]
+
+["test_bug1157097.html"]
+
+["test_bug1346623.html"]
+
+["test_bug1371453.html"]
+
+["test_chrome_only_media_queries.html"]
+support-files = ["chrome-only-media-queries.js"]
+
+["test_constructable_stylesheets_chrome_only_rules.html"]
+
+["test_display_mode.html"]
+support-files = ["display_mode.html"]
+tags = "fullscreen"
+
+["test_display_mode_reflow.html"]
+support-files = ["display_mode_reflow.html"]
+tags = "fullscreen"
+
+["test_hover.html"]
+skip-if = ["true"] # bug 1346353
+
+["test_moz_document_rules.html"]
+
+["test_moz_document_serialization.html"]
+
+["test_scrollbar_inline_size.html"]
+
+["test_stylesheet_clone_import_rule.html"]
+support-files = [
+ "import_useless1.css",
+ "import_useless2.css",
+]
diff --git a/layout/style/test/chrome/display_mode.html b/layout/style/test/chrome/display_mode.html
new file mode 100644
index 0000000000..a4a0afb57e
--- /dev/null
+++ b/layout/style/test/chrome/display_mode.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1104916
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+ for (var n of imports) {
+ window[n] = window.opener.wrappedJSObject[n];
+ }
+
+ /** Test for Display Mode **/
+
+ function waitOneEvent(element, name) {
+ return new Promise(function(resolve, reject) {
+ element.addEventListener(name, function() {
+ resolve();
+ }, {once: true});
+ });
+ }
+
+ function promiseNextTick() {
+ return new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ async function test_task() {
+ var iframe = document.getElementById("subdoc");
+ var subdoc = iframe.contentDocument;
+ var style = subdoc.getElementById("style");
+ var bodyComputedStyled = subdoc.defaultView.getComputedStyle(subdoc.body);
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ function queryApplies(q) {
+ style.setAttribute("media", q);
+ return bodyComputedStyled.getPropertyValue("text-decoration-line") ==
+ "underline";
+ }
+
+ function shouldApply(q) {
+ ok(queryApplies(q), q + " should apply");
+ }
+
+ function shouldNotApply(q) {
+ ok(!queryApplies(q), q + " should not apply");
+ }
+
+ function setDisplayMode(mode) {
+ window.browsingContext.top.displayMode = mode;
+ }
+
+ shouldApply("all and (display-mode: browser)");
+ shouldNotApply("all and (display-mode: fullscreen)");
+ shouldNotApply("all and (display-mode: standalone)");
+ shouldNotApply("all and (display-mode: minimal-ui)");
+
+ // Test entering the OS's fullscreen mode.
+ var fullScreenEntered = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("KEY_F11");
+ await fullScreenEntered;
+ // Wait for the next tick to apply media feature changes. See bug 1430380.
+ await promiseNextTick();
+ shouldApply("all and (display-mode: fullscreen)");
+ shouldNotApply("all and (display-mode: browser)");
+ var fullScreenExited = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("KEY_F11");
+ await fullScreenExited;
+ // Wait for the next tick to apply media feature changes. See bug 1430380.
+ await promiseNextTick();
+ shouldNotApply("all and (display-mode: fullscreen)");
+ shouldApply("all and (display-mode: browser)");
+
+ // Test entering fullscreen through document requestFullScreen.
+ fullScreenEntered = waitOneEvent(document, "mozfullscreenchange");
+ document.body.mozRequestFullScreen();
+ await fullScreenEntered
+ ok(document.mozFullScreenElement, "window entered fullscreen");
+ shouldApply("all and (display-mode: fullscreen)");
+ shouldNotApply("all and (display-mode: browser)");
+ fullScreenExited = waitOneEvent(document, "mozfullscreenchange");
+ document.mozCancelFullScreen();
+ await fullScreenExited;
+ ok(!document.mozFullScreenElement, "window exited fullscreen");
+ shouldNotApply("all and (display-mode: fullscreen)");
+ shouldApply("all and (display-mode: browser)");
+
+ // Test entering display mode mode through docshell
+ setDisplayMode("standalone");
+ shouldApply("all and (display-mode: standalone)");
+ shouldNotApply("all and (display-mode: fullscreen)");
+ shouldNotApply("all and (display-mode: browser)");
+ shouldNotApply("all and (display-mode: minimal-ui)");
+
+ // Test that changes in the display mode are reflected
+ setDisplayMode("minimal-ui");
+ shouldApply("all and (display-mode: minimal-ui)");
+ shouldNotApply("all and (display-mode: standalone)");
+
+ // Set the display mode back.
+ setDisplayMode("browser");
+
+ window.close();
+ window.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="test_task()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1104916">Mozilla Bug 1104916</a>
+<iframe id="subdoc" src="http://mochi.test:8888/tests/layout/style/test/chrome/media_queries_iframe.html" allowfullscreen></iframe>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/display_mode_reflow.html b/layout/style/test/chrome/display_mode_reflow.html
new file mode 100644
index 0000000000..7b2a118cd6
--- /dev/null
+++ b/layout/style/test/chrome/display_mode_reflow.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1256084
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+ for (var n of imports) {
+ window[n] = window.opener.wrappedJSObject[n];
+ }
+
+ /** Test for Display Mode **/
+
+ function waitOneEvent(element, name) {
+ return new Promise(function(resolve, reject) {
+ element.addEventListener(name, function() {
+ resolve();
+ }, {once: true});
+ });
+ }
+
+ function promiseNextTick() {
+ return new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ async function test_task() {
+ var iframe = document.getElementById("subdoc");
+ var subdoc = iframe.contentDocument;
+ var style = subdoc.getElementById("style");
+ var bodyComputedStyled = subdoc.defaultView.getComputedStyle(subdoc.body);
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ var secondDiv = subdoc.getElementById("b");
+ var offsetTop = secondDiv.offsetTop;
+
+ // Test entering the OS's fullscreen mode.
+ var fullScreenEntered = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("KEY_F11");
+ await fullScreenEntered;
+
+ // Wait for the next tick to apply media feature changes. See bug 1430380.
+ await promiseNextTick();
+ ok(offsetTop !== secondDiv.offsetTop, "offset top changes");
+ var fullScreenExited = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("KEY_F11");
+ await fullScreenExited;
+
+ // Wait for the next tick to apply media feature changes. See bug 1430380.
+ await promiseNextTick();
+ ok(offsetTop === secondDiv.offsetTop, "offset top returns to original value");
+
+ offsetTop = secondDiv.offsetTop;
+ // Test entering fullscreen through document requestFullScreen.
+ fullScreenEntered = waitOneEvent(document, "mozfullscreenchange");
+ document.body.mozRequestFullScreen();
+ await fullScreenEntered
+ ok(offsetTop !== secondDiv.offsetTop, "offset top changes");
+ fullScreenExited = waitOneEvent(document, "mozfullscreenchange");
+ document.mozCancelFullScreen();
+ await fullScreenExited;
+ ok(offsetTop === secondDiv.offsetTop, "offset top returns to original value");
+
+ window.close();
+ window.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="test_task()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1256084">Mozilla Bug 1256084</a>
+<iframe id="subdoc" src="http://mochi.test:8888/tests/layout/style/test/chrome/display_mode_reflow_iframe.html"></iframe>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/display_mode_reflow_iframe.html b/layout/style/test/chrome/display_mode_reflow_iframe.html
new file mode 100644
index 0000000000..c05880ce7f
--- /dev/null
+++ b/layout/style/test/chrome/display_mode_reflow_iframe.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US">
+<head>
+ <title>Display Mode Reflow inner frame</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <style type="text/css" id="style" media="all">
+ div {
+ border: 2px solid black;
+ width: 50px;
+ height: 50px;
+ }
+ @media (display-mode: fullscreen) {
+ #a { height: 100px; }
+ }
+ </style>
+</head>
+<body>
+ <div id="a"></div>
+ <div id="b"></div>
+</body>
+</html>
diff --git a/layout/style/test/chrome/hover_empty.html b/layout/style/test/chrome/hover_empty.html
new file mode 100644
index 0000000000..7879e1ce9f
--- /dev/null
+++ b/layout/style/test/chrome/hover_empty.html
@@ -0,0 +1,4 @@
+<html>
+<body>
+</body>
+</html>
diff --git a/layout/style/test/chrome/hover_helper.html b/layout/style/test/chrome/hover_helper.html
new file mode 100644
index 0000000000..b1ae14e8cc
--- /dev/null
+++ b/layout/style/test/chrome/hover_helper.html
@@ -0,0 +1,270 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for :hover</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <style type="text/css">
+
+ div#one { height: 10px; width: 10px; }
+ div#one:hover { background: #00f; }
+ div#one > div { height: 5px; width: 20px; }
+ div#one > div:hover { background: #f00; }
+
+ div#twoparent { overflow: hidden; height: 20px; }
+ div#two { width: 10px; height: 10px; }
+ div#two:hover { margin-left: 5px; background: #0f0; }
+ div#two + iframe { width: 50px; height: 10px; }
+ div#two:hover + iframe { width: 100px; }
+
+ </style>
+</head>
+<!-- need a set timeout because we need things to start after painting suppression ends -->
+<body onload="setTimeout(step1, 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="display" style="position: absolute; top: 0; left: 0; width: 300px; height: 300px">
+
+ <div id="one"><div></div></div>
+
+ <div id="twoparent">
+ <div id="two"></div>
+ <iframe id="twoi" src="hover_empty.html"></iframe>
+ <div style="width: 5000px; height: 10px;"></div>
+ </div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+for (var name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+}
+
+var div = document.getElementById("display");
+var divtwo = document.getElementById("two");
+var iframe = document.getElementById("twoi");
+var divtwoparent = document.getElementById("twoparent");
+
+iframe.contentDocument.open();
+iframe.contentDocument.write("<style type='text/css'>html, body { margin: 0; padding: 0; }<\/style><body>");
+iframe.contentDocument.close();
+
+var moveEvent = { type: "mousemove", clickCount: "0" };
+
+function setResize(str) {
+ var handler = function() {
+ iframe.contentWindow.removeEventListener("resize", arguments.callee);
+ setTimeout(str, 100);
+ };
+ iframe.contentWindow.addEventListener("resize", handler);
+}
+
+function step1() {
+ /** test basic hover **/
+ var divone = document.getElementById("one");
+ synthesizeMouse(divone, 5, 7, moveEvent, window);
+ is(getComputedStyle(divone, "").backgroundColor, "rgb(0, 0, 255)",
+ ":hover applies");
+ is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ synthesizeMouse(divone, 5, 2, moveEvent, window);
+ is(getComputedStyle(divone, "").backgroundColor, "rgb(0, 0, 255)",
+ ":hover applies hierarchically");
+ is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgb(255, 0, 0)",
+ ":hover applies");
+ synthesizeMouse(divone, 15, 7, moveEvent, window);
+ is(getComputedStyle(divone, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ synthesizeMouse(divone, 15, 2, moveEvent, window);
+ is(getComputedStyle(divone, "").backgroundColor, "rgb(0, 0, 255)",
+ ":hover applies hierarchically");
+ is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgb(255, 0, 0)",
+ ":hover applies");
+
+ /** Test for Bug 302561 **/
+ setResize("step2();");
+ is(iframe.contentDocument.body.offsetWidth, 50,
+ ":hover does not apply (iframe body width)");
+ synthesizeMouse(divtwoparent, 7, 5, moveEvent, window);
+ is(iframe.contentDocument.body.offsetWidth, 100,
+ ":hover applies (iframe body width)");
+}
+
+var step2called = false;
+function step2() {
+ is(step2called, false, "step2 called only once");
+ step2called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ is(iframe.contentDocument.body.offsetWidth, 100,
+ ":hover applies (iframe body width)");
+ setResize("step3()");
+ synthesizeMouse(divtwoparent, 2, 5, moveEvent, window);
+ is(iframe.contentDocument.body.offsetWidth, 50,
+ ":hover does not apply (iframe body width)");
+}
+
+var step3called = false;
+function step3() {
+ is(step3called, false, "step3 called only once");
+ step3called = true;
+ if (getComputedStyle(iframe, "").width == "100px") {
+ // The two resize events may be coalesced into a single one.
+ step4();
+ return;
+ }
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setResize("step4()");
+ /* expect to get a second resize from the oscillation */
+}
+
+var step4called = false;
+function step4() {
+ is(step4called, false, "step4 called only once (more than two cycles of oscillation)");
+ if (step4called)
+ return;
+ step4called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ setTimeout(step5, 500); // time to detect oscillations if they exist
+}
+
+var step5called = false;
+function step5() {
+ is(step5called, false, "step5 called only once");
+ step5called = true;
+ setResize("step6()");
+ synthesizeMouse(divtwoparent, 25, 5, moveEvent, window);
+}
+
+var step6called = false;
+function step6() {
+ is(step6called, false, "step6 called only once");
+ step6called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ synthesizeMouse(divtwoparent, 2, 5, moveEvent, window);
+ setTimeout(step7, 500); // time to detect oscillations if they exist
+}
+
+var step7called = false;
+function step7() {
+ is(step7called, false, "step7 called only once (more than two cycles of oscillation)");
+ if (step7called)
+ return;
+ step7called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setTimeout(step8, 500); // time to detect oscillations if they exist
+}
+
+/* test the same case with scrolltop */
+
+var step8called = false;
+function step8() {
+ is(step8called, false, "step8 called only once");
+ step8called = true;
+ iframe.contentDocument.body.removeAttribute("onresize");
+ /* move the mouse out of the way */
+ synthesizeMouse(divtwoparent, 200, 5, moveEvent, window);
+ divtwoparent.scrollLeft = 5;
+ setResize("step9()");
+ synthesizeMouse(divtwoparent, 2, 5, moveEvent, window);
+ /* mouse now over 7, 5 */
+}
+
+var step9called = false;
+function step9() {
+ is(step9called, false, "step9 called only once");
+ step9called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ setResize("step10()");
+ divtwoparent.scrollLeft = 0; /* mouse now over 2,5 */
+}
+
+var step10called = false;
+function step10() {
+ is(step10called, false, "step10 called only once");
+ step10called = true;
+ if (getComputedStyle(iframe, "").width == "100px") {
+ // The two resize events may be coalesced into a single one.
+ step11();
+ return;
+ }
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setResize("step11()");
+ /* expect to get a second resize from the oscillation */
+}
+
+var step11called = false;
+function step11() {
+ is(step11called, false, "step11 called only once (more than two cycles of oscillation)");
+ if (step11called)
+ return;
+ step11called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ setTimeout(step12, 500); // time to detect oscillations if they exist
+}
+
+var step12called = false;
+function step12() {
+ is(step12called, false, "step12 called only once");
+ step12called = true;
+ setResize("step13()");
+ divtwoparent.scrollLeft = 25; /* mouse now over 27,5 */
+}
+
+var step13called = false;
+function step13() {
+ is(step13called, false, "step13 called only once");
+ step13called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setResize("step14()");
+ divtwoparent.scrollLeft = 0; /* mouse now over 2,5 */
+}
+
+var step14called = false;
+function step14() {
+ is(step14called, false, "step14 called only once");
+ step14called = true;
+ if (getComputedStyle(iframe, "").width == "50px") {
+ // The two resize events may be coalesced into a single one.
+ step15();
+ return;
+ }
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ setResize("step15()");
+ /* expect to get a second resize from the oscillation */
+}
+
+var step15called = false;
+function step15() {
+ is(step15called, false, "step15 called only once (more than two cycles of oscillation)");
+ if (step15called)
+ return;
+ step15called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setTimeout(finish, 500); // time to detect oscillations if they exist
+}
+
+function finish() {
+ document.getElementById("display").style.display = "none";
+
+ var tester = window.SimpleTest;
+ window.close();
+ tester.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/import_useless1.css b/layout/style/test/chrome/import_useless1.css
new file mode 100644
index 0000000000..37e1a3d1d9
--- /dev/null
+++ b/layout/style/test/chrome/import_useless1.css
@@ -0,0 +1,3 @@
+.unlikely_to_match_anything {
+ color: black;
+}
diff --git a/layout/style/test/chrome/import_useless2.css b/layout/style/test/chrome/import_useless2.css
new file mode 100644
index 0000000000..37e1a3d1d9
--- /dev/null
+++ b/layout/style/test/chrome/import_useless2.css
@@ -0,0 +1,3 @@
+.unlikely_to_match_anything {
+ color: black;
+}
diff --git a/layout/style/test/chrome/match.png b/layout/style/test/chrome/match.png
new file mode 100644
index 0000000000..d3f299bf58
--- /dev/null
+++ b/layout/style/test/chrome/match.png
Binary files differ
diff --git a/layout/style/test/chrome/mismatch.png b/layout/style/test/chrome/mismatch.png
new file mode 100644
index 0000000000..8f9da3f00f
--- /dev/null
+++ b/layout/style/test/chrome/mismatch.png
Binary files differ
diff --git a/layout/style/test/chrome/moz_document_helper.html b/layout/style/test/chrome/moz_document_helper.html
new file mode 100644
index 0000000000..8b331b19e0
--- /dev/null
+++ b/layout/style/test/chrome/moz_document_helper.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<div id="display" style="position: relative"></div>
diff --git a/layout/style/test/chrome/test_bug1157097.html b/layout/style/test/chrome/test_bug1157097.html
new file mode 100644
index 0000000000..febf4952fb
--- /dev/null
+++ b/layout/style/test/chrome/test_bug1157097.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Test for bug 1157097</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<style>
+.blue { color: blue; }
+.red { color: red; }
+.inline-block { display: inline-block; }
+</style>
+<body onload=run()>
+<p><span id=s1 class=blue><b></b></span><span id=s2 class=red><b></b></span></p>
+<script>
+function run() {
+ window.windowUtils.postRestyleSelfEvent(document.querySelector("p"));
+ document.querySelectorAll("span")[0].className = "";
+ document.querySelectorAll("b")[0].className = "inline-block";
+ document.querySelectorAll("span")[1].className = "blue";
+ window.windowUtils.postRestyleSelfEvent(document.querySelectorAll("b")[1]);
+
+ document.body.offsetTop;
+
+ ok(true, "finished (hopefully we didn't assert)");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
diff --git a/layout/style/test/chrome/test_bug1346623.html b/layout/style/test/chrome/test_bug1346623.html
new file mode 100644
index 0000000000..027f839ace
--- /dev/null
+++ b/layout/style/test/chrome/test_bug1346623.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 1346623</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="startTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1346623">Mozilla Bug 1346623</a>
+<div id="display">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+var winUtils = window.windowUtils;
+
+function startTest() {
+ // load some styles at the agent level
+ var css = `
+ #ac-parent { color: green; }
+ #ac-child.abc { }
+ `;
+ var sheetURL = "data:text/css," + encodeURIComponent(css);
+ winUtils.loadSheetUsingURIString(sheetURL, winUtils.AGENT_SHEET);
+
+ // add canvas anonymous content
+ var bq = document.createElement("blockquote");
+ bq.id = "ac-parent";
+ bq.textContent = "This blockquote text should be green.";
+ var div = document.createElement("div");
+ div.id = "ac-child";
+ div.textContent = " This div text should be green.";
+ bq.appendChild(div);
+ var ac = document.insertAnonymousContent();
+ ac.root.appendChild(bq);
+ document.body.offsetWidth;
+
+ is(getComputedStyle(div).color, "rgb(0, 128, 0)",
+ "color before reframing");
+
+ // reframe the root
+ document.documentElement.style.display = "flex";
+ document.body.offsetWidth;
+
+ // restyle the div
+ div.className = "abc";
+ document.body.offsetWidth;
+
+ is(getComputedStyle(div).color, "rgb(0, 128, 0)",
+ "color after reframing");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_bug1371453.html b/layout/style/test/chrome/test_bug1371453.html
new file mode 100644
index 0000000000..6b3b4cb6eb
--- /dev/null
+++ b/layout/style/test/chrome/test_bug1371453.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Test for Bug 1371453</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<link rel="stylesheet" href="data:text/css,{}">
+<body>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+const Cu = SpecialPowers.Components.utils;
+
+document.styleSheetChangeEventsEnabled = true;
+
+onload = runTest;
+
+async function runTest() {
+ const sheet = document.getElementsByTagName("link")[1].sheet;
+ sheet.insertRule('@import url("blahblah")', 0);
+
+ const rule = sheet.cssRules[0];
+ is(rule.type, CSSRule.IMPORT_RULE, "Got expected import rule.");
+ isnot(rule.styleSheet, null, "Import rule contains a stylesheet.");
+ isnot(rule.media, null, "Import rule contains a media list.");
+ is(rule.href, "blahblah", "Import rule contains expected href.");
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_bug418986-2.xhtml b/layout/style/test/chrome/test_bug418986-2.xhtml
new file mode 100644
index 0000000000..152cac004e
--- /dev/null
+++ b/layout/style/test/chrome/test_bug418986-2.xhtml
@@ -0,0 +1,29 @@
+<?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=418986
+-->
+<window title="Mozilla Bug 418986"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <style id="test-css" scoped="true"></style>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986"
+ target="_blank">Mozilla Bug 418986</a>
+ <p id="display"></p>
+ <p id="pictures"></p>
+ </body>
+
+ <script type="text/javascript" src="bug418986-2.js"></script>
+ <!-- test code goes here -->
+ <script type="text/javascript">
+ // Run all tests now.
+ window.onload = function () {
+ add_task(async function() {
+ await test(false);
+ });
+ };
+ </script>
+</window>
diff --git a/layout/style/test/chrome/test_bug511909.html b/layout/style/test/chrome/test_bug511909.html
new file mode 100644
index 0000000000..fa28bbe854
--- /dev/null
+++ b/layout/style/test/chrome/test_bug511909.html
@@ -0,0 +1,194 @@
+<html><!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=511909
+ --><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>@media and @-moz-document testcases</title>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+
+<style type="text/css">
+a {
+ font-weight: bold;
+}
+ #pink {
+ color: pink;
+ }
+
+ #green {
+ color: green;
+ }
+
+ #blue {
+ color: blue;
+ }
+
+pre {
+ border: 1px solid black;
+}
+</style>
+
+<style type="text/css">
+@-moz-document regexp(".*test_bug511909.*"){
+ #d {
+ color: pink;
+ }
+}
+</style>
+
+<style type="text/css">
+@media screen {
+ #m {
+ color: green;
+ }
+}
+</style>
+
+<style type="text/css">
+@-moz-document regexp(".*test_bug511909.*"){
+ @media screen {
+ #dm {
+ color: blue;
+ }
+ }
+}
+</style>
+
+<!-- should parse -->
+<style type="text/css">
+@media print {
+ @-moz-document regexp("not_this_url"),}
+ #mx {
+ color: pink;
+ }
+ }
+}
+</style>
+
+<!-- should parse -->
+<style type="text/css">
+@-moz-document regexp("not_this_url"){
+ @media print ,}
+ #mxx {
+ color: blue;
+ }
+ }
+}
+</style>
+
+<style type="text/css">
+@media screen {
+ @-moz-document regexp(".*test_bug511909.*"){
+ #md {
+ color: green;
+ }
+ }
+}
+</style>
+
+<style type="text/css">
+@media screen {
+ @-moz-document regexp(".*test_bug511909.*"){
+ @media screen {
+ @-moz-document regexp(".*test_bug511909.*"){
+ @media screen {
+ #me {
+ color: blue;
+ }
+ }
+ }
+ }
+ }
+}
+</style>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511909">Mozilla Bug 511909</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <script class="testbody" type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ // Ensure all the sheets are re-parsed, so that the pref applies.
+ for (const sheet of Array.from(document.querySelectorAll('style'))) {
+ sheet.textContent += "/* dummy */";
+ }
+
+ var pink = getComputedStyle(document.getElementById("pink"), "");
+ var green = getComputedStyle(document.getElementById("green"), "");
+ var blue = getComputedStyle(document.getElementById("blue"), "");
+
+ var cs1 = getComputedStyle(document.getElementById("d"), "");
+ var cs2 = getComputedStyle(document.getElementById("m"), "");
+ var cs3 = getComputedStyle(document.getElementById("dm"), "");
+ var cs4 = getComputedStyle(document.getElementById("md"), "");
+ var cs5 = getComputedStyle(document.getElementById("mx"), "");
+ var cs6 = getComputedStyle(document.getElementById("mxx"), "");
+ var cs7 = getComputedStyle(document.getElementById("me"), "");
+
+ is(cs1.color, pink.color, "@-moz-document applies");
+ is(cs2.color, green.color, "@media applies");
+ is(cs3.color, blue.color, "@media nested in @-moz-document applies");
+ is(cs4.color, green.color, "@-moz-document nested in @media applies");
+ is(cs5.color, pink.color, "broken @media nested in @-moz-document correctly handled");
+ is(cs6.color, blue.color, "broken @-moz-document nested in @media correctly handled");
+ is(cs7.color, blue.color, "@media nested in @-moz-document nested in @media applies");
+ SimpleTest.finish();
+ });
+ </script>
+<div>
+<pre>default style
+</pre>
+<a id="pink">This line should be pink</a><br>
+
+<a id="green">This line should be green</a><br>
+
+<a id="blue">This line should be blue</a><br>
+
+<pre>@-moz-document {...}
+</pre>
+<a id="d">This line should be pink</a><br>
+<pre>@media screen {...}
+</pre>
+<a id="m">This line should be green</a><br>
+<pre>@-moz-document {
+ @media screen {...}
+}
+</pre>
+<a id="dm">This line should be blue</a><br>
+<pre>@media print {
+ @-moz-document regexp("not_this_url"),}
+ #mx {
+ color: pink;
+ }
+ }
+}
+</pre>
+<a id="mx">This line should be pink</a><br></div>
+<pre>@-moz-document regexp("not_this_url"){
+ @media print ,}
+ #mxx {
+ color: blue;
+ }
+ }
+}
+</pre>
+<a id="mxx">This line should be blue</a><br>
+<pre>@media screen {
+ @-moz-documen {...}
+}
+</pre>
+<a id="md">This line should be green</a><br>
+<pre>@media screen {
+ @-moz-document {
+ @media screen {...}
+ }
+}
+</pre>
+<a id="me">This line should be blue</a><br>
+
+
+</body></html>
diff --git a/layout/style/test/chrome/test_bug535806.xhtml b/layout/style/test/chrome/test_bug535806.xhtml
new file mode 100644
index 0000000000..7f4ec286bc
--- /dev/null
+++ b/layout/style/test/chrome/test_bug535806.xhtml
@@ -0,0 +1,43 @@
+<?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=535806
+-->
+<window title="Mozilla Bug 535806"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script 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=535806"
+ target="_blank">Mozilla Bug 535806</a>
+ </body>
+
+ <iframe id="f"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 535806 **/
+ SimpleTest.waitForExplicitFinish();
+
+ window.addEventListener("load", function() {
+ $("f").setAttribute("src", "bug535806-html.html");
+ });
+
+ function htmlLoaded() {
+ $("f").setAttribute("src", "bug535806-xul.xhtml");
+ }
+
+ function xulLoaded() {
+ var doc = $("f").contentDocument;
+ is(doc.defaultView.getComputedStyle(doc.getElementById("s")).color,
+ "rgb(0, 128, 0)");
+ SimpleTest.finish();
+ }
+
+
+ ]]>
+ </script>
+</window>
diff --git a/layout/style/test/chrome/test_chrome_only_media_queries.html b/layout/style/test/chrome/test_chrome_only_media_queries.html
new file mode 100644
index 0000000000..1a2fb098c0
--- /dev/null
+++ b/layout/style/test/chrome/test_chrome_only_media_queries.html
@@ -0,0 +1,84 @@
+<!doctype html>
+<title>Test for parsing of non-content-exposed media-queries.</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script src="chrome-only-media-queries.js"></script>
+<style></style>
+<script>
+const SHEET = document.querySelector('style');
+
+SimpleTest.waitForExplicitFinish();
+
+async function testWithPref() {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["layout.css.forced-colors.enabled", false],
+ ],
+ },
+ r
+ );
+ });
+ expectKnown("(forced-colors: none)");
+ expectKnown("(forced-colors: active)");
+ expectKnown("(forced-colors)");
+ SimpleTest.finish();
+}
+
+function expect(q, shouldBeKnown) {
+ is(matchMedia(q).media, q, "Serialization should roundtrip");
+ is(matchMedia(`${q} or (not ${q})`).matches, shouldBeKnown, `Query should${shouldBeKnown ? "" : " not"} be known`);
+}
+
+function expectKnown(q) {
+ expect(q, true);
+}
+
+function expectUnkown(q) {
+ expect(q, false);
+}
+
+// Test a toggle that should always match for `1` or `0`.
+function testToggle(toggle) {
+ expectKnown(`(${toggle})`);
+ expectKnown(`(${toggle}: 1)`);
+ expectKnown(`(${toggle}: 0)`);
+
+ expectUnkown(`(${toggle}: foo)`);
+ expectUnkown(`(${toggle}: true)`);
+ expectUnkown(`(${toggle}: false)`);
+ expectUnkown(`(${toggle}: -1)`);
+ expectUnkown(`(min-${toggle}: 0)`);
+ expectUnkown(`(max-${toggle}: 0)`);
+ expectUnkown(`(max-${toggle})`);
+ expectUnkown(`(min-${toggle})`);
+
+ let matches_1 = matchMedia(`(${toggle}: 1)`).matches;
+ let matches_0 = matchMedia(`(${toggle}: 0)`).matches;
+ isnot(matches_0, matches_1, `Should not match both true and false: ${toggle}`);
+ is(matches_0 || matches_1, true, `Should match at least one: ${toggle}`);
+}
+
+for (let toggle of CHROME_ONLY_TOGGLES) {
+ testToggle(toggle)
+}
+
+for (let query of CHROME_ONLY_QUERIES) {
+ expectKnown(query);
+}
+
+// These might be exposed to content by pref, we just want to make sure they're
+// always exposed to chrome.
+expectKnown("(prefers-contrast: more)")
+expectKnown("(prefers-contrast: no-preference)")
+expectKnown("(prefers-contrast: less)");
+expectKnown("(prefers-contrast)")
+
+expectKnown("(forced-colors: none)");
+expectKnown("(forced-colors: active)");
+expectKnown("(forced-colors)");
+
+expectUnkown("(-moz-platform: )");
+
+testWithPref();
+</script>
diff --git a/layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html b/layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html
new file mode 100644
index 0000000000..4d9647ba27
--- /dev/null
+++ b/layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Test for chrome-only rules in constructable stylesheets</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ add_task(async function chrome_rules_constructable_stylesheets() {
+ let sheet = new CSSStyleSheet();
+ sheet.replaceSync(".foo { -moz-default-appearance: none }");
+ is(sheet.cssRules[0].style.length, 1, "Should parse chrome-only property in chrome document");
+ });
+</script>
diff --git a/layout/style/test/chrome/test_display_mode.html b/layout/style/test/chrome/test_display_mode.html
new file mode 100644
index 0000000000..69e72d5ab8
--- /dev/null
+++ b/layout/style/test/chrome/test_display_mode.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1104916
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ async function startTest() {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ },
+ r
+ );
+ });
+ // Chrome test run tests in iframe, and fullscreen is disabled by default.
+ // So run the test in a separate window.
+ window.open("display_mode.html", "display_mode", "width=500,height=500,resizable");
+ }
+ </script>
+</head>
+<body onload="startTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1104916">Mozilla Bug 1104916</a>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_display_mode_reflow.html b/layout/style/test/chrome/test_display_mode_reflow.html
new file mode 100644
index 0000000000..01022207f3
--- /dev/null
+++ b/layout/style/test/chrome/test_display_mode_reflow.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1256084
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ async function startTest() {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ },
+ r
+ );
+ });
+ // Chrome test run tests in iframe, and fullscreen is disabled by default.
+ // So run the test in a separate window.
+ window.open("display_mode_reflow.html", "display_mode_reflow", "width=500,height=500,resizable");
+ }
+ </script>
+</head>
+<body onload="startTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1256084">Mozilla Bug 1256084</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_hover.html b/layout/style/test/chrome/test_hover.html
new file mode 100644
index 0000000000..019f537e8c
--- /dev/null
+++ b/layout/style/test/chrome/test_hover.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for :hover</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="startTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="display">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function startTest() {
+ // Run the test in a separate window so that the parent document doesn't have
+ // anything that will cause reflows and dispatch synth mouse moves when we don't
+ // want them and disturb our test.
+ window.open("hover_helper.html", "hover_helper", "width=200,height=300");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_moz_document_rules.html b/layout/style/test/chrome/test_moz_document_rules.html
new file mode 100644
index 0000000000..c28fc964ed
--- /dev/null
+++ b/layout/style/test/chrome/test_moz_document_rules.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for @-moz-document rules</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=398962">Mozilla Bug 398962</a>
+<iframe id="iframe" src="http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+var [gStyleSheetService, gIOService] = (function() {
+ return [
+ Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(Ci.nsIStyleSheetService),
+ Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ ];
+})();
+function set_user_sheet(sheeturi)
+{
+ var uri = gIOService.newURI(sheeturi);
+ gStyleSheetService.loadAndRegisterSheet(uri, gStyleSheetService.USER_SHEET);
+}
+function remove_user_sheet(sheeturi)
+{
+ var uri = gIOService.newURI(sheeturi);
+ gStyleSheetService.unregisterSheet(uri, gStyleSheetService.USER_SHEET);
+}
+
+function run()
+{
+ var iframe = document.getElementById("iframe");
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+ var cs = subwin.getComputedStyle(subdoc.getElementById("display"));
+ var zIndexCounter = 0;
+
+ function test_document_rule(urltests, shouldapply)
+ {
+ var zIndex = ++zIndexCounter;
+ var encodedRule = encodeURI("@-moz-document " + urltests + " { ") +
+ "%23" + // encoded hash character for "#display"
+ encodeURI("display { z-index: " + zIndex + " } }");
+ var sheeturi = "data:text/css," + encodedRule;
+ set_user_sheet(sheeturi);
+ if (shouldapply) {
+ is(cs.zIndex, String(zIndex),
+ "@-moz-document " + urltests +
+ " should apply to this document");
+ } else {
+ is(cs.zIndex, "auto",
+ "@-moz-document " + urltests +
+ " should NOT apply to this document");
+ }
+ remove_user_sheet(sheeturi);
+ }
+
+ test_document_rule("domain(mochi.test)", true);
+ test_document_rule("domain(\"mochi.test\")", true);
+ test_document_rule("domain('mochi.test')", true);
+ test_document_rule("domain('test')", true);
+ test_document_rule("domain(.test)", false);
+ test_document_rule("domain('.test')", false);
+ test_document_rule("domain('ochi.test')", false);
+ test_document_rule("domain(ochi.test)", false);
+ test_document_rule("url-prefix(http://moch)", true);
+ test_document_rule("url-prefix(http://och)", false);
+ test_document_rule("url-prefix(http://mochi.test)", true);
+ test_document_rule("url-prefix(http://mochi.test:88)", true);
+ test_document_rule("url-prefix(http://mochi.test:8888)", true);
+ test_document_rule("url-prefix(http://mochi.test:8888/)", true);
+ test_document_rule("url-prefix('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html')", true);
+ test_document_rule("url-prefix('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.htmlx')", false);
+ test_document_rule("url(http://mochi.test:8888/)", false);
+ test_document_rule("url('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html')", true);
+ test_document_rule("url('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.htmlx')", false);
+ test_document_rule("regexp(.*ochi.*)", false); // syntax error
+ test_document_rule("regexp('.*ochi.*')", true);
+ test_document_rule("regexp('ochi.*')", false);
+ test_document_rule("regexp('.*ochi')", false);
+ test_document_rule("regexp('http:.*ochi.*')", true);
+ test_document_rule("regexp('http:.*ochi')", false);
+ test_document_rule("regexp('http:.*oCHi.*')", false); // case sensitive
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_moz_document_serialization.html b/layout/style/test/chrome/test_moz_document_serialization.html
new file mode 100644
index 0000000000..0707880507
--- /dev/null
+++ b/layout/style/test/chrome/test_moz_document_serialization.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug </title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <style type="text/css" id="style"></style>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+
+var rules = [
+ { rule: "@-moz-document url(http://www.example.com/) {}" },
+ { rule: "@-moz-document url('http://www.example.com/') {}" },
+ { rule: '@-moz-document url("http://www.example.com/") {}' },
+ { rule: "@-moz-document url-prefix('http://www.example.com/') {}" },
+ { rule: '@-moz-document url-prefix("http://www.example.com/") {}' },
+ { rule: "@-moz-document domain('example.com') {}" },
+ { rule: '@-moz-document domain("example.com") {}' },
+ { rule: "@-moz-document regexp('http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/') {}" },
+ { rule: '@-moz-document regexp("http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/") {}' },
+];
+
+SimpleTest.waitForExplicitFinish();
+
+ var style = document.getElementById("style");
+ var style_text = document.createTextNode("");
+ style.appendChild(style_text);
+
+ for (var i in rules) {
+ var obj = rules[i];
+ var rule = obj.rule;
+
+ style_text.data = rule;
+ is(style.sheet.cssRules.length, 1, "should have one rule");
+ var ser1 = style.sheet.cssRules[0].cssText;
+ if ("is_canonical" in obj) {
+ is(ser1, rule, "rule '" + rule + "' should serialize to itself");
+ }
+
+ style_text.data = ser1;
+ is(style.sheet.cssRules.length, 1, "should have one rule");
+ var ser2 = style.sheet.cssRules[0].cssText;
+ is(ser2, ser1,
+ "parse+serialize for rule '" + rule + "' should be idempotent");
+ }
+
+ SimpleTest.finish();
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_scrollbar_inline_size.html b/layout/style/test/chrome/test_scrollbar_inline_size.html
new file mode 100644
index 0000000000..31161a9caf
--- /dev/null
+++ b/layout/style/test/chrome/test_scrollbar_inline_size.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Test for env(scrollbar-inline-size)</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="chrome://global/skin"/>
+<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<div id="scroller" style="width: 100px; height: 100px; overflow: scroll"></div>
+<div id="ref" style="width: env(scrollbar-inline-size, 1000px)"></div>
+<script>
+ SimpleTest.waitForExplicitFinish();
+ async function runTest() {
+ // We need to disable overlay scrollbars to measure the real scrollbar
+ // size.
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.useOverlayScrollbars", 0]],
+ });
+ runOnce();
+
+ info("with full zoom");
+ SpecialPowers.setFullZoom(window, 2.0);
+
+ runOnce();
+ }
+
+ function runOnce() {
+ let scroller = document.getElementById("scroller");
+ let ref = document.getElementById("ref");
+ let scrollbarSize = scroller.getBoundingClientRect().width - scroller.clientWidth;
+ ok(scrollbarSize > 0, "Should have a scrollbar");
+ // clientWidth rounds, so we might see a bit of rounding error
+ isfuzzy(ref.getBoundingClientRect().width, scrollbarSize, 1, "env() should match the scrollbar size");
+ }
+
+ runTest().then(SimpleTest.finish);
+</script>
diff --git a/layout/style/test/chrome/test_stylesheet_clone_import_rule.html b/layout/style/test/chrome/test_stylesheet_clone_import_rule.html
new file mode 100644
index 0000000000..37c3b9ccaa
--- /dev/null
+++ b/layout/style/test/chrome/test_stylesheet_clone_import_rule.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+
+<style>div { color: green; }</style>
+
+<link id="theOnlyLink" rel="stylesheet" type="text/css" href="import_useless1.css">
+
+<div id="theOnlyDiv">This text will change colors several times.</div>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ const Cu = SpecialPowers.Components.utils;
+
+
+ let theOnlyDiv = document.getElementById("theOnlyDiv");
+ let link = document.getElementById("theOnlyLink");
+ let stylesheet = link.sheet;
+
+ runTest().catch(function(reason) {
+ ok(false, "Failed with reason: " + reason);
+ }).then(function() {
+ SimpleTest.finish();
+ });
+
+ function cssRulesToString(cssRules) {
+ return Array.from(cssRules).map(rule => rule.cssText).join('');
+ }
+
+ async function runTest() {
+ // Test that the div is initially red (from base.css)
+ is(getComputedStyle(theOnlyDiv).color, "rgb(0, 128, 0)", "div begins as green.");
+
+ // Insert some import rules.
+ stylesheet.insertRule('@import url("import_useless2.css")', 0);
+ stylesheet.insertRule('@import url("import_useless2.css")', 1);
+
+ // Do some sanity checking of our import rules.
+ let primaryRules = stylesheet.cssRules;
+ await SimpleTest.promiseWaitForCondition(function() {
+ try {
+ primaryRules[0].styleSheet.cssRules;
+ primaryRules[1].styleSheet.cssRules;
+ return true;
+ } catch (ex) {
+ return false;
+ }
+ });
+
+ // Make some helper variables for the comparison tests.
+ let importSheet1 = primaryRules[0].styleSheet;
+ let rules1 = importSheet1.cssRules;
+
+ let importSheet2 = primaryRules[1].styleSheet;
+ let rules2 = importSheet2.cssRules;
+
+ // Confirm that these two sheets are meaningfully the same.
+ is(cssRulesToString(rules1), cssRulesToString(rules2), "Cloned sheet rules are equivalent.");
+
+ // Add a color-changing rule to the first stylesheet.
+ importSheet1.insertRule('div { color: blue; }');
+ rules1 = importSheet1.cssRules;
+
+ // And make sure that it has an effect.
+ is(getComputedStyle(theOnlyDiv).color, "rgb(0, 0, 255)", "div becomes blue.");
+
+ // Make sure that the two sheets have different rules now.
+ isnot(cssRulesToString(rules1), cssRulesToString(rules2), "Cloned sheet rules are no longer equivalent.");
+
+ // Add a color-changing rule to the second stylesheet (that will mask the first).
+ importSheet2.insertRule('div { color: red; }');
+ // And make sure that it has an effect.
+ is(getComputedStyle(theOnlyDiv).color, "rgb(255, 0, 0)", "div becomes red.");
+
+ // Delete the second sheet by removing the import rule, and make sure the color changes back.
+ stylesheet.deleteRule(1);
+ is(getComputedStyle(theOnlyDiv).color, "rgb(0, 0, 255)", "div goes back to blue.");
+
+ // Delete the first sheet by removing the import rule, and make sure the color changes back.
+ stylesheet.deleteRule(0);
+ is(getComputedStyle(theOnlyDiv).color, "rgb(0, 128, 0)", "div goes back to green.");
+ }
+</script>
+</html>
diff --git a/layout/style/test/css_properties_like_longhand.js b/layout/style/test/css_properties_like_longhand.js
new file mode 100644
index 0000000000..606de3ad68
--- /dev/null
+++ b/layout/style/test/css_properties_like_longhand.js
@@ -0,0 +1 @@
+var gShorthandPropertiesLikeLonghand = [{ name: "overflow", prop: "overflow" }];
diff --git a/layout/style/test/descriptor_database.js b/layout/style/test/descriptor_database.js
new file mode 100644
index 0000000000..f5abc8576d
--- /dev/null
+++ b/layout/style/test/descriptor_database.js
@@ -0,0 +1,142 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* vim: set shiftwidth=4 tabstop=4 autoindent cindent noexpandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Each property has the following fields:
+// domProp: The name of the relevant member of nsIDOM[NS]CSS2Properties
+// values: Strings that are values for the descriptor and should be accepted.
+// invalid_values: Things that are not values for the descriptor and
+// should be rejected.
+
+var gCSSFontFaceDescriptors = {
+ "font-family": {
+ domProp: "fontFamily",
+ values: [
+ '"serif"',
+ '"cursive"',
+ "seriff",
+ "Times New Roman",
+ "TimesRoman",
+ '"Times New Roman"',
+ ],
+ /* not clear that the generics are really invalid */
+ invalid_values: [
+ "sans-serif",
+ "Times New Roman, serif",
+ "'Times New Roman', serif",
+ "cursive",
+ "fantasy",
+ "Times )",
+ "Times !",
+ "Times ! foo",
+ "Times ! important",
+ ],
+ },
+ "font-stretch": {
+ domProp: "fontStretch",
+ values: [
+ "normal",
+ "ultra-condensed",
+ "extra-condensed",
+ "condensed",
+ "semi-condensed",
+ "semi-expanded",
+ "expanded",
+ "extra-expanded",
+ "ultra-expanded",
+ ],
+ invalid_values: ["wider", "narrower", "normal ! important", "normal )"],
+ },
+ "font-style": {
+ domProp: "fontStyle",
+ values: ["normal", "italic", "oblique"],
+ invalid_values: [],
+ },
+ "font-weight": {
+ domProp: "fontWeight",
+ values: [
+ "normal",
+ "400",
+ "bold",
+ "100",
+ "200",
+ "300",
+ "500",
+ "600",
+ "700",
+ "800",
+ "900",
+ "107",
+ "399",
+ "401",
+ "699",
+ "710",
+ "calc(1001)",
+ "calc(100 + 1)",
+ "calc(1)",
+ "100.6",
+ "99",
+ "700 900",
+ "300.4 500.4",
+ "calc(200.4) calc(400.4)",
+ ],
+ invalid_values: ["bolder", "lighter", "1001", "0", "0 100", "100 1001"],
+ },
+ src: {
+ domProp: null,
+ values: [
+ "url(404.ttf)",
+ 'url("404.eot")',
+ "url('404.otf')",
+ 'url(404.ttf) format("truetype")',
+ "local(Times New Roman)",
+ "local('Times New Roman')",
+ 'local("Times New Roman")',
+ 'local("serif")',
+ "url(404.ttf) format(truetype)",
+ 'url(404.ttf) format("truetype", "opentype"), url(\'404.eot\')',
+ 'url(404.ttf) format("truetype", "unknown"), local(Times New Roman), url(\'404.eot\')',
+ ],
+ invalid_values: [
+ 'url(404.ttf) format("truetype" "opentype")',
+ 'url(404.ttf) format("truetype",)',
+ 'local("Times New" Roman)',
+ "local(serif)" /* is this valid? */,
+ "url(404.ttf) )",
+ "url(404.ttf) ) foo",
+ "url(404.ttf) ! important",
+ "url(404.ttf) ! hello",
+ 'url(404.ttf) format("truetype", "opentype")',
+ ],
+ },
+ "unicode-range": {
+ domProp: null,
+ values: [
+ "U+0-10FFFF",
+ "U+3-7B3",
+ "U+3??",
+ "U+6A",
+ "U+3????",
+ "U+???",
+ "U+302-302",
+ "U+0-7,U+A-C",
+ "U+3??, U+500-513 ,U+612 , U+4????",
+ "U+1FFF,U+200-27F",
+ ],
+ invalid_values: [
+ "U+1????-2????",
+ "U+0-7,A-C",
+ "U+100-17F,U+200-17F",
+ "U+100-17F,200-27F",
+ "U+6A!important",
+ "U+6A)",
+ ],
+ },
+ "font-display": {
+ domProp: null,
+ values: ["auto", "block", "swap", "fallback", "optional"],
+ invalid_values: ["normal", "initial"],
+ },
+};
diff --git a/layout/style/test/empty.html b/layout/style/test/empty.html
new file mode 100644
index 0000000000..734c5a1c09
--- /dev/null
+++ b/layout/style/test/empty.html
@@ -0,0 +1 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"><html><head><title></title></head><body></body></html> \ No newline at end of file
diff --git a/layout/style/test/file_animations_async_tests.html b/layout/style/test/file_animations_async_tests.html
new file mode 100644
index 0000000000..9d4dfa1fed
--- /dev/null
+++ b/layout/style/test/file_animations_async_tests.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1086937</title>
+ <script>
+ var is = opener.is.bind(opener);
+ var ok = opener.ok.bind(opener);
+ var todo = opener.todo.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <style>
+ /* must use implicit value at one end */
+ @keyframes slide-left { from { margin-left: -1000px } }
+ </style>
+ <script type="application/javascript">
+
+ var gDisplay;
+
+ function run() {
+ gDisplay = document.getElementById("display");
+ opener.SimpleTest.executeSoon(test1);
+ }
+
+ /*
+ * Bug 1086937 - Animations continue correctly across load of
+ * downloadable font.
+ */
+ function test1() {
+ var animdiv = document.createElement("div");
+ // Take control of the refresh driver right from the start
+ advance_clock(0);
+ animdiv.style.animation = "slide-left 100s linear"; // 10px per second
+ gDisplay.appendChild(animdiv);
+ var cs = getComputedStyle(animdiv, "");
+ is(cs.marginLeft, "-1000px", "initial value of animation (force flush)");
+ advance_clock(1000);
+ is(cs.marginLeft, "-990px", "value of animation before font load");
+
+ var font = new FontFace("DownloadedAhem", "url(Ahem.ttf)");
+ document.fonts.add(font);
+
+ var fontdiv = document.createElement("div");
+ fontdiv.appendChild(document.createTextNode("A"));
+ fontdiv.style.fontFamily = "DownloadedAhem";
+ gDisplay.appendChild(fontdiv);
+
+ font.load().then(function(loadedFace) {
+ is(cs.marginLeft, "-990px", "value of animation after font load " +
+ "(clock only advances when we say so)");
+ advance_clock(1000);
+ is(cs.marginLeft, "-980px",
+ "animation should still be advancing after font load");
+
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ document.fonts.delete(font);
+ animdiv.remove();
+ fontdiv.remove();
+
+ finish();
+ });
+ }
+
+ </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086937">Mozilla Bug 1086937</a>
+<div id="display"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/file_animations_omta_scroll.html b/layout/style/test/file_animations_omta_scroll.html
new file mode 100644
index 0000000000..625b4b6c19
--- /dev/null
+++ b/layout/style/test/file_animations_omta_scroll.html
@@ -0,0 +1,392 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <title>Test for css-animations running on the compositor thread with scroll-timeline</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <style type="text/css">
+ @keyframes transform_anim {
+ from { transform: translate(50px); }
+ to { transform: translate(150px); }
+ }
+
+ @keyframes always_fifty {
+ from, to { transform: translate(50px); }
+ }
+
+ @keyframes geometry {
+ from { width: 50px; }
+ to { width: 100px; }
+ }
+
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+
+ .scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ scroll-timeline-name: scroll_timeline;
+ }
+
+ .content {
+ block-size: 100%;
+ padding-block-end: 100px;
+ }
+ </style>
+</head>
+<body>
+ <div id="display"></div>
+ <pre id="test"></pre>
+</body>
+<script type="application/javascript">
+"use strict";
+
+// Global state
+var gScroller = null;
+var gDiv = null;
+
+// Shortcut omta_is and friends by filling in the initial 'elem' argument
+// with gDiv.
+[ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) {
+ var origFn = window[fn];
+ window[fn] = function() {
+ var args = Array.from(arguments);
+ if (!(args[0] instanceof Element)) {
+ args.unshift(gDiv);
+ }
+ return origFn.apply(window, args);
+ };
+});
+
+// Shortcut new_div and done_div to update gDiv
+var originalNewDiv = window.new_div;
+window.new_div = function(style) {
+ [ gDiv ] = originalNewDiv(style);
+};
+var originalDoneDiv = window.done_div;
+window.done_div = function() {
+ originalDoneDiv();
+ gDiv = null;
+};
+
+// Bind the ok and todo to the opener, and close this window when we finish.
+var ok = opener.ok.bind(opener);
+var todo = opener.todo.bind(opener);
+function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+}
+
+function new_scroller() {
+ gScroller = document.createElement('div');
+ gScroller.className = `scroller`;
+
+ let content = document.createElement('div');
+ content.className = 'content';
+
+ gScroller.appendChild(content);
+ document.getElementById("display").appendChild(gScroller);
+ return gScroller;
+}
+
+function done_scroller() {
+ gScroller.firstChild.remove();
+ gScroller.remove();
+ gScroller = null;
+}
+
+waitUntilApzStable().then(() => {
+ runOMTATest(function() {
+ var onAbort = function() {
+ if (gDiv) {
+ done_div();
+ }
+ if (gScroller) {
+ done_scroller();
+ }
+ };
+ runAllAsyncAnimTests(onAbort).then(finish);
+ }, finish);
+});
+
+//----------------------------------------------------------------------
+//
+// Test cases
+//
+//----------------------------------------------------------------------
+
+// The non-omta property with scroll-timeline together with an omta property
+// with document-timeline.
+addAsyncAnimTest(async function() {
+ new_scroller();
+ new_div("animation: geometry 10s scroll_timeline, always_fifty 1s infinite;");
+ await waitForPaintsFlushed();
+
+ // Note: width is not a OMTA property, so it must be running on the main
+ // thread.
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "transform animations should runs on compositor thread");
+
+ done_div();
+ done_scroller();
+});
+
+// transform property with scroll-driven animations.
+addAsyncAnimTest(async function() {
+ let scroller = new_scroller();
+ new_div("animation: transform_anim 1s linear scroll_timeline;");
+ await waitForPaintsFlushed();
+
+ scroller.scrollTop = 50;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ done_div();
+ done_scroller();
+});
+
+
+// The scroll-driven animation with an underlying value and make it go from the
+// active phase to the before phase.
+addAsyncAnimTest(async function() {
+ let scroller = new_scroller();
+ new_div("animation: always_fifty 5s linear 5s scroll_timeline; " +
+ "transform: translate(25px);");
+ await waitForPaintsFlushed();
+
+ // NOTE: getOMTAStyle() can't detect the animation is running on the
+ // compositor during the delay phase.
+ omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either,
+ "The scroll animation is in delay");
+
+ scroller.scrollTop = 75;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ // Use setAsyncScrollOffset() to update apz (compositor thread only) to make
+ // sure Bug 1776077 is reproducible.
+ let utils = SpecialPowers.wrap(window).windowUtils;
+ utils.setAsyncScrollOffset(scroller, 0, -50);
+ utils.advanceTimeAndRefresh(16);
+ utils.restoreNormalRefresh();
+ await waitForPaints();
+
+ // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the
+ // OMTA style directly.
+ let compositorStr =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "matrix(1, 0, 0, 1, 25, 0)",
+ "scroll animations is in delay phase before calling main thread style " +
+ "udpate");
+
+ scroller.scrollTop = 25;
+ await waitForPaintsFlushed();
+
+ compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "",
+ "scroll animation in delay phase clears its OMTA style");
+ omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either,
+ "The scroll animation is in delay");
+
+ done_div();
+ done_scroller();
+});
+
+// The scroll-driven animation without an underlying value and make it go from
+// the active phase to the before phase.
+addAsyncAnimTest(async function() {
+ let scroller = new_scroller();
+ new_div("animation: always_fifty 5s linear 5s scroll_timeline; ");
+ await waitForPaintsFlushed();
+
+ // NOTE: getOMTAStyle() can't detect the animation is running on the
+ // compositor during the delay phase.
+ omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.Either,
+ "The scroll animation is in delay");
+
+ scroller.scrollTop = 75;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ // Use setAsyncScrollOffset() to update apz (compositor thread only) to make
+ // sure Bug 1776077 is reproducible.
+ let utils = SpecialPowers.wrap(window).windowUtils;
+ utils.setAsyncScrollOffset(scroller, 0, -50);
+ utils.advanceTimeAndRefresh(16);
+ utils.restoreNormalRefresh();
+ await waitForPaints();
+
+ // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the
+ // OMTA style directly.
+ let compositorStr =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "matrix(1, 0, 0, 1, 0, 0)",
+ "scroll animations is in delay phase before calling main thread style " +
+ "udpate");
+
+ done_div();
+ done_scroller();
+});
+
+// The scroll-driven animation is in delay, together with other runing
+// animations.
+addAsyncAnimTest(async function() {
+ let scroller = new_scroller();
+ new_div("animation: transform_anim 10s linear -5s paused, " +
+ " always_fifty 5s linear 5s scroll_timeline;");
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor,
+ "The scroll animation is in delay");
+
+ scroller.scrollTop = 75;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ let utils = SpecialPowers.wrap(window).windowUtils;
+ utils.setAsyncScrollOffset(scroller, 0, -50);
+ utils.advanceTimeAndRefresh(16);
+ utils.restoreNormalRefresh();
+ await waitForPaints();
+
+ // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the
+ // OMTA style directly.
+ let compositorStr =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "matrix(1, 0, 0, 1, 100, 0)",
+ "scroll animations is in delay phase before calling main thread style " +
+ "udpate");
+
+ done_div();
+ done_scroller();
+});
+
+addAsyncAnimTest(async function() {
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("style", "width: 200px; height: 200px");
+ iframe.setAttribute("scrolling", "no");
+ iframe.srcdoc =
+ "<!DOCTYPE HTML>" +
+ "<html style='min-height: 100%; padding-bottom: 100px;'>" +
+ "<style>" +
+ "@keyframes anim { from, to { transform: translate(50px) } }" +
+ "</style>" +
+ "<div id='target_in_iframe' " +
+ " style='width:50px; height:50px; background:green;" +
+ " animation: anim 10s linear scroll(root);'>" +
+ "</div>" +
+ "</html>";
+
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ document.body.appendChild(iframe);
+ });
+
+ gDiv = iframe.contentDocument.getElementById("target_in_iframe");
+
+ const root = iframe.contentDocument.scrollingElement;
+ const maxScroll = root.scrollHeight - root.clientHeight;
+ root.scrollTop = 0.5 * maxScroll;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0, RunningOn.MainThread,
+ "scroll transform animations inside an iframe with " +
+ "scrolling:no should run on the main thread");
+
+ gDiv = null;
+ iframe.remove();
+});
+
+// FIXME: Bug 1818346. Support OMTA for view-timeline.
+addAsyncAnimTest(async function() {
+ let scroller = document.createElement("div");
+ scroller.style.width = "100px";
+ scroller.style.height = "100px";
+ // Use hidden so we don't have scrollbar, to make sure the scrollport size
+ // is 100px x 100px, so view progress visibility range is 100px on block axis.
+ scroller.style.overflow = "hidden";
+
+ let content1 = document.createElement("div");
+ content1.style.height = "150px";
+ scroller.appendChild(content1);
+
+ let subject = document.createElement("div");
+ subject.style.width = "50px";
+ subject.style.height = "50px";
+ subject.style.viewTimelineName = "view_timeline";
+ scroller.appendChild(subject);
+
+ // Let |target| be the child of |subject|, so view-timeline-name property of
+ // |subject| is referenceable.
+ let target = document.createElement("div");
+ target.style.width = "10px";
+ target.style.height = "10px";
+ subject.appendChild(target);
+ gDiv = target;
+
+ let content2 = document.createElement("div");
+ content2.style.height = "150px";
+ scroller.appendChild(content2);
+
+ // So the DOM tree looks like this:
+ // <div class=scroller> <!-- "scroller", height: 100px; -->
+ // <div></div> <!-- "", height: 150px -->
+ // <div></div> <!-- "subject", height: 50px; -->
+ // <div></div> <!-- "", height: 150px; -->
+ // </div>
+ // The subject is in view when scroller.scrollTop is [50px, 200px].
+ document.getElementById("display").appendChild(scroller);
+ await waitForPaintsFlushed();
+
+ scroller.scrollTop = 0;
+ target.style.animation = "transform_anim 10s linear";
+ target.style.animationTimeline = "view_timeline";
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.OnMainThread,
+ "The scroll animation is out of view");
+
+ scroller.scrollTop = 50;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.OnMainThread,
+ "The scroll animation is 0%");
+
+ scroller.scrollTop = 125;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.OnMainThread,
+ "The scroll animation is 50%");
+
+ target.remove();
+ content2.remove();
+ subject.remove();
+ content1.remove();
+ scroller.remove();
+ gDiv = null;
+});
+
+</script>
+</html>
diff --git a/layout/style/test/file_animations_omta_scroll_rtl.html b/layout/style/test/file_animations_omta_scroll_rtl.html
new file mode 100644
index 0000000000..771cf6c38f
--- /dev/null
+++ b/layout/style/test/file_animations_omta_scroll_rtl.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+ <title>Test for css-animations running on the compositor thread with
+ scroll-timeline and right to left writing mode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <style type="text/css">
+ @keyframes transform_anim {
+ from { transform: translateX(-100px) }
+ to { transform: translateX(-200px) }
+ }
+
+ html {
+ min-width: 100%;
+ padding-left: 100px;
+ direction: rtl;
+ }
+
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+ </style>
+</head>
+<body>
+ <div id="display"></div>
+ <pre id="test"></pre>
+</body>
+<script type="application/javascript">
+"use strict";
+
+// Global state
+var gDiv = null;
+
+// Shortcut omta_is and friends by filling in the initial 'elem' argument
+// with gDiv.
+[ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) {
+ var origFn = window[fn];
+ window[fn] = function() {
+ var args = Array.from(arguments);
+ if (!(args[0] instanceof Element)) {
+ args.unshift(gDiv);
+ }
+ return origFn.apply(window, args);
+ };
+});
+
+// Shortcut new_div and done_div to update gDiv
+var originalNewDiv = window.new_div;
+window.new_div = function(style) {
+ [ gDiv ] = originalNewDiv(style);
+};
+var originalDoneDiv = window.done_div;
+window.done_div = function() {
+ originalDoneDiv();
+ gDiv = null;
+};
+
+// Bind the ok and todo to the opener, and close this window when we finish.
+var ok = opener.ok.bind(opener);
+var todo = opener.todo.bind(opener);
+function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+}
+
+waitUntilApzStable().then(() => {
+ runOMTATest(function() {
+ var onAbort = function() {
+ if (gDiv) {
+ done_div();
+ }
+ };
+ runAllAsyncAnimTests(onAbort).then(finish);
+ }, finish);
+});
+
+//----------------------------------------------------------------------
+//
+// Test cases
+//
+//----------------------------------------------------------------------
+
+// transform property with scroll-driven animations. The writing mode is from
+// right to left, so we have to use the negative scroll offset.
+addAsyncAnimTest(async function() {
+ new_div("animation: transform_anim 1s linear scroll(horizontal root);");
+ await waitForPaintsFlushed();
+
+ const root = document.scrollingElement;
+ const maxScroll = root.scrollWidth - root.clientWidth;
+ root.scrollLeft = -0.5 * maxScroll;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: -150 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ root.scrollLeft = 0;
+ done_div();
+});
+
+</script>
+</html>
diff --git a/layout/style/test/file_animations_with_disabled_properties.html b/layout/style/test/file_animations_with_disabled_properties.html
new file mode 100644
index 0000000000..5b206df693
--- /dev/null
+++ b/layout/style/test/file_animations_with_disabled_properties.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<head>
+ <meta charset=utf-8>
+ <style>
+ @keyframes enabled-and-disabled {
+ from {
+ left: 0px;
+ overflow-clip-box: padding-box;
+ }
+ to {
+ left: 100px;
+ overflow-clip-box: padding-box;
+ }
+ }
+ </style>
+ <script>
+ var is = opener.is.bind(opener);
+ var ok = opener.ok.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<div id="display"></div>
+<script>
+'use strict';
+
+var display = document.getElementById('display');
+display.style.animation = 'enabled-and-disabled 0.01s';
+
+var animation = display.getAnimations()[0];
+is(animation.effect.getKeyframes().length, 2,
+ 'Got two frames on the generated animation');
+
+ok(animation.effect.getKeyframes()[0].hasOwnProperty('left'),
+ 'Enabled property is set on initial keyframe');
+ok(!animation.effect.getKeyframes()[0].hasOwnProperty('overflowClipBox'),
+ 'Disabled property is not set on initial keyframe');
+
+ok(animation.effect.getKeyframes()[1].hasOwnProperty('left'),
+ 'Enabled property is set on final keyframe');
+ok(!animation.effect.getKeyframes()[1].hasOwnProperty('overflowClipBox'),
+ 'Disabled property is not set on final keyframe');
+
+finish();
+</script>
+</body>
diff --git a/layout/style/test/file_bug1055933_circle-xxl.png b/layout/style/test/file_bug1055933_circle-xxl.png
new file mode 100644
index 0000000000..3223a56900
--- /dev/null
+++ b/layout/style/test/file_bug1055933_circle-xxl.png
Binary files differ
diff --git a/layout/style/test/file_bug1089417_iframe.html b/layout/style/test/file_bug1089417_iframe.html
new file mode 100644
index 0000000000..95208dbc56
--- /dev/null
+++ b/layout/style/test/file_bug1089417_iframe.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1089417</title>
+ <style>
+ html { background: red }
+ @media (min-height: 300px) { html { background: green } }
+ </style>
+ <style id="s">/* empty */</style>
+ <script>
+ document.getElementById("s").disabled = true;
+ </script>
+</head>
+<body>
+
+</body>
+</html>
diff --git a/layout/style/test/file_bug1375944.html b/layout/style/test/file_bug1375944.html
new file mode 100644
index 0000000000..809ea4205b
--- /dev/null
+++ b/layout/style/test/file_bug1375944.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+@font-face {
+ font-family: Ahem;
+ src: url(Ahem.ttf);
+}
+#test {
+ display: inline-block;
+ font: 64px/1 Ahem;
+ margin-right: 1ex;
+}
+</style>
+<div id="test">X</div>
diff --git a/layout/style/test/file_bug1381233.html b/layout/style/test/file_bug1381233.html
new file mode 100644
index 0000000000..b82d309128
--- /dev/null
+++ b/layout/style/test/file_bug1381233.html
@@ -0,0 +1,4 @@
+<link rel="stylesheet" href="somestylesheet">
+<table><td>
+<embed src="whatever" type="application/x-shockwave-flash"></embed>
+</td></table>
diff --git a/layout/style/test/file_bug1443344.css b/layout/style/test/file_bug1443344.css
new file mode 100644
index 0000000000..74e81d1823
--- /dev/null
+++ b/layout/style/test/file_bug1443344.css
@@ -0,0 +1 @@
+#importTarget { color: red ! important }
diff --git a/layout/style/test/file_bug645998-1.css b/layout/style/test/file_bug645998-1.css
new file mode 100644
index 0000000000..328e6ed797
--- /dev/null
+++ b/layout/style/test/file_bug645998-1.css
@@ -0,0 +1 @@
+@import url("file_bug645998-2.css");
diff --git a/layout/style/test/file_bug645998-2.css b/layout/style/test/file_bug645998-2.css
new file mode 100644
index 0000000000..2d5edbe217
--- /dev/null
+++ b/layout/style/test/file_bug645998-2.css
@@ -0,0 +1 @@
+@import url("file_bug645998-1.css");
diff --git a/layout/style/test/file_bug829816.css b/layout/style/test/file_bug829816.css
new file mode 100644
index 0000000000..8f12ba6f56
--- /dev/null
+++ b/layout/style/test/file_bug829816.css
Binary files differ
diff --git a/layout/style/test/file_computed_style_bfcache_display_none.html b/layout/style/test/file_computed_style_bfcache_display_none.html
new file mode 100644
index 0000000000..d366aa68ee
--- /dev/null
+++ b/layout/style/test/file_computed_style_bfcache_display_none.html
@@ -0,0 +1,6 @@
+<div id=div style="display:none">Page 1</div>
+<script>
+ window.onload = function() {
+ opener.postMessage("loaded", "*");
+ }
+</script>
diff --git a/layout/style/test/file_computed_style_bfcache_display_none2.html b/layout/style/test/file_computed_style_bfcache_display_none2.html
new file mode 100644
index 0000000000..c337ca7214
--- /dev/null
+++ b/layout/style/test/file_computed_style_bfcache_display_none2.html
@@ -0,0 +1,6 @@
+<div>Page 2</div>
+<script>
+ window.onload = function() {
+ opener.postMessage("loaded", "*");
+ }
+</script>;
diff --git a/layout/style/test/file_font_loading_api_vframe.html b/layout/style/test/file_font_loading_api_vframe.html
new file mode 100644
index 0000000000..51dbbbee91
--- /dev/null
+++ b/layout/style/test/file_font_loading_api_vframe.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<style></style>
diff --git a/layout/style/test/file_shared_sheet_caching.css b/layout/style/test/file_shared_sheet_caching.css
new file mode 100644
index 0000000000..2ceb1b7e0b
--- /dev/null
+++ b/layout/style/test/file_shared_sheet_caching.css
@@ -0,0 +1,3 @@
+:root {
+ background-color: lime;
+}
diff --git a/layout/style/test/file_shared_sheet_caching.html b/layout/style/test/file_shared_sheet_caching.html
new file mode 100644
index 0000000000..1eb9a8ce31
--- /dev/null
+++ b/layout/style/test/file_shared_sheet_caching.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="stylesheet" href="file_shared_sheet_caching.css">
+<script>
+onload = function() {
+ if (parent != window) {
+ parent.childWindowLoaded(window);
+ } else {
+ window.opener.childWindowLoaded(window);
+ }
+}
+</script>
diff --git a/layout/style/test/file_specified_value_serialization_individual_transforms.html b/layout/style/test/file_specified_value_serialization_individual_transforms.html
new file mode 100644
index 0000000000..9dcf85f955
--- /dev/null
+++ b/layout/style/test/file_specified_value_serialization_individual_transforms.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for Bug 1207734 (individual transforms)</title>
+<!--
+ FIXME: This is only here in a separate file since it needs the
+ layout.css.individual-transform.enabled pref to be set when it runs and the
+ pref= annotation in mochitest.ini doesn't work on Android (bug 1393326).
+ Once we turn on that pref by default or fix bug 1393326 we can move this back
+ into test_specified_value_serialization.html.
+-->
+<script>
+const is = opener.is.bind(opener);
+function finish() {
+ const o = opener;
+ self.close();
+ o.SimpleTest.finish();
+}
+
+function runTest() {
+ // Test for rotate property serialization.
+ [
+ [" 90deg ", "90deg"],
+ [" 100grad ", "100grad"],
+ [" 100gRaD ", "100grad"],
+ [" 0.25turn ", "0.25turn"],
+ [" 0.25tUrN ", "0.25turn"],
+ [" 1.57RaD ", "1.57rad"],
+ ].forEach(function(arr) {
+ document.documentElement.style.rotate = arr[0];
+ is(document.documentElement.style.rotate, arr[1],
+ "bug-1207734: incorrect rotate serialization");
+ });
+ document.documentElement.style.rotate = "";
+
+ // Test for translate property serialization.
+ [
+ [" 50% 5px 6px ", "50% 5px 6px"],
+ [" 50% 10px 100px ", "50% 10px 100px"],
+ [" 4px 5px ", "4px 5px"],
+ [" 10% 10% 99px ", "10% 10% 99px"],
+ [" 50px ", "50px"],
+ ].forEach(function(arr) {
+ document.documentElement.style.translate = arr[0];
+ is(document.documentElement.style.translate, arr[1],
+ "bug-1207734: incorrect translate serialization");
+ });
+ document.documentElement.style.translate = "";
+
+ // Test for scale property serialization.
+ [
+ [" 10 ", "10"],
+ [" 10 20.5 ", "10 20.5"],
+ [" 10 20 30 ", "10 20 30"],
+ ].forEach(function(arr) {
+ document.documentElement.style.scale = arr[0];
+ is(document.documentElement.style.scale, arr[1],
+ "bug-1207734: incorrect scale serialization");
+ });
+
+ document.documentElement.style.scale = "";
+}
+
+runTest();
+finish();
+</script>
diff --git a/layout/style/test/flexbox_layout_testcases.js b/layout/style/test/flexbox_layout_testcases.js
new file mode 100644
index 0000000000..c8990877ab
--- /dev/null
+++ b/layout/style/test/flexbox_layout_testcases.js
@@ -0,0 +1,1317 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 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/. */
+
+/**
+ * For the purposes of this test, flex items are specified as a hash with a
+ * hash-entry for each CSS property that is to be set. In these per-property
+ * entries, the key is the property-name, and the value can be either of the
+ * following:
+ * (a) the property's specified value (which indicates that we don't need to
+ * bother checking the computed value of this particular property)
+ * ...OR...
+ * (b) an array with 2-3 entries...
+ * [specifiedValue, expectedComputedValue (, epsilon) ]
+ * ...which indicates that the property's computed value should be
+ * checked. The array's first entry (for the specified value) may be
+ * null; this means that no value should be explicitly specified for this
+ * property. The second entry is the property's expected computed
+ * value. The third (optional) entry is an epsilon value, which allows for
+ * fuzzy equality when testing the computed value.
+ *
+ * To allow these testcases to be re-used in both horizontal and vertical
+ * flex containers, we specify "width"/"min-width"/etc. using the aliases
+ * "_main-size", "_min-main-size", etc. The test code can map these
+ * placeholder names to their corresponding property-names using the maps
+ * defined below -- gRowPropertyMapping, gColumnPropertyMapping, etc.
+ *
+ * If the testcase needs to customize its flex container at all (e.g. by
+ * specifying a custom container-size), it can do so by including a hash
+ * called "container_properties", with propertyName:propertyValue mappings.
+ * (This hash can use aliased property-names like "_main-size" as well.)
+ */
+
+// The standard main-size we'll use for our flex container when setting up
+// the testcases defined below:
+var gDefaultFlexContainerSize = "200px";
+
+// Left-to-right versions of placeholder property-names used in
+// testcases below:
+var gRowPropertyMapping = {
+ "_main-size": "width",
+ "_min-main-size": "min-width",
+ "_max-main-size": "max-width",
+ "_border-main-start-width": "border-left-width",
+ "_border-main-end-width": "border-right-width",
+ "_padding-main-start": "padding-left",
+ "_padding-main-end": "padding-right",
+ "_margin-main-start": "margin-left",
+ "_margin-main-end": "margin-right",
+};
+
+// Right-to-left versions of placeholder property-names used in
+// testcases below:
+var gRowReversePropertyMapping = {
+ "_main-size": "width",
+ "_min-main-size": "min-width",
+ "_max-main-size": "max-width",
+ "_border-main-start-width": "border-right-width",
+ "_border-main-end-width": "border-left-width",
+ "_padding-main-start": "padding-right",
+ "_padding-main-end": "padding-left",
+ "_margin-main-start": "margin-right",
+ "_margin-main-end": "margin-left",
+};
+
+// Top-to-bottom versions of placeholder property-names used in
+// testcases below:
+var gColumnPropertyMapping = {
+ "_main-size": "height",
+ "_min-main-size": "min-height",
+ "_max-main-size": "max-height",
+ "_border-main-start-width": "border-top-width",
+ "_border-main-end-width": "border-bottom-width",
+ "_padding-main-start": "padding-top",
+ "_padding-main-end": "padding-bottom",
+ "_margin-main-start": "margin-top",
+ "_margin-main-end": "margin-bottom",
+};
+
+// Bottom-to-top versions of placeholder property-names used in
+// testcases below:
+var gColumnReversePropertyMapping = {
+ "_main-size": "height",
+ "_min-main-size": "min-height",
+ "_max-main-size": "max-height",
+ "_border-main-start-width": "border-bottom-width",
+ "_border-main-end-width": "border-top-width",
+ "_padding-main-start": "padding-bottom",
+ "_padding-main-end": "padding-top",
+ "_margin-main-start": "margin-bottom",
+ "_margin-main-end": "margin-top",
+};
+
+// The list of actual testcase definitions:
+var gFlexboxTestcases = [
+ // No flex properties specified --> should just use 'width' for sizing
+ {
+ items: [
+ { "_main-size": ["40px", "40px"] },
+ { "_main-size": ["65px", "65px"] },
+ ],
+ },
+ // flex-basis is specified:
+ {
+ items: [
+ { "flex-basis": "50px", "_main-size": [null, "50px"] },
+ {
+ "flex-basis": "20px",
+ "_main-size": [null, "20px"],
+ },
+ ],
+ },
+ // flex-basis is *large* -- sum of flex-basis values is > flex container size:
+ // (w/ 0 flex-shrink so we don't shrink):
+ {
+ items: [
+ {
+ flex: "0 0 150px",
+ "_main-size": [null, "150px"],
+ },
+ {
+ flex: "0 0 90px",
+ "_main-size": [null, "90px"],
+ },
+ ],
+ },
+ // flex-basis is *large* -- each flex-basis value is > flex container size:
+ // (w/ 0 flex-shrink so we don't shrink):
+ {
+ items: [
+ {
+ flex: "0 0 250px",
+ "_main-size": [null, "250px"],
+ },
+ {
+ flex: "0 0 400px",
+ "_main-size": [null, "400px"],
+ },
+ ],
+ },
+ // flex-basis has percentage value:
+ {
+ items: [
+ {
+ "flex-basis": "30%",
+ "_main-size": [null, "60px"],
+ },
+ {
+ "flex-basis": "45%",
+ "_main-size": [null, "90px"],
+ },
+ ],
+ },
+ // flex-basis has calc(percentage) value:
+ {
+ items: [
+ {
+ "flex-basis": "calc(20%)",
+ "_main-size": [null, "40px"],
+ },
+ {
+ "flex-basis": "calc(80%)",
+ "_main-size": [null, "160px"],
+ },
+ ],
+ },
+ // flex-basis has calc(percentage +/- length) value:
+ {
+ items: [
+ {
+ "flex-basis": "calc(10px + 20%)",
+ "_main-size": [null, "50px"],
+ },
+ {
+ "flex-basis": "calc(60% - 1px)",
+ "_main-size": [null, "119px"],
+ },
+ ],
+ },
+ // flex-grow is specified:
+ {
+ items: [
+ {
+ flex: "1",
+ "_main-size": [null, "60px"],
+ },
+ {
+ flex: "2",
+ "_main-size": [null, "120px"],
+ },
+ {
+ flex: "0 20px",
+ "_main-size": [null, "20px"],
+ },
+ ],
+ },
+ // Same ratio as prev. testcase; making sure we handle float inaccuracy
+ {
+ items: [
+ {
+ flex: "100000",
+ "_main-size": [null, "60px"],
+ },
+ {
+ flex: "200000",
+ "_main-size": [null, "120px"],
+ },
+ {
+ flex: "0.000001 20px",
+ "_main-size": [null, "20px"],
+ },
+ ],
+ },
+ // Same ratio as prev. testcase, but with items cycled and w/
+ // "flex: none" & explicit size instead of "flex: 0 20px"
+ {
+ items: [
+ {
+ flex: "none",
+ "_main-size": ["20px", "20px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "60px"],
+ },
+ {
+ flex: "2",
+ "_main-size": [null, "120px"],
+ },
+ ],
+ },
+
+ // ...and now with flex-grow:[huge] to be sure we handle infinite float values
+ // gracefully.
+ {
+ items: [
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "200px"],
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "99999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "99999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "99999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "99999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ ],
+ },
+
+ // And now, some testcases to check that we handle float accumulation error
+ // gracefully.
+
+ // First, a testcase with just a custom-sized huge container, to be sure we'll
+ // be able to handle content on that scale, in the subsequent more-complex
+ // testcases:
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "1",
+ "_main-size": [null, "9000000px"],
+ },
+ ],
+ },
+ // ...and now with two flex items dividing up that container's huge size:
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "2",
+ "_main-size": [null, "6000000px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "3000000px"],
+ },
+ ],
+ },
+
+ // OK, now to actually test accumulation error. Below, we have six flex items
+ // splitting up the container's size, with huge differences between flex
+ // weights. For simplicity, I've set up the weights so that they sum exactly
+ // to the container's size in px. So 1 unit of flex *should* get you 1px.
+ //
+ // NOTE: The expected computed "_main-size" values for the flex items below
+ // appear to add up to more than their container's size, which would suggest
+ // that they overflow their container unnecessarily. But they don't actually
+ // overflow -- this discrepancy is simply because Gecko's code for reporting
+ // computed-sizes rounds to 6 significant figures (in particular, the method
+ // (nsTSubstring_CharT::AppendFloat() does this). Internally, in app-units,
+ // the child frames' main-sizes add up exactly to the container's main-size,
+ // as you'd hope & expect.
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "3000000",
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px"],
+ },
+ {
+ flex: "2999999",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999998",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px", 0.2],
+ },
+ ],
+ },
+ // Same flex items as previous testcase, but now reordered such that the items
+ // with tiny flex weights are all listed last:
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "3000000",
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999999",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999998",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px", 0.2],
+ },
+ ],
+ },
+ // Same flex items as previous testcase, but now reordered such that the items
+ // with tiny flex weights are all listed first:
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "1",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths:
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "1",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths:
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "1",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths:
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "3000000",
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999999",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999998",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ ],
+ },
+
+ // Trying "flex: auto" (== "1 1 auto") w/ a mix of flex-grow/flex-basis values
+ {
+ items: [
+ {
+ flex: "auto",
+ "_main-size": [null, "45px"],
+ },
+ {
+ flex: "2",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "20px 1 0",
+ "_main-size": [null, "65px"],
+ },
+ ],
+ },
+ // Same as previous, but with items cycled & different syntax
+ {
+ items: [
+ {
+ flex: "20px",
+ "_main-size": [null, "65px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "45px"],
+ },
+ {
+ flex: "2",
+ "_main-size": [null, "90px"],
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "2",
+ "_main-size": [null, "100px"],
+ border: "0px dashed",
+ "_border-main-start-width": ["5px", "5px"],
+ "_border-main-end-width": ["15px", "15px"],
+ "_margin-main-start": ["22px", "22px"],
+ "_margin-main-end": ["8px", "8px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "50px"],
+ "_margin-main-start": ["auto", "0px"],
+ "_padding-main-end": ["auto", "0px"],
+ },
+ ],
+ },
+ // Test negative flexibility:
+
+ // Basic testcase: just 1 item (relying on initial "flex-shrink: 1") --
+ // should shrink to container size.
+ {
+ items: [{ "_main-size": ["400px", "200px"] }],
+ },
+ // ...and now with a "flex" specification and a different flex-shrink value:
+ {
+ items: [
+ {
+ flex: "4 2 250px",
+ "_main-size": [null, "200px"],
+ },
+ ],
+ },
+ // ...and now with multiple items, which all shrink proportionally (by 50%)
+ // to fit to the container, since they have the same (initial) flex-shrink val
+ {
+ items: [
+ { "_main-size": ["80px", "40px"] },
+ { "_main-size": ["40px", "20px"] },
+ { "_main-size": ["30px", "15px"] },
+ { "_main-size": ["250px", "125px"] },
+ ],
+ },
+ // ...and now with positive flexibility specified. (should have no effect, so
+ // everything still shrinks by the same proportion, since the flex-shrink
+ // values are all the same).
+ {
+ items: [
+ {
+ flex: "4 3 100px",
+ "_main-size": [null, "80px"],
+ },
+ {
+ flex: "5 3 50px",
+ "_main-size": [null, "40px"],
+ },
+ {
+ flex: "0 3 100px",
+ "_main-size": [null, "80px"],
+ },
+ ],
+ },
+ // ...and now with *different* flex-shrink values:
+ {
+ items: [
+ {
+ flex: "4 2 50px",
+ "_main-size": [null, "30px"],
+ },
+ {
+ flex: "5 3 50px",
+ "_main-size": [null, "20px"],
+ },
+ {
+ flex: "0 0 150px",
+ "_main-size": [null, "150px"],
+ },
+ ],
+ },
+ // Same ratio as prev. testcase; making sure we handle float inaccuracy
+ {
+ items: [
+ {
+ flex: "4 20000000 50px",
+ "_main-size": [null, "30px"],
+ },
+ {
+ flex: "5 30000000 50px",
+ "_main-size": [null, "20px"],
+ },
+ {
+ flex: "0 0.0000001 150px",
+ "_main-size": [null, "150px"],
+ },
+ ],
+ },
+ // Another "different flex-shrink values" testcase:
+ {
+ items: [
+ {
+ flex: "4 2 115px",
+ "_main-size": [null, "69px"],
+ },
+ {
+ flex: "5 1 150px",
+ "_main-size": [null, "120px"],
+ },
+ {
+ flex: "1 4 30px",
+ "_main-size": [null, "6px"],
+ },
+ {
+ flex: "1 0 5px",
+ "_main-size": [null, "5px"],
+ },
+ ],
+ },
+
+ // ...and now with min-size (clamping the effects of flex-shrink on one item):
+ {
+ items: [
+ {
+ flex: "4 5 75px",
+ "_min-main-size": "50px",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "5 5 100px",
+ "_main-size": [null, "62.5px"],
+ },
+ {
+ flex: "0 4 125px",
+ "_main-size": [null, "87.5px"],
+ },
+ ],
+ },
+
+ // Test a min-size that's much larger than initial preferred size, but small
+ // enough that our flexed size pushes us over it:
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "110px",
+ "_main-size": ["50px", "125px"],
+ },
+ {
+ flex: "auto",
+ "_main-size": [null, "75px"],
+ },
+ ],
+ },
+
+ // Test a min-size that's much larger than initial preferred size, and is
+ // even larger than our positively-flexed size, so that we have to increase it
+ // (as a 'min violation') after we've flexed.
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "150px",
+ "_main-size": ["50px", "150px"],
+ },
+ {
+ flex: "auto",
+ "_main-size": [null, "50px"],
+ },
+ ],
+ },
+
+ // Test min-size on multiple items simultaneously:
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "20px",
+ "_main-size": [null, "20px"],
+ },
+ {
+ flex: "9 auto",
+ "_min-main-size": "150px",
+ "_main-size": ["50px", "180px"],
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "1 1 0px",
+ "_min-main-size": "90px",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "1 1 0px",
+ "_min-main-size": "80px",
+ "_main-size": [null, "80px"],
+ },
+ {
+ flex: "1 1 40px",
+ "_main-size": [null, "30px"],
+ },
+ ],
+ },
+
+ // Test a case where _min-main-size will be violated on different items in
+ // successive iterations of the "resolve the flexible lengths" loop
+ {
+ items: [
+ {
+ flex: "1 2 100px",
+ "_min-main-size": "90px",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "1 1 100px",
+ "_min-main-size": "70px",
+ "_main-size": [null, "70px"],
+ },
+ {
+ flex: "1 1 100px",
+ "_main-size": [null, "40px"],
+ },
+ ],
+ },
+
+ // Test some cases that have a min-size violation on one item and a
+ // max-size violation on another:
+
+ // Here, both items initially grow to 100px. That violates both
+ // items' sizing constraints (it's smaller than the min-size and larger than
+ // the max-size), so we clamp both of them and sum the clamping-differences:
+ //
+ // (130px - 100px) + (50px - 100px) = (30px) + (-50px) = -20px
+ //
+ // This sum is negative, so (per spec) we freeze the item that had its
+ // max-size violated (the second one) and restart the algorithm. This time,
+ // all the available space (200px - 50px = 150px) goes to the not-yet-frozen
+ // first item, and that puts it above its min-size, so all is well.
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "130px",
+ "_main-size": [null, "150px"],
+ },
+ {
+ flex: "auto",
+ "_max-main-size": "50px",
+ "_main-size": [null, "50px"],
+ },
+ ],
+ },
+
+ // As above, both items initially grow to 100px, and that violates both items'
+ // constraints. However, now the sum of the clamping differences is:
+ //
+ // (130px - 100px) + (80px - 100px) = (30px) + (-20px) = 10px
+ //
+ // This sum is positive, so (per spec) we freeze the item that had its
+ // min-size violated (the first one) and restart the algorithm. This time,
+ // all the available space (200px - 130px = 70px) goes to the not-yet-frozen
+ // second item, and that puts it below its max-size, so all is well.
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "130px",
+ "_main-size": [null, "130px"],
+ },
+ {
+ flex: "auto",
+ "_max-main-size": "80px",
+ "_main-size": [null, "70px"],
+ },
+ ],
+ },
+
+ // As above, both items initially grow to 100px, and that violates both items'
+ // constraints. So we clamp both items and sum the clamping differences to
+ // see what to do next. The sum is:
+ //
+ // (80px - 100px) + (120px - 100px) = (-20px) + (20px) = 0px
+ //
+ // Per spec, if the sum is 0, we're done -- we leave both items at their
+ // clamped sizes.
+ {
+ items: [
+ {
+ flex: "auto",
+ "_max-main-size": "80px",
+ "_main-size": [null, "80px"],
+ },
+ {
+ flex: "auto",
+ "_min-main-size": "120px",
+ "_main-size": [null, "120px"],
+ },
+ ],
+ },
+
+ // Test cases where flex-grow sums to less than 1:
+ // ===============================================
+ // This makes us treat the flexibilities like "fraction of free space"
+ // instead of weights, so that e.g. a single item with "flex-grow: 0.1"
+ // will only get 10% of the free space instead of all of the free space.
+
+ // Basic cases where flex-grow sum is less than 1:
+ {
+ items: [
+ {
+ flex: "0.1 100px",
+ "_main-size": [null, "110px"], // +10% of free space
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "0.8 0px",
+ "_main-size": [null, "160px"], // +80% of free space
+ },
+ ],
+ },
+
+ // ... and now with two flex items:
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "110px"], // +40% of free space
+ },
+ {
+ flex: "0.2 30px",
+ "_main-size": [null, "50px"], // +20% of free space
+ },
+ ],
+ },
+
+ // ...and now with max-size modifying how much free space one item can take:
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "110px"], // +40% of free space
+ },
+ {
+ flex: "0.2 30px",
+ "_max-main-size": "35px",
+ "_main-size": [null, "35px"], // +20% free space, then clamped
+ },
+ ],
+ },
+ // ...and now with a max-size smaller than our flex-basis:
+ // (This makes us freeze the second item right away, before we compute
+ // the initial free space.)
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "118px"], // +40% of 200px-70px-10px
+ },
+ {
+ flex: "0.2 30px",
+ "_max-main-size": "10px",
+ "_main-size": [null, "10px"], // immediately frozen
+ },
+ ],
+ },
+ // ...and now with a max-size and a huge flex-basis, such that we initially
+ // have negative free space, which makes the "% of [original] free space"
+ // calculations a bit more subtle. We set the "original free space" after
+ // we've clamped the second item (the first time the free space is positive).
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "118px"], // +40% of free space _after freezing
+ // the other item_
+ },
+ {
+ flex: "0.2 150px",
+ "_max-main-size": "10px",
+ "_main-size": [null, "10px"], // clamped immediately
+ },
+ ],
+ },
+
+ // Now with min-size modifying how much free space our items take:
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "110px"], // +40% of free space
+ },
+ {
+ flex: "0.2 30px",
+ "_min-main-size": "70px",
+ "_main-size": [null, "70px"], // +20% free space, then clamped
+ },
+ ],
+ },
+
+ // ...and now with a large enough min-size that it prevents the other flex
+ // item from taking its full desired portion of the original free space:
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "80px"], // (Can't take my full +40% of
+ // free space due to other item's
+ // large min-size.)
+ },
+ {
+ flex: "0.2 30px",
+ "_min-main-size": "120px",
+ "_main-size": [null, "120px"], // +20% free space, then clamped
+ },
+ ],
+ },
+ // ...and now with a large-enough min-size that it pushes the other flex item
+ // to actually shrink a bit (with default "flex-shrink:1"):
+ {
+ items: [
+ {
+ flex: "0.3 30px",
+ "_main-size": [null, "20px"], // -10px, instead of desired +45px
+ },
+ {
+ flex: "0.2 20px",
+ "_min-main-size": "180px",
+ "_main-size": [null, "180px"], // +160px, instead of desired +30px
+ },
+ ],
+ },
+
+ // In this case, the items' flexibilities don't initially sum to < 1, but they
+ // do after we freeze the third item for violating its max-size.
+ {
+ items: [
+ {
+ flex: "0.3 30px",
+ "_main-size": [null, "75px"],
+ // 1st loop: desires (0.3 / 5) * 150px = 9px. Tentatively granted.
+ // 2nd loop: desires 0.3 * 150px = 45px. Tentatively granted.
+ // 3rd loop: desires 0.3 * 150px = 45px. Granted +45px.
+ },
+ {
+ flex: "0.2 20px",
+ "_max-main-size": "30px",
+ "_main-size": [null, "30px"],
+ // First loop: desires (0.2 / 5) * 150px = 6px. Tentatively granted.
+ // Second loop: desires 0.2 * 150px = 30px. Frozen at +10px.
+ },
+ {
+ flex: "4.5 0px",
+ "_max-main-size": "20px",
+ "_main-size": [null, "20px"],
+ // First loop: desires (4.5 / 5) * 150px = 135px. Frozen at +20px.
+ },
+ ],
+ },
+
+ // Make sure we calculate "original free space" correctly when one of our
+ // flex items will be clamped right away, due to max-size preventing it from
+ // growing at all:
+ {
+ // Here, the second flex item is effectively inflexible; it's
+ // immediately frozen at 40px since we're growing & this item's max size
+ // trivially prevents it from growing. This leaves us with an "original
+ // free space" of 60px. The first flex item takes half of that, due to
+ // its flex-grow value of 0.5.
+ items: [
+ {
+ flex: "0.5 100px",
+ "_main-size": [null, "130px"],
+ },
+ {
+ flex: "1 98px",
+ "_max-main-size": "40px",
+ "_main-size": [null, "40px"],
+ },
+ ],
+ },
+ {
+ // Same as previous example, but with a larger flex-basis on the second
+ // element (which shouldn't ultimately matter, because its max size clamps
+ // its size immediately anyway).
+ items: [
+ {
+ flex: "0.5 100px",
+ "_main-size": [null, "130px"],
+ },
+ {
+ flex: "1 101px",
+ "_max-main-size": "40px",
+ "_main-size": [null, "40px"],
+ },
+ ],
+ },
+
+ {
+ // Here, the third flex item is effectively inflexible; it's immediately
+ // frozen at 0px since we're growing & this item's max size trivially
+ // prevents it from growing. This leaves us with an "original free space" of
+ // 100px. The first flex item takes 40px, and the third takes 50px, due to
+ // their flex values of 0.4 and 0.5.
+ items: [
+ {
+ flex: "0.4 50px",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "0.5 50px",
+ "_main-size": [null, "100px"],
+ },
+ {
+ flex: "0 90px",
+ "_max-main-size": "0px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+ {
+ // Same as previous example, but with slightly larger flex-grow values on
+ // the first and second items, which sum to 1.0 and produce slightly larger
+ // main sizes. This demonstrates that there's no discontinuity between the
+ // "< 1.0 sum" to ">= 1.0 sum" behavior, in this situation at least.
+ items: [
+ {
+ flex: "0.45 50px",
+ "_main-size": [null, "95px"],
+ },
+ {
+ flex: "0.55 50px",
+ "_main-size": [null, "105px"],
+ },
+ {
+ flex: "0 90px",
+ "_max-main-size": "0px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+
+ // Test cases where flex-shrink sums to less than 1:
+ // =================================================
+ // This makes us treat the flexibilities more like "fraction of (negative)
+ // free space" instead of weights, so that e.g. a single item with
+ // "flex-shrink: 0.1" will only shrink by 10% of amount that it overflows
+ // its container by.
+ //
+ // It gets a bit more complex when there are multiple flex items, because
+ // flex-shrink is scaled by the flex-basis before it's used as a weight. But
+ // even with that scaling, the general principal is that e.g. if the
+ // flex-shrink values *sum* to 0.6, then the items will collectively only
+ // shrink by 60% (and hence will still overflow).
+
+ // Basic cases where flex-grow sum is less than 1:
+ {
+ items: [
+ {
+ flex: "0 0.1 300px",
+ "_main-size": [null, "290px"], // +10% of (negative) free space
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "0 0.8 400px",
+ "_main-size": [null, "240px"], // +80% of (negative) free space
+ },
+ ],
+ },
+
+ // ...now with two flex items, with the same flex-basis value:
+ {
+ items: [
+ {
+ flex: "0 0.4 150px",
+ "_main-size": [null, "110px"], // +40% of (negative) free space
+ },
+ {
+ flex: "0 0.2 150px",
+ "_main-size": [null, "130px"], // +20% of (negative) free space
+ },
+ ],
+ },
+
+ // ...now with two flex items, with different flex-basis values (and hence
+ // differently-scaled flex factors):
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "76px"],
+ },
+ {
+ flex: "0 0.1 200px",
+ "_main-size": [null, "184px"],
+ },
+ ],
+ // Notes:
+ // - Free space: -100px
+ // - Sum of flex-shrink factors: 0.3 + 0.1 = 0.4
+ // - Since that sum ^ is < 1, we'll only distribute that fraction of
+ // the free space. We'll distribute: -100px * 0.4 = -40px
+ //
+ // - 1st item's scaled flex factor: 0.3 * 100px = 30
+ // - 2nd item's scaled flex factor: 0.1 * 200px = 20
+ // - 1st item's share of distributed free space: 30/(30+20) = 60%
+ // - 2nd item's share of distributed free space: 20/(30+20) = 40%
+ //
+ // SO:
+ // - 1st item gets 60% * -40px = -24px. 100px-24px = 76px
+ // - 2nd item gets 40% * -40px = -16px. 200px-16px = 184px
+ },
+
+ // ...now with min-size modifying how much one item can shrink:
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "70px"],
+ },
+ {
+ flex: "0 0.1 200px",
+ "_min-main-size": "190px",
+ "_main-size": [null, "190px"],
+ },
+ ],
+ // Notes:
+ // - We proceed as in previous testcase, but clamp the second flex item
+ // at its min main size.
+ // - After that point, we have a total flex-shrink of = 0.3, so we
+ // distribute 0.3 * -100px = -30px to the remaining unfrozen flex
+ // items. Since there's only one unfrozen item left, it gets all of it.
+ },
+
+ // ...now with min-size larger than our flex-basis:
+ // (This makes us freeze the second item right away, before we compute
+ // the initial free space.)
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "55px"], // +30% of 200px-100px-250px
+ },
+ {
+ flex: "0 0.1 200px",
+ "_min-main-size": "250px",
+ "_main-size": [null, "250px"], // immediately frozen
+ },
+ ],
+ // (Same as previous example, except the min-main-size prevents the
+ // second item from shrinking at all)
+ },
+
+ // ...and now with a min-size and a small flex-basis, such that we initially
+ // have positive free space, which makes the "% of [original] free space"
+ // calculations a bit more subtle. We set the "original free space" after
+ // we've clamped the second item (the first time the free space is negative).
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "70px"],
+ },
+ {
+ flex: "0 0.1 50px",
+ "_min-main-size": "200px",
+ "_main-size": [null, "200px"],
+ },
+ ],
+ },
+
+ // Now with max-size making an item shrink more than its flex-shrink value
+ // calls for:
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "70px"],
+ },
+ {
+ flex: "0 0.1 200px",
+ "_max-main-size": "150px",
+ "_main-size": [null, "150px"],
+ },
+ ],
+ // Notes:
+ // - We proceed as in an earlier testcase, but clamp the second flex item
+ // at its max main size.
+ // - After that point, we have a total flex-shrink of = 0.3, so we
+ // distribute 0.3 * -100px = -30px to the remaining unfrozen flex
+ // items. Since there's only one unfrozen item left, it gets all of it.
+ },
+
+ // ...and now with a small enough max-size that it prevents the other flex
+ // item from taking its full desired portion of the (negative) original free
+ // space:
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "0 0.1 200px",
+ "_max-main-size": "110px",
+ "_main-size": [null, "110px"],
+ },
+ ],
+ // Notes:
+ // - We proceed as in an earlier testcase, but clamp the second flex item
+ // at its max main size.
+ // - After that point, we have a total flex-shrink of 0.3, which would
+ // have us distribute 0.3 * -100px = -30px to the (one) remaining
+ // unfrozen flex item. But our remaining free space is only -10px at
+ // that point, so we distribute that instead.
+ },
+
+ // ...and now with a small enough max-size that it pushes the other flex item
+ // to actually grow a bit (with custom "flex-grow: 1" for this testcase):
+ {
+ items: [
+ {
+ flex: "1 0.3 100px",
+ "_main-size": [null, "120px"],
+ },
+ {
+ flex: "1 0.1 200px",
+ "_max-main-size": "80px",
+ "_main-size": [null, "80px"],
+ },
+ ],
+ },
+
+ // In this case, the items' flexibilities don't initially sum to < 1, but they
+ // do after we freeze the third item for violating its min-size.
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "76px"],
+ },
+ {
+ flex: "0 0.1 150px",
+ "_main-size": [null, "138px"],
+ },
+ {
+ flex: "0 0.8 10px",
+ "_min-main-size": "40px",
+ "_main-size": [null, "40px"],
+ },
+ ],
+ // Notes:
+ // - We immediately freeze the 3rd item, since we're shrinking and its
+ // min size obviously prevents it from shrinking at all. This leaves
+ // 200px - 100px - 150px - 40px = -90px of "initial free space".
+ //
+ // - Our remaining flexible items have a total flex-shrink of 0.4,
+ // so we can distribute a total of 0.4 * -90px = -36px
+ //
+ // - We distribute that space using *scaled* flex factors:
+ // * 1st item's scaled flex factor: 0.3 * 100px = 30
+ // * 2nd item's scaled flex factor: 0.1 * 150px = 15
+ // ...which means...
+ // * 1st item's share of distributed free space: 30/(30+15) = 2/3
+ // * 2nd item's share of distributed free space: 15/(30+15) = 1/3
+ //
+ // SO:
+ // - 1st item gets 2/3 * -36px = -24px. 100px - 24px = 76px
+ // - 2nd item gets 1/3 * -36px = -12px. 150px - 12px = 138px
+ },
+
+ // In this case, the items' flexibilities sum to > 1, in part due to an item
+ // that *can't actually shrink* due to its 0 flex-basis (which gives it a
+ // "scaled flex factor" of 0). This prevents us from triggering the special
+ // behavior for flexibilities that sum to less than 1, and as a result, the
+ // first item ends up absorbing all of the free space.
+ {
+ items: [
+ {
+ flex: "0 .5 300px",
+ "_main-size": [null, "200px"],
+ },
+ {
+ flex: "0 5 0px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+
+ // This case is similar to the one above, but with a *barely* nonzero base
+ // size for the second item. This should produce a result similar to the case
+ // above. (In particular, we should first distribute a very small amount of
+ // negative free space to the second item, getting it to approximately zero,
+ // and distribute the bulk of the negative free space to the first item,
+ // getting it to approximately 200px.)
+ {
+ items: [
+ {
+ flex: "0 .5 300px",
+ "_main-size": [null, "200px"],
+ },
+ {
+ flex: "0 1 0.01px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+ // This case is similar to the ones above, but now we've increased the
+ // flex-shrink value on the second-item so that it claims enough of the
+ // negative free space to go below its min-size (0px). So, it triggers a min
+ // violation & is frozen. For the loop *after* the min violation, the sum of
+ // the remaining flex items' flex-shrink values is less than 1, so we trigger
+ // the special <1 behavior and only distribute half of the remaining
+ // (negative) free space to the first item (instead of all of it).
+ {
+ items: [
+ {
+ flex: "0 .5 300px",
+ "_main-size": [null, "250px"],
+ },
+ {
+ flex: "0 5 0.01px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+];
diff --git a/layout/style/test/gen-css-properties.py b/layout/style/test/gen-css-properties.py
new file mode 100644
index 0000000000..1d6fad226d
--- /dev/null
+++ b/layout/style/test/gen-css-properties.py
@@ -0,0 +1,24 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 os
+import sys
+import subprocess
+
+
+def main(output, css_properties, exe):
+ # moz.build passes in the exe name without any path, so to run it we need to
+ # prepend the './'
+ run_exe = exe if os.path.isabs(exe) else "./%s" % exe
+
+ # Use universal_newlines so everything is '\n', which gets converted to
+ # '\r\n' when writing out the file in Windows.
+ data = subprocess.check_output([run_exe], universal_newlines=True)
+ with open(css_properties) as f:
+ data += f.read()
+ output.write(data)
+
+
+if __name__ == "__main__":
+ main(sys.stdout, *sys.argv[1:])
diff --git a/layout/style/test/gtest/ImportScannerTest.cpp b/layout/style/test/gtest/ImportScannerTest.cpp
new file mode 100644
index 0000000000..c0b43221dd
--- /dev/null
+++ b/layout/style/test/gtest/ImportScannerTest.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/ImportScanner.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+using namespace mozilla;
+
+static nsTArray<nsString> Scan(const char* aCssCode) {
+ nsTArray<nsString> urls;
+ ImportScanner scanner;
+ scanner.Start();
+ urls.AppendElements(scanner.Scan(NS_ConvertUTF8toUTF16(aCssCode)));
+ urls.AppendElements(scanner.Stop());
+ return urls;
+}
+
+TEST(ImportScanner, Simple)
+{
+ auto urls = Scan(
+ "/* Something something */ "
+ "@charset \"utf-8\";"
+ "@import url(bar);"
+ "@import uRL( baz );"
+ "@import \"bazz)\"");
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz)"_ns);
+}
+
+TEST(ImportScanner, UrlWithQuotes)
+{
+ auto urls = Scan(
+ "/* Something something */ "
+ "@import url(\"bar\");"
+ "@import\tuRL( \"baz\" );"
+ "@imPort\turL( 'bazz' );"
+ "something else {}"
+ "@import\turL( 'bazz' ); ");
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz"_ns);
+}
+
+TEST(ImportScanner, MediaIsIgnored)
+{
+ auto urls = Scan(
+ "@chArset \"utf-8\";"
+ "@import url(\"bar\") print;"
+ "/* Something something */ "
+ "@import\tuRL( \"baz\" ) (min-width: 100px);"
+ "@import\turL( bazz ) (max-width: 100px);");
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz"_ns);
+}
+
+TEST(ImportScanner, Layers)
+{
+ auto urls = Scan(
+ "@layer foo, bar;\n"
+ "@import url(\"bar\") layer(foo);"
+ "@import url(\"baz\");"
+ "@import url(bazz);"
+ "@layer block {}"
+ // This one below is invalid now and shouldn't be scanned.
+ "@import\turL( 'bazzz' ); ");
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz"_ns);
+}
+
+TEST(ImportScanner, Supports)
+{
+ auto urls = Scan(
+ // Supported feature, should be included.
+ "@import url(bar) supports(display: block);"
+ // Unsupported feature, should not be included.
+ "@import url(baz) supports(foo: bar);"
+ // Supported condition with operator, should be included.
+ "@import url(bazz) supports((display: flex) and (display: block));"
+ // Unsupported condition with function, should be not included.
+ "@import url(bazzz) supports(selector(foo:bar(baz)));"
+ // Supported large condition with layer, supports, media list
+ "@import url(bazzzz) layer(A.B) supports(display: flex) (max-width: "
+ "100px)");
+
+ if (StaticPrefs::layout_css_import_supports_enabled()) {
+ // If the pref is enabled, expect the supports conditions to be evaluated
+ // and unsupported to not be emitted.
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"bazz"_ns);
+ ASSERT_EQ(urls[2], u"bazzzz"_ns);
+ } else {
+ // If disabled, all imports should be included as the supports conditions
+ // should be ignored.
+
+ ASSERT_EQ(urls.Length(), 5u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz"_ns);
+ ASSERT_EQ(urls[3], u"bazzz"_ns);
+ ASSERT_EQ(urls[4], u"bazzzz"_ns);
+ }
+}
diff --git a/layout/style/test/gtest/StyloParsingBench.cpp b/layout/style/test/gtest/StyloParsingBench.cpp
new file mode 100644
index 0000000000..4c14ebed7c
--- /dev/null
+++ b/layout/style/test/gtest/StyloParsingBench.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "gtest/MozGTestBench.h"
+#include "nsString.h"
+#include "ExampleStylesheet.h"
+#include "ServoBindings.h"
+#include "mozilla/dom/DOMString.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/css/SheetParsingMode.h"
+#include "ReferrerInfo.h"
+#include "nsCSSValue.h"
+#include "ReferrerInfo.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+using namespace mozilla::net;
+
+// Bug 1436018 - Disable Stylo microbenchmark on Windows
+#if !defined(_WIN32) && !defined(_WIN64)
+
+# define PARSING_REPETITIONS 20
+# define SETPROPERTY_REPETITIONS (1000 * 1000)
+# define GETPROPERTY_REPETITIONS (1000 * 1000)
+
+static void ServoParsingBench(const StyleUseCounters* aCounters) {
+ auto css = AsBytes(MakeStringSpan(EXAMPLE_STYLESHEET));
+ nsCString cssStr;
+ cssStr.Append(css);
+ ASSERT_EQ(Encoding::UTF8ValidUpTo(css), css.Length());
+
+ RefPtr<nsIURI> uri = NullPrincipal::CreateURI();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
+ RefPtr<URLExtraData> data =
+ new URLExtraData(uri.forget(), referrerInfo.forget(),
+ NullPrincipal::CreateWithoutOriginAttributes());
+ for (int i = 0; i < PARSING_REPETITIONS; i++) {
+ RefPtr<StyleStylesheetContents> stylesheet =
+ Servo_StyleSheet_FromUTF8Bytes(
+ nullptr, nullptr, nullptr, &cssStr, eAuthorSheetFeatures, data,
+ eCompatibility_FullStandards, nullptr, aCounters,
+ StyleAllowImportRules::Yes, StyleSanitizationKind::None, nullptr)
+ .Consume();
+ }
+}
+
+static constexpr auto STYLE_RULE = StyleCssRuleType::Style;
+
+static void ServoSetPropertyByIdBench(const nsACString& css) {
+ RefPtr<StyleLockedDeclarationBlock> block =
+ Servo_DeclarationBlock_CreateEmpty().Consume();
+ RefPtr<nsIURI> uri = NullPrincipal::CreateURI();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
+ RefPtr<URLExtraData> data =
+ new URLExtraData(uri.forget(), referrerInfo.forget(),
+ NullPrincipal::CreateWithoutOriginAttributes());
+ ASSERT_TRUE(IsUtf8(css));
+
+ for (int i = 0; i < SETPROPERTY_REPETITIONS; i++) {
+ Servo_DeclarationBlock_SetPropertyById(
+ block, eCSSProperty_width, &css,
+ /* is_important = */ false, data, StyleParsingMode::DEFAULT,
+ eCompatibility_FullStandards, nullptr, STYLE_RULE, {});
+ }
+}
+
+static void ServoGetPropertyValueById() {
+ RefPtr<StyleLockedDeclarationBlock> block =
+ Servo_DeclarationBlock_CreateEmpty().Consume();
+
+ RefPtr<nsIURI> uri = NullPrincipal::CreateURI();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
+ RefPtr<URLExtraData> data =
+ new URLExtraData(uri.forget(), referrerInfo.forget(),
+ NullPrincipal::CreateWithoutOriginAttributes());
+ constexpr auto css_ = "10px"_ns;
+ const nsACString& css = css_;
+ Servo_DeclarationBlock_SetPropertyById(
+ block, eCSSProperty_width, &css,
+ /* is_important = */ false, data, StyleParsingMode::DEFAULT,
+ eCompatibility_FullStandards, nullptr, STYLE_RULE, {});
+
+ for (int i = 0; i < GETPROPERTY_REPETITIONS; i++) {
+ nsAutoCString value;
+ Servo_DeclarationBlock_GetPropertyValueById(block, eCSSProperty_width,
+ &value);
+ ASSERT_TRUE(value.EqualsLiteral("10px"));
+ }
+}
+
+MOZ_GTEST_BENCH(Stylo, Servo_StyleSheet_FromUTF8Bytes_Bench,
+ [] { ServoParsingBench(nullptr); });
+
+MOZ_GTEST_BENCH(Stylo, Servo_StyleSheet_FromUTF8Bytes_Bench_UseCounters, [] {
+ UniquePtr<StyleUseCounters> counters(Servo_UseCounters_Create());
+ ServoParsingBench(counters.get());
+});
+
+MOZ_GTEST_BENCH(Stylo, Servo_DeclarationBlock_SetPropertyById_Bench,
+ [] { ServoSetPropertyByIdBench("10px"_ns); });
+
+MOZ_GTEST_BENCH(Stylo,
+ Servo_DeclarationBlock_SetPropertyById_WithInitialSpace_Bench,
+ [] { ServoSetPropertyByIdBench(" 10px"_ns); });
+
+MOZ_GTEST_BENCH(Stylo, Servo_DeclarationBlock_GetPropertyById_Bench,
+ ServoGetPropertyValueById);
+
+#endif
diff --git a/layout/style/test/gtest/example.css b/layout/style/test/gtest/example.css
new file mode 100644
index 0000000000..12a0fb9a4f
--- /dev/null
+++ b/layout/style/test/gtest/example.css
@@ -0,0 +1,2925 @@
+/* Copied from devtools/client/debugger/debugger.css on 2017-04-03 */
+
+.landing-page {
+ flex: 1;
+ display: flex;
+ width: 100vw;
+ height: 100vh;
+ flex-direction: row;
+ align-items: stretch;
+ /* Customs properties */
+ --title-font-size: 24px;
+ --ui-element-font-size: 16px;
+ --primary-line-height: 30px;
+ --secondary-line-height: 25px;
+ --base-spacing: 20px;
+ --base-transition: all 0.25s ease;
+}
+
+.landing-popup {
+ min-width: 0;
+}
+
+.landing-page .panel {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.landing-page .panel header {
+ display: flex;
+ align-items: baseline;
+ margin: calc(2 * var(--base-spacing)) 0 0;
+ padding-bottom: var(--base-spacing);
+}
+
+.landing-page .panel header input[type=search] {
+ flex: 1;
+ background-color: var(--theme-tab-toolbar-background);
+ color: var(--theme-comment);
+ font-size: var(--ui-element-font-size);
+ border: 1px solid var(--theme-splitter-color);
+ padding: calc(var(--base-spacing) / 2);
+ margin: 0 var(--base-spacing);
+ transition: var(--base-transition);
+}
+
+.landing-page .panel header input[type=button] {
+ background-color: var(--theme-tab-toolbar-background);
+ color: var(--theme-comment);
+ font-size: var(--ui-element-font-size);
+ border: 1px solid var(--theme-splitter-color);
+ padding: calc(var(--base-spacing) / 2);
+ margin: 0 var(--base-spacing);
+ transition: var(--base-transition);
+}
+
+.landing-page .panel header h1 {
+ color: var(--theme-body-color);
+ font-size: var(--title-font-size);
+ margin: 0;
+ line-height: var(--primary-line-height);
+ font-weight: normal;
+ padding-left: calc(2 * var(--base-spacing));
+}
+
+.landing-page .panel h3 {
+ padding-left: calc(2 * var(--base-spacing));
+}
+
+.landing-page .panel header input::placeholder {
+ color: var(--theme-body-color-inactive);
+}
+
+.landing-page .panel header input:focus {
+ border: 1px solid var(--theme-selection-background);
+}
+
+.landing-page .panel .center-message {
+ font-size: var(--ui-element-font-size);
+ line-height: var(--secondary-line-height);
+ padding: calc(var(--base-spacing) / 2);
+}
+
+.landing-page .center a {
+ color: var(--theme-highlight-bluegrey);
+ text-decoration: none;
+}
+
+.landing-page .panel .footer-note {
+ padding: var(--base-spacing) 0;
+ text-align: center;
+ font-size: 14px;
+ color: var(--theme-comment);
+}
+.landing-page .tab-group {
+ flex: 1;
+ overflow-y: auto;
+}
+
+.landing-page .tab-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.landing-page .tab {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding: calc(var(--base-spacing) / 2) var(--base-spacing);
+ font-family: sans-serif;
+}
+
+.landing-page .tab-sides {
+ display: flex;
+ justify-content: space-between;
+ margin: 0 calc(var(--base-spacing) * 2);
+}
+
+.landing-page .tab-title {
+ line-height: var(--secondary-line-height);
+ font-size: var(--ui-element-font-size);
+ color: var(--theme-highlight-bluegrey);
+ word-break: break-all;
+}
+
+.landing-page .tab-url {
+ color: var(--theme-comment);
+ word-break: break-all;
+}
+
+.landing-page .tab-value {
+ color: var(--theme-comment);
+ line-height: var(--secondary-line-height);
+ font-size: var(--ui-element-font-size);
+}
+
+.landing-page .tab:focus,
+.landing-page .tab.active {
+ background: var(--theme-selection-background);
+ color: var(--theme-selection-color);
+ cursor: pointer;
+ transition: var(--base-transition);
+}
+
+.landing-page .tab:focus .tab-title,
+.landing-page .tab.active .tab-title {
+ color: inherit;
+}
+
+.landing-page .tab:focus .tab-url,
+.landing-page .tab.active .tab-url {
+ color: var(--theme-highlight-gray);
+}
+.landing-page .sidebar {
+ display: flex;
+ background-color: var(--theme-tab-toolbar-background);
+ width: 200px;
+ flex-direction: column;
+ border-right: 1px solid var(--theme-splitter-color);
+}
+
+.landing-page .sidebar h1 {
+ color: var(--theme-body-color);
+ font-size: var(--title-font-size);
+ margin: 0;
+ line-height: var(--primary-line-height);
+ font-weight: normal;
+ padding: calc(2 * var(--base-spacing)) var(--base-spacing);
+}
+
+.landing-page .sidebar ul {
+ list-style: none;
+ padding: 0;
+ line-height: var(--primary-line-height);
+ font-size: var(--ui-element-font-size);
+}
+
+.landing-page .sidebar li {
+ padding: calc(var(--base-spacing) / 4) var(--base-spacing);
+}
+
+.landing-page .sidebar li a {
+ color: var(--theme-body-color);
+}
+
+.landing-page .sidebar li.selected {
+ background: var(--theme-highlight-bluegrey);
+ color: var(--theme-selection-color);
+ transition: var(--base-transition);
+}
+
+.landing-page .sidebar li.selected a {
+ color: inherit;
+}
+
+.landing-page .sidebar li:hover,
+.landing-page .sidebar li:focus {
+ background: var(--theme-selection-background);
+ color: var(--theme-selection-color);
+ cursor: pointer;
+}
+
+.landing-page .sidebar li:hover a,
+.landing-page .sidebar li:focus a {
+ color: inherit;
+}
+:root.theme-light,
+:root .theme-light {
+ --theme-search-overlays-semitransparent: rgba(221, 225, 228, 0.66);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+#mount {
+ display: flex;
+ height: 100%;
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+ background: transparent;
+}
+
+::-webkit-scrollbar-track {
+ border-radius: 8px;
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ border-radius: 8px;
+ background: rgba(113, 113, 113, 0.5);
+}
+
+:root.theme-dark .CodeMirror-scrollbar-filler {
+ background: transparent;
+}
+/* BASICS */
+
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px;
+ color: black;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+ white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+ border-left: 1px solid black;
+ border-right: none;
+ width: 0;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.cm-fat-cursor .CodeMirror-cursor {
+ width: auto;
+ border: 0 !important;
+ background: #7e7;
+}
+.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+@-moz-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@-webkit-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror-overwrite .CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-rulers {
+ position: absolute;
+ left: 0; right: 0; top: -50px; bottom: -20px;
+ overflow: hidden;
+}
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ top: 0; bottom: 0;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+ background: white;
+}
+
+.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actual scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ min-height: 100%;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ display: inline-block;
+ vertical-align: top;
+ margin-bottom: -30px;
+}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+.CodeMirror-gutter-wrapper {
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: contextual;
+ font-variant-ligatures: contextual;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+}
+
+.CodeMirror-cursor {
+ position: absolute;
+ pointer-events: none;
+}
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
+}
+div.CodeMirror-dragcursors {
+ visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursors {
+ visibility: hidden;
+ }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+:root {
+ /* --breakpoint-background: url("chrome://devtools/skin/images/breakpoint.svg#light"); */
+ /* --breakpoint-hover-background: url("chrome://devtools/skin/images/breakpoint.svg#light-hover"); */
+ --breakpoint-active-color: rgba(44,187,15,.2);
+ --breakpoint-active-color-hover: rgba(44,187,15,.5);
+ /* --breakpoint-conditional-background: url("chrome://devtools/skin/images/breakpoint.svg#light-conditional"); */
+}
+
+.theme-dark:root {
+ /* --breakpoint-background: url("chrome://devtools/skin/images/breakpoint.svg#dark"); */
+ /* --breakpoint-hover-background: url("chrome://devtools/skin/images/breakpoint.svg#dark-hover"); */
+ --breakpoint-active-color: rgba(0,255,175,.4);
+ --breakpoint-active-color-hover: rgba(0,255,175,.7);
+ /* --breakpoint-conditional-background: url("chrome://devtools/skin/images/breakpoint.svg#dark-conditional"); */
+}
+
+.CodeMirror .errors {
+ width: 16px;
+}
+
+.CodeMirror .error {
+ display: inline-block;
+ margin-left: 5px;
+ width: 12px;
+ height: 12px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+ /* background-image: url("chrome://devtools/skin/images/editor-error.png"); */
+ opacity: 0.75;
+}
+
+.CodeMirror .hit-counts {
+ width: 6px;
+}
+
+.CodeMirror .hit-count {
+ display: inline-block;
+ height: 12px;
+ border: solid rgba(0,0,0,0.2);
+ border-width: 1px 1px 1px 0;
+ border-radius: 0 3px 3px 0;
+ padding: 0 3px;
+ font-size: 10px;
+ pointer-events: none;
+}
+
+.CodeMirror-linenumber:before {
+ content: " ";
+ display: block;
+ width: calc(100% - 3px);
+ position: absolute;
+ top: 1px;
+ left: 0;
+ height: 12px;
+ z-index: -1;
+ background-size: calc(100% - 2px) 12px;
+ background-repeat: no-repeat;
+ background-position: right center;
+ padding-inline-end: 9px;
+}
+
+.breakpoint .CodeMirror-linenumber {
+ color: var(--theme-body-background);
+}
+
+.breakpoint .CodeMirror-linenumber:before {
+ background-image: var(--breakpoint-background) !important;
+}
+
+.conditional .CodeMirror-linenumber:before {
+ background-image: var(--breakpoint-conditional-background) !important;
+}
+
+.debug-line .CodeMirror-linenumber {
+ background-color: var(--breakpoint-active-color);
+}
+
+.theme-dark .debug-line .CodeMirror-linenumber {
+ color: #c0c0c0;
+}
+
+.debug-line .CodeMirror-line {
+ background-color: var(--breakpoint-active-color) !important;
+}
+
+/* Don't display the highlight color since the debug line
+ is already highlighted */
+.debug-line .CodeMirror-activeline-background {
+ display: none;
+}
+
+.CodeMirror {
+ cursor: text;
+ height: 100%;
+}
+
+.CodeMirror-gutters {
+ cursor: default;
+}
+
+/* This is to avoid the fake horizontal scrollbar div of codemirror to go 0
+height when floating scrollbars are active. Make sure that this value is equal
+to the maximum of `min-height` specific to the `scrollbar[orient="horizontal"]`
+selector in floating-scrollbar-light.css across all platforms. */
+.CodeMirror-hscrollbar {
+ min-height: 10px;
+}
+
+/* This is to avoid the fake vertical scrollbar div of codemirror to go 0
+width when floating scrollbars are active. Make sure that this value is equal
+to the maximum of `min-width` specific to the `scrollbar[orient="vertical"]`
+selector in floating-scrollbar-light.css across all platforms. */
+.CodeMirror-vscrollbar {
+ min-width: 10px;
+}
+
+.cm-trailingspace {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==");
+ opacity: 0.75;
+ background-position: left bottom;
+ background-repeat: repeat-x;
+}
+
+.cm-highlight {
+ position: relative;
+}
+
+.cm-highlight:before {
+ position: absolute;
+ border-top-style: solid;
+ border-bottom-style: solid;
+ border-top-color: var(--theme-comment-alt);
+ border-bottom-color: var(--theme-comment-alt);
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+ top: -1px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ content: "";
+ margin-bottom: -1px;
+}
+
+.cm-highlight-full:before {
+ border: 1px solid var(--theme-comment-alt);
+}
+
+.cm-highlight-start:before {
+ border-left-width: 1px;
+ border-left-style: solid;
+ border-left-color: var(--theme-comment-alt);
+ margin: 0 0 -1px -1px;
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+}
+
+.cm-highlight-end:before {
+ border-right-width: 1px;
+ border-right-style: solid;
+ border-right-color: var(--theme-comment-alt);
+ margin: 0 -1px -1px 0;
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+}
+
+/* CodeMirror dialogs styling */
+
+.CodeMirror-dialog {
+ padding: 4px 3px;
+}
+
+.CodeMirror-dialog,
+.CodeMirror-dialog input {
+ font: message-box;
+}
+
+/* Fold addon */
+
+.CodeMirror-foldmarker {
+ color: blue;
+ text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
+ font-family: sans-serif;
+ line-height: .3;
+ cursor: pointer;
+}
+
+.CodeMirror-foldgutter {
+ width: 16px; /* Same as breakpoints gutter above */
+}
+
+.CodeMirror-foldgutter-open,
+.CodeMirror-foldgutter-folded {
+ color: #555;
+ cursor: pointer;
+}
+
+.CodeMirror-foldgutter-open:after {
+ font-size: 120%;
+ content: "\25BE";
+}
+
+.CodeMirror-foldgutter-folded:after {
+ font-size: 120%;
+ content: "\25B8";
+}
+
+.CodeMirror-hints {
+ position: absolute;
+ z-index: 10;
+ overflow: hidden;
+ list-style: none;
+ margin: 0;
+ padding: 2px;
+ border-radius: 3px;
+ font-size: 90%;
+ max-height: 20em;
+ overflow-y: auto;
+}
+
+.CodeMirror-hint {
+ margin: 0;
+ padding: 0 4px;
+ border-radius: 2px;
+ max-width: 19em;
+ overflow: hidden;
+ white-space: pre;
+ cursor: pointer;
+}
+
+.CodeMirror-Tern-completion {
+ padding-inline-start: 22px;
+ position: relative;
+ line-height: 18px;
+}
+
+.CodeMirror-Tern-completion:before {
+ position: absolute;
+ left: 2px;
+ bottom: 2px;
+ border-radius: 50%;
+ font-size: 12px;
+ font-weight: bold;
+ height: 15px;
+ width: 15px;
+ line-height: 16px;
+ text-align: center;
+ color: #ffffff;
+ box-sizing: border-box;
+}
+
+.CodeMirror-Tern-completion-unknown:before {
+ content: "?";
+}
+
+.CodeMirror-Tern-completion-object:before {
+ content: "O";
+}
+
+.CodeMirror-Tern-completion-fn:before {
+ content: "F";
+}
+
+.CodeMirror-Tern-completion-array:before {
+ content: "A";
+}
+
+.CodeMirror-Tern-completion-number:before {
+ content: "N";
+}
+
+.CodeMirror-Tern-completion-string:before {
+ content: "S";
+}
+
+.CodeMirror-Tern-completion-bool:before {
+ content: "B";
+}
+
+.CodeMirror-Tern-completion-guess {
+ color: #999;
+}
+
+.CodeMirror-Tern-tooltip {
+ border-radius: 3px;
+ padding: 2px 5px;
+ white-space: pre-wrap;
+ max-width: 40em;
+ position: absolute;
+ z-index: 10;
+}
+
+.CodeMirror-Tern-hint-doc {
+ max-width: 25em;
+}
+
+.CodeMirror-Tern-farg-current {
+ text-decoration: underline;
+}
+
+.CodeMirror-Tern-fhint-guess {
+ opacity: .7;
+}
+:root.theme-light,
+:root .theme-light {
+ --search-overlays-semitransparent: rgba(221, 225, 228, 0.66);
+}
+
+:root.theme-dark,
+:root .theme-dark {
+ --search-overlays-semitransparent: rgba(42, 46, 56, 0.66);
+}
+.debugger {
+ display: flex;
+ flex: 1;
+ height: 100%;
+}
+
+.editor-pane {
+ display: flex;
+ position: relative;
+ flex: 1;
+ background-color: var(--theme-tab-toolbar-background);
+ height: calc(100% - 1px);
+ overflow: hidden;
+}
+
+.editor-container {
+ width: 100%;
+}
+
+.subsettings:hover {
+ cursor: pointer;
+}
+
+.search-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ z-index: 200;
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.search-container .close-button {
+ width: 16px;
+ margin-top: 25px;
+ margin-right: 20px;
+}
+menupopup {
+ position: fixed;
+ z-index: 10000;
+ background: white;
+ border: 1px solid #cccccc;
+ padding: 5px 0;
+ background: #f2f2f2;
+ border-radius: 5px;
+ color: #585858;
+ box-shadow: 0 0 4px 0 rgba(190, 190, 190, 0.8);
+ min-width: 130px;
+}
+
+menuitem {
+ display: block;
+ padding: 0 20px;
+ line-height: 20px;
+ font-weight: 500;
+ font-size: 13px;
+ user-select: none;
+}
+
+menuitem:hover {
+ background: #3780fb;
+ color: white;
+ cursor: pointer;
+}
+
+menuitem[disabled=true] {
+ color: #cccccc;
+}
+
+menuitem[disabled=true]:hover {
+ background-color: transparent;
+ cursor: default;
+}
+
+menuseparator {
+ border-bottom: 1px solid #cacdd3;
+ width: 100%;
+ height: 5px;
+ display: block;
+ margin-bottom: 5px;
+}
+
+#contextmenu-mask.show {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 999;
+}
+/* vim:set ts=2 sw=2 sts=2 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/. */
+
+.split-box {
+ display: flex;
+ flex: 1;
+ min-width: 0;
+ height: 100%;
+ width: 100%;
+}
+
+.split-box.vert {
+ flex-direction: row;
+}
+
+.split-box.horz {
+ flex-direction: column;
+}
+
+.split-box > .uncontrolled {
+ display: flex;
+ flex: 1;
+ min-width: 0;
+ overflow: auto;
+}
+
+.split-box > .controlled {
+ display: flex;
+ overflow: auto;
+}
+
+.split-box > .splitter {
+ background-image: none;
+ border: 0;
+ border-style: solid;
+ border-color: transparent;
+ background-color: var(--theme-splitter-color);
+ background-clip: content-box;
+ position: relative;
+
+ box-sizing: border-box;
+
+ /* Positive z-index positions the splitter on top of its siblings and makes
+ it clickable on both sides. */
+ z-index: 1;
+}
+
+.split-box.vert > .splitter {
+ min-width: calc(var(--devtools-splitter-inline-start-width) +
+ var(--devtools-splitter-inline-end-width) + 1px);
+
+ border-left-width: var(--devtools-splitter-inline-start-width);
+ border-right-width: var(--devtools-splitter-inline-end-width);
+
+ margin-left: calc(-1 * var(--devtools-splitter-inline-start-width) - 1px);
+ margin-right: calc(-1 * var(--devtools-splitter-inline-end-width));
+
+ cursor: ew-resize;
+}
+
+.split-box.horz > .splitter {
+ min-height: calc(var(--devtools-splitter-top-width) +
+ var(--devtools-splitter-bottom-width) + 1px);
+
+ border-top-width: var(--devtools-splitter-top-width);
+ border-bottom-width: var(--devtools-splitter-bottom-width);
+
+ margin-top: calc(-1 * var(--devtools-splitter-top-width) - 1px);
+ margin-bottom: calc(-1 * var(--devtools-splitter-bottom-width));
+
+ cursor: ns-resize;
+}
+
+.split-box.disabled {
+ pointer-events: none;
+}
+
+/**
+ * Make sure splitter panels are not processing any mouse
+ * events. This is good for performance during splitter
+ * bar dragging.
+ */
+.split-box.dragging > .controlled,
+.split-box.dragging > .uncontrolled {
+ pointer-events: none;
+}
+/* vim:set ts=2 sw=2 sts=2 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/. */
+
+.theme-dark,
+.theme-light {
+ --number-color: var(--theme-highlight-green);
+ --string-color: var(--theme-highlight-orange);
+ --null-color: var(--theme-comment);
+ --object-color: var(--theme-body-color);
+ --caption-color: var(--theme-highlight-blue);
+ --location-color: var(--theme-content-color1);
+ --source-link-color: var(--theme-highlight-blue);
+ --node-color: var(--theme-highlight-bluegrey);
+ --reference-color: var(--theme-highlight-purple);
+}
+
+.theme-firebug {
+ --number-color: #000088;
+ --string-color: #FF0000;
+ --null-color: #787878;
+ --object-color: DarkGreen;
+ --caption-color: #444444;
+ --location-color: #555555;
+ --source-link-color: blue;
+ --node-color: rgb(0, 0, 136);
+ --reference-color: rgb(102, 102, 255);
+}
+
+/******************************************************************************/
+
+.objectLink:hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.inline {
+ display: inline;
+ white-space: normal;
+}
+
+.objectBox-object {
+ font-weight: bold;
+ color: var(--object-color);
+ white-space: pre-wrap;
+}
+
+.objectBox-string,
+.objectBox-text,
+.objectLink-textNode,
+.objectBox-table {
+ white-space: pre-wrap;
+}
+
+.objectBox-number,
+.objectLink-styleRule,
+.objectLink-element,
+.objectLink-textNode,
+.objectBox-array > .length {
+ color: var(--number-color);
+}
+
+.objectBox-string {
+ color: var(--string-color);
+}
+
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+ color: var(--object-color);
+}
+
+.objectLink-Location {
+ font-style: italic;
+ color: var(--location-color);
+}
+
+.objectBox-null,
+.objectBox-undefined,
+.objectBox-hint,
+.logRowHint {
+ font-style: italic;
+ color: var(--null-color);
+}
+
+.objectLink-sourceLink {
+ position: absolute;
+ right: 4px;
+ top: 2px;
+ padding-left: 8px;
+ font-weight: bold;
+ color: var(--source-link-color);
+}
+
+/******************************************************************************/
+
+.objectLink-event,
+.objectLink-eventLog,
+.objectLink-regexp,
+.objectLink-object,
+.objectLink-Date {
+ font-weight: bold;
+ color: var(--object-color);
+ white-space: pre-wrap;
+}
+
+/******************************************************************************/
+
+.objectLink-object .nodeName,
+.objectLink-NamedNodeMap .nodeName,
+.objectLink-NamedNodeMap .objectEqual,
+.objectLink-NamedNodeMap .arrayLeftBracket,
+.objectLink-NamedNodeMap .arrayRightBracket,
+.objectLink-Attr .attrEqual,
+.objectLink-Attr .attrTitle {
+ color: var(--node-color);
+}
+
+.objectLink-object .nodeName {
+ font-weight: normal;
+}
+
+/******************************************************************************/
+
+.objectLeftBrace,
+.objectRightBrace,
+.arrayLeftBracket,
+.arrayRightBracket {
+ cursor: pointer;
+ font-weight: bold;
+}
+
+.objectLeftBrace,
+.arrayLeftBracket {
+ margin-right: 4px;
+}
+
+.objectRightBrace,
+.arrayRightBracket {
+ margin-left: 4px;
+}
+
+/******************************************************************************/
+/* Cycle reference*/
+
+.objectLink-Reference {
+ font-weight: bold;
+ color: var(--reference-color);
+}
+
+.objectBox-array > .objectTitle {
+ font-weight: bold;
+ color: var(--object-color);
+}
+
+.caption {
+ font-weight: bold;
+ color: var(--caption-color);
+}
+
+/******************************************************************************/
+/* Themes */
+
+.theme-dark .objectBox-null,
+.theme-dark .objectBox-undefined,
+.theme-light .objectBox-null,
+.theme-light .objectBox-undefined {
+ font-style: normal;
+}
+
+.theme-dark .objectBox-object,
+.theme-light .objectBox-object {
+ font-weight: normal;
+ white-space: pre-wrap;
+}
+
+.theme-dark .caption,
+.theme-light .caption {
+ font-weight: normal;
+}
+
+.search-container {
+ position: absolute;
+ top: 30px;
+ left: 0;
+ width: calc(100% - 1px);
+ height: calc(100% - 31px);
+ display: flex;
+ z-index: 200;
+ background-color: var(--theme-body-background);
+}
+
+.searchinput-container {
+ display: flex;
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.theme-dark .result-list li .subtitle {
+ color: var(--theme-comment-alt);
+}
+
+.arrow,
+.folder,
+.domain,
+.file,
+.worker,
+.refresh,
+.add-button {
+ fill: var(--theme-splitter-color);
+}
+
+.worker,
+.folder {
+ position: relative;
+ top: 2px;
+}
+
+.domain,
+.file,
+.worker,
+.refresh,
+.add-button {
+ position: relative;
+ top: 1px;
+}
+
+.domain svg,
+.folder svg,
+.worker svg,
+.refresh svg,
+.add-button svg {
+ width: 15px;
+}
+
+.file svg {
+ width: 13px;
+}
+
+.file svg,
+.domain svg,
+.folder svg,
+.refresh svg,
+.worker svg {
+ margin-inline-end: 5px;
+}
+
+.arrow svg {
+ fill: var(--theme-splitter-color);
+ margin-top: 3px;
+ transition: transform 0.25s ease;
+ width: 10px;
+}
+
+html:not([dir="rtl"]) .arrow svg {
+ margin-right: 5px;
+ transform: rotate(-90deg);
+}
+
+html[dir="rtl"] .arrow svg {
+ margin-left: 5px;
+ transform: rotate(90deg);
+}
+
+/* TODO (Amit): html is just for specificity. keep it like this? */
+html .arrow.expanded svg {
+ transform: rotate(0deg);
+}
+
+.arrow.hidden {
+ visibility: hidden;
+}
+.close-btn path {
+ fill: var(--theme-comment-alt);
+}
+
+.close-btn .close {
+ cursor: pointer;
+ width: 14px;
+ height: 14px;
+ padding: 2px;
+ text-align: center;
+ margin-top: 2px;
+ line-height: 7px;
+ transition: all 0.25s easeinout;
+}
+
+.close-btn .close svg {
+ width: 8px;
+}
+
+.close-btn:hover {
+ display: block;
+}
+
+.close-btn:hover .close {
+ background: var(--theme-selection-background);
+ border-radius: 2px;
+}
+
+.close-btn:hover .close path {
+ fill: white;
+}
+
+.close-btn-big {
+ padding: 11px;
+ margin-right: 7px;
+ width: 27px;
+ height: 40px;
+}
+
+.close-btn-big .close {
+ cursor: pointer;
+ display: inline-block;
+ padding: 2px;
+ text-align: center;
+ transition: all 0.25s easeinout;
+ line-height: 100%;
+ width: 16px;
+ height: 16px;
+}
+
+.close-btn-big .close svg {
+ width: 9px;
+ height: 9px;
+}
+
+.close-btn-big .close:hover {
+ border-radius: 2px;
+}
+
+.search-field {
+ width: calc(100% - 1px);
+ height: 27px;
+ background-color: var(--theme-toolbar-background);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding-right: 10px;
+ display: flex;
+ flex-shrink: 0;
+}
+
+.search-field.big {
+ height: 40px;
+ font-size: 14px;
+}
+
+.search-field.big input {
+ font-size: 14px;
+}
+
+.search-field i {
+ display: block;
+ padding: 0;
+ width: 16px;
+}
+
+.search-field i svg {
+ width: 16px;
+}
+
+.search-field.big input {
+ line-height: 40px;
+}
+
+.search-field input {
+ border: none;
+ line-height: 30px;
+ background-color: var(--theme-toolbar-background);
+ color: var(--theme-body-color-inactive);
+ width: calc(100% - 38px);
+ flex: 1;
+}
+
+.theme-dark .search-field input {
+ color: var(--theme-body-color-inactive);
+}
+
+.search-field i.magnifying-glass,
+.search-field i.sad-face {
+ padding: 6px;
+ width: 24px;
+}
+
+.search-field.big i.magnifying-glass,
+.search-field.big i.sad-face {
+ padding: 14px;
+ width: 40px;
+}
+
+.search-field .magnifying-glass path,
+.search-field .magnifying-glass ellipse {
+ stroke: var(--theme-splitter-color);
+}
+
+.search-field ::-webkit-input-placeholder {
+ color: var(--theme-body-color-inactive);
+}
+
+.search-field input::placeholder {
+ color: var(--theme-body-color-inactive);
+}
+
+.search-field input:focus {
+ outline-width: 0;
+}
+
+.search-field input.empty {
+ color: var(--theme-highlight-orange);
+}
+
+.search-field.big .summary {
+ line-height: 40px;
+}
+
+.search-field .summary {
+ line-height: 27px;
+ padding-right: 10px;
+ color: var(--theme-body-color-inactive);
+}
+
+.result-list {
+ list-style: none;
+ width: 100%;
+ background-color: var(--theme-toolbar-background);
+ margin: 0px;
+ padding: 0px;
+ overflow: auto;
+}
+
+.result-list.big {
+ max-height: calc(100% - 32px);
+}
+
+.result-list * {
+ user-select: none;
+}
+
+.result-list li {
+ color: var(--theme-body-color);
+ background-color: var(--theme-tab-toolbar-background);
+ padding: 4px 13px;
+ display: flex;
+ justify-content: space-between;
+}
+
+.result-list li:first-child {
+ border-top: none;
+}
+
+.result-list.big li {
+ padding: 10px;
+ flex-direction: column;
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.result-list li:hover {
+ background: var(--theme-tab-toolbar-background);
+ cursor: pointer;
+}
+
+.result-list li.selected {
+ border: 1px solid var(--theme-selection-background);
+}
+
+.result-list.big li.selected {
+ padding-left: 9px;
+ padding-top: 9px;
+}
+
+.search-bar .result-list li.selected {
+ background-color: var(--theme-selection-background);
+ color: white;
+}
+
+.result-list li .title {
+ line-height: 1.5em;
+ word-break: break-all;
+}
+
+.result-list li.selected .title {
+ color: white;
+}
+
+.result-list.big li.selected .title {
+ color: var(--theme-body-color);
+}
+
+.result-list.big li .subtitle {
+ word-break: break-all;
+ color: var(--theme-body-color-inactive);
+}
+
+.result-list.big li .subtitle {
+ line-height: 1.5em;
+}
+
+.search-bar .result-list li.selected .subtitle {
+ color: white;
+}
+
+.search-bar .result-list {
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.theme-dark .result-list {
+ background-color: var(--theme-body-background);
+}
+
+.autocomplete {
+ flex: 1;
+ width: 100%;
+}
+
+.autocomplete .no-result-msg {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ color: var(--theme-graphs-full-red);
+ font-size: 24px;
+ padding: 4px;
+ word-break: break-all;
+}
+
+.autocomplete .no-result-msg .sad-face {
+ width: 24px;
+ margin: 0 4px;
+ line-height: 0;
+ flex-shrink: 0;
+}
+
+.autocomplete .no-result-msg .sad-face svg {
+ fill: var(--theme-graphs-full-red);
+}
+.tree {
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+
+ white-space: nowrap;
+ overflow: auto;
+ min-width: 100%;
+}
+
+.tree button {
+ display: block;
+}
+
+.tree .node {
+ padding: 2px 5px;
+ position: relative;
+ cursor: pointer;
+}
+
+.tree .node.focused {
+ color: white;
+ background-color: var(--theme-selection-background);
+}
+
+html:not([dir="rtl"]) .tree .node > div {
+ margin-left: 10px;
+}
+
+html[dir="rtl"] .tree .node > div {
+ margin-right: 10px;
+}
+
+.tree .node.focused svg {
+ fill: white;
+}
+
+.tree-node button {
+ position: fixed;
+}
+.sources-panel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.sources-panel * {
+ user-select: none;
+}
+
+.sources-header {
+ height: 30px;
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding-top: 0px;
+ padding-bottom: 0px;
+ line-height: 30px;
+ font-size: 1.2em;
+ display: flex;
+ align-items: baseline;
+ user-select: none;
+ justify-content: flex-end;
+}
+
+.theme-dark .sources-header {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.sources-header {
+ padding-inline-start: 10px;
+}
+
+.sources-header-info {
+ font-size: 12px;
+ color: var(--theme-comment-alt);
+ font-weight: lighter;
+ white-space: nowrap;
+ padding-inline-end: 10px;
+ cursor: pointer;
+}
+
+.sources-list {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+}
+
+.theme-dark .sources-list .tree .node:not(.focused) svg {
+ fill: var(--theme-comment);
+}
+
+.theme-dark .source-list .tree .node.focused {
+ background-color: var(--theme-tab-toolbar-background);
+}
+.toggle-button-start,
+.toggle-button-end {
+ transform: translate(0, 2px);
+ transition: transform 0.25s ease-in-out;
+ cursor: pointer;
+ padding: 4px 2px;
+}
+
+.toggle-button-start svg,
+.toggle-button-end svg {
+ width: 16px;
+ fill: var(--theme-comment);
+}
+
+.theme-dark .toggle-button-start svg,
+.theme-dark .toggle-button-end svg {
+ fill: var(--theme-comment-alt);
+}
+
+.toggle-button-end {
+ margin-left: auto;
+ margin-right: 5px;
+}
+
+.toggle-button-start {
+ margin-left: 5px;
+}
+
+html:not([dir="rtl"]) .toggle-button-end svg,
+html[dir="rtl"] .toggle-button-start svg {
+ transform: rotate(180deg);
+}
+
+html .toggle-button-end.vertical svg {
+ transform: rotate(-90deg);
+}
+
+.toggle-button-end.vertical {
+ margin-bottom: 2px;
+}
+
+.toggle-button-start.collapsed,
+.toggle-button-end.collapsed {
+ transform: rotate(180deg);
+}
+
+.source-footer {
+ background: var(--theme-toolbar-background);
+ border-top: 1px solid var(--theme-splitter-color);
+ position: absolute;
+ display: flex;
+ bottom: 0;
+ left: 0;
+ right: 1px;
+ opacity: 1;
+ z-index: 100;
+ user-select: none;
+ height: 27px;
+ box-sizing: border-box;
+}
+
+.source-footer .commands {
+ display: flex;
+}
+
+.source-footer .commands * {
+ user-select: none;
+}
+
+.source-footer > .commands > .action {
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ transition: opacity 200ms;
+ border: none;
+ background: transparent;
+ padding: 8px 0.7em;
+}
+
+.source-footer > .commands > .action i {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+:root.theme-dark .source-footer > .commands > .action {
+ fill: var(--theme-body-color);
+}
+
+:root.theme-dark .source-footer > .commands > .action:hover {
+ fill: var(--theme-selection-color);
+}
+
+.source-footer > .commands > .action svg {
+ height: 16px;
+ width: 16px;
+}
+
+.source-footer .commands .coverage {
+ color: var(--theme-body-color);
+}
+
+.coverage-on .source-footer .commands .coverage {
+ color: var(--theme-highlight-blue);
+ border: 1px solid var(--theme-body-color-inactive);
+ border-radius: 2px;
+}
+
+.search-bar {
+ display: flex;
+ flex-direction: column;
+ max-height: 50%;
+}
+
+.search-bar .search-field {
+ padding-left: 7px;
+}
+
+.search-bar .close-btn {
+ padding: 6px;
+}
+
+.search-bottom-bar * {
+ user-select: none;
+}
+
+.search-bottom-bar {
+ display: flex;
+ flex-shrink: 0;
+ justify-content: space-between;
+ width: calc(100% - 1px);
+ height: 27px;
+ background-color: var(--theme-toolbar-background);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding: 0 13px;
+}
+
+.search-bottom-bar button:focus {
+ outline: none;
+}
+
+.search-bottom-bar .search-modifiers {
+ display: flex;
+ align-items: center;
+}
+
+.search-bottom-bar .search-modifiers button {
+ padding: 0 3px;
+ margin: 0 3px;
+ border: none;
+ background: none;
+ width: 20px;
+ height: 20px;
+ border-radius: 3px;
+}
+
+.search-bottom-bar .search-modifiers button i {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 0;
+ width: 16px;
+}
+
+.search-bottom-bar .search-modifiers button svg {
+ fill: var(--theme-comment-alt);
+ height: 16px;
+ width: 16px;
+}
+
+.search-bottom-bar .search-modifiers button svg:hover {
+ cursor: pointer;
+}
+
+.search-bottom-bar .search-modifiers button:active {
+ outline: none;
+}
+
+.search-bottom-bar .search-modifiers button.active svg {
+ fill: var(--theme-selection-background);
+}
+
+.theme-dark .search-bottom-bar .search-modifiers button.active svg {
+ fill: white;
+}
+
+.search-bottom-bar .search-modifiers button.disabled svg {
+ fill: var(--theme-comment-alt);
+}
+
+.search-bottom-bar .search-type-toggles {
+ display: flex;
+ align-items: center;
+}
+
+.search-bottom-bar .search-type-toggles .search-toggle-title {
+ color: var(--theme-body-color-inactive);
+ font-size: 11px;
+ font-weight: normal;
+ margin: 0;
+}
+
+.search-bottom-bar .search-type-toggles .search-type-btn {
+ margin: 0 6px;
+ border: none;
+ background: transparent;
+ color: var(--theme-comment-alt);
+}
+
+.search-bottom-bar .search-type-toggles .search-type-btn:hover {
+ cursor: pointer;
+}
+
+.search-bottom-bar .search-type-toggles .search-type-btn:active {
+ outline: none;
+}
+
+.search-bottom-bar .search-type-toggles .search-type-btn.active {
+ color: var(--theme-selection-background);
+}
+
+.theme-dark .search-bottom-bar .search-type-toggles .search-type-btn.active {
+ color: white;
+}
+
+.search-bar .result-list {
+ max-height: 230px;
+}
+.popover {
+ position: fixed;
+ z-index: 4;
+}
+
+.popover-gap {
+ height: 10px;
+ padding-top: 10px;
+}
+
+.popover::before,
+.popover::after {
+ content: '';
+ height: 0;
+ width: 0;
+ position: absolute;
+ border: 10px solid transparent;
+ left: calc(20% - 10px); /* corresponds to calculation in Popover.js */
+}
+
+.popover::before {
+ border-bottom-color: var(--theme-comment);
+ top: -10px;
+}
+
+.popover::after {
+ border-bottom-color: var(--theme-body-background);
+ top: -8px;
+}
+.preview {
+ background: var(--theme-body-background);
+ min-width: 200px;
+ min-height: 80px;
+ border: 1px solid var(--theme-comment);
+ padding: 10px;
+ height: auto;
+ min-height: inherit;
+ max-height: 130px;
+ overflow: auto;
+ max-width: 400px;
+}
+
+.preview .header {
+ width: 100%;
+ line-height: 20px;
+ border-bottom: 1px solid #cccccc;
+ display: flex;
+ flex-direction: column;
+}
+
+.preview .header .link {
+ align-self: flex-end;
+ color: var(--theme-highlight-blue);
+ text-decoration: underline;
+}
+.preview .header .link:hover {
+ cursor: pointer;
+}
+
+.selected-token {
+ background-color: var(--theme-search-overlays-semitransparent);
+ color: var(--theme-selection-color);
+}
+
+.selected-token:hover {
+ cursor: pointer;
+}
+
+.preview .function-signature {
+ padding-top: 10px;
+}
+
+.function-signature {
+ line-height: 20px;
+ align-self: center;
+}
+
+.function-signature .function-name {
+ color: var(--theme-highlight-blue);
+}
+
+.function-signature .param {
+ color: var(--string-color);
+}
+
+.function-signature .paren {
+ color: var(--object-color);
+}
+
+.function-signature .comma {
+ color: var(--object-color);
+}
+
+.theme-dark .preview {
+ border-color: var(--theme-body-color);
+}
+
+.theme-dark .preview .arrow svg {
+ fill: var(--theme-comment);
+}
+.conditional-breakpoint-panel {
+ cursor: initial;
+ margin: 1em 0;
+ position: relative;
+ display: flex;
+ align-items: center;
+ background: var(--theme-toolbar-background);
+ border-top: 1px solid var(--theme-splitter-color);
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.conditional-breakpoint-panel .prompt {
+ font-size: 1.8em;
+ color: var(--theme-comment-alt);
+ padding-left: 3px;
+}
+
+.conditional-breakpoint-panel input {
+ margin: 5px 10px;
+ width: calc(100% - 4em);
+ border: none;
+ background: var(--theme-toolbar-background);
+ font-size: 14px;
+ color: var(--theme-comment);
+ line-height: 30px;
+}
+
+.conditional-breakpoint-panel input:focus {
+ outline-width: 0;
+}
+/* vim:set ts=2 sw=2 sts=2 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/. */
+
+/**
+ * There's a known codemirror flex issue with chrome that this addresses.
+ * BUG https://github.com/devtools-html/debugger.html/issues/63
+ */
+.editor-wrapper {
+ position: absolute;
+ height: calc(100% - 31px);
+ width: 100%;
+ top: 30px;
+ left: 0px;
+}
+
+html[dir="rtl"] .editor-mount {
+ direction: ltr;
+}
+
+.editor-wrapper .breakpoints {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.function-search {
+ max-height: 300px;
+ overflow: hidden;
+}
+
+.function-search .results {
+ height: auto;
+}
+
+.editor.hit-marker {
+ height: 14px;
+}
+
+.coverage-on .CodeMirror-code :not(.hit-marker) .CodeMirror-line,
+.coverage-on .CodeMirror-code :not(.hit-marker) .CodeMirror-gutter-wrapper {
+ opacity: 0.5;
+}
+
+.editor.new-breakpoint svg {
+ fill: var(--theme-selection-background);
+ width: 60px;
+ height: 14px;
+ position: absolute;
+ top: 0px;
+ right: -4px;
+}
+
+.new-breakpoint.has-condition svg {
+ fill: var(--theme-graphs-yellow);
+}
+
+.editor.new-breakpoint.breakpoint-disabled svg {
+ opacity: 0.3;
+}
+
+.CodeMirror {
+ width: 100%;
+ height: 100%;
+}
+
+.editor-wrapper .editor-mount {
+ width: 100%;
+ height: calc(100% - 32px);
+ background-color: var(--theme-body-background);
+}
+
+.search-bar ~ .editor-mount {
+ height: calc(100% - 72px);
+}
+
+.CodeMirror-linenumber {
+ font-size: 11px;
+ line-height: 14px;
+}
+
+/* set the linenumber white when there is a breakpoint */
+.new-breakpoint .CodeMirror-gutter-wrapper .CodeMirror-linenumber {
+ color: white;
+}
+
+/* move the breakpoint below the linenumber */
+.new-breakpoint .CodeMirror-gutter-elt:last-child {
+ z-index: 0;
+}
+
+.editor-wrapper .CodeMirror-line {
+ font-size: 11px;
+ line-height: 14px;
+}
+
+.theme-dark .editor-wrapper .CodeMirror-line .cm-comment {
+ color: var(--theme-content-color3);
+}
+
+.debug-line .CodeMirror-line {
+ background-color: var(--breakpoint-active-color) !important;
+}
+
+/* Don't display the highlight color since the debug line
+ is already highlighted */
+.debug-line .CodeMirror-activeline-background {
+ display: none;
+}
+
+.highlight-line .CodeMirror-line {
+ animation: fade-highlight-out 1.5s normal forwards;
+}
+
+@keyframes fade-highlight-out {
+ 0% { background-color: var(--theme-highlight-gray); }
+ 100% { background-color: transparent; }
+}
+
+.welcomebox {
+ width: calc(100% - 1px);
+
+ /* Offsetting it by 30px for the sources-header area */
+ height: calc(100% - 30px);
+ position: absolute;
+ top: 30px;
+ left: 0;
+ padding: 50px 0;
+ text-align: center;
+ font-size: 1.25em;
+ color: var(--theme-comment-alt);
+ background-color: var(--theme-tab-toolbar-background);
+ font-weight: lighter;
+ z-index: 100;
+ user-select: none;
+}
+.input-expression {
+ width: 100%;
+ margin: 0px;
+ border: 1px;
+ cursor: pointer;
+ background-color: var(--theme-body-background);
+ font-size: 12px;
+ padding: 0px 20px;
+ color: var(--theme-highlight-blue);
+}
+
+.input-expression::-webkit-input-placeholder {
+ text-align: center;
+ font-style: italic;
+ color: var(--theme-comment-alt);
+}
+
+.input-expression::placeholder {
+ text-align: center;
+ font-style: italic;
+ color: var(--theme-comment-alt);
+ opacity: 1;
+}
+
+.input-expression:focus {
+ outline: none;
+ cursor: text;
+}
+
+.expression-input-container {
+ padding: 0.5em;
+ display: flex;
+}
+
+.expression-container {
+ border: 1px;
+ padding: 8px 5px 0px 0px;
+ width: 100%;
+ color: var(--theme-body-color);
+ background-color: var(--theme-body-background);
+ display: flex;
+ position: relative;
+}
+
+.expression-container > .tree {
+ width: 100%;
+ overflow: hidden;
+}
+
+:root.theme-light .expression-container:hover {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+:root.theme-dark .expression-container:hover {
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.expression-container .close-btn {
+ position: absolute;
+ inset-inline-end: 6px;
+ top: 6px;
+}
+
+.expression-container .close {
+ display: none;
+}
+
+.expression-container:hover .close {
+ display: block;
+}
+
+.expression-input {
+ cursor: pointer;
+ max-width: 50%;
+}
+
+.expression-separator {
+ padding: 0px 5px;
+}
+
+.expression-value {
+ overflow-x: scroll;
+ color: var(--theme-content-color2);
+ max-width: 50% !important;
+}
+
+.expression-error {
+ color: var(--theme-highlight-red);
+}
+
+.why-paused {
+ background-color: var(--breakpoint-active-color);
+ border: 1.7px solid var(--breakpoint-active-color);
+ color: var(--theme-highlight-blue);
+ padding: 10px 10px 10px 20px;
+ white-space: normal;
+ opacity: 0.9;
+ font-size: 12px;
+ font-weight: bold;
+ flex: 0 1 auto;
+}
+
+.theme-dark .secondary-panes .why-paused {
+ color: white;
+}
+.breakpoints-list * {
+ user-select: none;
+}
+
+.breakpoints-list .breakpoint {
+ font-size: 12px;
+ color: var(--theme-content-color1);
+ padding: 0.5em 1px;
+ line-height: 1em;
+ position: relative;
+ transition: all 0.25s ease;
+}
+
+html[dir="rtl"] .breakpoints-list .breakpoint {
+ border-right: 4px solid transparent;
+}
+
+html:not([dir="rtl"]) .breakpoints-list .breakpoint {
+ border-left: 4px solid transparent;
+}
+
+.breakpoints-list .breakpoint:last-of-type {
+ padding-bottom: 0.45em;
+}
+
+html:not([dir="rtl"]) .breakpoints-list .breakpoint.is-conditional {
+ border-left-color: var(--theme-graphs-yellow);
+}
+
+html[dir="rtl"] .breakpoints-list .breakpoint.is-conditional {
+ border-right-color: var(--theme-graphs-yellow);
+}
+
+html .breakpoints-list .breakpoint.paused {
+ background-color: var(--theme-toolbar-background-alt);
+ border-color: var(--breakpoint-active-color);
+}
+
+.breakpoints-list .breakpoint.disabled .breakpoint-label {
+ color: var(--theme-content-color3);
+ transition: color 0.5s linear;
+}
+
+.breakpoints-list .breakpoint:hover {
+ cursor: pointer;
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.breakpoints-list .breakpoint.paused:hover {
+ border-color: var(--breakpoint-active-color-hover);
+}
+
+.breakpoints-list .breakpoint-checkbox {
+ margin-inline-start: 0;
+}
+
+.breakpoints-list .breakpoint-label {
+ display: inline-block;
+ padding-inline-start: 2px;
+ padding-bottom: 4px;
+}
+
+.breakpoints-list .pause-indicator {
+ flex: 0 1 content;
+ order: 3;
+}
+
+:root.theme-light .breakpoint-snippet,
+:root.theme-firebug .breakpoint-snippet {
+ color: var(--theme-comment);
+}
+
+:root.theme-dark .breakpoint-snippet {
+ color: var(--theme-body-color);
+ opacity: 0.6;
+}
+
+.breakpoint-snippet {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-inline-start: 18px;
+ padding-inline-end: 18px;
+}
+
+.breakpoint .close-btn {
+ position: absolute;
+ inset-inline-end: 6px;
+ top: 12px;
+}
+
+.breakpoint .close {
+ display: none;
+}
+
+.breakpoint:hover .close {
+ display: block;
+}
+
+.object-node.default-property {
+ opacity: 0.6;
+}
+
+.object-label {
+ color: var(--theme-highlight-blue);
+}
+
+.objectBox-object,
+.objectBox-string,
+.objectBox-text,
+.objectBox-table,
+.objectLink-textNode,
+.objectLink-event,
+.objectLink-eventLog,
+.objectLink-regexp,
+.objectLink-object,
+.objectLink-Date,
+.theme-dark .objectBox-object,
+.theme-light .objectBox-object {
+ white-space: nowrap;
+}
+
+.scopes-list .tree-node {
+ overflow: hidden;
+}
+.frames ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.frames ul li {
+ cursor: pointer;
+ padding: 7px 10px 7px 21px;
+ overflow: hidden;
+ display: flex;
+ justify-content: space-between;
+}
+
+/* Style the focused call frame like so:
+.frames ul li:focus {
+ border: 3px solid red;
+}
+*/
+
+.frames ul li * {
+ user-select: none;
+}
+
+.frames ul li:nth-of-type(2n) {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.frames .location {
+ font-weight: lighter;
+}
+
+:root.theme-light .frames .location,
+:root.theme-firebug .frames .location {
+ color: var(--theme-comment);
+}
+
+:root.theme-dark .frames .location {
+ color: var(--theme-body-color);
+ opacity: 0.6;
+}
+
+.frames .title {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ margin-right: 1em;
+}
+
+.frames ul li:hover,
+.frames ul li:focus {
+ background-color: var(--theme-toolbar-background-alt);
+ outline: none;
+}
+
+.frames ul li.selected {
+ background-color: var(--theme-selection-background);
+ color: white;
+}
+
+:root.theme-light .frames ul li.selected .location,
+:root.theme-firebug .frames ul li.selected .location,
+:root.theme-dark .frames ul li.selected .location {
+ color: white;
+}
+
+:root.theme-dark .frames ul li:hover .location,
+:root.theme-dark .frames ul li.selected .location {
+ opacity: 1;
+}
+
+.show-more {
+ cursor: pointer;
+ text-align: center;
+ padding: 8px 0px;
+ border-top: 1px solid var(--theme-splitter-color);
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.show-more:hover {
+ background-color: var(--search-overlays-semitransparent);
+}
+.event-listeners {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.event-listeners .listener {
+ cursor: pointer;
+ padding: 7px 10px 7px 21px;
+ clear: both;
+ overflow: hidden;
+}
+
+.event-listeners .listener * {
+ user-select: none;
+}
+
+.event-listeners .listener:nth-of-type(2n) {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.event-listeners .listener .type {
+ color: var(--theme-highlight-bluegrey);
+ padding-right: 5px;
+}
+
+.event-listeners .listener .selector {
+ color: var(--theme-content-color2);
+}
+
+.event-listeners .listener-checkbox {
+ margin-left: 0;
+}
+
+.event-listeners .listener .close-btn {
+ float: right;
+}
+
+.event-listeners .listener .close {
+ display: none;
+}
+
+.event-listeners .listener:hover .close {
+ display: block;
+}
+.accordion {
+ background-color: var(--theme-body-background);
+ width: 100%;
+}
+
+.accordion ._header {
+ background-color: var(--theme-toolbar-background);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ cursor: pointer;
+ font-size: 12px;
+ padding: 5px;
+ transition: all 0.25s ease;
+ width: 100%;
+
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+.accordion ._header {
+ display: flex;
+}
+
+.accordion ._header:hover {
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.accordion ._header button svg,
+.accordion ._header:hover button svg {
+ fill: currentColor;
+ height: 16px;
+}
+
+.accordion ._content {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ font-size: 12px;
+}
+
+.accordion ._header .header-buttons {
+ display: flex;
+ margin-left: auto;
+ padding-right: 5px;
+}
+
+.accordion .header-buttons .add-button {
+ font-size: 180%;
+ text-align: center;
+ line-height: 16px;
+}
+
+.accordion .header-buttons button {
+ color: var(--theme-body-color);
+ border: none;
+ background: none;
+ outline: 0;
+ padding: 0;
+ width: 16px;
+ height: 16px;
+}
+
+.accordion .header-buttons button::-moz-focus-inner {
+ border: none;
+}
+.command-bar {
+ flex: 0 0 30px;
+ border-bottom: 1px solid var(--theme-splitter-color);
+ display: flex;
+ height: 30px;
+ overflow: hidden;
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ background-color: var(--theme-body-background);
+}
+
+.theme-dark .command-bar {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.command-bar > button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ display: inline-block;
+ text-align: center;
+ transition: all 0.25s ease;
+ padding: 8px 5px;
+ position: relative;
+ fill: currentColor;
+}
+
+:root.theme-dark .command-bar > button {
+ color: var(--theme-body-color);
+}
+
+.command-bar > button {
+ margin-inline-end: 0.7em;
+}
+
+html .command-bar > button:disabled {
+ opacity: 0.3;
+ cursor: default;
+}
+
+.command-bar > button > i {
+ height: 100%;
+ width: 100%;
+ display: block;
+}
+
+.command-bar > button > i > svg {
+ width: 16px;
+ height: 16px;
+}
+
+.command-bar button.pause-exceptions {
+ margin-inline-start: 0.5em;
+}
+
+.command-bar .subSettings {
+ float: right;
+}
+
+.command-bar button.pause-exceptions.uncaught {
+ color: var(--theme-highlight-purple);
+}
+
+.command-bar button.pause-exceptions.all {
+ color: var(--theme-highlight-blue);
+}
+.secondary-panes {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ white-space: nowrap;
+}
+
+.secondary-panes * {
+ user-select: none;
+}
+
+.secondary-panes .accordion {
+ overflow-y: auto;
+ overflow-x: hidden;
+ flex: 1 0 auto;
+}
+
+.pane {
+ color: var(--theme-body-color);
+}
+
+.pane .pane-info {
+ font-style: italic;
+ text-align: center;
+ padding: 0.5em;
+ user-select: none;
+}
+
+.theme-dark .secondary-panes .accordion .arrow svg {
+ fill: var(--theme-comment);
+}
+.welcomebox {
+ width: calc(100% - 1px);
+
+ /* Offsetting it by 30px for the sources-header area */
+ height: calc(100% - 30px);
+ position: absolute;
+ top: 30px;
+ left: 0;
+ padding: 50px 0 0 0;
+ text-align: center;
+ font-size: 1.25em;
+ color: var(--theme-comment-alt);
+ background-color: var(--theme-tab-toolbar-background);
+ font-weight: lighter;
+ z-index: 100;
+}
+
+html .welcomebox .toggle-button-end {
+ bottom: 11px;
+ position: absolute;
+ top: auto;
+}
+.dropdown {
+ --width: 150px;
+ background: var(--theme-body-background);
+ border: 1px solid var(--theme-splitter-color);
+ box-shadow: 0 4px 4px 0 var(--search-overlays-semitransparent);
+ max-height: 300px;
+ position: absolute;
+ right: 8px;
+ top: 35px;
+ width: var(--width);
+ z-index: 1000;
+}
+
+html[dir="rtl"] .dropdown {
+ right: calc((var(--width) - 11px) * (-1));
+}
+
+.dropdown-block {
+ padding: 0px 2px;
+ position: relative;
+ align-self: center;
+}
+
+.dropdown-button {
+ cursor: pointer;
+ color: var(--theme-comment);
+ background: none;
+ border: none;
+ padding: 0;
+ font-weight: 100;
+ margin-top: 6px;
+ font-size: 14px;
+}
+
+.dropdown li {
+ transition: all 0.25s ease;
+ padding: 2px 10px 10px 5px;
+ overflow: hidden;
+ height: 30px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.dropdown li:hover {
+ background-color: var(--search-overlays-semitransparent);
+ cursor: pointer;
+}
+
+.dropdown ul {
+ list-style: none;
+ line-height: 2em;
+ font-size: 1em;
+ margin: 0;
+ padding: 0;
+}
+
+.dropdown-mask {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ background: transparent;
+ z-index: 999;
+ left: 0;
+ top: 0;
+}
+.source-header {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ width: 100%;
+ height: 30px;
+ display: flex;
+ align-items: flex-end;
+}
+
+.source-header * {
+ user-select: none;
+}
+
+.source-header .new-tab-btn {
+ padding: 0px 4px;
+ margin-top: 8px;
+ cursor: pointer;
+ fill: var(--theme-comment);
+ transition: 0.1s ease;
+ align-self: center;
+}
+
+.source-header .new-tab-btn svg {
+ width: 12px;
+}
+
+.source-tabs {
+ max-width: calc(100% - 80px);
+ align-self: flex-start;
+}
+
+.source-tab {
+ border: 1px solid transparent;
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ height: 30px;
+ display: inline-flex;
+ align-items: center;
+ position: relative;
+ transition: all 0.25s ease;
+ min-width: 40px;
+ overflow: hidden;
+ padding: 6px;
+ margin-inline-start: 3px;
+ margin-top: 2px;
+}
+
+.source-tab:hover {
+ background-color: var(--theme-toolbar-background-alt);
+ border-color: var(--theme-splitter-color);
+ cursor: pointer;
+}
+
+.source-tab.active {
+ color: var(--theme-body-color);
+ background-color: var(--theme-body-background);
+ border-color: var(--theme-splitter-color);
+ border-bottom-color: transparent;
+}
+
+.source-tab.active path,
+.source-tab:hover path {
+ fill: var(--theme-body-color);
+}
+
+.source-tab .prettyPrint {
+ line-height: 0;
+}
+
+.source-tab .prettyPrint svg {
+ height: 12px;
+ width: 12px;
+}
+
+.source-tab .prettyPrint path {
+ fill: var(--theme-textbox-box-shadow);
+}
+
+.source-tab .filename {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.source-tab.pretty .filename {
+ padding-inline-start: 8px;
+}
+
+.source-tab .close-btn {
+ visibility: hidden;
+ line-height: 0;
+ margin-inline-start: 6px;
+}
+
+.source-tab.active .close-btn {
+ visibility: visible;
+}
+
+.source-tab:hover .close-btn {
+ visibility: visible;
+}
+
+.source-tab .close-btn .close {
+ padding: 0;
+ margin-top: 0;
+ display: inline-flex;
+ justify-content: center;
+}
diff --git a/layout/style/test/gtest/generate_example_stylesheet.py b/layout/style/test/gtest/generate_example_stylesheet.py
new file mode 100644
index 0000000000..5c69f5c702
--- /dev/null
+++ b/layout/style/test/gtest/generate_example_stylesheet.py
@@ -0,0 +1,16 @@
+def main(output, stylesheet):
+ css = open(stylesheet, "r").read()
+ css = (
+ css.replace("\\", "\\\\")
+ .replace("\r", "\\r")
+ .replace("\n", "\\n")
+ .replace('"', '\\"')
+ )
+
+ # Work around "error C2026: string too big"
+ # https://msdn.microsoft.com/en-us/library/dddywwsc.aspx
+ chunk_size = 10000
+ chunks = ('"%s"' % css[i : i + chunk_size] for i in range(0, len(css), chunk_size))
+
+ header = "#define EXAMPLE_STYLESHEET " + " ".join(chunks)
+ output.write(header)
diff --git a/layout/style/test/gtest/moz.build b/layout/style/test/gtest/moz.build
new file mode 100644
index 0000000000..aa3adbc9ab
--- /dev/null
+++ b/layout/style/test/gtest/moz.build
@@ -0,0 +1,24 @@
+# -*- 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/.
+
+Library("style-gtest")
+
+UNIFIED_SOURCES = [
+ "ImportScannerTest.cpp",
+ "StyloParsingBench.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/layout/style",
+]
+
+GeneratedFile(
+ "ExampleStylesheet.h",
+ script="generate_example_stylesheet.py",
+ inputs=["example.css"],
+)
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/layout/style/test/mapped.css b/layout/style/test/mapped.css
new file mode 100644
index 0000000000..7aebaecc82
--- /dev/null
+++ b/layout/style/test/mapped.css
@@ -0,0 +1,3 @@
+div {
+ color: #f06;
+}
diff --git a/layout/style/test/mapped.css^headers^ b/layout/style/test/mapped.css^headers^
new file mode 100644
index 0000000000..3b74491556
--- /dev/null
+++ b/layout/style/test/mapped.css^headers^
@@ -0,0 +1 @@
+X-SourceMap: mapped.css.map
diff --git a/layout/style/test/mapped2.css b/layout/style/test/mapped2.css
new file mode 100644
index 0000000000..fc42a5abef
--- /dev/null
+++ b/layout/style/test/mapped2.css
@@ -0,0 +1,4 @@
+span {
+ color: #f06;
+}
+//# sourceMappingURL: overridden-by-headers
diff --git a/layout/style/test/mapped2.css^headers^ b/layout/style/test/mapped2.css^headers^
new file mode 100644
index 0000000000..1656e66589
--- /dev/null
+++ b/layout/style/test/mapped2.css^headers^
@@ -0,0 +1,2 @@
+SourceMap: mapped2.css.map
+X-SourceMap: ignored.css.map
diff --git a/layout/style/test/media_queries_iframe.html b/layout/style/test/media_queries_iframe.html
new file mode 100644
index 0000000000..141ecdcd94
--- /dev/null
+++ b/layout/style/test/media_queries_iframe.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US">
+<head>
+ <title>Media Queries Test inner frame</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <style type="text/css" id="style" media="all">
+ body { text-decoration: underline; }
+ </style>
+</head>
+<body>
+
+</body>
+</html>
diff --git a/layout/style/test/media_queries_iframe2.html b/layout/style/test/media_queries_iframe2.html
new file mode 100644
index 0000000000..8768cd53c6
--- /dev/null
+++ b/layout/style/test/media_queries_iframe2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <title>Media Queries Test inner frame</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <style>
+ html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ }
+ </style>
+ <style type="text/css" id="style" media="all and (display-mode: browser)">
+ body { background: yellow; }
+ </style>
+ <style type="text/css" id="style" media="all and (display-mode: standalone)">
+ body { background: green; }
+ </style>
+ <style type="text/css" id="style" media="all and (display-mode: minimal-ui)">
+ body { background: red; }
+ </style>
+ <style type="text/css" id="style" media="all and (display-mode: fullscreen)">
+ body { background: blue; }
+ </style>
+</head>
+<body>
+<script>
+window.addEventListener("message", e => {
+ if (e.data == "get-background-color") {
+ let { backgroundColor } = document.defaultView.getComputedStyle(document.body);
+ e.source.postMessage({ backgroundColor }, e.origin);
+ }
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/mochitest.toml b/layout/style/test/mochitest.toml
new file mode 100644
index 0000000000..9df86ea539
--- /dev/null
+++ b/layout/style/test/mochitest.toml
@@ -0,0 +1,783 @@
+[DEFAULT]
+prefs = [
+ "dom.animations-api.compositing.enabled=true",
+ "dom.animations-api.timelines.enabled=true",
+ "gfx.omta.background-color=true",
+ "gfx.font_loader.delay=0",
+ "layout.css.container-queries.enabled=true",
+ "layout.css.individual-transform.enabled=true",
+ "layout.css.motion-path-ray.enabled=true",
+ "layout.css.motion-path-basic-shapes.enabled=true",
+ "layout.css.motion-path-coord-box.enabled=true",
+ "layout.css.motion-path-offset-position.enabled=true",
+ "layout.css.motion-path-url.enabled=true",
+ "layout.css.backdrop-filter.enabled=true",
+ "layout.css.fit-content-function.enabled=true",
+ "layout.css.scroll-driven-animations.enabled=true",
+ "layout.css.animation-composition.enabled=true",
+ "layout.css.basic-shape-rect.enabled=true",
+ "layout.css.basic-shape-xywh.enabled=true",
+ "layout.css.transform-box-content-stroke.enabled=true",
+]
+support-files = [
+ "animation_utils.js",
+ "bug1729861.js",
+ "ccd-quirks.html",
+ "ccd.sjs",
+ "ccd-standards.html",
+ "chrome/bug418986-2.js",
+ "chrome/match.png",
+ "chrome/mismatch.png",
+ "descriptor_database.js",
+ "!/dom/events/test/event_leak_utils.js",
+ "empty.html",
+ "file_computed_style_bfcache_display_none.html",
+ "file_computed_style_bfcache_display_none2.html",
+ "media_queries_iframe.html",
+ "media_queries_iframe2.html",
+ "neverending_font_load.sjs",
+ "neverending_stylesheet_load.sjs",
+ "post-redirect-1.css",
+ "post-redirect-2.css",
+ "post-redirect-3.css",
+ "property_database.js",
+ "redirect.sjs",
+ "style_attribute_tests.js",
+ "support/blue-100x100.png",
+ "support/1x1-transparent.png",
+ "unstyled.css",
+ "unstyled-frame.css",
+ "unstyled-frame.xml",
+ "unstyled.xml",
+ "viewport_units_iframe.html",
+ "visited_image_loading_frame_empty.html",
+ "visited_image_loading_frame.html",
+ "visited_image_loading.sjs",
+ "visited-lying-inner.html",
+ "visited-pref-iframe.html",
+]
+
+["test_acid3_test46.html"]
+
+["test_addSheet.html"]
+support-files = ["additional_sheets_helper.html"]
+
+["test_additional_sheets.html"]
+support-files = ["additional_sheets_helper.html"]
+
+["test_align_justify_computed_values.html"]
+
+["test_all_shorthand.html"]
+
+["test_animations.html"]
+
+["test_animations_async_tests.html"]
+support-files = [
+ "Ahem.ttf",
+ "file_animations_async_tests.html",
+]
+
+["test_animations_dynamic_changes.html"]
+
+["test_animations_effect_timing_duration.html"]
+
+["test_animations_effect_timing_enddelay.html"]
+
+["test_animations_effect_timing_iterations.html"]
+
+["test_animations_event_handler_attribute.html"]
+
+["test_animations_event_order.html"]
+
+["test_animations_iterationstart.html"]
+
+["test_animations_omta.html"]
+
+["test_animations_omta_scroll.html"]
+support-files = ["file_animations_omta_scroll.html"]
+
+["test_animations_omta_scroll_rtl.html"]
+support-files = ["file_animations_omta_scroll_rtl.html"]
+
+["test_animations_omta_start.html"]
+
+["test_animations_pausing.html"]
+
+["test_animations_playbackrate.html"]
+
+["test_animations_reverse.html"]
+
+["test_animations_styles_on_event.html"]
+
+["test_animations_variable_changes.html"]
+
+["test_animations_with_disabled_properties.html"]
+support-files = ["file_animations_with_disabled_properties.html"]
+
+["test_any_dynamic.html"]
+
+["test_area_url_cursor.html"]
+
+["test_asyncopen.html"]
+
+["test_at_rule_parse_serialize.html"]
+
+["test_attribute_selector_eof_behavior.html"]
+
+["test_backdrop_filter_enabled_state.html"]
+
+["test_background_blend_mode.html"]
+
+["test_border_device_pixel_rounding_initial_style.html"]
+
+["test_box_size_keywords.html"]
+
+["test_bug73586.html"]
+
+["test_bug74880.html"]
+
+["test_bug98997.html"]
+
+["test_bug160403.html"]
+
+["test_bug200089.html"]
+
+["test_bug221428.html"]
+
+["test_bug229915.html"]
+
+["test_bug302186.html"]
+
+["test_bug319381.html"]
+
+["test_bug357614.html"]
+
+["test_bug363146.html"]
+
+["test_bug372770.html"]
+
+["test_bug373293.html"]
+
+["test_bug377947.html"]
+
+["test_bug379440.html"]
+
+["test_bug379741.html"]
+
+["test_bug382027.html"]
+
+["test_bug383075.html"]
+
+["test_bug387615.html"]
+
+["test_bug389464.html"]
+
+["test_bug391034.html"]
+
+["test_bug391221.html"]
+
+["test_bug397427.html"]
+fail-if = ["xorigin"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_bug399349.html"]
+
+["test_bug401046.html"]
+skip-if = ["true"] # Bug 701060
+
+["test_bug405818.html"]
+
+["test_bug412901.html"]
+
+["test_bug413958.html"]
+
+["test_bug418986-2.html"]
+
+["test_bug437915.html"]
+
+["test_bug450191.html"]
+
+["test_bug470769.html"]
+
+["test_bug499655.html"]
+
+["test_bug499655.xhtml"]
+
+["test_bug517224.html"]
+support-files = ["bug517224.sjs"]
+
+["test_bug524175.html"]
+
+["test_bug525952.html"]
+
+["test_bug534804.html"]
+
+["test_bug573255.html"]
+
+["test_bug580685.html"]
+
+["test_bug621351.html"]
+
+["test_bug635286.html"]
+
+["test_bug645998.html"]
+support-files = [
+ "file_bug645998-1.css",
+ "file_bug645998-2.css",
+]
+
+["test_bug652486.html"]
+
+["test_bug657143.html"]
+
+["test_bug667520.html"]
+
+["test_bug716226.html"]
+
+["test_bug732153.html"]
+
+["test_bug732209.html"]
+support-files = ["bug732209-css.sjs"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_bug765590.html"]
+
+["test_bug771043.html"]
+
+["test_bug795520.html"]
+
+["test_bug798843_pref.html"]
+
+["test_bug829816.html"]
+support-files = ["file_bug829816.css"]
+
+["test_bug874919.html"]
+
+["test_bug887741_at-rules_in_declaration_lists.html"]
+
+["test_bug892929.html"]
+
+["test_bug1055933.html"]
+support-files = ["file_bug1055933_circle-xxl.png"]
+
+["test_bug1089417.html"]
+support-files = ["file_bug1089417_iframe.html"]
+
+["test_bug1112014.html"]
+
+["test_bug1203766.html"]
+
+["test_bug1232829.html"]
+
+["test_bug1292447.html"]
+
+["test_bug1330375.html"]
+
+["test_bug1371488.html"]
+
+["test_bug1375944.html"]
+support-files = [
+ "file_bug1375944.html",
+ "Ahem.ttf",
+]
+
+["test_bug1382568.html"]
+support-files = ["bug1382568-iframe.html"]
+
+["test_bug1394302.html"]
+
+["test_bug1443344-1.html"]
+scheme = "https"
+support-files = ["file_bug1443344.css"]
+
+["test_bug1443344-2.html"]
+scheme = "https"
+support-files = ["file_bug1443344.css"]
+
+["test_bug1451199-1.html"]
+
+["test_bug1451199-2.html"]
+
+["test_bug1490890.html"]
+
+["test_bug1505254.html"]
+
+["test_bug1729861.html"]
+
+["test_cascade.html"]
+
+["test_ch_ex_no_infloops.html"]
+
+["test_change_hint_optimizations.html"]
+
+["test_clip-path_polygon.html"]
+
+["test_color_rounding.html"]
+
+["test_compute_data_with_start_struct.html"]
+skip-if = ["os == 'android'"]
+
+["test_computed_style.html"]
+
+["test_computed_style_bfcache_display_none.html"]
+
+["test_computed_style_difference.html"]
+
+["test_computed_style_grid_with_pseudo.html"]
+
+["test_computed_style_in_created_document.html"]
+
+["test_computed_style_min_size_auto.html"]
+
+["test_computed_style_no_flush.html"]
+
+["test_computed_style_no_pseudo.html"]
+
+["test_computed_style_prefs.html"]
+
+["test_condition_text.html"]
+
+["test_constructable_stylesheets_chrome_only_rules_in_content.html"]
+
+["test_counter_descriptor_storage.html"]
+
+["test_counter_style.html"]
+
+["test_crash_with_content_policy.html"]
+support-files = ["file_bug1381233.html"]
+
+["test_css_cross_domain.html"]
+skip-if = [
+ "http3",
+ "http2",
+ "socketprocess_networking",
+]
+
+["test_css_cross_domain_no_orb.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_css_eof_handling.html"]
+
+["test_css_escape_api.html"]
+
+["test_css_function_mismatched_parenthesis.html"]
+
+["test_css_loader_crossorigin_data_url.html"]
+
+["test_css_parse_error_smoketest.html"]
+
+["test_css_supports.html"]
+
+["test_css_supports_variables.html"]
+
+["test_cue_restrictions.html"]
+
+["test_custom_content_inheritance.html"]
+
+["test_default_bidi_css.html"]
+
+["test_default_computed_style.html"]
+
+["test_descriptor_storage.html"]
+
+["test_descriptor_syntax_errors.html"]
+
+["test_display_mode.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_dont_use_document_colors.html"]
+
+["test_dont_use_document_fonts.html"]
+
+["test_dynamic_change_causing_reflow.html"]
+
+["test_exposed_prop_accessors.html"]
+
+["test_extra_inherit_initial.html"]
+
+["test_first_letter_restrictions.html"]
+
+["test_first_line_restrictions.html"]
+
+["test_flexbox_child_display_values.xhtml"]
+
+["test_flexbox_flex_grow_and_shrink.html"]
+
+["test_flexbox_flex_shorthand.html"]
+
+["test_flexbox_focus_order.html"]
+
+["test_flexbox_layout.html"]
+support-files = ["flexbox_layout_testcases.js"]
+
+["test_flexbox_order.html"]
+
+["test_flexbox_order_abspos.html"]
+
+["test_flexbox_order_table.html"]
+
+["test_flexbox_reflow_counts.html"]
+skip-if = ["verify"]
+
+["test_flushing_frame.html"]
+
+["test_font_face_cascade.html"]
+
+["test_font_face_parser.html"]
+
+["test_font_family_parsing.html"]
+
+["test_font_family_serialization.html"]
+
+["test_font_loading_api.html"]
+support-files = [
+ "BitPattern.woff",
+ "file_font_loading_api_vframe.html",
+]
+# This test checks font loading state. When loaded second time, fonts may be
+# loaded synchronously, causing this test to fail in test-verify task.
+skip-if = [
+ "verify", # Bug 1455824
+ "os == 'android'", # Bug 1455824
+ "http3",
+ "http2",
+]
+
+["test_garbage_at_end_of_declarations.html"]
+
+["test_grid_computed_values.html"]
+
+["test_grid_container_shorthands.html"]
+
+["test_grid_item_shorthands.html"]
+
+["test_grid_shorthand_serialization.html"]
+
+["test_group_insertRule.html"]
+
+["test_hover_on_part.html"]
+
+["test_hover_quirk.html"]
+
+["test_html_attribute_computed_values.html"]
+
+["test_ident_escaping.html"]
+
+["test_img_src_causing_reflow.html"]
+
+["test_import_preload.html"]
+support-files = ["slow_load.sjs"]
+# Test is slightly racy and on Android it fails frequently enough to be
+# annoying.
+skip-if = ["os == 'android'"]
+
+["test_inherit_computation.html"]
+
+["test_inherit_storage.html"]
+
+["test_initial_computation.html"]
+
+["test_initial_storage.html"]
+
+["test_invalidation_basic.html"]
+
+["test_keyframes_rules.html"]
+
+["test_keyframes_vendor_prefix.html"]
+
+["test_load_events_on_stylesheets.html"]
+support-files = [
+ "slow_broken_sheet.sjs",
+ "slow_ok_sheet.sjs",
+]
+
+["test_logical_properties.html"]
+
+["test_marker_restrictions.html"]
+
+["test_mask_image_CORS.html"]
+
+["test_media_queries.html"]
+# times out on verify, see bug 1461033.
+skip-if = ["verify"]
+support-files = ["chrome/chrome-only-media-queries.js"]
+
+["test_media_queries_dynamic.html"]
+skip-if = ["xorigin"] # Crashes, Assertion failure: mInFlightProcessId == 0, at /builds/worker/checkouts/gecko/docshell/base/CanonicalBrowsingContext.cpp:110, [Child][MessageChannel] Error: (msgtype=0xFFF7,name=<unknown IPC msg name>) Channel error: cannot send/recv
+
+["test_media_query_list.html"]
+
+["test_media_query_serialization.html"]
+
+["test_moz_device_pixel_ratio.html"]
+
+["test_moz_prefixed_cursor.html"]
+
+["test_mq_any_hover_and_any_pointer.html"]
+
+["test_mq_changes_in_iframe.html"]
+support-files = ["mq_changes_child.html"]
+skip-if = [
+ "headless",
+ "os == 'win'",
+]
+
+["test_mq_hover_and_pointer.html"]
+
+["test_mq_prefers_contrast_dynamic.html"]
+skip-if = [
+ "headless",
+ "os == 'win'",
+]
+
+["test_mq_prefers_reduced_motion_dynamic.html"]
+skip-if = [
+ "headless",
+ "os == 'win'",
+]
+
+["test_mql_event_listener_leaks.html"]
+
+["test_namespace_rule.html"]
+
+["test_non_content_accessible_env_vars.html"]
+
+["test_non_content_accessible_properties.html"]
+
+["test_non_content_accessible_pseudos.html"]
+
+["test_non_content_accessible_values.html"]
+
+["test_non_matching_sheet_media.html"]
+
+["test_of_type_selectors.xhtml"]
+
+["test_overscroll_behavior_pref.html"]
+
+["test_page_parser.html"]
+
+["test_parse_eof.html"]
+
+["test_parse_ident.html"]
+
+["test_parse_rule.html"]
+
+["test_parse_url.html"]
+
+["test_parser_diagnostics_unprintables.html"]
+
+["test_pixel_lengths.html"]
+
+["test_placeholder_restrictions.html"]
+
+["test_pointer-events.html"]
+
+["test_position_float_display.html"]
+
+["test_position_sticky.html"]
+
+["test_prefers_contrast_color_pairs.html"]
+
+["test_priority_preservation.html"]
+
+["test_property_database.html"]
+
+["test_property_syntax_errors.html"]
+
+["test_pseudo_display_fixup.html"]
+
+["test_pseudoelement_parsing.html"]
+
+["test_pseudoelement_state.html"]
+skip-if = ["verify && debug && os == 'linux'"]
+
+["test_query_container_for.html"]
+
+["test_redundant_font_download.html"]
+support-files = ["redundant_font_download.sjs"]
+
+["test_reframe_cb.html"]
+
+["test_reframe_image_loading.html"]
+
+["test_reframe_input.html"]
+
+["test_reframe_pseudo_element.html"]
+
+["test_rem_unit.html"]
+
+["test_restyle_table_wrapper.html"]
+
+["test_restyles_in_smil_animation.html"]
+
+["test_revert.html"]
+
+["test_root_node_display.html"]
+
+["test_rule_insertion.html"]
+
+["test_rules_out_of_sheets.html"]
+
+["test_selectors.html"]
+
+["test_setPropertyWithNull.html"]
+skip-if = ["xorigin && debug"]
+
+["test_shape_outside_CORS.html"]
+
+["test_shared_sheet_caching.html"]
+support-files = [
+ "file_shared_sheet_caching.css",
+ "file_shared_sheet_caching.html",
+]
+fail-if = ["xorigin"]
+
+["test_sheet_privilege.html"]
+
+["test_shorthand_property_getters.html"]
+
+["test_specified_value_serialization.html"]
+support-files = ["file_specified_value_serialization_individual_transforms.html"]
+
+["test_style_attr_listener.html"]
+
+["test_style_attribute_quirks.html"]
+
+["test_style_attribute_standards.html"]
+
+["test_style_struct_copy_constructors.html"]
+
+["test_stylesheet_additions.html"]
+
+["test_stylesheet_clone_font_face.html"]
+
+["test_supports_rules.html"]
+
+["test_system_font_serialization.html"]
+
+["test_text_decoration_shorthands.html"]
+
+["test_transitions.html"]
+
+["test_transitions_and_reframes.html"]
+
+["test_transitions_and_restyles.html"]
+
+["test_transitions_and_zoom.html"]
+
+["test_transitions_at_start.html"]
+
+["test_transitions_bug537151.html"]
+
+["test_transitions_cancel_near_end.html"]
+
+["test_transitions_computed_value_combinations.html"]
+
+["test_transitions_computed_values.html"]
+
+["test_transitions_dynamic_changes.html"]
+
+["test_transitions_events.html"]
+
+["test_transitions_per_property.html"]
+
+["test_transitions_replacement_on_busy_frame.html"]
+
+["test_transitions_replacement_with_setKeyframes.html"]
+
+["test_transitions_step_functions.html"]
+
+["test_unclosed_parentheses.html"]
+
+["test_unicode_range_loading.html"]
+support-files = [
+ "../../reftests/fonts/markA.woff",
+ "../../reftests/fonts/markB.woff",
+ "../../reftests/fonts/markC.woff",
+ "../../reftests/fonts/markD.woff",
+]
+
+["test_units_angle.html"]
+
+["test_units_frequency.html"]
+
+["test_units_length.html"]
+
+["test_units_time.html"]
+
+["test_use_counters.html"]
+skip-if = ["!nightly_build"]
+
+["test_user_sheet_shadow_dom.html"]
+
+["test_value_cloning.html"]
+# This test requires too much memory on TSan (bug 1612707)
+# See bug 775227 for android
+skip-if = [
+ "os == 'android'",
+ "tsan",
+]
+
+["test_value_computation.html"]
+# This test requires too much memory on TSan (bug 1612707)
+skip-if = ["tsan"]
+
+["test_value_storage.html"]
+
+["test_variable_serialization_computed.html"]
+
+["test_variable_serialization_specified.html"]
+
+["test_variables.html"]
+support-files = ["support/external-variable-url.css"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_variables_loop.html"]
+
+["test_variables_order.html"]
+support-files = ["support/external-variable-url.css"]
+
+["test_video_object_fit.html"]
+
+["test_viewport_scrollbar_causing_reflow.html"]
+skip-if = ["verify && (os == 'win' || os == 'mac')"]
+
+["test_viewport_units.html"]
+
+["test_visited_image_loading.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+
+["test_visited_image_loading_empty.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+
+["test_visited_lying.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+fail-if = ["xorigin"]
+
+["test_visited_pref.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+fail-if = ["xorigin"]
+
+["test_visited_reftests.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+
+["test_webkit_device_pixel_ratio.html"]
+skip-if = ["xorigin"] # process crash: Assertion failure: mInFlightProcessId == 0, at /builds/worker/checkouts/gecko/docshell/base/CanonicalBrowsingContext.cpp:110
+
+["test_webkit_flex_display.html"]
+skip-if = ["xorigin"] # Crashes, Assertion failure: mInFlightProcessId == 0, at /builds/worker/checkouts/gecko/docshell/base/CanonicalBrowsingContext.cpp:110
diff --git a/layout/style/test/moz.build b/layout/style/test/moz.build
new file mode 100644
index 0000000000..ee826be9ed
--- /dev/null
+++ b/layout/style/test/moz.build
@@ -0,0 +1,152 @@
+# -*- 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/.
+
+# ** Note: The comment below along with the CPP_UNIT_TESTS and LIBS variables
+# ** were commented out in the original Makefile.in, and should be restored
+# ** some day, perhaps as a gtest.
+#
+# ParseCSS.cpp used to be built as a test program, but it was not
+# being used for anything, and recent changes to the CSS loader have
+# made it fail to link. Further changes are planned which should make
+# it buildable again.
+
+DIRS += ["gtest"]
+
+HostSimplePrograms(
+ [
+ "host_ListCSSProperties",
+ ]
+)
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.toml",
+]
+BROWSER_CHROME_MANIFESTS += ["browser.toml"]
+MOCHITEST_CHROME_MANIFESTS += ["chrome/chrome.toml"]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test.chrome += [
+ "chrome/display_mode_reflow_iframe.html",
+ "chrome/moz_document_helper.html",
+ "media_queries_iframe.html",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test["css-visited"] += [
+ "/layout/reftests/css-visited/border-1-ref.html",
+ "/layout/reftests/css-visited/border-1.html",
+ "/layout/reftests/css-visited/border-2-ref.html",
+ "/layout/reftests/css-visited/border-2a.html",
+ "/layout/reftests/css-visited/border-2b.html",
+ "/layout/reftests/css-visited/border-collapse-1-ref.html",
+ "/layout/reftests/css-visited/border-collapse-1.html",
+ "/layout/reftests/css-visited/caret-color-on-visited-1-ref.html",
+ "/layout/reftests/css-visited/caret-color-on-visited-1.html",
+ "/layout/reftests/css-visited/color-choice-1-ref.html",
+ "/layout/reftests/css-visited/color-choice-1.html",
+ "/layout/reftests/css-visited/color-on-bullets-1-ref.html",
+ "/layout/reftests/css-visited/color-on-bullets-1.html",
+ "/layout/reftests/css-visited/color-on-link-1-ref.html",
+ "/layout/reftests/css-visited/color-on-link-1.html",
+ "/layout/reftests/css-visited/color-on-link-before-1.html",
+ "/layout/reftests/css-visited/color-on-text-decoration-1-ref.html",
+ "/layout/reftests/css-visited/color-on-text-decoration-1.html",
+ "/layout/reftests/css-visited/color-on-visited-1-ref.html",
+ "/layout/reftests/css-visited/color-on-visited-1.html",
+ "/layout/reftests/css-visited/color-on-visited-before-1.html",
+ "/layout/reftests/css-visited/color-on-visited-text-1-ref.html",
+ "/layout/reftests/css-visited/color-on-visited-text-1.html",
+ "/layout/reftests/css-visited/column-rule-1-notref.html",
+ "/layout/reftests/css-visited/column-rule-1-ref.html",
+ "/layout/reftests/css-visited/column-rule-1.html",
+ "/layout/reftests/css-visited/content-before-1-ref.html",
+ "/layout/reftests/css-visited/content-color-on-link-before-1-ref.html",
+ "/layout/reftests/css-visited/content-color-on-link-before-1.html",
+ "/layout/reftests/css-visited/content-color-on-visited-before-1-ref.html",
+ "/layout/reftests/css-visited/content-color-on-visited-before-1.html",
+ "/layout/reftests/css-visited/content-on-link-before-1.html",
+ "/layout/reftests/css-visited/content-on-visited-before-1.html",
+ "/layout/reftests/css-visited/first-line-1-ref.html",
+ "/layout/reftests/css-visited/first-line-1.html",
+ "/layout/reftests/css-visited/inherit-keyword-1-ref.html",
+ "/layout/reftests/css-visited/inherit-keyword-1.xhtml",
+ "/layout/reftests/css-visited/link-root-1-ref.xhtml",
+ "/layout/reftests/css-visited/link-root-1.xhtml",
+ "/layout/reftests/css-visited/logical-box-border-color-visited-link-001.html",
+ "/layout/reftests/css-visited/logical-box-border-color-visited-link-002.html",
+ "/layout/reftests/css-visited/logical-box-border-color-visited-link-003.html",
+ "/layout/reftests/css-visited/logical-box-border-color-visited-link-ref.html",
+ "/layout/reftests/css-visited/mathml-links-ref.html",
+ "/layout/reftests/css-visited/mathml-links.html",
+ "/layout/reftests/css-visited/outline-1-ref.html",
+ "/layout/reftests/css-visited/outline-1.html",
+ "/layout/reftests/css-visited/placeholder-1-ref.html",
+ "/layout/reftests/css-visited/placeholder-1.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-1-ref.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-1.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-2-ref.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-2.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-3-ref.xhtml",
+ "/layout/reftests/css-visited/selector-adj-sibling-3.xhtml",
+ "/layout/reftests/css-visited/selector-any-sibling-1-ref.html",
+ "/layout/reftests/css-visited/selector-any-sibling-1.html",
+ "/layout/reftests/css-visited/selector-any-sibling-2-ref.html",
+ "/layout/reftests/css-visited/selector-any-sibling-2.html",
+ "/layout/reftests/css-visited/selector-child-1-ref.html",
+ "/layout/reftests/css-visited/selector-child-1.html",
+ "/layout/reftests/css-visited/selector-child-2-ref.xhtml",
+ "/layout/reftests/css-visited/selector-child-2.xhtml",
+ "/layout/reftests/css-visited/selector-descendant-1-ref.html",
+ "/layout/reftests/css-visited/selector-descendant-1.html",
+ "/layout/reftests/css-visited/selector-descendant-2-ref.xhtml",
+ "/layout/reftests/css-visited/selector-descendant-2.xhtml",
+ "/layout/reftests/css-visited/subject-of-selector-1-ref.html",
+ "/layout/reftests/css-visited/subject-of-selector-adj-sibling-1.html",
+ "/layout/reftests/css-visited/subject-of-selector-any-sibling-1.html",
+ "/layout/reftests/css-visited/subject-of-selector-child-1.html",
+ "/layout/reftests/css-visited/subject-of-selector-descendant-1.html",
+ "/layout/reftests/css-visited/subject-of-selector-descendant-2-ref.xhtml",
+ "/layout/reftests/css-visited/subject-of-selector-descendant-2.xhtml",
+ "/layout/reftests/css-visited/svg-paint-currentcolor-visited-ref.svg",
+ "/layout/reftests/css-visited/svg-paint-currentcolor-visited.svg",
+ "/layout/reftests/css-visited/transition-on-visited-ref.html",
+ "/layout/reftests/css-visited/transition-on-visited.html",
+ "/layout/reftests/css-visited/variables-visited-ref.html",
+ "/layout/reftests/css-visited/variables-visited.html",
+ "/layout/reftests/css-visited/visited-inherit-1-ref.html",
+ "/layout/reftests/css-visited/visited-inherit-1.html",
+ "/layout/reftests/css-visited/visited-page.html",
+ "/layout/reftests/css-visited/white-to-transparent-1-ref.html",
+ "/layout/reftests/css-visited/white-to-transparent-1.html",
+ "/layout/reftests/css-visited/width-1-ref.html",
+ "/layout/reftests/css-visited/width-on-link-1.html",
+ "/layout/reftests/css-visited/width-on-visited-1.html",
+ "/layout/reftests/fonts/Ahem.ttf",
+ "/layout/reftests/svg/as-image/svg-image-visited-1-ref.html",
+ "/layout/reftests/svg/as-image/svg-image-visited-1a-helper.svg",
+ "/layout/reftests/svg/as-image/svg-image-visited-1a.html",
+ "/layout/reftests/svg/as-image/svg-image-visited-1b-helper.svg",
+ "/layout/reftests/svg/as-image/svg-image-visited-1b.html",
+ "/layout/reftests/svg/as-image/svg-image-visited-1c-helper.svg",
+ "/layout/reftests/svg/as-image/svg-image-visited-1c.html",
+ "/layout/reftests/svg/as-image/svg-image-visited-1d-helper.svg",
+ "/layout/reftests/svg/as-image/svg-image-visited-1d.html",
+ "/layout/reftests/svg/pseudo-classes-02-ref.svg",
+ "/layout/reftests/svg/pseudo-classes-02.svg",
+]
+
+DEFINES["MOZILLA_INTERNAL_API"] = True
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ GeneratedFile(
+ "css_properties.js",
+ script="gen-css-properties.py",
+ inputs=[
+ "css_properties_like_longhand.js",
+ "!host_ListCSSProperties%s" % CONFIG["HOST_BIN_SUFFIX"],
+ ],
+ )
+ TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test += [
+ "!css_properties.js"
+ ]
diff --git a/layout/style/test/mq_changes_child.html b/layout/style/test/mq_changes_child.html
new file mode 100644
index 0000000000..f594e9f00d
--- /dev/null
+++ b/layout/style/test/mq_changes_child.html
@@ -0,0 +1,8 @@
+<script>
+const mql = matchMedia("(prefers-reduced-motion: reduce)");
+mql.addEventListener("change", event => {
+ parent.postMessage({ "matches": event.matches }, "*");
+});
+
+window.onload = () => { parent.postMessage("ready", "*"); };
+</script>
diff --git a/layout/style/test/neverending_font_load.sjs b/layout/style/test/neverending_font_load.sjs
new file mode 100644
index 0000000000..b02fc377a8
--- /dev/null
+++ b/layout/style/test/neverending_font_load.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "application/octet-stream", false);
+ response.write("");
+}
diff --git a/layout/style/test/neverending_stylesheet_load.sjs b/layout/style/test/neverending_stylesheet_load.sjs
new file mode 100644
index 0000000000..048263fe54
--- /dev/null
+++ b/layout/style/test/neverending_stylesheet_load.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "text/css", false);
+ response.write("");
+}
diff --git a/layout/style/test/post-redirect-1.css b/layout/style/test/post-redirect-1.css
new file mode 100644
index 0000000000..3620c9f377
--- /dev/null
+++ b/layout/style/test/post-redirect-1.css
@@ -0,0 +1 @@
+#one { color: green; background: url("?1"); }
diff --git a/layout/style/test/post-redirect-2.css b/layout/style/test/post-redirect-2.css
new file mode 100644
index 0000000000..3bdf3279d3
--- /dev/null
+++ b/layout/style/test/post-redirect-2.css
@@ -0,0 +1 @@
+#two { color: green; background: url("?1"); }
diff --git a/layout/style/test/post-redirect-3.css b/layout/style/test/post-redirect-3.css
new file mode 100644
index 0000000000..dd98be8e66
--- /dev/null
+++ b/layout/style/test/post-redirect-3.css
@@ -0,0 +1 @@
+#three { color: green; background: url("?1"); }
diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js
new file mode 100644
index 0000000000..422f2ffe5c
--- /dev/null
+++ b/layout/style/test/property_database.js
@@ -0,0 +1,14128 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* eslint-disable dot-notation */
+/* vim: set ts=2 sw=2 sts=2 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/. */
+
+// Utility function. Returns true if the given boolean pref...
+// (a) exists and (b) is set to true.
+// Otherwise, returns false.
+//
+// This function also reports a test failure if the pref isn't set at all. This
+// ensures that we remove pref-checks from mochitests (instead of accidentally
+// disabling the tests that are controlled by that check) when we remove a
+// mature feature's pref from the rest of the codebase.
+function IsCSSPropertyPrefEnabled(prefName) {
+ try {
+ if (SpecialPowers.getBoolPref(prefName)) {
+ return true;
+ }
+ } catch (ex) {
+ ok(
+ false,
+ "Failed to look up property-controlling pref '" +
+ prefName +
+ "' (" +
+ ex +
+ ")"
+ );
+ }
+
+ return false;
+}
+
+// True longhand properties.
+const CSS_TYPE_LONGHAND = 0;
+
+// True shorthand properties.
+const CSS_TYPE_TRUE_SHORTHAND = 1;
+
+// Properties that we handle as shorthands but were longhands either in
+// the current spec or earlier versions of the spec.
+const CSS_TYPE_SHORTHAND_AND_LONGHAND = 2;
+
+// Legacy shorthand properties, that behave mostly like an alias
+// (CSS_TYPE_SHORTHAND_AND_LONGHAND) but not quite because their syntax may not
+// match, plus they shouldn't serialize in cssText.
+const CSS_TYPE_LEGACY_SHORTHAND = 3;
+
+// Each property has the following fields:
+// domProp: The name of the relevant member of nsIDOM[NS]CSS2Properties
+// inherited: Whether the property is inherited by default (stated as
+// yes or no in the property header in all CSS specs)
+// type: see above
+// alias_for: optional, indicates that the property is an alias for
+// some other property that is the preferred serialization. (Type
+// must not be CSS_TYPE_LONGHAND.)
+// logical: optional, indicates that the property is a logical directional
+// property. (Type must be CSS_TYPE_LONGHAND.)
+// axis: optional, indicates that the property is an axis-related logical
+// directional property. (Type must be CSS_TYPE_LONGHAND and 'logical'
+// must be true.)
+// initial_values: Values whose computed value should be the same as the
+// computed value for the property's initial value.
+// other_values: Values whose computed value should be different from the
+// computed value for the property's initial value.
+// XXX Should have a third field for values whose computed value may or
+// may not be the same as for the property's initial value.
+// invalid_values: Things that are not values for the property and
+// should be rejected, but which are balanced and should not absorb
+// what follows
+// quirks_values: Values that should be accepted in quirks mode only,
+// mapped to the values they are equivalent to.
+// unbalanced_values: Things that are not values for the property and
+// should be rejected, and which also contain unbalanced constructs
+// that should absorb what follows
+//
+// Note: By default, an alias is assumed to accept/reject the same values as
+// the property that it aliases, and to have the same prerequisites. So, if
+// "alias_for" is set, the "*_values" and "prerequisites" fields can simply
+// be omitted, and they'll be populated automatically to match the aliased
+// property's fields.
+
+// Helper functions used to construct gCSSProperties.
+
+function initial_font_family_is_sans_serif() {
+ // The initial value of 'font-family' might be 'serif' or
+ // 'sans-serif'.
+ const meta = document.createElement("meta");
+ meta.setAttribute("style", "font: initial;");
+ document.documentElement.appendChild(meta);
+ const family = getComputedStyle(meta).fontFamily;
+ meta.remove();
+ return family == "sans-serif";
+}
+
+var gInitialFontFamilyIsSansSerif = initial_font_family_is_sans_serif();
+
+// shared by background-image and border-image-source
+var validNonUrlImageValues = [
+ "-moz-element(#a)",
+ "-moz-element( #a )",
+ "-moz-element(#a-1)",
+ "-moz-element(#a\\:1)",
+ /* gradient torture test */
+ "linear-gradient(red, blue)",
+ "linear-gradient(red, yellow, blue)",
+ "linear-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "linear-gradient(red, yellow, green, blue 50%)",
+ "linear-gradient(red -50%, yellow -25%, green, blue)",
+ "linear-gradient(red -99px, yellow, green, blue 120%)",
+ "linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+ "linear-gradient(red, green calc(50% + 20px), blue)",
+ "linear-gradient(180deg, red, blue)",
+
+ "linear-gradient(to top, red, blue)",
+ "linear-gradient(to bottom, red, blue)",
+ "linear-gradient(to left, red, blue)",
+ "linear-gradient(to right, red, blue)",
+ "linear-gradient(to top left, red, blue)",
+ "linear-gradient(to top right, red, blue)",
+ "linear-gradient(to bottom left, red, blue)",
+ "linear-gradient(to bottom right, red, blue)",
+ "linear-gradient(to left top, red, blue)",
+ "linear-gradient(to left bottom, red, blue)",
+ "linear-gradient(to right top, red, blue)",
+ "linear-gradient(to right bottom, red, blue)",
+
+ "linear-gradient(-33deg, red, blue)",
+ "linear-gradient(30grad, red, blue)",
+ "linear-gradient(10deg, red, blue)",
+ "linear-gradient(1turn, red, blue)",
+ "linear-gradient(.414rad, red, blue)",
+
+ "linear-gradient(.414rad, red, 50%, blue)",
+ "linear-gradient(.414rad, red, 0%, blue)",
+ "linear-gradient(.414rad, red, 100%, blue)",
+
+ "linear-gradient(.414rad, red 50%, 50%, blue 50%)",
+ "linear-gradient(.414rad, red 50%, 20%, blue 50%)",
+ "linear-gradient(.414rad, red 50%, 30%, blue 10%)",
+ "linear-gradient(to right bottom, red, 20%, green 50%, 65%, blue)",
+ "linear-gradient(to right bottom, red, 20%, green 10%, blue)",
+ "linear-gradient(to right bottom, red, 50%, green 50%, 50%, blue)",
+ "linear-gradient(to right bottom, red, 0%, green 50%, 100%, blue)",
+
+ "linear-gradient(red 0% 100%)",
+ "linear-gradient(red 0% 50%, blue 50%)",
+ "linear-gradient(red 0% 50%, blue 50% 100%)",
+ "linear-gradient(red 0% 50%, 0%, blue 50%)",
+ "linear-gradient(red 0% 50%, 0%, blue 50% 100%)",
+
+ /* Unitless 0 is valid as an <angle> */
+ "linear-gradient(0, red, blue)",
+
+ "radial-gradient(red, blue)",
+ "radial-gradient(red, yellow, blue)",
+ "radial-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "radial-gradient(red, yellow, green, blue 50%)",
+ "radial-gradient(red -50%, yellow -25%, green, blue)",
+ "radial-gradient(red -99px, yellow, green, blue 120%)",
+ "radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+
+ "radial-gradient(0 0, red, blue)",
+ "radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "radial-gradient(at top left, red, blue)",
+ "radial-gradient(at 20% bottom, red, blue)",
+ "radial-gradient(at center 20%, red, blue)",
+ "radial-gradient(at left 35px, red, blue)",
+ "radial-gradient(at 10% 10em, red, blue)",
+ "radial-gradient(at 44px top, red, blue)",
+ "radial-gradient(at 0 0, red, blue)",
+
+ "radial-gradient(farthest-corner, red, blue)",
+ "radial-gradient(circle, red, blue)",
+ "radial-gradient(ellipse closest-corner, red, blue)",
+ "radial-gradient(closest-corner ellipse, red, blue)",
+ "radial-gradient(farthest-side circle, red, blue)",
+
+ "radial-gradient(at 43px, red, blue)",
+ "radial-gradient(at 43px 43px, red, blue)",
+ "radial-gradient(at 50% 50%, red, blue)",
+ "radial-gradient(at 43px 50%, red, blue)",
+ "radial-gradient(at 50% 43px, red, blue)",
+ "radial-gradient(circle 43px, red, blue)",
+ "radial-gradient(43px circle, red, blue)",
+ "radial-gradient(ellipse 43px 43px, red, blue)",
+ "radial-gradient(ellipse 50% 50%, red, blue)",
+ "radial-gradient(ellipse 43px 50%, red, blue)",
+ "radial-gradient(ellipse 50% 43px, red, blue)",
+ "radial-gradient(50% 43px ellipse, red, blue)",
+
+ "radial-gradient(farthest-corner at top left, red, blue)",
+ "radial-gradient(ellipse closest-corner at 45px, red, blue)",
+ "radial-gradient(circle farthest-side at 45px, red, blue)",
+ "radial-gradient(closest-side ellipse at 50%, red, blue)",
+ "radial-gradient(farthest-corner circle at 4em, red, blue)",
+
+ "radial-gradient(30% 40% at top left, red, blue)",
+ "radial-gradient(50px 60px at 15% 20%, red, blue)",
+ "radial-gradient(7em 8em at 45px, red, blue)",
+
+ "radial-gradient(circle at 15% 20%, red, blue)",
+
+ "radial-gradient(red 0% 100%)",
+ "radial-gradient(red 0% 50%, blue 50%)",
+ "radial-gradient(red 0% 50%, blue 50% 100%)",
+ "radial-gradient(red 0% 50%, 0%, blue 50%)",
+ "radial-gradient(red 0% 50%, 0%, blue 50% 100%)",
+
+ "repeating-radial-gradient(red, blue)",
+ "repeating-radial-gradient(red, yellow, blue)",
+ "repeating-radial-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "repeating-radial-gradient(red, yellow, green, blue 50%)",
+ "repeating-radial-gradient(red -50%, yellow -25%, green, blue)",
+ "repeating-radial-gradient(red -99px, yellow, green, blue 120%)",
+ "repeating-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "repeating-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "repeating-radial-gradient(at top left, red, blue)",
+ "repeating-radial-gradient(at 0 0, red, blue)",
+ "repeating-radial-gradient(at 20% bottom, red, blue)",
+ "repeating-radial-gradient(at center 20%, red, blue)",
+ "repeating-radial-gradient(at left 35px, red, blue)",
+ "repeating-radial-gradient(at 10% 10em, red, blue)",
+ "repeating-radial-gradient(at 44px top, red, blue)",
+
+ "repeating-radial-gradient(farthest-corner at top left, red, blue)",
+ "repeating-radial-gradient(closest-corner ellipse at 45px, red, blue)",
+ "repeating-radial-gradient(farthest-side circle at 45px, red, blue)",
+ "repeating-radial-gradient(ellipse closest-side at 50%, red, blue)",
+ "repeating-radial-gradient(circle farthest-corner at 4em, red, blue)",
+
+ "repeating-radial-gradient(30% 40% at top left, red, blue)",
+ "repeating-radial-gradient(50px 60px at 15% 20%, red, blue)",
+ "repeating-radial-gradient(7em 8em at 45px, red, blue)",
+
+ // When that happens this should be moved to the `invalid` list.
+ "repeating-radial-gradient(circle closest-side at left 0px bottom 7in, hsl(2,2%,5%), rgb(1,6,0))",
+
+ "radial-gradient(at calc(25%) top, red, blue)",
+ "radial-gradient(at left calc(25%), red, blue)",
+ "radial-gradient(at calc(25px) top, red, blue)",
+ "radial-gradient(at left calc(25px), red, blue)",
+ "radial-gradient(at calc(-25%) top, red, blue)",
+ "radial-gradient(at left calc(-25%), red, blue)",
+ "radial-gradient(at calc(-25px) top, red, blue)",
+ "radial-gradient(at left calc(-25px), red, blue)",
+ "radial-gradient(at calc(100px + -25%) top, red, blue)",
+ "radial-gradient(at left calc(100px + -25%), red, blue)",
+ "radial-gradient(at calc(100px + -25px) top, red, blue)",
+ "radial-gradient(at left calc(100px + -25px), red, blue)",
+
+ "image-set(linear-gradient(green, green) 1x, url(foobar.png) 2x)",
+ "image-set(linear-gradient(red, red), url(foobar.png) 2x)",
+ "image-set(url(foobar.png) 2x)",
+ "image-set(url(foobar.png) 1x, url(bar.png) 2x, url(baz.png) 3x)",
+ "image-set('foobar.png', 'bar.png' 2x, url(baz.png) 3x)",
+ "image-set(url(foobar.png) type('image/png'))",
+ "image-set(url(foobar.png) 1x type('image/png'))",
+ "image-set(url(foobar.png) type('image/png') 1x)",
+
+ ...(IsCSSPropertyPrefEnabled("layout.css.cross-fade.enabled")
+ ? [
+ "cross-fade(red, blue)",
+ "cross-fade(red)",
+ "cross-fade(red 50%)",
+ // see: <https://github.com/w3c/csswg-drafts/issues/5333>. This
+ // may become invalid depending on how discussion on that issue
+ // goes.
+ "cross-fade(red -50%, blue 150%)",
+ "cross-fade(red -50%, url(www.example.com))",
+
+ "cross-fade(url(http://placekitten.com/200/300), 55% linear-gradient(red, blue))",
+ "cross-fade(cross-fade(red, white), cross-fade(blue))",
+ "cross-fade(gold 77%, 60% blue)",
+
+ "cross-fade(url(http://placekitten.com/200/300), url(http://placekitten.com/200/300))",
+ "cross-fade(#F0F8FF, rgb(0, 0, 0), rgba(0, 255, 0, 1) 25%)",
+ ]
+ : []),
+
+ // Conic gradient
+ "conic-gradient(red, blue)",
+ "conic-gradient(red,blue,yellow)",
+ "conic-gradient( red , blue, yellow)",
+ "conic-gradient(red 0, blue 50deg)",
+ "conic-gradient(red 10%, blue 50%)",
+ "conic-gradient(red -50deg, blue 50deg)",
+ "conic-gradient(red 50deg, blue 0.3turn, yellow 200grad, orange 60% 5rad)",
+
+ "conic-gradient(red 0 100%)",
+ "conic-gradient(red 0 50%, blue 50%)",
+ "conic-gradient(red 0 50deg, blue 50% 100%)",
+ "conic-gradient(red 0 50%, 0deg, blue 50%)",
+ "conic-gradient(red 0deg 50%, 0%, blue 50% 100%)",
+
+ "conic-gradient(from 0, red, blue)",
+ "conic-gradient(from 40deg, red, blue)",
+ "conic-gradient(from 0.4turn, red, blue)",
+ "conic-gradient(from 200grad, red, blue)",
+ "conic-gradient(from 5rad, red, blue)",
+
+ "conic-gradient(at top, red, blue)",
+ "conic-gradient(at top left, red, blue)",
+ "conic-gradient(at left top, red, blue)",
+ "conic-gradient(at center center, red, blue)",
+ "conic-gradient(at 20% bottom, red, blue)",
+ "conic-gradient(at center 20%, red, blue)",
+ "conic-gradient(at left 35px, red, blue)",
+ "conic-gradient(at 10% 10em, red, blue)",
+ "conic-gradient(at 44px top, red, blue)",
+ "conic-gradient(at 0 0, red, blue)",
+ "conic-gradient(at 10px, red, blue)",
+
+ "conic-gradient(at calc(25%) top, red, blue)",
+ "conic-gradient(at left calc(25%), red, blue)",
+ "conic-gradient(at calc(25px) top, red, blue)",
+ "conic-gradient(at left calc(25px), red, blue)",
+ "conic-gradient(at calc(-25%) top, red, blue)",
+ "conic-gradient(at left calc(-25%), red, blue)",
+ "conic-gradient(at calc(-25px) top, red, blue)",
+ "conic-gradient(at left calc(-25px), red, blue)",
+ "conic-gradient(at calc(100px + -25%) top, red, blue)",
+ "conic-gradient(at left calc(100px + -25%), red, blue)",
+ "conic-gradient(at calc(100px + -25px) top, red, blue)",
+ "conic-gradient(at left calc(100px + -25px), red, blue)",
+
+ "conic-gradient(from 0 at 0 0, red, blue)",
+ "conic-gradient(from 40deg at 50%, red, blue)",
+ "conic-gradient(from 0.4turn at left 30%, red, blue)",
+ "conic-gradient(from 200grad at calc(100px + -25%) top, red, blue)",
+ "conic-gradient(from 5rad at 10px, red, blue)",
+
+ "repeating-conic-gradient(red, blue)",
+ "repeating-conic-gradient(red, yellow, blue)",
+ "repeating-conic-gradient(red 1deg, yellow 20%, blue 5rad, green)",
+ "repeating-conic-gradient(red, yellow, green, blue 50%)",
+ "repeating-conic-gradient(red -50%, yellow -25%, green, blue)",
+ "repeating-conic-gradient(red -99deg, yellow, green, blue 120%)",
+ "repeating-conic-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "repeating-conic-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "repeating-conic-gradient(from 0, red, blue)",
+ "repeating-conic-gradient(from 40deg, red, blue)",
+ "repeating-conic-gradient(from 0.4turn, red, blue)",
+ "repeating-conic-gradient(from 200grad, red, blue)",
+ "repeating-conic-gradient(from 5rad, red, blue)",
+
+ "repeating-conic-gradient(at top left, red, blue)",
+ "repeating-conic-gradient(at 0 0, red, blue)",
+ "repeating-conic-gradient(at 20% bottom, red, blue)",
+ "repeating-conic-gradient(at center 20%, red, blue)",
+ "repeating-conic-gradient(at left 35px, red, blue)",
+ "repeating-conic-gradient(at 10% 10em, red, blue)",
+ "repeating-conic-gradient(at 44px top, red, blue)",
+
+ "repeating-conic-gradient(from 0 at 0 0, red, blue)",
+ "repeating-conic-gradient(from 40deg at 50%, red, blue)",
+ "repeating-conic-gradient(from 0.4turn at left 30%, red, blue)",
+ "repeating-conic-gradient(from 200grad at calc(100px + -25%) top, red, blue)",
+ "repeating-conic-gradient(from 5rad at 10px, red, blue)",
+
+ // 2008 GRADIENTS: -webkit-gradient()
+ // ----------------------------------
+ // linear w/ no color stops (valid) and a variety of position values:
+ "-webkit-gradient(linear, 1 2, 3 4)",
+ "-webkit-gradient(linear,1 2,3 4)", // (no extra space)
+ "-webkit-gradient(linear , 1 2 , 3 4 )", // (lots of extra space)
+ "-webkit-gradient(linear, 1 10% , 0% 4)", // percentages
+ "-webkit-gradient(linear, +1.0 -2%, +5.3% -0)", // (+/- & decimals are valid)
+ "-webkit-gradient(linear, left top, right bottom)", // keywords
+ "-webkit-gradient(linear, right center, center top)",
+ "-webkit-gradient(linear, center center, center center)",
+ "-webkit-gradient(linear, center 5%, 30 top)", // keywords mixed w/ nums
+
+ // linear w/ just 1 color stop:
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, to(lime))",
+ // * testing the various allowable stop values (<number> & <percent>):
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-0, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-30, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(+9999, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-.1, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0%, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(100%, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(9999%, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-.5%, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(+0%, lime))",
+ // * testing the various color values:
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, transparent))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, rgb(1,2,3)))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, #00ff00))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, #00f))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, hsla(240, 30%, 50%, 0.8)))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, rgba(255, 230, 10, 0.5)))",
+
+ // linear w/ multiple color stops:
+ // * using from()/to() -- note that out-of-order is OK:
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime), from(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, to(lime), to(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime), to(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, to(lime), from(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime), to(blue), from(purple))",
+ // * using color-stop():
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, lime), color-stop(30%, blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, lime), color-stop(30%, blue), color-stop(100%, purple))",
+ // * using color-stop() intermixed with from()/to() functions:
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime), color-stop(30%, blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(30%, blue), to(lime))",
+ // * overshooting endpoints (0 & 1.0)
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-30%, lime), color-stop(.4, blue), color-stop(1.5, purple))",
+ // * repeating a stop position (valid)
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(30%, lime), color-stop(30%, blue))",
+ // * stops out of order (valid)
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(70%, lime), color-stop(20%, blue), color-stop(40%, purple))",
+
+ // radial w/ no color stops (valid) and a several different radius values:
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9)",
+ "-webkit-gradient(radial, 0 0, 10, 0 0, 5)",
+
+ // radial w/ color stops
+ // (mostly leaning on more-robust 'linear' tests above; just testing a few
+ // examples w/ radial as a sanity-check):
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9, from(lime))",
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9, to(blue))",
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9, color-stop(0.5, #00f), color-stop(0.8, rgba(100, 200, 0, 0.5)))",
+
+ // 2011 GRADIENTS: -webkit-linear-gradient(), -webkit-radial -gradient()
+ // ---------------------------------------------------------------------
+ // Basic linear-gradient syntax (valid when prefixed or unprefixed):
+ "-webkit-linear-gradient(red, green, blue)",
+
+ // Angled linear-gradients (valid when prefixed or unprefixed):
+ "-webkit-linear-gradient(135deg, red, blue)",
+ "-webkit-linear-gradient( 135deg , red , blue )",
+ "-webkit-linear-gradient(280deg, red 60%, blue)",
+
+ // Linear-gradient with unitless-0 <angle> (normally invalid for <angle>
+ // but accepted here for better webkit emulation):
+ "-webkit-linear-gradient(0, red, blue)",
+
+ // Linear-gradient with calc expression (bug 1363349)
+ "-webkit-gradient(linear, calc(5 + 5) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, calc(5 - 5) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, calc(5 * 5) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, calc(5 / 5) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, left calc(25% - 10%), right calc(75% + 10%), from(blue), to(lime))",
+ "-webkit-gradient(linear, calc(1) 2, 3 4)",
+
+ // Radial-gradient with calc expression (bug 1363349)
+ "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 - 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 * 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 / 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(8), 3 4, 9)",
+
+ // Basic radial-gradient syntax (valid when prefixed or unprefixed):
+ "-webkit-radial-gradient(circle, white, black)",
+ "-webkit-radial-gradient(circle, white, black)",
+ "-webkit-radial-gradient(ellipse closest-side, white, black)",
+ "-webkit-radial-gradient(circle farthest-corner, white, black)",
+
+ // Contain/cover keywords (valid only for -moz/-webkit prefixed):
+ "-webkit-radial-gradient(cover, red, blue)",
+ "-webkit-radial-gradient(cover circle, red, blue)",
+ "-webkit-radial-gradient(contain, red, blue)",
+ "-webkit-radial-gradient(contain ellipse, red, blue)",
+
+ // Initial side/corner/point (valid only for -moz/-webkit prefixed):
+ "-webkit-linear-gradient(top, red, blue)",
+ "-webkit-linear-gradient(left, red, blue)",
+ "-webkit-linear-gradient(bottom, red, blue)",
+ "-webkit-linear-gradient(right top, red, blue)",
+ "-webkit-linear-gradient(top right, red, blue)",
+ "-webkit-radial-gradient(right, red, blue)",
+ "-webkit-radial-gradient(left bottom, red, blue)",
+ "-webkit-radial-gradient(bottom left, red, blue)",
+ "-webkit-radial-gradient(center, red, blue)",
+ "-webkit-radial-gradient(center right, red, blue)",
+ "-webkit-radial-gradient(center center, red, blue)",
+ "-webkit-radial-gradient(center top, red, blue)",
+ "-webkit-radial-gradient(left 50%, red, blue)",
+ "-webkit-radial-gradient(20px top, red, blue)",
+ "-webkit-radial-gradient(20em 30%, red, blue)",
+
+ // Point + keyword-sized shape (valid only for -moz/-webkit prefixed):
+ "-webkit-radial-gradient(center, circle closest-corner, red, blue)",
+ "-webkit-radial-gradient(10px 20px, cover circle, red, blue)",
+ "-webkit-radial-gradient(5em 50%, ellipse contain, red, blue)",
+
+ // Repeating examples:
+ "-webkit-repeating-linear-gradient(red 10%, blue 30%)",
+ "-webkit-repeating-linear-gradient(30deg, pink 20px, orange 70px)",
+ "-webkit-repeating-linear-gradient(left, red, blue)",
+ "-webkit-repeating-linear-gradient(left, red 10%, blue 30%)",
+ "-webkit-repeating-radial-gradient(circle, red, blue 10%, red 20%)",
+ "-webkit-repeating-radial-gradient(circle farthest-corner, gray 10px, yellow 20px)",
+ "-webkit-repeating-radial-gradient(top left, circle, red, blue 4%, red 8%)",
+];
+var invalidNonUrlImageValues = [
+ "-moz-element(#a:1)",
+ "-moz-element(a#a)",
+ "-moz-element(#a a)",
+ "-moz-element(#a+a)",
+ "-moz-element(#a())",
+ /* no quirks mode colors */
+ "linear-gradient(red, ff00ff)",
+ /* no quirks mode colors */
+ "radial-gradient(at 10% bottom, ffffff, black) scroll no-repeat",
+ /* no quirks mode lengths */
+ "linear-gradient(red -99, yellow, green, blue 120%)",
+ /* Unitless nonzero numbers are valid as an <angle> */
+ "linear-gradient(30, red, blue)",
+ /* There must be a comma between gradient-line (e.g. <angle>) and colors */
+ "linear-gradient(30deg red, blue)",
+ "linear-gradient(to top left red, blue)",
+ "linear-gradient(to right red, blue)",
+ /* Invalid color or calc() function */
+ "linear-gradient(red, rgb(0, rubbish, 0) 50%, red)",
+ "linear-gradient(red, red calc(50% + rubbish), red)",
+ "linear-gradient(to top calc(50% + rubbish), red, blue)",
+
+ "radial-gradient(circle 175px 20px, black, white)",
+ "radial-gradient(175px 20px circle, black, white)",
+ "radial-gradient(ellipse 175px, black, white)",
+ "radial-gradient(175px ellipse, black, white)",
+ "radial-gradient(50%, red, blue)",
+ "radial-gradient(circle 50%, red, blue)",
+ "radial-gradient(50% circle, red, blue)",
+
+ /* Invalid units */
+ "conic-gradient(red, blue 50px, yellow 30px)",
+ "repeating-conic-gradient(red 1deg, yellow 20%, blue 24em, green)",
+ "conic-gradient(from 0%, black, white)",
+ "conic-gradient(from 60%, black, white)",
+ "conic-gradient(from 40px, black, white)",
+ "conic-gradient(from 50, black, white)",
+ "conic-gradient(at 50deg, black, white)",
+ "conic-gradient(from 40deg at 50deg, black, white)",
+ "conic-gradient(from 40deg at 50deg 60deg, black, white)",
+ /* Invalid keywords (or ordering) */
+ "conic-gradient(at 40% from 50deg, black, white)",
+ "conic-gradient(to 50deg, black, white)",
+
+ /* Used to be valid only when prefixed */
+ "linear-gradient(top left, red, blue)",
+ "linear-gradient(0 0, red, blue)",
+ "linear-gradient(20% bottom, red, blue)",
+ "linear-gradient(center 20%, red, blue)",
+ "linear-gradient(left 35px, red, blue)",
+ "linear-gradient(10% 10em, red, blue)",
+ "linear-gradient(44px top, red, blue)",
+
+ "linear-gradient(top left 45deg, red, blue)",
+ "linear-gradient(20% bottom -300deg, red, blue)",
+ "linear-gradient(center 20% 1.95929rad, red, blue)",
+ "linear-gradient(left 35px 30grad, red, blue)",
+ "linear-gradient(left 35px 0.1turn, red, blue)",
+ "linear-gradient(10% 10em 99999deg, red, blue)",
+ "linear-gradient(44px top -33deg, red, blue)",
+
+ "linear-gradient(30grad left 35px, red, blue)",
+ "linear-gradient(10deg 20px, red, blue)",
+ "linear-gradient(1turn 20px, red, blue)",
+ "linear-gradient(.414rad bottom, red, blue)",
+
+ "linear-gradient(to top, 0%, blue)",
+ "linear-gradient(to top, red, 100%)",
+ "linear-gradient(to top, red, 45%, 56%, blue)",
+ "linear-gradient(to top, red,, blue)",
+ "linear-gradient(to top, red, green 35%, 15%, 54%, blue)",
+
+ "linear-gradient(unset, 10px 10px, from(blue))",
+ "linear-gradient(unset, 10px 10px, blue 0)",
+ "repeating-linear-gradient(unset, 10px 10px, blue 0)",
+
+ "radial-gradient(top left 45deg, red, blue)",
+ "radial-gradient(20% bottom -300deg, red, blue)",
+ "radial-gradient(center 20% 1.95929rad, red, blue)",
+ "radial-gradient(left 35px 30grad, red, blue)",
+ "radial-gradient(10% 10em 99999deg, red, blue)",
+ "radial-gradient(44px top -33deg, red, blue)",
+
+ "radial-gradient(-33deg, red, blue)",
+ "radial-gradient(30grad left 35px, red, blue)",
+ "radial-gradient(10deg 20px, red, blue)",
+ "radial-gradient(.414rad bottom, red, blue)",
+
+ "radial-gradient(cover, red, blue)",
+ "radial-gradient(ellipse contain, red, blue)",
+ "radial-gradient(cover circle, red, blue)",
+
+ "radial-gradient(top left, cover, red, blue)",
+ "radial-gradient(15% 20%, circle, red, blue)",
+ "radial-gradient(45px, ellipse closest-corner, red, blue)",
+ "radial-gradient(45px, farthest-side circle, red, blue)",
+
+ "radial-gradient(99deg, cover, red, blue)",
+ "radial-gradient(-1.2345rad, circle, red, blue)",
+ "radial-gradient(399grad, ellipse closest-corner, red, blue)",
+ "radial-gradient(399grad, farthest-side circle, red, blue)",
+
+ "radial-gradient(top left 99deg, cover, red, blue)",
+ "radial-gradient(15% 20% -1.2345rad, circle, red, blue)",
+ "radial-gradient(45px 399grad, ellipse closest-corner, red, blue)",
+ "radial-gradient(45px 399grad, farthest-side circle, red, blue)",
+ "radial-gradient(circle red, blue)",
+
+ /* don't allow more than two positions with multi-position syntax */
+ "linear-gradient(red 0% 50% 100%)",
+ "linear-gradient(red 0% 50% 75%, blue 75%)",
+ "linear-gradient(to bottom, red 0% 50% 100%)",
+ "linear-gradient(to bottom, red 0% 50% 75%, blue 75%)",
+ "radial-gradient(red 0% 50% 100%)",
+ "radial-gradient(red 0% 50% 75%, blue 75%)",
+ "radial-gradient(center, red 0% 50% 100%)",
+ "radial-gradient(center, red 0% 50% 75%, blue 75%)",
+ "conic-gradient(red 0% 50% 100%)",
+ "conic-gradient(red 0% 50% 75%, blue 75%)",
+ "conic-gradient(center, red 0% 50% 100%)",
+ "conic-gradient(center, red 0% 50% 75%, blue 75%)",
+
+ // missing color in color stop
+ "conic-gradient(red 50deg, blue 0.3turn, yellow 200grad, orange 60%, 5rad)",
+
+ "-moz-linear-gradient(unset, 10px 10px, from(blue))",
+ "-moz-linear-gradient(unset, 10px 10px, blue 0)",
+ "-moz-repeating-linear-gradient(unset, 10px 10px, blue 0)",
+
+ // 2008 GRADIENTS: -webkit-gradient()
+ // https://www.webkit.org/blog/175/introducing-css-gradients/
+ // ----------------------------------
+ // Mostly-empty expressions (missing most required pieces):
+ "-webkit-gradient()",
+ "-webkit-gradient( )",
+ "-webkit-gradient(,)",
+ "-webkit-gradient(bogus)",
+ "-webkit-gradient(linear)",
+ "-webkit-gradient(linear,)",
+ "-webkit-gradient(,linear)",
+ "-webkit-gradient(radial)",
+ "-webkit-gradient(radial,)",
+
+ // linear w/ partial/missing <point> expression(s)
+ "-webkit-gradient(linear, 1)", // Incomplete <point>
+ "-webkit-gradient(linear, left)", // Incomplete <point>
+ "-webkit-gradient(linear, center)", // Incomplete <point>
+ "-webkit-gradient(linear, top)", // Incomplete <point>
+ "-webkit-gradient(linear, 5%)", // Incomplete <point>
+ "-webkit-gradient(linear, 1 2)", // Missing 2nd <point>
+ "-webkit-gradient(linear, 1, 3)", // 2 incomplete <point>s
+ "-webkit-gradient(linear, 1, 3 4)", // Incomplete 1st <point>
+ "-webkit-gradient(linear, 1 2, 3)", // Incomplete 2nd <point>
+ "-webkit-gradient(linear, 1 2, 3, 4)", // Comma inside <point>
+ "-webkit-gradient(linear, 1, 2, 3 4)", // Comma inside <point>
+ "-webkit-gradient(linear, 1, 2, 3, 4)", // Comma inside <point>
+
+ // linear w/ invalid units in <point> expression
+ "-webkit-gradient(linear, 1px 2, 3 4)",
+ "-webkit-gradient(linear, 1 2, 3 4px)",
+ "-webkit-gradient(linear, 1px 2px, 3px 4px)",
+ "-webkit-gradient(linear, 1 2em, 3 4)",
+
+ // linear w/ <radius> (only valid for radial)
+ "-webkit-gradient(linear, 1 2, 8, 3 4, 9)",
+
+ // linear w/ out-of-order position keywords in <point> expression
+ // (horizontal keyword is supposed to come first, for "x" coord)
+ "-webkit-gradient(linear, 0 0, top right)",
+ "-webkit-gradient(linear, bottom center, 0 0)",
+ "-webkit-gradient(linear, top bottom, 0 0)",
+ "-webkit-gradient(linear, bottom top, 0 0)",
+ "-webkit-gradient(linear, bottom top, 0 0)",
+
+ // linear w/ trailing comma (which implies missing color-stops):
+ "-webkit-gradient(linear, 1 2, 3 4,)",
+
+ // linear w/ invalid color values:
+ "-webkit-gradient(linear, 1 2, 3 4, from(invalidcolorname))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(inherit))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(initial))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(currentColor))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(00ff00))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(##00ff00))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(#00fff))", // wrong num hex digits
+ "-webkit-gradient(linear, 1 2, 3 4, from(xyz(0,0,0)))", // bogus color func
+ // Mixing <number> and <percentage> is invalid.
+ "-webkit-gradient(linear, 1 2, 3 4, from(rgb(100, 100%, 30)))",
+
+ // linear w/ color stops that have comma issues
+ "-webkit-gradient(linear, 1 2, 3 4 from(lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime,))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime),)",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime) to(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime),, to(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(rbg(0, 0, 0,)))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0 lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0,, lime))",
+
+ // radial w/ broken <point>/radius expression(s)
+ "-webkit-gradient(radial, 1)", // Incomplete <point>
+ "-webkit-gradient(radial, 1 2)", // Missing radius + 2nd <point>
+ "-webkit-gradient(radial, 1 2, 8)", // Missing 2nd <point>
+ "-webkit-gradient(radial, 1 2, 8, 3)", // Incomplete 2nd <point>
+ "-webkit-gradient(radial, 1 2, 8, 3 4)", // Missing 2nd radius
+ "-webkit-gradient(radial, 1 2, 3 4, 9)", // Missing 1st radius
+ "-webkit-gradient(radial, 1 2, -1.5, center center, +99999.9999)", // Negative radius
+
+ // radial w/ incorrect units on radius (invalid; expecting <number>)
+ "-webkit-gradient(radial, 1 2, 8%, 3 4, 9)",
+ "-webkit-gradient(radial, 1 2, 8px, 3 4, 9)",
+ "-webkit-gradient(radial, 1 2, 8em, 3 4, 9)",
+ "-webkit-gradient(radial, 1 2, top, 3 4, 9)",
+
+ // radial w/ trailing comma (which implies missing color-stops):
+ "-webkit-gradient(linear, 1 2, 8, 3 4, 9,)",
+
+ // radial w/ invalid color value (mostly leaning on 'linear' test above):
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9, from(invalidcolorname))",
+
+ // 2011 GRADIENTS: -webkit-linear-gradient(), -webkit-radial -gradient()
+ // ---------------------------------------------------------------------
+ // Syntax that's invalid for all types of gradients:
+ // * empty gradient expressions:
+ "-webkit-linear-gradient()",
+ "-webkit-radial-gradient()",
+ "-webkit-repeating-linear-gradient()",
+ "-webkit-repeating-radial-gradient()",
+
+ // * missing comma between <legacy-gradient-line> and color list:
+ "-webkit-linear-gradient(0 red, blue)",
+ "-webkit-linear-gradient(30deg red, blue)",
+ "-webkit-linear-gradient(top right red, blue)",
+ "-webkit-linear-gradient(bottom red, blue)",
+
+ // Linear-gradient with calc expression containing mixed units
+ // (bug 1363349)
+ "-webkit-gradient(linear, calc(5 + 5%) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, left calc(25 - 10%), right calc(75% + 10%), from(blue), to(lime))",
+
+ // Radial-gradient with calc expression containing mixed units, or a
+ // percentage in the radius (bug 1363349)
+ "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1% + 5%), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5%), from(blue), to(lime))",
+ "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1% + 2%), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))",
+
+ // Linear syntax that's invalid for both -webkit & unprefixed, but valid
+ // for -moz:
+ // * initial <legacy-gradient-line> which includes a length:
+ "-webkit-linear-gradient(10px, red, blue)",
+ "-webkit-linear-gradient(10px top, red, blue)",
+ // * initial <legacy-gradient-line> which includes a side *and* an angle:
+ "-webkit-linear-gradient(bottom 30deg, red, blue)",
+ "-webkit-linear-gradient(30deg bottom, red, blue)",
+ "-webkit-linear-gradient(10px top 50deg, red, blue)",
+ "-webkit-linear-gradient(50deg 10px top, red, blue)",
+ // * initial <legacy-gradient-line> which includes explicit "center":
+ "-webkit-linear-gradient(center, red, blue)",
+ "-webkit-linear-gradient(left center, red, blue)",
+ "-webkit-linear-gradient(top center, red, blue)",
+ "-webkit-linear-gradient(center top, red, blue)",
+
+ // Linear syntax that's invalid for -webkit, but valid for -moz & unprefixed:
+ // * "to" syntax:
+ "-webkit-linear-gradient(to top, red, blue)",
+
+ // * <shape> followed by angle:
+ "-webkit-radial-gradient(circle 10deg, red, blue)",
+
+ // Radial syntax that's invalid for both -webkit & -moz, but valid for
+ // unprefixed:
+ // * "<shape> at <position>" syntax:
+ "-webkit-radial-gradient(circle at left bottom, red, blue)",
+ // * explicitly-sized shape:
+ "-webkit-radial-gradient(circle 10px, red, blue)",
+ "-webkit-radial-gradient(ellipse 40px 20px, red, blue)",
+
+ // Radial syntax that's invalid for both -webkit & unprefixed, but valid
+ // for -moz:
+ // * initial angle
+ "-webkit-radial-gradient(30deg, red, blue)",
+ // * initial angle/position combo
+ "-webkit-radial-gradient(top 30deg, red, blue)",
+ "-webkit-radial-gradient(left top 30deg, red, blue)",
+ "-webkit-radial-gradient(10px 20px 30deg, red, blue)",
+
+ // Conic gradients should not support prefixed syntax
+ "-webkit-gradient(conic, 1 2, 3 4, color-stop(0, lime))",
+ "-webkit-conic-gradient(red, blue)",
+ "-moz-conic-gradient(red, blue)",
+ "-webkit-repeating-conic-gradient(red, blue)",
+ "-moz-repeating-conic-gradient(red, blue)",
+
+ "image-set(url(foobar.png) 1x, none)",
+ "image-set(garbage)",
+ "image-set(image-set('foobar.png', 'bar.png' 2x) 1x, url(baz.png) 3x)", // Nested image-sets should fail to parse
+ "image-set(image-set(garbage))",
+ "image-set()",
+ "image-set(type('image/png') url(foobar.png) 1x)",
+ "image-set(url(foobar.png) type('image/png') 1x type('image/png'))",
+ "image-set(url(foobar.png) type('image/png') type('image/png'))",
+ "image-set(url(foobar.png) type(image/png))",
+ "image-set(url(foobar.png) epyt('image/png'))",
+
+ ...(IsCSSPropertyPrefEnabled("layout.css.cross-fade.enabled")
+ ? [
+ "cross-fade(red blue)",
+ "cross-fade()",
+ "cross-fade(50%, blue 50%)",
+ // Old syntax
+ "cross-fade(red, white, 50%)",
+ // see: <https://github.com/w3c/csswg-drafts/issues/5333>. This
+ // may become invalid depending on how discussion on that issue
+ // goes.
+ "cross-fade(red, 150%, blue)",
+ "cross-fade(red auto, blue 10%)",
+
+ // nested invalidity should propagate.
+ "cross-fade(url(http://placekitten.com/200/300), 55% linear-gradient(center, red, blue))",
+ "cross-fade(cross-fade(red, white, 50%), cross-fade(blue))",
+
+ "cross-fade(url(http://placekitten.com/200/300) url(http://placekitten.com/200/300))",
+ "cross-fade(#F0F8FF, rgb(0, 0, 0), rgba(0, 255, 0, 1), 25%)",
+ ]
+ : []),
+];
+var unbalancedGradientAndElementValues = ["-moz-element(#a()"];
+
+var basicShapeSVGBoxValues = [
+ "fill-box",
+ "stroke-box",
+ "view-box",
+
+ "polygon(evenodd, 20pt 20cm) fill-box",
+ "polygon(evenodd, 20ex 20pc) stroke-box",
+ "polygon(evenodd, 20rem 20in) view-box",
+];
+
+var basicShapeOtherValues = [
+ "polygon(20px 20px)",
+ "polygon(20px 20%)",
+ "polygon(20% 20%)",
+ "polygon(20rem 20em)",
+ "polygon(20cm 20mm)",
+ "polygon(20px 20px, 30px 30px)",
+ "polygon(20px 20px, 30% 30%, 30px 30px)",
+
+ "content-box",
+ "padding-box",
+ "border-box",
+
+ "polygon(0 0) content-box",
+ "border-box polygon(0 0)",
+ "padding-box polygon( 0 20px , 30px 20% ) ",
+
+ "circle()",
+ "circle(at center)",
+ "circle(at top 0px left 20px)",
+ "circle(at bottom right)",
+ "circle(20%)",
+ "circle(300px)",
+ "circle(calc(20px + 30px))",
+ "circle(farthest-side)",
+ "circle(closest-side)",
+ "circle(closest-side at center)",
+ "circle(farthest-side at top)",
+ "circle(20px at top right)",
+ "circle(40% at 50% 100%)",
+ "circle(calc(20% + 20%) at right bottom)",
+ "circle() padding-box",
+
+ "ellipse()",
+ "ellipse(at center)",
+ "ellipse(at top 0px left 20px)",
+ "ellipse(at bottom right)",
+ "ellipse(20% 20%)",
+ "ellipse(300px 50%)",
+ "ellipse(calc(20px + 30px) 10%)",
+ "ellipse(farthest-side closest-side)",
+ "ellipse(closest-side farthest-side)",
+ "ellipse(farthest-side farthest-side)",
+ "ellipse(closest-side closest-side)",
+ "ellipse(closest-side closest-side at center)",
+ "ellipse(20% farthest-side at top)",
+ "ellipse(20px 50% at top right)",
+ "ellipse(closest-side 40% at 50% 100%)",
+ "ellipse(calc(20% + 20%) calc(20px + 20cm) at right bottom)",
+
+ "inset(1px)",
+ "inset(20% -20px)",
+ "inset(20em 4rem calc(20% + 20px))",
+ "inset(20vh 20vw 20pt 3%)",
+ "inset(5px round 3px)",
+ "inset(1px 2px round 3px / 3px)",
+ "inset(1px 2px 3px round 3px 2em / 20%)",
+ "inset(1px 2px 3px 4px round 3px 2vw 20% / 20px 3em 2vh 20%)",
+];
+
+var basicShapeOtherValuesWithFillRule = [
+ "polygon(nonzero, 20px 20px, 30% 30%, 30px 30px)",
+ "polygon(evenodd, 20px 20px, 30% 30%, 30px 30px)",
+ "polygon(evenodd, 20% 20em) content-box",
+ "polygon(evenodd, 20vh 20em) padding-box",
+ "polygon(evenodd, 20vh calc(20% + 20em)) border-box",
+ "polygon(evenodd, 20vh 20vw) margin-box",
+];
+
+var basicShapeInvalidValues = [
+ "url(#test) url(#tes2)",
+ "polygon (0 0)",
+ "polygon(20px, 40px)",
+ "border-box content-box",
+ "polygon(0 0) polygon(0 0)",
+ "polygon(nonzero 0 0)",
+ "polygon(evenodd 20px 20px)",
+ "polygon(20px 20px, evenodd)",
+ "polygon(20px 20px, nonzero)",
+ "polygon(0 0) conten-box content-box",
+ "content-box polygon(0 0) conten-box",
+ "padding-box polygon(0 0) conten-box",
+ "polygon(0 0) polygon(0 0) content-box",
+ "polygon(0 0) content-box polygon(0 0)",
+ "polygon(0 0), content-box",
+ "polygon(0 0), polygon(0 0)",
+ "content-box polygon(0 0) polygon(0 0)",
+ "content-box polygon(0 0) none",
+ "none content-box polygon(0 0)",
+ "inherit content-box polygon(0 0)",
+ "initial polygon(0 0)",
+ "polygon(0 0) farthest-side",
+ "farthest-corner polygon(0 0)",
+ "polygon(0 0) farthest-corner",
+ "polygon(0 0) conten-box",
+ "polygon(0 0) polygon(0 0) farthest-corner",
+ "polygon(0 0) polygon(0 0) polygon(0 0)",
+ "border-box polygon(0, 0)",
+ "border-box padding-box",
+ "margin-box farthest-side",
+ "nonsense() border-box",
+ "border-box nonsense()",
+
+ "circle(at)",
+ "circle(at 20% 20% 30%)",
+ "circle(20px 2px at center)",
+ "circle(2at center)",
+ "circle(closest-corner)",
+ "circle(at center top closest-side)",
+ "circle(-20px)",
+ "circle(farthest-side closest-side)",
+ "circle(20% 20%)",
+ "circle(at farthest-side)",
+ "circle(calc(20px + rubbish))",
+ "circle(at top left 20px)",
+
+ "ellipse(at)",
+ "ellipse(at 20% 20% 30%)",
+ "ellipse(20px at center)",
+ "ellipse(-20px 20px)",
+ "ellipse(closest-corner farthest-corner)",
+ "ellipse(20px -20px)",
+ "ellipse(-20px -20px)",
+ "ellipse(farthest-side)",
+ "ellipse(20%)",
+ "ellipse(at farthest-side farthest-side)",
+ "ellipse(at top left calc(20px + rubbish))",
+ "ellipse(at top left 20px)",
+
+ "polygon(at)",
+ "polygon(at 20% 20% 30%)",
+ "polygon(20px at center)",
+ "polygon(2px 2at center)",
+ "polygon(closest-corner farthest-corner)",
+ "polygon(at center top closest-side closest-side)",
+ "polygon(40% at 50% 100%)",
+ "polygon(40% farthest-side 20px at 50% 100%)",
+
+ "inset()",
+ "inset(round)",
+ "inset(round 3px)",
+ "inset(1px round 1px 2px 3px 4px 5px)",
+ "inset(1px 2px 3px 4px 5px)",
+ "inset(1px, round 3px)",
+ "inset(1px, 2px)",
+ "inset(1px 2px, 3px)",
+ "inset(1px at 3px)",
+ "inset(1px round 1px // 2px)",
+ "inset(1px round)",
+ "inset(1px calc(2px + rubbish))",
+ "inset(1px round 2px calc(3px + rubbish))",
+];
+
+var basicShapeUnbalancedValues = [
+ "polygon(30% 30%",
+ "polygon(nonzero, 20% 20px",
+ "polygon(evenodd, 20px 20px",
+
+ "circle(",
+ "circle(40% at 50% 100%",
+ "ellipse(",
+ "ellipse(40% at 50% 100%",
+
+ "inset(1px",
+ "inset(1px 2px",
+ "inset(1px 2px 3px",
+ "inset(1px 2px 3px 4px",
+ "inset(1px 2px 3px 4px round 5px",
+ "inset(1px 2px 3px 4px round 5px / 6px",
+];
+
+var basicShapeXywhRectValues = [];
+if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-xywh.enabled")) {
+ basicShapeXywhRectValues.push(
+ "xywh(1px 2% 3px 4em)",
+ "xywh(1px 2% 3px 4em round 0px)",
+ "xywh(1px 2% 3px 4em round 0px 1%)",
+ "xywh(1px 2% 3px 4em round 0px 1% 2px)",
+ "xywh(1px 2% 3px 4em round 0px 1% 2px 3em)"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-rect.enabled")) {
+ basicShapeXywhRectValues.push(
+ "rect(auto auto auto auto)",
+ "rect(1px 2% auto 4em)",
+ "rect(1px 2% auto 4em round 0px)",
+ "rect(1px 2% auto 4em round 0px 1%)",
+ "rect(1px 2% auto 4em round 0px 1% 2px)",
+ "rect(1px 2% auto 4em round 0px 1% 2px 3em)"
+ );
+}
+
+if (/* mozGradientsEnabled */ true) {
+ // Maybe one day :(
+ // Extend gradient lists with valid/invalid moz-prefixed expressions:
+ validNonUrlImageValues.push(
+ "-moz-linear-gradient(red, blue)",
+ "-moz-linear-gradient(red, yellow, blue)",
+ "-moz-linear-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "-moz-linear-gradient(red, yellow, green, blue 50%)",
+ "-moz-linear-gradient(red -50%, yellow -25%, green, blue)",
+ "-moz-linear-gradient(red -99px, yellow, green, blue 120%)",
+ "-moz-linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "-moz-linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "-moz-linear-gradient(top, red, blue)",
+
+ "-moz-linear-gradient(to top, red, blue)",
+ "-moz-linear-gradient(to bottom, red, blue)",
+ "-moz-linear-gradient(to left, red, blue)",
+ "-moz-linear-gradient(to right, red, blue)",
+ "-moz-linear-gradient(to top left, red, blue)",
+ "-moz-linear-gradient(to top right, red, blue)",
+ "-moz-linear-gradient(to bottom left, red, blue)",
+ "-moz-linear-gradient(to bottom right, red, blue)",
+ "-moz-linear-gradient(to left top, red, blue)",
+ "-moz-linear-gradient(to left bottom, red, blue)",
+ "-moz-linear-gradient(to right top, red, blue)",
+ "-moz-linear-gradient(to right bottom, red, blue)",
+
+ "-moz-linear-gradient(top left, red, blue)",
+ "-moz-linear-gradient(left, red, blue)",
+ "-moz-linear-gradient(bottom, red, blue)",
+
+ "-moz-linear-gradient(0, red, blue)",
+
+ "-moz-linear-gradient(-33deg, red, blue)",
+
+ "-moz-linear-gradient(blue calc(0px) ,green calc(25%) ,red calc(40px) ,blue calc(60px) , yellow calc(100px))",
+ "-moz-linear-gradient(-33deg, blue calc(-25%) ,red 40px)",
+ "-moz-linear-gradient(10deg, blue calc(100px + -25%),red calc(40px))",
+ "-moz-linear-gradient(10deg, blue calc(-25px),red calc(100%))",
+ "-moz-linear-gradient(.414rad, blue calc(100px + -25px) ,green calc(100px + -25px) ,red calc(100px + -25%) ,blue calc(-25px) , yellow calc(-25px))",
+ "-moz-linear-gradient(1turn, blue calc(-25%) ,green calc(25px) ,red calc(25%),blue calc(0px),white 50px, yellow calc(-25px))",
+
+ "-moz-radial-gradient(red, blue)",
+ "-moz-radial-gradient(red, yellow, blue)",
+ "-moz-radial-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "-moz-radial-gradient(red, yellow, green, blue 50%)",
+ "-moz-radial-gradient(red -50%, yellow -25%, green, blue)",
+ "-moz-radial-gradient(red -99px, yellow, green, blue 120%)",
+ "-moz-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+
+ "-moz-radial-gradient(top left, red, blue)",
+ "-moz-radial-gradient(20% bottom, red, blue)",
+ "-moz-radial-gradient(center 20%, red, blue)",
+ "-moz-radial-gradient(left 35px, red, blue)",
+ "-moz-radial-gradient(10% 10em, red, blue)",
+ "-moz-radial-gradient(44px top, red, blue)",
+
+ "-moz-radial-gradient(0 0, red, blue)",
+ "-moz-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "-moz-radial-gradient(cover, red, blue)",
+ "-moz-radial-gradient(cover circle, red, blue)",
+ "-moz-radial-gradient(contain, red, blue)",
+ "-moz-radial-gradient(contain ellipse, red, blue)",
+ "-moz-radial-gradient(circle, red, blue)",
+ "-moz-radial-gradient(ellipse closest-corner, red, blue)",
+ "-moz-radial-gradient(farthest-side circle, red, blue)",
+
+ "-moz-radial-gradient(top left, cover, red, blue)",
+ "-moz-radial-gradient(15% 20%, circle, red, blue)",
+ "-moz-radial-gradient(45px, ellipse closest-corner, red, blue)",
+ "-moz-radial-gradient(45px, farthest-side circle, red, blue)",
+
+ "-moz-repeating-linear-gradient(red, blue)",
+ "-moz-repeating-linear-gradient(red, yellow, blue)",
+ "-moz-repeating-linear-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "-moz-repeating-linear-gradient(red, yellow, green, blue 50%)",
+ "-moz-repeating-linear-gradient(red -50%, yellow -25%, green, blue)",
+ "-moz-repeating-linear-gradient(red -99px, yellow, green, blue 120%)",
+ "-moz-repeating-linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "-moz-repeating-linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "-moz-repeating-linear-gradient(to top, red, blue)",
+ "-moz-repeating-linear-gradient(to bottom, red, blue)",
+ "-moz-repeating-linear-gradient(to left, red, blue)",
+ "-moz-repeating-linear-gradient(to right, red, blue)",
+ "-moz-repeating-linear-gradient(to top left, red, blue)",
+ "-moz-repeating-linear-gradient(to top right, red, blue)",
+ "-moz-repeating-linear-gradient(to bottom left, red, blue)",
+ "-moz-repeating-linear-gradient(to bottom right, red, blue)",
+ "-moz-repeating-linear-gradient(to left top, red, blue)",
+ "-moz-repeating-linear-gradient(to left bottom, red, blue)",
+ "-moz-repeating-linear-gradient(to right top, red, blue)",
+ "-moz-repeating-linear-gradient(to right bottom, red, blue)",
+
+ "-moz-repeating-linear-gradient(top left, red, blue)",
+
+ "-moz-repeating-radial-gradient(red, blue)",
+ "-moz-repeating-radial-gradient(red, yellow, blue)",
+ "-moz-repeating-radial-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "-moz-repeating-radial-gradient(red, yellow, green, blue 50%)",
+ "-moz-repeating-radial-gradient(red -50%, yellow -25%, green, blue)",
+ "-moz-repeating-radial-gradient(red -99px, yellow, green, blue 120%)",
+ "-moz-repeating-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "-moz-repeating-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "-moz-repeating-radial-gradient(farthest-corner, red, blue)",
+ "-moz-repeating-radial-gradient(circle, red, blue)",
+ "-moz-repeating-radial-gradient(ellipse closest-corner, red, blue)",
+
+ "-moz-radial-gradient(calc(25%) top, red, blue)",
+ "-moz-radial-gradient(left calc(25%), red, blue)",
+ "-moz-radial-gradient(calc(25px) top, red, blue)",
+ "-moz-radial-gradient(left calc(25px), red, blue)",
+ "-moz-radial-gradient(calc(-25%) top, red, blue)",
+ "-moz-radial-gradient(left calc(-25%), red, blue)",
+ "-moz-radial-gradient(calc(-25px) top, red, blue)",
+ "-moz-radial-gradient(left calc(-25px), red, blue)",
+ "-moz-radial-gradient(calc(100px + -25%) top, red, blue)",
+ "-moz-radial-gradient(left calc(100px + -25%), red, blue)",
+ "-moz-radial-gradient(calc(100px + -25px) top, red, blue)",
+ "-moz-radial-gradient(left calc(100px + -25px), red, blue)"
+ );
+
+ invalidNonUrlImageValues.push(
+ // The entries in this block used to be valid with the older more-complex
+ // -moz prefixed gradient syntax, but we've since simplified the syntax for
+ // consistency with -webkit prefixed gradients, in a way that makes these
+ // invalid now.
+ "-moz-linear-gradient(center 0%, red, blue)",
+ "-moz-linear-gradient(50% top, red, blue)",
+ "-moz-linear-gradient(50% 0%, red, blue)",
+ "-moz-linear-gradient(0 0, red, blue)",
+ "-moz-linear-gradient(20% bottom, red, blue)",
+ "-moz-linear-gradient(center 20%, red, blue)",
+ "-moz-linear-gradient(left 35px, red, blue)",
+ "-moz-linear-gradient(10% 10em, red, blue)",
+ "-moz-linear-gradient(44px top, red, blue)",
+ "-moz-linear-gradient(0px, red, blue)",
+ "-moz-linear-gradient(top left 45deg, red, blue)",
+ "-moz-linear-gradient(20% bottom -300deg, red, blue)",
+ "-moz-linear-gradient(center 20% 1.95929rad, red, blue)",
+ "-moz-linear-gradient(left 35px 30grad, red, blue)",
+ "-moz-linear-gradient(left 35px 0.1turn, red, blue)",
+ "-moz-linear-gradient(10% 10em 99999deg, red, blue)",
+ "-moz-linear-gradient(44px top -33deg, red, blue)",
+ "-moz-linear-gradient(30grad left 35px, red, blue)",
+ "-moz-linear-gradient(10deg 20px, red, blue)",
+ "-moz-linear-gradient(1turn 20px, red, blue)",
+ "-moz-linear-gradient(.414rad bottom, red, blue)",
+ "-moz-radial-gradient(top left 45deg, red, blue)",
+ "-moz-radial-gradient(20% bottom -300deg, red, blue)",
+ "-moz-radial-gradient(center 20% 1.95929rad, red, blue)",
+ "-moz-radial-gradient(left 35px 30grad, red, blue)",
+ "-moz-radial-gradient(10% 10em 99999deg, red, blue)",
+ "-moz-radial-gradient(44px top -33deg, red, blue)",
+ "-moz-radial-gradient(-33deg, red, blue)",
+ "-moz-radial-gradient(30grad left 35px, red, blue)",
+ "-moz-radial-gradient(10deg 20px, red, blue)",
+ "-moz-radial-gradient(.414rad bottom, red, blue)",
+ "-moz-radial-gradient(99deg, cover, red, blue)",
+ "-moz-radial-gradient(-1.2345rad, circle, red, blue)",
+ "-moz-radial-gradient(399grad, ellipse closest-corner, red, blue)",
+ "-moz-radial-gradient(399grad, farthest-side circle, red, blue)",
+ "-moz-radial-gradient(top left 99deg, cover, red, blue)",
+ "-moz-radial-gradient(15% 20% -1.2345rad, circle, red, blue)",
+ "-moz-radial-gradient(45px 399grad, ellipse closest-corner, red, blue)",
+ "-moz-radial-gradient(45px 399grad, farthest-side circle, red, blue)",
+ "-moz-repeating-linear-gradient(0 0, red, blue)",
+ "-moz-repeating-linear-gradient(20% bottom, red, blue)",
+ "-moz-repeating-linear-gradient(center 20%, red, blue)",
+ "-moz-repeating-linear-gradient(left 35px, red, blue)",
+ "-moz-repeating-linear-gradient(10% 10em, red, blue)",
+ "-moz-repeating-linear-gradient(44px top, red, blue)",
+ "-moz-repeating-linear-gradient(top left 45deg, red, blue)",
+ "-moz-repeating-linear-gradient(20% bottom -300deg, red, blue)",
+ "-moz-repeating-linear-gradient(center 20% 1.95929rad, red, blue)",
+ "-moz-repeating-linear-gradient(left 35px 30grad, red, blue)",
+ "-moz-repeating-linear-gradient(10% 10em 99999deg, red, blue)",
+ "-moz-repeating-linear-gradient(44px top -33deg, red, blue)",
+ "-moz-repeating-linear-gradient(30grad left 35px, red, blue)",
+ "-moz-repeating-linear-gradient(10deg 20px, red, blue)",
+ "-moz-repeating-linear-gradient(.414rad bottom, red, blue)",
+
+ /* Negative radii */
+ "-moz-radial-gradient(40%, -100px -10%, red, blue)",
+
+ /* no quirks mode colors */
+ "-moz-radial-gradient(10% bottom, ffffff, black) scroll no-repeat",
+ /* no quirks mode lengths */
+ "-moz-linear-gradient(10 10px -45deg, red, blue) repeat",
+ "-moz-linear-gradient(10px 10 -45deg, red, blue) repeat",
+ /* Unitless 0 is invalid as an <angle> */
+ "-moz-linear-gradient(top left 0, red, blue)",
+ "-moz-linear-gradient(5px 5px 0, red, blue)",
+ /* There must be a comma between gradient-line (e.g. <angle>) and colors */
+ "-moz-linear-gradient(30deg red, blue)",
+ "-moz-linear-gradient(5px 5px 30deg red, blue)",
+ "-moz-linear-gradient(5px 5px red, blue)",
+ "-moz-linear-gradient(top left 30deg red, blue)",
+
+ /* Old syntax */
+ "-moz-linear-gradient(10px 10px, 20px, 30px 30px, 40px, from(blue), to(red))",
+ "-moz-radial-gradient(20px 20px, 10px 10px, from(green), to(#ff00ff))",
+ "-moz-radial-gradient(10px 10px, 20%, 40px 40px, 10px, from(green), to(#ff00ff))",
+ "-moz-linear-gradient(10px, 20px, 30px, 40px, color-stop(0.5, #00ccff))",
+ "-moz-linear-gradient(20px 20px, from(blue), to(red))",
+ "-moz-linear-gradient(40px 40px, 10px 10px, from(blue) to(red) color-stop(10%, fuchsia))",
+ "-moz-linear-gradient(20px 20px 30px, 10px 10px, from(red), to(#ff0000))",
+ "-moz-radial-gradient(left top, center, 20px 20px, 10px, from(blue), to(red))",
+ "-moz-linear-gradient(left left, top top, from(blue))",
+ "-moz-linear-gradient(inherit, 10px 10px, from(blue))",
+ /* New syntax */
+ "-moz-linear-gradient(10px 10px, 20px, 30px 30px, 40px, blue 0, red 100%)",
+ "-moz-radial-gradient(20px 20px, 10px 10px, from(green), to(#ff00ff))",
+ "-moz-radial-gradient(10px 10px, 20%, 40px 40px, 10px, from(green), to(#ff00ff))",
+ "-moz-linear-gradient(10px, 20px, 30px, 40px, #00ccff 50%)",
+ "-moz-linear-gradient(40px 40px, 10px 10px, blue 0 fuchsia 10% red 100%)",
+ "-moz-linear-gradient(20px 20px 30px, 10px 10px, red 0, #ff0000 100%)",
+ "-moz-radial-gradient(left top, center, 20px 20px, 10px, from(blue), to(red))",
+ "-moz-linear-gradient(left left, top top, blue 0)",
+ "-moz-linear-gradient(inherit, 10px 10px, blue 0)",
+ "-moz-linear-gradient(left left blue red)",
+ "-moz-linear-gradient(left left blue, red)",
+ "-moz-linear-gradient()",
+ "-moz-linear-gradient(cover, red, blue)",
+ "-moz-linear-gradient(auto, red, blue)",
+ "-moz-linear-gradient(22 top, red, blue)",
+ "-moz-linear-gradient(10% red blue)",
+ "-moz-linear-gradient(10%, red blue)",
+ "-moz-linear-gradient(10%,, red, blue)",
+ "-moz-linear-gradient(45px, center, red, blue)",
+ "-moz-linear-gradient(45px, center red, blue)",
+ "-moz-radial-gradient(contain, ellipse, red, blue)",
+ "-moz-radial-gradient(10deg contain, red, blue)",
+ "-moz-radial-gradient(10deg, contain,, red, blue)",
+ "-moz-radial-gradient(contain contain, red, blue)",
+ "-moz-radial-gradient(ellipse circle, red, blue)",
+ "-moz-radial-gradient(to top left, red, blue)",
+ "-moz-radial-gradient(center, 10%, red, blue)",
+ "-moz-radial-gradient(5rad, 20px, red, blue)",
+
+ "-moz-radial-gradient(at top left to cover, red, blue)",
+ "-moz-radial-gradient(at 15% 20% circle, red, blue)",
+
+ "-moz-radial-gradient(to cover, red, blue)",
+ "-moz-radial-gradient(to contain, red, blue)",
+ "-moz-radial-gradient(to closest-side circle, red, blue)",
+ "-moz-radial-gradient(to farthest-corner ellipse, red, blue)",
+
+ "-moz-radial-gradient(ellipse at 45px closest-corner, red, blue)",
+ "-moz-radial-gradient(circle at 45px farthest-side, red, blue)",
+ "-moz-radial-gradient(ellipse 45px, closest-side, red, blue)",
+ "-moz-radial-gradient(circle 45px, farthest-corner, red, blue)",
+ "-moz-radial-gradient(ellipse, ellipse closest-side, red, blue)",
+ "-moz-radial-gradient(circle, circle farthest-corner, red, blue)",
+
+ "-moz-radial-gradient(99deg to farthest-corner, red, blue)",
+ "-moz-radial-gradient(-1.2345rad circle, red, blue)",
+ "-moz-radial-gradient(ellipse 399grad to closest-corner, red, blue)",
+ "-moz-radial-gradient(circle 399grad to farthest-side, red, blue)",
+
+ "-moz-radial-gradient(at top left 99deg, to farthest-corner, red, blue)",
+ "-moz-radial-gradient(circle at 15% 20% -1.2345rad, red, blue)",
+ "-moz-radial-gradient(to top left at 30% 40%, red, blue)",
+ "-moz-radial-gradient(ellipse at 45px 399grad, to closest-corner, red, blue)",
+ "-moz-radial-gradient(at 45px 399grad to farthest-side circle, red, blue)",
+
+ "-moz-radial-gradient(to 50%, red, blue)",
+ "-moz-radial-gradient(circle to 50%, red, blue)",
+ "-moz-radial-gradient(circle to 43px 43px, red, blue)",
+ "-moz-radial-gradient(circle to 50% 50%, red, blue)",
+ "-moz-radial-gradient(circle to 43px 50%, red, blue)",
+ "-moz-radial-gradient(circle to 50% 43px, red, blue)",
+ "-moz-radial-gradient(ellipse to 43px, red, blue)",
+ "-moz-radial-gradient(ellipse to 50%, red, blue)",
+
+ "-moz-linear-gradient(to 0 0, red, blue)",
+ "-moz-linear-gradient(to 20% bottom, red, blue)",
+ "-moz-linear-gradient(to center 20%, red, blue)",
+ "-moz-linear-gradient(to left 35px, red, blue)",
+ "-moz-linear-gradient(to 10% 10em, red, blue)",
+ "-moz-linear-gradient(to 44px top, red, blue)",
+ "-moz-linear-gradient(to top left 45deg, red, blue)",
+ "-moz-linear-gradient(to 20% bottom -300deg, red, blue)",
+ "-moz-linear-gradient(to center 20% 1.95929rad, red, blue)",
+ "-moz-linear-gradient(to left 35px 30grad, red, blue)",
+ "-moz-linear-gradient(to 10% 10em 99999deg, red, blue)",
+ "-moz-linear-gradient(to 44px top -33deg, red, blue)",
+ "-moz-linear-gradient(to -33deg, red, blue)",
+ "-moz-linear-gradient(to 30grad left 35px, red, blue)",
+ "-moz-linear-gradient(to 10deg 20px, red, blue)",
+ "-moz-linear-gradient(to .414rad bottom, red, blue)",
+
+ "-moz-linear-gradient(to top top, red, blue)",
+ "-moz-linear-gradient(to bottom bottom, red, blue)",
+ "-moz-linear-gradient(to left left, red, blue)",
+ "-moz-linear-gradient(to right right, red, blue)",
+
+ "-moz-repeating-linear-gradient(10px 10px, 20px, 30px 30px, 40px, blue 0, red 100%)",
+ "-moz-repeating-radial-gradient(20px 20px, 10px 10px, from(green), to(#ff00ff))",
+ "-moz-repeating-radial-gradient(10px 10px, 20%, 40px 40px, 10px, from(green), to(#ff00ff))",
+ "-moz-repeating-linear-gradient(10px, 20px, 30px, 40px, #00ccff 50%)",
+ "-moz-repeating-linear-gradient(40px 40px, 10px 10px, blue 0 fuchsia 10% red 100%)",
+ "-moz-repeating-linear-gradient(20px 20px 30px, 10px 10px, red 0, #ff0000 100%)",
+ "-moz-repeating-radial-gradient(left top, center, 20px 20px, 10px, from(blue), to(red))",
+ "-moz-repeating-linear-gradient(left left, top top, blue 0)",
+ "-moz-repeating-linear-gradient(inherit, 10px 10px, blue 0)",
+ "-moz-repeating-linear-gradient(left left blue red)",
+ "-moz-repeating-linear-gradient()",
+
+ "-moz-repeating-linear-gradient(to 0 0, red, blue)",
+ "-moz-repeating-linear-gradient(to 20% bottom, red, blue)",
+ "-moz-repeating-linear-gradient(to center 20%, red, blue)",
+ "-moz-repeating-linear-gradient(to left 35px, red, blue)",
+ "-moz-repeating-linear-gradient(to 10% 10em, red, blue)",
+ "-moz-repeating-linear-gradient(to 44px top, red, blue)",
+ "-moz-repeating-linear-gradient(to top left 45deg, red, blue)",
+ "-moz-repeating-linear-gradient(to 20% bottom -300deg, red, blue)",
+ "-moz-repeating-linear-gradient(to center 20% 1.95929rad, red, blue)",
+ "-moz-repeating-linear-gradient(to left 35px 30grad, red, blue)",
+ "-moz-repeating-linear-gradient(to 10% 10em 99999deg, red, blue)",
+ "-moz-repeating-linear-gradient(to 44px top -33deg, red, blue)",
+ "-moz-repeating-linear-gradient(to -33deg, red, blue)",
+ "-moz-repeating-linear-gradient(to 30grad left 35px, red, blue)",
+ "-moz-repeating-linear-gradient(to 10deg 20px, red, blue)",
+ "-moz-repeating-linear-gradient(to .414rad bottom, red, blue)",
+
+ "-moz-repeating-linear-gradient(to top top, red, blue)",
+ "-moz-repeating-linear-gradient(to bottom bottom, red, blue)",
+ "-moz-repeating-linear-gradient(to left left, red, blue)",
+ "-moz-repeating-linear-gradient(to right right, red, blue)",
+
+ "-moz-repeating-radial-gradient(to top left at 30% 40%, red, blue)",
+ "-moz-repeating-radial-gradient(ellipse at 45px closest-corner, red, blue)",
+ "-moz-repeating-radial-gradient(circle at 45px farthest-side, red, blue)",
+
+ /* Valid only when unprefixed */
+ "-moz-radial-gradient(at top left, red, blue)",
+ "-moz-radial-gradient(at 20% bottom, red, blue)",
+ "-moz-radial-gradient(at center 20%, red, blue)",
+ "-moz-radial-gradient(at left 35px, red, blue)",
+ "-moz-radial-gradient(at 10% 10em, red, blue)",
+ "-moz-radial-gradient(at 44px top, red, blue)",
+ "-moz-radial-gradient(at 0 0, red, blue)",
+
+ "-moz-radial-gradient(circle 43px, red, blue)",
+ "-moz-radial-gradient(ellipse 43px 43px, red, blue)",
+ "-moz-radial-gradient(ellipse 50% 50%, red, blue)",
+ "-moz-radial-gradient(ellipse 43px 50%, red, blue)",
+ "-moz-radial-gradient(ellipse 50% 43px, red, blue)",
+
+ "-moz-radial-gradient(farthest-corner at top left, red, blue)",
+ "-moz-radial-gradient(ellipse closest-corner at 45px, red, blue)",
+ "-moz-radial-gradient(circle farthest-side at 45px, red, blue)",
+ "-moz-radial-gradient(closest-side ellipse at 50%, red, blue)",
+ "-moz-radial-gradient(farthest-corner circle at 4em, red, blue)",
+
+ "-moz-radial-gradient(30% 40% at top left, red, blue)",
+ "-moz-radial-gradient(50px 60px at 15% 20%, red, blue)",
+ "-moz-radial-gradient(7em 8em at 45px, red, blue)"
+ );
+}
+
+const pathValues = {
+ other_values: [
+ "path('M 10 10 20 20 H 90 V 90 Z')",
+ "path('M10 10 20,20H90V90Z')",
+ "path('M 10 10 C 20 20, 40 20, 50 10')",
+ "path('M 10 80 C 40 10, 65 10, 95 80 S 1.5e2 150, 180 80')",
+ "path('M 10 80 Q 95 10 180 80')",
+ "path('M 10 80 Q 52.5 10, 95 80 T 180 80')",
+ "path('M 80 80 A 45 45, 0, 0, 0, 1.25e2 1.25e2 L 125 80 Z')",
+ "path('M100-200h20z')",
+ "path('M10,10L20.6.5z')",
+ ],
+ invalid_values: [
+ "path()",
+ "path(a)",
+ "path('M 10 Z')",
+ "path('M 10-10 20')",
+ "path('M 10 10 C 20 20 40 20')",
+ ],
+};
+
+var gCSSProperties = {
+ animation: {
+ domProp: "animation",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ subproperties: [
+ "animation-name",
+ "animation-duration",
+ "animation-timing-function",
+ "animation-delay",
+ "animation-direction",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-play-state",
+ ],
+ initial_values: [
+ "none none 0s 0s ease normal running 1.0",
+ "none",
+ "0s",
+ "ease",
+ "normal",
+ "running",
+ "1.0",
+ ],
+ other_values: [
+ "none none 0s 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) normal running 1.0",
+ "bounce 1s linear 2s",
+ "bounce 1s 2s linear",
+ "bounce linear 1s 2s",
+ "linear bounce 1s 2s",
+ "linear 1s bounce 2s",
+ "linear 1s 2s bounce",
+ "1s bounce linear 2s",
+ "1s bounce 2s linear",
+ "1s 2s bounce linear",
+ "1s linear bounce 2s",
+ "1s linear 2s bounce",
+ "1s 2s linear bounce",
+ "bounce linear 1s",
+ "bounce 1s linear",
+ "linear bounce 1s",
+ "linear 1s bounce",
+ "1s bounce linear",
+ "1s linear bounce",
+ "1s 2s bounce",
+ "1s bounce 2s",
+ "bounce 1s 2s",
+ "1s 2s linear",
+ "1s linear 2s",
+ "linear 1s 2s",
+ "bounce 1s",
+ "1s bounce",
+ "linear 1s",
+ "1s linear",
+ "1s 2s",
+ "2s 1s",
+ "bounce",
+ "linear",
+ "1s",
+ "height",
+ "2s",
+ "ease-in-out",
+ "2s ease-in",
+ "opacity linear",
+ "ease-out 2s",
+ "2s color, 1s bounce, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)",
+ "1s \\32bounce linear 2s",
+ "1s -bounce linear 2s",
+ "1s -\\32bounce linear 2s",
+ "1s \\32 0bounce linear 2s",
+ "1s -\\32 0bounce linear 2s",
+ "1s \\2bounce linear 2s",
+ "1s -\\2bounce linear 2s",
+ "2s, 1s bounce",
+ "1s bounce, 2s",
+ "2s all, 1s bounce",
+ "1s bounce, 2s all",
+ "1s bounce, 2s none",
+ "2s none, 1s bounce",
+ "2s bounce, 1s all",
+ "2s all, 1s bounce",
+ ],
+ invalid_values: [
+ "2s inherit",
+ "inherit 2s",
+ "2s bounce, 1s inherit",
+ "2s inherit, 1s bounce",
+ "2s initial",
+ "2s all,, 1s bounce",
+ "2s all, , 1s bounce",
+ "bounce 1s cubic-bezier(0, rubbish) 2s",
+ "bounce 1s steps(rubbish) 2s",
+ "2s unset",
+ ],
+ },
+ "animation-delay": {
+ domProp: "animationDelay",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["0s", "0ms"],
+ other_values: [
+ "1s",
+ "250ms",
+ "-100ms",
+ "-1s",
+ "1s, 250ms, 2.3s",
+ "calc(1s + 2ms)",
+ ],
+ invalid_values: ["0", "0px"],
+ },
+ "animation-direction": {
+ domProp: "animationDirection",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["normal"],
+ other_values: [
+ "alternate",
+ "normal, alternate",
+ "alternate, normal",
+ "normal, normal",
+ "normal, normal, normal",
+ "reverse",
+ "alternate-reverse",
+ "normal, reverse, alternate-reverse, alternate",
+ ],
+ invalid_values: [
+ "normal normal",
+ "inherit, normal",
+ "reverse-alternate",
+ "normal, unset",
+ "unset, normal",
+ ],
+ },
+ "animation-duration": {
+ domProp: "animationDuration",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0s", "0ms"],
+ applies_to_marker: true,
+ other_values: ["1s", "250ms", "1s, 250ms, 2.3s", "calc(1s + 2ms)"],
+ invalid_values: ["0", "0px", "-1ms", "-2s"],
+ },
+ "animation-fill-mode": {
+ domProp: "animationFillMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["none"],
+ other_values: [
+ "forwards",
+ "backwards",
+ "both",
+ "none, none",
+ "forwards, backwards",
+ "forwards, none",
+ "none, both",
+ ],
+ invalid_values: ["all"],
+ },
+ "animation-iteration-count": {
+ domProp: "animationIterationCount",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["1"],
+ other_values: [
+ "infinite",
+ "0",
+ "0.5",
+ "7.75",
+ "-0.0",
+ "1, 2, 3",
+ "infinite, 2",
+ "1, infinite",
+ "calc(1 + 2.0)",
+ ],
+ // negatives forbidden per
+ // http://lists.w3.org/Archives/Public/www-style/2011Mar/0355.html
+ invalid_values: ["none", "-1", "-0.5", "-1, infinite", "infinite, -3"],
+ },
+ "animation-name": {
+ domProp: "animationName",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["none"],
+ other_values: [
+ "all",
+ "ball",
+ "mall",
+ "color",
+ "bounce, bubble, opacity",
+ "foobar",
+ "auto",
+ "\\32bounce",
+ "-bounce",
+ "-\\32bounce",
+ "\\32 0bounce",
+ "-\\32 0bounce",
+ "\\2bounce",
+ "-\\2bounce",
+ ],
+ invalid_values: [
+ "bounce, initial",
+ "initial, bounce",
+ "bounce, inherit",
+ "inherit, bounce",
+ "bounce, unset",
+ "unset, bounce",
+ ],
+ },
+ "animation-play-state": {
+ domProp: "animationPlayState",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["running"],
+ other_values: [
+ "paused",
+ "running, running",
+ "paused, running",
+ "paused, paused",
+ "running, paused",
+ "paused, running, running, running, paused, running",
+ ],
+ invalid_values: ["0"],
+ },
+ "animation-timing-function": {
+ domProp: "animationTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["ease"],
+ other_values: [
+ "cubic-bezier(0.25, 0.1, 0.25, 1.0)",
+ "linear",
+ "ease-in",
+ "ease-out",
+ "ease-in-out",
+ "linear, ease-in, cubic-bezier(0.1, 0.2, 0.8, 0.9)",
+ "cubic-bezier(0.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(0.25, 1.5, 0.75, -0.5)",
+ "step-start",
+ "step-end",
+ "steps(1)",
+ "steps(2, start)",
+ "steps(386)",
+ "steps(3, end)",
+ "steps(calc(2 + 1))",
+ "steps(1, jump-start)",
+ "steps(1, jump-end)",
+ "steps(2, jump-none)",
+ "steps(1, jump-both)",
+ ],
+ invalid_values: [
+ "none",
+ "auto",
+ "cubic-bezier(0.25, 0.1, 0.25)",
+ "cubic-bezier(0.25, 0.1, 0.25, 0.25, 1.0)",
+ "cubic-bezier(-0.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(1.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(0.5, 0.5, -0.5, 0.5)",
+ "cubic-bezier(0.5, 0.5, 1.5, 0.5)",
+ "steps(2, step-end)",
+ "steps(0)",
+ "steps(-2)",
+ "steps(0, step-end, 1)",
+ "steps(0, jump-start)",
+ "steps(0, jump-end)",
+ "steps(1, jump-none)",
+ "steps(0, jump-both)",
+ ],
+ },
+ appearance: {
+ domProp: "appearance",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["auto", "radio", "menulist"],
+ invalid_values: [],
+ },
+ "-moz-appearance": {
+ domProp: "MozAppearance",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "appearance",
+ subproperties: ["appearance"],
+ },
+ "-webkit-appearance": {
+ domProp: "webkitAppearance",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "appearance",
+ subproperties: ["appearance"],
+ },
+ "aspect-ratio": {
+ domProp: "aspectRatio",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "1",
+ "1.0",
+ "1 / 2",
+ "1/2",
+ "16.2 / 9.5",
+ "1/0",
+ "0/1",
+ "0 / 0",
+ "auto 1",
+ "0 auto",
+ ],
+ invalid_values: ["none", "1 test", "1 / auto", "auto / 1"],
+ },
+ "border-inline": {
+ domProp: "borderInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-inline-start-color",
+ "border-inline-start-style",
+ "border-inline-start-width",
+ "border-inline-end-color",
+ "border-inline-end-style",
+ "border-inline-end-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-inline-end": {
+ domProp: "borderInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-inline-end-color",
+ "border-inline-end-style",
+ "border-inline-end-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 green none"],
+ },
+ "border-inline-color": {
+ domProp: "borderInlineColor",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-inline-start-color", "border-inline-end-color"],
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5) blue", "blue transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-inline-end-color": {
+ domProp: "borderInlineEndColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-inline-style": {
+ domProp: "borderInlineStyle",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-inline-start-style", "border-inline-end-style"],
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed solid",
+ "solid dotted",
+ "double double",
+ "inset outset",
+ "inset double",
+ "none groove",
+ "ridge none",
+ ],
+ invalid_values: [],
+ },
+ "border-inline-end-style": {
+ domProp: "borderInlineEndStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-inline-width": {
+ domProp: "borderInlineWidth",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-inline-start-width", "border-inline-end-width"],
+ prerequisites: { "border-style": "solid" },
+ initial_values: ["medium", "3px", "medium medium"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(2px) thin",
+ "calc(-2px)",
+ "calc(-2px) thick",
+ "calc(0em)",
+ "medium calc(0em)",
+ "calc(0px)",
+ "1px calc(0px)",
+ "calc(5em)",
+ "1em calc(5em)",
+ ],
+ invalid_values: ["5%", "5", "5 thin", "thin 5%", "blue", "solid"],
+ },
+ "border-inline-end-width": {
+ domProp: "borderInlineEndWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { "border-inline-end-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ "border-image": {
+ domProp: "borderImage",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat",
+ ],
+ initial_values: ["none"],
+ other_values: [
+ "url('border.png') 27 27 27 27",
+ "url('border.png') 27",
+ "stretch url('border.png')",
+ "url('border.png') 27 fill",
+ "url('border.png') 27 27 27 27 repeat",
+ "repeat url('border.png') 27 27 27 27",
+ "url('border.png') repeat 27 27 27 27",
+ "url('border.png') fill 27 27 27 27 repeat",
+ "url('border.png') fill 27 27 27 27 repeat space",
+ "url('border.png') 27 27 27 27 / 1em",
+ "27 27 27 27 / 1em url('border.png') ",
+ "url('border.png') 27 27 27 27 / 10 10 10 / 10 10 repeat",
+ "repeat 27 27 27 27 / 10 10 10 / 10 10 url('border.png')",
+ "url('border.png') 27 27 27 27 / / 10 10 1em",
+ "fill 27 27 27 27 / / 10 10 1em url('border.png')",
+ "url('border.png') 27 27 27 27 / 1em 1em 1em 1em repeat",
+ "url('border.png') 27 27 27 27 / 1em 1em 1em 1em stretch round",
+ ],
+ invalid_values: [
+ "url('border.png') 27 27 27 27 27",
+ "url('border.png') 27 27 27 27 / 1em 1em 1em 1em 1em",
+ "url('border.png') 27 27 27 27 /",
+ "url('border.png') fill",
+ "url('border.png') fill repeat",
+ "fill repeat",
+ "url('border.png') fill / 1em",
+ "url('border.png') / repeat",
+ "url('border.png') 1 /",
+ "url('border.png') 1 / /",
+ "1 / url('border.png')",
+ "url('border.png') / 1",
+ "url('border.png') / / 1",
+ ],
+ },
+ "border-image-source": {
+ domProp: "borderImageSource",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["none"],
+ other_values: ["url('border.png')"].concat(validNonUrlImageValues),
+ invalid_values: ["url('border.png') url('border.png')"].concat(
+ invalidNonUrlImageValues
+ ),
+ unbalanced_values: [].concat(unbalancedGradientAndElementValues),
+ },
+ "border-image-slice": {
+ domProp: "borderImageSlice",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["100%", "100% 100% 100% 100%"],
+ other_values: [
+ "0%",
+ "10",
+ "10 100% 0 2",
+ "0 0 0 0",
+ "fill 10 10",
+ "10 10 fill",
+ ],
+ invalid_values: [
+ "-10%",
+ "-10",
+ "10 10 10 10 10",
+ "10 10 10 10 -10",
+ "10px",
+ "-10px",
+ "fill",
+ "fill fill 10px",
+ "10px fill fill",
+ ],
+ },
+ "border-image-width": {
+ domProp: "borderImageWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["1", "1 1 1 1"],
+ other_values: [
+ "0",
+ "0%",
+ "0px",
+ "auto auto auto auto",
+ "10 10% auto 15px",
+ "10px 10px 10px 10px",
+ "10",
+ "10 10",
+ "10 10 10",
+ "calc(10px)",
+ "calc(10px + 5%)",
+ ],
+ invalid_values: [
+ "-10",
+ "-10px",
+ "-10%",
+ "10 10 10 10 10",
+ "10 10 10 10 auto",
+ "auto auto auto auto auto",
+ "10px calc(nonsense)",
+ "1px red",
+ ],
+ unbalanced_values: ["10px calc("],
+ },
+ "border-image-outset": {
+ domProp: "borderImageOutset",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["0", "0 0 0 0"],
+ other_values: [
+ "10px",
+ "10",
+ "10 10",
+ "10 10 10",
+ "10 10 10 10",
+ "10px 10 10 10px",
+ ],
+ invalid_values: [
+ "-10",
+ "-10px",
+ "-10%",
+ "10%",
+ "10 10 10 10 10",
+ "10px calc(nonsense)",
+ "1px red",
+ ],
+ unbalanced_values: ["10px calc("],
+ },
+ "border-image-repeat": {
+ domProp: "borderImageRepeat",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["stretch", "stretch stretch"],
+ other_values: [
+ "round",
+ "repeat",
+ "stretch round",
+ "repeat round",
+ "stretch repeat",
+ "round round",
+ "repeat repeat",
+ "space",
+ "stretch space",
+ "repeat space",
+ "round space",
+ "space space",
+ ],
+ invalid_values: ["none", "stretch stretch stretch", "0", "10", "0%", "0px"],
+ },
+ "border-radius": {
+ domProp: "borderRadius",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ subproperties: [
+ "border-bottom-left-radius",
+ "border-bottom-right-radius",
+ "border-top-left-radius",
+ "border-top-right-radius",
+ ],
+ initial_values: [
+ "0",
+ "0px",
+ "0px 0 0 0px",
+ "calc(-2px)",
+ "calc(0px) calc(0pt)",
+ "calc(0px) calc(0pt) calc(0px) calc(0em)",
+ ],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em",
+ "3em 2px",
+ "2pt 3% 4em",
+ "2px 2px 2px 2px", // circular
+ "3% / 2%",
+ "1px / 4px",
+ "2em / 1em",
+ "3em 2px / 2px 3em",
+ "2pt 3% 4em / 4pt 1% 5em",
+ "2px 2px 2px 2px / 4px 4px 4px 4px",
+ "1pt / 2pt 3pt",
+ "4pt 5pt / 3pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "2px 2px calc(2px + 1%) 2px",
+ "1px 2px 2px 2px / 2px 2px calc(2px + 1%) 2px",
+ ],
+ invalid_values: [
+ "2px -2px",
+ "inherit 2px",
+ "inherit / 2px",
+ "2px inherit",
+ "2px / inherit",
+ "2px 2px 2px 2px 2px",
+ "1px / 2px 2px 2px 2px 2px",
+ "2",
+ "2 2",
+ "2px 2px 2px 2px / 2px 2px 2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "unset / 2px",
+ "2px unset",
+ "2px / unset",
+ ],
+ },
+ "border-bottom-left-radius": {
+ domProp: "borderBottomLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-bottom-right-radius": {
+ domProp: "borderBottomRightRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-top-left-radius": {
+ domProp: "borderTopLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-top-right-radius": {
+ domProp: "borderTopRightRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-start-start-radius": {
+ domProp: "borderStartStartRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-start-end-radius": {
+ domProp: "borderStartEndRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-end-start-radius": {
+ domProp: "borderEndStartRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-end-end-radius": {
+ domProp: "borderEndEndRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-inline-start": {
+ domProp: "borderInlineStart",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-inline-start-color",
+ "border-inline-start-style",
+ "border-inline-start-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 green solid"],
+ },
+ "border-inline-start-color": {
+ domProp: "borderInlineStartColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-inline-start-style": {
+ domProp: "borderInlineStartStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-inline-start-width": {
+ domProp: "borderInlineStartWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { "border-inline-start-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ "-moz-box-align": {
+ domProp: "MozBoxAlign",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["stretch"],
+ other_values: ["start", "center", "baseline", "end"],
+ invalid_values: [],
+ },
+ "-moz-box-direction": {
+ domProp: "MozBoxDirection",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["reverse"],
+ invalid_values: [],
+ },
+ "-moz-box-flex": {
+ domProp: "MozBoxFlex",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0", "0.0", "-0.0"],
+ other_values: ["1", "100", "0.1"],
+ invalid_values: ["10px", "-1"],
+ },
+ "-moz-box-ordinal-group": {
+ domProp: "MozBoxOrdinalGroup",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["1"],
+ other_values: ["2", "100", "0"],
+ invalid_values: ["1.0", "-1", "-1000"],
+ },
+ "-moz-box-orient": {
+ domProp: "MozBoxOrient",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["horizontal", "inline-axis"],
+ other_values: ["vertical", "block-axis"],
+ invalid_values: [],
+ },
+ "-moz-box-pack": {
+ domProp: "MozBoxPack",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["start"],
+ other_values: ["center", "end", "justify"],
+ invalid_values: [],
+ },
+ "box-decoration-break": {
+ domProp: "boxDecorationBreak",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["slice"],
+ other_values: ["clone"],
+ invalid_values: ["auto", "none", "1px"],
+ },
+ "box-sizing": {
+ domProp: "boxSizing",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["content-box"],
+ other_values: ["border-box"],
+ invalid_values: [
+ "padding-box",
+ "margin-box",
+ "content",
+ "padding",
+ "border",
+ "margin",
+ ],
+ },
+ "-moz-box-sizing": {
+ domProp: "MozBoxSizing",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "box-sizing",
+ subproperties: ["box-sizing"],
+ },
+ "print-color-adjust": {
+ domProp: "printColorAdjust",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["economy"],
+ other_values: ["exact"],
+ invalid_values: [],
+ },
+ "color-adjust": {
+ domProp: "colorAdjust",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "print-color-adjust",
+ subproperties: ["print-color-adjust"],
+ },
+ "color-scheme": {
+ domProp: "colorScheme",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "light",
+ "dark",
+ "light dark",
+ "light dark purple",
+ "light light dark",
+ "only light",
+ "only light dark",
+ "only light dark purple",
+ "light only",
+ ],
+ invalid_values: ["only normal", "normal only", "only light only"],
+ },
+ columns: {
+ domProp: "columns",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["column-count", "column-width"],
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "3",
+ "20px",
+ "2 10px",
+ "10px 2",
+ "2 auto",
+ "auto 2",
+ "auto 50px",
+ "50px auto",
+ ],
+ invalid_values: [
+ "5%",
+ "-1px",
+ "-1",
+ "3 5",
+ "10px 4px",
+ "10 2px 5in",
+ "30px -1",
+ "auto 3 5px",
+ "5 auto 20px",
+ "auto auto auto",
+ "calc(50px + rubbish) 2",
+ ],
+ },
+ "column-count": {
+ domProp: "columnCount",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["1", "17"],
+ // negative and zero invalid per editor's draft
+ invalid_values: ["-1", "0", "3px"],
+ },
+ "column-fill": {
+ domProp: "columnFill",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["balance"],
+ other_values: ["auto"],
+ invalid_values: ["2px", "dotted", "5em"],
+ },
+ "column-rule": {
+ domProp: "columnRule",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { color: "green" },
+ subproperties: [
+ "column-rule-width",
+ "column-rule-style",
+ "column-rule-color",
+ ],
+ initial_values: [
+ "medium none currentColor",
+ "none",
+ "medium",
+ "currentColor",
+ ],
+ other_values: [
+ "2px blue solid",
+ "red dotted 1px",
+ "ridge 4px orange",
+ "5px solid",
+ ],
+ invalid_values: [
+ "2px 3px 4px red",
+ "dotted dashed",
+ "5px dashed green 3px",
+ "5 solid",
+ "5 green solid",
+ ],
+ },
+ "column-rule-width": {
+ domProp: "columnRuleWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { "column-rule-style": "solid" },
+ initial_values: ["medium", "3px", "calc(3px)", "calc(5em + 3px - 5em)"],
+ other_values: [
+ "thin",
+ "15px",
+ /* valid calc() values */
+ "calc(-2px)",
+ "calc(2px)",
+ "calc(3em)",
+ "calc(3em + 2px)",
+ "calc( 3em + 2px)",
+ "calc(3em + 2px )",
+ "calc( 3em + 2px )",
+ "calc(3*25px)",
+ "calc(3 *25px)",
+ "calc(3 * 25px)",
+ "calc(3* 25px)",
+ "calc(25px*3)",
+ "calc(25px *3)",
+ "calc(25px* 3)",
+ "calc(25px * 3)",
+ "calc(25px * 3 / 4)",
+ "calc((25px * 3) / 4)",
+ "calc(25px * (3 / 4))",
+ "calc(3 * 25px / 4)",
+ "calc((3 * 25px) / 4)",
+ "calc(3 * (25px / 4))",
+ "calc(3em + 25px * 3 / 4)",
+ "calc(3em + (25px * 3) / 4)",
+ "calc(3em + 25px * (3 / 4))",
+ "calc(25px * 3 / 4 + 3em)",
+ "calc((25px * 3) / 4 + 3em)",
+ "calc(25px * (3 / 4) + 3em)",
+ "calc(3em + (25px * 3 / 4))",
+ "calc(3em + ((25px * 3) / 4))",
+ "calc(3em + (25px * (3 / 4)))",
+ "calc((25px * 3 / 4) + 3em)",
+ "calc(((25px * 3) / 4) + 3em)",
+ "calc((25px * (3 / 4)) + 3em)",
+ "calc(3*25px + 1in)",
+ "calc(1in - 3em + 2px)",
+ "calc(1in - (3em + 2px))",
+ "calc((1in - 3em) + 2px)",
+ "calc(50px/2)",
+ "calc(50px/(2 - 1))",
+ "calc(-3px)",
+ /* numeric reduction cases */
+ "calc(5 * 3 * 2em)",
+ "calc(2em * 5 * 3)",
+ "calc((5 * 3) * 2em)",
+ "calc(2em * (5 * 3))",
+ "calc((5 + 3) * 2em)",
+ "calc(2em * (5 + 3))",
+ "calc(2em / (5 + 3))",
+ "calc(2em * (5*2 + 3))",
+ "calc(2em * ((5*2) + 3))",
+ "calc(2em * (5*(2 + 3)))",
+
+ "calc((5 + 7) * 3em)",
+ "calc((5em + 3em) - 2em)",
+ "calc((5em - 3em) + 2em)",
+ "calc(2em - (5em - 3em))",
+ "calc(2em + (5em - 3em))",
+ "calc(2em - (5em + 3em))",
+ "calc(2em + (5em + 3em))",
+ "calc(2em + 5em - 3em)",
+ "calc(2em - 5em - 3em)",
+ "calc(2em + 5em + 3em)",
+ "calc(2em - 5em + 3em)",
+
+ "calc(2em / 4 * 3)",
+ "calc(2em * 4 / 3)",
+ "calc(2em * 4 * 3)",
+ "calc(2em / 4 / 3)",
+ "calc(4 * 2em / 3)",
+ "calc(4 / 3 * 2em)",
+
+ "calc((2em / 4) * 3)",
+ "calc((2em * 4) / 3)",
+ "calc((2em * 4) * 3)",
+ "calc((2em / 4) / 3)",
+ "calc((4 * 2em) / 3)",
+ "calc((4 / 3) * 2em)",
+
+ "calc(2em / (4 * 3))",
+ "calc(2em * (4 / 3))",
+ "calc(2em * (4 * 3))",
+ "calc(2em / (4 / 3))",
+ "calc(4 * (2em / 3))",
+
+ "min(5px)",
+ "min(5px,2em)",
+
+ "max(5px)",
+ "max(5px,2em)",
+
+ "calc(min(5px))",
+ "calc(min(5px,2em))",
+
+ "calc(max(5px))",
+ "calc(max(5px,2em))",
+
+ // Valid cases with unitless zero (which is never
+ // a length).
+ "calc(0 * 2em)",
+ "calc(2em * 0)",
+ "calc(3em + 0 * 2em)",
+ "calc(3em + 2em * 0)",
+ "calc((0 + 2) * 2em)",
+ "calc((2 + 0) * 2em)",
+ // And test zero lengths while we're here.
+ "calc(2 * 0px)",
+ "calc(0 * 0px)",
+ "calc(2 * 0em)",
+ "calc(0 * 0em)",
+ "calc(0px * 0)",
+ "calc(0px * 2)",
+ ],
+ invalid_values: [
+ "20",
+ "-1px",
+ "red",
+ "50%",
+ /* invalid calc() values */
+ "calc(2em+ 2px)",
+ "calc(2em +2px)",
+ "calc(2em+2px)",
+ "calc(2em- 2px)",
+ "calc(2em -2px)",
+ "calc(2em-2px)",
+ "-moz-min()",
+ "calc(min())",
+ "-moz-max()",
+ "calc(max())",
+ "-moz-min(5px)",
+ "-moz-max(5px)",
+ "-moz-min(5px,2em)",
+ "-moz-max(5px,2em)",
+ "calc(5 + 5)",
+ "calc(5 * 5)",
+ "calc(5em * 5em)",
+ "calc(5em / 5em * 5em)",
+
+ "calc(4 * 3 / 2em)",
+ "calc((4 * 3) / 2em)",
+ "calc(4 * (3 / 2em))",
+ "calc(4 / (3 * 2em))",
+
+ // Tests for handling of unitless zero, which cannot
+ // be a length inside calc().
+ "calc(0)",
+ "calc(0 + 2em)",
+ "calc(2em + 0)",
+ "calc(0 * 2)",
+ "calc(2 * 0)",
+ "calc(1 * (2em + 0))",
+ "calc((2em + 0))",
+ "calc((2em + 0) * 1)",
+ "calc(1 * (0 + 2em))",
+ "calc((0 + 2em))",
+ "calc((0 + 2em) * 1)",
+ ],
+ },
+ "column-rule-style": {
+ domProp: "columnRuleStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "hidden",
+ "ridge",
+ "groove",
+ "inset",
+ "outset",
+ "double",
+ "dotted",
+ "dashed",
+ ],
+ invalid_values: ["20", "foo"],
+ },
+ "column-rule-color": {
+ domProp: "columnRuleColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "green" },
+ initial_values: ["currentColor"],
+ other_values: ["red", "blue", "#ffff00"],
+ invalid_values: ["ffff00"],
+ },
+ "column-span": {
+ domProp: "columnSpan",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["all"],
+ invalid_values: ["-1", "0", "auto", "2px"],
+ },
+ "column-width": {
+ domProp: "columnWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "15px",
+ "calc(15px)",
+ "calc(30px - 3em)",
+ "calc(-15px)",
+ "0px",
+ "calc(0px)",
+ ],
+ invalid_values: ["20", "-1px", "50%"],
+ },
+ d: {
+ domProp: "d",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["path('')", "path(' ')"].concat(pathValues.other_values),
+ invalid_values: pathValues.invalid_values,
+ },
+ "-moz-float-edge": {
+ domProp: "MozFloatEdge",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["content-box"],
+ other_values: ["margin-box"],
+ invalid_values: ["content", "padding", "border", "margin"],
+ },
+ "-moz-force-broken-image-icon": {
+ domProp: "MozForceBrokenImageIcon",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["1"],
+ invalid_values: [],
+ },
+ "margin-inline": {
+ domProp: "marginInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["margin-inline-start", "margin-inline-end"],
+ initial_values: ["0", "0px 0em"],
+ other_values: [
+ "1px",
+ "3em 1%",
+ "5%",
+ "calc(2px) 1%",
+ "calc(-2px) 1%",
+ "calc(50%) 1%",
+ "calc(3*25px) calc(2px)",
+ "calc(25px*3) 1em",
+ "calc(3*25px + 50%) calc(3*25px - 50%)",
+ ],
+ invalid_values: [
+ "5",
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "margin-inline-end": {
+ domProp: "marginInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* no subproperties */
+ /* auto may or may not be initial */
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "0em",
+ "0ex",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ ],
+ other_values: [
+ "1px",
+ "3em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "5",
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "margin-inline-start": {
+ domProp: "marginInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* no subproperties */
+ /* auto may or may not be initial */
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "0em",
+ "0ex",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ ],
+ other_values: [
+ "1px",
+ "3em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "5",
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ mask: {
+ domProp: "mask",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ /* FIXME: All mask-border-* should be added when we implement them. */
+ subproperties: [
+ "mask-clip",
+ "mask-image",
+ "mask-mode",
+ "mask-origin",
+ "mask-position-x",
+ "mask-position-y",
+ "mask-repeat",
+ "mask-size",
+ "mask-composite",
+ ],
+ initial_values: [
+ "match-source",
+ "none",
+ "repeat",
+ "add",
+ "0% 0%",
+ "top left",
+ "0% 0% / auto",
+ "top left / auto",
+ "left top / auto",
+ "0% 0% / auto auto",
+ "top left none",
+ "left top none",
+ "none left top",
+ "none top left",
+ "none 0% 0%",
+ "top left / auto none",
+ "left top / auto none",
+ "top left / auto auto none",
+ "match-source none repeat add top left",
+ "top left repeat none add",
+ "none repeat add top left / auto",
+ "top left / auto repeat none add match-source",
+ "none repeat add 0% 0% / auto auto match-source",
+ "border-box",
+ "border-box border-box",
+ ],
+ other_values: [
+ "none alpha repeat add left top",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "no-repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') alpha left top add",
+ "repeat-x",
+ "repeat-y",
+ "no-repeat",
+ "none repeat-y alpha add 0% 0%",
+ "subtract",
+ "0% top subtract alpha repeat none",
+ "top",
+ "left",
+ "50% 50%",
+ "center",
+ "top / 100px",
+ "left / contain",
+ "left / cover",
+ "10px / 10%",
+ "10em / calc(20px)",
+ "top left / 100px 100px",
+ "top left / 100px auto",
+ "top left / 100px 10%",
+ "top left / 100px calc(20px)",
+ "bottom right add none alpha repeat",
+ "50% alpha",
+ "alpha 50%",
+ "50%",
+ "url(#mymask)",
+ "radial-gradient(at 10% bottom, #ffffff, black) add no-repeat",
+ "repeating-radial-gradient(at 10% bottom, #ffffff, black) no-repeat",
+ "-moz-element(#test) alpha",
+ /* multiple mask-image */
+ "url(404.png), url(404.png)",
+ "repeat-x, subtract, none",
+ "0% top url(404.png), url(404.png) 50% top",
+ "subtract repeat-y top left url(404.png), repeat-x alpha",
+ "top left / contain, bottom right / cover",
+ /* test cases with clip+origin in the shorthand */
+ "url(404.png) alpha padding-box",
+ "url(404.png) border-box alpha",
+ "content-box url(404.png)",
+ "url(404.png) alpha padding-box padding-box",
+ "url(404.png) alpha padding-box border-box",
+ "content-box border-box url(404.png)",
+ "alpha padding-box url(404.png) border-box",
+ "alpha padding-box url(404.png) padding-box",
+ ],
+ invalid_values: [
+ /* mixes with keywords have to be in correct order */
+ "50% left",
+ "top 50%",
+ /* no quirks mode colors */
+ "radial-gradient(at 10% bottom, ffffff, black) add no-repeat",
+ /* no quirks mode lengths */
+ "linear-gradient(red -99, yellow, green, blue 120%)",
+ /* bug 258080: don't accept background-position separated */
+ "left url(404.png) top",
+ "top url(404.png) left",
+ "-moz-element(#a rubbish)",
+ "left top / match-source",
+ ],
+ },
+ "mask-clip": {
+ domProp: "maskClip",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["border-box"],
+ other_values: [
+ "content-box",
+ "fill-box",
+ "stroke-box",
+ "view-box",
+ "no-clip",
+ "padding-box",
+ "border-box, padding-box",
+ "padding-box, padding-box, padding-box",
+ "border-box, border-box",
+ ],
+ invalid_values: ["content-box content-box", "margin-box"],
+ },
+ "mask-composite": {
+ domProp: "maskComposite",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["add"],
+ other_values: [
+ "subtract",
+ "intersect",
+ "exclude",
+ "add, add",
+ "subtract, intersect",
+ "subtract, subtract, add",
+ ],
+ invalid_values: ["add subtract", "intersect exclude"],
+ },
+ "mask-image": {
+ domProp: "maskImage",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')",
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ "none, none",
+ "none, none, none, none, none",
+ "url(#mymask)",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none",
+ "none, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ ].concat(validNonUrlImageValues),
+ invalid_values: [].concat(invalidNonUrlImageValues),
+ unbalanced_values: [].concat(unbalancedGradientAndElementValues),
+ },
+ "mask-mode": {
+ domProp: "maskMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["match-source"],
+ other_values: [
+ "alpha",
+ "luminance",
+ "match-source, match-source",
+ "match-source, alpha",
+ "alpha, luminance, match-source",
+ ],
+ invalid_values: ["match-source match-source", "alpha match-source"],
+ },
+ "mask-origin": {
+ domProp: "maskOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["border-box"],
+ other_values: [
+ "padding-box",
+ "content-box",
+ "fill-box",
+ "stroke-box",
+ "view-box",
+ "border-box, padding-box",
+ "padding-box, padding-box, padding-box",
+ "border-box, border-box",
+ ],
+ invalid_values: ["padding-box padding-box", "no-clip", "margin-box"],
+ },
+ "mask-position": {
+ domProp: "maskPosition",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ initial_values: [
+ "top 0% left 0%",
+ "top left",
+ "left top",
+ "0% 0%",
+ "0% top",
+ "left 0%",
+ ],
+ other_values: [
+ "top",
+ "left",
+ "right",
+ "bottom",
+ "center",
+ "center bottom",
+ "bottom center",
+ "center right",
+ "right center",
+ "center top",
+ "top center",
+ "center left",
+ "left center",
+ "right bottom",
+ "bottom right",
+ "50%",
+ "top left, top left",
+ "top left, top right",
+ "top right, top left",
+ "left top, 0% 0%",
+ "10% 20%, 30%, 40%",
+ "top left, bottom right",
+ "right bottom, left top",
+ "0%",
+ "0px",
+ "30px",
+ "0%, 10%, 20%, 30%",
+ "top, top, top, top, top",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ "0px 0px",
+ "right 20px top 60px",
+ "right 20px bottom 60px",
+ "left 20px top 60px",
+ "left 20px bottom 60px",
+ "right -50px top -50px",
+ "left -50px bottom -50px",
+ "right 20px top -50px",
+ "right -20px top 50px",
+ "right 3em bottom 10px",
+ "bottom 3em right 10px",
+ "top 3em right 10px",
+ "left 15px",
+ "10px top",
+ "left 20%",
+ "right 20%",
+ ],
+ subproperties: ["mask-position-x", "mask-position-y"],
+ invalid_values: [
+ "center 10px center 4px",
+ "center 10px center",
+ "top 20%",
+ "bottom 20%",
+ "50% left",
+ "top 50%",
+ "50% bottom 10%",
+ "right 10% 50%",
+ "left right",
+ "top bottom",
+ "left 10% right",
+ "top 20px bottom 20px",
+ "left left",
+ "0px calc(0px + rubbish)",
+ "left top 15px",
+ "left 10px top",
+ ],
+ },
+ "mask-position-x": {
+ domProp: "maskPositionX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["left", "0%"],
+ other_values: [
+ "right",
+ "center",
+ "50%",
+ "center, center",
+ "center, right",
+ "right, center",
+ "center, 50%",
+ "10%, 20%, 40%",
+ "1px",
+ "30px",
+ "50%, 10%, 20%, 30%",
+ "center, center, center, center, center",
+ "calc(20px)",
+ "calc(20px + 1em)",
+ "calc(20px / 2)",
+ "calc(20px + 50%)",
+ "calc(50% - 10px)",
+ "calc(-20px)",
+ "calc(-50%)",
+ "calc(-20%)",
+ "right 20px",
+ "left 20px",
+ "right -50px",
+ "left -50px",
+ "right 20px",
+ "right 3em",
+ ],
+ invalid_values: [
+ "center 10px",
+ "right 10% 50%",
+ "left right",
+ "left left",
+ "bottom 20px",
+ "top 10%",
+ "bottom 3em",
+ "top",
+ "bottom",
+ "top, top",
+ "top, bottom",
+ "bottom, top",
+ "top, 0%",
+ "top, top, top, top, top",
+ "calc(0px + rubbish)",
+ "center 0%",
+ ],
+ },
+ "mask-position-y": {
+ domProp: "maskPositionY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["top", "0%"],
+ other_values: [
+ "bottom",
+ "center",
+ "50%",
+ "center, center",
+ "center, bottom",
+ "bottom, center",
+ "center, 0%",
+ "10%, 20%, 40%",
+ "1px",
+ "30px",
+ "50%, 10%, 20%, 30%",
+ "center, center, center, center, center",
+ "calc(20px)",
+ "calc(20px + 1em)",
+ "calc(20px / 2)",
+ "calc(20px + 50%)",
+ "calc(50% - 10px)",
+ "calc(-20px)",
+ "calc(-50%)",
+ "calc(-20%)",
+ "bottom 20px",
+ "top 20px",
+ "bottom -50px",
+ "top -50px",
+ "bottom 20px",
+ "bottom 3em",
+ ],
+ invalid_values: [
+ "center 10px",
+ "bottom 10% 50%",
+ "top bottom",
+ "top top",
+ "right 20px",
+ "left 10%",
+ "right 3em",
+ "left",
+ "right",
+ "left, left",
+ "left, right",
+ "right, left",
+ "left, 0%",
+ "left, left, left, left, left",
+ "calc(0px + rubbish)",
+ "center 0%",
+ ],
+ },
+ "mask-repeat": {
+ domProp: "maskRepeat",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["repeat", "repeat repeat"],
+ other_values: [
+ "repeat-x",
+ "repeat-y",
+ "no-repeat",
+ "repeat-x, repeat-x",
+ "repeat, no-repeat",
+ "repeat-y, no-repeat, repeat-y",
+ "repeat, repeat, repeat",
+ "repeat no-repeat",
+ "no-repeat repeat",
+ "no-repeat no-repeat",
+ "repeat no-repeat",
+ "no-repeat no-repeat, no-repeat no-repeat",
+ ],
+ invalid_values: [
+ "repeat repeat repeat",
+ "repeat-x repeat-y",
+ "repeat repeat-x",
+ "repeat repeat-y",
+ "repeat-x repeat",
+ "repeat-y repeat",
+ ],
+ },
+ "mask-size": {
+ domProp: "maskSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "contain",
+ "cover",
+ "100px auto",
+ "auto 100px",
+ "100% auto",
+ "auto 100%",
+ "25% 50px",
+ "3em 40%",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ ],
+ invalid_values: [
+ "contain contain",
+ "cover cover",
+ "cover auto",
+ "auto cover",
+ "contain cover",
+ "cover contain",
+ "-5px 3px",
+ "3px -5px",
+ "auto -5px",
+ "-5px auto",
+ "5 3",
+ "10px calc(10px + rubbish)",
+ ],
+ },
+ "mask-type": {
+ domProp: "maskType",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["luminance"],
+ other_values: ["alpha"],
+ invalid_values: [],
+ },
+ "padding-inline-end": {
+ domProp: "paddingInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ logical: true,
+ /* no subproperties */
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "0em",
+ "0ex",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "3em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: ["5"],
+ },
+ "padding-inline-start": {
+ domProp: "paddingInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ logical: true,
+ /* no subproperties */
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "0em",
+ "0ex",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "3em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: ["5"],
+ },
+ resize: {
+ domProp: "resize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: { display: "block", overflow: "auto" },
+ initial_values: ["none"],
+ other_values: ["both", "horizontal", "vertical", "inline", "block"],
+ invalid_values: [],
+ },
+ "tab-size": {
+ domProp: "tabSize",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["8"],
+ other_values: [
+ "0",
+ "2.5",
+ "3",
+ "99",
+ "12000",
+ "0px",
+ "1em",
+ "calc(1px + 1em)",
+ "calc(1px - 2px)",
+ "calc(1 + 1)",
+ "calc(-2.5)",
+ ],
+ invalid_values: [
+ "9%",
+ "calc(9% + 1px)",
+ "calc(1 + 1em)",
+ "-1",
+ "-808",
+ "auto",
+ ],
+ },
+ "-moz-text-size-adjust": {
+ domProp: "MozTextSizeAdjust",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["-5%", "0", "100", "0%", "50%", "100%", "220.3%"],
+ },
+ transform: {
+ domProp: "transform",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { width: "300px", height: "50px" },
+ initial_values: ["none"],
+ other_values: [
+ "translatex(1px)",
+ "translatex(4em)",
+ "translatex(-4px)",
+ "translatex(3px)",
+ "translatex(0px) translatex(1px) translatex(2px) translatex(3px) translatex(4px)",
+ "translatey(4em)",
+ "translate(3px)",
+ "translate(10px, -3px)",
+ "rotate(45deg)",
+ "rotate(45grad)",
+ "rotate(45rad)",
+ "rotate(0.25turn)",
+ "rotate(0)",
+ "scalex(10)",
+ "scalex(10%)",
+ "scalex(-10)",
+ "scalex(-10%)",
+ "scaley(10)",
+ "scaley(10%)",
+ "scaley(-10)",
+ "scaley(-10%)",
+ "scale(10)",
+ "scale(10%)",
+ "scale(10, 20)",
+ "scale(10%, 20%)",
+ "scale(-10)",
+ "scale(-10%)",
+ "scale(-10, 20)",
+ "scale(10%, -20%)",
+ "scale(10, 20%)",
+ "scale(-10, 20%)",
+ "skewx(30deg)",
+ "skewx(0)",
+ "skewy(0)",
+ "skewx(30grad)",
+ "skewx(30rad)",
+ "skewx(0.08turn)",
+ "skewy(30deg)",
+ "skewy(30grad)",
+ "skewy(30rad)",
+ "skewy(0.08turn)",
+ "rotate(45deg) scale(2, 1)",
+ "skewx(45deg) skewx(-50grad)",
+ "translate(0, 0) scale(1, 1) skewx(0) skewy(0) matrix(1, 0, 0, 1, 0, 0)",
+ "translatex(50%)",
+ "translatey(50%)",
+ "translate(50%)",
+ "translate(3%, 5px)",
+ "translate(5px, 3%)",
+ "matrix(1, 2, 3, 4, 5, 6)",
+ /* valid calc() values */
+ "translatex(calc(5px + 10%))",
+ "translatey(calc(0.25 * 5px + 10% / 3))",
+ "translate(calc(5px - 10% * 3))",
+ "translate(calc(5px - 3 * 10%), 50px)",
+ "translate(-50px, calc(5px - 10% * 3))",
+ "translate(10px, calc(min(5px,10%)))",
+ "translate(calc(max(5px,10%)), 10%)",
+ "translate(max(5px,10%), 10%)",
+ "translatez(1px)",
+ "translatez(4em)",
+ "translatez(-4px)",
+ "translatez(0px)",
+ "translatez(2px) translatez(5px)",
+ "translate3d(3px, 4px, 5px)",
+ "translate3d(2em, 3px, 1em)",
+ "translatex(2px) translate3d(4px, 5px, 6px) translatey(1px)",
+ "scale3d(4, 4, 4)",
+ "scale3d(4%, 4%, 4%)",
+ "scale3d(-2, 3, -7)",
+ "scale3d(-2%, 3%, -7%)",
+ "scalez(4)",
+ "scalez(4%)",
+ "scalez(-6)",
+ "scalez(-6%)",
+ "rotate3d(2, 3, 4, 45deg)",
+ "rotate3d(-3, 7, 0, 12rad)",
+ "rotatex(15deg)",
+ "rotatey(-12grad)",
+ "rotatez(72rad)",
+ "rotatex(0.125turn)",
+ "rotate3d(0, 0, 0, 0rad)",
+ "perspective(0px)",
+ "perspective(1000px)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)",
+ ],
+ invalid_values: [
+ "1px",
+ "#0000ff",
+ "red",
+ "auto",
+ "translatex(1)",
+ "translatey(1)",
+ "translate(2)",
+ "translate(-3, -4)",
+ "translatex(1px 1px)",
+ "translatex(translatex(1px))",
+ "translatex(#0000ff)",
+ "translatex(red)",
+ "translatey()",
+ "matrix(1px, 2px, 3px, 4px, 5px, 6px)",
+ "skewx(red)",
+ "matrix(1%, 0, 0, 0, 0px, 0px)",
+ "matrix(0, 1%, 2, 3, 4px,5px)",
+ "matrix(0, 1, 2%, 3, 4px, 5px)",
+ "matrix(0, 1, 2, 3%, 4%, 5%)",
+ "matrix(1, 2, 3, 4, 5px, 6%)",
+ "matrix(1, 2, 3, 4, 5%, 6px)",
+ "matrix(1, 2, 3, 4, 5%, 6%)",
+ "matrix(1, 2, 3, 4, 5px, 6em)",
+ /* invalid calc() values */
+ "translatey(-moz-min(5px,10%))",
+ "translatex(-moz-max(5px,10%))",
+ "matrix(1, 0, 0, 1, max(5px * 3), calc(10% - 3px))",
+ "perspective(-10px)",
+ "matrix3d(dinosaur)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15%, 16)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16px)",
+ "rotatey(words)",
+ "rotatex(7)",
+ "translate3d(3px, 4px, 1px, 7px)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13px, 14em, 15px, 16)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20%, 10%, 15, 16)",
+ ],
+ },
+ "transform-box": {
+ domProp: "transformBox",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["view-box"],
+ other_values: ["fill-box", "border-box"],
+ invalid_values: ["padding-box", "margin-box"],
+ },
+ "transform-origin": {
+ domProp: "transformOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* no subproperties */
+ prerequisites: { width: "10px", height: "10px", display: "block" },
+ initial_values: ["50% 50%", "center", "center center"],
+ other_values: [
+ "25% 25%",
+ "6px 5px",
+ "20% 3em",
+ "0 0",
+ "0in 1in",
+ "top",
+ "bottom",
+ "top left",
+ "top right",
+ "top center",
+ "center left",
+ "center right",
+ "bottom left",
+ "bottom right",
+ "bottom center",
+ "20% center",
+ "6px center",
+ "13in bottom",
+ "left 50px",
+ "right 13%",
+ "center 40px",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ "6px 5px 5px",
+ "top center 10px",
+ ],
+ invalid_values: [
+ "red",
+ "auto",
+ "none",
+ "0.5 0.5",
+ "40px #0000ff",
+ "border",
+ "center red",
+ "right diagonal",
+ "#00ffff bottom",
+ "0px calc(0px + rubbish)",
+ "0px 0px calc(0px + rubbish)",
+ ],
+ },
+ "perspective-origin": {
+ domProp: "perspectiveOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* no subproperties */
+ prerequisites: { width: "10px", height: "10px", display: "block" },
+ initial_values: ["50% 50%", "center", "center center"],
+ other_values: [
+ "25% 25%",
+ "6px 5px",
+ "20% 3em",
+ "0 0",
+ "0in 1in",
+ "top",
+ "bottom",
+ "top left",
+ "top right",
+ "top center",
+ "center left",
+ "center right",
+ "bottom left",
+ "bottom right",
+ "bottom center",
+ "20% center",
+ "6px center",
+ "13in bottom",
+ "left 50px",
+ "right 13%",
+ "center 40px",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ ],
+ invalid_values: [
+ "red",
+ "auto",
+ "none",
+ "0.5 0.5",
+ "40px #0000ff",
+ "border",
+ "center red",
+ "right diagonal",
+ "#00ffff bottom",
+ ],
+ },
+ perspective: {
+ domProp: "perspective",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1000px", "500.2px", "0", "0px"],
+ invalid_values: ["pants", "200", "-100px", "-27.2em"],
+ },
+ "backface-visibility": {
+ domProp: "backfaceVisibility",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["visible"],
+ other_values: ["hidden"],
+ invalid_values: ["collapse"],
+ },
+ "transform-style": {
+ domProp: "transformStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["flat"],
+ other_values: ["preserve-3d"],
+ invalid_values: [],
+ },
+ "-moz-user-input": {
+ domProp: "MozUserInput",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: [],
+ },
+ "-moz-user-modify": {
+ domProp: "MozUserModify",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["read-only"],
+ other_values: ["read-write", "write-only"],
+ invalid_values: [],
+ },
+ "-moz-user-select": {
+ domProp: "MozUserSelect",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "user-select",
+ subproperties: ["user-select"],
+ },
+ "user-select": {
+ domProp: "userSelect",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none", "text", "all", "-moz-none"],
+ invalid_values: [],
+ },
+ background: {
+ domProp: "background",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "background-attachment",
+ "background-color",
+ "background-image",
+ "background-position-x",
+ "background-position-y",
+ "background-repeat",
+ "background-clip",
+ "background-origin",
+ "background-size",
+ ],
+ initial_values: [
+ "transparent",
+ "none",
+ "repeat",
+ "scroll",
+ "0% 0%",
+ "top left",
+ "left top",
+ "0% 0% / auto",
+ "top left / auto",
+ "left top / auto",
+ "0% 0% / auto auto",
+ "transparent none",
+ "top left none",
+ "left top none",
+ "none left top",
+ "none top left",
+ "none 0% 0%",
+ "left top / auto none",
+ "left top / auto auto none",
+ "transparent none repeat scroll top left",
+ "left top repeat none scroll transparent",
+ "transparent none repeat scroll top left / auto",
+ "left top / auto repeat none scroll transparent",
+ "none repeat scroll 0% 0% / auto auto transparent",
+ "padding-box border-box",
+ ],
+ other_values: [
+ /* without multiple backgrounds */
+ "green",
+ "none green repeat scroll left top",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') transparent left top scroll",
+ "repeat-x",
+ "repeat-y",
+ "no-repeat",
+ "none repeat-y transparent scroll 0% 0%",
+ "fixed",
+ "0% top transparent fixed repeat none",
+ "top",
+ "left",
+ "50% 50%",
+ "center",
+ "top / 100px",
+ "left / contain",
+ "left / cover",
+ "10px / 10%",
+ "10em / calc(20px)",
+ "top left / 100px 100px",
+ "top left / 100px auto",
+ "top left / 100px 10%",
+ "top left / 100px calc(20px)",
+ "bottom right 8px scroll none transparent repeat",
+ "50% transparent",
+ "transparent 50%",
+ "50%",
+ "radial-gradient(at 10% bottom, #ffffff, black) scroll no-repeat",
+ "repeating-radial-gradient(at 10% bottom, #ffffff, black) scroll no-repeat",
+ "-moz-element(#test) lime",
+ /* multiple backgrounds */
+ "url(404.png), url(404.png)",
+ "url(404.png), url(404.png) transparent",
+ "url(404.png), url(404.png) red",
+ "repeat-x, fixed, none",
+ "0% top url(404.png), url(404.png) 0% top",
+ "fixed repeat-y top left url(404.png), repeat-x green",
+ "top left / contain, bottom right / cover",
+ /* test cases with clip+origin in the shorthand */
+ "url(404.png) green padding-box",
+ "url(404.png) border-box transparent",
+ "content-box url(404.png) blue",
+ "url(404.png) green padding-box padding-box",
+ "url(404.png) green padding-box border-box",
+ "content-box border-box url(404.png) blue",
+ "url(404.png) green padding-box text",
+ "content-box text url(404.png) blue",
+ /* clip and origin separated in the shorthand */
+ "url(404.png) padding-box green border-box",
+ "url(404.png) padding-box green padding-box",
+ "transparent padding-box url(404.png) border-box",
+ "transparent padding-box url(404.png) padding-box",
+ /* text */
+ "text",
+ "text border-box",
+ ],
+ invalid_values: [
+ /* mixes with keywords have to be in correct order */
+ "50% left",
+ "top 50%",
+ /* no quirks mode colors */
+ "radial-gradient(at 10% bottom, ffffff, black) scroll no-repeat",
+ /* no quirks mode lengths */
+ "linear-gradient(red -99, yellow, green, blue 120%)",
+ /* bug 258080: don't accept background-position separated */
+ "left url(404.png) top",
+ "top url(404.png) left",
+ /* not allowed to have color in non-bottom layer */
+ "url(404.png) transparent, url(404.png)",
+ "url(404.png) red, url(404.png)",
+ "url(404.png) transparent, url(404.png) transparent",
+ "url(404.png) transparent red, url(404.png) transparent red",
+ "url(404.png) red, url(404.png) red",
+ "url(404.png) rgba(0, 0, 0, 0), url(404.png)",
+ "url(404.png) rgb(255, 0, 0), url(404.png)",
+ "url(404.png) rgba(0, 0, 0, 0), url(404.png) rgba(0, 0, 0, 0)",
+ "url(404.png) rgba(0, 0, 0, 0) rgb(255, 0, 0), url(404.png) rgba(0, 0, 0, 0) rgb(255, 0, 0)",
+ "url(404.png) rgb(255, 0, 0), url(404.png) rgb(255, 0, 0)",
+ /* error inside functions */
+ "-moz-element(#a rubbish) black",
+ "content-box text text",
+ "padding-box text url(404.png) text",
+ ],
+ },
+ "background-attachment": {
+ domProp: "backgroundAttachment",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["scroll"],
+ other_values: [
+ "fixed",
+ "local",
+ "scroll,scroll",
+ "fixed, scroll",
+ "scroll, fixed, local, scroll",
+ "fixed, fixed",
+ ],
+ invalid_values: [],
+ },
+ "background-blend-mode": {
+ domProp: "backgroundBlendMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "multiply",
+ "screen",
+ "overlay",
+ "darken",
+ "lighten",
+ "color-dodge",
+ "color-burn",
+ "hard-light",
+ "soft-light",
+ "difference",
+ "exclusion",
+ "hue",
+ "saturation",
+ "color",
+ "luminosity",
+ ],
+ invalid_values: ["none", "10px", "multiply multiply", "plus-lighter"],
+ },
+ "background-clip": {
+ /*
+ * When we rename this to 'background-clip', we also
+ * need to rename the values to match the spec.
+ */
+ domProp: "backgroundClip",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["border-box"],
+ other_values: [
+ "content-box",
+ "padding-box",
+ "border-box, padding-box",
+ "padding-box, padding-box, padding-box",
+ "border-box, border-box",
+ "text",
+ "content-box, text",
+ "text, border-box",
+ "text, text",
+ ],
+ invalid_values: [
+ "margin-box",
+ "border-box border-box",
+ "fill-box",
+ "stroke-box",
+ "view-box",
+ "no-clip",
+ ],
+ },
+ "background-color": {
+ domProp: "backgroundColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["transparent", "rgba(0, 0, 0, 0)"],
+ other_values: [
+ "green",
+ "rgb(255, 0, 128)",
+ "#fc2",
+ "#96ed2a",
+ "black",
+ "rgba(255,255,0,3)",
+ "hsl(240, 50%, 50%)",
+ "rgb(50%, 50%, 50%)",
+ "-moz-default-background-color",
+ "rgb(100, 100.0, 100)",
+ "rgba(255, 127, 15, 0)",
+ "hsla(240, 97%, 50%, 0.0)",
+ "rgba(255,255,255,-3.7)",
+ ],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "rgb(100, 100%, 100)",
+ ],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "background-image": {
+ domProp: "backgroundImage",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["none"],
+ other_values: [
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')",
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ "none, none",
+ "none, none, none, none, none",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none",
+ "none, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ ].concat(validNonUrlImageValues),
+ invalid_values: [].concat(invalidNonUrlImageValues),
+ unbalanced_values: [].concat(unbalancedGradientAndElementValues),
+ },
+ "background-origin": {
+ domProp: "backgroundOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["padding-box"],
+ other_values: [
+ "border-box",
+ "content-box",
+ "border-box, padding-box",
+ "padding-box, padding-box, padding-box",
+ "border-box, border-box",
+ ],
+ invalid_values: [
+ "margin-box",
+ "padding-box padding-box",
+ "fill-box",
+ "stroke-box",
+ "view-box",
+ "no-clip",
+ ],
+ },
+ "background-position": {
+ domProp: "backgroundPosition",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [
+ "top 0% left 0%",
+ "top 0% left",
+ "top left",
+ "left top",
+ "0% 0%",
+ "0% top",
+ "left 0%",
+ ],
+ other_values: [
+ "top",
+ "left",
+ "right",
+ "bottom",
+ "center",
+ "center bottom",
+ "bottom center",
+ "center right",
+ "right center",
+ "center top",
+ "top center",
+ "center left",
+ "left center",
+ "right bottom",
+ "bottom right",
+ "50%",
+ "top left, top left",
+ "top left, top right",
+ "top right, top left",
+ "left top, 0% 0%",
+ "10% 20%, 30%, 40%",
+ "top left, bottom right",
+ "right bottom, left top",
+ "0%",
+ "0px",
+ "30px",
+ "0%, 10%, 20%, 30%",
+ "top, top, top, top, top",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ "0px 0px",
+ "right 20px top 60px",
+ "right 20px bottom 60px",
+ "left 20px top 60px",
+ "left 20px bottom 60px",
+ "right -50px top -50px",
+ "left -50px bottom -50px",
+ "right 20px top -50px",
+ "right -20px top 50px",
+ "right 3em bottom 10px",
+ "bottom 3em right 10px",
+ "top 3em right 10px",
+ "left 15px",
+ "10px top",
+ "left top 15px",
+ "left 10px top",
+ "left 20%",
+ "right 20%",
+ ],
+ subproperties: ["background-position-x", "background-position-y"],
+ invalid_values: [
+ "center 10px center 4px",
+ "center 10px center",
+ "top 20%",
+ "bottom 20%",
+ "50% left",
+ "top 50%",
+ "50% bottom 10%",
+ "right 10% 50%",
+ "left right",
+ "top bottom",
+ "left 10% right",
+ "top 20px bottom 20px",
+ "left left",
+ "0px calc(0px + rubbish)",
+ ],
+ quirks_values: {
+ "20 20": "20px 20px",
+ "10 5px": "10px 5px",
+ "7px 2": "7px 2px",
+ },
+ },
+ "background-position-x": {
+ domProp: "backgroundPositionX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["left 0%", "left", "0%"],
+ other_values: [
+ "right",
+ "center",
+ "50%",
+ "left, left",
+ "left, right",
+ "right, left",
+ "left, 0%",
+ "10%, 20%, 40%",
+ "0px",
+ "30px",
+ "0%, 10%, 20%, 30%",
+ "left, left, left, left, left",
+ "calc(20px)",
+ "calc(20px + 1em)",
+ "calc(20px / 2)",
+ "calc(20px + 50%)",
+ "calc(50% - 10px)",
+ "calc(-20px)",
+ "calc(-50%)",
+ "calc(-20%)",
+ "right 20px",
+ "left 20px",
+ "right -50px",
+ "left -50px",
+ "right 20px",
+ "right 3em",
+ ],
+ invalid_values: [
+ "center 10px",
+ "right 10% 50%",
+ "left right",
+ "left left",
+ "bottom 20px",
+ "top 10%",
+ "bottom 3em",
+ "top",
+ "bottom",
+ "top, top",
+ "top, bottom",
+ "bottom, top",
+ "top, 0%",
+ "top, top, top, top, top",
+ "calc(0px + rubbish)",
+ ],
+ },
+ "background-position-y": {
+ domProp: "backgroundPositionY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["top 0%", "top", "0%"],
+ other_values: [
+ "bottom",
+ "center",
+ "50%",
+ "top, top",
+ "top, bottom",
+ "bottom, top",
+ "top, 0%",
+ "10%, 20%, 40%",
+ "0px",
+ "30px",
+ "0%, 10%, 20%, 30%",
+ "top, top, top, top, top",
+ "calc(20px)",
+ "calc(20px + 1em)",
+ "calc(20px / 2)",
+ "calc(20px + 50%)",
+ "calc(50% - 10px)",
+ "calc(-20px)",
+ "calc(-50%)",
+ "calc(-20%)",
+ "bottom 20px",
+ "top 20px",
+ "bottom -50px",
+ "top -50px",
+ "bottom 20px",
+ "bottom 3em",
+ ],
+ invalid_values: [
+ "center 10px",
+ "bottom 10% 50%",
+ "top bottom",
+ "top top",
+ "right 20px",
+ "left 10%",
+ "right 3em",
+ "left",
+ "right",
+ "left, left",
+ "left, right",
+ "right, left",
+ "left, 0%",
+ "left, left, left, left, left",
+ "calc(0px + rubbish)",
+ ],
+ },
+ "background-repeat": {
+ domProp: "backgroundRepeat",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["repeat", "repeat repeat"],
+ other_values: [
+ "repeat-x",
+ "repeat-y",
+ "no-repeat",
+ "repeat-x, repeat-x",
+ "repeat, no-repeat",
+ "repeat-y, no-repeat, repeat-y",
+ "repeat, repeat, repeat",
+ "repeat no-repeat",
+ "no-repeat repeat",
+ "no-repeat no-repeat",
+ "repeat repeat, repeat repeat",
+ "round, repeat",
+ "round repeat, repeat-x",
+ "round no-repeat, repeat-y",
+ "round round",
+ "space, repeat",
+ "space repeat, repeat-x",
+ "space no-repeat, repeat-y",
+ "space space",
+ "space round",
+ ],
+ invalid_values: [
+ "repeat repeat repeat",
+ "repeat-x repeat-y",
+ "repeat repeat-x",
+ "repeat repeat-y",
+ "repeat-x repeat",
+ "repeat-y repeat",
+ "round round round",
+ "repeat-x round",
+ "round repeat-x",
+ "repeat-y round",
+ "round repeat-y",
+ "space space space",
+ "repeat-x space",
+ "space repeat-x",
+ "repeat-y space",
+ "space repeat-y",
+ ],
+ },
+ "background-size": {
+ domProp: "backgroundSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "contain",
+ "cover",
+ "100px auto",
+ "auto 100px",
+ "100% auto",
+ "auto 100%",
+ "25% 50px",
+ "3em 40%",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ ],
+ invalid_values: [
+ "contain contain",
+ "cover cover",
+ "cover auto",
+ "auto cover",
+ "contain cover",
+ "cover contain",
+ "-5px 3px",
+ "3px -5px",
+ "auto -5px",
+ "-5px auto",
+ "5 3",
+ "10px calc(10px + rubbish)",
+ ],
+ },
+ border: {
+ domProp: "border",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-bottom-color",
+ "border-bottom-style",
+ "border-bottom-width",
+ "border-left-color",
+ "border-left-style",
+ "border-left-width",
+ "border-right-color",
+ "border-right-style",
+ "border-right-width",
+ "border-top-color",
+ "border-top-style",
+ "border-top-width",
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ "calc(4px - 1px) none",
+ ],
+ other_values: [
+ "solid",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "calc(2px) solid blue",
+ ],
+ invalid_values: ["5%", "medium solid ff00ff", "5 solid green"],
+ },
+ "border-bottom": {
+ domProp: "borderBottom",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-bottom-color",
+ "border-bottom-style",
+ "border-bottom-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-bottom-color": {
+ domProp: "borderBottomColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000"],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-bottom-style": {
+ domProp: "borderBottomStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-bottom-width": {
+ domProp: "borderBottomWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { "border-bottom-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%"],
+ quirks_values: { 5: "5px" },
+ },
+ "border-collapse": {
+ domProp: "borderCollapse",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["separate"],
+ other_values: ["collapse"],
+ invalid_values: [],
+ },
+ "border-color": {
+ domProp: "borderColor",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-top-color",
+ "border-right-color",
+ "border-bottom-color",
+ "border-left-color",
+ ],
+ initial_values: [
+ "currentColor",
+ "currentColor currentColor",
+ "currentColor currentColor currentColor",
+ "currentColor currentColor currentcolor CURRENTcolor",
+ ],
+ other_values: [
+ "green",
+ "currentColor green",
+ "currentColor currentColor green",
+ "currentColor currentColor currentColor green",
+ "rgba(255,128,0,0.5)",
+ "transparent",
+ ],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "red rgb(nonsense)",
+ "red 1px",
+ ],
+ unbalanced_values: ["red rgb("],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-left": {
+ domProp: "borderLeft",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-left-color",
+ "border-left-style",
+ "border-left-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: [
+ "5%",
+ "5",
+ "5 solid green",
+ "calc(5px + rubbish) green solid",
+ "5px rgb(0, rubbish, 0) solid",
+ ],
+ },
+ "border-left-color": {
+ domProp: "borderLeftColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000"],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-left-style": {
+ domProp: "borderLeftStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-left-width": {
+ domProp: "borderLeftWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { "border-left-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%"],
+ quirks_values: { 5: "5px" },
+ },
+ "border-right": {
+ domProp: "borderRight",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-right-color",
+ "border-right-style",
+ "border-right-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-right-color": {
+ domProp: "borderRightColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000"],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-right-style": {
+ domProp: "borderRightStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-right-width": {
+ domProp: "borderRightWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { "border-right-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%"],
+ quirks_values: { 5: "5px" },
+ },
+ "border-spacing": {
+ domProp: "borderSpacing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [
+ "0",
+ "0 0",
+ "0px",
+ "0 0px",
+ "calc(0px)",
+ "calc(0px) calc(0em)",
+ "calc(2em - 2em) calc(3px + 7px - 10px)",
+ "calc(-5px)",
+ "calc(-5px) calc(-5px)",
+ ],
+ other_values: [
+ "3px",
+ "4em 2px",
+ "4em 0",
+ "0px 2px",
+ "calc(7px)",
+ "0 calc(7px)",
+ "calc(7px) 0",
+ "calc(0px) calc(7px)",
+ "calc(7px) calc(0px)",
+ "7px calc(0px)",
+ "calc(0px) 7px",
+ "7px calc(0px)",
+ "3px calc(2em)",
+ ],
+ invalid_values: [
+ "0%",
+ "0 0%",
+ "-5px",
+ "-5px -5px",
+ "0 -5px",
+ "-5px 0",
+ "0 calc(0px + rubbish)",
+ ],
+ quirks_values: {
+ "2px 5": "2px 5px",
+ 7: "7px",
+ "3 4px": "3px 4px",
+ },
+ },
+ "border-style": {
+ domProp: "borderStyle",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-top-style",
+ "border-right-style",
+ "border-bottom-style",
+ "border-left-style",
+ ],
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: [
+ "none",
+ "none none",
+ "none none none",
+ "none none none none",
+ ],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ "none solid",
+ "none none solid",
+ "none none none solid",
+ "groove none none none",
+ "none ridge none none",
+ "none none double none",
+ "none none none dotted",
+ ],
+ invalid_values: [],
+ },
+ "border-top": {
+ domProp: "borderTop",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-top-color", "border-top-style", "border-top-width"],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-top-color": {
+ domProp: "borderTopColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000"],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-top-style": {
+ domProp: "borderTopStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-top-width": {
+ domProp: "borderTopWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { "border-top-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%"],
+ quirks_values: { 5: "5px" },
+ },
+ "border-width": {
+ domProp: "borderWidth",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-top-width",
+ "border-right-width",
+ "border-bottom-width",
+ "border-left-width",
+ ],
+ prerequisites: { "border-style": "solid" },
+ initial_values: [
+ "medium",
+ "3px",
+ "medium medium",
+ "3px medium medium",
+ "medium 3px medium medium",
+ "calc(3px) 3px calc(5px - 2px) calc(2px - -1px)",
+ ],
+ other_values: ["thin", "thick", "1px", "2em", "2px 0 0px 1em", "calc(2em)"],
+ invalid_values: ["5%", "1px calc(nonsense)", "1px red"],
+ unbalanced_values: ["1px calc("],
+ quirks_values: { 5: "5px" },
+ },
+ bottom: {
+ domProp: "bottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "box-shadow": {
+ domProp: "boxShadow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["none"],
+ prerequisites: { color: "blue" },
+ other_values: [
+ "2px 2px",
+ "2px 2px 1px",
+ "2px 2px 2px 2px",
+ "blue 3px 2px",
+ "2px 2px 1px 5px green",
+ "2px 2px red",
+ "green 2px 2px 1px",
+ "green 2px 2px, blue 1px 3px 4px",
+ "currentColor 3px 3px",
+ "blue 2px 2px, currentColor 1px 2px, 1px 2px 3px 2px orange",
+ "3px 0 0 0",
+ "inset 2px 2px 3px 4px black",
+ "2px -2px green inset, 4px 4px 3px blue, inset 2px 2px",
+ /* calc() values */
+ "2px 2px calc(-5px)" /* clamped */,
+ "calc(3em - 2px) 2px green",
+ "green calc(3em - 2px) 2px",
+ "2px calc(2px + 0.2em)",
+ "blue 2px calc(2px + 0.2em)",
+ "2px calc(2px + 0.2em) blue",
+ "calc(-2px) calc(-2px)",
+ "-2px -2px",
+ "calc(2px) calc(2px)",
+ "calc(2px) calc(2px) calc(2px)",
+ "calc(2px) calc(2px) calc(2px) calc(2px)",
+ ],
+ invalid_values: [
+ "3% 3%",
+ "1px 1px 1px 1px 1px",
+ "2px 2px, none",
+ "red 2px 2px blue",
+ "inherit, 2px 2px",
+ "2px 2px, inherit",
+ "2px 2px -5px",
+ "inset 4px 4px black inset",
+ "inset inherit",
+ "inset none",
+ "3 3",
+ "3px 3",
+ "3 3px",
+ "3px 3px 3",
+ "3px 3px 3px 3",
+ "3px calc(3px + rubbish)",
+ "3px 3px calc(3px + rubbish)",
+ "3px 3px 3px calc(3px + rubbish)",
+ "3px 3px 3px 3px rgb(0, rubbish, 0)",
+ "unset, 2px 2px",
+ "2px 2px, unset",
+ "inset unset",
+ ],
+ },
+ "caption-side": {
+ domProp: "captionSide",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["top"],
+ other_values: ["bottom"],
+ invalid_values: ["right", "left", "top-outside", "bottom-outside"],
+ },
+ "caret-color": {
+ domProp: "caretColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "black" },
+ // Though "auto" is an independent computed-value time keyword value,
+ // it is not distinguishable from currentcolor because getComputedStyle
+ // always returns used value for <color>.
+ initial_values: ["auto", "currentcolor", "black", "rgb(0,0,0)"],
+ other_values: ["green", "transparent", "rgba(128,128,128,.5)", "#123"],
+ invalid_values: ["#0", "#00", "#00000", "cc00ff"],
+ },
+ clear: {
+ domProp: "clear",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["left", "right", "both", "inline-start", "inline-end"],
+ invalid_values: [],
+ },
+ clip: {
+ domProp: "clip",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "rect(0 0 0 0)",
+ "rect(auto,auto,auto,auto)",
+ "rect(3px, 4px, 4em, 0)",
+ "rect(auto, 3em, 4pt, 2px)",
+ "rect(2px 3px 4px 5px)",
+ ],
+ invalid_values: ["rect(auto, 3em, 2%, 5px)"],
+ quirks_values: { "rect(1, 2, 3, 4)": "rect(1px, 2px, 3px, 4px)" },
+ },
+ color: {
+ domProp: "color",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ /* XXX should test currentColor, but may or may not be initial */
+ initial_values: [
+ "black",
+ "#000",
+ "#000f",
+ "#000000ff",
+ "-moz-default-color",
+ "rgb(0, 0, 0)",
+ "rgb(0%, 0%, 0%)",
+ /* css-color-4: */
+ /* rgb() and rgba() are aliases of each other. */
+ "rgb(0, 0, 0)",
+ "rgba(0, 0, 0)",
+ "rgb(0, 0, 0, 1)",
+ "rgba(0, 0, 0, 1)",
+ /* hsl() and hsla() are aliases of each other. */
+ "hsl(0, 0%, 0%)",
+ "hsla(0, 0%, 0%)",
+ "hsl(0, 0%, 0%, 1)",
+ "hsla(0, 0%, 0%, 1)",
+ /* rgb() and rgba() functions now accept <number> rather than <integer>. */
+ "rgb(0.0, 0.0, 0.0)",
+ "rgba(0.0, 0.0, 0.0)",
+ "rgb(0.0, 0.0, 0.0, 1)",
+ "rgba(0.0, 0.0, 0.0, 1)",
+ /* <alpha-value> now accepts <percentage> as well as <number> in rgba() and hsla(). */
+ "rgb(0.0, 0.0, 0.0, 100%)",
+ "hsl(0, 0%, 0%, 100%)",
+ /* rgb() and hsl() now support comma-less expression. */
+ "rgb(0 0 0)",
+ "rgb(0 0 0 / 1)",
+ "rgb(0/* comment */0/* comment */0)",
+ "rgb(0/* comment */0/* comment*/0/1.0)",
+ "hsl(0 0% 0%)",
+ "hsl(0 0% 0% / 1)",
+ "hsl(0/* comment */0%/* comment */0%)",
+ "hsl(0/* comment */0%/* comment */0%/1)",
+ /* Support <angle> for hsl() hue component. */
+ "hsl(0deg, 0%, 0%)",
+ "hsl(360deg, 0%, 0%)",
+ "hsl(0grad, 0%, 0%)",
+ "hsl(400grad, 0%, 0%)",
+ "hsl(0rad, 0%, 0%)",
+ "hsl(0turn, 0%, 0%)",
+ "hsl(1turn, 0%, 0%)",
+ /* CSS4 System Colors */
+ "canvastext",
+ /* Preserve previously available specially prefixed colors */
+ "-moz-default-color",
+ ],
+ other_values: [
+ "green",
+ "#f3c",
+ "#fed292",
+ "rgba(45,300,12,2)",
+ "transparent",
+ "LinkText",
+ "rgba(255,128,0,0.5)",
+ "#e0fc",
+ "#10fcee72",
+ /* css-color-4: */
+ "rgb(100, 100.0, 100)",
+ "rgb(300 300 300 / 200%)",
+ "rgb(300.0 300.0 300.0 / 2.0)",
+ "hsl(720, 200%, 200%, 2.0)",
+ "hsla(720 200% 200% / 200%)",
+ "hsl(480deg, 20%, 30%, 0.3)",
+ "hsl(55grad, 400%, 30%)",
+ "hsl(0.5grad 400% 500% / 9.0)",
+ "hsl(33rad 100% 90% / 4)",
+ "hsl(0.33turn, 40%, 40%, 10%)",
+ "hsl(63e292, 41%, 34%)",
+ /* CSS4 System Colors */
+ "canvas",
+ "linktext",
+ "visitedtext",
+ "activetext",
+ "buttonface",
+ "field",
+ "highlight",
+ "graytext",
+ /* Preserve previously available specially prefixed colors */
+ "-moz-activehyperlinktext",
+ "-moz-default-background-color",
+ "-moz-hyperlinktext",
+ "-moz-visitedhyperlinktext",
+ /* color-mix */
+ "color-mix(in srgb, red, blue)",
+ "color-mix(in srgb, highlight, rgba(0, 0, 0, .5))",
+ "color-mix(in srgb, color-mix(in srgb, red 10%, blue), green)",
+ "color-mix(in srgb, blue, red 80%)",
+ "color-mix(in srgb, rgba(0, 200, 32, .5) 90%, red 50%)",
+ "color-mix(in srgb, currentColor, red)",
+ ],
+ invalid_values: [
+ "#f",
+ "#ff",
+ "#fffff",
+ "#fffffff",
+ "#fffffffff",
+ "rgb(100%, 0, 100%)",
+ "rgba(100, 0, 100%, 30%)",
+ "hsl(0, 0, 0%)",
+ "hsla(0%, 0%, 0%, 0.1)",
+ /* trailing commas */
+ "rgb(0, 0, 0,)",
+ "rgba(0, 0, 0, 0,)",
+ "hsl(0, 0%, 0%,)",
+ "hsla(0, 0%, 0%, 1,)",
+ /* css-color-4: */
+ /* comma and comma-less expressions should not mix together. */
+ "rgb(0, 0, 0 / 1)",
+ "rgb(0 0 0, 1)",
+ "rgb(0, 0 0, 1)",
+ "rgb(0 0, 0 / 1)",
+ "hsl(0, 0%, 0% / 1)",
+ "hsl(0 0% 0%, 1)",
+ "hsl(0 0% 0%, 1)",
+ "hsl(0 0%, 0% / 1)",
+ /* trailing slash */
+ "rgb(0 0 0 /)",
+ "rgb(0, 0, 0 /)",
+ "hsl(0 0% 0% /)",
+ "hsl(0, 0%, 0% /)",
+ /* color-mix */
+ "color-mix(red, blue)",
+ "color-mix(red blue)",
+ "color-mix(in srgb, red blue)",
+ "color-mix(in srgb, red 10% blue)",
+ ],
+ quirks_values: {
+ "000000": "#000000",
+ "96ed2a": "#96ed2a",
+ fff: "#ffffff",
+ ffffff: "#ffffff",
+ },
+ },
+ content: {
+ domProp: "content",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ // XXX This really depends on pseudo-element-ness.
+ initial_values: ["normal", "none"],
+ other_values: [
+ '""',
+ "''",
+ '"hello"',
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')",
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ "counter(foo)",
+ "counter(bar, upper-roman)",
+ 'counters(foo, ".")',
+ "counters(bar, '-', lower-greek)",
+ "'-' counter(foo) '.'",
+ "attr(title)",
+ "open-quote",
+ "close-quote",
+ "no-open-quote",
+ "no-close-quote",
+ "close-quote attr(title) counters(foo, '.', upper-alpha)",
+ "attr(\\32)",
+ "attr(\\2)",
+ "attr(-\\2)",
+ "attr(-\\32)",
+ 'attr(title, "fallback")',
+ 'attr(\\32, "fallback")',
+ 'attr(-\\32, "fallback")',
+ "counter(\\2)",
+ "counters(\\32, '.')",
+ "counter(-\\32, upper-roman)",
+ "counters(-\\2, '-', lower-greek)",
+ "counter(\\()",
+ "counters(a\\+b, '.')",
+ "counter(\\}, upper-alpha)",
+ "-moz-alt-content",
+ "counter(foo, symbols('*'))",
+ "counter(foo, symbols(numeric '0' '1'))",
+ "counters(foo, '.', symbols('*'))",
+ "counters(foo, '.', symbols(numeric '0' '1'))",
+ "image-set(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==))",
+ ].concat(validNonUrlImageValues),
+ invalid_values: [
+ "counter(foo, none)",
+ "counters(bar, '.', none)",
+ "counters(foo)",
+ 'counter(foo, ".")',
+ 'attr("title")',
+ "attr('title')",
+ "attr(2)",
+ "attr(-2)",
+ "counter(2)",
+ "counters(-2, '.')",
+ "-moz-alt-content 'foo'",
+ "'foo' -moz-alt-content",
+ "counter(one, two, three) 'foo'",
+ ].concat(invalidNonUrlImageValues),
+ },
+ "counter-increment": {
+ domProp: "counterIncrement",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "foo 1",
+ "bar",
+ "foo 3 bar baz 2",
+ "\\32 1",
+ "-\\32 1",
+ "-c 1",
+ "\\32 1",
+ "-\\32 1",
+ "\\2 1",
+ "-\\2 1",
+ "-c 1",
+ "\\2 1",
+ "-\\2 1",
+ "-\\7f \\9e 1",
+ ],
+ invalid_values: ["none foo", "none foo 3", "foo none", "foo 3 none"],
+ unbalanced_values: ["foo 1 ("],
+ },
+ "counter-reset": {
+ domProp: "counterReset",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "foo 1",
+ "bar",
+ "foo 3 bar baz 2",
+ "\\32 1",
+ "-\\32 1",
+ "-c 1",
+ "\\32 1",
+ "-\\32 1",
+ "\\2 1",
+ "-\\2 1",
+ "-c 1",
+ "\\2 1",
+ "-\\2 1",
+ "-\\7f \\9e 1",
+ ],
+ invalid_values: ["none foo", "none foo 3", "foo none", "foo 3 none"],
+ },
+ "counter-set": {
+ domProp: "counterSet",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "foo 1",
+ "bar",
+ "foo 3 bar baz 2",
+ "\\32 1",
+ "-\\32 1",
+ "-c 1",
+ "\\32 1",
+ "-\\32 1",
+ "\\2 1",
+ "-\\2 1",
+ "-c 1",
+ "\\2 1",
+ "-\\2 1",
+ "-\\7f \\9e 1",
+ ],
+ invalid_values: ["none foo", "none foo 3", "foo none", "foo 3 none"],
+ },
+ cursor: {
+ domProp: "cursor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "crosshair",
+ "default",
+ "pointer",
+ "move",
+ "e-resize",
+ "ne-resize",
+ "nw-resize",
+ "n-resize",
+ "se-resize",
+ "sw-resize",
+ "s-resize",
+ "w-resize",
+ "text",
+ "wait",
+ "help",
+ "progress",
+ "copy",
+ "alias",
+ "context-menu",
+ "cell",
+ "not-allowed",
+ "col-resize",
+ "row-resize",
+ "no-drop",
+ "vertical-text",
+ "all-scroll",
+ "nesw-resize",
+ "nwse-resize",
+ "ns-resize",
+ "ew-resize",
+ "none",
+ "grab",
+ "grabbing",
+ "zoom-in",
+ "zoom-out",
+ "-moz-grab",
+ "-moz-grabbing",
+ "-moz-zoom-in",
+ "-moz-zoom-out",
+ "url(foo.png), move",
+ "url(foo.png) 5 7, move",
+ "url(foo.png) 12 3, url(bar.png), no-drop",
+ "url(foo.png), url(bar.png) 7 2, wait",
+ "url(foo.png) 3 2, url(bar.png) 7 9, pointer",
+ "url(foo.png) calc(1 + 2) calc(3), pointer",
+ "image-set(url(foo.png)), auto",
+ ],
+ invalid_values: [
+ "url(foo.png)",
+ "url(foo.png) 5 5",
+ "image-set(linear-gradient(red, blue)), auto",
+ // Gradients are supported per spec, but we don't have support for it yet
+ "linear-gradient(red, blue), auto",
+ ],
+ },
+ direction: {
+ domProp: "direction",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["ltr"],
+ other_values: ["rtl"],
+ invalid_values: [],
+ },
+ display: {
+ domProp: "display",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: ["inline"],
+ /* XXX none will really mess with other properties */
+ prerequisites: { float: "none", position: "static", contain: "none" },
+ other_values: [
+ "block",
+ "flex",
+ "inline-flex",
+ "list-item",
+ "inline list-item",
+ "inline flow-root list-item",
+ "inline-block",
+ "table",
+ "inline-table",
+ "table-row-group",
+ "table-header-group",
+ "table-footer-group",
+ "table-row",
+ "table-column-group",
+ "table-column",
+ "table-cell",
+ "table-caption",
+ "block ruby",
+ "ruby",
+ "ruby-base",
+ "ruby-base-container",
+ "ruby-text",
+ "ruby-text-container",
+ "contents",
+ "none",
+ ],
+ invalid_values: [],
+ },
+ "empty-cells": {
+ domProp: "emptyCells",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["show"],
+ other_values: ["hide"],
+ invalid_values: [],
+ },
+ float: {
+ domProp: "cssFloat",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["none"],
+ other_values: ["left", "right", "inline-start", "inline-end"],
+ invalid_values: [],
+ },
+ font: {
+ domProp: "font",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { "writing-mode": "initial" },
+ subproperties: [
+ "font-style",
+ "font-variant",
+ "font-weight",
+ "font-size",
+ "line-height",
+ "font-family",
+ "font-stretch",
+ "font-size-adjust",
+ "font-feature-settings",
+ "font-language-override",
+ "font-kerning",
+ "font-variant-alternates",
+ "font-variant-caps",
+ "font-variant-east-asian",
+ "font-variant-ligatures",
+ "font-variant-numeric",
+ "font-variant-position",
+ ],
+ initial_values: [
+ gInitialFontFamilyIsSansSerif ? "medium sans-serif" : "medium serif",
+ ],
+ other_values: [
+ "large serif",
+ "9px fantasy",
+ "condensed bold italic small-caps 24px/1.4 Times New Roman, serif",
+ "small inherit roman",
+ "small roman inherit",
+ // system fonts
+ "caption",
+ "icon",
+ "menu",
+ "message-box",
+ "small-caption",
+ "status-bar",
+ // line-height with calc()
+ "condensed bold italic small-caps 24px/calc(2px) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(50%) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(3*25px) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(25px*3) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(3*25px + 50%) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(1 + 2*3/4) Times New Roman, serif",
+ ],
+ invalid_values: [
+ "9 fantasy",
+ "-2px fantasy",
+ // line-height with calc()
+ "condensed bold italic small-caps 24px/calc(1 + 2px) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(100% + 0.1) Times New Roman, serif",
+ ],
+ },
+ "font-family": {
+ domProp: "fontFamily",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [gInitialFontFamilyIsSansSerif ? "sans-serif" : "serif"],
+ other_values: [
+ gInitialFontFamilyIsSansSerif ? "serif" : "sans-serif",
+ "Times New Roman, serif",
+ "'Times New Roman', serif",
+ "cursive",
+ "fantasy",
+ '\\"Times New Roman',
+ '"Times New Roman"',
+ 'Times, \\"Times New Roman',
+ 'Times, "Times New Roman"',
+ "-no-such-font-installed",
+ "inherit roman",
+ "roman inherit",
+ "Times, inherit roman",
+ "inherit roman, Times",
+ "roman inherit, Times",
+ "Times, roman inherit",
+ ],
+ invalid_values: [
+ '"Times New" Roman',
+ '"Times New Roman\n',
+ 'Times, "Times New Roman\n',
+ ],
+ },
+ "font-feature-settings": {
+ domProp: "fontFeatureSettings",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "'liga' on",
+ "'liga'",
+ '"liga" 1',
+ "'liga', 'clig' 1",
+ '"liga" off',
+ '"liga" 0',
+ '"cv01" 3, "cv02" 4',
+ '"cswh", "smcp" off, "salt" 4',
+ '"cswh" 1, "smcp" off, "salt" 4',
+ '"cswh" 0, \'blah\', "liga", "smcp" off, "salt" 4',
+ '"liga" ,"smcp" 0 , "blah"',
+ '"ab\\"c"',
+ '"ab\\\\c"',
+ "'vert' calc(2)",
+ ],
+ invalid_values: [
+ "liga",
+ "liga 1",
+ "liga normal",
+ '"liga" normal',
+ "normal liga",
+ 'normal "liga"',
+ 'normal, "liga"',
+ '"liga=1"',
+ "'foobar' on",
+ '"blahblah" 0',
+ '"liga" 3.14',
+ '"liga" 1 3.14',
+ '"liga" 1 normal',
+ '"liga" 1 off',
+ '"liga" on off',
+ '"liga" , 0 "smcp"',
+ '"liga" "smcp"',
+ ],
+ },
+ "font-kerning": {
+ domProp: "fontKerning",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["normal", "none"],
+ invalid_values: ["on"],
+ },
+ "font-language-override": {
+ domProp: "fontLanguageOverride",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: ["'ENG'", "'TRK'", '"TRK"', "'N\\'Ko'"],
+ invalid_values: ["TRK", "ja"],
+ },
+ "font-size": {
+ domProp: "fontSize",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [
+ "medium",
+ "1rem",
+ "calc(1rem)",
+ "calc(0.75rem + 200% - 125% + 0.25rem - 75%)",
+ ],
+ other_values: [
+ "large",
+ "2em",
+ "50%",
+ "xx-small",
+ "xxx-large",
+ "36pt",
+ "8px",
+ "larger",
+ "smaller",
+ "0px",
+ "0%",
+ "calc(2em)",
+ "calc(36pt + 75% + (30% + 2em + 2px))",
+ "calc(-2em)",
+ "calc(-50%)",
+ "calc(-1px)",
+ ],
+ invalid_values: ["-2em", "-50%", "-1px"],
+ quirks_values: { 5: "5px" },
+ },
+ "font-size-adjust": {
+ domProp: "fontSizeAdjust",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["none"],
+ other_values: [
+ "0.7",
+ "0.0",
+ "0",
+ "3",
+ "from-font",
+ "cap-height 0.8",
+ "ch-width 0.4",
+ "ic-width 0.4",
+ "ic-height 0.9",
+ "ch-width from-font",
+ ],
+ invalid_values: [
+ "-0.3",
+ "-1",
+ "normal",
+ "none none",
+ "cap-height none",
+ "none from-font",
+ "from-font none",
+ "0.5 from-font",
+ "0.5 cap-height",
+ "cap-height, 0.8",
+ ],
+ },
+ "font-stretch": {
+ domProp: "fontStretch",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "ultra-condensed",
+ "extra-condensed",
+ "condensed",
+ "semi-condensed",
+ "semi-expanded",
+ "expanded",
+ "extra-expanded",
+ "ultra-expanded",
+ ],
+ invalid_values: ["narrower", "wider"],
+ },
+ "font-style": {
+ domProp: "fontStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: ["italic", "oblique"],
+ invalid_values: [],
+ },
+ "font-synthesis": {
+ domProp: "fontSynthesis",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ subproperties: [
+ "font-synthesis-weight",
+ "font-synthesis-style",
+ "font-synthesis-small-caps",
+ "font-synthesis-position",
+ ],
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [
+ "weight style small-caps position",
+ "weight small-caps style position",
+ "small-caps weight position style",
+ "small-caps style position weight",
+ "style position weight small-caps",
+ "style position small-caps weight",
+ ],
+ other_values: [
+ "none",
+ "weight",
+ "style",
+ "small-caps",
+ "position",
+ "weight style",
+ "style weight",
+ "weight small-caps",
+ "small-caps weight",
+ "weight position",
+ "position weight",
+ "style small-caps",
+ "small-caps style",
+ "style position",
+ "position style",
+ "small-caps position",
+ "position small-caps",
+ "weight style small-caps",
+ "small-caps weight style",
+ "weight style position",
+ "position weight style",
+ "weight small-caps position",
+ "position weight small-caps",
+ ],
+ invalid_values: [
+ "10px",
+ "weight none",
+ "style none",
+ "none style",
+ "none 10px",
+ "weight 10px",
+ "weight weight",
+ "style style",
+ "small-caps none",
+ "small-caps small-caps",
+ "position none",
+ "position position",
+ ],
+ },
+ "font-synthesis-weight": {
+ domProp: "fontSynthesisWeight",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["auto none", "weight", "normal", "0"],
+ },
+ "font-synthesis-style": {
+ domProp: "fontSynthesisStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["auto none", "style", "normal", "0"],
+ },
+ "font-synthesis-small-caps": {
+ domProp: "fontSynthesisSmallCaps",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["auto none", "small-caps", "normal", "0"],
+ },
+ "font-synthesis-position": {
+ domProp: "fontSynthesisPosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["auto none", "position", "normal", "0"],
+ },
+ "font-variant": {
+ domProp: "fontVariant",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "font-variant-alternates",
+ "font-variant-caps",
+ "font-variant-east-asian",
+ "font-variant-ligatures",
+ "font-variant-numeric",
+ "font-variant-position",
+ ],
+ initial_values: ["normal"],
+ other_values: [
+ "small-caps",
+ "none",
+ "traditional oldstyle-nums",
+ "all-small-caps",
+ "common-ligatures no-discretionary-ligatures",
+ "proportional-nums oldstyle-nums",
+ "proportional-nums slashed-zero diagonal-fractions oldstyle-nums ordinal",
+ "traditional historical-forms styleset(ok-alt-a, ok-alt-b)",
+ "styleset(potato)",
+ ],
+ invalid_values: [
+ "small-caps normal",
+ "small-caps small-caps",
+ "none common-ligatures",
+ "common-ligatures none",
+ "small-caps potato",
+ "small-caps jis83 all-small-caps",
+ "super historical-ligatures sub",
+ "stacked-fractions diagonal-fractions historical-ligatures",
+ "common-ligatures traditional common-ligatures",
+ "lining-nums traditional slashed-zero ordinal normal",
+ "traditional historical-forms styleset(ok-alt-a, ok-alt-b) historical-forms",
+ "historical-forms styleset(ok-alt-a, ok-alt-b) traditional styleset(potato)",
+ "annotation(a,b,c)",
+ ],
+ },
+ "font-variant-alternates": {
+ domProp: "fontVariantAlternates",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "historical-forms",
+ "styleset(alt-a, alt-b)",
+ "character-variant(a, b, c)",
+ "annotation(circled)",
+ "swash(squishy)",
+ "styleset(complex\\ blob, a)",
+ "annotation(\\62 lah)",
+ ],
+ invalid_values: [
+ "historical-forms normal",
+ "historical-forms historical-forms",
+ "swash",
+ "swash(3)",
+ "annotation(a, b)",
+ "ornaments(a,b)",
+ "styleset(1234blah)",
+ "annotation(a), annotation(b)",
+ "annotation(a) normal",
+ ],
+ },
+ "font-variant-caps": {
+ domProp: "fontVariantCaps",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "small-caps",
+ "all-small-caps",
+ "petite-caps",
+ "all-petite-caps",
+ "titling-caps",
+ "unicase",
+ ],
+ invalid_values: [
+ "normal small-caps",
+ "petite-caps normal",
+ "unicase unicase",
+ ],
+ },
+ "font-variant-east-asian": {
+ domProp: "fontVariantEastAsian",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "jis78",
+ "jis83",
+ "jis90",
+ "jis04",
+ "simplified",
+ "traditional",
+ "full-width",
+ "proportional-width",
+ "ruby",
+ "jis78 full-width",
+ "jis78 full-width ruby",
+ "simplified proportional-width",
+ "ruby simplified",
+ ],
+ invalid_values: [
+ "jis78 normal",
+ "jis90 jis04",
+ "simplified traditional",
+ "full-width proportional-width",
+ "ruby simplified ruby",
+ "jis78 ruby simplified",
+ ],
+ },
+ "font-variant-ligatures": {
+ domProp: "fontVariantLigatures",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "none",
+ "common-ligatures",
+ "no-common-ligatures",
+ "discretionary-ligatures",
+ "no-discretionary-ligatures",
+ "historical-ligatures",
+ "no-historical-ligatures",
+ "contextual",
+ "no-contextual",
+ "common-ligatures no-discretionary-ligatures",
+ "contextual no-discretionary-ligatures",
+ "historical-ligatures no-common-ligatures",
+ "no-historical-ligatures discretionary-ligatures",
+ "common-ligatures no-discretionary-ligatures historical-ligatures no-contextual",
+ ],
+ invalid_values: [
+ "common-ligatures normal",
+ "common-ligatures no-common-ligatures",
+ "common-ligatures common-ligatures",
+ "no-historical-ligatures historical-ligatures",
+ "no-discretionary-ligatures discretionary-ligatures",
+ "no-contextual contextual",
+ "common-ligatures no-discretionary-ligatures no-common-ligatures",
+ "common-ligatures none",
+ "no-discretionary-ligatures none",
+ "none common-ligatures",
+ ],
+ },
+ "font-variant-numeric": {
+ domProp: "fontVariantNumeric",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "lining-nums",
+ "oldstyle-nums",
+ "proportional-nums",
+ "tabular-nums",
+ "diagonal-fractions",
+ "stacked-fractions",
+ "slashed-zero",
+ "ordinal",
+ "lining-nums diagonal-fractions",
+ "tabular-nums stacked-fractions",
+ "tabular-nums slashed-zero stacked-fractions",
+ "proportional-nums slashed-zero diagonal-fractions oldstyle-nums ordinal",
+ ],
+ invalid_values: [
+ "lining-nums normal",
+ "lining-nums oldstyle-nums",
+ "lining-nums normal slashed-zero ordinal",
+ "proportional-nums tabular-nums",
+ "diagonal-fractions stacked-fractions",
+ "slashed-zero diagonal-fractions slashed-zero",
+ "lining-nums slashed-zero diagonal-fractions oldstyle-nums",
+ "diagonal-fractions diagonal-fractions",
+ ],
+ },
+ "font-variant-position": {
+ domProp: "fontVariantPosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: ["super", "sub"],
+ invalid_values: ["normal sub", "super sub"],
+ },
+ "font-weight": {
+ domProp: "fontWeight",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal", "400"],
+ other_values: [
+ "bold",
+ "100",
+ "200",
+ "300",
+ "500",
+ "600",
+ "700",
+ "800",
+ "900",
+ "bolder",
+ "lighter",
+ "10.5",
+ "calc(10 + 10)",
+ "calc(10 - 99)",
+ "100.0",
+ "107",
+ "399",
+ "401",
+ "699",
+ "710",
+ "1000",
+ ],
+ invalid_values: ["0", "1001", "calc(10%)"],
+ },
+ height: {
+ domProp: "height",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: test zero, and test calc clamping */
+ initial_values: [" auto"],
+ /* computed value tests for height test more with display:block */
+ prerequisites: { display: "block" },
+ other_values: [
+ "15px",
+ "3em",
+ "15%",
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ quirks_values: { 5: "5px" },
+ },
+ "ime-mode": {
+ domProp: "imeMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["normal", "disabled", "active", "inactive"],
+ invalid_values: ["none", "enabled", "1px"],
+ },
+ left: {
+ domProp: "left",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "letter-spacing": {
+ domProp: "letterSpacing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["normal", "0", "0px", "calc(0px)"],
+ other_values: [
+ "1em",
+ "2px",
+ "-3px",
+ "calc(1em)",
+ "calc(1em + 3px)",
+ "calc(15px / 2)",
+ "calc(15px/2)",
+ "calc(-3px)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "line-break": {
+ domProp: "lineBreak",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["loose", "normal", "strict", "anywhere"],
+ invalid_values: [],
+ },
+ "line-height": {
+ domProp: "lineHeight",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ /*
+ * Inheritance tests require consistent font size, since
+ * getComputedStyle (which uses the CSS2 computed value, or
+ * CSS2.1 used value) doesn't match what the CSS2.1 computed
+ * value is. And they even require consistent font metrics for
+ * computation of 'normal'.
+ */
+ prerequisites: {
+ "font-size": "19px",
+ "font-size-adjust": "none",
+ "font-family": "serif",
+ "font-weight": "normal",
+ "font-style": "normal",
+ height: "18px",
+ display: "block",
+ "writing-mode": "initial",
+ },
+
+ initial_values: ["normal"],
+ other_values: [
+ "1.0",
+ "1",
+ "1em",
+ "47px",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "calc(1 + 2*3/4)",
+ ],
+ invalid_values: ["calc(1 + 2px)", "calc(100% + 0.1)"],
+ },
+ "list-style": {
+ domProp: "listStyle",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "list-style-type",
+ "list-style-position",
+ "list-style-image",
+ ],
+ initial_values: [
+ "outside",
+ "disc",
+ "disc outside",
+ "outside disc",
+ "disc none",
+ "none disc",
+ "none disc outside",
+ "none outside disc",
+ "disc none outside",
+ "disc outside none",
+ "outside none disc",
+ "outside disc none",
+ ],
+ other_values: [
+ "inside none",
+ "none inside",
+ "none none inside",
+ "square",
+ "none",
+ "none none",
+ "outside none none",
+ "none outside none",
+ "none none outside",
+ "none outside",
+ "outside none",
+ "outside outside",
+ "outside inside",
+ "\\32 style",
+ "\\32 style inside",
+ '"-"',
+ "'-'",
+ "inside '-'",
+ "'-' outside",
+ "none '-'",
+ "inside none '-'",
+ 'symbols("*" "\\2020" "\\2021" "\\A7")',
+ 'symbols(cyclic "*" "\\2020" "\\2021" "\\A7")',
+ 'inside symbols("*" "\\2020" "\\2021" "\\A7")',
+ 'symbols("*" "\\2020" "\\2021" "\\A7") outside',
+ 'none symbols("*" "\\2020" "\\2021" "\\A7")',
+ 'inside none symbols("*" "\\2020" "\\2021" "\\A7")',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside',
+ 'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'outside none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none',
+ 'none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside',
+ 'none outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside none',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none outside',
+ ],
+ invalid_values: [
+ "disc disc",
+ "unknown value",
+ "none none none",
+ "none disc url(404.png)",
+ "none url(404.png) disc",
+ "disc none url(404.png)",
+ "disc url(404.png) none",
+ "url(404.png) none disc",
+ "url(404.png) disc none",
+ "none disc outside url(404.png)",
+ ],
+ },
+ "list-style-image": {
+ domProp: "listStyleImage",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ // Add some tests for interesting url() values here to test serialization, etc.
+ "url('data:text/plain,\"')",
+ 'url("data:text/plain,\'")',
+ "url('data:text/plain,\\'')",
+ 'url("data:text/plain,\\"")',
+ "url('data:text/plain,\\\"')",
+ 'url("data:text/plain,\\\'")',
+ "url(data:text/plain,\\\\)",
+ ].concat(validNonUrlImageValues),
+ invalid_values: ["url('border.png') url('border.png')"].concat(
+ invalidNonUrlImageValues
+ ),
+ unbalanced_values: [].concat(unbalancedGradientAndElementValues),
+ },
+ "list-style-position": {
+ domProp: "listStylePosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["outside"],
+ other_values: ["inside"],
+ invalid_values: [],
+ },
+ "list-style-type": {
+ domProp: "listStyleType",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["disc"],
+ other_values: [
+ "none",
+ "circle",
+ "square",
+ "disclosure-closed",
+ "disclosure-open",
+ "decimal",
+ "decimal-leading-zero",
+ "lower-roman",
+ "upper-roman",
+ "lower-greek",
+ "lower-alpha",
+ "lower-latin",
+ "upper-alpha",
+ "upper-latin",
+ "hebrew",
+ "armenian",
+ "georgian",
+ "cjk-decimal",
+ "cjk-ideographic",
+ "hiragana",
+ "katakana",
+ "hiragana-iroha",
+ "katakana-iroha",
+ "japanese-informal",
+ "japanese-formal",
+ "korean-hangul-formal",
+ "korean-hanja-informal",
+ "korean-hanja-formal",
+ "simp-chinese-informal",
+ "simp-chinese-formal",
+ "trad-chinese-informal",
+ "trad-chinese-formal",
+ "ethiopic-numeric",
+ "-moz-cjk-heavenly-stem",
+ "-moz-cjk-earthly-branch",
+ "-moz-trad-chinese-informal",
+ "-moz-trad-chinese-formal",
+ "-moz-simp-chinese-informal",
+ "-moz-simp-chinese-formal",
+ "-moz-japanese-informal",
+ "-moz-japanese-formal",
+ "-moz-arabic-indic",
+ "-moz-persian",
+ "-moz-urdu",
+ "-moz-devanagari",
+ "-moz-gurmukhi",
+ "-moz-gujarati",
+ "-moz-oriya",
+ "-moz-kannada",
+ "-moz-malayalam",
+ "-moz-bengali",
+ "-moz-tamil",
+ "-moz-telugu",
+ "-moz-thai",
+ "-moz-lao",
+ "-moz-myanmar",
+ "-moz-khmer",
+ "-moz-hangul",
+ "-moz-hangul-consonant",
+ "-moz-ethiopic-halehame",
+ "-moz-ethiopic-numeric",
+ "-moz-ethiopic-halehame-am",
+ "-moz-ethiopic-halehame-ti-er",
+ "-moz-ethiopic-halehame-ti-et",
+ "other-style",
+ "inside",
+ "outside",
+ "\\32 style",
+ '"-"',
+ "'-'",
+ 'symbols("*" "\\2020" "\\2021" "\\A7")',
+ "symbols(cyclic '*' '\\2020' '\\2021' '\\A7')",
+ ],
+ invalid_values: [],
+ },
+ margin: {
+ domProp: "margin",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left",
+ ],
+ initial_values: ["0", "0px 0 0em", "0% 0px 0em 0pt"],
+ other_values: [
+ "3px 0",
+ "2em 4px 2pt",
+ "1em 2em 3px 4px",
+ "1em calc(2em + 3px) 4ex 5cm",
+ ],
+ invalid_values: ["1px calc(nonsense)", "1px red"],
+ unbalanced_values: ["1px calc("],
+ quirks_values: { 5: "5px", "3px 6px 2 5px": "3px 6px 2px 5px" },
+ },
+ "margin-bottom": {
+ domProp: "marginBottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "margin-left": {
+ domProp: "marginLeft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ ".5px",
+ "+32px",
+ "+.789px",
+ "-.328px",
+ "+0.56px",
+ "-0.974px",
+ "237px",
+ "-289px",
+ "-056px",
+ "1987.45px",
+ "-84.32px",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ quirks_values: { 5: "5px" },
+ },
+ "margin-right": {
+ domProp: "marginRight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "margin-top": {
+ domProp: "marginTop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "max-height": {
+ domProp: "maxHeight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "30px",
+ "50%",
+ "0",
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["auto"],
+ quirks_values: { 5: "5px" },
+ },
+ "max-width": {
+ domProp: "maxWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "30px",
+ "50%",
+ "0",
+ // these four keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["auto"],
+ quirks_values: { 5: "5px" },
+ },
+ "min-height": {
+ domProp: "minHeight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { display: "block" },
+ initial_values: ["auto", "0", "calc(0em)", "calc(-2px)"],
+ other_values: [
+ "30px",
+ "50%",
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ quirks_values: { 5: "5px" },
+ },
+ "min-width": {
+ domProp: "minWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { display: "block" },
+ initial_values: ["auto", "0", "calc(0em)", "calc(-2px)"],
+ other_values: [
+ "30px",
+ "50%",
+ // these four keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ quirks_values: { 5: "5px" },
+ },
+ "object-fit": {
+ domProp: "objectFit",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["fill"],
+ other_values: ["contain", "cover", "none", "scale-down"],
+ invalid_values: ["auto", "5px", "100%"],
+ },
+ "object-position": {
+ domProp: "objectPosition",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["50% 50%", "50%", "center", "center center"],
+ other_values: [
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ "0px 0px",
+ "right 20px top 60px",
+ "right 20px bottom 60px",
+ "left 20px top 60px",
+ "left 20px bottom 60px",
+ "right -50px top -50px",
+ "left -50px bottom -50px",
+ "right 20px top -50px",
+ "right -20px top 50px",
+ "right 3em bottom 10px",
+ "bottom 3em right 10px",
+ "top 3em right 10px",
+ "left 15px",
+ "10px top",
+ "left 20%",
+ "right 20%",
+ ],
+ invalid_values: [
+ "center 10px center 4px",
+ "center 10px center",
+ "top 20%",
+ "bottom 20%",
+ "50% left",
+ "top 50%",
+ "50% bottom 10%",
+ "right 10% 50%",
+ "left right",
+ "top bottom",
+ "left 10% right",
+ "top 20px bottom 20px",
+ "left left",
+ "20 20",
+ "left top 15px",
+ "left 10px top",
+ ],
+ },
+ opacity: {
+ domProp: "opacity",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [
+ "1",
+ "17",
+ "397.376",
+ "3e1",
+ "3e+1",
+ "3e0",
+ "3e+0",
+ "3e-0",
+ "300%",
+ ],
+ other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%"],
+ invalid_values: ["0px", "1px"],
+ },
+ "-moz-orient": {
+ domProp: "MozOrient",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["inline"],
+ other_values: ["horizontal", "vertical", "block"],
+ invalid_values: ["none"],
+ },
+ outline: {
+ domProp: "outline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["outline-color", "outline-style", "outline-width"],
+ initial_values: [
+ "none",
+ "medium",
+ "thin",
+ // XXX Should be invert, but currently currentcolor.
+ //"invert", "none medium invert"
+ "currentColor",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "outline-color": {
+ domProp: "outlineColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"], // XXX should be invert
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "cc00ff",
+ ],
+ },
+ "outline-offset": {
+ domProp: "outlineOffset",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [
+ "0",
+ "0px",
+ "-0",
+ "calc(0px)",
+ "calc(3em + 2px - 2px - 3em)",
+ "calc(-0em)",
+ ],
+ other_values: [
+ "-3px",
+ "1em",
+ "calc(3em)",
+ "calc(7pt + 3 * 2em)",
+ "calc(-3px)",
+ ],
+ invalid_values: ["5%"],
+ },
+ "outline-style": {
+ domProp: "outlineStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ // XXX Should 'hidden' be the same as initial?
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ "auto",
+ ],
+ invalid_values: [],
+ },
+ "outline-width": {
+ domProp: "outlineWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ prerequisites: { "outline-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0px)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ overflow: {
+ domProp: "overflow",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ prerequisites: { display: "block", contain: "none" },
+ subproperties: ["overflow-x", "overflow-y"],
+ initial_values: ["visible"],
+ other_values: [
+ "auto",
+ "scroll",
+ "hidden",
+ "clip",
+ "auto auto",
+ "auto scroll",
+ "hidden scroll",
+ "auto hidden",
+ "clip clip",
+ "overlay",
+ "overlay overlay",
+ ],
+ invalid_values: [
+ "clip -moz-scrollbars-none",
+ "-moz-scrollbars-none",
+ "-moz-scrollbars-horizontal",
+ "-moz-scrollbars-vertical",
+ ],
+ },
+ "overflow-x": {
+ domProp: "overflowX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: {
+ display: "block",
+ "overflow-y": "visible",
+ contain: "none",
+ },
+ initial_values: ["visible"],
+ other_values: ["auto", "scroll", "hidden", "clip", "overlay"],
+ invalid_values: [],
+ },
+ "overflow-y": {
+ domProp: "overflowY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: {
+ display: "block",
+ "overflow-x": "visible",
+ contain: "none",
+ },
+ initial_values: ["visible"],
+ other_values: ["auto", "scroll", "hidden", "clip", "overlay"],
+ invalid_values: [],
+ },
+ "overflow-inline": {
+ domProp: "overflowInline",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: {
+ display: "block",
+ "overflow-block": "visible",
+ contain: "none",
+ },
+ initial_values: ["visible"],
+ other_values: ["auto", "scroll", "hidden", "clip"],
+ invalid_values: [],
+ },
+ "overflow-block": {
+ domProp: "overflowBlock",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: {
+ display: "block",
+ "overflow-inline": "visible",
+ contain: "none",
+ },
+ initial_values: ["visible"],
+ other_values: ["auto", "scroll", "hidden", "clip"],
+ invalid_values: [],
+ },
+ "overflow-clip-margin": {
+ domProp: "overflowClipMargin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["1px", "2em", "calc(10px + 1vh)"],
+ invalid_values: ["-10px"],
+ },
+ padding: {
+ domProp: "padding",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left",
+ ],
+ initial_values: [
+ "0",
+ "0px 0 0em",
+ "0% 0px 0em 0pt",
+ "calc(0px) calc(0em) calc(-2px) calc(-1%)",
+ ],
+ other_values: ["3px 0", "2em 4px 2pt", "1em 2em 3px 4px"],
+ invalid_values: ["1px calc(nonsense)", "1px red", "-1px"],
+ unbalanced_values: ["1px calc("],
+ quirks_values: { 5: "5px", "3px 6px 2 5px": "3px 6px 2px 5px" },
+ },
+ "padding-block": {
+ domProp: "paddingBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["padding-block-start", "padding-block-end"],
+ initial_values: ["0", "0px 0em"],
+ other_values: ["3px 0", "2% 4px", "1em", "calc(1px) calc(-1%)"],
+ invalid_values: ["1px calc(nonsense)", "1px red", "-1px", "auto", "none"],
+ unbalanced_values: ["1px calc("],
+ },
+ "padding-inline": {
+ domProp: "paddingInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["padding-inline-start", "padding-inline-end"],
+ initial_values: ["0", "0px 0em"],
+ other_values: ["3px 0", "2% 4px", "1em", "calc(1px) calc(-1%)"],
+ invalid_values: ["1px calc(nonsense)", "1px red", "-1px", "auto", "none"],
+ unbalanced_values: ["1px calc("],
+ },
+ "padding-bottom": {
+ domProp: "paddingBottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "padding-left": {
+ domProp: "paddingLeft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "padding-right": {
+ domProp: "paddingRight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "padding-top": {
+ domProp: "paddingTop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "page-break-after": {
+ domProp: "pageBreakAfter",
+ inherited: false,
+ type: CSS_TYPE_LEGACY_SHORTHAND,
+ alias_for: "break-after",
+ subproperties: ["break-after"],
+ initial_values: ["auto"],
+ other_values: ["always", "avoid", "left", "right"],
+ legacy_mapping: {
+ always: "page",
+ },
+ invalid_values: ["page", "column"],
+ },
+ "page-break-before": {
+ domProp: "pageBreakBefore",
+ inherited: false,
+ type: CSS_TYPE_LEGACY_SHORTHAND,
+ alias_for: "break-before",
+ subproperties: ["break-before"],
+ initial_values: ["auto"],
+ other_values: ["always", "avoid", "left", "right"],
+ legacy_mapping: {
+ always: "page",
+ },
+ invalid_values: ["page", "column"],
+ },
+ "break-after": {
+ domProp: "breakAfter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["always", "page", "avoid", "left", "right"],
+ invalid_values: [],
+ },
+ "break-before": {
+ domProp: "breakBefore",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["always", "page", "avoid", "left", "right"],
+ invalid_values: [],
+ },
+ "break-inside": {
+ domProp: "breakInside",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["avoid", "avoid-page", "avoid-column"],
+ invalid_values: ["left", "right", "always"],
+ },
+ "page-break-inside": {
+ domProp: "pageBreakInside",
+ inherited: false,
+ type: CSS_TYPE_LEGACY_SHORTHAND,
+ alias_for: "break-inside",
+ subproperties: ["break-inside"],
+ initial_values: ["auto"],
+ other_values: ["avoid"],
+ invalid_values: ["avoid-page", "avoid-column"],
+ },
+ "paint-order": {
+ domProp: "paintOrder",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["normal"],
+ other_values: [
+ "fill",
+ "fill stroke",
+ "fill stroke markers",
+ "stroke markers fill",
+ ],
+ invalid_values: ["fill stroke markers fill", "fill normal"],
+ },
+ "pointer-events": {
+ domProp: "pointerEvents",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: ["auto"],
+ other_values: [
+ "visiblePainted",
+ "visibleFill",
+ "visibleStroke",
+ "visible",
+ "painted",
+ "fill",
+ "stroke",
+ "all",
+ "none",
+ ],
+ invalid_values: [],
+ },
+ position: {
+ domProp: "position",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["static"],
+ other_values: ["relative", "absolute", "fixed", "sticky"],
+ invalid_values: [],
+ },
+ quotes: {
+ domProp: "quotes",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "none",
+ "'\"' '\"'",
+ "'' ''",
+ '"\u201C" "\u201D" "\u2018" "\u2019"',
+ '"\\201C" "\\201D" "\\2018" "\\2019"',
+ ],
+ invalid_values: ["'\"'", '"" "" ""'],
+ },
+ right: {
+ domProp: "right",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "ruby-align": {
+ domProp: "rubyAlign",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["space-around"],
+ other_values: ["start", "center", "space-between"],
+ invalid_values: ["end", "1", "10px", "50%", "start center"],
+ },
+ "ruby-position": {
+ domProp: "rubyPosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ initial_values: ["alternate", "alternate over", "over alternate"],
+ other_values: ["over", "under", "alternate under", "under alternate"],
+ invalid_values: [
+ "left",
+ "right",
+ "auto",
+ "none",
+ "not_a_position",
+ "over left",
+ "right under",
+ "over under",
+ "alternate alternate",
+ "0",
+ "100px",
+ "50%",
+ ],
+ },
+ "scroll-behavior": {
+ domProp: "scrollBehavior",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["smooth"],
+ invalid_values: ["none", "1px"],
+ },
+ "scroll-snap-stop": {
+ domProp: "scrollSnapStop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["always"],
+ invalid_values: ["auto", "none", "1px"],
+ },
+ "scroll-snap-type": {
+ domProp: "scrollSnapType",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "both mandatory",
+ "y mandatory",
+ "inline proximity",
+ "both",
+ "x",
+ "y",
+ "block",
+ "inline",
+ ],
+ invalid_values: [
+ "auto",
+ "1px",
+ "x y",
+ "block mandatory inline",
+ "mandatory",
+ "proximity",
+ "mandatory inline",
+ "proximity both",
+ "mandatory x",
+ "proximity y",
+ "mandatory block",
+ "proximity mandatory",
+ ],
+ },
+ "scroll-snap-align": {
+ domProp: "scrollSnapAlign",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "start",
+ "end",
+ "center",
+ "start none",
+ "center end",
+ "start start",
+ ],
+ invalid_values: ["auto", "start invalid", "start end center"],
+ },
+ "scroll-margin": {
+ domProp: "scrollMargin",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "scroll-margin-top",
+ "scroll-margin-right",
+ "scroll-margin-bottom",
+ "scroll-margin-left",
+ ],
+ initial_values: ["0"],
+ other_values: [
+ "-10px",
+ "calc(2em + 3ex)",
+ "1px 2px",
+ "1px 2px 3px",
+ "1px 2px 3px 4px",
+ ],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px 3px 4px 5px"],
+ },
+ "scroll-margin-top": {
+ domProp: "scrollMarginTop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-right": {
+ domProp: "scrollMarginRight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-bottom": {
+ domProp: "scrollMarginBottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-left": {
+ domProp: "scrollMarginLeft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-inline": {
+ domProp: "scrollMarginInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-margin-inline-start", "scroll-margin-inline-end"],
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)", "1px 2px"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px 3px"],
+ },
+ "scroll-margin-inline-start": {
+ domProp: "scrollMarginInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-inline-end": {
+ domProp: "scrollMarginInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-block": {
+ domProp: "scrollMarginBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-margin-block-start", "scroll-margin-block-end"],
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)", "1px 2px"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px 3px"],
+ },
+ "scroll-margin-block-start": {
+ domProp: "scrollMarginBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-block-end": {
+ domProp: "scrollMarginBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-padding": {
+ domProp: "scrollPadding",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "scroll-padding-top",
+ "scroll-padding-right",
+ "scroll-padding-bottom",
+ "scroll-padding-left",
+ ],
+ initial_values: ["auto"],
+ other_values: [
+ "10px",
+ "0",
+ "20%",
+ "calc(2em + 3ex)",
+ "1px 2px",
+ "1px 2px 3%",
+ "1px 2px 3% 4px",
+ "1px auto",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-top": {
+ domProp: "scrollPaddingTop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-right": {
+ domProp: "scrollPaddingRight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-bottom": {
+ domProp: "scrollPaddingBottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-left": {
+ domProp: "scrollPaddingLeft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-inline": {
+ domProp: "scrollPaddingInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-padding-inline-start", "scroll-padding-inline-end"],
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "10px",
+ "0",
+ "20%",
+ "calc(2em + 3ex)",
+ "1px 2px",
+ "1px auto",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-inline-start": {
+ domProp: "scrollPaddingInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-inline-end": {
+ domProp: "scrollPaddingInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-block": {
+ domProp: "scrollPaddingBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-padding-block-start", "scroll-padding-block-end"],
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "10px",
+ "0",
+ "20%",
+ "calc(2em + 3ex)",
+ "1px 2px",
+ "1px auto",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-block-start": {
+ domProp: "scrollPaddingBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-block-end": {
+ domProp: "scrollPaddingBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "table-layout": {
+ domProp: "tableLayout",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["fixed"],
+ invalid_values: [],
+ },
+ "text-align": {
+ domProp: "textAlign",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ // don't know whether left and right are same as start
+ initial_values: ["start"],
+ other_values: ["center", "justify", "end", "match-parent"],
+ invalid_values: [
+ "true",
+ "true true",
+ "char",
+ "-moz-center-or-inherit",
+ "true left",
+ "unsafe left",
+ ],
+ },
+ "text-align-last": {
+ domProp: "textAlignLast",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["center", "justify", "start", "end", "left", "right"],
+ invalid_values: [],
+ },
+ "text-combine-upright": {
+ domProp: "textCombineUpright",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["none"],
+ other_values: ["all"],
+ invalid_values: [
+ "auto",
+ "all 2",
+ "none all",
+ "digits -3",
+ "digits 0",
+ "digits 12",
+ "none 3",
+ "digits 3.1415",
+ "digits3",
+ "digits 1",
+ "digits 3 all",
+ "digits foo",
+ "digits all",
+ "digits 3.0",
+ ],
+ },
+ "text-decoration": {
+ domProp: "textDecoration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ subproperties: [
+ "text-decoration-color",
+ "text-decoration-line",
+ "text-decoration-style",
+ "text-decoration-thickness",
+ ],
+ initial_values: ["none"],
+ other_values: [
+ "underline",
+ "overline",
+ "line-through",
+ "blink",
+ "blink line-through underline",
+ "underline overline line-through blink",
+ "underline red solid",
+ "underline #ff0000",
+ "solid underline",
+ "red underline",
+ "#ff0000 underline",
+ "dotted underline",
+ "solid underline 50px",
+ "underline 50px blue",
+ "50px dotted line-through purple",
+ "overline 2em",
+ "underline from-font",
+ "red from-font overline",
+ "5% underline blue",
+ "dotted line-through 25%",
+ ],
+ invalid_values: [
+ "none none",
+ "underline none",
+ "none underline",
+ "blink none",
+ "none blink",
+ "line-through blink line-through",
+ "underline overline line-through blink none",
+ "underline overline line-throuh blink blink",
+ "rgb(0, rubbish, 0) underline",
+ "from font blue underline",
+ ],
+ },
+ "text-decoration-color": {
+ domProp: "textDecorationColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "ff00ff",
+ ],
+ },
+ "text-decoration-line": {
+ domProp: "textDecorationLine",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["none"],
+ other_values: [
+ "underline",
+ "overline",
+ "line-through",
+ "blink",
+ "blink line-through underline",
+ "underline overline line-through blink",
+ ],
+ invalid_values: [
+ "none none",
+ "underline none",
+ "none underline",
+ "line-through blink line-through",
+ "underline overline line-through blink none",
+ "underline overline line-throuh blink blink",
+ ],
+ },
+ "text-decoration-style": {
+ domProp: "textDecorationStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["solid"],
+ other_values: ["double", "dotted", "dashed", "wavy", "-moz-none"],
+ invalid_values: [
+ "none",
+ "groove",
+ "ridge",
+ "inset",
+ "outset",
+ "solid dashed",
+ "wave",
+ ],
+ },
+ "text-decoration-thickness": {
+ domProp: "textDecorationThickness",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: [
+ "from-font",
+ "0",
+ "-14px",
+ "25px",
+ "100em",
+ "-45em",
+ "43%",
+ "-10%",
+ ],
+ invalid_values: ["13", "-25", "rubbish", ",./!@#$", "from font"],
+ },
+ "text-decoration-skip-ink": {
+ domProp: "textDecorationSkipInk",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ["none", "all"],
+ invalid_values: [
+ "13",
+ "15%",
+ "-1",
+ "0",
+ "otto",
+ "trash",
+ "non",
+ "nada",
+ "!@#$%^",
+ "none auto",
+ "auto none",
+ ],
+ },
+ "text-underline-offset": {
+ domProp: "textUnderlineOffset",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ["0", "-14px", "25px", "100em", "-45em", "43%", "-10%"],
+ invalid_values: [
+ "13",
+ "-25",
+ "rubbish",
+ ",./!@#$",
+ "from-font",
+ "from font",
+ ],
+ },
+ "text-underline-position": {
+ domProp: "textUnderlinePosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: [
+ "under",
+ "left",
+ "right",
+ "left under",
+ "under left",
+ "right under",
+ "under right",
+ "from-font",
+ "from-font left",
+ "from-font right",
+ "left from-font",
+ "right from-font",
+ ],
+ invalid_values: [
+ "none",
+ "auto from-font",
+ "auto under",
+ "under from-font",
+ "left right",
+ "right auto",
+ "0",
+ "1px",
+ "10%",
+ "from font",
+ ],
+ },
+ "text-emphasis": {
+ domProp: "textEmphasis",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { color: "black" },
+ subproperties: ["text-emphasis-style", "text-emphasis-color"],
+ initial_values: [
+ "none currentColor",
+ "currentColor none",
+ "none",
+ "currentColor",
+ "none black",
+ ],
+ other_values: [
+ "filled dot black",
+ "#f00 circle open",
+ "sesame filled rgba(0,0,255,0.5)",
+ "red",
+ "green none",
+ "currentColor filled",
+ "currentColor open",
+ ],
+ invalid_values: [
+ "filled black dot",
+ "filled filled red",
+ "open open circle #000",
+ "circle dot #f00",
+ "rubbish",
+ ],
+ },
+ "text-emphasis-color": {
+ domProp: "textEmphasisColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor", "black", "rgb(0,0,0)"],
+ other_values: ["red", "rgba(255,255,255,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "ff00ff",
+ "rgb(255,xxx,255)",
+ ],
+ },
+ "text-emphasis-position": {
+ domProp: "textEmphasisPosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["over right", "right over", "over"],
+ other_values: [
+ "over left",
+ "left over",
+ "under left",
+ "left under",
+ "under right",
+ "right under",
+ "under",
+ ],
+ invalid_values: [
+ "over over",
+ "left left",
+ "over right left",
+ "rubbish left",
+ "over rubbish",
+ ],
+ },
+ "text-emphasis-style": {
+ domProp: "textEmphasisStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "filled",
+ "open",
+ "dot",
+ "circle",
+ "double-circle",
+ "triangle",
+ "sesame",
+ "'#'",
+ "filled dot",
+ "filled circle",
+ "filled double-circle",
+ "filled triangle",
+ "filled sesame",
+ "dot filled",
+ "circle filled",
+ "double-circle filled",
+ "triangle filled",
+ "sesame filled",
+ "dot open",
+ "circle open",
+ "double-circle open",
+ "triangle open",
+ "sesame open",
+ ],
+ invalid_values: [
+ "rubbish",
+ "dot rubbish",
+ "rubbish dot",
+ "open rubbish",
+ "rubbish open",
+ "open filled",
+ "dot circle",
+ "open '#'",
+ "'#' filled",
+ "dot '#'",
+ "'#' circle",
+ "1",
+ "1 open",
+ "open 1",
+ ],
+ },
+ "text-indent": {
+ domProp: "textIndent",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0", "calc(3em - 5em + 2px + 2em - 2px)"],
+ other_values: [
+ "2em",
+ "5%",
+ "-10px",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "text-overflow": {
+ domProp: "textOverflow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ initial_values: ["clip"],
+ other_values: [
+ "ellipsis",
+ '""',
+ "''",
+ '"hello"',
+ "clip clip",
+ "ellipsis ellipsis",
+ "clip ellipsis",
+ 'clip ""',
+ '"hello" ""',
+ '"" ellipsis',
+ ],
+ invalid_values: [
+ "none",
+ "auto",
+ '"hello" inherit',
+ 'inherit "hello"',
+ "clip initial",
+ "initial clip",
+ "initial inherit",
+ "inherit initial",
+ "inherit none",
+ '"hello" unset',
+ 'unset "hello"',
+ "clip unset",
+ "unset clip",
+ "unset inherit",
+ "unset none",
+ "initial unset",
+ ],
+ },
+ "text-shadow": {
+ domProp: "textShadow",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ prerequisites: { color: "blue" },
+ initial_values: ["none"],
+ other_values: [
+ "2px 2px",
+ "2px 2px 1px",
+ "2px 2px green",
+ "2px 2px 1px green",
+ "green 2px 2px",
+ "green 2px 2px 1px",
+ "green 2px 2px, blue 1px 3px 4px",
+ "currentColor 3px 3px",
+ "blue 2px 2px, currentColor 1px 2px",
+ /* calc() values */
+ "2px 2px calc(-5px)" /* clamped */,
+ "calc(3em - 2px) 2px green",
+ "green calc(3em - 2px) 2px",
+ "2px calc(2px + 0.2em)",
+ "blue 2px calc(2px + 0.2em)",
+ "2px calc(2px + 0.2em) blue",
+ "calc(-2px) calc(-2px)",
+ "-2px -2px",
+ "calc(2px) calc(2px)",
+ "calc(2px) calc(2px) calc(2px)",
+ ],
+ invalid_values: [
+ "3% 3%",
+ "2px 2px -5px",
+ "2px 2px 2px 2px",
+ "2px 2px, none",
+ "none, 2px 2px",
+ "inherit, 2px 2px",
+ "2px 2px, inherit",
+ "2 2px",
+ "2px 2",
+ "2px 2px 2",
+ "2px 2px 2px 2",
+ "calc(2px) calc(2px) calc(2px) calc(2px)",
+ "3px 3px calc(3px + rubbish)",
+ "unset, 2px 2px",
+ "2px 2px, unset",
+ ],
+ },
+ "text-transform": {
+ domProp: "textTransform",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_marker: true,
+ initial_values: ["none"],
+ other_values: [
+ "capitalize",
+ "uppercase",
+ "lowercase",
+ "full-width",
+ "full-size-kana",
+ "uppercase full-width",
+ "full-size-kana capitalize",
+ "full-width lowercase full-size-kana",
+ ],
+ invalid_values: [
+ "none none",
+ "none uppercase",
+ "full-width none",
+ "uppercase lowercase",
+ "full-width capitalize full-width",
+ "uppercase full-width lowercase",
+ ],
+ },
+ "text-wrap": {
+ domProp: "textWrap",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["text-wrap-mode"],
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["wrap"],
+ other_values: ["nowrap"],
+ invalid_values: [],
+ },
+ "text-wrap-mode": {
+ domProp: "textWrapMode",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ applies_to_placeholder: true,
+ applies_to_marker: true,
+ initial_values: ["wrap"],
+ other_values: ["nowrap"],
+ invalid_values: ["none", "normal", "on", "off", "wrap nowrap"],
+ },
+ top: {
+ domProp: "top",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ transition: {
+ domProp: "transition",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ subproperties: [
+ "transition-property",
+ "transition-duration",
+ "transition-timing-function",
+ "transition-delay",
+ ],
+ initial_values: ["all 0s ease 0s", "all", "0s", "0s 0s", "ease"],
+ other_values: [
+ "all 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) 0s",
+ "width 1s linear 2s",
+ "width 1s 2s linear",
+ "width linear 1s 2s",
+ "linear width 1s 2s",
+ "linear 1s width 2s",
+ "linear 1s 2s width",
+ "1s width linear 2s",
+ "1s width 2s linear",
+ "1s 2s width linear",
+ "1s linear width 2s",
+ "1s linear 2s width",
+ "1s 2s linear width",
+ "width linear 1s",
+ "width 1s linear",
+ "linear width 1s",
+ "linear 1s width",
+ "1s width linear",
+ "1s linear width",
+ "1s 2s width",
+ "1s width 2s",
+ "width 1s 2s",
+ "1s 2s linear",
+ "1s linear 2s",
+ "linear 1s 2s",
+ "width 1s",
+ "1s width",
+ "linear 1s",
+ "1s linear",
+ "1s 2s",
+ "2s 1s",
+ "width",
+ "linear",
+ "1s",
+ "height",
+ "2s",
+ "ease-in-out",
+ "2s ease-in",
+ "opacity linear",
+ "ease-out 2s",
+ "2s color, 1s width, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)",
+ "1s \\32width linear 2s",
+ "1s -width linear 2s",
+ "1s -\\32width linear 2s",
+ "1s \\32 0width linear 2s",
+ "1s -\\32 0width linear 2s",
+ "1s \\2width linear 2s",
+ "1s -\\2width linear 2s",
+ "2s, 1s width",
+ "1s width, 2s",
+ "2s all, 1s width",
+ "1s width, 2s all",
+ "2s all, 1s width",
+ "2s width, 1s all",
+ "3s --my-color",
+ "none",
+ "none 2s linear 2s",
+ ],
+ invalid_values: [
+ "1s width, 2s none",
+ "2s none, 1s width",
+ "2s inherit",
+ "inherit 2s",
+ "2s width, 1s inherit",
+ "2s inherit, 1s width",
+ "2s initial",
+ "1s width,,2s color",
+ "1s width, ,2s color",
+ "bounce 1s cubic-bezier(0, rubbish) 2s",
+ "bounce 1s steps(rubbish) 2s",
+ "2s unset",
+ ],
+ },
+ "transition-delay": {
+ domProp: "transitionDelay",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["0s", "0ms"],
+ other_values: ["1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s"],
+ invalid_values: ["0", "0px"],
+ },
+ "transition-duration": {
+ domProp: "transitionDuration",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["0s", "0ms"],
+ other_values: ["1s", "250ms", "1s, 250ms, 2.3s"],
+ invalid_values: ["0", "0px", "-1ms", "-2s"],
+ },
+ "transition-property": {
+ domProp: "transitionProperty",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["all"],
+ other_values: [
+ "none",
+ "left",
+ "top",
+ "color",
+ "width, height, opacity",
+ "foobar",
+ "auto",
+ "\\32width",
+ "-width",
+ "-\\32width",
+ "\\32 0width",
+ "-\\32 0width",
+ "\\2width",
+ "-\\2width",
+ "all, all",
+ "all, color",
+ "color, all",
+ "--my-color",
+ ],
+ invalid_values: [
+ "none, none",
+ "color, none",
+ "none, color",
+ "inherit, color",
+ "color, inherit",
+ "initial, color",
+ "color, initial",
+ "none, color",
+ "color, none",
+ "unset, color",
+ "color, unset",
+ ],
+ },
+ "transition-timing-function": {
+ domProp: "transitionTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["ease"],
+ other_values: [
+ "cubic-bezier(0.25, 0.1, 0.25, 1.0)",
+ "linear",
+ "ease-in",
+ "ease-out",
+ "ease-in-out",
+ "linear, ease-in, cubic-bezier(0.1, 0.2, 0.8, 0.9)",
+ "cubic-bezier(0.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(0.25, 1.5, 0.75, -0.5)",
+ "step-start",
+ "step-end",
+ "steps(1)",
+ "steps(2, start)",
+ "steps(386)",
+ "steps(3, end)",
+ "steps(1, jump-start)",
+ "steps(1, jump-end)",
+ "steps(2, jump-none)",
+ "steps(1, jump-both)",
+ ],
+ invalid_values: [
+ "none",
+ "auto",
+ "cubic-bezier(0.25, 0.1, 0.25)",
+ "cubic-bezier(0.25, 0.1, 0.25, 0.25, 1.0)",
+ "cubic-bezier(-0.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(1.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(0.5, 0.5, -0.5, 0.5)",
+ "cubic-bezier(0.5, 0.5, 1.5, 0.5)",
+ "steps(2, step-end)",
+ "steps(0)",
+ "steps(-2)",
+ "steps(0, step-end, 1)",
+ "steps(0, jump-start)",
+ "steps(0, jump-end)",
+ "steps(1, jump-none)",
+ "steps(0, jump-both)",
+ ],
+ },
+ "unicode-bidi": {
+ domProp: "unicodeBidi",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["normal"],
+ other_values: [
+ "embed",
+ "bidi-override",
+ "isolate",
+ "plaintext",
+ "isolate-override",
+ ],
+ invalid_values: [
+ "auto",
+ "none",
+ "-moz-isolate",
+ "-moz-plaintext",
+ "-moz-isolate-override",
+ ],
+ },
+ "vertical-align": {
+ domProp: "verticalAlign",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["baseline"],
+ other_values: [
+ "sub",
+ "super",
+ "top",
+ "text-top",
+ "middle",
+ "bottom",
+ "text-bottom",
+ "-moz-middle-with-baseline",
+ "15%",
+ "3px",
+ "0.2em",
+ "-5px",
+ "-3%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "baseline-source": {
+ domProp: "baselineSource",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ["first", "last"],
+ invalid_values: [],
+ },
+ visibility: {
+ domProp: "visibility",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ initial_values: ["visible"],
+ other_values: ["hidden", "collapse"],
+ invalid_values: [],
+ },
+ "white-space": {
+ domProp: "whiteSpace",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["white-space-collapse", "text-wrap-mode"],
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["normal"],
+ other_values: [
+ "pre",
+ "nowrap",
+ "pre-wrap",
+ "pre-line",
+ "-moz-pre-space",
+ "break-spaces",
+ ],
+ invalid_values: [],
+ },
+ "white-space-collapse": {
+ domProp: "whiteSpaceCollapse",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["collapse"],
+ other_values: [
+ "preserve",
+ "preserve-breaks",
+ "preserve-spaces",
+ "break-spaces",
+ ],
+ invalid_values: ["normal", "auto"],
+ },
+ width: {
+ domProp: "width",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: {
+ // computed value tests for width test more with display:block
+ display: "block",
+ // add some margin to avoid the initial "auto" value getting
+ // resolved to the same length as the parent element.
+ "margin-left": "5px",
+ },
+ initial_values: [" auto"],
+ /* XXX these have prerequisites */
+ other_values: [
+ "15px",
+ "3em",
+ "15%",
+ // these three keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ // whether -moz-available computes to the initial value depends on
+ // the container size, and for the container size we're testing
+ // with, it does
+ // "-moz-available",
+ "3e1px",
+ "3e+1px",
+ "3e0px",
+ "3e+0px",
+ "3e-0px",
+ "3e-1px",
+ "3.2e1px",
+ "3.2e+1px",
+ "3.2e0px",
+ "3.2e+0px",
+ "3.2e-0px",
+ "3.2e-1px",
+ "3e1%",
+ "3e+1%",
+ "3e0%",
+ "3e+0%",
+ "3e-0%",
+ "3e-1%",
+ "3.2e1%",
+ "3.2e+1%",
+ "3.2e0%",
+ "3.2e+0%",
+ "3.2e-0%",
+ "3.2e-1%",
+ /* valid calc() values */
+ "calc(-2px)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(50% + 2px)",
+ "calc( 50% + 2px)",
+ "calc(50% + 2px )",
+ "calc( 50% + 2px )",
+ "calc(50% - -2px)",
+ "calc(2px - -50%)",
+ "calc(3*25px)",
+ "calc(3 *25px)",
+ "calc(3 * 25px)",
+ "calc(3* 25px)",
+ "calc(25px*3)",
+ "calc(25px *3)",
+ "calc(25px* 3)",
+ "calc(25px * 3)",
+ "calc(3*25px + 50%)",
+ "calc(50% - 3em + 2px)",
+ "calc(50% - (3em + 2px))",
+ "calc((50% - 3em) + 2px)",
+ "calc(2em)",
+ "calc(50%)",
+ "calc(50px/2)",
+ "calc(50px/(2 - 1))",
+ "calc(min(5px))",
+ "calc(min(5px,2em))",
+ "calc(max(5px))",
+ "calc(max(5px,2em))",
+ "min(5px)",
+ "min(5px,2em)",
+ "max(5px)",
+ "max(5px,2em)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: [
+ "none",
+ "-2px",
+ "content" /* (valid for 'flex-basis' but not 'width') */,
+ /* invalid calc() values */
+ "calc(50%+ 2px)",
+ "calc(50% +2px)",
+ "calc(50%+2px)",
+ "-moz-min()",
+ "calc(min())",
+ "-moz-max()",
+ "calc(max())",
+ "-moz-min(5px)",
+ "-moz-max(5px)",
+ "-moz-min(5px,2em)",
+ "-moz-max(5px,2em)",
+ /* If we ever support division by values, which is
+ * complicated for the reasons described in
+ * http://lists.w3.org/Archives/Public/www-style/2010Jan/0007.html
+ * , we should support all 4 of these as described in
+ * http://lists.w3.org/Archives/Public/www-style/2009Dec/0296.html
+ */
+ "calc((3em / 100%) * 3em)",
+ "calc(3em / 100% * 3em)",
+ "calc(3em * (3em / 100%))",
+ "calc(3em * 3em / 100%)",
+ ],
+ quirks_values: { 5: "5px" },
+ },
+ "will-change": {
+ domProp: "willChange",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "scroll-position",
+ "contents",
+ "transform",
+ "opacity",
+ "scroll-position, transform",
+ "transform, opacity",
+ "contents, transform",
+ "property-that-doesnt-exist-yet",
+ ],
+ invalid_values: [
+ "none",
+ "all",
+ "default",
+ "auto, scroll-position",
+ "scroll-position, auto",
+ "transform scroll-position",
+ ",",
+ "trailing,",
+ "will-change",
+ "transform, will-change",
+ ],
+ },
+ "word-break": {
+ domProp: "wordBreak",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["break-all", "keep-all"],
+ invalid_values: [],
+ },
+ "word-spacing": {
+ domProp: "wordSpacing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["normal", "0", "0px", "-0em", "calc(-0px)", "calc(0em)"],
+ other_values: [
+ "1em",
+ "2px",
+ "-3px",
+ "0%",
+ "50%",
+ "-120%",
+ "calc(1em)",
+ "calc(1em + 3px)",
+ "calc(15px / 2)",
+ "calc(15px/2)",
+ "calc(-2em)",
+ "calc(0% + 0px)",
+ "calc(-10%/2 - 1em)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "overflow-wrap": {
+ domProp: "overflowWrap",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["break-word"],
+ invalid_values: [],
+ },
+ hyphens: {
+ domProp: "hyphens",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["manual"],
+ other_values: ["none", "auto"],
+ invalid_values: [],
+ },
+ "z-index": {
+ domProp: "zIndex",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* XXX requires position */
+ initial_values: ["auto"],
+ other_values: ["0", "3", "-7000", "12000"],
+ invalid_values: ["3.0", "17.5", "3e1"],
+ },
+ "clip-path": {
+ domProp: "clipPath",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "path(nonzero, 'M 10 10 h 100 v 100 h-100 v-100 z')",
+ "path(evenodd, 'M 10 10 h 100 v 100 h-100 v-100 z')",
+ "path('M10,30A20,20 0,0,1 50,30A20,20 0,0,1 90,30Q90,60 50,90Q10,60 10,30z')",
+ "url(#mypath)",
+ "url('404.svg#mypath')",
+ "url(#my-clip-path)",
+ "margin-box",
+ ]
+ .concat(basicShapeSVGBoxValues)
+ .concat(basicShapeOtherValues)
+ .concat(basicShapeOtherValuesWithFillRule)
+ .concat(basicShapeXywhRectValues),
+ invalid_values: [
+ "path(nonzero)",
+ "path(abs, 'M 10 10 L 10 10 z')",
+ "path(evenodd, '')",
+ "path('')",
+ ].concat(basicShapeInvalidValues),
+ unbalanced_values: basicShapeUnbalancedValues,
+ },
+ "clip-rule": {
+ domProp: "clipRule",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["nonzero"],
+ other_values: ["evenodd"],
+ invalid_values: [],
+ },
+ "color-interpolation": {
+ domProp: "colorInterpolation",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["sRGB"],
+ other_values: ["auto", "linearRGB"],
+ invalid_values: [],
+ },
+ "color-interpolation-filters": {
+ domProp: "colorInterpolationFilters",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["linearRGB"],
+ other_values: ["sRGB", "auto"],
+ invalid_values: [],
+ },
+ "dominant-baseline": {
+ domProp: "dominantBaseline",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "ideographic",
+ "alphabetic",
+ "hanging",
+ "mathematical",
+ "central",
+ "middle",
+ "text-after-edge",
+ "text-before-edge",
+ ],
+ invalid_values: [],
+ },
+ fill: {
+ domProp: "fill",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ prerequisites: { color: "blue" },
+ initial_values: ["black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)"],
+ other_values: [
+ "green",
+ "#fc3",
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "none",
+ "currentColor",
+ "context-fill",
+ "context-stroke",
+ ],
+ invalid_values: ["000000", "ff00ff", "url('#myserver') rgb(0, rubbish, 0)"],
+ },
+ "fill-opacity": {
+ domProp: "fillOpacity",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["1", "2.8", "1.000", "300%"],
+ other_values: [
+ "0",
+ "0.3",
+ "-7.3",
+ "-100%",
+ "50%",
+ "context-fill-opacity",
+ "context-stroke-opacity",
+ ],
+ invalid_values: [],
+ },
+ "fill-rule": {
+ domProp: "fillRule",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["nonzero"],
+ other_values: ["evenodd"],
+ invalid_values: [],
+ },
+ filter: {
+ domProp: "filter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ // SVG reference filters
+ "url(#my-filter)",
+ "url(#my-filter-1) url(#my-filter-2)",
+
+ // Filter functions
+ "opacity(50%) saturate(1.0)",
+ "invert(50%) sepia(0.1) brightness(90%)",
+
+ // Mixed SVG reference filters and filter functions
+ "grayscale(1) url(#my-filter-1)",
+ "url(#my-filter-1) brightness(50%) contrast(0.9)",
+
+ // Bad URLs
+ "url('badscheme:badurl')",
+ "blur(3px) url('badscheme:badurl') grayscale(50%)",
+
+ "blur()",
+ "blur(0)",
+ "blur(0px)",
+ "blur(0.5px)",
+ "blur(3px)",
+ "blur(100px)",
+ "blur(0.1em)",
+ "blur(calc(-1px))", // Parses and becomes blur(0px).
+ "blur(calc(0px))",
+ "blur(calc(5px))",
+ "blur(calc(2 * 5px))",
+
+ "brightness()",
+ "brightness(0)",
+ "brightness(50%)",
+ "brightness(1)",
+ "brightness(1.0)",
+ "brightness(2)",
+ "brightness(350%)",
+ "brightness(4.567)",
+
+ "contrast()",
+ "contrast(0)",
+ "contrast(50%)",
+ "contrast(1)",
+ "contrast(1.0)",
+ "contrast(2)",
+ "contrast(350%)",
+ "contrast(4.567)",
+
+ "drop-shadow(2px 2px)",
+ "drop-shadow(2px 2px 1px)",
+ "drop-shadow(2px 2px green)",
+ "drop-shadow(2px 2px 1px green)",
+ "drop-shadow(green 2px 2px)",
+ "drop-shadow(green 2px 2px 1px)",
+ "drop-shadow(currentColor 3px 3px)",
+ "drop-shadow(2px 2px calc(-5px))" /* clamped */,
+ "drop-shadow(calc(3em - 2px) 2px green)",
+ "drop-shadow(green calc(3em - 2px) 2px)",
+ "drop-shadow(2px calc(2px + 0.2em))",
+ "drop-shadow(blue 2px calc(2px + 0.2em))",
+ "drop-shadow(2px calc(2px + 0.2em) blue)",
+ "drop-shadow(calc(-2px) calc(-2px))",
+ "drop-shadow(-2px -2px)",
+ "drop-shadow(calc(2px) calc(2px))",
+ "drop-shadow(calc(2px) calc(2px) calc(2px))",
+
+ "grayscale()",
+ "grayscale(0)",
+ "grayscale(50%)",
+ "grayscale(1)",
+ "grayscale(1.0)",
+ "grayscale(2)",
+ "grayscale(350%)",
+ "grayscale(4.567)",
+
+ "hue-rotate()",
+ "hue-rotate(0)",
+ "hue-rotate(0deg)",
+ "hue-rotate(90deg)",
+ "hue-rotate(540deg)",
+ "hue-rotate(-90deg)",
+ "hue-rotate(10grad)",
+ "hue-rotate(1.6rad)",
+ "hue-rotate(-1.6rad)",
+ "hue-rotate(0.5turn)",
+ "hue-rotate(-2turn)",
+
+ "invert()",
+ "invert(0)",
+ "invert(50%)",
+ "invert(1)",
+ "invert(1.0)",
+ "invert(2)",
+ "invert(350%)",
+ "invert(4.567)",
+
+ "opacity()",
+ "opacity(0)",
+ "opacity(50%)",
+ "opacity(1)",
+ "opacity(1.0)",
+ "opacity(2)",
+ "opacity(350%)",
+ "opacity(4.567)",
+
+ "saturate()",
+ "saturate(0)",
+ "saturate(50%)",
+ "saturate(1)",
+ "saturate(1.0)",
+ "saturate(2)",
+ "saturate(350%)",
+ "saturate(4.567)",
+
+ "sepia()",
+ "sepia(0)",
+ "sepia(50%)",
+ "sepia(1)",
+ "sepia(1.0)",
+ "sepia(2)",
+ "sepia(350%)",
+ "sepia(4.567)",
+ ],
+ invalid_values: [
+ // none
+ "none none",
+ "url(#my-filter) none",
+ "none url(#my-filter)",
+ "blur(2px) none url(#my-filter)",
+
+ // Nested filters
+ "grayscale(invert(1.0))",
+
+ // Comma delimited filters
+ "url(#my-filter),",
+ "invert(50%), url(#my-filter), brightness(90%)",
+
+ // Test the following situations for each filter function:
+ // - Invalid number of arguments
+ // - Comma delimited arguments
+ // - Wrong argument type
+ // - Argument value out of range
+ "blur(3px 5px)",
+ "blur(3px,)",
+ "blur(3px, 5px)",
+ "blur(#my-filter)",
+ "blur(0.5)",
+ "blur(50%)",
+ "blur(calc(0))", // Unitless zero in calc is not a valid length.
+ "blur(calc(0.1))",
+ "blur(calc(10%))",
+ "blur(calc(20px - 5%))",
+ "blur(-3px)",
+
+ "brightness(0.5 0.5)",
+ "brightness(0.5,)",
+ "brightness(0.5, 0.5)",
+ "brightness(#my-filter)",
+ "brightness(10px)",
+ "brightness(-1)",
+
+ "contrast(0.5 0.5)",
+ "contrast(0.5,)",
+ "contrast(0.5, 0.5)",
+ "contrast(#my-filter)",
+ "contrast(10px)",
+ "contrast(-1)",
+
+ "drop-shadow()",
+ "drop-shadow(3% 3%)",
+ "drop-shadow(2px 2px -5px)",
+ "drop-shadow(2px 2px 2px 2px)",
+ "drop-shadow(2px 2px, none)",
+ "drop-shadow(none, 2px 2px)",
+ "drop-shadow(inherit, 2px 2px)",
+ "drop-shadow(2px 2px, inherit)",
+ "drop-shadow(2 2px)",
+ "drop-shadow(2px 2)",
+ "drop-shadow(2px 2px 2)",
+ "drop-shadow(2px 2px 2px 2)",
+ "drop-shadow(calc(2px) calc(2px) calc(2px) calc(2px))",
+ "drop-shadow(green 2px 2px, blue 1px 3px 4px)",
+ "drop-shadow(blue 2px 2px, currentColor 1px 2px)",
+ "drop-shadow(unset, 2px 2px)",
+ "drop-shadow(2px 2px, unset)",
+
+ "grayscale(0.5 0.5)",
+ "grayscale(0.5,)",
+ "grayscale(0.5, 0.5)",
+ "grayscale(#my-filter)",
+ "grayscale(10px)",
+ "grayscale(-1)",
+
+ "hue-rotate(0.5 0.5)",
+ "hue-rotate(0.5,)",
+ "hue-rotate(0.5, 0.5)",
+ "hue-rotate(#my-filter)",
+ "hue-rotate(10px)",
+ "hue-rotate(-1)",
+ "hue-rotate(45deg,)",
+
+ "invert(0.5 0.5)",
+ "invert(0.5,)",
+ "invert(0.5, 0.5)",
+ "invert(#my-filter)",
+ "invert(10px)",
+ "invert(-1)",
+
+ "opacity(0.5 0.5)",
+ "opacity(0.5,)",
+ "opacity(0.5, 0.5)",
+ "opacity(#my-filter)",
+ "opacity(10px)",
+ "opacity(-1)",
+
+ "saturate(0.5 0.5)",
+ "saturate(0.5,)",
+ "saturate(0.5, 0.5)",
+ "saturate(#my-filter)",
+ "saturate(10px)",
+ "saturate(-1)",
+
+ "sepia(0.5 0.5)",
+ "sepia(0.5,)",
+ "sepia(0.5, 0.5)",
+ "sepia(#my-filter)",
+ "sepia(10px)",
+ "sepia(-1)",
+ ],
+ },
+ "flood-color": {
+ domProp: "floodColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "blue" },
+ initial_values: ["black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)"],
+ other_values: ["green", "#fc3", "currentColor"],
+ invalid_values: [
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "000000",
+ "ff00ff",
+ ],
+ },
+ "flood-opacity": {
+ domProp: "floodOpacity",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["1", "2.8", "1.000", "300%"],
+ other_values: ["0", "0.3", "-7.3", "-100%", "50%"],
+ invalid_values: [],
+ },
+ "image-orientation": {
+ domProp: "imageOrientation",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["from-image"],
+ other_values: ["none"],
+ invalid_values: ["0", "0deg"],
+ },
+ "image-rendering": {
+ domProp: "imageRendering",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "optimizeSpeed",
+ "optimizeQuality",
+ "-moz-crisp-edges",
+ "crisp-edges",
+ "smooth",
+ "pixelated",
+ ],
+ invalid_values: [],
+ },
+ isolation: {
+ domProp: "isolation",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["isolate"],
+ invalid_values: [],
+ },
+ "lighting-color": {
+ domProp: "lightingColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "blue" },
+ initial_values: [
+ "white",
+ "#fff",
+ "#ffffff",
+ "rgb(255,255,255)",
+ "rgba(255,255,255,1.0)",
+ "rgba(255,255,255,42.0)",
+ ],
+ other_values: ["green", "#fc3", "currentColor"],
+ invalid_values: [
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "000000",
+ "ff00ff",
+ ],
+ },
+ marker: {
+ domProp: "marker",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["marker-start", "marker-mid", "marker-end"],
+ initial_values: ["none"],
+ other_values: ["url(#mysym)"],
+ invalid_values: [
+ "none none",
+ "url(#mysym) url(#mysym)",
+ "none url(#mysym)",
+ "url(#mysym) none",
+ ],
+ },
+ "marker-end": {
+ domProp: "markerEnd",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["url(#mysym)"],
+ invalid_values: [],
+ },
+ "marker-mid": {
+ domProp: "markerMid",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["url(#mysym)"],
+ invalid_values: [],
+ },
+ "marker-start": {
+ domProp: "markerStart",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["url(#mysym)"],
+ invalid_values: [],
+ },
+ "mix-blend-mode": {
+ domProp: "mixBlendMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "multiply",
+ "screen",
+ "overlay",
+ "darken",
+ "lighten",
+ "color-dodge",
+ "color-burn",
+ "hard-light",
+ "soft-light",
+ "difference",
+ "exclusion",
+ "hue",
+ "saturation",
+ "color",
+ "luminosity",
+ "plus-lighter",
+ ],
+ invalid_values: [],
+ },
+ "shape-image-threshold": {
+ domProp: "shapeImageThreshold",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["0", "0.0000", "-3", "0%", "-100%"],
+ other_values: [
+ "0.4",
+ "1",
+ "17",
+ "397.376",
+ "3e1",
+ "3e+1",
+ "3e-1",
+ "3e0",
+ "3e+0",
+ "3e-0",
+ "50%",
+ "300%",
+ ],
+ invalid_values: ["0px", "1px", "default", "auto"],
+ },
+ "shape-margin": {
+ domProp: "shapeMargin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["0"],
+ other_values: ["2px", "2%", "1em", "calc(1px + 1em)", "calc(1%)"],
+ invalid_values: ["-1px", "auto", "none", "1px 1px", "-1%"],
+ },
+ "shape-outside": {
+ domProp: "shapeOutside",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["none"],
+ other_values: ["url(#my-shape-outside)", "margin-box"].concat(
+ basicShapeOtherValues,
+ basicShapeOtherValuesWithFillRule,
+ validNonUrlImageValues
+ ),
+ invalid_values: [].concat(
+ basicShapeSVGBoxValues,
+ basicShapeInvalidValues,
+ invalidNonUrlImageValues
+ ),
+ unbalanced_values: [].concat(
+ basicShapeUnbalancedValues,
+ unbalancedGradientAndElementValues
+ ),
+ },
+ "shape-rendering": {
+ domProp: "shapeRendering",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["optimizeSpeed", "crispEdges", "geometricPrecision"],
+ invalid_values: [],
+ },
+ "stop-color": {
+ domProp: "stopColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "blue" },
+ initial_values: ["black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)"],
+ other_values: ["green", "#fc3", "currentColor"],
+ invalid_values: [
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "000000",
+ "ff00ff",
+ ],
+ },
+ "stop-opacity": {
+ domProp: "stopOpacity",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["1", "2.8", "1.000", "300%"],
+ other_values: ["0", "0.3", "-7.3", "-100%", "50%"],
+ invalid_values: [],
+ },
+ stroke: {
+ domProp: "stroke",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["none"],
+ other_values: [
+ "black",
+ "#000",
+ "#000000",
+ "rgb(0,0,0)",
+ "rgba(0,0,0,1)",
+ "green",
+ "#fc3",
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "currentColor",
+ "context-fill",
+ "context-stroke",
+ ],
+ invalid_values: ["000000", "ff00ff"],
+ },
+ "stroke-dasharray": {
+ domProp: "strokeDasharray",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["none"],
+ other_values: [
+ "5px,3px,2px",
+ "5px 3px 2px",
+ " 5px ,3px\t, 2px ",
+ "1px",
+ "5%",
+ "3em",
+ "0.0002",
+ "context-value",
+ ],
+ invalid_values: ["-5px,3px,2px", "5px,3px,-2px"],
+ },
+ "stroke-dashoffset": {
+ domProp: "strokeDashoffset",
+ inherited: true,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0", "-0px", "0em"],
+ other_values: ["3px", "3%", "1em", "0.0002", "context-value"],
+ invalid_values: [],
+ },
+ "stroke-linecap": {
+ domProp: "strokeLinecap",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["butt"],
+ other_values: ["round", "square"],
+ invalid_values: [],
+ },
+ "stroke-linejoin": {
+ domProp: "strokeLinejoin",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["miter"],
+ other_values: ["round", "bevel"],
+ invalid_values: [],
+ },
+ "stroke-miterlimit": {
+ domProp: "strokeMiterlimit",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["4"],
+ other_values: ["0", "0.9", "1", "7", "5000", "1.1"],
+ invalid_values: ["-1", "3px", "-0.3"],
+ },
+ "stroke-opacity": {
+ domProp: "strokeOpacity",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["1", "2.8", "1.000", "300%"],
+ other_values: [
+ "0",
+ "0.3",
+ "-7.3",
+ "-100%",
+ "50%",
+ "context-fill-opacity",
+ "context-stroke-opacity",
+ ],
+ invalid_values: [],
+ },
+ "stroke-width": {
+ domProp: "strokeWidth",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["1px"],
+ other_values: [
+ "0",
+ "0px",
+ "-0em",
+ "17px",
+ "0.2em",
+ "0.0002",
+ "context-value",
+ ],
+ invalid_values: ["-0.1px", "-3px"],
+ },
+ x: {
+ domProp: "x",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["-1em", "17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "context-value", "0.0002"],
+ },
+ y: {
+ domProp: "y",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["-1em", "17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "context-value", "0.0002"],
+ },
+ cx: {
+ domProp: "cx",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["-1em", "17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "context-value", "0.0002"],
+ },
+ cy: {
+ domProp: "cy",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["-1em", "17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "context-value", "0.0002"],
+ },
+ r: {
+ domProp: "r",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "-1", "-1.5px", "0.0002"],
+ },
+ rx: {
+ domProp: "rx",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["17px", "0.2em", "23.4%"],
+ invalid_values: ["hello", "-12px", "0.0002"],
+ },
+ ry: {
+ domProp: "ry",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["17px", "0.2em", "23.4%"],
+ invalid_values: ["hello", "-1.3px", "0.0002"],
+ },
+ "text-anchor": {
+ domProp: "textAnchor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["start"],
+ other_values: ["middle", "end"],
+ invalid_values: [],
+ },
+ "text-rendering": {
+ domProp: "textRendering",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["auto"],
+ other_values: ["optimizeSpeed", "optimizeLegibility", "geometricPrecision"],
+ invalid_values: [],
+ },
+ "vector-effect": {
+ domProp: "vectorEffect",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["none"],
+ other_values: ["non-scaling-stroke"],
+ invalid_values: [],
+ },
+ "-moz-window-dragging": {
+ domProp: "MozWindowDragging",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["default"],
+ other_values: ["drag", "no-drag"],
+ invalid_values: ["none"],
+ },
+ "accent-color": {
+ domProp: "accentColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "black" },
+ initial_values: ["auto"],
+ other_values: [
+ "currentcolor",
+ "black",
+ "green",
+ "transparent",
+ "rgba(128,128,128,.5)",
+ "#123",
+ ],
+ invalid_values: ["#0", "#00", "#00000", "cc00ff"],
+ },
+ "align-content": {
+ domProp: "alignContent",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "start",
+ "end",
+ "flex-start",
+ "flex-end",
+ "center",
+ "space-between",
+ "space-around",
+ "space-evenly",
+ "first baseline",
+ "last baseline",
+ "baseline",
+ "stretch",
+ "safe start",
+ "unsafe end",
+ "safe end",
+ ],
+ invalid_values: [
+ "none",
+ "5",
+ "self-end",
+ "safe",
+ "normal unsafe",
+ "unsafe safe",
+ "safe baseline",
+ "baseline unsafe",
+ "baseline end",
+ "end normal",
+ "safe end unsafe start",
+ "safe end unsafe",
+ "normal safe start",
+ "unsafe end start",
+ "end start safe",
+ "space-between unsafe",
+ "stretch safe",
+ "auto",
+ "first",
+ "last",
+ "left",
+ "right",
+ ],
+ },
+ "align-items": {
+ domProp: "alignItems",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "end",
+ "flex-start",
+ "flex-end",
+ "self-start",
+ "self-end",
+ "center",
+ "stretch",
+ "first baseline",
+ "last baseline",
+ "baseline",
+ "start",
+ "unsafe center",
+ "safe center",
+ ],
+ invalid_values: [
+ "space-between",
+ "abc",
+ "5%",
+ "legacy",
+ "legacy end",
+ "end legacy",
+ "unsafe",
+ "unsafe baseline",
+ "normal unsafe",
+ "safe left unsafe",
+ "safe stretch",
+ "end end",
+ "auto",
+ "left",
+ "right",
+ ],
+ },
+ "align-self": {
+ domProp: "alignSelf",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "normal",
+ "start",
+ "flex-start",
+ "flex-end",
+ "center",
+ "stretch",
+ "first baseline",
+ "last baseline",
+ "baseline",
+ "unsafe center",
+ "self-start",
+ "safe self-end",
+ ],
+ invalid_values: [
+ "space-between",
+ "abc",
+ "30px",
+ "stretch safe",
+ "safe",
+ "left",
+ "right",
+ ],
+ },
+ "justify-content": {
+ domProp: "justifyContent",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "start",
+ "end",
+ "flex-start",
+ "flex-end",
+ "center",
+ "left",
+ "right",
+ "space-between",
+ "space-around",
+ "space-evenly",
+ "stretch",
+ "safe start",
+ "unsafe end",
+ "safe end",
+ ],
+ invalid_values: [
+ "30px",
+ "5%",
+ "self-end",
+ "safe",
+ "normal unsafe",
+ "unsafe safe",
+ "safe baseline",
+ "baseline unsafe",
+ "baseline end",
+ "normal end",
+ "safe end unsafe start",
+ "safe end unsafe",
+ "normal safe start",
+ "unsafe end start",
+ "end start safe",
+ "space-around unsafe",
+ "safe stretch",
+ "auto",
+ "first",
+ "last",
+ ],
+ },
+ "justify-items": {
+ domProp: "justifyItems",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["legacy", "normal"],
+ other_values: [
+ "end",
+ "flex-start",
+ "flex-end",
+ "self-start",
+ "self-end",
+ "center",
+ "left",
+ "right",
+ "stretch",
+ "start",
+ "legacy left",
+ "right legacy",
+ "legacy center",
+ "unsafe right",
+ "unsafe left",
+ "safe right",
+ "safe center",
+ ],
+ invalid_values: [
+ "auto",
+ "space-between",
+ "abc",
+ "30px",
+ "legacy start",
+ "end legacy",
+ "legacy baseline",
+ "legacy legacy",
+ "unsafe",
+ "safe legacy left",
+ "legacy left safe",
+ "legacy safe left",
+ "safe left legacy",
+ "legacy left legacy",
+ "baseline unsafe",
+ "safe unsafe",
+ "safe left unsafe",
+ "safe stretch",
+ "last",
+ ],
+ },
+ "justify-self": {
+ domProp: "justifySelf",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "normal",
+ "start",
+ "end",
+ "flex-start",
+ "flex-end",
+ "self-start",
+ "self-end",
+ "center",
+ "left",
+ "right",
+ "stretch",
+ "unsafe left",
+ "baseline",
+ "last baseline",
+ "first baseline",
+ "unsafe right",
+ "safe right",
+ "safe center",
+ ],
+ invalid_values: [
+ "space-between",
+ "abc",
+ "30px",
+ "none",
+ "first",
+ "last",
+ "legacy left",
+ "right legacy",
+ "baseline first",
+ "baseline last",
+ ],
+ },
+ "place-content": {
+ domProp: "placeContent",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["align-content", "justify-content"],
+ initial_values: ["normal"],
+ other_values: [
+ "normal start",
+ "baseline end",
+ "end end",
+ "space-between flex-end",
+ "last baseline start",
+ "space-evenly",
+ "flex-start",
+ "end",
+ "unsafe start",
+ "safe center",
+ "baseline",
+ "last baseline",
+ ],
+ invalid_values: [
+ "none",
+ "center safe",
+ "right / end",
+ "left",
+ "right",
+ "left left",
+ "right right",
+ ],
+ },
+ "place-items": {
+ domProp: "placeItems",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["align-items", "justify-items"],
+ initial_values: ["normal"],
+ other_values: [
+ "normal center",
+ "baseline end",
+ "end legacy",
+ "end",
+ "flex-end left",
+ "last baseline start",
+ "stretch",
+ "safe center",
+ "end legacy left",
+ ],
+ invalid_values: [
+ "space-between",
+ "start space-evenly",
+ "none",
+ "end/end",
+ "center safe",
+ "auto start",
+ "left",
+ "right",
+ "left left",
+ "right right",
+ ],
+ },
+ "place-self": {
+ domProp: "placeSelf",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["align-self", "justify-self"],
+ initial_values: ["auto"],
+ other_values: [
+ "normal start",
+ "first baseline end",
+ "end auto",
+ "end",
+ "normal",
+ "baseline start",
+ "baseline",
+ "start baseline",
+ "self-end left",
+ "last baseline start",
+ "stretch",
+ ],
+ invalid_values: [
+ "space-between",
+ "start space-evenly",
+ "none",
+ "end safe",
+ "auto legacy left",
+ "legacy left",
+ "auto/auto",
+ "left",
+ "right",
+ "left left",
+ "right right",
+ ],
+ },
+ flex: {
+ domProp: "flex",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["flex-grow", "flex-shrink", "flex-basis"],
+ initial_values: ["0 1 auto", "auto 0 1", "0 auto", "auto 0"],
+ other_values: [
+ "none",
+ "1",
+ "0",
+ "0 1",
+ "0.5",
+ "1.2 3.4",
+ "0 0 0",
+ "0 0 0px",
+ "0px 0 0",
+ "5px 0 0",
+ "2 auto",
+ "auto 4",
+ "auto 5.6 7.8",
+ "max-content",
+ "1 max-content",
+ "1 2 max-content",
+ "max-content 1",
+ "max-content 1 2",
+ "-0",
+ ],
+ invalid_values: [
+ "1 2px 3",
+ "1 auto 3",
+ "1px 2 3px",
+ "1px 2 3 4px",
+ "-1",
+ "1 -1",
+ "0 1 calc(0px + rubbish)",
+ ],
+ },
+ "flex-basis": {
+ domProp: "flexBasis",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [" auto"],
+ // NOTE: Besides "content", this is cribbed directly from the "width"
+ // chunk, since this property takes the exact same values as width
+ // (plus 'content' & with different semantics on 'auto').
+ // XXXdholbert (Maybe these should get separated out into
+ // a reusable array defined at the top of this file?)
+ other_values: [
+ "content",
+ "15px",
+ "3em",
+ "15%",
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // valid calc() values
+ "calc(-2px)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(50% + 2px)",
+ "calc( 50% + 2px)",
+ "calc(50% + 2px )",
+ "calc( 50% + 2px )",
+ "calc(50% - -2px)",
+ "calc(2px - -50%)",
+ "calc(3*25px)",
+ "calc(3 *25px)",
+ "calc(3 * 25px)",
+ "calc(3* 25px)",
+ "calc(25px*3)",
+ "calc(25px *3)",
+ "calc(25px* 3)",
+ "calc(25px * 3)",
+ "calc(3*25px + 50%)",
+ "calc(50% - 3em + 2px)",
+ "calc(50% - (3em + 2px))",
+ "calc((50% - 3em) + 2px)",
+ "calc(2em)",
+ "calc(50%)",
+ "calc(50px/2)",
+ "calc(50px/(2 - 1))",
+ "calc(min(5px))",
+ "calc(min(5px,2em))",
+ "calc(max(5px))",
+ "calc(max(5px,2em))",
+ "min(5px)",
+ "min(5px,2em)",
+ "max(5px)",
+ "max(5px,2em)",
+ ],
+ invalid_values: [
+ "none",
+ "-2px",
+ // invalid calc() values
+ "calc(50%+ 2px)",
+ "calc(50% +2px)",
+ "calc(50%+2px)",
+ "-moz-min()",
+ "calc(min())",
+ "-moz-max()",
+ "calc(max())",
+ "-moz-min(5px)",
+ "-moz-max(5px)",
+ "-moz-min(5px,2em)",
+ "-moz-max(5px,2em)",
+ // If we ever support division by values, which is
+ // complicated for the reasons described in
+ // http://lists.w3.org/Archives/Public/www-style/2010Jan/0007.html
+ // , we should support all 4 of these as described in
+ // http://lists.w3.org/Archives/Public/www-style/2009Dec/0296.html
+ "calc((3em / 100%) * 3em)",
+ "calc(3em / 100% * 3em)",
+ "calc(3em * (3em / 100%))",
+ "calc(3em * 3em / 100%)",
+ ],
+ },
+ "flex-direction": {
+ domProp: "flexDirection",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["row"],
+ other_values: ["row-reverse", "column", "column-reverse"],
+ invalid_values: ["10px", "30%", "justify", "column wrap"],
+ },
+ "flex-flow": {
+ domProp: "flexFlow",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["flex-direction", "flex-wrap"],
+ initial_values: ["row nowrap", "nowrap row", "row", "nowrap"],
+ other_values: [
+ // only specifying one property:
+ "column",
+ "wrap",
+ "wrap-reverse",
+ // specifying both properties, 'flex-direction' first:
+ "row wrap",
+ "row wrap-reverse",
+ "column wrap",
+ "column wrap-reverse",
+ // specifying both properties, 'flex-wrap' first:
+ "wrap row",
+ "wrap column",
+ "wrap-reverse row",
+ "wrap-reverse column",
+ ],
+ invalid_values: [
+ // specifying flex-direction twice (invalid):
+ "row column",
+ "row column nowrap",
+ "row nowrap column",
+ "nowrap row column",
+ // specifying flex-wrap twice (invalid):
+ "nowrap wrap-reverse",
+ "nowrap wrap-reverse row",
+ "nowrap row wrap-reverse",
+ "row nowrap wrap-reverse",
+ // Invalid data-type / invalid keyword type:
+ "1px",
+ "5%",
+ "justify",
+ "none",
+ ],
+ },
+ "flex-grow": {
+ domProp: "flexGrow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["3", "1", "1.0", "2.5", "123"],
+ invalid_values: ["0px", "-5", "1%", "3em", "stretch", "auto"],
+ },
+ "flex-shrink": {
+ domProp: "flexShrink",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["1"],
+ other_values: ["3", "0", "0.0", "2.5", "123"],
+ invalid_values: ["0px", "-5", "1%", "3em", "stretch", "auto"],
+ },
+ "flex-wrap": {
+ domProp: "flexWrap",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["nowrap"],
+ other_values: ["wrap", "wrap-reverse"],
+ invalid_values: ["10px", "30%", "justify", "column wrap", "auto"],
+ },
+ order: {
+ domProp: "order",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["1", "99999", "-1", "-50"],
+ invalid_values: ["0px", "1.0", "1.", "1%", "0.2", "3em", "stretch"],
+ },
+
+ // Aliases
+ "word-wrap": {
+ domProp: "wordWrap",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "overflow-wrap",
+ subproperties: ["overflow-wrap"],
+ },
+ "-moz-tab-size": {
+ domProp: "MozTabSize",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "tab-size",
+ subproperties: ["tab-size"],
+ },
+ "-moz-border-image": {
+ domProp: "MozBorderImage",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-image",
+ subproperties: [
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat",
+ ],
+ },
+ "-moz-font-feature-settings": {
+ domProp: "MozFontFeatureSettings",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "font-feature-settings",
+ subproperties: ["font-feature-settings"],
+ },
+ "-moz-font-language-override": {
+ domProp: "MozFontLanguageOverride",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "font-language-override",
+ subproperties: ["font-language-override"],
+ },
+ "-moz-hyphens": {
+ domProp: "MozHyphens",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "hyphens",
+ subproperties: ["hyphens"],
+ },
+ // vertical text properties
+ "writing-mode": {
+ domProp: "writingMode",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["horizontal-tb", "lr", "lr-tb", "rl", "rl-tb"],
+ other_values: [
+ "vertical-lr",
+ "vertical-rl",
+ "sideways-rl",
+ "sideways-lr",
+ "tb",
+ "tb-rl",
+ ],
+ invalid_values: ["10px", "30%", "justify", "auto", "1em"],
+ },
+ "text-orientation": {
+ domProp: "textOrientation",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["mixed"],
+ other_values: [
+ "upright",
+ "sideways",
+ "sideways-right",
+ ] /* sideways-right alias for backward compatibility */,
+ invalid_values: [
+ "none",
+ "3em",
+ "sideways-left",
+ ] /* sideways-left removed from CSS Writing Modes */,
+ },
+ "block-size": {
+ domProp: "blockSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["auto"],
+ prerequisites: { display: "block" },
+ other_values: [
+ "15px",
+ "3em",
+ "15%",
+ // These keywords are treated as initial value.
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ },
+ "border-block": {
+ domProp: "borderBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-block-start-color",
+ "border-block-start-style",
+ "border-block-start-width",
+ "border-block-end-color",
+ "border-block-end-style",
+ "border-block-end-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-block-end": {
+ domProp: "borderBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-block-end-color",
+ "border-block-end-style",
+ "border-block-end-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-block-color": {
+ domProp: "borderBlockColor",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-block-start-color", "border-block-end-color"],
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5) blue", "blue transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-block-end-color": {
+ domProp: "borderBlockEndColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-block-style": {
+ domProp: "borderBlockStyle",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-block-start-style", "border-block-end-style"],
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed solid",
+ "solid dotted",
+ "double double",
+ "inset outset",
+ "inset double",
+ "none groove",
+ "ridge none",
+ ],
+ invalid_values: [],
+ },
+ "border-block-end-style": {
+ domProp: "borderBlockEndStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-block-width": {
+ domProp: "borderBlockWidth",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-block-start-width", "border-block-end-width"],
+ prerequisites: { "border-style": "solid" },
+ initial_values: ["medium", "3px", "medium medium"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(2px) thin",
+ "calc(-2px)",
+ "calc(-2px) thick",
+ "calc(0em)",
+ "medium calc(0em)",
+ "calc(0px)",
+ "1px calc(0px)",
+ "calc(5em)",
+ "1em calc(5em)",
+ ],
+ invalid_values: ["5%", "5", "5 thin", "thin 5%", "blue", "solid"],
+ },
+ "border-block-end-width": {
+ domProp: "borderBlockEndWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { "border-block-end-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ "border-block-start": {
+ domProp: "borderBlockStart",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-block-start-color",
+ "border-block-start-style",
+ "border-block-start-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-block-start-color": {
+ domProp: "borderBlockStartColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-block-start-style": {
+ domProp: "borderBlockStartStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-block-start-width": {
+ domProp: "borderBlockStartWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { "border-block-start-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ "-moz-border-end": {
+ domProp: "MozBorderEnd",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-inline-end",
+ subproperties: [
+ "-moz-border-end-color",
+ "-moz-border-end-style",
+ "-moz-border-end-width",
+ ],
+ },
+ "-moz-border-end-color": {
+ domProp: "MozBorderEndColor",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-end-color",
+ subproperties: ["border-inline-end-color"],
+ },
+ "-moz-border-end-style": {
+ domProp: "MozBorderEndStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-end-style",
+ subproperties: ["border-inline-end-style"],
+ },
+ "-moz-border-end-width": {
+ domProp: "MozBorderEndWidth",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-end-width",
+ subproperties: ["border-inline-end-width"],
+ },
+ "-moz-border-start": {
+ domProp: "MozBorderStart",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-inline-start",
+ subproperties: [
+ "-moz-border-start-color",
+ "-moz-border-start-style",
+ "-moz-border-start-width",
+ ],
+ },
+ "-moz-border-start-color": {
+ domProp: "MozBorderStartColor",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-start-color",
+ subproperties: ["border-inline-start-color"],
+ },
+ "-moz-border-start-style": {
+ domProp: "MozBorderStartStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-start-style",
+ subproperties: ["border-inline-start-style"],
+ },
+ "-moz-border-start-width": {
+ domProp: "MozBorderStartWidth",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-start-width",
+ subproperties: ["border-inline-start-width"],
+ },
+ "inline-size": {
+ domProp: "inlineSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["auto"],
+ prerequisites: {
+ display: "block",
+ // add some margin to avoid the initial "auto" value getting
+ // resolved to the same length as the parent element.
+ "margin-left": "5px",
+ },
+ other_values: [
+ "15px",
+ "3em",
+ "15%",
+ // these three keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ // whether -moz-available computes to the initial value depends on
+ // the container size, and for the container size we're testing
+ // with, it does
+ // "-moz-available",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ },
+ "margin-block": {
+ domProp: "marginBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["margin-block-start", "margin-block-end"],
+ initial_values: ["0", "0px 0em"],
+ other_values: [
+ "1px",
+ "3em 1%",
+ "5%",
+ "calc(2px) 1%",
+ "calc(-2px) 1%",
+ "calc(50%) 1%",
+ "calc(3*25px) calc(2px)",
+ "calc(25px*3) 1em",
+ "calc(3*25px + 50%) calc(3*25px - 50%)",
+ ],
+ invalid_values: [
+ "5",
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "margin-block-end": {
+ domProp: "marginBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "margin-block-start": {
+ domProp: "marginBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "-moz-margin-end": {
+ domProp: "MozMarginEnd",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "margin-inline-end",
+ subproperties: ["margin-inline-end"],
+ },
+ "-moz-margin-start": {
+ domProp: "MozMarginStart",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "margin-inline-start",
+ subproperties: ["margin-inline-start"],
+ },
+ "max-block-size": {
+ domProp: "maxBlockSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ prerequisites: { display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "30px",
+ "50%",
+ // These keywords are treated as initial value.
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["auto", "5"],
+ },
+ "max-inline-size": {
+ domProp: "maxInlineSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ prerequisites: { display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "30px",
+ "50%",
+ // these four keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["auto", "5"],
+ },
+ "min-block-size": {
+ domProp: "minBlockSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ prerequisites: { display: "block" },
+ initial_values: ["auto", "0", "calc(0em)", "calc(-2px)"],
+ other_values: [
+ "30px",
+ "50%",
+ // These keywords are treated as initial value.
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none", "5"],
+ },
+ "min-inline-size": {
+ domProp: "minInlineSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ prerequisites: { display: "block" },
+ initial_values: ["auto", "0", "calc(0em)", "calc(-2px)"],
+ other_values: [
+ "30px",
+ "50%",
+ // these four keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none", "5"],
+ },
+ inset: {
+ domProp: "inset",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["top", "right", "bottom", "left"],
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ initial_values: ["auto"],
+ other_values: [
+ "3px 0",
+ "2em 4px 2pt",
+ "1em 2em 3px 4px",
+ "1em calc(2em + 3px) 4ex 5cm",
+ ],
+ invalid_values: ["1px calc(nonsense)", "1px red", "3"],
+ unbalanced_values: ["1px calc("],
+ },
+ "inset-block": {
+ domProp: "insetBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["inset-block-start", "inset-block-end"],
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "32px auto",
+ "auto -3em",
+ "12% auto",
+ "calc(2px)",
+ "calc(2px) auto",
+ "calc(-2px)",
+ "auto calc(-2px)",
+ "calc(50%)",
+ "auto calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) auto",
+ "calc(25px*3)",
+ "auto calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "auto calc(3*25px + 50%)",
+ ],
+ invalid_values: ["none"],
+ },
+ "inset-block-end": {
+ domProp: "insetBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "inset-block-start": {
+ domProp: "insetBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "inset-inline": {
+ domProp: "insetInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["inset-inline-start", "inset-inline-end"],
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "32px auto",
+ "auto -3em",
+ "12% auto",
+ "calc(2px)",
+ "calc(2px) auto",
+ "calc(-2px)",
+ "auto calc(-2px)",
+ "calc(50%)",
+ "auto calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) auto",
+ "calc(25px*3)",
+ "auto calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "auto calc(3*25px + 50%)",
+ ],
+ invalid_values: ["none"],
+ },
+ "inset-inline-end": {
+ domProp: "insetInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "inset-inline-start": {
+ domProp: "insetInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "padding-block-end": {
+ domProp: "paddingBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ logical: true,
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "padding-block-start": {
+ domProp: "paddingBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ logical: true,
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "-moz-padding-end": {
+ domProp: "MozPaddingEnd",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "padding-inline-end",
+ subproperties: ["padding-inline-end"],
+ },
+ "-moz-padding-start": {
+ domProp: "MozPaddingStart",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "padding-inline-start",
+ subproperties: ["padding-inline-start"],
+ },
+ "-webkit-animation": {
+ domProp: "webkitAnimation",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ alias_for: "animation",
+ subproperties: [
+ "animation-name",
+ "animation-duration",
+ "animation-timing-function",
+ "animation-delay",
+ "animation-direction",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-play-state",
+ ],
+ },
+ "-webkit-animation-delay": {
+ domProp: "webkitAnimationDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-delay",
+ subproperties: ["animation-delay"],
+ },
+ "-webkit-animation-direction": {
+ domProp: "webkitAnimationDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-direction",
+ subproperties: ["animation-direction"],
+ },
+ "-webkit-animation-duration": {
+ domProp: "webkitAnimationDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-duration",
+ subproperties: ["animation-duration"],
+ },
+ "-webkit-animation-fill-mode": {
+ domProp: "webkitAnimationFillMode",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-fill-mode",
+ subproperties: ["animation-fill-mode"],
+ },
+ "-webkit-animation-iteration-count": {
+ domProp: "webkitAnimationIterationCount",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-iteration-count",
+ subproperties: ["animation-iteration-count"],
+ },
+ "-webkit-animation-name": {
+ domProp: "webkitAnimationName",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-name",
+ subproperties: ["animation-name"],
+ },
+ "-webkit-animation-play-state": {
+ domProp: "webkitAnimationPlayState",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-play-state",
+ subproperties: ["animation-play-state"],
+ },
+ "-webkit-animation-timing-function": {
+ domProp: "webkitAnimationTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-timing-function",
+ subproperties: ["animation-timing-function"],
+ },
+ "-webkit-clip-path": {
+ domProp: "webkitClipPath",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "clip-path",
+ subproperties: ["clip-path"],
+ },
+ "-webkit-filter": {
+ domProp: "webkitFilter",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "filter",
+ subproperties: ["filter"],
+ },
+ "-webkit-text-security": {
+ domProp: "webkitTextSecurity",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["none"],
+ other_values: ["circle", "disc", "square"],
+ invalid_values: ["0", "auto", "true", "'*'"],
+ },
+ "-webkit-text-fill-color": {
+ domProp: "webkitTextFillColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor", "black", "#000", "#000000", "rgb(0,0,0)"],
+ other_values: ["red", "rgba(255,255,255,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "ff00ff",
+ "rgb(255,xxx,255)",
+ ],
+ },
+ "-webkit-text-stroke": {
+ domProp: "webkitTextStroke",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { color: "black" },
+ subproperties: ["-webkit-text-stroke-width", "-webkit-text-stroke-color"],
+ initial_values: [
+ "0 currentColor",
+ "currentColor 0px",
+ "0",
+ "currentColor",
+ "0px black",
+ ],
+ other_values: [
+ "thin black",
+ "#f00 medium",
+ "thick rgba(0,0,255,0.5)",
+ "calc(4px - 8px) green",
+ "2px",
+ "green 0",
+ "currentColor 4em",
+ "currentColor calc(5px - 1px)",
+ ],
+ invalid_values: ["-3px black", "calc(50%+ 2px) #000", "30% #f00"],
+ },
+ "-webkit-text-stroke-color": {
+ domProp: "webkitTextStrokeColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor", "black", "#000", "#000000", "rgb(0,0,0)"],
+ other_values: ["red", "rgba(255,255,255,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "ff00ff",
+ "rgb(255,xxx,255)",
+ ],
+ },
+ "-webkit-text-stroke-width": {
+ domProp: "webkitTextStrokeWidth",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["0", "0px", "0em", "0ex", "calc(0pt)", "calc(4px - 8px)"],
+ other_values: [
+ "thin",
+ "medium",
+ "thick",
+ "17px",
+ "0.2em",
+ "calc(3*25px + 5em)",
+ "calc(5px - 1px)",
+ ],
+ invalid_values: [
+ "5%",
+ "1px calc(nonsense)",
+ "1px red",
+ "-0.1px",
+ "-3px",
+ "30%",
+ ],
+ },
+ "-webkit-text-size-adjust": {
+ domProp: "webkitTextSizeAdjust",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-text-size-adjust",
+ subproperties: ["-moz-text-size-adjust"],
+ },
+ "-webkit-transform": {
+ domProp: "webkitTransform",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform",
+ subproperties: ["transform"],
+ },
+ "-webkit-transform-origin": {
+ domProp: "webkitTransformOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-origin",
+ subproperties: ["transform-origin"],
+ },
+ "-webkit-transform-style": {
+ domProp: "webkitTransformStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-style",
+ subproperties: ["transform-style"],
+ },
+ "-webkit-backface-visibility": {
+ domProp: "webkitBackfaceVisibility",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "backface-visibility",
+ subproperties: ["backface-visibility"],
+ },
+ "-webkit-perspective": {
+ domProp: "webkitPerspective",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective",
+ subproperties: ["perspective"],
+ },
+ "-webkit-perspective-origin": {
+ domProp: "webkitPerspectiveOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective-origin",
+ subproperties: ["perspective-origin"],
+ },
+ "-webkit-transition": {
+ domProp: "webkitTransition",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ alias_for: "transition",
+ subproperties: [
+ "transition-property",
+ "transition-duration",
+ "transition-timing-function",
+ "transition-delay",
+ ],
+ },
+ "-webkit-transition-delay": {
+ domProp: "webkitTransitionDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-delay",
+ subproperties: ["transition-delay"],
+ },
+ "-webkit-transition-duration": {
+ domProp: "webkitTransitionDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-duration",
+ subproperties: ["transition-duration"],
+ },
+ "-webkit-transition-property": {
+ domProp: "webkitTransitionProperty",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-property",
+ subproperties: ["transition-property"],
+ },
+ "-webkit-transition-timing-function": {
+ domProp: "webkitTransitionTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-timing-function",
+ subproperties: ["transition-timing-function"],
+ },
+ "-webkit-border-radius": {
+ domProp: "webkitBorderRadius",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-radius",
+ subproperties: [
+ "border-bottom-left-radius",
+ "border-bottom-right-radius",
+ "border-top-left-radius",
+ "border-top-right-radius",
+ ],
+ },
+ "-webkit-border-top-left-radius": {
+ domProp: "webkitBorderTopLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-top-left-radius",
+ subproperties: ["border-top-left-radius"],
+ },
+ "-webkit-border-top-right-radius": {
+ domProp: "webkitBorderTopRightRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-top-right-radius",
+ subproperties: ["border-top-right-radius"],
+ },
+ "-webkit-border-bottom-left-radius": {
+ domProp: "webkitBorderBottomLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-bottom-left-radius",
+ subproperties: ["border-bottom-left-radius"],
+ },
+ "-webkit-border-bottom-right-radius": {
+ domProp: "webkitBorderBottomRightRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-bottom-right-radius",
+ subproperties: ["border-bottom-right-radius"],
+ },
+ "-webkit-background-clip": {
+ domProp: "webkitBackgroundClip",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "background-clip",
+ subproperties: ["background-clip"],
+ },
+ "-webkit-background-origin": {
+ domProp: "webkitBackgroundOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "background-origin",
+ subproperties: ["background-origin"],
+ },
+ "-webkit-background-size": {
+ domProp: "webkitBackgroundSize",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "background-size",
+ subproperties: ["background-size"],
+ },
+ "-webkit-border-image": {
+ domProp: "webkitBorderImage",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-image",
+ subproperties: [
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat",
+ ],
+ },
+ "-webkit-box-shadow": {
+ domProp: "webkitBoxShadow",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "box-shadow",
+ subproperties: ["box-shadow"],
+ },
+ "-webkit-box-sizing": {
+ domProp: "webkitBoxSizing",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "box-sizing",
+ subproperties: ["box-sizing"],
+ },
+ "-webkit-box-flex": {
+ domProp: "webkitBoxFlex",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-flex",
+ subproperties: ["-moz-box-flex"],
+ },
+ "-webkit-box-ordinal-group": {
+ domProp: "webkitBoxOrdinalGroup",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-ordinal-group",
+ subproperties: ["-moz-box-ordinal-group"],
+ },
+ "-webkit-box-orient": {
+ domProp: "webkitBoxOrient",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-orient",
+ subproperties: ["-moz-box-orient"],
+ },
+ "-webkit-box-direction": {
+ domProp: "webkitBoxDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-direction",
+ subproperties: ["-moz-box-direction"],
+ },
+ "-webkit-box-align": {
+ domProp: "webkitBoxAlign",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-align",
+ subproperties: ["-moz-box-align"],
+ },
+ "-webkit-box-pack": {
+ domProp: "webkitBoxPack",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-pack",
+ subproperties: ["-moz-box-pack"],
+ },
+ "-webkit-flex-direction": {
+ domProp: "webkitFlexDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-direction",
+ subproperties: ["flex-direction"],
+ },
+ "-webkit-flex-wrap": {
+ domProp: "webkitFlexWrap",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-wrap",
+ subproperties: ["flex-wrap"],
+ },
+ "-webkit-flex-flow": {
+ domProp: "webkitFlexFlow",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "flex-flow",
+ subproperties: ["flex-direction", "flex-wrap"],
+ },
+ "-webkit-line-clamp": {
+ domProp: "webkitLineClamp",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1", "2"],
+ invalid_values: ["auto", "0", "-1"],
+ },
+ "-webkit-order": {
+ domProp: "webkitOrder",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "order",
+ subproperties: ["order"],
+ },
+ "-webkit-flex": {
+ domProp: "webkitFlex",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "flex",
+ subproperties: ["flex-grow", "flex-shrink", "flex-basis"],
+ },
+ "-webkit-flex-grow": {
+ domProp: "webkitFlexGrow",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-grow",
+ subproperties: ["flex-grow"],
+ },
+ "-webkit-flex-shrink": {
+ domProp: "webkitFlexShrink",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-shrink",
+ subproperties: ["flex-shrink"],
+ },
+ "-webkit-flex-basis": {
+ domProp: "webkitFlexBasis",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-basis",
+ subproperties: ["flex-basis"],
+ },
+ "-webkit-justify-content": {
+ domProp: "webkitJustifyContent",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "justify-content",
+ subproperties: ["justify-content"],
+ },
+ "-webkit-align-items": {
+ domProp: "webkitAlignItems",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "align-items",
+ subproperties: ["align-items"],
+ },
+ "-webkit-align-self": {
+ domProp: "webkitAlignSelf",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "align-self",
+ subproperties: ["align-self"],
+ },
+ "-webkit-align-content": {
+ domProp: "webkitAlignContent",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "align-content",
+ subproperties: ["align-content"],
+ },
+ "-webkit-user-select": {
+ domProp: "webkitUserSelect",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "user-select",
+ subproperties: ["user-select"],
+ },
+ "-webkit-mask": {
+ domProp: "webkitMask",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "mask",
+ subproperties: [
+ "mask-clip",
+ "mask-image",
+ "mask-mode",
+ "mask-origin",
+ "mask-position",
+ "mask-repeat",
+ "mask-size",
+ "mask-composite",
+ ],
+ },
+ "-webkit-mask-clip": {
+ domProp: "webkitMaskClip",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-clip",
+ subproperties: ["mask-clip"],
+ },
+
+ "-webkit-mask-composite": {
+ domProp: "webkitMaskComposite",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-composite",
+ subproperties: ["mask-composite"],
+ },
+
+ "-webkit-mask-image": {
+ domProp: "webkitMaskImage",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-image",
+ subproperties: ["mask-image"],
+ },
+ "-webkit-mask-origin": {
+ domProp: "webkitMaskOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-origin",
+ subproperties: ["mask-origin"],
+ },
+ "-webkit-mask-position": {
+ domProp: "webkitMaskPosition",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-position",
+ subproperties: ["mask-position"],
+ },
+ "-webkit-mask-position-x": {
+ domProp: "webkitMaskPositionX",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-position-x",
+ subproperties: ["mask-position-x"],
+ },
+ "-webkit-mask-position-y": {
+ domProp: "webkitMaskPositionY",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-position-y",
+ subproperties: ["mask-position-y"],
+ },
+ "-webkit-mask-repeat": {
+ domProp: "webkitMaskRepeat",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-repeat",
+ subproperties: ["mask-repeat"],
+ },
+ "-webkit-mask-size": {
+ domProp: "webkitMaskSize",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-size",
+ subproperties: ["mask-size"],
+ },
+}; // end of gCSSProperties
+
+// Get the computed value for a property. For shorthands, return the
+// computed values of all the subproperties, delimited by " ; ".
+function get_computed_value(cs, property) {
+ var info = gCSSProperties[property];
+ if (
+ info.type == CSS_TYPE_TRUE_SHORTHAND ||
+ info.type == CSS_TYPE_LEGACY_SHORTHAND ||
+ (info.type == CSS_TYPE_SHORTHAND_AND_LONGHAND &&
+ (property == "text-decoration" || property == "mask"))
+ ) {
+ var results = [];
+ for (var idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ results.push(get_computed_value(cs, subprop));
+ }
+ return results.join(" ; ");
+ }
+ return cs.getPropertyValue(property);
+}
+
+{
+ const mozHiddenUnscrollableEnabled = IsCSSPropertyPrefEnabled(
+ "layout.css.overflow-moz-hidden-unscrollable.enabled"
+ );
+ for (let p of ["overflow", "overflow-x", "overflow-y"]) {
+ let prop = gCSSProperties[p];
+ let mozHiddenUnscrollableValues = mozHiddenUnscrollableEnabled
+ ? prop.other_values
+ : prop.invalid_values;
+ mozHiddenUnscrollableValues.push("-moz-hidden-unscrollable");
+ if (p == "overflow") {
+ mozHiddenUnscrollableValues.push(
+ "-moz-hidden-unscrollable -moz-hidden-unscrollable"
+ );
+ }
+ }
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.individual-transform.enabled")) {
+ gCSSProperties.rotate = {
+ domProp: "rotate",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "45deg",
+ "45grad",
+ "72rad",
+ "0.25turn",
+ ".57rad",
+ "0 0 0 0rad",
+ "0 0 1 45deg",
+ "0 0 1 0rad",
+ "0rad 0 0 1",
+ "10rad 10 20 30",
+ "x 10rad",
+ "y 10rad",
+ "z 10rad",
+ "10rad x",
+ "10rad y",
+ "10rad z",
+ /* valid calc() values */
+ "calc(1) 0 0 calc(45deg + 5rad)",
+ "0 1 0 calc(400grad + 1rad)",
+ "calc(0.5turn + 10deg)",
+ ],
+ invalid_values: [
+ "0",
+ "7",
+ "0, 0, 1, 45deg",
+ "0 0 45deg",
+ "0 0 20rad",
+ "0 0 0 0",
+ "x x 10rad",
+ "x y 10rad",
+ "0 0 1 10rad z",
+ "0 0 1 z 10rad",
+ "z 0 0 1 10rad",
+ "0 0 z 1 10rad",
+ /* invalid calc() values */
+ "0.5 1 0 calc(45deg + 10)",
+ "calc(0.5turn + 10%)",
+ ],
+ };
+
+ gCSSProperties.translate = {
+ domProp: "translate",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { width: "10px", height: "10px", display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "-4px",
+ "3px",
+ "4em",
+ "50%",
+ "4px 5px 6px",
+ "4px 5px",
+ "50% 5px 6px",
+ "50% 10% 6em",
+ /* valid calc() values */
+ "calc(5px + 10%)",
+ "calc(0.25 * 5px + 10% / 3)",
+ "calc(5px - 10% * 3)",
+ "calc(5px - 3 * 10%) 50px",
+ "-50px calc(5px - 10% * 3)",
+ "10px calc(min(5px,10%))",
+ ],
+ invalid_values: [
+ "1",
+ "-moz-min(5px,10%)",
+ "4px, 5px, 6px",
+ "3px 4px 1px 7px",
+ "4px 5px 10%",
+ /* invalid calc() values */
+ "calc(max(5px,10%) 10%)",
+ "calc(nonsense)",
+ ],
+ };
+ gCSSProperties.scale = {
+ domProp: "scale",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "10",
+ "10%",
+ "10 20",
+ "10% 20%",
+ "10 20 30",
+ "10% 20% 30%",
+ "10 20% 30",
+ "-10",
+ "-10%",
+ "-10 20",
+ "-10% 20%",
+ "-10 20 -30",
+ "-10% 20% -30%",
+ "-10 20% -30",
+ "0 2.0",
+ /* valid calc() values */
+ "calc(1 + 2)",
+ "calc(10) calc(20) 30",
+ ],
+ invalid_values: [
+ "10px",
+ "10deg",
+ "10, 20, 30",
+ /* invalid calc() values */
+ "calc(1 + 20%)",
+ "10 calc(1 + 10px)",
+ ],
+ };
+}
+
+if (
+ IsCSSPropertyPrefEnabled("layout.css.transform-box-content-stroke.enabled")
+) {
+ gCSSProperties["transform-box"]["other_values"].push(
+ "content-box",
+ "stroke-box"
+ );
+}
+
+gCSSProperties["touch-action"] = {
+ domProp: "touchAction",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "none",
+ "pan-x",
+ "pan-y",
+ "pinch-zoom",
+ "pan-x pan-y",
+ "pan-y pan-x",
+ "pinch-zoom pan-x",
+ "pinch-zoom pan-y",
+ "pan-x pinch-zoom",
+ "pan-y pinch-zoom",
+ "pinch-zoom pan-x pan-y",
+ "pinch-zoom pan-y pan-x",
+ "pan-x pinch-zoom pan-y",
+ "pan-y pinch-zoom pan-x",
+ "pan-x pan-y pinch-zoom",
+ "pan-y pan-x pinch-zoom",
+ "manipulation",
+ ],
+ invalid_values: [
+ "zoom",
+ "pinch",
+ "tap",
+ "10px",
+ "2",
+ "auto pan-x",
+ "pan-x auto",
+ "none pan-x",
+ "pan-x none",
+ "auto pan-y",
+ "pan-y auto",
+ "none pan-y",
+ "pan-y none",
+ "pan-x pan-x",
+ "pan-y pan-y",
+ "auto pinch-zoom",
+ "pinch-zoom auto",
+ "none pinch-zoom",
+ "pinch-zoom none",
+ "pinch-zoom pinch-zoom",
+ "pan-x pan-y none",
+ "pan-x none pan-y",
+ "none pan-x pan-y",
+ "pan-y pan-x none",
+ "pan-y none pan-x",
+ "none pan-y pan-x",
+ "pan-x pinch-zoom none",
+ "pan-x none pinch-zoom",
+ "none pan-x pinch-zoom",
+ "pinch-zoom pan-x none",
+ "pinch-zoom none pan-x",
+ "none pinch-zoom pan-x",
+ "pinch-zoom pan-y none",
+ "pinch-zoom none pan-y",
+ "none pinch-zoom pan-y",
+ "pan-y pinch-zoom none",
+ "pan-y none pinch-zoom",
+ "none pan-y pinch-zoom",
+ "pan-x pan-y auto",
+ "pan-x auto pan-y",
+ "auto pan-x pan-y",
+ "pan-y pan-x auto",
+ "pan-y auto pan-x",
+ "auto pan-y pan-x",
+ "pan-x pinch-zoom auto",
+ "pan-x auto pinch-zoom",
+ "auto pan-x pinch-zoom",
+ "pinch-zoom pan-x auto",
+ "pinch-zoom auto pan-x",
+ "auto pinch-zoom pan-x",
+ "pinch-zoom pan-y auto",
+ "pinch-zoom auto pan-y",
+ "auto pinch-zoom pan-y",
+ "pan-y pinch-zoom auto",
+ "pan-y auto pinch-zoom",
+ "auto pan-y pinch-zoom",
+ "pan-x pan-y zoom",
+ "pan-x zoom pan-y",
+ "zoom pan-x pan-y",
+ "pan-y pan-x zoom",
+ "pan-y zoom pan-x",
+ "zoom pan-y pan-x",
+ "pinch-zoom pan-y zoom",
+ "pinch-zoom zoom pan-y",
+ "zoom pinch-zoom pan-y",
+ "pan-y pinch-zoom zoom",
+ "pan-y zoom pinch-zoom",
+ "zoom pan-y pinch-zoom",
+ "pan-x pinch-zoom zoom",
+ "pan-x zoom pinch-zoom",
+ "zoom pan-x pinch-zoom",
+ "pinch-zoom pan-x zoom",
+ "pinch-zoom zoom pan-x",
+ "zoom pinch-zoom pan-x",
+ "pan-x pan-y pan-x",
+ "pan-x pan-x pan-y",
+ "pan-y pan-x pan-x",
+ "pan-y pan-x pan-y",
+ "pan-y pan-y pan-x",
+ "pan-x pan-y pan-y",
+ "pan-x pinch-zoom pan-x",
+ "pan-x pan-x pinch-zoom",
+ "pinch-zoom pan-x pan-x",
+ "pinch-zoom pan-x pinch-zoom",
+ "pinch-zoom pinch-zoom pan-x",
+ "pan-x pinch-zoom pinch-zoom",
+ "pinch-zoom pan-y pinch-zoom",
+ "pinch-zoom pinch-zoom pan-y",
+ "pan-y pinch-zoom pinch-zoom",
+ "pan-y pinch-zoom pan-y",
+ "pan-y pan-y pinch-zoom",
+ "pinch-zoom pan-y pan-y",
+ "manipulation none",
+ "none manipulation",
+ "manipulation auto",
+ "auto manipulation",
+ "manipulation zoom",
+ "zoom manipulation",
+ "manipulation manipulation",
+ "manipulation pan-x",
+ "pan-x manipulation",
+ "manipulation pan-y",
+ "pan-y manipulation",
+ "manipulation pinch-zoom",
+ "pinch-zoom manipulation",
+ "manipulation pan-x pan-y",
+ "pan-x manipulation pan-y",
+ "pan-x pan-y manipulation",
+ "manipulation pan-y pan-x",
+ "pan-y manipulation pan-x",
+ "pan-y pan-x manipulation",
+ "manipulation pinch-zoom pan-y",
+ "pinch-zoom manipulation pan-y",
+ "pinch-zoom pan-y manipulation",
+ "manipulation pan-y pinch-zoom",
+ "pan-y manipulation pinch-zoom",
+ "pan-y pinch-zoom manipulation",
+ "manipulation pan-x pinch-zoom",
+ "pan-x manipulation pinch-zoom",
+ "pan-x pinch-zoom manipulation",
+ "manipulation pinch-zoom pan-x",
+ "pinch-zoom manipulation pan-x",
+ "pinch-zoom pan-x manipulation",
+ ],
+};
+
+gCSSProperties["page"] = {
+ domProp: "page",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["page", "small_page", "large_page", "A4"],
+ invalid_values: ["page1 page2", "auto page", "1cm"],
+};
+
+gCSSProperties["text-justify"] = {
+ domProp: "textJustify",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ["none", "inter-word", "inter-character", "distribute"],
+ invalid_values: [],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.text-indent-keywords.enabled")) {
+ gCSSProperties["text-indent"].other_values.push(
+ "2em hanging",
+ "5% each-line",
+ "-10px hanging each-line",
+ "hanging calc(2px)",
+ "each-line calc(-2px)",
+ "each-line calc(50%) hanging",
+ "hanging calc(3*25px) each-line",
+ "each-line hanging calc(25px*3)"
+ );
+ gCSSProperties["text-indent"].invalid_values.push(
+ "hanging",
+ "each-line",
+ "-10px hanging hanging",
+ "each-line calc(2px) each-line"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) {
+ gCSSProperties["font-variation-settings"] = {
+ domProp: "fontVariationSettings",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_marker: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "'wdth' 0",
+ "'wdth' -.1",
+ '"wdth" 1',
+ "'wdth' 2, 'wght' 3",
+ '"XXXX" 0',
+ ],
+ invalid_values: [
+ "wdth",
+ "wdth 1", // unquoted tags
+ "'wdth'",
+ "'wdth' 'wght'",
+ "'wdth', 'wght'", // missing values
+ "'' 1",
+ "'wid' 1",
+ "'width' 1", // incorrect tag lengths
+ "'wd\th' 1", // non-graphic character in tag
+ "'wdth' 1 'wght' 2", // missing comma between pairs
+ "'wdth' 1,", // trailing comma
+ "'wdth' 1 , , 'wght' 2", // extra comma
+ "'wdth', 1", // comma within pair
+ ],
+ unbalanced_values: [
+ "'wdth\" 1",
+ "\"wdth' 1", // mismatched quotes
+ ],
+ };
+ gCSSProperties["font"].subproperties.push("font-variation-settings");
+ gCSSProperties["font-optical-sizing"] = {
+ domProp: "fontOpticalSizing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_marker: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["on"],
+ };
+ gCSSProperties["font"].subproperties.push("font-optical-sizing");
+ gCSSProperties["font-variation-settings"].other_values.push(
+ "'vert' calc(2.5)"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.font-palette.enabled")) {
+ gCSSProperties["font-palette"] = {
+ domProp: "fontPalette",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ initial_values: ["normal"],
+ other_values: ["light", "dark", "--custom"],
+ invalid_values: ["custom"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.font-variant-emoji.enabled")) {
+ gCSSProperties["font"].subproperties.push("font-variant-emoji");
+ gCSSProperties["font-variant"].subproperties.push("font-variant-emoji");
+ gCSSProperties["font-variant-emoji"] = {
+ domProp: "fontVariantEmoji",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: ["text", "emoji", "unicode"],
+ invalid_values: [
+ "none",
+ "auto",
+ "text emoji",
+ "auto text",
+ "normal, unicode",
+ ],
+ };
+}
+
+var isGridTemplateMasonryValueEnabled = IsCSSPropertyPrefEnabled(
+ "layout.css.grid-template-masonry-value.enabled"
+);
+
+if (isGridTemplateMasonryValueEnabled) {
+ gCSSProperties["masonry-auto-flow"] = {
+ domProp: "masonryAutoFlow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["pack"],
+ other_values: ["pack ordered", "ordered next", "next definite-first"],
+ invalid_values: ["auto", "none", "10px", "row", "dense"],
+ };
+
+ let alignTracks = { ...gCSSProperties["align-content"] };
+ alignTracks.domProp = "alignTracks";
+ gCSSProperties["align-tracks"] = alignTracks;
+
+ let justifyTracks = { ...gCSSProperties["justify-content"] };
+ justifyTracks.domProp = "justifyTracks";
+ gCSSProperties["justify-tracks"] = justifyTracks;
+}
+
+gCSSProperties["display"].other_values.push("grid", "inline-grid");
+gCSSProperties["grid-auto-flow"] = {
+ domProp: "gridAutoFlow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["row"],
+ other_values: [
+ "column",
+ "column dense",
+ "row dense",
+ "dense column",
+ "dense row",
+ "dense",
+ ],
+ invalid_values: ["", "auto", "none", "10px", "column row", "dense row dense"],
+};
+
+gCSSProperties["grid-auto-columns"] = {
+ domProp: "gridAutoColumns",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "40px",
+ "2em",
+ "2.5fr",
+ "12%",
+ "min-content",
+ "max-content",
+ "calc(2px - 99%)",
+ "minmax(20px, max-content)",
+ "minmax(min-content, auto)",
+ "minmax(auto, max-content)",
+ "m\\69nmax(20px, 4Fr)",
+ "MinMax(min-content, calc(20px + 10%))",
+ "fit-content(1px)",
+ "fit-content(calc(1px - 99%))",
+ "fit-content(10%)",
+ "40px 12%",
+ "2.5fr min-content fit-content(1px)",
+ ],
+ invalid_values: [
+ "",
+ "normal",
+ "40ms",
+ "-40px",
+ "-12%",
+ "-2em",
+ "-2.5fr",
+ "minmax()",
+ "minmax(20px)",
+ "mİnmax(20px, 100px)",
+ "minmax(20px, 100px, 200px)",
+ "maxmin(100px, 20px)",
+ "minmax(min-content, minmax(30px, max-content))",
+ "fit-content(-1px)",
+ "fit-content(auto)",
+ "fit-content(min-content)",
+ "1px [a] 1px",
+ ],
+};
+gCSSProperties["grid-auto-rows"] = {
+ domProp: "gridAutoRows",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: gCSSProperties["grid-auto-columns"].initial_values,
+ other_values: gCSSProperties["grid-auto-columns"].other_values,
+ invalid_values: gCSSProperties["grid-auto-columns"].invalid_values,
+};
+
+gCSSProperties["grid-template-columns"] = {
+ domProp: "gridTemplateColumns",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "auto",
+ "40px",
+ "2.5fr",
+ "[normal] 40px [] auto [ ] 12%",
+ "[foo] 40px min-content [ bar ] calc(2px - 99%) max-content",
+ "40px min-content calc(20px + 10%) max-content",
+ "minmax(min-content, auto)",
+ "minmax(auto, max-content)",
+ "m\\69nmax(20px, 4Fr)",
+ "40px MinMax(min-content, calc(20px + 10%)) max-content",
+ "40px 2em",
+ "[] 40px [-foo] 2em [bar baz This is one ident]",
+ // TODO bug 978478: "[a] repeat(3, [b] 20px [c] 40px [d]) [e]",
+ "repeat(1, 20px)",
+ "repeat(1, [a] 20px)",
+ "[a] Repeat(4, [a] 20px [] auto [b c]) [d]",
+ "[a] 2.5fr Repeat(4, [a] 20px [] auto [b c]) [d]",
+ "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto [b c]) [d]",
+ "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto) [d]",
+ "[a] 2.5fr [z] Repeat(4, 20px [b c] auto [b c]) [d]",
+ "[a] 2.5fr [z] Repeat(4, 20px auto) [d]",
+ "repeat(auto-fill, 0)",
+ "[a] repeat( Auto-fill,1%)",
+ "minmax(auto,0) [a] repeat(Auto-fit, 0) minmax(0,auto)",
+ "minmax(calc(1% + 1px),auto) repeat(Auto-fit,[] 1%) minmax(auto,1%)",
+ "[a] repeat( auto-fit,[a b] minmax(0,0) )",
+ "[a] 40px repeat(auto-fit,[a b] minmax(1px, 0) [])",
+ "[a] calc(1px - 99%) [b] repeat(auto-fit,[a b] minmax(1mm, 1%) [c]) [c]",
+ "repeat(auto-fill, 0 0)",
+ "repeat(auto-fill, 0 [] 0)",
+ "repeat(auto-fill,minmax(1%,auto))",
+ "repeat(auto-fill,minmax(1em,min-content)) minmax(min-content,0)",
+ "repeat(auto-fill,minmax(max-content,1mm))",
+ "repeat(2, fit-content(1px))",
+ "fit-content(1px) 1fr",
+ "[a] fit-content(calc(1px - 99%)) [b]",
+ "[a] fit-content(10%) [b c] fit-content(1em)",
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=981300
+ "[none subgrid min-content max-content foo] 40px",
+ "subgrid",
+ "subgrid [] [foo bar]",
+ "subgrid repeat(1, [])",
+ "subgrid Repeat(4, [a] [b c] [] [d])",
+ "subgrid repeat(auto-fill, [])",
+ "subgrid repeat(Auto-fill, [a b c]) [a] []",
+ "subgrid [x] repeat( Auto-fill, [a b c]) []",
+ "subgrid [x] repeat( auto-fill , [a b] [c]) [y]",
+ "subgrid repeat(auto-fill, [a] [b] [c]) [d]",
+ "subgrid repeat(Auto-fill, [a] [b c] [] [d])",
+ "subgrid [x y] [x] repeat(auto-fill, [a b] [c] [d] [d]) [x] [x]",
+ "subgrid [x] repeat(auto-fill, []) [y z]",
+ "subgrid [x] repeat(auto-fill, [y]) [z] [] repeat(2, [a] [b]) [y] []",
+ "subgrid [x] repeat(auto-fill, []) [x y] [z] [] []",
+ ],
+ invalid_values: [
+ "",
+ "normal",
+ "40ms",
+ "-40px",
+ "-12%",
+ "-2fr",
+ "[foo]",
+ "[inherit] 40px",
+ "[initial] 40px",
+ "[unset] 40px",
+ "[default] 40px",
+ "[span] 40px",
+ "[6%] 40px",
+ "[5th] 40px",
+ "[foo[] bar] 40px",
+ "[foo]] 40px",
+ "(foo) 40px",
+ "[foo] [bar] 40px",
+ "40px [foo] [bar]",
+ "minmax()",
+ "minmax(20px)",
+ "mİnmax(20px, 100px)",
+ "minmax(20px, 100px, 200px)",
+ "maxmin(100px, 20px)",
+ "minmax(min-content, minmax(30px, max-content))",
+ "repeat(0, 20px)",
+ "repeat(-3, 20px)",
+ "rêpeat(1, 20px)",
+ "repeat(1)",
+ "repeat(1, )",
+ "repeat(3px, 20px)",
+ "repeat(2.0, 20px)",
+ "repeat(2.5, 20px)",
+ "repeat(2, (foo))",
+ "repeat(2, foo)",
+ "40px calc(0px + rubbish)",
+ "repeat(1, repeat(1, 20px))",
+ "repeat(auto-fill, auto)",
+ "repeat(auto-fit,auto)",
+ "repeat(auto-fill, fit-content(1px))",
+ "repeat(auto-fit, fit-content(1px))",
+ "repeat(auto-fit,[])",
+ "repeat(auto-fill, 0) repeat(auto-fit, 0) ",
+ "repeat(auto-fit, 0) repeat(auto-fill, 0) ",
+ "[a] repeat(auto-fit, 0) repeat(auto-fit, 0) ",
+ "[a] repeat(auto-fill, 0) [a] repeat(auto-fill, 0) ",
+ "repeat(auto-fill, min-content)",
+ "repeat(auto-fit,max-content)",
+ "repeat(auto-fit,1fr)",
+ "repeat(auto-fit,minmax(auto,auto))",
+ "repeat(auto-fit,minmax(min-content,1fr))",
+ "repeat(auto-fit,minmax(1fr,auto))",
+ "repeat(auto-fill,minmax(1fr,1em))",
+ "repeat(auto-fill, 10px) auto",
+ "auto repeat(auto-fit, 10px)",
+ "minmax(min-content,max-content) repeat(auto-fit, 0)",
+ "10px [a] 10px [b a] 1fr [b] repeat(auto-fill, 0)",
+ "fit-content(-1px)",
+ "fit-content(auto)",
+ "fit-content(min-content)",
+ "fit-content(1px) repeat(auto-fit, 1px)",
+ "fit-content(1px) repeat(auto-fill, 1px)",
+ "subgrid [inherit]",
+ "subgrid [initial]",
+ "subgrid [unset]",
+ "subgrid [default]",
+ "subgrid [span]",
+ "subgrid [foo] 40px",
+ "subgrid [foo 40px]",
+ "[foo] subgrid",
+ "subgrid rêpeat(1, [])",
+ "subgrid repeat(0, [])",
+ "subgrid repeat(-3, [])",
+ "subgrid repeat(2.0, [])",
+ "subgrid repeat(2.5, [])",
+ "subgrid repeat(3px, [])",
+ "subgrid repeat(1)",
+ "subgrid repeat(1, )",
+ "subgrid repeat(2, [40px])",
+ "subgrid repeat(2, foo)",
+ "subgrid repeat(1, repeat(1, []))",
+ "subgrid repeat(auto-fill)",
+ "subgrid repeat(auto-fill) [a]",
+ "subgrid repeat(auto-fill) []",
+ "subgrid [a] repeat(auto-fill)",
+ "subgrid repeat(auto-fill,)",
+ "subgrid repeat(auto-fill,)",
+ "subgrid repeat(auto-fill,) [a]",
+ "subgrid repeat(auto-fill,) []",
+ "subgrid [a] repeat(auto-fill,)",
+ "subgrid repeat(auto-fit,[])",
+ "subgrid [] repeat(auto-fit,[])",
+ "subgrid [a] repeat(auto-fit,[])",
+ "subgrid repeat(auto-fill, 1px)",
+ "subgrid repeat(auto-fill, 1px [])",
+ "subgrid repeat(auto-fill, []) repeat(auto-fill, [])",
+ ],
+ unbalanced_values: ["(foo] 40px"],
+};
+if (isGridTemplateMasonryValueEnabled) {
+ gCSSProperties["grid-template-columns"].other_values.push("masonry");
+ gCSSProperties["grid-template-columns"].invalid_values.push(
+ "masonry []",
+ "masonry [foo] 40px",
+ "masonry 40px",
+ "[foo] masonry",
+ "0px masonry",
+ "masonry masonry",
+ "subgrid masonry",
+ "masonry subgrid",
+ "masonry repeat(1, [])"
+ );
+}
+gCSSProperties["grid-template-rows"] = {
+ domProp: "gridTemplateRows",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: gCSSProperties["grid-template-columns"].initial_values,
+ other_values: gCSSProperties["grid-template-columns"].other_values,
+ invalid_values: gCSSProperties["grid-template-columns"].invalid_values,
+};
+gCSSProperties["grid-template-areas"] = {
+ domProp: "gridTemplateAreas",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "'1a-é_ .' \"b .\"",
+ "' Z\t\\aZ' 'Z Z'",
+ " '. . a b' '. .a b' ",
+ "'a.b' '. . .'",
+ "'.' '..'",
+ "'...' '.'",
+ "'...-blah' '. .'",
+ "'.. ..' '.. ...'",
+ ],
+ invalid_values: [
+ "''",
+ "' '",
+ "'' ''",
+ "'a b' 'a/b'",
+ "'a . a'",
+ "'. a a' 'a a a'",
+ "'a a .' 'a a a'",
+ "'a a' 'a .'",
+ "'a a'\n'..'\n'a a'",
+ ],
+};
+
+gCSSProperties["grid-template"] = {
+ domProp: "gridTemplate",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "grid-template-areas",
+ "grid-template-rows",
+ "grid-template-columns",
+ ],
+ initial_values: ["none", "none / none"],
+ other_values: [
+ // <'grid-template-rows'> / <'grid-template-columns'>
+ "40px / 100px",
+ "[foo] 40px [bar] / [baz] repeat(auto-fill,100px) [fizz]",
+ " none/100px",
+ "40px/none",
+ // [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?
+ "'fizz'",
+ "[bar] 'fizz'",
+ "'fizz' / [foo] 40px",
+ "[bar] 'fizz' / [foo] 40px",
+ "'fizz' 100px / [foo] 40px",
+ "[bar] 'fizz' 100px / [foo] 40px",
+ "[bar] 'fizz' 100px [buzz] / [foo] 40px",
+ "[bar] 'fizz' 100px [buzz] \n [a] '.' 200px [b] / [foo] 40px",
+ "subgrid / subgrid",
+ "subgrid/40px 20px",
+ "subgrid [foo] [] [bar baz] / 40px 20px",
+ "40px 20px/subgrid",
+ "40px 20px/subgrid [foo] [] repeat(3, [a] [b]) [bar baz]",
+ "subgrid/subgrid",
+ "subgrid [foo] [] [bar baz]/subgrid [foo] [] [bar baz]",
+ ],
+ invalid_values: [
+ "'fizz' / repeat(1, 100px)",
+ "'fizz' repeat(1, 100px) / 0px",
+ "[foo] [bar] 40px / 100px",
+ "[fizz] [buzz] 100px / 40px",
+ "[fizz] [buzz] 'foo' / 40px",
+ "'foo' / none",
+ "subgrid",
+ "subgrid []",
+ "subgrid [] / 'fizz'",
+ "subgrid / 'fizz'",
+ ],
+};
+if (isGridTemplateMasonryValueEnabled) {
+ gCSSProperties["grid-template"].other_values.push(
+ "masonry / subgrid",
+ "subgrid / masonry",
+ "masonry / masonry" /* valid but behaves as 'masonry / none' */,
+ "masonry/40px 20px",
+ "subgrid [foo] [] [bar baz] / masonry",
+ "40px 20px/masonry",
+ "masonry/subgrid [foo] [] repeat(3, [a] [b]) [bar baz]",
+ "subgrid [foo] [] [bar baz]/masonry"
+ );
+ gCSSProperties["grid-template"].invalid_values.push(
+ "masonry",
+ "masonry / 'fizz'"
+ );
+}
+
+gCSSProperties["grid"] = {
+ domProp: "grid",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "grid-template-areas",
+ "grid-template-rows",
+ "grid-template-columns",
+ "grid-auto-flow",
+ "grid-auto-rows",
+ "grid-auto-columns",
+ ],
+ initial_values: ["none", "none / none"],
+ other_values: [
+ "auto-flow 40px / none",
+ "auto-flow 40px 100px / 0",
+ "auto-flow / 40px",
+ "auto-flow dense auto / auto",
+ "dense auto-flow minmax(min-content, 2fr) / auto",
+ "dense auto-flow / 100px",
+ "none / auto-flow 40px",
+ "40px / auto-flow",
+ "none / dense auto-flow auto",
+ ].concat(gCSSProperties["grid-template"].other_values),
+ invalid_values: [
+ "auto-flow",
+ " / auto-flow",
+ "dense 0 / 0",
+ "dense dense 40px / 0",
+ "auto-flow / auto-flow",
+ "auto-flow / dense",
+ "auto-flow [a] 0 / 0",
+ "0 / auto-flow [a] 0",
+ "auto-flow -20px / 0",
+ "auto-flow 200ms / 0",
+ "auto-flow 1px [a] 1px / 0",
+ ].concat(
+ gCSSProperties["grid-template"].invalid_values,
+ gCSSProperties["grid-auto-flow"].other_values,
+ gCSSProperties["grid-auto-flow"].invalid_values.filter(v => v != "none")
+ ),
+};
+
+var gridLineOtherValues = [
+ "foo",
+ "2",
+ "2 foo",
+ "foo 2",
+ "-3",
+ "-3 bar",
+ "bar -3",
+ "span 2",
+ "2 span",
+ "span foo",
+ "foo span",
+ "span 2 foo",
+ "span foo 2",
+ "2 foo span",
+ "foo 2 span",
+];
+var gridLineInvalidValues = [
+ "",
+ "4th",
+ "span",
+ "inherit 2",
+ "2 inherit",
+ "20px",
+ "2 3",
+ "2.5",
+ "2.0",
+ "0",
+ "0 foo",
+ "span 0",
+ "2 foo 3",
+ "foo 2 foo",
+ "2 span foo",
+ "foo span 2",
+ "span -3",
+ "span -3 bar",
+ "span 2 span",
+ "span foo span",
+ "span 2 foo span",
+];
+
+gCSSProperties["grid-column-start"] = {
+ domProp: "gridColumnStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: gridLineOtherValues,
+ invalid_values: gridLineInvalidValues,
+};
+gCSSProperties["grid-column-end"] = {
+ domProp: "gridColumnEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: gridLineOtherValues,
+ invalid_values: gridLineInvalidValues,
+};
+gCSSProperties["grid-row-start"] = {
+ domProp: "gridRowStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: gridLineOtherValues,
+ invalid_values: gridLineInvalidValues,
+};
+gCSSProperties["grid-row-end"] = {
+ domProp: "gridRowEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: gridLineOtherValues,
+ invalid_values: gridLineInvalidValues,
+};
+
+// The grid-column and grid-row shorthands take values of the form
+// <grid-line> [ / <grid-line> ]?
+var gridColumnRowOtherValues = [].concat(gridLineOtherValues);
+gridLineOtherValues.concat(["auto"]).forEach(function (val) {
+ gridColumnRowOtherValues.push(" foo / " + val);
+ gridColumnRowOtherValues.push(val + "/2");
+});
+var gridColumnRowInvalidValues = ["foo, bar", "foo / bar / baz"].concat(
+ gridLineInvalidValues
+);
+gridLineInvalidValues.forEach(function (val) {
+ gridColumnRowInvalidValues.push("span 3 / " + val);
+ gridColumnRowInvalidValues.push(val + " / foo");
+});
+gCSSProperties["grid-column"] = {
+ domProp: "gridColumn",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["grid-column-start", "grid-column-end"],
+ initial_values: ["auto", "auto / auto"],
+ other_values: gridColumnRowOtherValues,
+ invalid_values: gridColumnRowInvalidValues,
+};
+gCSSProperties["grid-row"] = {
+ domProp: "gridRow",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["grid-row-start", "grid-row-end"],
+ initial_values: ["auto", "auto / auto"],
+ other_values: gridColumnRowOtherValues,
+ invalid_values: gridColumnRowInvalidValues,
+};
+
+var gridAreaOtherValues = gridLineOtherValues.slice();
+gridLineOtherValues.forEach(function (val) {
+ gridAreaOtherValues.push("foo / " + val);
+ gridAreaOtherValues.push(val + "/2/3");
+ gridAreaOtherValues.push("foo / bar / " + val + " / baz");
+});
+var gridAreaInvalidValues = [
+ "foo, bar",
+ "foo / bar / baz / fizz / buzz",
+ "default / foo / bar / baz",
+ "foo / initial / bar / baz",
+ "foo / bar / inherit / baz",
+ "foo / bar / baz / unset",
+].concat(gridLineInvalidValues);
+gridLineInvalidValues.forEach(function (val) {
+ gridAreaInvalidValues.push("foo / " + val);
+ gridAreaInvalidValues.push("foo / bar / " + val);
+ gridAreaInvalidValues.push("foo / 4 / bar / " + val);
+});
+
+gCSSProperties["grid-area"] = {
+ domProp: "gridArea",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "grid-row-start",
+ "grid-column-start",
+ "grid-row-end",
+ "grid-column-end",
+ ],
+ initial_values: [
+ "auto",
+ "auto / auto",
+ "auto / auto / auto",
+ "auto / auto / auto / auto",
+ ],
+ other_values: gridAreaOtherValues,
+ invalid_values: gridAreaInvalidValues,
+};
+
+gCSSProperties["column-gap"] = {
+ domProp: "columnGap",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "2px",
+ "2%",
+ "1em",
+ "calc(1px + 1em)",
+ "calc(1%)",
+ "calc(1% + 1ch)",
+ "calc(1px - 99%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "auto",
+ "none",
+ "1px 1px",
+ "-1%",
+ "fit-content(1px)",
+ ],
+};
+gCSSProperties["grid-column-gap"] = {
+ domProp: "gridColumnGap",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "column-gap",
+ subproperties: ["column-gap"],
+};
+gCSSProperties["row-gap"] = {
+ domProp: "rowGap",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "2px",
+ "2%",
+ "1em",
+ "calc(1px + 1em)",
+ "calc(1%)",
+ "calc(1% + 1ch)",
+ "calc(1px - 99%)",
+ ],
+ invalid_values: ["-1px", "auto", "none", "1px 1px", "-1%", "min-content"],
+};
+gCSSProperties["grid-row-gap"] = {
+ domProp: "gridRowGap",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "row-gap",
+ subproperties: ["row-gap"],
+};
+gCSSProperties["gap"] = {
+ domProp: "gap",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["column-gap", "row-gap"],
+ initial_values: ["normal", "normal normal"],
+ other_values: [
+ "1ch 0",
+ "1px 1%",
+ "1em 1px",
+ "calc(1px) calc(1%)",
+ "normal 0",
+ "1% normal",
+ ],
+ invalid_values: [
+ "-1px",
+ "1px -1px",
+ "1px 1px 1px",
+ "inherit 1px",
+ "1px auto",
+ ],
+};
+gCSSProperties["grid-gap"] = {
+ domProp: "gridGap",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "gap",
+ subproperties: ["column-gap", "row-gap"],
+};
+
+gCSSProperties["contain"] = {
+ domProp: "contain",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "strict",
+ "layout",
+ "size",
+ "content",
+ "paint",
+ "layout paint",
+ "paint layout",
+ "size layout",
+ "paint size",
+ "layout size paint",
+ "layout paint size",
+ "size paint layout",
+ "paint size layout",
+ ],
+ invalid_values: [
+ "none strict",
+ "none size",
+ "strict layout",
+ "strict layout size",
+ "layout strict",
+ "layout content",
+ "strict content",
+ "layout size strict",
+ "layout size paint strict",
+ "paint strict",
+ "size strict",
+ "paint paint",
+ "content content",
+ "size content",
+ "content strict size",
+ "paint layout content",
+ "layout size content",
+ "size size",
+ "strict strict",
+ "auto",
+ "10px",
+ "0",
+ ],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.initial-letter.enabled")) {
+ gCSSProperties["initial-letter"] = {
+ domProp: "initialLetter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["normal"],
+ other_values: ["2", "2.5", "3.7 2", "4 3"],
+ invalid_values: ["-3", "3.7 -2", "25%", "16px", "1 0", "0", "0 1"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.osx-font-smoothing.enabled")) {
+ gCSSProperties["-moz-osx-font-smoothing"] = {
+ domProp: "MozOsxFontSmoothing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["auto"],
+ other_values: ["grayscale"],
+ invalid_values: ["none", "subpixel-antialiased", "antialiased"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.scroll-anchoring.enabled")) {
+ gCSSProperties["overflow-anchor"] = {
+ domProp: "overflowAnchor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: [],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.overflow-clip-box.enabled")) {
+ gCSSProperties["overflow-clip-box-block"] = {
+ domProp: "overflowClipBoxBlock",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ initial_values: ["padding-box"],
+ other_values: ["content-box"],
+ invalid_values: ["auto", "border-box", "0", "padding-box padding-box"],
+ };
+ gCSSProperties["overflow-clip-box-inline"] = {
+ domProp: "overflowClipBoxInline",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ initial_values: ["padding-box"],
+ other_values: ["content-box"],
+ invalid_values: ["none", "border-box", "0", "content-box content-box"],
+ };
+ gCSSProperties["overflow-clip-box"] = {
+ domProp: "overflowClipBox",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["overflow-clip-box-block", "overflow-clip-box-inline"],
+ initial_values: ["padding-box"],
+ other_values: [
+ "content-box",
+ "padding-box content-box",
+ "content-box padding-box",
+ "content-box content-box",
+ ],
+ invalid_values: [
+ "none",
+ "auto",
+ "content-box none",
+ "border-box",
+ "0",
+ "content-box, content-box",
+ ],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.overscroll-behavior.enabled")) {
+ gCSSProperties["overscroll-behavior-x"] = {
+ domProp: "overscrollBehaviorX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["contain", "none"],
+ invalid_values: ["left", "1px"],
+ };
+ gCSSProperties["overscroll-behavior-y"] = {
+ domProp: "overscrollBehaviorY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["contain", "none"],
+ invalid_values: ["left", "1px"],
+ };
+ gCSSProperties["overscroll-behavior-inline"] = {
+ domProp: "overscrollBehaviorInline",
+ inherited: false,
+ logical: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["contain", "none"],
+ invalid_values: ["left", "1px"],
+ };
+ gCSSProperties["overscroll-behavior-block"] = {
+ domProp: "overscrollBehaviorBlock",
+ inherited: false,
+ logical: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["contain", "none"],
+ invalid_values: ["left", "1px"],
+ };
+ gCSSProperties["overscroll-behavior"] = {
+ domProp: "overscrollBehavior",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["overscroll-behavior-x", "overscroll-behavior-y"],
+ initial_values: ["auto"],
+ other_values: [
+ "contain",
+ "none",
+ "contain contain",
+ "contain auto",
+ "none contain",
+ ],
+ invalid_values: ["left", "1px", "contain auto none", "contain nonsense"],
+ };
+}
+
+{
+ const patterns = {
+ background: [
+ "{} scroll no-repeat",
+ "{} repeat",
+ "url(404.png), {}, -moz-element(#a) black",
+ ],
+ mask: [
+ "{} add no-repeat",
+ "{} repeat",
+ "url(404.png), {}, -moz-element(#a) alpha",
+ ],
+ };
+
+ for (const prop of ["background", "mask"]) {
+ let i = 0;
+ const p = patterns[prop];
+ for (const v of invalidNonUrlImageValues) {
+ gCSSProperties[prop].invalid_values.push(
+ p[i++ % p.length].replace("{}", v)
+ );
+ }
+ for (const v of validNonUrlImageValues) {
+ gCSSProperties[prop].other_values.push(
+ p[i++ % p.length].replace("{}", v)
+ );
+ }
+ }
+}
+
+gCSSProperties["display"].other_values.push("flow-root");
+
+gCSSProperties["hyphenate-character"] = {
+ domProp: "hyphenateCharacter",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ['"="', '"/-/"', '"\1400"', '""'],
+ invalid_values: ["none", "auto auto", "1400", "U+1400"],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.content-visibility.enabled")) {
+ gCSSProperties["content-visibility"] = {
+ domProp: "contentVisibility",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["visible"],
+ other_values: ["auto", "hidden"],
+ invalid_values: [
+ "invisible",
+ "partially-visible",
+ "auto auto",
+ "visible hidden",
+ ],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) {
+ gCSSProperties["contain-intrinsic-width"] = {
+ domProp: "containIntrinsicWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1em", "1px", "auto 1px", "auto none"],
+ invalid_values: ["auto auto", "auto", "-1px"],
+ };
+ gCSSProperties["contain-intrinsic-height"] = {
+ domProp: "containIntrinsicHeight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1em", "1px", "auto 1px", "auto none"],
+ invalid_values: ["auto auto", "auto", "-1px"],
+ };
+ gCSSProperties["contain-intrinsic-block-size"] = {
+ domProp: "containIntrinsicBlockSize",
+ inherited: false,
+ logical: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1em", "1px", "auto 1px", "auto none"],
+ invalid_values: ["auto auto", "auto", "-1px"],
+ };
+ gCSSProperties["contain-intrinsic-inline-size"] = {
+ domProp: "containIntrinsicInlineSize",
+ inherited: false,
+ logical: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1em", "1px", "auto 1px", "auto none"],
+ invalid_values: ["auto auto", "auto", "-1px"],
+ };
+
+ gCSSProperties["contain-intrinsic-size"] = {
+ domProp: "containIntrinsicSize",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["contain-intrinsic-width", "contain-intrinsic-height"],
+ initial_values: ["none"],
+ other_values: ["1em 1em", "1px 1px", "auto 1px auto 1px", "1px auto 1px"],
+ invalid_values: ["auto auto", "-1px -1px", "1px, auto none"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.container-queries.enabled")) {
+ gCSSProperties["container-type"] = {
+ domProp: "containerType",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["inline-size", "size"],
+ invalid_values: [
+ "none style",
+ "none inline-size",
+ "inline-size none",
+ "style none",
+ "style style",
+ "inline-size style inline-size",
+ "inline-size block-size",
+ "block-size",
+ "block-size style",
+ "size inline-size",
+ "size block-size",
+ ],
+ };
+ gCSSProperties["container-name"] = {
+ domProp: "containerName",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["foo bar", "foo", "baz bazz", "foo foo"],
+ invalid_values: ["foo unset", "none bar", "foo initial", "initial foo"],
+ };
+ gCSSProperties["container"] = {
+ domProp: "container",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["container-type", "container-name"],
+ initial_values: ["none"],
+ other_values: ["foo / size", "foo bar / size", "foo / inline-size", "foo"],
+ invalid_values: ["size / foo", "size / foo bar"],
+ };
+}
+
+if (false) {
+ // TODO These properties are chrome-only, and are not exposed via CSSOM.
+ // We may still want to find a way to test them. See bug 1206999.
+ gCSSProperties["-moz-window-shadow"] = {
+ //domProp: "MozWindowShadow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["default"],
+ other_values: ["none", "menu", "tooltip", "sheet", "cliprounded"],
+ invalid_values: [],
+ };
+
+ gCSSProperties["-moz-window-opacity"] = {
+ // domProp: "MozWindowOpacity",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [
+ "1",
+ "17",
+ "397.376",
+ "3e1",
+ "3e+1",
+ "3e0",
+ "3e+0",
+ "3e-0",
+ "300%",
+ ],
+ other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%"],
+ invalid_values: ["0px", "1px", "20%", "default", "auto"],
+ };
+
+ gCSSProperties["-moz-window-transform"] = {
+ // domProp: "MozWindowTransform",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { width: "300px", height: "50px" },
+ initial_values: ["none"],
+ other_values: [
+ "translatex(1px)",
+ "translatex(4em)",
+ "translatex(-4px)",
+ "translatex(3px)",
+ "translatex(0px) translatex(1px) translatex(2px) translatex(3px) translatex(4px)",
+ "translatey(4em)",
+ "translate(3px)",
+ "translate(10px, -3px)",
+ "rotate(45deg)",
+ "rotate(45grad)",
+ "rotate(45rad)",
+ "rotate(0.25turn)",
+ "rotate(0)",
+ "scalex(10)",
+ "scalex(10%)",
+ "scalex(-10)",
+ "scalex(-10%)",
+ "scaley(10)",
+ "scaley(10%)",
+ "scaley(-10)",
+ "scaley(-10%)",
+ "scale(10)",
+ "scale(10%)",
+ "scale(10, 20)",
+ "scale(10%, 20%)",
+ "scale(-10)",
+ "scale(-10%)",
+ "scale(-10, 20)",
+ "scale(10%, -20%)",
+ "scale(10, 20%)",
+ "scale(-10, 20%)",
+ "skewx(30deg)",
+ "skewx(0)",
+ "skewy(0)",
+ "skewx(30grad)",
+ "skewx(30rad)",
+ "skewx(0.08turn)",
+ "skewy(30deg)",
+ "skewy(30grad)",
+ "skewy(30rad)",
+ "skewy(0.08turn)",
+ "rotate(45deg) scale(2, 1)",
+ "skewx(45deg) skewx(-50grad)",
+ "translate(0, 0) scale(1, 1) skewx(0) skewy(0) matrix(1, 0, 0, 1, 0, 0)",
+ "translatex(50%)",
+ "translatey(50%)",
+ "translate(50%)",
+ "translate(3%, 5px)",
+ "translate(5px, 3%)",
+ "matrix(1, 2, 3, 4, 5, 6)",
+ /* valid calc() values */
+ "translatex(calc(5px + 10%))",
+ "translatey(calc(0.25 * 5px + 10% / 3))",
+ "translate(calc(5px - 10% * 3))",
+ "translate(calc(5px - 3 * 10%), 50px)",
+ "translate(-50px, calc(5px - 10% * 3))",
+ "translatez(1px)",
+ "translatez(4em)",
+ "translatez(-4px)",
+ "translatez(0px)",
+ "translatez(2px) translatez(5px)",
+ "translate3d(3px, 4px, 5px)",
+ "translate3d(2em, 3px, 1em)",
+ "translatex(2px) translate3d(4px, 5px, 6px) translatey(1px)",
+ "scale3d(4, 4, 4)",
+ "scale3d(4%, 4%, 4%)",
+ "scale3d(-2, 3, -7)",
+ "scale3d(-2%, 3%, -7%)",
+ "scalez(4)",
+ "scalez(4%)",
+ "scalez(-6)",
+ "scalez(-6%)",
+ "rotate3d(2, 3, 4, 45deg)",
+ "rotate3d(-3, 7, 0, 12rad)",
+ "rotatex(15deg)",
+ "rotatey(-12grad)",
+ "rotatez(72rad)",
+ "rotatex(0.125turn)",
+ "perspective(0px)",
+ "perspective(1000px)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)",
+ "translate(10px, calc(min(5px,10%)))",
+ "translate(calc(max(5px,10%)), 10%)",
+ "translate(max(5px,10%), 10%)",
+ ],
+ invalid_values: [
+ "1px",
+ "#0000ff",
+ "red",
+ "auto",
+ "translatex(1)",
+ "translatey(1)",
+ "translate(2)",
+ "translate(-3, -4)",
+ "translatex(1px 1px)",
+ "translatex(translatex(1px))",
+ "translatex(#0000ff)",
+ "translatex(red)",
+ "translatey()",
+ "matrix(1px, 2px, 3px, 4px, 5px, 6px)",
+ "skewx(red)",
+ "matrix(1%, 0, 0, 0, 0px, 0px)",
+ "matrix(0, 1%, 2, 3, 4px,5px)",
+ "matrix(0, 1, 2%, 3, 4px, 5px)",
+ "matrix(0, 1, 2, 3%, 4%, 5%)",
+ "matrix(1, 2, 3, 4, 5px, 6%)",
+ "matrix(1, 2, 3, 4, 5%, 6px)",
+ "matrix(1, 2, 3, 4, 5%, 6%)",
+ "matrix(1, 2, 3, 4, 5px, 6em)",
+ /* invalid calc() values */
+ "translatey(-moz-min(5px,10%))",
+ "translatex(-moz-max(5px,10%))",
+ "matrix(1, 0, 0, 1, max(5px * 3), calc(10% - 3px))",
+ "perspective(-10px)",
+ "matrix3d(dinosaur)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15%, 16)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16px)",
+ "rotatey(words)",
+ "rotatex(7)",
+ "translate3d(3px, 4px, 1px, 7px)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13px, 14em, 15px, 16)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20%, 10%, 15, 16)",
+ ],
+ };
+
+ gCSSProperties["-moz-window-transform-origin"] = {
+ // domProp: "MozWindowTransformOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* no subproperties */
+ prerequisites: { width: "10px", height: "10px", display: "block" },
+ initial_values: ["50% 50%", "center", "center center"],
+ other_values: [
+ "25% 25%",
+ "6px 5px",
+ "20% 3em",
+ "0 0",
+ "0in 1in",
+ "top",
+ "bottom",
+ "top left",
+ "top right",
+ "top center",
+ "center left",
+ "center right",
+ "bottom left",
+ "bottom right",
+ "bottom center",
+ "20% center",
+ "6px center",
+ "13in bottom",
+ "left 50px",
+ "right 13%",
+ "center 40px",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ ],
+ invalid_values: [
+ "red",
+ "auto",
+ "none",
+ "0.5 0.5",
+ "40px #0000ff",
+ "border",
+ "center red",
+ "right diagonal",
+ "#00ffff bottom",
+ "0px calc(0px + rubbish)",
+ "0px 0px calc(0px + rubbish)",
+ "6px 5px 5px",
+ "top center 10px",
+ ],
+ };
+
+ gCSSProperties["-moz-context-properties"] = {
+ //domProp: "MozContextProperties",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "fill",
+ "stroke",
+ "fill, stroke",
+ "fill, stroke, fill",
+ "fill, foo",
+ "foo",
+ ],
+ invalid_values: [
+ "default",
+ "fill, auto",
+ "all, stroke",
+ "none, fill",
+ "fill, none",
+ "fill, default",
+ "2px",
+ ],
+ };
+}
+
+gCSSProperties["scrollbar-color"] = {
+ domProp: "scrollbarColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["red green", "blue yellow", "#ffff00 white"],
+ invalid_values: ["ffff00 red", "auto red", "red auto", "green"],
+};
+
+gCSSProperties["scrollbar-width"] = {
+ domProp: "scrollbarWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none", "thin"],
+ invalid_values: ["1px"],
+};
+
+gCSSProperties["offset"] = {
+ domProp: "offset",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "offset-path",
+ "offset-distance",
+ "offset-rotate",
+ "offset-anchor",
+ ],
+ initial_values: ["none"],
+ other_values: [
+ "none 30deg reverse",
+ "none 50px reverse 30deg",
+ "none calc(10px + 20%) auto",
+ "none reverse",
+ "none / left center",
+ "path('M 0 0 H 1') -200% auto",
+ "path('M 0 0 H 1') -200%",
+ "path('M 0 0 H 1') 50px",
+ "path('M 0 0 H 1') auto",
+ "path('M 0 0 H 1') reverse 30deg 50px",
+ "path('M 0 0 H 1')",
+ "path('m 20 0 h 100') -7rad 8px / auto",
+ "path('m 0 30 v 100') -7rad 8px / left top",
+ "path('m 0 0 h 100') -7rad 8px",
+ "path('M 0 0 H 100') 100px 0deg",
+ ],
+ invalid_values: [
+ "100px 0deg path('m 0 0 h 100')",
+ "30deg",
+ "auto 30deg 100px",
+ "auto / none",
+ "none /",
+ "none / 100px 20px 30deg",
+ "path('M 20 30 A 60 70 80') bottom",
+ "path('M 20 30 A 60 70 80') bottom top",
+ "path('M 20 30 A 60 70 80') 100px 200px",
+ "path('M 20 30 A 60 70 80') reverse auto",
+ "path('M 20 30 A 60 70 80') reverse 10px 30deg",
+ "path('M 20 30 A 60 70 80') /",
+ ],
+};
+
+gCSSProperties["offset-path"] = {
+ domProp: "offsetPath",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [...pathValues.other_values],
+ invalid_values: ["path('')"].concat(pathValues.invalid_values),
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.motion-path-ray.enabled")) {
+ gCSSProperties["offset-path"]["other_values"].push(
+ "ray(0deg)",
+ "ray(45deg closest-side)",
+ "ray(0rad farthest-side)",
+ "ray(0.5turn closest-corner contain)",
+ "ray(200grad farthest-corner)",
+ "ray(sides 180deg)",
+ "ray(contain farthest-side 180deg)",
+ "ray(calc(180deg - 45deg) farthest-side)",
+ "ray(0deg at center center)",
+ "ray(at 10% 10% 1rad)"
+ );
+
+ gCSSProperties["offset-path"]["invalid_values"].push(
+ "ray(closest-side)",
+ "ray(0deg, closest-side)",
+ "ray(contain 0deg closest-side contain)"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.motion-path-basic-shapes.enabled")) {
+ gCSSProperties["offset-path"]["other_values"].push(
+ ...basicShapeOtherValues,
+ ...basicShapeXywhRectValues
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.motion-path-url.enabled")) {
+ gCSSProperties["offset-path"]["other_values"].push("url(#svgPath)");
+}
+
+gCSSProperties["offset-distance"] = {
+ domProp: "offsetDistance",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["10px", "10%", "190%", "-280%", "calc(30px + 40%)"],
+ invalid_values: ["none", "45deg"],
+};
+
+gCSSProperties["offset-rotate"] = {
+ domProp: "offsetRotate",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["reverse", "0deg", "0rad reverse", "-45deg", "5turn auto"],
+ invalid_values: ["none", "10px", "reverse 0deg reverse", "reverse auto"],
+};
+
+gCSSProperties["offset-anchor"] = {
+ domProp: "offsetAnchor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "left bottom",
+ "center center",
+ "calc(20% + 10px) center",
+ "right 30em",
+ "10px 20%",
+ "left -10px top -20%",
+ "right 10% bottom 20em",
+ ],
+ invalid_values: ["none", "10deg", "left 10% top"],
+};
+
+if (
+ IsCSSPropertyPrefEnabled("layout.css.motion-path-offset-position.enabled")
+) {
+ gCSSProperties["offset"]["subproperties"].push("offset-position");
+ gCSSProperties["offset"]["other_values"].push("top right / top left");
+
+ if (IsCSSPropertyPrefEnabled("layout.css.motion-path-ray.enabled")) {
+ gCSSProperties["offset"]["other_values"].push(
+ "top right ray(45deg closest-side)",
+ "50% 50% ray(0rad farthest-side)"
+ );
+ }
+
+ gCSSProperties["offset-position"] = {
+ domProp: "offsetPosition",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "auto",
+ "left bottom",
+ "center center",
+ "calc(20% + 10px) center",
+ "right 30em",
+ "10px 20%",
+ "left -10px top -20%",
+ "right 10% bottom 20em",
+ ],
+ invalid_values: ["none", "10deg", "left 10% top"],
+ };
+}
+
+{
+ let linear_function_other_values = [
+ "linear(0, 1)",
+ "linear(0 0% 50%, 1 50% 100%)",
+ ];
+
+ let linear_function_invalid_values = [
+ "linear()",
+ "linear(0.5)",
+ "linear(0% 0 100%)",
+ "linear(0,)",
+ ];
+ gCSSProperties["animation-timing-function"].other_values.push(
+ ...linear_function_other_values
+ );
+ gCSSProperties["animation-timing-function"].invalid_values.push(
+ ...linear_function_invalid_values
+ );
+
+ gCSSProperties["transition-timing-function"].other_values.push(
+ ...linear_function_other_values
+ );
+ gCSSProperties["transition-timing-function"].invalid_values.push(
+ ...linear_function_invalid_values
+ );
+
+ gCSSProperties["animation"].other_values.push(
+ "1s 2s linear(0, 1) bounce",
+ "4s linear(0, 0.5 25% 75%, 1 100% 100%)"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.backdrop-filter.enabled")) {
+ gCSSProperties["backdrop-filter"] = {
+ domProp: "backdropFilter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: gCSSProperties["filter"].other_values,
+ invalid_values: gCSSProperties["filter"].invalid_values,
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.math-depth.enabled")) {
+ gCSSProperties["math-depth"] = {
+ domProp: "mathDepth",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: [
+ // auto-add cannot be tested here because it has no effect when the
+ // inherited math-style is equal to the default (normal).
+ "123",
+ "-123",
+ "add(123)",
+ "add(-123)",
+ "calc(1 + 2*3)",
+ "add(calc(4 - 2/3))",
+ ],
+ invalid_values: ["auto", "1,23", "1.23", "add(1,23)", "add(1.23)"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.math-style.enabled")) {
+ gCSSProperties["math-style"] = {
+ domProp: "mathStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["compact"],
+ invalid_values: [],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.forced-color-adjust.enabled")) {
+ gCSSProperties["forced-color-adjust"] = {
+ domProp: "forcedColorAdjust",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: [],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.animation-composition.enabled")) {
+ gCSSProperties["animation-composition"] = {
+ domProp: "animationComposition",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["replace"],
+ other_values: [
+ "add",
+ "accumulate",
+ "replace, add",
+ "add, accumulate",
+ "replace, add, accumulate",
+ ],
+ invalid_values: ["all", "none"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.animations")) {
+ Object.assign(gCSSProperties, {
+ "-moz-animation": {
+ domProp: "MozAnimation",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ alias_for: "animation",
+ subproperties: [
+ "animation-name",
+ "animation-duration",
+ "animation-timing-function",
+ "animation-delay",
+ "animation-direction",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-play-state",
+ ],
+ },
+ "-moz-animation-delay": {
+ domProp: "MozAnimationDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-delay",
+ subproperties: ["animation-delay"],
+ },
+ "-moz-animation-direction": {
+ domProp: "MozAnimationDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-direction",
+ subproperties: ["animation-direction"],
+ },
+ "-moz-animation-duration": {
+ domProp: "MozAnimationDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-duration",
+ subproperties: ["animation-duration"],
+ },
+ "-moz-animation-fill-mode": {
+ domProp: "MozAnimationFillMode",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-fill-mode",
+ subproperties: ["animation-fill-mode"],
+ },
+ "-moz-animation-iteration-count": {
+ domProp: "MozAnimationIterationCount",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-iteration-count",
+ subproperties: ["animation-iteration-count"],
+ },
+ "-moz-animation-name": {
+ domProp: "MozAnimationName",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-name",
+ subproperties: ["animation-name"],
+ },
+ "-moz-animation-play-state": {
+ domProp: "MozAnimationPlayState",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-play-state",
+ subproperties: ["animation-play-state"],
+ },
+ "-moz-animation-timing-function": {
+ domProp: "MozAnimationTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-timing-function",
+ subproperties: ["animation-timing-function"],
+ },
+ });
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.scroll-driven-animations.enabled")) {
+ // Basically, web-platform-tests should cover most cases, so here we only
+ // put some basic test cases.
+ gCSSProperties["animation"].subproperties.push("animation-timeline");
+ gCSSProperties["animation"].initial_values.push(
+ "none none 0s 0s ease normal running 1.0 auto",
+ "none none auto"
+ );
+ gCSSProperties["animation"].other_values.push(
+ "none none 0s 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) normal running 1.0 auto",
+ "bounce 1s linear 2s timeline",
+ "bounce 1s 2s linear none",
+ "bounce timeline",
+ "2s, 1s bounce timeline",
+ "1s bounce timeline, 2s",
+ "1s bounce none, 2s none auto"
+ );
+
+ gCSSProperties["-moz-animation"].subproperties.push("animation-timeline");
+ gCSSProperties["-webkit-animation"].subproperties.push("animation-timeline");
+
+ gCSSProperties["animation-timeline"] = {
+ domProp: "animationTimeline",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["auto"],
+ other_values: [
+ "none",
+ "all",
+ "ball",
+ "mall",
+ "color",
+ "bounce, bubble, opacity",
+ "foobar",
+ "\\32bounce",
+ "-bounce",
+ "-\\32bounce",
+ "\\32 0bounce",
+ "-\\32 0bounce",
+ "\\2bounce",
+ "-\\2bounce",
+ "scroll()",
+ "scroll(block)",
+ "scroll(inline)",
+ "scroll(horizontal)",
+ "scroll(vertical)",
+ "scroll(root)",
+ "scroll(nearest)",
+ "scroll(inline nearest)",
+ "scroll(vertical root)",
+ "scroll(root horizontal)",
+ "view()",
+ "view(inline)",
+ "view(auto)",
+ "view(auto 1px)",
+ "view(inline auto)",
+ "view(vertical auto auto)",
+ "view(horizontal 1px 1%)",
+ "view(1px 1% block)",
+ ],
+ invalid_values: [
+ "bounce, initial",
+ "initial, bounce",
+ "bounce, inherit",
+ "inherit, bounce",
+ "bounce, unset",
+ "unset, bounce",
+ ],
+ };
+
+ gCSSProperties["scroll-timeline-name"] = {
+ domProp: "scrollTimelineName",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "all",
+ "auto",
+ "ball",
+ "mall",
+ "color",
+ "foobar",
+ "\\32bounce",
+ "-bounce",
+ "-\\32bounce",
+ "\\32 0bounce",
+ "-\\32 0bounce",
+ "\\2bounce",
+ "-\\2bounce",
+ ],
+ invalid_values: ["abc bounce", "10px", "rgb(1, 2, 3)"],
+ };
+
+ gCSSProperties["scroll-timeline-axis"] = {
+ domProp: "scrollTimelineAxis",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["block"],
+ other_values: ["inline", "vertical", "horizontal"],
+ invalid_values: ["auto", "none", "abc"],
+ };
+
+ gCSSProperties["scroll-timeline"] = {
+ domProp: "scrollTimeline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-timeline-name", "scroll-timeline-axis"],
+ initial_values: ["none block", "none"],
+ other_values: [
+ "auto inline",
+ "bounce inline",
+ "bounce vertical",
+ "\\32bounce inline",
+ "-bounce block",
+ "\\32 0bounce vertical",
+ "-\\32 0bounce horizontal",
+ "a, b, c",
+ "a block, b inline, c vertical",
+ ],
+ invalid_values: ["", "bounce bounce", "horizontal a", "block abc"],
+ };
+
+ gCSSProperties["view-timeline-name"] = {
+ domProp: "viewTimelineName",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "all",
+ "auto",
+ "ball",
+ "mall",
+ "color",
+ "foobar",
+ "\\32bounce",
+ "-bounce",
+ "-\\32bounce",
+ "\\32 0bounce",
+ "-\\32 0bounce",
+ "\\2bounce",
+ "-\\2bounce",
+ "bounce, abc",
+ "none, none",
+ ],
+ invalid_values: ["abc bounce", "10px", "rgb(1, 2, 3)"],
+ };
+
+ gCSSProperties["view-timeline-axis"] = {
+ domProp: "viewTimelineAxis",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["block"],
+ other_values: ["inline", "vertical", "horizontal", "inline, block"],
+ invalid_values: ["auto", "none", "abc", "inline block"],
+ };
+
+ gCSSProperties["view-timeline-inset"] = {
+ domProp: "viewTimelineInset",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["0px", "1%", "1px 1%", "0px 0%", "calc(0px) auto"],
+ invalid_values: ["none", "rgb(1, 2, 3)", "foo bar", "1px 2px 3px"],
+ };
+
+ gCSSProperties["view-timeline"] = {
+ domProp: "viewTimeline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["view-timeline-name", "view-timeline-axis"],
+ initial_values: ["none block", "none"],
+ other_values: [
+ "auto inline",
+ "bounce inline",
+ "bounce vertical",
+ "\\32bounce inline",
+ "-bounce block",
+ "\\32 0bounce vertical",
+ "-\\32 0bounce horizontal",
+ "a, b, c",
+ "a block, b inline, c vertical",
+ ],
+ invalid_values: ["", ",", "abc abc", "horizontal a", "block abc"],
+ };
+}
+
+gCSSProperties["scrollbar-gutter"] = {
+ domProp: "scrollbarGutter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["stable", "stable both-edges", "both-edges stable"],
+ invalid_values: [
+ "auto stable",
+ "auto both-edges",
+ "both-edges",
+ "stable mirror",
+ // The following values are from scrollbar-gutter extension in CSS
+ // Overflow 4 https://drafts.csswg.org/css-overflow-4/#sbg-ext.
+ "always",
+ "always both-edges",
+ "always force",
+ "always both-edges force",
+ "stable both-edges force",
+ "match-parent",
+ ],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.text-wrap-balance.enabled")) {
+ gCSSProperties["text-wrap-style"] = {
+ domProp: "textWrapStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["auto"],
+ other_values: ["stable", "balance"],
+ invalid_values: ["wrap", "nowrap", "normal"],
+ };
+ gCSSProperties["text-wrap"].subproperties.push("text-wrap-style");
+ gCSSProperties["text-wrap"].other_values.push("stable");
+ gCSSProperties["text-wrap"].other_values.push("balance");
+ gCSSProperties["text-wrap"].other_values.push("wrap stable");
+ gCSSProperties["text-wrap"].other_values.push("nowrap balance");
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.transforms")) {
+ Object.assign(gCSSProperties, {
+ "-moz-transform": {
+ domProp: "MozTransform",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform",
+ subproperties: ["transform"],
+ },
+ "-moz-transform-origin": {
+ domProp: "MozTransformOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-origin",
+ subproperties: ["transform-origin"],
+ },
+ "-moz-perspective-origin": {
+ domProp: "MozPerspectiveOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective-origin",
+ subproperties: ["perspective-origin"],
+ },
+ "-moz-perspective": {
+ domProp: "MozPerspective",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective",
+ subproperties: ["perspective"],
+ },
+ "-moz-backface-visibility": {
+ domProp: "MozBackfaceVisibility",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "backface-visibility",
+ subproperties: ["backface-visibility"],
+ },
+ "-moz-transform-style": {
+ domProp: "MozTransformStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-style",
+ subproperties: ["transform-style"],
+ },
+ });
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.zoom.enabled")) {
+ Object.assign(gCSSProperties, {
+ zoom: {
+ domProp: "zoom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal", "1", "100%", "0", "0%"],
+ other_values: ["1.5", "2", "150%", "200%"],
+ invalid_values: ["-1", "-40%"],
+ },
+ });
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.transitions")) {
+ Object.assign(gCSSProperties, {
+ "-moz-transition": {
+ domProp: "MozTransition",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ alias_for: "transition",
+ subproperties: [
+ "transition-property",
+ "transition-duration",
+ "transition-timing-function",
+ "transition-delay",
+ ],
+ },
+ "-moz-transition-delay": {
+ domProp: "MozTransitionDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-delay",
+ subproperties: ["transition-delay"],
+ },
+ "-moz-transition-duration": {
+ domProp: "MozTransitionDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-duration",
+ subproperties: ["transition-duration"],
+ },
+ "-moz-transition-property": {
+ domProp: "MozTransitionProperty",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-property",
+ subproperties: ["transition-property"],
+ },
+ "-moz-transition-timing-function": {
+ domProp: "MozTransitionTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-timing-function",
+ subproperties: ["transition-timing-function"],
+ },
+ });
+}
+
+// Copy aliased properties' fields from their alias targets. Keep this logic
+// at the bottom of this file to ensure all the aliased properties are
+// processed.
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ if (entry.alias_for) {
+ var aliasTargetEntry = gCSSProperties[entry.alias_for];
+ if (!aliasTargetEntry) {
+ ok(
+ false,
+ "Alias '" +
+ prop +
+ "' alias_for field, '" +
+ entry.alias_for +
+ "', " +
+ "must be set to a recognized CSS property in gCSSProperties"
+ );
+ } else {
+ // Copy 'values' fields & 'prerequisites' field from aliasTargetEntry:
+ var fieldsToCopy = [
+ "initial_values",
+ "other_values",
+ "invalid_values",
+ "quirks_values",
+ "unbalanced_values",
+ "prerequisites",
+ ];
+
+ fieldsToCopy.forEach(function (fieldName) {
+ // (Don't copy the field if the alias already has something there,
+ // or if the aliased property doesn't have anything to copy.)
+ if (!(fieldName in entry) && fieldName in aliasTargetEntry) {
+ entry[fieldName] = aliasTargetEntry[fieldName];
+ }
+ });
+ }
+ }
+}
diff --git a/layout/style/test/redirect.sjs b/layout/style/test/redirect.sjs
new file mode 100644
index 0000000000..43fec90b5a
--- /dev/null
+++ b/layout/style/test/redirect.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", request.queryString, false);
+}
diff --git a/layout/style/test/redundant_font_download.sjs b/layout/style/test/redundant_font_download.sjs
new file mode 100644
index 0000000000..09236563de
--- /dev/null
+++ b/layout/style/test/redundant_font_download.sjs
@@ -0,0 +1,63 @@
+"use strict";
+
+const BinaryOutputStream = Components.Constructor(
+ "@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream"
+);
+
+// this is simply a hex dump of a red square .PNG image
+// prettier-ignore
+const RED_SQUARE =
+ [
+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00,
+ 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x20, 0x08, 0x02, 0x00, 0x00, 0x00, 0xFC,
+ 0x18, 0xED, 0xA3, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
+ 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x28,
+ 0x49, 0x44, 0x41, 0x54, 0x48, 0xC7, 0xED, 0xCD, 0x41, 0x0D,
+ 0x00, 0x00, 0x08, 0x04, 0xA0, 0xD3, 0xFE, 0x9D, 0x35, 0x85,
+ 0x0F, 0x37, 0x28, 0x40, 0x4D, 0x6E, 0x75, 0x04, 0x02, 0x81,
+ 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0xC1, 0x93, 0x60, 0x01,
+ 0xA3, 0xC4, 0x01, 0x3F, 0x58, 0x1D, 0xEF, 0x27, 0x00, 0x00,
+ 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
+ ];
+
+function handleRequest(request, response) {
+ let query = {};
+ request.queryString.split("&").forEach(function (val) {
+ let [name, value] = val.split("=");
+ query[name] = unescape(value);
+ });
+
+ response.setHeader("Cache-Control", "no-cache");
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ let log = getState("bug-879963-request-log") || "";
+
+ let stream = new BinaryOutputStream(response.bodyOutputStream);
+
+ if (query.q == "init") {
+ log = "init"; // initialize the log, and return a PNG image
+ response.setHeader("Content-Type", "image/png", false);
+ stream.writeByteArray(RED_SQUARE);
+ } else if (query.q == "image") {
+ log = log + ";" + query.q;
+ response.setHeader("Content-Type", "image/png", false);
+ stream.writeByteArray(RED_SQUARE);
+ } else if (query.q == "font") {
+ log = log + ";" + query.q;
+ // we don't provide a real font; that's ok, OTS will just reject it
+ response.write("Junk");
+ } else if (query.q == "report") {
+ // don't include the actual "report" request in the log we return
+ response.write(log);
+ } else {
+ log = log + ";" + query.q;
+ response.setStatusLine(request.httpVersion, 404, "Not Found");
+ }
+
+ setState("bug-879963-request-log", log);
+}
diff --git a/layout/style/test/slow_broken_sheet.sjs b/layout/style/test/slow_broken_sheet.sjs
new file mode 100644
index 0000000000..6af03ee4c6
--- /dev/null
+++ b/layout/style/test/slow_broken_sheet.sjs
@@ -0,0 +1,19 @@
+// Make sure our timer stays alive.
+let gTimer;
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine("1.1", 404, "Not Found");
+ response.processAsync();
+
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ // Wait for 1s before responding; this should usually make sure this load comes in last.
+ gTimer.init(
+ () => {
+ response.write("<h1>Hello</h1>");
+ response.finish();
+ },
+ 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/layout/style/test/slow_load.sjs b/layout/style/test/slow_load.sjs
new file mode 100644
index 0000000000..3873f466ce
--- /dev/null
+++ b/layout/style/test/slow_load.sjs
@@ -0,0 +1,29 @@
+// Make sure our timer stays alive.
+let gTimer;
+
+function handleRequest(request, response) {
+ let isCss = request.queryString.indexOf("css") != -1;
+
+ response.setHeader("Content-Type", isCss ? "text/css" : "text/plain", false);
+ response.setStatusLine("1.1", 200, "OK");
+ response.processAsync();
+
+ let time = Date.now();
+
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ // Wait for 1s before responding; this should usually make sure this load comes in last.
+ gTimer.init(
+ () => {
+ if (isCss) {
+ // FIXME(emilio): This clamps the date to the 32-bit integer range which
+ // is what we use to store the z-index... We don't seem to store f64s
+ // anywhere in the specified values...
+ time = time % (Math.pow(2, 31) - 1);
+ response.write(":root { z-index: " + time + "}");
+ }
+ response.finish();
+ },
+ 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/layout/style/test/slow_ok_sheet.sjs b/layout/style/test/slow_ok_sheet.sjs
new file mode 100644
index 0000000000..5673bb2be8
--- /dev/null
+++ b/layout/style/test/slow_ok_sheet.sjs
@@ -0,0 +1,21 @@
+// Make sure our timer stays alive.
+let gTimer;
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/css", false);
+ response.setStatusLine("1.1", 200, "OK");
+ response.processAsync();
+
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ // Wait for 1s before responding; this should usually make sure this load comes in last.
+ gTimer.init(
+ () => {
+ // This sheet _does_ still get applied even though its importing link
+ // overall reports failure...
+ response.write("nosuchelement { background: red }");
+ response.finish();
+ },
+ 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/layout/style/test/sourcemap_css.html b/layout/style/test/sourcemap_css.html
new file mode 100644
index 0000000000..1f29a38d5f
--- /dev/null
+++ b/layout/style/test/sourcemap_css.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for bug 1306887</title>
+ <link rel="stylesheet" type="text/css" href="mapped.css"/>
+ <link rel="stylesheet" type="text/css" href="mapped2.css"/>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1306887">Mozilla Bug 1306887</a>
+ </body>
+</html>
diff --git a/layout/style/test/style_attribute_tests.js b/layout/style/test/style_attribute_tests.js
new file mode 100644
index 0000000000..243ec3380d
--- /dev/null
+++ b/layout/style/test/style_attribute_tests.js
@@ -0,0 +1,25 @@
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("load", runTests);
+
+function runTests(event) {
+ if (event.target != document) {
+ return;
+ }
+
+ var elt = document.getElementById("content");
+
+ elt.setAttribute("style", "color: blue; background-color: fuchsia");
+ is(elt.style.color, "blue", "setting correct style attribute (color)");
+ is(
+ elt.style.backgroundColor,
+ "fuchsia",
+ "setting correct style attribute (color)"
+ );
+
+ elt.setAttribute("style", "{color: blue; background-color: fuchsia}");
+ is(elt.style.color, "", "setting braced style attribute (color)");
+ is(elt.style.backgroundColor, "", "setting braced style attribute (color)");
+
+ SimpleTest.finish();
+}
diff --git a/layout/style/test/support/1x1-transparent.png b/layout/style/test/support/1x1-transparent.png
new file mode 100644
index 0000000000..56cd9eb930
--- /dev/null
+++ b/layout/style/test/support/1x1-transparent.png
Binary files differ
diff --git a/layout/style/test/support/blue-100x100.png b/layout/style/test/support/blue-100x100.png
new file mode 100644
index 0000000000..3b72d5ce53
--- /dev/null
+++ b/layout/style/test/support/blue-100x100.png
Binary files differ
diff --git a/layout/style/test/support/external-variable-url.css b/layout/style/test/support/external-variable-url.css
new file mode 100644
index 0000000000..d730ac0cea
--- /dev/null
+++ b/layout/style/test/support/external-variable-url.css
@@ -0,0 +1,3 @@
+#t4 {
+ --a: url('image.png');
+}
diff --git a/layout/style/test/test_acid3_test46.html b/layout/style/test/test_acid3_test46.html
new file mode 100644
index 0000000000..4ec50cfddc
--- /dev/null
+++ b/layout/style/test/test_acid3_test46.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=156716
+-->
+<!--
+
+This is test 46 from the Acid3 test, http://acid3.acidtests.org/
+extracted from the test framework there and put into Mochitest.
+
+(from irc.mozilla.org, developers)
+[2008-05-14 18:07:38] <Hixie> dbaron: I hereby grant all files available from the server http://acid3.acidtests.org/ under the following license: (c) copyright 2008 Ian Hickson. These documents may be used under the terms of any of the following licenses: MPL. GPL. LGPL. BSD.
+
+-->
+<head>
+ <title>Test for Bug 156716</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style type="text/css">
+ iframe#selectors { width: 0; height: 0; }
+ </style>
+ <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=156716">Mozilla Bug 156716</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 156716 **/
+SimpleTest.waitForExplicitFinish();
+function runTest() {
+
+ function getTestDocument() {
+ var iframe = document.getElementById("selectors");
+ var doc = iframe.contentDocument;
+ for (var i = doc.documentElement.childNodes.length-1; i >= 0; i -= 1)
+ doc.documentElement.removeChild(doc.documentElement.childNodes[i]);
+ doc.documentElement.appendChild(doc.createElement('head'));
+ doc.documentElement.firstChild.appendChild(doc.createElement('title'));
+ doc.documentElement.appendChild(doc.createElement('body'));
+ return doc;
+ }
+
+ // test 46: media queries
+ var doc = getTestDocument();
+ var style = doc.createElement('style');
+ style.setAttribute('type', 'text/css');
+ style.appendChild(doc.createTextNode('@media all and (min-color: 0) { #a { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media not all and (min-color: 0) { #b { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media only all and (min-color: 0) { #c { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media (bogus) { #d { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and (bogus) { #e { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media not all and (bogus) { #f { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media only all and (bogus) { #g { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media (bogus), all { #h { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all and (bogus), all { #i { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media not all and (bogus), all { #j { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media only all and (bogus), all { #k { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all, (bogus) { #l { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all, all and (bogus) { #m { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all, not all and (bogus) { #n { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all, only all and (bogus) { #o { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all and color { #p { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and min-color: 0 { #q { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all, all and color { #r { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all, all and min-color: 0 { #s { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and min-color: 0, all { #t { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media (max-color: 0) and (max-monochrome: 0) { #u { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media (min-color: 1), (min-monochrome: 1) { #v { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all and (min-color: 0) and (min-monochrome: 0) { #w { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media not all and (min-color: 1), not all and (min-monochrome: 1) { #x { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all and (min-height: 1em) and (min-width: 1em) { #y1 { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and (max-height: 1em) and (min-width: 1em) { #y2 { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and (min-height: 1em) and (max-width: 1em) { #y3 { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and (max-height: 1em) and (max-width: 1em) { #y4 { text-transform: uppercase; } }')); // matches
+ doc.getElementsByTagName('head')[0].appendChild(style);
+ var names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y1', 'y2', 'y3', 'y4'];
+ for (var i in names) {
+ let p = doc.createElement('p');
+ p.id = names[i];
+ doc.body.appendChild(p);
+ }
+ var count = 0;
+ var check = function (c, e) {
+ count += 1;
+ let p = doc.getElementById(c);
+ is(doc.defaultView.getComputedStyle(p).textTransform, e ? 'uppercase' : 'none', "case " + c + " failed (index " + count + ")");
+ }
+ check('a', true); // 1
+ check('b', false);
+ check('c', true);
+ check('d', false);
+ check('e', false);
+ check('f', false); // true in old spec; commented out in real Acid3
+ check('g', false);
+ check('h', true);
+ check('i', true);
+ check('j', true); // 10
+ check('k', true);
+ check('l', true);
+ check('m', true);
+ check('n', true);
+ check('o', true);
+ check('p', false);
+ check('q', false);
+ check('r', true); // false in old spec
+ check('s', true); // false in old spec
+ check('t', true); // 20 - false in old spec
+ check('u', false);
+ check('v', true);
+ check('w', true);
+ check('x', true);
+ // here the viewport is 0x0
+ check('y1', false); // 25
+ check('y2', false);
+ check('y3', false);
+ check('y4', true);
+ document.getElementById("selectors").setAttribute("style", "height: 100px; width: 100px");
+ // now the viewport is more than 1em by 1em
+ check('y1', true); // 29
+ check('y2', false);
+ check('y3', false);
+ check('y4', false);
+ document.getElementById("selectors").removeAttribute("style");
+ // here the viewport is 0x0 again
+ check('y1', false); // 33
+ check('y2', false);
+ check('y3', false);
+ check('y4', true);
+ SimpleTest.finish();
+}
+</script>
+</pre>
+<p id="display">
+ <iframe src="empty.html" id="selectors" onload="runTest()"></iframe>
+</p>
+</body>
+</html>
diff --git a/layout/style/test/test_addSheet.html b/layout/style/test/test_addSheet.html
new file mode 100644
index 0000000000..06f9f93fc3
--- /dev/null
+++ b/layout/style/test/test_addSheet.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for addSheet</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1024707">Mozilla Bug 1024707</a>
+
+<iframe id="iframe1" src="additional_sheets_helper.html"></iframe>
+<iframe id="iframe2" src="additional_sheets_helper.html"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+
+let gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+
+let gSSService = SpecialPowers.Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(SpecialPowers.Ci.nsIStyleSheetService);
+
+function test(win, sheet) {
+ let cs = win.getComputedStyle(win.document.body);
+ is(cs.getPropertyValue('color'), "rgb(0, 0, 0)", "should have default color");
+ var windowUtils = SpecialPowers.getDOMWindowUtils(win);
+ windowUtils.addSheet(sheet, SpecialPowers.Ci.nsIDOMWindowUtils.USER_SHEET);
+ is(cs.getPropertyValue('color'), "rgb(255, 0, 0)", "should have changed color to red");
+}
+
+function run() {
+ var uri = gIOService.newURI("data:text/css,body{color:red;}");
+ let sheet = gSSService.preloadSheet(uri, SpecialPowers.Ci.nsIStyleSheetService.USER_SHEET);
+
+ test(document.getElementById("iframe1").contentWindow, sheet);
+ test(document.getElementById("iframe2").contentWindow, sheet);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_additional_sheets.html b/layout/style/test/test_additional_sheets.html
new file mode 100644
index 0000000000..8cd8ffd93a
--- /dev/null
+++ b/layout/style/test/test_additional_sheets.html
@@ -0,0 +1,310 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for additional sheets</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=737003">Mozilla Bug 737003</a>
+<iframe id="iframe" src="additional_sheets_helper.html"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService)
+
+var gSSService = SpecialPowers.Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(SpecialPowers.Ci.nsIStyleSheetService);
+
+function getUri(style)
+{
+ return "data:text/css," + style;
+}
+
+function getStyle(color, swapped)
+{
+ return "body {color: " + color + (swapped ? " !important" : "") +
+ "; background-color: " + color + (swapped ? "" : " !important;") + ";}";
+}
+
+function loadUserSheet(win, style)
+{
+ loadSheet(win, style, "USER_SHEET");
+}
+
+function loadAgentSheet(win, style)
+{
+ loadSheet(win, style, "AGENT_SHEET");
+}
+
+function loadAuthorSheet(win, style)
+{
+ loadSheet(win, style, "AUTHOR_SHEET");
+}
+
+function removeUserSheet(win, style)
+{
+ removeSheet(win, style, "USER_SHEET");
+}
+
+function removeAgentSheet(win, style)
+{
+ removeSheet(win, style, "AGENT_SHEET");
+}
+
+function removeAuthorSheet(win, style)
+{
+ removeSheet(win, style, "AUTHOR_SHEET");
+}
+
+function loadSheet(win, style, type)
+{
+ var uri = gIOService.newURI(getUri(style));
+ var windowUtils = SpecialPowers.getDOMWindowUtils(win);
+ windowUtils.loadSheet(uri, windowUtils[type]);
+}
+
+function removeSheet(win, style, type)
+{
+ var uri = gIOService.newURI(getUri(style));
+ var windowUtils = SpecialPowers.getDOMWindowUtils(win);
+ windowUtils.removeSheet(uri, windowUtils[type]);
+}
+
+function loadAndRegisterUserSheet(win, style)
+{
+ loadAndRegisterSheet(win, style, "USER_SHEET");
+}
+
+function loadAndRegisterAgentSheet(win, style)
+{
+ loadAndRegisterSheet(win, style, "AGENT_SHEET");
+}
+
+function loadAndRegisterAuthorSheet(win, style)
+{
+ loadAndRegisterSheet(win, style, "AUTHOR_SHEET");
+}
+
+function unregisterUserSheet(win, style)
+{
+ unregisterSheet(win, style, "USER_SHEET");
+}
+
+function unregisterAgentSheet(win, style)
+{
+ unregisterSheet(win, style, "AGENT_SHEET");
+}
+
+function unregisterAuthorSheet(win, style)
+{
+ unregisterSheet(win, style, "AUTHOR_SHEET");
+}
+
+function loadAndRegisterSheet(win, style, type)
+{
+ uri = gIOService.newURI(getUri(style));
+ gSSService.loadAndRegisterSheet(uri, gSSService[type]);
+ is(gSSService.sheetRegistered(uri, gSSService[type]), true);
+}
+
+function unregisterSheet(win, style, type)
+{
+ var uri = gIOService.newURI(getUri(style));
+ gSSService.unregisterSheet(uri, gSSService[type]);
+ is(gSSService.sheetRegistered(uri, gSSService[type]), false);
+}
+
+function setDocSheet(win, style)
+{
+ var subdoc = win.document;
+ var headID = subdoc.getElementsByTagName("head")[0];
+ var cssNode = subdoc.createElement('style');
+ cssNode.type = 'text/css';
+ cssNode.innerHTML = style;
+ cssNode.id = 'docsheet';
+ headID.appendChild(cssNode);
+}
+
+function removeDocSheet(win)
+{
+ var subdoc = win.document;
+ var node = subdoc.getElementById('docsheet');
+ node.remove();
+}
+
+var agent = {
+ type: 'agent',
+ color: 'rgb(255, 0, 0)',
+ addRules: loadAndRegisterAgentSheet,
+ removeRules: unregisterAgentSheet
+};
+
+var user = {
+ type: 'user',
+ color: 'rgb(0, 255, 0)',
+ addRules: loadAndRegisterUserSheet,
+ removeRules: unregisterUserSheet
+};
+
+var additionalAgent = {
+ type: 'additionalAgent',
+ color: 'rgb(0, 0, 255)',
+ addRules: loadAgentSheet,
+ removeRules: removeAgentSheet
+};
+
+var additionalUser = {
+ type: 'additionalUser',
+ color: 'rgb(255, 255, 0)',
+ addRules: loadUserSheet,
+ removeRules: removeUserSheet
+};
+
+var additionalAuthor = {
+ type: 'additionalAuthor',
+ color: 'rgb(255, 255, 0)',
+ addRules: loadAuthorSheet,
+ removeRules: removeAuthorSheet
+};
+
+var doc = {
+ type: 'doc',
+ color: 'rgb(0, 255, 255)',
+ addRules: setDocSheet,
+ removeRules: removeDocSheet
+};
+
+var author = {
+ type: 'author',
+ color: 'rgb(255, 0, 255)',
+ addRules: loadAndRegisterAuthorSheet,
+ removeRules: unregisterAuthorSheet
+};
+
+function loadAndCheck(win, firstType, secondType, swap, result1, result2)
+{
+ var firstStyle = getStyle(firstType.color, false);
+ var secondStyle = getStyle(secondType.color, swap);
+
+ firstType.addRules(win, firstStyle);
+ secondType.addRules(win, secondStyle);
+
+ var cs = win.getComputedStyle(win.document.body);
+ is(cs.getPropertyValue('color'), result1,
+ firstType.type + "(normal)" + " vs " + secondType.type + (swap ? "(important)" : "(normal)" ) + " 1");
+ is(cs.getPropertyValue('background-color'), result2,
+ firstType.type + "(important)" + " vs " + secondType.type + (swap ? "(normal)" : "(important)" ) + " 2");
+
+ firstType.removeRules(win, firstStyle);
+ secondType.removeRules(win, secondStyle);
+
+ is(cs.getPropertyValue('color'), 'rgb(0, 0, 0)', firstType.type + " vs " + secondType.type + " 3");
+ is(cs.getPropertyValue('background-color'), 'rgba(0, 0, 0, 0)', firstType.type + " vs " + secondType.type + " 4");
+}
+
+// There are 8 cases. Regular against regular, regular against important, important
+// against regular, important against important. We can load style from typeA first
+// then typeB or the other way around so that's 4*2=8 cases.
+
+function testStyleVsStyle(win, typeA, typeB, results)
+{
+ function color(res)
+ {
+ return res ? typeB.color : typeA.color;
+ }
+
+ loadAndCheck(win, typeA, typeB, false, color(results.AB.rr), color(results.AB.ii));
+ loadAndCheck(win, typeB, typeA, false, color(results.BA.rr), color(results.BA.ii));
+
+ loadAndCheck(win, typeA, typeB, true, color(results.AB.ri), color(results.AB.ir));
+ loadAndCheck(win, typeB, typeA, true, color(results.BA.ir), color(results.BA.ri));
+}
+
+// 5 user agent normal declarations
+// 4 user normal declarations
+// 3 author normal declarations
+// 2 author important declarations
+// 1 user important declarations
+// 0 user agent important declarations
+
+function run()
+{
+ var iframe = document.getElementById("iframe");
+ var win = iframe.contentWindow;
+
+// Some explanation how to interpret this result table...
+// in case of loading the agent style first and the user style later (AB)
+// if there is an important rule in both for let's say color (ii)
+// the rule specified in the agent style will lead (AB.ii == 0)
+// If both rules would be just regular rules the one specified in the user style
+// would lead. (AB.rr == 1). If we would load/add the rules in reverse order that
+// would not change that (BA.rr == 1)
+ testStyleVsStyle(win, agent, user,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, agent, doc,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+
+ testStyleVsStyle(win, additionalUser, agent,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalUser, doc,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAgent, user,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAgent, doc,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+
+ testStyleVsStyle(win, additionalAgent, additionalUser,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, author, doc,
+ {AB:{rr:0, ii:0, ri:1, ir:0}, BA:{rr:0, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, author, user,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, author, agent,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, author, additionalUser,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, doc,
+ {AB:{rr:0, ii:0, ri:1, ir:0}, BA:{rr:0, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, author,
+ {AB:{rr:0, ii:0, ri:1, ir:0}, BA:{rr:0, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, user,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, agent,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, additionalUser,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ // Bug 1228542
+ var url = getStyle('rgb(255, 0, 0)');
+ loadAndRegisterAuthorSheet(win, url);
+ // Avoiding security exception...
+ (new win.Function("document.open()"))();
+ (new win.Function("document.close()"))();
+ unregisterAuthorSheet(win, url);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_align_justify_computed_values.html b/layout/style/test/test_align_justify_computed_values.html
new file mode 100644
index 0000000000..aa13762cb7
--- /dev/null
+++ b/layout/style/test/test_align_justify_computed_values.html
@@ -0,0 +1,484 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696253
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test align/justify-items/self/content computed values</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="position:relative">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696253">Mozilla Bug 696253</a>
+<style>
+#flexContainer, #flexContainerGrid { display: flex; position:relative; }
+#gridContainer, #gridContainerFlex { display: grid; position:relative; }
+#display b, #absChild { position:absolute; }
+</style>
+<div id="display">
+ <div id="myDiv"></div>
+ <div id="flexContainer"><a></a><b></b></div>
+ <div id="gridContainer"><a></a><b></b></div>
+ <div id="flexContainerGrid"><a style="diplay:grid"></a><b style="diplay:grid"></b></div>
+ <div id="gridContainerFlex"><a style="diplay:flex"></a><b style="diplay:flex"></b></div>
+</div>
+<div id="absChild"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+/*
+ * Utility function for getting computed style of "align-self":
+ */
+function getComputedAlignSelf(elem) {
+ return window.getComputedStyle(elem).alignSelf;
+}
+function getComputedAlignItems(elem) {
+ return window.getComputedStyle(elem).alignItems;
+}
+function getComputedAlignContent(elem) {
+ return window.getComputedStyle(elem).alignContent;
+}
+function getComputedJustifySelf(elem) {
+ return window.getComputedStyle(elem).justifySelf;
+}
+function getComputedJustifyItems(elem) {
+ return window.getComputedStyle(elem).justifyItems;
+}
+function getComputedJustifyContent(elem) {
+ return window.getComputedStyle(elem).justifyContent;
+}
+
+/**
+ * Test behavior of 'align-self:auto' (Bug 696253 and Bug 1304012)
+ * ===============================================
+ *
+ * In a previous revision of the CSS Alignment spec, align-self:auto
+ * was required to actually *compute* to the parent's align-items value --
+ * but now, the spec says it simply computes to itself, and it should
+ * only get converted into the parent's align-items value when it's used
+ * in layout. This test verifies that we do indeed have it compute to
+ * itself, regardless of the parent's align-items value.
+ */
+
+/*
+ * Tests for a block node with a parent node:
+ */
+function testGeneralNode(elem) {
+ // Test initial computed style
+ // (Initial value should be 'auto', which should compute to itself)
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "initial computed value of 'align-self' should be 'auto'");
+
+ // Test value after setting align-self explicitly to "auto"
+ elem.style.alignSelf = "auto";
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "computed value of 'align-self: auto' should be 'auto'");
+ elem.style.alignSelf = ""; // clean up
+
+ // Test value after setting align-self explicitly to "inherit"
+ elem.style.alignSelf = "inherit";
+ if (elem.parentNode && elem.parentNode.style) {
+ is(getComputedAlignSelf(elem), getComputedAlignSelf(elem.parentNode),
+ elem.tagName + ": computed value of 'align-self: inherit' " +
+ "should match the value on the parent");
+ } else {
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "computed value of 'align-self: inherit' should be 'auto', " +
+ "when there is no parent");
+ }
+ elem.style.alignSelf = ""; // clean up
+}
+
+/*
+ * Tests that depend on us having a parent node:
+ */
+function testNodeThatHasParent(elem) {
+ // Sanity-check that we actually do have a styleable parent:
+ ok(elem.parentNode && elem.parentNode.style, elem.tagName + ": " +
+ "bug in test -- expecting caller to pass us a node with a parent");
+
+ // Test initial computed style when "align-items" has been set on our parent.
+ // (elem's initial "align-self" value should be "auto", which should compute
+ // to its parent's "align-items" value, which in this case is "center".)
+ elem.parentNode.style.alignItems = "center";
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "initial computed value of 'align-self' should be 'auto', even " +
+ "after changing parent's 'align-items' value");
+
+ // ...and now test computed style after setting "align-self" explicitly to
+ // "auto" (with parent "align-items" still at "center")
+ elem.style.alignSelf = "auto";
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "computed value of 'align-self: auto' should remain 'auto', after " +
+ "being explicitly set");
+
+ elem.style.alignSelf = ""; // clean up
+ elem.parentNode.style.alignItems = ""; // clean up
+
+ // Finally: test computed style after setting "align-self" to "inherit"
+ // and leaving parent at its initial value which should be "auto".
+ elem.style.alignSelf = "inherit";
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "computed value of 'align-self: inherit' should take parent's " +
+ "computed 'align-self' value (which should be 'auto', " +
+ "if we haven't explicitly set any other style");
+ elem.style.alignSelf = ""; // clean up
+ }
+
+/*
+ * Main test function
+ */
+function main() {
+ // Test the root node
+ // ==================
+ // (It's special because it has no parent ComputedStyle.)
+
+ var rootNode = document.documentElement;
+
+ // Sanity-check that we actually have the root node, as far as CSS is concerned.
+ // (Note: rootNode.parentNode is a HTMLDocument object -- not an element that
+ // we inherit style from.)
+ ok(!rootNode.parentNode.style,
+ "expecting root node to have no node to inherit style from");
+
+ testGeneralNode(rootNode);
+
+ // Test the body node
+ // ==================
+ // (It's special because it has no grandparent ComputedStyle.)
+
+ var body = document.getElementsByTagName("body")[0];
+ is(body.parentNode, document.documentElement,
+ "expecting body element's parent to be the root node");
+
+ testGeneralNode(body);
+ testNodeThatHasParent(body);
+
+ //
+ // align-items/self tests:
+ //
+ //// Block tests
+ var element = document.body;
+ var child = document.getElementById("display");
+ var absChild = document.getElementById("absChild");
+ is(getComputedAlignItems(element), 'normal', "default align-items value for block container");
+ is(getComputedAlignSelf(child), 'auto', "default align-self value for block child");
+ is(getComputedAlignSelf(absChild), 'auto', "default align-self value for block container abs.pos. child");
+ element.style.alignItems = "end";
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child");
+ is(getComputedAlignSelf(absChild), 'auto', "align-self:auto value persists for block container abs.pos. child");
+ element.style.alignItems = "left";
+ is(getComputedAlignItems(element), 'end', "align-items:left is an invalid declaration");
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto persists for block child");
+ is(getComputedAlignSelf(absChild), 'auto', "align-self:auto value persists for block container abs.pos. child");
+ element.style.alignItems = "right";
+ is(getComputedAlignItems(element), 'end', "align-items:right is an invalid declaration");
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child");
+ is(getComputedAlignSelf(absChild), 'auto', "align-self:auto value persists for block container abs.pos. child");
+
+ //// Flexbox tests
+ function testFlexAlignItemsSelf(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedAlignItems(elem), 'normal', "default align-items value for flex container");
+ is(getComputedAlignSelf(item), 'auto', "default align-self value for flex item");
+ is(getComputedAlignSelf(abs), 'auto', "default align-self value for flex container abs.pos. child");
+ elem.style.alignItems = "flex-end";
+ is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for flex container child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for flex container abs.pos. child");
+ elem.style.alignItems = "left";
+ is(getComputedAlignItems(elem), 'flex-end', "align-items:left is an invalid declaration");
+ elem.style.alignItems = "";
+ }
+ testFlexAlignItemsSelf(document.getElementById("flexContainer"));
+ testFlexAlignItemsSelf(document.getElementById("flexContainerGrid"));
+
+ //// Grid tests
+ function testGridAlignItemsSelf(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedAlignItems(elem), 'normal', "default align-items value for grid container");
+ is(getComputedAlignSelf(item), 'auto', "default align-self value for grid item");
+ is(getComputedAlignSelf(abs), 'auto', "default align-self value for grid container abs.pos. child");
+ elem.style.alignItems = "end";
+ is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for grid container abs.pos. child");
+
+ elem.style.alignItems = "left";
+ is(getComputedAlignItems(elem), 'end', "align-items:left is an invalid declaration");
+ is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for grid container abs.pos. child");
+ elem.style.alignItems = "right";
+ is(getComputedAlignItems(elem), 'end', "align-items:right is an invalid declaration");
+ is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for grid container abs.pos. child");
+
+ item.style.alignSelf = "";
+ abs.style.alignSelf = "";
+ elem.style.alignItems = "";
+ item.style.alignSelf = "";
+ }
+ testGridAlignItemsSelf(document.getElementById("gridContainer"));
+ testGridAlignItemsSelf(document.getElementById("gridContainerFlex"));
+
+ //
+ // justify-items/self tests:
+ //
+ //// Block tests
+ element = document.body;
+ child = document.getElementById("display");
+ absChild = document.getElementById("absChild");
+ is(getComputedJustifyItems(element), 'normal', "default justify-items value for block container");
+ is(getComputedJustifySelf(child), 'auto', "default justify-self value for block container child");
+ is(getComputedJustifySelf(absChild), 'auto', "default justify-self value for block container abs.pos. child");
+ element.style.justifyItems = "end";
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ is(getComputedJustifySelf(absChild), 'auto', "justify-self:auto value persists for block container abs.pos. child");
+ element.style.justifyItems = "left";
+ is(getComputedJustifyItems(element), 'left', "justify-items:left computes to itself on a block");
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ is(getComputedJustifySelf(absChild), 'auto', "justify-self:auto value persists for block container abs.pos. child");
+ element.style.justifyItems = "right";
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ is(getComputedJustifySelf(absChild), 'auto', "justify-self:auto value persists for block container abs.pos. child");
+ element.style.justifyItems = "safe right";
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ element.style.justifyItems = "";
+ child.style.justifySelf = "left";
+ is(getComputedJustifySelf(child), 'left', "justify-self:left computes to left on block child");
+ child.style.justifySelf = "right";
+ is(getComputedJustifySelf(child), 'right', "justify-self:right computes to right on block child");
+ child.style.justifySelf = "";
+ absChild.style.justifySelf = "right";
+ is(getComputedJustifySelf(absChild), 'right', "justify-self:right computes to right on block container abs.pos. child");
+
+ //// Flexbox tests
+ function testFlexJustifyItemsSelf(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedJustifyItems(elem), 'normal', "default justify-items value for flex container");
+ is(getComputedJustifySelf(item), 'auto', "default justify-self value for flex item");
+ is(getComputedJustifySelf(abs), 'auto', "default justify-self value for flex container abs.pos. child");
+ elem.style.justifyItems = "flex-end";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for flex container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for flex container abs.pos. child");
+ elem.style.justifyItems = "left";
+ is(getComputedJustifyItems(elem), 'left', "justify-items:left computes to itself for flex container");
+ elem.style.justifyItems = "safe right";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for flex container child");
+ // XXX TODO: add left/right tests (bug 1221565)
+ elem.style.justifyItems = "";
+ }
+ testFlexJustifyItemsSelf(document.getElementById("flexContainer"));
+ testFlexJustifyItemsSelf(document.getElementById("flexContainerGrid"));
+
+ //// Grid tests
+ function testGridJustifyItemsSelf(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedJustifyItems(elem), 'normal', "default justify-items value for grid container");
+ is(getComputedJustifySelf(item), 'auto', "default justify-self value for grid item");
+ is(getComputedJustifySelf(abs), 'auto', "default justify-self value for grid container abs.pos. child");
+ elem.style.justifyItems = "end";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "left";
+ is(getComputedJustifyItems(elem), 'left', "justify-items:left computes to itself for grid container");
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "legacy left";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "right";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "safe right";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ elem.style.justifyItems = "legacy right";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "legacy center";
+ item.style.justifyItems = "inherit";
+ abs.style.justifyItems = "inherit";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ is(getComputedJustifyItems(elem), 'legacy center', "justify-items computes to itself grid container");
+ is(getComputedJustifyItems(item), 'legacy center', "justify-items inherits including legacy keyword to grid item");
+ is(getComputedJustifyItems(abs), 'legacy center', "justify-items inherits including legacy keyword to grid container abs.pos. child");
+ elem.style.justifyItems = "";
+ item.style.justifySelf = "left";
+ is(getComputedJustifySelf(item), 'left', "justify-self:left computes to left on grid item");
+ item.style.justifySelf = "right";
+ is(getComputedJustifySelf(item), 'right', "justify-self:right computes to right on grid item");
+ item.style.justifySelf = "safe right";
+ is(getComputedJustifySelf(item), 'safe right', "justify-self:'safe right' computes to 'safe right' on grid item");
+ item.style.justifySelf = "";
+ abs.style.justifySelf = "right";
+ is(getComputedJustifySelf(abs), 'right', "justify-self:right computes to right on grid container abs.pos. child");
+ abs.style.justifySelf = "";
+ elem.style.justifyItems = "";
+ item.style.justifySelf = "";
+ }
+ testGridJustifyItemsSelf(document.getElementById("gridContainer"));
+ testGridJustifyItemsSelf(document.getElementById("gridContainerFlex"));
+
+ //
+ // align-content tests:
+ //
+ //// Block tests
+ element = document.body;
+ child = document.getElementById("display");
+ absChild = document.getElementById("absChild");
+ is(getComputedAlignContent(element), 'normal', "default align-content value for block container");
+ is(getComputedAlignContent(child), 'normal', "default align-content value for block child");
+ is(getComputedAlignContent(absChild), 'normal', "default align-content value for block container abs.pos. child");
+ element.style.alignContent = "end";
+ is(getComputedAlignContent(child), 'normal', "default align-content isn't affected by parent align-content value for in-flow child");
+ is(getComputedAlignContent(absChild), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child");
+ element.style.alignContent = "left";
+ is(getComputedAlignContent(element), 'end', "align-content:left isn't a valid declaration");
+ is(getComputedAlignContent(absChild), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child");
+ element.style.alignContent = "right";
+ is(getComputedAlignContent(element), 'end', "align-content:right isn't a valid declaration");
+ is(getComputedAlignContent(absChild), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child");
+ element.style.alignContent = "";
+
+ //// Flexbox tests
+ function testFlexAlignContent(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedAlignContent(elem), 'normal', "default align-content value for flex container");
+ is(getComputedAlignContent(item), 'normal', "default align-content value for flex item");
+ is(getComputedAlignContent(abs), 'normal', "default align-content value for flex container abs.pos. child");
+ elem.style.alignContent = "safe end";
+ is(getComputedAlignContent(elem), 'safe end', "align-content:'safe end' computes to itself for flex container");
+ is(getComputedAlignContent(item), 'normal', "default align-content isn't affected by parent align-content value for flex item");
+ is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for flex container abs.pos. child");
+ elem.style.alignContent = "";
+ }
+ testFlexAlignContent(document.getElementById("flexContainer"));
+ testFlexAlignContent(document.getElementById("flexContainerGrid"));
+
+ //// Grid tests
+ function testGridAlignContent(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedAlignContent(elem), 'normal', "default align-content value for grid container");
+ is(getComputedAlignContent(item), 'normal', "default align-content value for grid item");
+ is(getComputedAlignContent(abs), 'normal', "default align-content value for grid container abs.pos. child");
+ elem.style.alignContent = "safe end";
+ is(getComputedAlignContent(elem), 'safe end', "align-content:'safe end' computes to itself on grid container");
+ is(getComputedAlignContent(item), 'normal', "default align-content isn't affected by parent align-content value for grid item");
+ is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for grid container abs.pos. child");
+ elem.style.alignContent = "safe end";
+ item.style.alignContent = "inherit";
+ abs.style.alignContent = "inherit";
+ is(getComputedAlignContent(elem), 'safe end', "align-content:'safe end' computes to 'align-content:safe end' on grid container");
+ is(getComputedAlignContent(item), 'safe end', "align-content:'safe end' inherits as 'align-content:safe end' to grid item");
+ is(getComputedAlignContent(abs), 'safe end', "align-content:'safe end' inherits as 'align-content:safe end' to grid container abs.pos. child");
+ item.style.alignContent = "";
+ abs.style.alignContent = "";
+ elem.style.alignContent = "";
+ item.style.alignContent = "";
+ }
+ testGridAlignContent(document.getElementById("gridContainer"));
+ testGridAlignContent(document.getElementById("gridContainerFlex"));
+
+
+ //
+ // justify-content tests:
+ //
+ //// Block tests
+ element = document.body;
+ child = document.getElementById("display");
+ absChild = document.getElementById("absChild");
+ is(getComputedJustifyContent(element), 'normal', "default justify-content value for block container");
+ is(getComputedJustifyContent(child), 'normal', "default justify-content value for block child");
+ is(getComputedJustifyContent(absChild), 'normal', "default justify-content value for block container abs.pos. child");
+ element.style.justifyContent = "end";
+ is(getComputedJustifyContent(child), 'normal', "default justify-content isn't affected by parent justify-content value for in-flow child");
+ is(getComputedJustifyContent(absChild), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child");
+ element.style.justifyContent = "left";
+ is(getComputedJustifyContent(element), 'left', "justify-content:left computes to left on block child");
+ is(getComputedJustifyContent(absChild), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child");
+ element.style.justifyContent = "right";
+ is(getComputedJustifyContent(element), 'right', "justify-content:right computes to right on block child");
+ is(getComputedJustifyContent(absChild), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child");
+ element.style.justifyContent = "safe right";
+ is(getComputedJustifyContent(element), 'safe right', "justify-content:'safe right' computes to 'justify-content:safe right'");
+ element.style.justifyContent = "";
+ child.style.justifyContent = "left";
+ is(getComputedJustifyContent(child), 'left', "justify-content:left computes to left on block child");
+ child.style.justifyContent = "right";
+ is(getComputedJustifyContent(child), 'right', "justify-content:right computes to right on block child");
+ child.style.justifyContent = "safe left";
+ is(getComputedJustifyContent(child), 'safe left', "justify-content:safe left computes to 'safe left' on block child");
+ child.style.justifyContent = "";
+ absChild.style.justifyContent = "right";
+ is(getComputedJustifyContent(absChild), 'right', "justify-content:right computes to right on block container abs.pos. child");
+ absChild.style.justifyContent = "";
+
+ //// Flexbox tests
+ function testFlexJustifyContent(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedJustifyContent(elem), 'normal', "default justify-content value for flex container");
+ is(getComputedJustifyContent(item), 'normal', "default justify-content value for flex item");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content value for flex container abs.pos. child");
+ elem.style.justifyContent = "safe end";
+ is(getComputedJustifyContent(elem), 'safe end', "justify-content:'safe end' computes to itself for flex container");
+ is(getComputedJustifyContent(item), 'normal', "default justify-content isn't affected by parent justify-content value for flex item");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for flex container abs.pos. child");
+ // XXX TODO: add left/right tests (bug 1221565)
+ elem.style.justifyContent = "";
+ }
+ testFlexJustifyContent(document.getElementById("flexContainer"));
+ testFlexJustifyContent(document.getElementById("flexContainerGrid"));
+
+ //// Grid tests
+ function testGridJustifyContent(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedJustifyContent(elem), 'normal', "default justify-content value for grid container");
+ is(getComputedJustifyContent(item), 'normal', "default justify-content value for grid item");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content value for grid container abs.pos. child");
+ elem.style.justifyContent = "safe end";
+ is(getComputedJustifyContent(elem), 'safe end', "justify-content:'safe end' computes to itself on grid container");
+ is(getComputedJustifyContent(item), 'normal', "default justify-content isn't affected by parent justify-content value for grid item");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for grid container abs.pos. child");
+ elem.style.justifyContent = "left";
+ is(getComputedJustifyContent(elem), 'left', "justify-content:left computes to left on grid container");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for grid container abs.pos. child");
+ elem.style.justifyContent = "right";
+ is(getComputedJustifyContent(elem), 'right', "justify-content:right computes to right on grid container");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for grid container abs.pos. child");
+ elem.style.justifyContent = "safe right";
+ item.style.justifyContent = "inherit";
+ abs.style.justifyContent = "inherit";
+ is(getComputedJustifyContent(elem), 'safe right', "justify-content:'safe right' computes to 'justify-content:safe right' on grid container");
+ is(getComputedJustifyContent(item), 'safe right', "justify-content:'safe right' inherits as 'justify-content:safe right' to grid item");
+ is(getComputedJustifyContent(abs), 'safe right', "justify-content:'safe right' inherits as 'justify-content:safe right' to grid container abs.pos. child");
+ item.style.justifyContent = "left";
+ is(getComputedJustifyContent(item), 'left', "justify-content:left computes to left on grid item");
+ item.style.justifyContent = "right";
+ is(getComputedJustifyContent(item), 'right', "justify-content:right computes to right on grid item");
+ item.style.justifyContent = "safe right";
+ is(getComputedJustifyContent(item), 'safe right', "justify-content:'safe right' computes to 'safe right' on grid item");
+ item.style.justifyContent = "";
+ abs.style.justifyContent = "right";
+ is(getComputedJustifyContent(abs), 'right', "justify-content:right computes to right on grid container abs.pos. child");
+ abs.style.justifyContent = "";
+ elem.style.justifyContent = "";
+ item.style.justifyContent = "";
+ }
+ testGridJustifyContent(document.getElementById("gridContainer"));
+ testGridJustifyContent(document.getElementById("gridContainerFlex"));
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_all_shorthand.html b/layout/style/test/test_all_shorthand.html
new file mode 100644
index 0000000000..0a3bfd29fd
--- /dev/null
+++ b/layout/style/test/test_all_shorthand.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<title>Test the 'all' shorthand property</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="property_database.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<body onload="runTest()">
+
+<style id="stylesheet">
+#parent { }
+#child { }
+#child { }
+</style>
+
+<div style="display: none">
+ <div id="parent">
+ <div id="child"></div>
+ </div>
+</div>
+
+<script>
+function runTest() {
+ var sheet = document.getElementById("stylesheet").sheet;
+ var parentRule = sheet.cssRules[0];
+ var childRule1 = sheet.cssRules[1];
+ var childRule2 = sheet.cssRules[2];
+ var parent = document.getElementById("parent");
+ var child = document.getElementById("child");
+
+ // Longhand properties that are NOT considered to be subproperties of the 'all'
+ // shorthand.
+ var excludedSubproperties = ["direction", "unicode-bidi"];
+ var excludedSubpropertiesSet = new Set(excludedSubproperties);
+
+ // Longhand properties that are considered to be subproperties of the 'all'
+ // shorthand.
+ var includedSubproperties = Object.keys(gCSSProperties).filter(function(prop) {
+ var info = gCSSProperties[prop];
+ return info.type == CSS_TYPE_LONGHAND &&
+ !excludedSubpropertiesSet.has(prop);
+ });
+
+ // All longhand properties to be tested.
+ var allSubproperties = includedSubproperties.concat(excludedSubproperties);
+
+
+ // First, get the computed value for the initial value and one other value of
+ // each property.
+ var initialComputedValues = new Map();
+ var otherComputedValues = new Map();
+
+ allSubproperties.forEach(function(prop) {
+ parentRule.style.setProperty(prop, "initial", "");
+ initialComputedValues.set(prop, getComputedStyle(parent, "").getPropertyValue(prop));
+ parentRule.style.cssText = "";
+ });
+
+ allSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ otherComputedValues.set(prop, getComputedStyle(parent, "").getPropertyValue(prop));
+ parentRule.style.cssText = "";
+ });
+
+
+ // Test setting all:inherit through setProperty.
+ includedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "initial");
+ childRule2.style.setProperty("all", "inherit");
+ is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop),
+ "computed value for " + prop + " when 'all:inherit' set with setProperty");
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+ excludedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "initial");
+ childRule2.style.setProperty("all", "inherit");
+ is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop),
+ "computed value for excluded subproperty " + prop + " when 'all:inherit' set with setProperty");
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+
+ // Test setting all:initial through setProperty.
+ includedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "inherit");
+ childRule2.style.setProperty("all", "initial");
+ is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop),
+ "computed value for " + prop + " when 'all:initial' set with setProperty");
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+ excludedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, info.other_values[0], "");
+ childRule2.style.setProperty("all", "initial");
+ is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop),
+ "computed value for excluded subproperty " + prop + " when 'all:initial' set with setProperty");
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+
+ // Test setting all:unset through setProperty.
+ includedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ if (info.inherited) {
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "initial", "");
+ childRule2.style.setProperty("all", "unset");
+ is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop),
+ "computed value for " + prop + " when 'all:unset' set with setProperty");
+ } else {
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, info.other_values[0], "");
+ childRule2.style.setProperty("all", "unset");
+ is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop),
+ "computed value for " + prop + " when 'all:unset' set with setProperty");
+ }
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+ excludedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ if (info.inherited) {
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "initial", "");
+ childRule2.style.setProperty("all", "unset");
+ is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop),
+ "computed value for excluded subproperty " + prop + " when 'all:unset' set with setProperty");
+ } else {
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, info.other_values[0], "");
+ childRule2.style.setProperty("all", "unset");
+ is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop),
+ "computed value for excluded subproperty " + prop + " when 'all:unset' set with setProperty");
+ }
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
diff --git a/layout/style/test/test_animations.html b/layout/style/test/test_animations.html
new file mode 100644
index 0000000000..846eb1d2a0
--- /dev/null
+++ b/layout/style/test/test_animations.html
@@ -0,0 +1,2107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435442
+-->
+<!--
+
+ ====== PLEASE KEEP THIS IN SYNC WITH test_animations_omta.html =======
+
+ test_animations_omta.html mimicks the content of this file but with
+ extra machinery for testing animation values on the compositor thread.
+
+ If you are making changes to this file or to test_animations_omta.html, please
+ try to keep them consistent where appropriate.
+
+-->
+<head>
+ <title>Test for css3-animations (Bug 435442)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ @keyframes anim1 {
+ 0% { margin-left: 0px }
+ 50% { margin-left: 80px }
+ 100% { margin-left: 100px }
+ }
+ @keyframes anim2 {
+ from { margin-right: 0 } to { margin-right: 100px }
+ }
+ @keyframes anim3 {
+ from { margin-top: 0 } to { margin-top: 100px }
+ }
+ @keyframes anim4 {
+ from { margin-bottom: 0 } to { margin-bottom: 100px }
+ }
+ @keyframes anim5 {
+ from { margin-left: 0 } to { margin-left: 100px }
+ }
+
+ @keyframes kf1 {
+ 50% { margin-top: 50px }
+ to { margin-top: 150px }
+ }
+ @keyframes kf2 {
+ from { margin-top: 150px }
+ 50% { margin-top: 50px }
+ }
+ @keyframes kf3 {
+ 25% { margin-top: 100px }
+ }
+ @keyframes kf4 {
+ to, from { display: none; margin-top: 37px }
+ }
+ @keyframes kf_cascade1 {
+ from { padding-top: 50px }
+ 50%, from { padding-top: 30px } /* wins: 0% */
+ 75%, 85%, 50% { padding-top: 20px } /* wins: 75%, 50% */
+ 100%, 85% { padding-top: 70px } /* wins: 100% */
+ 85.1% { padding-top: 60px } /* wins: 85.1% */
+ 85% { padding-top: 30px } /* wins: 85% */
+ }
+ @keyframes kf_cascade2 { from, to { margin-top: 100px } }
+ @keyframes kf_cascade2 { from, to { margin-left: 200px } }
+ @keyframes kf_cascade2 { from, to { margin-left: 300px } }
+ @keyframes kf_tf1 {
+ 0% { padding-bottom: 20px; animation-timing-function: ease }
+ 25% { padding-bottom: 60px; }
+ 50% { padding-bottom: 160px; animation-timing-function: steps(5) }
+ 75% { padding-bottom: 120px; animation-timing-function: linear }
+ 100% { padding-bottom: 20px; animation-timing-function: ease-out }
+ }
+
+ @keyframes always_fifty {
+ from, to { margin-left: 50px }
+ }
+
+ #withbefore::before, #withafter::after {
+ content: "";
+ animation: anim2 1s linear alternate 3;
+ }
+
+ @keyframes multiprop {
+ 0% {
+ padding-top: 10px; padding-left: 30px;
+ animation-timing-function: ease;
+ }
+ 25% {
+ padding-left: 50px;
+ animation-timing-function: ease-out;
+ }
+ 50% {
+ padding-top: 40px;
+ }
+ 75% {
+ padding-top: 80px; padding-left: 60px;
+ animation-timing-function: ease-in;
+ }
+ }
+
+ @keyframes uaoverride {
+ 0%, 100% { white-space: pre; margin-top: 20px }
+ 50% { margin-top: 120px }
+ }
+
+ @keyframes cascade {
+ 0%, 25%, 100% { top: 0 }
+ 50%, 75% { top: 100px }
+ 0%, 75%, 100% { left: 0 }
+ 25%, 50% { left: 100px }
+ }
+ @keyframes cascade2 {
+ 0% { text-indent: 0 }
+ 25% { text-indent: 30px; animation-timing-function: ease-in } /* beaten by rule below */
+ 50% { text-indent: 0 }
+ 25% { text-indent: 50px }
+ 100% { text-indent: 100px }
+ }
+
+ @keyframes primitives1 {
+ from { transform: rotate(0deg) translateX(0px) scaleX(1)
+ translate(0px) scale3d(1, 1, 1); }
+ to { transform: rotate(270deg) translate3d(0px, 0px, 0px) scale(1)
+ translateY(0px) scaleY(1); }
+ }
+
+ @keyframes important1 {
+ from { margin-top: 50px; }
+ 50% { margin-top: 150px !important; } /* ignored */
+ to { margin-top: 100px; }
+ }
+
+ @keyframes important2 {
+ from { margin-top: 50px;
+ margin-bottom: 100px; }
+ to { margin-top: 150px !important; /* ignored */
+ margin-bottom: 50px; }
+ }
+
+ @keyframes empty { }
+ @keyframes nearlyempty { to { margin-left: 100px; } }
+
+ @keyframes lowerpriority {
+ 0% {
+ top: 0px;
+ left: 0px;
+ }
+ 100% {
+ top: 100px;
+ left: 100px;
+ }
+ }
+
+ @keyframes overrideleft {
+ 0%, 100% { left: 0px }
+ }
+
+ @keyframes overridetop {
+ 0%, 100% { top: 0px }
+ }
+
+ @keyframes opacitymid {
+ 0% { opacity: 0.2 }
+ 100% { opacity: 0.8 }
+ }
+
+ @keyframes "string name 1" { /* using string for keyframes name */
+ 0%, 100% { left: 1px }
+ }
+
+ @keyframes "string name 2" {
+ 0%, 100% { left: 2px }
+ }
+
+ @keyframes custom\ ident\ 1 {
+ 0%, 100% { left: 3px }
+ }
+
+ @keyframes custom\ ident\ 2 {
+ 0%, 100% { left: 4px }
+ }
+
+ @keyframes "initial" {
+ 0%, 100% { left: 5px }
+ }
+
+ @keyframes initial { /* illegal as an identifier, should be dropped */
+ 0%, 100% { left: 6px }
+ }
+
+ @keyframes "none" {
+ 0%, 100% { left: 7px }
+ }
+
+ @keyframes none { /* illegal as an identifier, should be dropped */
+ 0%, 100% { left: 8px }
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435442">Mozilla Bug 435442</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for css3-animations (Bug 435442) **/
+
+var e = new AnimationEvent("foo",
+ {
+ bubbles: true,
+ cancelable: true,
+ animationName: "name",
+ elapsedTime: 0.5,
+ pseudoElement: "pseudo"
+ });
+is(e.bubbles, true);
+is(e.cancelable, true);
+is(e.animationName, "name");
+is(e.elapsedTime, 0.5);
+is(e.pseudoElement, "pseudo");
+is(e.isTrusted, false)
+
+// Shortcut new_div to update div, cs
+var div, cs;
+var originalNewDiv = window.new_div;
+window.new_div = function(style) {
+ [ div, cs ] = originalNewDiv(style);
+};
+
+// take over the refresh driver right from the start.
+advance_clock(0);
+
+SimpleTest.registerCleanupFunction(() => {
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+});
+
+/*
+ * css3-animations: 2. Animations
+ * http://dev.w3.org/csswg/css3-animations/#animations
+ */
+
+// Test that animations don't affect the computed value before the
+// start of the animation or after its end. Test without
+// animation-fill-mode, but then repeat the test with all the values of
+// animation-fill-mode.
+function test_fill_mode(fill_mode, fills_backwards, fills_forwards)
+{
+ var style = "margin-left: 30px; animation: 10s 3s anim1 linear";
+ var desc;
+ if (fill_mode.length > 0) {
+ style += " " + fill_mode;
+ desc = "fill mode " + fill_mode + ": ";
+ } else {
+ desc = "default fill mode: ";
+ }
+ new_div(style);
+ listen();
+ if (fills_backwards)
+ is(cs.marginLeft, "0px", desc + "does affect value during delay (0s)");
+ else
+ is(cs.marginLeft, "30px", desc + "doesn't affect value during delay (0s)");
+ advance_clock(2000);
+ if (fills_backwards)
+ is(cs.marginLeft, "0px", desc + "does affect value during delay (2s)");
+ else
+ is(cs.marginLeft, "30px", desc + "doesn't affect value during delay (2s)");
+ check_events([], "before start in test_fill_mode");
+ advance_clock(1000);
+ check_events([{ type: 'animationstart', target: div,
+ bubbles: true, cancelable: false,
+ animationName: 'anim1', elapsedTime: 0.0,
+ pseudoElement: "" }],
+ "right after start in test_fill_mode");
+ if (fills_backwards)
+ is(cs.marginLeft, "0px", desc + "affects value at start of animation");
+ advance_clock(125);
+ is(cs.marginLeft, "2px", desc + "affects value during animation");
+ advance_clock(2375);
+ is(cs.marginLeft, "40px", desc + "affects value during animation");
+ advance_clock(2500);
+ is(cs.marginLeft, "80px", desc + "affects value during animation");
+ advance_clock(2500);
+ is(cs.marginLeft, "90px", desc + "affects value during animation");
+ advance_clock(2375);
+ is(cs.marginLeft, "99.5px", desc + "affects value during animation");
+ check_events([], "before end in test_fill_mode");
+ advance_clock(125);
+ check_events([{ type: 'animationend', target: div,
+ bubbles: true, cancelable: false,
+ animationName: 'anim1', elapsedTime: 10.0,
+ pseudoElement: "" }],
+ "right after end in test_fill_mode");
+ if (fills_forwards)
+ is(cs.marginLeft, "100px", desc + "affects value at end of animation");
+ advance_clock(10);
+ if (fills_forwards)
+ is(cs.marginLeft, "100px", desc + "does affect value after animation");
+ else
+ is(cs.marginLeft, "30px", desc + "does not affect value after animation");
+ done_div();
+}
+test_fill_mode("", false, false);
+test_fill_mode("none", false, false);
+test_fill_mode("forwards", false, true);
+test_fill_mode("backwards", true, false);
+test_fill_mode("both", true, true);
+
+// Test that animations continue running when the animation name
+// list is changed.
+new_div("animation: anim1 linear 10s");
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "just anim1, margin-top at start");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "just anim1, margin-right at start");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "just anim1, margin-bottom at start");
+ is(cs.getPropertyValue("margin-left"), "0px",
+ "just anim1, margin-left at start");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "just anim1, margin-top at 1s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "just anim1, margin-right at 1s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "just anim1, margin-bottom at 1s");
+ is(cs.getPropertyValue("margin-left"), "16px",
+ "just anim1, margin-left at 1s");
+// append anim2
+div.style.animation = "anim1 linear 10s, anim2 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim1 + anim2, margin-top at 1s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim1 + anim2, margin-right at 1s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim2, margin-bottom at 1s");
+ is(cs.getPropertyValue("margin-left"), "16px",
+ "anim1 + anim2, margin-left at 1s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim1 + anim2, margin-top at 2s");
+ is(cs.getPropertyValue("margin-right"), "10px",
+ "anim1 + anim2, margin-right at 2s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim2, margin-bottom at 2s");
+ is(cs.getPropertyValue("margin-left"), "32px",
+ "anim1 + anim2, margin-left at 2s");
+// prepend anim3
+div.style.animation = "anim3 linear 10s, anim1 linear 10s, anim2 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim3 + anim1 + anim2, margin-top at 2s");
+ is(cs.getPropertyValue("margin-right"), "10px",
+ "anim3 + anim1 + anim2, margin-right at 2s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim2, margin-bottom at 2s");
+ is(cs.getPropertyValue("margin-left"), "32px",
+ "anim3 + anim1 + anim2, margin-left at 2s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "10px",
+ "anim3 + anim1 + anim2, margin-top at 3s");
+ is(cs.getPropertyValue("margin-right"), "20px",
+ "anim3 + anim1 + anim2, margin-right at 3s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim2, margin-bottom at 3s");
+ is(cs.getPropertyValue("margin-left"), "48px",
+ "anim3 + anim1 + anim2, margin-left at 3s");
+// remove anim2 from end
+div.style.animation = "anim3 linear 10s, anim1 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "10px",
+ "anim3 + anim1, margin-top at 3s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1, margin-right at 3s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1, margin-bottom at 3s");
+ is(cs.getPropertyValue("margin-left"), "48px",
+ "anim3 + anim1, margin-left at 3s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "20px",
+ "anim3 + anim1, margin-top at 4s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1, margin-right at 4s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1, margin-bottom at 4s");
+ is(cs.getPropertyValue("margin-left"), "64px",
+ "anim3 + anim1, margin-left at 4s");
+// swap anim1 and anim3, change duration of anim3
+div.style.animation = "anim1 linear 10s, anim3 linear 5s";
+ is(cs.getPropertyValue("margin-top"), "40px",
+ "anim1 + anim3, margin-top at 4s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim1 + anim3, margin-right at 4s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim3, margin-bottom at 4s");
+ is(cs.getPropertyValue("margin-left"), "64px",
+ "anim1 + anim3, margin-left at 4s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "60px",
+ "anim1 + anim3, margin-top at 5s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim1 + anim3, margin-right at 5s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim3, margin-bottom at 5s");
+ is(cs.getPropertyValue("margin-left"), "80px",
+ "anim1 + anim3, margin-left at 5s");
+// list anim1 twice, last duration wins, original start time still applies
+div.style.animation = "anim1 linear 10s, anim3 linear 5s, anim1 linear 20s";
+ is(cs.getPropertyValue("margin-top"), "60px",
+ "anim1 + anim3 + anim1, margin-top at 5s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim1 + anim3 + anim1, margin-right at 5s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim3 + anim1, margin-bottom at 5s");
+ is(cs.getPropertyValue("margin-left"), "40px",
+ "anim1 + anim3 + anim1, margin-left at 5s");
+// drop one of the anim1, and list anim5 as well, which animates
+// the same property as anim1
+div.style.animation = "anim3 linear 5s, anim1 linear 20s, anim5 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "60px",
+ "anim3 + anim1 + anim5, margin-top at 5s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 5s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 5s");
+ is(cs.getPropertyValue("margin-left"), "0px",
+ "anim3 + anim1 + anim5, margin-left at 5s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "80px",
+ "anim3 + anim1 + anim5, margin-top at 6s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 6s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 6s");
+ is(cs.getPropertyValue("margin-left"), "10px",
+ "anim3 + anim1 + anim5, margin-left at 6s");
+// now swap the anim5 and anim1 order
+div.style.animation = "anim3 linear 5s, anim5 linear 10s, anim1 linear 20s";
+ is(cs.getPropertyValue("margin-top"), "80px",
+ "anim3 + anim1 + anim5, margin-top at 6s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 6s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 6s");
+ is(cs.getPropertyValue("margin-left"), "48px",
+ "anim3 + anim1 + anim5, margin-left at 6s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim3 + anim1 + anim5, margin-top at 7s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 7s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 7s");
+ is(cs.getPropertyValue("margin-left"), "56px",
+ "anim3 + anim1 + anim5, margin-left at 7s");
+// swap anim1 and anim5 back
+div.style.animation = "anim3 linear 5s, anim1 linear 20s, anim5 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim3 + anim1 + anim5, margin-top at 7s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 7s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 7s");
+ is(cs.getPropertyValue("margin-left"), "20px",
+ "anim3 + anim1 + anim5, margin-left at 7s");
+advance_clock(100);
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim3 + anim1 + anim5, margin-top at 7.1s");
+// Change the animation fill mode on the completed animation.
+div.style.animation = "anim3 linear 5s forwards, anim1 linear 20s, anim5 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "100px",
+ "anim3 + anim1 + anim5, margin-top at 7.1s, with fill mode");
+advance_clock(900);
+ is(cs.getPropertyValue("margin-top"), "100px",
+ "anim3 + anim1 + anim5, margin-top at 8s, with fill mode");
+// Change the animation duration on the completed animation, so it is
+// no longer completed.
+div.style.animation = "anim3 linear 10s, anim1 linear 20s, anim5 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "60px",
+ "anim3 + anim1 + anim5, margin-top at 8s, with fill mode");
+ is(cs.getPropertyValue("margin-left"), "30px",
+ "anim3 + anim1 + anim5, margin-left at 8s");
+done_div();
+
+/*
+ * css3-animations: 3. Keyframes
+ * http://dev.w3.org/csswg/css3-animations/#keyframes
+ *
+ * Also see test_keyframes_rules.html .
+ */
+
+// Test the rules on keyframes that lack a 0% or 100% rule:
+// (simultaneously, test that reverse animations have their keyframes
+// run backwards)
+
+// 100px at 0%, 50px at 50%, 150px at 100%
+new_div("margin-top: 100px; animation: kf1 ease 1s alternate infinite");
+is(cs.marginTop, "100px", "no-0% at 0.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.2), 0.01,
+ "no-0% at 0.1s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.6), 0.01,
+ "no-0% at 0.3s");
+advance_clock(200);
+is(cs.marginTop, "50px", "no-0% at 0.5s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.4), 0.01,
+ "no-0% at 0.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.8), 0.01,
+ "no-0% at 0.9s");
+advance_clock(100);
+is(cs.marginTop, "150px", "no-0% at 1.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.8), 0.01,
+ "no-0% at 1.1s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.2), 0.01,
+ "no-0% at 1.4s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.6), 0.01,
+ "no-0% at 1.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.2), 0.01,
+ "no-0% at 1.9s");
+advance_clock(100);
+is(cs.marginTop, "100px", "no-0% at 2.0s");
+done_div();
+
+// 150px at 0%, 50px at 50%, 100px at 100%
+new_div("margin-top: 100px; animation: kf2 ease-in 1s alternate infinite");
+is(cs.marginTop, "150px", "no-100% at 0.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.2), 0.01,
+ "no-100% at 0.1s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.6), 0.01,
+ "no-100% at 0.3s");
+advance_clock(200);
+is(cs.marginTop, "50px", "no-100% at 0.5s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.4), 0.01,
+ "no-100% at 0.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.8), 0.01,
+ "no-100% at 0.9s");
+advance_clock(100);
+is(cs.marginTop, "100px", "no-100% at 1.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.8), 0.01,
+ "no-100% at 1.1s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.2), 0.01,
+ "no-100% at 1.4s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.6), 0.01,
+ "no-100% at 1.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.2), 0.01,
+ "no-100% at 1.9s");
+advance_clock(100);
+is(cs.marginTop, "150px", "no-100% at 2.0s");
+done_div();
+
+
+// 50px at 0%, 100px at 25%, 50px at 100%
+new_div("margin-top: 50px; animation: kf3 ease-out 1s alternate infinite");
+is(cs.marginTop, "50px", "no-0%-no-100% at 0.0s");
+advance_clock(50);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.2), 0.01,
+ "no-0%-no-100% at 0.05s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.6), 0.01,
+ "no-0%-no-100% at 0.15s");
+advance_clock(100);
+is(cs.marginTop, "100px", "no-0%-no-100% at 0.25s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.4), 0.01,
+ "no-0%-no-100% at 0.55s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.8), 0.01,
+ "no-0%-no-100% at 0.85s");
+advance_clock(150);
+is(cs.marginTop, "50px", "no-0%-no-100% at 1.0s");
+advance_clock(150);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.8), 0.01,
+ "no-0%-no-100% at 1.15s");
+advance_clock(450);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.2), 0.01,
+ "no-0%-no-100% at 1.6s");
+advance_clock(250);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.6), 0.01,
+ "no-0%-no-100% at 1.85s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.2), 0.01,
+ "no-0%-no-100% at 1.95s");
+advance_clock(50);
+is(cs.marginTop, "50px", "no-0%-no-100% at 2.0s");
+done_div();
+
+// Test that non-animatable properties are ignored.
+// Simultaneously, test that the block is still honored, and that
+// we still override the value when two consecutive keyframes have
+// the same value.
+new_div("animation: kf4 ease 10s");
+is(cs.display, "block",
+ "non-animatable properties should be ignored (linear, 0s)");
+is(cs.marginTop, "37px",
+ "animatable properties should still apply (linear, 0s)");
+advance_clock(1000);
+is(cs.display, "block",
+ "non-animatable properties should be ignored (linear, 1s)");
+is(cs.marginTop, "37px",
+ "animatable properties should still apply (linear, 1s)");
+done_div();
+new_div("animation: kf4 step-start 10s");
+is(cs.display, "block",
+ "non-animatable properties should be ignored (step-start, 0s)");
+is(cs.marginTop, "37px",
+ "animatable properties should still apply (step-start, 0s)");
+advance_clock(1000);
+is(cs.display, "block",
+ "non-animatable properties should be ignored (step-start, 1s)");
+is(cs.marginTop, "37px",
+ "animatable properties should still apply (step-start, 1s)");
+done_div();
+
+// Test cascading of the keyframes within an @keyframes rule.
+new_div("animation: kf_cascade1 linear 10s");
+// 0%: 30px
+// 50%: 20px
+// 75%: 20px
+// 85%: 30px
+// 85.1%: 60px
+// 100%: 70px
+is(cs.paddingTop, "30px", "kf_cascade1 at 0s");
+advance_clock(2500);
+is(cs.paddingTop, "25px", "kf_cascade1 at 2.5s");
+advance_clock(2500);
+is(cs.paddingTop, "20px", "kf_cascade1 at 5s");
+advance_clock(2000);
+is(cs.paddingTop, "20px", "kf_cascade1 at 7s");
+advance_clock(500);
+is(cs.paddingTop, "20px", "kf_cascade1 at 7.5s");
+advance_clock(500);
+is(cs.paddingTop, "25px", "kf_cascade1 at 8s");
+advance_clock(500);
+is(cs.paddingTop, "30px", "kf_cascade1 at 8.5s");
+advance_clock(10);
+is_approx(px_to_num(cs.paddingTop), 60, 0.001, "kf_cascade1 at 8.51s");
+advance_clock(745);
+is(cs.paddingTop, "65px", "kf_cascade1 at 9.2505s");
+done_div();
+
+// Test cascading of the @keyframes rules themselves.
+new_div("animation: kf_cascade2 linear 10s");
+is(cs.marginTop, "0px", "@keyframes rule with margin-top should be ignored");
+is(cs.marginLeft, "300px", "last @keyframes rule with margin-left should win");
+done_div();
+
+/*
+ * css3-animations: 3.1. Timing functions for keyframes
+ * http://dev.w3.org/csswg/css3-animations/#timing-functions-for-keyframes-
+ */
+new_div("animation: kf_tf1 ease-in 10s alternate infinite");
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 0s (test needed for flush)");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
+ "keyframe timing functions test at 1s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.8), 0.01,
+ "keyframe timing functions test at 2s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01,
+ "keyframe timing functions test at 3s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
+ "keyframe timing functions test at 4s");
+advance_clock(1000);
+is(cs.paddingBottom, "160px",
+ "keyframe timing functions test at 5s");
+advance_clock(1001); // avoid floating-point error
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
+ "keyframe timing functions test at 6s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01,
+ "keyframe timing functions test at 7s");
+advance_clock(999);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
+ "keyframe timing functions test at 8s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.6), 0.01,
+ "keyframe timing functions test at 9s");
+advance_clock(1000);
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 10s");
+advance_clock(20000);
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 30s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.6), 0.01,
+ "keyframe timing functions test at 31s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
+ "keyframe timing functions test at 32s");
+advance_clock(999); // avoid floating-point error
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01,
+ "keyframe timing functions test at 33s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
+ "keyframe timing functions test at 34s");
+advance_clock(1001);
+is(cs.paddingBottom, "160px",
+ "keyframe timing functions test at 35s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
+ "keyframe timing functions test at 36s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01,
+ "keyframe timing functions test at 37s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.8), 0.01,
+ "keyframe timing functions test at 38s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
+ "keyframe timing functions test at 39s");
+advance_clock(1000);
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 40s");
+done_div();
+
+// spot-check the same thing without alternate
+new_div("animation: kf_tf1 ease-in 10s infinite");
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 0s (test needed for flush)");
+advance_clock(11000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
+ "keyframe timing functions test at 11s");
+advance_clock(3000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
+ "keyframe timing functions test at 14s");
+advance_clock(2001); // avoid floating-point error
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
+ "keyframe timing functions test at 16s");
+advance_clock(1999);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
+ "keyframe timing functions test at 18s");
+done_div();
+
+/*
+ * css3-animations: 3.2. The 'animation-name' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-name-property-
+ */
+
+// animation-name is reasonably well-tested up in the tests for Section
+// 2, particularly the tests that "Test that animations continue running
+// when the animation name list is changed."
+
+// Test that 'animation-name: none' steps the animation, and setting
+// it again starts a new one.
+
+new_div("");
+div.style.animation = "anim2 ease-in-out 10s";
+is(cs.marginRight, "0px", "after setting animation-name to anim2");
+advance_clock(1000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in_out(0.1), 0.01,
+ "before changing animation-name to none");
+div.style.animationName = "none";
+is(cs.marginRight, "0px", "after changing animation-name to none");
+advance_clock(1000);
+is(cs.marginRight, "0px", "after changing animation-name to none plus 1s");
+div.style.animationName = "anim2";
+is(cs.marginRight, "0px", "after changing animation-name to anim2");
+advance_clock(1000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in_out(0.1), 0.01,
+ "at 1s in animation when animation-name no longer none again");
+div.style.animationName = "none";
+is(cs.marginRight, "0px", "after changing animation-name to none");
+advance_clock(1000);
+is(cs.marginRight, "0px", "after changing animation-name to none plus 1s");
+done_div();
+
+/*
+ * css3-animations: 3.3. The 'animation-duration' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-duration-property-
+ */
+
+// FIXME: test animation-duration of 0 (quite a bit, including interaction
+// with fill-mode, count, and reversing), once I know what the right
+// behavior is.
+
+/*
+ * css3-animations: 3.4. The 'animation-timing-function' Property
+ * http://dev.w3.org/csswg/css3-animations/#animation-timing-function_tag
+ */
+
+// tested in tests for section 3.1
+
+/*
+ * css3-animations: 3.5. The 'animation-iteration-count' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-iteration-count-property-
+ */
+new_div("animation: anim2 ease-in 10s 0.3 forwards");
+is(cs.marginRight, "0px", "animation-iteration-count test 1 at 0s");
+advance_clock(2000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-iteration-count test 1 at 2s");
+advance_clock(900);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.29), 0.01,
+ "animation-iteration-count test 1 at 2.9s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-iteration-count test 1 at 3s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-iteration-count test 1 at 3.1s");
+advance_clock(5000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-iteration-count test 1 at 8.1s");
+done_div();
+
+new_div("animation: anim2 ease-in 10s 0.3"
+ + ", anim3 ease-out 20s 1.2 alternate forwards"
+ + ", anim4 ease-in-out 5s 1.6 forwards");
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 0s");
+is(cs.marginTop, "0px", "animation-iteration-count test 3 at 0s");
+is(cs.marginBottom, "0px", "animation-iteration-count test 4 at 0s");
+advance_clock(2000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-iteration-count test 2 at 2s");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.1), 0.01,
+ "animation-iteration-count test 3 at 2s");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.4), 0.01,
+ "animation-iteration-count test 4 at 2s");
+advance_clock(900);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.29), 0.01,
+ "animation-iteration-count test 2 at 2.9s");
+advance_clock(200);
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 3.1s");
+advance_clock(1800);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.98), 0.01,
+ "animation-iteration-count test 4 at 4.9s");
+advance_clock(200);
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 5.1s");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.02), 0.01,
+ "animation-iteration-count test 4 at 5.1s");
+advance_clock(2800);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.58), 0.01,
+ "animation-iteration-count test 4 at 7.9s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+ "animation-iteration-count test 4 at 8s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+ "animation-iteration-count test 4 at 8.1s");
+advance_clock(11700);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.99), 0.01,
+ "animation-iteration-count test 3 at 19.8s");
+advance_clock(200);
+is(cs.marginTop, "100px", "animation-iteration-count test 3 at 20s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.99), 0.01,
+ "animation-iteration-count test 3 at 20.2s");
+advance_clock(3600);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.81), 0.01,
+ "animation-iteration-count test 3 at 23.8s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.8), 0.01,
+ "animation-iteration-count test 3 at 24s");
+advance_clock(200);
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 25s");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.8), 0.01,
+ "animation-iteration-count test 3 at 25s");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+ "animation-iteration-count test 4 at 25s");
+done_div();
+
+/*
+ * css3-animations: 3.6. The 'animation-direction' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-direction-property-
+ */
+
+// Tested in tests for sections 3.1 and 3.5.
+
+new_div("animation: anim2 ease-in 10s infinite");
+div.style.animationDirection = "normal";
+is(cs.marginRight, "0px", "animation-direction test 1 (normal) at 0s");
+div.style.animationDirection = "reverse";
+is(cs.marginRight, "100px", "animation-direction test 1 (reverse) at 0s");
+div.style.animationDirection = "alternate";
+is(cs.marginRight, "0px", "animation-direction test 1 (alternate) at 0s");
+div.style.animationDirection = "alternate-reverse";
+is(cs.marginRight, "100px", "animation-direction test 1 (alternate-reverse) at 0s");
+advance_clock(2000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (normal) at 2s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (reverse) at 2s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (alternate) at 2s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 2s");
+advance_clock(5000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.7), 0.01,
+ "animation-direction test 1 (normal) at 7s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-direction test 1 (reverse) at 7s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.7), 0.01,
+ "animation-direction test 1 (alternate) at 7s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 7s");
+advance_clock(5000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (normal) at 12s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (reverse) at 12s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (alternate) at 12s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 12s");
+advance_clock(10000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (normal) at 22s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (reverse) at 22s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (alternate) at 22s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 22s");
+advance_clock(30000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (normal) at 52s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (reverse) at 52s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (alternate) at 52s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 52s");
+done_div();
+
+/*
+ * css3-animations: 3.7. The 'animation-play-state' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-play-state-property-
+ */
+
+// simple test with just one animation
+new_div("");
+div.style.animationTimingFunction = "ease";
+div.style.animationName = "anim1";
+div.style.animationDuration = "1s";
+div.style.animationDirection = "alternate";
+div.style.animationIterationCount = "2";
+is(cs.marginLeft, "0px", "animation-play-state test 1, at 0s");
+advance_clock(250);
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 250ms");
+div.style.animationPlayState = "paused";
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 250ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 still at 500ms");
+div.style.animationPlayState = "running";
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 still at 500ms");
+advance_clock(500);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 1000ms");
+advance_clock(250);
+is(cs.marginLeft, "100px", "animation-play-state test 1 at 1250ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 1500ms");
+div.style.animationPlayState = "paused";
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 1500ms");
+advance_clock(2000);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 3500ms");
+advance_clock(500);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 4000ms");
+div.style.animationPlayState = "";
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 4000ms");
+advance_clock(500);
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 4500ms");
+advance_clock(250);
+is(cs.marginLeft, "0px", "animation-play-state test 1, at 4750ms");
+advance_clock(250);
+is(cs.marginLeft, "0px", "animation-play-state test 1, at 5000ms");
+done_div();
+
+// more complicated test with multiple animations (and different directions
+// and iteration counts)
+new_div("");
+div.style.animationTimingFunction = "ease-out, ease-in, ease-in-out";
+div.style.animationName = "anim2, anim3, anim4";
+div.style.animationDuration = "1s, 2s, 1s";
+div.style.animationDirection = "alternate, normal, normal";
+div.style.animationIterationCount = "4, 2, infinite";
+is(cs.marginRight, "0px", "animation-play-state test 2, at 0s");
+is(cs.marginTop, "0px", "animation-play-state test 3, at 0s");
+is(cs.marginBottom, "0px", "animation-play-state test 4, at 0s");
+advance_clock(250);
+div.style.animationPlayState = "paused, running"; // pause 1 and 3
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+ "animation-play-state test 2 at 250ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.125), 0.01,
+ "animation-play-state test 3 at 250ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01,
+ "animation-play-state test 4 at 250ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+ "animation-play-state test 2 at 500ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.25), 0.01,
+ "animation-play-state test 3 at 500ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01,
+ "animation-play-state test 4 at 500ms");
+div.style.animationPlayState = "paused, running, running"; // unpause 3
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+ "animation-play-state test 2 at 500ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.25), 0.01,
+ "animation-play-state test 3 at 500ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01,
+ "animation-play-state test 4 at 500ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+ "animation-play-state test 2 at 750ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 750ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.5), 0.01,
+ "animation-play-state test 4 at 750ms");
+div.style.animationPlayState = "running, paused"; // unpause 1, pause 2
+advance_clock(0); // notify refresh observers
+advance_clock(250);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.5), 0.01,
+ "animation-play-state test 2 at 1000ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 1000ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.75), 0.01,
+ "animation-play-state test 4 at 1000ms");
+div.style.animationPlayState = "paused"; // pause all
+advance_clock(0); // notify refresh observers
+advance_clock(3000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.5), 0.01,
+ "animation-play-state test 2 at 4000ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 4000ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.75), 0.01,
+ "animation-play-state test 4 at 4000ms");
+div.style.animationPlayState = "running, paused"; // pause 2
+advance_clock(0); // notify refresh observers
+advance_clock(850);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.65), 0.01,
+ "animation-play-state test 2 at 4850ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 4850ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+ "animation-play-state test 4 at 4850ms");
+advance_clock(300);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.35), 0.01,
+ "animation-play-state test 2 at 5150ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 5150ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.9), 0.01,
+ "animation-play-state test 4 at 5150ms");
+advance_clock(2300);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.05), 0.01,
+ "animation-play-state test 2 at 7450ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 7450ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.2), 0.01,
+ "animation-play-state test 4 at 7450ms");
+advance_clock(100);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 7550ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 7550ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01,
+ "animation-play-state test 4 at 7550ms");
+div.style.animationPlayState = "running"; // unpause 2
+advance_clock(0); // notify refresh observers
+advance_clock(1000);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 7550ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.875), 0.01,
+ "animation-play-state test 3 at 7550ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01,
+ "animation-play-state test 4 at 7550ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 8050ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.125), 0.01,
+ "animation-play-state test 3 at 8050ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01,
+ "animation-play-state test 4 at 8050ms");
+advance_clock(1000);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 9050ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.625), 0.01,
+ "animation-play-state test 3 at 9050ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01,
+ "animation-play-state test 4 at 9050ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 9550ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.875), 0.01,
+ "animation-play-state test 3 at 9550ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01,
+ "animation-play-state test 4 at 9550ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 10050ms");
+is(cs.marginTop, "0px", "animation-play-state test 3 at 10050ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01,
+ "animation-play-state test 4 at 10050ms");
+done_div();
+
+// an initially paused animation (bug 1063992)
+new_div("animation: anim1 1s paused both");
+is(cs.marginLeft, "0px", "animation-play-state test 5, at 0s");
+advance_clock(500);
+is(cs.marginLeft, "0px", "animation-play-state test 5, at 0.5s");
+div.style.animationPlayState = "running";
+is(cs.marginLeft, "0px",
+ "animation-play-state test 5, at 0.5s after unpausing");
+advance_clock(500);
+is(cs.marginLeft, "80px",
+ "animation-play-state test 5, at 1s after unpaused");
+done_div();
+
+/*
+ * css3-animations: 3.8. The 'animation-delay' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-delay-property-
+ */
+
+// test positive delay
+new_div("animation: anim2 1s 0.5s ease-out");
+is(cs.marginRight, "0px", "positive delay test at 0ms");
+advance_clock(400);
+is(cs.marginRight, "0px", "positive delay test at 400ms");
+advance_clock(100);
+is(cs.marginRight, "0px", "positive delay test at 500ms");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+ "positive delay test at 500ms");
+done_div();
+
+// test dynamic changes to delay (i.e., that we preserve the start time
+// that's before the delay)
+new_div("animation: anim2 1s 0.5s ease-out both");
+is(cs.marginRight, "0px", "dynamic delay delay test at 0ms");
+advance_clock(400);
+is(cs.marginRight, "0px", "dynamic delay delay test at 400ms (1)");
+div.style.animationDelay = "0.2s";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.2), 0.01,
+ "dynamic delay delay test at 400ms (2)");
+div.style.animationDelay = "0.6s";
+advance_clock(0);
+advance_clock(200);
+is(cs.marginRight, "0px", "dynamic delay delay test at 600ms");
+advance_clock(200);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.2), 0.01,
+ "dynamic delay delay test at 800ms");
+advance_clock(1000);
+is(cs.marginRight, "100px", "dynamic delay delay test at 1800ms (1)");
+div.style.animationDelay = "1.5s";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.3), 0.01,
+ "dynamic delay delay test at 1800ms (2)");
+div.style.animationDelay = "2s";
+is(cs.marginRight, "0px", "dynamic delay delay test at 1800ms (3)");
+done_div();
+
+// test delay and play-state interaction
+new_div("animation: anim2 1s 0.5s ease-out");
+is(cs.marginRight, "0px", "delay and play-state delay test at 0ms");
+advance_clock(400);
+is(cs.marginRight, "0px", "delay and play-state delay test at 400ms");
+div.style.animationPlayState = "paused";
+advance_clock(0);
+advance_clock(100);
+is(cs.marginRight, "0px", "delay and play-state delay test at 500ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "delay and play-state delay test at 1000ms");
+div.style.animationPlayState = "running";
+advance_clock(0);
+advance_clock(100);
+is(cs.marginRight, "0px", "delay and play-state delay test at 1100ms");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+ "delay and play-state delay test at 1200ms");
+div.style.animationPlayState = "paused";
+advance_clock(0);
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+ "delay and play-state delay test at 1300ms");
+done_div();
+
+// test negative delay and implicit starting values
+new_div("margin-top: 1000px");
+advance_clock(300);
+div.style.marginTop = "100px";
+div.style.animation = "kf1 1s -0.1s ease-in";
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_in(0.2), 0.01,
+ "delay and implicit starting values test");
+done_div();
+
+// test large negative delay that causes the animation to start
+// in the fourth iteration
+new_div("animation: anim2 1s -3.6s ease-in 5 alternate forwards");
+listen(); // rely on no flush having happened yet
+cs.animationName; // flush styles so animation is created
+advance_clock(0); // complete pending animation start
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.4), 0.01,
+ "large negative delay test at 0ms");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 3.6,
+ pseudoElement: "" }],
+ "right after start in large negative delay test");
+advance_clock(380);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.02), 0.01,
+ "large negative delay test at 380ms");
+check_events([]);
+advance_clock(20);
+is(cs.marginRight, "0px", "large negative delay test at 400ms");
+check_events([{ type: 'animationiteration', target: div,
+ animationName: 'anim2', elapsedTime: 4.0,
+ pseudoElement: "" }],
+ "large negative delay test at 400ms");
+advance_clock(800);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "large negative delay test at 1200ms");
+check_events([]);
+advance_clock(200);
+is(cs.marginRight, "100px", "large negative delay test at 1400ms");
+check_events([{ type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 5.0,
+ pseudoElement: "" }],
+ "large negative delay test at 1400ms");
+done_div();
+
+/*
+ * css3-animations: 3.9. The 'animation-fill-mode' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-fill-mode-property-
+ */
+
+// animation-fill-mode is tested in the tests for section (2).
+
+/*
+ * css3-animations: 3.10. The 'animation' Shorthand Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-shorthand-property-
+ */
+
+// shorthand vs. longhand is adequately tested by the
+// property_database.js-based tests.
+
+/**
+ * Basic tests of animations on pseudo-elements
+ */
+new_div("");
+listen();
+div.id = "withbefore";
+var cs_before = getComputedStyle(div, ":before");
+is(cs_before.marginRight, "0px", ":before test at 0ms");
+advance_clock(400);
+is(cs_before.marginRight, "40px", ":before test at 400ms");
+advance_clock(800);
+is(cs_before.marginRight, "80px", ":before test at 1200ms");
+is(cs.marginRight, "0px", ":before animation should not affect element");
+advance_clock(800);
+is(cs_before.marginRight, "0px", ":before test at 2000ms");
+advance_clock(300);
+is(cs_before.marginRight, "30px", ":before test at 2300ms");
+advance_clock(700);
+check_events([ { type: "animationstart", animationName: "anim2", elapsedTime: 0, pseudoElement: "::before" },
+ { type: "animationiteration", animationName: "anim2", elapsedTime: 1, pseudoElement: "::before" },
+ { type: "animationiteration", animationName: "anim2", elapsedTime: 2, pseudoElement: "::before" },
+ { type: "animationend", animationName: "anim2", elapsedTime: 3, pseudoElement: "::before" }]);
+done_div();
+
+new_div("");
+listen();
+div.id = "withafter";
+var cs_after = getComputedStyle(div, ":after");
+is(cs_after.marginRight, "0px", ":after test at 0ms");
+advance_clock(400);
+is(cs_after.marginRight, "40px", ":after test at 400ms");
+advance_clock(800);
+is(cs_after.marginRight, "80px", ":after test at 1200ms");
+is(cs.marginRight, "0px", ":after animation should not affect element");
+advance_clock(800);
+is(cs_after.marginRight, "0px", ":after test at 2000ms");
+advance_clock(300);
+is(cs_after.marginRight, "30px", ":after test at 2300ms");
+advance_clock(700);
+check_events([ { type: "animationstart", animationName: "anim2", elapsedTime: 0, pseudoElement: "::after" },
+ { type: "animationiteration", animationName: "anim2", elapsedTime: 1, pseudoElement: "::after" },
+ { type: "animationiteration", animationName: "anim2", elapsedTime: 2, pseudoElement: "::after" },
+ { type: "animationend", animationName: "anim2", elapsedTime: 3, pseudoElement: "::after" }]);
+done_div();
+
+/**
+ * Test handling of properties that are present in only some of the
+ * keyframes.
+ */
+new_div("animation: multiprop 1s ease-in-out alternate infinite");
+is(cs.paddingTop, "10px", "multiprop top at 0ms");
+is(cs.paddingLeft, "30px", "multiprop top at 0ms");
+advance_clock(100);
+is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.2), 0.01,
+ "multiprop top at 100ms");
+is_approx(px_to_num(cs.paddingLeft), 30 + 20 * gTF.ease(0.4), 0.01,
+ "multiprop left at 100ms");
+advance_clock(200);
+is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.6), 0.01,
+ "multiprop top at 300ms");
+is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.1), 0.01,
+ "multiprop left at 300ms");
+advance_clock(300);
+is_approx(px_to_num(cs.paddingTop), 40 + 40 * gTF.ease_in_out(0.4), 0.01,
+ "multiprop top at 600ms");
+is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.7), 0.01,
+ "multiprop left at 600ms");
+advance_clock(200);
+is_approx(px_to_num(cs.paddingTop), 80 - 80 * gTF.ease_in(0.2), 0.01,
+ "multiprop top at 800ms");
+is_approx(px_to_num(cs.paddingLeft), 60 - 60 * gTF.ease_in(0.2), 0.01,
+ "multiprop left at 800ms");
+advance_clock(400);
+is_approx(px_to_num(cs.paddingTop), 80 - 80 * gTF.ease_in(0.2), 0.01,
+ "multiprop top at 1200ms");
+is_approx(px_to_num(cs.paddingLeft), 60 - 60 * gTF.ease_in(0.2), 0.01,
+ "multiprop left at 1200ms");
+advance_clock(200);
+is_approx(px_to_num(cs.paddingTop), 40 + 40 * gTF.ease_in_out(0.4), 0.01,
+ "multiprop top at 1400ms");
+is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.7), 0.01,
+ "multiprop left at 1400ms");
+advance_clock(300);
+is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.6), 0.01,
+ "multiprop top at 1700ms");
+is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.1), 0.01,
+ "multiprop left at 1700ms");
+advance_clock(200);
+is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.2), 0.01,
+ "multiprop top at 1900ms");
+is_approx(px_to_num(cs.paddingLeft), 30 + 20 * gTF.ease(0.4), 0.01,
+ "multiprop left at 1900ms");
+done_div();
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=651456 -- make
+// sure that refreshing of animations doesn't break when we get two
+// refreshes with the same timestamp.
+new_div("animation: anim2 1s linear");
+is(cs.marginRight, "0px", "bug 651456 at 0ms");
+advance_clock(100);
+is(cs.marginRight, "10px", "bug 651456 at 100ms (1)");
+advance_clock(0); // still forces a refresh
+is(cs.marginRight, "10px", "bug 651456 at 100ms (2)");
+advance_clock(100);
+is(cs.marginRight, "20px", "bug 651456 at 200ms");
+done_div();
+
+// Test that UA !important rules override animations.
+// This test depends on forms.css having a rule
+// option { white-space: !important }
+// If that rule changes, we should rewrite it to depend on a different rule.
+var option;
+[ option, cs ] = new_element("option", "");
+var default_white_space = cs.whiteSpace;
+isnot(default_white_space, "pre",
+ "default style should not be the same as animation style");
+done_element();
+[ option, cs ] = new_element("option",
+ "animation: uaoverride 2s linear infinite");
+is(cs.whiteSpace, default_white_space,
+ "animations should not override UA !important at 0ms");
+is(cs.marginTop, "20px",
+ "rest of animation should still work when UA !important present at 0ms");
+advance_clock(200);
+is(cs.whiteSpace, default_white_space,
+ "animations should not override UA !important at 200ms");
+is(cs.marginTop, "40px",
+ "rest of animation should still work when UA !important present at 200ms");
+done_element();
+
+// Test that author !important rules override animations, but
+// that animations override regular author rules.
+new_div("animation: always_fifty 1s linear infinite; margin-left: 200px");
+is(cs.marginLeft, "50px", "animations override regular author rules");
+done_div();
+new_div("animation: always_fifty 1s linear infinite;"
+ + " margin-left: 200px ! important;");
+is(cs.marginLeft, "200px", "important author rules override animations");
+done_div();
+
+// Test interaction of animations and restyling (Bug 686656).
+// This test depends on kf3 getting its 0% and 100% values from the
+// rules below it in the cascade; we're checking that the animation
+// isn't rebuilt when the restyles happen.
+new_div("animation: kf3 1s linear forwards");
+is(cs.marginTop, "0px", "bug 686656 test 1 at 0ms");
+advance_clock(250);
+display.style.color = "blue";
+is(cs.marginTop, "100px", "bug 686656 test 1 at 250ms");
+advance_clock(375);
+is(cs.marginTop, "50px", "bug 686656 test 1 at 625ms");
+advance_clock(375);
+is(cs.marginTop, "0px", "bug 686656 test 1 at 1000ms");
+done_div();
+display.style.color = "";
+
+// Test interaction of animations and restyling (Bug 686656),
+// with reframing.
+// This test depends on kf3 getting its 0% and 100% values from the
+// rules below it in the cascade; we're checking that the animation
+// isn't rebuilt when the restyles happen.
+new_div("animation: kf3 1s linear forwards");
+is(cs.marginTop, "0px", "bug 686656 test 2 at 0ms");
+advance_clock(250);
+display.style.overflow = "scroll";
+is(cs.marginTop, "100px", "bug 686656 test 2 at 250ms");
+advance_clock(375);
+is(cs.marginTop, "50px", "bug 686656 test 2 at 625ms");
+advance_clock(375);
+is(cs.marginTop, "0px", "bug 686656 test 2 at 1000ms");
+done_div();
+display.style.overflow = "";
+
+// Test that cascading between keyframes rules is per-property rather
+// than per-rule (bug ), and that the timing function isn't taken from a
+// rule that's skipped. (Bug 738003)
+new_div("animation: cascade 1s linear forwards; position: relative");
+is(cs.top, "0px", "cascade test (top) at 0ms");
+is(cs.left, "0px", "cascade test (top) at 0ms");
+advance_clock(125);
+is(cs.top, "0px", "cascade test (top) at 125ms");
+is(cs.left, "50px", "cascade test (top) at 125ms");
+advance_clock(125);
+is(cs.top, "0px", "cascade test (top) at 250ms");
+is(cs.left, "100px", "cascade test (top) at 250ms");
+advance_clock(125);
+is(cs.top, "50px", "cascade test (top) at 375ms");
+is(cs.left, "100px", "cascade test (top) at 375ms");
+advance_clock(125);
+is(cs.top, "100px", "cascade test (top) at 500ms");
+is(cs.left, "100px", "cascade test (top) at 500ms");
+advance_clock(125);
+is(cs.top, "100px", "cascade test (top) at 625ms");
+is(cs.left, "50px", "cascade test (top) at 625ms");
+advance_clock(125);
+is(cs.top, "100px", "cascade test (top) at 750ms");
+is(cs.left, "0px", "cascade test (top) at 750ms");
+advance_clock(125);
+is(cs.top, "50px", "cascade test (top) at 875ms");
+is(cs.left, "0px", "cascade test (top) at 875ms");
+advance_clock(125);
+is(cs.top, "0px", "cascade test (top) at 1000ms");
+is(cs.left, "0px", "cascade test (top) at 1000ms");
+done_div();
+
+new_div("animation: cascade2 8s linear forwards");
+is(cs.textIndent, "0px", "cascade2 test at 0s");
+advance_clock(1000);
+is(cs.textIndent, "25px", "cascade2 test at 1s");
+advance_clock(1000);
+is(cs.textIndent, "50px", "cascade2 test at 2s");
+advance_clock(1000);
+is(cs.textIndent, "25px", "cascade2 test at 3s");
+advance_clock(1000);
+is(cs.textIndent, "0px", "cascade2 test at 4s");
+advance_clock(3000);
+is(cs.textIndent, "75px", "cascade2 test at 7s");
+advance_clock(1000);
+is(cs.textIndent, "100px", "cascade2 test at 8s");
+done_div();
+
+new_div("animation: primitives1 2s linear forwards");
+is(cs.getPropertyValue("transform"), "matrix(1, 0, 0, 1, 0, 0)",
+ "primitives1 at 0s");
+advance_clock(1000);
+is(cs.getPropertyValue("transform"),
+ "matrix(-0.707107, 0.707107, -0.707107, -0.707107, 0, 0)",
+ "primitives1 at 1s");
+advance_clock(1000);
+is(cs.getPropertyValue("transform"), "matrix(0, -1, 1, 0, 0, 0)",
+ "primitives1 at 0s");
+done_div();
+
+new_div("animation: important1 1s linear forwards");
+is(cs.marginTop, "50px", "important1 test at 0s");
+advance_clock(500);
+is(cs.marginTop, "75px", "important1 test at 0.5s");
+advance_clock(500);
+is(cs.marginTop, "100px", "important1 test at 1s");
+done_div();
+
+new_div("animation: important2 1s linear forwards");
+is(cs.marginTop, "50px", "important2 (margin-top) test at 0s");
+is(cs.marginBottom, "100px", "important2 (margin-bottom) test at 0s");
+advance_clock(1000);
+is(cs.marginTop, "0px", "important2 (margin-top) test at 1s");
+is(cs.marginBottom, "50px", "important2 (margin-bottom) test at 1s");
+done_div();
+
+// Test that it's the length of the 'animation-name' list that's used to
+// start animations.
+// note: anim2 animates margin-right from 0 to 100px
+// note: anim3 animates margin-top from 0 to 100px
+new_div("animation-name: anim2, anim3;"
+ + " animation-duration: 1s;"
+ + " animation-timing-function: linear;"
+ + " animation-delay: -250ms, -250ms, -750ms, -500ms;");
+is(cs.marginRight, "25px", "animation-name list length is the length that matters");
+is(cs.marginTop, "25px", "animation-name list length is the length that matters");
+done_div();
+new_div("animation-name: anim2, anim3, anim2;"
+ + " animation-duration: 1s;"
+ + " animation-timing-function: linear;"
+ + " animation-delay: -250ms, -250ms, -750ms, -500ms;");
+is(cs.marginRight, "75px", "animation-name list length is the length that matters, and the last occurrence of a name wins");
+is(cs.marginTop, "25px", "animation-name list length is the length that matters");
+done_div();
+
+var dyn_sheet_elt = document.createElement("style");
+document.head.appendChild(dyn_sheet_elt);
+var dyn_sheet = dyn_sheet_elt.sheet;
+dyn_sheet.insertRule("@keyframes dyn1 { from { margin-left: 0 } 50% { margin-left: 50px } to { margin-left: 100px } }", 0);
+dyn_sheet.insertRule("@keyframes dyn2 { from { margin-left: 100px } to { margin-left: 200px } }", 1);
+var dyn1 = dyn_sheet.cssRules[0];
+var dyn2 = dyn_sheet.cssRules[1];
+new_div("animation: dyn1 1s linear");
+is(cs.marginLeft, "0px", "dynamic rule change test, initial state");
+advance_clock(250);
+is(cs.marginLeft, "25px", "dynamic rule change test, 250ms");
+dyn2.name = "dyn1";
+is(cs.marginLeft, "125px", "dynamic rule change test, change in @keyframes name applies");
+dyn2.appendRule("50% { margin-left: 0px }");
+is(cs.marginLeft, "50px", "dynamic rule change test, @keyframes appendRule");
+var dyn2_kf1 = dyn2.cssRules[0]; // currently 0% { margin-left: 100px }
+dyn2_kf1.style.marginLeft = "-100px";
+is(cs.marginLeft, "-50px", "dynamic rule change test, keyframe style set");
+dyn2.name = "dyn2";
+is(cs.marginLeft, "25px", "dynamic rule change test, change in @keyframes name applies (second time)");
+var dyn1_kf2 = dyn1.cssRules[1]; // currently 50% { margin-left: 50px }
+dyn1_kf2.keyText = "25%";
+is(cs.marginLeft, "50px", "dynamic rule change test, change in keyframe keyText");
+dyn1.deleteRule("25%");
+is(cs.marginLeft, "25px", "dynamic rule change test, @keyframes deleteRule");
+done_div();
+dyn_sheet_elt.remove();
+dyn_sheet_elt = null;
+dyn_sheet = null;
+
+/*
+ * Bug 1004361 - CSS animations with short duration sometimes don't dispatch
+ * a start event
+ */
+new_div("animation: anim2 1s 0.1s");
+listen();
+advance_clock(0); // Trigger animation
+advance_clock(1200); // Skip past end of animation's entire active duration
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events after skipping over animation interval");
+done_div();
+
+/*
+ * Bug 1007513 - AnimationEvent.elapsedTime should be animation time
+ */
+new_div("animation: anim2 1s 2");
+listen();
+advance_clock(0); // Trigger animation
+advance_clock(500); // Jump to middle of first interval
+advance_clock(1000); // Jump to middle of second interval
+advance_clock(1000); // Jump past end of last interval
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationiteration', target: div,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 2,
+ pseudoElement: "" }],
+ "events after skipping past event moments");
+done_div();
+
+new_div("animation: anim2 1s -2s");
+listen();
+cs.animationName; // build animation
+advance_clock(0); // finish pending
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events after skipping over animation with negative delay");
+done_div();
+
+/*
+ * Bug 1004365 - zero-duration animations
+ */
+
+new_div("margin-right: 200px; animation: anim2 0s 1s both");
+listen();
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of zero-duration animation");
+advance_clock(2000); // Skip over animation
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during forwards fill of zero-duration animation");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration animation");
+done_div();
+
+new_div("margin-right: 200px; animation: anim2 0s 1s both");
+listen();
+advance_clock(0);
+// Seek to just before the animation starts and stops
+advance_clock(999);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right at exact end of zero-duration animation");
+check_events([]);
+// Seek to exactly the point where the animation starts and stops
+advance_clock(1);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right at exact end of zero-duration animation");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of zero-duration animation");
+// Check no further events are dispatched
+advance_clock(0);
+advance_clock(100);
+check_events([]);
+done_div();
+
+// Test with animation-direction reverse
+new_div("margin-right: 200px;"
+ + " animation: anim2 0s 1s both reverse");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during backwards fill of reversed zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during forwards fill of reversed zero-duration animation");
+done_div();
+
+// Test with animation-direction alternate
+new_div("margin-right: 200px; animation: anim2 0s 1s both alternate 2");
+listen();
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of alternating zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during forwards fill of alternating zero-duration animation");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of zero-duration animation"
+ + " that repeats twice");
+done_div();
+
+// Test with animation-direction alternate and odd number of iterations
+new_div("margin-right: 200px; animation: anim2 0s 1s both alternate 3");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of alternating zero-duration " +
+ "animation with odd number of iterations");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during forwards fill of alternating zero-duration " +
+ "animation with odd number of iterations");
+done_div();
+
+// Test with animation-direction alternate and non-integral number of iterations
+new_div("margin-right: 200px;"
+ + " animation: anim2 0s 1s both alternate 7.3 linear");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of alternating zero-duration " +
+ "animation with non-integral number of iterations");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "70px",
+ "margin-right during forwards fill of alternating zero-duration " +
+ "animation with non-integral number of iterations");
+done_div();
+
+// Test with infinite iteration count
+// CSS Animations doesn't actually define what the behavior is in this case
+// (and many many other similar cases) so we follow the behavior defined in Web
+// Animations which is that the zero-duration "wins".
+new_div("margin-right: 200px; animation: anim2 0s 1s both infinite");
+listen();
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of infinitely repeating " +
+ "zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during forwards fill of infinitely repeating " +
+ "zero-duration animation");
+// Check we don't get infinite iteration events :)
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of infinitely repeating " +
+ "zero-duration animation");
+done_div();
+
+// Test with infinite iteration count and alternating direction
+new_div("margin-right: 200px; animation: anim2 0s 1s alternate both infinite");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of infinitely repeating and " +
+ "alternating zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during forwards fill of infinitely repeating and " +
+ "alternating zero-duration animation");
+done_div();
+
+// Test with infinite iteration count and alternate-reverse direction
+new_div("margin-right: 200px;"
+ + " animation: anim2 0s 1s alternate-reverse infinite both");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during backwards fill of infinitely repeating and " +
+ "alternate-reverse zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during forwards fill of infinitely repeating and " +
+ "alternate-reverse zero-duration animation");
+done_div();
+
+// Test with negative delay
+new_div("margin-right: 200px;"
+ + " animation: anim2 0s -1s both reverse 12.7 linear");
+listen();
+cs.animationName; // build animation
+advance_clock(0); // finish pending
+is(cs.getPropertyValue("margin-right"), "30px",
+ "margin-right during forwards fill of reversed and repeated " +
+ "zero-duration animation with negative delay");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration animation " +
+ "with negative delay");
+done_div();
+
+// Test zero duration with zero iteration count
+new_div("margin-right: 200px; animation: anim2 0s 1s both 0");
+listen();
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of zero-duration animation");
+advance_clock(2000); // Skip over animation
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during forwards fill of zero-duration animation");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration, zero iteration count"
+ + " animation");
+done_div();
+
+/*
+ * Bug 1004377 - Animations with empty keyframes rule
+ */
+
+new_div("margin-right: 200px; animation: empty 2s 1s both");
+listen();
+advance_clock(0);
+check_events([], "events during delay");
+advance_clock(2000); // Skip to middle of animation
+div.clientTop; // Trigger events
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "middle of animation with empty keyframes rule");
+advance_clock(1000); // Skip to end of animation
+div.clientTop; // Trigger events
+check_events([{ type: 'animationend', target: div,
+ animationName: 'empty', elapsedTime: 2,
+ pseudoElement: "" }],
+ "end of animation with empty keyframes rule");
+done_div();
+
+// Test with a zero-duration animation and empty @keyframes rule
+new_div("margin-right: 200px; animation: empty 0s 1s both");
+listen();
+advance_clock(0);
+advance_clock(1000);
+div.clientTop; // Trigger events
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "end of zero-duration animation with empty keyframes rule");
+done_div();
+
+// Test with a keyframes rule that becomes empty
+new_div("animation: nearlyempty 1s both linear");
+advance_clock(0);
+advance_clock(500);
+is(cs.getPropertyValue("margin-left"), "50px",
+ "margin-left for animation that is about to be emptied");
+listen();
+findKeyframesRule("nearlyempty").deleteRule("to");
+is(cs.getPropertyValue("margin-left"), "0px",
+ "margin-left for animation with (now) empty keyframes rule");
+check_events([], "events after emptying keyframes rule");
+advance_clock(500);
+div.clientTop; // Trigger events
+check_events([{ type: 'animationend', target: div,
+ animationName: 'nearlyempty', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events at end of animation with newly " +
+ "empty keyframes rule");
+done_div();
+
+// Test when we update to point to an empty animation
+new_div("animation: always_fifty 1s both linear");
+advance_clock(0);
+advance_clock(500);
+is(cs.getPropertyValue("margin-left"), "50px",
+ "margin-left for animation that will soon point to an empty keyframes rule");
+listen();
+div.style.animationName = "empty";
+is(cs.getPropertyValue("margin-left"), "0px",
+ "margin-left for animation now points to empty keyframes rule");
+advance_clock(500);
+div.clientTop; // Trigger events
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events at start of animation updated to use " +
+ "empty keyframes rule");
+done_div();
+
+/*
+ * Bug 1031319 - 'none' animations
+ *
+ * The code under test here is run entirely on the main thread so there is no
+ * OMTA version of these tests in test_animations_omta.html.
+ */
+
+// Setting "animation: none" after animations have finished should not trigger
+// animation events
+new_div("animation: always_fifty 1s");
+listen();
+advance_clock(0);
+advance_clock(1000);
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' }],
+ "events after running initial animation");
+div.style.animation = "none";
+div.clientTop; // Trigger events
+check_events([], "events after setting animation to 'none'");
+done_div();
+
+// Setting "animation: " after animations have finished should not trigger
+// animation events
+new_div("animation: always_fifty 1s");
+listen();
+advance_clock(0);
+advance_clock(1000);
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' }],
+ "events after running initial animation");
+div.style.animation = "";
+div.clientTop; // Trigger events
+check_events([], "events after setting animation to ''");
+done_div();
+
+// Setting "animation: none 1s" should not trigger events
+new_div("animation: none 1s");
+listen();
+advance_clock(0);
+advance_clock(1000);
+check_events([], "events after setting animation to 'none 1s'");
+done_div();
+
+// Setting "animation: 1s" should not trigger events
+new_div("animation: 1s");
+listen();
+advance_clock(0);
+advance_clock(1000);
+check_events([], "events after setting animation to '1s'");
+done_div();
+
+// Setting animation-name: none among other animations should cause only that
+// animation to be skipped
+new_div("animation-name: always_fifty, none, always_fifty;"
+ + " animation-duration: 1s");
+listen();
+advance_clock(0);
+advance_clock(500);
+advance_clock(500);
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' }],
+ "events for animation-name: a, none, a");
+done_div();
+
+/*
+ * Bug 1033881 - Non-matching animation-name
+ *
+ * The code under test here is run entirely on the main thread so there is no
+ * OMTA version of these tests in test_animations_omta.html.
+ */
+
+new_div("animation-name: non_existent, always_fifty; animation-duration: 1s");
+listen();
+advance_clock(0);
+advance_clock(500);
+advance_clock(500);
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' }],
+ "events for animation-name: non_existent, always_fifty");
+done_div();
+
+/*
+ * Bug 1038032 - Infinite repetition and delay causes overflow
+ */
+new_div("animation: always_fifty 10s 1s infinite");
+advance_clock(0);
+advance_clock(2000);
+is(cs.marginLeft, "50px",
+ "infinitely repeating animation with positive delay takes effect"
+ + " (does not overflow)");
+done_div();
+
+/*
+ * Bug 1140134 - A property in a CSS animation being overridden by later
+ * animation causes later properties in that animation to be skipped
+ */
+new_div("position: relative; animation: lowerpriority 1s linear infinite alternate, overridetop 1s linear infinite alternate");
+advance_clock(0);
+advance_clock(500);
+is(cs.getPropertyValue("left"), "50px", "left is animating");
+is(cs.getPropertyValue("top"), "0px", "top is not animating");
+done_div();
+
+new_div("position: relative; animation: lowerpriority 1s linear infinite alternate, overrideleft 1s linear infinite alternate");
+advance_clock(0);
+advance_clock(500);
+is(cs.getPropertyValue("left"), "0px", "left is not animating");
+is(cs.getPropertyValue("top"), "50px", "top is animating");
+done_div();
+
+/*
+ * Bug 962594 - Turn off CSS animations when the element is display:none, or
+ * is in a display:none subtree.
+ */
+
+// Helper function for the two tests below
+function testDisplayNoneTurnsOffAnimations(aTestName, aElementToDisplayNone) {
+ is(cs.getPropertyValue("margin-right"), "0px",
+ aTestName + "margin-right at 0s");
+ advance_clock(1000);
+ is(cs.getPropertyValue("margin-right"), "10px",
+ aTestName + "margin-right at 1s");
+ aElementToDisplayNone.style.display = "none";
+ is(cs.getPropertyValue("margin-right"), "0px",
+ aTestName + "margin-right after display:none");
+ advance_clock(1000);
+ is(cs.getPropertyValue("margin-right"), "0px",
+ aTestName + "margin-right 1s after display:none");
+ aElementToDisplayNone.style.display = "";
+ is(cs.getPropertyValue("margin-right"), "0px",
+ aTestName + "margin-right after display:block");
+ advance_clock(1000);
+ is(cs.getPropertyValue("margin-right"), "10px",
+ aTestName + "margin-right 1s after display:block");
+}
+
+// Check that it works if the animated element itself becomes display:none
+new_div("animation: anim2 linear 10s");
+testDisplayNoneTurnsOffAnimations("AnimatedElement ", div);
+done_div();
+
+// Check that it works if an ancestor of the animated element becomes display:none
+new_div("animation: anim2 linear 10s");
+var ancestor = document.createElement("div");
+div.parentNode.insertBefore(ancestor, div);
+ancestor.appendChild(div);
+testDisplayNoneTurnsOffAnimations("AncestorElement ", ancestor);
+ancestor.parentNode.insertBefore(div, ancestor);
+ancestor.remove();
+done_div();
+
+
+/*
+ * Bug 1125455 - Transitions should not run when animations are running.
+ */
+new_div("transition: opacity 2s linear; opacity: 0.8");
+advance_clock(0);
+is(cs.getPropertyValue("opacity"), "0.8", "initial opacity");
+div.style.opacity = "0.2";
+is(cs.getPropertyValue("opacity"), "0.8", "opacity transition at 0s");
+advance_clock(500);
+is(cs.getPropertyValue("opacity"), "0.65", "opacity transition at 0.5s");
+div.style.animation = "opacitymid 2s linear";
+is(cs.getPropertyValue("opacity"), "0.2", "opacity animation overriding transition at 0s");
+advance_clock(500);
+is(cs.getPropertyValue("opacity"), "0.35", "opacity animation overriding transition at 0.5s");
+done_div();
+
+
+/*
+ * Bug 1320474 - keyframes-name may be a string, allows names that would otherwise be excluded
+ */
+new_div("position: relative; animation: \"string name 1\" 1s linear");
+advance_clock(0);
+is(cs.getPropertyValue("left"), "1px", "animation name as a string");
+div.style.animation = "string\\ name\\ 2 1s linear";
+is(cs.getPropertyValue("left"), "2px", "animation name specified as string, referenced using custom ident");
+div.style.animation = "custom\\ ident\\ 1 1s linear";
+is(cs.getPropertyValue("left"), "3px", "animation name specified as custom-ident");
+div.style.animation = "\"custom ident 2\" 1s linear";
+is(cs.getPropertyValue("left"), "4px", "animation name specified as custom-ident, referenced using string");
+div.style.animation = "unset";
+div.style.animation = "initial 1s linear";
+is(cs.getPropertyValue("left"), "0px", "animation name 'initial' as identifier is ignored");
+div.style.animation = "unset";
+div.style.animation = "\"initial\" 1s linear";
+is(cs.getPropertyValue("left"), "5px", "animation name 'initial' as string is accepted");
+div.style.animation = "unset";
+div.style.animation = "none 1s linear";
+is(cs.getPropertyValue("left"), "0px", "animation name 'none' as identifier is ignored");
+div.style.animation = "unset";
+div.style.animation = "\"none\" 1s linear";
+is(cs.getPropertyValue("left"), "7px", "animation name 'none' as string is accepted");
+done_div();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_async_tests.html b/layout/style/test/test_animations_async_tests.html
new file mode 100644
index 0000000000..7ad1d0d598
--- /dev/null
+++ b/layout/style/test/test_animations_async_tests.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1086937</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ window.open("file_animations_async_tests.html");
+ }
+ </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086937">Mozilla Bug 1086937</a>
+<div id="display"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_dynamic_changes.html b/layout/style/test/test_animations_dynamic_changes.html
new file mode 100644
index 0000000000..1863d08829
--- /dev/null
+++ b/layout/style/test/test_animations_dynamic_changes.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=978833
+-->
+<head>
+ <title>Test for Bug 978833</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+ @keyframes a {
+ from, to {
+ /* a non-inherited property, so it's cached in the rule tree */
+ margin-left: 50px;
+ }
+ }
+ .alwaysa {
+ animation: a linear 1s infinite;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978833">Mozilla Bug 978833</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+var style = document.getElementById("style").sheet;
+
+/** Test for Bug 978833 **/
+function test_bug978833() {
+ var kfs = style.cssRules[0];
+ var kf = kfs.cssRules[0];
+ is(kf.style.marginLeft, "50px", "we found the right keyframe rule");
+
+ p.classList.add("alwaysa");
+ is(cs.marginLeft, "50px", "p margin-left should be 50px");
+
+ // Temporarily remove the animation style, since we resolve keyframes
+ // on top of current animation styles (although maybe we shouldn't),
+ // so we need to remove those styles to hit the rule tree cache.
+ p.classList.remove("alwaysa");
+ is(cs.marginLeft, "0px", "p margin-left should be 0px without animation");
+
+ p.classList.add("alwaysa");
+ kf.style.marginLeft = "100px";
+ is(cs.marginLeft, "100px", "p margin-left should be 100px after change");
+
+ // Try the same thing a second time, just to make sure it works again.
+ p.classList.remove("alwaysa");
+ is(cs.marginLeft, "0px", "p margin-left should be 0px without animation");
+ p.classList.add("alwaysa");
+ kf.style.marginLeft = "150px";
+ is(cs.marginLeft, "150px", "p margin-left should be 150px after second change");
+
+ p.style.animation = "";
+}
+test_bug978833();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_effect_timing_duration.html b/layout/style/test/test_animations_effect_timing_duration.html
new file mode 100644
index 0000000000..7b3c443669
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_duration.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for animation.effect.updateTiming({ duration }) on compositor
+ animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)', easing: "steps(2, start)" },
+ { transform: 'translate(100px)' } ], 4000);
+ await waitForPaints();
+
+ advance_clock(500);
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ animation.effect.updateTiming({ duration: 2000 });
+ // Setter of timing option should set up the changes to animations for the
+ // next layer transaction but it won't schedule a paint immediately so we need
+ // to tick the refresh driver before we can wait on the next paint.
+ advance_clock(0);
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation remains on compositor");
+
+ advance_clock(1000);
+ omta_is(div, "transform", { tx: 100 }, RunningOn.Compositor,
+ "Animation is updated on compositor");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)', easing: "steps(2, end)" },
+ { transform: 'translate(100px)' } ], 4000);
+ await waitForPaints();
+
+ advance_clock(1000);
+ animation.effect.updateTiming({ duration: 2000 });
+ advance_clock(0);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ done_div();
+})
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_effect_timing_enddelay.html b/layout/style/test/test_animations_effect_timing_enddelay.html
new file mode 100644
index 0000000000..ad018f6373
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_enddelay.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for animation.effect.updateTiming({ endDelay }) on compositor
+ animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, fill: 'none' });
+ await waitForPaints();
+
+ advance_clock(100);
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ animation.effect.updateTiming({ endDelay: 1000 });
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "Animation remains on compositor when endDelay is changed");
+
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is updated on main thread");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, endDelay: -500, fill: 'none' });
+ await waitForPaints();
+
+ advance_clock(400);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 40 }, RunningOn.Compositor,
+ "Animation is updated on compositor " +
+ "duration 1000, endDelay -500, fill none, current time 400");
+
+ advance_clock(100);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill none, current time 500");
+
+ advance_clock(400);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill none, current time 900");
+
+ advance_clock(100);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill none, current time 1000");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, endDelay: 1000, fill: 'forwards' });
+ await waitForPaints();
+
+ advance_clock(1500);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 100 }, RunningOn.Compositor,
+ "The end delay is performed on the compositor thread");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, endDelay: -500, fill: 'forwards' });
+ await waitForPaints();
+
+ advance_clock(400);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 40 }, RunningOn.Compositor,
+ "Animation is updated on compositor " +
+ "duration 1000, endDelay -500, fill forwards, current time 400");
+
+ advance_clock(100);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill forwards, current time 500");
+
+ advance_clock(400);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill forwards, current time 900");
+
+ advance_clock(100);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill forwards, current time 1000");
+
+ done_div();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_effect_timing_iterations.html b/layout/style/test/test_animations_effect_timing_iterations.html
new file mode 100644
index 0000000000..380cab1763
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_iterations.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for Animation.effect.updateTiming({ iterations }) on compositor
+ animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' },
+ { transform: 'translate(100px)' } ],
+ { duration: 4000,
+ iterations: 2
+ });
+ await waitForPaints();
+
+ advance_clock(6000);
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ animation.effect.updateTiming({ iterations: 1 });
+ advance_clock(0);
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is on MainThread");
+
+ animation.effect.updateTiming({ iterations: 3 });
+
+ advance_clock(0);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running again on compositor");
+
+ done_div();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_event_handler_attribute.html b/layout/style/test/test_animations_event_handler_attribute.html
new file mode 100644
index 0000000000..7a9da1809c
--- /dev/null
+++ b/layout/style/test/test_animations_event_handler_attribute.html
@@ -0,0 +1,204 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=911987
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for CSS Animation and Transition event handler
+ attributes. (Bug 911987)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @keyframes anim { to { margin-left: 100px } }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=911987">Mozilla Bug
+ 911987</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+'use strict';
+
+// Create the div element with event handlers.
+// We need two elements: one with the content attribute speficied and one
+// with the IDL attribute specified since we can't set these independently.
+function createAndRegisterTargets(eventAttributes) {
+ var displayElement = document.getElementById('display');
+ var contentAttributeElement = document.createElement("div");
+ var idlAttributeElement = document.createElement("div");
+ displayElement.appendChild(contentAttributeElement);
+ displayElement.appendChild(idlAttributeElement);
+
+ // Add handlers
+ eventAttributes.forEach(event => {
+ contentAttributeElement.setAttribute(event, 'handleEvent(event);');
+ contentAttributeElement.handlerType = 'content attribute';
+ idlAttributeElement[event] = handleEvent;
+ idlAttributeElement.handlerType = 'IDL attribute';
+ });
+
+ return [contentAttributeElement, idlAttributeElement];
+}
+
+function handleEvent(event) {
+ if (event.target.receivedEventType) {
+ ok(false, `Received ${event.type} event, but this element have previous `
+ `received event '${event.target.receivedEventType}'.`);
+ return;
+ }
+ event.target.receivedEventType = event.type;
+}
+
+function checkReceivedEvents(eventType, elements) {
+ elements.forEach(element => {
+ is(element.receivedEventType, eventType,
+ `Expected to receive '${eventType}', got
+ '${element.receivedEventType}', for event handler registered
+ using ${element.handlerType}`);
+ element.receivedEventType = undefined;
+ });
+}
+
+// Take over the refresh driver right from the start.
+advance_clock(0);
+
+// 1a. Test CSS Animation event handlers (without animationcancel)
+
+var targets = createAndRegisterTargets([ 'onanimationstart',
+ 'onanimationiteration',
+ 'onanimationend',
+ 'onanimationcancel']);
+targets.forEach(div => {
+ div.setAttribute('style', 'animation: anim 100ms 2');
+ getComputedStyle(div).animationName; // flush
+});
+
+advance_clock(0);
+checkReceivedEvents("animationstart", targets);
+
+advance_clock(100);
+checkReceivedEvents("animationiteration", targets);
+
+advance_clock(200);
+checkReceivedEvents("animationend", targets);
+
+targets.forEach(div => { div.remove(); });
+
+// 1b. Test CSS Animation cancel event handler
+var targets = createAndRegisterTargets([ 'onanimationcancel' ]);
+
+targets.forEach(div => {
+ div.setAttribute('style', 'animation: anim 100ms 2 200ms');
+ getComputedStyle(div).animationName; // flush
+});
+
+advance_clock(200);
+
+targets.forEach(div => {
+ div.style.display = "none"
+ getComputedStyle(div).display; // flush
+});
+
+advance_clock(0);
+checkReceivedEvents("animationcancel", targets);
+
+advance_clock(200);
+
+targets.forEach(div => { div.remove(); });
+
+
+// 2a. Test CSS Transition event handlers (without transitioncancel)
+
+var targets = createAndRegisterTargets([ 'ontransitionrun',
+ 'ontransitionstart',
+ 'ontransitionend',
+ 'ontransitioncancel' ]);
+targets.forEach(div => {
+ div.style.transition = 'margin-left 100ms 200ms';
+ getComputedStyle(div).marginLeft; // flush
+ div.style.marginLeft = "200px";
+ getComputedStyle(div).marginLeft; // flush
+});
+
+advance_clock(0);
+checkReceivedEvents("transitionrun", targets);
+
+advance_clock(200);
+checkReceivedEvents("transitionstart", targets);
+
+advance_clock(100);
+checkReceivedEvents("transitionend", targets);
+
+targets.forEach(div => { div.remove(); });
+
+// 2b. Test CSS Transition cancel event handler.
+
+var targets = createAndRegisterTargets([ 'ontransitioncancel' ]);
+targets.forEach(div => {
+ div.style.transition = 'margin-left 100ms 200ms';
+ getComputedStyle(div).marginLeft; // flush
+ div.style.marginLeft = "200px";
+ getComputedStyle(div).marginLeft; // flush
+});
+
+advance_clock(200);
+
+targets.forEach(div => {
+ div.style.display = "none"
+});
+getComputedStyle(targets[0]).display; // flush
+
+advance_clock(0);
+checkReceivedEvents("transitioncancel", targets);
+
+advance_clock(100);
+targets.forEach( div => { is(div.receivedEventType, undefined); });
+
+targets.forEach(div => { div.remove(); });
+
+// 3. Test prefixed CSS Animation event handlers.
+
+var targets = createAndRegisterTargets([ 'onwebkitanimationstart',
+ 'onwebkitanimationiteration',
+ 'onwebkitanimationend' ]);
+targets.forEach(div => {
+ div.setAttribute('style', 'animation: anim 100ms 2');
+ getComputedStyle(div).animationName; // flush
+});
+
+advance_clock(0);
+checkReceivedEvents("webkitAnimationStart", targets);
+
+advance_clock(100);
+checkReceivedEvents("webkitAnimationIteration", targets);
+
+advance_clock(200);
+checkReceivedEvents("webkitAnimationEnd", targets);
+
+targets.forEach(div => { div.remove(); });
+
+// 4. Test prefixed CSS Transition event handlers.
+
+advance_clock(0);
+var targets = createAndRegisterTargets([ 'onwebkittransitionend' ]);
+targets.forEach(div => {
+ div.style.transition = 'margin-left 100ms';
+ getComputedStyle(div).marginLeft; // flush
+ div.style.marginLeft = "200px";
+ getComputedStyle(div).marginLeft; // flush
+});
+
+advance_clock(100);
+checkReceivedEvents("webkitTransitionEnd", targets);
+
+targets.forEach(div => { div.remove(); });
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_event_order.html b/layout/style/test/test_animations_event_order.html
new file mode 100644
index 0000000000..7caee2bdcb
--- /dev/null
+++ b/layout/style/test/test_animations_event_order.html
@@ -0,0 +1,710 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1183461
+-->
+<!--
+ This test is similar to those in test_animations.html with the exception
+ that those tests interact with a single element at a time. The tests in this
+ file are specifically concerned with testing the ordering of events between
+ elements for which most of the utilities in animation_utils.js are not
+ suited.
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for CSS Animation and Transition event ordering
+ (Bug 1183461)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <!-- We still need animation_utils.js for advance_clock -->
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @keyframes anim { to { margin-left: 100px } }
+ @keyframes animA { to { margin-left: 100px } }
+ @keyframes animB { to { margin-left: 100px } }
+ @keyframes animC { to { margin-left: 100px } }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1183461">Mozilla Bug
+ 1183461</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+'use strict';
+
+/* eslint-disable no-shadow */
+
+// Take over the refresh driver right from the start.
+advance_clock(0);
+
+// Common test scaffolding
+
+var gEventsReceived = [];
+var gDisplay = document.getElementById('display');
+
+[ 'animationstart',
+ 'animationiteration',
+ 'animationend',
+ 'animationcancel',
+ 'transitionrun',
+ 'transitionstart',
+ 'transitionend',
+ 'transitioncancel' ]
+ .forEach(event =>
+ gDisplay.addEventListener(event,
+ event => gEventsReceived.push(event)));
+
+function checkEventOrder(...args) {
+ // Argument format:
+ // Arguments = ExpectedEvent*, desc
+ // ExpectedEvent =
+ // [ target|animationName|transitionProperty, (pseudo,) message ]
+ var expectedEvents = args.slice(0, -1);
+ var desc = args[args.length - 1];
+ var isTestingNameOrProperty = expectedEvents.length &&
+ typeof expectedEvents[0][0] == 'string';
+
+ var formatEvent = (target, nameOrProperty, pseudo, message) =>
+ isTestingNameOrProperty ?
+ `${nameOrProperty}${pseudo}:${message}` :
+ `${target.id}${pseudo}:${message}`;
+
+ var actual = gEventsReceived.map(
+ event => formatEvent(event.target,
+ event.animationName || event.propertyName,
+ event.pseudoElement, event.type)
+ ).join(';');
+ var expected = expectedEvents.map(
+ event => event.length == 3 ?
+ formatEvent(event[0], event[0], event[1], event[2]) :
+ formatEvent(event[0], event[0], '', event[1])
+ ).join(';');
+ is(actual, expected, desc);
+ gEventsReceived = [];
+}
+
+// 1. TESTS FOR SORTING BY TREE ORDER
+
+// 1a. Test that simultaneous events are sorted by tree order (siblings)
+
+var divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+ getComputedStyle(div).animationName; // trigger building of animation
+});
+
+advance_clock(0);
+checkEventOrder([ divs[0], 'animationstart' ],
+ [ divs[1], 'animationstart' ],
+ [ divs[2], 'animationstart' ],
+ 'Simultaneous start on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 1b. Test that simultaneous events are sorted by tree order (children)
+
+divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// / \
+// div[0] div[1]
+// /
+// div[2]
+
+gDisplay.appendChild(divs[0]);
+gDisplay.appendChild(divs[1]);
+divs[0].appendChild(divs[2]);
+
+divs.forEach((div, i) => {
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+ getComputedStyle(div).animationName; // trigger building of animation
+});
+
+advance_clock(0);
+checkEventOrder([ divs[0], 'animationstart' ],
+ [ divs[2], 'animationstart' ],
+ [ divs[1], 'animationstart' ],
+ 'Simultaneous start on children');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 1c. Test that simultaneous events are sorted by tree order (pseudos)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// |
+// div[0]
+// ::before,
+// ::after
+// |
+// div[1]
+
+gDisplay.appendChild(divs[0]);
+divs[0].appendChild(divs[1]);
+
+divs.forEach((div, i) => {
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+});
+
+var extraStyle = document.createElement('style');
+document.head.appendChild(extraStyle);
+var sheet = extraStyle.sheet;
+sheet.insertRule('#div0::after { animation: anim 10s }', 0);
+sheet.insertRule('#div0::before { animation: anim 10s }', 1);
+sheet.insertRule('#div0::after, #div0::before { ' +
+ ' content: " " }', 2);
+getComputedStyle(divs[0]).animationName; // build animation
+getComputedStyle(divs[1]).animationName; // build animation
+
+advance_clock(0);
+checkEventOrder([ divs[0], 'animationstart' ],
+ [ divs[0], '::before', 'animationstart' ],
+ [ divs[0], '::after', 'animationstart' ],
+ [ divs[1], 'animationstart' ],
+ 'Simultaneous start on pseudo-elements');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+sheet = undefined;
+extraStyle.remove();
+extraStyle = undefined;
+
+// 2. TESTS FOR SORTING BY TIME
+
+// 2a. Test that events are sorted by time
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.animationDelay = '5s';
+
+advance_clock(0);
+advance_clock(20000);
+
+checkEventOrder([ divs[1], 'animationstart' ],
+ [ divs[0], 'animationstart' ],
+ [ divs[1], 'animationend' ],
+ [ divs[0], 'animationend' ],
+ 'Sorting of start and end events by time');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 2b. Test different events within the one element
+
+var div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.animation = 'anim 4s 2, ' + // Repeat at t=4s
+ 'anim 10s 5s, ' + // Start at t=5s
+ 'anim 3s'; // End at t=3s
+div.setAttribute('id', 'div');
+getComputedStyle(div).animationName; // build animation
+
+advance_clock(0);
+advance_clock(5000);
+
+checkEventOrder([ div, 'animationstart' ],
+ [ div, 'animationstart' ],
+ [ div, 'animationend' ],
+ [ div, 'animationiteration' ],
+ [ div, 'animationstart' ],
+ 'Sorting of different events by time within an element');
+
+div.remove();
+div = undefined;
+
+// 2c. Test negative delay is sorted equal to zero delay but before
+// positive delay
+
+divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.animation = 'anim 20s 5s'; // Positive delay, sorts last
+divs[1].style.animation = 'anim 20s'; // 0s delay
+divs[2].style.animation = 'anim 20s -5s'; // Negative delay, sorts same as
+ // 0s delay, i.e. use document
+ // position
+
+advance_clock(0);
+advance_clock(5000);
+checkEventOrder([ divs[1], 'animationstart' ],
+ [ divs[2], 'animationstart' ],
+ [ divs[0], 'animationstart' ],
+ 'Sorting of events including negative delay');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 3. TESTS FOR SORTING BY animation-name POSITION
+
+// 3a. Test animation-name position
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.animation = 'animA 10s, animB 5s, animC 5s 2';
+div.setAttribute('id', 'div');
+getComputedStyle(div).animationName; // build animation
+
+advance_clock(0);
+
+checkEventOrder([ 'animA', 'animationstart' ],
+ [ 'animB', 'animationstart' ],
+ [ 'animC', 'animationstart' ],
+ 'Sorting of simultaneous animationstart events by ' +
+ 'animation-name');
+
+advance_clock(5000);
+
+checkEventOrder([ 'animB', 'animationend' ],
+ [ 'animC', 'animationiteration' ],
+ 'Sorting of different types of events by animation-name');
+
+div.remove();
+div = undefined;
+
+// 3b. Test time trumps animation-name position
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.animation = 'animA 10s 2s, animB 10s 1s';
+div.setAttribute('id', 'div');
+
+advance_clock(0);
+advance_clock(3000);
+
+checkEventOrder([ 'animB', 'animationstart' ],
+ [ 'animA', 'animationstart' ],
+ 'Events are sorted by time first, before animation-position');
+
+div.remove();
+div = undefined;
+
+// 4. TESTS FOR TRANSITIONS
+
+// 4a. Test sorting transitions by document position
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.style.transition = 'margin-left 10s';
+ div.setAttribute('id', 'div' + i);
+});
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4b. Test sorting transitions by document position (children)
+
+divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// / \
+// div[0] div[1]
+// /
+// div[2]
+
+gDisplay.appendChild(divs[0]);
+gDisplay.appendChild(divs[1]);
+divs[0].appendChild(divs[2]);
+
+divs.forEach((div, i) => {
+ div.style.marginLeft = '0px';
+ div.style.transition = 'margin-left 10s';
+ div.setAttribute('id', 'div' + i);
+});
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[2], 'transitionrun' ],
+ [ divs[2], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[2], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on children');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4c. Test sorting transitions by document position (pseudos)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// |
+// div[0]
+// ::before,
+// ::after
+// |
+// div[1]
+
+gDisplay.appendChild(divs[0]);
+divs[0].appendChild(divs[1]);
+
+divs.forEach((div, i) => {
+ div.setAttribute('id', 'div' + i);
+});
+
+extraStyle = document.createElement('style');
+document.head.appendChild(extraStyle);
+sheet = extraStyle.sheet;
+sheet.insertRule('div, #div0::after, #div0::before { ' +
+ ' transition: margin-left 10s; ' +
+ ' margin-left: 0px }', 0);
+sheet.insertRule('div.active, #div0.active::after, #div0.active::before { ' +
+ ' margin-left: 100px }', 1);
+sheet.insertRule('#div0::after, #div0::before { ' +
+ ' content: " " }', 2);
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.classList.add('active'));
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[0], '::before', 'transitionrun' ],
+ [ divs[0], '::before', 'transitionstart' ],
+ [ divs[0], '::after', 'transitionrun' ],
+ [ divs[0], '::after', 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[0], '::before', 'transitionend' ],
+ [ divs[0], '::after', 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on pseudo-elements');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+sheet = undefined;
+extraStyle.remove();
+extraStyle = undefined;
+
+// 4d. Test sorting transitions by time
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s';
+divs[1].style.transition = 'margin-left 5s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Sorting of transitionrun/start/end events by time');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4e. Test sorting transitions by time (with delay)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 5s 5s';
+divs[1].style.transition = 'margin-left 5s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Sorting of transitionrun/start/end events by time' +
+ '(including delay)');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4f. Test sorting transitions by transition-property
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.opacity = '0';
+div.style.marginLeft = '0px';
+div.style.transition = 'all 5s';
+
+getComputedStyle(div).marginLeft;
+div.style.opacity = '1';
+div.style.marginLeft = '100px';
+getComputedStyle(div).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'margin-left', 'transitionend' ],
+ [ 'opacity', 'transitionend' ],
+ 'Sorting of transitionrun/start/end events by ' +
+ 'transition-property')
+
+div.remove();
+div = undefined;
+
+// 4g. Test document position beats transition-property
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.style.opacity = '0';
+ div.style.transition = 'all 10s';
+ div.setAttribute('id', 'div' + i);
+});
+
+getComputedStyle(divs[0]).marginLeft;
+divs[0].style.opacity = '1';
+divs[1].style.marginLeft = '100px';
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Transition events are sorted by document position first, ' +
+ 'before transition-property');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4h. Test time beats transition-property
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.opacity = '0';
+div.style.marginLeft = '0px';
+div.style.transition = 'margin-left 10s, opacity 5s';
+
+getComputedStyle(div).marginLeft;
+div.style.opacity = '1';
+div.style.marginLeft = '100px';
+getComputedStyle(div).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'opacity', 'transitionend' ],
+ [ 'margin-left', 'transitionend' ],
+ 'Transition events are sorted by time first, before ' +
+ 'transition-property');
+
+div.remove();
+div = undefined;
+
+// 4i. Test sorting transitions by document position (negative delay)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s 5s';
+divs[1].style.transition = 'margin-left 10s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(15 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4j. Test sorting transitions with cancel
+// The order of transitioncancel is based on StyleManager.
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s 5s';
+divs[1].style.transition = 'margin-left 10s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(5 * 1000);
+divs.forEach(div => {
+ div.style.display = 'none';
+ // The transitioncancel event order is not absolute when firing siblings
+ // transitioncancel on same elapsed time.
+ // Force to flush style for the element so that the transition on the element
+ // iscancelled and corresponding cancel event is queued respectively.
+ getComputedStyle(div).display;
+});
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[0], 'transitioncancel' ],
+ [ divs[1], 'transitioncancel' ],
+ 'Simultaneous transitionrun/start/cancel on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+
+// 4k. Test sorting animations with cancel
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.animation = 'anim 10s 5s';
+divs[1].style.animation = 'anim 10s';
+
+getComputedStyle(divs[0]).animation; // flush
+
+advance_clock(0); // divs[1]'s animation start
+advance_clock(5 * 1000); // divs[0]'s animation start
+divs.forEach(div => {
+ div.style.display = 'none';
+ // The animationcancel event order is not absolute when firing siblings
+ // animationcancel on same elapsed time.
+ // Force to flush style for the element so that the transition on the element
+ // iscancelled and corresponding cancel event is queued respectively.
+ getComputedStyle(div).display;
+});
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[1], 'animationstart' ],
+ [ divs[0], 'animationstart' ],
+ [ divs[0], 'animationcancel' ],
+ [ divs[1], 'animationcancel' ],
+ 'Simultaneous animationcancel on siblings');
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_iterationstart.html b/layout/style/test/test_animations_iterationstart.html
new file mode 100644
index 0000000000..dbca9490e4
--- /dev/null
+++ b/layout/style/test/test_animations_iterationstart.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for Animation.effect.timing.iterationStart on compositor animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("test");
+ var animation = div.animate(
+ { transform: ["translate(0px)", "translate(100px)"] },
+ { iterationStart: 0.5, duration: 10000, fill: "both"}
+ );
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, "Start of Animation");
+
+ advance_clock(4000);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 90 }, RunningOn.Compositor, "40% of Animation");
+
+ advance_clock(6000);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread, "End of Animation");
+
+ done_div();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_omta.html b/layout/style/test/test_animations_omta.html
new file mode 100644
index 0000000000..06a409b490
--- /dev/null
+++ b/layout/style/test/test_animations_omta.html
@@ -0,0 +1,2969 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=964646
+-->
+<!--
+
+ ========= PLEASE KEEP THIS IN SYNC WITH test_animations.html =========
+
+ This test mimicks the content of test_animations.html but performs tests
+ specific to animations that run on the compositor thread since they require
+ special (asynchronous) handling. Furthermore, these tests check that
+ animations that are expected to run on the compositor thread, are actually
+ doing so.
+
+ If you are making changes to this file or to test_animations.html, please
+ try to keep them consistent where appropriate.
+
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for css3-animations running on the compositor thread (Bug
+ 964646)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ @keyframes transform-anim {
+ to {
+ transform: translate(100px);
+ }
+ }
+ @keyframes anim1 {
+ 0% { transform: translate(0px) }
+ 50% { transform: translate(80px) }
+ 100% { transform: translate(100px) }
+ }
+ @keyframes anim2 {
+ from { opacity: 0 } to { opacity: 1 }
+ }
+ @keyframes anim3 {
+ from { opacity: 0 } to { opacity: 1 }
+ }
+ @keyframes anim4 {
+ from { transform: translate(0px, 0px) }
+ to { transform: translate(0px, 100px) }
+ }
+
+ @keyframes kf1 {
+ 50% { transform: translate(50px) }
+ to { transform: translate(150px) }
+ }
+ @keyframes kf2 {
+ from { transform: translate(150px) }
+ 50% { transform: translate(50px) }
+ }
+ @keyframes kf3 {
+ 25% { transform: translate(100px) }
+ }
+ @keyframes kf4 {
+ to, from { display: none; transform: translate(37px) }
+ }
+ @keyframes kf_cascade1 {
+ from { transform: translate(50px) }
+ 50%, from { transform: translate(30px) } /* wins: 0% */
+ 75%, 85%, 50% { transform: translate(20px) } /* wins: 75%, 50% */
+ 100%, 85% { transform: translate(70px) } /* wins: 100% */
+ 85.1% { transform: translate(60px) } /* wins: 85.1% */
+ 85% { transform: translate(30px) } /* wins: 85% */
+ }
+ @keyframes kf_cascade2 { from, to { opacity: 0.3 } }
+ @keyframes kf_cascade2 { from, to { transform: translate(50px) } }
+ @keyframes kf_cascade2 { from, to { transform: translate(100px) } }
+ @keyframes kf_tf1 {
+ 0% { transform: translate(20px); animation-timing-function: ease }
+ 25% { transform: translate(60px); }
+ 50% { transform: translate(160px); animation-timing-function: steps(5) }
+ 75% { transform: translate(120px); animation-timing-function: linear }
+ 100% { transform: translate(20px); animation-timing-function: ease-out }
+ }
+ @keyframes kf_scale {
+ to { scale: 2.25 2.25; }
+ }
+
+ @keyframes always_fifty {
+ from, to { transform: translate(50px) }
+ }
+
+ #withbefore::before, #withafter::after {
+ content: "test";
+ animation: anim4 1s linear alternate 3;
+ display:block;
+ }
+
+ @keyframes multiprop {
+ 0% {
+ transform: translate(10px); opacity: 0.3;
+ animation-timing-function: ease;
+ }
+ 25% {
+ opacity: 0.5;
+ animation-timing-function: ease-out;
+ }
+ 50% {
+ transform: translate(40px);
+ }
+ 75% {
+ transform: translate(80px); opacity: 0.6;
+ animation-timing-function: ease-in;
+ }
+ }
+
+ @keyframes cascade {
+ 0%, 25%, 100% { transform: translate(0px) }
+ 50%, 75% { transform: translate(100px) }
+ 0%, 75%, 100% { opacity: 0 }
+ 25%, 50% { opacity: 1 }
+ }
+ @keyframes cascade2 {
+ 0% { transform: translate(0px) }
+ 25% { transform: translate(30px);
+ animation-timing-function: ease-in } /* beaten by rule below */
+ 50% { transform: translate(0px) }
+ 25% { transform: translate(50px) }
+ 100% { transform: translate(100px) }
+ }
+
+ @keyframes primitives1 {
+ from { transform: rotate(0deg) translateX(0px) scaleX(1)
+ translate(0px) scale3d(1, 1, 1); }
+ to { transform: rotate(270deg) translate3d(0px, 0px, 0px) scale(1)
+ translateY(0px) scaleY(1); }
+ }
+
+ @keyframes important1 {
+ from { opacity: 0.5; }
+ 50% { opacity: 1 !important; } /* ignored */
+ to { opacity: 0.8; }
+ }
+ @keyframes important2 {
+ from { opacity: 0.5;
+ transform: translate(100px); }
+ to { opacity: 0.2 !important; /* ignored */
+ transform: translate(50px); }
+ }
+
+ @keyframes empty { }
+ @keyframes nearlyempty {
+ to {
+ transform: translate(100px);
+ }
+ }
+
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+
+ .visitedLink:link { background-color: yellow }
+ .visitedLink:visited { background-color: blue }
+
+ @keyframes opacitymid {
+ 0% { opacity: 0.2 }
+ 100% { opacity: 0.8 }
+ }
+
+ @keyframes transformnone {
+ 0%, 100% { transform: translateX(50px) }
+ 25%, 75% { transform: none }
+ }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=964646">Mozilla Bug
+ 964646</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+/** Test for css3-animations running on the compositor thread (Bug 964646) **/
+
+// Global state
+var gDisplay = document.getElementById("display")
+ , gDiv = null;
+
+// Shortcut omta_is and friends by filling in the initial 'elem' argument
+// with gDiv.
+[ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) {
+ var origFn = window[fn];
+ window[fn] = function() {
+ var args = Array.from(arguments);
+ if (!(args[0] instanceof Element)) {
+ args.unshift(gDiv);
+ }
+ return origFn.apply(window, args);
+ };
+});
+
+// Shortcut new_div and done_div to update gDiv
+var originalNewDiv = window.new_div;
+window.new_div = function(style) {
+ [ gDiv ] = originalNewDiv(style);
+};
+var originalDoneDiv = window.done_div;
+window.done_div = function() {
+ originalDoneDiv();
+ gDiv = null;
+};
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+runOMTATest(function() {
+ var onAbort = function() {
+ if (gDiv) {
+ done_div();
+ }
+ };
+ runAllAsyncAnimTests(onAbort).then(function() {
+ SimpleTest.finish();
+ });
+}, SimpleTest.finish);
+
+//----------------------------------------------------------------------
+//
+// Test cases
+//
+//----------------------------------------------------------------------
+
+// This test is not in test_animations.html but is here to test that
+// transform animations are actually run on the compositor thread as expected.
+addAsyncAnimTest(async function() {
+ new_div("animation: transform-anim linear 300s");
+
+ await waitForPaintsFlushed();
+
+ advance_clock(200000);
+ omta_is("transform", { tx: 100 * 2 / 3 }, RunningOn.Compositor,
+ "OMTA animation is animating as expected");
+ done_div();
+});
+
+async function testFillMode(fillMode, fillsBackwards, fillsForwards)
+{
+ var style = "transform: translate(30px); animation: 10s 3s anim1 linear";
+ var desc;
+ if (fillMode.length > 0) {
+ style += " " + fillMode;
+ desc = "fill mode " + fillMode + ": ";
+ } else {
+ desc = "default fill mode: ";
+ }
+ new_div(style);
+ listen();
+
+ await waitForPaintsFlushed();
+
+ if (fillsBackwards)
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ desc + "does affect value during delay (0s)");
+ else
+ omta_is("transform", { tx: 30 }, RunningOn.MainThread,
+ desc + "doesn't affect value during delay (0s)");
+
+ advance_clock(2000);
+ if (fillsBackwards)
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ desc + "does affect value during delay (0s)");
+ else
+ omta_is("transform", { tx: 30 }, RunningOn.MainThread,
+ desc + "does affect value during delay (0s)");
+
+ check_events([], "before start in testFillMode");
+ advance_clock(1000);
+ check_events([{ type: "animationstart", target: gDiv,
+ bubbles: true, cancelable: false,
+ animationName: "anim1", elapsedTime: 0.0,
+ pseudoElement: "" }],
+ "right after start in testFillMode");
+
+ // If we have a backwards fill then at the start of the animation we will end
+ // up applying the same value as the fill value. Various optimizations in
+ // RestyleManager may filter out this meaning that the animation doesn't get
+ // added to the compositor thread until the first time the value changes.
+ //
+ // As a result we look for this first sample on either the compositor or the
+ // computed style
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.Either,
+ desc + "affects value at start of animation");
+ advance_clock(125);
+ // We might not add the animation to compositor until the second sample (due
+ // to the optimizations mentioned above) so we should wait for paints before
+ // proceeding
+ await waitForPaints();
+ omta_is("transform", { tx: 2 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ advance_clock(2375);
+ omta_is("transform", { tx: 40 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ advance_clock(2500);
+ omta_is("transform", { tx: 80 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ advance_clock(2500);
+ omta_is("transform", { tx: 90 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ advance_clock(2375);
+ omta_is("transform", { tx: 99.5 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ check_events([], "before end in testFillMode");
+ advance_clock(125);
+ check_events([{ type: "animationend", target: gDiv,
+ bubbles: true, cancelable: false,
+ animationName: "anim1", elapsedTime: 10.0,
+ pseudoElement: "" }],
+ "right after end in testFillMode");
+
+ // Currently the compositor will apply a forwards fill until it gets told by
+ // the main thread to clear the animation. As a result we should wait for
+ // paints to be flushed before checking that the animated value does *not*
+ // appear on the compositor thread.
+ await waitForPaints();
+ if (fillsForwards)
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ desc + "affects value at end of animation");
+ advance_clock(10);
+ if (fillsForwards)
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ desc + "affects value after animation");
+ else
+ omta_is("transform", { tx: 30 }, RunningOn.MainThread,
+ desc + "does not affect value after animation");
+
+ done_div();
+}
+
+addAsyncAnimTest(function() { return testFillMode("", false, false); });
+addAsyncAnimTest(function() { return testFillMode("none", false, false); });
+addAsyncAnimTest(function() { return testFillMode("forwards", false, true); });
+addAsyncAnimTest(function() { return testFillMode("backwards", true, false); });
+addAsyncAnimTest(function() { return testFillMode("both", true, true); });
+
+// Test that animations continue running when the animation name
+// list is changed.
+//
+// test_animations.html combines all these tests into one block but this is
+// difficult for OMTA because currently there are only two properties to which
+// we apply OMTA. Instead we break the test down into a few independent pieces
+// in order to exercise the same functionality.
+
+// Append to list
+addAsyncAnimTest(async function() {
+ new_div("animation: anim1 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Either,
+ "just anim1, translate at start");
+ advance_clock(1000);
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "just anim1, translate at 1s");
+ // append anim2
+ gDiv.style.animation = "anim1 linear 10s, anim2 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "anim1 + anim2, translate at 1s");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim1 + anim2, opacity at 1s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim1 + anim2, translate at 2s");
+ omta_is("opacity", 0.1, RunningOn.Compositor,
+ "anim1 + anim2, opacity at 2s");
+ done_div();
+});
+
+// Prepend to list; delete from list
+addAsyncAnimTest(async function() {
+ new_div("animation: anim1 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Either,
+ "just anim1, translate at start");
+ advance_clock(1000);
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "just anim1, translate at 1s");
+ // prepend anim2
+ gDiv.style.animation = "anim2 linear 10s, anim1 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "anim2 + anim1, translate at 1s");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim2 + anim1, opacity at 1s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim2 + anim1, translate at 2s");
+ omta_is("opacity", 0.1, RunningOn.Compositor,
+ "anim2 + anim1, opacity at 2s");
+ // remove anim2 from list
+ gDiv.style.animation = "anim1 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "just anim1, translate at 2s");
+ omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 2s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 48 }, RunningOn.Compositor,
+ "just anim1, translate at 3s");
+ omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 3s");
+ done_div();
+});
+
+// Swap elements
+addAsyncAnimTest(async function() {
+ new_div("animation: anim1 linear 10s, anim2 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Either,
+ "anim1 + anim2, translate at start");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim1 + anim2, opacity at start");
+ advance_clock(1000);
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "anim1 + anim2, translate at 1s");
+ omta_is("opacity", 0.1, RunningOn.Compositor,
+ "anim1 + anim2, opacity at 1s");
+ // swap anim1 and anim2, change duration of anim2
+ gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "anim2 + anim1, translate at 1s");
+ omta_is("opacity", 0.2, RunningOn.Compositor,
+ "anim2 + anim1, opacity at 1s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim2 + anim1, translate at 2s");
+ omta_is("opacity", 0.4, RunningOn.Compositor,
+ "anim2 + anim1, opacity at 2s");
+ // list anim2 twice, last duration wins, original start time still applies
+ gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s, anim2 linear 20s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim2 + anim1 + anim2, translate at 2s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1",
+ "anim2 + anim1 + anim2, opacity at 2s");
+ // drop one of the anim2, and list anim3 as well, which animates
+ // the same property as anim2
+ gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim1 + anim2 + anim3, translate at 2s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0",
+ "anim1 + anim2 + anim3, opacity at 2s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 48 }, RunningOn.Compositor,
+ "anim1 + anim2 + anim3, translate at 3s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1",
+ "anim1 + anim2 + anim3, opacity at 3s");
+ // now swap the anim3 and anim2 order
+ gDiv.style.animation = "anim1 linear 10s, anim3 linear 10s, anim2 linear 20s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 48 }, RunningOn.Compositor,
+ "anim1 + anim3 + anim2, translate at 3s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.15",
+ "anim1 + anim3 + anim2, opacity at 3s");
+ advance_clock(2000); // (unlike test_animations.html, we seek 2s forwards here
+ // since at 4s anim2 and anim3 produce the same result so
+ // we can't tell which won.)
+ omta_is("transform", { tx: 80 }, RunningOn.Compositor,
+ "anim1 + anim3 + anim2, translate at 5s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.25",
+ "anim1 + anim3 + anim2, opacity at 5s");
+ // swap anim3 and anim2 back
+ gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 80 }, RunningOn.Compositor,
+ "anim1 + anim2 + anim3, translate at 5s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.3",
+ "anim1 + anim2 + anim3, opacity at 5s");
+ // seek past end of anim1
+ advance_clock(5100);
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "anim1 + anim2 + anim3, translate at 10.1s");
+ // Change the animation fill mode on the completed animation.
+ gDiv.style.animation =
+ "anim1 linear 10s forwards, anim2 linear 20s, anim3 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ "anim1 + anim2 + anim3, translate at 10.1s with fill mode");
+ advance_clock(900);
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ "anim1 + anim2 + anim3, translate at 11s with fill mode");
+ // Change the animation duration on the completed animation, so it is
+ // no longer completed.
+ // XXX Not sure about this---there seems to be a bug in test_animations.html
+ // in that it drops the fill mode but the test comment says it has a fill mode
+ gDiv.style.animation = "anim1 linear 20s, anim2 linear 20s, anim3 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 82 }, RunningOn.Compositor,
+ "anim1 + anim2 + anim3, translate at 11s with fill mode");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.9",
+ "anim1 + anim2 + anim3, opacity at 11s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3. Keyframes
+ * http://dev.w3.org/csswg/css3-animations/#keyframes
+ */
+
+// Test the rules on keyframes that lack a 0% or 100% rule:
+// (simultaneously, test that reverse animations have their keyframes
+// run backwards)
+
+addAsyncAnimTest(async function() {
+ // 100px at 0%, 50px at 50%, 150px at 100%
+ new_div("transform: translate(100px); " +
+ "animation: kf1 ease 1s alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 0.0s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0% at 0.1s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 0.01,
+ RunningOn.Compositor, "no-0% at 0.3s");
+ advance_clock(200);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-0% at 0.5s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.4) }, 0.01,
+ RunningOn.Compositor, "no-0% at 0.7s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 0.01,
+ RunningOn.Compositor, "no-0% at 0.9s");
+ advance_clock(100);
+ omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-0% at 1.0s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 0.01,
+ RunningOn.Compositor, "no-0% at 1.1s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0% at 1.4s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 0.01,
+ RunningOn.Compositor, "no-0% at 1.7s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0% at 1.9s");
+ advance_clock(100);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 2.0s");
+ done_div();
+
+ // 150px at 0%, 50px at 50%, 100px at 100%
+ new_div("transform: translate(100px); " +
+ "animation: kf2 ease-in 1s alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 0.0s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "no-100% at 0.1s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor, "no-100% at 0.3s");
+ advance_clock(200);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-100% at 0.5s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.4) }, 0.01,
+ RunningOn.Compositor, "no-100% at 0.7s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 0.01,
+ RunningOn.Compositor, "no-100% at 0.9s");
+ advance_clock(100);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-100% at 1.0s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 0.01,
+ RunningOn.Compositor, "no-100% at 1.1s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "no-100% at 1.4s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor, "no-100% at 1.7s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "no-100% at 1.9s");
+ advance_clock(100);
+ omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 2.0s");
+ done_div();
+
+ // 50px at 0%, 100px at 25%, 50px at 100%
+ new_div("transform: translate(50px); " +
+ "animation: kf3 ease-out 1s alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "no-0%-no-100% at 0.0s");
+ advance_clock(50);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 0.05s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 0.15s");
+ advance_clock(100);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "no-0%-no-100% at 0.25s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.4) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 0.55s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 0.85s");
+ advance_clock(150);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "no-0%-no-100% at 1.0s");
+ advance_clock(150);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 1.15s");
+ advance_clock(450);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 1.6s");
+ advance_clock(250);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 1.85s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 1.95s");
+ advance_clock(50);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "no-0%-no-100% at 2.0s");
+ done_div();
+
+ // Test that non-animatable properties are ignored.
+ // Simultaneously, test that the block is still honored, and that
+ // we still override the value when two consecutive keyframes have
+ // the same value.
+ new_div("animation: kf4 ease 10s");
+ await waitForPaintsFlushed();
+ var cs = window.getComputedStyle(gDiv);
+ is(cs.display, "block",
+ "non-animatable properties should be ignored (linear, 0s)");
+ omta_is("transform", { tx: 37 }, RunningOn.Compositor,
+ "animatable properties should still apply (linear, 0s)");
+ advance_clock(1000);
+ is(cs.display, "block",
+ "non-animatable properties should be ignored (linear, 1s)");
+ omta_is("transform", { tx: 37 }, RunningOn.Compositor,
+ "animatable properties should still apply (linear, 1s)");
+ done_div();
+ new_div("animation: kf4 step-start 10s");
+ await waitForPaintsFlushed();
+ cs = window.getComputedStyle(gDiv);
+ is(cs.display, "block",
+ "non-animatable properties should be ignored (step-start, 0s)");
+ omta_is("transform", { tx: 37 }, RunningOn.Compositor,
+ "animatable properties should still apply (step-start, 0s)");
+ advance_clock(1000);
+ is(cs.display, "block",
+ "non-animatable properties should be ignored (step-start, 1s)");
+ omta_is("transform", { tx: 37 }, RunningOn.Compositor,
+ "animatable properties should still apply (step-start, 1s)");
+ done_div();
+
+ // Test cascading of the keyframes within an @keyframes rule.
+ new_div("animation: kf_cascade1 linear 10s");
+ await waitForPaintsFlushed();
+ // 0%: 30px
+ // 50%: 20px
+ // 75%: 20px
+ // 85%: 30px
+ // 85.1%: 60px
+ // 100%: 70px
+ omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 0s");
+ advance_clock(2500);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 2.5s");
+ advance_clock(2500);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 5s");
+ advance_clock(2000);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7s");
+ advance_clock(500);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7.5s");
+ advance_clock(500);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 8s");
+ advance_clock(500);
+ omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 8.5s");
+ advance_clock(10);
+ // For some reason we get an error of 0.0003 for this test only
+ omta_is_approx("transform", { tx: 60 }, 0.001, RunningOn.Compositor,
+ "kf_cascade1 at 8.51s");
+ advance_clock(745);
+ omta_is("transform", { tx: 65 }, RunningOn.Compositor,
+ "kf_cascade1 at 9.2505s");
+ done_div();
+
+ // Test cascading of the @keyframes rules themselves.
+ new_div("animation: kf_cascade2 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "last @keyframes rule with transform should win");
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "last @keyframes rule with transform should win");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.1. Timing functions for keyframes
+ * http://dev.w3.org/csswg/css3-animations/#timing-functions-for-keyframes-
+ */
+
+addAsyncAnimTest(async function() {
+ new_div("animation: kf_tf1 ease-in 10s alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 0s (test needed for flush)");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 1s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.8) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 2s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 3s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 4s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 160 }, RunningOn.Compositor,
+ "keyframe timing functions test at 5s");
+ advance_clock(1010); // avoid floating-point error
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 6s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 7s");
+ advance_clock(990);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 8s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.6) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 9s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 10s");
+ advance_clock(20000);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 30s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.6) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 31s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 32s");
+ advance_clock(990); // avoid floating-point error
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 33s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 34s");
+ advance_clock(1010);
+ omta_is("transform", { tx: 160 }, RunningOn.Compositor,
+ "keyframe timing functions test at 35s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 36s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 37s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.8) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 38s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 39s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 40s");
+ done_div();
+
+ // spot-check the same thing without alternate
+ new_div("animation: kf_tf1 ease-in 10s infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 0s (test needed for flush)");
+ advance_clock(11000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 11s");
+ advance_clock(3000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 14s");
+ advance_clock(2010); // avoid floating-point error
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 16s");
+ advance_clock(1990);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 18s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.2. The 'animation-name' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-name-property-
+ */
+
+// animation-name is reasonably well-tested up in the tests for Section
+// 2, particularly the tests that "Test that animations continue running
+// when the animation name list is changed."
+
+// Test that 'animation-name: none' stops the animation, and setting
+// it again starts a new one.
+
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 ease-in-out 10s");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "after setting animation-name to anim2");
+ advance_clock(1000);
+ omta_is_approx("opacity", gTF.ease_in_out(0.1), 0.01, RunningOn.Compositor,
+ "before changing animation-name to none");
+ gDiv.style.animationName = "none";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "after changing animation-name to none");
+ advance_clock(1000);
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "after changing animation-name to none plus 1s");
+ gDiv.style.animationName = "anim2";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "after changing animation-name to anim2");
+ advance_clock(1000);
+ omta_is_approx("opacity", gTF.ease_in_out(0.1), 0.01, RunningOn.Compositor,
+ "at 1s in animation when animation-name no longer none again");
+ gDiv.style.animationName = "none";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "after changing animation-name to none");
+ advance_clock(1000);
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "after changing animation-name to none plus 1s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.3. The 'animation-duration' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-duration-property-
+ */
+
+// FIXME: test animation-duration of 0 (quite a bit, including interaction
+// with fill-mode, count, and reversing), once I know what the right
+// behavior is.
+
+/*
+ * css3-animations: 3.4. The 'animation-timing-function' Property
+ * http://dev.w3.org/csswg/css3-animations/#animation-timing-function_tag
+ */
+
+// tested in tests for section 3.1
+
+/*
+ * css3-animations: 3.5. The 'animation-iteration-count' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-iteration-count-property-
+ */
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 ease-in 10s 0.3 forwards");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-iteration-count test 1 at 0s");
+ advance_clock(2000);
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-iteration-count test 1 at 2s");
+ advance_clock(900);
+ omta_is_approx("opacity", gTF.ease_in(0.29), 0.01, RunningOn.Compositor,
+ "animation-iteration-count test 1 at 2.9s");
+ advance_clock(100);
+ // Animation has reached the end so allow it to be cleared from the compositor
+ await waitForPaints();
+ // For transform animations we can tell whether a transform on the compositor
+ // thread was set by animation or not since there is a special flag for it.
+ //
+ // For opacity animations, however, there is no such flag so we'll get an
+ // "OMTA" opacity even when it wasn't set by animation. When we pause an
+ // opacity animation we don't worry about where it is reported to be running
+ // (main thread or compositor) so long as the result is correct, hence we
+ // check for "either" below.
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either,
+ "animation-iteration-count test 1 at 3s");
+ advance_clock(100);
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either,
+ "animation-iteration-count test 1 at 3.1s");
+ advance_clock(5000);
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either,
+ "animation-iteration-count test 1 at 8.1s");
+ done_div();
+
+ // The corresponding test in test_animations.html runs three animations in
+ // parallel but since we only have two properties that are OMTA-enabled at
+ // this time and no additive animation we split this test into two parts.
+ new_div("animation: anim2 ease-in 10s 0.3, " +
+ "anim4 ease-out 20s 1.2 alternate forwards");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-iteration-count test 2 at 0s");
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "animation-iteration-count test 3 at 0s");
+ advance_clock(2000);
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-iteration-count test 2 at 2s");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.1) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 2s");
+ advance_clock(900);
+ omta_is_approx("opacity", gTF.ease_in(0.29), 0.01, RunningOn.Compositor,
+ "animation-iteration-count test 2 at 2.9s");
+ advance_clock(200);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-iteration-count test 2 at 3.1s");
+ advance_clock(2000);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-iteration-count test 2 at 5.1s");
+ advance_clock(14700);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.99) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 19.8s");
+ advance_clock(200);
+ omta_is("transform", { ty: 100 }, RunningOn.Compositor,
+ "animation-iteration-count test 3 at 20s");
+ advance_clock(200);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.99) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 20.2s");
+ advance_clock(3600);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.81) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 23.8s");
+ advance_clock(200);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.8) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 24s");
+ advance_clock(200);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-iteration-count test 2 at 25s");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.8) }, 0.01,
+ RunningOn.MainThread,
+ "animation-iteration-count test 3 at 25s");
+ done_div();
+
+ new_div("animation: anim4 ease-in-out 5s 1.6 forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "animation-iteration-count test 4 at 0s");
+ advance_clock(2000);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 2s");
+ advance_clock(2900);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.98) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 4.9s");
+ advance_clock(200);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.02) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 5.1s");
+ advance_clock(2800);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.58) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 7.9s");
+ advance_clock(100);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 8s");
+ advance_clock(100);
+ await waitForPaints();
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01,
+ RunningOn.Either,
+ "animation-iteration-count test 4 at 8.1s");
+ advance_clock(16100);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01,
+ RunningOn.Either,
+ "animation-iteration-count test 4 at 25s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.6. The 'animation-direction' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-direction-property-
+ */
+
+// Tested in tests for sections 3.1 and 3.5.
+
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 ease-in 10s infinite");
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 0s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 0s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 0s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 0s");
+ advance_clock(2000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 2s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 2s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 2s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 2s");
+ advance_clock(5000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.7), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 7s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 7s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.7), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 7s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 7s");
+ advance_clock(5000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 12s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 12s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 12s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 12s");
+ advance_clock(10000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 22s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 22s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 22s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 22s");
+ advance_clock(30000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 52s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 52s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 52s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 52s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.7. The 'animation-play-state' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-play-state-property-
+ */
+
+addAsyncAnimTest(async function() {
+ // simple test with just one animation
+ new_div("");
+ gDiv.style.animationTimingFunction = "ease";
+ gDiv.style.animationName = "anim1";
+ gDiv.style.animationDuration = "1s";
+ gDiv.style.animationDirection = "alternate";
+ gDiv.style.animationIterationCount = "2";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "animation-play-state test 1, at 0s");
+ advance_clock(250);
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 250ms");
+ gDiv.style.animationPlayState = "paused";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 at 250ms");
+ advance_clock(250);
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 still at 500ms");
+ gDiv.style.animationPlayState = "running";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 still at 500ms");
+ advance_clock(500);
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 1000ms");
+ advance_clock(250);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "animation-play-state test 1 at 1250ms");
+ advance_clock(250);
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 1500ms");
+ gDiv.style.animationPlayState = "paused";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 at 1500ms");
+ advance_clock(2000);
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 at 3500ms");
+ advance_clock(500);
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 at 4000ms");
+ gDiv.style.animationPlayState = "";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 4000ms");
+ advance_clock(500);
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 4500ms");
+ advance_clock(250);
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "animation-play-state test 1, at 4750ms");
+ advance_clock(250);
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "animation-play-state test 1, at 5000ms");
+ done_div();
+
+ // The corresponding test in test_animations.html tests various cases of
+ // pausing individual animations in a list of three different animations
+ // but since there are only two OMTA properties we can animate
+ // independently this test is substantially simpler.
+ new_div("");
+ gDiv.style.animationTimingFunction = "ease-out, ease-in";
+ gDiv.style.animationName = "anim2, anim4";
+ gDiv.style.animationDuration = "1s, 2s";
+ gDiv.style.animationDirection = "alternate, normal";
+ gDiv.style.animationIterationCount = "4, 2";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-play-state test 2, at 0s");
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "animation-play-state test 3, at 0s");
+ advance_clock(250);
+ gDiv.style.animationPlayState = "paused, running"; // pause 1
+ await waitForPaintsFlushed();
+ // As noted with the tests for animation-iteration-count, for opacity
+ // animations we don't strictly check the finished animation is being animated
+ // on the main thread, but simply that it is producing the correct result.
+ omta_is_approx("opacity", gTF.ease_out(0.25), 0.01, RunningOn.MainThread,
+ "animation-play-state test 2 at 250ms"); // paused
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.125) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 250ms");
+ advance_clock(250);
+ omta_is_approx("opacity", gTF.ease_out(0.25), 0.01, RunningOn.MainThread,
+ "animation-play-state test 2 at 500ms"); // paused
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.25) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 500ms");
+ advance_clock(250);
+ gDiv.style.animationPlayState = "running, paused"; // unpause 1, pause 2
+ await waitForPaintsFlushed();
+ advance_clock(250);
+ omta_is_approx("opacity", gTF.ease_out(0.5), 0.01, RunningOn.Compositor,
+ "animation-play-state test 2 at 1000ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 1000ms"); // paused
+ gDiv.style.animationPlayState = "paused"; // pause all
+ await waitForPaintsFlushed();
+ advance_clock(3000);
+ omta_is_approx("opacity", gTF.ease_out(0.5), 0.01, RunningOn.MainThread,
+ "animation-play-state test 2 at 4000ms"); // paused
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 4000ms"); // paused
+ gDiv.style.animationPlayState = "running, paused"; // pause 2
+ await waitForPaintsFlushed();
+ advance_clock(850);
+ omta_is_approx("opacity", gTF.ease_out(0.65), 0.01, RunningOn.Compositor,
+ "animation-play-state test 2 at 4850ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 4850ms");
+ advance_clock(300);
+ omta_is_approx("opacity", gTF.ease_out(0.35), 0.01, RunningOn.Compositor,
+ "animation-play-state test 2 at 5150ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 5150ms");
+ advance_clock(2300);
+ omta_is_approx("opacity", gTF.ease_out(0.05), 0.01, RunningOn.Compositor,
+ "animation-play-state test 2 at 7450ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 7450ms");
+ advance_clock(100);
+ // test 2 has finished so wait for it to be removed from the
+ // compositor (otherwise it will fill forwards)
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 7550ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 7550ms");
+ gDiv.style.animationPlayState = "running"; // unpause 2
+ await waitForPaintsFlushed();
+ advance_clock(1000);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 7550ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.875) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 7550ms");
+ advance_clock(500);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 8050ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.125) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 8050ms");
+ advance_clock(1000);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 9050ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.625) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 9050ms");
+ advance_clock(500);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 9550ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.875) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 9550ms");
+ advance_clock(500);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 10050ms");
+ omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+ "animation-play-state test 3 at 10050ms");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.8. The 'animation-delay' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-delay-property-
+ */
+
+addAsyncAnimTest(async function() {
+ // test positive delay
+ new_div("animation: anim2 1s 0.5s ease-out");
+ await waitForPaintsFlushed();
+ // NOTE: getOMTAStyle() can't detect the animation is running on the
+ // compositor or not during the delay phase, since no opacity style is
+ // applied during the delay phase.
+ omta_is("opacity", 1, RunningOn.Either, "positive delay test at 0ms");
+ advance_clock(400);
+ omta_is("opacity", 1, RunningOn.Either, "positive delay test at 400ms");
+ advance_clock(100);
+ await waitForPaints();
+ omta_is("opacity", 0, RunningOn.Compositor, "positive delay test at 500ms");
+ advance_clock(100);
+ omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Compositor,
+ "positive delay test at 500ms");
+ done_div();
+
+ // test dynamic changes to delay (i.e., that we preserve the start time
+ // that's before the delay)
+ new_div("animation: anim2 1s 0.5s ease-out both");
+ await waitForPaintsFlushed();
+ // NOTE: As noted above, getOMTAStyle() can't detect the animation is running
+ // on the compositor during the delay phase.
+ omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 0ms");
+ advance_clock(400);
+ omta_is("opacity", 0, RunningOn.Either,
+ "dynamic delay delay test at 400ms (1)");
+ gDiv.style.animationDelay = "0.2s";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor,
+ "dynamic delay delay test at 400ms (2)");
+ gDiv.style.animationDelay = "0.6s";
+ await waitForPaintsFlushed();
+ advance_clock(200);
+ omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 600ms");
+ advance_clock(200);
+ await waitForPaints();
+ omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor,
+ "dynamic delay delay test at 800ms");
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "dynamic delay delay test at 1800ms (1)");
+ gDiv.style.animationDelay = "1.5s";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_out(0.3), 0.01, RunningOn.Compositor,
+ "dynamic delay delay test at 1800ms (2)");
+ gDiv.style.animationDelay = "2s";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Either,
+ "dynamic delay delay test at 1800ms (3)");
+ done_div();
+
+ // test delay and play-state interaction
+ new_div("animation: anim2 1s 0.5s ease-out");
+ await waitForPaintsFlushed();
+ // NOTE: As noted above, getOMTAStyle() can't detect the animation is running
+ // on the compositor during the delay phase.
+ omta_is("opacity", 1, RunningOn.Either,
+ "delay and play-state delay test at 0ms");
+ advance_clock(400);
+ omta_is("opacity", 1, RunningOn.Either,
+ "delay and play-state delay test at 400ms");
+ gDiv.style.animationPlayState = "paused";
+ await waitForPaintsFlushed();
+ advance_clock(100);
+ omta_is("opacity", 1, RunningOn.MainThread, // paused
+ "delay and play-state delay test at 500ms");
+ advance_clock(500);
+ omta_is("opacity", 1, RunningOn.MainThread, // paused
+ "delay and play-state delay test at 1000ms");
+ gDiv.style.animationPlayState = "running";
+ await waitForPaintsFlushed();
+ advance_clock(100);
+ await waitForPaints();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "delay and play-state delay test at 1100ms");
+ advance_clock(100);
+ omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Compositor,
+ "delay and play-state delay test at 1200ms");
+ gDiv.style.animationPlayState = "paused";
+ await waitForPaintsFlushed();
+ advance_clock(100);
+ omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Either,
+ "delay and play-state delay test at 1300ms");
+ done_div();
+
+ // test negative delay and implicit starting values
+ new_div("transform: translate(1000px)");
+ await waitForPaintsFlushed();
+ advance_clock(300);
+ gDiv.style.transform = "translate(100px)";
+ gDiv.style.animation = "kf1 1s -0.1s ease-in";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_in(0.2) },
+ 0.01, RunningOn.Compositor,
+ "delay and implicit starting values test");
+ done_div();
+
+ // test large negative delay that causes the animation to start
+ // in the fourth iteration
+ new_div("animation: anim2 1s -3.6s ease-in 5 alternate forwards");
+ listen();
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.4), 0.01, RunningOn.Compositor,
+ "large negative delay test at 0ms");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim2', elapsedTime: 3.6,
+ pseudoElement: "" }],
+ "right after start in large negative delay test");
+ advance_clock(380);
+ omta_is_approx("opacity", gTF.ease_in(0.02), 0.01, RunningOn.Compositor,
+ "large negative delay test at 380ms");
+ check_events([]);
+ advance_clock(20);
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "large negative delay test at 400ms");
+ check_events([{ type: 'animationiteration', target: gDiv,
+ animationName: 'anim2', elapsedTime: 4.0,
+ pseudoElement: "" }],
+ "right after start in large negative delay test");
+ advance_clock(800);
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "large negative delay test at 1200ms");
+ check_events([]);
+ advance_clock(200);
+ omta_is("opacity", 1, RunningOn.Either,
+ "large negative delay test at 1400ms");
+ check_events([{ type: 'animationend', target: gDiv,
+ animationName: 'anim2', elapsedTime: 5.0,
+ pseudoElement: "" }],
+ "right after start in large negative delay test");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.9. The 'animation-fill-mode' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-fill-mode-property-
+ */
+
+// animation-fill-mode is tested in the tests for section (2).
+
+/*
+ * css3-animations: 3.10. The 'animation' Shorthand Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-shorthand-property-
+ */
+
+/**
+ * Basic tests of animations on pseudo-elements
+ */
+addAsyncAnimTest(async function() {
+ new_div("");
+ listen();
+ gDiv.id = "withbefore";
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ ":before test at 0ms", "::before");
+ advance_clock(400);
+ omta_is("transform", { ty: 40 }, RunningOn.Compositor,
+ ":before test at 400ms", "::before");
+ advance_clock(800);
+ omta_is("transform", { ty: 80 }, RunningOn.Compositor,
+ ":before test at 1200ms", "::before");
+ omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+ ":before animation should not affect element");
+ advance_clock(800);
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ ":before test at 2000ms", "::before");
+ advance_clock(300);
+ omta_is("transform", { ty: 30 }, RunningOn.Compositor,
+ ":before test at 2300ms", "::before");
+ advance_clock(700);
+ check_events([ { type: "animationstart", animationName: "anim4",
+ elapsedTime: 0, pseudoElement: "::before" },
+ { type: "animationiteration", animationName: "anim4",
+ elapsedTime: 1, pseudoElement: "::before" },
+ { type: "animationiteration", animationName: "anim4",
+ elapsedTime: 2, pseudoElement: "::before" },
+ { type: "animationend", animationName: "anim4",
+ elapsedTime: 3, pseudoElement: "::before" }]);
+ done_div();
+
+ new_div("");
+ listen();
+ gDiv.id = "withafter";
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ ":after test at 0ms", "::after");
+ advance_clock(400);
+ omta_is("transform", { ty: 40 }, RunningOn.Compositor,
+ ":after test at 400ms", "::after");
+ advance_clock(800);
+ omta_is("transform", { ty: 80 }, RunningOn.Compositor,
+ ":after test at 1200ms", "::after");
+ omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+ ":before animation should not affect element");
+ advance_clock(800);
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ ":after test at 2000ms", "::after");
+ advance_clock(300);
+ omta_is("transform", { ty: 30 }, RunningOn.Compositor,
+ ":after test at 2300ms", "::after");
+ advance_clock(700);
+ check_events([ { type: "animationstart", animationName: "anim4",
+ elapsedTime: 0, pseudoElement: "::after" },
+ { type: "animationiteration", animationName: "anim4",
+ elapsedTime: 1, pseudoElement: "::after" },
+ { type: "animationiteration", animationName: "anim4",
+ elapsedTime: 2, pseudoElement: "::after" },
+ { type: "animationend", animationName: "anim4",
+ elapsedTime: 3, pseudoElement: "::after" }]);
+ done_div();
+});
+
+/**
+ * Test handling of properties that are present in only some of the
+ * keyframes.
+ */
+addAsyncAnimTest(async function() {
+ new_div("animation: multiprop 1s ease-in-out alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 10 }, RunningOn.Compositor,
+ "multiprop transform at 0ms");
+ omta_is("opacity", 0.3, RunningOn.Compositor, "multiprop opacity at 0ms");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 100ms");
+ omta_is_approx("opacity", 0.3 + 0.2 * gTF.ease(0.4), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 100ms");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.6) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 300ms");
+ omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.1), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 300ms");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 40 + 40 * gTF.ease_in_out(0.4) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 600ms");
+ omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.7), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 600ms");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 80 - 80 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 800ms");
+ omta_is_approx("opacity", 0.6 + 0.4 * gTF.ease_in(0.2), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 800ms");
+ advance_clock(400);
+ omta_is_approx("transform", { tx: 80 - 80 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 1200ms");
+ omta_is_approx("opacity", 0.6 + 0.4 * gTF.ease_in(0.2), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 1200ms");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 40 + 40 * gTF.ease_in_out(0.4) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 1400ms");
+ omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.7), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 1400ms");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.6) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 1700ms");
+ omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.1), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 1700ms");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 1900ms");
+ omta_is_approx("opacity", 0.3 + 0.2 * gTF.ease(0.4), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 1900ms");
+ done_div();
+});
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=651456 -- make
+// sure that refreshing of animations doesn't break when we get two
+// refreshes with the same timestamp.
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 1s linear");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor, "bug 651456 at 0ms");
+ advance_clock(100);
+ omta_is("opacity", 0.1, RunningOn.Compositor, "bug 651456 at 100ms (1)");
+ advance_clock(0); // still forces a refresh
+ omta_is("opacity", 0.1, RunningOn.Compositor, "bug 651456 at 100ms (2)");
+ advance_clock(100);
+ omta_is("opacity", 0.2, RunningOn.Compositor, "bug 651456 at 200ms");
+ done_div();
+});
+
+// test_animations.html includes a test that UA !important rules override
+// animations. Unfortunately, there do not appear to be any UA !important rules
+// for opacity or transform except for one targetting a pseudo-element and
+// pseudo elements are not animated on the compositor. As a result we cannot
+// currently test this behavior.
+
+// Test that author !important rules override animations, but
+// that animations override regular author rules.
+addAsyncAnimTest(async function() {
+ new_div("animation: always_fifty 1s linear infinite; " +
+ "transform: translate(200px)");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "animations override regular author rules");
+ done_div();
+ new_div("animation: always_fifty 1s linear infinite; " +
+ "transform: translate(200px) ! important;");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 200 }, RunningOn.MainThread,
+ "important author rules override animations");
+ done_div();
+});
+
+// Test interaction of animations and restyling (Bug 686656).
+// This test depends on kf3 getting its 0% and 100% values from the
+// rules below it in the cascade; we're checking that the animation
+// isn't rebuilt when the restyles happen.
+addAsyncAnimTest(async function() {
+ new_div("animation: kf3 1s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "bug 686656 test 1 at 0ms");
+ advance_clock(250);
+ gDisplay.style.color = "blue";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "bug 686656 test 1 at 250ms");
+ advance_clock(375);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "bug 686656 test 1 at 625ms");
+ advance_clock(375);
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "bug 686656 test 1 at 1000ms");
+ done_div();
+ gDisplay.style.color = "";
+});
+
+// Test interaction of animations and restyling (Bug 686656),
+// with reframing.
+// This test depends on kf3 getting its 0% and 100% values from the
+// rules below it in the cascade; we're checking that the animation
+// isn't rebuilt when the restyles happen.
+addAsyncAnimTest(async function() {
+ new_div("animation: kf3 1s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "bug 686656 test 2 at 0ms");
+ advance_clock(250);
+ gDisplay.style.overflow = "scroll";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "bug 686656 test 2 at 250ms");
+ advance_clock(375);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "bug 686656 test 2 at 625ms");
+ advance_clock(375);
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "bug 686656 test 2 at 1000ms");
+ done_div();
+ gDisplay.style.overflow = "";
+});
+
+// Test that cascading between keyframes rules is per-property rather
+// than per-rule (bug ), and that the timing function isn't taken from a
+// rule that's skipped. (Bug 738003)
+addAsyncAnimTest(async function() {
+ new_div("animation: cascade 1s linear forwards; position: relative");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "cascade test (transform) at 0ms");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "cascade test (opacity) at 0ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "cascade test (transform) at 125ms");
+ omta_is("opacity", 0.5, RunningOn.Compositor,
+ "cascade test (opacity) at 125ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "cascade test (transform) at 250ms");
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "cascade test (opacity) at 250ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "cascade test (transform) at 375ms");
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "cascade test (opacity) at 375ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "cascade test (transform) at 500ms");
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "cascade test (opacity) at 500ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "cascade test (transform) at 625ms");
+ omta_is("opacity", 0.5, RunningOn.Compositor,
+ "cascade test (opacity) at 625ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "cascade test (transform) at 750ms");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "cascade test (opacity) at 750ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "cascade test (transform) at 875ms");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "cascade test (opacity) at 875ms");
+ advance_clock(125);
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "cascade test (transform) at 1000ms");
+ omta_is("opacity", 0, RunningOn.Either,
+ "cascade test (opacity) at 1000ms");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("animation: cascade2 8s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor, "cascade2 test at 0s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor, "cascade2 test at 1s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor, "cascade2 test at 2s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor, "cascade2 test at 3s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor, "cascade2 test at 4s");
+ advance_clock(3000);
+ omta_is("transform", { tx: 75 }, RunningOn.Compositor, "cascade2 test at 7s");
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ "cascade2 test at 8s");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("animation: primitives1 2s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { }, RunningOn.Compositor, "primitives1 at 0s");
+ advance_clock(1000);
+ omta_is("transform", [ -0.707107, 0.707107, -0.707107, -0.707107, 0, 0 ],
+ RunningOn.Compositor, "primitives1 at 1s");
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("transform", [ 0, -1, 1, 0, 0, 0 ], RunningOn.MainThread,
+ "primitives1 at 0s");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("animation: important1 1s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.5, RunningOn.Compositor, "important1 test at 0s");
+ advance_clock(500);
+ omta_is("opacity", 0.65, RunningOn.Compositor, "important1 test at 0.5s");
+ advance_clock(500);
+ await waitForPaints();
+ omta_is("opacity", 0.8, RunningOn.Either, "important1 test at 1s");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("animation: important2 1s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.5, RunningOn.Compositor,
+ "important2 (opacity) test at 0s");
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "important2 (transform) test at 0s");
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "important2 (opacity) test at 1s");
+ omta_is("transform", { tx: 50 }, RunningOn.MainThread,
+ "important2 (transform) test at 1s");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ // Test that it's the length of the 'animation-name' list that's used to
+ // start animations.
+ // note: anim2 animates opacity from 0 to 1
+ // note: anim4 animates transform's y translation component from 0 to 100px
+ new_div("animation-name: anim2, anim4; " +
+ "animation-duration: 1s; " +
+ "animation-timing-function: linear; " +
+ "animation-delay: -250ms, -250ms, -750ms, -500ms;");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.25, RunningOn.Compositor,
+ "animation-name list length is the length that matters");
+ omta_is("transform", { ty: 25 }, RunningOn.Compositor,
+ "animation-name list length is the length that matters");
+ done_div();
+ new_div("animation-name: anim2, anim4, anim2; " +
+ "animation-duration: 1s; " +
+ "animation-timing-function: linear; " +
+ "animation-delay: -250ms, -250ms, -750ms, -500ms;");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "animation-name list length is the length that matters, " +
+ "and the last occurrence of a name wins");
+ omta_is("transform", { ty: 25 }, RunningOn.Compositor,
+ "animation-name list length is the length that matters");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var dyn_sheet_elt = document.createElement("style");
+ document.head.appendChild(dyn_sheet_elt);
+ var dyn_sheet = dyn_sheet_elt.sheet;
+ dyn_sheet.insertRule(
+ "@keyframes dyn1 { from { transform: translate(0px) } " +
+ "50% { transform: translate(50px) } " +
+ "to { transform: translate(100px) } }", 0);
+ dyn_sheet.insertRule(
+ "@keyframes dyn2 { from { transform: translate(100px) } " +
+ "to { transform: translate(200px) } }", 1);
+ var dyn1 = dyn_sheet.cssRules[0];
+ var dyn2 = dyn_sheet.cssRules[1];
+ new_div("animation: dyn1 1s linear");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "dynamic rule change test, initial state");
+ advance_clock(250);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor,
+ "dynamic rule change test, 250ms");
+ dyn2.name = "dyn1";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 125 }, RunningOn.Compositor,
+ "dynamic rule change test, change in @keyframes name applies");
+ dyn2.appendRule("50% { transform: translate(0px) }");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "dynamic rule change test, @keyframes appendRule");
+ // currently 0% { transform: translate(100px) }
+ var dyn2_kf1 = dyn2.cssRules[0];
+ dyn2_kf1.style.transform = "translate(-100px)";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: -50 }, RunningOn.Compositor,
+ "dynamic rule change test, keyframe style set");
+ dyn2.name = "dyn2";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor,
+ "dynamic rule change test, " +
+ "change in @keyframes name applies (second time)");
+ // currently 50% { transform: translate(50px) }
+ var dyn1_kf2 = dyn1.cssRules[1];
+ dyn1_kf2.keyText = "25%";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "dynamic rule change test, change in keyframe keyText");
+ dyn1.deleteRule("25%");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor,
+ "dynamic rule change test, @keyframes deleteRule");
+ done_div();
+ dyn_sheet_elt.remove();
+ dyn_sheet_elt = null;
+ dyn_sheet = null;
+});
+
+/*
+ * Bug 1004361 - CSS animations with short duration sometimes don't dispatch
+ * a start event
+ */
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 1s 0.1s");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(1200); // Skip past end of animation's entire active duration
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events after skipping over animation interval");
+ done_div();
+});
+
+/*
+ * Bug 1007513 - AnimationEvent.elapsedTime should be animation time
+ *
+ * There is no OMTA-version of this test since it is specific to the
+ * contents of animation events which are dispatched on the main thread.
+ *
+ * We *do* provide an OMTA-version of some tests regarding the *dispatch* of
+ * events to catch possible regressions if in future event dispatch is tied
+ * to animation throttling.
+ */
+
+/*
+ * Bug 1004365 - zero-duration animations
+ */
+
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(0, 200px); animation: anim4 0s 1s both");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(0);
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform during backwards fill of zero-duration animation");
+ advance_clock(2000); // Skip over animation
+ await waitForPaints();
+ omta_is("transform", { ty: 100 }, RunningOn.MainThread,
+ "transform during backwards fill of zero-duration animation");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration animation");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(0, 200px); animation: anim4 0s 1s both");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(0);
+ // Seek to exactly the point where the animation starts and stops
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("transform", { ty: 100 }, RunningOn.MainThread,
+ "transform during backwards fill of zero-duration animation");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of zero-duration animation");
+ // Check no further events are dispatched
+ advance_clock(0);
+ advance_clock(100);
+ check_events([]);
+ done_div();
+});
+
+// We don't need to include all the animation-direction related tests
+// found in test_animations.html. We have already asserted above that
+// these zero-length animations do in fact run on the main thread and
+// we have checked that they dispatch events correctly.
+// The actual calculation of values on the main thread is covered by
+// test_animations.html
+
+// We do however still want to test with an infinite repeat count and zero
+// duration to ensure this does not confuse the screening of OMTA animations.
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(0, 200px); " +
+ "animation: anim4 0s 1s both infinite");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(0);
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform during backwards fill of infinitely repeating " +
+ "zero-duration animation");
+ advance_clock(2000);
+ await waitForPaints();
+ omta_is("transform", { ty: 100 }, RunningOn.MainThread,
+ "transform during forwards fill of infinitely repeating " +
+ "zero-duration animation");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of infinitely repeating " +
+ "zero-duration animation");
+ done_div();
+});
+
+// Test with negative delay
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(0, 200px); " +
+ "animation: anim4 0s -1s both reverse 12.7 linear");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(0);
+ omta_is("transform", { ty: 30 }, RunningOn.MainThread,
+ "transform during forwards fill of reversed and repeated " +
+ "zero-duration animation with negative delay");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration animation " +
+ "with negative delay");
+ done_div();
+});
+
+/*
+ * Bug 1004377 - Animations with empty keyframes rule
+ */
+
+addAsyncAnimTest(async function() {
+ new_div("margin-right: 200px; animation: empty 2s 1s both");
+ listen();
+ advance_clock(0);
+ await waitForPaintsFlushed();
+ check_events([], "events during delay");
+ advance_clock(2000); // Skip to middle of animation
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events during middle of animation with empty keyframes rule");
+ advance_clock(1000); // Skip to end of animation
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationend', target: gDiv,
+ animationName: 'empty', elapsedTime: 2,
+ pseudoElement: "" }],
+ "events at end of animation with empty keyframes rule");
+ done_div();
+});
+
+// Test with a zero-duration animation and empty @keyframes rule
+addAsyncAnimTest(async function() {
+ new_div("margin-right: 200px; animation: empty 0s 1s both");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(1000);
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events at end of zero-duration animation with " +
+ "empty keyframes rule");
+ done_div();
+});
+
+// Test with a keyframes rule that becomes empty
+addAsyncAnimTest(async function() {
+ new_div("animation: nearlyempty 1s both linear");
+ await waitForPaintsFlushed();
+ advance_clock(500);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is animating on compositor");
+
+ // Update keyframes rule and check the result gets removed
+ listen();
+ findKeyframesRule("nearlyempty").deleteRule("to");
+ await waitForPaintsFlushed();
+ omta_is("transform", { }, RunningOn.MainThread,
+ "Animation with (now) empty keyframes rule is cleared " +
+ "from compositor");
+
+ // Check we still dispatch the end event however
+ advance_clock(500);
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationend', target: gDiv,
+ animationName: 'nearlyempty', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events at end of animation with newly " +
+ "empty keyframes rule");
+
+ done_div();
+});
+
+// Test when we update to point to an empty animation
+addAsyncAnimTest(async function() {
+ new_div("animation: always_fifty 1s both linear");
+ await waitForPaintsFlushed();
+ advance_clock(500);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is animating on compositor");
+
+ // Update animation name
+ listen();
+ gDiv.style.animationName = "empty";
+ await waitForPaintsFlushed();
+ omta_is("transform", { }, RunningOn.MainThread,
+ "Animation updated to use empty keyframes rule is cleared " +
+ "from compositor");
+
+ // Check events
+ advance_clock(500);
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events at start of animation updated to use " +
+ "empty keyframes rule");
+
+ done_div();
+});
+
+// Bug 996796 patch 12 - test for correct visited styles during
+// animation-only style flush.
+addAsyncAnimTest(async function() {
+ if (AppConstants.platform === "android") {
+ todo(false, "no global history on GeckoView; can't run test");
+ return;
+ }
+
+ var div1 = document.createElement("div");
+ div1.classList.add("target");
+ div1.style.height = "10px";
+ div1.style.animation = "anim2 linear 1s";
+
+ const topLocation =
+ await SpecialPowers.spawnChrome([], () => browsingContext.top.currentURI.spec);
+
+ var visitedLink = document.createElement("a");
+ visitedLink.setAttribute("href", topLocation);
+ visitedLink.classList.add("visitedLink");
+ visitedLink.classList.add("target");
+ visitedLink.style.display = "block";
+ visitedLink.style.height = "10px";
+ visitedLink.style.animation = "anim2 linear 1s";
+
+ var refVisitedLink = document.createElement("a");
+ refVisitedLink.setAttribute("href", topLocation);
+ refVisitedLink.classList.add("visitedLink");
+
+ gDisplay.appendChild(div1);
+ gDisplay.appendChild(visitedLink);
+ gDisplay.appendChild(refVisitedLink);
+
+ // Wait for visited link coloring.
+ await waitForVisitedLinkColoring(refVisitedLink,
+ "background-color", "rgb(0, 0, 255)");
+
+ // Wait for animations to start.
+ await waitForPaintsFlushed();
+
+ var bgColor = SpecialPowers.DOMWindowUtils
+ .getVisitedDependentComputedStyle(visitedLink, "", "background-color");
+ is(bgColor, "rgb(0, 0, 255)", "initial visited link background color");
+
+ advance_clock(250);
+
+ // Trigger a style change on div1 that will force us to do a miniflush,
+ // but which will not trigger a style change on visitedLink.
+ div1.style.color = "blue";
+ advance_clock(250);
+
+ bgColor = SpecialPowers.DOMWindowUtils
+ .getVisitedDependentComputedStyle(visitedLink, "", "background-color");
+
+ is(bgColor, "rgb(0, 0, 255)",
+ "visited link background color after animation-only flush");
+
+ div1.remove();
+ visitedLink.remove();
+ refVisitedLink.remove();
+});
+
+/*
+ * Bug 962594 - Turn off CSS animations when the element is display:none, or
+ * is in a display:none subtree.
+ */
+
+// Check that it works if the animated element itself becomes display:none
+addAsyncAnimTest(async function() {
+ new_div("animation: anim4 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform animation is running on compositor");
+ advance_clock(1000);
+ omta_is("transform", { ty: 10 }, RunningOn.Compositor,
+ "transform animation is at 1s on compositor");
+ gDiv.style.display = "none";
+ await waitForPaintsFlushed();
+ omta_is("transform", "none", RunningOn.MainThread,
+ "transform animation stopped on compositor");
+ advance_clock(1000);
+ omta_is("transform", "none", RunningOn.MainThread,
+ "transform animation 1s after display:none");
+ gDiv.style.display = "";
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform animation after display:block");
+ advance_clock(1000);
+ omta_is("transform", { ty: 10 }, RunningOn.Compositor,
+ "transform animation 1s after display:block");
+ done_div();
+});
+
+// Check that it works if an ancestor of the animated element becomes display:none
+addAsyncAnimTest(async function() {
+ new_div("animation: anim4 linear 10s");
+ var ancestor = document.createElement("div");
+ gDiv.parentNode.insertBefore(ancestor, gDiv);
+ ancestor.appendChild(gDiv);
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform animation is running on compositor");
+ advance_clock(1000);
+ omta_is("transform", { ty: 10 }, RunningOn.Compositor,
+ "transform animation is at 1s on compositor");
+ gDiv.style.display = "none";
+ await waitForPaintsFlushed();
+ omta_is("transform", "none", RunningOn.MainThread,
+ "transform animation stopped on compositor");
+ advance_clock(1000);
+ omta_is("transform", "none", RunningOn.MainThread,
+ "transform animation 1s after display:none");
+ gDiv.style.display = "";
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform animation after display:block");
+ advance_clock(1000);
+ omta_is("transform", { ty: 10 }, RunningOn.Compositor,
+ "transform animation 1s after display:block");
+ ancestor.parentNode.insertBefore(gDiv, ancestor);
+ ancestor.remove();
+ done_div();
+});
+
+// Bug 1125455 - Transitions should not run when animations are running.
+addAsyncAnimTest(async function() {
+ new_div("transition: opacity 2s linear; opacity: 0.8");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.8, RunningOn.MainThread,
+ "initial opacity");
+ gDiv.style.opacity = "0.2";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.8, RunningOn.Compositor,
+ "opacity transition at 0s");
+ advance_clock(500);
+ omta_is("opacity", 0.65, RunningOn.Compositor,
+ "opacity transition at 0.5s");
+ gDiv.style.animation = "opacitymid 2s linear";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.2, RunningOn.Compositor,
+ "opacity animation overriding transition at 0s");
+ advance_clock(500);
+ omta_is("opacity", 0.35, RunningOn.Compositor,
+ "opacity animation overriding transition at 0.5s");
+ done_div();
+});
+
+// Bug 1320474 - keyframes-name may be a string, allows names that would
+// otherwise be excluded.
+// These tests don't need to be duplicated here as they relate purely to
+// the animation setup which is common to both main-thread and compositor
+// animations.
+
+// Bug 847287 - Test that changes of when an animation is dynamically
+// overridden work correctly.
+addAsyncAnimTest(async function() {
+ // anim2 and anim3 are both animations from opacity 0 to 1
+
+ new_div("animation: anim2 1s linear forwards; opacity: 0.5 ! important");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation at start (0s)");
+ advance_clock(750);
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation while running (750ms)");
+ advance_clock(1000);
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation while filling (1750ms)");
+ done_div();
+
+ new_div("animation: anim2 1s linear; opacity: 0.5 ! important");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation at start (0s)");
+ advance_clock(750);
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation while running (750ms)");
+ advance_clock(1000);
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation after complete (1750ms)");
+ done_div();
+
+ // One animation overriding another, and then not.
+ new_div("animation: anim2 1s linear, anim3 500ms linear reverse");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "anim3 overriding anim2 at start (0s)");
+ advance_clock(400);
+ omta_is("opacity", 0.2, RunningOn.Compositor,
+ "anim3 overriding anim2 at 400ms");
+ advance_clock(200);
+ // Wait for paints because we're resending animations to the
+ // compositor via an UpdateOpacityLayer hint, which does the resending
+ // via painting.
+ await waitForPaints();
+ omta_is("opacity", 0.6, RunningOn.Compositor,
+ "anim2 at 600ms");
+ done_div();
+
+ // One animation overriding another, and then not, but without a
+ // restyle when the overriding one ends.
+ new_div("animation: anim2 1s steps(8, end)");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim2 at start (0s)");
+ advance_clock(300);
+ omta_is("opacity", 0.25, RunningOn.Compositor,
+ "anim2 at 300ms");
+ gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim3 overriding anim2 at 300ms");
+ advance_clock(475);
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "anim3 the same as anim2 at 775ms");
+ advance_clock(50);
+ // Wait for paints because we're resending animations to the
+ // compositor via an UpdateOpacityLayer hint, which does the resending
+ // via painting.
+ await waitForPaints();
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "anim2 at 825ms");
+ advance_clock(75);
+ omta_is("opacity", 0.875, RunningOn.Compositor,
+ "anim2 at 900ms");
+ done_div();
+
+ // Exactly the same as the previous test, except with an extra
+ // waitForPaintsFlushed(), since that extra one exposes other bugs.
+ new_div("animation: anim2 1s steps(8, end)");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim2 at start (0s)");
+ advance_clock(300);
+ omta_is("opacity", 0.25, RunningOn.Compositor,
+ "anim2 at 300ms");
+ gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim3 overriding anim2 at 300ms");
+ advance_clock(475);
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "anim3 the same as anim2 at 775ms");
+ // Extra waitForPaintsFlushed to expose bugs.
+ await waitForPaintsFlushed();
+ advance_clock(50);
+ // Wait for paints because we're resending animations to the
+ // compositor via an UpdateOpacityLayer hint, which does the resending
+ // via painting.
+ await waitForPaints();
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "anim2 at 825ms");
+ advance_clock(75);
+ omta_is("opacity", 0.875, RunningOn.Compositor,
+ "anim2 at 900ms");
+ done_div();
+
+ // Test that an interpolation that produces transform: none doesn't
+ // crash.
+ new_div("animation: transformnone 1s linear");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "transformnone animation at 0ms");
+ advance_clock(500);
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "transformnone animation at 500ms, interpolating none values");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(100px); transition: transform 10s 5s linear");
+ await waitForPaintsFlushed();
+ gDiv.style.transform = "translate(200px)";
+ await waitForPaintsFlushed();
+ // NOTE: As noted above, getOMTAStyle() can't detect the animation is running
+ // on the compositor during the delay phase.
+ omta_is("transform", { tx: 100 }, RunningOn.Either,
+ "transition runs on compositor thread during delay");
+ // At the *very* start of the transition the start value of the transition
+ // will match the underlying transform value. Various optimizations in
+ // RestyleManager may recognize this a "no change" and filter out the
+ // transition meaning that the animation doesn't get added to the compositor
+ // thread until the first time the value changes. As a result, we fast-forward
+ // a little past the beginning and then wait for the animation to be sent
+ // to the compositor.
+ advance_clock(5100);
+ await waitForPaints();
+ omta_is("transform", { tx: 101 }, RunningOn.Compositor,
+ "transition runs on compositor at start of active interval");
+ advance_clock(4900);
+ omta_is("transform", { tx: 150 }, RunningOn.Compositor,
+ "transition runs on compositor at during active interval");
+ advance_clock(5000);
+ // Currently the compositor will apply a forwards fill until it gets told by
+ // the main thread to clear the animation. As a result we should wait for
+ // paints before checking that the animated value does *not* appear on the
+ // compositor thread.
+ await waitForPaints();
+ omta_is("transform", { tx: 200 }, RunningOn.MainThread,
+ "transition runs on main thread at end of active interval");
+
+ done_div();
+});
+
+// Normal background-color animation.
+addAsyncAnimTest(async function() {
+ new_div("background-color: rgb(255, 0, 0); " +
+ "transition: background-color 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.backgroundColor = "rgb(0, 255, 0)";
+ await waitForPaintsFlushed();
+
+ omta_is("background-color", "rgb(255, 0, 0)", RunningOn.Compositor,
+ "background-color transition runs on compositor thread");
+
+ advance_clock(5000);
+ omta_is("background-color", "rgb(128, 128, 0)", RunningOn.Compositor,
+ "background-color on compositor at 5s");
+
+ done_div();
+});
+
+// background-color animation with currentColor.
+addAsyncAnimTest(async function() {
+ new_div("color: rgb(255, 0, 0); " +
+ "background-color: currentColor; " +
+ "transition: background-color 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.backgroundColor = "rgb(0, 255, 0)";
+ await waitForPaintsFlushed();
+
+ omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor,
+ "background-color transition starting with current-color runs on " +
+ "compositor thread");
+
+ advance_clock(5000);
+ omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor,
+ "background-color on compositor at 5s");
+
+ done_div();
+});
+
+// Tests that a background-color animation from inherited currentColor to
+// a normal color on the compositor is updated when the parent color is
+// changed.
+addAsyncAnimTest(async function() {
+ new_div("");
+ const parent = document.createElement("div");
+ gDiv.parentNode.insertBefore(parent, gDiv);
+ parent.style.color = "rgb(255, 0, 0)";
+ parent.appendChild(gDiv);
+
+ gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000);
+
+ await waitForPaintsFlushed();
+
+ omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor,
+ "background-color animation starting with current-color runs on " +
+ "compositor thread");
+
+ advance_clock(500);
+
+ omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor,
+ "background-color on compositor at 5s");
+
+ // Change the parent's color in the middle of the animation.
+ parent.style.color = "rgb(0, 0, 255)";
+ await waitForPaintsFlushed();
+
+ omta_todo_is("background-color", "rgb(0, 128, 128)", RunningOn.TodoCompositor,
+ "background-color on compositor is reflected by the parent's " +
+ "color change");
+
+ done_div();
+ parent.remove();
+});
+
+// Tests that a background-color animation from currentColor to a normal color
+// on <a> element is updated when the link is visited.
+addAsyncAnimTest(async function() {
+ if (AppConstants.platform === "android") {
+ todo(false, "no global history on GeckoView; can't run test");
+ return;
+ }
+
+ [ gDiv ] = new_element("a", "display: block");
+ gDiv.setAttribute("href", "not-exist.html");
+ gDiv.classList.add("visited");
+
+ const extraStyle = document.createElement('style');
+ document.head.appendChild(extraStyle);
+ extraStyle.sheet.insertRule(".visited:visited { color: rgb(0, 0, 255); }", 0);
+ extraStyle.sheet.insertRule(".visited:link { color: rgb(255, 0, 0); }", 1);
+
+ gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000);
+ await waitForPaintsFlushed();
+
+ omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor,
+ "background-color animation starting with current-color runs on " +
+ "compositor thread");
+
+ advance_clock(500);
+
+ omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor,
+ "background-color on compositor at 5s");
+
+ const topLocation =
+ await SpecialPowers.spawnChrome([], () => browsingContext.top.currentURI.spec);
+ gDiv.setAttribute("href", topLocation);
+ await waitForVisitedLinkColoring(gDiv, "color", "rgb(0, 0, 255)");
+ await waitForPaintsFlushed();
+
+ // `omta_is` checks that the result on the compositor equals to the value by
+ // getComputedValue() but getComputedValue lies for visited link values so
+ // we use getOMTAStyle directly instead.
+ todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "background-color"),
+ "rgb(0, 128, 128)",
+ "background-color on <a> element after the link is visited");
+
+ extraStyle.remove();
+ done_element();
+ gDiv = null;
+});
+
+// Normal translate animation.
+addAsyncAnimTest(async function() {
+ new_div("translate: 100px; " +
+ "transition: translate 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.translate = "200px";
+ await waitForPaintsFlushed();
+
+ omta_is("translate", { compositorValue: { tx: 100 }, computed: "100px" },
+ RunningOn.Compositor,
+ "translate transition runs on compositor thread");
+
+ advance_clock(5000);
+ omta_is("translate", { compositorValue: { tx: 150 }, computed: "150px" },
+ RunningOn.Compositor, "translate on compositor at 5s");
+
+ done_div();
+});
+
+// Normal rotate animation.
+addAsyncAnimTest(async function() {
+ new_div("rotate: 0deg; " +
+ "transition: rotate 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.rotate = "90deg";
+ await waitForPaintsFlushed();
+
+ omta_is("rotate", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "0deg"},
+ RunningOn.Compositor, "rotate transition runs on compositor thread");
+
+ advance_clock(5000);
+ omta_is("rotate",
+ { compositorValue: [ Math.cos(Math.PI / 4), Math.sin(Math.PI / 4),
+ -Math.sin(Math.PI / 4), Math.cos(Math.PI / 4),
+ 0, 0 ],
+ computed: "45deg" }, RunningOn.Compositor,
+ "rotate on compositor at 5s");
+
+ done_div();
+});
+
+// Normal scale animation.
+addAsyncAnimTest(async function() {
+ new_div("scale: 1 1; " +
+ "transition: scale 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.scale = "2 2";
+ await waitForPaintsFlushed();
+
+ omta_is("scale", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "1" },
+ RunningOn.Compositor, "scale transition runs on compositor thread");
+
+ advance_clock(5000);
+ omta_is("scale",
+ { compositorValue: [ 1.5, 0, 0, 1.5, 0, 0 ], computed: "1.5" },
+ RunningOn.Compositor, "scale on compositor at 5s");
+
+ done_div();
+});
+
+// Normal multiple transform-like properties animation.
+addAsyncAnimTest(async function() {
+ new_div("translate: 100px; " +
+ "scale: 1 1; " +
+ "transform: translate(200px); " +
+ "transition: all 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.translate = "200px";
+ gDiv.style.scale = "2 2";
+ gDiv.style.transform = "translate(100px)";
+ await waitForPaintsFlushed();
+
+ omta_is("transform", { compositorValue: { tx: 300 },
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("transform",
+ // The order is: translate, scale, transform.
+ // So the translate() in transform should be multiplied by 1.5.
+ { compositorValue: [ 1.5, 0, 0, 1.5, (150 + 150*1.5), 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties on compositor at 5s");
+
+ done_div();
+});
+
+// Multiple transform-like properties animation. The non-animating properties
+// shouldn't be overridden by animating ones.
+addAsyncAnimTest(async function() {
+ new_div("translate: 100px; " +
+ "scale: 1 1; " +
+ "transform: translate(200px); " +
+ "transition: all 10s linear");
+ await waitForPaintsFlushed();
+
+ // No transition on transform property.
+ gDiv.style.translate = "200px";
+ gDiv.style.scale = "2 2";
+ await waitForPaintsFlushed();
+
+ omta_is("transform", { compositorValue: { tx: 300 },
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("transform",
+ // The order is: translate, scale, transform.
+ // So the translate() in transform should be multiplied by 1.5.
+ { compositorValue: [ 1.5, 0, 0, 1.5, (150 + 200*1.5), 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties on compositor at 5s");
+
+ done_div();
+});
+
+// Multiple transform-like properties animation with delay. The delayed
+// animating properties shouldn't be overridden.
+//
+// Note:
+// In delay phase, the SampleResult should be None, even though there is
+// an non-animating property which is also sent to the compositor.
+// If the SampleResult is Sampled in this case, we may get an incorrect result
+// ("scale" would be "1" because the final matrix is calculated by a default
+// scale value which overrides the scale css style).
+// That's why we shouldn't take non-animating properties into account on
+// SampleResult.
+addAsyncAnimTest(async function() {
+ new_div("translate: 100px; " +
+ "scale: 1.25 1.25; " +
+ "animation: kf_scale 10s 2.5s linear");
+ await waitForPaintsFlushed();
+
+ // All transform-like properties use DisplayItemType::TYPE_TRANSFORM,
+ // so using "transform" is enough.
+ var compositorStr =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "",
+ "transform-like properties should not run on the compositor at 0s " +
+ "(in delay phase)");
+ var computedStr = window.getComputedStyle(gDiv).translate;
+ ok(computedStr === "100px",
+ "The computed value of translate property should be equal to 100px " +
+ "in delay phase, got " + computedStr);
+ computedStr = window.getComputedStyle(gDiv).scale;
+ ok(computedStr === "1.25",
+ "The computed value of scale property should be equal to 1.25 " +
+ "in delay phase, got " + computedStr);
+
+ advance_clock(2500);
+
+ omta_is("transform", { compositorValue: [ 1.25, 0, 0, 1.25, 100, 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties on compositor at 2.5s");
+
+ advance_clock(5000);
+
+ omta_is("transform",
+ { compositorValue: [ 1.75, 0, 0, 1.75, 100, 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties on compositor at 7.5s");
+
+ done_div();
+});
+
+// Normal offset-path animation with path().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: path('M50 50L100 100'); " +
+ "offset-distance: 50%; " +
+ "offset-rotate: 0deg; " +
+ "transition: offset-path 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetPath = "path('M50 50L200 200')";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-path",
+ { compositorValue: { tx: 25, ty: 25 },
+ computed: 'path("M 50 50 L 100 100")' },
+ RunningOn.Compositor,
+ "offset-path transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-path",
+ { compositorValue: { tx: 50, ty: 50 },
+ computed: 'path("M 50 50 L 150 150")' },
+ RunningOn.Compositor,
+ "offset-path on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-path animation with ray().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: ray(90deg); " +
+ "offset-distance: 0%; " +
+ "offset-position: auto; " +
+ "transition: offset-path 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetPath = "ray(180deg)";
+ await waitForPaintsFlushed();
+
+ // At 0%, it's ray(90deg), so there is no rotation and only movement because
+ // the default offset-anchor is 50%.
+ omta_is("offset-path",
+ { compositorValue: { tx: -50, ty: -50 },
+ computed: 'ray(90deg)' },
+ RunningOn.Compositor,
+ "offset-path transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ // At 50%, ray() is 135deg. so the matrix is kind of rotate -45 deg:
+ // [cos(-1/4 * pi), -sin(-1/4 * pi), sin(-1/4 * pi), cos(-1/4 * pi), -50, -50]
+ // Note: the movement of (-50 -50) is from the default offset-anchor.
+ omta_is("offset-path",
+ {
+ compositorValue: [
+ Math.cos(-Math.PI * 1/4), -Math.sin(-Math.PI * 1/4),
+ Math.sin(-Math.PI * 1/4), Math.cos(-Math.PI * 1/4),
+ -50, -50
+ ],
+ computed: 'ray(135deg)'
+ },
+ RunningOn.Compositor,
+ "offset-path on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-path animation with polygon().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: polygon(0px 0px, 100px 0px, 50px 100px); " +
+ "offset-distance: 0%; " +
+ "offset-rotate: 0deg; " +
+ "offset-anchor: left top; " +
+ "transition: offset-path 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetPath = "polygon(50px 0px, 100px 0px, 50px 100px)";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-path",
+ { compositorValue: { tx: 0 },
+ computed: 'polygon(0px 0px, 100px 0px, 50px 100px)' },
+ RunningOn.Compositor,
+ "offset-path transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-path",
+ { compositorValue: { tx: 25 },
+ computed: 'polygon(25px 0px, 100px 0px, 50px 100px)' },
+ RunningOn.Compositor,
+ "offset-path on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-distance animation with path().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: path('M50 50v100'); " +
+ "offset-distance: 0%; " +
+ "offset-rotate: 0deg; " +
+ "transition: offset-distance 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetDistance = "100%";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-distance",
+ { compositorValue: { ty: 0 }, computed: '0%' },
+ RunningOn.Compositor,
+ "offset-distance transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-distance",
+ { compositorValue: { ty: 50 }, computed: '50%' },
+ RunningOn.Compositor,
+ "offset-distance on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-distance animation with polygon().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: polygon(0px 0px, 100px 0px, 100px 50px, 0px 50px); " +
+ "offset-distance: 0%; " +
+ "offset-rotate: 0deg; " +
+ "offset-anchor: left top; " +
+ "transition: offset-distance 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetDistance = "100%";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-distance",
+ { compositorValue: { tx: 0 }, computed: '0%' },
+ RunningOn.Compositor,
+ "offset-distance transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-distance",
+ { compositorValue: { tx: 100, ty: 50 }, computed: '50%' },
+ RunningOn.Compositor,
+ "offset-distance on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-rotate animation.
+addAsyncAnimTest(async function() {
+ new_div("offset-path: path('M50 50v100'); " +
+ "offset-rotate: auto; " +
+ "transition: offset-rotate 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetRotate = "auto 90deg";
+ await waitForPaintsFlushed();
+
+ // The direction vector is 90deg (because the path go from top to bottom), and
+ // offset-rotate is auto 0deg, so the entire rotation is 90deg.
+ // The matrix is [cos(pi/2), sin(pi/2), -sin(pi/2), cos(pi/2), 0, 0].
+ omta_is("offset-rotate",
+ { compositorValue: [ 0, 1, -1, 0, 0, 0 ], computed: 'auto' },
+ RunningOn.Compositor,
+ "offset-rotate transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ // At 50%, offset-rotate is auto 45deg, so the entire rotation is 135deg
+ // (= 90deg + 45deg = pi * 3/4), so the matrix is
+ // [cos(pi * 3/4), sin(pi * 3/4), -sin(pi * 3/4), cos(pi * 3/4), 0, 0]
+ omta_is("offset-rotate",
+ {
+ compositorValue: [
+ Math.cos(Math.PI * 3/4), Math.sin(Math.PI * 3/4),
+ -Math.sin(Math.PI * 3/4), Math.cos(Math.PI * 3/4),
+ 0, 0
+ ],
+ computed: 'auto 45deg',
+ },
+ RunningOn.Compositor,
+ "offset-rotate on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-anchor animation.
+addAsyncAnimTest(async function() {
+ new_div("offset-path: path('M50 50v100'); " +
+ "offset-rotate: 0deg; " +
+ "offset-anchor: 0% 0%; " +
+ "transition: offset-anchor 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetAnchor = "100% 100%";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-anchor",
+ { compositorValue: { tx: 50, ty: 50 }, computed: '0% 0%' },
+ RunningOn.Compositor,
+ "offset-anchor transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-anchor",
+ { compositorValue: { tx: 0, ty: 0 }, computed: '50% 50%' },
+ RunningOn.Compositor,
+ "offset-anchor on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-position animation.
+addAsyncAnimTest(async function() {
+ new_div("offset-path: ray(0deg sides); " +
+ "offset-rotate: 0deg; " +
+ "offset-anchor: 0% 0%; " +
+ "offset-position: 0px 0px; " +
+ "transition: offset-position 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetPosition = "100px 100px";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-position",
+ { compositorValue: { tx: 0, ty: 0 }, computed: '0px 0px' },
+ RunningOn.Compositor,
+ "offset-position transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-position",
+ { compositorValue: { tx: 50, ty: 50 }, computed: '50px 50px' },
+ RunningOn.Compositor,
+ "offset-position on compositor at 5s");
+
+ done_div();
+});
+
+// Normal multiple transform-like properties animation (including motion-path).
+addAsyncAnimTest(async function() {
+ new_div("translate: 0px; " +
+ "offset-path: path('M50 50v100');" +
+ "offset-distance: 0%;" +
+ "transform: translateX(0px); " +
+ "transition: all 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.translate = "0px 100px";
+ gDiv.style.transform = "translateX(-100px)";
+ gDiv.style.offsetDistance = "100%";
+ await waitForPaintsFlushed();
+
+ omta_is("transform", { compositorValue: [ 0, 1, -1, 0, 0, 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("transform", { compositorValue: [ 0, 1, -1, 0, 0, 50 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties transition runs on compositor thread");
+
+ done_div();
+});
+
+</script>
+</html>
diff --git a/layout/style/test/test_animations_omta_scroll.html b/layout/style/test/test_animations_omta_scroll.html
new file mode 100644
index 0000000000..324264c3e7
--- /dev/null
+++ b/layout/style/test/test_animations_omta_scroll.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test for css-animations running on the compositor thread with
+ scroll-timeline</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1676780">Scroll-driven
+ animations generated by CSS</a>
+<pre id="test"></pre>
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+// Open a new window to make sure we test this with scrolling attribute.
+window.open('file_animations_omta_scroll.html');
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_omta_scroll_rtl.html b/layout/style/test/test_animations_omta_scroll_rtl.html
new file mode 100644
index 0000000000..73339211c5
--- /dev/null
+++ b/layout/style/test/test_animations_omta_scroll_rtl.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test for css-animations running on the compositor thread with
+ scroll-timeline with right to left writing mode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1676780">Scroll-driven
+ animations generated by CSS</a>
+<pre id="test"></pre>
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+// Open a new window to make sure we test this with scrolling attribute.
+window.open('file_animations_omta_scroll_rtl.html');
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_omta_start.html b/layout/style/test/test_animations_omta_start.html
new file mode 100644
index 0000000000..92423bf67d
--- /dev/null
+++ b/layout/style/test/test_animations_omta_start.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=975261
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test OMTA animations start correctly (Bug 975261)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ @keyframes anim-opacity {
+ 0% { opacity: 0.5 }
+ 100% { opacity: 0.5 }
+ }
+ @keyframes anim-opacity-2 {
+ 0% { opacity: 0.0 }
+ 100% { opacity: 1.0 }
+ }
+ @keyframes anim-transform {
+ 0% { transform: translate(50px); }
+ 100% { transform: translate(50px); }
+ }
+ @keyframes anim-transform-2 {
+ 0% { transform: translate(0px); }
+ 100% { transform: translate(100px); }
+ }
+ .target {
+ /* These two lines are needed so that an opacity/transform layer
+ * already exists when the animation is applied. */
+ opacity: 0.99;
+ transform: translate(99px);
+
+ /* Element needs geometry in order to be animated on the
+ * compositor. */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=975261">Mozilla Bug
+ 975261</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+var gUtils = SpecialPowers.DOMWindowUtils;
+
+SimpleTest.waitForExplicitFinish();
+runOMTATest(testDelay, SimpleTest.finish);
+
+function newTarget() {
+ var target = document.createElement("div");
+ target.classList.add("target");
+ document.getElementById("display").appendChild(target);
+ return target;
+}
+
+function testDelay() {
+ gUtils.advanceTimeAndRefresh(0);
+
+ var target = newTarget();
+ target.setAttribute("style", "animation: 10s 10s anim-opacity linear");
+ gUtils.advanceTimeAndRefresh(0);
+
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(10100);
+ waitForAllPaints(function() {
+ var opacity = gUtils.getOMTAStyle(target, "opacity");
+ is(opacity, "0.5",
+ "opacity is set on compositor thread after delayed start");
+ target.removeAttribute("style");
+ gUtils.restoreNormalRefresh();
+ testTransform();
+ });
+ });
+}
+
+function testTransform() {
+ gUtils.advanceTimeAndRefresh(0);
+
+ var target = newTarget();
+ target.setAttribute("style", "animation: 10s 10s anim-transform linear");
+ gUtils.advanceTimeAndRefresh(0);
+
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(10100);
+ waitForAllPaints(function() {
+ var transform = gUtils.getOMTAStyle(target, "transform");
+ ok(matricesRoughlyEqual(convertTo3dMatrix(transform),
+ convertTo3dMatrix("matrix(1, 0, 0, 1, 50, 0)")),
+ "transform is set on compositor thread after delayed start");
+ target.remove();
+ gUtils.restoreNormalRefresh();
+ testBackwardsFill();
+ });
+ });
+}
+
+function testBackwardsFill() {
+ gUtils.advanceTimeAndRefresh(0);
+
+ var target = newTarget();
+ target.setAttribute("style",
+ "transform: translate(30px); " +
+ "animation: 10s 10s anim-transform-2 linear backwards");
+
+ gUtils.advanceTimeAndRefresh(0);
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(10000);
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(100);
+ waitForAllPaints(function() {
+ var transform = gUtils.getOMTAStyle(target, "transform");
+ ok(matricesRoughlyEqual(convertTo3dMatrix(transform),
+ convertTo3dMatrix("matrix(1, 0, 0, 1, 1, 0)")),
+ "transform is set on compositor thread after delayed start " +
+ "with backwards fill");
+ target.remove();
+ gUtils.restoreNormalRefresh();
+ testTransitionTakingOver();
+ });
+ });
+ });
+}
+
+function testTransitionTakingOver() {
+ gUtils.advanceTimeAndRefresh(0);
+
+ var parent = newTarget();
+ var child = newTarget();
+ parent.appendChild(child);
+ parent.style.opacity = "0.0";
+ parent.style.animation = "10s anim-opacity-2 linear";
+ child.style.opacity = "inherit";
+ child.style.transition = "10s opacity linear";
+
+ var childCS = getComputedStyle(child, "");
+
+ gUtils.advanceTimeAndRefresh(0);
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(4000);
+ waitForAllPaints(function() {
+ child.style.opacity = "1.0";
+ var opacity = gUtils.getOMTAStyle(child, "opacity");
+ // FIXME Bug 1039799 (or lower priority followup): Animations
+ // inherited from an animating parent element don't get shipped to
+ // the compositor thread.
+ todo_is(opacity, "0.4",
+ "transition that interrupted animation is correct");
+
+ // Trigger to start the transition, without this the transition will
+ // be pending in advanceTimeAndRefresh(0) so the transition will not
+ // be sent to the compositor until we call advanceTimeAndRefresh with
+ // a positive time value.
+ getComputedStyle(child).opacity;
+ gUtils.advanceTimeAndRefresh(0);
+ waitForAllPaints(function() {
+ opacity = gUtils.getOMTAStyle(child, "opacity");
+ is(opacity, "0.4",
+ "transition that interrupted animation is correct");
+ gUtils.advanceTimeAndRefresh(5000);
+ waitForAllPaints(function() {
+ opacity = gUtils.getOMTAStyle(child, "opacity");
+ is(opacity, "0.7",
+ "transition that interrupted animation is correct");
+ is(childCS.opacity, "0.7",
+ "transition that interrupted animation is correct");
+ parent.remove();
+ gUtils.restoreNormalRefresh();
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_pausing.html b/layout/style/test/test_animations_pausing.html
new file mode 100644
index 0000000000..8aba46b5e8
--- /dev/null
+++ b/layout/style/test/test_animations_pausing.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for Animation.play() and Animation.pause() on compositor animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 2 linear alternate");
+
+ // Animation is initially running on compositor
+ await waitForPaintsFlushed();
+ advance_clock(1000);
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "Animation is initally animating on compositor");
+
+ // pause() means it is no longer on the compositor
+ var animation = div.getAnimations()[0];
+ animation.pause();
+ // pause() should set up the changes to animations for the next layer
+ // transaction but it won't schedule a paint immediately so we need to tick
+ // the refresh driver before we can wait on the next paint.
+ advance_clock(0);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 10 }, RunningOn.MainThread,
+ "After pausing, animation is removed from compositor");
+
+ // Animation remains paused
+ advance_clock(1000);
+ omta_is(div, "transform", { tx: 10 }, RunningOn.MainThread,
+ "Animation remains paused");
+
+ // play() puts the animation back on the compositor
+ animation.play();
+ // As with pause(), play() will set up pending animations for the next layer
+ // transaction but won't schedule a paint so we need to tick the refresh
+ // driver before waiting on the next paint.
+ advance_clock(0);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "After playing, animation is sent to compositor");
+
+ // Where it continues to run
+ advance_clock(1000);
+ omta_is(div, "transform", { tx: 20 }, RunningOn.Compositor,
+ "Animation continues playing on compositor");
+
+ done_div();
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_playbackrate.html b/layout/style/test/test_animations_playbackrate.html
new file mode 100644
index 0000000000..8ecfdb6f28
--- /dev/null
+++ b/layout/style/test/test_animations_playbackrate.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for Animation.playbackRate on compositor animations</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards");
+ var animation = div.getAnimations()[0];
+ animation.playbackRate = 10;
+
+ advance_clock(300);
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 30 }, RunningOn.Compositor,
+ "at 300ms");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards");
+ var animation = div.getAnimations()[0];
+ advance_clock(300);
+ await waitForPaints();
+
+ animation.playbackRate = 0;
+
+ await waitForPaintsFlushed();
+
+ omta_is(div, "transform", { tx: 3 }, RunningOn.MainThread,
+ "animation with zero playback rate should stay in the " +
+ "same position and be running on the main thread");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 1s");
+ var animation = div.getAnimations()[0];
+ animation.playbackRate = 0.5;
+
+ advance_clock(2000); // 1s * (1 / playbackRate)
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.Compositor,
+ "animation with positive delay and playbackRate > 1 should " +
+ "start from the initial position at the beginning of the " +
+ "active duration");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 1s");
+ var animation = div.getAnimations()[0];
+ animation.playbackRate = 2.0;
+
+ advance_clock(500); // 1s * (1 / playbackRate)
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.Compositor,
+ "animation with positive delay and playbackRate < 1 should " +
+ "start from the initial position at the beginning of the " +
+ "active duration");
+ done_div();
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_reverse.html b/layout/style/test/test_animations_reverse.html
new file mode 100644
index 0000000000..93ff8c37a9
--- /dev/null
+++ b/layout/style/test/test_animations_reverse.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test for Animation.reverse() on compositor animations</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s linear");
+ const animation = div.getAnimations()[0];
+
+ // Animation is initially running on compositor
+ await waitForPaintsFlushed();
+ advance_clock(5000);
+ omta_is(div, 'transform', { tx: 50 }, RunningOn.Compositor,
+ 'Animation is initally animating on compositor');
+
+ // Reverse animation
+ animation.reverse();
+
+ // At this point the playbackRate has changed but the transform will
+ // not have changed.
+ await waitForPaints();
+ omta_is(div, 'transform', { tx: 50 }, RunningOn.Compositor,
+ 'Animation value does not change after being reversed');
+
+ // However, we should still have sent a layer transaction to update the
+ // playbackRate on the compositor so that on the next tick we advance
+ // in the right direction.
+ advance_clock(1000);
+ omta_is(div, 'transform', { tx: 40 }, RunningOn.Compositor,
+ 'Animation proceeds in reverse direction');
+
+ done_div();
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_styles_on_event.html b/layout/style/test/test_animations_styles_on_event.html
new file mode 100644
index 0000000000..0e96711530
--- /dev/null
+++ b/layout/style/test/test_animations_styles_on_event.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test that mouse movement immediately after finish() should involve
+ restyling for finished state (Bug 1228137)
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript"
+ src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translateX(0px) }
+ 100% { transform: translateX(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function () {
+ // To avoid the effect that newly created element's styles are
+ // not updated immediately, we need to add an element without
+ // animation properties first.
+ var [ div ] = new_div("");
+ div.setAttribute("id", "bug1228137");
+
+ waitForPaints().then(function() {
+ var initialRect = div.getBoundingClientRect();
+
+ // Now we can set animation properties.
+ div.style.animation = "anim 100s linear forwards";
+
+ div.addEventListener("mousemove", function(event) {
+ is(event.target.id, "bug1228137",
+ "The target of the animation should receive the mouse move event " +
+ "on the position of the animation's effect end.");
+ done_div();
+ SimpleTest.finish();
+ });
+
+ var animation = div.getAnimations()[0];
+ animation.finish();
+
+ // Mouse over where the animation is positioned at finished state.
+ // We can't use synthesizeMouse here since synthesizeMouse causes
+ // layout flush. We need to check the position without explicit flushes.
+ synthesizeMouseAtPoint(initialRect.left + initialRect.width / 2 + 100,
+ initialRect.top + initialRect.height / 2,
+ { type: "mousemove" }, window);
+ });
+};
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_variable_changes.html b/layout/style/test/test_animations_variable_changes.html
new file mode 100644
index 0000000000..ac254e1136
--- /dev/null
+++ b/layout/style/test/test_animations_variable_changes.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Tests that animations respond to changes to variables</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<style>
+:root {
+ --width: 100px;
+}
+.wider {
+ --width: 200px;
+}
+@keyframes widen {
+ to { margin-left: var(--width) }
+}
+</style>
+<body>
+<div id="log"></div>
+<script>
+
+test(() => {
+ const div = document.createElement('div');
+ document.body.append(div);
+
+ div.style.animation = 'widen step-start 100s';
+ assert_equals(getComputedStyle(div).marginLeft, '100px',
+ 'Animation value before updating CSS variable');
+
+ div.classList.add('wider');
+
+ assert_equals(getComputedStyle(div).marginLeft, '200px',
+ 'Animation value after updating CSS variable');
+
+ div.remove();
+}, 'Animation reflects changes to custom properties');
+
+test(() => {
+ const parent = document.createElement('div');
+ const child = document.createElement('div');
+ parent.append(child);
+ document.body.append(parent);
+
+ child.style.animation = 'widen step-start 100s';
+ assert_equals(getComputedStyle(child).marginLeft, '100px',
+ 'Animation value before updating CSS variable');
+
+ parent.classList.add('wider');
+
+ assert_equals(getComputedStyle(child).marginLeft, '200px',
+ 'Animation value after updating CSS variable');
+
+ parent.remove();
+ child.remove();
+}, 'Animation reflect changes to custom properties on parent');
+
+</script>
+</body>
diff --git a/layout/style/test/test_animations_with_disabled_properties.html b/layout/style/test/test_animations_with_disabled_properties.html
new file mode 100644
index 0000000000..9eb395003f
--- /dev/null
+++ b/layout/style/test/test_animations_with_disabled_properties.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265611
+-->
+<head>
+ <title>Test CSS animations ignore disabled properties (Bug 1265611)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265611">Mozilla Bug
+ 1265611</a>
+<pre id="test">
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+/*
+ * This test relies on the fact that the overflow-clip-box property
+ * is disabled by the layout.css.overflow-clip-box.enabled pref. If we ever
+ * remove that pref we will need to substitute some other pref:property
+ * combination.
+ */
+SpecialPowers.pushPrefEnv(
+ { 'set': [[ 'layout.css.overflow-clip-box.enabled', false ]] },
+ () => window.open('file_animations_with_disabled_properties.html'));
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_any_dynamic.html b/layout/style/test/test_any_dynamic.html
new file mode 100644
index 0000000000..9c9a9e2a3f
--- /dev/null
+++ b/layout/style/test/test_any_dynamic.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=544834
+-->
+<head>
+ <title>Test for Bug 544834</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ :-moz-any(#display, #display2) { text-decoration-line: underline }
+ p:-moz-any([foo], [bar]) { z-index: 17 }
+
+ </style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=544834">Mozilla Bug 544834</a>
+<p id="display" style="position:absolute"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 544834
+ *
+ * In particular, test that we go through :-moz-any() in AddRule.
+ */
+
+function run()
+{
+ var p = document.getElementById("display");
+ var cs = getComputedStyle(p, "");
+ is(cs.textDecorationLine, "underline", "should match first rule");
+ is(cs.zIndex, "auto", "should not match second rule");
+ p.removeAttribute("id");
+ is(cs.textDecorationLine, "none", "should not match first rule");
+ is(cs.zIndex, "auto", "should not match second rule");
+ p.setAttribute("foo", "v");
+ is(cs.textDecorationLine, "none", "should not match first rule");
+ is(cs.zIndex, "17", "should match second rule");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_area_url_cursor.html b/layout/style/test/test_area_url_cursor.html
new file mode 100644
index 0000000000..cbc5efed74
--- /dev/null
+++ b/layout/style/test/test_area_url_cursor.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<title>cursor: url() doesn't assert for area elements</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+area {
+ /* Doesn't matter to trigger the assert */
+ cursor: url(invalid.cur), auto;
+}
+</style>
+<img width="300" height="98" usemap="#map">
+<map name="map" id="map">
+ <area class="url" shape="rect" coords="0,0,300,98" href="https://mozilla.org"></area>
+</map>
+<div></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(() => {
+
+ let checked = false;
+ document.querySelector("area").addEventListener("mousemove", function() {
+ setTimeout(() => {
+ if (checked) {
+ return;
+ }
+ checked = true;
+ ok(true, "Didn't assert");
+ SimpleTest.finish()
+ }, 0);
+ });
+
+ synthesizeMouseAtCenter(document.querySelector("img"), { type: "mousemove" });
+});
+</script>
diff --git a/layout/style/test/test_asyncopen.html b/layout/style/test/test_asyncopen.html
new file mode 100644
index 0000000000..9a52ea37eb
--- /dev/null
+++ b/layout/style/test/test_asyncopen.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1195173
+-->
+<head>
+ <title>Bug 1195173 - Test asyncOpen security exception</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!-- Note: the following stylesheet does not exist -->
+ <link rel="stylesheet" id="myCSS" type="text/css" href="file:///home/foo/bar.css">
+
+</head>
+<body onload="checkCSS()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1195173">Mozilla Bug 1195173</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+
+<script type="application/javascript">
+/*
+ * Description of the test:
+ * Accessing a stylesheet that got blocked by asyncOpen should
+ * throw an exception.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+function checkCSS()
+{
+ try {
+ // accessing tests/SimpleTest/test.css should not throw
+ var goodCSS = document.styleSheets[0].cssRules
+ ok(true, "accessing test.css should be allowed");
+ }
+ catch(e) {
+ ok(false, "accessing test.css should be allowed");
+ }
+
+ try {
+ // accessing file:///home/foo/bar.css should throw
+ var badCSS = document.styleSheets[1].cssRules
+ ok(false, "accessing bar.css should throw");
+ }
+ catch(e) {
+ ok(true, "accessing bar.css should throw");
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_at_rule_parse_serialize.html b/layout/style/test/test_at_rule_parse_serialize.html
new file mode 100644
index 0000000000..2c6f2e2d5c
--- /dev/null
+++ b/layout/style/test/test_at_rule_parse_serialize.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=478160
+-->
+<head>
+ <title>Test for Bug 478160</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style" type="text/css"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478160">Mozilla Bug 478160</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 478160 **/
+
+var style_element = document.getElementById("style");
+var style_text = document.createTextNode("");
+style_element.appendChild(style_text);
+
+function test_at_rule(str) {
+ style_text.data = str;
+ is(style_element.sheet.cssRules.length, 1,
+ "should have one rule from " + str);
+ var ser1 = style_element.sheet.cssRules[0].cssText;
+ isnot(ser1, "", "should have non-empty rule from " + str);
+ style_text.data = ser1;
+ var ser2 = style_element.sheet.cssRules[0].cssText;
+ is(ser2, ser1, "parse+serialize should be idempotent for " + str);
+}
+
+test_at_rule("@namespace 'a b'");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_attribute_selector_eof_behavior.html b/layout/style/test/test_attribute_selector_eof_behavior.html
new file mode 100644
index 0000000000..76635f9ed0
--- /dev/null
+++ b/layout/style/test/test_attribute_selector_eof_behavior.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for EOF behavior of attribute selectors in selectors API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_equals(document.querySelector("[id"),
+ document.getElementById("log"),
+ "We only have one element with an id");
+}, "']' should be implied if EOF after attribute name");
+test(function() {
+ assert_equals(document.querySelector('[id="log"'),
+ document.getElementById("log"),
+ "We should find the element with id=log");
+}, "']' should be implied if EOF after attribute value");
+</script>
diff --git a/layout/style/test/test_backdrop_filter_enabled_state.html b/layout/style/test/test_backdrop_filter_enabled_state.html
new file mode 100644
index 0000000000..f8f2995b28
--- /dev/null
+++ b/layout/style/test/test_backdrop_filter_enabled_state.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<head>
+ <meta charset=utf-8>
+ <title>Test Backdrop-Filter Enabled State</title>
+ <link rel="author" title="Erik Nordin" href="mailto:nordzilla@mozilla.com">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+(async function() {
+ for (let enabled of [true, false]) {
+ await SpecialPowers.pushPrefEnv({"set": [["layout.css.backdrop-filter.enabled", enabled]]});
+ is(enabled, CSS.supports("backdrop-filter: initial"),
+ "backdrop-filter is available only if backdrop-filter pref is set");
+ }
+ SimpleTest.finish();
+}());
+
+</script>
+
diff --git a/layout/style/test/test_background_blend_mode.html b/layout/style/test/test_background_blend_mode.html
new file mode 100644
index 0000000000..23551ebda9
--- /dev/null
+++ b/layout/style/test/test_background_blend_mode.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for miscellaneous computed style issues</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for miscellaneous computed style issues **/
+
+var frame_container = document.getElementById("display");
+var noframe_container = document.getElementById("content");
+
+function test_bug_841601() {
+ // Test handling of background-blend-mode
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+
+ frame_container.appendChild(p);
+ is(cs.backgroundBlendMode, "normal",
+ "default value of background-blend-mode");
+
+ p.setAttribute("style", "background-blend-mode: normal, invalid");
+ cs = getComputedStyle(p, "");
+ is(cs.backgroundBlendMode, "normal",
+ "set invalid blendmode");
+
+ p.setAttribute("style", "background-blend-mode: normal, normal");
+ cs = getComputedStyle(p, "");
+ is(cs.backgroundBlendMode, "normal, normal",
+ "set normal blendmode twice");
+
+ p.setAttribute("style", "background-blend-mode: normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity");
+ cs = getComputedStyle(p, "");
+ is(cs.backgroundBlendMode, "normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity",
+ "set all blendmodes");
+
+ p.remove();
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+test_bug_841601();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_border_device_pixel_rounding_initial_style.html b/layout/style/test/test_border_device_pixel_rounding_initial_style.html
new file mode 100644
index 0000000000..a8b5b0546d
--- /dev/null
+++ b/layout/style/test/test_border_device_pixel_rounding_initial_style.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<iframe id="frame"></iframe>
+<script>
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ "set": [["layout.css.devPixelsPerPx", "1.25"]] },
+ function() {
+ is(window.devicePixelRatio, 1.25, "devPixelsPerPx should work");
+ const frame = document.getElementById("frame");
+ frame.addEventListener("load", function() {
+ let doc = frame.contentDocument;
+ let win = frame.contentWindow;
+ is(win.devicePixelRatio, 1.25, "devPixelsPerPx should work inside the frame");
+ is(win.getComputedStyle(doc.querySelector("div")).borderTopWidth, "0.8px", "Shouldn't incorrectly round with 60 app units after getting the initial style");
+ SimpleTest.finish();
+ });
+ frame.srcdoc = "<div style='border: 1px solid; display: none;'></div>";
+ });
+</script>
diff --git a/layout/style/test/test_box_size_keywords.html b/layout/style/test/test_box_size_keywords.html
new file mode 100644
index 0000000000..c4074c7d8f
--- /dev/null
+++ b/layout/style/test/test_box_size_keywords.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for keywords on box sizing properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="property_database.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=1122253">Mozilla Bug 1122253</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1496558">Mozilla Bug 1496558</a>
+
+<style>
+#outer {
+ position: absolute;
+ width: 200px;
+ height: 200px;
+}
+#horizontal, #vertical {
+ background-color: #ccc;
+ line-height: 1px;
+}
+#vertical {
+ writing-mode: vertical-rl;
+ position: relative;
+ top: 10px;
+}
+.small, .big {
+ display: inline-block;
+ block-size: 10px;
+}
+.small {
+ background-image: linear-gradient(to bottom right, black, fuchsia);
+ inline-size: 10px;
+}
+.big {
+ background-image: linear-gradient(to bottom right, black, cyan);
+ inline-size: 90px;
+}
+</style>
+
+<div id=outer>
+ <div id=horizontal><span class=small></span><span class=big></span><span class=big></span><span class=big></span></div>
+ <div id=vertical><span class=small></span><span class=big></span><span class=big></span><span class=big></span></div>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1122253 and Bug 1496558 **/
+
+// Test that the -moz-available, min-content, max-content, and
+// fit-content keywords are usable only on width, when the writing
+// mode is horizontal, or height, when the writing mode is vertical,
+// and that they are always available on inline-size and never on
+// block-size. When used on the wrong properties, they should be
+// equivalent to unset.
+//
+// Also test the corresponding min-* and max-* properties.
+
+var gTests = [
+ { orientation: "horizontal", property: "width", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "horizontal", property: "width", specified_value: "min-content", computed_value: "90px", },
+ { orientation: "horizontal", property: "width", specified_value: "max-content", computed_value: "280px", },
+ { orientation: "horizontal", property: "width", specified_value: "fit-content", computed_value: "200px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "min-content", computed_value: "90px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "max-content", computed_value: "280px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "fit-content", computed_value: "200px", },
+ { orientation: "horizontal", property: "min-width", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "min-width", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", property: "min-width", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", property: "min-width", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", property: "max-width", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "max-width", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", property: "max-width", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", property: "max-width", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", property: "height", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "vertical", property: "height", specified_value: "min-content", computed_value: "90px", },
+ { orientation: "vertical", property: "height", specified_value: "max-content", computed_value: "280px", },
+ { orientation: "vertical", property: "height", specified_value: "fit-content", computed_value: "200px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "min-content", computed_value: "90px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "max-content", computed_value: "280px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "fit-content", computed_value: "200px", },
+ { orientation: "vertical", property: "min-height", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "min-height", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", property: "min-height", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", property: "min-height", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", property: "max-height", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "max-height", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", property: "max-height", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", property: "max-height", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "-moz-available", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "min-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "max-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "fit-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "min-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "max-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "fit-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-available", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "min-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "max-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "fit-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "min-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "max-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "fit-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "fit-content", computed_value: "fit-content", },
+];
+
+gTests.forEach(function(t) {
+ var e = document.getElementById(t.orientation);
+ e.style = (t.prerequisites || "") + t.property + ": " + t.specified_value;
+ is(get_computed_value(getComputedStyle(e), t.property), t.computed_value,
+ `${t.orientation} ${t.property}:${t.specified_value}`);
+ e.style = "";
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1055933.html b/layout/style/test/test_bug1055933.html
new file mode 100644
index 0000000000..de96d1d441
--- /dev/null
+++ b/layout/style/test/test_bug1055933.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1055933
+-->
+<head>
+ <title>Test for Bug 1055933</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1055933">Mozilla Bug 1055933</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 1055933 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var element = document.getElementById('area');
+ is(getComputedStyle(element).cursor, "pointer", "Does the <area> element return the correct cursor?");
+ //Force mPrimaryFrame to be set
+ requestAnimationFrame( function() {
+ synthesizeMouseAtCenter(document.getElementById('image'), {}, window);
+ is(getComputedStyle(element).cursor, "pointer", "Does the <area> element still return the correct cursor after mPrimaryFrame is set?");
+ SimpleTest.finish();
+ });
+});
+</script>
+</pre>
+ <div style="cursor: crosshair">
+ <img usemap="#map" border ="0" id="image" src="file_bug1055933_circle-xxl.png">
+ <map id="map" name="map">
+ <area id="area" onmousedown="this.setCapture();" onmouseup="this.releaseCapture();" shape="circle" coords="128,129,103" style="cursor: pointer">
+ </map>
+ </div>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1089417.html b/layout/style/test/test_bug1089417.html
new file mode 100644
index 0000000000..d4b09beffd
--- /dev/null
+++ b/layout/style/test/test_bug1089417.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1089417
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1089417</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1089417 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ var f = document.getElementById("f");
+ var fwin = f.contentWindow;
+ var fdoc = f.contentDocument;
+
+ f.height = "400";
+ fdoc.getElementById("s").disabled = false;
+ is(fwin.getComputedStyle(fdoc.documentElement).backgroundColor,
+ "rgb(0, 128, 0)",
+ "media query change should have restyled");
+
+ f.height = "200";
+ fdoc.getElementById("s").disabled = true;
+ fdoc.getElementById("s").disabled = false;
+ is(fwin.getComputedStyle(fdoc.documentElement).backgroundColor,
+ "rgb(255, 0, 0)",
+ "media query change should have restyled");
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1089417">Mozilla Bug 1089417</a>
+<div id="display">
+ <iframe id="f" src="file_bug1089417_iframe.html" width="300" height="200"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1112014.html b/layout/style/test/test_bug1112014.html
new file mode 100644
index 0000000000..cd4330e50f
--- /dev/null
+++ b/layout/style/test/test_bug1112014.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1112014
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1112014</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="property_database.js"></script>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+async function test() {
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ // This holds a canonical test value for each TYPE_ constant.
+ let testValues = {
+ "color": "rgb(3,3,3)",
+ "gradient": "linear-gradient( 45deg, blue, red )",
+ "timing-function": "cubic-bezier(0.1, 0.7, 1.0, 0.1)",
+ };
+
+ // The canonical test values don't work for all properties, in
+ // particular some shorthand properties. For these cases we have
+ // override values.
+ let overrideValues = {
+ "box-shadow": {
+ "color": testValues.color + " 2px 2px"
+ },
+ "-webkit-box-shadow": {
+ "color": testValues.color + " 2px 2px"
+ },
+ "scrollbar-color": {
+ "color": testValues.color + " " + testValues.color,
+ },
+ "text-shadow": {
+ "color": testValues.color + " 2px 2px"
+ },
+ };
+
+
+ // Ensure that all the TYPE_ constants have a representative
+ // test value, to try to ensure that this test is updated
+ // whenever a new type is added.
+ let reps = await SpecialPowers.spawn(window, [], () => {
+ return Object.getOwnPropertyNames(InspectorUtils).filter(tc => /TYPE_/.test(tc));
+ }).then(v => v.filter(tc => !(tc in testValues)));
+ is(reps.join(","), "", "all types have representative test value");
+
+ for (let propertyName in gCSSProperties) {
+ let prop = gCSSProperties[propertyName];
+
+ for (let iter in testValues) {
+ let testValue = testValues[iter];
+ if (propertyName in overrideValues &&
+ iter in overrideValues[propertyName]) {
+ testValue = overrideValues[propertyName][iter];
+ }
+
+ let supported =
+ InspectorUtils.cssPropertySupportsType(propertyName, iter);
+ let parsed = CSS.supports(propertyName, testValue);
+ is(supported, parsed, propertyName + " supports " + iter);
+ }
+ }
+
+ // Regression test for an assertion failure in an earlier version of
+ // the code. Note that cssPropertySupportsType returns false for
+ // all types for a variable.
+ ok(!InspectorUtils.cssPropertySupportsType("--variable", "color"),
+ "cssPropertySupportsType returns false for variable");
+
+ SimpleTest.finish();
+}
+ </script>
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1112014">Mozilla Bug 1112014</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1203766.html b/layout/style/test/test_bug1203766.html
new file mode 100644
index 0000000000..42da6520ac
--- /dev/null
+++ b/layout/style/test/test_bug1203766.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for bug 1203766</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<style>
+.x { color: red; }
+body > .x { color: green; }
+.y { color: green; }
+body > .y { display: none; color: red; }
+div > .z { color: red; }
+.z { color: green; }
+.a { color: red; }
+body > .a { display: none; color: green; }
+.b { display: none; }
+.c { color: red; }
+.b > .c { color: green; }
+.e { color: red; }
+.d > .e { color: green; }
+.f { color: red; }
+.g { color: green; }
+.h > .i { color: red; }
+.j > .i { color: green; }
+</style>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1203766">Mozilla Bug 1203766</a>
+<p id="display"></p>
+<div class=y></div>
+<div class=b></div>
+<pre id="test">
+<script class="testbody">
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+
+ // Element that goes from being out of the document to in the document.
+ var e = document.createElement("div");
+ e.className = "x";
+ var cs = getComputedStyle(e);
+ is(cs.color, "");
+ document.body.appendChild(e);
+ is(cs.color, "rgb(0, 128, 0)");
+
+ // Element that goes from in the document (and display:none) to out of
+ // the document.
+ e = document.querySelector(".y");
+ cs = getComputedStyle(e);
+ is(cs.color, "rgb(255, 0, 0)");
+ e.remove();
+ is(cs.color, "");
+
+ // Element that is removed from an out-of-document tree.
+ e = document.createElement("div");
+ f = document.createElement("span");
+ f.className = "z";
+ e.appendChild(f);
+ cs = getComputedStyle(f);
+ is(cs.color, "");
+ f.remove();
+ is(cs.color, "");
+
+ // Element going from not in document to in document and display:none.
+ e = document.createElement("div");
+ e.className = "a";
+ cs = getComputedStyle(e);
+ is(cs.color, "");
+ document.body.appendChild(e);
+ is(cs.color, "rgb(0, 128, 0)");
+
+ // Element going from not in document to in document and child of
+ // display:none element.
+ e = document.createElement("div");
+ e.className = "c";
+ cs = getComputedStyle(e);
+ is(cs.color, "");
+ document.querySelector(".b").appendChild(e);
+ is(cs.color, "rgb(0, 128, 0)");
+
+ // Element that is added to an out-of-document tree.
+ e = document.createElement("div");
+ e.className = "d";
+ f = document.createElement("span");
+ f.className = "e";
+ cs = getComputedStyle(f);
+ is(cs.color, "");
+ e.appendChild(f);
+ is(cs.color, "");
+
+ // Element that is outside the document when an attribute is modified to
+ // cause a different rule to match.
+ e = document.createElement("div");
+ e.className = "f";
+ cs = getComputedStyle(e);
+ is(cs.color, "");
+ e.className = "g";
+ is(cs.color, "");
+
+ // Element that is outside the document when an ancestor is modified to
+ // cause a different rule to match.
+ e = document.createElement("div");
+ e.className = "h";
+ f = document.createElement("span");
+ f.className = "i";
+ e.appendChild(f);
+ cs = getComputedStyle(f);
+ is(cs.color, "");
+ e.className = "j";
+ is(cs.color, "");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
diff --git a/layout/style/test/test_bug1232829.html b/layout/style/test/test_bug1232829.html
new file mode 100644
index 0000000000..65bea2014d
--- /dev/null
+++ b/layout/style/test/test_bug1232829.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1232829
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 1232829</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+
+/** Test for Bug 1232829 **/
+
+// This should be a crashtest but it relies on using a pop-up window which
+// isn't supported in crashtests.
+function boom() {
+ var popup = window.open("data:text/html,2");
+ setTimeout(function() {
+ var frameDoc = document.querySelector("iframe").contentDocument;
+ frameDoc.write("3");
+ requestAnimationFrame(function() {
+ popup.close();
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+ });
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</head>
+<body onload="boom()">
+ <iframe srcdoc="<style>@keyframes a { to { opacity: 0.5 } }</style>
+ <div style='animation: a 1ms'></div>"></iframe>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1292447.html b/layout/style/test/test_bug1292447.html
new file mode 100644
index 0000000000..6937b648c5
--- /dev/null
+++ b/layout/style/test/test_bug1292447.html
@@ -0,0 +1,350 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Was for: https://bugzilla.mozilla.org/show_bug.cgi?id=365932
+ Updated for: https://bugzilla.mozilla.org/show_bug.cgi?id=1292447
+-->
+<head>
+ <title>Test for Bug 1292447</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ #content {
+ width: 800px;
+ height: 800px;
+ padding: 0 200px;
+ border-width: 0 200px;
+ border-style: solid;
+ border-color: transparent
+ }
+ #content2 {
+ display: none;
+ }
+ #content > div, #content2 > div {
+ width: 400px;
+ height: 400px;
+ padding: 0 100px;
+ border-width: 0 100px;
+ border-style: solid;
+ border-color: transparent
+ }
+ #content > div.auto, #content2 > div.auto {
+ width: auto; height: auto;
+ padding: 0 100px;
+ border-width: 0 80px;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1292447">Mozilla Bug 1292447</a>
+<p id="display"></p>
+<div id="content">
+ <div id="indent1" style="text-indent: 400px"></div>
+ <div id="indent2" style="text-indent: 50%"></div>
+
+ <div id="widthheight-1" class="auto"></div>
+
+ <div id="minwidth1-1" style="min-width: 200px"></div>
+ <div id="minwidth1-2" style="min-width: 25%"></div>
+ <div id="minwidth2-1" style="min-width: 600px"></div>
+ <div id="minwidth2-2" style="min-width: 75%"></div>
+ <div id="minwidth3-1" class="auto" style="min-width: 200px"></div>
+ <div id="minwidth3-2" class="auto" style="min-width: 25%"></div>
+ <div id="minwidth4-1" class="auto" style="min-width: 600px"></div>
+ <div id="minwidth4-2" class="auto" style="min-width: 75%"></div>
+
+ <div id="maxwidth1-1" style="max-width: 320px"></div>
+ <div id="maxwidth1-2" style="max-width: 40%"></div>
+ <div id="maxwidth2-1" style="max-width: 480px"></div>
+ <div id="maxwidth2-2" style="max-width: 60%"></div>
+ <div id="maxwidth3-1" class="auto" style="max-width: 320px"></div>
+ <div id="maxwidth3-2" class="auto" style="max-width: 40%"></div>
+ <div id="maxwidth4-1" class="auto" style="max-width: 480px"></div>
+ <div id="maxwidth4-2" class="auto" style="max-width: 60%"></div>
+
+ <div id="minmaxwidth1-1" style="min-width: 200px; max-width: 320px"></div>
+ <div id="minmaxwidth1-2" style="min-width: 200px; max-width: 40%"></div>
+ <div id="minmaxwidth2-1" style="min-width: 25%; max-width: 320px"></div>
+ <div id="minmaxwidth2-2" style="min-width: 25%; max-width: 40%"></div>
+ <div id="minmaxwidth3-1" style="min-width: 600px; max-width: 320px"></div>
+ <div id="minmaxwidth3-2" style="min-width: 600px; max-width: 40%"></div>
+ <div id="minmaxwidth4-1" style="min-width: 75%; max-width: 320px"></div>
+ <div id="minmaxwidth4-2" style="min-width: 75%; max-width: 40%"></div>
+ <div id="minmaxwidth5-1"
+ style="display:none; min-width: 200px; max-width: 320px"></div>
+ <div id="minmaxwidth6-1"
+ style="display: none; min-width: 25%; max-width: 320px"></div>
+ <div id="minmaxwidth7-1"
+ style="display: none; min-width: 600px; max-width: 320px"></div>
+ <div id="minmaxwidth7-2"
+ style="display: none; min-width: 600px; max-width: 40%"></div>
+ <div id="minmaxwidth8-1" class="auto"
+ style="min-width: 200px; max-width: 320px"></div>
+ <div id="minmaxwidth8-2" class="auto"
+ style="min-width: 200px; max-width: 40%"></div>
+ <div id="minmaxwidth9-1" class="auto"
+ style="min-width: 25%; max-width: 320px"></div>
+ <div id="minmaxwidth9-2" class="auto"
+ style="min-width: 25%; max-width: 40%"></div>
+ <div id="minmaxwidth10-1" class="auto"
+ style="min-width: 600px; max-width: 320px"></div>
+ <div id="minmaxwidth10-2" class="auto"
+ style="min-width: 600px; max-width: 40%"></div>
+ <div id="minmaxwidth11-1" class="auto"
+ style="min-width: 75%; max-width: 320px"></div>
+ <div id="minmaxwidth11-2" class="auto"
+ style="min-width: 75%; max-width: 40%"></div>
+
+ <div id="minheight1-1" style="min-height: 200px"></div>
+ <div id="minheight1-2" style="min-height: 25%"></div>
+ <div id="minheight2-1" style="min-height: 600px"></div>
+ <div id="minheight2-2" style="min-height: 75%"></div>
+ <div id="minheight3-1" class="auto" style="min-height: 200px"></div>
+ <div id="minheight3-2" class="auto" style="min-height: 25%"></div>
+ <div id="minheight4-1" class="auto" style="min-height: 600px"></div>
+ <div id="minheight4-2" class="auto" style="min-height: 75%"></div>
+
+ <div id="maxheight1-1" style="max-height: 320px"></div>
+ <div id="maxheight1-2" style="max-height: 40%"></div>
+ <div id="maxheight2-1" style="max-height: 480px"></div>
+ <div id="maxheight2-2" style="max-height: 60%"></div>
+ <div id="maxheight3-1" class="auto" style="max-height: 320px"></div>
+ <div id="maxheight3-2" class="auto" style="max-height: 40%"></div>
+ <div id="maxheight4-1" class="auto" style="max-height: 480px"></div>
+ <div id="maxheight4-2" class="auto" style="max-height: 60%"></div>
+
+ <div id="minmaxheight1-1" style="min-height: 200px; max-height: 320px"></div>
+ <div id="minmaxheight1-2" style="min-height: 200px; max-height: 40%"></div>
+ <div id="minmaxheight2-1" style="min-height: 25%; max-height: 320px"></div>
+ <div id="minmaxheight2-2" style="min-height: 25%; max-height: 40%"></div>
+ <div id="minmaxheight3-1" style="min-height: 600px; max-height: 320px"></div>
+ <div id="minmaxheight3-2" style="min-height: 600px; max-height: 40%"></div>
+ <div id="minmaxheight4-1" style="min-height: 75%; max-height: 320px"></div>
+ <div id="minmaxheight4-2" style="min-height: 75%; max-height: 40%"></div>
+ <div id="minmaxheight5-1"
+ style="display:none; min-height: 200px; max-height: 320px"></div>
+ <div id="minmaxheight6-1"
+ style="display: none; min-height: 25%; max-height: 320px"></div>
+ <div id="minmaxheight7-1"
+ style="display: none; min-height: 600px; max-height: 320px"></div>
+ <div id="minmaxheight7-2"
+ style="display: none; min-height: 600px; max-height: 40%"></div>
+ <div id="minmaxheight8-1" class="auto"
+ style="min-height: 200px; max-height: 320px"></div>
+ <div id="minmaxheight8-2" class="auto"
+ style="min-height: 200px; max-height: 40%"></div>
+ <div id="minmaxheight9-1" class="auto"
+ style="min-height: 25%; max-height: 320px"></div>
+ <div id="minmaxheight9-2" class="auto"
+ style="min-height: 25%; max-height: 40%"></div>
+ <div id="minmaxheight10-1" class="auto"
+ style="min-height: 600px; max-height: 320px"></div>
+ <div id="minmaxheight10-2" class="auto"
+ style="min-height: 600px; max-height: 40%"></div>
+ <div id="minmaxheight11-1" class="auto"
+ style="min-height: 75%; max-height: 320px"></div>
+ <div id="minmaxheight11-2" class="auto"
+ style="min-height: 75%; max-height: 40%"></div>
+
+ <div id="radius1" style="border-radius: 80px"></div>
+ <div id="radius2" style="border-radius: 20% / 20%"></div>
+</div>
+<div id="content2" style="display: none">
+ <div id="indent3" style="text-indent: 400px"></div>
+ <div id="indent4" style="text-indent: 50%"></div>
+
+ <div id="minwidth1-3" style="min-width: 200px"></div>
+ <div id="minwidth1-4" style="min-width: 25%"></div>
+ <div id="minwidth2-3" style="min-width: 600px"></div>
+ <div id="minwidth2-4" style="min-width: 75%"></div>
+
+ <div id="maxwidth1-3" style="max-width: 320px"></div>
+ <div id="maxwidth1-4" style="max-width: 40%"></div>
+ <div id="maxwidth2-3" style="max-width: 480px"></div>
+ <div id="maxwidth2-4" style="max-width: 60%"></div>
+
+ <div id="minmaxwidth1-3" style="min-width: 200px; max-width: 320px"></div>
+ <div id="minmaxwidth1-4" style="min-width: 200px; max-width: 40%"></div>
+ <div id="minmaxwidth2-3" style="min-width: 25%; max-width: 320px"></div>
+ <div id="minmaxwidth2-4" style="min-width: 25%; max-width: 40%"></div>
+ <div id="minmaxwidth3-3" style="min-width: 600px; max-width: 320px"></div>
+ <div id="minmaxwidth3-4" style="min-width: 600px; max-width: 40%"></div>
+ <div id="minmaxwidth4-3" style="min-width: 75%; max-width: 320px"></div>
+ <div id="minmaxwidth4-4" style="min-width: 75%; max-width: 40%"></div>
+
+ <div id="minheight1-3" style="min-height: 200px"></div>
+ <div id="minheight1-4" style="min-height: 25%"></div>
+ <div id="minheight2-3" style="min-height: 600px"></div>
+ <div id="minheight2-4" style="min-height: 75%"></div>
+
+ <div id="maxheight1-3" style="max-height: 320px"></div>
+ <div id="maxheight1-4" style="max-height: 40%"></div>
+ <div id="maxheight2-3" style="max-height: 480px"></div>
+ <div id="maxheight2-4" style="max-height: 60%"></div>
+
+ <div id="minmaxheight1-3" style="min-height: 200px; max-height: 320px"></div>
+ <div id="minmaxheight1-4" style="min-height: 200px; max-height: 40%"></div>
+ <div id="minmaxheight2-3" style="min-height: 25%; max-height: 320px"></div>
+ <div id="minmaxheight2-4" style="min-height: 25%; max-height: 40%"></div>
+ <div id="minmaxheight3-3" style="min-height: 600px; max-height: 320px"></div>
+ <div id="minmaxheight3-4" style="min-height: 600px; max-height: 40%"></div>
+ <div id="minmaxheight4-3" style="min-height: 75%; max-height: 320px"></div>
+ <div id="minmaxheight4-4" style="min-height: 75%; max-height: 40%"></div>
+
+ <div id="radius3" style="border-radius: 80px"></div>
+ <div id="radius4" style="border-radius: 20%"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1292447 **/
+
+document.body.offsetWidth;
+
+doATest("text-indent", "indent", 400, 50);
+doATest("border-top-left-radius", "radius", 80, 20);
+
+doATest("width", "widthheight-", 440, 0, true);
+doATest("height", "widthheight-", 0, 0, true);
+
+doATest("min-width", "minwidth1-", 200, 25);
+doATest("min-width", "minwidth2-", 600, 75);
+doATest("max-width", "maxwidth1-", 320, 40);
+doATest("max-width", "maxwidth2-", 480, 60);
+
+// Test that min-width doesn't affect computed max-width
+doATest("max-width", "minmaxwidth1-", 320, 40);
+doATest("max-width", "minmaxwidth2-", 320, 40);
+doATest("max-width", "minmaxwidth3-", 320, 40);
+doATest("max-width", "minmaxwidth4-", 320, 40);
+
+// Test that max and min-width affect computed width correctly
+doATest("width", "minwidth1-", 400, 0, true);
+doATest("width", "minwidth2-", 600, 0, true);
+doATest("width", "minwidth3-", 440, 0, true);
+doATest("width", "minwidth4-", 600, 0, true);
+doATest("width", "maxwidth1-", 320, 0, true);
+doATest("width", "maxwidth2-", 400, 0, true);
+doATest("width", "maxwidth3-", 320, 0, true);
+doATest("width", "maxwidth4-", 440, 0, true);
+doATest("width", "minmaxwidth1-", 320, 0, true);
+doATest("width", "minmaxwidth2-", 320, 0, true);
+doATest("width", "minmaxwidth3-", 600, 0, true);
+doATest("width", "minmaxwidth4-", 600, 0, true);
+doATest("width", "minmaxwidth5-", 400, 0, true);
+doATest("width", "minmaxwidth6-", 400, 0, true);
+doATest("width", "minmaxwidth7-", 400, 0, true);
+doATest("width", "minmaxwidth8-", 320, 0, true);
+doATest("width", "minmaxwidth9-", 320, 0, true);
+doATest("width", "minmaxwidth10-", 600, 0, true);
+doATest("width", "minmaxwidth11-", 600, 0, true);
+
+doATest("min-height", "minheight1-", 200, 25);
+doATest("min-height", "minheight2-", 600, 75);
+doATest("max-height", "maxheight1-", 320, 40);
+doATest("max-height", "maxheight2-", 480, 60);
+
+// Test that min-height doesn't affect computed max-height
+doATest("max-height", "minmaxheight1-", 320, 40);
+doATest("max-height", "minmaxheight2-", 320, 40);
+doATest("max-height", "minmaxheight3-", 320, 40);
+doATest("max-height", "minmaxheight4-", 320, 40);
+
+// Test that max and min-height affect computed height correctly
+doATest("height", "minheight1-", 400, 0, true);
+doATest("height", "minheight2-", 600, 0, true);
+doATest("height", "minheight3-", 200, 0, true);
+doATest("height", "minheight4-", 600, 0, true);
+doATest("height", "maxheight1-", 320, 0, true);
+doATest("height", "maxheight2-", 400, 0, true);
+doATest("height", "maxheight3-", 0, 0, true);
+doATest("height", "maxheight4-", 0, 0, true);
+doATest("height", "minmaxheight1-", 320, 0, true);
+doATest("height", "minmaxheight2-", 320, 0, true);
+doATest("height", "minmaxheight3-", 600, 0, true);
+doATest("height", "minmaxheight4-", 600, 0, true);
+doATest("height", "minmaxheight5-", 400, 0, true);
+doATest("height", "minmaxheight6-", 400, 0, true);
+doATest("height", "minmaxheight7-", 400, 0, true);
+doATest("height", "minmaxheight8-", 200, 0, true);
+doATest("height", "minmaxheight9-", 200, 0, true);
+doATest("height", "minmaxheight10-", 600, 0, true);
+doATest("height", "minmaxheight11-", 600, 0, true);
+
+function style(id) {
+ return document.defaultView.getComputedStyle($(id));
+}
+
+function round(num, decimals) {
+ return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals);
+}
+
+function coordValueTest(camelProp, decl, coordVal, prettyName) {
+ is(decl[camelProp], coordVal + "px", prettyName);
+}
+
+function percentValueTest(camelProp, decl, percentVal, prettyName) {
+ is(decl[camelProp], percentVal + "%", prettyName);
+}
+
+function doATest(propName, idBase, coordVal, percentVal, resolveToUsedVal = false) {
+ var cssCamelPropName = "";
+ var parts = propName.split("-");
+ ok(parts.length > 0, "CSS prop name should not be empty");
+ var i;
+ if (parts[0]) {
+ i = 0;
+ } else {
+ is(parts[1], "moz", "Testing an extension property that's not -moz");
+ ok(parts.length > 2, "-moz prop name should not have more than 2 parts");
+ cssCamelPropName = "Moz";
+ i = 2;
+ }
+ for (; i < parts.length; ++i) {
+ var part = parts[i];
+ isnot(part, "", "Must have a nonempty part");
+ if (cssCamelPropName) {
+ cssCamelPropName += part.charAt(0).toUpperCase() +
+ part.substring(1, part.length);
+ } else {
+ cssCamelPropName += part;
+ }
+ }
+
+ /* Test $(id)-1 */
+ coordValueTest(cssCamelPropName,
+ style(idBase + "1"), coordVal,
+ propName + " of " + idBase + "1");
+
+ if (!$(idBase + "2")) {
+ // Nothing else to do here
+ return
+ }
+
+ /* Test $(id)-2 */
+ if (resolveToUsedVal) {
+ coordValueTest(cssCamelPropName,
+ style(idBase + "2"), coordVal,
+ propName + " of " + idBase + "2");
+ } else {
+ percentValueTest(cssCamelPropName,
+ style(idBase + "2"), percentVal,
+ propName + " of " + idBase + "2");
+ }
+
+ if (percentVal) {
+ /* Test $(id)-3 */
+ coordValueTest(cssCamelPropName,
+ style(idBase + "3"), coordVal,
+ propName + " of " + idBase + "3");
+
+ /* Test $(id)-4 */
+ percentValueTest(cssCamelPropName,
+ style(idBase + "4"), percentVal,
+ propName + " of " + idBase + "4");
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1330375.html b/layout/style/test/test_bug1330375.html
new file mode 100644
index 0000000000..c9bc0f6715
--- /dev/null
+++ b/layout/style/test/test_bug1330375.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<!-- https://bugzil.la/1330375 -->
+<meta charset="utf-8">
+<title>Test for Bug 1330375</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest.css"/>
+<body>
+ <div id="content">
+ <table>
+ <tbody>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ </tbody>
+ </table>
+ </div>
+</body>
+<script>
+"use strict";
+
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function flush_layout(element) {
+ (element || document.documentElement).offsetHeight;
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function() {
+ flush_layout(document.getElementById("content"));
+
+ let before = {
+ framesConstructed: gUtils.framesConstructed,
+ framesReflowed: gUtils.framesReflowed,
+ };
+
+ // Begin test
+ let rows = document.getElementsByTagName("tr");
+ for (var r = 0; r < rows.length; r++) {
+ let row = rows[r];
+ row.innerText;
+ // Cause potential invalidation of layout:
+ row.style.display = "none";
+ }
+
+ is(gUtils.framesConstructed, before.framesConstructed, "Frames constructed should be 0");
+ is(gUtils.framesReflowed, before.framesReflowed, "Frames reflowed should be 0");
+
+ SimpleTest.finish();
+}
+</script>
+
diff --git a/layout/style/test/test_bug1371488.html b/layout/style/test/test_bug1371488.html
new file mode 100644
index 0000000000..7e32fa0031
--- /dev/null
+++ b/layout/style/test/test_bug1371488.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for bug 1371488</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style id="test">
+ @import url(non-exist-file.css);
+ </style>
+</head>
+<body>
+<pre id="log">
+<script>
+ let sheet = document.getElementById("test").sheet;
+ let rule = sheet.cssRules[0];
+ ok(rule, "The import rule should not be null even if the file fails to load");
+ is(rule.type, CSSRule.IMPORT_RULE, "It is the import rule");
+ ok(rule.styleSheet, "The associated stylesheet should exists as well");
+ is(rule.styleSheet.cssRules.length, 0, "The stylesheet should be empty");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1375944.html b/layout/style/test/test_bug1375944.html
new file mode 100644
index 0000000000..c265691170
--- /dev/null
+++ b/layout/style/test/test_bug1375944.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for bug 1375944</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<iframe id="subframe"></iframe>
+<pre id="log">
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+async function runTest() {
+ let f = new FontFace("Ahem", "url(Ahem.ttf)", {});
+ await f.load();
+ is(f.status, "loaded", "Loaded Ahem font");
+
+ let subframe = document.getElementById("subframe");
+ subframe.src = "file_bug1375944.html";
+ await new Promise(resolve => subframe.onload = resolve);
+ let elem = subframe.contentDocument.getElementById("test");
+ is(elem.getBoundingClientRect().width, 64,
+ "The font should be loaded properly");
+
+ SimpleTest.finish();
+}
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1382568.html b/layout/style/test/test_bug1382568.html
new file mode 100644
index 0000000000..23d4dfe5b6
--- /dev/null
+++ b/layout/style/test/test_bug1382568.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for bug 1382568: calling innerText on an uninitialized presshell doesn't crash</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ window.onmessage = function(e) {
+ is(e.data.result, "ok", "Child frame should load properly");
+ SimpleTest.finish();
+ };
+</script>
+<iframe src="https://example.com/tests/layout/style/test/bug1382568-iframe.html"></iframe>
+<script>
+ SimpleTest.waitForExplicitFinish();
+</script>
diff --git a/layout/style/test/test_bug1394302.html b/layout/style/test/test_bug1394302.html
new file mode 100644
index 0000000000..e21bcc4ea1
--- /dev/null
+++ b/layout/style/test/test_bug1394302.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1394302
+-->
+<head>
+ <title>Test for Bug 1394302</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ #inner {
+ animation: setFontSize 0s forwards;
+ }
+ @keyframes setFontSize {
+ to { font-size: calc(110% + 0.1em); }
+ }
+ </style>
+</head>
+<body>
+<div id=outer>
+ <div id=inner></div>
+</div>
+<script>
+var outer = document.getElementById("outer");
+outer.style.fontSize = '10px';
+is(getComputedStyle(inner).fontSize, "12px");
+
+outer.style.fontSize = '20px';
+is(getComputedStyle(inner).fontSize, "24px");
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1443344-1.html b/layout/style/test/test_bug1443344-1.html
new file mode 100644
index 0000000000..fbbcb1ecbb
--- /dev/null
+++ b/layout/style/test/test_bug1443344-1.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1443344
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1443344</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1443344 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var sheetURL = new URL("file_bug1443344.css", location.href);
+ sheetURL.protocol = "http";
+ var link = document.createElement("link");
+ link.href = `data:text/css,@import url("${sheetURL}");`
+ link.rel = "stylesheet";
+ var loadFired = false, errorFired = false;
+ link.onload = () => loadFired = true;
+ link.onerror = () => errorFired = true;
+ document.head.appendChild(link);
+
+ addLoadEvent(() => {
+ is(loadFired, false, "Should not fire onload for erroring @import");
+ is(errorFired, true, "Should fire onerror for erroring @import");
+ is(getComputedStyle($("importTarget")).color, "rgb(0, 255, 0)",
+ "Erroring sheet should not load");
+ SimpleTest.finish();
+ });
+
+ </script>
+ <style>
+ #importTarget { color: rgb(0, 255, 0); }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1443344">Mozilla Bug 1443344</a>
+<p id="display"><div id="importTarget"></div></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1443344-2.html b/layout/style/test/test_bug1443344-2.html
new file mode 100644
index 0000000000..224f575f36
--- /dev/null
+++ b/layout/style/test/test_bug1443344-2.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1443344
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1443344</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1443344 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var sheetURL = new URL("file_bug1443344.css", location.href);
+ sheetURL.protocol = "http";
+ var link = document.createElement("link");
+ link.href = `data:text/css,@import url("data:text/css,@import url('${sheetURL}');");`
+ link.rel = "stylesheet";
+ var loadFired = false, errorFired = false;
+ link.onload = () => loadFired = true;
+ link.onerror = () => errorFired = true;
+ document.head.appendChild(link);
+
+ addLoadEvent(() => {
+ is(loadFired, false, "Should not fire onload for erroring @import");
+ is(errorFired, true, "Should fire onerror for erroring @import");
+ is(getComputedStyle($("importTarget")).color, "rgb(0, 255, 0)",
+ "Erroring sheet should not load");
+ SimpleTest.finish();
+ });
+
+ </script>
+ <style>
+ #importTarget { color: rgb(0, 255, 0); }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1443344">Mozilla Bug 1443344</a>
+<p id="display"><div id="importTarget"></div></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1451199-1.html b/layout/style/test/test_bug1451199-1.html
new file mode 100644
index 0000000000..4deb6aac1b
--- /dev/null
+++ b/layout/style/test/test_bug1451199-1.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1451199
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1451199</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1451199 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ var iframe = document.querySelector("iframe");
+ iframe.width = "50";
+ iframe.contentDocument.documentElement.offsetWidth; // Flush layout
+
+ // We have to be careful to not check l.matches until the very end
+ // of the test.
+ var l = frames[0].matchMedia("(orientation: portrait)");
+ l.onchange = function() {
+ is(l.matches, false,
+ "Should not match portrait by the time we get notified");
+ SimpleTest.finish();
+ };
+ iframe.width = "200";
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1451199">Mozilla Bug 1451199</a>
+<p id="display"><iframe height="100" width="200"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1451199-2.html b/layout/style/test/test_bug1451199-2.html
new file mode 100644
index 0000000000..d29d644c0b
--- /dev/null
+++ b/layout/style/test/test_bug1451199-2.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1451199
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1451199</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1451199 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(async function() {
+ // We have to be careful to not check l.matches until the very end
+ // of the test.
+ const l = frames[0].matchMedia("(orientation: portrait)");
+ const iframe = document.querySelector("iframe");
+ iframe.width = "50";
+
+ await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
+
+ l.addEventListener("change", function() {
+ is(l.matches, false,
+ "Should not match portrait by the time we get notified");
+ SimpleTest.finish();
+ }, { once: true });
+ iframe.width = "200";
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1451199">Mozilla Bug 1451199</a>
+<p id="display"><iframe height="100" width="200"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1490890.html b/layout/style/test/test_bug1490890.html
new file mode 100644
index 0000000000..00a8176ed6
--- /dev/null
+++ b/layout/style/test/test_bug1490890.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1490890
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1490890</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #flex {
+ display: flex;
+ flex-direction: column;
+ height: 100px;
+ max-height: 100px;
+ overflow: hidden;
+ border: 1px solid black;
+ }
+ #overflowAuto {
+ overflow: auto;
+ white-space: pre-wrap;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1490890">Mozilla Bug 1490890</a>
+<div id="display">
+ <div id="content">
+ <div id="flex">
+ <div id="overflowAuto">
+ <!-- Populated by test JS below: -->
+ <div id="tall"></div>
+ </div>
+ <div id="testNode">abc</div>
+ </div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 1490890 **/
+
+/**
+ * This test checks how many reflows are required, when we make a change inside
+ * a flex item, with a tall scrollable sibling flex item.
+ */
+
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+// The elements that we will modify here:
+const gTall = document.getElementById("tall");
+const gTestNode = document.getElementById("testNode");
+
+// Helper function to undo our modifications:
+function cleanup()
+{
+ gTall.firstChild.remove();
+ gTestNode.style = "";
+}
+
+// Flush layout & return the global frame-reflow-count
+function getReflowCount()
+{
+ let unusedVal = document.getElementById("flex").offsetHeight; // flush layout
+ return gUtils.framesReflowed;
+}
+
+// This function changes gTestNode to "display:none", and returns the number
+// of frames that need to be reflowed as a result of that tweak.
+function makeTweakAndCountReflows()
+{
+ let beforeCount = getReflowCount();
+ gTestNode.style.display = "none";
+ let afterCount = getReflowCount();
+
+ let numReflows = afterCount - beforeCount;
+ if (numReflows <= 0) {
+ ok(false, "something's wrong -- we should've reflowed *something*");
+ }
+ return numReflows;
+}
+
+// ACTUAL TEST LOGIC STARTS HERE
+// -----------------------------
+const testLineCount = 100;
+const refLineCount = 5000;
+
+// "Reference" measurement: put enough lines of text into gTall to trigger
+// a vertical scrollbar, and then see how many frames need to be reflowed
+// in response to a tweak in gTestNode:
+let text = document.createTextNode("a\n".repeat(testLineCount));
+gTall.appendChild(text);
+let numReferenceReflows = makeTweakAndCountReflows();
+cleanup();
+
+// "Test" measurement: put many more lines of text into gTall (many more than
+// for reference case), and then see how many frames need to be reflowed
+// in response to a tweak in gTestNode:
+text = document.createTextNode("a\n".repeat(refLineCount));
+gTall.appendChild(text);
+let numTestReflows = makeTweakAndCountReflows();
+cleanup();
+
+is(numTestReflows, numReferenceReflows,
+ "Tweak should trigger the same number of reflows regardless of " +
+ "how much content is present in descendant of sibling");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1505254.html b/layout/style/test/test_bug1505254.html
new file mode 100644
index 0000000000..9cb3d1e316
--- /dev/null
+++ b/layout/style/test/test_bug1505254.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1505254
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1505254</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ /* Note: this CSS/DOM structure is loosely based on WhatsApp Web. */
+ #outerFlex {
+ display: flex;
+ height: 200px;
+ border: 3px solid purple;
+ overflow: hidden;
+ position: relative;
+ }
+ #outerItem {
+ flex: 0 0 60%;
+ overflow: hidden;
+ position: relative;
+ }
+ #abspos {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+ }
+ #insideAbspos {
+ position: relative;
+ flex: 1 1 0;
+ width: 100%;
+ height: 100%;
+ }
+ #scroller {
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ height: 100%;
+ width: 100%;
+ }
+ #initiallyHidden {
+ display:none;
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1505254">Mozilla Bug 1505254</a>
+<div id="display">
+ <div id="content">
+ <div id="outerFlex">
+ <div id="outerItem">
+ <div id="abspos">
+ <div id="insideAbspos">
+ <div>
+ <div id="scroller">
+ <div style="min-height: 600px">abc</div>
+ <div id="initiallyHidden">def</div>
+ </div>
+ </div>
+ </div>
+ <div id="testNode"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 1505254 **/
+
+/**
+ * This test checks how many reflows are required when we make a change inside
+ * of an abpsos element, which itself is inside of a flex item with cached
+ * block-size measurements. This test is checking that this sort of change
+ * doesn't invalidate those cached block-size measurements on the flex item
+ * ancestor. (We're testing that indirectly by seeing how many frames are
+ * reflowed.)
+ */
+
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+// The elements that we will modify here:
+const gInitiallyHidden = document.getElementById("initiallyHidden");
+const gTestNode = document.getElementById("testNode");
+
+// Helper function to undo our modifications:
+function cleanup()
+{
+ gTestNode.textContent = "";
+ gInitiallyHidden.style = "";
+}
+
+// Helper function to flush layout & return the global frame-reflow-count:
+function getReflowCount()
+{
+ let unusedVal = document.getElementById("scroller").offsetHeight; // flush layout
+ return gUtils.framesReflowed;
+}
+
+// This function adds some text in gTestNode and returns the number of frames
+// that need to be reflowed as a result of that tweak:
+function makeTweakAndCountReflows()
+{
+ let beforeCount = getReflowCount();
+ gTestNode.textContent = "def";
+ let afterCount = getReflowCount();
+
+ let numReflows = afterCount - beforeCount;
+ if (numReflows <= 0) {
+ ok(false, "something's wrong -- we should've reflowed *something*");
+ }
+ return numReflows;
+}
+
+// ACTUAL TEST LOGIC STARTS HERE
+// -----------------------------
+
+// "Reference" measurement: see how many frames need to be reflowed
+// in response to a tweak in gTestNode, before we've shown
+// #initiallyHidden:
+let numReferenceReflows = makeTweakAndCountReflows();
+cleanup();
+
+// "Test" measurement: see how many frames need to be reflowed
+// in response to a tweak in gTestNode, after we've shown #initiallyHidden:
+gInitiallyHidden.style.display = "block";
+let numTestReflows = makeTweakAndCountReflows();
+cleanup();
+
+// Any difference between our measurements is an indication that we're reflowing
+// frames in a non-"dirty" subtree. (The gTestNode tweak has no reason to cause
+// #initiallyHidden to be dirty -- and therefore, the presence/absence of
+// #initiallyHidden shouldn't affect the number of frames that get reflowed in
+// response to the gTestNode tweak).
+is(numTestReflows, numReferenceReflows,
+ "Tweak should trigger the same number of reflows regardless of " +
+ "content in unmodified sibling");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug160403.html b/layout/style/test/test_bug160403.html
new file mode 100644
index 0000000000..79b10462d8
--- /dev/null
+++ b/layout/style/test/test_bug160403.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=160403
+-->
+<head>
+ <title>Test for Bug 160403</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=160403">Mozilla Bug 160403</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 160403 **/
+
+var element = document.getElementById("content");
+var style = element.style;
+
+element.setAttribute("style", "border-top-style: dotted");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "");
+is(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "");
+
+element.setAttribute("style", "border-top-style: dotted ! important");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "important");
+is(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "");
+
+element.setAttribute("style", "border-top-style: dotted ! important; border-bottom-style: dotted ! important; border-left-style: dotted ! important");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "important");
+is(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "");
+
+element.setAttribute("style", "border-top-style: dotted ! important; border-right-style: dotted; border-bottom-style: dotted ! important; border-left-style: dotted ! important");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "important");
+is(style.getPropertyValue("border-right-style"), "dotted");
+is(style.getPropertyPriority("border-right-style"), "");
+is(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "");
+
+element.setAttribute("style", "border-top-style: dotted ! important; border-right-style: dotted ! important; border-bottom-style: dotted ! important; border-left-style: dotted ! important");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "important");
+is(style.getPropertyValue("border-right-style"), "dotted");
+is(style.getPropertyPriority("border-right-style"), "important");
+isnot(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "important");
+
+// Also test that we check consistency of inherit and initial.
+element.setAttribute("style", "border-top-style: dotted; border-right-style: dotted; border-bottom-style: dotted; border-left-style: dotted");
+isnot(style.getPropertyValue("border-style"), "", "serialize shorthand when all values not inherit/initial");
+element.setAttribute("style", "border-top-style: inherit; border-right-style: inherit; border-bottom-style: inherit; border-left-style: inherit");
+is(style.getPropertyValue("border-style"), "inherit", "serialize shorthand as inherit");
+element.setAttribute("style", "border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial");
+is(style.getPropertyValue("border-style"), "initial", "serialize shorthand as initial");
+element.setAttribute("style", "border-top-style: dotted; border-right-style: dotted; border-bottom-style: dotted; border-left-style: inherit");
+is(style.getPropertyValue("border-style"), "", "don't serialize shorthand when partly inherit");
+element.setAttribute("style", "border-top-style: initial; border-right-style: dotted; border-bottom-style: initial; border-left-style: initial");
+is(style.getPropertyValue("border-style"), "", "don't serialize shorthand when partly initial");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1729861.html b/layout/style/test/test_bug1729861.html
new file mode 100644
index 0000000000..247b7c2644
--- /dev/null
+++ b/layout/style/test/test_bug1729861.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1729861
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test that toggling the resistFingerprinting pref re-evaluates device media queries</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="test-css"></style>
+ <script src="bug1729861.js"></script>
+ <script>
+ // Run all tests now.
+ window.onload = function () {
+ add_task(async function() {
+ await test();
+ });
+ };
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1729861">Bug 1729861</a>
+<p id="display">TEST</p>
+</body>
+</html>
diff --git a/layout/style/test/test_bug200089.html b/layout/style/test/test_bug200089.html
new file mode 100644
index 0000000000..496de50e98
--- /dev/null
+++ b/layout/style/test/test_bug200089.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=200089
+-->
+<head>
+ <title>Test for Bug 200089</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=200089">Mozilla Bug 200089</a>
+<div id="display" style="width: 600px">
+ <table border="0" id="t" style="width: 300px; margin-left: auto; margin-right: auto">
+ <tr><td>Cell</td></tr>
+ </table>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 200089 **/
+is(getComputedStyle($("t"), "").width, "300px",
+ "Used width should match specified width in this case");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug221428.html b/layout/style/test/test_bug221428.html
new file mode 100644
index 0000000000..7e84319462
--- /dev/null
+++ b/layout/style/test/test_bug221428.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=221428
+-->
+<head>
+ <title>Test for Bug 221428</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <link rel="stylesheet" href="data:text/css,body { color: green; }">
+ <style>
+ @import url("data:text/css,body { border: 1px solid transparent; }");
+ body { color: black; }
+ </style>
+ <script>
+ var executed = false;
+ </script>
+ <link rel="stylesheet" href="javascript:executed = true;">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=221428">Mozilla Bug 221428</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 221428 **/
+
+var exceptionThrown = false;
+try {
+ is(document.styleSheets[1].cssRules[0].cssText, "body { color: green; }",
+ "Should get the color: green rule back");
+} catch (e) {
+ exceptionThrown = true;
+}
+
+ok(!exceptionThrown, "Should be able to access data: <link> stylesheet");
+
+exceptionThrown = false;
+try {
+ is(document.styleSheets[2].cssRules[1].cssText, "body { color: black; }",
+ "Should get the color: black rule back");
+} catch (e) {
+ exceptionThrown = true;
+}
+ok(!exceptionThrown, "Should be able to access <style> stylesheet");
+
+exceptionThrown = false;
+try {
+ is(document.styleSheets[2].cssRules[0].styleSheet.cssRules[0].cssText,
+ "body { border: 1px solid transparent; }",
+ "Should get the 'border: 1px solid transparent' rule back");
+} catch (e) {
+ exceptionThrown = true;
+}
+ok(!exceptionThrown, "Should be able to access data: @import stylesheet");
+
+ok(!executed,
+ "Shouldn't be executing stylesheet-link javascript: URIs against " +
+ "the page context");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug229915.html b/layout/style/test/test_bug229915.html
new file mode 100644
index 0000000000..0a23d8b799
--- /dev/null
+++ b/layout/style/test/test_bug229915.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=229915
+-->
+<head>
+ <title>Test for Bug 229915</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ p { color: black; background: transparent; }
+ p.prev + p { color: green; }
+ p.prev ~ p { background: white; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=229915">Mozilla Bug 229915</a>
+<div id="display">
+
+<div>
+ <p id="toinsertbefore">After testing, this should turn green.</p>
+</div>
+
+<div>
+ <p id="toreplace">To be replaced.</p>
+ <p id="replacecolor">After testing, this should turn green.</p>
+</div>
+
+<div>
+ <p class="prev">Previous paragraph.</p>
+ <p id="toremove">To be removed.</p>
+ <p id="removecolor">After testing, this should turn green.</p>
+</div>
+
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 229915 **/
+
+const GREEN = "rgb(0, 128, 0)";
+const BLACK = "rgb(0, 0, 0)";
+const TRANSPARENT = "rgba(0, 0, 0, 0)";
+const WHITE = "rgb(255, 255, 255)";
+
+function make_prev() {
+ var result = document.createElement("p");
+ result.setAttribute("class", "prev");
+ var t = document.createTextNode("Dynamically created previous paragraph.");
+ result.appendChild(t);
+ return result;
+}
+
+function color(id) {
+ return getComputedStyle(document.getElementById(id), "").color;
+}
+function bg(id) {
+ return getComputedStyle(document.getElementById(id), "").backgroundColor;
+}
+
+var node;
+
+// test insert
+is(color("toinsertbefore"), BLACK, "initial state (insertion test)");
+is(bg("toinsertbefore"), TRANSPARENT, "initial state (insertion test)");
+node = document.getElementById("toinsertbefore");
+node.parentNode.insertBefore(make_prev(), node);
+is(color("toinsertbefore"), GREEN, "inserting should turn node green");
+is(bg("toinsertbefore"), WHITE, "inserting should turn background white");
+
+// test replace
+is(color("replacecolor"), BLACK, "initial state (replacement test)");
+is(bg("replacecolor"), TRANSPARENT, "initial state (replacement test)");
+node = document.getElementById("toreplace");
+node.parentNode.replaceChild(make_prev(), node);
+is(color("replacecolor"), GREEN, "replacing should turn node green");
+is(bg("replacecolor"), WHITE, "replacing should turn background white");
+
+// test remove
+is(color("removecolor"), BLACK, "initial state (removal test)");
+is(bg("removecolor"), WHITE, "initial state (removal test; no change)");
+node = document.getElementById("toremove");
+node.remove();
+is(color("removecolor"), GREEN, "removing should turn node green");
+is(bg("removecolor"), WHITE, "removing should leave background");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug302186.html b/layout/style/test/test_bug302186.html
new file mode 100644
index 0000000000..28ff676ff0
--- /dev/null
+++ b/layout/style/test/test_bug302186.html
@@ -0,0 +1,508 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=302186
+-->
+<head>
+ <title>Test for Bug 302186</title>
+ <script type="text/javascript" src="/MochiKit/Base.js"></script>
+ <script type="text/javascript" src="/MochiKit/DOM.js"></script>
+ <script type="text/javascript" src="/MochiKit/Style.js"></script>
+ <script type="text/javascript" src="/MochiKit/Color.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<style>
+
+
+span { color: red }
+:default + span { color: green }
+
+span.reverse { color: green }
+:default + span.reverse { color: red }
+
+button { display: none }
+input { display: none }
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=302186">Mozilla Bug 302186</a>
+<p id="display"></p>
+<div id="content" style="display: block">
+
+ <!-- static default 1 -->
+ <form>
+ <div>
+ <input type="submit" checked="checked"><span id="s1a">There should be no red.</span>
+ </div>
+ <div>
+ <input type="submit"><span id="s1b" class="reverse">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- static default 2 -->
+ <form>
+ <div>
+ <button type="submit" checked="checked" id="foo"></button>
+ <span id="s2a">There should be no red.</span>
+ </div>
+ <div>
+ <button type="submit"></button>
+ <span class="reverse" id="s2b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- static default 3 -->
+ <form>
+ <div>
+ <input type="checkbox" checked="checked" id="foo">
+ <span id="s3a">There should be no red.</span>
+ </div>
+ <div>
+ <input checked="checked">
+ <span class="reverse" id="s3b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- static default 3 -->
+ <form>
+ <div>
+ <input type="radio" checked="checked" id="foo">
+ <span id="s4a">There should be no red.</span>
+ </div>
+ <div>
+ <input checked="checked">
+ <span class="reverse" id="s4b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- static default 5 -->
+ <form>
+ <div>
+ <input type="image"><span id="s5a">There should be no red.</span>
+ </div>
+ <div>
+ <input type="image"><span id="s5b" class="reverse">There should be no red.</span>
+
+ </div>
+ </form>
+
+ <!-- dynamic default 1 -->
+ <form>
+ <div>
+ <input type="submit" checked="checked" id="foo1">
+ <span class="reverse" id="1a">There should be no red.</span>
+ </div>
+ <div>
+ <input type="submit">
+ <span id="1b">There should be no red.</span>
+
+ </div>
+ </form>
+
+ <!-- dynamic default 2 -->
+ <form>
+ <div>
+ <button type="submit" checked="checked" id="foo2"></button>
+ <span class="reverse" id="2a">There should be no red.</span>
+ </div>
+ <div>
+ <button type="submit"></button>
+ <span id="2b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- dynamic default 3 -->
+ <form>
+ <div>
+ <input type="checkbox" checked="checked" id="foo3">
+ <span class="reverse" id="3a">There should be no red.</span>
+ </div>
+ <div>
+ <input checked="checked" id="bar3">
+ <span id="3b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- dynamic default 4 -->
+ <form>
+ <div>
+ <input type="radio" checked="checked" id="foo4">
+ <span class="reverse" id="4a" >There should be no red.</span>
+ </div>
+ <div>
+ <input checked="checked" id="bar4">
+ <span id="4b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- dynamic default 5 -->
+ <form>
+ <div>
+ <input type="submit">
+ <input type="radio" checked="checked" id="foo5">
+ <span id="5" class="reverse">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- dynamic default 6 -->
+ <form>
+ <div id="div6">
+ <span id="6a">There should be no red.</span>
+</div>
+<div>
+ <input type="submit"><span id="6b" class="reverse">There should be no red.</span>
+</div>
+ </form>
+
+ <!-- dynamic default 7 -->
+ <form>
+<div>
+ <input type="submit"><span id="7a">There should be no red.</span>
+</div>
+<div id="div7">
+ <span class="reverse" id="7b">There should be no red.</span>
+
+</div>
+</form>
+
+ <!-- dynamic default 8 -->
+<form>
+<div id="div8"><span id="8a">There should be no red.</span>
+</div>
+<div>
+ <input type="image" id="foo"><span class="reverse" id="8b">There should be no red.</span>
+
+</div>
+</form>
+
+ <!-- dynamic default 9 -->
+<form>
+<div>
+ <input type="image"><span id="9a">There should be no red.</span>
+</div>
+<div id="div9">
+ <span class="reverse" id="9b">There should be no red.</span>
+
+</div>
+</form>
+
+ <!-- dynamic default 10 -->
+<form>
+<div id="div10">
+ <input type="submit"><span id="10a" class="reverse">There should be no red.</span>
+</div>
+<div>
+ <input type="submit"><span id="10b" >There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 11 -->
+<form>
+<div id="div11a">
+ <input type="submit"><span id="11a">There should be no red.</span>
+</div>
+<div id="div11">
+ <input type="submit"><span id="11b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 12 -->
+<form>
+<div id="div12">
+ <input type="image"><span id="12a" class="reverse">There should be no red.</span>
+</div>
+<div>
+ <input type="image"><span id="12b">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 13 -->
+<form>
+<div id="div13a">
+ <input type="image"><span id="13a">There should be no red.</span>
+</div>
+<div id="div13">
+ <input type="image"><span id="13b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 14 -->
+<form>
+<div id="div14a">
+ <input type="submit" id="foo14"><span id="14a">There should be no red.</span>
+</div>
+<div id="div14b">
+ <input type="submit" id="foo14b"><span id="14b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 15 -->
+<form>
+<div id="div15a">
+ <input type="image" id="foo15a"><span id="15a">There should be no red.</span>
+</div>
+<div id="div15b">
+ <input type="image" id="foo15b"><span id="15b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 16 -->
+<form>
+<div>
+ <input type="image" checked="checked" id="foo16"></button>
+ <span class="reverse" id="16a">There should be no red.</span>
+</div>
+<div>
+ <input type="image"></button><span id="16b">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 17 -->
+<form>
+<div>
+ <button type="button" id="foo17"></button>
+ <span id="17a">There should be no red.</span>
+</div>
+<div>
+ <button type="submit"></button><span class="reverse" id="17b">There should be no red.</span>
+</div>
+</form>
+
+<!-- dynamic default 18 -->
+<form>
+<div>
+ <input type="button" id="foo18"></button>
+ <span id="18a">There should be no red.</span>
+</div>
+<div>
+ <input type="submit"></button><span id="18b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 19 -->
+<form>
+<div id="div19">
+ <span id="19a">There should be no red.</span>
+</div>
+</form>
+
+<!-- dynamic default 20 -->
+<form>
+<div id="div20">
+ <span id="20a">There should be no red.</span>
+</div>
+</form>
+
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 302186 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function idColor(anId) {
+ var color = Color.fromComputedStyle(anId, "color");
+ return color.toRGBString();
+}
+
+is(idColor("s1a"),"rgb(0,128,0)", "CSS static-default 1a");
+is(idColor("s1b"),"rgb(0,128,0)", "CSS static-default 1b");
+is(idColor("s2a"),"rgb(0,128,0)", "CSS static-default 2a");
+is(idColor("s2b"),"rgb(0,128,0)", "CSS static-default 2b");
+is(idColor("s3a"),"rgb(0,128,0)", "CSS static-default 3a");
+is(idColor("s3b"),"rgb(0,128,0)", "CSS static-default 3b");
+is(idColor("s4a"),"rgb(0,128,0)", "CSS static-default 4a");
+is(idColor("s4b"),"rgb(0,128,0)", "CSS static-default 4b");
+is(idColor("s5a"),"rgb(0,128,0)", "CSS static-default 5a");
+is(idColor("s5b"),"rgb(0,128,0)", "CSS static-default 5b");
+
+function dynamicDefault1() {
+ $('foo1').removeAttribute("type");
+ is(idColor("1a"),"rgb(0,128,0)", "CSS dynamic-default 1a");
+ is(idColor("1b"),"rgb(0,128,0)", "CSS dynamic-default 1b");
+}
+
+function dynamicDefault2() {
+ $('foo2').setAttribute("type", "button");
+ is(idColor("2a"),"rgb(0,128,0)", "CSS dynamic-default 2a");
+ is(idColor("2b"),"rgb(0,128,0)", "CSS dynamic-default 2b");
+}
+
+function dynamicDefault3() {
+ $('foo3').removeAttribute("type");
+ $('bar3').setAttribute("type", "checkbox");
+ is(idColor("3a"),"rgb(0,128,0)", "CSS dynamic-default 3a");
+ is(idColor("3b"),"rgb(0,128,0)", "CSS dynamic-default 3b");
+}
+
+function dynamicDefault4() {
+ $('foo4').removeAttribute("type");
+ $('bar4').setAttribute("type", "radio");
+ is(idColor("4a"),"rgb(0,128,0)", "CSS dynamic-default 4a");
+ is(idColor("4b"),"rgb(0,128,0)", "CSS dynamic-default 4b");
+}
+
+function dynamicDefault5() {
+ $('foo5').setAttribute("type", "submit")
+ is(idColor("5"),"rgb(0,128,0)", "CSS dynamic-default 5");
+}
+
+function dynamicDefault6() {
+ var but = document.createElement("input");
+ but.setAttribute("type", "submit");
+ $('div6').insertBefore(but, $('div6').firstChild);
+ is(idColor("6a"),"rgb(0,128,0)", "CSS dynamic-default 6a");
+ is(idColor("6b"),"rgb(0,128,0)", "CSS dynamic-default 6b");
+}
+
+function dynamicDefault7() {
+ var but = document.createElement("input");
+ but.setAttribute("type", "submit");
+ $('div7').insertBefore(but, $('div7').firstChild);
+ is(idColor("7a"),"rgb(0,128,0)", "CSS dynamic-default 7a");
+ is(idColor("7b"),"rgb(0,128,0)", "CSS dynamic-default 7b");
+}
+
+function dynamicDefault8() {
+ var but = document.createElement("input");
+ but.setAttribute("type", "image");
+ $('div8').insertBefore(but, $('div8').firstChild);
+ is(idColor("8a"),"rgb(0,128,0)", "CSS dynamic-default 8a");
+ is(idColor("8b"),"rgb(0,128,0)", "CSS dynamic-default 8b");
+}
+
+function dynamicDefault9() {
+ var but = document.createElement("input");
+ but.setAttribute("type", "image");
+ $('div9').insertBefore(but, $('div9').firstChild);
+ is(idColor("9a"),"rgb(0,128,0)", "CSS dynamic-default 9a");
+ is(idColor("9b"),"rgb(0,128,0)", "CSS dynamic-default 9b");
+}
+
+function dynamicDefault10() {
+ var inputs = $('div10').getElementsByTagName("input");
+ $('div10').removeChild(inputs[0]);
+ is(idColor("10a"),"rgb(0,128,0)", "CSS dynamic-default 10a");
+ is(idColor("10b"),"rgb(0,128,0)", "CSS dynamic-default 10b");
+}
+
+function dynamicDefault11() {
+ var inputs = $('div11').getElementsByTagName("input");
+ $('div11').removeChild(inputs[0]);
+ is(idColor("11a"),"rgb(0,128,0)", "CSS dynamic-default 11a");
+ is(idColor("11b"),"rgb(0,128,0)", "CSS dynamic-default 11b");
+}
+
+function dynamicDefault12() {
+ var inputs = $('div12').getElementsByTagName("input");
+ $('div12').removeChild(inputs[0]);
+ is(idColor("12a"),"rgb(0,128,0)", "CSS dynamic-default 12a");
+ is(idColor("12b"),"rgb(0,128,0)", "CSS dynamic-default 12b");
+}
+
+function dynamicDefault13() {
+ var inputs = $('div13').getElementsByTagName("input");
+ $('div13').removeChild(inputs[0]);
+ is(idColor("13a"),"rgb(0,128,0)", "CSS dynamic-default 13a");
+ is(idColor("13b"),"rgb(0,128,0)", "CSS dynamic-default 13b");
+}
+
+function dynamicDefault14() {
+ var div1 = document.getElementById("div14a");
+ var inputs = div1.getElementsByTagName("input");
+ var firstElement = div1.removeChild(inputs[0]);
+ var div2 = document.getElementById("div14b");
+ inputs = div2.getElementsByTagName("input");
+ var secondElement = div2.removeChild(inputs[0]);
+ div1.insertBefore(secondElement, div1.firstChild);
+ div2.insertBefore(firstElement, div2.firstChild);
+ is(idColor("14a"),"rgb(0,128,0)", "CSS dynamic-default 14a");
+ is(idColor("14b"),"rgb(0,128,0)", "CSS dynamic-default 14b");
+}
+
+function dynamicDefault15() {
+ var div1 = document.getElementById("div15a");
+ var inputs = div1.getElementsByTagName("input");
+ var firstElement = div1.removeChild(inputs[0]);
+ var div2 = document.getElementById("div15b");
+ inputs = div2.getElementsByTagName("input");
+ var secondElement = div2.removeChild(inputs[0]);
+ div1.insertBefore(secondElement, div1.firstChild);
+ div2.insertBefore(firstElement, div2.firstChild);
+ is(idColor("15a"),"rgb(0,128,0)", "CSS dynamic-default 15a");
+ is(idColor("15b"),"rgb(0,128,0)", "CSS dynamic-default 15b");
+}
+
+function dynamicDefault16() {
+ $("foo16").setAttribute("type", "button");
+ is(idColor("16a"),"rgb(0,128,0)", "CSS dynamic-default 16a");
+ is(idColor("16b"),"rgb(0,128,0)", "CSS dynamic-default 16b");
+}
+
+function dynamicDefault17() {
+ $("foo17").setAttribute("type", "submit");
+ is(idColor("17a"),"rgb(0,128,0)", "CSS dynamic-default 17a");
+ is(idColor("17b"),"rgb(0,128,0)", "CSS dynamic-default 17b");
+}
+
+function dynamicDefault18() {
+ $("foo18").setAttribute("type", "submit");
+ is(idColor("18a"),"rgb(0,128,0)", "CSS dynamic-default 18a");
+ is(idColor("18b"),"rgb(0,128,0)", "CSS dynamic-default 18b");
+}
+
+function dynamicDefault19() {
+ var newSubmit = document.createElement("input");
+ newSubmit.setAttribute("type", "submit");
+ var div1 = document.getElementById("div19");
+ div1.insertBefore(newSubmit, div1.firstChild);
+ is(idColor("19a"),"rgb(0,128,0)", "CSS dynamic-default 19a");
+}
+
+function dynamicDefault20() {
+ var newSubmit = document.createElement("input");
+ newSubmit.setAttribute("type", "image");
+ var div1 = document.getElementById("div20");
+ div1.insertBefore(newSubmit, div1.firstChild);
+ is(idColor("20a"),"rgb(0,128,0)", "CSS dynamic-default 20a");
+}
+
+addLoadEvent(dynamicDefault1);
+addLoadEvent(dynamicDefault2);
+addLoadEvent(dynamicDefault3);
+addLoadEvent(dynamicDefault4);
+addLoadEvent(dynamicDefault5);
+addLoadEvent(dynamicDefault6);
+addLoadEvent(dynamicDefault7);
+addLoadEvent(dynamicDefault8);
+addLoadEvent(dynamicDefault9);
+addLoadEvent(dynamicDefault10);
+addLoadEvent(dynamicDefault11);
+addLoadEvent(dynamicDefault12);
+addLoadEvent(dynamicDefault13);
+addLoadEvent(dynamicDefault14);
+addLoadEvent(dynamicDefault15);
+addLoadEvent(dynamicDefault16);
+addLoadEvent(dynamicDefault17);
+addLoadEvent(dynamicDefault18);
+addLoadEvent(dynamicDefault19);
+addLoadEvent(dynamicDefault20);
+
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug319381.html b/layout/style/test/test_bug319381.html
new file mode 100644
index 0000000000..d29f1bbd39
--- /dev/null
+++ b/layout/style/test/test_bug319381.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=319381
+-->
+<head>
+ <title>Test for Bug 319381</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=319381">Mozilla Bug 319381</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="t"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 319381 **/
+
+function c() {
+ return document.defaultView.getComputedStyle($('t')).
+ getPropertyValue("overflow");
+}
+
+var vals = ["visible", "hidden", "auto", "scroll"];
+var mozVals = ["-moz-scrollbars-vertical", "-moz-scrollbars-horizontal"];
+var i, j;
+
+for (i = 0; i < vals.length; ++i) {
+ $('t').style.overflow = vals[i];
+ is($('t').style.overflow, vals[i], "Roundtrip");
+ is(c(), vals[i], "Simple property set");
+}
+
+for (i = 0; i < vals.length; ++i) {
+ for (j = 0; j < vals.length; ++j) {
+ $('t').setAttribute("style",
+ "overflow-x: " + vals[i] + "; overflow-y: " + vals[j]);
+ is($('t').style.getPropertyValue("overflow-x"), vals[i], "Roundtrip");
+ is($('t').style.getPropertyValue("overflow-y"), vals[j], "Roundtrip");
+
+ if (i == j) {
+ is($('t').style.overflow, vals[i], "Shorthand serialization");
+ } else {
+ is($('t').style.overflow, vals[i] + " " + vals[j], "Shorthand serialization");
+ }
+
+ // "visible" overflow-x and overflow-y become "auto" in computed style if
+ // the other direction is not also "visible".
+ if (i == j || (vals[i] == "visible" && vals[j] == "auto")) {
+ is(c(), vals[j], "Shorthand computation");
+ } else if (vals[j] == "visible" && vals[i] == "auto") {
+ is(c(), vals[i], "Shorthand computation");
+ } else {
+ let x = vals[i] == "visible" ? "auto" : vals[i];
+ let y = vals[j] == "visible" ? "auto" : vals[j];
+ is(c(), x + " " + y, "Shorthand computation");
+ }
+ }
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug357614.html b/layout/style/test/test_bug357614.html
new file mode 100644
index 0000000000..37475512d4
--- /dev/null
+++ b/layout/style/test/test_bug357614.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=357614
+-->
+<head>
+ <title>Test for Bug 357614</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style type="text/css" id="style">
+ a { color: red; }
+ a { color: green; }
+ </style>
+ <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=357614">Mozilla Bug 357614</a>
+<p id="display"><a href="http://www.FOO.com/" rel="next" rev="PREV" foo="bar">a link</a></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 357614 **/
+
+var sheet = document.getElementById("style").sheet;
+var rule1 = sheet.cssRules[0];
+var rule2 = sheet.cssRules[1];
+
+var a = document.getElementById("display").firstChild;
+var cs = getComputedStyle(a, "");
+
+function change_selector_text(selector) {
+ // rule2.selectorText = selector; // NOT IMPLEMENTED
+
+ sheet.deleteRule(1);
+ sheet.insertRule(selector + " { color: green; }", 1);
+}
+
+var cs_green = cs.getPropertyValue("color");
+change_selector_text('p');
+var cs_red = cs.getPropertyValue("color");
+isnot(cs_green, cs_red, "computed values for green and red are different");
+
+change_selector_text('a[href="http://www.FOO.com/"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on href value matches case-sensitively");
+
+change_selector_text('a[href="http://www.foo.com/"]');
+is(cs.getPropertyValue("color"), cs_red, "selector on href value does not match case-insensitively");
+
+change_selector_text('a[rel="next"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on rel value matches case-sensitively");
+
+change_selector_text('a[rel="NEXT"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on rel value matches case-insensitively");
+
+change_selector_text('a[rev="PREV"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on rev value matches case-sensitively");
+
+change_selector_text('a[rev="prev"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on rev value matches case-insensitively");
+
+change_selector_text('a[foo="bar"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on foo value matches case-sensitively");
+
+change_selector_text('a[foo="Bar"]');
+is(cs.getPropertyValue("color"), cs_red, "selector on foo value does not match case-insensitively");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug363146.html b/layout/style/test/test_bug363146.html
new file mode 100644
index 0000000000..09ed45a8e2
--- /dev/null
+++ b/layout/style/test/test_bug363146.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=363146
+-->
+<head>
+ <title>Test for Bug 363146</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=363146">Mozilla Bug 363146</a>
+<div style="width:100px; height:400px; position:relative;">
+ <table id="t1" border="0" cellspacing="0" cellpadding="0"
+ style="border:10px solid black; margin:20px; position:absolute; left:50px; top:35px;">
+ <caption align="top" style="height:100px;">Caption</caption>
+ <tr>
+ <td><div style="width:400px; height:100px;">Cell</div></td>
+ </tr>
+ </table>
+</div>
+<div style="width:100px; height:400px; position:relative;">
+ <table id="t2" border="0" cellspacing="0" cellpadding="0"
+ style="margin:20%;">
+ <caption align="top" style="height:100px;">Caption</caption>
+ <tr>
+ <td><div style="width:400px; height:100px;">Cell</div></td>
+ </tr>
+ </table>
+</div>
+<p id="display"></p>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var c = window.getComputedStyle(document.getElementById("t1"));
+is(c.width, "420px");
+is(c.height, "120px");
+is(c.left, "50px");
+is(c.top, "35px");
+is(c.borderLeftWidth, "10px");
+is(c.borderRightWidth, "10px");
+is(c.borderTopWidth, "10px");
+is(c.borderBottomWidth, "10px");
+is(c.marginLeft, "20px");
+is(c.marginRight, "20px");
+is(c.marginTop, "20px");
+is(c.marginBottom, "20px");
+
+var c2 = window.getComputedStyle(document.getElementById("t2"));
+is(c2.marginLeft, "20px");
+is(c2.marginRight, "20px");
+is(c2.marginTop, "20px");
+is(c2.marginBottom, "20px");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug372770.html b/layout/style/test/test_bug372770.html
new file mode 100644
index 0000000000..ac3879c9d4
--- /dev/null
+++ b/layout/style/test/test_bug372770.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372770
+-->
+<head>
+ <title>Test for Bug 372770</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style id="testStyle">
+ #content {}
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372770">Mozilla Bug 372770</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 372770 **/
+var style1 = $("content").style;
+var style2 = $("testStyle").sheet.cssRules[0].style;
+
+var colors = [ "rgb(128, 128, 128)", "transparent" ]
+var i;
+
+for (i = 0; i < colors.length; ++i) {
+ var color = colors[i];
+ style1.color = color;
+ style2.color = color;
+ is(style1.color, color, "Inline style color roundtripping failed at color " + i);
+ is(style2.color, color, "Rule style color roundtripping failed at color " + i);
+}
+
+style1.color = "rgba(0, 0, 0, 0)";
+style2.color = "rgba(0, 0, 0, 0)";
+is(style1.color, "rgba(0, 0, 0, 0)",
+ "Inline style should round-trip black transparent color correctly");
+is(style2.color, "rgba(0, 0, 0, 0)",
+ "Rule style should round-trip black transparent color correctly");
+
+for (var i = 0; i <= 100; ++i) {
+ if (i == 70 || i == 90) {
+ // Tinderbox unhappy for some reason... just skip these for now?
+ continue;
+ }
+ var color1 = "rgba(128, 128, 128, " + i/100 + ")";
+ var color2 = "rgba(175, 63, 27, " + i/100 + ")";
+ style1.color = color1;
+ style1.backgroundColor = color2;
+ style2.color = color2;
+ style2.background = color1;
+
+ if (i == 100) {
+ // Bug 372783 means this doesn't round-trip quite right
+ todo(style1.color == color1,
+ "Inline style color roundtripping failed at opacity " + i);
+ todo(style1.backgroundColor == color2,
+ "Inline style background roundtripping failed at opacity " + i);
+ todo(style2.color == color2,
+ "Rule style color roundtripping failed at opacity " + i);
+ todo(style2.backgroundColor == color1,
+ "Rule style background roundtripping failed at opacity " + i);
+ color1 = "rgb(128, 128, 128)";
+ color2 = "rgb(175, 63, 27)";
+ }
+
+ is(style1.color, color1,
+ "Inline style color roundtripping failed at opacity " + i);
+ is(style1.backgroundColor, color2,
+ "Inline style background roundtripping failed at opacity " + i);
+ is(style2.color, color2,
+ "Rule style color roundtripping failed at opacity " + i);
+ is(style2.backgroundColor, color1,
+ "Rule style background roundtripping failed at opacity " + i);
+
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug373293.html b/layout/style/test/test_bug373293.html
new file mode 100644
index 0000000000..d23c865b67
--- /dev/null
+++ b/layout/style/test/test_bug373293.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=373293
+-->
+<head>
+ <title>Test for Bug 373293</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=373293">Mozilla Bug 373293</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="t" style="color: transparent;"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 373293**/
+
+var actual = $("t").getAttribute("style");
+var expected = "color: transparent;";
+is(actual, expected, "Expected style content did not match the actual style content");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug377947.html b/layout/style/test/test_bug377947.html
new file mode 100644
index 0000000000..88fccd0dc2
--- /dev/null
+++ b/layout/style/test/test_bug377947.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377947
+-->
+<head>
+ <title>Test for Bug 377947</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=377947">Mozilla Bug 377947</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 377947 **/
+
+/*
+ * In particular, test that CSSStyleDeclaration.getPropertyValue doesn't
+ * return values for shorthands when some of the subproperties are not
+ * specified (a change that wasn't all that related to the main point of
+ * the bug). And also test that the internal system-font property added
+ * in bug 377947 doesn't interfere with that.
+ */
+
+var s = document.getElementById("display").style;
+
+is(s.getPropertyValue("list-style"), "",
+ "list-style shorthand should start off empty");
+s.listStyleType="disc";
+s.listStyleImage="none";
+is(s.getPropertyValue("list-style"), "",
+ "list-style shorthand should be empty when some subproperties specified");
+s.listStylePosition="inside";
+isnot(s.getPropertyValue("list-style"), "",
+ "list-style shorthand should produce value when all subproperties set");
+s.removeProperty("list-style");
+is(s.getPropertyValue("list-style"), "",
+ "list-style shorthand be empty after removal");
+s.listStyle="none";
+isnot(s.getPropertyValue("list-style"), "",
+ "list-style shorthand should produce value when shorthand set");
+s.removeProperty("list-style");
+is(s.getPropertyValue("list-style"), "",
+ "list-style shorthand be empty after removal");
+
+is(s.getPropertyValue("font"), "",
+ "font shorthand should start off empty");
+var all_but_one = {
+ "font-family": "serif",
+ "font-style": "normal",
+ "font-variant": "normal",
+ "font-weight": "bold",
+ "font-size": "small",
+ "font-stretch": "normal",
+ "font-size-adjust": "none", // has to be default value
+ "font-feature-settings": "normal", // has to be default value
+ "font-variation-settings": "normal", // has to be default value
+ "font-language-override": "normal", // has to be default value
+ "font-kerning": "auto", // has to be default value
+ "font-optical-sizing": "auto", // has to be default value
+ "font-synthesis": "weight style", // has to be default value
+ "font-variant-alternates": "normal", // has to be default value
+ "font-variant-caps": "normal", // has to be default value
+ "font-variant-east-asian": "normal", // has to be default value
+ "font-variant-emoji": "auto", // has to be default value
+ "font-variant-ligatures": "normal", // has to be default value
+ "font-variant-numeric": "normal", // has to be default value
+ "font-variant-position": "normal" // has to be default value
+};
+
+for (var prop in all_but_one) {
+ s.setProperty(prop, all_but_one[prop], "");
+}
+is(s.getPropertyValue("font"), "",
+ "font shorthand should be empty when some subproperties specified");
+s.setProperty("line-height", "1.5", "");
+isnot(s.getPropertyValue("font"), "",
+ "font shorthand should produce value when all subproperties set");
+s.setProperty("font-size-adjust", "0.5", "");
+is(s.getPropertyValue("font"), "",
+ "font shorthand should be empty when font-size-adjust is non-default");
+s.setProperty("font-size-adjust", "none", "");
+isnot(s.getPropertyValue("font"), "",
+ "font shorthand should produce value when all subproperties set");
+s.removeProperty("font");
+is(s.getPropertyValue("font"), "",
+ "font shorthand be empty after removal");
+s.font="medium serif";
+isnot(s.getPropertyValue("font"), "",
+ "font shorthand should produce value when shorthand set");
+s.removeProperty("font");
+is(s.getPropertyValue("font"), "",
+ "font shorthand be empty after removal");
+s.font="menu";
+isnot(s.getPropertyValue("font"), "",
+ "font shorthand should produce value when shorthand (system font) set");
+s.removeProperty("font");
+is(s.getPropertyValue("font"), "",
+ "font shorthand be empty after removal");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug379440.html b/layout/style/test/test_bug379440.html
new file mode 100644
index 0000000000..56644f85cb
--- /dev/null
+++ b/layout/style/test/test_bug379440.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=379440
+-->
+<head>
+ <title>Test for Bug 379440</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ #display > * { cursor: auto }
+ #t1 {
+ cursor: url(file:///tmp/foo), url(file:///c|/),
+ url(http://example.com/), crosshair;
+ }
+ #t2 {
+ cursor: url(file:///tmp/foo), url(file:///c|/), crosshair;
+ }
+ #t3 {
+ cursor: url(http://example.com/), crosshair;
+ }
+ #t4 {
+ cursor: url(http://example.com/);
+ }
+ #t5 {
+ cursor: url(http://example.com/), no-such-cursor-exists;
+ }
+ #t6 {
+ cursor: crosshair;
+ }
+ #t7 {
+ cursor: no-such-cursor-exists;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=379440">Mozilla Bug 379440</a>
+<p id="display">
+ <div id="t1"> </div>
+ <div id="t2"></div>
+ <div id="t3"></div>
+ <div id="t4"></div>
+ <div id="t5"></div>
+ <div id="t6"></div>
+ <div id="t7"></div>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 379440 **/
+
+function cur(id) {
+ return document.defaultView.getComputedStyle($(id)).cursor;
+}
+
+is(cur("t1"), 'url("file:///tmp/foo"), url("file:///c:/"), ' +
+ 'url("http://example.com/"), crosshair',
+ "Serialize unloadable URLs using their specified value");
+is(cur("t2"), 'url("file:///tmp/foo"), url("file:///c:/"), crosshair',
+ "Serialize unloadable URLs using their specified value");
+is(cur("t3"), 'url("http://example.com/"), crosshair', "URI + fallback");
+is(cur("t4"), "auto", "Must have a fallback");
+is(cur("t5"), "auto", "Fallback must be recognized");
+is(cur("t6"), "crosshair", "Just a fallback");
+is(cur("t7"), "auto", "Invalid fallback means ignore");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug379741.html b/layout/style/test/test_bug379741.html
new file mode 100644
index 0000000000..7bb2463ce7
--- /dev/null
+++ b/layout/style/test/test_bug379741.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=379741
+-->
+<head>
+ <title>Test for Bug 379741</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=379741">Mozilla Bug 379741</a>
+<div id="content" style="display: none">
+<div id="noframe"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 379741 **/
+
+var cs = getComputedStyle(document.getElementById("noframe"), "");
+ok(cs.marginTop == "0" || cs.marginTop == "0px",
+ "computed margin-top is not none");
+ok(cs.marginRight == "0" || cs.marginRight == "0px",
+ "computed margin-right is not none");
+ok(cs.marginBottom == "0" || cs.marginBottom == "0px",
+ "computed margin-bottom is not none");
+ok(cs.marginLeft == "0" || cs.marginLeft == "0px",
+ "computed margin-left is not none");
+ok(cs.paddingTop == "0" || cs.paddingTop == "0px",
+ "computed padding-top is not none");
+ok(cs.paddingRight == "0" || cs.paddingRight == "0px",
+ "computed padding-right is not none");
+ok(cs.paddingBottom == "0" || cs.paddingBottom == "0px",
+ "computed padding-bottom is not none");
+ok(cs.paddingLeft == "0" || cs.paddingLeft == "0px",
+ "computed padding-left is not none");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug382027.html b/layout/style/test/test_bug382027.html
new file mode 100644
index 0000000000..795a17048a
--- /dev/null
+++ b/layout/style/test/test_bug382027.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=382027
+-->
+<head>
+ <title>Test for Bug 382027</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=382027">Mozilla Bug 382027</a>
+<div id="content" style="display: none;"
+ ><div style="border-style: groove none none;"
+ ><div style="border-style: none none double"
+ ></div
+ ></div
+></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/*
+ * Test that the regression in the original patch checked in by bug
+ * 382027 is caught by something other than an unexpected pass.
+ */
+
+var e = document.createElement("div");
+e.style.setProperty("border-style", "groove none none none", "");
+is(e.getAttribute("style"), "border-style: groove none none;");
+e.style.setProperty("border-style", "none none double none", "");
+is(e.getAttribute("style"), "border-style: none none double;");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug383075.html b/layout/style/test/test_bug383075.html
new file mode 100644
index 0000000000..8d902103e9
--- /dev/null
+++ b/layout/style/test/test_bug383075.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=383075
+-->
+<head>
+ <title>Test for bug 383075</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style type="text/css">
+
+ html,body {
+ color:black; background-color:white; font-size:16px; font-family: Arial;
+ }
+
+
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=383075">Mozilla bug 383075</a>
+<p id="display">
+
+The X'es below should have the same size:<br>
+
+<span id="a1" style="font-size:72px;">X</span>
+<span id="a2" style="font-size:72px;">X</span>
+<span id="a3" style="font-size:72px;">X</span>
+<span id="a4" style="font-size:72px;">X</span>
+<span id="a5" style="font-size:72px;">X</span>
+<span id="a6" style="font-size:72px;">X</span>
+<span id="a7" style="font-size:24px;">X</span>
+<span id="a8" style="font-size:72px;">X</span>
+<span id="a9" style="font:24px Arial;">X</span>
+
+<br>
+
+<span id="b1" style="font-size:72px;">X</span>
+<span id="b2" style="font-size:72px;">X</span>
+<span id="b3" style="font-size:72px;">X</span>
+<span id="b4" style="font-size:72px;">X</span>
+<span id="b5" style="font-size:72px;">X</span>
+<span id="b6" style="font-size:72px;">X</span>
+<span id="b7" style="font-size:24px;">X</span>
+<span id="b8" style="font-size:72px;">X</span>
+<span id="b9" style="font:24px Arial;">X</span>
+</p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ document.getElementById("a1").style.fontSize = "illegal";
+ document.getElementById("a2").style.fontSize = "24px;";
+ document.getElementById("a3").style.fontSize = "24px; font-size-adjust:2";
+ document.getElementById("a4").style.fontSize = ";";
+ document.getElementById("a5").style.font = "24px Arial;";
+ document.getElementById("a6").style.font = "24px;";
+ document.getElementById("a7").style.fontSize = " 72px "; // correct
+ document.getElementById("a8").style.font = ";";
+ document.getElementById("a9").style.font = " 72px Arial "; // correct
+
+ document.getElementById("b1").style.setProperty("font-size", "illegal", null);
+ document.getElementById("b2").style.setProperty("font-size", "24px;", null);
+ document.getElementById("b3").style.setProperty("font-size", "24px; font-size-adjust:2", null);
+ document.getElementById("b4").style.setProperty("font-size", ";", null);
+ document.getElementById("b5").style.setProperty("font", "24px Arial;", null);
+ document.getElementById("b6").style.setProperty("font", "24px;", null);
+ document.getElementById("b7").style.setProperty("font-size", " 72px ", null); // correct
+ document.getElementById("b8").style.setProperty("font", ";", null);
+ document.getElementById("b9").style.setProperty("font", " 72px Arial ", null); // correct
+
+
+for (i=1; i <= 9; ++i)
+ is($('a'+i).style.fontSize, '72px', "font size");
+
+for (i=1; i <= 9; ++i)
+ is($('b'+i).style.fontSize, '72px', "font size");
+
+
+</script>
+</pre>
+
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug387615.html b/layout/style/test/test_bug387615.html
new file mode 100644
index 0000000000..eec7109289
--- /dev/null
+++ b/layout/style/test/test_bug387615.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=387615
+-->
+<head>
+ <title>Test for Bug 387615</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ @namespace html url(http://www.w3.org/1999/xhtml);
+ a { color: red; }
+ a[rel="next"] { color: green; }
+ a[html|rel="next"] { color: green; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=387615">Mozilla Bug 387615</a>
+<p id="display"><a>link</a></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 387615 **/
+
+var htmlns = "http://www.w3.org/1999/xhtml";
+
+var a = document.getElementById("display").firstChild;
+
+function col(elt) { return getComputedStyle(elt, "").color; }
+
+var red_cs = col(a);
+a.setAttribute("rel", "next");
+var green_cs = col(a);
+isnot(green_cs, red_cs, "computed values for red and green are different");
+
+a.setAttribute("rel", "NEXT");
+is(col(a), green_cs, "rel attribute should match case insensitively");
+
+a.removeAttribute("rel");
+a.setAttributeNS(htmlns, "html:rel", "next");
+is(col(a), green_cs, "html:rel attribute should match case-sensitively");
+
+a.setAttributeNS(htmlns, "html:rel", "NEXT");
+is(col(a), red_cs, "html:rel attribute should not match case-insensitively");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug389464.html b/layout/style/test/test_bug389464.html
new file mode 100644
index 0000000000..2e05ed848f
--- /dev/null
+++ b/layout/style/test/test_bug389464.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <!-- above is to force x-western language group -->
+ <title>Test for preference not to use document colors</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=58048">Mozilla Bug 58048</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=255411">Mozilla Bug 255411</a>
+<div id="display">
+
+<pre><font id="one" size="-1">text</font></pre>
+<p><font id="two" size="-1">text</font></p>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var cs1 = getComputedStyle(document.getElementById("one"), "");
+var cs2 = getComputedStyle(document.getElementById("two"), "");
+
+SpecialPowers.pushPrefEnv({'set': [['variable.x-western', 25], ['fixed.x-western', 20]]}, part1);
+
+function part1()
+{
+ var fs1 = cs1.fontSize.match(/(.*)px/)[1];
+ var fs2 = cs2.fontSize.match(/(.*)px/)[1];
+ ok(fs1 < fs2, "<font size=-1> shrinks relative to font-family: -moz-fixed");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug391034.html b/layout/style/test/test_bug391034.html
new file mode 100644
index 0000000000..f9544035b1
--- /dev/null
+++ b/layout/style/test/test_bug391034.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=391034
+-->
+<head>
+ <title>Test for Bug 391034</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391034">Mozilla Bug 391034</a>
+<div id="display" style="width: 90px; height: 80px">
+ <div id="width-ref" style="width: 2ch"></div>
+ <div id="width-ref2" style="width: 5ch"></div>
+ <div id="one" style="position: relative; left: 2ch; bottom: 5ch"></div>
+ <div id="two" style="position: relative; left: 10%; bottom: 20%"></div>
+ <div id="three" style="position: relative; left: 10px; bottom: 6px"></div>
+</div>
+<div id="content" style="display: none">
+ <div id="four" style="position: relative; left: 10%; bottom: 20%"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 391034 **/
+function getComp(id) {
+ return document.defaultView.getComputedStyle($(id));
+}
+
+is(getComp("one").top, "-" + getComp("width-ref2").width,
+ "Incorrect computed top offset if specified in ch")
+is(getComp("one").right, "-" + getComp("width-ref").width,
+ "Incorrect computed right offset if specified in ch")
+is(getComp("one").bottom, getComp("width-ref2").width,
+ "Incorrect computed bottom offset if specified in ch")
+is(getComp("one").left, getComp("width-ref").width,
+ "Incorrect computed left offset if specified in ch")
+
+is(getComp("two").top, "-16px",
+ "Incorrect computed top offset if specified in %")
+is(getComp("two").right, "-9px",
+ "Incorrect computed right offset if specified in %")
+is(getComp("two").bottom, "16px",
+ "Incorrect computed bottom offset if specified in %")
+is(getComp("two").left, "9px",
+ "Incorrect computed left offset if specified in %")
+
+is(getComp("three").top, "-6px",
+ "Incorrect computed top offset if specified in %")
+is(getComp("three").right, "-10px",
+ "Incorrect computed right offset if specified in %")
+is(getComp("three").bottom, "6px",
+ "Incorrect computed bottom offset if specified in %")
+is(getComp("three").left, "10px",
+ "Incorrect computed left offset if specified in %")
+
+is(getComp("four").top, "auto",
+ "Incorrect undisplayed computed top offset if specified in %")
+is(getComp("four").right, "auto",
+ "Incorrect undisplayed computed right offset if specified in %")
+is(getComp("four").bottom, "20%",
+ "Incorrect undisplayed computed bottom offset if specified in %")
+is(getComp("four").left, "10%",
+ "Incorrect undisplayed computed left offset if specified in %")
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug391221.html b/layout/style/test/test_bug391221.html
new file mode 100644
index 0000000000..bf78711197
--- /dev/null
+++ b/layout/style/test/test_bug391221.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=391221
+-->
+<head>
+ <title>Test for Bug 391221</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391221">Mozilla Bug 391221</a>
+<p id="display">
+ <div id="width-ref" style="width: 2ch"></div>
+</p>
+<div id="content">
+
+<div id="one" style="width: 1000px; max-width: 2ch"></div>
+<div id="two" style="width: 0px; min-width: 2ch"></div>
+<div id="three" style="width: 1000ch; max-width: 2px"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 391221 **/
+function getComp(id) {
+ return document.defaultView.getComputedStyle($(id));
+}
+
+is(getComp("one").width, getComp("width-ref").width,
+ "max-width in ch units not working?");
+
+is(getComp("two").width, getComp("width-ref").width,
+ "min-width in ch units not working?");
+
+is(getComp("three").width, "2px", "max-width not applied to width in chars?");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug397427.html b/layout/style/test/test_bug397427.html
new file mode 100644
index 0000000000..ff0e71f238
--- /dev/null
+++ b/layout/style/test/test_bug397427.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=397427
+-->
+<head>
+ <title>Test for Bug 397427</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style id="a">
+ @import url("redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-1.css");
+ @import url("redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-2.css");
+ .test { color: red }
+ </style>
+ <link id="b" rel="stylesheet" href="http://example.com">
+ <link id="c" rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-2.css">
+ <link id="d" rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-3.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=397427">Mozilla Bug 397427</a>
+<p id="display">
+<span id="one" class="test"></span>
+<span id="two" class="test"></span>
+<span id="three" class="test"></span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 397427 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is($("a").sheet.href, null, "href should be null");
+ is(typeof($("a").sheet.href), "object", "should be actual null");
+
+ // Make sure the redirected sheets are loaded and have the right base URI
+ is(document.defaultView.getComputedStyle($("one")).color,
+ "rgb(0, 128, 0)", "Redirect 1 did not work");
+ is(document.defaultView.getComputedStyle($("one")).backgroundImage,
+ "url(\"http://example.org/tests/layout/style/test/post-redirect-1.css?1\")",
+ "Redirect 1 did not get right base URI");
+ is(document.defaultView.getComputedStyle($("two")).color,
+ "rgb(0, 128, 0)", "Redirect 2 did not work");
+ is(document.defaultView.getComputedStyle($("two")).backgroundImage,
+ "url(\"http://example.org/tests/layout/style/test/post-redirect-2.css?1\")",
+ "Redirect 2 did not get right base URI");
+ is(document.defaultView.getComputedStyle($("three")).color,
+ "rgb(0, 128, 0)", "Redirect 3 did not work");
+ is(document.defaultView.getComputedStyle($("three")).backgroundImage,
+ "url(\"http://example.org/tests/layout/style/test/post-redirect-3.css?1\")",
+ "Redirect 3 did not get right base URI");
+
+ var ruleList = $("a").sheet.cssRules;
+
+ var redirHrefBase =
+ window.location.href.replace(/test_bug397427.html$/,
+ "redirect.sjs?http://example.org/tests/layout/style/test/post-");
+
+ is(ruleList[0].styleSheet.href, redirHrefBase + "redirect-1.css",
+ "Unexpected href for imported sheet");
+ todo_is(ruleList[0].href, redirHrefBase + "redirect-1.css",
+ "Rule href should be absolute");
+ is(ruleList[1].styleSheet.href, redirHrefBase + "redirect-2.css",
+ "Unexpected href for imported sheet");
+ todo_is(ruleList[1].href, redirHrefBase + "redirect-2.css",
+ "Rule href should be absolute");
+
+ is($("b").href, "http://example.com/", "Unexpected href one");
+ is($("b").href, $("b").sheet.href,
+ "Should have the same href when not redirecting");
+
+ is($("c").href, redirHrefBase + "redirect-2.css",
+ "Unexpected href two");
+ is($("c").href, $("c").sheet.href,
+ "Should have the same href when redirecting");
+
+ is($("d").href, redirHrefBase + "redirect-3.css",
+ "Unexpected href three");
+ is($("d").href, $("d").sheet.href,
+ "Should have the same href when redirecting again");
+})
+
+addLoadEvent(SimpleTest.finish);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug399349.html b/layout/style/test/test_bug399349.html
new file mode 100644
index 0000000000..a36451927f
--- /dev/null
+++ b/layout/style/test/test_bug399349.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=363146
+-->
+<head>
+ <title>Test for Bug 363146</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=399349">Mozilla Bug 399349</a>
+
+<!-- Test parsing of integer numbers -->
+<div id="Aone" style="width:100px; height:400px; top:-100px; left: -200px;position:relative;"></div>
+
+<!-- Test parsing of float numbers -->
+<div id="Atwo" style="width:150.2px; height:450.25px; top:-150.2px; left: -450.25px;position:relative;"></div>
+<div id="Athree" style="width:.1px; height:0.3px; top:-0.1px; left:-0.3px;position:relative;"></div>
+<div id="Afour" style="width:+100.017px; height:+400.017px; top:-.117px; left: -.217px;position:relative;"></div>
+
+<!-- Test parsing of long fractions -->
+<div id="Afive" style="width:+2345.0000000000000000000000000000000000001px; height:+456.000000000000000000000000000001px;
+ top:-2123.000000000000000000000000000000000001px; left:-6543.99999999999999999999999999999999px;
+ position:relative;"></div>
+
+<!-- Force parsing of long numbers (>9 digits), if they are zero's. Note css itself can't handle large numers -->
+<div id="Asix" style="width:+000000000012px; height:+000000000037.456788px;
+ top:-000000000023px; left:-000000000044.456788px;
+ position:relative;"></div>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var a1 = window.getComputedStyle(document.getElementById("Aone"));
+is(a1.width, "100px");
+is(a1.height, "400px");
+is(a1.top, "-100px");
+is(a1.left, "-200px");
+
+var a2 = window.getComputedStyle(document.getElementById("Atwo"));
+is(a2.width, "150.2px");
+is(a2.height, "450.25px");
+is(a2.top, "-150.2px");
+is(a2.left, "-450.25px");
+
+var a3 = window.getComputedStyle(document.getElementById("Athree"));
+is(a3.width, "0.1px");
+is(a3.height, "0.3px");
+is(a3.top, "-0.1px");
+is(a3.left, "-0.3px");
+
+var a4 = window.getComputedStyle(document.getElementById("Afour"));
+is(a4.width, "100.017px");
+is(a4.height, "400.017px");
+is(a4.top, "-0.117px");
+is(a4.left, "-0.217px");
+
+var a5 = window.getComputedStyle(document.getElementById("Afive"));
+is(a5.width, "2345px");
+is(a5.height, "456px");
+is(a5.top, "-2123px");
+is(a5.left, "-6544px");
+
+var a6 = window.getComputedStyle(document.getElementById("Asix"));
+is(a6.width, "12px");
+is(a6.height, "37.45px");
+is(a6.top, "-23px");
+is(a6.left, "-44.4568px");
+
+</script>
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug401046.html b/layout/style/test/test_bug401046.html
new file mode 100644
index 0000000000..e4492a5ca8
--- /dev/null
+++ b/layout/style/test/test_bug401046.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=401046
+-->
+<head>
+ <title>Test for Bug 401046</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ #display span { margin-bottom: 1em; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=401046">Mozilla Bug 401046</a>
+<p id="display" lang="zh-Hans">
+ <span id="s0" style="font-size: 0">汉字</span>
+ <span id="s4" style="font-size: 4px">汉字</span>
+ <span id="s12" style="font-size: 12px">汉字</span>
+ <span id="s28" style="font-size: 28px">汉字</span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 401046 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var elts = [
+ document.getElementById("s0"),
+ document.getElementById("s4"),
+ document.getElementById("s12"),
+ document.getElementById("s28")
+];
+
+function fs(idx) {
+ // The computed font size actually *doesn't* currently reflect the
+ // minimum font size preference, but things in em units do. Not sure
+ // if this is how it ought to be...
+ return getComputedStyle(elts[idx], "").marginBottom;
+}
+
+SpecialPowers.pushPrefEnv({'clear': [['font.minimum-size.zh-CN']]}, step1);
+
+function step1() {
+ is(fs(0), "0px", "at min font size 0, 0px should compute to 0px");
+ is(fs(1), "4px", "at min font size 0, 4px should compute to 4px");
+ is(fs(2), "12px", "at min font size 0, 12px should compute to 12px");
+ is(fs(3), "28px", "at min font size 0, 28px should compute to 28px");
+
+ SpecialPowers.pushPrefEnv({'set': [['font.minimum-size.zh-CN', 7]]}, step2);
+}
+
+function step2() {
+ is(fs(0), "0px", "at min font size 7, 0px should compute to 0px");
+ is(fs(1), "7px", "at min font size 7, 4px should compute to 7px");
+ is(fs(2), "12px", "at min font size 7, 12px should compute to 12px");
+ is(fs(3), "28px", "at min font size 7, 28px should compute to 28px");
+
+ SpecialPowers.pushPrefEnv({'set': [['font.minimum-size.zh-CN', 18]]}, step3);
+}
+
+function step3() {
+ is(fs(0), "0px", "at min font size 18, 0px should compute to 0px");
+ is(fs(1), "18px", "at min font size 18, 4px should compute to 18px");
+ is(fs(2), "18px", "at min font size 18, 12px should compute to 18px");
+ is(fs(3), "28px", "at min font size 18, 28px should compute to 28px");
+
+ SpecialPowers.pushPrefEnv({'clear': [['font.minimum-size.zh-CN']]}, SimpleTest.finish);
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug405818.html b/layout/style/test/test_bug405818.html
new file mode 100644
index 0000000000..eff4da449b
--- /dev/null
+++ b/layout/style/test/test_bug405818.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=405818
+-->
+<head>
+ <title>Test for Bug 405818</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <link rel="stylesheet" type="text/css" href="data:text/css,%23myDiv{color:green;}">
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin/popup.css">
+ <!-- Script to make sure sheets gets a chance to load fully in Gecko 1.8 and earlier -->
+ <script type="text/javascript" src="data:text/javascript,"></script>
+ <link rel="stylesheet" type="text/css" href="data:text/css,%23myDiv{color:green;}">
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin/popup.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=405818">Mozilla Bug 405818</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="myDiv"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 405818 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is(document.styleSheets[1].href,
+ "data:text/css,%23myDiv{color:green;}",
+ "Unexpected href for linked sheet before cloning");
+ is(document.styleSheets[3].href,
+ "data:text/css,%23myDiv{color:green;}",
+ "Unexpected href for later linked sheet before cloning");
+
+ is(document.styleSheets[2].href,
+ "chrome://global/skin/popup.css",
+ "Unexpected href for linked chrome sheet before cloning");
+ is(document.styleSheets[4].href,
+ "chrome://global/skin/popup.css",
+ "Unexpected href for later linked chrome sheet before cloning");
+
+ // Force cloning of inners
+ document.styleSheets[1].cssRules[0];
+ SpecialPowers.wrap(document.styleSheets[2]).cssRules[0];
+
+ is(document.styleSheets[1].href,
+ "data:text/css,%23myDiv{color:green;}",
+ "Unexpected href for linked sheet after cloning");
+ is(document.styleSheets[3].href,
+ "data:text/css,%23myDiv{color:green;}",
+ "Unexpected href for later linked sheet after cloning");
+
+ is(document.styleSheets[2].href,
+ "chrome://global/skin/popup.css",
+ "Unexpected href for linked chrome sheet after cloning");
+ is(document.styleSheets[4].href,
+ "chrome://global/skin/popup.css",
+ "Unexpected href for later linked chrome sheet after cloning");
+
+ var myDiv = document.getElementById("myDiv");
+ is(getComputedStyle(myDiv, "").color, "rgb(0, 128, 0)",
+ "Unexpected color for div (data URI stylesheet not being honored?)");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug412901.html b/layout/style/test/test_bug412901.html
new file mode 100644
index 0000000000..fb37be57f7
--- /dev/null
+++ b/layout/style/test/test_bug412901.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=412901
+-->
+<head>
+ <title>Test for Bug 412901</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=412901">Mozilla Bug 412901</a>
+<div id="testDiv" style="width:20px; height:20px; border:solid silver; border-left-width:0.2px; border-right-width:0px; border-top-width:2.3px; border-bottom-width:5.5px;">
+<div id="testDiv2" style="width:20px; height:20px; border:hidden solid silver; border-left-width:0.2px; border-right-width:0px; border-top-width:2.3px; border-bottom-width:5.5px;">
+<p id="display"></p>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 412901 **/
+
+var div = document.getElementById("testDiv");
+var computedStyle = document.defaultView.getComputedStyle(div);
+// we never round down to 0px, very small widths are rounded up to 1px
+is(computedStyle.borderLeftWidth, "1px");
+is(computedStyle.borderRightWidth, "0px");
+is(computedStyle.borderTopWidth, "2px");
+is(computedStyle.borderBottomWidth, "5px");
+
+var div2 = document.getElementById("testDiv2");
+var computedStyle2 = document.defaultView.getComputedStyle(div2);
+is(computedStyle2.borderLeftWidth, "0px");
+is(computedStyle2.borderRightWidth, "0px");
+is(computedStyle2.borderTopWidth, "0px");
+is(computedStyle2.borderBottomWidth, "0px");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug413958.html b/layout/style/test/test_bug413958.html
new file mode 100644
index 0000000000..0b48e3aa68
--- /dev/null
+++ b/layout/style/test/test_bug413958.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=413958
+-->
+<head>
+ <title>Test for Bug 413958</title>
+ <meta charset="UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<style>span { color: red }</style><!-- backstop -->
+<p><a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=413958"
+ >Mozilla Bug 413958</a>. All text below should be black on white.</p>
+<p>Sheet: <span id="s1">1</span>
+ <span id="s2">2</span>
+ <span id="s3">3</span>.
+ Style attr: <span id="setStyle">4</span>.
+ Properties: <span id="setStyleProp" style="">5</span>.</p>
+<script>
+SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true;
+
+var tests = [
+ function() {
+ var s = document.createTextNode(
+"#s1{nosuchprop:auto; color:black}\n"+
+"#s2{nosuchprop:auto; color:black}invalid?sel{}#s3{color:black}"),
+ e = document.createElement("style");
+ e.appendChild(s);
+ document.body.appendChild(e);
+ },
+ function() {
+ document.getElementById("setStyle")
+ .setAttribute("style", "width:200;color:black");
+ },
+ function() {
+ var s = document.getElementById("setStyleProp").style;
+ s.width = "200";
+ s.color = "black";
+ },
+];
+var results = [
+ [ { errorMessage: /Unknown property \u2018nosuchprop\u2019/,
+ lineNumber: 1, columnNumber: 16, sourceLine: "", cssSelectors: "#s1" },
+ { errorMessage: /Unknown property \u2018nosuchprop\u2019/,
+ lineNumber: 2, columnNumber: 16, sourceLine: "", cssSelectors: "#s2" },
+ { errorMessage: /Ruleset ignored due to bad selector/,
+ lineNumber: 2, columnNumber: 41, sourceLine: "", cssSelectors: "" } ],
+ [ { errorMessage: /parsing value for \u2018width\u2019/,
+ lineNumber: 1, columnNumber: 7, sourceLine: "", cssSelectors: "" } ],
+ [ { errorMessage: /parsing value for \u2018width\u2019/,
+ lineNumber: 1, columnNumber: 1, sourceLine: "", cssSelectors: "" } ],
+];
+var curTest = -1;
+
+function doTest() {
+ if (++curTest == tests.length) {
+ var ss = document.getElementsByTagName("span");
+ for (var i = 0; i < ss.length; i++) {
+ is(window.getComputedStyle(ss[i]).color, "rgb(0, 0, 0)",
+ "recovery | " + ss[i].id);
+ }
+ SimpleTest.finish();
+ } else {
+ SimpleTest.expectConsoleMessages(tests[curTest], results[curTest], doTest);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+doTest();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_bug418986-2.html b/layout/style/test/test_bug418986-2.html
new file mode 100644
index 0000000000..04443a3553
--- /dev/null
+++ b/layout/style/test/test_bug418986-2.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418986
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2/3 for Bug #418986: Resist fingerprinting by preventing exposure of screen and system info</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="test-css"></style>
+ <script type="text/javascript" src="chrome/bug418986-2.js"></script>
+ <script type="text/javascript">
+ // Run all tests now.
+ window.onload = function () {
+ add_task(async function() {
+ await test(true);
+ });
+ };
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
+<p id="display">TEST</p>
+<div id="content" style="display: none">
+
+</div>
+<p id="pictures"></p>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug437915.html b/layout/style/test/test_bug437915.html
new file mode 100644
index 0000000000..fb6830dd57
--- /dev/null
+++ b/layout/style/test/test_bug437915.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=437915
+-->
+<head>
+ <title>Test for Bug 437915</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ div.classvalue { text-decoration: underline; }
+ div[title~="titlevalue"] { visibility: hidden; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437915">Mozilla Bug 437915</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 437915 **/
+
+var div = document.getElementById("content");
+var cs = document.defaultView.getComputedStyle(div);
+
+var chars = {
+ 0x09: true, // tab
+ 0x0a: true, // newline
+ 0x0b: false, // vertical tab (MAY CHANGE IN FUTURE!)
+ 0x0c: true, // form feed
+ 0x0d: true, // carriage return
+ 0x0e: false,
+ 0x20: true, // space
+ 0x2003: false,
+ 0x200b: false,
+ 0x2028: false,
+ 0x2029: false,
+ 0x3000: false
+};
+
+var wsmap = {
+ false: { str: " NOT", "text-decoration-line": "none", "visibility": "visible" },
+ true: { str: "", "text-decoration-line": "underline", "visibility": "hidden" }
+};
+
+for (var char in chars) {
+ var is_whitespace = chars[char];
+ var mapent = wsmap[is_whitespace];
+ div.setAttribute("class", "classvalue" + String.fromCharCode(char) + "b")
+ div.setAttribute("title", "a" + String.fromCharCode(char) + "titlevalue")
+ for (var prop of ["text-decoration-line", "visibility"]) {
+ is(cs.getPropertyValue(prop), mapent[prop],
+ "Character " + char + " should" + mapent.str +
+ " be treated as whitespace ("
+ + prop + " should be " + mapent[prop] + ")");
+ }
+}
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug450191.html b/layout/style/test/test_bug450191.html
new file mode 100644
index 0000000000..91488e9496
--- /dev/null
+++ b/layout/style/test/test_bug450191.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=450191
+-->
+<head>
+ <title>Test for Bug 450191</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450191">Mozilla Bug 450191</a>
+<iframe id="display" src="about:blank"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 450191 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var iframe = document.getElementById("display");
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+
+ var doctext = "<div style='font-size: 2em'>div text <table><tr><td id='t'>table text</td></tr></table></div>";
+
+ function subdoc_body_font() {
+ return subwin.getComputedStyle(subdoc.body).fontSize;
+ }
+
+ function subdoc_cell_font() {
+ return subwin.getComputedStyle(subdoc.getElementById("t")).fontSize;
+ }
+
+ subdoc.open();
+ subdoc.write(doctext);
+ subdoc.close();
+
+ is(subdoc_cell_font(), subdoc_body_font(),
+ "Quirks style sheet should be applied.");
+
+ subdoc.open();
+ subdoc.write("<!DOCTYPE HTML>" + doctext);
+ subdoc.close();
+
+ isnot(subdoc_cell_font(), subdoc_body_font(),
+ "Quirks style sheet should NOT be applied.");
+
+ subdoc.open();
+ subdoc.write(doctext);
+ subdoc.close();
+
+ is(subdoc_cell_font(), subdoc_body_font(),
+ "Quirks style sheet should be applied.");
+
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug470769.html b/layout/style/test/test_bug470769.html
new file mode 100644
index 0000000000..589cf790b0
--- /dev/null
+++ b/layout/style/test/test_bug470769.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=470769
+-->
+<head>
+ <title>Test for Bug 470769</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=470769">Mozilla Bug 470769</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 470769 **/
+
+var e = document.getElementById("display");
+e.setAttribute("style", "z-index: 2147483647"); // maximum signed 32-bit
+is(e.style.zIndex, "2147483647", "element.style should roundtrip correctly");
+is(window.getComputedStyle(e).zIndex, "2147483647",
+ "getComputedStyle should roundtrip correctly");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug499655.html b/layout/style/test/test_bug499655.html
new file mode 100644
index 0000000000..37ad553206
--- /dev/null
+++ b/layout/style/test/test_bug499655.html
@@ -0,0 +1,45 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=499655
+-->
+<head>
+ <title>Test for Bug 499655</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499655">Mozilla Bug 499655</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 499655 **/
+
+test1 = document.createElementNS("http://www.w3.org/1999/xhtml","test");
+test2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst");
+test3 = document.createElementNS("test","test");
+test4 = document.createElementNS("test","TEst");
+
+content = document.getElementById("content");
+
+content.appendChild(test1);
+content.appendChild(test2);
+content.appendChild(test3);
+content.appendChild(test4);
+
+list = document.querySelectorAll('test');
+is(list.length, 2, "Number of elements found");
+is(list[0], test1, "First element didn't match");
+is(list[1], test3, "Third element didn't match");
+
+list = document.querySelectorAll('TEst');
+is(list.length, 2, "Wrong number of elements found");
+is(list[0], test1, "First element didn't match");
+is(list[1], test4, "Fourth element didn't match");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug499655.xhtml b/layout/style/test/test_bug499655.xhtml
new file mode 100644
index 0000000000..b398d33967
--- /dev/null
+++ b/layout/style/test/test_bug499655.xhtml
@@ -0,0 +1,48 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=499655
+-->
+<head>
+ <title>Test for Bug 499655</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499655">Mozilla Bug 499655</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+test1 = document.createElementNS("http://www.w3.org/1999/xhtml","test");
+test2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst");
+test3 = document.createElementNS("test","test");
+test4 = document.createElementNS("test","TEst");
+
+content = document.getElementById("content");
+
+content.appendChild(test1);
+content.appendChild(test2);
+content.appendChild(test3);
+content.appendChild(test4);
+
+
+list = document.querySelectorAll('test');
+is(list.length, 2, "Number of elements found");
+is(list[0], test1, "First element didn't match");
+is(list[1] , test3, "Third element didn't match");
+
+list = document.querySelectorAll('TEst');
+is(list.length, 2, "Number of elements found");
+is(list[0], test2, "Second element didn't match");
+is(list[1], test4, "Fourth element didn't match");
+
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug517224.html b/layout/style/test/test_bug517224.html
new file mode 100644
index 0000000000..83d2bb8d80
--- /dev/null
+++ b/layout/style/test/test_bug517224.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=517224
+-->
+<head>
+ <title>Test for Bug 517224</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/ecmascript" src="bug517224.sjs?reset"></script>
+ <style type="text/css">
+
+ p#display { background: url(bug517224.sjs?image); }
+ p#display { background-image: none; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=517224">Mozilla Bug 517224</a>
+<p id="display">Element with overridden background</p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 517224 **/
+
+// Test that we don't load background images for style rules that are
+// always overridden.
+
+// Make sure the style has been computed
+var bi =
+ getComputedStyle(document.getElementById('display'), "").backgroundImage;
+SimpleTest.waitForExplicitFinish();
+window.onload = run;
+
+function run()
+{
+ var script = document.createElement("script");
+ script.setAttribute("src", "bug517224.sjs?result");
+ document.body.appendChild(script);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug524175.html b/layout/style/test/test_bug524175.html
new file mode 100644
index 0000000000..5a57b61ba2
--- /dev/null
+++ b/layout/style/test/test_bug524175.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=524175
+-->
+<head>
+ <title>Test for Bug 524175</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ span::before ::before {}
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=524175">Mozilla Bug 524175</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 524175 **/
+is(document.styleSheets[1].cssRules.length, 0, "Shouldn't have parsed that rule");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug525952.html b/layout/style/test/test_bug525952.html
new file mode 100644
index 0000000000..d3ad02419d
--- /dev/null
+++ b/layout/style/test/test_bug525952.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=525952
+-->
+<head>
+ <title>Test for Bug 525952</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525952">Mozilla Bug 525952</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 525952 **/
+var bodies = document.querySelectorAll("::before, div::before, body");
+is(bodies.length, 1, "Unexpected length");
+is(bodies[0], document.body, "Unexpected element");
+
+is(document.querySelector("div > ::after, body"), document.body,
+ "Unexpected return value");
+
+var emptyList = document.querySelectorAll("::before, div::before");
+is(emptyList.length, 0, "Unexpected empty list length");
+
+is(document.querySelectorAll("div > ::after").length, 0,
+ "Pseudo-element matched something?");
+
+is(document.body.matches("::first-line"), false,
+ "body shouldn't match ::first-line");
+
+is(document.body.matches("::first-line, body"), true,
+ "body should match 'body'");
+
+is(document.body.matches("::first-line, body, ::first-letter"), true,
+ "body should match 'body' here too");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug534804.html b/layout/style/test/test_bug534804.html
new file mode 100644
index 0000000000..0b60e6d89c
--- /dev/null
+++ b/layout/style/test/test_bug534804.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=534804
+-->
+<head>
+ <title>Test for Bug 534804</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css" id="styleone"> </style>
+ <style type="text/css" id="styletwo"> </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=534804">Mozilla Bug 534804</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 534804 **/
+
+var styleone = document.getElementById("styleone");
+var styletwo = document.getElementById("styletwo");
+var display = document.getElementById("display");
+
+run1();
+styletwo.firstChild.data = "#e > span:nth-child(2n+1) { color: green }";
+run1();
+styletwo.firstChild.data = "#e > span:first-child { color: green }";
+run1();
+styletwo.firstChild.data = "#e > span:nth-last-child(2n+1) { color: green }";
+run1();
+styletwo.firstChild.data = "#e > span:last-child { color: green }";
+run1();
+
+function run1()
+{
+ function identity(bool) { return bool; }
+ function inverse(bool) { return !bool; }
+ function always_false(bool) { return false; }
+ run2("#e:empty + span", identity, always_false);
+ run2("#e:empty ~ span", identity, identity);
+ run2("#e:not(:empty) + span", inverse, always_false);
+ run2("#e:not(:empty) ~ span", inverse, inverse);
+}
+
+function run2(sel, next_sibling_rule, later_sibling_rule)
+{
+ styleone.firstChild.data = sel + " { text-decoration: underline }";
+
+ // Rebuild the subtree every time.
+ var span1 = document.createElement("span");
+ span1.id = "e";
+ var span2 = document.createElement("span");
+ var span3 = document.createElement("span");
+ display.appendChild(span1);
+ display.appendChild(span2);
+ display.appendChild(span3);
+
+ function td(e) { return getComputedStyle(e, "").textDecorationLine; }
+
+ function check(desc, isempty) {
+ is(td(span2), next_sibling_rule(isempty) ? "underline" : "none",
+ "match of next sibling in state " + desc);
+ is(td(span3), later_sibling_rule(isempty) ? "underline" : "none",
+ "match of next sibling in state " + desc);
+ }
+
+ check("initially empty", true);
+ var kid = document.createElement("span");
+ span1.appendChild(kid);
+ check("after append", false);
+ span1.removeChild(kid);
+ check("after remove", true);
+ span1.appendChild(document.createTextNode(""));
+ span1.appendChild(document.createComment("a comment"));
+ span1.appendChild(document.createTextNode(""));
+ check("after append of insignificant children", true);
+ span1.insertBefore(kid, span1.childNodes[1]);
+ check("after insert", false);
+
+ display.removeChild(span1);
+ display.removeChild(span2);
+ display.removeChild(span3);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug573255.html b/layout/style/test/test_bug573255.html
new file mode 100644
index 0000000000..182087e1ec
--- /dev/null
+++ b/layout/style/test/test_bug573255.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=573255
+-->
+<head>
+ <title>Test for Bug 573255</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display:not(.hasSummary) { visibility: hidden }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=573255">Mozilla Bug 573255</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+
+is(cs.visibility, "hidden", "should be visibility:none since it has no class");
+p.className = "hasSummary";
+is(cs.visibility, "visible", "changing class attribute should remove visibility:hidden");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug580685.html b/layout/style/test/test_bug580685.html
new file mode 100644
index 0000000000..e54dfc2d7f
--- /dev/null
+++ b/layout/style/test/test_bug580685.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=580685
+-->
+<head>
+ <title>Test for Bug 580685</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=580685">Mozilla Bug 580685</a>
+<p id="display">
+ <iframe id="f" srcdoc="<body style='outline-offset: 1rem'>">
+ </iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 580685 **/
+SimpleTest.waitForExplicitFinish()
+addLoadEvent(function() {
+ var doc = $("f").contentDocument;
+ var b = doc.body;
+ doc.removeChild(doc.documentElement);
+ is(doc.defaultView.getComputedStyle(b).outlineOffset,
+ doc.defaultView.getComputedStyle(b).fontSize,
+ "1rem did not compute correctly");
+ SimpleTest.finish();
+});
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug621351.html b/layout/style/test/test_bug621351.html
new file mode 100644
index 0000000000..6a6d373afc
--- /dev/null
+++ b/layout/style/test/test_bug621351.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang=en>
+<title>Test for Bug 160403</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<span></span>
+<style>
+span {
+ border-inline-start: 0px solid rgb(0, 0, 0);
+ border-inline-end: 0px solid rgb(0, 0, 0);
+ transition: border 100s linear -50s;
+}
+span.transitioned {
+ border-inline-start: 100px solid rgb(100, 100, 100);
+ border-inline-end: 10px solid rgb(10, 10, 10);
+}
+</style>
+<script>
+// Test that transitioning each of border-{left,right}-{color,width}
+// works when the values are set through the -moz-border-{start,end}
+// shorthands.
+
+var e = document.querySelector("span");
+var cs = getComputedStyle(e);
+is(cs.borderLeftColor, "rgb(0, 0, 0)", "value of border-left-color before transition");
+is(cs.borderLeftWidth, "0px", "value of border-left-width before transition");
+is(cs.borderRightColor, "rgb(0, 0, 0)", "value of border-right-color before transition");
+is(cs.borderRightWidth, "0px", "value of border-right-width before transition");
+e.className = "transitioned";
+is(cs.borderLeftWidth, "50px", "value of border-left-width during transition");
+is(cs.borderLeftColor, "rgb(50, 50, 50)", "value of border-left-color during transition");
+is(cs.borderRightWidth, "5px", "value of border-right-width during transition");
+is(cs.borderRightColor, "rgb(5, 5, 5)", "value of border-right-color during transition");
+e.remove();
+</script>
diff --git a/layout/style/test/test_bug635286.html b/layout/style/test/test_bug635286.html
new file mode 100644
index 0000000000..80ea5a98ef
--- /dev/null
+++ b/layout/style/test/test_bug635286.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635286
+-->
+<head>
+ <title>Test for Bug 635286</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ div { background: transparent; }
+ :-moz-any(#case1.before) { background: gray; }
+ #case2:not(.after) { background: gray; }
+ :-moz-any(#case3:not(.after)) { background: gray; }
+ #case4:not(:-moz-any(.after)) { background: gray; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=635286">Mozilla Bug 635286</a>
+<div id="case1" class="before">case1, :-moz-any()</div>
+<div id="case2" class="before">case2, :not()</div>
+<div id="case3" class="before">case3, :not() in :-moz-any()</div>
+<div id="case4" class="before">case4, :-moz-any() in :not()</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 635286 **/
+
+window.addEventListener("load", function() {
+ var cases = Array.from(document.getElementsByTagName("div"));
+ cases.forEach(function(aCase, aIndex) {
+ aCase.className = "after";
+ });
+ window.setTimeout(function() {
+ cases.forEach(function(aCase, aIndex) {
+ is(window.getComputedStyle(aCase)
+ .getPropertyValue("background-color"),
+ "rgba(0, 0, 0, 0)",
+ aCase.textContent);
+ });
+ SimpleTest.finish();
+ }, 1);
+});
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug645998.html b/layout/style/test/test_bug645998.html
new file mode 100644
index 0000000000..ed8feccf7d
--- /dev/null
+++ b/layout/style/test/test_bug645998.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=645998
+-->
+<head>
+ <title>Test for Bug 645998</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <!-- This is the real test: will these stylesheets ever finish loading? -->
+ <link rel="stylesheet" href="file_bug645998-1.css">
+ <link rel="stylesheet" href="file_bug645998-2.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=645998">Mozilla Bug 645998</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 645998 **/
+ok(true, "Hey, we got here! That's a good sign");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug652486.html b/layout/style/test/test_bug652486.html
new file mode 100644
index 0000000000..cdee3f33a7
--- /dev/null
+++ b/layout/style/test/test_bug652486.html
@@ -0,0 +1,192 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=652486
+https://bugzilla.mozilla.org/show_bug.cgi?id=1039488
+-->
+<head>
+ <title>Test for Bug 652486, Bug 1039488 and Bug 1574222</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=652486">Mozilla Bug 652486</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1039488">Mozilla Bug 1039488</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1574222">Mozilla Bug 1574222</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="t"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 652486, Bug 1039488 and Bug 1574222 **/
+
+function c() {
+ return document.defaultView.getComputedStyle($('t')).
+ getPropertyValue("text-decoration");
+}
+
+// The default value of the 'color' property, which in turn establishes the
+// default value of 'text-decoration-color' (via the 'currentColor' keyword).
+var defaultLineColor = "rgb(0, 0, 0)";
+
+var tests = [
+ // When only text-decoration was specified, text-decoration should look like
+ // a longhand property. However, as of Bug 1574222, the getComputedStyle()
+ // serialization for "text-decoration" will always include the color,
+ // because we can't tell whether the resolved color value came from the
+ // initial "currentColor" value (and could safely be omitted) vs. whether it
+ // came from a custom specified value (and cannot be omitted).
+ { decoration: "none",
+ line: null, color: null, style: null,
+ expectedValue: defaultLineColor },
+ { decoration: "underline",
+ line: null, color: null, style: null,
+ expectedValue: "underline " + defaultLineColor },
+ { decoration: "overline",
+ line: null, color: null, style: null,
+ expectedValue: "overline " + defaultLineColor },
+ { decoration: "line-through",
+ line: null, color: null, style: null,
+ expectedValue: "line-through " + defaultLineColor },
+ { decoration: "blink",
+ line: null, color: null, style: null,
+ expectedValue: "blink " + defaultLineColor },
+ { decoration: "underline overline",
+ line: null, color: null, style: null,
+ expectedValue: "underline overline " + defaultLineColor },
+ { decoration: "underline line-through",
+ line: null, color: null, style: null,
+ expectedValue: "underline line-through " + defaultLineColor },
+ { decoration: "blink underline",
+ line: null, color: null, style: null,
+ expectedValue: "underline blink " + defaultLineColor },
+ { decoration: "underline blink",
+ line: null, color: null, style: null,
+ expectedValue: "underline blink " + defaultLineColor },
+
+ // When only text-decoration-line or text-blink was specified,
+ // text-decoration should look like a longhand property.
+ // However, as of Bug 1574222, the getComputedStyle() serialization for
+ // "text-decoration" will always include the color, because we can't tell
+ // whether the resolved color value came from the initial "currentColor"
+ // value (and could safely be omitted) vs. whether it came from a custom
+ // specified value (and cannot be omitted).
+ { decoration: null,
+ line: "blink", color: null, style: null,
+ expectedValue: "blink " + defaultLineColor },
+ { decoration: null,
+ line: "underline", color: null, style: null,
+ expectedValue: "underline " + defaultLineColor },
+ { decoration: null,
+ line: "overline", color: null, style: null,
+ expectedValue: "overline " + defaultLineColor },
+ { decoration: null,
+ line: "line-through", color: null, style: null,
+ expectedValue: "line-through " + defaultLineColor },
+ { decoration: null,
+ line: "blink underline", color: null, style: null,
+ expectedValue: "underline blink " + defaultLineColor },
+
+ // When text-decoration-color isn't its initial value,
+ // text-decoration should be a shorthand property.
+ { decoration: "blink",
+ line: null, color: "rgb(0, 0, 0)", style: null,
+ expectedValue: "blink rgb(0, 0, 0)" },
+ { decoration: "underline",
+ line: null, color: "black", style: null,
+ expectedValue: "underline rgb(0, 0, 0)" },
+ { decoration: "overline",
+ line: null, color: "#ff0000", style: null,
+ expectedValue: "overline rgb(255, 0, 0)" },
+ { decoration: "line-through",
+ line: null, color: "initial", style: null,
+ expectedValue: "line-through " + defaultLineColor },
+ { decoration: "blink underline",
+ line: null, color: "currentColor", style: null,
+ expectedValue: "underline blink " + defaultLineColor },
+ { decoration: "underline line-through",
+ line: null, color: "currentcolor", style: null,
+ expectedValue: "underline line-through " + defaultLineColor },
+
+ // When text-decoration-style isn't its initial value,
+ // text-decoration should be a shorthand property.
+ { decoration: "blink",
+ line: null, color: null, style: "-moz-none",
+ expectedValue: "blink -moz-none " + defaultLineColor },
+ { decoration: "underline",
+ line: null, color: null, style: "dotted",
+ expectedValue: "underline dotted " + defaultLineColor },
+ { decoration: "overline",
+ line: null, color: null, style: "dashed",
+ expectedValue: "overline dashed " + defaultLineColor },
+ { decoration: "line-through",
+ line: null, color: null, style: "double",
+ expectedValue: "line-through double " + defaultLineColor },
+ { decoration: "blink underline",
+ line: null, color: null, style: "wavy",
+ expectedValue: "underline blink wavy " + defaultLineColor },
+ { decoration: "underline blink overline line-through",
+ line: null, color: null, style: "solid",
+ expectedValue: "underline overline line-through blink " + defaultLineColor },
+ { decoration: "line-through overline underline",
+ line: null, color: null, style: "initial",
+ expectedValue: "underline overline line-through " + defaultLineColor }
+];
+
+function makeDeclaration(aTest)
+{
+ var str = "";
+ if (aTest.decoration) {
+ str += "text-decoration: " + aTest.decoration + "; ";
+ }
+ if (aTest.color) {
+ str += "text-decoration-color: " + aTest.color + "; ";
+ }
+ if (aTest.line) {
+ str += "text-decoration-line: " + aTest.line + "; ";
+ }
+ if (aTest.style) {
+ str += "text-decoration-style: " + aTest.style + "; ";
+ }
+ return str;
+}
+
+function clearStyleObject()
+{
+ $('t').style.textDecoration = null;
+}
+
+for (var i = 0; i < tests.length; ++i) {
+ var test = tests[i];
+ if (test.decoration) {
+ $('t').style.textDecoration = test.decoration;
+ }
+ if (test.color) {
+ $('t').style.textDecorationColor = test.color;
+ }
+ if (test.line) {
+ $('t').style.textDecorationLine = test.line;
+ }
+ if (test.style) {
+ $('t').style.textDecorationStyle = test.style;
+ }
+
+ var dec = makeDeclaration(test);
+ is(c(), test.expectedValue, "Test1 (computed value): " + dec);
+
+ clearStyleObject();
+
+ $('t').setAttribute("style", dec);
+
+ is(c(), test.expectedValue, "Test2 (computed value): " + dec);
+
+ $('t').removeAttribute("style");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug657143.html b/layout/style/test/test_bug657143.html
new file mode 100644
index 0000000000..de8a1961d4
--- /dev/null
+++ b/layout/style/test/test_bug657143.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=657143
+-->
+<head>
+ <title>Test for Bug 657143</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657143">Mozilla Bug 657143</a>
+<p id="display"></p>
+<style>
+/* Ensure that there is at least one custom property on the root element's
+ computed style */
+:root { --test: some value; }
+</style>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 657143 **/
+
+// Check ordering of CSS properties in nsComputedDOMStylePropertyList.h
+// by splitting the getComputedStyle() into five sublists
+// then cloning and sort()ing the lists and comparing with originals.
+
+function isMozPrefixed(aProp) {
+ return aProp.startsWith("-moz");
+}
+
+function isNotMozPrefixed(aProp) {
+ return !isMozPrefixed(aProp);
+}
+
+function isWebkitPrefixed(aProp) {
+ return aProp.startsWith("-webkit");
+}
+
+function isNotWebkitPrefixed(aProp) {
+ return !isWebkitPrefixed(aProp);
+}
+
+function isCustom(aProp) {
+ return aProp.startsWith("--");
+}
+
+function isNotCustom(aProp) {
+ return !isCustom(aProp);
+}
+
+var styleList = window.getComputedStyle(document.documentElement);
+ cssA = [], mozA = [], webkitA = [], customA = [];
+
+// Partition the list of property names into four lists:
+//
+// cssA: regular properties
+// mozA: '-moz-' prefixed properties
+// customA: '--' prefixed custom properties
+
+var list = cssA;
+for (var i = 0, j = styleList.length; i < j; i++) {
+ var prop = styleList.item(i);
+
+ switch (list) {
+ case cssA:
+ if (isMozPrefixed(prop)) {
+ list = mozA;
+ }
+ // fall through
+ case mozA:
+ if (isWebkitPrefixed(prop)) {
+ list = webkitA;
+ }
+ // fall through
+ case webkitA:
+ if (isCustom(prop)) {
+ list = customA;
+ }
+ // fall through
+ }
+
+ list.push(prop);
+}
+
+var cssB = cssA.slice(0).sort(),
+ mozB = mozA.slice(0).sort(),
+ webkitB = webkitA.slice(0).sort();
+
+is(cssA.toString(), cssB.toString(), 'CSS property list should be alphabetical');
+is(mozA.toString(), mozB.toString(), 'Experimental -moz- CSS property list should be alphabetical');
+is(webkitA.toString(), webkitB.toString(), 'Compatible -webkit- CSS property list should be alphabetical');
+
+// We don't test that the custom property list is sorted, as the CSSOM
+// specification does not yet require it, and we don't return them
+// in sorted order.
+
+ok(!cssA.find(isWebkitPrefixed), 'Compatible -webkit- CSS properties should not be in the mature CSS property list');
+ok(!cssA.find(isMozPrefixed), 'Experimental -moz- CSS properties should not be in the mature CSS property list');
+ok(!cssA.find(isCustom), 'Custom CSS properties should not be in the mature CSS property list');
+ok(!mozA.find(isWebkitPrefixed), 'Compatible -webkit- CSS properties should not be in the experimental -moz- CSS '
+ + 'property list');
+ok(!mozA.find(isNotMozPrefixed), 'Experimental -moz- CSS property list should not contain non -moz- prefixed '
+ + 'CSS properties');
+ok(!mozA.find(isCustom), 'Custom CSS properties should not be in the experimental -moz- CSS property list');
+ok(!webkitA.find(isNotWebkitPrefixed), 'Compatible -webkit- CSS properties should not contain non -webkit- prefixed '
+ + 'CSS properties');
+ok(!webkitA.find(isMozPrefixed), 'Experimental -moz- CSS properties should not be in the compatible -webkit- CSS '
+ + 'property list');
+ok(!webkitA.find(isCustom), 'Custom CSS properties should not be in the compatible -webkit- CSS property list');
+ok(!customA.find(isNotCustom), 'Non-custom CSS properties should not be in the custom property list');
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug667520.html b/layout/style/test/test_bug667520.html
new file mode 100644
index 0000000000..fb46943e8e
--- /dev/null
+++ b/layout/style/test/test_bug667520.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=667520
+-->
+<head>
+ <title>Test for Bug 667520</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=667520">Mozilla Bug 667520</a>
+<p id="display">
+ <!-- important: we need a <span> as the second element child and then
+ non-span elements between that and the next </span> -->
+ <i></i>
+ <span id="2"></span>
+ <i></i>
+ <i></i>
+ <i></i>
+ <i></i>
+ <span id="7"></span>
+ <span id="8"></span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 667520 **/
+var spans = $("display").querySelectorAll("span");
+is(spans.length, 3, "Should have 3 span kids");
+
+is($("display").querySelector("span:nth-child(3)"), null, "Third child is not span");
+is($("display").querySelector("span:nth-child(4)"), null, "Fourth child is not span");
+
+for (var i = 0; i < spans.length; ++i) {
+ var id = spans[i].id;
+ /* Important: need to include 'span' in that selector so we only match
+ nth-child against spans. */
+ var target = $("display").querySelector("span:nth-child("+id+")");
+ is(target, spans[i], "Unexpected element");
+ is(target.id, spans[i].id, "Unexpected id");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug716226.html b/layout/style/test/test_bug716226.html
new file mode 100644
index 0000000000..ed538cd822
--- /dev/null
+++ b/layout/style/test/test_bug716226.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=716226
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 716226</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="s">
+ @keyframes foo { }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=716226">Mozilla Bug 716226</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 716226 **/
+var sheet = $("s").sheet;
+var rules = sheet.cssRules;
+is(rules.length, 1, "Should have one keyframes rule");
+var keyframesRule = rules[0];
+var keyframeRules = keyframesRule.cssRules;
+is(keyframeRules.length, 0, "Should have no keyframe rules yet");
+
+keyframesRule.appendRule('0% { }');
+is(keyframeRules.length, 1, "Should have a keyframe rule now");
+var keyframeRule = keyframeRules[0];
+is(keyframeRule.parentRule, keyframesRule,
+ "Parent of keyframe should be keyframes");
+is(keyframeRule.parentStyleSheet, sheet,
+ "Parent stylesheet of keyframe should be our sheet");
+
+is(keyframeRule.style.cssText, "", "Should have no declarations yet");
+// Note: purposefully non-canonical cssText string so we can make sure we
+// really invoked the CSS parser and serializer.
+keyframeRule.style.cssText = "color:green";
+is(keyframeRule.style.cssText, "color: green;",
+ "Should have the declarations we set now");
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug732153.html b/layout/style/test/test_bug732153.html
new file mode 100644
index 0000000000..03591e3dc6
--- /dev/null
+++ b/layout/style/test/test_bug732153.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=732153
+-->
+<title>Test for Bug 732153</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=732153">Mozilla Bug 732153</a>
+<div></div>
+<script>
+var div = document.querySelector("div");
+[
+ "",
+ "backface-visibility: hidden",
+ "transform-style: preserve-3d",
+ "backface-visibility: hidden; transform-style: preserve-3d",
+].forEach(function(style) {
+ div.setAttribute("style", style);
+ is(getComputedStyle(div).transform, "none",
+ "Computed 'transform' with style=\"" + style + '"');
+});
+</script>
diff --git a/layout/style/test/test_bug732209.html b/layout/style/test/test_bug732209.html
new file mode 100644
index 0000000000..1bf5825000
--- /dev/null
+++ b/layout/style/test/test_bug732209.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=732209
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 732209</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #content span { color: red; }
+ #content span.reverse { color: green; }
+ #content { display: block !important; }
+ #content span::before { content: attr(id); }
+ </style>
+ <link rel="stylesheet" href="bug732209-css.sjs?one">
+ <link rel="stylesheet" href="bug732209-css.sjs?two" crossorigin>
+ <link rel="stylesheet" href="bug732209-css.sjs?three" crossorigin="use-credentials">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?four">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?five"
+ crossorigin>
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?six"
+ crossorigin="use-credentials">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?seven&cors-anonymous">
+ <link rel="stylesheet" id="cross-origin-sheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?eight&cors-anonymous"
+ crossorigin>
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?nine&cors-anonymous"
+ crossorigin="use-credentials">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?ten&cors-credentials">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?eleven&cors-credentials"
+ crossorigin>
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?twelve&cors-credentials"
+ crossorigin="use-credentials">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=732209">Mozilla Bug 732209</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <span id="one"></span>
+ <span id="two"></span>
+ <span id="three"></span>
+ <span id="four"></span>
+ <span id="five" class="reverse"></span>
+ <span id="six" class="reverse"></span>
+ <span id="seven"></span>
+ <span id="eight"></span>
+ <span id="nine" class="reverse"></span>
+ <span id="ten"></span>
+ <span id="eleven"></span>
+ <span id="twelve"></span>
+</div>
+<pre id="test" style="color: red">
+<script type="application/javascript">
+
+/** Test for Bug 732209 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var spans = $("content").querySelectorAll("span");
+ for (var i = 0; i < spans.length; ++i) {
+ is(getComputedStyle(spans[i], "").color, "rgb(0, 128, 0)",
+ "Span " + spans[i].id + " should be green");
+ }
+
+ try {
+ var sheet = $("cross-origin-sheet").sheet;
+ dump('aaa\n');
+ is(sheet.cssRules.length, 2,
+ "Should be able to get length of list of rules");
+ is(sheet.cssRules[0].style.color, "green",
+ "Should be able to read individual rules");
+ } catch (e) {
+ ok(false,
+ "Should be allowed to access cross-origin sheet that opted in with CORS: " + e);
+ }
+
+ SimpleTest.finish();
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug73586.html b/layout/style/test/test_bug73586.html
new file mode 100644
index 0000000000..6a95703cd7
--- /dev/null
+++ b/layout/style/test/test_bug73586.html
@@ -0,0 +1,189 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=73586
+-->
+<head>
+ <title>Test for Bug 73586</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ span { background: white; color: black; border: medium solid black; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=73586">Mozilla Bug 73586</a>
+<div id="display"></div>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 73586 **/
+
+const GREEN = "rgb(0, 128, 0)";
+const LIME = "rgb(0, 255, 0)";
+const BLACK = "rgb(0, 0, 0)";
+const WHITE = "rgb(255, 255, 255)";
+
+function cs(elt) { return getComputedStyle(elt, ""); }
+
+function check_children(p, check_cb) {
+ var len = p.childNodes.length;
+ var elts = 0;
+ var i, elt, child;
+ for (i = 0; i < len; ++i) {
+ if (p.childNodes[i].nodeType == Node.ELEMENT_NODE)
+ ++elts;
+ }
+
+ elt = 0;
+ for (i = 0; i < len; ++i) {
+ child = p.childNodes[i];
+ if (child.nodeType != Node.ELEMENT_NODE)
+ continue;
+ check_cb(child, elt, elts, i, len);
+ ++elt;
+ }
+}
+
+function run_series(check_cb) {
+ var display = document.getElementById("display");
+ // Use a new parent node every time since the optimizations cause
+ // bits to be set (permanently) on the parent.
+ var p = document.createElement("p");
+ display.appendChild(p);
+ p.innerHTML = "x<span></span><span></span>";
+
+ check_children(p, check_cb);
+ var text = p.removeChild(p.childNodes[0]);
+ check_children(p, check_cb);
+ var span = p.removeChild(p.childNodes[0]);
+ check_children(p, check_cb);
+ p.appendChild(span);
+ check_children(p, check_cb);
+ p.removeChild(span);
+ check_children(p, check_cb);
+ p.insertBefore(span, p.childNodes[0]);
+ check_children(p, check_cb);
+ p.removeChild(span);
+ check_children(p, check_cb);
+ p.insertBefore(span, null);
+ check_children(p, check_cb);
+ p.appendChild(document.createElement("span"));
+ check_children(p, check_cb);
+ p.insertBefore(document.createElement("span"), p.childNodes[2]);
+ check_children(p, check_cb);
+ p.appendChild(text);
+ check_children(p, check_cb);
+
+ display.removeChild(p);
+}
+
+var style = document.createElement("style");
+style.setAttribute("type", "text/css");
+var styleText = document.createTextNode("");
+style.appendChild(styleText);
+document.getElementsByTagName("head")[0].appendChild(style);
+
+styleText.data = "span:first-child { background: lime; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).backgroundColor, (elt == 0) ? LIME : WHITE,
+ "child " + node + " should " + ((elt == 0) ? "" : "NOT ") +
+ " match :first-child");
+ });
+
+styleText.data = "span:last-child { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).color, (elt == elts - 1) ? GREEN : BLACK,
+ "child " + node + " should " + ((elt == elts - 1) ? "" : "NOT ") +
+ " match :last-child");
+ });
+
+styleText.data = "span:only-child { border: medium solid green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).borderTopColor, (elts == 1) ? GREEN : BLACK,
+ "child " + node + " should " + ((elts == 1) ? "" : "NOT ") +
+ " match :only-child");
+ });
+
+styleText.data = "span:-moz-first-node { text-decoration-line: underline; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).textDecorationLine, (node == 0) ? "underline" : "none",
+ "child " + node + " should " + ((node == 0) ? "" : "NOT ") +
+ " match :-moz-first-node");
+ });
+
+styleText.data = "span:-moz-last-node { visibility: hidden; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).visibility, (node == nodes - 1) ? "hidden" : "visible",
+ "child " + node + " should " + ((node == nodes - 1) ? "" : "NOT ") +
+ " match :-moz-last-node");
+ });
+
+styleText.data = "span:nth-child(1) { background: lime; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = elt == 0;
+ is(cs(child).backgroundColor, matches ? LIME : WHITE,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:nth-last-child(0n+2) { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = (elt == elts - 2);
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:nth-of-type(2n+3) { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var nidx = elt + 1;
+ var matches = nidx % 2 == 1 && nidx >= 3;
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:nth-last-of-type(-2n+5) { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var nlidx = elts - elt;
+ var matches = nlidx % 2 == 1 && nlidx <= 5;
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:first-of-type { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = (elt == 0);
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:last-of-type { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = (elt == elts - 1);
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:only-of-type { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = elts == 1;
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug74880.html b/layout/style/test/test_bug74880.html
new file mode 100644
index 0000000000..054189056b
--- /dev/null
+++ b/layout/style/test/test_bug74880.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=74880
+-->
+<head>
+ <title>Test for Bug 74880</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ /* so that computed values for other border properties work right */
+ #display { border-style: solid; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=74880">Mozilla Bug 74880</a>
+<div style="margin: 1px 2px 3px 4px; border-width: 5px 6px 7px 8px; border-style: dotted dashed solid double; border-color: blue fuchsia green orange; padding: 9px 10px 11px 12px">
+<p id="display"></p>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 74880 **/
+
+var gProps = [
+ [ "margin-left", "margin-right", "margin-inline-start", "margin-inline-end" ],
+ [ "padding-left", "padding-right", "padding-inline-start", "padding-inline-end" ],
+ [ "border-left-color", "border-right-color", "border-inline-start-color", "border-inline-end-color" ],
+ [ "border-left-style", "border-right-style", "border-inline-start-style", "border-inline-end-style" ],
+ [ "border-left-width", "border-right-width", "border-inline-start-width", "border-inline-end-width" ],
+];
+
+var gLengthValues = [ "inherit", "initial", "2px", "1em", "unset" ];
+var gColorValues = [ "inherit", "initial", "currentColor", "green", "unset" ];
+var gStyleValues = [ "inherit", "initial", "double", "dashed", "unset" ];
+
+function values_for(set) {
+ var values;
+ if (set[0].match(/style$/))
+ values = gStyleValues;
+ else if (set[0].match(/color$/))
+ values = gColorValues;
+ else
+ values = gLengthValues;
+ return values;
+}
+
+var e = document.getElementById("display");
+var s = e.style;
+var c = window.getComputedStyle(e);
+
+for (var set of gProps) {
+ var values = values_for(set);
+ for (var val of values) {
+ function check(dir, plogical, pvisual) {
+ var v0 = c.getPropertyValue(pvisual);
+ s.setProperty("direction", dir, "");
+ s.setProperty(pvisual, val, "");
+ var v1 = c.getPropertyValue(pvisual);
+ if (val != "initial" && val != "unset" && val != "currentColor")
+ isnot(v1, v0, "setProperty set the property " + pvisual + ": " + val);
+ s.removeProperty(pvisual);
+ is(c.getPropertyValue(pvisual), v0, "removeProperty worked for " + pvisual);
+ s.setProperty(plogical, val, "")
+ var v2 = c.getPropertyValue(pvisual);
+ is(v2, v1, "the logical property " + plogical + ": " + val + " showed up in the right place");
+ s.removeProperty(plogical);
+ is(c.getPropertyValue(pvisual), v0, "removeProperty worked for " + plogical);
+
+ s.removeProperty("direction");
+ }
+
+ check("ltr", set[2], set[0]);
+ check("ltr", set[3], set[1]);
+ check("rtl", set[2], set[1]);
+ check("rtl", set[3], set[0]);
+ }
+
+ function check_cascading(dir, plogical, pvisual) {
+ var dirstr = "direction: " + dir + ";";
+ e.setAttribute("style", dirstr + pvisual + ":" + values[2]);
+ var v2 = c.getPropertyValue(pvisual);
+ e.setAttribute("style", dirstr + pvisual + ":" + values[3]);
+ var v3 = c.getPropertyValue(pvisual);
+ isnot(v2, v3, "values should produce different computed values");
+
+ var desc = ["cascading for", pvisual, "and", plogical, "with direction", dir].join(" ");
+ e.setAttribute("style", dirstr + pvisual + ":" + values[3] + ";" +
+ plogical + ":" + values[2]);
+ is(c.getPropertyValue(pvisual), v2, desc);
+ e.setAttribute("style", dirstr + plogical + ":" + values[3] + ";" +
+ pvisual + ":" + values[2]);
+ is(c.getPropertyValue(pvisual), v2, desc);
+ e.setAttribute("style", dirstr + pvisual + ":" + values[2] + ";" +
+ plogical + ":" + values[3]);
+ is(c.getPropertyValue(pvisual), v3, desc);
+ e.setAttribute("style", dirstr + plogical + ":" + values[2] + ";" +
+ pvisual + ":" + values[3]);
+ is(c.getPropertyValue(pvisual), v3, desc);
+ e.removeAttribute("style");
+ }
+
+ check_cascading("ltr", set[2], set[0]);
+ check_cascading("ltr", set[3], set[1]);
+ check_cascading("rtl", set[2], set[1]);
+ check_cascading("rtl", set[3], set[0]);
+}
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug765590.html b/layout/style/test/test_bug765590.html
new file mode 100644
index 0000000000..acc9f5b5c0
--- /dev/null
+++ b/layout/style/test/test_bug765590.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=765590
+-->
+<head>
+ <title>Test for Bug 765590</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @namespace svg "http://www.w3.org/2000/svg";
+ </style>
+</head>
+<body>
+ <script>
+ var styleElement = document.getElementsByTagName("style")[0]
+ var rule = styleElement.sheet.cssRules[0];
+ is(rule.type, 10, "rule type should be equal 10")
+ is(CSSRule.NAMESPACE_RULE, 10, "NAMESPACE_RULE should be equal to 10")
+ </script>
+</body>
diff --git a/layout/style/test/test_bug771043.html b/layout/style/test/test_bug771043.html
new file mode 100644
index 0000000000..a5073d681e
--- /dev/null
+++ b/layout/style/test/test_bug771043.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=771043
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 771043</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 771043 **/
+ var expectedValue;
+ var callCount = 0;
+ var storedHeight;
+ function callback(arg) {
+ ++callCount;
+ is(arg.matches, expectedValue,
+ "Should have the right value on call #" + callCount + " to the callback");
+ SimpleTest.executeSoon(tests.shift());
+ }
+
+ function flushLayout() {
+ storedHeight = document.querySelector("iframe").offsetHeight;
+ }
+
+ function setHeight(height) {
+ var ifr = document.querySelector("iframe");
+ ifr.style.height = height + "px";
+ flushLayout();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ var tests = [
+ () => { expectedValue = true; setHeight(50); },
+ () => { expectedValue = false; setHeight(200); },
+ () => {
+ var ifr = document.querySelector("iframe");
+ ifr.style.display = "none";
+ flushLayout();
+ ifr.style.display = "";
+ expectedValue = true;
+ setHeight(50);
+ },
+ () => { expectedValue = false; setHeight(200); },
+ SimpleTest.finish.bind(SimpleTest)
+ ];
+
+ addLoadEvent(function() {
+ var mql = frames[0].matchMedia("(orientation: landscape)");
+ mql.addListener(callback);
+
+ tests.shift()();
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=771043">Mozilla Bug 771043</a>
+<!-- Important: the iframe needs to be displayed -->
+<p id="display"><iframe style="width: 100px; height: 200px"</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug795520.html b/layout/style/test/test_bug795520.html
new file mode 100644
index 0000000000..66726c2b8e
--- /dev/null
+++ b/layout/style/test/test_bug795520.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795520
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 795520</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795520">Mozilla Bug 795520</a>
+<p id="display">
+ <iframe id="f" style="display:none"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 795520 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ doc = $("f").contentDocument;
+ $("f").style.display = "";
+ isnot(doc.defaultView.getComputedStyle(doc.body), null,
+ "Should have computed style here");
+ SimpleTest.finish();
+});
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug798843_pref.html b/layout/style/test/test_bug798843_pref.html
new file mode 100644
index 0000000000..aea12ccc35
--- /dev/null
+++ b/layout/style/test/test_bug798843_pref.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Make sure that the SVG glyph context-* values are not considered real values
+ when gfx.font_rendering.opentype_svg.enabled is pref'ed off.
+-->
+<head>
+ <title>Test that SVG glyph context-* values can be pref'ed off</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+
+<script>
+
+var props = {
+ "strokeDasharray" : "context-value",
+ "strokeDashoffset" : "context-value",
+ "strokeWidth" : "context-value"
+};
+
+function testDisabled() {
+ for (var p in props) {
+ document.body.style[p] = props[p];
+ is(document.body.style[p], "", p + " not settable to " + props[p]);
+ document.body.style[p] = "";
+ }
+ SimpleTest.finish();
+}
+
+function testEnabled() {
+ for (var p in props) {
+ document.body.style[p] = props[p];
+ is(document.body.style[p], props[p], p + " settable to " + props[p]);
+ document.body.style[p] = "";
+ }
+
+ SpecialPowers.pushPrefEnv(
+ {'set': [['gfx.font_rendering.opentype_svg.enabled', false]]},
+ testDisabled
+ );
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ {'set': [['gfx.font_rendering.opentype_svg.enabled', true]]},
+ testEnabled
+);
+
+</script>
+
+</body>
+</html>
diff --git a/layout/style/test/test_bug829816.html b/layout/style/test/test_bug829816.html
new file mode 100644
index 0000000000..a4614cf6eb
--- /dev/null
+++ b/layout/style/test/test_bug829816.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=829816
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 829816</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <style type="text/css">
+ b { content: "\0"; counter-reset: \0 }
+ b { content: "\00"; counter-reset: \00 }
+ b { content: "\000"; counter-reset: \000 }
+ b { content: "\0000"; counter-reset: \0000 }
+ b { content: "\00000"; counter-reset: \00000 }
+ b { content: "\000000"; counter-reset: \000000 }
+ </style>
+
+ <!-- U+0000 characters in <style> would be replaced by the HTML parser -->
+ <link rel="stylesheet" type="text/css" href="file_bug829816.css"/>
+
+ <script type="application/javascript">
+
+ /** Test for Bug 829816 **/
+ var ss = document.styleSheets[1];
+
+ for (var i = 0; i < 6; i++) {
+ is(ss.cssRules[i].style.content, "\"\uFFFD\"",
+ "\\0 in strings should be converted to U+FFFD");
+ is(ss.cssRules[i].style.counterReset, "\uFFFD 0",
+ "\\0 in identifiers should be converted to U+FFFD");
+ }
+
+ is(document.styleSheets[2].cssRules[0].style.content, "\"\uFFFD\"",
+ "U+0000 in strings should be converted to U+FFFD");
+ is(document.styleSheets[2].cssRules[0].style.counterReset, "\uFFFD 0",
+ "U+0000 in identifiers should be converted to U+FFFD");
+ is(document.styleSheets[2].cssRules[1].style.content, "\"\uFFFD\"",
+ "U+0000 in strings should be converted to U+FFFD");
+ is(document.styleSheets[2].cssRules[1].style.counterReset, "\uFFFD 0",
+ "U+0000 in identifiers should be converted to U+FFFD");
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=829816">Mozilla Bug 829816</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug874919.html b/layout/style/test/test_bug874919.html
new file mode 100644
index 0000000000..be480600b9
--- /dev/null
+++ b/layout/style/test/test_bug874919.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=874919
+-->
+<head>
+ <title>Test for Bug 874919</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=874919">Mozilla Bug 874919</a>
+<p id="display"></p>
+<div id="content" style="width: 150px">
+ <svg id="outer_SVG" style="display: inline; width: 100%">
+ <circle cx="120" cy="120" r="120" fill="blue"></circle>
+ <svg id="inner_SVG">
+ <circle id="circle" cx="120" cy="120" r="120" fill="red"></circle>
+ </svg>
+ </svg>
+</div>
+<pre id="test">
+
+<script type="text/javascript">
+
+ var shouldUseComputed = ["inner_SVG"]
+ var shouldUseUsed = ["outer_SVG"]
+
+ shouldUseUsed.forEach(function(elemId) {
+
+ var style = window.getComputedStyle(document.getElementById(elemId));
+
+ ok(style.width.match(/^\d+px$/),
+ "Inline Outer SVG element's getComputedStyle.width should be used value. ");
+
+ ok(style.height.match(/^\d+px$/),
+ "Inline Outer SVG element's getComputedStyle.height should be used value.");
+ });
+
+ shouldUseComputed.forEach(function(elemId) {
+ var style = window.getComputedStyle(document.getElementById(elemId));
+
+ // Computed value should match either the percentage used, or "auto" in the case of the inner SVG element.
+ ok(style.width.match(/^\d+%$|^auto$/),
+ "Inline inner SVG element's getComputedStyle.width should be computed value. " + style.width);
+
+ ok(style.height.match(/^\d+%$|^auto$/),
+ "Inline inner SVG element's getComputedStyle.height should be computed value. " + style.height);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug887741_at-rules_in_declaration_lists.html b/layout/style/test/test_bug887741_at-rules_in_declaration_lists.html
new file mode 100644
index 0000000000..739ace0f04
--- /dev/null
+++ b/layout/style/test/test_bug887741_at-rules_in_declaration_lists.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=887741
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 887741: at-rules in declaration lists</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style>
+ #foo {
+ color: red;
+ @invalid-rule {
+ ignored: ignored;
+ }
+ /* No semicolon */
+ color: green;
+ }
+ @page {
+ margin-top: 0;
+ @bottom-center {
+ content: counter(page);
+ }
+ /* No semicolon */
+ margin-top: 5cm;
+ }
+ @keyframes dummy-animation {
+ 12% {
+ color: red;
+ @invalid-rule {}
+ /* No semicolon */
+ color: green;
+ }
+ }
+ /* TODO: other at-rules that use declaration syntax? */
+ </style>
+ <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=887741">Mozilla Bug 887741</a>
+<p id="display"></p>
+<div id="content" style="display: none; color: red;
+ @invalid-rule{} /* No semicolon */ color: green;">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 887741 **/
+
+ var style = document.getElementById('content').style;
+ is(style.display, 'none', 'Sanity check: we have the right element');
+ is(style.color, 'green', 'Support at-rules in style attributes');
+
+ style.cssText = 'display: none; color: red; @invalid-rule{} /* No semicolon */ color: lime;';
+ is(style.color, 'lime', 'Support at-rules in CSSStyleDeclaration.cssText');
+
+ var rules = document.styleSheets[0].cssRules;
+ var style_rule = rules[0];
+ is(style_rule.selectorText, '#foo', 'Sanity check: we have the right style rule');
+ is(style_rule.style.color, 'green', 'Support at-rules in style rules');
+
+ var page_rule = rules[1];
+ is(page_rule.type, page_rule.PAGE_RULE, 'Sanity check: we have the right style rule');
+ is(page_rule.style.marginTop, '5cm', 'Support at-rules in @page rules');
+
+ var keyframe_rule = rules[2].cssRules[0];
+ is(keyframe_rule.keyText, '12%', 'Sanity check: we have the right keyframe rule');
+ is(keyframe_rule.style.color, 'green', 'Support at-rules in keyframe rules')
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug892929.html b/layout/style/test/test_bug892929.html
new file mode 100644
index 0000000000..a67db56ee3
--- /dev/null
+++ b/layout/style/test/test_bug892929.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Bug 892929 test</title>
+ <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com">
+ <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#om-fontfeaturevalues" />
+ <meta name="assert" content="window.CSSFontFeatureValuesRule should appear by default" />
+ <script type="text/javascript" src="/resources/testharness.js"></script>
+ <script type="text/javascript" src="/resources/testharnessreport.js"></script>
+ <style type="text/css">
+ </style>
+</head>
+<body>
+<div id="log"></div>
+<pre id="display"></pre>
+
+<script type="text/javascript">
+
+function testCSSFontFeatureValuesRuleOM() {
+ var s = document.documentElement.style;
+ var cs = window.getComputedStyle(document.documentElement);
+
+ var hasFFVRule = "CSSFontFeatureValuesRule" in window;
+ var hasStyleAlternates = "fontVariantAlternates" in s;
+ var hasCompStyleAlternates = "fontVariantAlternates" in cs;
+
+ test(function() {
+ assert_equals(hasFFVRule,
+ hasStyleAlternates,
+ "style.fontVariantAlternates " +
+ (hasStyleAlternates ? "available" : "not available") +
+ " but " +
+ "window.CSSFontFeatureValuesRule " +
+ (hasFFVRule ? "available" : "not available") +
+ " - ");
+ }, "style.fontVariantAlternates availability matches window.CSSFontFeatureValuesRule availability");
+
+ test(function() {
+ assert_equals(hasFFVRule,
+ hasCompStyleAlternates,
+ "computedStyle.fontVariantAlternates " +
+ (hasCompStyleAlternates ? "available" : "not available") +
+ " but " +
+ "window.CSSFontFeatureValuesRule " +
+ (hasFFVRule ? "available" : "not available") +
+ " - ");
+ }, "computedStyle.fontVariantAlternates availability matches window.CSSFontFeatureValuesRule availability");
+
+ // if window.CSSFontFeatureValuesRule isn't around, neither should any of the font feature props
+ fontFeatureProps = [ "fontKerning", "fontVariantAlternates", "fontVariantCaps", "fontVariantEastAsian",
+ "fontVariantLigatures", "fontVariantNumeric", "fontVariantPosition", "fontSynthesis",
+ "fontFeatureSettings", "fontLanguageOverride" ];
+
+ if (!hasFFVRule) {
+ var i;
+ for (i = 0; i < fontFeatureProps.length; i++) {
+ var prop = fontFeatureProps[i];
+ test(function() {
+ assert_true(!(prop in s), "window.CSSFontFeatureValuesRule not available but style." + prop + " is available - ");
+ }, "style." + prop + " availability");
+ test(function() {
+ assert_true(!(prop in cs), "window.CSSFontFeatureValuesRule not available but computedStyle." + prop + " is available - ");
+ }, "computedStyle." + prop + " availability");
+ }
+ }
+
+}
+
+testCSSFontFeatureValuesRuleOM();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_bug98997.html b/layout/style/test/test_bug98997.html
new file mode 100644
index 0000000000..5dc325f9ef
--- /dev/null
+++ b/layout/style/test/test_bug98997.html
@@ -0,0 +1,144 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=98997
+-->
+<head>
+ <title>Test for Bug 98997</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ /*
+ * This test does NOT test any of the cases where :empty and
+ * :-moz-only-whitespace differ. We should probably have some tests
+ * for that as well.
+ */
+ div.test { width: 200px; height: 30px; margin: 5px 0; }
+ div.test.to, div.test.from:empty { background: orange; }
+ div.test.to:empty, div.test.from { background: green; }
+ div.test.to, div.test.from:-moz-only-whitespace { color: maroon; }
+ div.test.to:-moz-only-whitespace, div.test.from { color: navy; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=98997">Mozilla Bug 98997</a>
+<div id="display">
+<div class="test to" onclick="testReplaceChild(this, '')">x</div>
+<div class="test to" onclick="testReplaceChild(this, '')"><span>x</span></div>
+<div class="test to" onclick="testReplaceChild(this, '')">x<!-- comment --></div>
+<div class="test to" onclick="testRemoveChild(this)">x</div>
+<div class="test to" onclick="testRemoveChild(this)"><span>x</span></div>
+<div class="test to" onclick="testRemoveChild(this)">x<!-- comment --></div>
+<div class="test to" onclick="testChangeData(this, '')">x</div>
+<div class="test to" onclick="testChangeData(this, '')">x<!-- comment --></div>
+<div class="test to" onclick="testDeleteData(this)">x</div>
+<div class="test to" onclick="testDeleteData(this)">x<!-- comment --></div>
+<div class="test to" onclick="testReplaceData(this, '')">x</div>
+<div class="test to" onclick="testReplaceData(this, '')">x<!-- comment --></div>
+
+<div class="test from makeemptytext" onclick="testReplaceChild(this, 'x')"></div>
+<div class="test from makeemptytext" onclick="testReplaceChild(this, 'x')"><!-- comment --></div>
+<div class="test from" onclick="testReplaceChild(this, 'x')"><!-- comment --></div>
+<div class="test from" onclick="testInsertBefore(this, 'x')"></div>
+<div class="test from" onclick="testInsertBefore(this, 'x')"><!-- comment --></div>
+<div class="test from" onclick="testAppendChild(this, 'x')"></div>
+<div class="test from" onclick="testAppendChild(this, 'x')"><!-- comment --></div>
+<div class="test from makeemptytext" onclick="testChangeData(this, 'x')"></div>
+<div class="test from makeemptytext" onclick="testChangeData(this, 'x')"><!-- comment --></div>
+<div class="test from makeemptytext" onclick="testAppendData(this, 'x')"></div>
+<div class="test from makeemptytext" onclick="testAppendData(this, 'x')"><!-- comment --></div>
+<div class="test from makeemptytext" onclick="testReplaceData(this, 'x')"></div>
+<div class="test from makeemptytext" onclick="testReplaceData(this, 'x')"><!-- comment --></div>
+</div>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 98997 **/
+
+function testInsertBefore(elt, text) {
+ elt.insertBefore(document.createTextNode(text), elt.firstChild);
+}
+
+function testAppendChild(elt, text) {
+ elt.appendChild(document.createTextNode(text));
+}
+
+function testReplaceChild(elt, text) {
+ elt.replaceChild(document.createTextNode(text), elt.firstChild);
+}
+
+function testRemoveChild(elt) {
+ elt.firstChild.remove();
+}
+
+function testChangeData(elt, text) {
+ elt.firstChild.data = text;
+}
+
+function testAppendData(elt, text) {
+ elt.firstChild.appendData(text);
+}
+
+function testDeleteData(elt) {
+ elt.firstChild.deleteData(0, elt.firstChild.length);
+}
+
+function testReplaceData(elt, text) {
+ elt.firstChild.replaceData(0, elt.firstChild.length, text);
+}
+
+var cnodes = document.getElementById("display").childNodes;
+var divs = [];
+var i;
+for (i = 0; i < cnodes.length; ++i) {
+ if (cnodes[i].nodeName == "DIV")
+ divs.push(cnodes[i]);
+}
+
+for (i in divs) {
+ let div = divs[i];
+ if (div.className.match(/makeemptytext/))
+ div.insertBefore(document.createTextNode(""), div.firstChild);
+}
+
+const ORANGE = "rgb(255, 165, 0)";
+const MAROON = "rgb(128, 0, 0)";
+const GREEN = "rgb(0, 128, 0)";
+const NAVY = "rgb(0, 0, 128)";
+
+function color(div) {
+ return getComputedStyle(div, "").color;
+}
+function bg(div) {
+ return getComputedStyle(div, "").backgroundColor;
+}
+
+for (i in divs) {
+ let div = divs[i];
+ is(bg(div), ORANGE, "should be orange");
+ is(color(div), MAROON, "should be maroon");
+}
+
+for (i in divs) {
+ let div = divs[i];
+ var e = document.createEvent("MouseEvents");
+ e.initEvent("click", true, true);
+ div.dispatchEvent(e);
+}
+
+for (i in divs) {
+ let div = divs[i];
+ is(bg(div), GREEN, "should be green");
+ is(color(div), NAVY, "should be navy");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_cascade.html b/layout/style/test/test_cascade.html
new file mode 100644
index 0000000000..6479d52617
--- /dev/null
+++ b/layout/style/test/test_cascade.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=4 tabstop=8 autoindent 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/. -->
+<html>
+<head>
+ <title>Test for Author style sheet aspects of CSS cascading</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ </style>
+</head>
+<body id="thebody">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div class="content_class" id="content" style="position:relative"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Author style sheet aspects of CSS cascading **/
+
+var style_element = document.createElement("style");
+var style_contents = document.createTextNode("");
+style_element.appendChild(style_contents);
+document.getElementsByTagName("head")[0].appendChild(style_element);
+
+var div = document.getElementById("content");
+var cs = window.getComputedStyle(div);
+var zindex = 0;
+
+/**
+ * Given the selectors |sel1| and |sel2|, in that order (the "order"
+ * aspect of the cascade), with declarations that are !important if
+ * |imp1|/|imp2| are true, assert that the one that wins in the
+ * cascading order is given by |winning| (which must be either 1 or 2).
+ */
+function do_test(sel1, imp1, sel2, imp2, winning) {
+ var ind1 = ++zindex;
+ var ind2 = ++zindex;
+ style_contents.data =
+ sel1 + " { z-index: " + ind1 + (imp1 ? "!important" :"") + " } " +
+ sel2 + " { z-index: " + ind2 + (imp2 ? "!important" :"") + " } ";
+ var result = cs.zIndex;
+ is(result, String((winning == 1) ? ind1 : ind2),
+ "cascading of " + style_contents.data);
+}
+
+// Test order, and order combined with !important
+do_test("div", false, "div", false, 2);
+do_test("div", false, "div", true, 2);
+do_test("div", true, "div", false, 1);
+do_test("div", true, "div", true, 2);
+
+// Test specificity on a single element
+do_test("div", false, "div.content_class", false, 2);
+do_test("div.content_class", false, "div", false, 1);
+
+// Test specificity across elements
+do_test("body#thebody div", false, "body div.content_class", false, 1);
+do_test("body div.content_class", false, "body#thebody div", false, 2);
+
+// Test specificity combined with !important
+do_test("div.content_class", false, "div", false, 1);
+do_test("div.content_class", true, "div", false, 1);
+do_test("div.content_class", false, "div", true, 2);
+do_test("div.content_class", true, "div", true, 1);
+
+function do_test_greater(sel1, sel2) {
+ do_test(sel1, false, sel2, false, 1);
+ do_test(sel2, false, sel1, false, 2);
+}
+
+function do_test_equal(sel1, sel2) {
+ do_test(sel1, false, sel2, false, 2);
+ do_test(sel2, false, sel1, false, 2);
+}
+
+// Test specificity of contents of :not()
+do_test_equal("div.content_class", "div:not(.wrong_class)");
+do_test_greater("div.content_class.content_class", "div.content_class");
+do_test_greater("div.content_class", "div");
+do_test_greater("div:not(.wrong_class)", "div");
+do_test_greater("div:not(.wrong_class):not(.wrong_class)",
+ "div:not(.wrong_class)");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_ch_ex_no_infloops.html b/layout/style/test/test_ch_ex_no_infloops.html
new file mode 100644
index 0000000000..db9eda20df
--- /dev/null
+++ b/layout/style/test/test_ch_ex_no_infloops.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=678671
+-->
+<head>
+ <title>Test for Bug 678671</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.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=678671">Mozilla Bug 678671</a>
+<p id="display"></p>
+<div id="content"></div>
+<script type="application/javascript">
+
+/** Test for Bug 678671 **/
+
+/**
+ * Test 'ex' and 'ch' units in every place we possible can to make
+ * sure they don't cause an infinite loop.
+ */
+
+var content = document.getElementById("content");
+var cs = getComputedStyle(content, "");
+
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ function test_val(v) {
+ content.style.setProperty(prop, v, "");
+ isnot(get_computed_value(cs, prop), "",
+ "Setting '" + prop + "' to '" + v + "' should not cause infinite loop");
+ }
+ test_val('3ex');
+ test_val('2ch');
+ function test_replaced_values(value_list) {
+ // For each item in value_list, if it looks like it has a dimension
+ // in it, replace those dimensions with 3ex and 2ch and test it.
+ for (var i = 0; i < value_list.length; ++i) {
+ var value = value_list[i];
+ function try_replace(withval) {
+ var rep = value.replace(/[0-9.]+[a-zA-Z]+/g, withval)
+ if (rep != value) {
+ test_val(rep);
+ }
+ }
+ try_replace('3ex');
+ try_replace('2ch');
+ }
+ }
+ test_replaced_values(info.initial_values);
+ test_replaced_values(info.other_values);
+ content.style.removeProperty(prop);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_change_hint_optimizations.html b/layout/style/test/test_change_hint_optimizations.html
new file mode 100644
index 0000000000..82e149154c
--- /dev/null
+++ b/layout/style/test/test_change_hint_optimizations.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for style change hint optimizations</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTests() {
+
+ /** Test for Bug 1251075 **/
+ function test_bug1251075_div(id) {
+ var utils = SpecialPowers.DOMWindowUtils;
+
+ var div = document.getElementById(id);
+ div.style.display = "";
+
+ var description = div.style.cssText;
+
+ div.firstElementChild.offsetTop;
+ var constructedBefore = utils.framesConstructed;
+
+ div.style.transform = "translateX(10px)";
+ div.firstElementChild.offsetTop;
+ is(utils.framesConstructed, constructedBefore,
+ "adding a transform style to an element with " + description +
+ " should not cause frame reconstruction even when the element " +
+ "has absolutely positioned descendants");
+
+ div.style.display = "none";
+ }
+
+ test_bug1251075_div("bug1251075_a");
+ test_bug1251075_div("bug1251075_b");
+
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="runTests()">
+<div id="bug1251075_a" style="will-change: transform; display:none">
+ <div style="position: absolute; top: 0; left: 0;"></div>
+ <div style="position: fixed; top: 0; left: 0;"></div>
+</div>
+<div id="bug1251075_b" style="filter: blur(3px); display:none">
+ <div style="position: absolute; top: 0; left: 0;"></div>
+ <div style="position: fixed; top: 0; left: 0;"></div>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_clip-path_polygon.html b/layout/style/test/test_clip-path_polygon.html
new file mode 100644
index 0000000000..3c77103ba2
--- /dev/null
+++ b/layout/style/test/test_clip-path_polygon.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+body {padding: 0;margin:0;}
+div {
+ width: 200px;
+ height: 200px;
+ position: fixed;
+ top: 50px;
+ left: 50px;
+ margin: 50px;
+ padding: 50px;
+ border: 50px solid red;
+ transform-origin: 0 0;
+ transform: translate(50px, 50px) scale(0.5);
+ background-color: green;
+ clip-path: polygon(0 0, 200px 0, 0 200px) content-box;
+}
+</style>
+<title>clip-path with polygon() hit test</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<div id="a"></div>
+<p style="margin-top: 110px">
+<script>
+var a = document.getElementById("a");
+isnot(a, document.elementFromPoint(199, 199), "a shouldn't be found");
+isnot(a, document.elementFromPoint(199, 250), "a shouldn't be found");
+isnot(a, document.elementFromPoint(250, 199), "a shouldn't be found");
+isnot(a, document.elementFromPoint(255, 255), "a shouldn't be found");
+isnot(a, document.elementFromPoint(301, 200), "a shouldn't be found");
+isnot(a, document.elementFromPoint(200, 301), "a shouldn't be found");
+is(a, document.elementFromPoint(200, 200), "a should be found");
+is(a, document.elementFromPoint(299, 200), "a should be found");
+is(a, document.elementFromPoint(200, 299), "a should be found");
+is(a, document.elementFromPoint(250, 250), "a should be found");
+</script>
+</html>
diff --git a/layout/style/test/test_color_rounding.html b/layout/style/test/test_color_rounding.html
new file mode 100644
index 0000000000..46698ede3d
--- /dev/null
+++ b/layout/style/test/test_color_rounding.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test rounding of CSS color valus</title>
+ <link rel="author" title="Manish Goregaokar" href="mailto:mgoregaokar@mozilla.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+<div id="colordiv"></div>
+<script>
+
+function do_test(color, computed, reason) {
+ test(function() {
+ var element = document.getElementById('colordiv');
+ // assume this works; this way we clear any previous state
+ element.style.color = "red";
+ element.style.color = color;
+ assert_equals(getComputedStyle(element).color, computed);
+ }, `${reason}: ${color}`);
+}
+
+do_test("rgb(10%, 10%, 10%, 10%)", "rgba(26, 26, 26, 0.1)", "rgb percent-to-int should round");
+do_test("rgb(10%, 10%, 10%)", "rgb(26, 26, 26)", "rgb percent-to-int should round");
+do_test("hsl(0, 0%, 90%)", "rgb(230, 230, 230)", "hsl-to rgb should round");
+do_test("hsla(0, 0%, 90%, 10%)", "rgba(230, 230, 230, 0.1)", "hsl-to rgb should round");
+do_test("rgb(100%, 100%, 0%)", "rgb(255, 255, 0)", "handling of extrema");
+do_test("rgba(100%, 100%, 100%, 100%)", "rgb(255, 255, 255)", "handling of extrema");
+do_test("rgba(100%, 100%, 100%, 0%)", "rgba(255, 255, 255, 0)", "handling of extrema");
+do_test("rgb(255.5, 260, 500, 50)", "rgb(255, 255, 255)", "out of bounds should be handled");
+do_test("rgb(254.5, 254.55, 254.45)", "rgb(255, 255, 254)", "number values should be rounded");
+do_test("rgb(99.8%, 99.9%, 99.7%)", "rgb(254, 255, 254)", "percentage values should be rounded");
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_compute_data_with_start_struct.html b/layout/style/test/test_compute_data_with_start_struct.html
new file mode 100644
index 0000000000..6970270c95
--- /dev/null
+++ b/layout/style/test/test_compute_data_with_start_struct.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for correct handling of aStartStruct parameter to nsRuleNode::Compute*Data</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <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=216456">Mozilla Bug 216456</a>
+<p id="display">
+ <span id="base"></span>
+ <span id="test"></span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * The purpose of this test was to test that in the old style system
+ * the nsRuleNode::Compute*Data functions were written correctly.
+ * In particular, in these functions, when the specified value of a
+ * property had unit eCSSUnit_Null, touching the computed data is
+ * forbidden. This is because we sometimes would stop walking up the
+ * rule tree when we find computed data for an initial subsequence of
+ * our rules (i.e., an ancestor rule node) that we can use as a starting
+ * point (aStartStruct) for the computation for the current rule node.
+ *
+ * However, we don't cache style structs in the rule tree in the current
+ * style system code, and property cascading no longer relies on hand
+ * written functions, so this particular failure mode isn't as likely to
+ * happen.
+ */
+
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#base, #test {}", gStyleSheet.cssRules.length)];
+var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#test {}", gStyleSheet.cssRules.length)];
+
+var gBase = getComputedStyle(document.getElementById("base"), "");
+var gTest = getComputedStyle(document.getElementById("test"), "");
+
+function test_property(prop, lower_set, higher_set) {
+ var info = gCSSProperties[prop];
+ if (info.subproperties || info.logical)
+ return;
+
+ gRule1.style.setProperty(prop, info[lower_set][0]);
+ gRule2.style.setProperty(prop, info[higher_set][0]);
+
+ if ("prerequisites" in info) {
+ for (var prereq in info.prerequisites) {
+ gRule2.style.setProperty(prereq, info.prerequisites[prereq], "");
+ }
+ }
+
+ gBase.getPropertyValue(prop);
+ var higher_set_val = gTest.getPropertyValue(prop);
+ gRule2.style.setProperty(prop, info[lower_set][0], "");
+ var lower_set_val = gTest.getPropertyValue(prop);
+ isnot(higher_set_val, lower_set_val, "initial and other values of " + prop + " are different");
+
+ gRule2.style.removeProperty(prop);
+ is(gTest.getPropertyValue(prop), lower_set_val, prop + " is not touched when its value comes from aStartStruct");
+
+ gRule1.style.removeProperty(prop);
+ if ("prerequisites" in info) {
+ for (var prereq in info.prerequisites) {
+ gRule2.style.removeProperty(prereq);
+ }
+ }
+}
+
+function round(lower_set, higher_set) {
+ for (var prop in gCSSProperties)
+ test_property(prop, lower_set, higher_set);
+}
+
+round("other_values", "initial_values");
+round("initial_values", "other_values");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style.html b/layout/style/test/test_computed_style.html
new file mode 100644
index 0000000000..c2dc667f2f
--- /dev/null
+++ b/layout/style/test/test_computed_style.html
@@ -0,0 +1,664 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for miscellaneous computed style issues</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for miscellaneous computed style issues **/
+
+var frame_container = document.getElementById("display");
+var noframe_container = document.getElementById("content");
+
+(function test_bug_595650() {
+ // Test handling of horizontal and vertical percentages for border-radius.
+ var p = document.createElement("p");
+ p.setAttribute("style", "width: 256px; height: 128px");
+ p.style.borderTopLeftRadius = "1.5625%"; /* 1/64 == 4px 2px */
+ p.style.borderTopRightRadius = "5px";
+ p.style.borderBottomRightRadius = "5px 3px";
+ p.style.borderBottomLeftRadius = "1.5625% 3.125%" /* 1/64 1/32 == 4px 4px */
+ var cs = getComputedStyle(p, "");
+
+ frame_container.appendChild(p);
+ is(cs.borderTopLeftRadius, "1.5625%",
+ "computed value of % border-radius, with frame");
+ is(cs.borderTopRightRadius, "5px",
+ "computed value of px border-radius, with frame");
+ is(cs.borderBottomRightRadius, "5px 3px",
+ "computed value of px border-radius, with frame");
+ is(cs.borderBottomLeftRadius, "1.5625% 3.125%",
+ "computed value of % border-radius, with frame");
+
+ noframe_container.appendChild(p);
+ is(cs.borderTopLeftRadius, "1.5625%",
+ "computed value of % border-radius, without frame");
+ is(cs.borderTopRightRadius, "5px",
+ "computed value of px border-radius, without frame");
+ is(cs.borderBottomRightRadius, "5px 3px",
+ "computed value of px border-radius, without frame");
+ is(cs.borderBottomLeftRadius, "1.5625% 3.125%",
+ "computed value of % border-radius, without frame");
+
+ p.remove();
+})();
+
+(function test_bug_1292447() {
+ // Was for bug 595651 which tests that clamping of border-radius
+ // is reflected in computed style.
+ // For compatibility issue, resolved value is computed value now.
+ var p = document.createElement("p");
+ p.setAttribute("style", "width: 190px; height: 90px; border: 5px solid;");
+ p.style.borderRadius = "1000px";
+ var cs = getComputedStyle(p, "");
+
+ frame_container.appendChild(p);
+ is(cs.borderTopLeftRadius, "1000px",
+ "computed value of clamped border radius (top left)");
+ is(cs.borderTopRightRadius, "1000px",
+ "computed value of clamped border radius (top right)");
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of clamped border radius (bottom right)");
+ is(cs.borderBottomLeftRadius, "1000px",
+ "computed value of clamped border radius (bottom left)");
+
+ p.style.overflowY = "scroll";
+ is(cs.borderTopLeftRadius, "1000px",
+ "computed value of clamped border radius (top left, overflow-y)");
+ // Fennec doesn't have scrollbars for overflow:scroll content
+ if (p.clientWidth == p.offsetWidth - 10) {
+ is(cs.borderTopRightRadius, "1000px",
+ "computed value of border radius (top right, overflow-y)");
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of border radius (bottom right, overflow-y)");
+ } else {
+ is(cs.borderTopRightRadius, "1000px",
+ "computed value of clamped border radius (top right, overflow-y)");
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of clamped border radius (bottom right, overflow-y)");
+ }
+ is(cs.borderBottomLeftRadius, "1000px",
+ "computed value of clamped border radius (bottom left, overflow-y)");
+
+ p.style.overflowY = "hidden";
+ p.style.overflowX = "scroll";
+ is(cs.borderTopLeftRadius, "1000px",
+ "computed value of clamped border radius (top left, overflow-x)");
+ is(cs.borderTopRightRadius, "1000px",
+ "computed value of clamped border radius (top right, overflow-x)");
+ // Fennec doesn't have scrollbars for overflow:scroll content
+ if (p.clientHeight == p.offsetHeight - 10) {
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of border radius (bottom right, overflow-x)");
+ is(cs.borderBottomLeftRadius, "1000px",
+ "computed value of border radius (bottom left, overflow-x)");
+ } else {
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of clamped border radius (bottom right, overflow-x)");
+ is(cs.borderBottomLeftRadius, "1000px",
+ "computed value of clamped border radius (bottom left, overflow-x)");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_647885_1() {
+ // Test that various background-position styles round-trip correctly
+ var backgroundPositions = [
+ [ "0 0", "0px 0px", "unitless 0" ],
+ [ "0px 0px", "0px 0px", "0 with units" ],
+ [ "0% 0%", "0% 0%", "0%" ],
+ [ "calc(0px) 0", "0px 0px", "0 calc with units x" ],
+ [ "0 calc(0px)", "0px 0px", "0 calc with units y" ],
+ [ "calc(3px - 3px) 0", "0px 0px", "computed 0 calc with units x" ],
+ [ "0 calc(3px - 3px)", "0px 0px", "computed 0 calc with units y" ],
+ [ "calc(0%) 0", "0% 0px", "0% calc x"],
+ [ "0 calc(0%)", "0px 0%", "0% calc y"],
+ [ "calc(3px + 2% - 2%) 0", "calc(0% + 3px) 0px",
+ "computed 0% calc x"],
+ [ "0 calc(3px + 2% - 2%)", "0px calc(0% + 3px)",
+ "computed 0% calc y"],
+ [ "calc(3px - 5px) calc(6px - 7px)", "-2px -1px",
+ "negative pixel width"],
+ [ "", "0% 0%", "initial value" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundPositions.length; ++i) {
+ var test = backgroundPositions[i];
+ p.style.backgroundPosition = test[0];
+ is(cs.backgroundPosition, test[1], "computed value of " + test[2] + " background-position");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_647885_2() {
+ // Test that various background-size styles round-trip correctly
+ var backgroundSizes = [
+ [ "0 0", "0px 0px", "unitless 0" ],
+ [ "0px 0px", "0px 0px", "0 with units" ],
+ [ "0% 0%", "0% 0%", "0%" ],
+ [ "calc(0px) 0", "0px 0px", "0 calc with units horizontal" ],
+ [ "0 calc(0px)", "0px 0px", "0 calc with units vertical" ],
+ [ "calc(3px - 3px) 0", "0px 0px", "computed 0 calc with units horizontal" ],
+ [ "0 calc(3px - 3px)", "0px 0px", "computed 0 calc with units vertical" ],
+ [ "calc(0%) 0", "0% 0px", "0% calc horizontal"],
+ [ "0 calc(0%)", "0px 0%", "0% calc vertical"],
+ [ "calc(3px + 2% - 2%) 0", "calc(0% + 3px) 0px",
+ "computed 0% calc horizontal"],
+ [ "0 calc(3px + 2% - 2%)", "0px calc(0% + 3px)",
+ "computed 0% calc vertical"],
+ [ "calc(3px - 5px) calc(6px - 9px)", "0px 0px", "negative pixel width" ],
+ [ "", "auto", "initial value" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundSizes.length; ++i) {
+ var test = backgroundSizes[i];
+ p.style.backgroundSize = test[0];
+ is(cs.backgroundSize, test[1], "computed value of " + test[2] + " background-size");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_716628() {
+ // Test that various gradient styles round-trip correctly
+ var backgroundImages = [
+ [ "radial-gradient(at 10% bottom, #ffffff, black)",
+ "radial-gradient(at 10% 100%, rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient 1" ],
+ [ "radial-gradient(#ffffff, black)",
+ "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient 2" ],
+ [ "radial-gradient(farthest-corner, #ffffff, black)",
+ "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient 3" ],
+ [ "linear-gradient(red, blue)",
+ "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient 1" ],
+ [ "linear-gradient(to bottom, red, blue)",
+ "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient 2" ],
+ [ "linear-gradient(to right, red, blue)",
+ "linear-gradient(to right, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient 3" ],
+ [ "linear-gradient(-45deg, red, blue)",
+ "linear-gradient(-45deg, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient with angle in degrees" ],
+ [ "linear-gradient(-0.125turn, red, blue)",
+ "linear-gradient(-45deg, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient with angle in turns" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundImages.length; ++i) {
+ var test = backgroundImages[i];
+ p.style.backgroundImage = test[0];
+ is(cs.backgroundImage, test[1], "computed value of " + test[2] + " background-image");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1363349_linear() {
+ const specPrefix = "-webkit-gradient(linear, ";
+ const specSuffix = ", from(blue), to(lime))";
+
+ const expPrefix = "linear-gradient(";
+ const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+ let testcases = [
+ [ "calc(5 + 5) top, calc(10 + 10) top",
+ "to right",
+ "calc(num+num) in position"
+ ],
+ [ "left calc(25% - 10%), right calc(75% + 10%)",
+ "to right bottom",
+ "calc(pct+pct) in position "
+ ]
+ ];
+
+ let p = document.createElement("p");
+ let cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (let test of testcases) {
+ let specifiedStyle = specPrefix + test[0] + specSuffix;
+ let expectedStyle = expPrefix;
+ if (test[1] != "") {
+ expectedStyle += test[1] + ", ";
+ }
+ expectedStyle += expSuffix;
+
+ p.style.backgroundImage = specifiedStyle;
+ is(cs.backgroundImage, expectedStyle,
+ "computed value of -webkit-gradient expression (" + test[2] + ")");
+ p.style.backgroundImage = "";
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1363349_radial() {
+ const specPrefix = "-webkit-gradient(radial, ";
+ const specSuffix = ", from(blue), to(lime))";
+
+ const expPrefix = "radial-gradient(";
+ const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+ let testcases = [
+ [ "1 2, 0, 3 4, calc(1 + 5)",
+ "6px at 3px 4px",
+ "calc(num+num) in radius"
+ ],
+ [ "1 2, calc(1 + 2), 3 4, calc(1 + 5)",
+ "6px at 3px 4px",
+ "calc(num+num) in radius"
+ ],
+ [ "calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5)",
+ "6px at 3px 4px",
+ "calc(num+num) in position and radius"
+ ]
+ ];
+
+ let p = document.createElement("p");
+ let cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (let test of testcases) {
+ let specifiedStyle = specPrefix + test[0] + specSuffix;
+ let expectedStyle = expPrefix;
+ if (test[1] != "") {
+ expectedStyle += test[1] + ", ";
+ }
+ expectedStyle += expSuffix;
+
+ p.style.backgroundImage = specifiedStyle;
+ is(cs.backgroundImage, expectedStyle,
+ "computed value of -webkit-gradient expression (" + test[2] + ")");
+ p.style.backgroundImage = "";
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1241623() {
+ // Test that -webkit-gradient() styles are approximated the way we expect:
+
+ // For compactness, we'll pull out the common prefix & suffix from all of the
+ // specified & expected styles, and construct the full expression on the fly:
+ const specPrefix = "-webkit-gradient(linear, ";
+ const specSuffix = ", from(blue), to(lime))";
+
+ const expPrefix = "linear-gradient(";
+ const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+ let testcases = [
+ //
+ // [ legacyDirection,
+ // modernDirection, // (empty string means use default direction)
+ // descriptionOfTestcase ],
+
+ // If start & end are at same point, we just produce a gradient with
+ // the default direction.
+ [ "left top, left top",
+ "",
+ "start & end point are the same" ],
+ [ "40 40, 40 40",
+ "",
+ "start & end point are the same" ],
+ [ "center center, center center",
+ "",
+ "start & end point are the same" ],
+
+ // If start & end use different units in the same coordinate, we generally
+ // can't extract a direction (because we can't know whether arbitrary
+ // percent values are larger or smaller than arbitrary pixel values). So
+ // we produce a gradient in the default direction.
+ [ "left top, 30 100%", // (Note: keywords like "left" are really % vals.)
+ "",
+ "start & end point have different units" ],
+ [ "100% 15, right bottom",
+ "",
+ "start & end point have different units" ],
+ [ "0 0%, 20 20",
+ "",
+ "start & end point have different units" ],
+ [ "0 0, 100% 20",
+ "",
+ "start & end point have different units" ],
+ [ "5% 30, 20 50%",
+ "",
+ "start & end point have different units" ],
+ [ "5% 6%, 20 30",
+ "",
+ "start & end point have different units" ],
+
+ // Gradient starting/ending somewhere arbitrary in middle:
+ [ "center center, right top",
+ "to right top",
+ "from center to right top" ],
+ [ "left top, center center",
+ "to right bottom",
+ "from left top to center" ],
+ [ "10 15, 5 20",
+ "to left bottom",
+ "from arbitrary point to another point in lower-left direction" ],
+
+ // Gradient using negative coordinates:
+ [ "-10 -15, 0 0",
+ "to right bottom",
+ "from negative point to origin" ],
+ [ "-100 10, 20 30",
+ "to right bottom",
+ "from negative-x point to another point in lower-right direction" ],
+ [ "10 -100, 5 10",
+ "to left bottom",
+ "from negative-y point to another point in lower-left direction" ],
+
+ // Diagonal gradient between sides/corners:
+ [ "center top, left center",
+ "to left bottom",
+ "left/bottom-wards, using edge keywords" ],
+ [ "left center, center top",
+ "to right top",
+ "top/right-wards, using edge keywords" ],
+ [ "right center, center top",
+ "to left top",
+ "top/left-wards, using edge keywords" ],
+ [ "right top, center bottom",
+ "to left bottom",
+ "bottom/left-wards, using edge keywords" ],
+ [ "left top, right bottom",
+ "to right bottom",
+ "bottom/right-wards, using edge keywords" ],
+ [ "left bottom, right top",
+ "to right top",
+ "top/right-wards, using edge keywords" ],
+ ];
+
+ let p = document.createElement("p");
+ let cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (let test of testcases) {
+ let specifiedStyle = specPrefix + test[0] + specSuffix;
+ let expectedStyle = expPrefix;
+ if (test[1] != "") {
+ expectedStyle += test[1] + ", ";
+ }
+ expectedStyle += expSuffix;
+
+ p.style.backgroundImage = specifiedStyle;
+ is(cs.backgroundImage, expectedStyle,
+ "computed value of -webkit-gradient expression (" + test[2] + ")");
+ p.style.backgroundImage = "";
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1293164() {
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ var docPath = document.URL.substring(0, document.URL.lastIndexOf("/") + 1);
+
+ var localURL = "url(\"#foo\")";
+ var nonLocalURL = "url(\"foo.svg#foo\")";
+ var resolvedNonLocalURL = "url(\"" + docPath + "foo.svg#foo\")";
+
+ var testStyles = [
+ "maskImage",
+ "backgroundImage",
+ "markerStart",
+ "markerMid",
+ "markerEnd",
+ "clipPath",
+ "filter",
+ "fill",
+ "stroke",
+ ];
+
+ for (var prop of testStyles) {
+ p.style[prop] = localURL;
+ is(cs[prop], localURL, "computed value of " + prop);
+ p.style[prop] = nonLocalURL;
+ is(cs[prop], resolvedNonLocalURL, "computed value of " + prop);
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1347164() {
+ // Test that computed color values are serialized as "rgb()"
+ // IFF they're fully-opaque (and otherwise as "rgba()").
+ var color = [
+ ["rgba(0, 0, 0, 1)", "rgb(0, 0, 0)"],
+ ["rgba(0, 0, 0, 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["hsla(0, 0%, 0%, 1)", "rgb(0, 0, 0)"],
+ ["hsla(0, 0%, 0%, 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ];
+
+ var css_color_4 = [
+ ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgba(0 0 0 / 0.1)", "rgba(0, 0, 0, 0.1)"],
+ ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgb(0 0 0 / 0.2)", "rgba(0, 0, 0, 0.2)"],
+ ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsla(0deg 0% 0% / 0.3)", "rgba(0, 0, 0, 0.3)"],
+ ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsl(0 0% 0% / 0.4)", "rgba(0, 0, 0, 0.4)"],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < color.length; ++i) {
+ var test = color[i];
+ p.style.color = test[0];
+ is(cs.color, test[1], "computed value of " + test[0]);
+ }
+ for (var i = 0; i < css_color_4.length; ++i) {
+ var test = css_color_4[i];
+ p.style.color = test[0];
+ is(cs.color, test[1], "css-color-4 computed value of " + test[0]);
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1357117() {
+ // Test that vendor-prefixed gradient styles round-trip with the same prefix,
+ // or with no prefix.
+ var backgroundImages = [
+ // [ specified style,
+ // expected computed style,
+ // descriptionOfTestcase ],
+ // Linear gradient with legacy-gradient-line (needs prefixed syntax):
+ [ "-webkit-linear-gradient(10deg, red, blue)",
+ "-webkit-linear-gradient(10deg, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "-webkit-linear-gradient with angled legacy-gradient-line" ],
+
+ // Linear gradient with box corner (needs prefixed syntax):
+ [ "-webkit-linear-gradient(top left, red, blue)",
+ "-webkit-linear-gradient(left top, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "-webkit-linear-gradient with box corner" ],
+
+ // Linear gradient with default keyword (should be serialized without keyword):
+ [ "-webkit-linear-gradient(top, red, blue)",
+ "-webkit-linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))",
+ "-webkit-linear-gradient with legacy default direction keyword" ],
+
+ // Radial gradients (should be serialized using modern unprefixed style):
+ [ "-webkit-radial-gradient(contain, red, blue)",
+ "-webkit-radial-gradient(closest-side, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "-webkit-radial-gradient with legacy 'contain' keyword" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundImages.length; ++i) {
+ var test = backgroundImages[i];
+ p.style.backgroundImage = test[0];
+ is(cs.backgroundImage, test[1],
+ "computed value of prefixed gradient expression (" + test[2] + ")");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1367028() {
+ const borderImageSubprops = [
+ "border-image-slice",
+ "border-image-outset",
+ "border-image-width"
+ ];
+ const rectValues = [
+ {
+ values: ["5 5 5 5", "5 5 5", "5 5", "5"],
+ expected: "5",
+ desc: "identical four sides",
+ },
+ {
+ values: ["5 6 5 6", "5 6 5", "5 6"],
+ expected: "5 6",
+ desc: "identical values on each axis",
+ },
+ {
+ values: ["5 6 7 6", "5 6 7"],
+ expected: "5 6 7",
+ desc: "identical values on left and right",
+ },
+ {
+ values: ["5 6 5 7"],
+ desc: "identical values on top and bottom",
+ },
+ {
+ values: ["5 5 6 6", "5 6 6 5"],
+ desc: "identical values on unrelated sides",
+ },
+ {
+ values: ["5 6 7 8"],
+ desc: "different values on all sides",
+ },
+ ];
+
+ let frameContainer = document.getElementById("display");
+ let p = document.createElement("p");
+ frameContainer.appendChild(p);
+ let cs = getComputedStyle(p);
+
+ for (let prop of borderImageSubprops) {
+ for (let {values, expected, desc} of rectValues) {
+ for (let value of values) {
+ p.style.setProperty(prop, value);
+ is(cs.getPropertyValue(prop),
+ expected ? expected : value, `${desc} for ${prop}`);
+ p.style.removeProperty(prop);
+ }
+ }
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1378368() {
+ // Test that negative results of calc()s in basic-shapes (e.g. polygon()) should
+ // not be clamped to 0px.
+ var clipPaths = [
+ // [ specified style,
+ // expected computed style,
+ // descriptionOfTestcase ],
+ // polygon:
+ [ "polygon(calc(10px - 20px) 0px, 100px 100px, 0px 100px)",
+ "polygon(-10px 0px, 100px 100px, 0px 100px)",
+ "polygon with negative calc() coordinates" ],
+ // inset:
+ [ "inset(calc(10px - 20px))",
+ "inset(-10px)",
+ "inset with negative calc() coordinates" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (let test of clipPaths) {
+ p.style.clipPath = test[0];
+ is(cs.clipPath, test[1],
+ "computed value of clip-path for basic-shapes (" + test[2] + ")");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1418433() {
+ // Test that the style data read through getComputedStyle is always up-to-date,
+ // even for non-displayed elements.
+
+ var d = document.createElement("div");
+ d.setAttribute("id", "nonDisplayedDiv");
+ var cs = getComputedStyle(d, null);
+ noframe_container.appendChild(d);
+
+ // Test for stylesheet change, i.e., added/changed/removed
+ var style = document.getElementById("style");
+ is(cs.height, "auto",
+ "computed value of display none element (before testing)");
+ style.textContent = "#nonDisplayedDiv { height: 100px; }";
+ is(cs.height, "100px",
+ "computed value of display none element (sheet added)");
+ style.textContent = "#nonDisplayedDiv { height: 10px; }";
+ is(cs.height, "10px",
+ "computed value of display none element (sheet changed)");
+ style.textContent = "";
+ is(cs.height, "auto",
+ "computed value of display none element (sheet removed)");
+
+ // Test for rule change, i.e., added/changed/removed
+ var styleSheet = style.sheet;
+ is(cs.width, "auto",
+ "computed value of display none element (before testing)");
+ styleSheet.insertRule("#nonDisplayedDiv { width: 100px; }", 0);
+ is(cs.width, "100px",
+ "computed value of display none element (rule added)");
+ styleSheet.deleteRule(0);
+ styleSheet.insertRule("#nonDisplayedDiv { width: 10px; }", 0);
+ is(cs.width, "10px",
+ "computed value of display none element (rule changed)");
+ styleSheet.deleteRule(0);
+ is(cs.width, "auto",
+ "computed value of display none element (rule removed)");
+
+ d.remove();
+})();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_bfcache_display_none.html b/layout/style/test/test_computed_style_bfcache_display_none.html
new file mode 100644
index 0000000000..8322e4977a
--- /dev/null
+++ b/layout/style/test/test_computed_style_bfcache_display_none.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<title>Test for getting the computed style on the root node of a display:none subtree in a document in the bfcache</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1377010">Mozilla Bug 1377010</a>
+<p id="display"></p>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+let testDiv;
+let loadedPromiseResolve;
+
+const TEST_PATH = "http://mochi.test:8888/tests/layout/style/test/";
+const TEST_FILE1 = TEST_PATH + "file_computed_style_bfcache_display_none.html";
+const TEST_FILE2 = TEST_PATH + "file_computed_style_bfcache_display_none2.html";
+
+// Open a new window.
+const w = window.open(TEST_FILE1);
+waitForLoadMessage().then(() => {
+ // Take a reference to a node in the new window.
+ testDiv = w.document.getElementById('div');
+
+ // Open a new document so that the test div now refers to a node in a
+ // document in the bfcache.
+ w.location = TEST_FILE2;
+ return waitForLoadMessage();
+}).then(() => {
+ // Compute styles for the node in the bfcache document.
+ is(w.getComputedStyle(testDiv).opacity, '1');
+
+ // Restore the bfcache document.
+ return goBack(w);
+}).then(() => {
+ // Fetch the style once again.
+ is(w.getComputedStyle(testDiv).opacity, '1');
+
+ w.close();
+ SimpleTest.finish();
+});
+
+window.addEventListener('message', e => {
+ if (e.data === 'loaded' && loadedPromiseResolve) {
+ loadedPromiseResolve();
+ loadedPromiseResolve = undefined;
+ }
+});
+
+function waitForLoadMessage() {
+ return new Promise(resolve => {
+ loadedPromiseResolve = resolve;
+ });
+}
+
+function goBack(win) {
+ return new Promise(resolve => {
+ win.onpagehide = e => resolve(win);
+ win.history.back();
+ });
+}
+</script>
diff --git a/layout/style/test/test_computed_style_difference.html b/layout/style/test/test_computed_style_difference.html
new file mode 100644
index 0000000000..f4008ff476
--- /dev/null
+++ b/layout/style/test/test_computed_style_difference.html
@@ -0,0 +1,104 @@
+<!doctype html>
+<title>Test that the difference of the computed style of an element is always correctly propagated</title>
+<!--
+ There are CSS property changes that don't have an effect in computed style.
+
+ It's relatively easy to return `nsChangeHint(0)` for the case where the
+ property changes but it should have no rendering difference.
+
+ That's however incorrect, since if it's an inherited property, or a
+ descendant explicitly inherits it, we should still propagate the change
+ downwards.
+
+ This test tests that computed style diffing is correct.
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="property_database.js"></script>
+<div id="outer">
+ <div id="inner"></div>
+</div>
+<script>
+// We need to skip checking for properties for which the value returned by
+// getComputedStyle depends on the parent.
+//
+// TODO(emilio): We could test a subset of these, see below.
+const kWhitelist = [
+ // Could test display values that don't force blockification of children.
+ "display",
+
+ // Could avoid testing only the ones that have percentages.
+ "transform",
+ "transform-origin",
+ "perspective-origin",
+
+ "padding-bottom",
+ "padding-left",
+ "padding-right",
+ "padding-top",
+ "padding-inline-end",
+ "padding-inline-start",
+ "padding-block-end",
+ "padding-block-start",
+
+ "margin-bottom",
+ "margin-left",
+ "margin-right",
+ "margin-top",
+ "margin-inline-end",
+ "margin-inline-start",
+ "margin-block-end",
+ "margin-block-start",
+
+ "width",
+ "height",
+ "block-size",
+ "inline-size",
+
+ "min-height",
+ "min-width",
+ "min-block-size",
+ "min-inline-size",
+];
+
+const outer = document.getElementById("outer");
+const inner = document.getElementById("inner");
+
+function testValue(prop, value) {
+ outer.style.setProperty(prop, value);
+ const computed = getComputedStyle(outer).getPropertyValue(prop);
+ assert_equals(
+ getComputedStyle(inner).getPropertyValue(prop), computed,
+ "Didn't handle the inherited change correctly?"
+ )
+}
+
+// Note that we intentionally ignore the "prerequisites" here, since that's
+// the most likely place where the diffing could potentially go wrong.
+function testProperty(prop, info) {
+ // We only care about longhands, changing shorthands is not that interesting,
+ // since we're interested of changing as little as possible, and changing
+ // them would be equivalent to changing all the longhands at the same time.
+ if (info.type !== CSS_TYPE_LONGHAND)
+ return;
+ if (kWhitelist.includes(prop))
+ return;
+
+ inner.style.setProperty(prop, "inherit");
+ for (const v of info.initial_values)
+ testValue(prop, v);
+ for (const v of info.other_values)
+ testValue(prop, v);
+ // Test again the first value so that we test changing to it, not just from
+ // it.
+ //
+ // TODO(emilio): We could test every value against every-value if we wanted,
+ // might be worth it.
+ testValue(prop, info.initial_values[0]);
+
+ inner.style.removeProperty(prop);
+}
+
+for (let prop in gCSSProperties)
+ test(() => testProperty(prop, gCSSProperties[prop]), "Diffing for " + prop);
+</script>
diff --git a/layout/style/test/test_computed_style_grid_with_pseudo.html b/layout/style/test/test_computed_style_grid_with_pseudo.html
new file mode 100644
index 0000000000..24eb520776
--- /dev/null
+++ b/layout/style/test/test_computed_style_grid_with_pseudo.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1350780
+-->
+<head>
+<title>Test for Bug 1350780</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<style>
+#container {
+ width: 100px;
+}
+
+.gridBefore::before {
+ content: "";
+ display: grid;
+ grid-template-columns: auto;
+}
+
+.gridBeforeNoContent::before {
+ display: grid;
+ grid-template-columns: 40px;
+}
+</style>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function checkTemplateWithData(data) {
+ let obj = document.createElement("div");
+
+ // We need either a template or an additionalClass.
+ if (typeof(data.template != "undefined")) {
+ obj.style.display = "grid";
+ obj.style.gridTemplateColumns = data.template;
+ }
+
+ if (typeof(data.additionalClass != "undefined")) {
+ obj.className = data.additionalClass;
+ }
+
+ let container = document.getElementById("container");
+ container.appendChild(obj);
+
+ let computedStyle = getComputedStyle(obj, data.pseudo);
+ let computedTemplate = computedStyle.getPropertyValue("grid-template-columns");
+
+ let message = "Got expected template with pseudo " + data.pseudo;
+ if (typeof(data.additionalClass != "undefined")) {
+ message += " with class " + data.additionalClass;
+ }
+ message += ".";
+
+ is(computedTemplate, data.expected, message);
+
+ container.removeChild(obj);
+}
+
+function runTest() {
+ let dataToTest = [
+ { template: "40px",
+ pseudo: "::selection",
+ expected: "none"},
+ { template: "40px",
+ pseudo: "::before",
+ expected: "none" },
+ { additionalClass: "gridBefore",
+ pseudo: "::before",
+ expected: "100px" },
+ { additionalClass: "gridBeforeNoContent",
+ pseudo: "::before",
+ expected: "40px" },
+ ];
+
+ for (let i = 0; i < dataToTest.length; ++i) {
+ checkTemplateWithData(dataToTest[i]);
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</head>
+<body onload="runTest()">
+<div id="container"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1350780">Mozilla Bug 1350780</a>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_in_created_document.html b/layout/style/test/test_computed_style_in_created_document.html
new file mode 100644
index 0000000000..72ff0f5921
--- /dev/null
+++ b/layout/style/test/test_computed_style_in_created_document.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for bug 1398619</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+SimpleTest.waitForExplicitFinish();
+let referenceFontSize = getComputedStyle(document.body).fontSize;
+
+function checkComputedStyle(desc, doc) {
+ try {
+ let fontSize = getComputedStyle(doc.body).fontSize;
+ is(fontSize, referenceFontSize, `${desc}: get computed font-size`);
+ } catch (e) {
+ ok(false, `${desc}: fail to get computed font-size, ${e}`);
+ }
+}
+
+async function runTest() {
+ // DOMParser
+ {
+ let parser = new DOMParser();
+ let doc = parser.parseFromString("<body>", "text/html");
+ checkComputedStyle("DOMParser", doc);
+ }
+ // DOMImplementation
+ {
+ let doc = document.implementation.createHTMLDocument("");
+ checkComputedStyle("DOMImplementation", doc);
+ }
+ // XMLHttpRequest
+ {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "empty.html");
+ xhr.responseType = "document";
+ let promise = new Promise(resolve => {
+ xhr.onload = resolve;
+ });
+ xhr.send();
+ await promise;
+ checkComputedStyle("XMLHttpRequest", xhr.responseXML);
+ }
+}
+runTest()
+ .catch(e => ok(false, `Exception: ${e}`))
+ .then(() => SimpleTest.finish());
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_min_size_auto.html b/layout/style/test/test_computed_style_min_size_auto.html
new file mode 100644
index 0000000000..12b4e48b46
--- /dev/null
+++ b/layout/style/test/test_computed_style_min_size_auto.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=763689
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test behavior of 'min-height:auto' and 'min-width:auto' (Bug 763689 and Bug 1304636)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=763689">Mozilla Bug 763689</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1304636">Mozilla Bug 1304636</a>
+<body>
+<div id="display">
+ <div id="block-item">abc</div>
+
+ <div style="display: flex">
+ <div id="horizontal-flex-item">abc</div>
+ <div id="horizontal-flex-item-OH" style="overflow: hidden">def</div>
+ </div>
+
+ <div style="display: flex; flex-direction: column">
+ <div id="vertical-flex-item">abc</div>
+ <div id="vertical-flex-item-OH" style="overflow: hidden">def</div>
+ </div>
+
+ <div style="display: grid">
+ <div id="grid-item"></div>
+ <div id="grid-item-OH" style="overflow: hidden"></div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/**
+ * Test 'min-height:auto' and 'min-width:auto' (Bug 763689 and Bug 1304636)
+ * ========================================================
+ * This test checks the computed-style value of the "auto" keyword introduced
+ * for the "min-height" and "min-width" properties in CSS3 Flexbox Section 4.5
+ * and CSS3 Grid Section 6.2.
+ * https://www.w3.org/TR/css-flexbox-1/#min-size-auto
+ * https://www.w3.org/TR/css-grid-1/#grid-item-sizing
+ *
+ * Quoting that chunk of spec:
+ * # auto
+ * # On a flex item whose overflow is visible in the main axis,
+ * # when specified on the flex item’s main-axis min-size property,
+ * # specifies an automatic minimum size. It otherwise computes to 0
+ * # (unless otherwise defined by a future specification).
+ *
+ */
+
+// Given an element ID, this function sets the corresponding
+// element's inline-style min-width and min-height explicitly to "auto".
+function setElemMinSizesToAuto(aElemId) {
+ var elem = document.getElementById(aElemId);
+
+ is(elem.style.minWidth, "", "min-width should be initially unset");
+ elem.style.minWidth = "auto";
+ is(elem.style.minWidth, "auto", "min-width should accept 'auto' value");
+
+ is(elem.style.minHeight, "", "min-height should be initially unset");
+ elem.style.minHeight = "auto";
+ is(elem.style.minHeight, "auto", "min-height should accept 'auto' value");
+}
+
+// Given an element ID, this function compares the corresponding element's
+// computed min-width and min-height against expected values.
+function checkElemMinSizes(aElemId,
+ aExpectedMinWidth,
+ aExpectedMinHeight)
+{
+ var elem = document.getElementById(aElemId);
+ is(window.getComputedStyle(elem).minWidth, aExpectedMinWidth,
+ "checking min-width of " + aElemId);
+
+ is(window.getComputedStyle(elem).minHeight, aExpectedMinHeight,
+ "checking min-height of " + aElemId);
+}
+
+// This function goes through all the elements we're interested in
+// and checks their computed min-sizes against expected values,
+// farming out each per-element job to checkElemMinSizes.
+function checkAllTheMinSizes() {
+ // This is the normal part -- generally, the default value of "min-width"
+ // and "min-height" (auto) computes to "0px".
+ checkElemMinSizes("block-item", "0px", "0px");
+
+ // ...but for a flex item or grid item, "min-width: auto" and
+ // "min-height: auto" both compute to "auto" (even in cases where
+ // we know it'll actually resolve to 0 in layout, like for example
+ // when the item has "overflow:hidden").
+ checkElemMinSizes("horizontal-flex-item", "auto", "auto");
+ checkElemMinSizes("horizontal-flex-item-OH", "auto", "auto");
+ checkElemMinSizes("vertical-flex-item", "auto", "auto");
+ checkElemMinSizes("vertical-flex-item-OH", "auto", "auto");
+ checkElemMinSizes("grid-item", "auto", "auto");
+ checkElemMinSizes("grid-item-OH", "auto", "auto");
+}
+
+// Main test function
+function main() {
+ // First: check that min-sizes are what we expect, with min-size properties
+ // at their initial value.
+ checkAllTheMinSizes();
+
+ // Now, we *explicitly* set min-size properties to "auto"...
+ var elemIds = [ "block-item",
+ "horizontal-flex-item",
+ "horizontal-flex-item-OH",
+ "vertical-flex-item",
+ "vertical-flex-item-OH",
+ "grid-item",
+ "grid-item-OH"];
+ elemIds.forEach(setElemMinSizesToAuto);
+
+ // ...and try again (should have the same result):
+ checkAllTheMinSizes();
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_no_flush.html b/layout/style/test/test_computed_style_no_flush.html
new file mode 100644
index 0000000000..2caea9d294
--- /dev/null
+++ b/layout/style/test/test_computed_style_no_flush.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1363805: We only restyle as little as needed
+</title>
+<link rel="author" href="mailto:wpan@mozilla.com" title="Wei-Cheng Pan">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+.black {
+ background-color: black;
+}
+.black + div {
+ background-color: gray;
+}
+</style>
+<div id="container">
+ <div>
+ <div id="foo">
+ </div>
+ <div id="bar">
+ </div>
+ </div>
+</div>
+<script>
+function flushStyle () {
+ getComputedStyle(document.body).width;
+}
+
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+const container = document.querySelector('#container');
+const foo = document.querySelector('#foo');
+const bar = document.querySelector('#bar');
+
+flushStyle();
+let currentRestyleGeneration = utils.restyleGeneration;
+
+// No style changed, so we should not restyle.
+getComputedStyle(foo).backgroundColor;
+is(utils.restyleGeneration, currentRestyleGeneration,
+ "Shouldn't restyle anything if no style changed");
+
+// foo's parent has changed, must restyle.
+container.classList.toggle('black');
+getComputedStyle(foo).backgroundColor;
+isnot(utils.restyleGeneration, currentRestyleGeneration,
+ "Should have restyled something");
+
+currentRestyleGeneration = utils.restyleGeneration;
+
+// The change of foo should not affect its parent.
+foo.classList.toggle('black');
+getComputedStyle(container).backgroundColor;
+is(utils.restyleGeneration, currentRestyleGeneration,
+ "Shouldn't restyle anything if no style changed");
+
+// It should restyle for foo's later sibling.
+getComputedStyle(bar).backgroundColor;
+isnot(utils.restyleGeneration, currentRestyleGeneration,
+ "Should have restyled something");
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_computed_style_no_pseudo.html b/layout/style/test/test_computed_style_no_pseudo.html
new file mode 100644
index 0000000000..efb0dda7b4
--- /dev/null
+++ b/layout/style/test/test_computed_style_no_pseudo.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=505515
+-->
+<head>
+ <title>Test for Bug 505515</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display { color: black; background: white; }
+ #display span { position: relative; display: inline-block; }
+ #display:first-line { color: blue; }
+
+ </style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=505515">Mozilla Bug 505515</a>
+<p id="display" style="width: 30em">This <span id="sp">is</span> some text in which the first line is in a different color.</p>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/** Test for Bug 505515 **/
+
+function run() {
+ var p = document.getElementById("display");
+ var span = document.getElementById("sp");
+
+ isnot(span.offsetWidth, 0,
+ "span should have width (and we flushed layout)");
+ is(getComputedStyle(p, "").color, "rgb(0, 0, 0)",
+ "p should be black too");
+
+ let spanStyle = getComputedStyle(span, "");
+ let width = spanStyle.width;
+
+ isnot(width.indexOf("px"), -1,
+ "should be able to get the used value")
+ is(width, spanStyle.width,
+ "shouldn't lose track of the frame");
+ is(spanStyle.color, "rgb(0, 0, 0)",
+ "span should be black");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_prefs.html b/layout/style/test/test_computed_style_prefs.html
new file mode 100644
index 0000000000..0f297477d6
--- /dev/null
+++ b/layout/style/test/test_computed_style_prefs.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that preffed off properties do not appear in computed style</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=919594">Mozilla Bug 919594</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test that preffed off properties do not appear in computed style **/
+
+function testWithAllPrefsDisabled() {
+ let exposedProperties = Object.keys(gCS).map(i => gCS[i]);
+
+ // Store the number of properties for later tests to use.
+ gLengthWithAllPrefsDisabled = gCS.length;
+
+ // Check that all of the properties behind the prefs are not exposed.
+ for (let pref in gProps) {
+ for (let prop of gProps[pref]) {
+ ok(!exposedProperties.includes(prop), prop + " not exposed when prefs are false");
+ }
+ }
+}
+
+function testWithOnePrefEnabled(aPref) {
+ let exposedProperties = Object.keys(gCS).map(i => gCS[i]);
+
+ // Check that the number of properties on the object is as expected.
+ is(gCS.length, gLengthWithAllPrefsDisabled + gProps[aPref].length, "length when " + aPref + " is true");
+
+ // Check that the properties corresponding to aPref are exposed.
+ for (let prop of gProps[aPref]) {
+ ok(exposedProperties.includes(prop), prop + " exposed when " + aPref + " is true");
+ }
+}
+
+function step() {
+ if (gTestIndex == gTests.length) {
+ // Reached the end of the tests.
+ SimpleTest.finish();
+ return;
+ }
+
+ if (gPrefsPushed) {
+ // We've just finished running one tests. Pop the prefs and go on to
+ // the next test.
+ gTestIndex++;
+ gPrefsPushed = false;
+ SpecialPowers.popPrefEnv(step);
+ return;
+ }
+
+ // About to run one test. Push the prefs and run it.
+ let fn = gTests[gTestIndex].fn;
+ gPrefsPushed = true;
+ SpecialPowers.pushPrefEnv(gTests[gTestIndex].settings,
+ function() { fn(); SimpleTest.executeSoon(step); });
+}
+
+// ----
+
+var gProps = {
+ "layout.css.backdrop-filter.enabled": ["backdrop-filter"],
+};
+
+var gCS = getComputedStyle(document.body, "");
+var gLengthWithAllPrefsDisabled;
+
+var gTestIndex = 0;
+var gPrefsPushed = false;
+var gTests = [
+ // First, test when all of the prefs are disabled.
+ { settings: { set: Object.keys(gProps).map(x => [x, false]) },
+ fn: testWithAllPrefsDisabled },
+ // Then, test each pref enabled individually.
+ ...Object.keys(gProps).map(p =>
+ ({ settings: { set: Object.keys(gProps).map(x => [x, x == p]) },
+ fn: testWithOnePrefEnabled.bind(null, p) }))
+];
+
+SimpleTest.waitForExplicitFinish();
+step();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_condition_text.html b/layout/style/test/test_condition_text.html
new file mode 100644
index 0000000000..e2462979c2
--- /dev/null
+++ b/layout/style/test/test_condition_text.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814907
+-->
+<head>
+ <title>Test for Bug 814907</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+ @media all {}
+ @media only color {}
+ @media (color ) {}
+ @media color \0061ND ( monochrome ) {}
+ @media (max-width: 200px), (color) {}
+
+ @supports(color: green){}
+ @supports (color: green) {}
+ @supports ((color: green)) {}
+ @supports (color: green) and (color: blue) {}
+ @supports ( Font: 20px serif ! Important) {}
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=814907">Mozilla Bug 814907</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 814907 **/
+
+function runTest()
+{
+ // re-parse the style sheet with the pref turned on
+ var style = document.getElementById("style");
+ style.textContent += " ";
+
+ var sheet = style.sheet;
+
+ var conditions = [
+ "all",
+ "only color",
+ "(color)",
+ "color and (monochrome)",
+ "(max-width: 200px), (color)",
+ "(color: green)",
+ "(color: green)",
+ "((color: green))",
+ "(color: green) and (color: blue)",
+ "( Font: 20px serif ! Important)"
+ ];
+
+ is(sheet.cssRules.length, conditions.length);
+
+ for (var i = 0; i < sheet.cssRules.length; i++) {
+ var rule = sheet.cssRules[i];
+ is(rule.conditionText, conditions[i], "rule " + i + " has expected conditionText");
+ if (rule.type == CSSRule.MEDIA_RULE) {
+ is(rule.conditionText, rule.media.mediaText, "rule " + i + " conditionText matches media.mediaText");
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html b/layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html
new file mode 100644
index 0000000000..6d80b2bb7b
--- /dev/null
+++ b/layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Test for chrome-only rules in constructable stylesheets (in content)</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ add_task(async function chrome_rules_constructable_stylesheets_in_content() {
+ let sheet = new CSSStyleSheet();
+ sheet.replaceSync(".foo { -moz-default-appearance: none }");
+ is(sheet.cssRules[0].style.length, 0, "Should not parse chrome-only property in content document");
+ });
+
+ add_task(async function chrome_rules_constructable_stylesheets_in_content() {
+ let sheet = new CSSStyleSheet({ baseURL: "chrome://browser/content/browser.xhtml" })
+ sheet.replaceSync(".foo { -moz-default-appearance: none }");
+ is(sheet.cssRules[0].style.length, 0, "Should not parse chrome-only property in content document, even with chrome baseURL");
+ });
+</script>
diff --git a/layout/style/test/test_counter_descriptor_storage.html b/layout/style/test/test_counter_descriptor_storage.html
new file mode 100644
index 0000000000..bb91b6d12c
--- /dev/null
+++ b/layout/style/test/test_counter_descriptor_storage.html
@@ -0,0 +1,268 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for parsing, storage and serialization of CSS @counter-style descriptor values</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=966166">Mozilla Bug 966166</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+var gStyleElement = document.createElement("style");
+gStyleElement.setAttribute("type", "text/css");
+document.getElementsByTagName("head")[0].appendChild(gStyleElement);
+var gSheet = gStyleElement.sheet;
+gSheet.insertRule(
+ "@counter-style test { system: extends decimal }", 0);
+var gRule = gSheet.cssRules[0];
+
+function set_rule(ruleText) {
+ gSheet.deleteRule(0);
+ gSheet.insertRule("@counter-style test { " + ruleText + " }", 0);
+ gRule = gSheet.cssRules[0];
+}
+
+function run_tests(tests) {
+ for (var desc in tests) {
+ var items = tests[desc];
+ for (var i in items) {
+ var item = items[i];
+ var ref = item[0];
+ if (ref === null) {
+ ref = gRule[desc];
+ }
+ for (var j in item) {
+ if (item[j] !== null) {
+ gRule[desc] = item[j];
+ is(gRule[desc], ref,
+ "setting '" + item[j] + "' on '" + desc + "'");
+ }
+ }
+ }
+ }
+}
+
+function test_system_dep_desc() {
+ // for system requires at least one symbol
+ var oneSymbolTests = [
+ [null, "", "0"],
+ ["x y", "x y"],
+ ["\"x\"", "'x'"],
+ ["\\-", "\\2D"],
+ ["\\*", "\\2A"],
+ ];
+ // for system requires at least two symbols
+ var twoSymbolsTests = [
+ [null, "", "0", "x", "\"x\""],
+ ["x y", "x y"],
+ ["\"x\" \"y\"", "'x' 'y'"],
+ ];
+ var info = [
+ {
+ system: "cyclic",
+ base: "symbols: x",
+ base_tests: {
+ system: "cyclic",
+ symbols: "x"
+ },
+ tests: {
+ system: [
+ [null, "", "symbolic"],
+ ["cyclic", "Cyclic"],
+ ],
+ symbols: oneSymbolTests
+ }
+ },
+ {
+ system: "fixed",
+ base: "symbols: x",
+ base_tests: {
+ system: "fixed",
+ symbols: "x"
+ },
+ tests: {
+ system: [
+ [null, "", "symbolic"],
+ ["fixed 0"],
+ ["fixed", "FixeD"],
+ ["fixed 1", "FixeD 1"],
+ ["fixed -1"],
+ [null, "fixed a", "fixed \"0\"", "fixed 0 1"],
+ ],
+ symbols: oneSymbolTests
+ }
+ },
+ {
+ system: "symbolic",
+ base: "symbols: x",
+ base_tests: {
+ system: "symbolic",
+ symbols: "x"
+ },
+ tests: {
+ system: [
+ [null, "", "cyclic"],
+ ["symbolic", "SymBolic"],
+ ],
+ symbols: oneSymbolTests
+ }
+ },
+ {
+ system: "alphabetic",
+ base: "symbols: x y",
+ base_tests: {
+ system: "alphabetic",
+ symbols: "x y"
+ },
+ tests: {
+ system: [
+ [null, "", "cyclic"],
+ ["alphabetic", "AlphaBetic"],
+ ],
+ symbols: twoSymbolsTests
+ }
+ },
+ {
+ system: "numeric",
+ base: "symbols: x y",
+ base_tests: {
+ system: "numeric",
+ symbols: "x y"
+ },
+ tests: {
+ system: [
+ [null, "", "cyclic"],
+ ["numeric", "NumEric"],
+ ],
+ symbols: twoSymbolsTests
+ }
+ },
+ {
+ system: "additive",
+ base: "additive-symbols: 0 x",
+ base_tests: {
+ system: "additive",
+ additiveSymbols: "0 x"
+ },
+ tests: {
+ system: [
+ [null, "", "cyclic"],
+ ],
+ additiveSymbols: [
+ [null, "", "x", "0", "\"x\"", "1 x, 0", "0 x, 1 y"],
+ ["0 x", "x 0"],
+ ["1 y, 0 x", "y 1, 0 x", "1 y, x 0", "y 1, x 0"],
+ ["1 \"0\"", "\"0\" 1", "1 '0'"],
+ ]
+ }
+ },
+ {
+ system: "extends decimal",
+ base: "",
+ base_tests: {
+ system: "extends decimal",
+ symbols: "",
+ additiveSymbols: ""
+ },
+ tests: {
+ system: [
+ [null, "extends", "fixed", "cyclic", "extends symbols('*')"],
+ ["extends cjk-decimal", "ExTends cjk-decimal", "extends CJK-decimal"],
+ ],
+ symbols: [
+ [null, "x", "x y"],
+ ],
+ additiveSymbols: [
+ [null, "0 x", "1 y, 0 x"],
+ ]
+ }
+ }
+ ];
+ for (var i = 0; i < info.length; i++) {
+ var item = info[i];
+ set_rule("system: " + item.system + "; " + item.base);
+ for (var desc in item.base_tests) {
+ is(gRule[desc], item.base_tests[desc],
+ "checking base value of '" + desc + "' " +
+ "for system '" + item.system + "'");
+ }
+ run_tests(item.tests);
+ }
+}
+
+function test_system_indep_desc() {
+ var tests = {
+ name: [
+ [null, "", "-", " ", "a b"],
+ [null, "decimal", "none", "Decimal", "NONE"],
+ ["cjk-decimal", "CJK-Decimal", "cjk-Decimal"],
+ ["X"],
+ ["x", "\\78"],
+ ["\\-", "\\2D"],
+ ],
+ negative: [
+ [null, "-", "", "0", "a b c"],
+ ["\"-\"", "'-'", "\"\\2D\""],
+ ["\\-", "\\2D"],
+ ["a b"],
+ ["\"(\" \")\"", "'(' ')'"],
+ ],
+ prefix: [
+ [null, "0", "-", " ", "a b"],
+ ["a"],
+ ["\"a\""],
+ ],
+ suffix: [
+ [null, "0", "-", " ", "a b"],
+ ["a"],
+ ["\"a\""],
+ ],
+ range: [
+ ["auto", "auTO"],
+ ["infinite infinite", "INFinite inFinite"],
+ ["0 infinite", "0 INFINITE"],
+ ["infinite 100"],
+ ["1 1"],
+ ["0 100", "0 100"],
+ ["0 100, 2 300, -1 1, infinite -100"],
+ [null, "0", "0 a", "a 0"],
+ [null, "1 -1", "1 -1, 0 100", "-1 1, 100 0"],
+ ],
+ pad: [
+ ["0 \"\"", "\"\" 0"],
+ ["1 a", "a 1", "1 a", "\\61 1"],
+ [null, "0", "\"\"", "0 0", "a a", "0 a a"],
+ ],
+ fallback: [
+ [null, "", "-", "0", "a b", "symbols('*')"],
+ ["a"],
+ ["A"],
+ ["decimal", "Decimal"],
+ ],
+ speakAs: [
+ [null, "", "-", "0", "a b", "symbols('*')"],
+ ["auto", "AuTo"],
+ ["bullets", "BULLETs"],
+ ["numbers", "NumBers"],
+ ["words", "WordS"],
+ // Currently spell-out is not supported, so it should be treated
+ // as an invalid value.
+ [null, "spell-out", "Spell-Out"],
+ ["a"],
+ ["A"],
+ ["decimal", "Decimal"],
+ ],
+ };
+ set_rule("system: extends decimal");
+ run_tests(tests);
+}
+
+test_system_dep_desc();
+test_system_indep_desc();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_counter_style.html b/layout/style/test/test_counter_style.html
new file mode 100644
index 0000000000..58f5763451
--- /dev/null
+++ b/layout/style/test/test_counter_style.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for css3-counter-style (Bug 966166)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ #ol_test, #ol_ref {
+ display: inline-block;
+ list-style-position: inside;
+ }
+ #ol_test { list-style-type: test; }
+ #ol_ref { list-style-type: ref; }
+ #div_test, #div_ref {
+ display: inline-block;
+ counter-reset: a -1;
+ }
+ #div_test::before { content: counter(a, test); }
+ #div_ref::before { content: counter(a, ref); }
+ </style>
+ <style type="text/css" id="counter">
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=966166">Mozilla Bug 966166</a>
+<div id="display"></div>
+<ol id="ol_test" start="-1"><li></li></ol><br>
+<ol id="ol_ref" start="-1"><li></li></ol><br>
+<div id="div_test"></div><br>
+<div id="div_ref"></div><br>
+<pre id="test">
+<script type="application/javascript">
+var gOlTest = document.getElementById("ol_test"),
+ gOlRef = document.getElementById("ol_ref"),
+ gDivTest = document.getElementById("div_test"),
+ gDivRef = document.getElementById("div_ref"),
+ gCounterSheet = document.getElementById("counter").sheet;
+
+var testRule, refRule;
+
+var basicStyle = "system: extends decimal; range: infinite infinite; ";
+var info = [
+ ["system",
+ "system: fixed -1; symbols: xxx;",
+ "system: fixed; symbols: xxx;"],
+ ["system",
+ "system: extends decimal;",
+ "system: extends cjk-ideographic;"],
+ ["negative", "", "negative: '((' '))';"],
+ ["negative", "", "negative: '---';"],
+ ["prefix", "", "prefix: '###';"],
+ ["suffix", "", "suffix: '###';"],
+ ["range",
+ "fallback: cjk-ideographic;",
+ "fallback: cjk-ideographic; range: 10 infinite;"],
+ ["pad", "", "pad: 10 '0';"],
+ ["fallback",
+ "range: 0 infinite;",
+ "range: 0 infinite; fallback: cjk-ideographic;"],
+ ["symbols",
+ "system: symbolic; symbols: '1';",
+ "system: symbolic; symbols: '111';"],
+ ["additiveSymbols",
+ "system: additive; additive-symbols: 1 '1';",
+ "system: additive; additive-symbols: 1 '111';"],
+];
+
+// force a reflow before test to eliminate bug 994418
+gOlTest.getBoundingClientRect().width;
+
+for (var i in info) {
+ var item = info[i];
+ var desc = item[0],
+ testStyle = item[1],
+ refStyle = item[2];
+ var isFix = (desc == "prefix" || desc == "suffix");
+
+ while (gCounterSheet.cssRules.length > 0) {
+ gCounterSheet.deleteRule(0);
+ }
+ gCounterSheet.insertRule("@counter-style test { " +
+ basicStyle + testStyle + "}", 0);
+ gCounterSheet.insertRule("@counter-style ref { " +
+ basicStyle + refStyle + "}", 1);
+ testRule = gCounterSheet.cssRules[0];
+ refRule = gCounterSheet.cssRules[1];
+
+ var olTestWidth = gOlTest.getBoundingClientRect().width;
+ var olRefWidth = gOlRef.getBoundingClientRect().width;
+ ok(olTestWidth > 0, "test ol has width");
+ ok(olRefWidth > 0, "ref ol has width");
+ ok(olTestWidth != olRefWidth,
+ "OLs have different width " +
+ "for rule '" + testStyle + "' and '" + refStyle + "'");
+
+ var divTestWidth = gDivTest.getBoundingClientRect().width;
+ var divRefWidth = gDivRef.getBoundingClientRect().width;
+ if (!isFix) {
+ ok(divTestWidth > 0, "test div has width");
+ ok(divRefWidth > 0, "ref div has width");
+ ok(divTestWidth != divRefWidth,
+ "DIVs have different width" +
+ "for rule '" + testStyle + "' and '" + refStyle + "'");
+ }
+
+ ok(testRule[desc] != refRule[desc],
+ "rules have different values for desciptor '" + desc + "'");
+ testRule[desc] = refRule[desc];
+
+ var olNewWidth = gOlTest.getBoundingClientRect().width;
+ var divNewWidth = gDivTest.getBoundingClientRect().width;
+ is(olNewWidth, olRefWidth);
+ if (!isFix) {
+ is(divNewWidth, divRefWidth);
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_crash_with_content_policy.html b/layout/style/test/test_crash_with_content_policy.html
new file mode 100644
index 0000000000..9acec58243
--- /dev/null
+++ b/layout/style/test/test_crash_with_content_policy.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtests for style system with content policy</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<iframe id="iframe"></iframe>
+<script>
+const TESTS = [
+ "file_bug1381233.html",
+];
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+
+var policyID = SpecialPowers.wrap(SpecialPowers.Components).ID("{b80e19d0-878f-d41b-2654-194714a4115c}");
+var policyName = "@mozilla.org/testpolicy;1";
+var policy = {
+ // nsISupports implementation
+ QueryInterface: function(iid) {
+
+ iid = SpecialPowers.wrap(iid);
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsIContentPolicy))
+ return this;
+
+ throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIFactory implementation
+ createInstance: function(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad: function(contentLocation, loadInfo) {
+ info(`shouldLoad is invoked for ${SpecialPowers.wrap(contentLocation).spec}`);
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+ shouldProcess: function(contentLocation, loadInfo) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+}
+policy = SpecialPowers.wrapCallbackObject(policy);
+
+// Register content policy
+var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager
+ .QueryInterface(Ci.nsIComponentRegistrar);
+componentManager.registerFactory(policyID, "Test content policy", policyName, policy);
+var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+categoryManager.addCategoryEntry("content-policy", policyName, policyName, false, true);
+
+
+SimpleTest.waitForExplicitFinish();
+
+async function runTests() {
+ let iframe = document.getElementById("iframe");
+ for (let test of TESTS) {
+ iframe.src = test;
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ });
+ ok(true, `${test} doesn't crash`);
+ }
+ categoryManager.deleteCategoryEntry("content-policy", policyName, false);
+ componentManager.unregisterFactory(policyID, policy);
+ SimpleTest.finish();
+}
+runTests();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_css_cross_domain.html b/layout/style/test/test_css_cross_domain.html
new file mode 100644
index 0000000000..8541c9c5ce
--- /dev/null
+++ b/layout/style/test/test_css_cross_domain.html
@@ -0,0 +1,158 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=524223 -->
+<head>
+ <title>Test cross-domain CSS loading</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ hr { border: none; clear: both }
+ .column {
+ margin: 10px;
+ float: left;
+ }
+ iframe {
+ width: 40px;
+ height: 680px;
+ border: none;
+ margin: 0;
+ padding: 0;
+ }
+ h2 { font-weight: normal; padding: 0 }
+ ol, h2 { font-size: 13px; line-height: 20px; }
+ ol { padding-left: 1em;
+ list-style-type: upper-roman }
+ ol ol { list-style-type: upper-alpha }
+ ol ol ol { list-style-type: decimal }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524223">Mozilla
+ Bug 524223</a>
+
+<hr/>
+
+<div class="column">
+<h2>&nbsp;</h2>
+<ol><li>text/css<ol><li>same origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>same to cross<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross to same<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li></ol></li>
+ <li>text/html<ol><li>same origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>same to cross<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross to same<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li></ol></li>
+</ol>
+</div>
+
+<div class="column">
+<h2>Quirks</h2>
+<div id="quirks-placeholder"></div>
+</div>
+
+<div class="column">
+<h2>Standards</h2>
+<div id="standards-placeholder"></div>
+</div>
+
+<script type="application/javascript">
+
+const COLOR = {red: "rgb(255, 0, 0)", lime: "rgb(0, 255, 0)"};
+
+// Cross origin requests with text/html as the contentType.
+// These requests will be blocked by ORB (when ORB is enabled),
+// thus the color of the element is not going to be changed.
+const BLOCKED_BY_ORB = ["JD1i", "JD1l", "JD2i", "JD2l"];
+
+/** Test for Bug 524223 **/
+function check_iframe(ifr) {
+ var doc = ifr.contentDocument;
+ var cases = doc.getElementsByTagName("p");
+
+ for (var i = 0; i < cases.length; i++) {
+ var color = doc.defaultView.getComputedStyle(cases[i])
+ .getPropertyValue("background-color");
+
+ var id = cases[i].id;
+ // only 'quirks' can have requests that are blocked by ORB.
+ if (BLOCKED_BY_ORB.includes(id) && ifr.id === "quirks") {
+ is(color, COLOR.red, ifr.id + " " + id);
+ } else {
+ is(color, COLOR.lime, ifr.id + " " + id);
+ }
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+function insertIFrames(src, id) {
+ const quirks = document.createElement("iframe");
+ quirks.src = "ccd-quirks.html";
+ quirks.id = "quirks";
+ document.getElementById("quirks-placeholder").replaceWith(quirks);
+
+ const standards = document.createElement("iframe");
+ standards.src = "ccd-standards.html";
+ standards.id = "standards";
+ document.getElementById("standards-placeholder").replaceWith(standards);
+}
+
+var hasQuirksLoaded = false;
+var hasStandardsLoaded = false;
+
+function quirksLoaded() {
+ hasQuirksLoaded = true;
+ MaybeRunTest();
+}
+
+function standardsLoaded() {
+ hasStandardsLoaded = true;
+ MaybeRunTest();
+}
+
+function runTest() {
+ check_iframe(document.getElementById("quirks"));
+ check_iframe(document.getElementById("standards"));
+}
+
+function MaybeRunTest() {
+ if (!hasQuirksLoaded || !hasStandardsLoaded) {
+ return;
+ }
+
+ runTest();
+ SimpleTest.finish();
+}
+
+window.onload = async function() {
+ await SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ['browser.opaqueResponseBlocking', true],
+ ['browser.opaqueResponseBlocking.javascriptValidator', true],
+ ],
+ }
+ );
+ insertIFrames();
+};
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_css_cross_domain_no_orb.html b/layout/style/test/test_css_cross_domain_no_orb.html
new file mode 100644
index 0000000000..27ede793be
--- /dev/null
+++ b/layout/style/test/test_css_cross_domain_no_orb.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=524223 -->
+<head>
+ <title>Test cross-domain CSS loading</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ hr { border: none; clear: both }
+ .column {
+ margin: 10px;
+ float: left;
+ }
+ iframe {
+ width: 40px;
+ height: 680px;
+ border: none;
+ margin: 0;
+ padding: 0;
+ }
+ h2 { font-weight: normal; padding: 0 }
+ ol, h2 { font-size: 13px; line-height: 20px; }
+ ol { padding-left: 1em;
+ list-style-type: upper-roman }
+ ol ol { list-style-type: upper-alpha }
+ ol ol ol { list-style-type: decimal }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524223">Mozilla
+ Bug 524223</a>
+
+<hr/>
+
+<div class="column">
+<h2>&nbsp;</h2>
+<ol><li>text/css<ol><li>same origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>same to cross<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross to same<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li></ol></li>
+ <li>text/html<ol><li>same origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>same to cross<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross to same<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li></ol></li>
+</ol>
+</div>
+
+<div class="column">
+<h2>Quirks</h2>
+<div id="quirks-placeholder"></div>
+</div>
+
+<div class="column">
+<h2>Standards</h2>
+<div id="standards-placeholder"></div>
+</div>
+
+<script type="application/javascript">
+
+const COLOR = {red: "rgb(255, 0, 0)", lime: "rgb(0, 255, 0)"};
+
+/** Test for Bug 524223 **/
+function check_iframe(ifr) {
+ var doc = ifr.contentDocument;
+ var cases = doc.getElementsByTagName("p");
+
+ for (var i = 0; i < cases.length; i++) {
+ var color = doc.defaultView.getComputedStyle(cases[i])
+ .getPropertyValue("background-color");
+
+ var id = cases[i].id;
+ is(color, COLOR.lime, ifr.id + " " + id);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+function insertIFrames(src, id) {
+ const quirks = document.createElement("iframe");
+ quirks.src = "ccd-quirks.html";
+ quirks.id = "quirks";
+ document.getElementById("quirks-placeholder").replaceWith(quirks);
+
+ const standards = document.createElement("iframe");
+ standards.src = "ccd-standards.html";
+ standards.id = "standards";
+ document.getElementById("standards-placeholder").replaceWith(standards);
+}
+
+var hasQuirksLoaded = false;
+var hasStandardsLoaded = false;
+
+function quirksLoaded() {
+ hasQuirksLoaded = true;
+ MaybeRunTest();
+}
+
+function standardsLoaded() {
+ hasStandardsLoaded = true;
+ MaybeRunTest();
+}
+
+function runTest() {
+ check_iframe(document.getElementById("quirks"));
+ check_iframe(document.getElementById("standards"));
+}
+
+function MaybeRunTest() {
+ if (!hasQuirksLoaded || !hasStandardsLoaded) {
+ return;
+ }
+
+ runTest();
+ SimpleTest.finish();
+}
+
+window.onload = async function() {
+ await SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ['browser.opaqueResponseBlocking', false],
+ ],
+ }
+ );
+ insertIFrames();
+};
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_css_eof_handling.html b/layout/style/test/test_css_eof_handling.html
new file mode 100644
index 0000000000..099ca4c752
--- /dev/null
+++ b/layout/style/test/test_css_eof_handling.html
@@ -0,0 +1,268 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS EOF handling</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p><a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=311616">bug 311616</a>,
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=325064">bug 325064</a></p>
+<iframe id="display"></iframe>
+<p id="log"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+const tests = [
+ {
+ name: "basic rule",
+ ref: "#r {background-color : orange}",
+ tst: "#t {background-color : orange",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "function",
+ ref: "#r {background-color: rgb(0,255,0)}",
+ tst: "#t {background-color: rgb(0,255,0",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "comment",
+ ref: "#r {background-color: aqua/*marine*/}",
+ tst: "#t {background-color: aqua/*marine",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "@media 1",
+ ref: "@media all { #r { background-color: yellow } }",
+ tst: "@media all { #t { background-color: yellow }",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "@media 2",
+ ref: "@media all { #r { background-color: magenta } }",
+ tst: "@media all { #t { background-color: magenta",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "@import 1",
+ ref: "@import 'data:text/css,%23r%7Bbackground-color%3Agray%7D';",
+ tst: "@import 'data:text/css,%23t%7Bbackground-color%3Agray%7D",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "@import 2",
+ ref: "@import 'data:text/css,%23r%7Bbackground-color%3Ablack%7D' all;",
+ tst: "@import 'data:text/css,%23t%7Bbackground-color%3Ablack%7D' all",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "url-token 1",
+ ref: "#r { background-image: url(data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAADklEQVQI12NI" +
+ "YJgAhAkAB4gB4Ry+pcoAAAAASUVORK5CYII=) }",
+ tst: "#t { background-image: url(data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAADklEQVQI12NI" +
+ "YJgAhAkAB4gB4Ry+pcoAAAAASUVORK5CYII=",
+ prop: "background-image", pseudo: ""
+ },
+ {
+ name: "url-token 2",
+ ref: "#r { background-image: url('data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12Mo" +
+ "YNjAcIHhAQAJ2ALR4kRk1gAAAABJRU5ErkJggg==') }",
+ tst: "#t { background-image: url('data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12Mo" +
+ "YNjAcIHhAQAJ2ALR4kRk1gAAAABJRU5ErkJggg==",
+ prop: "background-image", pseudo: ""
+ },
+ {
+ name: "url-token 3",
+ ref: "#r { background-image: url('data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12N4" +
+ "wHCBYQNDAQAMuALRrGb97AAAAABJRU5ErkJggg==') }",
+ tst: "#t { background-image: url('data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12N4" +
+ "wHCBYQNDAQAMuALRrGb97AAAAABJRU5ErkJggg=='",
+ prop: "background-image", pseudo: ""
+ },
+ {
+ name: "url-token 4", /*Bug 751939*/
+ ref: "#r { background-image: url( )}",
+ tst: "#t { background-image: url(" ,
+ prop: "background-image", pseudo: ""
+ },
+ {
+ name: "counter",
+ ref: "#r::before { content: counter(tr, upper-alpha) }",
+ tst: "#t::before { content: counter(tr, upper-alpha",
+ prop: "content", pseudo: "::before"
+ },
+ {
+ name: "string",
+ ref: "#r::before { content: 'B' }",
+ tst: "#t::before { content: 'B",
+ prop: "content", pseudo: "::before"
+ },
+
+ /* For these tests, there is no visible effect on computed style;
+ instead we have to audit the DOM stylesheet object. */
+
+ {
+ todo: 1, /* bug 446226 */
+ name: "selector 1",
+ ref: "td[colspan='3'] {}",
+ tst: "td[colspan='3"
+ },
+ {
+ todo: 1, /* bug 446226 */
+ name: "selector 2",
+ ref: "td[colspan='3'] {}",
+ tst: "td[colspan='3'"
+ },
+ {
+ todo: 1, /* bug 446226 */
+ name: "selector 3",
+ ref: "td:lang(en) {}",
+ tst: "td:lang(en"
+ },
+
+ {
+ name: "@media 3",
+ ref: "@media all {}",
+ tst: "@media all {",
+ },
+ {
+ name: "@namespace 1a",
+ ref: "@namespace foo url('http://foo.example.com/');",
+ tst: "@namespace foo url('http://foo.example.com/')"
+ },
+ {
+ name: "@namespace 1b",
+ ref: "@namespace foo url(http://foo.example.com/);",
+ tst: "@namespace foo url(http://foo.example.com/"
+ },
+ {
+ name: "@namespace 1c",
+ ref: "@namespace foo url('http://foo.example.com/');",
+ tst: "@namespace foo url('http://foo.example.com/"
+ },
+ {
+ name: "@namespace 1d",
+ ref: "@namespace foo 'http://foo.example.com/';",
+ tst: "@namespace foo 'http://foo.example.com/'"
+ },
+ {
+ name: "@namespace 1e",
+ ref: "@namespace foo 'http://foo.example.com/';",
+ tst: "@namespace foo 'http://foo.example.com/"
+ },
+ {
+ name: "@namespace 2a",
+ ref: "@namespace url('http://foo.example.com/');",
+ tst: "@namespace url('http://foo.example.com/')"
+ },
+ {
+ name: "@namespace 2b",
+ ref: "@namespace url('http://foo.example.com/');",
+ tst: "@namespace url('http://foo.example.com/'"
+ },
+ {
+ name: "@namespace 2c",
+ ref: "@namespace url('http://foo.example.com/');",
+ tst: "@namespace url('http://foo.example.com/"
+ },
+ {
+ name: "@namespace 2d",
+ ref: "@namespace 'http://foo.example.com/';",
+ tst: "@namespace 'http://foo.example.com/'"
+ },
+ {
+ name: "@namespace 2e",
+ ref: "@namespace 'http://foo.example.com/';",
+ tst: "@namespace 'http://foo.example.com/"
+ }
+];
+
+const basestyle = ("table {\n"+
+ " border-collapse: collapse;\n"+
+ "}\n"+
+ "td {\n"+
+ " width: 1.5em;\n"+
+ " height: 1.5em;\n"+
+ " border: 1px solid black;\n"+
+ " text-align: center;\n"+
+ " margin: 0;\n"+
+ "}\n"+
+ "tr { counter-increment: tr }\n");
+
+/* This is more complicated than it might look like it needs to be,
+ because for each subtest we have to splat stuff into the iframe,
+ allow the renderer to run, and only then interrogate the computed
+ styles. */
+
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function() {
+ const frame = document.getElementById("display");
+ var curTest = 0;
+
+ const prepareTest = function() {
+ var cd = frame.contentDocument;
+ cd.open();
+ cd.write('<!DOCTYPE HTML><html><head>' +
+ '<style>\n' + basestyle + '</style>\n' +
+ '<style>\n' + tests[curTest].ref + '</style>\n' +
+ '<style>\n' + tests[curTest].tst + '</style>\n' +
+ '</head><body>\n' +
+ '<table><tr><td id="r"><td id="t"></table>' +
+ '</body></html>');
+ cd.close();
+ };
+
+ const checkTest = function() {
+ var cd = frame.contentDocument;
+ var _is = tests[curTest].todo ? todo_is : is;
+ var _ok = tests[curTest].todo ? todo : ok;
+
+ if (cd.styleSheets[1].cssRules.length == 1 &&
+ cd.styleSheets[2].cssRules.length == 1) {
+ // If we have a .prop for this test, the .cssText of the reference
+ // and test rules will differ in the selector. Change #t to #r
+ // in the test rule.
+ var ref_canon = cd.styleSheets[1].cssRules[0].cssText;
+ var tst_canon = cd.styleSheets[2].cssRules[0].cssText;
+ tst_canon = tst_canon.replace(/(#|%23)t\b/, "$1r");
+ _is(tst_canon, ref_canon,
+ tests[curTest].name + " (canonicalized rule)");
+ } else {
+ _ok(false, tests[curTest].name + " (rule missing)");
+ }
+ if (tests[curTest].prop) {
+ var prop = tests[curTest].prop;
+ var pseudo = tests[curTest].pseudo;
+
+ var refElt = cd.getElementById("r");
+ var tstElt = cd.getElementById("t");
+ var refStyle = cd.defaultView.getComputedStyle(refElt, pseudo);
+ var tstStyle = cd.defaultView.getComputedStyle(tstElt, pseudo);
+ _is(tstStyle.getPropertyValue(prop),
+ refStyle.getPropertyValue(prop),
+ tests[curTest].name + " (computed style)");
+ }
+ curTest++;
+ if (curTest < tests.length) {
+ prepareTest();
+ } else {
+ SimpleTest.finish();
+ }
+ };
+
+ frame.onload = function(){setTimeout(checkTest, 0);};
+ prepareTest();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_css_escape_api.html b/layout/style/test/test_css_escape_api.html
new file mode 100644
index 0000000000..2bcc094be1
--- /dev/null
+++ b/layout/style/test/test_css_escape_api.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=955860
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 955860</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=955860">Mozilla Bug 955860</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script>
+// Tests taken from:
+// https://github.com/mathiasbynens/CSS.escape/blob/master/tests/tests.js
+
+SimpleTest.doesThrow(() => CSS.escape(), 'undefined');
+
+is(CSS.escape('\0'), '\uFFFD', "escaping for 0 char (1)");
+is(CSS.escape('a\0'), 'a\uFFFD', "escaping for 0 char (2)");
+is(CSS.escape('\0b'), '\uFFFDb', "escaping for 0 char (3)");
+is(CSS.escape('a\0b'), 'a\uFFFDb', "escaping for 0 char (4)");
+
+is(CSS.escape('\uFFFD'), '\uFFFD', "escaping for replacement char (1)");
+is(CSS.escape('a\uFFFD'), 'a\uFFFD', "escaping replacement char (2)");
+is(CSS.escape('\uFFFDb'), '\uFFFDb', "escaping replacement char (3)");
+is(CSS.escape('a\uFFFDb'), 'a\uFFFDb', "escaping replacement char (4)");
+
+is(CSS.escape(true), 'true', "escapingFailed Character : true(bool)");
+is(CSS.escape(false), 'false', "escapingFailed Character : false(bool)");
+is(CSS.escape(null), 'null', "escapingFailed Character : null");
+is(CSS.escape(''), '', "escapingFailed Character : '' ");
+
+is(CSS.escape('\x01\x02\x1E\x1F'), '\\1 \\2 \\1e \\1f ',"escapingFailed Char: \\x01\\x02\\x1E\\x1F");
+
+is(CSS.escape('0a'), '\\30 a', "escapingFailed Char: 0a");
+is(CSS.escape('1a'), '\\31 a', "escapingFailed Char: 1a");
+is(CSS.escape('2a'), '\\32 a', "escapingFailed Char: 2a");
+is(CSS.escape('3a'), '\\33 a', "escapingFailed Char: 3a");
+is(CSS.escape('4a'), '\\34 a', "escapingFailed Char: 4a");
+is(CSS.escape('5a'), '\\35 a', "escapingFailed Char: 5a");
+is(CSS.escape('6a'), '\\36 a', "escapingFailed Char: 6a");
+is(CSS.escape('7a'), '\\37 a', "escapingFailed Char: 7a");
+is(CSS.escape('8a'), '\\38 a', "escapingFailed Char: 8a");
+is(CSS.escape('9a'), '\\39 a', "escapingFailed Char: 9a");
+
+is(CSS.escape('a0b'), 'a0b', "escapingFailed Char: a0b");
+is(CSS.escape('a1b'), 'a1b', "escapingFailed Char: a1b");
+is(CSS.escape('a2b'), 'a2b', "escapingFailed Char: a2b");
+is(CSS.escape('a3b'), 'a3b', "escapingFailed Char: a3b");
+is(CSS.escape('a4b'), 'a4b', "escapingFailed Char: a4b");
+is(CSS.escape('a5b'), 'a5b', "escapingFailed Char: a5b");
+is(CSS.escape('a6b'), 'a6b', "escapingFailed Char: a6b");
+is(CSS.escape('a7b'), 'a7b', "escapingFailed Char: a7b");
+is(CSS.escape('a8b'), 'a8b', "escapingFailed Char: a8b");
+is(CSS.escape('a9b'), 'a9b', "escapingFailed Char: a9b");
+
+is(CSS.escape('-0a'), '-\\30 a', "escapingFailed Char: -0a");
+is(CSS.escape('-1a'), '-\\31 a', "escapingFailed Char: -1a");
+is(CSS.escape('-2a'), '-\\32 a', "escapingFailed Char: -2a");
+is(CSS.escape('-3a'), '-\\33 a', "escapingFailed Char: -3a");
+is(CSS.escape('-4a'), '-\\34 a', "escapingFailed Char: -4a");
+is(CSS.escape('-5a'), '-\\35 a', "escapingFailed Char: -5a");
+is(CSS.escape('-6a'), '-\\36 a', "escapingFailed Char: -6a");
+is(CSS.escape('-7a'), '-\\37 a', "escapingFailed Char: -7a");
+is(CSS.escape('-8a'), '-\\38 a', "escapingFailed Char: -8a");
+is(CSS.escape('-9a'), '-\\39 a', "escapingFailed Char: -9a");
+
+is(CSS.escape('--a'), '--a', 'Should not need to escape leading "--"');
+
+is(CSS.escape('\x7F\x80\x2D\x5F\xA9'), '\\7f \x80\x2D\x5F\xA9', "escapingFailed Char: \\x7F\\x80\\x2D\\x5F\\xA9");
+is(CSS.escape('\xA0\xA1\xA2'), '\xA0\xA1\xA2', "escapingFailed Char: \\xA0\\xA1\\xA2");
+is(CSS.escape('a0123456789b'), 'a0123456789b', "escapingFailed Char: a0123465789");
+is(CSS.escape('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz', "escapingFailed Char: abcdefghijklmnopqrstuvwxyz");
+is(CSS.escape('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', "escapingFailed Char: ABCDEFGHIJKLMNOPQRSTUVWXYZBCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+is(CSS.escape('\x20\x21\x78\x79'), '\\ \\!xy', "escapingFailed Char: \\x20\\x21\\x78\\x79");
+
+// astral symbol (U+1D306 TETRAGRAM FOR CENTRE)
+is(CSS.escape('\uD834\uDF06'), '\uD834\uDF06', "escapingFailed Char:\\uD834\\uDF06");
+// lone surrogates
+is(CSS.escape('\uDF06'), '\uDF06', "escapingFailed Char: \\uDF06");
+is(CSS.escape('\uD834'), '\uD834', "escapingFailed Char: \\uD834");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_css_function_mismatched_parenthesis.html b/layout/style/test/test_css_function_mismatched_parenthesis.html
new file mode 100644
index 0000000000..e7e78cc545
--- /dev/null
+++ b/layout/style/test/test_css_function_mismatched_parenthesis.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=897094
+
+This test verifies that:
+(1) Mismatched parentheses in a CSS function prevent parsing of subsequent CSS
+properties.
+(2) Properly matched parentheses do not prevent parsing of subsequent CSS
+properties.
+-->
+<head>
+ <title>Test for Bug 897094</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=897094">Mozilla Bug 897094</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="target"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 897094 **/
+function check_parens(declaration, parens_are_balanced)
+{
+ var element = document.getElementById("target");
+ element.setAttribute("style",
+ "background-color: " + (parens_are_balanced ? "red" : "green") + "; " +
+ declaration + "; " +
+ "background-color: " + (parens_are_balanced ? "green" : "red") + "; ");
+ var resultColor = element.style.getPropertyValue("background-color");
+ is(resultColor, "green", "parenthesis balancing within " + declaration);
+}
+
+check_parens("transform: scale()", true);
+check_parens("transform: scale(", false);
+check_parens("transform: scale(,)", true);
+check_parens("transform: scale(,", false);
+check_parens("transform: scale(1)", true);
+check_parens("transform: scale(1", false);
+check_parens("transform: scale(1,)", true);
+check_parens("transform: scale(1,", false);
+check_parens("transform: scale(1,1)", true);
+check_parens("transform: scale(1,1", false);
+check_parens("transform: scale(1,1,)", true);
+check_parens("transform: scale(1,1,", false);
+check_parens("transform: scale(1,1,1)", true);
+check_parens("transform: scale(1,1,1", false);
+check_parens("transform: scale(1,1,1,)", true);
+check_parens("transform: scale(1,1,1,", false);
+check_parens("transform: scale(1px)", true);
+check_parens("transform: scale(1px", false);
+check_parens("transform: scale(1px,)", true);
+check_parens("transform: scale(1px,", false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_css_loader_crossorigin_data_url.html b/layout/style/test/test_css_loader_crossorigin_data_url.html
new file mode 100644
index 0000000000..67105d61f0
--- /dev/null
+++ b/layout/style/test/test_css_loader_crossorigin_data_url.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for handling of 'crossorigin' attribute on CSS link with data: URL</title>
+<link id="testlink" crossorigin rel="stylesheet" href="data:text/css,%23someuniqueidhere{display:none}">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="someuniqueidhere"></div>
+<script>
+ var t = async_test("link@crossorigin with data: href");
+ window.addEventListener("load", t.step_func_done(function() {
+ assert_equals(getComputedStyle(document.getElementById("someuniqueidhere")).display,
+ "none", "sheet should be applied");
+ assert_equals(document.getElementById("testlink").sheet.cssRules[0].style.display,
+ "none", "should be able to read data from the sheet");
+ }));
+</script>
diff --git a/layout/style/test/test_css_parse_error_smoketest.html b/layout/style/test/test_css_parse_error_smoketest.html
new file mode 100644
index 0000000000..96d8edce3a
--- /dev/null
+++ b/layout/style/test/test_css_parse_error_smoketest.html
@@ -0,0 +1,160 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for CSS parser reporting parsing errors with expected precision</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<style id="testbench"></style>
+<script>
+ SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true;
+ // Tests that apply to all types of style sheets
+ var tests = [
+ {
+ css: "@unknown {}",
+ error: "Unrecognized at-rule or error parsing at-rule ‘@unknown’.",
+ }, {
+ css: "x { color: invalid; }",
+ error: "Expected color but found ‘invalid’. Error in parsing value for ‘color’. Declaration dropped.",
+ cssSelectors: "x",
+ }, {
+ css: "x { filter: alpha(foo); }",
+ error: "Expected ‘none’, URL, or filter function but found ‘alpha(’. Error in parsing value for ‘filter’. Declaration dropped.",
+ cssSelectors: "x",
+ }, {
+ css: "x { color: red; abc; }",
+ error: "Unknown property ‘abc;’. Declaration dropped.",
+ cssSelectors: "x",
+ }, {
+ css: "x { filter: 5; }",
+ error: "Expected ‘none’, URL, or filter function but found ‘5’. Error in parsing value for ‘filter’. Declaration dropped.",
+ cssSelectors: "x",
+ }, {
+ css: "::unknown {}",
+ error: "Unknown pseudo-class or pseudo-element ‘unknown’. Ruleset ignored due to bad selector.",
+ }, {
+ css: ":unknown {}",
+ error: "Unknown pseudo-class or pseudo-element ‘unknown’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "::5 {}",
+ error: "Expected identifier for pseudo-class or pseudo-element but found ‘5’. Ruleset ignored due to bad selector.",
+ }, {
+ css: ": {}",
+ error: "Expected identifier for pseudo-class or pseudo-element but found ‘ ’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x[a.]{}",
+ error: "Unexpected token in attribute selector: ‘.’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x[*a]{}",
+ error: "Expected ‘|’ but found ‘a’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x[a=5]{}",
+ error: "Expected identifier or string for value in attribute selector but found ‘5’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x[$] {}",
+ error: "Expected attribute name or namespace but found ‘$’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "a[|5] {}",
+ error: "Expected identifier for attribute name but found ‘5’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "a[x|] {}",
+ error: "Unknown namespace prefix ‘x’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x| {}",
+ error: "Unknown namespace prefix ‘x’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "a> {}",
+ error: "Dangling combinator. Ruleset ignored due to bad selector.",
+ }, {
+ css: "~ {}",
+ error: "Selector expected. Ruleset ignored due to bad selector.",
+ }, {
+ css: "| {}",
+ error: "Expected element name or ‘*’ but found ‘ ’. Ruleset ignored due to bad selector.",
+ }, {
+ css: ". {}",
+ error: "Expected identifier for class selector but found ‘ ’. Ruleset ignored due to bad selector.",
+ }, {
+ css: ":not() {}",
+ error: "Selector expected. Ruleset ignored due to bad selector.",
+ }, {
+ css: "* { -webkit-text-size-adjust: 100% }",
+ error: "Error in parsing value for ‘-webkit-text-size-adjust’. Declaration dropped.",
+ cssSelectors: "*",
+ }, {
+ css: "@media (totally-unknown-feature) {}",
+ error: "Expected media feature name but found ‘totally-unknown-feature’.",
+ }, {
+ css: "@media \"foo\" {}",
+ error: "Unexpected token ‘\"foo\"’ in media list.",
+ }, {
+ css: "@media (min-width) {}",
+ error: "Media features with min- or max- must have a value.",
+ }, {
+ css: "@media (min-width >= 3px) {}",
+ error: "Unexpected operator in media list.",
+ }, {
+ css: "@media (device-height: three) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (min-width: foo) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (min-resolution: 2) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (min-monochrome: 1.1) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (min-aspect-ratio: 1 invalid) {}",
+ error: "Unexpected token ‘invalid’ in media list.",
+ }, {
+ css: "@media (min-aspect-ratio: 1 / invalid) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (orientation: invalid-orientation-value) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "a, .b, #c { unknown: invalid; }",
+ error: "Unknown property ‘unknown’. Declaration dropped.",
+ cssSelectors: "a, .b, #c"
+ },
+ {
+ css: ":host:hover { color: red; }",
+ error: ":host selector in ‘:host:hover’ is not featureless and will never match. Maybe you intended to use :host()?"
+ },
+ ];
+
+ // Tests that apply only to constructed style sheets
+ var constructedSheetTests = [
+ {
+ css: '@import url("sheet.css");',
+ error: "@import rules are not yet valid in constructed stylesheets."
+ }
+ ];
+
+ function assertMessages(messages, action) {
+ return new Promise(resolve => {
+ SimpleTest.expectConsoleMessages(action, messages, resolve);
+ });
+ }
+
+ async function runTests() {
+ for (let {css, cssSelectors = "", error} of tests) {
+ let messages = [ { cssSelectors, errorMessage: error } ];
+ await assertMessages(messages, () => { testbench.innerHTML = css });
+ await assertMessages(messages, () => { new CSSStyleSheet().replaceSync(css) });
+ await assertMessages(messages, async () => { await new CSSStyleSheet().replace(css) });
+ }
+ for (let {css, cssSelectors = "", error} of constructedSheetTests) {
+ let messages = [ { cssSelectors, errorMessage: error } ];
+ await assertMessages(messages, () => { new CSSStyleSheet().replaceSync(css) });
+ await assertMessages(messages, async () => { await new CSSStyleSheet().replace(css) });
+ }
+ }
+
+ add_task(runTests);
+
+</script>
diff --git a/layout/style/test/test_css_supports.html b/layout/style/test/test_css_supports.html
new file mode 100644
index 0000000000..a6b1e8d303
--- /dev/null
+++ b/layout/style/test/test_css_supports.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=779917
+-->
+<head>
+ <title>Test for Bug 779917</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=779917">Mozilla Bug 779917</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 779917 **/
+
+function runTest()
+{
+ var passingConditions = [
+ "color: green",
+ "(color: green)",
+ "((color: green))",
+ "(color: green !important)",
+ "(color: rainbow) or (color: green)",
+ "(color: green) or (color: rainbow)",
+ "(color: green) and (color: blue)",
+ "(color: rainbow) or (color: iridescent) or (color: green)",
+ "(color: red) and (color: green) and (color: blue)",
+ "(color:green)",
+ "not (color: rainbow)",
+ "not (not (color: green))",
+ "(unknown:) or (color: green)",
+ "(unknown) or (color: green)",
+ "(font: 16px serif)",
+ "(color:) or (color: green)",
+ "not (@page)",
+ "not ({ something @with [ balanced ] brackets })",
+ "an-extension(of some kind) or (color: green)",
+ "not ()",
+ "( Font: 20px serif ! Important) ",
+ "(color: /* comment */ green)",
+ "(/* comment */ color: green)",
+ "(color: green /* comment */)",
+ "(color: green) /* comment */",
+ "/* comment */ (color: green)",
+ "(color /* comment */: green)",
+ "(color: green) /* unclosed comment",
+ "(color: green",
+ "(((((((color: green",
+ "(font-family: 'Helvetica"
+ ];
+
+ var failingConditions = [
+ "(color: rainbow)",
+ "(color: rainbow) and (color: green)",
+ "(color: blue) and (color: rainbow)",
+ "(color: green) and (color: green) or (color: green)",
+ "(color: green) or (color: green) and (color: green)",
+ "not not (color: green)",
+ "not (color: rainbow) and not (color: iridescent)",
+ "not (color: rainbow) or (color: green)",
+ "(not (color: rainbow) or (color: green))",
+ "(unknown: green)",
+ "not ({ something @with (unbalanced brackets })",
+ "(color: green) or an-extension(that is [unbalanced)",
+ "not(unknown: unknown)",
+ "(color: green) or(color: blue)",
+ "(color: green;)",
+ "(font-family: 'Helvetica\n",
+ "(font-family: 'Helvetica\n')",
+ "()",
+ ""
+ ];
+
+ var passingDeclarations = [
+ ["color", "green"],
+ ["color", " green "],
+ ["Color", "Green"],
+ ["color", "green /* comment */"],
+ ["color", "/* comment */ green"],
+ ["color", "green /* unclosed comment"],
+ ["font", "16px serif"],
+ ["font", "16px /* comment */ serif"],
+ ["font", "16px\nserif"],
+ ["color", "\\0067reen"]
+ ];
+
+ var failingDeclarations = [
+ ["color ", "green"],
+ ["color", "rainbow"],
+ ["color", "green green"],
+ ["color", "green !important"],
+ ["\\0063olor", "green"],
+ ["/* comment */color", "green"],
+ ["color/* comment */", "green"],
+ ["font-family", "'Helvetica\n"],
+ ["font-family", "'Helvetica\n'"],
+ ["color", "green;"],
+ ["color", ""],
+ ["unknown", "unknown"],
+ ["", "green"],
+ ["", ""]
+ ];
+
+ passingConditions.forEach(function(aCondition) {
+ is(CSS.supports(aCondition), true, "CSS.supports returns true for passing condition \"" + aCondition + "\"");
+ });
+
+ failingConditions.forEach(function(aCondition) {
+ is(CSS.supports(aCondition), false, "CSS.supports returns false for failing condition \"" + aCondition + "\"");
+ });
+
+ passingDeclarations.forEach(function(aDeclaration) {
+ is(CSS.supports(aDeclaration[0], aDeclaration[1]), true, "CSS.supports returns true for supported declaration \"" + aDeclaration.join(":") + "\"");
+ });
+
+ failingDeclarations.forEach(function(aDeclaration) {
+ is(CSS.supports(aDeclaration[0], aDeclaration[1]), false, "CSS.supports returns false for unsupported declaration \"" + aDeclaration.join(":") + "\"");
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_css_supports_variables.html b/layout/style/test/test_css_supports_variables.html
new file mode 100644
index 0000000000..7efc14a4fc
--- /dev/null
+++ b/layout/style/test/test_css_supports_variables.html
@@ -0,0 +1,247 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=773296
+-->
+<head>
+ <title>Test for Bug 773296</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=773296">Mozilla Bug 773296</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 773296 **/
+
+function runTest()
+{
+ var passingConditions = [
+ "(color:var(--a))",
+ "(color: var(--a))",
+ "(color: var(--a) )",
+ "(color: var( --a ) )",
+ "(color: var(--a, ))",
+ "(color: var(--a,/**/a))",
+ "(color: var(--a,))",
+ "(color: var(--a,/**/))",
+ "(color: 1px var(--a))",
+ "(color: var(--a) 1px)",
+ "(color: something 3px url(whereever) calc(var(--a) + 1px))",
+ "(color: var(--a) !important)",
+ "(color: var(--a)var(--b))",
+ "(color: var(--a, var(--b, var(--c, black))))",
+ "(color: var(--a) <!--)",
+ "(color: --> var(--a))",
+ "(color: { [ var(--a) ] })",
+ "(color: [;] var(--a))",
+ "(color: var(--a,(;)))",
+ "(color: VAR(--a))",
+ "(color: var(--0))",
+ "(color: var(--\\30))",
+ "(color: var(--\\d800))",
+ "(color: var(--\\ffffff))",
+ "(color: var(--a",
+ "(color: var(--a , ",
+ "(color: var(--a, ",
+ "(color: var(--a, var(--b",
+ "(color: var(--a /* unclosed comment",
+ "(color: var(--a, '",
+ "(color: var(--a, '\\",
+ "(color: var(--a, \\",
+
+ "(--a:var(--b))",
+ "(--a: var(--b))",
+ "(--a: var(--b) )",
+ "(--a: var( --b ) )",
+ "(--a: var(--b, ))",
+ "(--a: var(--b,/**/a))",
+ "(--a: var(--b,))",
+ "(--a: var(--b,/**/))",
+ "(--a: 1px var(--b))",
+ "(--a: var(--b) 1px)",
+ "(--a: something 3px url(whereever) calc(var(--b) + 1px))",
+ "(--a: var(--b) !important)",
+ "(--a: var(--b)var(--b))",
+ "(--a: var(--b, var(--c, var(--d, black))))",
+ "(--a: var(--b) <!--)",
+ "(--a: --> var(--b))",
+ "(--a: { [ var(--b) ] })",
+ "(--a: [;] var(--b))",
+ "(--a: )",
+ "(--a:var(--a))",
+ "(--0: a)",
+ "(--\\30: a)",
+ "(--\\61: a)",
+ "(--\\d800: a)",
+ "(--\\ffffff: a)",
+ "(--\0: 1)",
+ "(--a: ",
+ "(--a: /* unclosed comment",
+ "(--a: var(--b",
+ "(--a: var(--b, ",
+ "(--a: var(--b, var(--c",
+ "(--a: [{(((",
+ "(--a: '",
+ "(--a: '\\",
+ "(--a: \\",
+ "(--a:)",
+ ];
+
+ var failingConditions = [
+ "(color: var(--a,!))",
+ "(color: var(--a,!important))",
+ "(color: var(--a) !important !important)",
+ "(color: var(--a,;))",
+ "(color: var(--a);)",
+ "(color: var(1px))",
+ "(color: var(--a)))",
+ "(color: var(--a) \"\n",
+ "(color: var(--a) url(\"\n",
+ "(color: var(a))",
+ "(color: var(--",
+ "(color: var(--))",
+
+ "(--a: var(--b,!))",
+ "(--a: var(--b,!important))",
+ "((--a: var(--b) !important !important))",
+ "(--a: var(--b,;))",
+ "(--a: var(--b);)",
+ "(--a: var(1px))",
+ "(--a: a))",
+ "(--a: \"\n",
+ "(--a: url(\"\n",
+ "(--a: var(a))",
+ "(--: a)",
+ ];
+
+ var passingDeclarations = [
+ ["color", "var(--a)"],
+ ["color", " var(--a)"],
+ ["color", "var(--a) "],
+ ["color", "var( --a ) "],
+ ["color", "var(--a, )"],
+ ["color", "var(--a,/**/a)"],
+ ["color", "1px var(--a)"],
+ ["color", "var(--a) 1px"],
+ ["color", "something 3px url(whereever) calc(var(--a) + 1px)"],
+ ["color", "var(--a)var(--b)"],
+ ["color", "var(--a, var(--b, var(--c, black)))"],
+ ["color", "var(--a) <!--"],
+ ["color", "--> var(--a)"],
+ ["color", "{ [ var(--a) ] }"],
+ ["color", "[;] var(--a)"],
+ ["color", "var(--a,(;))"],
+ ["color", "VAR(--a)"],
+ ["color", "var(--0)"],
+ ["color", "var(--\\30)"],
+ ["color", "var(--\\d800)"],
+ ["color", "var(--\\ffffff)"],
+ ["color", "var(--a"],
+ ["color", "var(--a , "],
+ ["color", "var(--a, "],
+ ["color", "var(--a, var(--b"],
+ ["color", "var(--a /* unclosed comment"],
+ ["color", "var(--a, '"],
+ ["color", "var(--a, '\\"],
+ ["color", "var(--a, \\"],
+ ["color", "var(--a,)"],
+ ["color", "var(--a,/**/)"],
+
+ ["--a", " var(--b)"],
+ ["--a", "var(--b)"],
+ ["--a", "var(--b) "],
+ ["--a", "var( --b ) "],
+ ["--a", "var(--b, )"],
+ ["--a", "var(--b,/**/a)"],
+ ["--a", "var(--b,)"],
+ ["--a", "var(--b,/**/)"],
+ ["--a", "1px var(--b)"],
+ ["--a", "var(--b) 1px"],
+ ["--a", "something 3px url(whereever) calc(var(--b) + 1px)"],
+ ["--a", "var(--b)var(--b)"],
+ ["--a", "var(--b, var(--c, var(--d, black)))"],
+ ["--a", "var(--b) <!--"],
+ ["--a", "--> var(--b)"],
+ ["--a", "{ [ var(--b) ] }"],
+ ["--a", "[;] var(--b)"],
+ ["--a", " "],
+ ["--a", ""],
+ ["--a", "var(--a)"],
+ ["--0", "a"],
+ ["--\\30", "a"],
+ ["--\\61", "a"],
+ ["--\\d800", "a"],
+ ["--\\ffffff", "a"],
+ ["--\0", "a"],
+ ["--\ud800", "a"],
+ ["--a", "a /* unclosed comment"],
+ ["--a", "var(--b"],
+ ["--a", "var(--b, "],
+ ["--a", "var(--b, var(--c"],
+ ["--a", "[{((("],
+ ["--a ", "a"],
+ ["--a ", "'"],
+ ["--a ", "'\\"],
+ ["--a ", "\\"],
+ ];
+
+ var failingDeclarations = [
+ ["color", "var(--a,!)"],
+ ["color", "var(--a,!important)"],
+ ["color", "var(--a,;)"],
+ ["color", "var(--a);"],
+ ["color", "var(1px)"],
+ ["color", "var(--a))"],
+ ["color", "var(--a) \"\n"],
+ ["color", "var(--a) url(\"\n"],
+ ["color", "var(--a) !important"],
+ ["color", "var(--a) !important !important"],
+ ["color", "var(a)"],
+ ["color", "var(--"],
+
+ ["--a", "var(--b,!)"],
+ ["--a", "var(--b,!important)"],
+ ["--a", "var(--b) !important !important"],
+ ["--a", "var(--b,;)"],
+ ["--a", "var(--b);"],
+ ["--a", "var(1px)"],
+ ["(VAR-a", "a"],
+ ["--a", "a)"],
+ ["--a", "\"\n"],
+ ["--a", "url(\"\n"],
+ ["--a", "var(--b))"],
+ ["--a", "var(b)"],
+ ["--", "a"],
+ ];
+
+ passingConditions.forEach(function(aCondition) {
+ is(CSS.supports(aCondition), true, "CSS.supports returns true for passing condition \"" + aCondition + "\"");
+ });
+
+ failingConditions.forEach(function(aCondition) {
+ is(CSS.supports(aCondition), false, "CSS.supports returns false for failing condition \"" + aCondition + "\"");
+ });
+
+ passingDeclarations.forEach(function(aDeclaration) {
+ is(CSS.supports(aDeclaration[0], aDeclaration[1]), true, "CSS.supports returns true for supported declaration \"" + aDeclaration.join(":") + "\"");
+ });
+
+ failingDeclarations.forEach(function(aDeclaration) {
+ is(CSS.supports(aDeclaration[0], aDeclaration[1]), false, "CSS.supports returns false for unsupported declaration \"" + aDeclaration.join(":") + "\"");
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_cue_restrictions.html b/layout/style/test/test_cue_restrictions.html
new file mode 100644
index 0000000000..c5b3cf3bda
--- /dev/null
+++ b/layout/style/test/test_cue_restrictions.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for ::cue property restrictions.</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="property_database.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<style id="s"></style>
+<video id="test"></video>
+<video id="control"></video>
+<script>
+const test = getComputedStyle($("test"), "::cue");
+const control = getComputedStyle($("control"), "::cue");
+
+for (const prop in gCSSProperties) {
+ const info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND)
+ continue;
+
+ let prereqs = "";
+ if (info.prerequisites)
+ for (let name in info.prerequisites)
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+
+ $("s").textContent = `
+ #control::cue { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::cue { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+
+ (info.applies_to_cue ? isnot : is)(
+ get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should ${info.applies_to_cue ? "" : "not "}apply to ::cue`);
+}
+</script>
diff --git a/layout/style/test/test_custom_content_inheritance.html b/layout/style/test/test_custom_content_inheritance.html
new file mode 100644
index 0000000000..625f1ad9c3
--- /dev/null
+++ b/layout/style/test/test_custom_content_inheritance.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>Test for custom content inheritance</title>
+<style>
+ html { color: red !important; }
+</style>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+onload = function() {
+ let doc = SpecialPowers.wrap(document);
+ let div = doc.createElement('div');
+ div.id = "test-id";
+ ok(!!doc.insertAnonymousContent,
+ "Must have the insertAnonymousContent API");
+ let content = doc.insertAnonymousContent();
+ ok(!!content, "Must have anon content");
+ content.root.appendChild(div);
+ let color = SpecialPowers.wrap(window).getComputedStyle(div).color;
+ ok(!!color, "Should be able to get a color");
+ isnot(color, getComputedStyle(document.documentElement).color,
+ "Custom anon content shouldn't inherit from the root element");
+ SimpleTest.finish();
+};
+SimpleTest.waitForExplicitFinish();
+</script>
diff --git a/layout/style/test/test_default_bidi_css.html b/layout/style/test/test_default_bidi_css.html
new file mode 100644
index 0000000000..9033c9b6a7
--- /dev/null
+++ b/layout/style/test/test_default_bidi_css.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for default bidi css **/
+function styleOf(name, attributes) {
+ var element = document.createElement(name);
+ for (var name in attributes) {
+ var value = attributes[name];
+ element.setAttribute(name, value);
+ }
+ document.body.appendChild(element);
+ return getComputedStyle(element);
+}
+
+var tests = [
+ ['div', {}, 'ltr', 'isolate'],
+ ['div', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['div', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['div', {'dir': 'auto'}, 'ltr', 'isolate'],
+ ['div', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['span', {}, 'ltr', 'normal'],
+ ['span', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['span', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['span', {'dir': 'auto'}, 'ltr', 'isolate'],
+ ['span', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['bdi', {}, 'ltr', 'isolate'],
+ ['bdi', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['bdi', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['bdi', {'dir': 'auto'}, 'ltr', 'isolate'],
+ ['bdi', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['output', {}, 'ltr', 'isolate'],
+ ['output', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['output', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['output', {'dir': 'auto'}, 'ltr', 'isolate'],
+ ['output', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['bdo', {}, 'ltr', 'isolate-override'],
+ ['bdo', {'dir': 'ltr'}, 'ltr', 'isolate-override'],
+ ['bdo', {'dir': 'rtl'}, 'rtl', 'isolate-override'],
+ ['bdo', {'dir': 'auto'}, 'ltr', 'isolate-override'],
+ ['bdo', {'dir': ''}, 'ltr', 'isolate-override'],
+
+ ['textarea', {}, 'ltr', 'normal'],
+ ['textarea', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['textarea', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['textarea', {'dir': 'auto'}, 'ltr', 'plaintext'],
+ ['textarea', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['pre', {}, 'ltr', 'isolate'],
+ ['pre', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['pre', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['pre', {'dir': 'auto'}, 'ltr', 'plaintext'],
+ ['pre', {'dir': ''}, 'ltr', 'isolate'],
+].forEach(function (test) {
+ var style = styleOf(test[0], test[1]);
+ is(style.direction, test[2], "default value for direction");
+ is(style.unicodeBidi, test[3], "default value for unicode-bidi");
+});
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_default_computed_style.html b/layout/style/test/test_default_computed_style.html
new file mode 100644
index 0000000000..56b5863935
--- /dev/null
+++ b/layout/style/test/test_default_computed_style.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=800983
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 800983</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #display::before { content: "Visible"; display: block }
+ #display {
+ display: inline;
+ margin-top: 0;
+ background: yellow;
+ color: blue;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=800983">Mozilla Bug 800983</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 800983 **/
+var cs = getComputedStyle($("display"));
+var cs_pseudo = getComputedStyle($("display"), "::before")
+
+var cs_default = getDefaultComputedStyle($("display"));
+var cs_default_pseudo = getDefaultComputedStyle($("display"), "::before");
+
+// Sanity checks for normal computed style
+is(cs.display, "inline", "We have inline display");
+is(cs.marginTop, "0px", "We have 0 margin");
+is(cs.backgroundColor, "rgb(255, 255, 0)", "We have yellow background");
+is(cs.color, "rgb(0, 0, 255)", "We have blue text");
+is(cs_pseudo.content, '"Visible"', "We have some content");
+is(cs_pseudo.display, "block", "Our ::before is block");
+
+// And now our actual tests
+is(cs_default.display, "block", "We have block display by default");
+is(cs_default.marginTop, "16px", "We have 16px margin by default");
+is(cs_default.backgroundColor, "rgba(0, 0, 0, 0)",
+ "We have transparent background by default");
+is(cs_default.color, "rgb(0, 0, 0)", "We have black text by default");
+is(cs_default_pseudo.content, "none", "We have no content by default");
+is(cs_default_pseudo.display, "inline", "Our ::before is inline by default");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_descriptor_storage.html b/layout/style/test/test_descriptor_storage.html
new file mode 100644
index 0000000000..27750a2bad
--- /dev/null
+++ b/layout/style/test/test_descriptor_storage.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS @font-face descriptor values</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="descriptor_database.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>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for parsing, storage, and serialization of CSS @font-face descriptor values **/
+
+/*
+ * For explanation of some of the more interesting tests here, see the comment
+ * in test_value_storage.html .
+ */
+
+var gStyleElement = document.createElement("style");
+gStyleElement.setAttribute("type", "text/css");
+document.getElementsByTagName("head")[0].appendChild(gStyleElement);
+var gSheet = gStyleElement.sheet;
+gSheet.insertRule("@font-face { }", 0);
+var gRule = gSheet.cssRules[0];
+var gDeclaration = gRule.style;
+
+function fake_set_property(descriptor, value) {
+ gSheet.deleteRule(0);
+ gSheet.insertRule("@font-face { " + descriptor + ": " + value + "}", 0);
+ gRule = gSheet.cssRules[0];
+ gDeclaration = gRule.style;
+}
+
+function xfail_parse(descriptor, value) {
+ switch (descriptor) {
+ case "src":
+ // not clear whether this is an error or not, so mark todo for now
+ return value == "local(serif)";
+ }
+ return false;
+}
+
+function test_descriptor(descriptor)
+{
+ var info = gCSSFontFaceDescriptors[descriptor];
+
+ function test_value(value) {
+// // We don't implement SetProperty yet (bug 443978).
+// gDeclaration.setProperty(descriptor, value, "");
+ fake_set_property(descriptor, value);
+
+ var idx;
+
+ var step1val = gDeclaration.getPropertyValue(descriptor);
+ var step1ser = gDeclaration.cssText;
+
+ var func = xfail_parse(descriptor, value) ? todo_isnot : isnot;
+ func(step1val, "", "setting '" + value + "' on '" + descriptor + "'");
+
+ // We don't care particularly about the whitespace or the placement of
+ // semicolons, but for simplicity we'll test the current behavior.
+ var expected_serialization = "";
+ if (step1val != "")
+ expected_serialization = descriptor + ": " + step1val + "; ";
+ is(step1ser, expected_serialization,
+ "serialization should match descriptor value");
+
+ gDeclaration.removeProperty(descriptor);
+// // We don't implement SetProperty yet (bug 443978).
+// gDeclaration.setProperty(descriptor, step1val, "");
+ fake_set_property(descriptor, step1val);
+
+ is(gDeclaration.getPropertyValue(descriptor), step1val,
+ "parse+serialize should be idempotent for '" +
+ descriptor + ": " + value + "'");
+
+ gDeclaration.removeProperty(descriptor);
+ }
+
+ var idx;
+ for (idx in info.values)
+ test_value(info.values[idx]);
+}
+
+// To avoid triggering the slow script dialog, we have to test one
+// descriptor at a time.
+SimpleTest.waitForExplicitFinish();
+function runTest() {
+ var descs = [];
+ for (var desc in gCSSFontFaceDescriptors)
+ descs.push(desc);
+ descs = descs.reverse();
+ function do_one() {
+ if (descs.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ test_descriptor(descs.pop());
+ SimpleTest.executeSoon(do_one);
+ }
+ SimpleTest.executeSoon(do_one);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(5);
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_descriptor_syntax_errors.html b/layout/style/test/test_descriptor_syntax_errors.html
new file mode 100644
index 0000000000..bf73b15b64
--- /dev/null
+++ b/layout/style/test/test_descriptor_syntax_errors.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test that we reject syntax errors listed in descriptor_database.js</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="descriptor_database.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>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gStyleElement = document.createElement("style");
+gStyleElement.setAttribute("type", "text/css");
+document.getElementsByTagName("head")[0].appendChild(gStyleElement);
+var gSheet = gStyleElement.sheet;
+gSheet.insertRule("@font-face { }", 0);
+var gRule = gSheet.cssRules[0];
+var gDeclaration = gRule.style;
+
+function fake_set_property(descriptor, value) {
+ gSheet.deleteRule(0);
+ gSheet.insertRule("@font-face { " + descriptor + ": " + value + "}", 0);
+ gRule = gSheet.cssRules[0];
+ gDeclaration = gRule.style;
+}
+
+for (var descriptor in gCSSFontFaceDescriptors) {
+ var info = gCSSFontFaceDescriptors[descriptor];
+ for (var idx in info.invalid_values) {
+ var badval = info.invalid_values[idx];
+
+// // We don't implement SetProperty yet (bug 443978).
+// gDeclaration.setProperty(descriptor, badval, "");
+ fake_set_property(descriptor, badval);
+
+ is(gDeclaration.getPropertyValue(descriptor), "",
+ "invalid value '" + badval + "' not accepted for '" + descriptor +
+ "' descriptor");
+
+ gDeclaration.removeProperty(descriptor);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_display_mode.html b/layout/style/test/test_display_mode.html
new file mode 100644
index 0000000000..2fbf78bdb4
--- /dev/null
+++ b/layout/style/test/test_display_mode.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1648157
+-->
+<head>
+ <title>Test for displayMode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="property_database.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+ <iframe id="iframe" src="http://example.org/tests/layout/style/test/media_queries_iframe2.html"></iframe>
+</p>
+<pre id="test">
+<script class="testbody">
+let iframe = document.getElementById("iframe");
+
+// Keep in sync with media_queries_iframe2.html
+const DISPLAY_MODES_BACKGROUND_COLOR = {
+ 'minimal-ui': 'rgb(255, 0, 0)',
+ 'standalone': 'rgb(0, 128, 0)',
+ 'fullscreen': 'rgb(0, 0, 255)',
+ 'browser': 'rgb(255, 255, 0)'
+};
+const DISPLAY_MODES = Object.keys(DISPLAY_MODES_BACKGROUND_COLOR);
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("load", async (event) => {
+ const wrappedWindow = SpecialPowers.wrap(window);
+
+ function setDisplayMode(mode) {
+ wrappedWindow.browsingContext.top.displayMode = mode;
+ }
+
+ async function displayModeApplies(mode) {
+ let responsePromise = new Promise(resolve => {
+ window.addEventListener("message", e => {
+ resolve(e.data.backgroundColor);
+ }, { once: true });
+ });
+ iframe.contentWindow.postMessage('get-background-color', '*');
+ let response = await responsePromise;
+ let expected = DISPLAY_MODES_BACKGROUND_COLOR[mode];
+
+ info(`displayModeApplies: ${response} === ${expected}`);
+ return response === expected;
+ }
+
+ async function checkIfApplies(q, shouldApply) {
+ let message = shouldApply ? "should apply" : "should not apply";
+ is((await displayModeApplies(q)), shouldApply, `${q} ${message}`);
+ }
+
+ for (let currentMode of DISPLAY_MODES) {
+ setDisplayMode(currentMode);
+
+ for (let mode of DISPLAY_MODES) {
+ await checkIfApplies(mode, currentMode === mode);
+ }
+ }
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_dont_use_document_colors.html b/layout/style/test/test_dont_use_document_colors.html
new file mode 100644
index 0000000000..71fc48278d
--- /dev/null
+++ b/layout/style/test/test_dont_use_document_colors.html
@@ -0,0 +1,201 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for preference not to use document colors</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ #one, #three { background: blue; color: yellow; border: thin solid red; column-rule: 2px solid green; text-shadow: 2px 2px green; box-shadow: 3px 7px blue; }
+ #two { background: transparent; border: thin solid; }
+ #five, #six {border: thick solid red; border-inline-start-color:green; border-inline-end-color:blue}
+ #seven {
+ border: 3px solid;
+ }
+ #eight {
+ border: 10px solid transparent;
+ border-image: repeating-linear-gradient(45deg, blue, blue 1%, red 1%, red 8%) 10;
+ }
+ #nine {
+ border: 10px solid blue;
+ border-image: none;
+ }
+
+ #eleven {
+ background-color: transparent;
+ }
+
+ /* XXX also test rgba() */
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=58048">Mozilla Bug 58048</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=255411">Mozilla Bug 255411</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430969">Mozilla Bug 1430969</a>
+<div id="display">
+
+<div id="one">Hello</div>
+<div id="two">Hello</div>
+<input id="three" type="button" value="Hello">
+<input id="four" type="button" value="Hello">
+<div id="five" dir="ltr">Hello</div>
+<div id="six" dir="rtl">Hello</div>
+<div id="seven">Hello</div>
+<div id="eight">I have a border-image</div>
+<div id="nine">I do not have a border-image</div>
+
+<input id="ten" type="button" value="Hello"><!-- Nothing should match this -->
+
+<button id="eleven">Hello</button>
+</div>
+<pre id="test">
+<script class="testbody">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout('nsPresContext internally delays applying prefs using an nsITimer');
+
+var cs1 = getComputedStyle(document.getElementById("one"));
+var cs2 = getComputedStyle(document.getElementById("two"));
+var cs3 = getComputedStyle(document.getElementById("three"));
+var cs4 = getComputedStyle(document.getElementById("four"));
+var cs5 = getComputedStyle(document.getElementById("five"));
+var cs6 = getComputedStyle(document.getElementById("six"));
+var cs7 = getComputedStyle(document.getElementById("seven"));
+var cs8 = getComputedStyle(document.getElementById("eight"));
+var cs9 = getComputedStyle(document.getElementById("nine"));
+var cs10 = getComputedStyle(document.getElementById("ten"));
+var cs11 = getComputedStyle(document.getElementById("eleven"));
+
+function pushPrefEnvAndWait(args, cb) {
+ SpecialPowers.pushPrefEnv(args).then(cb)
+}
+
+pushPrefEnvAndWait({'set': [['browser.display.document_color_use', 1]]}, part1);
+
+function part1()
+{
+ isnot(cs1.backgroundColor, cs2.backgroundColor, "background-color applies");
+ isnot(cs1.color, cs2.color, "color applies");
+ isnot(cs1.borderTopColor, cs2.borderTopColor, "border-top-color applies");
+ isnot(cs1.borderRightColor, cs2.borderRightColor,
+ "border-right-color applies");
+ isnot(cs1.borderLeftColor, cs2.borderLeftColor,
+ "border-left-color applies");
+ isnot(cs1.borderBottomColor, cs2.borderBottomColor,
+ "border-top-color applies");
+ isnot(cs1.columnRuleColor, cs2.columnRuleColor,
+ "column-rule-color applies");
+ isnot(cs1.textShadow, cs2.textShadow,
+ "text-shadow applies");
+ isnot(cs1.boxShadow, cs2.boxShadow,
+ "box-shadow applies");
+ is(cs1.borderTopColor, cs3.borderTopColor, "border-top-color applies");
+ is(cs1.borderRightColor, cs3.borderRightColor,
+ "border-right-color applies");
+ is(cs1.borderLeftColor, cs3.borderLeftColor,
+ "border-left-color applies");
+ is(cs1.borderBottomColor, cs3.borderBottomColor,
+ "border-top-color applies");
+ is(cs1.columnRuleColor, cs3.columnRuleColor,
+ "column-rule-color applies");
+ is(cs1.textShadow, cs3.textShadow,
+ "text-shadow applies");
+ is(cs1.boxShadow, cs3.boxShadow,
+ "box-shadow applies");
+ isnot(cs5.borderRightColor, cs2.borderRightColor,
+ "border-inline-end-color applies");
+ isnot(cs5.borderLeftColor, cs2.borderLeftColor,
+ "border-inline-start-color applies");
+ isnot(cs6.borderRightColor, cs2.borderRightColor,
+ "border-inline-start-color applies");
+ isnot(cs6.borderLeftColor, cs2.borderLeftColor,
+ "border-inline-end-color applies");
+ is(cs1.color, cs3.color, "color applies");
+ is(cs1.backgroundColor, cs3.backgroundColor, "background-color applies");
+ isnot(cs3.backgroundColor, cs4.backgroundColor, "background-color applies");
+ isnot(cs3.color, cs4.color, "color applies");
+ isnot(cs3.borderTopColor, cs4.borderTopColor, "border-top-color applies");
+ isnot(cs3.borderRightColor, cs4.borderRightColor,
+ "border-right-color applies");
+ isnot(cs3.borderLeftColor, cs4.borderLeftColor,
+ "border-left-color applies");
+ isnot(cs3.borderBottomColor, cs4.borderBottomColor,
+ "border-bottom-color applies");
+ isnot(cs8.borderImageSource, cs9.borderImageSource, "border-image-source applies");
+ pushPrefEnvAndWait({'set': [['browser.display.document_color_use', 2]]}, part2);
+}
+
+function toRGBA(c) {
+ return SpecialPowers.wrap(window).InspectorUtils.colorToRGBA(c, document);
+}
+
+function systemColor(c) {
+ let {r, g, b, a} = toRGBA(c);
+ if (a == 1)
+ return `rgb(${r}, ${g}, ${b})`;
+ // Match ColorComponentToFloat's max number of decimals (3), and remove trailing zeros.
+ let alphaString = a.toFixed(3);
+ if (alphaString.includes(".")) {
+ while (alphaString[alphaString.length - 1] == "0")
+ alphaString = alphaString.substring(0, alphaString.length - 1);
+ }
+ return `rgba(${r}, ${g}, ${b}, ${alphaString})`;
+}
+
+function part2()
+{
+ isnot(cs1.backgroundColor, cs2.backgroundColor, "background-color transparency preserved (opaque)");
+ is(toRGBA(cs2.backgroundColor).a, 0, "background-color transparency is preserved (transparent)");
+ is(cs1.color, cs2.color, "color is blocked");
+ is(cs1.borderTopColor, cs2.borderTopColor, "border-top-color is blocked");
+ is(cs1.borderRightColor, cs2.borderRightColor,
+ "border-right-color is blocked");
+ is(cs1.borderLeftColor, cs2.borderLeftColor,
+ "border-left-color is blocked");
+ is(cs5.borderRightColor, cs2.borderRightColor,
+ "border-inline-end-color is blocked");
+ is(cs5.borderLeftColor, cs2.borderLeftColor,
+ "border-inline-start-color is blocked");
+ is(cs6.borderRightColor, cs2.borderRightColor,
+ "border-inline-start-color is blocked");
+ is(cs6.borderLeftColor, cs2.borderLeftColor,
+ "border-inline-end-color is blocked");
+ is(cs1.borderBottomColor, cs2.borderBottomColor,
+ "border-bottom-color is blocked");
+ is(cs1.columnRuleColor, cs2.columnRuleColor,
+ "column-rule-color is blocked");
+ is(cs1.textShadow, cs2.textShadow,
+ "text-shadow is blocked");
+ is(cs1.boxShadow, cs2.boxShadow,
+ "box-shadow is blocked");
+ is(cs3.backgroundColor, cs10.backgroundColor, "background-color transparency preserved (opaque)");
+ is(cs3.color, cs10.color, "color is blocked");
+ is(cs3.borderTopColor, cs4.borderTopColor, "border-top-color is blocked");
+ is(cs3.borderRightColor, cs4.borderRightColor,
+ "border-right-color is blocked");
+ is(cs3.borderLeftColor, cs4.borderLeftColor,
+ "border-left-color is blocked");
+ is(cs3.borderBottomColor, cs4.borderBottomColor,
+ "border-bottom-color is blocked");
+ is(cs4.backgroundColor, systemColor("ButtonFace"), "background-color not broken on inputs");
+ is(cs4.color, systemColor("ButtonText"), "color not broken on inputs");
+ is(cs4.borderTopColor, systemColor("ButtonBorder"), "border-top-color not broken on inputs");
+ is(cs4.borderRightColor, systemColor("ButtonBorder"),
+ "border-right-color not broken on inputs");
+ is(cs4.borderLeftColor, systemColor("ButtonBorder"),
+ "border-left-color not broken on inputs");
+ is(cs4.borderBottomColor, systemColor("ButtonBorder"),
+ "border-bottom-color not broken on inputs");
+ is(cs8.borderImageSource, cs9.borderImageSource, "border-image-source is blocked");
+ is(toRGBA(cs11.backgroundColor).a, 0, "background-color transparency is preserved on buttons");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_dont_use_document_fonts.html b/layout/style/test/test_dont_use_document_fonts.html
new file mode 100644
index 0000000000..59bc6c6d62
--- /dev/null
+++ b/layout/style/test/test_dont_use_document_fonts.html
@@ -0,0 +1,116 @@
+<!doctype html>
+<title>Test for preference to not use document fonts</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel='stylesheet' href='/resources/testharness.css'>
+<div id="content"></div>
+<script>
+const content = document.getElementById("content");
+
+// This is just a subset of browser.display.use_document_fonts.icon_font_allowlist
+// that we feel are worth double-checking via this test. In particular:
+// * Chromium Bug Tracker and https://developers.google.com use "Material Icons"
+// * Google Translate and Google Timeline use "Material Icons Extended"
+// * https://fonts.google.com/icons uses "Material Symbols Outlined"
+// * Google Calendar and Google Contacts use "Google Material Icons"
+const kKnownLigatureIconFonts = "Material Icons, Material Icons Extended, " +
+ "Material Symbols Outlined, Google Material Icons";
+
+setup({explicit_done: true })
+
+content.style.fontFamily = "initial";
+const kInitialFamily = getComputedStyle(content).fontFamily;
+content.style.fontFamily = "";
+
+const kTests = [
+ {
+ specified: "monospace",
+ computed: "monospace",
+ description: "Single generic family should not be changed",
+ },
+ {
+ specified: "monospace, sans-serif",
+ computed: "monospace, sans-serif",
+ description: "Generic families should not be changed",
+ },
+ {
+ specified: "Courier, monospace",
+ computed: "monospace, Courier",
+ description: "Generics are preferred, but may still fall back to document fonts",
+ },
+ {
+ specified: "system-ui, sans-serif",
+ computed: "sans-serif, system-ui",
+ description: "system-ui is not prioritized",
+ },
+ {
+ specified: "Courier, something-else",
+ computed: `${kInitialFamily}, Courier, something-else`,
+ description: "Generic is prepended to the font-family if none is found",
+ },
+ {
+ specified: kKnownLigatureIconFonts + ", something-else, sans-serif",
+ computed: kKnownLigatureIconFonts + ", sans-serif, something-else",
+ description: "Known ligature-icon fonts remain ahead of the generic",
+ },
+ {
+ specified: "Material Icons, something-else, Material Symbols Outlined, sans-serif",
+ computed: "Material Icons, sans-serif, something-else, Material Symbols Outlined",
+ description: "Generic is moved ahead of the first non-allowlisted font",
+ },
+ {
+ specified: "Material Icons, something-else, Material Symbols Outlined",
+ computed: `Material Icons, ${kInitialFamily}, something-else, Material Symbols Outlined`,
+ description: "Default generic is inserted ahead of the first non-allowlisted font",
+ },
+ {
+ specified: "Material Icons, cursive, Material Symbols Outlined, serif",
+ computed: "Material Icons, serif, cursive, Material Symbols Outlined",
+ description: "cursive is not treated as a generic to be prioritized",
+ },
+ {
+ specified: "Material Icons, fantasy, Material Symbols Outlined",
+ computed: `Material Icons, ${kInitialFamily}, fantasy, Material Symbols Outlined`,
+ description: "fantasy is not treated as a generic to be prioritized",
+ },
+];
+
+let systemFont;
+
+// compute expectations while the pref is not active yet.
+test(function() {
+ for (const test of kTests) {
+ content.style.fontFamily = "";
+ content.style.fontFamily = test.computed;
+ assert_not_equals(content.style.fontFamily, "", `computed font ${test.computed} was invalid`);
+ test.expected = getComputedStyle(content).fontFamily;
+ }
+
+ content.style.font = "menu";
+ systemFont = getComputedStyle(content).fontFamily;
+ assert_not_equals(systemFont, "", `computed menu system font was invalid`);
+
+ content.style.font = "";
+}, "Sanity");
+
+function runTest({ specified, computed, description, expected }) {
+ test(function() {
+ content.style.fontFamily = "";
+ content.style.fontFamily = specified;
+ assert_equals(getComputedStyle(content).fontFamily, expected);
+ }, description);
+}
+
+(async function() {
+ await SpecialPowers.pushPrefEnv({'set': [['browser.display.use_document_fonts', 0]]});
+ for (const test of kTests)
+ runTest(test);
+
+ test(function() {
+ content.style.font = "menu";
+ assert_equals(getComputedStyle(content).fontFamily, systemFont);
+ }, "System font should be honored");
+
+ done();
+})();
+</script>
diff --git a/layout/style/test/test_dynamic_change_causing_reflow.html b/layout/style/test/test_dynamic_change_causing_reflow.html
new file mode 100644
index 0000000000..7a000c87a6
--- /dev/null
+++ b/layout/style/test/test_dynamic_change_causing_reflow.html
@@ -0,0 +1,1014 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1131371
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1131371</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1131371">Mozilla Bug 1131371</a>
+<style>
+ #elemWithScrollbars { overflow: scroll }
+ .flexScrollerWithTransformedItems {
+ display: flex;
+ overflow: hidden;
+ width: 200px;
+ position: relative;
+ }
+ .flexScrollerWithTransformedItems div {
+ min-width: 50px;
+ min-height: 50px;
+ margin: 10px;
+ will-change: transform;
+ }
+ .absposFlexItem {
+ left: 250px;
+ top: 0;
+ position: absolute;
+ }
+ #tableCell,
+ #tableCellWithAbsPosChild {
+ display: table-cell;
+ }
+ .blockmargin {
+ margin: 25px 0;
+ }
+</style>
+<div id="display">
+ <div id="content">
+ </div>
+ <div id="elemWithAbsPosChild"><div style="position:absolute"></div></div>
+ <div id="elemWithFixedPosChild"><div style="position:fixed"></div></div>
+ <div id="elemWithScrollbars"></div>
+ <div id="elemWithoutScrollbars"></div>
+ <div class="flexScrollerWithTransformedItems">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div></div>
+ <div id="flexItemMovementTarget"></div>
+ </div>
+ <div class="flexScrollerWithTransformedItems">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div id="flexItemMovementTarget2"></div>
+ <div class="absposFlexItem"></div>
+ </div>
+ <input id="inputElem" type="image">
+ <textarea id="textareaElem">
+ Some
+ lines
+ of
+ text
+ </textarea>
+ <select id="selectElem">
+ <option>A</option>
+ <option>B</option>
+ <option>C</option>
+ </select>
+ <button id="buttonElem">
+ Something
+ </button>
+ <button id="buttonElemWithAbsPosChild"><div style="position:absolute"></div></button>
+ <div id="tableCell"></div>
+ <div id="tableCellWithAbsPosChild">
+ <div style="position: absolute"></div>
+ </div>
+ <div id="containNode">
+ <div></div>
+ </div>
+ <div id="containNodeWithAbsPosChild">
+ <div style="position: absolute"></div>
+ </div>
+ <div id="containNodeWithFixedPosChild">
+ <div style="position: fixed"></div>
+ </div>
+ <div id="containNodeWithFloatingChild">
+ <div style="float: left"></div>
+ </div>
+ <div id="containNodeWithMarginCollapsing" class="blockmargin">
+ <div class="blockmargin"></div>
+ </div>
+ <div id="contentVisibilityNode">
+ <div></div>
+ </div>
+</div>
+<pre id="test">
+<script>
+"use strict";
+
+/** Test for Bug 1131371 **/
+
+const contentVisibilityEnabled = SpecialPowers.getBoolPref("layout.css.content-visibility.enabled");
+const elemWithAbsPosChild = document.getElementById("elemWithAbsPosChild");
+const elemWithFixedPosChild = document.getElementById("elemWithFixedPosChild");
+const elemWithScrollbars = document.getElementById("elemWithScrollbars");
+const elemWithoutScrollbars = document.getElementById("elemWithoutScrollbars");
+const inputElem = document.getElementById("inputElem");
+const textareaElem = document.getElementById("textareaElem");
+const selectElem = document.getElementById("selectElem");
+const buttonElem = document.getElementById("buttonElem");
+const buttonElemWithAbsPosChild = document.getElementById("buttonElemWithAbsPosChild");
+const tableCell = document.getElementById("tableCell");
+const tableCellWithAbsPosChild = document.getElementById("tableCellWithAbsPosChild");
+
+for (let scroller of document.querySelectorAll(".flexScrollerWithTransformedItems")) {
+ scroller.scrollLeft = 10000;
+}
+
+const flexItemMovementTarget = document.getElementById("flexItemMovementTarget");
+const flexItemMovementTarget2 = document.getElementById("flexItemMovementTarget2");
+
+/**
+ * This test verifies that certain style changes do or don't cause reflow
+ * and/or frame construction. We do this by checking the framesReflowed &
+ * framesConstructed counts, before & after a style-change, and verifying
+ * that any change to these counts is in line with our expectations.
+ *
+ * Each entry in gTestcases contains these member-values:
+ * - beforeStyle (optional): initial value to use for "style" attribute.
+ * - afterStyle: value to change the "style" attribute to.
+ *
+ * Testcases may also include two optional member-values to express that reflow
+ * and/or frame construction *are* in fact expected:
+ * - expectConstruction (optional): if set to something truthy, then we expect
+ * frame construction to occur when afterStyle is set. Otherwise, we
+ * expect that frame construction should *not* occur.
+ * - expectReflow (optional): if set to something truthy, then we expect
+ * reflow to occur when afterStyle is set. Otherwise, we expect that
+ * reflow should *not* occur.
+ */
+const gTestcases = [
+ // Things that shouldn't cause reflow:
+ // -----------------------------------
+ // * Adding an outline (e.g. for focus ring).
+ {
+ afterStyle: "outline: 1px dotted black",
+ },
+
+ // * Changing between completely different outlines.
+ {
+ beforeStyle: "outline: 2px solid black",
+ afterStyle: "outline: 6px dashed yellow",
+ },
+
+ // * Adding a box-shadow.
+ {
+ afterStyle: "box-shadow: inset 3px 3px gray",
+ },
+ {
+ afterStyle: "box-shadow: 0px 0px 10px 30px blue"
+ },
+
+ // * Changing between completely different box-shadow values,
+ // e.g. from an upper-left shadow to a bottom-right shadow:
+ {
+ beforeStyle: "box-shadow: -15px -20px teal",
+ afterStyle: "box-shadow: 30px 40px yellow",
+ },
+
+ // * Adding a text-shadow.
+ {
+ afterStyle: "text-shadow: 3px 3px gray",
+ },
+ {
+ afterStyle: "text-shadow: 0px 0px 10px blue"
+ },
+
+ // * Changing between completely different text-shadow values,
+ // e.g. from an upper-left shadow to a bottom-right shadow:
+ {
+ beforeStyle: "text-shadow: -15px -20px teal",
+ afterStyle: "text-shadow: 30px 40px yellow",
+ },
+
+ // * switching overflow between things that shouldn't create scrollframes.
+ {
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: clip",
+ },
+
+ // Things that *should* cause reflow:
+ // ----------------------------------
+ // (e.g. to make sure our counts are actually measuring something)
+
+ // * Changing 'height' should cause reflow, but not frame construction.
+ {
+ beforeStyle: "height: 10px",
+ afterStyle: "height: 15px",
+ expectReflow: true,
+ },
+
+ // * Changing 'shape-outside' on a non-floating box should not cause anything to happen.
+ {
+ beforeStyle: "shape-outside: none",
+ afterStyle: "shape-outside: circle()",
+ },
+
+ // * Changing 'shape-outside' should cause reflow, but not frame construction.
+ {
+ beforeStyle: "float: left; shape-outside: none",
+ afterStyle: "float: left; shape-outside: circle()",
+ expectReflow: true,
+ },
+
+ // * Changing 'overflow' on <body> should cause reflow,
+ // but not frame reconstruction
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: visible",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Changing 'overflow' on <html> should cause reflow,
+ // but not frame reconstruction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Setting 'overflow' on arbitrary node should cause reflow as well as
+ // frame reconstruction
+ {
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: auto",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: visible",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+ // * but only reflow if we don't need to construct / unconstruct a new frame.
+ {
+ beforeStyle: "overflow: scroll",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ {
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: auto",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: scroll",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: scroll",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ {
+ elem: elemWithoutScrollbars,
+ beforeStyle: "scrollbar-width: auto",
+ afterStyle: "scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: elemWithScrollbars,
+ beforeStyle: "scrollbar-width: auto",
+ afterStyle: "scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ // Not the scrolling element, so nothing should happen.
+ elem: document.body,
+ beforeStyle: "scrollbar-width: none",
+ afterStyle: "scrollbar-width: auto",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ // Not the scrolling element, so nothing should happen.
+ elem: document.body,
+ beforeStyle: "scrollbar-width: auto",
+ afterStyle: "scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "scrollbar-width: none;",
+ afterStyle: "scrollbar-width: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: scroll; scrollbar-width: none;",
+ afterStyle: "overflow: scroll; scrollbar-width: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: scroll; scrollbar-width: auto;",
+ afterStyle: "overflow: scroll; scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Changing 'display' should cause frame construction and reflow.
+ {
+ beforeStyle: "display: inline",
+ afterStyle: "display: table",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+
+ // * Position changes trigger a reframe, unless whether we're a containing
+ // block doesn't change, in which case we just need to reflow.
+ {
+ beforeStyle: "position: static",
+ afterStyle: "position: absolute",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "position: absolute",
+ afterStyle: "position: fixed",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "position: relative",
+ afterStyle: "position: fixed",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+ // This doesn't change whether we're a containing block because there are no
+ // abspos descendants.
+ {
+ afterStyle: "position: static",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+
+ // This doesn't change whether we're a containing block, shouldn't reframe.
+ {
+ afterStyle: "position: sticky",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+
+ // These don't change whether we're a containing block for our
+ // absolutely-positioned child, so shouldn't reframe.
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: sticky",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+ {
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: sticky",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+ {
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+ {
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: sticky",
+ expectReflow: true,
+ },
+ {
+ // Even if we're a scroll frame.
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: static; overflow: auto;",
+ beforeStyle: "position: relative; overflow: auto;",
+ expectReflow: true,
+ },
+ {
+ elem: tableCell,
+ afterStyle: "position: static;",
+ beforeStyle: "position: relative;",
+ expectReflow: true,
+ },
+ {
+ elem: tableCell,
+ afterStyle: "filter: none",
+ beforeStyle: "filter: saturate(1)",
+ expectReflow: false,
+ },
+
+ // These ones do though.
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: relative",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: sticky",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: static; overflow: auto;",
+ beforeStyle: "position: relative; overflow: auto;",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: tableCellWithAbsPosChild,
+ afterStyle: "position: static;",
+ beforeStyle: "position: relative;",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+ // Adding transform to a scrollframe without abspos / fixedpos children shouldn't reframe.
+ {
+ elem: elemWithScrollbars,
+ afterStyle: "transform: translateX(1px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+
+ // <select> can't contain abspos / floating children so shouldn't reframe
+ // when changing containing block-ness.
+ {
+ elem: selectElem,
+ afterStyle: "transform: translateX(1px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: selectElem,
+ afterStyle: "position: relative",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // <button> shouldn't be reframed either in the absence of positioned descendants.
+ {
+ elem: buttonElem,
+ afterStyle: "transform: translateX(1px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: buttonElem,
+ afterStyle: "position: relative",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: buttonElemWithAbsPosChild,
+ afterStyle: "position: relative",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ // changing scroll-behavior should not cause reflow or frame construction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'scroll-behavior: auto' */
+ afterStyle: "scroll-behavior: smooth",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "scroll-behavior: smooth",
+ afterStyle: "scroll-behavior: auto",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'scroll-behavior: auto' */
+ afterStyle: "scroll-behavior: smooth",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "scroll-behavior: smooth",
+ afterStyle: "scroll-behavior: auto",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ // changing scroll-snap-type should not cause reflow or frame construction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: y mandatory",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: x proximity",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "scroll-snap-type: y mandatory",
+ afterStyle: "scroll-snap-type: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: y mandatory",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: x proximity",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "scroll-snap-type: y mandatory",
+ afterStyle: "scroll-snap-type: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: inputElem,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: textareaElem,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: flexItemMovementTarget,
+ beforeStyle: "transform: translateX(0)",
+ afterStyle: "transform: translateX(-100px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: flexItemMovementTarget2,
+ beforeStyle: "transform: translateX(0)",
+ afterStyle: "transform: translateX(-100px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ // Style containment affects counters so we need to re-frame.
+ // For other containments, we only need to reflow.
+ {
+ elem: containNode,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: style",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: style",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: size",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: size",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ // paint/layout containment boxes establish an absolute positioning
+ // containing block, so need a re-frame to handle abs/fixed positioned boxes.
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: size",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: size",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: size",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: size",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ // paint/layout containment boxes establish an independent formatting context
+ // but floats and margin collapsing can be handled without reconstruction.
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: size",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: size",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithMarginCollapsing,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithMarginCollapsing,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithMarginCollapsing,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithMarginCollapsing,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ // content-visibility: auto/hidden implies style containment contrary to
+ // content-visibility: visible, so we generally need a re-frame (see above)
+ // when going from one case to the other.
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: visible",
+ afterStyle: "content-visibility: hidden",
+ expectConstruction: contentVisibilityEnabled,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: hidden",
+ afterStyle: "content-visibility: visible",
+ expectConstruction: contentVisibilityEnabled,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: visible",
+ afterStyle: "content-visibility: auto",
+ expectConstruction: contentVisibilityEnabled,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: auto",
+ afterStyle: "content-visibility: visible",
+ expectConstruction: contentVisibilityEnabled,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: hidden",
+ afterStyle: "content-visibility: auto",
+ expectConstruction: false,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: auto",
+ afterStyle: "content-visibility: hidden",
+ expectConstruction: false,
+ expectReflow: contentVisibilityEnabled,
+ },
+ // However that's not the case if we force style containment explicitly.
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: visible; contain: style",
+ afterStyle: "content-visibility: hidden; contain: style",
+ expectConstruction: false,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: hidden; contain: style",
+ afterStyle: "content-visibility: visible; contain: style",
+ expectConstruction: false,
+ expectReflow: contentVisibilityEnabled,
+ },
+];
+
+// Helper function to let us call either "is" or "isnot" & assemble
+// the failure message, based on the provided parameters.
+function checkFinalCount(aFinalCount, aExpectedCount,
+ aExpectChange, aMsgPrefix, aCountDescription)
+{
+ let compareFunc;
+ let msg = aMsgPrefix;
+ if (aExpectChange) {
+ compareFunc = isnot;
+ msg += "should cause " + aCountDescription;
+ } else {
+ compareFunc = is;
+ msg += "should not cause " + aCountDescription;
+ }
+
+ compareFunc(aFinalCount, aExpectedCount, msg);
+}
+
+// Vars used in runOneTest that we really only have to look up once:
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+const gElem = document.getElementById("content");
+
+function runOneTest(aTestcase)
+{
+ // sanity-check that we have the one main thing we need:
+ if (!aTestcase.afterStyle) {
+ ok(false, "testcase is missing an 'afterStyle' to change to");
+ return;
+ }
+
+ // Figure out which element we'll be tweaking (defaulting to gElem)
+ let elem = aTestcase.elem ? aTestcase.elem : gElem;
+
+ // Verify that 'style' attribute is unset (avoid causing ourselves trouble):
+ const oldStyle = elem.getAttribute("style");
+
+ // Set the "before" style, and compose the first part of the message
+ // to be used in our "is"/"isnot" invocations:
+ let msgPrefix = "Changing style ";
+ if (aTestcase.beforeStyle) {
+ elem.setAttribute("style", aTestcase.beforeStyle);
+ msgPrefix += "from '" + aTestcase.beforeStyle + "' ";
+ }
+ msgPrefix += "to '" + aTestcase.afterStyle + "' ";
+ msgPrefix += "on " + elem.nodeName + " ";
+
+ // Establish initial counts:
+ let unusedVal = elem.offsetHeight; // flush layout
+ let origFramesConstructed = gUtils.framesConstructed;
+ let origFramesReflowed = gUtils.framesReflowed;
+
+ // Make the change and flush:
+ elem.setAttribute("style", aTestcase.afterStyle);
+ unusedVal = elem.offsetHeight; // flush layout
+
+ // Make our is/isnot assertions about whether things should have changed:
+ checkFinalCount(gUtils.framesConstructed, origFramesConstructed,
+ aTestcase.expectConstruction, msgPrefix,
+ "frame construction");
+ checkFinalCount(gUtils.framesReflowed, origFramesReflowed,
+ aTestcase.expectReflow, msgPrefix,
+ "reflow");
+
+ // Clean up!
+ if (oldStyle) {
+ elem.setAttribute("style", oldStyle);
+ } else {
+ elem.removeAttribute("style");
+ }
+
+ unusedVal = elem.offsetHeight; // flush layout
+}
+
+gTestcases.forEach(runOneTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_exposed_prop_accessors.html b/layout/style/test/test_exposed_prop_accessors.html
new file mode 100644
index 0000000000..765818bae4
--- /dev/null
+++ b/layout/style/test/test_exposed_prop_accessors.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375363
+-->
+<head>
+ <title>Test for cloning of CSS property values (including 'inherit', 'initial' and 'unset')</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * Test that makes sure that we have exposed getters/setters for all the
+ * various variants of our CSS property names that the spec calls for.
+ */
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+
+ var s = document.createElement("div").style;
+
+ is(s[info.domProp], "", prop + " should not be set yet");
+ s[info.domProp] = info.initial_values[0];
+ isnot(s[info.domProp], "", prop + " should now be set");
+ is(s[prop], s[info.domProp],
+ "Getting " + prop + " via name should work")
+ s = document.createElement("div").style;
+ is(s[info.domProp], "", prop + " should not be set here either");
+ s[prop] = info.initial_values[0];
+ isnot(s[info.prop], "", prop + " should now be set again");
+ is(s[info.domProp], s[prop],
+ "Setting " + prop + " via name should work");
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_extra_inherit_initial.html b/layout/style/test/test_extra_inherit_initial.html
new file mode 100644
index 0000000000..34c63b3626
--- /dev/null
+++ b/layout/style/test/test_extra_inherit_initial.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=940229
+-->
+<head>
+ <title>Test handling extra inherit/initial/unset in CSS declarations (Bug 940229)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.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=940229">Mozilla Bug 940229</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+/*
+ * Inspired by mistake in quotes noticed while reviewing bug 189519.
+ */
+
+let gPropsNeedComma = {
+ "font": true,
+ "font-family": true,
+ "voice-family": true,
+};
+
+let gElement = document.getElementById("testnode");
+let gDeclaration = gElement.style;
+
+let kValuesToTestThoroughly = 3;
+
+function test_property(property)
+{
+ let info = gCSSProperties[property];
+
+ let delim = (property in gPropsNeedComma) ? ", " : " ";
+
+ function test_value_pair(relation, val1, val2, extraval) {
+ let decl = property + ": " + val1 + delim + val2;
+ gElement.setAttribute("style", decl);
+ if ("subproperties" in info) {
+ // Shorthand property; inspect each subproperty value.
+ for (let subprop of info.subproperties) {
+ is(gDeclaration.getPropertyValue(subprop), "",
+ ["expected", extraval, "ignored", relation, "value in",
+ "'" + decl + "'", "when looking at subproperty",
+ "'" + subprop + "'"].join(" "));
+ }
+ } else {
+ // Longhand property.
+ is(gDeclaration.getPropertyValue(property), "",
+ ["expected", extraval, "ignored", relation, "value in",
+ "'" + decl + "'"].join(" "));
+ }
+ }
+
+ function test_value(value, valueIdx) {
+ let specialKeywords = [ "inherit", "initial", "unset" ];
+
+ if (valueIdx < kValuesToTestThoroughly) {
+ // For the first few values, we test each special-keyword both before
+ // and after the value.
+ for (let keyword of specialKeywords) {
+ test_value_pair("before", keyword, value, keyword);
+ test_value_pair("after", value, keyword, keyword);
+ }
+ } else {
+ // For later values, only test one keyword before & after it.
+ let keywordIdx =
+ (valueIdx - kValuesToTestThoroughly) % specialKeywords.length;
+ keyword = specialKeywords[keywordIdx];
+ test_value_pair("before", keyword, value, keyword);
+ test_value_pair("after", value, keyword, keyword);
+ }
+ }
+
+ for (let idx in info.initial_values) {
+ test_value(info.initial_values[idx], idx);
+ }
+ for (let idx in info.other_values) {
+ test_value(info.initial_values[idx], idx);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
+
+function start_test() {
+ for (let prop in gCSSProperties) {
+ test_property(prop);
+ }
+ SimpleTest.finish();
+}
+
+// Turn off CSS error reporting for this test, since it's a bit expensive,
+// and we're expecting to generate tons and tons of parse errors here.
+SpecialPowers.pushPrefEnv({ "set": [["layout.css.report_errors", false]] },
+ start_test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_first_letter_restrictions.html b/layout/style/test/test_first_letter_restrictions.html
new file mode 100644
index 0000000000..99aaba8b93
--- /dev/null
+++ b/layout/style/test/test_first_letter_restrictions.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1382786
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1382786</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="s"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1382786">Mozilla Bug 1382786</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="test"></div>
+<div id="control"></div>
+<script type="application/javascript">
+
+/** Test for Bug 1382786 **/
+var test = getComputedStyle($("test"), "::first-letter");
+var control = getComputedStyle($("control"), "::first-letter");
+for (let prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND) {
+ // Can't get useful info out of getComputedStyle.
+ continue;
+ }
+ let prereqs = "";
+ if (info.prerequisites) {
+ for (let name in info.prerequisites) {
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+ }
+ }
+ $("s").textContent = `
+ #control::first-letter { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::first-letter { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+ if (info.applies_to_first_letter) {
+ isnot(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should apply to ::first-letter`);
+ } else {
+ is(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should not apply to ::first-letter`);
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_first_line_restrictions.html b/layout/style/test/test_first_line_restrictions.html
new file mode 100644
index 0000000000..bb8e116e8b
--- /dev/null
+++ b/layout/style/test/test_first_line_restrictions.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1382786
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1382786</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="s"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1382786">Mozilla Bug 1382786</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="test"></div>
+<div id="control"></div>
+<script type="application/javascript">
+
+/** Test for Bug 1382786 **/
+var test = getComputedStyle($("test"), "::first-line");
+var control = getComputedStyle($("control"), "::first-line");
+for (let prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND) {
+ // Can't get useful info out of getComputedStyle.
+ continue;
+ }
+ let prereqs = "";
+ if (info.prerequisites) {
+ for (let name in info.prerequisites) {
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+ }
+ }
+ $("s").textContent = `
+ #control::first-line { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::first-line { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+ if (info.applies_to_first_line) {
+ isnot(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should apply to ::first-line`);
+ } else {
+ is(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should not apply to ::first-line`);
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_child_display_values.xhtml b/layout/style/test/test_flexbox_child_display_values.xhtml
new file mode 100644
index 0000000000..7ba870c69f
--- /dev/null
+++ b/layout/style/test/test_flexbox_child_display_values.xhtml
@@ -0,0 +1,178 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783415
+-->
+<head>
+ <meta charset="utf-8"/>
+ <title>Test "display" values of content in a flex container (Bug 783415)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783415">Mozilla Bug 783415</a>
+<div id="display">
+ <div id="wrapper"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+"use strict";
+
+/**
+ * Test "display" values of content in a flex container (Bug 783415)
+ * ================================================================
+ *
+ * This test creates content with a variety of specified "display" values
+ * and checks what the computed "display" is when we put that content
+ * in a flex container. (Generally, it'll be the "blockified" form of the
+ * specified display-value.)
+ */
+
+/*
+ * Utility function for getting computed style of "display".
+ *
+ * @arg aElem The element to query for its computed "display" value.
+ * @return The computed display value
+ */
+function getComputedDisplay(aElem) {
+ return window.getComputedStyle(aElem).display;
+}
+
+/*
+ * Function used for testing a given specified "display" value and checking
+ * its computed value against expectations.
+ *
+ * @arg aSpecifiedDisplay
+ * The specified value of "display" that we should test.
+ *
+ * @arg aExpectedDisplayAsFlexContainerChild
+ * (optional) The expected computed "display" when an element with
+ * aSpecifiedDisplay is a child of a flex container. If omitted,
+ * this argument defaults to aSpecifiedDisplay.
+ *
+ * @arg aExpectedDisplayAsOutOfFlowFlexContainerChild
+ * (optional) The expected computed "display" when an element with
+ * aSpecifiedDisplay is a child of a flex container *and* has
+ * position:[fixed|absolute] or float: [left|right] set. If omitted,
+ * this argument defaults to aExpectedDisplayAsFlexContainerChild.
+ */
+function testDisplayValue(aSpecifiedDisplay,
+ aExpectedDisplayAsFlexContainerChild,
+ aExpectedDisplayAsOutOfFlowFlexContainerChild) {
+ // DEFAULT-ARGUMENT-VALUES MAGIC: Make 2nd and 3rd args each default to
+ // the preceding arg, if they're unspecified.
+ if (typeof aExpectedDisplayAsFlexContainerChild == "undefined") {
+ aExpectedDisplayAsFlexContainerChild = aSpecifiedDisplay;
+ }
+ if (typeof aExpectedDisplayAsOutOfFlowFlexContainerChild == "undefined") {
+ aExpectedDisplayAsOutOfFlowFlexContainerChild =
+ aExpectedDisplayAsFlexContainerChild;
+ }
+
+ // FIRST: Create a node with display:aSpecifiedDisplay, and make sure that
+ // this original display-type is honored in a non-flex-container context.
+ let wrapper = document.getElementById("wrapper");
+ let node = document.createElement("div");
+ wrapper.appendChild(node);
+
+ node.style.display = aSpecifiedDisplay;
+ is(getComputedDisplay(node), aSpecifiedDisplay,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "should be the same as specified value, when parent is a block");
+
+
+ // SECOND: We make our node's parent into a flex container, and make sure
+ // that this produces the correct computed "display" value.
+ wrapper.style.display = "flex";
+ is(getComputedDisplay(node), aExpectedDisplayAsFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container");
+
+
+ // THIRD: We set "float" and "position" on our node (still inside of a
+ // flex container), and make sure that this produces the correct computed
+ // "display" value.
+ node.style.cssFloat = "left";
+ is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container and we're floated left");
+ node.style.cssFloat = "";
+
+ node.style.cssFloat = "right";
+ is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container and we're floated right");
+ node.style.cssFloat = "";
+
+ node.style.position = "absolute";
+ is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container and we're abs-pos");
+ node.style.position = "";
+
+ node.style.position = "fixed";
+ is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container and we're fixed-pos");
+ node.style.position = "";
+
+ // FINALLY: Clean up -- remove the node we created, and turn the wrapper
+ // back into its original display type (a block).
+ wrapper.removeChild(node);
+ wrapper.style.display = "";
+}
+
+/*
+ * Main test function
+ */
+function main() {
+ testDisplayValue("none");
+ testDisplayValue("block");
+ testDisplayValue("flex");
+ testDisplayValue("inline-flex", "flex");
+ testDisplayValue("list-item");
+ testDisplayValue("table");
+ testDisplayValue("inline-table", "table");
+
+ // These values all compute to "block" in a flex container. Do them in a
+ // loop, so that I don't have to type "block" a zillion times.
+ var dispValsThatComputeToBlockInAFlexContainer = [
+ "inline",
+ "inline-block",
+ ];
+
+ dispValsThatComputeToBlockInAFlexContainer.forEach(
+ function(aSpecifiedDisplay) {
+ testDisplayValue(aSpecifiedDisplay, "block");
+ });
+
+ // Table-parts are special. When they're a child of a flex container,
+ // they normally don't get blockified -- instead, they trigger table-fixup
+ // and get wrapped in a table. So, their expected display as the child of
+ // a flex container is the same as their specified display. BUT, if
+ // we apply out-of-flow styling, then *that* blockifies them before
+ // we get to the table-fixup stage -- so then, their computed display
+ // is "block".
+ let tablePartsDispVals = [
+ "table-row-group",
+ "table-column",
+ "table-column-group",
+ "table-header-group",
+ "table-footer-group",
+ "table-row",
+ "table-cell",
+ "table-caption"
+ ];
+
+ tablePartsDispVals.forEach(
+ function(aSpecifiedDisplay) {
+ testDisplayValue(aSpecifiedDisplay, "block", "block");
+ });
+}
+
+main();
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_flex_grow_and_shrink.html b/layout/style/test/test_flexbox_flex_grow_and_shrink.html
new file mode 100644
index 0000000000..fc5090fd61
--- /dev/null
+++ b/layout/style/test/test_flexbox_flex_grow_and_shrink.html
@@ -0,0 +1,154 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696253
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for flex-grow and flex-shrink animation (Bug 696253)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ /* Set flex-grow and flex-shrink to nonzero values,
+ when no animations are applied. */
+
+ * { flex-grow: 10; flex-shrink: 20 }
+
+ /* Animations that we'll test (individually) in the script below: */
+ @keyframes flexGrowTwoToThree {
+ 0% { flex-grow: 2 }
+ 100% { flex-grow: 3 }
+ }
+ @keyframes flexShrinkTwoToThree {
+ 0% { flex-shrink: 2 }
+ 100% { flex-shrink: 3 }
+ }
+ @keyframes flexGrowZeroToZero {
+ 0% { flex-grow: 0 }
+ 100% { flex-grow: 0 }
+ }
+ @keyframes flexShrinkZeroToZero {
+ 0% { flex-shrink: 0 }
+ 100% { flex-shrink: 0 }
+ }
+ @keyframes flexGrowZeroToOne {
+ 0% { flex-grow: 0 }
+ 100% { flex-grow: 1 }
+ }
+ @keyframes flexShrinkZeroToOne {
+ 0% { flex-shrink: 0 }
+ 100% { flex-shrink: 1 }
+ }
+ @keyframes flexGrowOneToZero {
+ 0% { flex-grow: 1 }
+ 100% { flex-grow: 0 }
+ }
+ @keyframes flexShrinkOneToZero {
+ 0% { flex-shrink: 1 }
+ 100% { flex-shrink: 0 }
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696253">Mozilla Bug 696253</a>
+<div id="display">
+ <div id="myDiv"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for flex-grow and flex-shrink animation (Bug 696253) **/
+
+// take over the refresh driver
+advance_clock(0);
+
+// ANIMATIONS THAT SHOULD AFFECT COMPUTED STYLE
+// --------------------------------------------
+
+// flexGrowTwoToThree: 2.0 at 0%, 2.5 at 50%, 10 after animation is over
+var [ div, cs ] = new_div("animation: flexGrowTwoToThree linear 1s");
+is_approx(+cs.flexGrow, 2, 0.01, "flexGrowTwoToThree at 0.0s");
+advance_clock(500);
+is_approx(+cs.flexGrow, 2.5, 0.01, "flexGrowTwoToThree at 0.5s");
+advance_clock(1000);
+is(cs.flexGrow, "10", "flexGrowTwoToThree at 1.5s");
+done_div();
+
+// flexShrinkTwoToThree: 2.0 at 0%, 2.5 at 50%, 20 after animation is over
+[ div, cs ] = new_div("animation: flexShrinkTwoToThree linear 1s");
+is_approx(cs.flexShrink, 2, 0.01, "flexShrinkTwoToThree at 0.0s");
+advance_clock(500);
+is_approx(cs.flexShrink, 2.5, 0.01, "flexShrinkTwoToThree at 0.5s");
+advance_clock(1000);
+is(cs.flexShrink, "20", "flexShrinkTwoToThree at 1.5s");
+done_div();
+
+// flexGrowZeroToZero: 0 at 0%, 0 at 50%, 10 after animation is over
+[ div, cs ] = new_div("animation: flexGrowZeroToZero linear 1s");
+is(cs.flexGrow, "0", "flexGrowZeroToZero at 0.0s");
+advance_clock(500);
+is(cs.flexGrow, "0", "flexGrowZeroToZero at 0.5s");
+advance_clock(1000);
+is(cs.flexGrow, "10", "flexGrowZeroToZero at 1.5s");
+done_div();
+
+// flexShrinkZeroToZero: 0 at 0%, 0 at 50%, 20 after animation is over
+[ div, cs ] = new_div("animation: flexShrinkZeroToZero linear 1s");
+is(cs.flexShrink, "0", "flexShrinkZeroToZero at 0.0s");
+advance_clock(500);
+is(cs.flexShrink, "0", "flexShrinkZeroToZero at 0.5s");
+advance_clock(1000);
+is(cs.flexShrink, "20", "flexShrinkZeroToZero at 1.5s");
+done_div();
+
+// ANIMATIONS THAT DIDN'T USED TO AFFECT COMPUTED STYLE, BUT NOW DO
+// ----------------------------------------------------------------
+// (In an older version of the flexbox spec, flex-grow & flex-shrink were not
+// allowed to animate between 0 and other values. But now that's allowed.)
+
+// flexGrowZeroToOne: 0 at 0%, 0.5 at 50%, 10 after animation is over.
+[ div, cs ] = new_div("animation: flexGrowZeroToOne linear 1s");
+is(cs.flexGrow, "0", "flexGrowZeroToOne at 0.0s");
+advance_clock(500);
+is(cs.flexGrow, "0.5", "flexGrowZeroToOne at 0.5s");
+advance_clock(1000);
+is(cs.flexGrow, "10", "flexGrowZeroToOne at 1.5s");
+done_div();
+
+// flexShrinkZeroToOne: 0 at 0%, 0.5 at 50%, 20 after animation is over.
+[ div, cs ] = new_div("animation: flexShrinkZeroToOne linear 1s");
+is(cs.flexShrink, "0", "flexShrinkZeroToOne at 0.0s");
+advance_clock(500);
+is(cs.flexShrink, "0.5", "flexShrinkZeroToOne at 0.5s");
+advance_clock(1000);
+is(cs.flexShrink, "20", "flexShrinkZeroToOne at 1.5s");
+done_div();
+
+// flexGrowOneToZero: 1 at 0%, 0.5 at 50%, 10 after animation is over.
+[ div, cs ] = new_div("animation: flexGrowOneToZero linear 1s");
+is(cs.flexGrow, "1", "flexGrowOneToZero at 0.0s");
+advance_clock(500);
+is(cs.flexGrow, "0.5", "flexGrowOneToZero at 0.5s");
+advance_clock(1000);
+is(cs.flexGrow, "10", "flexGrowOneToZero at 1.5s");
+done_div();
+
+// flexShrinkOneToZero: 1 at 0%, 0.5 at 50%, 20 after animation is over.
+[ div, cs ] = new_div("animation: flexShrinkOneToZero linear 1s");
+is(cs.flexShrink, "1", "flexShrinkOneToZero at 0.0s");
+advance_clock(500);
+is(cs.flexShrink, "0.5", "flexShrinkOneToZero at 0.5s");
+advance_clock(1000);
+is(cs.flexShrink, "20", "flexShrinkOneToZero at 1.5s");
+done_div();
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_flex_shorthand.html b/layout/style/test/test_flexbox_flex_shorthand.html
new file mode 100644
index 0000000000..b8416403b6
--- /dev/null
+++ b/layout/style/test/test_flexbox_flex_shorthand.html
@@ -0,0 +1,280 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696253
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 696253</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.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=696253">Mozilla Bug 696253</a>
+<div id="display">
+ <div id="content">
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 696253 **/
+/* (Testing the 'flex' CSS shorthand property) */
+
+// The CSS property name for the shorthand we're testing:
+const gFlexPropName = "flex";
+
+// Info from property_database.js on this property:
+const gFlexPropInfo = gCSSProperties[gFlexPropName];
+
+// The name of the property in the DOM (i.e. in elem.style):
+// (NOTE: In this case it's actually the same as the CSS property name --
+// "flex" -- but that's not guaranteed in general.)
+const gFlexDOMName = gFlexPropInfo.domProp;
+
+// Default values for shorthand subproperties, when they're not specified
+// explicitly in a testcase. This lets the testcases be more concise.
+//
+// The values here are from the flexbox spec on the 'flex' shorthand:
+// "When omitted, [flex-grow and flex-shrink are] set to '1'."
+// "When omitted [..., flex-basis's] specified value is '0%'."
+let gFlexShorthandDefaults = {
+ "flex-grow": "1",
+ "flex-shrink": "1",
+ "flex-basis": "0%"
+};
+
+let gFlexShorthandTestcases = [
+/*
+ {
+ "flex": "SPECIFIED value for flex shorthand",
+
+ // Expected Computed Values of Subproperties
+ // Semi-optional -- if unspecified, the expected value is taken
+ // from gFlexShorthandDefaults.
+ "flex-grow": "EXPECTED computed value for flex-grow property",
+ "flex-shrink": "EXPECTED computed value for flex-shrink property",
+ "flex-basis": "EXPECTED computed value for flex-basis property",
+ },
+*/
+
+ // Initial values of subproperties:
+ // --------------------------------
+ // (checked by another test that uses property_database.js, too, but
+ // might as well check here, too, for thoroughness).
+ {
+ "flex": "",
+ "flex-grow": "0",
+ "flex-shrink": "1",
+ "flex-basis": "auto",
+ },
+ {
+ "flex": "initial",
+ "flex-grow": "0",
+ "flex-shrink": "1",
+ "flex-basis": "auto",
+ },
+
+ // Special keyword "none" --> "0 0 auto"
+ // -------------------------------------
+ {
+ "flex": "none",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ "flex-basis": "auto",
+ },
+
+ // One Value (numeric) --> sets flex-grow
+ // --------------------------------------
+ {
+ "flex": "0",
+ "flex-grow": "0",
+ },
+ {
+ "flex": "5",
+ "flex-grow": "5",
+ },
+ {
+ "flex": "1000",
+ "flex-grow": "1000",
+ },
+ {
+ "flex": "0.0000001",
+ "flex-grow": "1e-7"
+ },
+ {
+ "flex": "20000000",
+ "flex-grow": "20000000",
+ },
+
+ // One Value (length or other nonnumeric) --> sets flex-basis
+ // ----------------------------------------------------------
+ {
+ "flex": "0px",
+ "flex-basis": "0px",
+ },
+ {
+ "flex": "0%",
+ "flex-basis": "0%",
+ },
+ {
+ "flex": "25px",
+ "flex-basis": "25px",
+ },
+ {
+ "flex": "5%",
+ "flex-basis": "5%",
+ },
+ {
+ "flex": "auto",
+ "flex-basis": "auto",
+ },
+ {
+ "flex": "fit-content",
+ "flex-basis": "fit-content",
+ },
+ {
+ "flex": "calc(5px + 6px)",
+ "flex-basis": "11px",
+ },
+ {
+ "flex": "calc(15% + 30px)",
+ "flex-basis": "calc(15% + 30px)",
+ },
+
+ // Two Values (numeric) --> sets flex-grow, flex-shrink
+ // ----------------------------------------------------
+ {
+ "flex": "0 0",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ },
+ {
+ "flex": "0 2",
+ "flex-grow": "0",
+ "flex-shrink": "2",
+ },
+ {
+ "flex": "3 0",
+ "flex-grow": "3",
+ "flex-shrink": "0",
+ },
+ {
+ "flex": "0.5000 2.03",
+ "flex-grow": "0.5",
+ "flex-shrink": "2.03",
+ },
+ {
+ "flex": "300.0 500.0",
+ "flex-grow": "300",
+ "flex-shrink": "500",
+ },
+
+ // Two Values (numeric & length-ish) --> sets flex-grow, flex-basis
+ // ----------------------------------------------------------------
+ {
+ "flex": "0 0px",
+ "flex-grow": "0",
+ "flex-basis": "0px",
+ },
+ {
+ "flex": "0 0%",
+ "flex-grow": "0",
+ "flex-basis": "0%",
+ },
+ {
+ "flex": "10 30px",
+ "flex-grow": "10",
+ "flex-basis": "30px",
+ },
+ {
+ "flex": "99px 2.3",
+ "flex-grow": "2.3",
+ "flex-basis": "99px",
+ },
+ {
+ "flex": "99% 6",
+ "flex-grow": "6",
+ "flex-basis": "99%",
+ },
+ {
+ "flex": "auto 5",
+ "flex-grow": "5",
+ "flex-basis": "auto",
+ },
+ {
+ "flex": "5 fit-content",
+ "flex-grow": "5",
+ "flex-basis": "fit-content",
+ },
+ {
+ "flex": "calc(5% + 10px) 3",
+ "flex-grow": "3",
+ "flex-basis": "calc(5% + 10px)",
+ },
+
+ // Three Values --> Sets all three subproperties
+ // ---------------------------------------------
+ {
+ "flex": "0 0 0",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ "flex-basis": "0px",
+ },
+ {
+ "flex": "0.0 0.00 0px",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ "flex-basis": "0px",
+ },
+ {
+ "flex": "0% 0 0",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ "flex-basis": "0%",
+ },
+ {
+ "flex": "10px 3 2",
+ "flex-grow": "3",
+ "flex-shrink": "2",
+ "flex-basis": "10px",
+ },
+];
+
+function runFlexShorthandTest(aFlexShorthandTestcase)
+{
+ let content = document.getElementById("content");
+
+ let elem = document.createElement("div");
+
+ elem.style[gFlexDOMName] = aFlexShorthandTestcase[gFlexPropName];
+ content.appendChild(elem);
+
+ gFlexPropInfo.subproperties.forEach(function(aSubPropName) {
+ var expectedVal = aSubPropName in aFlexShorthandTestcase ?
+ aFlexShorthandTestcase[aSubPropName] :
+ gFlexShorthandDefaults[aSubPropName];
+
+ // Compare computed value against expected computed value (from testcase)
+ is(window.getComputedStyle(elem).getPropertyValue(aSubPropName),
+ expectedVal,
+ "Computed value of subproperty \"" + aSubPropName + "\" when we set \"" +
+ gFlexPropName + ": " + aFlexShorthandTestcase[gFlexPropName] + "\"");
+ });
+
+ // Clean up
+ content.removeChild(elem);
+}
+
+function main() {
+ gFlexShorthandTestcases.forEach(runFlexShorthandTest);
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_focus_order.html b/layout/style/test/test_flexbox_focus_order.html
new file mode 100644
index 0000000000..0c1f023e3c
--- /dev/null
+++ b/layout/style/test/test_flexbox_focus_order.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=812687
+-->
+<head>
+ <title>Test for Bug 812687: focus order of reordered flex items</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ .container { display: flex; }
+
+ #focus1 { background: yellow; }
+ #focus2 { background: lightgray; }
+ #focus3 { background: orange; }
+ #focus4 { background: lightblue; }
+ #focus5 { background: pink; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=812687">Mozilla Bug 812687</a>
+<p id="display">
+ <a href="#" id="beforeContainer">Link before container</a>
+ <!-- This flex container's children are reordered visually with the "order"
+ CSS property, but their focus order (when tabbing through them) should
+ match their DOM order. So, #focus1 should receive focus before the other
+ flex items, even though it isn't visually the first flex item. And so
+ on, up to #focus5, which is visually first (due to its negative "order"
+ value) but should be focused last (due to being last in the DOM). -->
+ <div class="container">
+ <a href="#" id="focus1">1</a>
+ <div><a href="#" id="focus2">2</a></div>
+ <div style="order: 100"><a href="#" id="focus3">3</a></div>
+ <div><a href="#" id="focus4">4</a></div>
+ <a href="#" id="focus5" style="order: -1">5</a>
+ </div>
+</p>
+<div id="content" style="display: none"></div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 812687 **/
+
+const gExpectedFocusedIds = [
+ "focus1",
+ "focus2",
+ "focus3",
+ "focus4",
+ "focus5"
+];
+
+function doTest() {
+ // First, we focus something just before the flex container:
+ document.getElementById('beforeContainer').focus();
+ is(document.activeElement, document.getElementById('beforeContainer'),
+ "explicitly-focused link should gain focus");
+
+ // And then we advance focus across the focusable things in the flex container
+ // and check that we traverse them in the expected order:
+ for (let expectedId of gExpectedFocusedIds) {
+ synthesizeKey("KEY_Tab");
+ is(document.activeElement, document.getElementById(expectedId),
+ "expecting element '#" + expectedId + "' to be focused");
+ }
+
+ SimpleTest.finish();
+}
+
+// Before we start, we have to bump Mac to make its 'tab'-key focus behavior
+// predicatble:
+function begin() {
+ SpecialPowers.pushPrefEnv({ set: [[ "accessibility.tabfocus", 7 ]] }, doTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(begin);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_layout.html b/layout/style/test/test_flexbox_layout.html
new file mode 100644
index 0000000000..49ee287aa2
--- /dev/null
+++ b/layout/style/test/test_flexbox_layout.html
@@ -0,0 +1,184 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666041
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 666041</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="flexbox_layout_testcases.js"></script>
+ <script type="text/javascript" src="property_database.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=666041">Mozilla Bug 666041</a>
+<div id="display">
+ <div id="content">
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 666041 **/
+
+/* Flexbox Layout Tests
+ * --------------------
+ * This mochitest exercises our implementation of the flexbox layout algorithm
+ * by creating a flex container, inserting some flexible children, and then
+ * verifying that the computed width of those children is what we expect.
+ *
+ * See flexbox_layout_testcases.js for the actual testcases & testcase format.
+ */
+
+function getComputedStyleWrapper(elem, prop)
+{
+ return window.getComputedStyle(elem).getPropertyValue(prop);
+}
+
+function setPossiblyAliasedProperty(aElem, aPropertyName, aPropertyValue,
+ aPropertyMapping)
+{
+ let actualPropertyName = (aPropertyName in aPropertyMapping ?
+ aPropertyMapping[aPropertyName] : aPropertyName);
+
+ if (!gCSSProperties[actualPropertyName]) {
+ ok(false, "Bug in test: property '" + actualPropertyName +
+ "' doesn't exist in gCSSProperties");
+ } else {
+ let domPropertyName = gCSSProperties[actualPropertyName].domProp;
+ aElem.style[domPropertyName] = aPropertyValue;
+ }
+}
+
+// Helper function to strip "px" off the end of a string
+// (so that we can compare two lengths using "isfuzzy()" with an epsilon)
+function stripPx(aLengthInPx)
+{
+ let pxOffset = aLengthInPx.length - 2; // subtract off length of "px"
+
+ // Sanity-check the arg:
+ ok(pxOffset > 0 && aLengthInPx.substr(pxOffset) == "px",
+ "expecting value with 'px' units");
+
+ return aLengthInPx.substr(0, pxOffset);
+}
+
+// The main test function.
+// aFlexboxTestcase is an entry from the list in flexbox_layout_testcases.js
+function testFlexboxTestcase(aFlexboxTestcase, aFlexDirection, aPropertyMapping)
+{
+ let content = document.getElementById("content");
+
+ // Create flex container
+ let flexContainer = document.createElement("div");
+ flexContainer.style.display = "flex";
+ flexContainer.style.flexDirection = aFlexDirection;
+ setPossiblyAliasedProperty(flexContainer, "_main-size",
+ gDefaultFlexContainerSize,
+ aPropertyMapping);
+
+ // Apply testcase's customizations for flex container (if any).
+ if (aFlexboxTestcase.container_properties) {
+ for (let propName in aFlexboxTestcase.container_properties) {
+ let propValue = aFlexboxTestcase.container_properties[propName];
+ setPossiblyAliasedProperty(flexContainer, propName, propValue,
+ aPropertyMapping);
+ }
+ }
+
+ // Create & append flex items
+ aFlexboxTestcase.items.forEach(function(aChildSpec) {
+ // Create an element for our item
+ let child = document.createElement("div");
+
+ // Set all the specified properties on our item
+ for (let propName in aChildSpec) {
+ // aChildSpec[propName] is either a specified value,
+ // or an array of [specifiedValue, computedValue]
+ let specifiedValue = Array.isArray(aChildSpec[propName]) ?
+ aChildSpec[propName][0] :
+ aChildSpec[propName];
+
+ // SANITY CHECK:
+ if (Array.isArray(aChildSpec[propName])) {
+ ok(aChildSpec[propName].length >= 2 &&
+ aChildSpec[propName].length <= 3,
+ "unexpected number of elements in array within child spec");
+ }
+
+ if (specifiedValue !== null) {
+ setPossiblyAliasedProperty(child, propName, specifiedValue,
+ aPropertyMapping);
+ }
+ }
+
+ // Append the item to the flex container
+ flexContainer.appendChild(child);
+ });
+
+ // Append the flex container
+ content.appendChild(flexContainer);
+
+ // NOW: Test the computed style on the flex items
+ let child = flexContainer.firstChild;
+ for (let i = 0; i < aFlexboxTestcase.items.length; i++) {
+ if (!child) { // sanity
+ ok(false, "should have created a child for each child-spec");
+ }
+
+ let childSpec = aFlexboxTestcase.items[i];
+ for (let propName in childSpec) {
+ if (Array.isArray(childSpec[propName])) {
+ let expectedVal = childSpec[propName][1];
+ let actualPropName = (propName in aPropertyMapping ?
+ aPropertyMapping[propName] : propName);
+ let actualVal = getComputedStyleWrapper(child, actualPropName);
+ let message = "computed value of '" + actualPropName +
+ "' should match expected";
+
+ if (childSpec[propName].length > 2) {
+ // 3rd entry in array is epsilon
+ // Need to strip off "px" units in order to use epsilon:
+ let actualValNoPx = stripPx(actualVal);
+ let expectedValNoPx = stripPx(expectedVal);
+ isfuzzy(actualValNoPx, expectedValNoPx,
+ childSpec[propName][2], message);
+ } else {
+ is(actualVal, expectedVal, message);
+ }
+ }
+ }
+
+ child = child.nextSibling;
+ }
+
+ // Clean up: drop the flex container.
+ content.removeChild(flexContainer);
+}
+
+function main()
+{
+ gFlexboxTestcases.forEach(
+ function(aTestcase) {
+ testFlexboxTestcase(aTestcase, "",
+ gRowPropertyMapping);
+ testFlexboxTestcase(aTestcase, "row",
+ gRowPropertyMapping);
+ testFlexboxTestcase(aTestcase, "row-reverse",
+ gRowReversePropertyMapping);
+ testFlexboxTestcase(aTestcase, "column",
+ gColumnPropertyMapping);
+ testFlexboxTestcase(aTestcase, "column-reverse",
+ gColumnReversePropertyMapping);
+ }
+ );
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_order.html b/layout/style/test/test_flexbox_order.html
new file mode 100644
index 0000000000..64b5431da8
--- /dev/null
+++ b/layout/style/test/test_flexbox_order.html
@@ -0,0 +1,194 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666041
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 666041</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ div.ref {
+ display: none;
+ height: 30px;
+ }
+
+ refA, refB, refC {
+ display: block;
+ float: left;
+ }
+
+ div#a, refA {
+ background: lightgreen;
+ width: 20px;
+ height: 30px;
+ }
+ div#b, refB {
+ background: orange;
+ width: 30px;
+ height: 30px;
+ }
+ div#c, refC {
+ background: blue;
+ width: 50px;
+ height: 30px;
+ }
+ div#flexContainer {
+ display: flex;
+ width: 100px;
+ height: 30px;
+ }
+ div#flexContainerParent {
+ display: none;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666041">Mozilla Bug 666041</a>
+<div id="display">
+
+ <!-- Reference cases (display:none; only shown during initRefSnapshots) -->
+ <div id="references">
+ <div class="ref" id="abc"><refA></refA><refB></refB><refC></refC></div>
+ <div class="ref" id="acb"><refA></refA><refC></refC><refB></refB></div>
+ <div class="ref" id="bac"><refB></refB><refA></refA><refC></refC></div>
+ <div class="ref" id="bca"><refB></refB><refC></refC><refA></refA></div>
+ <div class="ref" id="cab"><refC></refC><refA></refA><refB></refB></div>
+ <div class="ref" id="cba"><refC></refC><refB></refB><refA></refA></div>
+ </div>
+
+ <div id="flexContainerParent">
+ <!-- The flex container that we'll be testing
+ (its parent is display:none initially) -->
+ <div id="flexContainer">
+ <div id="a"></div>
+ <div id="b"></div>
+ <div id="c"></div>
+ </div>
+ </div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 666041 **/
+
+/* This testcase ensures that we honor the "order" property when ordering
+ * flex items within a flex container.
+ *
+ * Note: The items in this testcase don't overlap, so this testcase does _not_
+ * test paint ordering. It only tests horizontal ordering in a flex container.
+ */
+
+// DATA
+// ----
+
+// This will store snapshots of our reference divs
+let gRefSnapshots = {};
+
+// These are the sets of 'order' values that we'll test.
+// The first three values in each array are the 'order' values that we'll
+// assign to elements a, b, and c (respectively). The final value in each
+// array is the ID of the expected reference rendering.
+let gOrderTestcases = [
+ // The 6 basic permutations:
+ [ 1, 2, 3, "abc"],
+ [ 1, 3, 2, "acb"],
+ [ 2, 1, 3, "bac"],
+ [ 2, 3, 1, "cab"],
+ [ 3, 1, 2, "bca"],
+ [ 3, 2, 1, "cba"],
+
+ // Test negative values
+ [ 1, -5, -2, "bca"],
+ [ -50, 0, -2, "acb"],
+
+ // Non-integers should be ignored.
+ // (So, they'll leave their div with the initial 'order' value, which is 0.)
+ [ 1, 1.5, 2, "bac"],
+ [ 2.5, 3.4, 1, "abc"],
+ [ 0.5, 1, 1.5, "acb"],
+
+ // Decimal values that happen to be equal to integers (e.g. "3.0") are still
+ // <numbers>, and are _not_ <integers>.
+ // Source: http://www.w3.org/TR/CSS21/syndata.html#value-def-integer
+ // (So, they'll leave their div with the initial 'order' value, which is 0.)
+ // (NOTE: We have to use quotes around "3.0" and "2.0" to be sure JS doesn't
+ // coerce them into integers before we get a chance to set them in CSS.)
+ [ "3.0", "2.0", "1.0", "abc"],
+ [ 3, "2.0", 1, "bca"],
+];
+
+// FUNCTIONS
+// ---------
+
+function initRefSnapshots() {
+ let refIds = ["abc", "acb", "bac", "bca", "cab", "cba"];
+ for (let id of refIds) {
+ let elem = document.getElementById(id);
+ elem.style.display = "block";
+ gRefSnapshots[id] = snapshotWindow(window, false);
+ elem.style.display = "";
+ }
+}
+
+function complainIfSnapshotsDiffer(aSnap1, aSnap2, aMsg) {
+ let compareResult = compareSnapshots(aSnap1, aSnap2, true);
+ ok(compareResult[0],
+ "flex container rendering should match expected (" + aMsg +")");
+ if (!compareResult[0]) {
+ todo(false, "TESTCASE: " + compareResult[1]);
+ todo(false, "REFERENCE: "+ compareResult[2]);
+ }
+}
+
+function runOrderTestcase(aOrderTestcase) {
+ // Sanity-check
+ ok(Array.isArray(aOrderTestcase), "expecting testcase to be an array");
+ is(aOrderTestcase.length, 4, "expecting testcase to have 4 elements");
+
+ document.getElementById("a").style.order = aOrderTestcase[0];
+ document.getElementById("b").style.order = aOrderTestcase[1];
+ document.getElementById("c").style.order = aOrderTestcase[2];
+
+ let snapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(snapshot, gRefSnapshots[aOrderTestcase[3]],
+ aOrderTestcase);
+
+ // Clean up
+ for (let id of ["a", "b", "c"]) {
+ document.getElementById(id).style.order = "";
+ }
+}
+
+// Main Function
+function main() {
+ initRefSnapshots();
+
+ // un-hide the flex container's parent
+ let flexContainerParent = document.getElementById("flexContainerParent");
+ flexContainerParent.style.display = "block";
+
+ // Initial sanity-check: should be in expected document order
+ let initialSnapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(initialSnapshot, gRefSnapshots.abc,
+ "initial flex container rendering, " +
+ "no 'order' value yet");
+
+ // OK, now we run our tests
+ gOrderTestcases.forEach(runOrderTestcase);
+
+ // Re-hide the flex container at the end
+ flexContainerParent.style.display = "";
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_order_abspos.html b/layout/style/test/test_flexbox_order_abspos.html
new file mode 100644
index 0000000000..bf4c99aa76
--- /dev/null
+++ b/layout/style/test/test_flexbox_order_abspos.html
@@ -0,0 +1,217 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1345873
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1345873</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ div.ref {
+ display: none;
+ height: 30px;
+ }
+
+ refA, refB, refC {
+ display: block;
+ float: left;
+ }
+
+ div#a, refA {
+ background: lightgreen;
+ width: 20px;
+ height: 30px;
+ }
+ div#b, refB {
+ background: orange;
+ width: 30px;
+ height: 30px;
+ }
+ div#c, refC {
+ background: blue;
+ width: 50px;
+ height: 30px;
+ }
+ div#flexContainer {
+ display: flex;
+ width: 100px;
+ height: 30px;
+ }
+ div#flexContainerParent {
+ display: none;
+ }
+ .abs {
+ position: absolute !important;
+ width: 15px !important;
+ height: 15px !important;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1345873">Mozilla Bug 1345873</a>
+<div id="display">
+
+ <!-- Reference cases (display:none; only shown during initRefSnapshots) -->
+ <div id="references">
+ <div class="ref" id="abc"><refA></refA><refB></refB><refC></refC></div>
+ <div class="ref" id="Abc">
+ <refA class="abs"></refA><refB></refB><refC></refC></div>
+ <div class="ref" id="Bac">
+ <refB class="abs"></refB><refA></refA><refC></refC></div>
+ <div class="ref" id="Bca">
+ <refB class="abs"></refB><refC></refC><refA></refA></div>
+ <div class="ref" id="Cab">
+ <refC class="abs"></refC><refA></refA><refB></refB></div>
+ <div class="ref" id="ABc">
+ <refA class="abs"></refA><refB class="abs"></refB><refC></refC></div>
+ <div class="ref" id="ACb">
+ <refA class="abs"></refA><refC class="abs"></refC><refB></refB></div>
+ <div class="ref" id="BCa">
+ <refB class="abs"></refB><refC class="abs"></refC><refA></refA></div>
+ <div class="ref" id="ABC">
+ <refA class="abs"></refA><refB class="abs"></refB><refC class="abs"></refC></div>
+ </div>
+
+ <div id="flexContainerParent">
+ <!-- The flex container that we'll be testing
+ (its parent is display:none initially) -->
+ <div id="flexContainer">
+ <div id="a"></div>
+ <div id="b"></div>
+ <div id="c"></div>
+ </div>
+ </div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 1345873 **/
+
+/* This testcase ensures that we honor the "order" property when ordering
+ * flex items within a flex container.
+ *
+ * Note: The items in this testcase don't overlap, so this testcase does _not_
+ * test paint ordering. It only tests horizontal ordering in a flex container.
+ */
+
+// DATA
+// ----
+
+// This will store snapshots of our reference divs
+let gRefSnapshots = {};
+
+// These are the sets of 'order' values that we'll test.
+// * The first three values in each array are the 'order' values that we'll
+// assign to elements a, b, and c (respectively).
+// * The next value is a string containing the concatenated IDs of any
+// elements that should be absolutely positioned.
+// * The final value in each array is the ID of the expected reference
+// rendering. (By convention, in those IDs, capital = abspos)
+var gOrderTestcases = [
+ // Just one child is abspos:
+ [ 1, 2, 3, "a", "Abc"],
+ [ 1, 2, 3, "b", "Bac"],
+ [ 1, 2, 3, "c", "Cab"],
+ [ 2, 3, 1, "b", "Bca"],
+ [ 3, 1, 1, "b", "Bca"],
+
+ // Two children are abspos:
+ // (Note: "order" doesn't influence position or paint order for abspos
+ // children - only for (in-flow) flex items.)
+ [ 1, 2, 3, "ab", "ABc"],
+ [ 2, 1, 3, "ab", "ABc"],
+ [ 1, 2, 3, "ac", "ACb"],
+ [ 3, 2, 1, "ac", "ACb"],
+ [ 3, 2, 1, "bc", "BCa"],
+
+ // All three children are abspos:
+ // (Rendering always the same regardless of "order" values)
+ [ 1, 2, 3, "abc", "ABC"],
+ [ 3, 1, 2, "abc", "ABC"],
+ [ 3, 2, 1, "abc", "ABC"],
+];
+
+// FUNCTIONS
+// ---------
+
+function initRefSnapshots() {
+ let refIds = ["abc",
+ "Abc", "Bac", "Bca", "Cab",
+ "ABc", "ACb", "BCa",
+ "ABC"];
+ for (let id of refIds) {
+ let elem = document.getElementById(id);
+ elem.style.display = "block";
+ gRefSnapshots[id] = snapshotWindow(window, false);
+ elem.style.display = "";
+ }
+}
+
+function complainIfSnapshotsDiffer(aSnap1, aSnap2, aMsg) {
+ let compareResult = compareSnapshots(aSnap1, aSnap2, true);
+ ok(compareResult[0],
+ "flex container rendering should match expected (" + aMsg +")");
+ if (!compareResult[0]) {
+ todo(false, "TESTCASE: " + compareResult[1]);
+ todo(false, "REFERENCE: "+ compareResult[2]);
+ }
+}
+
+function runOrderTestcase(aOrderTestcase) {
+ // Sanity-check
+ ok(Array.isArray(aOrderTestcase), "expecting testcase to be an array");
+ is(aOrderTestcase.length, 5, "expecting testcase to have 5 elements");
+
+ document.getElementById("a").style.order = aOrderTestcase[0];
+ document.getElementById("b").style.order = aOrderTestcase[1];
+ document.getElementById("c").style.order = aOrderTestcase[2];
+
+ let idsToMakeAbspos = aOrderTestcase[3].split("");
+ for (let absPosId of idsToMakeAbspos) {
+ document.getElementById(absPosId).classList.add("abs");
+ }
+
+ let snapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(snapshot, gRefSnapshots[aOrderTestcase[4]],
+ aOrderTestcase);
+
+ // Clean up
+ for (let id of ["a", "b", "c"]) {
+ document.getElementById(id).style.order = "";
+ document.getElementById(id).classList.remove("abs");
+ }
+}
+
+// Main Function
+function main() {
+ initRefSnapshots();
+
+ // un-hide the flex container's parent
+ let flexContainerParent = document.getElementById("flexContainerParent");
+ flexContainerParent.style.display = "block";
+
+ // Initial sanity-check: should be in expected document order
+ let initialSnapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(initialSnapshot, gRefSnapshots.abc,
+ "initial flex container rendering, " +
+ "no 'order' value yet");
+
+ // OK, now we run our tests
+ gOrderTestcases.forEach(runOrderTestcase);
+
+ // Re-hide the flex container at the end
+ flexContainerParent.style.display = "";
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_order_table.html b/layout/style/test/test_flexbox_order_table.html
new file mode 100644
index 0000000000..2423d5d6d6
--- /dev/null
+++ b/layout/style/test/test_flexbox_order_table.html
@@ -0,0 +1,198 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=799775
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 799775</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ div.ref {
+ display: none;
+ height: 30px;
+ }
+
+ refA, refB, refC {
+ display: block;
+ float: left;
+ }
+
+ div#a, div#b, div#c {
+ display: table;
+ }
+
+ div#a, refA {
+ background: lightgreen;
+ width: 20px;
+ height: 30px;
+ }
+ div#b, refB {
+ background: orange;
+ width: 30px;
+ height: 30px;
+ }
+ div#c, refC {
+ background: blue;
+ width: 50px;
+ height: 30px;
+ }
+ div#flexContainer {
+ display: flex;
+ width: 100px;
+ height: 30px;
+ }
+ div#flexContainerParent {
+ display: none;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=799775">Mozilla Bug 799775</a>
+<div id="display">
+
+ <!-- Reference cases (display:none; only shown during initRefSnapshots) -->
+ <div id="references">
+ <div class="ref" id="abc"><refA></refA><refB></refB><refC></refC></div>
+ <div class="ref" id="acb"><refA></refA><refC></refC><refB></refB></div>
+ <div class="ref" id="bac"><refB></refB><refA></refA><refC></refC></div>
+ <div class="ref" id="bca"><refB></refB><refC></refC><refA></refA></div>
+ <div class="ref" id="cab"><refC></refC><refA></refA><refB></refB></div>
+ <div class="ref" id="cba"><refC></refC><refB></refB><refA></refA></div>
+ </div>
+
+ <div id="flexContainerParent">
+ <!-- The flex container that we'll be testing
+ (its parent is display:none initially) -->
+ <div id="flexContainer">
+ <div id="a"></div>
+ <div id="b"></div>
+ <div id="c"></div>
+ </div>
+ </div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 799775 **/
+
+/* This testcase ensures that we honor the "order" property when ordering
+ * tables as flex items within a flex container.
+ *
+ * Note: The items in this testcase don't overlap, so this testcase does _not_
+ * test paint ordering. It only tests horizontal ordering in a flex container.
+ */
+
+// DATA
+// ----
+
+// This will store snapshots of our reference divs
+let gRefSnapshots = {};
+
+// These are the sets of 'order' values that we'll test.
+// The first three values in each array are the 'order' values that we'll
+// assign to elements a, b, and c (respectively). The final value in each
+// array is the ID of the expected reference rendering.
+let gOrderTestcases = [
+ // The 6 basic permutations:
+ [ 1, 2, 3, "abc"],
+ [ 1, 3, 2, "acb"],
+ [ 2, 1, 3, "bac"],
+ [ 2, 3, 1, "cab"],
+ [ 3, 1, 2, "bca"],
+ [ 3, 2, 1, "cba"],
+
+ // Test negative values
+ [ 1, -5, -2, "bca"],
+ [ -50, 0, -2, "acb"],
+
+ // Non-integers should be ignored.
+ // (So, they'll leave their div with the initial 'order' value, which is 0.)
+ [ 1, 1.5, 2, "bac"],
+ [ 2.5, 3.4, 1, "abc"],
+ [ 0.5, 1, 1.5, "acb"],
+
+ // Decimal values that happen to be equal to integers (e.g. "3.0") are still
+ // <numbers>, and are _not_ <integers>.
+ // Source: http://www.w3.org/TR/CSS21/syndata.html#value-def-integer
+ // (So, they'll leave their div with the initial 'order' value, which is 0.)
+ // (NOTE: We have to use quotes around "3.0" and "2.0" to be sure JS doesn't
+ // coerce them into integers before we get a chance to set them in CSS.)
+ [ "3.0", "2.0", "1.0", "abc"],
+ [ 3, "2.0", 1, "bca"],
+];
+
+// FUNCTIONS
+// ---------
+
+function initRefSnapshots() {
+ let refIds = ["abc", "acb", "bac", "bca", "cab", "cba"];
+ for (let id of refIds) {
+ let elem = document.getElementById(id);
+ elem.style.display = "block";
+ gRefSnapshots[id] = snapshotWindow(window, false);
+ elem.style.display = "";
+ }
+}
+
+function complainIfSnapshotsDiffer(aSnap1, aSnap2, aMsg) {
+ let compareResult = compareSnapshots(aSnap1, aSnap2, true);
+ ok(compareResult[0],
+ "flex container rendering should match expected (" + aMsg +")");
+ if (!compareResult[0]) {
+ todo(false, "TESTCASE: " + compareResult[1]);
+ todo(false, "REFERENCE: "+ compareResult[2]);
+ }
+}
+
+function runOrderTestcase(aOrderTestcase) {
+ // Sanity-check
+ ok(Array.isArray(aOrderTestcase), "expecting testcase to be an array");
+ is(aOrderTestcase.length, 4, "expecting testcase to have 4 elements");
+
+ document.getElementById("a").style.order = aOrderTestcase[0];
+ document.getElementById("b").style.order = aOrderTestcase[1];
+ document.getElementById("c").style.order = aOrderTestcase[2];
+
+ let snapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(snapshot, gRefSnapshots[aOrderTestcase[3]],
+ aOrderTestcase);
+
+ // Clean up
+ for (let id of ["a", "b", "c"]) {
+ document.getElementById(id).style.order = "";
+ }
+}
+
+// Main Function
+function main() {
+ initRefSnapshots();
+
+ // un-hide the flex container's parent
+ let flexContainerParent = document.getElementById("flexContainerParent");
+ flexContainerParent.style.display = "block";
+
+ // Initial sanity-check: should be in expected document order
+ let initialSnapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(initialSnapshot, gRefSnapshots.abc,
+ "initial flex container rendering, " +
+ "no 'order' value yet");
+
+ // OK, now we run our tests
+ gOrderTestcases.forEach(runOrderTestcase);
+
+ // Re-hide the flex container at the end
+ flexContainerParent.style.display = "";
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_reflow_counts.html b/layout/style/test/test_flexbox_reflow_counts.html
new file mode 100644
index 0000000000..a8f4913f7d
--- /dev/null
+++ b/layout/style/test/test_flexbox_reflow_counts.html
@@ -0,0 +1,199 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1142686
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1142686</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .flex {
+ display: flex;
+ }
+ #outerFlex {
+ border: 1px solid black;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142686">Mozilla Bug 1142686</a>
+<div id="display">
+ <div id="content">
+ <div class="flex" id="outerFlex">
+ <div class="flex" id="midFlex">
+ <div id="innerBlock">
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 1142686 **/
+
+/**
+ * This test checks how many reflows are required, when we make a change inside
+ * a set of two nested flex containers, with various styles applied to
+ * the containers & innermost child. Some flex layout operations require two
+ * passes (which can cause exponential blowup). This test is intended to verify
+ * that certain configurations do *not* require two-pass layout, by comparing
+ * the reflow-count for a more-complex scenario against a less-complex scenario.
+ *
+ * We have two nested flex containers around an initially-empty block. For each
+ * measurement, we put some text in the block, and we see how many frame-reflow
+ * operations occur as a result.
+ */
+
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+// The elements:
+const gOuterFlex = document.getElementById("outerFlex");
+const gMidFlex = document.getElementById("midFlex");
+const gInnerBlock = document.getElementById("innerBlock");
+
+// This cleanup helper-function removes all children from 'parent'
+// except for 'childToPreserve' (if specified)
+function removeChildrenExcept(parent, childToPreserve)
+{
+ if (childToPreserve && childToPreserve.parentNode != parent) {
+ // This is just a sanity/integrity-check -- if this fails, it's probably a
+ // bug in this test rather than in the code. I'm not checking this via
+ // e.g. "is(childToPreserve.parentNode, parent)", because this *should*
+ // always pass, and each "pass" is not interesting here since it's a
+ // sanity-check. It's only interesting/noteworthy if it fails. So, to
+ // avoid bloating this test's passed-subtest-count & output, we only bother
+ // reporting on this in the case where something's wrong.
+ ok(false, "bug in test; 'childToPreserve' should be child of 'parent'");
+ }
+
+ // For simplicity, we just remove *all children* and then reappend
+ // childToPreserve as the sole child.
+ while (parent.firstChild) {
+ parent.removeChild(parent.firstChild);
+ }
+ if (childToPreserve) {
+ parent.appendChild(childToPreserve);
+ }
+}
+
+// Appends 'childCount' new children to 'parent'
+function addNChildren(parent, childCount)
+{
+ for (let i = 0; i < childCount; i++) {
+ let newChild = document.createElement("div");
+ // Give the new child some text so it's got a nonzero content-size:
+ newChild.append("a");
+ parent.appendChild(newChild);
+ }
+}
+
+// Undoes whatever styling customizations and DOM insertions that a given
+// testcase has done, to prepare for running the next testcase.
+function cleanup()
+{
+ gOuterFlex.style = gMidFlex.style = gInnerBlock.style = "";
+ removeChildrenExcept(gInnerBlock);
+ removeChildrenExcept(gMidFlex, gInnerBlock);
+ removeChildrenExcept(gOuterFlex, gMidFlex);
+}
+
+// Each testcase here has a label (used in test output), a function to set up
+// the testcase, and (optionally) a function to set up the reference case.
+let gTestcases = [
+ {
+ label : "border on flex items",
+ addTestStyle : function() {
+ gMidFlex.style.border = gInnerBlock.style.border = "3px solid black";
+ },
+ },
+ {
+ label : "padding on flex items",
+ addTestStyle : function() {
+ gMidFlex.style.padding = gInnerBlock.style.padding = "5px";
+ },
+ },
+ {
+ label : "margin on flex items",
+ addTestStyle : function() {
+ gMidFlex.style.margin = gInnerBlock.style.margin = "2px";
+ },
+ },
+ {
+ // When we make a change in one flex item, the number of reflows should not
+ // scale with its number of siblings (as long as those siblings' sizes
+ // aren't impacted by the change):
+ label : "additional flex items in outer flex container",
+
+ // Compare 5 bonus flex items vs. 1 bonus flex item:
+ addTestStyle : function() {
+ addNChildren(gOuterFlex, 5);
+ },
+ addReferenceStyle : function() {
+ addNChildren(gOuterFlex, 1);
+ },
+ },
+ {
+ // (As above, but now the bonus flex items are one step deeper in the tree,
+ // on the nested flex container rather than the outer one)
+ label : "additional flex items in nested flex container",
+ addTestStyle : function() {
+ addNChildren(gMidFlex, 5);
+ },
+ addReferenceStyle : function() {
+ addNChildren(gMidFlex, 1);
+ },
+ },
+];
+
+// Flush layout & return the global frame-reflow-count
+function getReflowCount()
+{
+ let unusedVal = gOuterFlex.offsetHeight; // flush layout
+ return gUtils.framesReflowed;
+}
+
+// This function adds some text inside of gInnerBlock, and returns the number
+// of frames that need to be reflowed as a result.
+function makeTweakAndCountReflows()
+{
+ let beforeCount = getReflowCount();
+ gInnerBlock.appendChild(document.createTextNode("hello"));
+ let afterCount = getReflowCount();
+
+ let numReflows = afterCount - beforeCount;
+ if (numReflows <= 0) {
+ ok(false, "something's wrong -- we should've reflowed *something*");
+ }
+ return numReflows;
+}
+
+// Given a testcase (from gTestcases), this function verifies that the
+// testcase scenario requires the same number of reflows as the reference
+// scenario.
+function runOneTest(aTestcase)
+{
+ aTestcase.addTestStyle();
+ let numTestcaseReflows = makeTweakAndCountReflows();
+ cleanup();
+
+ if (aTestcase.addReferenceStyle) {
+ aTestcase.addReferenceStyle();
+ }
+ let numReferenceReflows = makeTweakAndCountReflows();
+ cleanup();
+
+ is(numTestcaseReflows, numReferenceReflows,
+ "Testcase & reference case should require same number of reflows" +
+ " (testcase label: '" + aTestcase.label + "')");
+}
+
+gTestcases.forEach(runOneTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flushing_frame.html b/layout/style/test/test_flushing_frame.html
new file mode 100644
index 0000000000..fa6d751647
--- /dev/null
+++ b/layout/style/test/test_flushing_frame.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1545516: We don't flush layout unnecessarily on the parent
+ document when the frame is already disconnected.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div id="content"></div>
+<script>
+ SimpleTest.waitForExplicitFinish();
+ const iframe = document.createElement("iframe");
+ const content = document.querySelector("#content");
+ const parentUtils = SpecialPowers.getDOMWindowUtils(window);
+ iframe.onload = function() {
+ const win = iframe.contentWindow;
+ iframe.offsetTop; // flush layout
+ content.style.display = "inline"; // Dirty style with something that will reframe.
+
+ const previousConstructCount = parentUtils.framesConstructed;
+ let pagehideRan = false;
+ win.addEventListener("pagehide", function() {
+ pagehideRan = true;
+ win.foo = win.innerWidth;
+ is(parentUtils.framesConstructed, previousConstructCount, "innerWidth shouldn't have flushed parent document layout")
+ win.bar = win.document.documentElement.offsetHeight;
+ is(parentUtils.framesConstructed, previousConstructCount, "offsetHeight shouldn't have flushed parent document layout")
+ win.baz = win.getComputedStyle(win.document.documentElement).color;
+ is(parentUtils.framesConstructed, previousConstructCount, "getComputedStyle shouldn't have flushed parent document layout")
+ });
+
+ iframe.remove(); // Remove the iframe
+ is(pagehideRan, true, "pagehide handler should've ran");
+ is(parentUtils.framesConstructed, previousConstructCount, "Nothing should've flushed the parent document layout yet");
+ content.offsetTop;
+ isnot(parentUtils.framesConstructed, previousConstructCount, "We should've flushed layout now");
+ SimpleTest.finish();
+ };
+ document.body.appendChild(iframe);
+</script>
diff --git a/layout/style/test/test_font_face_cascade.html b/layout/style/test/test_font_face_cascade.html
new file mode 100644
index 0000000000..0d98f9d606
--- /dev/null
+++ b/layout/style/test/test_font_face_cascade.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<title>Test that @font-face rules from different origins cascade correctly</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<script>
+let io = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+
+let utils = SpecialPowers.getDOMWindowUtils(window);
+
+function load_sheet(sheet_text, level) {
+ if (level != "AGENT_SHEET" && level != "USER_SHEET" && level != "AUTHOR_SHEET") {
+ throw "unknown level";
+ }
+
+ let uri = io.newURI("data:text/css," + encodeURI(sheet_text));
+ utils.loadSheet(uri, utils[level]);
+}
+
+load_sheet(
+ "@font-face { font-family: TestAgent; src: url(about:invalid); }",
+ "AGENT_SHEET");
+
+load_sheet(
+ "@font-face { font-family: TestAuthor; src: url(about:invalid); }",
+ "AUTHOR_SHEET");
+
+load_sheet(
+ "@font-face { font-family: TestUser; src: url(about:invalid); }",
+ "USER_SHEET");
+
+is([...document.fonts].map(f => f.family).join(" "),
+ 'TestAuthor',
+ "document.fonts only contains author @font-face rules");
+</script>
diff --git a/layout/style/test/test_font_face_parser.html b/layout/style/test/test_font_face_parser.html
new file mode 100644
index 0000000000..448db4ff14
--- /dev/null
+++ b/layout/style/test/test_font_face_parser.html
@@ -0,0 +1,386 @@
+<!DOCTYPE HTML><html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=441469 -->
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test of @font-face parser</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<p>@font-face parsing (<a
+ target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441469"
+>bug 441469</a>)</p>
+<pre id="display"></pre>
+<style type="text/css" id="testbox"></style>
+<script class="testbody" type="text/javascript">
+function runTest() {
+ function _(b) { return "@font-face { " + b + " }"; };
+ var testset = [
+ // Complete nonsense - shouldn't make a font-face rule at all.
+ { rule: "@font-face;" },
+ { rule: "font-face { }" },
+ { rule: "@fontface { }" },
+ { rule: "@namespace foo url(http://example.com/foo);" },
+
+ // Empty rule.
+ { rule: "@font-face { }", d: {} },
+ { rule: "@font-face {", d: {} },
+ { rule: "@font-face { ; }", d: {}, noncanonical: true },
+
+ // Correct font-family.
+ { rule: _("font-family: \"Mouse\";"), d: {"font-family" : "\"Mouse\""} },
+ { rule: _("font-family: \"Mouse\""), d: {"font-family" : "\"Mouse\""},
+ noncanonical: true },
+ { rule: _("font-family: Mouse;"), d: {"font-family" : "Mouse" },
+ noncanonical: true },
+ { rule: _("font-family: Mouse"), d: {"font-family" : "Mouse" },
+ noncanonical: true },
+
+ // Correct but unusual font-family.
+ { rule: _("font-family: Hoefler Text;"),
+ d: {"font-family" : "Hoefler Text"},
+ noncanonical: true },
+
+ // Incorrect font-family.
+ { rule: _("font-family:"), d: {} },
+ { rule: _("font-family \"Mouse\""), d: {} },
+ { rule: _("font-family: *"), d: {} },
+ { rule: _("font-family: Mouse, Rat"), d: {} },
+ { rule: _("font-family: sans-serif"), d: {} },
+
+ // Correct font-style.
+ { rule: _("font-style: normal;"), d: {"font-style" : "normal"} },
+ { rule: _("font-style: italic;"), d: {"font-style" : "italic"} },
+ { rule: _("font-style: oblique;"), d: {"font-style" : "oblique"} },
+
+ // Correct font-weight.
+ { rule: _("font-weight: 100;"), d: {"font-weight" : "100"} },
+ { rule: _("font-weight: 200;"), d: {"font-weight" : "200"} },
+ { rule: _("font-weight: 300;"), d: {"font-weight" : "300"} },
+ { rule: _("font-weight: 400;"), d: {"font-weight" : "400"} },
+ { rule: _("font-weight: 500;"), d: {"font-weight" : "500"} },
+ { rule: _("font-weight: 600;"), d: {"font-weight" : "600"} },
+ { rule: _("font-weight: 700;"), d: {"font-weight" : "700"} },
+ { rule: _("font-weight: 800;"), d: {"font-weight" : "800"} },
+ { rule: _("font-weight: 900;"), d: {"font-weight" : "900"} },
+ { rule: _("font-weight: normal;"), d: {"font-weight" : "normal"} },
+ { rule: _("font-weight: bold;"), d: {"font-weight" : "bold"} },
+
+ // Incorrect font-weight.
+ { rule: _("font-weight: bolder;"), d: {} },
+ { rule: _("font-weight: lighter;"), d: {} },
+
+ // Correct font-stretch.
+ { rule: _("font-stretch: ultra-condensed;"),
+ d: {"font-stretch" : "ultra-condensed"} },
+ { rule: _("font-stretch: extra-condensed;"),
+ d: {"font-stretch" : "extra-condensed"} },
+ { rule: _("font-stretch: condensed;"),
+ d: {"font-stretch" : "condensed"} },
+ { rule: _("font-stretch: semi-condensed;"),
+ d: {"font-stretch" : "semi-condensed"} },
+ { rule: _("font-stretch: normal;"),
+ d: {"font-stretch" : "normal"} },
+ { rule: _("font-stretch: semi-expanded;"),
+ d: {"font-stretch" : "semi-expanded"} },
+ { rule: _("font-stretch: expanded;"),
+ d: {"font-stretch" : "expanded"} },
+ { rule: _("font-stretch: extra-expanded;"),
+ d: {"font-stretch" : "extra-expanded"} },
+ { rule: _("font-stretch: ultra-expanded;"),
+ d: {"font-stretch" : "ultra-expanded"} },
+
+ // Incorrect font-stretch.
+ { rule: _("font-stretch: wider;"), d: {} },
+ { rule: _("font-stretch: narrower;"), d: {} },
+
+ // Correct src:
+ { rule: _("src: url(\"/fonts/Mouse\");"),
+ d: { "src" : "url(\"/fonts/Mouse\")" } },
+ { rule: _("src: url(/fonts/Mouse);"),
+ d: { "src" : "url(\"/fonts/Mouse\")" }, noncanonical: true },
+
+ { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\");"),
+ d: { "src" : "url(\"/fonts/Mouse\") format(\"truetype\")" } },
+
+ { rule: _("src: url(\"/fonts/Mouse\"), url(\"/fonts/Rat\");"),
+ d: { "src" : "url(\"/fonts/Mouse\"), url(\"/fonts/Rat\")" } },
+
+ { rule: _("src: local(Mouse), url(\"/fonts/Mouse\");"),
+ d: { "src" : "local(Mouse), url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+
+ { rule: _("src: local(\"老鼠\"), url(\"/fonts/Mouse\");"),
+ d: { "src" : "local(\"老鼠\"), url(\"/fonts/Mouse\")" } },
+
+ { rule: _("src: local(\"老鼠\"), url(\"/fonts/Mouse\") format(\"truetype\");"),
+ d: { "src" : "local(\"老鼠\"), url(\"/fonts/Mouse\") format(\"truetype\")" } },
+
+ { rule: _("src: url(\"/fonts/Mouse\") format(truetype);"),
+ d: { "src" : "url(\"/fonts/Mouse\") format(truetype)" } },
+
+ // Correct but unusual src:
+ { rule: _("src: local(Hoefler Text);"),
+ d: {"src" : "local(Hoefler Text)"}, noncanonical: true },
+
+ // Incorrect src:
+ { rule: _("src:"), d: {} },
+ { rule: _("src: \"/fonts/Mouse\";"), d: {} },
+ { rule: _("src: /fonts/Mouse;"), d: {} },
+ { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\",opentype);"), d: {} },
+ { rule: _("src: local(*);"), d: {} },
+ { rule: _("src: format(\"truetype\");"), d: {} },
+ { rule: _("src: local(Mouse) format(\"truetype\");"), d: {} },
+ { rule: _("src: local(Mouse, Rat);"), d: {} },
+ { rule: _("src: local(sans-serif);"), d: {} },
+ { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\", \"opentype\");"), d: {} },
+
+ // Repeated descriptors
+ { rule: _("font-weight: 700; font-weight: 200;"),
+ d: {"font-weight" : "200"},
+ noncanonical: true },
+ { rule: _("src: url(\"/fonts/Cat\"); src: url(\"/fonts/Mouse\");"),
+ d: { "src" : "url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+ { rule: _("src: local(Cat); src: local(Mouse)"),
+ d: { "src" : "local(Mouse)" },
+ noncanonical: true },
+
+ // Correct parenthesis matching for local()
+ { rule: _("src: local(Mouse); src: local(Cat(); src: local(Rat); )"),
+ d: { "src" : "local(Mouse)" },
+ noncanonical: true },
+ { rule: _("src: local(Mouse); src: local(\"Cat\"; src: local(Rat); )"),
+ d: { "src" : "local(Mouse)" },
+ noncanonical: true },
+
+ // Correct parenthesis matching for format()
+ { rule: _("src: url(\"/fonts/Mouse\"); " +
+ "src: url(\"/fonts/Cat\") format(Cat(); src: local(Rat); )"),
+ d: { "src" : "url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+ { rule: _("src: url(\"/fonts/Mouse\"); " +
+ "src: url(\"/fonts/Cat\") format(\"Cat\"; src: local(Rat); )"),
+ d: { "src" : "url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+ { rule: _("src: url(\"/fonts/Mouse\"); " +
+ "src: url(\"/fonts/Cat\") format((); src: local(Rat); )"),
+ d: { "src" : "url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+
+ // Correct unicode-range:
+ { rule: _("unicode-range: U+A5;"), d: { "unicode-range" : "U+A5" } },
+ { rule: _("unicode-range: U+00A5;"),
+ d: { "unicode-range" : "U+A5" }, noncanonical: true },
+ { rule: _("unicode-range: U+00a5;"),
+ d: { "unicode-range" : "U+A5" }, noncanonical: true },
+ { rule: _("unicode-range: u+00a5;"),
+ d: { "unicode-range" : "U+A5" }, noncanonical: true },
+ { rule: _("unicode-range: U+0-FF;"),
+ d: { "unicode-range" : "U+0-FF" } },
+ { rule: _("unicode-range: U+00??;"),
+ d: { "unicode-range" : "U+0-FF" }, noncanonical: true },
+ { rule: _("unicode-range: U+?"),
+ d: { "unicode-range" : "U+0-F" }, noncanonical: true },
+ { rule: _("unicode-range: U+590-5ff;"),
+ d: { "unicode-range" : "U+590-5FF" }, noncanonical: true },
+
+ { rule: _("unicode-range: U+A5, U+4E00-9FFF, U+30??, U+FF00-FF9F;"),
+ d: { "unicode-range" : "U+A5, U+4E00-9FFF, U+3000-30FF, U+FF00-FF9F" },
+ noncanonical: true },
+
+ { rule: _("unicode-range: U+104??;"),
+ d: { "unicode-range" : "U+10400-104FF" }, noncanonical: true },
+ { rule: _("unicode-range: U+320??, U+321??, U+322??, U+323??, U+324??, U+325??;"),
+ d: { "unicode-range" : "U+32000-320FF, U+32100-321FF, U+32200-322FF, U+32300-323FF, U+32400-324FF, U+32500-325FF" },
+ noncanonical: true },
+ { rule: _("unicode-range: U+100000-10ABCD;"),
+ d: { "unicode-range" : "U+100000-10ABCD" } },
+ { rule: _("unicode-range: U+0121 , U+1023"),
+ d: { "unicode-range" : "U+121, U+1023" }, noncanonical: true },
+ { rule: _("unicode-range: U+0121/**/, U+1023"),
+ d: { "unicode-range" : "U+121, U+1023" }, noncanonical: true },
+
+ // Incorrect unicode-range:
+ { rule: _("unicode-range:"), d: {} },
+ { rule: _("unicode-range: U+"), d: {} },
+ { rule: _("unicode-range: U+8FFFFFFF"), d: {} },
+ { rule: _("unicode-range: U+8FFF-7000"), d: {} },
+ { rule: _("unicode-range: U+8F??-9000"), d: {} },
+ { rule: _("unicode-range: U+9000-9???"), d: {} },
+ { rule: _("unicode-range: U+??00"), d: {} },
+ { rule: _("unicode-range: U+12345678?"), d: {} },
+ { rule: _("unicode-range: U+1????????"), d: {} },
+ { rule: _("unicode-range: twelve"), d: {} },
+ { rule: _("unicode-range: 1000"), d: {} },
+ { rule: _("unicode-range: 13??"), d: {} },
+ { rule: _("unicode-range: 1300-1377"), d: {} },
+ { rule: _("unicode-range: U-1000"), d: {} },
+ { rule: _("unicode-range: U+nnnn"), d: {} },
+ { rule: _("unicode-range: U+0121 U+1023"), d: {} },
+ { rule: _("unicode-range: U+ 0121"), d: {} },
+ { rule: _("unicode-range: U +0121"), d: {} },
+ { rule: _("unicode-range: U+0121-"), d: {} },
+ { rule: _("unicode-range: U+0121- 1023"), d: {} },
+ { rule: _("unicode-range: U+0121 -1023"), d: {} },
+ { rule: _("unicode-range: U+012 ?"), d: {} },
+ { rule: _("unicode-range: U+01 2?"), d: {} },
+ { rule: _("unicode-range: U+A0000-12FFFF"), d: {} },
+ { rule: _("unicode-range: U+??????"), d: {} },
+
+ // Thorough test of seven-digit rejection: all these are syntax errors
+ { rule: _("unicode-range: U+1034560, U+A5"), d: {} },
+ { rule: _("unicode-range: U+1034569, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456a, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456f, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456?, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456-1034560, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456-1034569, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456-103456a, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456-103456f, U+A5"), d: {} },
+
+ // Syntactically invalid unicode-range tokens invalidate the
+ // entire descriptor
+ { rule: _("unicode-range: U+1, U+2, U+X"), d: {} },
+ { rule: _("unicode-range: U+A5, U+0?F"), d: {} },
+ { rule: _("unicode-range: U+A5, U+0F?-E00"), d: {} },
+
+ // Descending ranges and ranges outside 0-10FFFF are invalid
+ { rule: _("unicode-range: U+A5, U+90-30"), d: {} },
+ { rule: _("unicode-range: U+A5, U+220043"), d: {} },
+
+ // font-feature-settings
+ { rule: _("font-feature-settings: normal;"),
+ d: { "font-feature-settings" : "normal" } },
+ { rule: _("font-feature-settings: \"dlig\";"),
+ d: { "font-feature-settings" : "\"dlig\"" } },
+ { rule: _("font-feature-settings: \"dlig\" 1;"),
+ d: { "font-feature-settings" : "\"dlig\"" }, noncanonical: true },
+ { rule: _("font-feature-settings: 'dlig' 1"),
+ d: { "font-feature-settings" : "\"dlig\"" }, noncanonical: true },
+
+ // incorrect font-feature-settings
+ { rule: _("font-feature-settings: dlig 1"), d: {} },
+ { rule: _("font-feature-settings: none;"), d: {} },
+ { rule: _("font-feature-settings: 0;"), d: {} },
+ { rule: _("font-feature-settings: 3.14;"), d: {} },
+ { rule: _("font-feature-settings: 'blah' 3.14;"), d: {} },
+ { rule: _("font-feature-settings: 'dlig' 1 'hist' 0;"), d: {} },
+ { rule: _("font-feature-settings: 'dlig=1,hist=1'"), d: {} },
+
+ // font-language-override:
+ { rule: _("font-language-override: normal;"),
+ d: { "font-language-override" : "normal" } },
+ { rule: _("font-language-override: \"TRK\";"),
+ d: { "font-language-override" : "\"TRK\"" } },
+ { rule: _("font-language-override: 'TRK'"),
+ d: { "font-language-override" : "\"TRK\"" }, noncanonical: true },
+
+ // incorrect font-language-override
+ { rule: _("font-language-override: TRK"), d: {} },
+ { rule: _("font-language-override: none;"), d: {} },
+ { rule: _("font-language-override: 0;"), d: {} },
+ { rule: _("font-language-override: #999;"), d: {} },
+ { rule: _("font-language-override: 'TRK' 'SRB'"), d: {} },
+ { rule: _("font-language-override: 'TRK', 'SRB'"), d: {} },
+
+ // font-display:
+ { rule: _("font-display: auto;"),
+ d: { "font-display" : "auto" } },
+ { rule: _("font-display: block;"),
+ d: { "font-display" : "block" } },
+ { rule: _("font-display: swap;"),
+ d: { "font-display" : "swap" } },
+ { rule: _("font-display: fallback;"),
+ d: { "font-display" : "fallback" } },
+ { rule: _("font-display: optional;"),
+ d: { "font-display" : "optional" } },
+
+ // incorrect font-display
+ { rule: _("font-display: hidden"), d: {} },
+ { rule: _("font-display: swap 3"), d: {} },
+ { rule: _("font-display: block 2 swap 0"), d: {} },
+ { rule: _("font-display: all"), d: {} },
+ ];
+
+ var display = document.getElementById("display");
+ var sheet = document.styleSheets[1];
+
+ for (var curTest = 0; curTest < testset.length; curTest++) {
+ try {
+ while(sheet.cssRules.length > 0)
+ sheet.deleteRule(0);
+ sheet.insertRule(testset[curTest].rule, 0);
+ } catch (e) {
+ ok(
+ e.name == "SyntaxError"
+ && e instanceof DOMException
+ && e.code == DOMException.SYNTAX_ERR
+ && !('d' in testset[curTest]),
+ testset[curTest].rule + " syntax error thrown - " + e
+ );
+ }
+
+ try {
+ if (testset[curTest].d) {
+ is(sheet.cssRules.length, 1,
+ testset[curTest].rule + " rule count");
+ is(sheet.cssRules[0].type, 5 /*FONT_FACE_RULE*/,
+ testset[curTest].rule + " rule type");
+
+ var d = testset[curTest].d;
+ var s = sheet.cssRules[0].style;
+ var n = 0;
+
+ is(s.getPropertyValue("pointless"), "", "Unknown descriptors don't assert");
+
+ // everything is set that should be
+ for (var name in d) {
+ is(s.getPropertyValue(name), d[name],
+ testset[curTest].rule + " (prop " + name + ")");
+ n++;
+ }
+ // nothing else is set
+ is(s.length, n, testset[curTest].rule + "prop count");
+ for (var i = 0; i < s.length; i++) {
+ ok(
+ s[i] in d,
+ testset[curTest].rule +
+ " - Unexpected item #" + i + ": " + s[i]
+ );
+ }
+
+ // round-tripping of cssText
+ // this is a strong test; it's okay if the exact serialization
+ // changes in the future
+ if (n && !testset[curTest].noncanonical) {
+ is(sheet.cssRules[0].cssText.replace(/[ \n]+/g, " "),
+ testset[curTest].rule,
+ testset[curTest].rule + " rule text");
+ }
+ } else {
+ if (sheet.cssRules.length == 0) {
+ is(sheet.cssRules.length, 0,
+ testset[curTest].rule + " rule count (0)");
+ } else {
+ is(sheet.cssRules.length, 1,
+ testset[curTest].rule + " rule count (1 non-fontface)");
+ isnot(sheet.cssRules[0].type, 5 /*FONT_FACE_RULE*/,
+ testset[curTest].rule + " rule type (1 non-fontface)");
+ }
+ }
+ } catch (e) {
+ ok(false, testset[curTest].rule + " - During test: " + e);
+ }
+ }
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_font_family_parsing.html b/layout/style/test/test_font_family_parsing.html
new file mode 100644
index 0000000000..59bedc0b94
--- /dev/null
+++ b/layout/style/test/test_font_family_parsing.html
@@ -0,0 +1,272 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Font family name parsing tests</title>
+ <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com">
+ <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-family-prop" />
+ <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-prop" />
+ <meta name="assert" content="tests that valid font family names parse and invalid ones don't" />
+ <script type="text/javascript" src="/resources/testharness.js"></script>
+ <script type="text/javascript" src="/resources/testharnessreport.js"></script>
+ <style type="text/css">
+ </style>
+</head>
+<body>
+<div id="log"></div>
+<pre id="display"></pre>
+<style type="text/css" id="testbox"></style>
+
+<script type="text/javascript">
+
+function fontProp(n, size, s1, s2) { return (s1 ? s1 + " " : "") + (s2 ? s2 + " " : "") + size + " " + n; }
+function font(n, size, s1, s2) { return "font: " + fontProp(n, size, s1, s2); }
+
+// testrules
+// namelist - font family list
+// invalid - true if declarations won't parse in either font-family or font
+// fontonly - only test with the 'font' property
+// single - namelist includes only a single name (@font-face rules only allow a single name)
+
+var testFontFamilyLists = [
+
+ /* basic syntax */
+ { namelist: "simple", single: true },
+ { namelist: "'simple'", single: true },
+ { namelist: '"simple"', single: true },
+ { namelist: "-simple", single: true },
+ { namelist: "_simple", single: true },
+ { namelist: "quite simple", single: true },
+ { namelist: "quite _simple", single: true },
+ { namelist: "quite -simple", single: true },
+ { namelist: "0simple", invalid: true, single: true },
+ { namelist: "simple!", invalid: true, single: true },
+ { namelist: "simple()", invalid: true, single: true },
+ { namelist: "quite@simple", invalid: true, single: true },
+ { namelist: "#simple", invalid: true, single: true },
+ { namelist: "quite 0simple", invalid: true, single: true },
+ { namelist: "納豆嫌い", single: true },
+ { namelist: "納豆嫌い, ick, patooey" },
+ { namelist: "ick, patooey, 納豆嫌い" },
+ { namelist: "納豆嫌い, 納豆大嫌い" },
+ { namelist: "納豆嫌い, 納豆大嫌い, 納豆本当に嫌い" },
+ { namelist: "納豆嫌い, 納豆大嫌い, 納豆本当に嫌い, 納豆は好みではない" },
+ { namelist: "arial, helvetica, sans-serif" },
+ { namelist: "arial, helvetica, 'times' new roman, sans-serif", invalid: true },
+ { namelist: "arial, helvetica, \"times\" new roman, sans-serif", invalid: true },
+
+ { namelist: "arial, helvetica, \"\\\"times new roman\", sans-serif" },
+ { namelist: "arial, helvetica, '\\\"times new roman', sans-serif" },
+ { namelist: "arial, helvetica, times 'new' roman, sans-serif", invalid: true },
+ { namelist: "arial, helvetica, times \"new\" roman, sans-serif", invalid: true },
+ { namelist: "\"simple", single: true },
+ { namelist: "\\\"simple", single: true },
+ { namelist: "\"\\\"simple\"", single: true },
+ { namelist: "İsimple", single: true },
+ { namelist: "ßsimple", single: true },
+ { namelist: "ẙsimple", single: true },
+
+ /* escapes */
+ { namelist: "\\s imple", single: true },
+ { namelist: "\\073 imple", single: true },
+
+ { namelist: "\\035 simple", single: true },
+ { namelist: "sim\\035 ple", single: true },
+ { namelist: "simple\\02cinitial", single: true },
+ { namelist: "simple, \\02cinitial" },
+ { namelist: "sim\\020 \\035 ple", single: true },
+ { namelist: "sim\\020 5ple", single: true },
+ { namelist: "\\@simple", single: true },
+ { namelist: "\\@simple\\;", single: true },
+ { namelist: "\\@font-face", single: true },
+ { namelist: "\\@font-face\\;", single: true },
+ { namelist: "\\031 \\036 px", single: true },
+ { namelist: "\\031 \\036 px", single: true },
+ { namelist: "\\1f4a9", single: true },
+ { namelist: "\\01f4a9", single: true },
+ { namelist: "\\0001f4a9", single: true },
+ { namelist: "\\AbAb", single: true },
+
+ /* keywords */
+ { namelist: "italic", single: true },
+ { namelist: "bold", single: true },
+ { namelist: "bold italic", single: true },
+ { namelist: "italic bold", single: true },
+ { namelist: "larger", single: true },
+ { namelist: "smaller", single: true },
+ { namelist: "bolder", single: true },
+ { namelist: "lighter", single: true },
+ { namelist: "default", invalid: true, fontonly: true, single: true },
+ { namelist: "initial", invalid: true, fontonly: true, single: true },
+ { namelist: "inherit", invalid: true, fontonly: true, single: true },
+ { namelist: "normal", single: true },
+ { namelist: "default, simple", invalid: true },
+ { namelist: "initial, simple", invalid: true },
+ { namelist: "inherit, simple", invalid: true },
+ { namelist: "normal, simple" },
+ { namelist: "simple, default", invalid: true },
+ { namelist: "simple, initial", invalid: true },
+ { namelist: "simple, inherit", invalid: true },
+ { namelist: "simple, default bongo" },
+ { namelist: "simple, initial bongo" },
+ { namelist: "simple, inherit bongo" },
+ { namelist: "simple, bongo default" },
+ { namelist: "simple, bongo initial" },
+ { namelist: "simple, bongo inherit" },
+ { namelist: "simple, normal" },
+ { namelist: "simple default", single: true },
+ { namelist: "simple initial", single: true },
+ { namelist: "simple inherit", single: true },
+ { namelist: "simple normal", single: true },
+ { namelist: "default simple", single: true },
+ { namelist: "initial simple", single: true },
+ { namelist: "inherit simple", single: true },
+ { namelist: "normal simple", single: true },
+ { namelist: "caption", single: true }, // these are keywords for the 'font' property but only when in the first position
+ { namelist: "icon", single: true },
+ { namelist: "menu", single: true },
+ { namelist: "unset", invalid: true, fontonly: true, single: true },
+ { namelist: "unset, simple", invalid: true },
+ { namelist: "simple, unset", invalid: true },
+ { namelist: "simple, unset bongo" },
+ { namelist: "simple, bongo unset" },
+ { namelist: "simple unset", single: true },
+ { namelist: "unset simple", single: true }
+
+];
+
+var gTest = 0;
+
+/* strip out just values */
+function extractDecl(rule)
+{
+ var t = rule.replace(/[ \n]+/g, " ");
+ t = t.replace(/.*{[ \n]*/, "");
+ t = t.replace(/[ \n]*}.*/, "");
+ return t;
+}
+
+
+function testStyleRuleParse(decl, invalid) {
+ var sheet = document.styleSheets[1];
+ var rule = ".test" + gTest++ + " { " + decl + "; }";
+
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+
+ // shouldn't throw but improper handling of punctuation may cause some parsers to throw
+ try {
+ sheet.insertRule(rule, 0);
+ } catch (e) {
+ assert_unreached("unexpected error with " + decl + " ==> " + e.name);
+ }
+
+ assert_equals(sheet.cssRules.length, 1,
+ "strange number of rules (" + sheet.cssRules.length + ") with " + decl);
+
+ var s = extractDecl(sheet.cssRules[0].cssText);
+
+ if (invalid) {
+ assert_equals(s, "", "rule declaration shouldn't parse - " + rule + " ==> " + s);
+ } else {
+ assert_not_equals(s, "", "rule declaration should parse - " + rule);
+
+ // check that the serialization also parses
+ var r = ".test" + gTest++ + " { " + s + " }";
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+ try {
+ sheet.insertRule(r, 0);
+ } catch (e) {
+ assert_unreached("exception occurred parsing serialized form of rule - " + rule + " ==> " + r + " " + e.name);
+ }
+ var s2 = extractDecl(sheet.cssRules[0].cssText);
+ assert_not_equals(s2, "", "serialized form of rule should also parse - " + rule + " ==> " + r);
+ }
+}
+
+var kDefaultFamilySetting = "onelittlepiggywenttomarket";
+
+function testFontFamilySetterParse(namelist, invalid) {
+ var el = document.getElementById("display");
+
+ el.style.fontFamily = kDefaultFamilySetting;
+ var def = el.style.fontFamily;
+ el.style.fontFamily = namelist;
+ if (!invalid) {
+ assert_not_equals(el.style.fontFamily, def, "fontFamily setter should parse - " + namelist);
+ var parsed = el.style.fontFamily;
+ el.style.fontFamily = kDefaultFamilySetting;
+ el.style.fontFamily = parsed;
+ assert_equals(el.style.fontFamily, parsed, "fontFamily setter should parse serialized form to identical serialization - " + parsed + " ==> " + el.style.fontFamily);
+ } else {
+ assert_equals(el.style.fontFamily, def, "fontFamily setter shouldn't parse - " + namelist);
+ }
+}
+
+var kDefaultFontSetting = "16px onelittlepiggywenttomarket";
+
+function testFontSetterParse(n, size, s1, s2, invalid) {
+ var el = document.getElementById("display");
+
+ el.style.font = kDefaultFontSetting;
+ var def = el.style.font;
+ var fp = fontProp(n, size, s1, s2);
+ el.style.font = fp;
+ if (!invalid) {
+ assert_not_equals(el.style.font, def, "font setter should parse - " + fp);
+ var parsed = el.style.font;
+ el.style.font = kDefaultFontSetting;
+ el.style.font = parsed;
+ assert_equals(el.style.font, parsed, "font setter should parse serialized form to identical serialization - " + parsed + " ==> " + el.style.font);
+ } else {
+ assert_equals(el.style.font, def, "font setter shouldn't parse - " + fp);
+ }
+}
+
+var testFontVariations = [
+ { size: "16px" },
+ { size: "900px" },
+ { size: "900em" },
+ { size: "35%" },
+ { size: "7832.3%" },
+ { size: "xx-large" },
+ { size: "larger", s1: "lighter" },
+ { size: "16px", s1: "italic" },
+ { size: "16px", s1: "italic", s2: "bold" },
+ { size: "smaller", s1: "normal" },
+ { size: "16px", s1: "normal", s2: "normal" },
+ { size: "16px", s1: "400", s2: "normal" },
+ { size: "16px", s1: "bolder", s2: "oblique" }
+];
+
+function testFamilyNameParsing() {
+ var i;
+ for (i = 0; i < testFontFamilyLists.length; i++) {
+ var tst = testFontFamilyLists[i];
+ var n = tst.namelist;
+ var t;
+
+ if (!tst.fontonly) {
+ t = "font-family: " + n;
+ test(function() { testStyleRuleParse(t, tst.invalid); }, t);
+ test(function() { testFontFamilySetterParse(n, tst.invalid); }, t + " (setter)");
+ }
+
+ var v;
+ for (v = 0; v < testFontVariations.length; v++) {
+ var f = testFontVariations[v];
+ t = font(n, f.size, f.s1, f.s2);
+ test(function() { testStyleRuleParse(t, tst.invalid); }, t);
+ test(function() { testFontSetterParse(n, f.size, f.s1, f.s2, tst.invalid); }, t + " (setter)");
+ }
+ }
+}
+
+testFamilyNameParsing();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_font_family_serialization.html b/layout/style/test/test_font_family_serialization.html
new file mode 100644
index 0000000000..ad922158f5
--- /dev/null
+++ b/layout/style/test/test_font_family_serialization.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="display"></div>
+<script>
+// This cannot be a web-platform test because this doesn't match what
+// the spec says at the moment. Specifically, the spec wants to have
+// all font family serialized to string, while in practice, all browsers
+// serialize simple them to identifiers in some cases.
+// We want to check our current behavior. This can be changed once
+// browsers have an agreement on the exact behavior to spec.
+
+// format: [input, expected serialization]
+const tests = [
+ // Basic cases
+ ['simple', 'simple'],
+ [' simple ', 'simple'],
+ ['multi idents font', 'multi idents font'],
+ [' multi idents font ', 'multi idents font'],
+ ['"wrapped name"', '"wrapped name"'],
+ ['" wrapped name "', '" wrapped name "'],
+
+ // Special whitespaces
+ ['\\ leading ws', '" leading ws"'],
+ [' \\ leading ws', '" leading ws"'],
+ ['\\ \\ leading ws', '" leading ws"'],
+ [' \\ \\ leading ws', '" leading ws"'],
+ ['\\ \\ \\ leading ws', '" leading ws"'],
+ ['trailing ws\\ ', '"trailing ws "'],
+ ['trailing ws\\ ', '"trailing ws "'],
+ ['trailing ws \\ ', '"trailing ws "'],
+ ['trailing ws\\ \\ ', '"trailing ws "'],
+ ['escaped\\ ws', '"escaped ws"'],
+ ['escaped\\ ws', '"escaped ws"'],
+ ['escaped\\ \\ ws', '"escaped ws"'],
+ ['escaped \\ ws', '"escaped ws"'],
+ ['escaped \\ ws', '"escaped ws"'],
+ ['escaped number\\ 5', '"escaped number 5"'],
+];
+
+let el = document.getElementById("display");
+for (let [input, expected] of tests) {
+ test(function() {
+ el.style.fontFamily = input;
+ assert_equals(el.style.fontFamily, expected);
+ }, "Reserialization for " + JSON.stringify(input));
+}
+</script>
diff --git a/layout/style/test/test_font_loading_api.html b/layout/style/test/test_font_loading_api.html
new file mode 100644
index 0000000000..7b2a0d9e1b
--- /dev/null
+++ b/layout/style/test/test_font_loading_api.html
@@ -0,0 +1,1895 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for the CSS Font Loading API</title>
+<script src=/tests/SimpleTest/SimpleTest.js></script>
+<link rel=stylesheet type=text/css href=/tests/SimpleTest/test.css>
+
+<script src=descriptor_database.js></script>
+
+<body onload="runTest()">
+<iframe id=v src="file_font_loading_api_vframe.html"></iframe>
+<iframe id=n style="display: none"></iframe>
+
+<script>
+// Map of FontFace descriptor attribute names to @font-face rule descriptor
+// names.
+var descriptorNames = {
+ style: "font-style",
+ weight: "font-weight",
+ stretch: "font-stretch",
+ unicodeRange: "unicode-range",
+ variant: "font-variant",
+ featureSettings: "font-feature-settings",
+ display: "font-display"
+};
+
+// Default values for the FontFace descriptor attributes other than family, as
+// Gecko currently serializes them.
+var defaultValues = {
+ style: "normal",
+ weight: "normal",
+ stretch: "normal",
+ unicodeRange: "U+0-10FFFF",
+ variant: "normal",
+ featureSettings: "normal",
+ display: "auto"
+};
+
+// Non-default values for the FontFace descriptor attributes other than family
+// along with how Gecko currently serializes them. Each value is chosen to be
+// different from the default value and also has a different serialized form.
+var nonDefaultValues = {
+ style: ["Italic", "italic"],
+ weight: ["Bold", "bold"],
+ stretch: ["Ultra-Condensed", "ultra-condensed"],
+ unicodeRange: ["U+3??", "U+300-3FF"],
+ variant: ["Small-Caps", "small-caps"],
+ featureSettings: ["'dlig' on", "\"dlig\""],
+ display: ["Block", "block"]
+};
+
+// Invalid values for the FontFace descriptor attributes other than family.
+var invalidValues = {
+ style: "initial",
+ weight: "bolder",
+ stretch: "wider",
+ unicodeRange: "U+1????-2????",
+ variant: "inherit",
+ featureSettings: "dlig",
+ display: "normal"
+};
+
+// Invalid font family names.
+var invalidFontFamilyNames = [
+ "", "sans-serif", "A, B", "inherit", "a 1"
+];
+
+// Font family list where at least one is likely to be available on
+// platforms we care about.
+var likelyPlatformFonts = "Helvetica Neue, Bitstream Vera Sans, Bitstream Vera Sans Roman, FreeSans, Free Sans, SwissA, DejaVu Sans, Arial";
+
+// Will hold an ArrayBuffer containing a valid font.
+var fontData;
+
+var queue = Promise.resolve();
+
+function is_resolved_with(aPromise, aExpectedValue, aDescription, aTestID) {
+ // This assumes that all Promise tasks come from the task source.
+ var handled = false;
+ return new Promise(function(aResolve, aReject) {
+ aPromise.then(function(aValue) {
+ if (!handled) {
+ handled = true;
+ is(aValue, aExpectedValue, aDescription + " should be resolved with the expected value " + aTestID);
+ aResolve();
+ }
+ }, function(aError) {
+ if (!handled) {
+ handled = true;
+ ok(false, aDescription + " should be resolved; instead it was rejected with " + aError + " " + aTestID);
+ aResolve();
+ }
+ });
+ Promise.resolve().then(function() {
+ if (!handled) {
+ handled = true;
+ ok(false, aDescription + " should be resolved; instead it is pending " + aTestID);
+ aResolve();
+ }
+ });
+ });
+}
+
+function is_pending(aPromise, aDescription, aTestID) {
+ // This assumes that all Promise tasks come from the task source.
+ var handled = false;
+ return new Promise(function(aResolve, aReject) {
+ aPromise.then(function(aValue) {
+ if (!handled) {
+ handled = true;
+ ok(false, aDescription + " should be pending; instead it was resolved with " + aValue + " " + aTestID);
+ aResolve();
+ }
+ }, function(aError) {
+ if (!handled) {
+ handled = true;
+ ok(false, aDescription + " should be pending; instead it was rejected with " + aError + " " + aTestID);
+ aResolve();
+ }
+ });
+ Promise.resolve().then(function() {
+ if (!handled) {
+ handled = true;
+ ok(true, aDescription + " should be pending " + aTestID);
+ aResolve();
+ }
+ });
+ });
+}
+
+function fetchAsArrayBuffer(aURL) {
+ return new Promise(function(aResolve, aReject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aURL);
+ xhr.responseType = "arraybuffer";
+ xhr.onreadystatechange = function(evt) {
+ if (xhr.readyState == 4) {
+ if (xhr.status >= 200 && xhr.status <= 299) {
+ aResolve(xhr.response);
+ } else {
+ aReject(new Error("Error fetching file " + aURL + ", status " + xhr.status));
+ }
+ }
+ };
+ xhr.send();
+ });
+}
+
+function setTimeoutZero() {
+ return new Promise(function(aResolve, aReject) {
+ setTimeout(aResolve, 0);
+ });
+}
+
+function awaitRefresh() {
+ function awaitOneRefresh() {
+ return new Promise(function(aResolve, aReject) {
+ requestAnimationFrame(aResolve);
+ });
+ }
+
+ return awaitOneRefresh().then(awaitOneRefresh);
+}
+
+function flushStyles() {
+ getComputedStyle(document.body).width;
+}
+
+function runTest() {
+ // Document and window from inside the display:none iframe.
+ var nframe = document.getElementById("n");
+ var ndocument = nframe.contentDocument;
+ var nwindow = nframe.contentWindow;
+
+ // Document and window from inside the visible iframe.
+ var vframe = document.getElementById("v");
+ var vdocument = vframe.contentDocument;
+ var vwindow = vframe.contentWindow;
+
+ // For iterating over different combinations of documents and windows
+ // to test with.
+ var sources = [
+ { win: window, doc: document, what: "window/document" },
+ { win: vwindow, doc: vdocument, what: "vwindow/vdocument" },
+ { win: nwindow, doc: ndocument, what: "nwindow/ndocument" },
+ { win: window, doc: vdocument, what: "window/vdocument" },
+ { win: window, doc: ndocument, what: "window/ndocument" },
+ { win: vwindow, doc: document, what: "vwindow/document" },
+ { win: vwindow, doc: ndocument, what: "vwindow/ndocument" },
+ { win: nwindow, doc: document, what: "nwindow/document" },
+ { win: nwindow, doc: vdocument, what: "nwindow/vdocument" },
+ ];
+
+ var sourceDocuments = [
+ { doc: document, what: "document" },
+ { doc: vdocument, what: "vdocument" },
+ { doc: ndocument, what: "ndocument" },
+ ];
+
+ var sourceWindows = [
+ { win: window, what: "window" },
+ { win: vwindow, what: "vwindow" },
+ { win: nwindow, what: "nwindow" },
+ ];
+
+ queue = queue.then(function() {
+
+ // First, initialize fontData.
+ return fetchAsArrayBuffer("BitPattern.woff")
+ .then(function(aResult) { fontData = aResult; });
+
+ }).then(function() {
+
+ // (TEST 1) Some miscellaneous tests for FontFaceSet and FontFace.
+ ok(window.FontFaceSet, "FontFaceSet interface object should be present (TEST 1)");
+ is(Object.getPrototypeOf(FontFaceSet.prototype), EventTarget.prototype, "FontFaceSet should inherit from EventTarget (TEST 1)");
+ ok(document.fonts instanceof FontFaceSet, "document.fonts should be a a FontFaceSet (TEST 1)");
+ ok(window.FontFace, "FontFace interface object should be present (TEST 1)");
+ is(Object.getPrototypeOf(FontFace.prototype), Object.prototype, "FontFace should inherit from Object (TEST 1)");
+
+ // (TEST 2) Some miscellaneous tests for FontFaceSetLoadEvent.
+ ok(window.FontFaceSetLoadEvent, "FontFaceSetLoadEvent interface object should be present (TEST 2)");
+ is(Object.getPrototypeOf(FontFaceSetLoadEvent.prototype), Event.prototype, "FontFaceSetLoadEvent should inherit from Event (TEST 2)");
+
+ }).then(function() {
+
+ // (TEST 3) Test that document.fonts.ready is resolved with the
+ // document.fonts FontFaceSet.
+ var p = Promise.resolve();
+ sourceDocuments.forEach(function({ doc, what }) {
+ p = p.then(_ => { return doc.fonts.ready }).then(function() {
+ return is_resolved_with(doc.fonts.ready, doc.fonts, "document.fonts.ready resolves with document.fonts.", "(TEST 3) (" + what + ")");
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 4) Test that document.fonts in this test document starts out with no
+ // FontFace objects in it.
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(Array.from(doc.fonts).length, 0, "initial number of FontFace objects in document.fonts (TEST 4) (" + what + ")");
+ });
+
+ // (TEST 5) Test that document.fonts.status starts off as loaded.
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(doc.fonts.status, "loaded", "initial value of document.fonts.status (TEST 5) (" + what + ")");
+ });
+
+ // (TEST 6) Test initial value of FontFace.status when a url() source is
+ // used.
+ sourceWindows.forEach(function({ win, what }) {
+ is(new win.FontFace("test", "url(x)").status, "unloaded", "initial value of FontFace.status when a url() source is used (TEST 6) (" + what + ")");
+ });
+
+ // (TEST 7) Test initial value of FontFace.status when an invalid
+ // ArrayBuffer source is used. Because it has an implicit initial
+ // load() call, it should either be "loading" if the browser is
+ // asynchronously parsing the font data, or "error" if it parsed
+ // it immediately.
+ sourceWindows.forEach(function({ win, what }) {
+ var status = new win.FontFace("test", new ArrayBuffer(0)).status;
+ ok(status == "loading" || status == "error", "initial value of FontFace.status when an invalid ArrayBuffer source is used (TEST 7) (" + what + ")");
+ });
+
+ // (TEST 8) Test initial value of FontFace.status when a valid ArrayBuffer
+ // source is used. Because it has an implicit initial load() call, it
+ // should either be "loading" if the browser is asynchronously parsing the
+ // font data, or "loaded" if it parsed it immediately.
+ sourceWindows.forEach(function({ win, what }) {
+ status = new win.FontFace("test", fontData).status;
+ ok(status == "loading" || status == "loaded", "initial value of FontFace.status when a valid ArrayBuffer source is used (TEST 8) (" + what + ")");
+ });
+
+ // (TEST 9) (old test became redundant with TEST 19)
+
+ }).then(function() {
+
+ // (TEST 10) Test initial value of FontFace.loaded when a valid url()
+ // source is used.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ return is_pending(new win.FontFace("test", "url(x)").loaded, "initial value of FontFace.loaded when a valid url() source is used", "(TEST 10) (" + what + ")");
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 11) (old test became redundant with TEST 21)
+
+ }).then(function() {
+
+ // (TEST 12) (old test became redundant with TEST 20)
+
+ }).then(function() {
+
+ // (TEST 13) Test initial values of the descriptor attributes on FontFace
+ // objects.
+ sourceWindows.forEach(function({ win, what }) {
+ var face = new win.FontFace("test", fontData);
+ // XXX Spec issue: what values do the descriptor attributes have before the
+ // constructor's dictionary argument is parsed?
+ for (var desc in defaultValues) {
+ is(face[desc], defaultValues[desc], "initial value of FontFace." + desc + " (TEST 13) (" + what + ")");
+ }
+ });
+
+ // (TEST 14) Test default values of the FontFaceDescriptors dictionary.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var face = new win.FontFace("test", fontData);
+ return face.loaded.then(function() {
+ for (var desc in defaultValues) {
+ is(face[desc], defaultValues[desc], "default value of FontFace." + desc + " (TEST 14) (" + what + ")");
+ }
+ }, function(aError) {
+ ok(false, "FontFace should have loaded succesfully (TEST 14) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 15) Test passing non-default descriptor values to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var descriptorTests = Promise.resolve();
+ Object.keys(nonDefaultValues).forEach(function(aDesc) {
+ descriptorTests = descriptorTests.then(function() {
+ var init = {};
+ init[aDesc] = nonDefaultValues[aDesc][0];
+ var face = new win.FontFace("test", fontData, init);
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " immediately after construction (TEST 15) (" + what + ")");
+ return face.loaded.then(function() {
+ ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " (TEST 15) (" + what + ")");
+ }, function(aError) {
+ ok(false, "FontFace should have loaded succesfully (TEST 15) (" + what + ")");
+ });
+ });
+ });
+ return descriptorTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 16) Test passing invalid descriptor values to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var descriptorTests = Promise.resolve();
+ Object.keys(invalidValues).forEach(function(aDesc) {
+ descriptorTests = descriptorTests.then(function() {
+ var init = {};
+ init[aDesc] = invalidValues[aDesc];
+ var face = new win.FontFace("test", fontData, init);
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ ok_todo(face.status == "error", "FontFace should be error immediately after construction with invalid value of FontFace." + aDesc + " (TEST 16) (" + what + ")");
+ return face.loaded.then(function() {
+ ok_todo(false, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")");
+ }, function(aError) {
+ ok(true, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")");
+ is(aError.name, "SyntaxError", "FontFace.loaded with invalid value of FontFace." + aDesc + " should be rejected with a SyntaxError (TEST 16) (" + what + ")");
+ });
+ });
+ });
+ return descriptorTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 17) Test passing an invalid font family name to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var familyTests = Promise.resolve();
+ invalidFontFamilyNames.forEach(function(aFamilyName) {
+ familyTests = familyTests.then(function() {
+ var face = new win.FontFace(aFamilyName, fontData);
+ is(face.status, "error", "FontFace should be error immediately after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")");
+ is(face.family, "", "FontFace.family should be the empty string after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")");
+ return face.loaded.then(function() {
+ ok(false, "FontFace should not load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")");
+ }, function(aError) {
+ ok(true, "FontFace should not load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")");
+ is(aError.name, "SyntaxError", "FontFace.loaded with invalid family name " + aFamilyName + " should be rejected with a SyntaxError (TEST 17) (" + what + ")");
+ });
+ });
+ });
+ return familyTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 18) Test passing valid url() source strings to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+
+ // The sub-test is very fragile on Android platform, see Bug 1455824,
+ // especially Comment 34.
+ if (navigator.appVersion.includes("Android")) {
+ return p;
+ }
+
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var srcTests = Promise.resolve();
+ gCSSFontFaceDescriptors.src.values.forEach(function(aSrc) {
+ srcTests = srcTests.then(function() {
+ var face = new win.FontFace("test", aSrc);
+ return face.load().then(function() {
+ ok(true, "FontFace should load with valid url() src " + aSrc + " (TEST 18) (" + what + ")");
+ }, function(aError) {
+ is(aError.name, "NetworkError", "FontFace had NetworkError when loading with valid url() src " + aSrc + " (TEST 18) (" + what + ")");
+ });
+ });
+ });
+ return srcTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 19) Test passing invalid url() source strings to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var srcTests = Promise.resolve();
+ gCSSFontFaceDescriptors.src.invalid_values.forEach(function(aSrc) {
+ srcTests = srcTests.then(function() {
+ var face = new win.FontFace("test", aSrc);
+ is(face.status, "error", "FontFace.status should be \"error\" when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")");
+ return face.loaded.then(function() {
+ ok(false, "FontFace should not load with invalid url() src " + aSrc + " (TEST 19) (" + what + ")");
+ }, function(aError) {
+ is(aError.name, "SyntaxError", "FontFace.ready should have been rejected with a SyntaxError when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")");
+ });
+ });
+ });
+ return srcTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 20) Test that the status of a FontFace constructed with a valid
+ // ArrayBuffer source eventually becomes "loaded".
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var face = new win.FontFace("test", fontData);
+ return face.loaded.then(function(aFace) {
+ is(face.status, "loaded", "status of FontFace constructed with a valid ArrayBuffer source should eventually be \"loaded\" (TEST 20) (" + what + ")");
+ is(face, aFace, "FontFace.loaded was resolved with the FontFace object once loaded (TEST 20) (" + what + ")");
+ }, function(aError) {
+ ok(false, "FontFace constructed with a valid ArrayBuffer should eventually load (TEST 20) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 21) Test that the status of a FontFace constructed with an invalid
+ // ArrayBuffer source eventually becomes "error".
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var face = new win.FontFace("test", new ArrayBuffer(0));
+ return face.loaded.then(function() {
+ ok(false, "FontFace constructed with an invalid ArrayBuffer should not load (TEST 21) (" + what + ")");
+ }, function(aError) {
+ is(aError.name, "SyntaxError", "loaded of FontFace constructed with an invalid ArrayBuffer source should be rejected with TypeError (TEST 21) (" + what + ")");
+ is(face.status, "error", "status of FontFace constructed with an invalid ArrayBuffer source should eventually be \"error\" (TEST 21) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 22) Test assigning non-default descriptor values on the FontFace.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var descriptorTests = Promise.resolve();
+ Object.keys(nonDefaultValues).forEach(function(aDesc) {
+ descriptorTests = descriptorTests.then(function() {
+ var face = new win.FontFace("test", fontData);
+ return face.loaded.then(function() {
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ face[aDesc] = nonDefaultValues[aDesc][0];
+ ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "assigned valid non-default value to FontFace." + aDesc + " (TEST 22) (" + what + ")");
+ }, function(aError) {
+ ok(false, "FontFace should have loaded succesfully (TEST 22) (" + what + ")");
+ });
+ });
+ });
+ return descriptorTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 23) Test assigning invalid descriptor values on the FontFace.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var descriptorTests = Promise.resolve();
+ Object.keys(invalidValues).forEach(function(aDesc) {
+ descriptorTests = descriptorTests.then(function() {
+ var face = new win.FontFace("test", fontData);
+ return face.loaded.then(function() {
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ var exceptionName = "";
+ try {
+ face[aDesc] = invalidValues[aDesc];
+ } catch (ex) {
+ exceptionName = ex.name;
+ }
+ ok_todo(exceptionName == "SyntaxError", "assigning invalid value to FontFace." + aDesc + " should throw a SyntaxError (TEST 23) (" + what + ")");
+ }, function(aError) {
+ ok(false, "FontFace should have loaded succesfully (TEST 23) (" + what + ")");
+ });
+ });
+ });
+ return descriptorTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 24) Test that the status of a FontFace with a non-existing url()
+ // source is set to "loading" right after load() is called, that its .loaded
+ // Promise is returned, and that the Promise is eventually rejected with a
+ // NetworkError and its status is set to "error".
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var face = new win.FontFace("test", "url(x)");
+ var result = face.load();
+ is(face.status, "loading", "FontFace.status should be \"loading\" right after load() is called (TEST 24) (" + what + ")");
+ is(result, face.loaded, "FontFace.load() should return the .loaded Promise (TEST 24) (" + what + ")");
+
+ return result.then(function() {
+ ok(false, "FontFace with a non-existing url() source should not load (TEST 24) (" + what + ")");
+ }, function(aError) {
+ is(aError.name, "NetworkError", "FontFace with a non-existing url() source should result in its .loaded Promise being rejected with a NetworkError (TEST 24) (" + what + ")");
+ is(face.status, "error", "FontFace with a non-existing url() source should result in its .status being set to \"error\" (TEST 24) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 25) Test simple manipulation of the FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var face, face2, all;
+ face = new win.FontFace("test", "url(x)");
+ face2 = new win.FontFace("test2", "url(x)");
+ ok(!doc.fonts.has(face), "newly created FontFace should not be in document.fonts (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ ok(doc.fonts.has(face), "should be able to add a FontFace to document.fonts (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ ok(doc.fonts.has(face), "should be able to repeatedly add a FontFace to document.fonts (TEST 25) (" + what + ")");
+ ok(doc.fonts.delete(face), "FontFaceSet.delete should return true when it succeeds (TEST 25) (" + what + ")");
+ ok(!doc.fonts.has(face), "FontFace should be gone from document.fonts after delete is called (TEST 25) (" + what + ")");
+ ok(!doc.fonts.delete(face), "FontFaceSet.delete should return false when it fails (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ doc.fonts.add(face2);
+ ok(doc.fonts.has(face2), "should be able to add a second FontFace to document.fonts (TEST 25) (" + what + ")");
+ doc.fonts.clear();
+ ok(!doc.fonts.has(face) && !doc.fonts.has(face2), "FontFaces should be gone from document.fonts after clear is called (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ doc.fonts.add(face2);
+ all = Array.from(doc.fonts);
+ is(all[0], face, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")");
+ is(all[1], face2, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ all = Array.from(doc.fonts);
+ is(all[0], face, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")");
+ is(all[1], face2, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")");
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 26) Test that FontFaceSet.ready is replaced, .status is set to
+ // "loading", and a loading event is dispatched when a loading FontFace is
+ // added to it.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingTriggered = false, loadingDispatched = false;
+
+ function check() {
+ if (onloadingTriggered && loadingDispatched) {
+ doc.fonts.onloading = null;
+ doc.fonts.removeEventListener("loading", listener);
+ aResolve();
+ }
+ }
+
+ var listener = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")");
+ loadingDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loading", listener);
+ doc.fonts.onloading = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")");
+ onloadingTriggered = true;
+ check();
+ };
+ });
+
+ is(doc.fonts.status, "loaded", "FontFaceSet.status initially (TEST 26) (" + what + ")");
+
+ var oldReady = doc.fonts.ready;
+ var face = new win.FontFace("test", "url(neverending_font_load.sjs)");
+ face.load();
+ doc.fonts.add(face);
+
+ var newReady = doc.fonts.ready;
+ isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when a loading FontFace is added to it (TEST 26) (" + what + ")");
+ is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when a loading FontFace is added to it (TEST 26) (" + what + ")");
+
+ return awaitEvents
+ .then(function() {
+ return is_pending(newReady, "FontFaceSet.ready should be replaced with a fresh pending Promise when a loading FontFace is added to it", "(TEST 26) (" + what + ")");
+ })
+ .then(function() {
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 27) Test that FontFaceSet.ready is resolved, .status is set to
+ // "loaded", and a loadingdone event (but no loadingerror event) is
+ // dispatched when the only loading FontFace in it is removed.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingdoneTriggered = false, loadingdoneDispatched = false;
+ var onloadingerrorTriggered = false, loadingerrorDispatched = false;
+
+ function check() {
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.removeEventListener("loadingdone", doneListener);
+ doc.fonts.removeEventListener("loadingerror", errorListener);
+ aResolve();
+ }
+
+ var doneListener = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")");
+ is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")");
+ loadingdoneDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loadingdone", doneListener);
+ doc.fonts.onloadingdone = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")");
+ is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")");
+ onloadingdoneTriggered = true;
+ check();
+ };
+ var errorListener = function(aEvent) {
+ loadingerrorDispatched = true;
+ check();
+ }
+ doc.fonts.addEventListener("loadingerror", errorListener);
+ doc.fonts.onloadingerror = function(aEvent) {
+ onloadingdoneTriggered = true;
+ check();
+ };
+ });
+
+ is(doc.fonts.status, "loaded", "FontFaceSet.status should be \"loaded\" initially (TEST 27) (" + what + ")");
+
+ var f = new win.FontFace("test", "url(neverending_font_load.sjs)");
+ f.load();
+ doc.fonts.add(f);
+
+ is(doc.fonts.status, "loading", "FontFaceSet.status should be \"loading\" when a loading FontFace is in it (TEST 27) (" + what + ")");
+
+ doc.fonts.clear();
+
+ return awaitEvents
+ .then(function() {
+ return is_resolved_with(doc.fonts.ready, doc.fonts, "FontFaceSet.ready when the FontFaceSet is cleared", "(TEST 27) (" + what + ")");
+ })
+ .then(function() {
+ is(doc.fonts.status, "loaded", "FontFaceSet.status should be set to \"loaded\" when it is cleared (TEST 27) (" + what + ")");
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 28) Test that FontFaceSet.ready is replaced, .status is set to
+ // "loading", and a loading event is dispatched when a FontFace in it
+ // starts loading.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingTriggered = false, loadingDispatched = false;
+
+ function check() {
+ if (onloadingTriggered && loadingDispatched) {
+ doc.fonts.onloading = null;
+ doc.fonts.removeEventListener("loading", listener);
+ aResolve();
+ }
+ }
+
+ var listener = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")");
+ loadingDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loading", listener);
+ doc.fonts.onloading = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")");
+ onloadingTriggered = true;
+ check();
+ };
+ });
+
+ var oldReady = doc.fonts.ready;
+ var face = new win.FontFace("test", "url(neverending_font_load.sjs)");
+ doc.fonts.add(face);
+ face.load();
+
+ var newReady = doc.fonts.ready;
+ isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when its only FontFace starts loading (TEST 28) (" + what + ")");
+ is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when its only FontFace starts loading (TEST 28) (" + what + ")");
+
+ return awaitEvents
+ .then(function() {
+ return is_pending(newReady, "FontFaceSet.ready when the FontFaceSet's only FontFace starts loading", "(TEST 28) (" + what + ")");
+ })
+ .then(function() {
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 29) Test that a loadingdone and a loadingerror event is dispatched
+ // when a FontFace that eventually becomes status "error" is added to the
+ // FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var face;
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingdoneTriggered = false, loadingdoneDispatched = false;
+ var onloadingerrorTriggered = false, loadingerrorDispatched = false;
+
+ function check() {
+ if (onloadingdoneTriggered && loadingdoneDispatched &&
+ onloadingerrorTriggered && loadingerrorDispatched) {
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.removeEventListener("loadingdone", doneListener);
+ doc.fonts.removeEventListener("loadingerror", errorListener);
+ aResolve();
+ }
+ }
+
+ var doneListener = function(aEvent) {
+ loadingdoneDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loadingdone", doneListener);
+ doc.fonts.onloadingdone = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")");
+ is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 29) (" + what + ")");
+ onloadingdoneTriggered = true;
+ check();
+ };
+ var errorListener = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingerror event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")");
+ is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 29) (" + what + ")");
+ loadingerrorDispatched = true;
+ check();
+ }
+ doc.fonts.addEventListener("loadingerror", errorListener);
+ doc.fonts.onloadingerror = function(aEvent) {
+ onloadingerrorTriggered = true;
+ check();
+ };
+ });
+
+ face = new win.FontFace("test", "url(x)");
+ face.load();
+ is(face.status, "loading", "FontFace should have status \"loading\" (TEST 29) (" + what + ")");
+ doc.fonts.add(face);
+
+ return face.loaded
+ .then(function() {
+ ok(false, "the FontFace should not load (TEST 29) (" + what + ")");
+ }, function(aError) {
+ is(face.status, "error", "FontFace should have status \"error\" (TEST 29) (" + what + ")");
+ return awaitEvents;
+ })
+ .then(function() {
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 30) Test that a loadingdone event is dispatched when a FontFace
+ // that eventually becomes status "loaded" is added to the FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }, i) {
+ p = p.then(function() {
+ var face;
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingdoneTriggered = false, loadingdoneDispatched = false;
+
+ function check() {
+ if (onloadingdoneTriggered && loadingdoneDispatched) {
+ doc.fonts.onloadingdone = null;
+ doc.fonts.removeEventListener("loadingdone", doneListener);
+ aResolve();
+ }
+ }
+
+ var doneListener = function(aEvent) {
+ loadingdoneDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loadingdone", doneListener);
+ doc.fonts.onloadingdone = function(aEvent) {
+ is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 30) (" + what + ")");
+ onloadingdoneTriggered = true;
+ check();
+ };
+ });
+
+ face = new win.FontFace("test", "url(BitPattern.woff?test30." + i + ")");
+ face.load();
+ is(face.status, "loading", "FontFace should have status \"loading\" (TEST 30) (" + what + ")");
+ doc.fonts.add(face);
+
+ return face.loaded
+ .then(function() {
+ is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 30) (" + what + ")");
+ return awaitEvents;
+ })
+ .then(function() {
+ doc.fonts.clear();
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 31) Test that a loadingdone event is dispatched when a FontFace
+ // with status "unloaded" is added to the FontFaceSet and load() is called
+ // on it.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }, i) {
+ p = p.then(function() {
+ var face;
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingdoneTriggered = false, loadingdoneDispatched = false;
+
+ function check() {
+ if (onloadingdoneTriggered && loadingdoneDispatched) {
+ doc.fonts.onloadingdone = null;
+ doc.fonts.removeEventListener("loadingdone", doneListener);
+ aResolve();
+ }
+ }
+
+ var doneListener = function(aEvent) {
+ loadingdoneDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loadingdone", doneListener);
+ doc.fonts.onloadingdone = function(aEvent) {
+ is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 31) (" + what + ")");
+ onloadingdoneTriggered = true;
+ check();
+ };
+ });
+
+ face = new win.FontFace("test", "url(BitPattern.woff?test31." + i + ")");
+ is(face.status, "unloaded", "FontFace should have status \"unloaded\" (TEST 31) (" + what + ")");
+ doc.fonts.add(face);
+
+ return face.load()
+ .then(function() {
+ return awaitEvents;
+ })
+ .then(function() {
+ is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 31) (" + what + ")");
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 32) Test that pending restyles prevent document.fonts.status
+ // from becoming loaded.
+ var face = new FontFace("test", "url(neverending_font_load.sjs)");
+ face.load();
+ document.fonts.add(face);
+
+ is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace (TEST 32)");
+
+ document.fonts.clear();
+ flushStyles();
+
+ is(document.fonts.status, "loaded", "FontFaceSet.status after clearing (TEST 32)");
+
+ document.fonts.add(face);
+
+ is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace again (TEST 32)");
+
+ var div = document.querySelector("div");
+ div.style.color = "blue";
+
+ document.fonts.clear();
+ is(document.fonts.status, "loading", "FontFaceSet.status after clearing but when there is a pending restyle (TEST 32)");
+
+ return awaitRefresh() // wait for a refresh driver tick
+ .then(function() {
+ is(document.fonts.status, "loaded", "FontFaceSet.status after clearing and the restyle has been flushed (TEST 32)");
+ return document.fonts.ready;
+ });
+
+ }).then(function() {
+
+ // (TEST 33) Test that CSS-connected FontFace objects are created
+ // for @font-face rules in the document.
+
+ is(document.fonts.status, "loaded", "document.fonts.status should initially be loaded (TEST 33)");
+
+ var style = document.querySelector("style");
+ var ruleText = "@font-face { font-family: something; src: url(x); ";
+ Object.keys(nonDefaultValues).forEach(function(aDesc) {
+ ruleText += descriptorNames[aDesc] + ": " + nonDefaultValues[aDesc][0] + "; ";
+ });
+ ruleText += "}";
+
+ style.textContent = ruleText;
+
+ var rule = style.sheet.cssRules[0];
+
+ var all = Array.from(document.fonts);
+ is(all.length, 1, "document.fonts should contain one FontFace (TEST 33)");
+
+ var face = all[0];
+ is(face.family, "something", "FontFace should have correct family value (TEST 33)");
+ Object.keys(nonDefaultValues).forEach(function(aDesc) {
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "FontFace should have correct " + aDesc + " value (TEST 33)");
+ });
+
+ is(document.fonts.status, "loaded", "document.fonts.status should still be loaded (TEST 33)");
+ is(face.status, "unloaded", "FontFace.status should be unloaded (TEST 33)");
+
+ document.fonts.clear();
+ ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when clear is called (TEST 33)");
+
+ is(document.fonts.delete(face), false, "attempting to remove CSS-connected FontFace from document.fonts should return false (TEST 33)");
+ ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when delete is called (TEST 33)");
+
+ style.textContent = "";
+
+ ok(!document.fonts.has(face), "CSS-connected FontFace should be removed from document.fonts once the rule has been removed (TEST 33)");
+
+ is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after rule is removed (TEST 33)");
+ is(face.status, "unloaded", "FontFace.status should still be unloaded after rule is removed (TEST 33)");
+
+ document.fonts.add(face);
+ ok(document.fonts.has(face), "previously CSS-connected FontFace should be able to be added to document.fonts (TEST 33)");
+
+ is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after now disconnected FontFace is added (TEST 33)");
+ is(face.status, "unloaded", "FontFace.status should still be unloaded after now disconnected FontFace is added (TEST 33)");
+
+ document.fonts.delete(face);
+ ok(!document.fonts.has(face), "previously CSS-connected FontFace should be able to be removed from document.fonts (TEST 33)");
+
+ }).then(function() {
+
+ // (TEST 34) Test that descriptor getters for unspecified descriptors on
+ // CSS-connected FontFace objects return their default values.
+ var style = document.querySelector("style");
+ var ruleText = "@font-face { font-family: something; src: url(x); }";
+
+ style.textContent = ruleText;
+
+ var all = Array.from(document.fonts);
+ var face = all[0];
+
+ Object.keys(defaultValues).forEach(function(aDesc) {
+ is(face[aDesc], defaultValues[aDesc], "FontFace should return default value for " + aDesc + " (TEST 34)");
+ });
+
+ style.textContent = "";
+
+ }).then(function() {
+
+ // (TEST 35) Test that no loadingdone event is dispatched when a FontFace
+ // with "loaded" status is added to a "loaded" FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var gotLoadingDone = false;
+ doc.fonts.onloadingdone = function(aEvent) {
+ gotLoadingDone = true;
+ };
+
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 35) (" + what + ")");
+ var face = new win.FontFace("test", fontData);
+
+ return face.loaded
+ .then(function() {
+ is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 35) (" + what + ")");
+ doc.fonts.add(face);
+ is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 35) (" + what + ")");
+ return doc.fonts.ready;
+ })
+ .then(function() {
+ ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 35) (" + what + ")");
+ doc.fonts.onloadingdone = null;
+ doc.fonts.clear();
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 36) Test that no loadingdone or loadingerror event is dispatched
+ // when a FontFace with "error" status is added to a "loaded" FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ var doc = win.document;
+ p = p.then(function() {
+ var gotLoadingDone = false, gotLoadingError = false;
+ doc.fonts.onloadingdone = function(aEvent) {
+ gotLoadingDone = true;
+ };
+ doc.fonts.onloadingerror = function(aEvent) {
+ gotLoadingError = true;
+ };
+
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 36) (" + what + ")");
+ var face = new win.FontFace("test", new ArrayBuffer(0));
+
+ return face.loaded
+ .then(function() {
+ ok(false, "FontFace should not have loaded (TEST 36) (" + what + ")");
+ }, function() {
+ is(face.status, "error", "FontFace should have status \"error\" (TEST 36) (" + what + ")");
+ doc.fonts.add(face);
+ is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 36) (" + what + ")");
+ return doc.fonts.ready;
+ })
+ .then(function() {
+ ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 36) (" + what + ")");
+ ok(!gotLoadingError, "loadingerror event should not be dispatched (TEST 36) (" + what + ")");
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.clear();
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 37) Test that a FontFace only has one loadingdone event dispatched
+ // at the FontFaceSet containing it.
+
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what}, i) {
+ p = p.then(function() {
+ return setTimeoutZero(); // wait for any previous events to be dispatched
+ }).then(function() {
+ var events = [], face, face2;
+
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+ doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) {
+ events.push(e);
+ if (events.length == 2) {
+ aResolve();
+ }
+ };
+ });
+
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 37) (" + what + ")");
+
+ face = new win.FontFace("test", "url(BitPattern.woff?test37." + i + "a)");
+ face.load();
+ doc.fonts.add(face);
+ is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 37) (" + what + ")");
+
+ return doc.fonts.ready
+ .then(function() {
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 37) (" + what + ")");
+ is(face.status, "loaded", "first FontFace should have status \"loaded\" (TEST 37) (" + what + ")");
+
+ face2 = new win.FontFace("test2", "url(BitPattern.woff?test37." + i + "b)");
+ face2.load();
+ doc.fonts.add(face2);
+ is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 37) (" + what + ")");
+
+ return doc.fonts.ready;
+ }).then(function() {
+ return awaitEvents;
+ }).then(function() {
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 37) (" + what + ")");
+ is(face2.status, "loaded", "second FontFace should have status \"loaded\" (TEST 37) (" + what + ")");
+
+ is(events.length, 2, "should receive two events (TEST 37) (" + what + ")");
+
+ is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 37) (" + what + ")");
+ is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 37) (" + what + ")");
+ is(events[0].fontfaces[0], face, "first event should have the first FontFace");
+
+ is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 37) (" + what + ")");
+ is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 37) (" + what + ")");
+ is(events[1].fontfaces[0], face2, "second event should have the second FontFace (TEST 37) (" + what + ")");
+
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 38) Test that a FontFace only has one loadingerror event dispatched
+ // at the FontFaceSet containing it.
+
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ return setTimeoutZero(); // wait for any previous events to be dispatched
+ }).then(function() {
+ var events = [], face, face2;
+
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+ doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) {
+ events.push(e);
+ if (events.length == 4) {
+ aResolve();
+ }
+ };
+ });
+
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 38) (" + what + ")");
+
+ face = new win.FontFace("test", "url(x)");
+ face.load();
+ doc.fonts.add(face);
+ is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 38) (" + what + ")");
+
+ return doc.fonts.ready
+ .then(function() {
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font failed to load (TEST 38) (" + what + ")");
+ is(face.status, "error", "first FontFace should have status \"error\" (TEST 38) (" + what + ")");
+
+ face2 = new win.FontFace("test2", "url(x)");
+ face2.load();
+ doc.fonts.add(face2);
+ is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 38) (" + what + ")");
+
+ return doc.fonts.ready;
+ }).then(function() {
+ return awaitEvents;
+ }).then(function() {
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font failed to load (TEST 38) (" + what + ")");
+ is(face2.status, "error", "second FontFace should have status \"error\" (TEST 38) (" + what + ")");
+
+ is(events.length, 4, "should receive four events (TEST 38) (" + what + ")");
+
+ is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 38) (" + what + ")");
+ is(events[0].fontfaces.length, 0, "first event should have no FontFaces (TEST 38) (" + what + ")");
+
+ is(events[1].type, "loadingerror", "second event should be \"loadingerror\" (TEST 38) (" + what + ")");
+ is(events[1].fontfaces.length, 1, "second event should have 1 FontFace (TEST 38) (" + what + ")");
+ is(events[1].fontfaces[0], face, "second event should have the first FontFace");
+
+ is(events[2].type, "loadingdone", "third event should be \"loadingdone\" (TEST 38) (" + what + ")");
+ is(events[2].fontfaces.length, 0, "third event should have no FontFaces (TEST 38) (" + what + ")");
+
+ is(events[3].type, "loadingerror", "third event should be \"loadingerror\" (TEST 38) (" + what + ")");
+ is(events[3].fontfaces.length, 1, "third event should only have 1 FontFace (TEST 38) (" + what + ")");
+ is(events[3].fontfaces[0], face2, "third event should have the second FontFace");
+
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 39) Test that a FontFace for an @font-face rule only has one
+ // loadingdone event dispatched at the FontFaceSet containing it.
+
+ var style, all, events, awaitEvents;
+
+ return setTimeoutZero() // wait for any previous events to be dispatched
+ .then(function() {
+ style = document.querySelector("style");
+ var ruleText = "@font-face { font-family: test; src: url(BitPattern.woff?test39a); } " +
+ "@font-face { font-family: test2; src: url(BitPattern.woff?test39b); }";
+
+ style.textContent = ruleText;
+
+ all = Array.from(document.fonts);
+ events = [];
+
+ awaitEvents = new Promise(function(aResolve, aReject) {
+ document.fonts.onloadingdone = document.fonts.onloadingerror = function(e) {
+ events.push(e);
+ if (events.length == 2) {
+ aResolve();
+ }
+ };
+ });
+
+ is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 39)");
+
+ all[0].load();
+ is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font loading (TEST 39)");
+
+ return document.fonts.ready
+ }).then(function() {
+ is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 39)");
+ is(all[0].status, "loaded", "first FontFace should have status \"loaded\" (TEST 39)");
+ is(all[1].status, "unloaded", "second FontFace should have status \"unloaded\" (TEST 39)");
+
+ all[1].load();
+ is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font loading (TEST 39)");
+
+ return document.fonts.ready;
+ }).then(function() {
+ return awaitEvents;
+ }).then(function() {
+ is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 39)");
+ is(all[1].status, "loaded", "second FontFace should have status \"loaded\" (TEST 39)");
+
+ is(events.length, 2, "should receive two events (TEST 39)");
+
+ is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 39)");
+ is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 39)");
+ is(events[0].fontfaces[0], all[0], "first event should have the first FontFace");
+
+ is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 39)");
+ is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 39)");
+ is(events[1].fontfaces[0], all[1], "second event should have the second FontFace (TEST 39)");
+
+ style.textContent = "";
+
+ document.fonts.onloadingdone = null;
+ document.fonts.onloadingerror = null;
+ document.fonts.clear();
+ return document.fonts.ready;
+ });
+
+ }).then(function() {
+
+ // (TEST 40) Test that an attempt to add the same FontFace object a second
+ // time to a FontFaceSet (where one of the FontFace objects is reflecting
+ // an @font-face rule) will be ignored.
+
+ // First set up a @font-face rule.
+ var style = document.querySelector("style");
+ style.textContent = "@font-face { font-family: something; src: url(x); }";
+
+ // Then add a couple of non-connected FontFace objects.
+ var f1 = new FontFace("test1", "url(x)");
+ var f2 = new FontFace("test2", "url(x)");
+
+ document.fonts.add(f1);
+ document.fonts.add(f2);
+
+ var all = Array.from(document.fonts);
+ var ruleFontFace = all[0];
+
+ is(all.length, 3, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 40)");
+ is(all[1], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)");
+ is(all[2], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)");
+
+ document.fonts.add(f1);
+
+ all = Array.from(document.fonts);
+ is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #1 (TEST 40)");
+ is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #1 (TEST 40)");
+ is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)");
+ is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)");
+
+ document.fonts.add(ruleFontFace);
+
+ all = Array.from(document.fonts);
+ is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #2 (TEST 40)");
+ is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #2 (TEST 40)");
+ is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)");
+ is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)");
+
+ style.textContent = "";
+
+ document.fonts.clear();
+
+ }).then(function() {
+
+ // (TEST 41) Test that an attempt to add the same FontFace object a second
+ // time to a FontFaceSet (where none of the FontFace objects are reflecting
+ // an @font-face rule) will be ignored.
+
+ sources.forEach(function({ win, doc, what }) {
+ // Add a couple of non-connected FontFace objects.
+ var f1 = new win.FontFace("test1", "url(x)");
+ var f2 = new win.FontFace("test2", "url(x)");
+
+ doc.fonts.add(f1);
+ doc.fonts.add(f2);
+
+ var all = Array.from(doc.fonts);
+
+ is(all.length, 2, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 41) (" + what + ")");
+ is(all[0], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")");
+ is(all[1], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")");
+
+ doc.fonts.add(f1);
+
+ all = Array.from(doc.fonts);
+ is(all.length, 2, "number of FontFace objects in the FontFaceSet after duplicate add (TEST 41) (" + what + ")");
+ is(all[0], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")");
+ is(all[1], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")");
+
+ doc.fonts.clear();
+ });
+
+ }).then(function() {
+
+ // (TEST 42) Test that adding a FontFace to multiple FontFaceSets and then
+ // loading it updates the status of all FontFaceSets.
+
+ var face = new FontFace("test", "url(x)");
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ doc.fonts.add(face);
+ });
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(doc.fonts.status, "loaded", what + ".fonts.status before loading (TEST 42)");
+ });
+
+ face.load();
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(doc.fonts.status, "loading", what + ".fonts.status after loading started (TEST 42)");
+ });
+
+ return Promise.all(sourceDocuments.map(function({ doc }) { return doc.fonts.ready; }))
+ .then(function() {
+ is(face.status, "error", "FontFace.status after loading finished (TEST 42)");
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(doc.fonts.status, "loaded", what + ".fonts.status after loading finished (TEST 42)");
+ });
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ doc.fonts.clear();
+ });
+ });
+
+ }).then(function() {
+
+ // (TEST 43) Test the check method with platform fonts and some
+ // degenerate cases.
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ // Invalid font shorthands should throw a SyntaxError.
+ try {
+ doc.fonts.check("Helvetica");
+ ok(false, "check should throw when a syntactically invalid font shorthand is given (TEST 43) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "SyntaxError", "exception name when check is called with a syntactically invalid font shorthand (TEST 43) (" + what + ")");
+ }
+
+ // System fonts should throw a SyntaxError.
+ try {
+ doc.fonts.check("caption");
+ ok(false, "check should throw when a system font value is given (TEST 43) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "SyntaxError", "exception name when check is called with a system font value (TEST 43) (" + what + ")");
+ }
+
+ // CSS-wide keywords should throw a SyntaxError.
+ try {
+ doc.fonts.check("inherit");
+ ok(false, "check should throw when a CSS-wide keyword is given (TEST 43) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "SyntaxError", "exception name when check is called with a CSS-wide keyword (TEST 43) (" + what + ")");
+ }
+
+ // CSS variables should throw a SyntaxError.
+ try {
+ doc.fonts.check("16px var(--family)");
+ ok(false, "check should throw when CSS variables are used (TEST 43) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "SyntaxError", "exception name when check is called with CSS variables (TEST 43) (" + what + ")");
+ }
+
+ // No matching font family names => return true.
+ is(doc.fonts.check("16px NonExistentFont1, NonExistentFont2"), true, "check return value when no matching font family names are used (TEST 43) (" + what + ")");
+
+ // Matching platform font family name => return true.
+ is(doc.fonts.check("16px NonExistentFont, " + likelyPlatformFonts), true, "check return value when a matching platform font family name is used (TEST 43) (" + what + ")");
+
+ // Matching platform font family name, but using a different test
+ // strings. (Platform fonts always return true from check, regardless
+ // of the actual glyphs present.)
+ [
+ { test: "\0", desc: "a single non-matching glyph" },
+ { test: "A\0", desc: "a matching and a non-matching glyph" },
+ { test: "A", desc: "a matching glyph" },
+ { test: "AB", desc: "multiple matching glyphs" }
+ ].forEach(function({ test, desc }) {
+ is(doc.fonts.check("16px " + likelyPlatformFonts, test), true, "check return value when a matching platform font family name is used but with " + desc + " (TEST 43) (" + what + ")");
+ });
+
+ // No matching font family name, but an empty test string.
+ is(doc.fonts.check("16px NonExistentFont", ""), true, "check return value with a non-matching font family name and an empty test string (TEST 43) (" + what + ")");
+
+ // Matching platform font family name, but empty test string.
+ is(doc.fonts.check("16px " + likelyPlatformFonts, ""), true, "check return value with an empty test string (TEST 43) (" + what + ")");
+ });
+
+ }).then(function() {
+
+ // (TEST 44) Test the check method with script-created FontFaces.
+
+ var tests = [
+ // at least one matching FontFace is not loaded ==> false
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }] },
+ { result: false, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded" }] },
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "loaded" }] },
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "loading" }] },
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "error" }] },
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic" }] },
+ { result: false, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold" }] },
+ { result: false, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] },
+ { result: false, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] },
+
+ // all matching FontFaces are loaded ==> true
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "loaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Irrelevant", status: "unloaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed" }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }, { family: "Test", status: "unloaded", weight: "600" }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "loaded" }] },
+
+ // no matching FontFaces at all ==> true
+ { result: true, font: "16px Test", faces: [] },
+ { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] },
+
+ // matching FontFace for one sample text character is loaded but
+ // not the other ==> false
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "unloaded", unicodeRange: "U+62" }] },
+
+ // matching FontFaces for separate sample text characters are all
+ // loaded ==> true
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "loaded", unicodeRange: "U+62" }] },
+ ];
+
+ sources.forEach(function({ win, doc, what }, i) {
+ tests.forEach(function({ result, font, faces }, j) {
+ faces.forEach(function(f, k) {
+ var fontFace;
+ if (f.status == "loaded") {
+ fontFace = new win.FontFace(f.family, fontData, f);
+ } else if (f.status == "error") {
+ fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f);
+ } else {
+ fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test44." + [i, j, k] + ")", f);
+ if (f.status == "loading") {
+ fontFace.load();
+ }
+ }
+ is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 44) (" + what + ")");
+ doc.fonts.add(fontFace);
+ });
+ is(doc.fonts.check(font, "ab"), result, "check return value for subtest " + j + " (TEST 44) (" + what + ")");
+ doc.fonts.clear();
+ });
+ });
+
+ }).then(function() {
+
+ // (TEST 45) Test the load method with platform fonts and some
+ // degenerate cases.
+
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ // Invalid font shorthands should reject the promise with a SyntaxError.
+ return doc.fonts.load("Helvetica").then(function() {
+ ok(false, "load should reject when a syntactically invalid font shorthand is given (TEST 45) (" + what + ")");
+ }, function(ex) {
+ is(ex.name, "SyntaxError", "exception name when load is called with a syntactically invalid font shorthand (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // System fonts should reject with a SyntaxError.
+ return doc.fonts.load("caption").then(function() {
+ ok(false, "load should throw when a system font value is given (TEST 45) (" + what + ")");
+ }, function(ex) {
+ is(ex.name, "SyntaxError", "exception name when load is called with a system font value (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // CSS-wide keywords should reject with a SyntaxError.
+ return doc.fonts.load("inherit").then(function() {
+ ok(false, "load should throw when a CSS-wide keyword is given (TEST 45) (" + what + ")");
+ }, function(ex) {
+ is(ex.name, "SyntaxError", "exception name when load is called with a CSS-wide keyword (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // CSS variables should throw a SyntaxError.
+ return doc.fonts.load("16px var(--family)").then(function() {
+ ok(false, "load should throw when CSS variables are used (TEST 45) (" + what + ")");
+ }, function(ex) {
+ is(ex.name, "SyntaxError", "exception name when load is called with CSS variables (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // No matching font family names => return true.
+ return doc.fonts.load("16px NonExistentFont1, NonExistentFont2").then(function(result) {
+ is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // Matching platform font family name => return true.
+ return doc.fonts.load("16px NonExistentFont1, " + likelyPlatformFonts).then(function(result) {
+ is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")");
+ });
+ });
+
+ // Matching platform font family name, but using a different test
+ // strings. (Platform fonts always return true from load, regardless
+ // of the actual glyphs present.)
+ [
+ { sample: "\0", desc: "a single non-matching glyph" },
+ { sample: "A\0", desc: "a matching and a non-matching glyph" },
+ { sample: "A", desc: "a matching glyph" },
+ { sample: "AB", desc: "multiple matching glyphs" }
+ ].forEach(function({ sample, desc }) {
+ p = p.then(function() {
+ return doc.fonts.load("16px " + likelyPlatformFonts, sample).then(function(result) {
+ is(result.length, 0, "load resolves with an empty array when a matching platform font family name is used but with " + desc + " (TEST 45) (" + what + ")");
+ });
+ });
+ });
+
+ p = p.then(function() {
+ // No matching font family name, but an empty test string.
+ return doc.fonts.load("16px NonExistentFont", "").then(function(result) {
+ is(result.length, 0, "load resolves with an empty array when a non-matching platform font family name and an empty test string is used (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // Matching font family name, but an empty test string.
+ return doc.fonts.load("16px " + likelyPlatformFonts, "").then(function(result) {
+ is(result.length, 0, "load resolves with an empty array when a matching platform font family name and an empty test string is used (TEST 45) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 46) Test the load method with script-created FontFaces.
+
+ var tests = [
+ // at least one matching FontFace is not yet loaded, but will load ==> resolve
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }, { family: "Test", status: "loaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loading", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic", included: true }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600", included: true }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold", included: true }] },
+ { result: true, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] },
+
+ // at least one matching FontFace is in an error state ==> reject
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "error" }] },
+
+ // all matching FontFaces are already loaded ==> resolve
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "loaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Irrelevant", status: "unloaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed", included: true }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }, { family: "Test", status: "loaded", weight: "600" }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "loaded", weight: "bold", included: true }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "loaded", included: true }] },
+
+ // no matching FontFaces at all ==> resolve
+ { result: true, font: "16px Test", faces: [] },
+ { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] },
+
+ // matching FontFace for one sample text character is already loaded but
+ // the other is not (but will) ==> resolve
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+62", included: true }] },
+
+ // matching FontFaces for separate sample text characters are all
+ // loaded ==> resolve
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "loaded", unicodeRange: "U+62", included: true }] },
+ ];
+
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }, i) {
+ tests.forEach(function({ result, font, faces }, j) {
+ p = p.then(function() {
+ var fontFaces = [];
+ faces.forEach(function(f, k) {
+ var fontFace;
+ if (f.status == "loaded") {
+ fontFace = new win.FontFace(f.family, fontData, f);
+ } else if (f.status == "error") {
+ fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f);
+ } else {
+ fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test46." + [i, j, k] + ")", f);
+ if (f.status == "loading") {
+ fontFace.load();
+ }
+ }
+ is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 46) (" + what + ")");
+ doc.fonts.add(fontFace);
+ fontFaces.push(fontFace);
+ });
+ return doc.fonts.load(font, "ab").then(function(array) {
+ ok(result, "load should resolve for subtest " + j + " (TEST 46) (" + what + ")");
+ var expected = [];
+ for (var k = 0; k < faces.length; k++) {
+ if (faces[k].included) {
+ expected.push(fontFaces[k]);
+ }
+ }
+ is(array.length, expected.length, "length of array load resolves with for subtest " + j + " (TEST 46) (" + what + ")");
+ for (var k = 0; k < array.length; k++) {
+ is(array[k], expected[k], "value in array[" + k + "] load resolves with for subtest " + j + " (TEST 46) (" + what + ")");
+ }
+ }, function(ex) {
+ ok(!result, "load should not resolve for subtest " + j + " (TEST 46) (" + what + ")");
+ is(ex.name, "SyntaxError", "exception load's return value is rejected with for subtest " + j + " (TEST 46) (" + what + ")");
+ }).then(function() {
+ doc.fonts.clear();
+ });
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 47) Test that CSS-connected FontFaces can't be added to other
+ // FontFaceSets.
+
+ var style = document.querySelector("style");
+ style.textContent = "@font-face { font-family: something; src: url(x); }";
+
+ var rule = style.sheet.cssRules[0];
+
+ var all = Array.from(document.fonts);
+ is(all.length, 1, "document.fonts should contain one FontFace (TEST 47)");
+
+ var face = all[0];
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ if (doc == document) {
+ return;
+ }
+
+ var exceptionName;
+ try {
+ doc.fonts.add(face);
+ ok(false, "add should throw when attempting to add a CSS-connected FontFace to another FontFaceSet (TEST 47) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "InvalidModificationError", "exception name when add is called with a CSS-connected FontFace from another FontFaceSet (TEST 47) (" + what + ")");
+ }
+ });
+
+ style.textContent = "";
+ document.body.offsetTop;
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ if (doc == document) {
+ return;
+ }
+
+ ok(!doc.fonts.has(face), "FontFaceSet initially doesn't have the FontFace (TEST 47) (" + what + ")");
+ doc.fonts.add(face);
+ ok(doc.fonts.has(face), "add should allow a previously CSS-connected FontFace to be added to another FontFaceSet (TEST 47) (" + what + ")");
+ doc.fonts.clear();
+ });
+
+ document.fonts.clear();
+
+ }).then(function() {
+
+ // (TEST 48) Test that FontFaceSets that hold a combination of FontFaces
+ // from different documents expose the right set of FontFaces.
+
+ // Expected FontFaceSet contents.
+ var expected = {
+ document: [],
+ vdocument: [],
+ ndocument: [],
+ };
+
+ // Create a CSS-connected FontFace in the top-level document.
+ var style = document.querySelector("style");
+ style.textContent = "@font-face { font-family: something; src: url(x); }";
+
+ var all = Array.from(document.fonts);
+ is(all.length, 1, "document.fonts should contain one FontFace (TEST 48)");
+
+ all[0]._description = "CSS-connected in document";
+ expected.document.push(all[0]);
+
+ // Create a CSS-connected FontFace in the visible iframe.
+ var vstyle = vdocument.querySelector("style");
+ vstyle.textContent = "@font-face { font-family: somethingelse; src: url(x); }";
+
+ all = Array.from(vdocument.fonts);
+ all[0]._description = "CSS-connected in vdocument";
+ is(all.length, 1, "vdocument.fonts should contain one FontFace (TEST 48)");
+
+ expected.vdocument.push(all[0]);
+
+ // Create a FontFace in each window and add it to each document's FontFaceSet.
+ var faces = [];
+ sourceWindows.forEach(function({ win, what: whatWin }, index) {
+ var f = new win.FontFace("test" + index, "url(x)");
+ sourceDocuments.forEach(function({ doc, what: whatDoc }) {
+ doc.fonts.add(f);
+ expected[whatDoc].push(f);
+ f._description = whatWin + "/" + whatDoc;
+ });
+ });
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ let allFonts = Array.from(doc.fonts);
+ is(expected[what].length, allFonts.length, "expected FontFaceSet size (TEST 48) (" + what + ")");
+ for (let i = 0; i < expected[what].length; i++) {
+ is(expected[what][i], allFonts[i], "expected FontFace (" + expected[what][i]._description + ") at index " + i + " (TEST 48) (" + what + ")");
+ }
+ });
+
+ vstyle.textContent = "";
+ style.textContent = "";
+
+ sourceDocuments.forEach(function({ doc }) { doc.fonts.clear(); });
+
+ }).then(function() {
+
+ // (TEST LAST) Test that a pending style sheet load prevents
+ // document.fonts.status from being set to "loaded".
+
+ // First, add a FontFace to document.fonts that will load soon.
+ var face = new FontFace("test", "url(BitPattern.woff?testlast)");
+ face.load();
+ document.fonts.add(face);
+
+ // Next, add a style sheet reference.
+ var link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = "neverending_stylesheet_load.sjs";
+ link.type = "text/css";
+ document.head.appendChild(link);
+
+ return setTimeoutZero() // wait for the style sheet to start loading
+ .then(function() {
+ document.fonts.clear();
+ is(document.fonts.status, "loading", "FontFaceSet.status when the FontFaceSet has been cleared of loading FontFaces but there is a pending style sheet load (TEST LAST)");
+ document.head.removeChild(link);
+ // XXX Removing the <link> element won't cancel the load of the
+ // style sheet, so we can't do that to test that
+ // document.fonts.ready is resolved once there are no more
+ // loading style sheets.
+ });
+
+ // NOTE: It is important that this style sheet test comes last in the file,
+ // as the neverending style sheet load will interfere with subsequent
+ // sub-tests.
+
+ }).then(function() {
+
+ // End of the tests.
+ SimpleTest.finish();
+
+ }, function(aError) {
+
+ // Something failed.
+ ok(false, "Something failed: " + aError);
+ SimpleTest.finish();
+
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(5);
+
+</script>
+
+<style></style>
+<div></div>
diff --git a/layout/style/test/test_garbage_at_end_of_declarations.html b/layout/style/test/test_garbage_at_end_of_declarations.html
new file mode 100644
index 0000000000..95882d1591
--- /dev/null
+++ b/layout/style/test/test_garbage_at_end_of_declarations.html
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test handling of garbage at the end of CSS declarations</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.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 id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/* eslint-disable dot-notation */
+/** Test for correct ExpectEndProperty calls in CSS parser **/
+
+
+/*
+ * Inspired by review comments on bug 378217.
+ *
+ * The original idea was to test that ExpectEndProperty calls are made
+ * in the correct places in the CSS parser so that we don't accept
+ * garbage at the end of property values.
+ *
+ * However, there's actually other code (in ParseDeclaration) that
+ * ensures that we don't accept garbage.
+ *
+ * Despite that, I'm checking it in anyway, since it caught an infinite
+ * loop in the patch for bug 435441.
+ */
+
+var gElement = document.getElementById("testnode");
+var gDeclaration = gElement.style;
+
+/*
+ * This lists properties where garbage identifiers are allowed at the
+ * end, with values in property_database.js that are exceptions that
+ * should be tested anyway. "inherit", "initial" and "unset" are always
+ * tested.
+ */
+var gAllowsExtra = {
+ "counter-increment": { "none": true },
+ "counter-reset": { "none": true },
+ "font-family": {},
+ "font": { "caption": true, "icon": true, "menu": true, "message-box": true,
+ "small-caption": true, "status-bar": true },
+ "voice-family": {},
+ "list-style": {
+ "inside none": true, "none inside": true, "none": true,
+ "none outside": true, "outside none": true,
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")': true,
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside': true,
+ 'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")': true
+ },
+};
+
+/* These are the reverse of the above list; they're the unusual values
+ that do allow extra keywords afterwards */
+var gAllowsExtraUnusual = {
+ "transition": { "all": true, "0s": true, "0s 0s": true, "ease": true,
+ "1s 2s linear": true, "1s linear 2s": true,
+ "linear 1s 2s": true, "linear 1s": true,
+ "1s linear": true, "1s 2s": true, "2s 1s": true,
+ "linear": true, "1s": true, "2s": true,
+ "ease-in-out": true, "2s ease-in": true,
+ "ease-out 2s": true, "1s width, 2s": true },
+ "animation": { "none": true, "0s": true, "ease": true,
+ "normal": true, "running": true, "1.0": true,
+ "1s 2s linear": true, "1s linear 2s": true,
+ "linear 1s 2s": true, "linear 1s": true,
+ "1s linear": true, "1s 2s": true, "2s 1s": true,
+ "linear": true, "1s": true, "2s": true,
+ "ease-in-out": true, "2s ease-in": true,
+ "ease-out 2s": true, "1s bounce, 2s": true,
+ "1s bounce, 2s none": true },
+ "font-family": { "inherit": true, "initial": true, "unset": true }
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.transitions")) {
+ gAllowsExtraUnusual["-moz-transition"] = gAllowsExtraUnusual["transition"];
+}
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.animations")) {
+ gAllowsExtraUnusual["-moz-animation"] = gAllowsExtraUnusual["animation"];
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ function test_value(value) {
+ if (property in gAllowsExtra &&
+ value != "inherit" && value != "initial" && value != "unset" &&
+ !(value in gAllowsExtra[property])) {
+ return;
+ }
+ if (property in gAllowsExtraUnusual &&
+ value in gAllowsExtraUnusual[property]) {
+ return;
+ }
+
+ // Include non-identifier characters in the garbage
+ // in case |value| would also be valid with a <custom-ident> added.
+ gElement.setAttribute("style", property + ": " + value + " +blah/");
+ if ("subproperties" in info) {
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ is(gDeclaration.getPropertyValue(subprop), "",
+ ["expected garbage ignored after '", property, ": ", value,
+ "' when looking at subproperty '", subprop, "'"].join(""));
+ }
+ } else {
+ is(gDeclaration.getPropertyValue(property), "",
+ ["expected garbage ignored after '", property, ": ", value,
+ "'"].join(""));
+ }
+ }
+
+ var idx;
+ test_value("inherit");
+ test_value("initial");
+ test_value("unset");
+ for (idx in info.initial_values)
+ test_value(info.initial_values[idx]);
+ for (idx in info.other_values)
+ test_value(info.other_values[idx]);
+}
+
+// To avoid triggering the slow script dialog, we have to test one
+// property at a time.
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+var props = [];
+for (var prop in gCSSProperties)
+ props.push(prop);
+props = props.reverse();
+function do_one() {
+ if (props.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ test_property(props.pop());
+ SimpleTest.executeSoon(do_one);
+}
+SimpleTest.executeSoon(do_one);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_grid_computed_values.html b/layout/style/test/test_grid_computed_values.html
new file mode 100644
index 0000000000..68a183606c
--- /dev/null
+++ b/layout/style/test/test_grid_computed_values.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test computed grid values</title>
+ <link rel="author" title="Tobias Schneider" href="mailto:schneider@jancona.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+ <style>
+
+ #grid {
+ display: grid;
+ width: 500px;
+ height: 400px;
+ grid-template-columns:
+ [a] auto
+ [b] minmax(min-content, 1fr)
+ [b c d] repeat(2, [e] 40px)
+ repeat(5, auto);
+ grid-template-rows:
+ [a] minmax(min-content, 1fr)
+ [b] auto
+ [b c d e] 30px 30px
+ auto auto;
+ grid-auto-columns: 3fr;
+ grid-auto-rows: 2fr;
+ }
+ #grid2 {
+ display: grid;
+ width: 500px;
+ height: 400px;
+ grid-auto-columns: 10px;
+ grid-auto-rows: 2fr;
+ }
+
+ </style>
+</head>
+<body>
+
+<div>
+ <div id="grid">
+ <div style="grid-column-start:1; width:50px"></div>
+ <div style="grid-column-start:9; width:50px"></div>
+ </div>
+ <div id="grid2">
+ <div style="grid-column: span X / 1"></div>
+ <div style="grid-column: 1 / span X 2"></div>
+ </div>
+<div>
+
+<script>
+
+ var gridElement = document.getElementById("grid");
+
+ function test_grid_template(assert_fn, width, height, desc) {
+ test(function() {
+ assert_fn(getComputedStyle(gridElement).gridTemplateColumns,
+ "[a] 50px [b] " + width + "px [b c d e] 40px [e] 40px 0px 0px 0px 0px 50px");
+ assert_fn(getComputedStyle(gridElement).gridTemplateRows,
+ "[a] " + height + "px [b] 0px [b c d e] 30px 30px 0px 0px");
+ }, desc);
+ }
+
+ test_grid_template(assert_equals, 320, 340, "test computed grid-template-{columns,rows} values");
+
+ gridElement.style.overflow = 'scroll';
+ var v_scrollbar = gridElement.offsetWidth - gridElement.clientWidth;
+ var h_scrollbar = gridElement.offsetHeight - gridElement.clientHeight;
+ test_grid_template(assert_equals, 320 - v_scrollbar, 340 - h_scrollbar,
+ "test computed grid-template-{columns,rows} values, overflow: scroll");
+
+ gridElement.style.width = '600px';
+ gridElement.style.overflow = 'visible';
+ test_grid_template(assert_equals, 420, 340,
+ "test computed grid-template-{columns,rows} values, after reflow");
+
+ gridElement.style.display = 'none';
+ test_grid_template(assert_not_equals, 420, 340,
+ "test computed grid-template-{columns,rows} values, display: none");
+
+ gridElement.style.display = 'grid';
+ gridElement.parentNode.style.display = 'none';
+ test_grid_template(assert_not_equals, 420, 340,
+ "test computed grid-template-{columns,rows} values, display: none on parent");
+
+ gridElement.parentNode.style.display = '';
+ function test_grid2() {
+ gridElement = document.getElementById("grid2");
+ test(function() {
+ const expectedCols = SpecialPowers.getBoolPref("layout.css.serialize-grid-implicit-tracks")
+ ? "10px 10px 10px"
+ : "none";
+ const expectedRows = SpecialPowers.getBoolPref("layout.css.serialize-grid-implicit-tracks")
+ ? "400px"
+ : "none";
+
+ assert_equals(getComputedStyle(gridElement).gridTemplateColumns,
+ expectedCols);
+ assert_equals(getComputedStyle(gridElement).gridTemplateRows,
+ expectedRows);
+ }, "test #grid2 computed grid-template-{columns,rows} values");
+ }
+
+ test(function() {
+ assert_equals(getComputedStyle(gridElement).gridAutoColumns, "3fr");
+ assert_equals(getComputedStyle(gridElement).gridAutoRows, "2fr");
+ test_grid2();
+ }, "test computed grid-auto-{columns,rows} values");
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_grid_container_shorthands.html b/layout/style/test/test_grid_container_shorthands.html
new file mode 100644
index 0000000000..1b7a434208
--- /dev/null
+++ b/layout/style/test/test_grid_container_shorthands.html
@@ -0,0 +1,271 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test parsing of grid container shorthands (grid-template, grid)</title>
+ <link rel="author" title="Simon Sapin" href="mailto:simon.sapin@exyr.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+
+<script>
+
+var initial_values = {
+ gridTemplateAreas: "none",
+ gridTemplateRows: "none",
+ gridTemplateColumns: "none",
+ gridAutoFlow: "row",
+ // Computed value for 'auto'
+ gridAutoRows: "auto",
+ gridAutoColumns: "auto",
+};
+
+// For various specified values of the grid-template shorthand,
+// test the computed values of the corresponding longhands.
+var grid_template_test_cases = [
+ {
+ specified: "none",
+ },
+ {
+ specified: "40px / 100px",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "100px",
+ },
+ {
+ specified: "minmax(auto,1fr) / minmax(auto,1fr)",
+ gridTemplateRows: "1fr",
+ gridTemplateColumns: "1fr",
+ },
+ {
+ specified: "[foo] 40px [bar] / [baz] 100px [fizz]",
+ gridTemplateRows: "[foo] 40px [bar]",
+ gridTemplateColumns: "[baz] 100px [fizz]",
+ },
+ {
+ specified: " none/100px",
+ gridTemplateRows: "none",
+ gridTemplateColumns: "100px",
+ },
+ {
+ specified: "40px/none",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "none",
+ },
+ {
+ specified: "40px/repeat(1, 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(1, 20px)",
+ },
+ {
+ specified: "40px/[a]repeat(1, 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(1, 20px)",
+ },
+ {
+ specified: "40px/repeat(1, [a] 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(1, [a] 20px)",
+ },
+ {
+ specified: "40px/[a]repeat(2, [b]20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(2, [b] 20px)",
+ },
+ {
+ specified: "40px/[a]repeat(2, 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(2, 20px)",
+ },
+ {
+ specified: "40px/repeat(2, [a] 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(2, [a] 20px)",
+ },
+ {
+ specified: "40px/[a]repeat(2, [b]20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(2, [b] 20px)",
+ },
+ {
+ specified: "40px/repeat(2, 20px[a])",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(2, 20px [a])",
+ },
+ {
+ specified: "40px/repeat(2, 20px[a]) [b]",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(2, 20px [a]) [b]",
+ },
+ {
+ specified: "40px/repeat(2, [a] 20px[b]) [c]",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(2, [a] 20px [b]) [c]",
+ },
+ {
+ specified: "40px/[a] repeat(3, [b c] 20px [d] 100px [e f]) [g]",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(3, [b c] 20px [d] 100px [e f]) [g]",
+ },
+ {
+ specified: "'fizz'",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "auto",
+ },
+ {
+ specified: "[bar] 'fizz'",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "[bar] auto",
+ },
+ {
+ specified: "'fizz' / [foo] 40px",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "auto",
+ gridTemplateColumns: "[foo] 40px",
+ },
+ {
+ specified: "[bar] 'fizz' / [foo] 40px",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "[bar] auto",
+ gridTemplateColumns: "[foo] 40px",
+ },
+ {
+ specified: "'fizz' 100px / [foo] 40px",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "100px",
+ gridTemplateColumns: "[foo] 40px",
+ },
+ {
+ specified: "[bar] 'fizz' 100px [buzz] \n [a] '.' 200px [b] / [foo] 40px",
+ gridTemplateAreas: "\"fizz\" \".\"",
+ gridTemplateRows: "[bar] 100px [buzz a] 200px [b]",
+ gridTemplateColumns: "[foo] 40px",
+ },
+ {
+ specified: "subgrid / subgrid",
+ gridTemplateColumns: "subgrid",
+ gridTemplateRows: "subgrid",
+ },
+ {
+ specified: "subgrid [foo] / subgrid",
+ gridTemplateColumns: "subgrid",
+ gridTemplateRows: "subgrid [foo]",
+ },
+ {
+ specified: "subgrid [foo] repeat(3, [] [a b] [c]) / subgrid",
+ gridTemplateColumns: "subgrid",
+ gridTemplateRows: "subgrid [foo] repeat(3, [] [a b] [c])",
+ },
+ {
+ // Test that the number of lines is clamped to kMaxLine = 10000.
+ // https://drafts.csswg.org/css-grid/#overlarge-grids
+ specified: "subgrid [foo] repeat(999999999, [a]) / subgrid",
+ gridTemplateColumns: "subgrid",
+ gridTemplateRows: "subgrid [foo] repeat(10000, [a])",
+ },
+ {
+ specified: "subgrid [bar]/ subgrid [] [foo",
+ gridTemplateColumns: "subgrid [] [foo]",
+ gridTemplateRows: "subgrid [bar]",
+ },
+ {
+ specified: "'fizz' repeat(1, 100px)",
+ },
+ {
+ specified: "'fizz' repeat(auto-fill, 100px)",
+ },
+ {
+ specified: "'fizz' / repeat(1, 100px)",
+ },
+ {
+ specified: "'fizz' / repeat(auto-fill, 100px)",
+ },
+];
+
+grid_test_cases = grid_template_test_cases.concat([
+ {
+ specified: "auto-flow / 0",
+ gridAutoFlow: "row",
+ gridAutoRows: "auto",
+ gridTemplateColumns: "0px",
+ },
+ {
+ specified: "auto-flow dense / 0",
+ gridAutoFlow: "dense",
+ gridAutoRows: "auto",
+ gridTemplateColumns: "0px",
+ },
+ {
+ specified: "auto-flow minmax(auto,1fr) / none",
+ gridAutoFlow: "row",
+ gridAutoRows: "1fr",
+ },
+ {
+ specified: "auto-flow 40px / none",
+ gridAutoFlow: "row",
+ gridAutoRows: "40px",
+ },
+ {
+ specified: "none / auto-flow 40px",
+ gridAutoFlow: "column",
+ gridAutoRows: "auto",
+ gridAutoColumns: "40px",
+ },
+ {
+ specified: "none / auto-flow minmax(auto,1fr)",
+ gridAutoFlow: "column",
+ gridAutoRows: "auto",
+ gridAutoColumns: "1fr",
+ },
+ {
+ specified: "0 / auto-flow dense auto",
+ gridAutoFlow: "column dense",
+ gridAutoRows: "auto",
+ gridAutoColumns: "auto",
+ gridTemplateRows: "0px",
+ },
+ {
+ specified: "dense auto-flow minmax(min-content, 2fr) / 0",
+ gridAutoFlow: "dense",
+ gridAutoRows: "minmax(min-content, 2fr)",
+ gridAutoColumns: "auto",
+ gridTemplateColumns: "0px",
+ },
+ {
+ specified: "auto-flow 40px / 100px",
+ gridAutoFlow: "row",
+ gridAutoRows: "40px",
+ gridAutoColumns: "auto",
+ gridTemplateColumns: "100px",
+ },
+]);
+
+function run_tests(test_cases, shorthand, subproperties) {
+ test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ element.style[shorthand] = test_case.specified;
+ var computed = window.getComputedStyle(element);
+ subproperties.forEach(function(longhand) {
+ assert_equals(
+ computed[longhand],
+ test_case[longhand] || initial_values[longhand],
+ longhand
+ );
+ });
+ }, "test parsing of 'grid-template: " + test_case.specified + "'");
+ });
+}
+
+run_tests(grid_template_test_cases, "gridTemplate", [
+ "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows"]);
+
+run_tests(grid_test_cases, "grid", [
+ "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows",
+ "gridAutoFlow", "gridAutoColumns", "gridAutoRows"]);
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_grid_item_shorthands.html b/layout/style/test/test_grid_item_shorthands.html
new file mode 100644
index 0000000000..a50be6112d
--- /dev/null
+++ b/layout/style/test/test_grid_item_shorthands.html
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test parsing of grid item shorthands (grid-column, grid-row, grid-area)</title>
+ <link rel="author" title="Simon Sapin" href="mailto:simon.sapin@exyr.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+
+<script>
+
+// For various specified values of the grid-column and grid-row shorthands,
+// test the computed values of the corresponding longhands.
+var grid_column_row_test_cases = [
+ {
+ specified: "3 / 4",
+ expected_start: "3",
+ expected_end: "4",
+ },
+ {
+ specified: "foo / span bar",
+ expected_start: "foo",
+ expected_end: "span bar",
+ },
+ // http://dev.w3.org/csswg/css-grid/#placement-shorthands
+ // "When the second value is omitted,
+ // if the first value is a <custom-ident>,
+ // the grid-row-end/grid-column-end longhand
+ // is also set to that <custom-ident>;
+ // otherwise, it is set to auto."
+ {
+ specified: "foo",
+ expected_start: "foo",
+ expected_end: "foo",
+ },
+ {
+ specified: "7",
+ expected_start: "7",
+ expected_end: "auto",
+ },
+ {
+ specified: "foo 7",
+ expected_start: "7 foo",
+ expected_end: "auto",
+ },
+ {
+ specified: "span foo",
+ expected_start: "span foo",
+ expected_end: "auto",
+ },
+ {
+ specified: "foo 7 span",
+ expected_start: "span 7 foo",
+ expected_end: "auto",
+ },
+ {
+ specified: "7 span",
+ expected_start: "span 7",
+ expected_end: "auto",
+ },
+];
+
+// For various specified values of the grid-area shorthand,
+// test the computed values of the corresponding longhands.
+var grid_area_test_cases = [
+ {
+ specified: "10 / 20 / 30 / 40",
+ gridRowStart: "10",
+ gridColumnStart: "20",
+ gridRowEnd: "30",
+ gridColumnEnd: "40",
+ },
+ {
+ specified: "foo / bar / baz",
+ gridRowStart: "foo",
+ gridColumnStart: "bar",
+ gridRowEnd: "baz",
+ gridColumnEnd: "bar",
+ },
+ {
+ specified: "foo / span bar / baz",
+ gridRowStart: "foo",
+ gridColumnStart: "span bar",
+ gridRowEnd: "baz",
+ gridColumnEnd: "auto",
+ },
+ {
+ specified: "foo / bar",
+ gridRowStart: "foo",
+ gridColumnStart: "bar",
+ gridRowEnd: "foo",
+ gridColumnEnd: "bar",
+ },
+ {
+ specified: "foo / 4",
+ gridRowStart: "foo",
+ gridColumnStart: "4",
+ gridRowEnd: "foo",
+ gridColumnEnd: "auto",
+ },
+ {
+ specified: "foo",
+ gridRowStart: "foo",
+ gridColumnStart: "foo",
+ gridRowEnd: "foo",
+ gridColumnEnd: "foo",
+ },
+ {
+ specified: "7",
+ gridRowStart: "7",
+ gridColumnStart: "auto",
+ gridRowEnd: "auto",
+ gridColumnEnd: "auto",
+ },
+]
+
+grid_column_row_test_cases.forEach(function(test_case) {
+ ["Column", "Row"].forEach(function(axis) {
+ var shorthand = "grid" + axis;
+ var start_longhand = "grid" + axis + "Start";
+ var end_longhand = "grid" + axis + "End";
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ element.style[shorthand] = test_case.specified;
+ var computed = window.getComputedStyle(element);
+ assert_equals(computed[start_longhand], test_case.expected_start);
+ assert_equals(computed[end_longhand], test_case.expected_end);
+ }, "test parsing of '" + shorthand + ": " + test_case.specified + "'");
+ });
+});
+
+grid_area_test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ element.style.gridArea = test_case.specified;
+ var computed = window.getComputedStyle(element);
+ [
+ "gridRowStart", "gridColumnStart", "gridRowEnd", "gridColumnEnd"
+ ].forEach(function(longhand) {
+ assert_equals(computed[longhand], test_case[longhand], longhand);
+ });
+ }, "test parsing of 'grid-area: " + test_case.specified + "'");
+});
+
+</script>
+
+</body>
+</html>
diff --git a/layout/style/test/test_grid_shorthand_serialization.html b/layout/style/test/test_grid_shorthand_serialization.html
new file mode 100644
index 0000000000..b2d32b9364
--- /dev/null
+++ b/layout/style/test/test_grid_shorthand_serialization.html
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test serialization of CSS 'grid' shorthand property</title>
+ <link rel="author" title="Simon Sapin" href="mailto:simon.sapin@exyr.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+
+<script>
+
+var initial_values = {
+ gridTemplateAreas: "none",
+ gridTemplateRows: "none",
+ gridTemplateColumns: "none",
+ gridAutoFlow: "row",
+ gridAutoRows: "auto",
+ gridAutoColumns: "auto",
+ gridRowGap: "0px",
+ gridColumnGap: "0px",
+};
+
+// For various specified values of the grid-template subproperties,
+// test the serialization of the shorthand.
+var grid_template_test_cases = [
+ {
+ gridTemplateColumns: "100px",
+ shorthand: "none / 100px",
+ },
+ {
+ gridTemplateRows: "minmax(auto,1fr)",
+ shorthand: "1fr / none",
+ },
+ {
+ gridTemplateColumns: "minmax(auto,1fr)",
+ shorthand: "none / 1fr",
+ },
+ {
+ gridTemplateRows: "40px",
+ shorthand: "40px / none",
+ },
+ {
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "subgrid",
+ shorthand: "40px / subgrid",
+ },
+ {
+ gridTemplateRows: "[foo] 40px [bar]",
+ gridTemplateColumns: "[baz] 100px [fizz]",
+ shorthand: "[foo] 40px [bar] / [baz] 100px [fizz]",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "20px",
+ shorthand: "\"a\" 20px",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "[foo] 20px [bar]",
+ shorthand: "[foo] \"a\" 20px [bar]",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "[foo] repeat(1, 20px) [bar]",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a a\"",
+ gridTemplateColumns: "repeat(2, 100px)",
+ gridTemplateRows: "auto",
+ shorthand: "",
+ },
+ // Combinations of longhands that make the shorthand non-serializable:
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "20px 100px",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\" \"b\"",
+ gridTemplateRows: "20px",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "subgrid",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "subgrid [foo]",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "20px",
+ gridTemplateColumns: "subgrid",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "repeat(auto-fill, 20px)",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateColumns: "repeat(auto-fill, 100px)",
+ gridTemplateRows: "auto",
+ shorthand: "",
+ },
+];
+
+grid_test_cases = grid_template_test_cases.concat([
+ {
+ gridAutoFlow: "row",
+ shorthand: "none",
+ },
+ {
+ gridAutoRows: "40px",
+ shorthand: "auto-flow 40px / none",
+ },
+ {
+ gridAutoRows: "minmax(auto,1fr)",
+ shorthand: "auto-flow 1fr / none",
+ },
+ {
+ gridAutoFlow: "column dense",
+ gridAutoRows: "minmax(min-content, max-content)",
+ shorthand: "",
+ },
+ {
+ gridAutoFlow: "column dense",
+ gridAutoColumns: "minmax(min-content, max-content)",
+ shorthand: "none / auto-flow dense minmax(min-content, max-content)",
+ },
+ {
+ gridAutoFlow: "column",
+ gridAutoColumns: "minmax(auto,1fr)",
+ shorthand: "none / auto-flow 1fr",
+ },
+ {
+ gridAutoFlow: "row dense",
+ gridAutoColumns: "minmax(min-content, 2fr)",
+ shorthand: "",
+ },
+ {
+ gridAutoFlow: "row dense",
+ gridAutoRows: "minmax(min-content, 2fr)",
+ shorthand: "auto-flow dense minmax(min-content, 2fr) / none",
+ },
+ {
+ gridAutoFlow: "row",
+ gridAutoRows: "40px",
+ gridTemplateColumns: "100px",
+ shorthand: "auto-flow 40px / 100px",
+ },
+ // Test that grid-gap properties don't affect serialization.
+ {
+ gridRowGap: "1px",
+ shorthand: "none",
+ },
+ {
+ gridColumnGap: "1px",
+ shorthand: "none",
+ },
+]);
+
+var grid_important_test_cases = [
+ {
+ "grid-auto-flow": "row",
+ shorthand: "",
+ },
+];
+
+
+function run_tests(test_cases, shorthand, subproperties) {
+ test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ subproperties.forEach(function(longhand) {
+ element.style[longhand] = test_case[longhand] ||
+ initial_values[longhand];
+ });
+ assert_equals(element.style[shorthand], test_case.shorthand);
+ }, "test shorthand serialization " + JSON.stringify(test_case));
+ });
+}
+
+function run_important_tests(test_cases, shorthand, subproperties) {
+ test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ subproperties.forEach(function(longhand) {
+ element.style.setProperty(longhand,
+ test_case[longhand] || initial_values[longhand],
+ "important");
+ });
+ assert_equals(element.style[shorthand], test_case.shorthand);
+ }, "test shorthand serialization " + JSON.stringify(test_case));
+ });
+}
+
+run_tests(grid_template_test_cases, "gridTemplate", [
+ "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows"]);
+
+run_tests(grid_test_cases, "grid", [
+ "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows",
+ "gridAutoFlow", "gridAutoColumns", "gridAutoRows"]);
+
+run_important_tests(grid_important_test_cases, "grid", [
+ "grid-template-areas", "grid-template-columns", "grid-template-rows",
+ "grid-auto-flow", "grid-auto-columns", "grid-auto-rows"]);
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_group_insertRule.html b/layout/style/test/test_group_insertRule.html
new file mode 100644
index 0000000000..85edc2a1a0
--- /dev/null
+++ b/layout/style/test/test_group_insertRule.html
@@ -0,0 +1,243 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>CSS Variables Allowed Syntax</title>
+ <link rel="author" title="L. David Baron" href="https://dbaron.org/">
+ <link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
+ <link rel="help" href="http://www.w3.org/TR/css3-conditional/#the-cssgroupingrule-interface">
+ <meta name="assert" content="requirements in definition of insertRule">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+<style id="style">
+@media print {}
+</style>
+<script id="metadata_cache">/*
+{
+ "rule_type": {},
+ "rule_length": {},
+ "insert_import_throws": {},
+ "insert_index_throws1": {},
+ "insert_index_throws2": {},
+ "insert_media_succeed": {},
+ "insert_style_succeed": {},
+ "insert_bad_media_throw": {},
+ "insert_empty_throw": {},
+ "insert_garbage_after_media_throw": {},
+ "insert_garbage_after_style_throw": {},
+ "insert_two_media_throw": {},
+ "insert_style_media_throw": {},
+ "insert_media_style_throw": {},
+ "insert_two_style_throw": {},
+ "insert_retval": {}
+}
+*/</script>
+</head>
+<body onload="run()">
+<div id=log></div>
+<div id="test"></div>
+<script>
+
+ var sheet = document.getElementById("style").sheet;
+
+ var grouping_rule = sheet.cssRules[0];
+
+ test(function() {
+ assert_equals(grouping_rule.type, CSSRule.MEDIA_RULE,
+ "Rule type of @media rule");
+ },
+ "rule_type");
+
+ test(function() {
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Starting cssRules.length of @media rule");
+ },
+ "rule_length");
+
+ test(function() {
+ assert_throws("HIERARCHY_REQUEST_ERR",
+ function() {
+ grouping_rule.insertRule("@import url(foo.css);", 0);
+ },
+ "inserting a disallowed rule should throw HIERARCHY_REQUEST_ERR");
+ },
+ "insert_import_throws");
+
+ test(function() {
+ assert_throws("INDEX_SIZE_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: green }", 1);
+ },
+ "inserting at a bad index throws INDEX_SIZE_ERR");
+ },
+ "insert_index_throws1");
+ test(function() {
+ grouping_rule.insertRule("p { color: green }", 0);
+ assert_equals(grouping_rule.cssRules.length, 1,
+ "Modified cssRules.length of @media rule");
+ grouping_rule.insertRule("p { color: blue }", 1);
+ assert_equals(grouping_rule.cssRules.length, 2,
+ "Modified cssRules.length of @media rule");
+ grouping_rule.insertRule("p { color: aqua }", 1);
+ assert_equals(grouping_rule.cssRules.length, 3,
+ "Modified cssRules.length of @media rule");
+ assert_throws("INDEX_SIZE_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: green }", 4);
+ },
+ "inserting at a bad index throws INDEX_SIZE_ERR");
+ assert_equals(grouping_rule.cssRules.length, 3,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_index_throws2");
+
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ grouping_rule.insertRule("@media print {}", 0);
+ assert_equals(grouping_rule.cssRules.length, 1,
+ "Modified cssRules.length of @media rule");
+ assert_equals(grouping_rule.cssRules[0].type, CSSRule.MEDIA_RULE,
+ "inserting syntactically correct media rule succeeds");
+ },
+ "insert_media_succeed");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ grouping_rule.insertRule("p { color: yellow }", 0);
+ assert_equals(grouping_rule.cssRules.length, 1,
+ "Modified cssRules.length of @media rule");
+ assert_equals(grouping_rule.cssRules[0].type, CSSRule.STYLE_RULE,
+ "inserting syntactically correct style rule succeeds");
+ },
+ "insert_style_succeed");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("@media bad syntax;", 0);
+ },
+ "inserting syntactically invalid rule throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_bad_media_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("", 0);
+ },
+ "inserting empty rule throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_empty_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("@media print {} foo", 0);
+ },
+ "inserting rule with garbage afterwards throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_garbage_after_media_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: yellow } foo", 0);
+ },
+ "inserting rule with garbage afterwards throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_garbage_after_style_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("@media print {} @media print {}", 0);
+ },
+ "inserting multiple rules throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_two_media_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: yellow } @media print {}", 0);
+ },
+ "inserting multiple rules throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_style_media_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("@media print {} p { color: yellow }", 0);
+ },
+ "inserting multiple rules throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_media_style_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: yellow } p { color: yellow }", 0);
+ },
+ "inserting multiple rules throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_two_style_throw");
+
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ var res = grouping_rule.insertRule("p { color: green }", 0);
+ assert_equals(res, 0, "return value should be index");
+ assert_equals(grouping_rule.cssRules.length, 1,
+ "Modified cssRules.length of @media rule");
+ res = grouping_rule.insertRule("p { color: green }", 0);
+ assert_equals(res, 0, "return value should be index");
+ assert_equals(grouping_rule.cssRules.length, 2,
+ "Modified cssRules.length of @media rule");
+ res = grouping_rule.insertRule("p { color: green }", 2);
+ assert_equals(res, 2, "return value should be index");
+ assert_equals(grouping_rule.cssRules.length, 3,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_retval");
+
+
+</script>
+</body>
+</html>
+
diff --git a/layout/style/test/test_hover_on_part.html b/layout/style/test/test_hover_on_part.html
new file mode 100644
index 0000000000..fc39dcc307
--- /dev/null
+++ b/layout/style/test/test_hover_on_part.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<title>Shadow parts are invalidated correctly when only a pseudo-class state to the right of the part matches</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ div {
+ width: 100px;
+ height: 100px;
+ }
+ #host::part(p) {
+ background-color: red;
+ }
+ #host::part(p):hover {
+ background-color: lime;
+ }
+</style>
+<div id="host"></div>
+<div id="random-element-to-force-change"></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+let host = document.getElementById("host");
+host.attachShadow({ mode: "open" }).innerHTML = `
+ <style>
+ div {
+ width: 100px;
+ height: 100px;
+ }
+ </style>
+ <div part=p></div>
+`;
+
+let part = host.shadowRoot.querySelector("div");
+let other = document.getElementById("random-element-to-force-change");
+
+SimpleTest.waitForFocus(function() {
+ synthesizeMouseAtCenter(other, {type: "mousemove"});
+ is(
+ getComputedStyle(part).backgroundColor,
+ "rgb(255, 0, 0)",
+ "Part is red"
+ );
+
+ synthesizeMouseAtCenter(part, {type: "mousemove"});
+ is(
+ getComputedStyle(part).backgroundColor,
+ "rgb(0, 255, 0)",
+ "Part is lime"
+ );
+ SimpleTest.finish();
+});
+</script>
diff --git a/layout/style/test/test_hover_quirk.html b/layout/style/test/test_hover_quirk.html
new file mode 100644
index 0000000000..61e19f2a60
--- /dev/null
+++ b/layout/style/test/test_hover_quirk.html
@@ -0,0 +1,118 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783213
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for the :active and :hover quirk</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ /* Should apply to all elements: */
+ #content :hover:first-of-type {
+ color: rgb(255, 0, 0);
+ }
+ #content :-moz-any(:hover) {
+ text-transform: lowercase;
+ }
+ #content :hover::after {
+ content: "any element";
+ }
+
+ #content :hover:first-of-type .child::after {
+ content: "any child";
+ }
+
+ #content .parent .child::after {
+ content: "wrong" !important;
+ }
+
+ #content .parent:hover .child::after {
+ content: "any child" !important;
+ }
+
+ /* Should apply only to links: */
+ #content :hover {
+ color: rgb(0, 255, 0) !important;
+ text-transform: uppercase !important;
+ }
+ #content :hover .child::after {
+ content: "link child" !important;
+ }
+
+ #dynamic-test {
+ width: 100px;
+ height: 100px;
+ background: green;
+ }
+
+ #dynamic-test > * {
+ width: 100%;
+ height: 100%;
+ background: red;
+ }
+
+ #dynamic-test:hover > * {
+ background: rgb(0, 255, 0);
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript">
+ /** Test for the :active and :hover quirk **/
+ function test(element, isLink) {
+ if (!isLink)
+ var styles = {color: "rgb(255, 0, 0)", textTransform: "lowercase",
+ childContent: '"any child"'};
+ else
+ var styles = {color: "rgb(0, 255, 0)", textTransform: "uppercase",
+ childContent: '"link child"'};
+
+ // Trigger the :hover pseudo-class.
+ synthesizeMouseAtCenter(element, {type: "mousemove"});
+
+ var computedStyle = getComputedStyle(element);
+ is(computedStyle.color, styles.color, "Unexpected color value");
+ is(computedStyle.textTransform, styles.textTransform,
+ "Unexpected text-transform value");
+
+ computedStyle = getComputedStyle(element, "::after");
+ is(computedStyle.content, '"any element"',
+ "Unexpected pseudo-element content");
+
+ computedStyle = getComputedStyle(
+ element.getElementsByClassName("child")[0], "::after");
+ is(computedStyle.content, styles.childContent,
+ "Unexpected pseudo-element content for child");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ test(document.getElementById("span"), false);
+ test(document.getElementById("label"), false);
+ test(document.getElementById("link"), true);
+ test(document.getElementById("div"), false);
+ // Dynamic change test.
+ // Trigger the :hover pseudo-class.
+ synthesizeMouseAtCenter(document.getElementById('dynamic-test'), {type: "mousemove"});
+ is(getComputedStyle(document.getElementById('should-be-green-on-hover')).backgroundColor,
+ "rgb(0, 255, 0)",
+ "Dynamic change should invalidate properly");
+ SimpleTest.finish();
+ });
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783213">Mozilla Bug 783213</a>
+ <p id="display"></p>
+ <div id="dynamic-test">
+ <div id="should-be-green-on-hover"></div>
+ </div>
+ <div id="content">
+ <span id="span">Span<span class="child"></span></span><br>
+ <label id="label">Label<span class="child"></span></label><br>
+ <a id="link" href="#">Link<span class="child"></span></a><br>
+ <div id="div" class="parent">Div <span><span class="child"></span></span></div><br>
+ </div>
+ <pre id="test"></pre>
+</body>
+</html>
diff --git a/layout/style/test/test_html_attribute_computed_values.html b/layout/style/test/test_html_attribute_computed_values.html
new file mode 100644
index 0000000000..74e5b9754a
--- /dev/null
+++ b/layout/style/test/test_html_attribute_computed_values.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="content"></div>
+<pre id="test">
+<script type="application/javascript">
+
+
+var gValues = [
+ {
+ element: "<li type='i'></li>",
+ property: "list-style-type",
+ value: "lower-roman"
+ },
+ {
+ element: "<li type='I'></li>",
+ property: "list-style-type",
+ value: "upper-roman"
+ },
+ {
+ element: "<li type='a'></li>",
+ property: "list-style-type",
+ value: "lower-alpha"
+ },
+ {
+ element: "<li type='A'></li>",
+ property: "list-style-type",
+ value: "upper-alpha"
+ },
+ {
+ element: "<li type='1'></li>",
+ property: "list-style-type",
+ value: "decimal"
+ },
+ {
+ element: "<ol type='i'></ol>",
+ property: "list-style-type",
+ value: "lower-roman"
+ },
+ {
+ element: "<ol type='I'></ol>",
+ property: "list-style-type",
+ value: "upper-roman"
+ },
+ {
+ element: "<ol type='a'></ol>",
+ property: "list-style-type",
+ value: "lower-alpha"
+ },
+ {
+ element: "<ol type='A'></ol>",
+ property: "list-style-type",
+ value: "upper-alpha"
+ },
+ {
+ element: "<ol type='1'></ol>",
+ property: "list-style-type",
+ value: "decimal"
+ },
+];
+
+var content = document.getElementById("content");
+for (var i = 0; i < gValues.length; ++i) {
+ var v = gValues[i];
+
+ content.innerHTML = v.element;
+ is(getComputedStyle(content.firstChild, "").getPropertyValue(v.property),
+ v.value,
+ v.property + " for " + v.element);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_ident_escaping.html b/layout/style/test/test_ident_escaping.html
new file mode 100644
index 0000000000..d727e7f207
--- /dev/null
+++ b/layout/style/test/test_ident_escaping.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=543428
+-->
+<head>
+ <title>Test for Bug 543428</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css" id="sheet">p { color: blue; }</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=543428">Mozilla Bug 543428</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 543428 **/
+
+var sheet = document.getElementById("sheet").sheet;
+var rule = sheet.cssRules[0];
+
+function set_selector_text(selector)
+ // no cssText or selectorText setter implemented yet
+{
+ try {
+ // insertRule might throw on syntax error
+ sheet.insertRule(selector + " { color : green }", 0);
+ sheet.deleteRule(1);
+ } catch(ex) {}
+ rule = sheet.cssRules[0];
+}
+
+is(rule.selectorText, "p", "simple identifier not escaped");
+set_selector_text('\\P');
+is(rule.selectorText, "P", "simple identifier not escaped");
+set_selector_text('\\70');
+is(rule.selectorText, "p", "simple identifier not escaped");
+set_selector_text('font-family_72756');
+is(rule.selectorText, "font-family_72756", "simple identifier not escaped");
+set_selector_text('-font-family_72756');
+is(rule.selectorText, "-font-family_72756", "simple identifier not escaped");
+set_selector_text('-0invalid');
+set_selector_text('0invalid');
+is(rule.selectorText, "-font-family_72756", "setting invalid value ignored");
+set_selector_text('Håkon\\ Lie');
+is(rule.selectorText, "Håkon\\ Lie", "escaping done only where needed");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_img_src_causing_reflow.html b/layout/style/test/test_img_src_causing_reflow.html
new file mode 100644
index 0000000000..e63b39f9fe
--- /dev/null
+++ b/layout/style/test/test_img_src_causing_reflow.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for bug 1787072</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+img {
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+}
+</style>
+<img> <!-- Initially broken -->
+<script>
+add_task(async function() {
+ const utils = SpecialPowers.DOMWindowUtils;
+ const img = document.querySelector("img");
+ img.getBoundingClientRect();
+
+ let origFramesConstructed = utils.framesConstructed;
+ let origFramesReflowed = utils.framesReflowed;
+
+ let error = new Promise(r => img.addEventListener("error", r, { once: true }));
+
+ // Doesn't need to be an actual image.
+ img.src = "/some-valid-url";
+
+ img.getBoundingClientRect();
+ is(origFramesReflowed, utils.framesReflowed, "Shouldn't have reflowed when going broken -> loading");
+ is(origFramesConstructed, utils.framesConstructed, "Shouldn't have reflowed when going broken -> loading");
+
+ await error;
+
+ is(origFramesReflowed, utils.framesReflowed, "Shouldn't have reflowed when going loading -> broken");
+ is(origFramesConstructed, utils.framesConstructed, "Shouldn't have reflowed when going loading -> broken");
+});
+</script>
diff --git a/layout/style/test/test_import_preload.html b/layout/style/test/test_import_preload.html
new file mode 100644
index 0000000000..1c095c9768
--- /dev/null
+++ b/layout/style/test/test_import_preload.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script src="slow_load.sjs"></script>
+<script>
+ let time = Date.now();
+ SimpleTest.waitForExplicitFinish();
+
+ onload = function() {
+ let styleTime = parseInt(getComputedStyle(document.documentElement).zIndex, 10);
+ isnot(styleTime, 0, "Should apply the @import sheet");
+ ok(!isNaN(styleTime), "Should apply the @import sheet (and not be nan)");
+ // This is technically a bit racy... Also see the comment in slow_load.sjs about the clamping.
+ time = time % (Math.pow(2, 31) - 1);
+ ok(styleTime < time, "Should try to fetch the import before running the script: " + styleTime + " vs. " + time);
+ SimpleTest.finish();
+ }
+</script>
+<style>
+ @import url(slow_load.sjs?css)
+</style>
diff --git a/layout/style/test/test_inherit_computation.html b/layout/style/test/test_inherit_computation.html
new file mode 100644
index 0000000000..9ac056518c
--- /dev/null
+++ b/layout/style/test/test_inherit_computation.html
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for computation of CSS 'inherit' on all properties and 'unset' on inherited properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"><span id="fparent"><span id="fchild"></span></span></p>
+<div id="content" style="display: none">
+
+<div id="testnode"><span id="nparent"><span id="nchild"></span></span></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for computation of CSS 'inherit' on all properties and 'unset' on
+ inherited properties **/
+
+var gDisplayTree = document.getElementById("display");
+// elements without a frame
+var gNParent = document.getElementById("nparent");
+var gNChild = document.getElementById("nchild");
+// elements with a frame
+var gFParent = document.getElementById("fparent");
+var gFChild = document.getElementById("fchild");
+
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gChildRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild, #fchild {}", gStyleSheet.cssRules.length)];
+var gChildRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild, #fchild {}", gStyleSheet.cssRules.length)];
+var gChildRule3 = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild.allother, #fchild.allother {}", gStyleSheet.cssRules.length)];
+var gChildRuleTop = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild, #nchild.allother, #fchild, #fchild.allother {}", gStyleSheet.cssRules.length)];
+var gParentRuleTop = gStyleSheet.cssRules[gStyleSheet.insertRule("#nparent, #fparent {}", gStyleSheet.cssRules.length)];
+
+function get_computed_value_node(node, property)
+{
+ var cs = getComputedStyle(node, "");
+ return get_computed_value(cs, property);
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ var keywords = ["inherit"];
+ if (info.inherited)
+ keywords.push("unset");
+
+ keywords.forEach(function(keyword) {
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gParentRuleTop.style.setProperty(prereq, prereqs[prereq], "");
+ gChildRuleTop.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+
+ if (info.inherited) {
+ gParentRuleTop.style.setProperty(property, info.initial_values[0], "");
+ var initial_computed_n = get_computed_value_node(gNChild, property);
+ var initial_computed_f = get_computed_value_node(gFChild, property);
+ gChildRule1.style.setProperty(property, info.other_values[0], "");
+ var other_computed_n = get_computed_value_node(gNChild, property);
+ var other_computed_f = get_computed_value_node(gFChild, property);
+ isnot(other_computed_n, initial_computed_n,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ isnot(other_computed_f, initial_computed_f,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ gChildRule3.style.setProperty(property, keyword, "");
+ gFChild.className="allother";
+ gNChild.className="allother";
+ var inherit_initial_computed_n = get_computed_value_node(gNChild, property);
+ var inherit_initial_computed_f = get_computed_value_node(gFChild, property);
+ is(inherit_initial_computed_n, initial_computed_n,
+ keyword + " should cause inheritance of initial value for '" +
+ property + "'");
+ is(inherit_initial_computed_f, initial_computed_f,
+ keyword + " should cause inheritance of initial value for '" +
+ property + "'");
+ gParentRuleTop.style.setProperty(property, info.other_values[0], "");
+ var inherit_other_computed_n = get_computed_value_node(gNChild, property);
+ var inherit_other_computed_f = get_computed_value_node(gFChild, property);
+ is(inherit_other_computed_n, other_computed_n,
+ keyword + " should cause inheritance of other value for '" +
+ property + "'");
+ is(inherit_other_computed_f, other_computed_f,
+ keyword + " should cause inheritance of other value for '" +
+ property + "'");
+ gParentRuleTop.style.removeProperty(property);
+ gChildRule1.style.removeProperty(property);
+ gChildRule3.style.setProperty(property, info.other_values[0], "");
+ gFChild.className="";
+ gNChild.className="";
+ } else {
+ gParentRuleTop.style.setProperty(property, info.other_values[0], "");
+ var initial_computed_n = get_computed_value_node(gNChild, property);
+ var initial_computed_f = get_computed_value_node(gFChild, property);
+ var other_computed_n = get_computed_value_node(gNParent, property);
+ var other_computed_f = get_computed_value_node(gFParent, property);
+ isnot(other_computed_n, initial_computed_n,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ isnot(other_computed_f, initial_computed_f,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ gChildRule2.style.setProperty(property, keyword, "");
+ var inherit_other_computed_n = get_computed_value_node(gNChild, property);
+ var inherit_other_computed_f = get_computed_value_node(gFChild, property);
+ is(inherit_other_computed_n, other_computed_n,
+ keyword + " should cause inheritance of other value for '" +
+ property + "'");
+ is(inherit_other_computed_f, other_computed_f,
+ keyword + " should cause inheritance of other value for '" +
+ property + "'");
+ gParentRuleTop.style.removeProperty(property);
+ gChildRule1.style.setProperty(property, info.other_values[0], "");
+ var inherit_initial_computed_n = get_computed_value_node(gNChild, property);
+ var inherit_initial_computed_f = get_computed_value_node(gFChild, property);
+ is(inherit_initial_computed_n, initial_computed_n,
+ keyword + " should cause inheritance of initial value for '" +
+ property + "'");
+ // For width and inline-size, getComputedStyle returns used value
+ // when the element is displayed. Their initial value "auto" makes
+ // the element fill available space of the parent, so it doesn't
+ // make sense to compare it with the value we get before.
+ if (property != "width" && property != "inline-size") {
+ is(inherit_initial_computed_f, initial_computed_f,
+ keyword + " should cause inheritance of initial value for '" +
+ property + "'");
+ }
+ gParentRuleTop.style.removeProperty(property);
+ gChildRule1.style.removeProperty(property);
+ gChildRule2.style.removeProperty(property);
+ }
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gParentRuleTop.style.removeProperty(prereq);
+ gChildRuleTop.style.removeProperty(prereq);
+ }
+ }
+
+ // FIXME -moz-binding value makes gElementF and its parent loses
+ // their frame. Force it to get recreated after each property.
+ // See bug 1331903.
+ gDisplayTree.style.display = "none";
+ get_computed_value(getComputedStyle(gFChild, ""), "width");
+ gDisplayTree.style.display = "";
+ get_computed_value(getComputedStyle(gFChild, ""), "width");
+ });
+}
+
+for (var prop in gCSSProperties) {
+ // Skip zoom because it affects other props.
+ if (prop == "zoom") {
+ continue;
+ }
+ var info = gCSSProperties[prop];
+ gChildRule3.style.setProperty(prop, info.other_values[0], "");
+}
+
+for (var prop in gCSSProperties)
+ test_property(prop);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_inherit_storage.html b/layout/style/test/test_inherit_storage.html
new file mode 100644
index 0000000000..a9587d34d9
--- /dev/null
+++ b/layout/style/test/test_inherit_storage.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375363
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS 'inherit' on all properties and 'unset' on inherited properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.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=375363">Mozilla Bug 375363</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for parsing, storage, and serialization of CSS 'inherit' on all
+ properties and 'unset' on inherited properties **/
+
+var gDeclaration = document.getElementById("testnode").style;
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ var keywords = ["inherit"];
+ if (info.inherited)
+ keywords.push("unset");
+
+ keywords.forEach(function(keyword) {
+ function check_initial(sproperty) {
+ var sinfo = gCSSProperties[sproperty];
+ var val = gDeclaration.getPropertyValue(sproperty);
+ is(val, "", "value of '" + sproperty + "' before we do anything");
+ is(val, gDeclaration[sinfo.domProp],
+ "consistency between decl.getPropertyValue('" + sproperty + "') and decl." + sinfo.domProp);
+ }
+ check_initial(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_initial(info.subproperties[idx]);
+
+ gDeclaration.setProperty(property, keyword, "");
+
+ function check_set(sproperty) {
+ var sinfo = gCSSProperties[sproperty];
+ val = gDeclaration.getPropertyValue(sproperty);
+ is(val, keyword,
+ keyword + " reported back for property '" + sproperty + "'");
+ is(val, gDeclaration[sinfo.domProp],
+ "consistency between decl.getPropertyValue('" + sproperty +
+ "') and decl." + sinfo.domProp);
+ }
+ check_set(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_set(info.subproperties[idx]);
+
+ // We don't care particularly about the whitespace or the placement of
+ // semicolons, but for simplicity we'll test the current behavior.
+ if ("alias_for" in info) {
+ is(gDeclaration.cssText, info.alias_for + ": " + keyword + ";",
+ "declaration should serialize to exactly what went in (for " + keyword + ")");
+ } else if (info.type == CSS_TYPE_LEGACY_SHORTHAND) {
+ // We can't assert anything more meaningful here, really.
+ is(property, "zoom", "Zoom is a bit special because it never " +
+ "serializes as-is, we always serialize the longhands, " +
+ "but it doesn't just map to a single property " +
+ "(and thus we can't use the 'alias_for' mechanism)");
+ } else {
+ is(gDeclaration.cssText, property + ": " + keyword + ";",
+ "declaration should serialize to exactly what went in (for " + keyword + ")");
+ }
+
+ gDeclaration.removeProperty(property);
+
+ function check_final(sproperty) {
+ var sinfo = gCSSProperties[sproperty];
+ var val = gDeclaration.getPropertyValue(sproperty);
+ is(val, "", "value of '" + sproperty + "' after removal of value");
+ is(val, gDeclaration[sinfo.domProp],
+ "consistency between decl.getPropertyValue('" + sproperty + "') and decl." + sinfo.domProp);
+ }
+ check_final(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_final(info.subproperties[idx]);
+
+ // can all properties be removed from the style?
+ function test_remove_all_properties(propName, value) {
+ var i, p = [];
+ for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]);
+ for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]);
+ var errstr = "when setting property " + propName + " to " + value;
+ is(gDeclaration.length, 0, "unremovable properties " + errstr);
+ is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr);
+ }
+
+ // sanity check shorthands to make sure disabled props aren't exposed
+ if (info.type != CSS_TYPE_LONGHAND) {
+ gDeclaration.setProperty(property, keyword, "");
+ test_remove_all_properties(property, keyword);
+ gDeclaration.removeProperty(property);
+ }
+ });
+}
+
+for (var prop in gCSSProperties)
+ test_property(prop);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_initial_computation.html b/layout/style/test/test_initial_computation.html
new file mode 100644
index 0000000000..c8a36af227
--- /dev/null
+++ b/layout/style/test/test_initial_computation.html
@@ -0,0 +1,159 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for computation of CSS 'initial' on all properties and 'unset' on reset properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <style type="text/css">
+ /* For 'width', 'height', etc., need a constant size container. */
+ #display { width: 500px; height: 200px }
+ </style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var load_count = 0;
+ function load_done() {
+ if (++load_count == 3)
+ run_tests();
+ }
+ </script>
+</head>
+<body>
+<p id="display"><span><span id="elementf"></span></span>
+<iframe id="unstyledn" src="unstyled.xml" height="10" width="10" onload="load_done()"></iframe>
+<iframe id="unstyledf" src="unstyled-frame.xml" height="10" width="10" onload="load_done()"></iframe>
+</p>
+<div id="content" style="display: none">
+
+<div><span id="elementn"></span></div>
+
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for computation of CSS 'initial' on all properties and 'unset' on
+ reset properties **/
+
+var gBrokenInitial = {
+};
+
+function xfail_initial(property) {
+ return property in gBrokenInitial;
+}
+
+var gDisplayTree = document.getElementById("display");
+var gElementN = document.getElementById("elementn");
+var gElementF = document.getElementById("elementf");
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)];
+var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)];
+
+var gInitialValuesN;
+var gInitialValuesF;
+var gInitialPrereqsRuleN;
+var gInitialPrereqsRuleF;
+
+function setup_initial_values(id, ivalprop, prereqprop) {
+ var iframe = document.getElementById(id);
+ window[ivalprop] = iframe.contentWindow.getComputedStyle(
+ iframe.contentDocument.documentElement.firstChild);
+ var sheet = iframe.contentDocument.styleSheets[0];
+ // For 'width', 'height', etc., need a constant size container.
+ sheet.insertRule(":root { height: 200px; width: 500px }", sheet.cssRules.length);
+
+ window[prereqprop] = sheet.cssRules[sheet.insertRule(":root > * {}", sheet.cssRules.length)];
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ var keywords = ["initial"];
+ if (!info.inherited)
+ keywords.push("unset");
+
+ keywords.forEach(function(keyword) {
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gRule1.style.setProperty(prereq, prereqs[prereq], "");
+ gInitialPrereqsRuleN.style.setProperty(prereq, prereqs[prereq], "");
+ gInitialPrereqsRuleF.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+ if (info.inherited) {
+ gElementN.parentNode.style.setProperty(property, info.other_values[0], "");
+ gElementF.parentNode.style.setProperty(property, info.other_values[0], "");
+ }
+
+ var initial_computed_n = get_computed_value(gInitialValuesN, property);
+ var initial_computed_f = get_computed_value(gInitialValuesF, property);
+ gRule1.style.setProperty(property, info.other_values[0], "");
+ var other_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property);
+ var other_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property);
+ isnot(other_computed_n, initial_computed_n,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ isnot(other_computed_f, initial_computed_f,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ // It used to be important for values that are supposed to compute to the
+ // initial value (given the design of the old rule tree, nsRuleNode) that
+ // we're modifying the most specific rule that matches the element, and
+ // that we've already requested style while that rule was empty. This
+ // means we'd have a cached aStartStruct from the parent in the rule
+ // tree (caching the "other" value), so we'd make sure we don't get the
+ // initial value from the luck of default-initialization. This means
+ // that it would've been important that we set the prereqs on
+ // gRule1.style rather than on gElement.style.
+ //
+ // However, the rule tree no longer stores cached structs, and we only
+ // temporarily cache reset structs during a single restyle. So the
+ // particular failure mode this was designed to test for isn't as
+ // likely to eventuate.
+ gRule2.style.setProperty(property, keyword, "");
+ var initial_val_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property);
+ var initial_val_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property);
+ (xfail_initial(property) ? todo_is : is)(
+ initial_val_computed_n, initial_computed_n,
+ keyword + " should cause initial value for '" + property + "'");
+ (xfail_initial(property) ? todo_is : is)(
+ initial_val_computed_f, initial_computed_f,
+ keyword + " should cause initial value for '" + property + "'");
+ gRule1.style.removeProperty(property);
+ gRule2.style.removeProperty(property);
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gRule1.style.removeProperty(prereq);
+ gInitialPrereqsRuleN.style.removeProperty(prereq);
+ gInitialPrereqsRuleF.style.removeProperty(prereq);
+ }
+ }
+ if (info.inherited) {
+ gElementN.parentNode.style.removeProperty(property);
+ gElementF.parentNode.style.removeProperty(property);
+ }
+ });
+}
+
+function run_tests() {
+ setup_initial_values("unstyledn", "gInitialValuesN", "gInitialPrereqsRuleN");
+ setup_initial_values("unstyledf", "gInitialValuesF", "gInitialPrereqsRuleF");
+ for (var prop in gCSSProperties)
+ test_property(prop);
+ SimpleTest.finish();
+}
+
+load_done();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_initial_storage.html b/layout/style/test/test_initial_storage.html
new file mode 100644
index 0000000000..a1a081c5a6
--- /dev/null
+++ b/layout/style/test/test_initial_storage.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375363
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS 'initial' on all properties and 'unset' on reset properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.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=375363">Mozilla Bug 375363</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for parsing, storage, and serialization of CSS 'initial' on all
+ properties and 'unset' on reset properties **/
+
+var gDeclaration = document.getElementById("testnode").style;
+
+/**
+ * Checks that the passed-in property-value (returned by getPropertyValue) is
+ * consistent with the DOM accessors that we know about for the given sproperty.
+ */
+function check_consistency(sproperty, valFromGetPropertyValue, messagePrefix)
+{
+ var sinfo = gCSSProperties[sproperty];
+ is(valFromGetPropertyValue, gDeclaration[sinfo.domProp],
+ `(${messagePrefix}) consistency between ` +
+ `decl.getPropertyValue(${sproperty}) and decl.${sinfo.domProp}`);
+
+ if (sinfo.domProp.startsWith("webkit")) {
+ // For webkit-prefixed DOM accessors, test with lowercase and uppercase
+ // first letter.
+ var uppercaseDomProp = "W" + sinfo.domProp.substring(1);
+ is(valFromGetPropertyValue, gDeclaration[uppercaseDomProp],
+ `(${messagePrefix}) consistency between ` +
+ `decl.getPropertyValue(${sproperty}) and decl.${uppercaseDomProp}`);
+ }
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ var keywords = ["initial"];
+ if (!info.inherited)
+ keywords.push("unset");
+
+ keywords.forEach(function(keyword) {
+ function check_initial(sproperty) {
+ var val = gDeclaration.getPropertyValue(sproperty);
+ is(val, "", "value of '" + sproperty + "' before we do anything");
+ check_consistency(sproperty, val, "initial");
+ }
+ check_initial(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_initial(info.subproperties[idx]);
+
+ gDeclaration.setProperty(property, keyword, "");
+
+ function check_set(sproperty) {
+ val = gDeclaration.getPropertyValue(sproperty);
+ is(val, keyword,
+ keyword + " reported back for property '" + sproperty + "'");
+ check_consistency(sproperty, val, "set");
+ }
+ check_set(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_set(info.subproperties[idx]);
+
+ // We don't care particularly about the whitespace or the placement of
+ // semicolons, but for simplicity we'll test the current behavior.
+ if ("alias_for" in info) {
+ is(gDeclaration.cssText, info.alias_for + ": " + keyword + ";",
+ "declaration should serialize to exactly what went in (for " + keyword + ")");
+ } else if (info.type == CSS_TYPE_LEGACY_SHORTHAND) {
+ // We can't assert anything more meaningful here, really.
+ is(property, "zoom", "Zoom is a bit special because it never " +
+ "serializes as-is, we always serialize the longhands, " +
+ "but it doesn't just map to a single property " +
+ "(and thus we can't use the 'alias_for' mechanism)");
+ } else {
+ is(gDeclaration.cssText, property + ": " + keyword + ";",
+ "declaration should serialize to exactly what went in (for " + keyword + ")");
+ }
+
+ gDeclaration.removeProperty(property);
+
+ function check_final(sproperty) {
+ var val = gDeclaration.getPropertyValue(sproperty);
+ is(val, "", "value of '" + sproperty + "' after removal of value");
+ check_consistency(sproperty, val, "final");
+ }
+ check_final(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_final(info.subproperties[idx]);
+
+ // can all properties be removed from the style?
+ function test_remove_all_properties(propName, value) {
+ var i, p = [];
+ for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]);
+ for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]);
+ var errstr = "when setting property " + propName + " to " + value;
+ is(gDeclaration.length, 0, "unremovable properties " + errstr);
+ is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr);
+ }
+
+ // sanity check shorthands to make sure disabled props aren't exposed
+ if (info.type != CSS_TYPE_LONGHAND) {
+ gDeclaration.setProperty(property, keyword, "");
+ test_remove_all_properties(property, keyword);
+ gDeclaration.removeProperty(property);
+ }
+ });
+}
+
+for (var prop in gCSSProperties)
+ test_property(prop);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_invalidation_basic.html b/layout/style/test/test_invalidation_basic.html
new file mode 100644
index 0000000000..b5a6928405
--- /dev/null
+++ b/layout/style/test/test_invalidation_basic.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1368240: We only invalidate style as little as needed
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+.foo .bar {
+ color: red;
+}
+#container ~ .bar {
+ color: green;
+}
+</style>
+<div id="container">
+ <div></div>
+ <div></div>
+ <div></div>
+</div>
+<div></div>
+<div></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+
+// TODO(emilio): Add an API to get the ComputedStyles we've recreated, to make
+// more elaborated tests.
+document.documentElement.offsetTop;
+const initialRestyleGeneration = utils.restyleGeneration;
+
+// Normally we'd restyle the whole subtree in this case, but we should go down
+// the tree invalidating as little as needed (nothing in this case).
+container.classList.add("foo");
+document.documentElement.offsetTop;
+is(utils.restyleGeneration, initialRestyleGeneration,
+ "Shouldn't have restyled any descendant");
+
+container.setAttribute("id", "");
+document.documentElement.offsetTop;
+is(utils.restyleGeneration, initialRestyleGeneration,
+ "Shouldn't have restyled any sibling");
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_keyframes_rules.html b/layout/style/test/test_keyframes_rules.html
new file mode 100644
index 0000000000..027a406009
--- /dev/null
+++ b/layout/style/test/test_keyframes_rules.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=577974
+-->
+<head>
+ <title>Test for Bug 577974</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+
+ @keyframes bounce {
+ from {
+ margin-left: 0
+ }
+
+ /*
+ * These rules should get dropped due to syntax errors. The script
+ * below tests that the 25% rule following them is at cssRules[1].
+ */
+ from, { margin-left: 0 }
+ from , { margin-left: 0 }
+ , from { margin-left: 0 }
+ ,from { margin-left: 0 }
+ from from { margin-left: 0 }
+ from, 1 { margin-left: 0 }
+ 1 { margin-left: 0 }
+ 1, from { margin-left: 0 }
+ from, 1.0 { margin-left: 0 }
+ 1.0 { margin-left: 0 }
+ 1.0, from { margin-left: 0 }
+
+ 25% {
+ margin-left: 25px;
+ }
+
+ 75%, 85% {
+ margin-left: 90px;
+ }
+
+ 100% {
+ margin-left: 100px;
+ }
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=577974">Mozilla Bug 577974</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 577974 **/
+
+var sheet = document.getElementById("style").sheet;
+
+var bounce = sheet.cssRules[0];
+is(bounce.type, CSSRule.KEYFRAMES_RULE, "bounce.type");
+is(bounce.type, 7, "bounce.type");
+is(bounce.name, "bounce", "bounce.name");
+bounce.name = "bouncier";
+is(bounce.name, "bouncier", "setting bounce.name");
+
+is(bounce.cssRules[0].type, CSSRule.KEYFRAME_RULE, "keyframe rule type");
+is(bounce.cssRules[0].type, 8, "keyframe rule type");
+is(bounce.cssRules[0].keyText, "0%", "keyframe rule keyText");
+is(bounce.cssRules[1].keyText, "25%", "keyframe rule keyText");
+is(bounce.cssRules[2].keyText, "75%, 85%", "keyframe rule keyText");
+is(bounce.cssRules[3].keyText, "100%", "keyframe rule keyText");
+is(bounce.cssRules[0].style.marginLeft, "0px", "keyframe rule style");
+is(bounce.cssRules[1].style.marginLeft, "25px", "keyframe rule style");
+
+is(bounce.cssRules[0].cssText, "0% { margin-left: 0px; }");
+is(bounce.cssText, "@keyframes bouncier {\n" +
+ "0% { margin-left: 0px; }\n" +
+ "25% { margin-left: 25px; }\n" +
+ "75%, 85% { margin-left: 90px; }\n" +
+ "100% { margin-left: 100px; }\n" +
+ "}");
+
+bounce.cssRules[1].keyText = "from, 1"; // syntax error
+bounce.cssRules[1].keyText = "from, x"; // syntax error
+bounce.cssRules[1].keyText = "from,"; // syntax error
+bounce.cssRules[1].keyText = "from x"; // syntax error
+bounce.cssRules[1].keyText = "x"; // syntax error
+is(bounce.cssRules[1].keyText, "25%", "keyframe rule keyText parsing");
+bounce.cssRules[1].keyText = "from, 10%";
+is(bounce.cssRules[1].keyText, "0%, 10%", "keyframe rule keyText parsing");
+bounce.cssRules[1].keyText = "from, 0%";
+is(bounce.cssRules[1].keyText, "0%, 0%", "keyframe rule keyText parsing");
+bounce.cssRules[1].keyText = "from, from, from";
+is(bounce.cssRules[1].keyText, "0%, 0%, 0%", "keyframe rule keyText parsing");
+
+is(bounce.findRule("75%"), null, "findRule should match all keys");
+is(bounce.findRule("85%, 75%"), null,
+ "findRule should match all keys in order");
+is(bounce.findRule("75%,85%"), bounce.cssRules[2],
+ "findRule should match all keys in order, parsed");
+is(bounce.findRule("to"), bounce.cssRules[3],
+ "findRule should match keys as parsed");
+is(bounce.findRule("100%"), bounce.cssRules[3],
+ "findRule should match keys as parsed");
+is(bounce.findRule("100%, 100%"), null,
+ "findRule should match key list");
+is(bounce.findRule("100%,"), null,
+ "findRule should fail when given bad selector");
+is(bounce.findRule(",100%"), null,
+ "findRule should fail when given bad selector");
+is(bounce.cssRules.length, 4, "length of css rules");
+bounce.deleteRule("85%");
+is(bounce.cssRules.length, 4, "deleteRule should match all keys");
+bounce.deleteRule("85%, 75%");
+is(bounce.cssRules.length, 4, "deleteRule should match key list");
+bounce.deleteRule("75% ,85%");
+is(bounce.cssRules.length, 3, "deleteRule should match keys in order, parsed");
+bounce.deleteRule("0%");
+is(bounce.cssRules.length, 2, "deleteRule should match keys in order, parsed");
+bounce.appendRule("from { color: blue }");
+is(bounce.cssRules.length, 3, "appendRule should append");
+is(bounce.cssRules[2].keyText, "0%", "appendRule should append");
+bounce.appendRule("from { color: blue }");
+is(bounce.cssRules.length, 4, "appendRule should append");
+is(bounce.cssRules[3].keyText, "0%", "appendRule should append");
+bounce.appendRule("from { color: blue } to { color: green }");
+is(bounce.cssRules.length, 4, "appendRule should ignore garbage at end");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_keyframes_vendor_prefix.html b/layout/style/test/test_keyframes_vendor_prefix.html
new file mode 100644
index 0000000000..4463bd259a
--- /dev/null
+++ b/layout/style/test/test_keyframes_vendor_prefix.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>
+Test for interaction between prefixed and non-prefixed @keyframes rules with
+the same name
+</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<div id='log'></div>
+<script>
+/**
+ * Appends a style element to the document head.
+ *
+ * @param t The testharness.js Test object. If provided, this will be used
+ * to register a cleanup callback to remove the style element
+ * when the test finishes.
+ *
+ * @param rules A dictionary object with selector names and rules to set on
+ * the style sheet.
+ */
+function addStyle(t, rules) {
+ var extraStyle = document.createElement('style');
+ document.head.appendChild(extraStyle);
+ if (rules) {
+ var sheet = extraStyle.sheet;
+ for (var selector in rules) {
+ sheet.insertRule(selector + '{' + rules[selector] + '}',
+ sheet.cssRules.length);
+ }
+ }
+
+ if (t && typeof t.add_cleanup === 'function') {
+ t.add_cleanup(function() {
+ extraStyle.remove();
+ });
+ }
+}
+
+/**
+ * Appends a div to the document body.
+ *
+ * @param t The testharness.js Test object. If provided, this will be used
+ * to register a cleanup callback to remove the div when the test
+ * finishes.
+ *
+ * @param attrs A dictionary object with attribute names and values to set on
+ * the div.
+ */
+function addDiv(t, attrs) {
+ var div = document.createElement('div');
+ if (attrs) {
+ for (var attrName in attrs) {
+ div.setAttribute(attrName, attrs[attrName]);
+ }
+ }
+ document.body.appendChild(div);
+ if (t && typeof t.add_cleanup === 'function') {
+ t.add_cleanup(function() {
+ div.remove();
+ });
+ }
+ return div;
+}
+
+test(function(t) {
+ addStyle(t,
+ { '@-webkit-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-webkit- prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-moz-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-moz- prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-WEBKIT-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-WEBKIT- prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-MOZ-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-MOZ- prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-webkit-KEYFRAMES anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-webkit- prefix KEYFRAMES');
+
+test(function(t) {
+ addStyle(t,
+ { '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }',
+ '@-webkit-keyframes anim': 'from,to { color: rgb(255, 0, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-webkit-keyframes should not override earlier non-prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }',
+ '@-moz-keyframes anim': 'from,to { color: rgb(255, 0, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-moz-keyframes should not override earlier non-prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-moz-keyframes anim': 'from,to { color: rgb(255, 0, 0); }',
+ '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, 'non-prefix keyframes should override earlier -moz-keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-webkit-keyframes anim': 'from,to { color: rgb(255, 0, 0); }',
+ '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, 'non-prefix keyframes should override earlier -webkit-keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-webkit-keyframes anim': 'from,to { color: rgb(255, 0, 0); }',
+ '@-moz-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+
+ addStyle(t,
+ { '@-moz-keyframes anim2': 'from,to { color: rgb(255, 0, 0); }',
+ '@-webkit-keyframes anim2': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim2 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, 'last prefixed keyframes should override earlier prefixed keyframes');
+</script>
diff --git a/layout/style/test/test_load_events_on_stylesheets.html b/layout/style/test/test_load_events_on_stylesheets.html
new file mode 100644
index 0000000000..7344e27b22
--- /dev/null
+++ b/layout/style/test/test_load_events_on_stylesheets.html
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=185236
+-->
+<head>
+ <title>Test for Bug 185236</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script>
+ var pendingEventCounter = 0;
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ is(pendingEventCounter, 0,
+ "How did onload for the page fire before onload for all the stylesheets?");
+ SimpleTest.finish();
+ });
+ // Count the link we're about to parse
+ pendingEventCounter = 1;
+ </script>
+ <link rel="stylesheet" href="data:text/css,*{}"
+ onload="--pendingEventCounter;
+ ok(true, 'Load event firing on basic stylesheet')"
+ onerror="--pendingEventCounter;
+ ok(false, 'Error event firing on basic stylesheet')">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=185236">Mozilla Bug 185236</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 185236 **/
+
+function checkSheetComplete(sheet, length) {
+ try {
+ is(sheet.cssRules.length, length, `Should be loaded with ${length} rule(s)`);
+ } catch (e) {
+ ok(false, "Sheet has not been loaded completely");
+ }
+}
+
+
+// There should be usually 1 pending event but the load event might have fired
+// if the parser yields between the link and starting the script execution.
+const pendingEventCounterAtStart = pendingEventCounter;
+
+ok(pendingEventCounter <= 1, `There should be at most one pending event, got ${pendingEventCounterAtStart}`);
+
+is(document.styleSheets.length, 2, "Should have two stylesheets");
+checkSheetComplete(document.styleSheets[1], 1);
+
+// Test sheet that will already be complete when we write it out
+++pendingEventCounter;
+document.write('<link rel="stylesheet" href="data:text/css,*{}"\
+ onload="--pendingEventCounter;\
+ ok(true, \'Load event firing on basic stylesheet\')"\
+ onerror="--pendingEventCounter;\
+ ok(false, \'Error event firing on basic stylesheet\')">');
+
+// Make sure we have that second stylesheet
+is(document.styleSheets.length, 3, "Should have three stylesheets");
+
+// Make sure that the second stylesheet is all loaded
+// If we ever switch away from sync loading of already-complete sheets, this
+// test will need adjusting
+checkSheetComplete(document.styleSheets[2], 1);
+
+// Make sure the load event for that stylesheet has not fired yet
+is(pendingEventCounter, pendingEventCounterAtStart + 1, "After first document write");
+++pendingEventCounter;
+
+document.write('<style\
+ onload="--pendingEventCounter;\
+ ok(true, \'Load event firing on inline stylesheet\')"\
+ onerror="--pendingEventCounter;\
+ ok(false, \'Error event firing on inline stylesheet\')"></style>');
+
+// Make sure the load event for that second stylesheet has not fired yet
+is(pendingEventCounter, pendingEventCounterAtStart + 2, "after second document write");
+++pendingEventCounter;
+
+document.write('<link rel="stylesheet" href="http://www.example.com"\
+ onload="--pendingEventCounter;\
+ ok(false, \'Load event firing on broken stylesheet 1\')"\
+ onerror="--pendingEventCounter;\
+ ok(true, \'Error event firing on broken stylesheet 1\')">');
+++pendingEventCounter;
+
+var link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "http://www.example.com";
+link.onload = function() { --pendingEventCounter;
+ ok(false, 'Load event firing on broken stylesheet 2');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet 2');
+}
+document.body.appendChild(link);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "data:text/css,*{}";
+link.onload = function() { --pendingEventCounter;
+ ok(true, 'Load event firing on external stylesheet');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(false, 'Error event firing on external stylesheet');
+}
+document.body.appendChild(link);
+
+// If we ever switch away from sync loading of already-complete sheets, this
+// test will need adjusting
+checkSheetComplete(link.sheet, 1);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "data:text/css,@import url('data:text/css,*{}')";
+link.onload = function() { --pendingEventCounter;
+ ok(true, 'Load event firing on external stylesheet');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(false, 'Error event firing on external stylesheet');
+}
+document.body.appendChild(link);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "data:text/css,@import url('http://www.example.com')";
+link.onload = function() { --pendingEventCounter;
+ ok(false, 'Load event firing on broken stylesheet 3');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet 3');
+}
+document.body.appendChild(link);
+
+function absoluteURL(relativeURL) {
+ return new URL(relativeURL, location.href).href;
+}
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = `data:text/css,@import url('http://www.example.com'); @import url(${absoluteURL('slow_ok_sheet.sjs')});`;
+link.onload = function() { --pendingEventCounter;
+ ok(false, 'Load event firing on broken stylesheet 4');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet 4');
+}
+document.body.appendChild(link);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = `data:text/css,@import url(${absoluteURL('slow_broken_sheet.sjs')}); @import url('data:text/css,');`;
+link.onload = function() { --pendingEventCounter;
+ ok(false, 'Load event firing on broken stylesheet 5');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet 5');
+}
+document.body.appendChild(link);
+
+// Make sure the load events for all those stylesheets have not fired yet
+is(pendingEventCounter, pendingEventCounterAtStart + 9, "There should be nine more pending events");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_logical_properties.html b/layout/style/test/test_logical_properties.html
new file mode 100644
index 0000000000..a6947791cb
--- /dev/null
+++ b/layout/style/test/test_logical_properties.html
@@ -0,0 +1,422 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for handling of logical and physical properties</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<style id="sheet"></style>
+
+<!-- specify size for <body> to avoid unconstrained-isize warnings
+ when writing-mode of the test <div> is vertical-* -->
+<body style="width:100px; height: 100px;">
+ <div id="test" class="test"></div>
+</body>
+
+<script>
+var gSheet = document.getElementById("sheet");
+var gTest = document.getElementById("test");
+
+// list of groups of physical and logical box properties, such as
+//
+// { left: "margin-left", right: "margin-right",
+// top: "margin-top", bottom: "margin-bottom",
+// inlineStart: "margin-inline-start", inlineEnd: "margin-inline-end",
+// blockStart: "margin-block-start", blockEnd: "margin-block-end",
+// type: "length", prerequisites: "..." }
+//
+// where the type is a key from the gValues object and the prerequisites
+// is a declaration including gCSSProperties' listed prerequisites for
+// all four physical properties.
+var gBoxPropertyGroups;
+
+// list of groups of physical and logical axis properties, such as
+//
+// { horizontal: "width", vertical: "height",
+// inline: "inline-size", block: "block-size",
+// type: "length", prerequisites: "..." }
+var gAxisPropertyGroups;
+
+// values to use while testing
+var gValues = {
+ "length": ["1px", "2px", "3px", "4px", "5px"],
+ "color": ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"],
+ "border-style": ["solid", "dashed", "dotted", "double", "groove"],
+};
+
+// Six unique overall writing modes for property-mapping purposes.
+// Note that text-orientation does not affect these mappings, now that
+// the proposed sideways-left value no longer exists (superseded in CSS
+// Writing Modes by writing-mode: sideways-lr).
+var gWritingModes = [
+ { style: [
+ "writing-mode: horizontal-tb; direction: ltr; ",
+ ],
+ blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right",
+ block: "vertical", inline: "horizontal" },
+ { style: [
+ "writing-mode: horizontal-tb; direction: rtl; ",
+ ],
+ blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left",
+ block: "vertical", inline: "horizontal" },
+ { style: [
+ "writing-mode: vertical-rl; direction: rtl; ",
+ "writing-mode: sideways-rl; direction: rtl; ",
+ ],
+ blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top",
+ block: "horizontal", inline: "vertical" },
+ { style: [
+ "writing-mode: vertical-rl; direction: ltr; ",
+ "writing-mode: sideways-rl; direction: ltr; ",
+ ],
+ blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom",
+ block: "horizontal", inline: "vertical" },
+ { style: [
+ "writing-mode: vertical-lr; direction: rtl; ",
+ "writing-mode: sideways-lr; direction: ltr; ",
+ ],
+ blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top",
+ block: "horizontal", inline: "vertical" },
+ { style: [
+ "writing-mode: vertical-lr; direction: ltr; ",
+ "writing-mode: sideways-lr; direction: rtl; ",
+ ],
+ blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom",
+ block: "horizontal", inline: "vertical" },
+];
+
+function init() {
+ gBoxPropertyGroups = [];
+
+ for (var p in gCSSProperties) {
+ var type = gCSSProperties[p].type;
+
+ if ((type == CSS_TYPE_SHORTHAND_AND_LONGHAND ||
+ type == CSS_TYPE_LONGHAND && gCSSProperties[p].logical) &&
+ /-inline-end/.test(p)) {
+ var valueType;
+ if (/margin|padding|width|inset|offset/.test(p)) {
+ valueType = "length";
+ } else if (/color/.test(p)) {
+ valueType = "color";
+ } else if (/border.*style/.test(p)) {
+ valueType = "border-style";
+ } else {
+ throw `unexpected property ${p}`;
+ }
+ var group = {
+ inlineStart: p.replace("-inline-end", "-inline-start"),
+ inlineEnd: p,
+ blockStart: p.replace("-inline-end", "-block-start"),
+ blockEnd: p.replace("-inline-end", "-block-end"),
+ type: valueType
+ };
+ if (/^(offset|inset)/.test(p)) {
+ group.left = "left";
+ group.right = "right";
+ group.top = "top";
+ group.bottom = "bottom";
+ } else {
+ group.left = p.replace("-inline-end", "-left");
+ group.right = p.replace("-inline-end", "-right");
+ group.top = p.replace("-inline-end", "-top");
+ group.bottom = p.replace("-inline-end", "-bottom");
+ }
+ group.prerequisites =
+ make_declaration(gCSSProperties[group.top].prerequisites) +
+ make_declaration(gCSSProperties[group.right].prerequisites) +
+ make_declaration(gCSSProperties[group.bottom].prerequisites) +
+ make_declaration(gCSSProperties[group.left].prerequisites);
+ gBoxPropertyGroups.push(group);
+ }
+ }
+
+ // We don't populate this automatically since the only entries we have, for
+ // inline-size etc., don't lend themselves to automatically determining
+ // the names "width", "height", "min-width", etc.
+ gAxisPropertyGroups = [];
+ ["", "max-", "min-"].forEach(function(aPrefix) {
+ gAxisPropertyGroups.push({
+ horizontal: `${aPrefix}width`, vertical: `${aPrefix}height`,
+ inline: `${aPrefix}inline-size`, block: `${aPrefix}block-size`,
+ type: "length",
+ prerequisites:
+ make_declaration(gCSSProperties[`${aPrefix}height`].prerequisites)
+ });
+ });
+}
+
+function test_computed_values(aTestName, aRules, aExpectedValues) {
+ gSheet.textContent = aRules;
+ var cs = getComputedStyle(gTest);
+ aExpectedValues.forEach(function(aPair) {
+ is(cs.getPropertyValue(aPair[0]), aPair[1], `${aTestName}, ${aPair[0]}`);
+ });
+ gSheet.textContent = "";
+}
+
+function make_declaration(aObject) {
+ var decl = "";
+ if (aObject) {
+ for (var p in aObject) {
+ decl += `${p}: ${aObject[p]}; `;
+ }
+ }
+ return decl;
+}
+
+function start() {
+ var script = document.createElement("script");
+ script.src = "property_database.js";
+ script.onload = function() {
+ init();
+ run_tests();
+ };
+ document.body.appendChild(script);
+}
+
+function run_axis_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl) {
+ var values = gValues[aGroup.type];
+ var decl;
+
+ // Test that logical axis properties are converted to their physical
+ // equivalent correctly when all four are present on a single
+ // declaration, with no overwriting of previous properties and
+ // no physical properties present. We put the writing mode properties
+ // on a separate declaration to test that the computed values of these
+ // properties are used, rather than those on the same declaration.
+
+ decl = aGroup.prerequisites +
+ `${aGroup.inline}: ${values[0]}; ` +
+ `${aGroup.block}: ${values[1]}; `;
+ test_computed_values('logical properties on one declaration, writing ' +
+ 'mode properties on another, ' +
+ `'${aWritingModeDecl}'`,
+ `.test { ${aWritingModeDecl} } ` +
+ `.test { ${decl} }`,
+ [[aGroup[aWritingMode.inline], values[0]],
+ [aGroup[aWritingMode.block], values[1]]]);
+
+
+ // Test that logical and physical axis properties are cascaded together,
+ // honoring their relative order on a single declaration.
+
+ // (a) with a single logical property after the physical ones
+
+ ["inline", "block"].forEach(function(aLogicalAxis) {
+ decl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.horizontal}: ${values[0]}; ` +
+ `${aGroup.vertical}: ${values[1]}; ` +
+ `${aGroup[aLogicalAxis]}: ${values[2]}; `;
+ var expected = ["horizontal", "vertical"].map(
+ (axis, i) => [aGroup[axis],
+ values[axis == aWritingMode[aLogicalAxis] ? 2 : i]]
+ );
+ test_computed_values(`${aLogicalAxis} last on single declaration, ` +
+ `'${aWritingModeDecl}'`,
+ `.test { ${decl} }`,
+ expected);
+ });
+
+ // (b) with a single physical property after the logical ones
+
+ ["horizontal", "vertical"].forEach(function(aPhysicalAxis) {
+ decl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.inline}: ${values[0]}; ` +
+ `${aGroup.block}: ${values[1]}; ` +
+ `${aGroup[aPhysicalAxis]}: ${values[2]}; `;
+ var expected = ["inline", "block"].map(
+ (axis, i) => [aGroup[aWritingMode[axis]],
+ values[aWritingMode[axis] == aPhysicalAxis ? 2 : i]]
+ );
+ test_computed_values(`${aPhysicalAxis} last on single declaration, ` +
+ `'${aWritingModeDecl}'`,
+ `.test { ${decl} }`,
+ expected);
+ });
+
+
+ // Test that logical and physical axis properties are cascaded properly when
+ // on different declarations.
+
+ var loDecl; // lower specifity
+ var hiDecl; // higher specificity
+
+ // (a) with a logical property in the high specificity rule
+
+ loDecl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.horizontal}: ${values[0]}; ` +
+ `${aGroup.vertical}: ${values[1]}; `;
+
+ ["inline", "block"].forEach(function(aLogicalAxis) {
+ hiDecl = `${aGroup[aLogicalAxis]}: ${values[2]}; `;
+ var expected = ["horizontal", "vertical"].map(
+ (axis, i) => [aGroup[axis],
+ values[axis == aWritingMode[aLogicalAxis] ? 2 : i]]
+ );
+ test_computed_values(`${aLogicalAxis}, two declarations, ` +
+ `'${aWritingModeDecl}'`,
+ `#test { ${hiDecl} } ` +
+ `.test { ${loDecl} }`,
+ expected);
+ });
+
+ // (b) with a physical property in the high specificity rule
+
+ loDecl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.inline}: ${values[0]}; ` +
+ `${aGroup.block}: ${values[1]}; `;
+
+ ["horizontal", "vertical"].forEach(function(aPhysicalAxis) {
+ hiDecl = `${aGroup[aPhysicalAxis]}: ${values[2]}; `;
+ var expected = ["inline", "block"].map(
+ (axis, i) => [aGroup[aWritingMode[axis]],
+ values[aWritingMode[axis] == aPhysicalAxis ? 2 : i]]
+ );
+ test_computed_values(`${aPhysicalAxis}, two declarations, ` +
+ `'${aWritingModeDecl}'`,
+ `#test { ${hiDecl} } ` +
+ `.test { ${loDecl} }`,
+ expected);
+ });
+}
+
+function run_box_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl) {
+ var values = gValues[aGroup.type];
+ var decl;
+
+ // Test that logical box properties are converted to their physical
+ // equivalent correctly when all four are present on a single
+ // declaration, with no overwriting of previous properties and
+ // no physical properties present. We put the writing mode properties
+ // on a separate declaration to test that the computed values of these
+ // properties are used, rather than those on the same declaration.
+
+ decl = aGroup.prerequisites +
+ `${aGroup.inlineStart}: ${values[0]}; ` +
+ `${aGroup.inlineEnd}: ${values[1]}; ` +
+ `${aGroup.blockStart}: ${values[2]}; ` +
+ `${aGroup.blockEnd}: ${values[3]}; `;
+ test_computed_values('logical properties on one declaration, writing ' +
+ 'mode properties on another, ' +
+ `'${aWritingModeDecl}'`,
+ `.test { ${aWritingModeDecl} } ` +
+ `.test { ${decl} }`,
+ [[aGroup[aWritingMode.inlineStart], values[0]],
+ [aGroup[aWritingMode.inlineEnd], values[1]],
+ [aGroup[aWritingMode.blockStart], values[2]],
+ [aGroup[aWritingMode.blockEnd], values[3]]]);
+
+ // Test that logical and physical box properties are cascaded together,
+ // honoring their relative order on a single declaration.
+
+ // (a) with a single logical property after the physical ones
+
+ ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].forEach(function(aLogicalSide) {
+ decl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.left}: ${values[0]}; ` +
+ `${aGroup.right}: ${values[1]}; ` +
+ `${aGroup.top}: ${values[2]}; ` +
+ `${aGroup.bottom}: ${values[3]}; ` +
+ `${aGroup[aLogicalSide]}: ${values[4]}; `;
+ var expected = ["left", "right", "top", "bottom"].map(
+ (side, i) => [aGroup[side],
+ values[side == aWritingMode[aLogicalSide] ? 4 : i]]
+ );
+ test_computed_values(`${aLogicalSide} last on single declaration, ` +
+ `'${aWritingModeDecl}'`,
+ `.test { ${decl} }`,
+ expected);
+ });
+
+ // (b) with a single physical property after the logical ones
+
+ ["left", "right", "top", "bottom"].forEach(function(aPhysicalSide) {
+ decl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.inlineStart}: ${values[0]}; ` +
+ `${aGroup.inlineEnd}: ${values[1]}; ` +
+ `${aGroup.blockStart}: ${values[2]}; ` +
+ `${aGroup.blockEnd}: ${values[3]}; ` +
+ `${aGroup[aPhysicalSide]}: ${values[4]}; `;
+ var expected = ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].map(
+ (side, i) => [aGroup[aWritingMode[side]],
+ values[aWritingMode[side] == aPhysicalSide ? 4 : i]]
+ );
+ test_computed_values(`${aPhysicalSide} last on single declaration, ` +
+ `'${aWritingModeDecl}'`,
+ `.test { ${decl} }`,
+ expected);
+ });
+
+
+ // Test that logical and physical box properties are cascaded properly when
+ // on different declarations.
+
+ var loDecl; // lower specifity
+ var hiDecl; // higher specificity
+
+ // (a) with a logical property in the high specificity rule
+
+ loDecl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.left}: ${values[0]}; ` +
+ `${aGroup.right}: ${values[1]}; ` +
+ `${aGroup.top}: ${values[2]}; ` +
+ `${aGroup.bottom}: ${values[3]}; `;
+
+ ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].forEach(function(aLogicalSide) {
+ hiDecl = `${aGroup[aLogicalSide]}: ${values[4]}; `;
+ var expected = ["left", "right", "top", "bottom"].map(
+ (side, i) => [aGroup[side],
+ values[side == aWritingMode[aLogicalSide] ? 4 : i]]
+ );
+ test_computed_values(`${aLogicalSide}, two declarations, ` +
+ `'${aWritingModeDecl}'`,
+ `#test { ${hiDecl} } ` +
+ `.test { ${loDecl} }`,
+ expected);
+ });
+
+ // (b) with a physical property in the high specificity rule
+
+ loDecl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.inlineStart}: ${values[0]}; ` +
+ `${aGroup.inlineEnd}: ${values[1]}; ` +
+ `${aGroup.blockStart}: ${values[2]}; ` +
+ `${aGroup.blockEnd}: ${values[3]}; `;
+
+ ["left", "right", "top", "bottom"].forEach(function(aPhysicalSide) {
+ hiDecl = `${aGroup[aPhysicalSide]}: ${values[4]}; `;
+ var expected = ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].map(
+ (side, i) => [aGroup[aWritingMode[side]],
+ values[aWritingMode[side] == aPhysicalSide ? 4 : i]]
+ );
+ test_computed_values(`${aPhysicalSide}, two declarations, ` +
+ `'${aWritingModeDecl}'`,
+ `#test { ${hiDecl} } ` +
+ `.test { ${loDecl} }`,
+ expected);
+ });
+}
+
+function run_tests() {
+ gBoxPropertyGroups.forEach(function(aGroup) {
+ gWritingModes.forEach(function(aWritingMode) {
+ aWritingMode.style.forEach(function(aWritingModeDecl) {
+ run_box_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl);
+ });
+ });
+ });
+
+ gAxisPropertyGroups.forEach(function(aGroup) {
+ gWritingModes.forEach(function(aWritingMode) {
+ aWritingMode.style.forEach(function(aWritingModeDecl) {
+ run_axis_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl);
+ });
+ });
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+start();
+</script>
diff --git a/layout/style/test/test_marker_restrictions.html b/layout/style/test/test_marker_restrictions.html
new file mode 100644
index 0000000000..f547f928cb
--- /dev/null
+++ b/layout/style/test/test_marker_restrictions.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for ::marker property restrictions.</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="property_database.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<style id="s"></style>
+<div id="test"></div>
+<div id="control"></div>
+<script>
+const test = getComputedStyle($("test"), "::marker");
+const control = getComputedStyle($("control"), "::marker");
+
+for (const prop in gCSSProperties) {
+ const info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND)
+ continue;
+
+ let prereqs = "";
+ if (info.prerequisites)
+ for (let name in info.prerequisites)
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+
+ $("s").textContent = `
+ #control::marker { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::marker { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+
+ (info.applies_to_marker ? isnot : is)(
+ get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should ${info.applies_to_marker ? "" : " not"} apply to ::marker`);
+}
+
+</script>
diff --git a/layout/style/test/test_mask_image_CORS.html b/layout/style/test/test_mask_image_CORS.html
new file mode 100644
index 0000000000..8edd8af48e
--- /dev/null
+++ b/layout/style/test/test_mask_image_CORS.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test mask-image CORS anonymous retrieval</title>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+<style>
+.block100 {
+ width: 100px;
+ height: 100px;
+}
+#allow {
+ /*
+ * shape-outside is unnecessary for the mask, but using it ensures that the first frame
+ * of the image is decoded and reflow is called before onload is fired. Since the
+ * shape-outside uses the same url as the mask, this ensures that the css image resource
+ * is decoded and available for the repaint triggered by the call to snapshotRect.
+ */
+ shape-outside: url("support/blue-100x100.png");
+ mask-image: url("support/blue-100x100.png");
+ background-color: #00FF00
+}
+#refuse {
+ shape-outside: url("http://example.com/tests/layout/style/test/support/blue-100x100.png");
+ mask-image: url("http://example.com/tests/layout/style/test/support/blue-100x100.png");
+ background-color: #FF0000
+}
+</style>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function checkBothSquares() {
+ checkIsColor("allow", "0,255,0,255");
+ checkIsColor("refuse", "255,255,255,255");
+
+ SimpleTest.finish();
+}
+
+function checkIsColor(elementId, color) {
+ let e = document.getElementById(elementId);
+ let r = e.getBoundingClientRect();
+ info("Element " + elementId + " has rect " + r.top + ", " + r.left + ", " + r.width + ", " + r.height + ".");
+
+ let canvas = snapshotRect(window, r);
+ let context = canvas.getContext('2d');
+
+ // Only check the top left pixel.
+ let image = context.getImageData(0, 0, 1, 1);
+ let pixel = image.data.toString();
+ is(pixel, color, "Element " + elementId + " has expected color.");
+}
+</script>
+
+</head>
+<body onload="checkBothSquares()">
+ <p>There should be a green square, but no red square.</p>
+ <div id="allow" class="block100"></div>
+ <div id="refuse" class="block100"></div>
+</body>
+</html>
diff --git a/layout/style/test/test_media_queries.html b/layout/style/test/test_media_queries.html
new file mode 100644
index 0000000000..441fc1105a
--- /dev/null
+++ b/layout/style/test/test_media_queries.html
@@ -0,0 +1,867 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=156716
+-->
+<head>
+ <title>Test for Bug 156716</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome/chrome-only-media-queries.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=156716">Mozilla Bug 156716</a>
+<iframe id="subdoc" src="media_queries_iframe.html"></iframe>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 156716 **/
+
+// Note that many other tests are in test_acid3_test46.html .
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+var iframe;
+
+function getScreenPixelsPerCSSPixel() {
+ return window.devicePixelRatio;
+}
+
+function run() {
+ iframe = document.getElementById("subdoc");
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+ var style = subdoc.getElementById("style");
+ var iframe_style = iframe.style;
+ var body_cs = subdoc.defaultView.getComputedStyle(subdoc.body);
+
+ function query_applies(q) {
+ style.setAttribute("media", q);
+ return body_cs.getPropertyValue("text-decoration-line") == "underline";
+ }
+
+ function should_apply(q) {
+ ok(query_applies(q), q + " should apply");
+ test_serialization(q, true, true);
+ }
+
+ function should_not_apply(q) {
+ ok(!query_applies(q), q + " should not apply");
+ test_serialization(q, true, false);
+ }
+
+ /* for queries that are parseable standalone but not within CSS */
+ function should_apply_unbalanced(q) {
+ ok(query_applies(q), q + " should apply");
+ }
+
+ /* for queries that are parseable standalone but not within CSS */
+ function should_not_apply_unbalanced(q) {
+ ok(!query_applies(q), q + " should not apply");
+ }
+
+ /*
+ * Functions to test whether a query is parseable at all. (Should not
+ * be used for parse errors within expressions.)
+ */
+ var parse_test_style_element = document.createElement("style");
+ parse_test_style_element.type = "text/css";
+ parse_test_style_element.disabled = true; // for performance, hopefully
+ var parse_test_style_text = document.createTextNode("");
+ parse_test_style_element.appendChild(parse_test_style_text);
+ document.getElementsByTagName("head")[0]
+ .appendChild(parse_test_style_element);
+
+ function query_is_parseable(q) {
+ parse_test_style_text.data = "@media screen, " + q + " {}";
+ var sheet = parse_test_style_element.sheet; // XXX yikes, not live!
+ if (sheet.cssRules.length == 1 &&
+ sheet.cssRules[0].type == CSSRule.MEDIA_RULE)
+ return sheet.cssRules[0].media.mediaText != "screen, not all";
+ ok(false, "unexpected result testing whether query " + q +
+ " is parseable");
+ return true; // doesn't matter, we already failed
+ }
+
+ function query_should_be_parseable(q) {
+ ok(query_is_parseable(q), "query " + q + " should be parseable");
+ test_serialization(q, false, false);
+ }
+
+ function query_should_not_be_parseable(q) {
+ ok(!query_is_parseable(q), "query " + q + " should not be parseable");
+ }
+
+ function expression_should_be_known(e) {
+ should_apply(`(${e}) or (not (${e}))`);
+ }
+
+ function expression_should_not_be_known(e) {
+ should_not_apply(`(${e}) or (not (${e}))`);
+ }
+
+ // Helper to share code between -moz & -webkit device-pixel-ratio versions:
+ function test_device_pixel_ratio(equal_name, min_name, max_name) {
+ var real_dpr = 1.0 * getScreenPixelsPerCSSPixel();
+ var high_dpr = 1.1 * getScreenPixelsPerCSSPixel();
+ var low_dpr = 0.9 * getScreenPixelsPerCSSPixel();
+ should_apply("all and (" + max_name + ": " + real_dpr + ")");
+ should_apply("all and (" + min_name + ": " + real_dpr + ")");
+ should_not_apply("not all and (" + max_name + ": " + real_dpr + ")");
+ should_not_apply("not all and (" + min_name + ": " + real_dpr + ")");
+ should_apply("all and (" + min_name + ": " + low_dpr + ")");
+ should_apply("all and (" + max_name + ": " + high_dpr + ")");
+ should_not_apply("all and (" + max_name + ": " + low_dpr + ")");
+ should_not_apply("all and (" + min_name + ": " + high_dpr + ")");
+ should_apply("not all and (" + max_name + ": " + low_dpr + ")");
+ should_apply("not all and (" + min_name + ": " + high_dpr + ")");
+ should_apply("(" + equal_name + ": " + real_dpr + ")");
+ should_not_apply("(" + equal_name + ": " + high_dpr + ")");
+ should_not_apply("(" + equal_name + ": " + low_dpr + ")");
+ should_apply("(" + equal_name + ")");
+ expression_should_not_be_known(min_name);
+ expression_should_not_be_known(max_name);
+ }
+
+ function test_serialization(q, test_application, expected_to_apply) {
+ style.setAttribute("media", q);
+ var ser1 = style.sheet.media.mediaText;
+ isnot(ser1, "", "serialization of '" + q + "' should not be empty");
+ style.setAttribute("media", ser1);
+ var ser2 = style.sheet.media.mediaText;
+ is(ser2, ser1, "parse+serialize of '" + q + "' should be idempotent");
+ if (test_application) {
+ let applies = body_cs.getPropertyValue("text-decoration-line") ==
+ "underline";
+ is(applies, expected_to_apply,
+ "Media query '" + q + "' should " + (expected_to_apply ? "" : "NOT ") +
+ "apply after serialize + reparse");
+ }
+
+ // Test cloning
+ var sheet = "@media " + q + " { body { text-decoration: underline } }"
+ var sheeturl = "data:text/css," + escape(sheet);
+ var link = "<link rel='stylesheet' href='" + sheeturl + "'>";
+ var htmldoc = "<!DOCTYPE HTML>" + link + link + "<body>";
+ post_clone_test(htmldoc, function() {
+ var clonedoc = iframe.contentDocument;
+ var clonewin = iframe.contentWindow;
+ var links = clonedoc.getElementsByTagName("link");
+ // cause a clone
+ var clonedsheet = links[1].sheet;
+ clonedsheet.insertRule("#nonexistent { color: purple}", 1);
+ // remove the uncloned sheet
+ links[0].remove();
+
+ var ser3 = clonedsheet.cssRules[0].media.mediaText;
+ is(ser3, ser1, "cloning query '" + q + "' should not change " +
+ "serialization");
+ if (test_application) {
+ let applies = clonewin.getComputedStyle(clonedoc.body).
+ textDecorationLine == "underline";
+ is(applies, expected_to_apply,
+ "Media query '" + q + "' should " + (expected_to_apply ? "" : "NOT ") +
+ "apply after cloning");
+ }
+ });
+ }
+
+ // The no-type syntax doesn't mix with the not and only keywords.
+ expression_should_be_known("(orientation)");
+ expression_should_be_known("not (orientation)");
+ query_should_not_be_parseable("only (orientation)");
+ query_should_be_parseable("all and (orientation)");
+ query_should_be_parseable("not all and (orientation)");
+ query_should_be_parseable("only all and (orientation)");
+
+ query_should_not_be_parseable("not not (orientation)");
+ expression_should_be_known("(orientation) and (orientation)");
+ expression_should_be_known("(orientation) or (orientation)");
+ expression_should_be_known("(orientation) or ((orientation) and ((orientation) or (orientation) or (not (orientation))))");
+
+ query_should_not_be_parseable("all and (orientation) or (orientation)");
+ query_should_be_parseable("all and (orientation) and (orientation)");
+
+ query_should_not_be_parseable("(orientation) and (orientation) or (orientation)");
+ query_should_not_be_parseable("(orientation) and not (orientation)");
+
+ query_should_be_parseable("(-moz-device-orientation)");
+ query_should_be_parseable("not (-moz-device-orientation)");
+ query_should_not_be_parseable("only (-moz-device-orientation)");
+ query_should_be_parseable("all and (-moz-device-orientation)");
+ query_should_be_parseable("not all and (-moz-device-orientation)");
+ query_should_be_parseable("only all and (-moz-device-orientation)");
+
+ // Test that the 'not', 'only', 'and', and 'or' keywords are not
+ // allowed as media types.
+ query_should_not_be_parseable("not");
+ query_should_not_be_parseable("and");
+ query_should_not_be_parseable("or");
+ query_should_not_be_parseable("only");
+ query_should_be_parseable("unknowntype");
+ query_should_not_be_parseable("not not");
+ query_should_not_be_parseable("not and");
+ query_should_not_be_parseable("not or");
+ query_should_not_be_parseable("not only");
+ query_should_be_parseable("not unknowntype");
+ query_should_not_be_parseable("only not");
+ query_should_not_be_parseable("only and");
+ query_should_not_be_parseable("only or");
+ query_should_not_be_parseable("only only");
+ query_should_be_parseable("only unknowntype");
+ query_should_not_be_parseable("not and (width)");
+ query_should_not_be_parseable("and and (width)");
+ query_should_not_be_parseable("or and (width)");
+ query_should_not_be_parseable("only and (width)");
+ query_should_be_parseable("unknowntype and (width)");
+ query_should_not_be_parseable("not not and (width)");
+ query_should_not_be_parseable("not and and (width)");
+ query_should_not_be_parseable("not or and (width)");
+ query_should_not_be_parseable("not only and (width)");
+ query_should_be_parseable("not unknowntype and (width)");
+ query_should_not_be_parseable("only not and (width)");
+ query_should_not_be_parseable("only and and (width)");
+ query_should_not_be_parseable("only or and (width)");
+ query_should_not_be_parseable("only only and (width)");
+ query_should_be_parseable("only unknowntype and (width)");
+
+ var features = [ "width", "height", "device-width", "device-height" ];
+ var separators = [ ":", ">", ">=", "=", "<=", "<" ];
+
+ var feature;
+ var i;
+ for (i in features) {
+ feature = features[i];
+ expression_should_be_known(feature);
+ expression_should_not_be_known("min-" + feature);
+ expression_should_not_be_known("max-" + feature);
+ for (let separator of separators) {
+ expression_should_be_known(feature + " " + separator + " 0");
+ expression_should_be_known(feature + " " + separator + " 0px");
+ expression_should_be_known(feature + " " + separator + " 0em");
+ expression_should_be_known(feature + " " + separator + " -0");
+ expression_should_be_known(feature + " " + separator + " -0cm");
+ expression_should_be_known(feature + " " + separator + " 1px");
+ expression_should_be_known(feature + " " + separator + " 0.001mm");
+ expression_should_be_known(feature + " " + separator + " 100000px");
+ expression_should_be_known(feature + " " + separator + " -1px");
+ if (separator == ":") {
+ expression_should_be_known("min-" + feature + " " + separator + " -0");
+ expression_should_be_known("max-" + feature + " " + separator + " -0");
+ expression_should_be_known("min-" + feature + " " + separator + " -1px");
+ expression_should_be_known("max-" + feature + " " + separator + " -1px");
+ expression_should_be_known(feature + " " + separator + " -0.00001mm");
+ expression_should_be_known(feature + " " + separator + " -100000em");
+ } else {
+ expression_should_not_be_known("min-" + feature + " " + separator + " -0");
+ expression_should_not_be_known("max-" + feature + " " + separator + " -0");
+ expression_should_not_be_known("min-" + feature + " " + separator + " -1px");
+ expression_should_not_be_known("max-" + feature + " " + separator + " -1px");
+ let multi_range = "0px " + separator + " " + feature + " " + separator + " 100000px"
+ if (separator == "=") {
+ expression_should_not_be_known(multi_range);
+ } else {
+ expression_should_be_known(multi_range);
+ }
+ }
+ if (separator == ">=") {
+ expression_should_not_be_known(feature + " > = 0px");
+ } else if (separator == "<=") {
+ expression_should_not_be_known(feature + " < = 0px");
+ }
+ }
+ }
+
+ var mediatypes = ["browser", "minimal-ui", "standalone", "fullscreen"];
+
+ mediatypes.forEach(function(type) {
+ expression_should_be_known("display-mode: " + type);
+ });
+
+ expression_should_not_be_known("display-mode: invalid")
+
+ var content_div = document.getElementById("content");
+ content_div.style.font = "initial";
+ var em_size =
+ getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1];
+
+ // in this test, assume the common underlying implementation is correct
+ var width_val = 117; // pick two not-too-round numbers
+ var height_val = 76;
+ change_state(function() {
+ iframe_style.width = width_val + "px";
+ iframe_style.height = height_val + "px";
+ });
+ var device_width = window.screen.width;
+ var device_height = window.screen.height;
+ features = {
+ "width": width_val,
+ "height": height_val,
+ "device-width": device_width,
+ "device-height": device_height
+ };
+ for (feature in features) {
+ var value = features[feature];
+ should_apply("all and (" + feature + ": " + value + "px)");
+ should_apply("all and (" + feature + " = " + value + "px)");
+ should_not_apply("all and (" + feature + ": " + (value + 1) + "px)");
+ should_not_apply("all and (" + feature + ": " + (value - 1) + "px)");
+ should_not_apply("all and (" + feature + " = " + (value + 1) + "px)");
+ should_not_apply("all and (" + feature + " = " + (value - 1) + "px)");
+
+ should_apply("all and (min-" + feature + ": " + value + "px)");
+ should_not_apply("all and (min-" + feature + ": " + (value + 1) + "px)");
+ should_apply("all and (min-" + feature + ": " + (value - 1) + "px)");
+ should_apply("all and (max-" + feature + ": " + value + "px)");
+ should_apply("all and (max-" + feature + ": " + (value + 1) + "px)");
+ should_not_apply("all and (max-" + feature + ": " + (value - 1) + "px)");
+ should_not_apply("all and (min-" + feature + ": " +
+ (Math.ceil(value/em_size) + 1) + "em)");
+ should_apply("all and (min-" + feature + ": " +
+ (Math.floor(value/em_size) - 1) + "em)");
+ should_apply("all and (max-" + feature + ": " +
+ (Math.ceil(value/em_size) + 1) + "em)");
+ should_not_apply("all and (max-" + feature + ": " +
+ (Math.floor(value/em_size) - 1) + "em)");
+ should_not_apply("all and (min-" + feature + ": " +
+ (Math.ceil(value/em_size) + 1) + "rem)");
+ should_apply("all and (min-" + feature + ": " +
+ (Math.floor(value/em_size) - 1) + "rem)");
+ should_apply("all and (max-" + feature + ": " +
+ (Math.ceil(value/em_size) + 1) + "rem)");
+ should_not_apply("all and (max-" + feature + ": " +
+ (Math.floor(value/em_size) - 1) + "rem)");
+
+ should_apply("(" + feature + " <= " + value + "px)");
+ should_apply("(" + feature + " >= " + value + "px)");
+
+ should_apply("(0px < " + feature + " <= " + value + "px)");
+ should_apply("(" + value + "px >= " + feature + " > 0px)");
+
+ should_not_apply("(0px < " + feature + " < " + value + "px)");
+ should_not_apply("(" + value + "px > " + feature + " > 0px)");
+
+ should_not_apply("(" + feature + " < " + value + "px)");
+ should_not_apply("(" + feature + " > " + value + "px)");
+
+ should_apply("(" + feature + " < " + (value + 1) + "px)");
+ should_apply("(" + feature + " <= " + (value + 1) + "px)");
+ should_not_apply("(" + feature + " > " + (value + 1) + "px)");
+ should_not_apply("(" + feature + " >= " + (value + 1) + "px)");
+
+ should_apply("(" + feature + " > " + (value - 1) + "px)");
+ should_apply("(" + feature + " >= " + (value - 1) + "px)");
+ should_not_apply("(" + feature + " < " + (value - 1) + "px)");
+ should_not_apply("(" + feature + " <= " + (value - 1) + "px)");
+ }
+
+ change_state(function() {
+ iframe_style.width = "0";
+ });
+ should_apply("all and (height)");
+ should_not_apply("all and (width)");
+ change_state(function() {
+ iframe_style.height = "0";
+ });
+ should_not_apply("all and (height)");
+ should_not_apply("all and (width)");
+ should_apply("all and (device-height)");
+ should_apply("all and (device-width)");
+ change_state(function() {
+ iframe_style.width = width_val + "px";
+ });
+ should_not_apply("all and (height)");
+ should_apply("all and (width)");
+ change_state(function() {
+ iframe_style.height = height_val + "px";
+ });
+ should_apply("all and (height)");
+ should_apply("all and (width)");
+
+ // ratio that reduces to 59/40
+ change_state(function() {
+ iframe_style.width = "236px";
+ iframe_style.height = "160px";
+ });
+ expression_should_be_known("orientation");
+ expression_should_be_known("orientation: portrait");
+ expression_should_be_known("orientation: landscape");
+ expression_should_not_be_known("min-orientation");
+ expression_should_not_be_known("min-orientation: portrait");
+ expression_should_not_be_known("min-orientation: landscape");
+ expression_should_not_be_known("max-orientation");
+ expression_should_not_be_known("max-orientation: portrait");
+ expression_should_not_be_known("max-orientation: landscape");
+ should_apply("(orientation)");
+ should_apply("(orientation: landscape)");
+ should_not_apply("(orientation: portrait)");
+ should_apply("not all and (orientation: portrait)");
+ // ratio that reduces to 59/80
+ change_state(function() {
+ iframe_style.height = "320px";
+ });
+ should_apply("(orientation)");
+ should_not_apply("(orientation: landscape)");
+ should_apply("not all and (orientation: landscape)");
+ should_apply("(orientation: portrait)");
+
+ expression_should_be_known("-moz-device-orientation");
+ expression_should_be_known("-moz-device-orientation: portrait");
+ expression_should_be_known("-moz-device-orientation: landscape");
+ expression_should_not_be_known("min--moz-device-orientation");
+ expression_should_not_be_known("min--moz-device-orientation: portrait");
+ expression_should_not_be_known("min--moz-device-orientation: landscape");
+ expression_should_not_be_known("max--moz-device-orientation");
+ expression_should_not_be_known("max--moz-device-orientation: portrait");
+ expression_should_not_be_known("max--moz-device-orientation: landscape");
+
+ // determine the actual configuration of the screen and test against it
+ var device_orientation = (device_width > device_height) ? "landscape" : "portrait";
+ var not_device_orientation = (device_orientation == "landscape") ? "portrait" : "landscape";
+ should_apply("(-moz-device-orientation)");
+ should_apply("(-moz-device-orientation: " + device_orientation + ")");
+ should_not_apply("(-moz-device-orientation: " + not_device_orientation + ")");
+ should_apply("not all and (-moz-device-orientation: " + not_device_orientation + ")");
+
+ should_apply("(aspect-ratio: 59/80)");
+ should_not_apply("(aspect-ratio: 58/80)");
+ should_not_apply("(aspect-ratio: 59/81)");
+ should_not_apply("(aspect-ratio: 60/80)");
+ should_not_apply("(aspect-ratio: 59/79)");
+ should_apply("(aspect-ratio: 177/240)");
+ should_apply("(aspect-ratio: 413/560)");
+ should_apply("(aspect-ratio: 5900/8000)");
+ should_not_apply("(aspect-ratio: 5901/8000)");
+ should_not_apply("(aspect-ratio: 5899/8000)");
+ should_not_apply("(aspect-ratio: 5900/8001)");
+ should_not_apply("(aspect-ratio: 5900/7999)");
+ should_apply("(aspect-ratio)");
+
+ // Test "unreasonable", but still valid aspect ratios, such as aspect ratios with negative numbers,
+ // and zeros, and with numbers near 2^32 and 2^64 (to check overflow).
+ should_not_apply("(aspect-ratio: 0/1)");
+ should_not_apply("(aspect-ratio: 1/0)");
+ should_not_apply("(aspect-ratio: -1/1)");
+ should_not_apply("(aspect-ratio: 1/-1)");
+ should_not_apply("(aspect-ratio: -1/-1)");
+ should_not_apply("(aspect-ratio: -59/-80)");
+ should_not_apply("(aspect-ratio: 4294967295/4294967295)");
+ should_not_apply("(aspect-ratio: 4294967297/4294967297)");
+ should_not_apply("(aspect-ratio: 18446744073709560000/18446744073709560000)");
+
+ // Test min and max aspect ratios.
+ should_apply("(min-aspect-ratio: 59/80)");
+ should_apply("(min-aspect-ratio: 58/80)");
+ should_apply("(min-aspect-ratio: 59/81)");
+ should_not_apply("(min-aspect-ratio: 60/80)");
+ should_not_apply("(min-aspect-ratio: 59/79)");
+ expression_should_not_be_known("min-aspect-ratio");
+
+ should_apply("(max-aspect-ratio: 59/80)");
+ should_not_apply("(max-aspect-ratio: 58/80)");
+ should_not_apply("(max-aspect-ratio: 59/81)");
+ should_apply("(max-aspect-ratio: 60/80)");
+ should_apply("(max-aspect-ratio: 59/79)");
+ expression_should_not_be_known("max-aspect-ratio");
+
+ var real_dar = device_width + "/" + device_height;
+ var high_dar_1 = (device_width + 1) + "/" + device_height;
+ var high_dar_2 = device_width + "/" + (device_height - 1);
+ var low_dar_1 = (device_width - 1) + "/" + device_height;
+ var low_dar_2 = device_width + "/" + (device_height + 1);
+ should_apply("(device-aspect-ratio: " + real_dar + ")");
+ should_apply("not all and (device-aspect-ratio: " + high_dar_1 + ")");
+ should_not_apply("all and (device-aspect-ratio: " + high_dar_2 + ")");
+ should_not_apply("all and (device-aspect-ratio: " + low_dar_1 + ")");
+ should_apply("not all and (device-aspect-ratio: " + low_dar_2 + ")");
+ should_apply("(device-aspect-ratio)");
+
+ should_apply("(min-device-aspect-ratio: " + real_dar + ")");
+ should_not_apply("all and (min-device-aspect-ratio: " + high_dar_1 + ")");
+ should_apply("not all and (min-device-aspect-ratio: " + high_dar_2 + ")");
+ should_not_apply("not all and (min-device-aspect-ratio: " + low_dar_1 + ")");
+ should_apply("all and (min-device-aspect-ratio: " + low_dar_2 + ")");
+ expression_should_not_be_known("min-device-aspect-ratio");
+
+ should_apply("all and (max-device-aspect-ratio: " + real_dar + ")");
+ should_apply("(max-device-aspect-ratio: " + high_dar_1 + ")");
+ should_apply("(max-device-aspect-ratio: " + high_dar_2 + ")");
+ should_not_apply("all and (max-device-aspect-ratio: " + low_dar_1 + ")");
+ should_apply("not all and (max-device-aspect-ratio: " + low_dar_2 + ")");
+ expression_should_not_be_known("max-device-aspect-ratio");
+
+ // Tests for -moz- & -webkit versions of "device-pixel-ratio"
+ // (Note that the vendor prefixes go in different places.)
+ test_device_pixel_ratio("-moz-device-pixel-ratio",
+ "min--moz-device-pixel-ratio",
+ "max--moz-device-pixel-ratio");
+ test_device_pixel_ratio("-webkit-device-pixel-ratio",
+ "-webkit-min-device-pixel-ratio",
+ "-webkit-max-device-pixel-ratio");
+
+ // Make sure that we don't accidentally start accepting *unprefixed*
+ // "device-pixel-ratio" expressions:
+ expression_should_be_known("-webkit-device-pixel-ratio: 1.0");
+ expression_should_not_be_known("device-pixel-ratio: 1.0");
+ expression_should_be_known("-webkit-min-device-pixel-ratio: 1.0");
+ expression_should_not_be_known("min-device-pixel-ratio: 1.0");
+ expression_should_be_known("-webkit-max-device-pixel-ratio: 1.0");
+ expression_should_not_be_known("max-device-pixel-ratio: 1.0");
+
+ should_apply("(-webkit-transform-3d)");
+
+ features = [ "max-aspect-ratio", "device-aspect-ratio" ];
+ for (i in features) {
+ feature = features[i];
+ expression_should_be_known(feature + ": 1/1");
+ expression_should_be_known(feature + ": 1 /1");
+ expression_should_be_known(feature + ": 1 / \t\n1");
+ expression_should_be_known(feature + ": 1/\r1");
+ expression_should_be_known(feature + ": 1");
+ expression_should_be_known(feature + ": 0.5");
+ expression_should_be_known(feature + ": 1.0/1");
+ expression_should_be_known(feature + ": 1/1.0");
+ expression_should_be_known(feature + ": 1.0/1.0");
+ expression_should_be_known(feature + ": 1.5/1.2");
+ expression_should_be_known(feature + ": 1.5");
+ expression_should_be_known(feature + ": calc(1.2 * 1.3)");
+ expression_should_be_known(feature + ": 1.1/calc(2.2 * 2.3)");
+ expression_should_be_known(feature + ": calc(1.2 * 1.3)/2.2");
+ expression_should_be_known(feature + ": calc(1.2 * 1.3)/calc(2.2 * 2.3)");
+ expression_should_be_known(feature + ": 0/1");
+ expression_should_be_known(feature + ": 1/0");
+ expression_should_be_known(feature + ": 0/0");
+ expression_should_not_be_known(feature + ": -1/1");
+ expression_should_not_be_known(feature + ": 1/-1");
+ expression_should_not_be_known(feature + ": -1/-1");
+ expression_should_not_be_known(feature + ": -1/-1");
+ expression_should_not_be_known(feature + ": -1/-1");
+ expression_should_not_be_known(feature + ": invalid");
+ expression_should_not_be_known(feature + ": 1 / invalid");
+ expression_should_not_be_known(feature + ": 1 invalid");
+ }
+
+ var is_monochrome = query_applies("all and (min-monochrome: 1)");
+ test_serialization("all and (min-monochrome: 1)", true, is_monochrome);
+ var is_color = query_applies("all and (min-color: 1)");
+ test_serialization("all and (min-color: 1)", true, is_color);
+ isnot(is_monochrome, is_color, "should be either monochrome or color");
+
+ function depth_query(prefix, depth) {
+ return "all and (" + prefix + (is_color ? "color" : "monochrome") +
+ ":" + depth + ")";
+ }
+
+ var depth = 0;
+ do {
+ if (depth > 50) {
+ ok(false, "breaking from loop, depth > 50");
+ break;
+ }
+ } while (query_applies(depth_query("min-", ++depth)));
+ --depth;
+
+ should_apply(depth_query("", depth));
+ should_not_apply(depth_query("", depth - 1));
+ should_not_apply(depth_query("", depth + 1));
+ should_apply(depth_query("max-", depth));
+ should_not_apply(depth_query("max-", depth - 1));
+ should_apply(depth_query("max-", depth + 1));
+
+ (is_color ? should_apply : should_not_apply)("all and (color)");
+ expression_should_not_be_known("max-color");
+ expression_should_not_be_known("min-color");
+ (is_color ? should_not_apply : should_apply)("all and (monochrome)");
+ expression_should_not_be_known("max-monochrome");
+ expression_should_not_be_known("min-monochrome");
+ (is_color ? should_apply : should_not_apply)("not all and (monochrome)");
+ (is_color ? should_not_apply : should_apply)("not all and (color)");
+ (is_color ? should_apply : should_not_apply)("only all and (color)");
+ (is_color ? should_not_apply : should_apply)("only all and (monochrome)");
+
+ features = [ "color", "min-monochrome", "max-color-index" ];
+ for (i in features) {
+ feature = features[i];
+ expression_should_be_known(feature + ": 1");
+ expression_should_be_known(feature + ": 327");
+ expression_should_be_known(feature + ": 0");
+ expression_should_be_known(feature + ": -1");
+ expression_should_not_be_known(feature + ": 1.0");
+ expression_should_not_be_known(feature + ": 1/1");
+ }
+
+ // Presume that we never support indexed color (at least not usefully
+ // enough to call it indexed color).
+ should_apply("(color-index: 0)");
+ should_not_apply("(color-index: 1)");
+ should_apply("(min-color-index: 0)");
+ should_not_apply("(min-color-index: 1)");
+ should_apply("(max-color-index: 0)");
+ should_apply("(max-color-index: 1)");
+ should_apply("(max-color-index: 157)");
+
+ features = [ "resolution", "min-resolution", "max-resolution" ];
+ for (i in features) {
+ feature = features[i];
+ expression_should_be_known(feature + ": 3dpi");
+ expression_should_be_known(feature + ":3dpi");
+ expression_should_be_known(feature + ": 3.0dpi");
+ expression_should_be_known(feature + ": 3.4dpi");
+ expression_should_be_known(feature + "\t: 120dpcm");
+ expression_should_be_known(feature + ": 1dppx");
+ expression_should_be_known(feature + ": 1x");
+ expression_should_be_known(feature + ": 1.5dppx");
+ expression_should_be_known(feature + ": 1.5x");
+ expression_should_be_known(feature + ": 2.0dppx");
+ expression_should_be_known(feature + ": 0dpi");
+ expression_should_be_known(feature + ": 0dppx");
+ expression_should_be_known(feature + ": 0x");
+ expression_should_not_be_known(feature + ": -3dpi");
+ }
+
+ // Find the resolution using max-resolution
+ var resolution = 0;
+ do {
+ ++resolution;
+ if (resolution > 10000) {
+ ok(false, "resolution greater than 10000dpi???");
+ break;
+ }
+ } while (!query_applies("(max-resolution: " + resolution + "dpi)"));
+
+ // resolution should now be Math.ceil() of the actual resolution.
+ var dpi_high;
+ var dpi_low = resolution - 1;
+ if (query_applies("(min-resolution: " + resolution + "dpi)")) {
+ // It's exact!
+ should_apply("(resolution: " + resolution + "dpi)");
+ should_apply("(resolution: " + Math.floor(resolution/96) + "dppx)");
+ should_apply("(resolution: " + Math.floor(resolution/96) + "x)");
+ should_not_apply("(resolution: " + (resolution + 1) + "dpi)");
+ should_not_apply("(resolution: " + (resolution - 1) + "dpi)");
+ dpi_high = resolution + 1;
+ } else {
+ // We have no way to test resolution applying since it need not be
+ // an integer.
+ should_not_apply("(resolution: " + resolution + "dpi)");
+ should_not_apply("(resolution: " + (resolution - 1) + "dpi)");
+ dpi_high = resolution;
+ }
+
+ should_apply("(min-resolution: " + dpi_low + "dpi)");
+ should_not_apply("not all and (min-resolution: " + dpi_low + "dpi)");
+ should_apply("not all and (min-resolution: " + dpi_high + "dpi)");
+ should_not_apply("all and (min-resolution: " + dpi_high + "dpi)");
+
+ // Test dpcm units based on what we computed in dpi.
+ var dpcm_high = Math.ceil(dpi_high / 2.54);
+ var dpcm_low = Math.floor(dpi_low / 2.54);
+ should_apply("(min-resolution: " + dpcm_low + "dpcm)");
+ should_apply("(max-resolution: " + dpcm_high + "dpcm)");
+ should_not_apply("(max-resolution: " + dpcm_low + "dpcm)");
+ should_apply("not all and (min-resolution: " + dpcm_high + "dpcm)");
+
+ expression_should_be_known("scan");
+ expression_should_be_known("scan: progressive");
+ expression_should_be_known("scan:interlace");
+ expression_should_not_be_known("min-scan:interlace");
+ expression_should_not_be_known("scan: 1");
+ expression_should_not_be_known("max-scan");
+ expression_should_not_be_known("max-scan: progressive");
+ // Assume we don't support tv devices.
+ should_not_apply("(scan)");
+ should_not_apply("(scan: progressive)");
+ should_not_apply("(scan: interlace)");
+ should_apply("not all and (scan)");
+ should_apply("not all and (scan: progressive)");
+ should_apply("not all and (scan: interlace)");
+
+ expression_should_be_known("grid");
+ expression_should_be_known("grid: 0");
+ expression_should_be_known("grid: 1");
+ expression_should_be_known("grid: 1");
+ expression_should_not_be_known("min-grid");
+ expression_should_not_be_known("min-grid:0");
+ expression_should_not_be_known("max-grid: 1");
+ expression_should_not_be_known("grid: 2");
+ expression_should_not_be_known("grid: -1");
+
+ // Assume we don't support grid devices
+ should_not_apply("(grid)");
+ should_apply("(grid: 0)");
+ should_not_apply("(grid: 1)");
+ should_not_apply("(grid: 2)");
+ should_not_apply("(grid: -1)");
+
+ for (let toggle of CHROME_ONLY_TOGGLES) {
+ expression_should_not_be_known(toggle);
+ expression_should_not_be_known(toggle + ": 1");
+ expression_should_not_be_known(toggle + ": 0");
+ expression_should_not_be_known(toggle + ": -1");
+ expression_should_not_be_known(toggle + ": true");
+ expression_should_not_be_known(toggle + ": false");
+ }
+
+ for (let query of CHROME_ONLY_QUERIES) {
+ expression_should_not_be_known(query);
+ }
+
+ {
+ let should_be_parseable_if_enabled = SpecialPowers.getBoolPref('layout.css.prefers-contrast.enabled')
+ ? expression_should_be_known
+ : expression_should_not_be_known;
+ should_be_parseable_if_enabled("prefers-contrast");
+ should_be_parseable_if_enabled("prefers-contrast: more");
+ should_be_parseable_if_enabled("prefers-contrast: less");
+ should_be_parseable_if_enabled("prefers-contrast: custom");
+ should_be_parseable_if_enabled("prefers-contrast: no-preference");
+ }
+
+ {
+ let should_be_parseable_if_enabled = SpecialPowers.getBoolPref('layout.css.forced-colors.enabled')
+ ? expression_should_be_known
+ : expression_should_not_be_known;
+ should_be_parseable_if_enabled("forced-colors");
+ should_be_parseable_if_enabled("forced-colors: none");
+ should_be_parseable_if_enabled("forced-colors: active");
+ }
+
+ // OpenType SVG media features
+ expression_should_not_be_known("(-moz-is-glyph)");
+ expression_should_not_be_known("not (-moz-is-glyph)");
+ expression_should_not_be_known("only (-moz-is-glyph)");
+ expression_should_not_be_known("all and (-moz-is-glyph)");
+ expression_should_not_be_known("not all and (-moz-is-glyph)");
+ expression_should_not_be_known("only all and (-moz-is-glyph)");
+
+ expression_should_not_be_known("(-moz-is-glyph:0)");
+ expression_should_not_be_known("not (-moz-is-glyph:0)");
+ expression_should_not_be_known("only (-moz-is-glyph:0)");
+ expression_should_not_be_known("all and (-moz-is-glyph:0)");
+ expression_should_not_be_known("not all and (-moz-is-glyph:0)");
+ expression_should_not_be_known("only all and (-moz-is-glyph:0)");
+
+ expression_should_not_be_known("(-moz-is-glyph:1)");
+ expression_should_not_be_known("not (-moz-is-glyph:1)");
+ expression_should_not_be_known("only (-moz-is-glyph:1)");
+ expression_should_not_be_known("all and (-moz-is-glyph:1)");
+ expression_should_not_be_known("not all and (-moz-is-glyph:1)");
+ expression_should_not_be_known("only all and (-moz-is-glyph:1)");
+
+ expression_should_not_be_known("(min--moz-is-glyph:0)");
+ expression_should_not_be_known("(max--moz-is-glyph:0)");
+ expression_should_not_be_known("(min--moz-is-glyph:1)");
+ expression_should_not_be_known("(max--moz-is-glyph:1)");
+
+ should_not_apply("not all and (-moz-is-glyph)");
+ should_not_apply("(-moz-is-glyph:0)");
+ should_not_apply("not all and (-moz-is-glyph:1)");
+ should_not_apply("only all and (-moz-is-glyph:0)");
+ should_not_apply("(-moz-is-glyph)");
+ should_not_apply("(-moz-is-glyph:1)");
+ should_not_apply("not all and (-moz-is-glyph:0)");
+ should_not_apply("only all and (-moz-is-glyph:1)");
+
+ // Resource documents (UA-only).
+ expression_should_not_be_known("(-moz-is-resource-document)");
+
+ // Parsing tests
+ // bug 454227
+ should_apply_unbalanced("(orientation");
+ should_not_apply_unbalanced("not all and (orientation");
+ should_not_apply_unbalanced("(orientation:");
+ should_apply_unbalanced("all,(orientation:");
+ should_not_apply_unbalanced("(orientation:,all");
+ should_apply_unbalanced("not all and (grid");
+ should_not_apply_unbalanced("only all and (grid");
+ should_not_apply_unbalanced("(grid");
+ should_apply_unbalanced("all,(grid");
+ should_not_apply_unbalanced("(grid,all");
+ // bug 454226
+ should_apply(",all");
+ should_apply("all,");
+ should_apply(",all,");
+ should_apply("all,badmedium");
+ should_apply("badmedium,all");
+ should_not_apply(",badmedium,");
+ should_apply("all,(badexpression)");
+ should_apply("(badexpression),all");
+ should_not_apply("(badexpression),badmedium");
+ should_not_apply("badmedium,(badexpression)");
+ should_apply("all,[badsyntax]");
+ should_apply("[badsyntax],all");
+ should_not_apply("badmedium,[badsyntax]");
+ should_not_apply("[badsyntax],badmedium");
+ // bug 528096
+ should_not_apply_unbalanced("((resolution),all");
+ should_not_apply_unbalanced("(resolution(),all");
+ should_not_apply_unbalanced("(resolution (),all");
+ should_not_apply_unbalanced("(resolution:(),all");
+
+ for (let rangeFeature of ["dynamic-range", "video-dynamic-range"]) {
+ should_apply("(" + rangeFeature + ": standard)");
+ expression_should_be_known("(" + rangeFeature + ": high)");
+ expression_should_not_be_known("(" + rangeFeature + ": low)");
+ }
+
+ handle_posted_items();
+}
+
+/*
+ * The cloning tests have to post tests that wait for onload. However,
+ * we also make a bunch of state changes during the tests above. So we
+ * always change state using the change_state call, with both makes the
+ * change immediately and posts an item in the same queue so that we
+ * make the same state change again later.
+ */
+
+var posted_items = [];
+
+function change_state(func)
+{
+ func();
+ posted_items.push({state: func});
+}
+
+function post_clone_test(srcdoc, testfunc)
+{
+ posted_items.push({srcdoc, testfunc});
+}
+
+function handle_posted_items()
+{
+ if (posted_items.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+
+ if ("state" in posted_items[0]) {
+ var item = posted_items.shift();
+ item.state();
+ handle_posted_items();
+ return;
+ }
+
+ var srcdoc = posted_items[0].srcdoc;
+ iframe.onload = handle_iframe_onload;
+ iframe.srcdoc = srcdoc;
+}
+
+function handle_iframe_onload(event)
+{
+ if (event.target != iframe)
+ return;
+
+ var item = posted_items.shift();
+ item.testfunc();
+ handle_posted_items();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_media_queries_dynamic.html b/layout/style/test/test_media_queries_dynamic.html
new file mode 100644
index 0000000000..52e5fda9ca
--- /dev/null
+++ b/layout/style/test/test_media_queries_dynamic.html
@@ -0,0 +1,207 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=473400
+-->
+<head>
+ <title>Test for Bug 473400</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=473400">Mozilla Bug 473400</a>
+<iframe id="subdoc" src="about:blank"></iframe>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 473400 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var subdoc = document.getElementById("subdoc").contentDocument;
+ var subwin = document.getElementById("subdoc").contentWindow;
+ var style = subdoc.createElement("style");
+ style.setAttribute("type", "text/css");
+ subdoc.getElementsByTagName("head")[0].appendChild(style);
+ var sheet = style.sheet;
+ var iframe_style = document.getElementById("subdoc").style;
+
+ // Create a style rule and an element now based on the given media
+ // query "q", and return the computed style that should be passed to
+ // query_applies to see if that query currently applies.
+ var n = 0;
+ function make_query(q) {
+ var i = ++n;
+ sheet.insertRule("@media " + q + " { #e" + i + " { text-decoration: underline; } }", sheet.cssRules.length);
+ var e = subdoc.createElement("div");
+ e.id = "e" + i;
+ subdoc.body.appendChild(e);
+ var cs = subdoc.defaultView.getComputedStyle(e);
+ cs._originalQueryText = q;
+ return cs;
+ }
+ function query_applies(cs) {
+ return cs.getPropertyValue("text-decoration-line") == "underline";
+ }
+
+ function should_apply(cs) {
+ ok(query_applies(cs), cs._originalQueryText + " should apply");
+ }
+
+ function should_not_apply(cs) {
+ ok(!query_applies(cs), cs._originalQueryText + " should not apply");
+ }
+
+ var content_div = document.getElementById("content");
+ content_div.style.font = "initial";
+ var em_size =
+ getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1];
+
+ let width_val = 317; // pick two not-too-round numbers
+ let height_val = 228;
+ iframe_style.width = width_val + "px";
+ iframe_style.height = height_val + "px";
+ var wh_queries = [
+ make_query("all and (min-width: " +
+ (Math.ceil(width_val/em_size) + 1) + "em)"),
+ make_query("all and (min-width: " +
+ (Math.floor(width_val/em_size) - 1) + "em)"),
+ make_query("all and (max-width: " +
+ (Math.ceil(width_val/em_size) + 1) + "em)"),
+ make_query("all and (max-width: " +
+ (Math.floor(width_val/em_size) - 1) + "em)"),
+ make_query("all and (min-width: " +
+ (Math.ceil(width_val/(em_size*2)) + 1) + "em)"),
+ make_query("all and (min-width: " +
+ (Math.floor(width_val/(em_size*2)) - 1) + "em)"),
+ make_query("all and (max-width: " +
+ (Math.ceil(width_val/(em_size*2)) + 1) + "em)"),
+ make_query("all and (max-width: " +
+ (Math.floor(width_val/(em_size*2)) - 1) + "em)")
+ ];
+
+ is(wh_queries[0].fontSize, em_size + "px", "text zoom is 1.0");
+ should_not_apply(wh_queries[0]);
+ should_apply(wh_queries[1]);
+ should_apply(wh_queries[2]);
+ should_not_apply(wh_queries[3]);
+ SpecialPowers.setTextZoom(subwin, 2.0);
+ isnot(wh_queries[0].fontSize, em_size + "px", "text zoom is not 1.0");
+ should_not_apply(wh_queries[4]);
+ should_apply(wh_queries[5]);
+ should_apply(wh_queries[6]);
+ should_not_apply(wh_queries[7]);
+ SpecialPowers.setTextZoom(subwin, 1.0);
+ is(wh_queries[0].fontSize, em_size + "px", "text zoom is 1.0");
+ is(subwin.innerHeight, 228, "full zoom is 1.0");
+ should_not_apply(wh_queries[0]);
+ should_apply(wh_queries[1]);
+ should_apply(wh_queries[2]);
+ should_not_apply(wh_queries[3]);
+ SpecialPowers.setFullZoom(subwin, 2.0);
+ isnot(subwin.innerHeight, 228, "full zoom is not 1.0");
+ should_not_apply(wh_queries[4]);
+ should_apply(wh_queries[5]);
+ should_apply(wh_queries[6]);
+ should_not_apply(wh_queries[7]);
+ SpecialPowers.setFullZoom(subwin, 1.0);
+ is(subwin.innerHeight, 228, "full zoom is 1.0");
+
+
+ // Now test that certain things *don't* happen, i.e., that we're
+ // making the optimizations we expect.
+ subdoc.body.textContent = "";
+ subdoc.body.appendChild(subdoc.createElement("div"));
+ for (var ruleIdx = sheet.cssRules.length; ruleIdx-- != 0; ) {
+ sheet.deleteRule(ruleIdx);
+ }
+
+ var utils = SpecialPowers.getDOMWindowUtils(subwin);
+ var restyleGeneration, framesConstructed, framesReflowed;
+ function reset_change_counters()
+ {
+ restyleGeneration = utils.restyleGeneration;
+ framesConstructed = utils.framesConstructed;
+ framesReflowed = utils.framesReflowed;
+ }
+
+ function flush_and_assert_change_counters(desc, expected) {
+ subdoc.body.offsetHeight;
+
+ if (!("restyle" in expected) ||
+ !("construct" in expected) ||
+ !("reflow" in expected)) {
+ ok(false, "parameter missing expectation");
+ return;
+ }
+
+ var didRestyle = utils.restyleGeneration != restyleGeneration;
+ var constructs = utils.framesConstructed - framesConstructed;
+ var reflows = utils.framesReflowed - framesReflowed;
+
+ (expected.restyle ? isnot : is)(didRestyle, false, "restyle: " + desc);
+ (expected.construct ? isnot : is)(constructs, 0,
+ "frame construct count: " + desc);
+ (expected.reflow ? isnot : is)(reflows, 0, "reflow count: " + desc);
+
+ reset_change_counters();
+ }
+
+ subdoc.body.offsetHeight;
+ reset_change_counters();
+
+ iframe_style.width = "103px";
+ flush_and_assert_change_counters("change width with no media queries",
+ { restyle: false, construct: false, reflow: true });
+
+ flush_and_assert_change_counters("no change",
+ { restyle: false, construct: false, reflow: false });
+
+ iframe_style.height = "123px";
+ flush_and_assert_change_counters("change height with no media queries",
+ { restyle: false, construct: false, reflow: true });
+
+ sheet.insertRule("@media (min-width: 150px) { div { display:flex } }", 0);
+ flush_and_assert_change_counters("add non-matching media query",
+ { restyle: false, construct: false, reflow: false });
+
+ iframe_style.width = "177px";
+ flush_and_assert_change_counters("resize width across media query with 'display'",
+ { restyle: true, construct: true, reflow: true });
+
+ iframe_style.width = "162px";
+ flush_and_assert_change_counters("resize width without crossing media query",
+ { restyle: false, construct: false, reflow: true });
+
+ sheet.deleteRule(0);
+ flush_and_assert_change_counters("remove matching media query with 'display'",
+ { restyle: true, construct: true, reflow: true });
+
+ sheet.insertRule("@media (max-height: 150px) { div { display:flex } }", 0);
+ flush_and_assert_change_counters("add matching media query with 'display'",
+ { restyle: true, construct: true, reflow: true });
+
+ iframe_style.height = "111px";
+ flush_and_assert_change_counters("resize height without crossing media query",
+ { restyle: false, construct: false, reflow: true });
+
+ iframe_style.height = "184px";
+ flush_and_assert_change_counters("resize height across media query with 'display'",
+ { restyle: true, construct: true, reflow: true });
+
+ sheet.deleteRule(0);
+ flush_and_assert_change_counters("remove non-matching media query",
+ { restyle: false, construct: false, reflow: false });
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_media_query_list.html b/layout/style/test/test_media_query_list.html
new file mode 100644
index 0000000000..5f213117e5
--- /dev/null
+++ b/layout/style/test/test_media_query_list.html
@@ -0,0 +1,373 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=542058
+-->
+<head>
+ <title>Test for MediaQueryList (Bug 542058)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=542058">Mozilla Bug 542058</a>
+<iframe id="subdoc" src="about:blank"></iframe>
+<div id="content" style="display:none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for MediaQueryList (Bug 542058) **/
+
+SimpleTest.waitForExplicitFinish();
+
+function tick() {
+ // MediaQueryList events are guaranteed to run before requestAnimationFrame
+ // per spec.
+ return new Promise(r => requestAnimationFrame(r));
+}
+
+async function run() {
+ var iframe = document.getElementById("subdoc");
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+ var subroot = subdoc.documentElement;
+
+ var content_div = document.getElementById("content");
+ content_div.style.font = "initial";
+ var em_size =
+ getComputedStyle(content_div).fontSize.match(/^(\d+)px$/)[1];
+
+ var w = Math.floor(em_size * 9.3);
+ var h = Math.floor(em_size * 4.2);
+ iframe.style.width = w + "px";
+ iframe.style.height = h + "px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ function setup_mql(str) {
+ var obj = {
+ str: str,
+ mql: subwin.matchMedia(str),
+ notifyCount: 0,
+ listener: function(event) {
+ ok(event instanceof subwin.MediaQueryListEvent,
+ "correct argument to listener: " + obj.str);
+ is(event.media, obj.mql.media,
+ "correct media in the event: " + obj.str);
+ is(event.target, obj.mql,
+ "correct target in the event: " + obj.str);
+ ++obj.notifyCount;
+ // Test the last match result only on odd
+ // notifications.
+ if (obj.notifyCount & 1) {
+ obj.lastOddMatchResult = event.target.matches;
+ }
+ }
+ }
+ obj.mql.addListener(obj.listener);
+ return obj;
+ }
+
+ function finish_mql(obj) {
+ obj.mql.removeListener(obj.listener);
+ }
+
+ var w_exact_w = setup_mql("(width: " + w + "px)");
+ var w_min_9em = setup_mql("(min-width : 9em)");
+ var w_min_10em = setup_mql("( min-width: 10em ) ");
+ var w_max_9em = setup_mql("(max-width: 9em)");
+ var w_max_10em = setup_mql("(max-width: 10em)");
+
+ is(w_exact_w.mql.media, "(width: " + w + "px)", "serialization");
+ is(w_min_9em.mql.media, "(min-width: 9em)", "serialization");
+ is(w_min_10em.mql.media, "(min-width: 10em)", "serialization");
+ is(w_max_9em.mql.media, "(max-width: 9em)", "serialization");
+ is(w_max_10em.mql.media, "(max-width: 10em)", "serialization");
+
+ function check_match(obj, expected, desc) {
+ is(obj.mql.matches, expected,
+ obj.str + " media query list .matches " + desc);
+ if (obj.notifyCount & 1) { // odd notifications only
+ is(obj.lastOddMatchResult, expected,
+ obj.str + " media query list last notify result " + desc);
+ }
+ }
+ function check_notify(obj, expected, desc) {
+ is(obj.notifyCount, expected,
+ obj.str + " media query list .notify count " + desc);
+ }
+ check_match(w_exact_w, true, "initially");
+ check_notify(w_exact_w, 0, "initially");
+ check_match(w_min_9em, true, "initially");
+ check_notify(w_min_9em, 0, "initially");
+ check_match(w_min_10em, false, "initially");
+ check_notify(w_min_10em, 0, "initially");
+ check_match(w_max_9em, false, "initially");
+ check_notify(w_max_9em, 0, "initially");
+ check_match(w_max_10em, true, "initially");
+ check_notify(w_max_10em, 0, "initially");
+
+ var w2 = Math.floor(em_size * 10.3);
+ iframe.style.width = w2 + "px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ check_match(w_exact_w, false, "after width increase to around 10.3em");
+ check_notify(w_exact_w, 1, "after width increase to around 10.3em");
+ check_match(w_min_9em, true, "after width increase to around 10.3em");
+ check_notify(w_min_9em, 0, "after width increase to around 10.3em");
+ check_match(w_min_10em, true, "after width increase to around 10.3em");
+ check_notify(w_min_10em, 1, "after width increase to around 10.3em");
+ check_match(w_max_9em, false, "after width increase to around 10.3em");
+ check_notify(w_max_9em, 0, "after width increase to around 10.3em");
+ check_match(w_max_10em, false, "after width increase to around 10.3em");
+ check_notify(w_max_10em, 1, "after width increase to around 10.3em");
+
+ var w3 = w * 2;
+ iframe.style.width = w3 + "px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ check_match(w_exact_w, false, "after width double from original");
+ check_notify(w_exact_w, 1, "after width double from original");
+ check_match(w_min_9em, true, "after width double from original");
+ check_notify(w_min_9em, 0, "after width double from original");
+ check_match(w_min_10em, true, "after width double from original");
+ check_notify(w_min_10em, 1, "after width double from original");
+ check_match(w_max_9em, false, "after width double from original");
+ check_notify(w_max_9em, 0, "after width double from original");
+ check_match(w_max_10em, false, "after width double from original");
+ check_notify(w_max_10em, 1, "after width double from original");
+
+ SpecialPowers.setFullZoom(subwin, 2.0);
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ check_match(w_exact_w, true, "after zoom");
+ check_notify(w_exact_w, 2, "after zoom");
+ check_match(w_min_9em, true, "after zoom");
+ check_notify(w_min_9em, 0, "after zoom");
+ check_match(w_min_10em, false, "after zoom");
+ check_notify(w_min_10em, 2, "after zoom");
+ check_match(w_max_9em, false, "after zoom");
+ check_notify(w_max_9em, 0, "after zoom");
+ check_match(w_max_10em, true, "after zoom");
+ check_notify(w_max_10em, 2, "after zoom");
+
+ SpecialPowers.setFullZoom(subwin, 1.0);
+
+ await tick();
+
+ finish_mql(w_exact_w);
+ finish_mql(w_min_9em);
+ finish_mql(w_min_10em);
+ finish_mql(w_max_9em);
+ finish_mql(w_max_10em);
+
+ // Additional tests of listener mutation.
+ {
+ let received = [];
+ let received_mql = [];
+ function listener1(event) {
+ received.push(1);
+ received_mql.push(event.target);
+ }
+ function listener2(event) {
+ received.push(2);
+ received_mql.push(event.target);
+ }
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ let mql = subwin.matchMedia("(min-width: 150px)");
+ mql.addListener(listener1);
+ mql.addListener(listener1);
+ mql.addListener(listener2);
+ is(JSON.stringify(received), "[]", "listeners before notification");
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[1,2]", "duplicate listeners removed");
+ received = [];
+ mql.removeListener(listener1);
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[2]", "listener removal");
+ received = [];
+ mql.addListener(listener1);
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[2,1]", "listeners notified in order");
+ received = [];
+ mql.addListener(listener2);
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[2,1]", "add of existing listener is no-op");
+ received = [];
+ mql.addListener(listener1);
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[2,1]", "add of existing listener is no-op");
+ mql.removeListener(listener2);
+ received = [];
+ received_mql = [];
+
+ var mql2 = subwin.matchMedia("(min-width: 160px)");
+ mql2.addListener(listener1);
+ mql.addListener(listener2);
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ // mql (1, 2), mql2 (1)
+ is(JSON.stringify(received), "[1,2,1]",
+ "notification of lists in order created");
+ is(received_mql[0], mql,
+ "notification of lists in order created");
+ is(received_mql[1], mql,
+ "notification of lists in order created");
+ is(received_mql[2], mql2,
+ "notification of lists in order created");
+ received = [];
+ received_mql = [];
+
+ function removing_listener(event) {
+ received.push(3);
+ received_mql.push(event.target);
+ event.target.removeListener(listener2);
+ mql2.removeListener(listener1);
+ }
+
+ mql.addListener(removing_listener);
+ mql.removeListener(listener2);
+ mql.addListener(listener2); // after removing_listener (3)
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ // mql(1, 3)
+ is(JSON.stringify(received), "[1,3]",
+ "listeners still notified after removed if change was before");
+ is(received_mql[0], mql,
+ "notification order (removal tests)");
+ is(received_mql[1], mql,
+ "notification order (removal tests)");
+ received = [];
+ received_mql = [];
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ // mql(1, 3)
+ is(JSON.stringify(received), "[1,3]",
+ "listeners not notified for changes after their removal");
+ is(received_mql[0], mql,
+ "notification order (removal tests)");
+ is(received_mql[1], mql,
+ "notification order (removal tests)");
+ }
+
+ /* Bug 753777: test that things work in a freshly-created iframe */
+ {
+ let newIframe = document.createElement("iframe");
+ document.body.appendChild(newIframe);
+
+ is(newIframe.contentWindow.matchMedia("all").matches, true,
+ "matchMedia should work in newly-created iframe");
+ is(newIframe.contentWindow.matchMedia("(min-width: 1px)").matches, true,
+ "(min-width: 1px) should match in newly-created iframe");
+ is(newIframe.contentWindow.matchMedia("(max-width: 1px)").matches, false,
+ "(max-width: 1px) should not match in newly-created iframe");
+
+ document.body.removeChild(newIframe);
+ }
+
+ /* Bug 716751: listeners lost due to GC */
+ var gc_received = [];
+ {
+ let received = [];
+ let listener1 = function(event) {
+ gc_received.push(1);
+ }
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ let mql = subwin.matchMedia("(min-width: 150px)");
+ mql.addListener(listener1);
+ is(JSON.stringify(gc_received), "[]", "GC test: before notification");
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(gc_received), "[1]", "GC test: after notification 1");
+
+ // Because of conservative GC, we need to go back to the event loop
+ // to GC properly.
+ setTimeout(step2, 0);
+ }
+
+ async function step2() {
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(gc_received), "[1,1]", "GC test: after notification 2");
+
+ bug1270626();
+ }
+
+ /* Bug 1270626: listeners that throw exceptions */
+ async function bug1270626() {
+ var throwingListener = function(event) {
+ throw "error";
+ }
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ var mql = subwin.matchMedia("(min-width: 150px)");
+ mql.addListener(throwingListener);
+
+ SimpleTest.expectUncaughtException(true);
+ is(SimpleTest.isExpectingUncaughtException(), true,
+ "should be waiting for an uncaught exception");
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(SimpleTest.isExpectingUncaughtException(), false,
+ "should have gotten an uncaught exception");
+
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_media_query_serialization.html b/layout/style/test/test_media_query_serialization.html
new file mode 100644
index 0000000000..ed82653db0
--- /dev/null
+++ b/layout/style/test/test_media_query_serialization.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test media query list serialization</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style>
+ @media PrInT {}
+ @media screen, PrInT, SPEECH {}
+
+ @media GARbAGE7 {}
+ @media AAAAA {}
+ @media ZZZZZ {}
+
+ @media NotAValidMediaType_!000 {}
+ @media NotAValidMediaType_!000, AlsoInvalid!!!! {}
+
+ @media PrInT, GARbAGE7, notAValidMediaType_!000 {}
+</style>
+<script type="application/javascript">
+
+let expectedValues = [
+ // Valid types
+ ["print", "Valid media types are ascii lowercased."],
+ ["screen, print, speech", "Media query lists with only valid types are ascii lowercased."],
+
+ // Invalid types
+ ["garbage7", "Invalid media types are ascii lowercased."],
+ ["aaaaa", "Ascii conversion handles 'A' correctly."],
+ ["zzzzz", "Ascii conversion handles 'Z' correctly."],
+
+ // Malformed types
+ ["not all", "Malformed media types are serialized to 'not all'."],
+ ["not all, not all", "Multiple malformed media types are each serialized to 'not all'."],
+
+ // Mixes
+ ["print, garbage7, not all", "Media query lists with a mix of valid, invalid, and malformed types serialize " +
+ "as lowercase with malformed types changed to 'not all'."],
+];
+
+let sheet = document.styleSheets[1];
+
+expectedValues.forEach(function (entry, index) {
+ let rule = sheet.cssRules[index];
+ let serializedList = rule.media.mediaText;
+ is(serializedList, entry[0], entry[1]);
+});
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/style/test/test_moz_device_pixel_ratio.html b/layout/style/test/test_moz_device_pixel_ratio.html
new file mode 100644
index 0000000000..0e3e143fe8
--- /dev/null
+++ b/layout/style/test/test_moz_device_pixel_ratio.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=474356
+-->
+<head>
+ <title>Test for Bug 474356</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>.zoom-test { visibility: hidden; }</style>
+ <style><!-- placeholder for dynamic additions --></style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=474356">Mozilla Bug 474356</a>
+<div id="content" style="display: none">
+
+</div>
+<script type="text/javascript">
+</script>
+<pre id="test">
+<div id="zoom1" class="zoom-test"></div>
+<div id="zoom2" class="zoom-test"></div>
+<div id="zoom3" class="zoom-test"></div>
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 474356 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ function zoom(factor) {
+ var previous = SpecialPowers.getFullZoom(window);
+ SpecialPowers.setFullZoom(window, factor);
+ return previous;
+ }
+
+ function isVisible(divName) {
+ return window.getComputedStyle(document.getElementById(divName)).visibility == "visible";
+ }
+
+ var screenPixelsPerCSSPixel = window.devicePixelRatio;
+ var baseRatio = 1.0 * screenPixelsPerCSSPixel;
+ var doubleRatio = 2.0 * screenPixelsPerCSSPixel;
+ var halfRatio = 0.5 * screenPixelsPerCSSPixel;
+ var styleElem = document.getElementsByTagName("style")[1];
+ styleElem.textContent =
+ ["@media all and (-moz-device-pixel-ratio: " + baseRatio + ") {",
+ "#zoom1 { visibility: visible; }",
+ "}",
+ "@media all and (-moz-device-pixel-ratio: " + doubleRatio + ") {",
+ "#zoom2 { visibility: visible; }",
+ "}",
+ "@media all and (-moz-device-pixel-ratio: " + halfRatio + ") {",
+ "#zoom3 { visibility: visible; }",
+ "}"
+ ].join("\n");
+
+ ok(isVisible("zoom1"), "Base ratio rule should apply at base zoom level");
+ ok(!isVisible("zoom2") && !isVisible("zoom3"), "no other rules should apply");
+ var origZoom = zoom(2);
+ ok(isVisible("zoom2"), "Double ratio rule should apply at double zoom level");
+ ok(!isVisible("zoom1") && !isVisible("zoom3"), "no other rules should apply");
+ zoom(0.5);
+ ok(isVisible("zoom3"), "Half ratio rule should apply at half zoom level");
+ ok(!isVisible("zoom1") && !isVisible("zoom2"), "no other rules should apply");
+ zoom(origZoom);
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_moz_prefixed_cursor.html b/layout/style/test/test_moz_prefixed_cursor.html
new file mode 100644
index 0000000000..db7ffaaf56
--- /dev/null
+++ b/layout/style/test/test_moz_prefixed_cursor.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Cursor aliases compute to the unprefixed keyword</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div></div>
+<script>
+test(function() {
+ let div = document.querySelector("div");
+ for (const kw of ["grab", "grabbing", "zoom-in", "zoom-out"]) {
+ div.style.cursor = "-moz-" + kw;
+ assert_equals(getComputedStyle(div).cursor, kw);
+ }
+}, "-moz- prefixed cursor keywords compute to its unprefixed version");
+</script>
diff --git a/layout/style/test/test_mq_any_hover_and_any_pointer.html b/layout/style/test/test_mq_any_hover_and_any_pointer.html
new file mode 100644
index 0000000000..1725af0725
--- /dev/null
+++ b/layout/style/test/test_mq_any_hover_and_any_pointer.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1483111
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1035774</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1483111">Mozilla Bug 1483111</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+
+const NO_POINTER = 0x00;
+const COARSE_POINTER = 0x01;
+const FINE_POINTER = 0x02;
+const HOVER_CAPABLE_POINTER = 0x04;
+
+var isAndroid = navigator.appVersion.includes("Android");
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['privacy.resistFingerprinting', true] ]
+ });
+
+ // When resistFingerprinting is enabled, we pretend that the system has a
+ // mouse pointer (or finger on mobile).
+ let invertIfAndroid = function(b) { return isAndroid ? !b : b; };
+
+ ok(!matchMedia("(any-pointer: none)").matches,
+ "Doesn't match (any-pointer: none)");
+ ok(matchMedia("(any-pointer)").matches, "Matches (any-pointer)");
+
+ ok(invertIfAndroid(!matchMedia("(any-pointer: coarse)").matches),
+ "Doesn't match (any-pointer: coarse)");
+ ok(invertIfAndroid(matchMedia("(any-pointer: fine)").matches), "Matches (any-pointer: fine)");
+
+ ok(invertIfAndroid(!matchMedia("(any-hover: none)").matches),
+ "Doesn't match (any-hover: none)");
+ ok(invertIfAndroid(matchMedia("(any-hover: hover)").matches),
+ "Matches (any-hover: hover)");
+ ok(invertIfAndroid(matchMedia("(any-hover)").matches), "Matches (any-hover)");
+
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async () => {
+ // No pointer.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.allPointerCapabilities', NO_POINTER] ]
+ });
+
+ ok(matchMedia("(any-pointer: none)").matches, "Matches (any-pointer: none)");
+ ok(!matchMedia("(any-pointer: coarse)").matches,
+ "Doesn't match (any-pointer: coarse)");
+ ok(!matchMedia("(any-pointer: fine)").matches,
+ "Doesn't match (any-pointer: fine)");
+ ok(!matchMedia("(any-pointer)").matches, "Matches (any-pointer)");
+
+ ok(matchMedia("(any-hover: none)").matches, "Matches (any-hover: none)");
+ ok(!matchMedia("(any-hover: hover)").matches,
+ "Doesn't match (any-hover: hover)");
+ ok(!matchMedia("(any-hover)").matches, "Doesn't match (any-hover)");
+});
+
+add_task(async () => {
+ // Mouse type pointer and touchscreen
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.allPointerCapabilities',
+ FINE_POINTER | COARSE_POINTER | HOVER_CAPABLE_POINTER] ]
+ });
+
+ ok(!matchMedia("(any-pointer: none)").matches,
+ "Doesn't match (any-pointer: none)");
+ ok(matchMedia("(any-pointer: coarse)").matches,
+ "Matches (any-pointer: coarse)");
+ ok(matchMedia("(any-pointer: fine)").matches, "Matches (any-pointer: fine)");
+ ok(matchMedia("(any-pointer)").matches, "Matches (any-pointer)");
+
+ ok(!matchMedia("(any-hover: none)").matches,
+ "Doesn't match (any-hover: none)");
+ ok(matchMedia("(any-hover: hover)").matches,
+ "Matches (any-hover: hover)");
+ ok(matchMedia("(any-hover)").matches, "Matches (any-hover)");
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mq_changes_in_iframe.html b/layout/style/test/test_mq_changes_in_iframe.html
new file mode 100644
index 0000000000..3a36476c42
--- /dev/null
+++ b/layout/style/test/test_mq_changes_in_iframe.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Media feature value change propagation in an iframe</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<iframe id="iframe"></iframe>
+<pre id="test"></pre>
+<script>
+add_task(async () => {
+ const mqString = "(prefers-reduced-motion: reduce)";
+
+ await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 0]]});
+
+ iframe.src = SimpleTest.getTestFileURL("mq_changes_child.html")
+ .replace("mochi.test:8888", "example.com");
+ await new Promise(resolve => window.addEventListener("message", event => {
+ if (event.data == "ready") {
+ resolve();
+ }
+ }, { once: true } ));
+
+ const mql = matchMedia(mqString);
+ ok(!mql.matches, `Doesn't matches ${mqString}`);
+
+ const changedInThisDocument = new Promise(resolve => {
+ mql.addEventListener("change", event => { resolve(event.matches); });
+ });
+ const changedInIFrame = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if ("matches" in event.data) {
+ resolve(event.data.matches);
+ }
+ }, { once: true });
+ });
+
+ await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 1]]});
+
+ const results =
+ await Promise.allSettled([ changedInThisDocument, changedInIFrame ]);
+
+ results.forEach(result => {
+ is(result.status, "fulfilled");
+ ok(result.value, `Matches ${mqString}`);
+ });
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mq_hover_and_pointer.html b/layout/style/test/test_mq_hover_and_pointer.html
new file mode 100644
index 0000000000..40eaed5d0b
--- /dev/null
+++ b/layout/style/test/test_mq_hover_and_pointer.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1035774
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1035774</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1035774">Mozilla Bug 1035774</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+
+const NO_POINTER = 0x00;
+const COARSE_POINTER = 0x01;
+const FINE_POINTER = 0x02;
+const HOVER_CAPABLE_POINTER = 0x04;
+
+var isAndroid = navigator.appVersion.includes("Android");
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['privacy.resistFingerprinting', true] ]
+ });
+
+ // When resistFingerprinting is enabled, we pretend that the system has a
+ // mouse pointer (or finger on mobile).
+ let invertIfAndroid = function(b) { return isAndroid ? !b : b; };
+
+ ok(!matchMedia("(pointer: none)").matches,
+ "Doesn't match (pointer: none)");
+
+ ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(invertIfAndroid(!matchMedia("(pointer: coarse)").matches),
+ "Doesn't match (pointer: coarse)");
+ ok(invertIfAndroid(matchMedia("(pointer: fine)").matches), "Matches (pointer: fine)");
+
+ ok(invertIfAndroid(!matchMedia("(hover: none)").matches), "Doesn't match (hover: none)");
+ ok(invertIfAndroid(matchMedia("(hover: hover)").matches), "Matches (hover: hover)");
+ ok(invertIfAndroid(matchMedia("(hover)").matches), "Matches (hover)");
+
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async () => {
+ // No pointer.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.primaryPointerCapabilities', NO_POINTER] ]
+ });
+
+ ok(matchMedia("(pointer: none)").matches, "Matches (pointer: none)");
+ ok(!matchMedia("(pointer: coarse)").matches,
+ "Doesn't match (pointer: coarse)");
+ ok(!matchMedia("(pointer: fine)").matches, "Doesn't match (pointer: fine)");
+ ok(!matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(matchMedia("(hover: none)").matches, "Matches (hover: none)");
+ ok(!matchMedia("(hover: hover)").matches, "Doesn't match (hover: hover)");
+ ok(!matchMedia("(hover)").matches, "Doesn't match (hover)");
+});
+
+add_task(async () => {
+ // Mouse type pointer.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.primaryPointerCapabilities', FINE_POINTER | HOVER_CAPABLE_POINTER] ]
+ });
+
+ ok(!matchMedia("(pointer: none)").matches,
+ "Doesn't match (pointer: none)");
+ ok(!matchMedia("(pointer: coarse)").matches,
+ "Doesn't match (pointer: coarse)");
+ ok(matchMedia("(pointer: fine)").matches, "Matches (pointer: fine)");
+ ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(!matchMedia("(hover: none)").matches, "Doesn't match (hover: none)");
+ ok(matchMedia("(hover: hover)").matches, "Matches (hover: hover)");
+ ok(matchMedia("(hover)").matches, "Matches (hover)");
+});
+
+add_task(async () => {
+ // Mouse type pointer.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.primaryPointerCapabilities',
+ FINE_POINTER |
+ HOVER_CAPABLE_POINTER] ]
+ });
+
+ ok(!matchMedia("(pointer: none)").matches,
+ "Doesn't match (pointer: none)");
+ ok(!matchMedia("(pointer: coarse)").matches,
+ "Doesn't match (pointer: coarse)");
+ ok(matchMedia("(pointer: fine)").matches, "Matches (pointer: fine)");
+ ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(!matchMedia("(hover: none)").matches, "Doesn't match (hover: none)");
+ ok(matchMedia("(hover: hover)").matches, "Matches (hover: hover)");
+ ok(matchMedia("(hover)").matches, "Matches (hover)");
+});
+
+add_task(async () => {
+ // Touch screen.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.primaryPointerCapabilities', COARSE_POINTER] ]
+ });
+
+ ok(!matchMedia("(pointer: none)").matches, "Doesn't match (pointer: none)");
+ ok(matchMedia("(pointer: coarse)").matches, "Matches (pointer: coarse)");
+ ok(!matchMedia("(pointer: fine)").matches, "Doesn't match (pointer: fine)");
+ ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(matchMedia("(hover: none)").matches, "Match (hover: none)");
+ ok(!matchMedia("(hover: hover)").matches, "Doesn't match (hover: hover)");
+ ok(!matchMedia("(hover)").matches, "Doesn't match (hover)");
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mq_prefers_contrast_dynamic.html b/layout/style/test/test_mq_prefers_contrast_dynamic.html
new file mode 100644
index 0000000000..c3ccebbe82
--- /dev/null
+++ b/layout/style/test/test_mq_prefers_contrast_dynamic.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1691793
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1691793</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1691793">Mozilla Bug 1691793</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+"use strict";
+
+SimpleTest.registerCleanupFunction(async () => {
+ await SpecialPowers.flushPrefEnv();
+});
+
+// Returns a Promise which will be resolved when the "change" event is received
+// for the given media query string.
+function promiseForChange(mediaQuery) {
+ return new Promise(resolve => {
+ window.matchMedia(mediaQuery).addEventListener("change", event => {
+ resolve(event.matches);
+ }, { once: true });
+ });
+}
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({ set: [["layout.css.prefers-contrast.enabled", true]]});
+ const initiallyMatches =
+ window.matchMedia("(prefers-contrast: more)").matches;
+ const changePromise = initiallyMatches ?
+ promiseForChange("(prefers-contrast: more)") : null;
+ await SpecialPowers.pushPrefEnv({ set: [["ui.useAccessibilityTheme", 0]]});
+
+ if (changePromise) {
+ await changePromise;
+ }
+
+ ok(!window.matchMedia("(prefers-contrast: more)").matches,
+ "Does not match prefers-contrast: more) when the system unsets " +
+ "UseAccessibilityTheme");
+ ok(!window.matchMedia("(prefers-contrast)").matches,
+ "Does not match (prefers-contrast) when the system unsets " +
+ "UseAccessibilityTheme");
+ ok(window.matchMedia("(prefers-contrast: no-preference)").matches,
+ "Matches (prefers-contrast: no-preference) when the system unsets " +
+ "UseAccessibilityTheme");
+});
+
+add_task(async () => {
+ const more = promiseForChange("(prefers-contrast: more)");
+ const booleanContext = promiseForChange("(prefers-contrast)");
+ const noPreference = promiseForChange("(prefers-contrast: no-preference)");
+
+ await SpecialPowers.pushPrefEnv({ set: [["ui.useAccessibilityTheme", 1]]});
+
+ const [ moreResult, booleanContextResult, noPreferenceResult ] =
+ await Promise.all([ more, booleanContext, noPreference ]);
+
+ ok(moreResult,
+ "Matches (prefers-contrast: more) when the system sets " +
+ "UseAccessibilityTheme");
+ ok(booleanContextResult,
+ "Matches (prefers-contrast) when the system sets UseAccessibilityTheme");
+ ok(!noPreferenceResult,
+ "Does not match (prefers-contrast: no-preference) when the " +
+ "system sets UseAccessibilityTheme");
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mq_prefers_reduced_motion_dynamic.html b/layout/style/test/test_mq_prefers_reduced_motion_dynamic.html
new file mode 100644
index 0000000000..570c0d3954
--- /dev/null
+++ b/layout/style/test/test_mq_prefers_reduced_motion_dynamic.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1486971
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1478519</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1486971">Mozilla Bug 1486971</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+'use strict';
+
+async function waitForFrame() {
+ return new Promise(resolve => {
+ window.requestAnimationFrame(resolve);
+ });
+}
+
+// Returns a Promise which will be resolved when the 'change' event is received
+// for the given media query string.
+async function promiseForChange(mediaQuery) {
+ return new Promise(resolve => {
+ window.matchMedia(mediaQuery).addEventListener('change', event => {
+ resolve(event.matches);
+ }, { once: true });
+ });
+}
+
+add_task(async () => {
+ const initiallyMatches =
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+
+ const changePromise = initiallyMatches ? promiseForChange('(prefers-reduced-motion: reduce)') : null;
+
+ await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 0]]});
+
+ if (changePromise) {
+ await changePromise;
+ }
+
+ ok(!window.matchMedia('(prefers-reduced-motion: reduce)').matches,
+ 'Does not matches prefers-reduced-motion: reduced) when the system sets ' +
+ 'prefers-reduced-motion false');
+ ok(!window.matchMedia('(prefers-reduced-motion)').matches,
+ 'Does not matches (prefers-reduced-motion) when the system sets ' +
+ 'prefers-reduced-motion false');
+ ok(window.matchMedia('(prefers-reduced-motion: no-preference)').matches,
+ 'Matches (prefers-reduced-motion: no-preference) when the system sets ' +
+ 'prefers-reduced-motion false');
+});
+
+add_task(async () => {
+ const reduce = promiseForChange('(prefers-reduced-motion: reduce)');
+ const booleanContext = promiseForChange('(prefers-reduced-motion)');
+ const noPreference = promiseForChange('(prefers-reduced-motion: no-preference)');
+
+ await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 1]]});
+
+ const [ reduceResult, booleanContextResult, noPreferenceResult ] =
+ await Promise.all([ reduce, booleanContext, noPreference ]);
+
+ ok(reduceResult,
+ 'Matches (prefers-reduced-motion: reduced) when the system sets ' +
+ 'prefers-reduced-motion true');
+ ok(booleanContextResult,
+ 'Matches (prefers-reduced-motion) when the system sets ' +
+ 'prefers-reduced-motion true');
+ ok(!noPreferenceResult,
+ 'Does not matches (prefers-reduced-motion: no-preference) when the ' +
+ 'system sets prefers-reduced-motion true');
+
+ await SpecialPowers.flushPrefEnv();
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mql_event_listener_leaks.html b/layout/style/test/test_mql_event_listener_leaks.html
new file mode 100644
index 0000000000..3ceb5412a2
--- /dev/null
+++ b/layout/style/test/test_mql_event_listener_leaks.html
@@ -0,0 +1,43 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1450271 - Test MediaQueryList event listener leak conditions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+// Manipulate MediaQueryList. Its important here that we create a
+// listener callback from the DOM objects back to the frame's global
+// in order to exercise the leak condition.
+async function useMediaQuery(contentWindow) {
+ contentWindow.messageCount = 0;
+
+ let mql = contentWindow.matchMedia("(max-width: 600px)");
+ mql.onchange = _ => {
+ contentWindow.mediaCount += 1;
+ };
+}
+
+async function runTest() {
+ try {
+ await checkForEventListenerLeaks("MediaQueryList", useMediaQuery);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", runTest, { once: true });
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_namespace_rule.html b/layout/style/test/test_namespace_rule.html
new file mode 100644
index 0000000000..6e8ba52c90
--- /dev/null
+++ b/layout/style/test/test_namespace_rule.html
@@ -0,0 +1,461 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS Namespace rules</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<p id="display"><iframe id="iframe" src="data:application/xhtml+xml,<html%20xmlns='http://www.w3.org/1999/xhtml'><head/><body/></html>"></iframe></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function run() {
+ var wrappedFrame = SpecialPowers.wrap($("iframe"));
+ var ifwin = wrappedFrame.contentWindow;
+ var ifdoc = wrappedFrame.contentDocument;
+ var ifbody = ifdoc.getElementsByTagName("body")[0];
+
+ function setup_style_text() {
+ var style_elem = ifdoc.createElement("style");
+ style_elem.setAttribute("type", "text/css");
+ ifdoc.getElementsByTagName("head")[0].appendChild(style_elem);
+ let style_text = ifdoc.createCDATASection("");
+ style_elem.appendChild(style_text);
+ return style_text;
+ }
+
+ let style_text = setup_style_text();
+ var gCounter = 0;
+
+ /*
+ * namespaceRules: the @namespace rules to use
+ * selector: the selector to test
+ * body_contents: what to set the body's innerHTML to
+ * match_fn: a function that, given the document object into which
+ * body_contents has been inserted, produces an array of nodes that
+ * should match selector
+ * notmatch_fn: likewise, but for nodes that should not match
+ */
+ function test_selector_in_html(namespaceRules, selector, body_contents,
+ match_fn, notmatch_fn)
+ {
+ var zi = ++gCounter;
+ if (typeof(body_contents) == "string") {
+ ifbody.innerHTML = body_contents;
+ } else {
+ // It's a function.
+ ifbody.innerHTML = "";
+ body_contents(ifbody);
+ }
+ style_text.data =
+ namespaceRules + " " + selector + "{ z-index: " + zi + " }";
+ var should_match = match_fn(ifdoc);
+ var should_not_match = notmatch_fn(ifdoc);
+ if (should_match.length + should_not_match.length == 0) {
+ ok(false, "nothing to check");
+ }
+
+ for (var i = 0; i < should_match.length; ++i) {
+ var e = should_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched " + selector);
+ }
+ for (var i = 0; i < should_not_match.length; ++i) {
+ var e = should_not_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match " + selector);
+ }
+
+ // Now, since we're here, may as well make sure serialization
+ // works correctly. It need not produce the exact same text,
+ // but it should produce a selector that matches the same
+ // elements.
+ zi = ++gCounter;
+ var ruleList = style_text.parentNode.sheet.cssRules;
+ var ser1 = ruleList[ruleList.length-1].selectorText;
+ style_text.data =
+ namespaceRules + " " + ser1 + "{ z-index: " + zi + " }";
+ for (var i = 0; i < should_match.length; ++i) {
+ var e = should_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched " + ser1 +
+ " which is the reserialization of " + selector);
+ }
+ for (var i = 0; i < should_not_match.length; ++i) {
+ var e = should_not_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match " + ser1 +
+ " which is the reserialization of " + selector);
+ }
+
+ // But when we serialize the serialized result, we should get
+ // the same text.
+ var ser2 = ruleList[ruleList.length-1].selectorText;
+ is(ser2, ser1, "parse+serialize of selector \"" + selector +
+ "\" is idempotent");
+
+ ifbody.innerHTML = "";
+ style_text.data = "";
+ }
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-001.xml
+ test_selector_in_html(
+ '@namespace foo "x"; @namespace Foo "y";',
+ 'Foo|test',
+ '<test xmlns="y"/>',
+ function (doc) { return doc.getElementsByTagName("test"); },
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace foo "x"; @namespace Foo "y";',
+ 'foo|test',
+ '<test xmlns="y"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-002.xml
+ test_selector_in_html(
+ '@namespace foo "";',
+ 'test',
+ '<test xmlns=""/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace foo "";',
+ 'foo|test',
+ '<test xmlns=""/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-003.xml
+ test_selector_in_html(
+ '@namespace foo "";',
+ 'test',
+ '<foo xmlns=""><test/></foo>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace foo "";',
+ 'foo|test',
+ '<foo xmlns=""><test/></foo>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // 4 tests from http://tc.labs.opera.com/css/namespaces/prefix-004.xml
+ test_selector_in_html(
+ '@namespace ""; @namespace x "test";',
+ 'test[x]',
+ '<foo xmlns=""><test x=""/></foo>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace ""; @namespace x "test";',
+ '*|test',
+ '<foo xmlns=""><test x=""/></foo>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace ""; @namespace x "test";',
+ '*|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace ""; @namespace x "test";',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-005.xml
+ test_selector_in_html(
+ '@namespace x "test";',
+ 'test',
+ '<test/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace x "test";',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // Skipping the scope tests because they involve import, and we have no way
+ // to know when the import load completes.
+
+ // 1 test from http://tc.labs.opera.com/css/namespaces/syntax-001.xml
+ test_selector_in_html(
+ '@NAmespace x "http://www.w3.org/1999/xhtml";',
+ 'x|test',
+ '<test/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // 1 test from http://tc.labs.opera.com/css/namespaces/syntax-002.xml
+ test_selector_in_html(
+ '@NAmespac\\65 x "http://www.w3.org/1999/xhtml";',
+ 'x|test',
+ '<test/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // 3 tests from http://tc.labs.opera.com/css/namespaces/syntax-003.xml
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ 'test',
+ '<test/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // 3 tests from http://tc.labs.opera.com/css/namespaces/syntax-004.xml
+ test_selector_in_html(
+ '@namespace u\\00072l("test");',
+ '*|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace u\\00072l("test");',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace u\\00072l("test");',
+ 'test',
+ '<test/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // Skipping http://tc.labs.opera.com/css/namespaces/syntax-005.xml because it
+ // involves import, and we have no way // to know when the import load completes.
+
+ // Skipping http://tc.labs.opera.com/css/namespaces/syntax-006.xml because it
+ // involves import, and we have no way // to know when the import load completes.
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/syntax-007.xml
+ test_selector_in_html(
+ '@charset "x"; @namespace url("test"); @namespace url("test2");',
+ '*|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@charset "x"; @namespace url("test"); @namespace url("test2");',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/syntax-008.xml
+ test_selector_in_html(
+ '@namespace \\72x url("test");',
+ 'rx|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace \\72x url("test");',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // And now some :not() tests
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not(test)',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not(test)',
+ '<test xmlns="testing"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace x url("test");',
+ '*|*:not(x|test)',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace x url("test");',
+ '*|*:not(x|test)',
+ '<test xmlns="testing"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not(*)',
+ '<test xmlns="testing"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not(*)',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace x url("test");',
+ '*|*:not(x|*)',
+ '<test xmlns="testing"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace x url("test");',
+ '*|*:not(x|*)',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not([foo])',
+ '<test xmlns="testing" foo="bar"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not([foo])',
+ '<test xmlns="test" foo="bar"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*[foo]',
+ '<test foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*[|foo]',
+ '<test foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace test url("test");',
+ '*|*[test|foo]',
+ '<test foo="bar"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace test url("test");',
+ '*|*[test|foo]',
+ '<test xmlns:t="test" t:foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*[foo]',
+ '<test xmlns:t="test" t:foo="bar"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*[*|foo]',
+ '<test xmlns:t="test" t:foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '',
+ '*|*[*|foo]',
+ '<test xmlns:t="test" t:foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_non_content_accessible_env_vars.html b/layout/style/test/test_non_content_accessible_env_vars.html
new file mode 100644
index 0000000000..d9714216b7
--- /dev/null
+++ b/layout/style/test/test_non_content_accessible_env_vars.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe></iframe>
+<iframe srcdoc="Foo"></iframe>
+<script>
+const NON_CONTENT_ACCESSIBLE_ENV_VARS = [
+ "-moz-gtk-csd-titlebar-radius",
+ "-moz-gtk-csd-minimize-button-position",
+ "-moz-gtk-csd-maximize-button-position",
+ "-moz-gtk-csd-close-button-position",
+ "-moz-content-preferred-color-scheme",
+ "-moz-overlay-scrollbar-fade-duration",
+ "scrollbar-inline-size",
+];
+
+function testInWin(win) {
+ let doc = win.document;
+ const div = doc.createElement("div");
+ doc.documentElement.appendChild(div);
+ for (const envVar of NON_CONTENT_ACCESSIBLE_ENV_VARS) {
+ div.style.setProperty("--foo", `env(${envVar},FALLBACK_VALUE)`);
+
+ assert_equals(
+ win.getComputedStyle(div).getPropertyValue("--foo"),
+ "FALLBACK_VALUE",
+ `${envVar} shouldn't be exposed to content in ${doc.documentURI}`
+ );
+ }
+}
+
+let t = async_test("Test non-content-accessible env() vars");
+onload = t.step_func_done(function() {
+ testInWin(window);
+ for (let f of document.querySelectorAll("iframe")) {
+ testInWin(f.contentWindow);
+ }
+});
+</script>
diff --git a/layout/style/test/test_non_content_accessible_properties.html b/layout/style/test/test_non_content_accessible_properties.html
new file mode 100644
index 0000000000..220ddf2d26
--- /dev/null
+++ b/layout/style/test/test_non_content_accessible_properties.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe></iframe>
+<iframe srcdoc="Foo"></iframe>
+<script>
+const CHROME_ONLY_PROPERTIES = [
+ "-moz-window-input-region-margin",
+ "-moz-window-shadow",
+ "-moz-window-opacity",
+ "-moz-window-transform",
+ "-moz-window-transform-origin",
+ "-moz-default-appearance",
+ "-moz-user-focus",
+];
+
+const UA_ONLY_PROPERTIES = [
+ "-x-span",
+ "-x-lang",
+ "-x-text-scale",
+ "-moz-control-character-visibility",
+ "-moz-top-layer",
+ "-moz-script-level",
+ "-moz-math-display",
+ "-moz-math-variant",
+ "-moz-inert",
+ "-moz-min-font-size-ratio",
+ // TODO: This should ideally be in CHROME_ONLY_PROPERTIES, but due to how
+ // [Pref] and [ChromeOnly] interact in WebIDL, the former wins.
+ "-moz-context-properties",
+];
+
+function testInWin(win) {
+ const doc = win.document;
+ const sheet = doc.createElement("style");
+ const div = doc.createElement("div");
+ doc.documentElement.appendChild(sheet);
+ doc.documentElement.appendChild(div);
+
+ sheet.textContent = `div { color: initial }`;
+ assert_equals(sheet.sheet.cssRules[0].style.length, 1, `sanity: ${doc.documentURI}`);
+
+ for (const prop of CHROME_ONLY_PROPERTIES.concat(UA_ONLY_PROPERTIES)) {
+ sheet.textContent = `div { ${prop}: initial }`;
+ let block = sheet.sheet.cssRules[0].style;
+ assert_false(prop in block, `${prop} shouldn't be exposed in CSS2Properties`);
+
+ let isUAOnly = UA_ONLY_PROPERTIES.includes(prop);
+ assert_equals(prop in SpecialPowers.wrap(block), !isUAOnly, `${prop} should be exposed to chrome code if needed`);
+
+ assert_equals(
+ block.length,
+ 0,
+ `${prop} shouldn't be parsed in ${doc.documentURI}`
+ );
+ block.setProperty(prop, "initial");
+ assert_equals(
+ block.length,
+ 0,
+ `${prop} shouldn't be settable via CSSOM in ${doc.documentURI}`
+ );
+ assert_equals(
+ win.getComputedStyle(div).getPropertyValue(prop),
+ "",
+ `${prop} shouldn't be accessible via CSSOM in ${doc.documentURI}`
+ );
+ assert_false(
+ win.CSS.supports(prop + ': initial'),
+ `${prop} shouldn't be exposed in CSS.supports in ${doc.documentURI}`
+ );
+ assert_false(
+ win.CSS.supports(prop, 'initial'),
+ `${prop} shouldn't be exposed in CSS.supports in ${doc.documentURI} (2-value version)`
+ );
+ }
+}
+
+let t = async_test("test non-content-accessible props");
+onload = t.step_func_done(function() {
+ testInWin(window);
+ for (let f of document.querySelectorAll("iframe")) {
+ testInWin(f.contentWindow);
+ }
+});
+</script>
diff --git a/layout/style/test/test_non_content_accessible_pseudos.html b/layout/style/test/test_non_content_accessible_pseudos.html
new file mode 100644
index 0000000000..81493b1a4a
--- /dev/null
+++ b/layout/style/test/test_non_content_accessible_pseudos.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style id="sheet"></style>
+<script>
+// Even though some of these no longer exist, we still do want to test that
+// they aren't exposed to the web.
+const NON_CONTENT_ACCESIBLE_PSEUDOS = [
+ "::-moz-complex-control-wrapper",
+ "::-moz-number-wrapper",
+ "::-moz-number-text",
+ "::-moz-number-spin-up",
+ "::-moz-number-spin-down",
+ "::-moz-number-spin-box",
+ "::-moz-search-clear-button",
+
+ ":-moz-native-anonymous",
+ ":-moz-table-border-nonzero",
+ ":-moz-browser-frame",
+ ":-moz-devtools-highlighted",
+ ":-moz-styleeditor-transitioning",
+ ":-moz-handler-clicktoplay",
+ ":-moz-handler-vulnerable-updatable",
+ ":-moz-handler-vulnerable-no-update",
+ ":-moz-handler-disabled",
+ ":-moz-handler-blocked",
+ ":-moz-handler-chrased",
+ ":-moz-has-dir-attr",
+ ":-moz-dir-attr-ltr",
+ ":-moz-dir-attr-rtl",
+ ":-moz-dir-attr-like-auto",
+ ":-moz-autofill-preview",
+ ":-moz-lwtheme",
+ ":-moz-is-html",
+ ":-moz-locale-dir(rtl)",
+ ":-moz-locale-dir(ltr)",
+
+ "::-moz-tree-row",
+ "::-moz-tree-row(foo)",
+];
+
+test(function() {
+ sheet.textContent = `div { color: initial }`;
+ assert_equals(sheet.sheet.cssRules.length, 1);
+}, "sanity");
+
+for (const pseudo of NON_CONTENT_ACCESIBLE_PSEUDOS) {
+ test(function() {
+ sheet.textContent = `${pseudo} { color: blue; }`;
+ assert_equals(sheet.sheet.cssRules.length, 0,
+ pseudo + " shouldn't be accessible to content");
+ if (pseudo.indexOf("::") === 0) {
+ let pseudoStyle;
+ try {
+ pseudoStyle = getComputedStyle(document.documentElement, pseudo);
+ } catch (ex) {
+ // We can hit this when layout.css.computed-style.throw-on-invalid-pseudo is true
+ assert_true(true, `${pseudo} shouldn't be visible to content`);
+ return;
+ }
+ let elementStyle = getComputedStyle(document.documentElement);
+ for (prop of pseudoStyle) {
+ assert_equals(pseudoStyle[prop], elementStyle[prop],
+ pseudo + " styles shouldn't be visible from getComputedStyle");
+ }
+ }
+ }, pseudo);
+}
+</script>
diff --git a/layout/style/test/test_non_content_accessible_values.html b/layout/style/test/test_non_content_accessible_values.html
new file mode 100644
index 0000000000..633427f8e1
--- /dev/null
+++ b/layout/style/test/test_non_content_accessible_values.html
@@ -0,0 +1,172 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style id="sheet"></style>
+<div></div>
+<script>
+const NON_CONTENT_ACCESSIBLE_VALUES = {
+ "color": [
+ "-moz-buttonactivetext",
+ "-moz-buttonactiveface",
+ "-moz-buttondisabledface",
+ "-moz-disabledfield",
+ "-moz-colheaderhovertext",
+ "-moz-colheadertext",
+ "-moz-nativevisitedhyperlinktext",
+ "text-select-disabled-background",
+ "text-select-attention-background",
+ "text-select-attention-foreground",
+ "-moz-autofill-background",
+ ],
+ "display": [
+ "-moz-box",
+ "-moz-inline-box",
+ ],
+ "font": [
+ "-moz-pull-down-menu",
+ "-moz-button",
+ "-moz-list",
+ "-moz-field",
+ ],
+ "-moz-appearance": [
+ "button-arrow-down",
+ "button-arrow-next",
+ "button-arrow-previous",
+ "button-arrow-up",
+ "button-focus",
+ "dualbutton",
+ "groupbox",
+ "menubar",
+ "menuitem",
+ "checkmenuitem",
+ "radiomenuitem",
+ "menuitemtext",
+ "menupopup",
+ "menucheckbox",
+ "menuradio",
+ "menuseparator",
+ "menuimage",
+ "-moz-menulist-arrow-button",
+ "checkbox-container",
+ "radio-container",
+ "checkbox-label",
+ "radio-label",
+ "resizerpanel",
+ "resizer",
+ "scrollbar",
+ "scrollbar-small",
+ "scrollbar-horizontal",
+ "scrollbar-vertical",
+ "scrollbarbutton-up",
+ "scrollbarbutton-down",
+ "scrollbarbutton-left",
+ "scrollbarbutton-right",
+ "scrollcorner",
+ "separator",
+ "spinner",
+ "spinner-upbutton",
+ "spinner-downbutton",
+ "spinner-textfield",
+ "splitter",
+ "statusbar",
+ "statusbarpanel",
+ "tab",
+ "tabpanel",
+ "tabpanels",
+ "tab-scroll-arrow-back",
+ "tab-scroll-arrow-forward",
+ "toolbar",
+ "toolbarbutton",
+ "toolbarbutton-dropdown",
+ "toolbargripper",
+ "toolbox",
+ "tooltip",
+ "treeheader",
+ "treeheadercell",
+ "treeheadersortarrow",
+ "treeitem",
+ "treeline",
+ "treetwisty",
+ "treetwistyopen",
+ "treeview",
+ "window",
+ "dialog",
+ "-moz-win-communications-toolbox",
+ "-moz-win-media-toolbox",
+ "-moz-win-browsertabbar-toolbox",
+ "-moz-win-borderless-glass",
+ "-moz-win-exclude-glass",
+ "-moz-mac-help-button",
+ "-moz-window-button-box",
+ "-moz-window-button-box-maximized",
+ "-moz-window-button-close",
+ "-moz-window-button-maximize",
+ "-moz-window-button-minimize",
+ "-moz-window-button-restore",
+ "-moz-window-titlebar",
+ "-moz-window-titlebar-maximized",
+ "-moz-mac-active-source-list-selection",
+ "-moz-mac-disclosure-button-closed",
+ "-moz-mac-disclosure-button-open",
+ "-moz-mac-source-list",
+ "-moz-mac-source-list-selection",
+
+ "button-bevel",
+ "caret",
+ "listitem",
+ "menulist-textfield",
+ "menulist-text",
+ ],
+ "user-select": [
+ "-moz-text",
+ ],
+ "line-height": [
+ "-moz-block-height",
+ ],
+ "text-align": [
+ "-moz-center-or-inherit",
+ ],
+};
+
+const sheet = document.getElementById("sheet");
+const div = document.querySelector("div");
+
+test(function() {
+ sheet.textContent = `div { color: initial }`;
+ assert_equals(sheet.sheet.cssRules[0].style.length, 1);
+}, "sanity");
+
+for (const prop in NON_CONTENT_ACCESSIBLE_VALUES) {
+ const values = NON_CONTENT_ACCESSIBLE_VALUES[prop];
+ test(function() {
+ for (const value of values) {
+ sheet.textContent = `div { ${prop}: ${value} }`;
+ const block = sheet.sheet.cssRules[0].style;
+ assert_equals(
+ block.length,
+ 0,
+ `${prop}: ${value} should not be parsed in content`
+ );
+ block.setProperty(prop, value);
+ assert_equals(
+ block.length,
+ 0,
+ `${prop}: ${value} should not be settable via CSSOM in content`
+ );
+ div.style.setProperty(prop, value);
+ assert_equals(
+ div.style.length,
+ 0,
+ `${prop}: ${value} should not be settable via CSSOM in content (inline style)`
+ );
+ assert_not_equals(
+ getComputedStyle(div).getPropertyValue(prop),
+ value,
+ `${prop}: ${value} should not be settable via CSSOM in content (gcs)`
+ );
+
+ assert_false(CSS.supports(prop, value), `${prop}: ${value} should not claim to be supported`)
+ }
+ }, prop + " non-accessible values: " + values.join(", "))
+}
+</script>
diff --git a/layout/style/test/test_non_matching_sheet_media.html b/layout/style/test/test_non_matching_sheet_media.html
new file mode 100644
index 0000000000..a57444df45
--- /dev/null
+++ b/layout/style/test/test_non_matching_sheet_media.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1386840 -->
+<title>Test for Bug 1386840: non-matching media list doesn't block rendering</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test("Test that <link> doesn't block rendering with non-matching media");
+var loadFired = false;
+var scriptExecuted = false;
+
+function sheetLoaded() {
+ loadFired = true;
+ assert_true(scriptExecuted, "Shouldn't wait for load to execute script");
+ t.done();
+}
+</script>
+<!--
+ NOTE(emilio): This can theoretically race if an HTTP packet boundary with a
+ very long delay came right after the link and before the script. If this
+ ever becomes a problem, the way to fix this is using document.write() as
+ explained in bug 1386840 comment 12.
+-->
+<link rel="stylesheet" href="data:text/css,foo {}" media="print" onload="t.step(sheetLoaded)">
+<script>
+ t.step(function() {
+ scriptExecuted = true;
+ assert_false(loadFired, "Shouldn't have waited for load");
+ });
+</script>
diff --git a/layout/style/test/test_of_type_selectors.xhtml b/layout/style/test/test_of_type_selectors.xhtml
new file mode 100644
index 0000000000..edf2e6ee97
--- /dev/null
+++ b/layout/style/test/test_of_type_selectors.xhtml
@@ -0,0 +1,97 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=75375
+-->
+<head>
+ <title>Test for *-of-type selectors in Bug 75375</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=75375">Mozilla Bug 75375</a>
+<div id="content" style="display: none"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+<p>This is a <code>p</code> element in the HTML namespace.</p>
+<p>This is a second <code>p</code> element in the HTML namespace.</p>
+<html:p>This is an <code>html:p</code> element in the HTML namespace.</html:p>
+<p xmlns="http://www.example.com/ns">This is a <code>p</code> element in the <code>http://www.example.com/ns</code> namespace.</p>
+<html:address>This is an <code>html:address</code> element in the HTML namespace.</html:address>
+<address xmlns="">This is a <code>address</code> element in no namespace.</address>
+<address xmlns="">This is a <code>address</code> element in no namespace.</address>
+<p xmlns="">This is a <code>p</code> element in no namespace.</p>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for *-of-type selectors in Bug 75375 **/
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function setup_style_text() {
+ var result = document.createCDATASection("");
+ var style = document.createElementNS(HTML_NS, "style");
+ style.appendChild(result);
+ document.getElementsByTagName("head")[0].appendChild(style);
+ return result;
+}
+
+function run() {
+ var styleText = setup_style_text();
+
+ var elements = [];
+
+ var div = document.getElementById("content");
+ for (let i = 0; i < div.childNodes.length; ++i) {
+ var child = div.childNodes[i];
+ if (child.nodeType == Node.ELEMENT_NODE)
+ elements.push(child);
+ }
+
+ var counter = 0;
+
+ function test_selector(selector, match_indices, notmatch_indices)
+ {
+ var zi = ++counter;
+ styleText.data = selector + " { z-index: " + zi + " }";
+ let i;
+ for (i in match_indices) {
+ var e = elements[match_indices[i]];
+ is(getComputedStyle(e, "").zIndex, String(zi),
+ "element " + match_indices[i] + " matched " + selector);
+ }
+ for (i in notmatch_indices) {
+ var e = elements[notmatch_indices[i]];
+ is(getComputedStyle(e, "").zIndex, "auto",
+ "element " + notmatch_indices[i] + " did not match " + selector);
+ }
+ }
+
+ // 0 - html:p
+ // 1 - html:p
+ // 2 - html:p
+ // 3 - example:p
+ // 4 - html:address
+ // 5 - :address
+ // 6 - :address
+ // 7 - :p
+ test_selector(":nth-of-type(1)", [0, 3, 4, 5, 7], [1, 2, 6]);
+ test_selector(":nth-last-of-type(1)", [2, 3, 4, 6, 7], [0, 1, 5]);
+ test_selector(":nth-last-of-type(-n+1)", [2, 3, 4, 6, 7], [0, 1, 5]);
+ test_selector(":nth-of-type(even)", [1, 6], [0, 2, 3, 4, 5, 7]);
+ test_selector(":nth-last-of-type(odd)", [0, 2, 3, 4, 6, 7], [1, 5]);
+ test_selector(":nth-last-of-type(n+2)", [0, 1, 5], [2, 3, 4, 6, 7]);
+ test_selector(":first-of-type", [0, 3, 4, 5, 7], [1, 2, 6]);
+ test_selector(":last-of-type", [2, 3, 4, 6, 7], [0, 1, 5]);
+ test_selector(":only-of-type", [3, 4, 7], [0, 1, 2, 5, 6]);
+}
+
+run();
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_overscroll_behavior_pref.html b/layout/style/test/test_overscroll_behavior_pref.html
new file mode 100644
index 0000000000..c12d4ce081
--- /dev/null
+++ b/layout/style/test/test_overscroll_behavior_pref.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test pref for overscroll-behavior property</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+<script class="testbody" type="text/javascript">
+function runTest() {
+ let css = "div { overscroll-behavior: auto; }";
+ let style = document.createElement('style');
+ style.appendChild(document.createTextNode(css));
+ document.head.appendChild(style);
+
+ is(document.styleSheets[0].cssRules[0].style.length,
+ 0,
+ "overscroll-behavior shouldn't be parsed if the pref is off");
+ SimpleTest.finish();
+}
+SpecialPowers.pushPrefEnv({ set: [["layout.css.overscroll-behavior.enabled", false]] },
+ runTest);
+SimpleTest.waitForExplicitFinish();
+</script>
+</html>
diff --git a/layout/style/test/test_page_parser.html b/layout/style/test/test_page_parser.html
new file mode 100644
index 0000000000..6f6860d0f2
--- /dev/null
+++ b/layout/style/test/test_page_parser.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=115199 -->
+<head>
+ <meta charset="UTF-8">
+ <title>Test of @page parser</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<p>@page parsing (<a
+ target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=115199"
+>bug 115199</a>)</p>
+<pre id="display"></pre>
+<style type="text/css" id="testbox"></style>
+<script class="testbody" type="text/javascript">
+ function _(b) { return "@page { " + b + " }"; };
+
+ var testset = [
+ // CSS 2.1 only allows margin properties in the page rule.
+
+ // Check a bad property.
+ { rule: _("position: absolute;") },
+
+ // Check good properties with invalid units.
+ { rule: _("margin: 2in; margin: 2vw;"), expected: {
+ "margin-top": "2in",
+ "margin-right": "2in",
+ "margin-bottom": "2in",
+ "margin-left": "2in"
+ }},
+ { rule: _("margin-top: 2in; margin-top: 2vw;"), expected: {"margin-top": "2in"}},
+ { rule: _("margin-top: 2in; margin-top: 2vh;"), expected: {"margin-top": "2in"}},
+ { rule: _("margin-top: 2in; margin-top: 2vmax;"), expected: {"margin-top": "2in"}},
+ { rule: _("margin-top: 2in; margin-top: 2vmin;"), expected: {"margin-top": "2in"}},
+
+ // Check good properties.
+ { rule: _("margin: 2in;"), expected: {
+ "margin-top": "2in",
+ "margin-right": "2in",
+ "margin-bottom": "2in",
+ "margin-left": "2in"
+ }},
+ { rule: _("margin-top: 2in;"), expected: {"margin-top": "2in"}},
+ { rule: _("margin-left: 2in;"), expected: {"margin-left": "2in"}},
+ { rule: _("margin-bottom: 2in;"), expected: {"margin-bottom": "2in"}},
+ { rule: _("margin-right: 2in;"), expected: {"margin-right": "2in"}}
+ ];
+
+ var display = document.getElementById("display");
+ var sheet = document.styleSheets[1];
+
+ for (var curTest = 0; curTest < testset.length; curTest++) {
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+ sheet.insertRule(testset[curTest].rule, 0);
+
+ try {
+ is(sheet.cssRules.length, 1,
+ testset[curTest].rule + " rule count");
+ is(sheet.cssRules[0].type, CSSRule.PAGE_RULE,
+ testset[curTest].rule + " rule type");
+
+ if (testset[curTest].expected) {
+ var expected = testset[curTest].expected;
+ var s = sheet.cssRules[0].style;
+ var n = 0;
+
+ // everything is set that should be
+ for (var name in expected) {
+ is(s.getPropertyValue(name), expected[name],
+ testset[curTest].rule + " (prop " + name + ")");
+ n++;
+ }
+ // nothing else is set
+ is(s.length, n, testset[curTest].rule + " prop count");
+ for (var i = 0; i < s.length; i++) {
+ ok(s[i] in expected, testset[curTest].rule +
+ " - Unexpected item #" + i + ": " + s[i]);
+ }
+ } else {
+ is(Object.keys(sheet.cssRules[0].style).length, 0,
+ testset[curTest].rule + " rule has no properties");
+ }
+ } catch (e) {
+ ok(false, testset[curTest].rule + " - During test: " + e);
+ }
+ }
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_parse_eof.html b/layout/style/test/test_parse_eof.html
new file mode 100644
index 0000000000..63ef95b980
--- /dev/null
+++ b/layout/style/test/test_parse_eof.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test parsing behaviour of backslash just before EOF</title>
+ <link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+ <meta name="flags" content="">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+
+<style>#a::before { content: "ab\</style>
+<style>#b { background-image: url("ab\</style>
+<style>#c { background-image: url(ab\</style>
+<style>#d { counter-reset: ab\</style>
+
+<style>
+#a-ref::before { content: "ab"; }
+#b-ref { background-image: url("ab"); }
+#c-ref { background-image: url(ab�); }
+#d-ref { counter-reset: ab�; }
+</style>
+
+<div style="display: none">
+ <div id="a"></div>
+ <div id="b"></div>
+ <div id="c"></div>
+ <div id="d"></div>
+
+ <div id="a-ref"></div>
+ <div id="b-ref"></div>
+ <div id="c-ref"></div>
+ <div id="d-ref"></div>
+</div>
+
+<script>
+var a = document.getElementById("a");
+var b = document.getElementById("b");
+var c = document.getElementById("c");
+var d = document.getElementById("d");
+var a_ref = document.getElementById("a-ref");
+var b_ref = document.getElementById("b-ref");
+var c_ref = document.getElementById("c-ref");
+var d_ref = document.getElementById("d-ref");
+
+test(function() {
+ assert_equals(window.getComputedStyle(a, ":before").content,
+ window.getComputedStyle(a_ref, ":before").content);
+}, "test backslash before EOF inside a string");
+
+test(function() {
+ assert_equals(window.getComputedStyle(b).backgroundImage,
+ window.getComputedStyle(b_ref).backgroundImage);
+}, "test backslash before EOF inside a url(\"\")");
+
+test(function() {
+ assert_equals(window.getComputedStyle(c).backgroundImage,
+ window.getComputedStyle(c_ref).backgroundImage);
+}, "test backslash before EOF inside a url()");
+
+test(function() {
+ assert_equals(window.getComputedStyle(d).counterReset,
+ window.getComputedStyle(d_ref).counterReset);
+}, "test backslash before EOF outside a string");
+</script>
+
+</body>
+</html>
diff --git a/layout/style/test/test_parse_ident.html b/layout/style/test/test_parse_ident.html
new file mode 100644
index 0000000000..390412e2fc
--- /dev/null
+++ b/layout/style/test/test_parse_ident.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS identifier parsing</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+var div = document.getElementById("content");
+
+function counter_increment_parses(i)
+{
+ div.style.counterIncrement = "";
+ div.style.counterIncrement = i;
+ return div.style.counterIncrement != "";
+}
+
+function is_valid_identifier(i)
+{
+ ok(counter_increment_parses(i),
+ "'" + i + "' is a valid CSS identifier");
+}
+
+function is_invalid_identifier(i)
+{
+ ok(!counter_increment_parses(i),
+ "'" + i + "' is not a valid CSS identifier");
+}
+
+for (var i = 0x7B; i < 0x80; ++i) {
+ is_invalid_identifier(String.fromCharCode(i));
+ is_invalid_identifier("a" + String.fromCharCode(i));
+ is_invalid_identifier(String.fromCharCode(i) + "a");
+}
+
+for (var i = 0x80; i < 0xFF; ++i) {
+ is_valid_identifier(String.fromCharCode(i));
+}
+
+is_valid_identifier(String.fromCharCode(0x100));
+is_valid_identifier(String.fromCharCode(0x375));
+is_valid_identifier(String.fromCharCode(0xFEFF));
+is_valid_identifier(String.fromCharCode(0xFFFD));
+is_valid_identifier(String.fromCharCode(0xFFFE));
+is_valid_identifier(String.fromCharCode(0xFFFF));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_parse_rule.html b/layout/style/test/test_parse_rule.html
new file mode 100644
index 0000000000..0fb1cc80ed
--- /dev/null
+++ b/layout/style/test/test_parse_rule.html
@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<html lang=en>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<body>
+<iframe></iframe>
+<!-- Note that the following style and div elements are duplicates
+ of the ones written into the iframe; they are here for convienience
+ in resolving the "standard" computed value for a given specification
+-->
+<style></style>
+<div id=a class='a b c' title='zxcv weeqweqeweasd&#13;&#10;a&#10;'></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+window.onload=function(){
+
+var base;
+
+// A short note about escaping: all of the strings in this test go through
+// Javascript unescaping before getting passed to CSS. This means that
+// sequences like "\n" refer to a newline, a single backslash is written "\\",
+// a CSS escape sequence is something like "\\A", and some quotes must be
+// escaped.
+
+var testset = [
+
+// Color tests
+// Generic property for testing
+{ base : base = "div {color:green}",
+ tests : [
+// My misc tests
+"<!--#a {color:green}",
+base + "<!-#a {color:red}",
+base + "#a<!--{color:red}",
+"-->#a{color:green}",
+base + "--#a {color:red}",
+base + "--aasdf, #a {color:green}",
+base + "-0aasdf, #a {color:red}",
+"-asdf, #a {color:green}",
+base + "#a {color: rgb\n(255, 0, 0)}",
+"#a {font: \"Arial\n;color:green}",
+"#a {color: @charset{}\"\\\n'\"url(\na\na); color:green}",
+"#a\r{color:green}",
+"#a\n{color:green}",
+"#a\t{color:green}",
+"@threedee maroon url('asdf\n) ra('asdf\n); " + base,
+"@threedee {maroon url('asdf\n) ra('asdf\n);} " + base,
+"div[title='zxcv weeqweqeweasd\\D\\A a']{color:green}",
+"div[title~='weeqweqeweasd']{color:green}",
+base + "#a\\\n{color:red}",
+base + "#a\v{color:red}",
+
+// CSS1 section 7.1
+"#a {color: green; rotation: 70deg;}",
+"#a {color: green;} #a{color:invalidValue;}",
+base + "#a {color: \"red\"}",
+base + "@three-dee {\n @background-lighting {\n azimuth: 30deg;\n elevation: 190deg;\n }\n #a { color: red }\n }",
+"#a {COLOR: GREEN}",
+base + "#a:wait {color: red}",
+"#a:lang(en) {color: green}",
+"#a:lang(\nen\r\t ) {color: green}",
+base + "div ! em, #a {color: red}",
+base + "//asdf.zxcv,\n#a {color: red}",
+"#a {rotation-code: \"}\"; color: green;}",
+"#a {rotation-code: \"\\\"}\\\"\"; color: green;}",
+"#a {rotation-code: '}'; color: green;}",
+"#a {rotation-code: '\\'}\\''; color: green;}",
+"#a {\n type-display: @threedee {rotation-code: '}';};\n color: green;\n }",
+base + "p {text-indent: 0.5in;} color: maroon #a {color: red;}",
+base + "p {text-indent: 0.5in;} color: maroon; #a {color: red;}",
+
+// string tokenization as error token, not EOF (bug 311566 comment 70)
+"#a { color: green; foo: { \"bar\n;color: red}",
+
+// CSS 2.1 section 4.1.3
+"@MediA All {#a {ColOR :RgB(\t0,\r128,\n0 ) } };",
+base + "\\#a{color:red;}",
+base + "#a\\{color:red;\\}",
+base + "#a{color\\:red;}",
+base + "#a{color:red\\;}",
+"#a {c\\o\\l\\o\\r:\\g\\ree\\n}",
+"#a{ co\\00006Cor: gr\\000065en; }",
+"#a{ co\\4C or: gr\\000045en; }",
+".IdE6n-3t0_6, #a { color: green }",
+"#IdE6n-3t0_6, #a { color: green }",
+"._ident, #a { color: green }",
+"#_ident, #a { color: green }",
+".-ident, .a { color: green; }", // Testsuite has incorrect version
+"#怀ident, .a { color: green }",
+"#iden怀t怀, .a { color: green }",
+"#\\6000ident, .a { color: green }",
+"#iden\\6000t\\6000, .a { color: green }",
+".怀ident, .a { color: green }",
+".iden怀t怀, .a { color: green }",
+".\\6000ident, .a { color: green }",
+".iden\\6000t\\6000, .a { color: green }",
+base + "#6ident, #a {color: red }",
+".id4ent6, .a { color: green }",
+"#\\ident, .a { color: green; }",
+"#ide\\n\\t, .a { color: green; }",
+".\\6ident, .a { color: green; }",
+".\\--ident, .a { color: green; }",
+
+// CSS2.1 section 4.1.5 and 4.2
+"@import 'data:text/css,%23a{color:green}';",
+"@import \"data:text/css,%23a{color:green}\";",
+"@import url(data:text/css,%23a{color:green});",
+"@import 'data:text/css,%23a{color:green}' screen;",
+base + "@import 'data:text/css,%23a{color:red}' blahblahblah;",
+"@import 'data:text/css,%23a{color:green}'",
+"@import 'data:text/css,%23a{color:green}",
+"@foo {}" + base,
+"@foo bar {}" + base,
+"@foo; " + base,
+"@foo bar baz; " + base,
+base + "@foo {}; #a {color: red}",
+
+// CSS2.1 section 4.1.9
+"/* This is a CSS comment. */" + base,
+base + "/* #a {color: red} */",
+"/*********** /*/" + base,
+
+// CSS2.1 section 4.3.6
+base + "#a {color: rgb(255, 0, 0%)}",
+base + "#a {color: rgb(100%, 0, 0)}",
+"#a {color: rgb(0, 128, 0)}",
+"#a {color: rgb(0%, 50%, 0%)}",
+"#a {color: rgb(0%, 49.999999999999%, 0%)}",
+
+// CSS-Color-4
+// https://drafts.csswg.org/css-color/#rgb-functions
+"#a {color: rgb(0, 128.0, 0)}",
+], prop: "color", pseudo: ""
+},
+
+// Border tests
+// For testing lengths
+{ base : base = "#a {border-style:solid}",
+ tests : [
+// CSS1 section 7.1
+base + "#a {border-width: funny}",
+base + "#a {border-width: 50zu}",
+base + "#a {border-width: px}",
+
+// Number/unit parsing
+base + "#a {border-width: 0.px}",
+base + "#a {border-width: ..0px}",
+base + "#a {border-width: 0..0px}",
+base + "#a {border-width: 0.}",
+base + "#a {border-width: ..0}",
+base + "#a {border-width: 0..0}",
+base + "#a {border-width: 0; border-width: .0px medium}",
+base + "#a {border-width: 0; border-width: .0 medium}",
+base + "#a {border-width: 0; border-width: 0.0px medium}",
+], prop: "borderRightWidth", pseudo: ""},
+
+// Content tests
+// Tests for strings and pseudos
+{base : base = ".a::before {content: 'This is \\a'}",
+ tests : [
+// CSS 2.1 section 4.1.3
+"#a::before {content: 'This is \\a '}",
+"#a::before {content: 'This is \\A '}",
+"#a::before {content: 'This is \\0000a '}",
+"#a::before {content: 'This is \\00000a '}",
+"#a::before {content: 'This is \\\n\\00000a '}",
+"#a::before {content: 'This is \\\015\012\\00000a '}",
+"#a::before {content: 'This is \\\015\\00000a '}",
+"#a::before {content: 'This is \\\f\\00000a '}",
+"#a::before {content: 'This is\\20\f\\a'}",
+"#a::before {content: 'This is\\20\r\\a'}",
+"#a::before {content: 'This is\\20\n\\a'}",
+"#a::before {content: 'This is\\20\r\n\\a'}",
+base + "#a::before {content: 'FAIL \f\\a'}",
+base + "#a::before {content: 'FAIL \\\n\r\\a'}",
+"#a:before {content: 'This is \\a'}",
+
+base + "#a:: before {content: 'FAIL'}",
+base + "#a ::before {content: 'FAIL'}",
+"#a::before {content: 'This is \\a",
+
+], prop: "content", pseudo: "::before"
+},
+
+// Background color tests
+// For basic URL parsing sanity checks
+{ base : base = "div {background: blue}",
+ tests : [
+"#a {background: url() blue}",
+"#a {background: url(怀) blue}",
+], prop: "backgroundColor", pseudo: ""
+},
+
+// A one-off test I couldn't come up with a better way to do
+{ base : base = "div {border-style: dotted}",
+ tests : [
+// Sanity-check to make sure this test will work
+// This test requires a color name that starts with a "-"
+base + "#a {border: dotted 0 -moz-menuhover}",
+// The actual test: check that 0-moz-menuhover get parsed as an unknown dimension
+// rather than a separate identifier
+base + "#a {border: solid 0-moz-menuhover}",
+], prop: "borderLeftStyle", pseudo: ""
+},
+
+];
+
+var curTest = -1;
+var curSubTest = 0;
+
+var styleElement = document.getElementsByTagName("style")[0];
+var divElement = document.getElementById("a");
+var frame = document.getElementsByTagName("iframe")[0];
+
+var canonical;
+
+var doTests = function() {
+ if (curTest >= 0) {
+ var curElement = frame.contentDocument.getElementsByTagName("div")[0];
+ var curStyle = frame.contentDocument.defaultView.getComputedStyle(curElement, testset[curTest].pseudo);
+ if (testset[curTest].todo && testset[curTest].todo[testset[curTest].tests[curSubTest]]) {
+ todo_is(curStyle[testset[curTest].prop], canonical, testset[curTest].tests[curSubTest]);
+ } else {
+ is(curStyle[testset[curTest].prop], canonical, testset[curTest].tests[curSubTest]);
+ }
+ curSubTest++;
+ }
+ if (curTest == -1 || curSubTest >= testset[curTest].tests.length) {
+ curTest++;
+ curSubTest = 0;
+ }
+ if (!(curTest < testset.length)) {
+ SimpleTest.finish();
+ return;
+ }
+ if (curSubTest == 0) {
+ styleElement.textContent = "";
+ let computedBase = document.defaultView.getComputedStyle(divElement, testset[curTest].pseudo)[testset[curTest].prop];
+ styleElement.textContent = testset[curTest].base;
+ canonical = document.defaultView.getComputedStyle(divElement, testset[curTest].pseudo)[testset[curTest].prop];
+ styleElement.textContent = "";
+ isnot(computedBase, canonical, "Sanity check for rule: " + testset[curTest].base);
+ }
+ frame.contentDocument.open();
+ frame.contentDocument.write("<html lang=en><style>" + testset[curTest].tests[curSubTest] + "</style><div id=a class='a b c' title='zxcv weeqweqeweasd&#13;&#10;a'></div>");
+ frame.contentWindow.onload = function(){setTimeout(doTests, 0);};
+ frame.contentDocument.close();
+};
+
+// Running a debug build on the Android emulator is slooow and collecting
+// all the session history this test amasses through repeated navigations
+// adds considerably to the running time. Therefore, we restrict the
+// amount of session history we keep around during this test.
+SpecialPowers.pushPrefEnv({"set": [['browser.sessionhistory.max_entries', 4]]}, doTests);
+
+};
+
+</script>
diff --git a/layout/style/test/test_parse_url.html b/layout/style/test/test_parse_url.html
new file mode 100644
index 0000000000..7a637c18e3
--- /dev/null
+++ b/layout/style/test/test_parse_url.html
@@ -0,0 +1,195 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=473914
+-->
+<head>
+ <title>Test for Bug 473914</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=473914">Mozilla Bug 473914</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 473914 **/
+
+var div = document.getElementById("content");
+
+// This test relies on normalization (insertion of quote marks) that
+// we're not really guaranteed to continue doing in the future.
+div.style.listStyleImage = 'url(http://example.org/**/)';
+is(div.style.listStyleImage, 'url("http://example.org/**/")',
+ "not treated as comment");
+div.style.listStyleImage = 'url("http://example.org/**/")';
+is(div.style.listStyleImage, 'url("http://example.org/**/")',
+ "not treated as comment");
+div.style.listStyleImage = 'url(/**/foo)';
+is(div.style.listStyleImage, 'url("/**/foo")',
+ "not treated as comment");
+div.style.listStyleImage = 'url("/**/foo")';
+is(div.style.listStyleImage, 'url("/**/foo")',
+ "not treated as comment");
+div.style.listStyleImage = 'url(/**/)';
+is(div.style.listStyleImage, 'url("/**/")',
+ "not treated as comment");
+div.style.listStyleImage = 'url("/**/")';
+is(div.style.listStyleImage, 'url("/**/")',
+ "not treated as comment");
+
+// Tests from Alfred Keyser's patch in bug 337287 (modified by dbaron)
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good /*bad comment*/)';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url(good /*bad comments*/ /*Hello*/)';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url(good/*commentaspartofurl*/)';
+is(div.style.listStyleImage, 'url("good/*commentaspartofurl*/")',
+ "comment-like syntax not comment inside of url");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good/**/ /*secondcommentcanbeskipped*/ )';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url(/*partofurl*/good)';
+is(div.style.listStyleImage, 'url("/*partofurl*/good")',
+ "comment not parsed as part of url");
+
+div.style.listStyleImage = 'url(good';
+is(div.style.listStyleImage, 'url("good")',
+ "URL ending with eof not correctly handled");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good /*)*/';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good /*)*/ tokenaftercommentevenwithclosebracketisinvalid';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url(bad)';
+div.style.listStyleImage = 'url("good"';
+is(div.style.listStyleImage, 'url("good")',
+ "URL as string without close bracket");
+
+div.style.listStyleImage = 'url(bad)';
+div.style.listStyleImage = 'url("good';
+is(div.style.listStyleImage, 'url("good")',
+ "URL as string without closing quote");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good notgood';
+is(div.style.listStyleImage, 'url("bad")',
+ "second token should make url invalid");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good(notgood';
+is(div.style.listStyleImage, 'url("bad")',
+ "open bracket in url not recognized as invalid");
+
+var longurl = '';
+for (i=0;i<1000;i++) {
+ longurl = longurl + 'verylongurlindeed_thequickbrownfoxjumpsoverthelazydoq';
+}
+div.style.listStyleImage = 'url(' + longurl;
+is(div.style.listStyleImage, 'url("' + longurl + '")',
+ "very long url not correctly parsed");
+
+
+// Additional tests from
+// https://bugzilla.mozilla.org/show_bug.cgi?id=337287#c21
+
+div.style.listStyleImage = 'url(good/*)';
+is(div.style.listStyleImage, 'url("good/*")',
+ "URL containing comment start is valid");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good bad)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(\\g b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url( \\g b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(c\\g b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(cc\\g b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(\\f b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url( \\f b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(c\\f b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(cc\\f b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+var chars = [ 1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 15, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127];
+
+for (var i in chars) {
+ var charcode = chars[i];
+ div.style.listStyleImage = 'url(' + String.fromCharCode(charcode) + ')';
+ is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with control character " + charcode + " not allowed");
+}
+
+div.style.listStyleImage = 'url(\u00ff)';
+is(div.style.listStyleImage, 'url("\u00ff")', "U+A0-U+FF allowed in unquoted URL");
+
+div.style.listStyleImage = 'url(\\f good)';
+is(div.style.listStyleImage, 'url("\\f good")', "URL allowed");
+div.style.listStyleImage = 'url( \\f good)';
+is(div.style.listStyleImage, 'url("\\f good")', "URL allowed");
+div.style.listStyleImage = 'url(f\\f good)';
+is(div.style.listStyleImage, 'url("f\\f good")', "URL allowed");
+div.style.listStyleImage = 'url(go\\od)';
+is(div.style.listStyleImage, 'url("good")', "URL allowed");
+div.style.listStyleImage = 'url(goo\\d)';
+is(div.style.listStyleImage, 'url("goo\\d ")', "URL allowed");
+div.style.listStyleImage = 'url(go\\o)';
+is(div.style.listStyleImage, 'url("goo")', "URL allowed");
+
+div.setAttribute("style", "color: url(/*); color: green");
+is(div.style.color, 'green',
+ "URL tokenized correctly outside properties taking URLs");
+
+div.style.listStyleImage = 'url("foo\\\nbar1")';
+is(div.style.listStyleImage, 'url("foobar1")',
+ "escaped newline allowed in string form of URL");
+div.style.listStyleImage = 'url(foo\\\nbar2)';
+is(div.style.listStyleImage, 'url("foobar1")',
+ "escaped newline NOT allowed in NON-string form of URL");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_parser_diagnostics_unprintables.html b/layout/style/test/test_parser_diagnostics_unprintables.html
new file mode 100644
index 0000000000..f872c00a8f
--- /dev/null
+++ b/layout/style/test/test_parser_diagnostics_unprintables.html
@@ -0,0 +1,220 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for CSS parser diagnostics escaping unprintable
+ characters correctly</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=229827"
+>Mozilla Bug 229827</a>
+<style id="testbench"></style>
+<script type="application/javascript">
+// This test has intimate knowledge of how to get the CSS parser to
+// emit diagnostics that contain text under control of the user.
+// That's not the point of the test, though; the point is only that
+// *that text* is properly escaped.
+
+SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true;
+
+// There is one "pattern" for each code path through the error reporter
+// that might need to escape some kind of user-supplied text.
+// Each "pattern" is tested once with each of the "substitution"s below:
+// <t>, <i>, and <s> are replaced by the t:, i:, and s: fields of
+// each substitution object in turn.
+let patterns = [
+ // REPORT_UNEXPECTED_P (only ever used in contexts where identifier-like
+ // escaping is appropriate)
+ { i: "<t>|x{}", o: "prefix \u2018<i>\u2019" },
+ // REPORT_UNEXPECTED_TOKEN with:
+ // _Ident
+ { i: "@namespace fnord <t>;", o: "within @namespace: \u2018<i>\u2019" },
+ // _Ref
+ { i: "@namespace fnord #<t>;", o: "within @namespace: \u2018#<i>\u2019" },
+ // _Function
+ { i: "@namespace fnord <t>();", o: "within @namespace: \u2018<i>(\u2019" },
+ // _Dimension
+ { i: "@namespace fnord 14<t>;", o: "within @namespace: \u201814<i>\u2019" },
+ // _AtKeyword
+ { i: "x{@<t>: }", o: "declaration but found \u2018@<i>\u2019." },
+ // _String
+ { i: "x{ color: '<t>'}" , o: 'color but found \u2018"<s>"\u2019.' },
+ // _Bad_String
+ { i: "x{ color: '<t>\n}", o: 'color but found \u2018"<s>\u2019.' },
+];
+
+// Blocks of characters to test, and how they should be escaped when
+// they appear in identifiers and string constants.
+const substitutions = [
+ // ASCII printables that _can_ normally appear in identifiers,
+ // so should of course _not_ be escaped.
+ { t: "-_0123456789", i: "-_0123456789",
+ s: "-_0123456789" },
+ { t: "abcdefghijklmnopqrstuvwxyz", i: "abcdefghijklmnopqrstuvwxyz",
+ s: "abcdefghijklmnopqrstuvwxyz" },
+ { t: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", i: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ s: "ABCDEFGHIJKLMNOPQRSTUVWXYZ" },
+
+ // ASCII printables that are not normally valid as the first character
+ // of an identifier, or the character immediately after a leading dash,
+ // but can be forced into that position with escapes.
+ { t: "\\-", i: "\\-", s: "-" },
+ { t: "\\30 ", i: "\\30 ", s: "0" },
+ { t: "\\31 ", i: "\\31 ", s: "1" },
+ { t: "\\32 ", i: "\\32 ", s: "2" },
+ { t: "\\33 ", i: "\\33 ", s: "3" },
+ { t: "\\34 ", i: "\\34 ", s: "4" },
+ { t: "\\35 ", i: "\\35 ", s: "5" },
+ { t: "\\36 ", i: "\\36 ", s: "6" },
+ { t: "\\37 ", i: "\\37 ", s: "7" },
+ { t: "\\38 ", i: "\\38 ", s: "8" },
+ { t: "\\39 ", i: "\\39 ", s: "9" },
+ { t: "-\\-", i: "--", s: "--" },
+ { t: "-\\30 ", i: "-\\30 ", s: "-0" },
+ { t: "-\\31 ", i: "-\\31 ", s: "-1" },
+ { t: "-\\32 ", i: "-\\32 ", s: "-2" },
+ { t: "-\\33 ", i: "-\\33 ", s: "-3" },
+ { t: "-\\34 ", i: "-\\34 ", s: "-4" },
+ { t: "-\\35 ", i: "-\\35 ", s: "-5" },
+ { t: "-\\36 ", i: "-\\36 ", s: "-6" },
+ { t: "-\\37 ", i: "-\\37 ", s: "-7" },
+ { t: "-\\38 ", i: "-\\38 ", s: "-8" },
+ { t: "-\\39 ", i: "-\\39 ", s: "-9" },
+
+ // ASCII printables that must be escaped in identifiers.
+ // Most of these should not be escaped in strings.
+ { t: "\\!\\\"\\#\\$", i: "\\!\\\"\\#\\$", s: "!\\\"#$" },
+ { t: "\\%\\&\\'\\(", i: "\\%\\&\\'\\(", s: "%&'(" },
+ { t: "\\)\\*\\+\\,", i: "\\)\\*\\+\\,", s: ")*+," },
+ { t: "\\.\\/\\:\\;", i: "\\.\\/\\:\\;", s: "./:;" },
+ { t: "\\<\\=\\>\\?", i: "\\<\\=\\>\\?", s: "<=>?", },
+ { t: "\\@\\[\\\\\\]", i: "\\@\\[\\\\\\]", s: "@[\\\\]" },
+ { t: "\\^\\`\\{\\}\\~", i: "\\^\\`\\{\\}\\~", s: "^`{}~" },
+
+ // U+0000 - U+0020 (C0 controls, space)
+ // U+000A LINE FEED, U+000C FORM FEED, and U+000D CARRIAGE RETURN
+ // cannot be put into a CSS token as escaped literal characters, so
+ // we do them with hex escapes instead.
+ // The parser replaces U+0000 with U+FFFD.
+ { t: "\\\x00\\\x01\\\x02\\\x03", i: "�\\1 \\2 \\3 ",
+ s: "�\\1 \\2 \\3 " },
+ { t: "\\\x04\\\x05\\\x06\\\x07", i: "\\4 \\5 \\6 \\7 ",
+ s: "\\4 \\5 \\6 \\7 " },
+ { t: "\\\x08\\\x09\\000A\\\x0B", i: "\\8 \\9 \\a \\b ",
+ s: "\\8 \\9 \\a \\b " },
+ { t: "\\000C\\000D\\\x0E\\\x0F", i: "\\c \\d \\e \\f ",
+ s: "\\c \\d \\e \\f " },
+ { t: "\\\x10\\\x11\\\x12\\\x13", i: "\\10 \\11 \\12 \\13 ",
+ s: "\\10 \\11 \\12 \\13 " },
+ { t: "\\\x14\\\x15\\\x16\\\x17", i: "\\14 \\15 \\16 \\17 ",
+ s: "\\14 \\15 \\16 \\17 " },
+ { t: "\\\x18\\\x19\\\x1A\\\x1B", i: "\\18 \\19 \\1a \\1b ",
+ s: "\\18 \\19 \\1a \\1b " },
+ { t: "\\\x1C\\\x1D\\\x1E\\\x1F\\ ", i: "\\1c \\1d \\1e \\1f \\ ",
+ s: "\\1c \\1d \\1e \\1f " },
+
+ // U+007F (DELETE) and U+0080 - U+009F (C1 controls)
+ { t: "\\\x7f\\\x80\\\x81\\\x82", i: "\\7f \x80\x81\x82",
+ s: "\\7f \x80\x81\x82" },
+ { t: "\\\x83\\\x84\\\x85\\\x86", i: "\x83\x84\x85\x86",
+ s: "\x83\x84\x85\x86" },
+ { t: "\\\x87\\\x88\\\x89\\\x8A", i: "\x87\x88\x89\x8A",
+ s: "\x87\x88\x89\x8A" },
+ { t: "\\\x8B\\\x8C\\\x8D\\\x8E", i: "\x8B\x8C\x8D\x8E",
+ s: "\x8B\x8C\x8D\x8E" },
+ { t: "\\\x8F\\\x90\\\x91\\\x92", i: "\x8F\x90\x91\x92",
+ s: "\x8F\x90\x91\x92" },
+ { t: "\\\x93\\\x94\\\x95\\\x96", i: "\x93\x94\x95\x96",
+ s: "\x93\x94\x95\x96" },
+ { t: "\\\x97\\\x98\\\x99\\\x9A", i: "\x97\x98\x99\x9A",
+ s: "\x97\x98\x99\x9A" },
+ { t: "\\\x9B\\\x9C\\\x9D\\\x9E\\\x9F", i: "\x9B\x9C\x9D\x9E\x9F",
+ s: "\x9B\x9C\x9D\x9E\x9F" },
+
+ // CSS doesn't bother with the full Unicode rules for identifiers,
+ // instead declaring that any code point greater than or equal to
+ // U+0080 is a valid identifier character. Test a small handful
+ // of both basic and astral plane characters.
+
+ // Arabic (caution to editors: there is a possibly-invisible U+200E
+ // LEFT-TO-RIGHT MARK in each string, just before the close quote)
+ { t: "أبجدهوزحطيكلمنسعفصقرشتثخذضظغ‎",
+ i: "أبجدهوزحطيكلمنسعفصقرشتثخذضظغ‎",
+ s: "أبجدهوزحطيكلمنسعفصقرشتثخذضظغ‎" },
+
+ // Box drawing
+ { t: "─│┌┐└┘├┤┬┴┼╭╮╯╰╴╵╶╷",
+ i: "─│┌┐└┘├┤┬┴┼╭╮╯╰╴╵╶╷",
+ s: "─│┌┐└┘├┤┬┴┼╭╮╯╰╴╵╶╷" },
+
+ // CJK Unified Ideographs
+ { t: "一丁丂七丄丅丆万丈三上下丌不与丏",
+ i: "一丁丂七丄丅丆万丈三上下丌不与丏",
+ s: "一丁丂七丄丅丆万丈三上下丌不与丏" },
+
+ // CJK Unified Ideographs Extension B (astral)
+ { t: "𠀀𠀁𠀂𠀃𠀄𠀅𠀆𠀇𠀈𠀉𠀊𠀋𠀌𠀍𠀎𠀏",
+ i: "𠀀𠀁𠀂𠀃𠀄𠀅𠀆𠀇𠀈𠀉𠀊𠀋𠀌𠀍𠀎𠀏",
+ s: "𠀀𠀁𠀂𠀃𠀄𠀅𠀆𠀇𠀈𠀉𠀊𠀋𠀌𠀍𠀎𠀏" },
+
+ // Devanagari
+ { t: "कखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसह",
+ i: "कखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसह",
+ s: "कखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसह" },
+
+ // Emoticons (astral)
+ { t: "😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐",
+ i: "😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐",
+ s: "😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐" },
+
+ // Greek
+ { t: "αβγδεζηθικλμνξοπρςστυφχψω",
+ i: "αβγδεζηθικλμνξοπρςστυφχψω",
+ s: "αβγδεζηθικλμνξοπρςστυφχψω" }
+];
+
+const npatterns = patterns.length;
+const nsubstitutions = substitutions.length;
+
+function quotemeta(str) {
+ return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+}
+function subst(str, sub) {
+ return str.replace("<t>", sub.t)
+ .replace("<i>", sub.i)
+ .replace("<s>", sub.s);
+}
+
+var curpat = 0;
+var cursubst = -1;
+var testbench = document.getElementById("testbench");
+
+function nextTest() {
+ cursubst++;
+ if (cursubst == nsubstitutions) {
+ curpat++;
+ cursubst = 0;
+ }
+ if (curpat == npatterns) {
+ SimpleTest.finish();
+ return;
+ }
+
+ let css = subst(patterns[curpat].i, substitutions[cursubst]);
+ let msg = quotemeta(subst(patterns[curpat].o, substitutions[cursubst]));
+
+ info(css);
+ info(msg);
+ SimpleTest.expectConsoleMessages(function () { testbench.innerHTML = css },
+ [{ errorMessage: new RegExp(msg) }],
+ nextTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+nextTest();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_pixel_lengths.html b/layout/style/test/test_pixel_lengths.html
new file mode 100644
index 0000000000..346547507c
--- /dev/null
+++ b/layout/style/test/test_pixel_lengths.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that pixel lengths don't change based on DPI</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+
+<div id="pt" style="width:90pt; height:90pt; background:lime;">pt</div>
+<div id="pc" style="width:5pc; height:5pc; background:yellow;">pc</div>
+<div id="mm" style="width:25.4mm; height:25.4mm; background:orange;">mm</div>
+<div id="cm" style="width:2.54cm; height:2.54cm; background:purple;">cm</div>
+<div id="in" style="width:1in; height:1in; background:magenta;">in</div>
+<div id="q" style="width:101.6q; height:101.6q; background:blue;">q</div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var oldDPI = SpecialPowers.getIntPref("layout.css.dpi");
+var dpi = oldDPI;
+
+function check(id, val) {
+ var e = document.getElementById(id);
+ is(Math.round(e.getBoundingClientRect().width), Math.round(val),
+ "Checking width in " + id + " at " + dpi + " DPI");
+ is(Math.round(e.getBoundingClientRect().height), Math.round(val),
+ "Checking height in " + id + " at " + dpi + " DPI");
+}
+
+function checkPixelRelativeUnits() {
+ check("pt", 120);
+ check("pc", 80);
+ check("mm", 96);
+ check("cm", 96);
+ check("in", 96);
+ check("q", 96);
+}
+
+checkPixelRelativeUnits();
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({'set': [['layout.css.dpi', dpi=96]]}, test1);
+
+function test1() {
+ checkPixelRelativeUnits();
+ SpecialPowers.pushPrefEnv({'set': [['layout.css.dpi', dpi=192]]}, test2);
+}
+
+function test2() {
+ checkPixelRelativeUnits();
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_placeholder_restrictions.html b/layout/style/test/test_placeholder_restrictions.html
new file mode 100644
index 0000000000..4e3e87ef16
--- /dev/null
+++ b/layout/style/test/test_placeholder_restrictions.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1382786
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1382786</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="s"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1382786">Mozilla Bug 1382786</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<input id="test">
+<input id="control">
+<script type="application/javascript">
+
+/** Test for Bug 1382786 **/
+var test = getComputedStyle($("test"), "::placeholder");
+var control = getComputedStyle($("control"), "::placeholder");
+for (let prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND) {
+ // Can't get useful info out of getComputedStyle.
+ continue;
+ }
+ let prereqs = "";
+ if (info.prerequisites) {
+ for (let name in info.prerequisites) {
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+ }
+ }
+ $("s").textContent = `
+ #control::placeholder { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::placeholder { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+ // line-height does apply to ::placeholder, but only on <textarea>. We could
+ // switch the test to use a <textarea>.
+ if (info.applies_to_placeholder && prop != "line-height") {
+ isnot(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should apply to ::placeholder`);
+ } else {
+ is(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should not apply to ::placeholder`);
+ }
+}
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_pointer-events.html b/layout/style/test/test_pointer-events.html
new file mode 100644
index 0000000000..faeb6c6335
--- /dev/null
+++ b/layout/style/test/test_pointer-events.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for pointer-events in HTML</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ div { height: 10px; width: 10px; background: black; }
+
+ </style>
+</head>
+<!-- need a set timeout because we need things to start after painting suppression ends -->
+<body onload="setTimeout(run_test, 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="display" style="position: absolute; top: 0; left: 0; width: 300px; height: 300px">
+
+ <div id="one"></div>
+ <div id="two" style="pointer-events: visiblePainted;"></div>
+ <div id="three" style="height: 20px; pointer-events: none;">
+ <div id="four"style="margin-top: 10px;"></div>
+ </div>
+ <a id="five" style="pointer-events: none;" href="http://mozilla.org/">link</a>
+ <input id="six" style="pointer-events: none;" type="button" value="button" />
+ <table>
+ <tr style="pointer-events: none;">
+ <td id="seven">no</td>
+ <td id="eight" style="pointer-events: visiblePainted;">yes</td>
+ <td id="nine" style="pointer-events: auto;">yes</td>
+ </td>
+ <tr style="opacity: 0.5; pointer-events: none;">
+ <td id="ten">no</td>
+ <td id="eleven" style="pointer-events: visiblePainted;">yes</td>
+ <td id="twelve" style="pointer-events: auto;">yes</td>
+ </td>
+ </table>
+ <iframe id="thirteen" style="pointer-events: none;" src="about:blank" width="100" height="100"></iframe>
+ <script type="application/javascript">
+ var iframe = document.getElementById("thirteen");
+ iframe.contentDocument.open();
+ iframe.contentDocument.writeln("<script type='application/javascript'>");
+ iframe.contentDocument.writeln("document.addEventListener('mousedown', fail, false);");
+ iframe.contentDocument.writeln("function fail() { parent.ok(false, 'thirteen: iframe content must not get pointer events with explicit none') }");
+ iframe.contentDocument.writeln("<"+"/script>");
+ iframe.contentDocument.close();
+ </script>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.expectAssertions(0, 1);
+
+SimpleTest.waitForExplicitFinish();
+
+function catches_pointer_events(element_id)
+{
+ // we just assume the element is on top here.
+ var element = document.getElementById(element_id);
+ var bounds = element.getBoundingClientRect();
+ var point = { x: bounds.left + bounds.width/2, y: bounds.top + bounds.height/2 };
+ return element == document.elementFromPoint(point.x, point.y);
+}
+
+function synthesizeMouseEvent(type, // string
+ x, // float
+ y, // float
+ button, // long
+ clickCount, // long
+ modifiers, // long
+ ignoreWindowBounds) // boolean
+{
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent(type, x, y, button, clickCount,
+ modifiers, ignoreWindowBounds);
+}
+
+function run_test()
+{
+ ok(catches_pointer_events("one"), "one: div should default to catching pointer events");
+ ok(catches_pointer_events("two"), "two: div should catch pointer events with explicit visiblePainted");
+ ok(!catches_pointer_events("three"), "three: div should not catch pointer events with explicit none");
+ ok(!catches_pointer_events("four"), "four: div should not catch pointer events with inherited none");
+ ok(!catches_pointer_events("five"), "five: link should not catch pointer events with explicit none");
+ ok(!catches_pointer_events("six"), "six: native-themed form control should not catch pointer events with explicit none");
+ ok(!catches_pointer_events("seven"), "seven: td should not catch pointer events with inherited none");
+ ok(catches_pointer_events("eight"), "eight: td should catch pointer events with explicit visiblePainted overriding inherited none");
+ ok(catches_pointer_events("nine"), "nine: td should catch pointer events with explicit auto overriding inherited none");
+ ok(!catches_pointer_events("ten"), "ten: td should not catch pointer events with inherited none");
+ ok(catches_pointer_events("eleven"), "eleven: td should catch pointer events with explicit visiblePainted overriding inherited none");
+ ok(catches_pointer_events("twelve"), "twelve: td should catch pointer events with explicit auto overriding inherited none");
+
+ // elementFromPoint can't be used for iframe
+ var iframe = document.getElementById("thirteen");
+ iframe.parentNode.addEventListener('mousedown', handleIFrameClick);
+ var bounds = iframe.getBoundingClientRect();
+ var x = bounds.left + bounds.width/2;
+ var y = bounds.top + bounds.height/2;
+ synthesizeMouseEvent('mousedown', x, y, 0, 1, 0, true);
+}
+
+function handleIFrameClick()
+{
+ ok(true, "thirteen: iframe content must not get pointer events with explicit none");
+
+ document.getElementById("display").style.display = "none";
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_position_float_display.html b/layout/style/test/test_position_float_display.html
new file mode 100644
index 0000000000..ee75144dcb
--- /dev/null
+++ b/layout/style/test/test_position_float_display.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1038929
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1038929</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.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=1038929">Mozilla Bug 1038929</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="float-left" style="float: left"></div>
+ <div id="float-right" style="float: right"></div>
+ <div id="position-absolute" style="position: absolute"></div>
+ <div id="position-fixed" style="position: fixed"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1038929: Test that "display" on a floated or absolutely/fixed
+ position node is correctly converted to a block display as given in the table
+ in CSS 2.1 9.7. */
+
+// Maps from display value to expected conversion when floated/positioned
+// This loosely follows the spec in CSS 2.1 section 9.7. Except for "other"
+// values which the spec says should be "same as specified." For these, we do
+// whatever the spec for the value itself says.
+var mapping = {
+ "inline": "block",
+ "table-row-group": "block",
+ "table-column": "block",
+ "table-column-group": "block",
+ "table-header-group": "block",
+ "table-footer-group": "block",
+ "table-row": "block",
+ "table-cell": "block",
+ "table-caption": "block",
+ "inline-block": "block",
+ "block ruby": "block ruby",
+ "ruby": "block ruby",
+ "ruby-base": "block",
+ "ruby-base-container": "block",
+ "ruby-text": "block",
+ "ruby-text-container": "block",
+ "flex": "flex",
+ "grid": "grid",
+ "none": "none",
+ "table": "table",
+ "inline-grid": "grid",
+ "inline-flex": "flex",
+ "inline-table": "table",
+ "block": "block",
+ "contents": "contents",
+ "flow-root": "flow-root",
+ // Note: this is sometimes block
+ "list-item": "list-item",
+ "inline list-item": "list-item",
+ "inline flow-root list-item": "list-item",
+};
+
+function test_display_value(val)
+{
+ var floatLeftElem = document.getElementById("float-left");
+ floatLeftElem.style.display = val;
+ var floatLeftConversion = window.getComputedStyle(floatLeftElem).display;
+ floatLeftElem.style.display = "";
+
+ var floatRightElem = document.getElementById("float-right");
+ floatRightElem.style.display = val;
+ var floatRightConversion = window.getComputedStyle(floatRightElem).display;
+ floatRightElem.style.display = "";
+
+ var posAbsoluteElem = document.getElementById("position-absolute");
+ posAbsoluteElem.style.display = val;
+ var posAbsoluteConversion = window.getComputedStyle(posAbsoluteElem).display;
+ posAbsoluteElem.style.display = "";
+
+ var posFixedElem = document.getElementById("position-fixed");
+ posFixedElem.style.display = val;
+ var posFixedConversion = window.getComputedStyle(posFixedElem).display;
+ posFixedElem.style.display = "";
+
+ if (mapping[val]) {
+ is(floatLeftConversion, mapping[val],
+ "Element display should be converted when floated left");
+ is(floatRightConversion, mapping[val],
+ "Element display should be converted when floated right");
+ is(posAbsoluteConversion, mapping[val],
+ "Element display should be converted when absolutely positioned");
+ is(posFixedConversion, mapping[val],
+ "Element display should be converted when fixed positioned");
+ } else {
+ ok(false, "missing rules for display value " + val);
+ }
+}
+
+var displayInfo = gCSSProperties.display;
+displayInfo.initial_values.forEach(test_display_value);
+displayInfo.other_values.forEach(test_display_value);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_position_sticky.html b/layout/style/test/test_position_sticky.html
new file mode 100644
index 0000000000..b542737e54
--- /dev/null
+++ b/layout/style/test/test_position_sticky.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=886646
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886646</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ #scroller {
+ width: 100px;
+ height: 100px;
+ padding: 10px;
+ border: 10px solid black;
+ margin: 10px;
+ overflow: hidden;
+ }
+ #container {
+ width: 50px;
+ height: 50px;
+ }
+ #sticky {
+ position: sticky;
+ width: 10px;
+ height: 10px;
+ overflow: hidden;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=886646">Mozilla Bug 886646</a>
+<div id="display">
+ <div id="scroller">
+ <div id="container">
+ <div id="sticky"></div>
+ </div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+
+/** Test for Bug 886646 - Offsets for sticky positioning, when accessed through
+ * getComputedStyle(), should be accurately computed. In particular,
+ * percentage offsets should be computed in terms of the scroll container's
+ * content box. */
+
+// Test that percentage sticky offsets are computed in terms of the
+// scroll container's content box
+var offsets = {
+ "top": 10,
+ "left": 20,
+ "bottom": 30,
+ "right": 40,
+};
+
+var scroller = document.getElementById("scroller");
+var container = document.getElementById("container");
+var sticky = document.getElementById("sticky");
+var cs = getComputedStyle(sticky, "");
+
+for (var prop in offsets) {
+ sticky.style[prop] = offsets[prop] + "%";
+ is(cs[prop], offsets[prop] + "px");
+}
+
+// ... even in the presence of scrollbars
+scroller.style.overflow = "scroll";
+container.style.width = "100%";
+container.style.height = "100%";
+
+var ccs = getComputedStyle(container, "");
+
+function isApproximatelyEqual(a, b) {
+ return Math.abs(a - b) < 0.001;
+}
+
+for (var prop in offsets) {
+ sticky.style[prop] = offsets[prop] + "%";
+ var basis = parseFloat(ccs[prop == "left" || prop == "right" ?
+ "width" : "height"]) / 100;
+ ok(isApproximatelyEqual(parseFloat(cs[prop]), offsets[prop] * basis));
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_prefers_contrast_color_pairs.html b/layout/style/test/test_prefers_contrast_color_pairs.html
new file mode 100644
index 0000000000..f4a8945804
--- /dev/null
+++ b/layout/style/test/test_prefers_contrast_color_pairs.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<title>Test for Bug 922669</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script>
+ function assertMatches(query) {
+ ok(matchMedia(query).matches, `${query} should match`);
+ }
+ function assertPrefersContrastIs(value) {
+ assertMatches(`(prefers-contrast: ${value})`);
+ }
+ add_task(async function setupPrefs() {
+ assertPrefersContrastIs("no-preference");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.display.document_color_use", 2],
+ ["browser.display.use_system_colors", false],
+ ]
+ });
+ assertMatches("(prefers-contrast)");
+ });
+ async function testColors(foreground, background, expected) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.display.foreground_color", foreground],
+ ["browser.display.background_color", background],
+ ]
+ });
+
+ assertPrefersContrastIs(expected);
+
+ // Test the inversed order too.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.display.foreground_color", background],
+ ["browser.display.background_color", foreground],
+ ]
+ });
+
+ assertPrefersContrastIs(expected);
+ }
+
+ add_task(async function test_prefers_contrast_colors() {
+ await testColors("black", "black", "less");
+ await testColors("black", "white", "more");
+ await testColors("red", "black", "custom");
+ });
+</script>
diff --git a/layout/style/test/test_priority_preservation.html b/layout/style/test/test_priority_preservation.html
new file mode 100644
index 0000000000..7177949555
--- /dev/null
+++ b/layout/style/test/test_priority_preservation.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for property priority preservation</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test that priorities are preserved correctly when setProperty is
+ * called, and during declaration block expansion/compression when other
+ * properties are manipulated.
+ */
+
+var div = document.getElementById("content");
+var s = div.style;
+
+s.setProperty("text-decoration", "underline", "");
+is(s.getPropertyValue("text-decoration"), "underline",
+ "text-decoration stored");
+is(s.getPropertyPriority("text-decoration"), "",
+ "text-decoration priority stored");
+s.setProperty("z-index", "7", "important");
+is(s.getPropertyValue("z-index"), "7",
+ "z-index stored");
+is(s.getPropertyPriority("z-index"), "important",
+ "z-index priority stored");
+s.setProperty("z-index", "3", "");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index overridden by setting non-important");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority overridden by setting non-important");
+is(s.getPropertyValue("text-decoration"), "underline",
+ "text-decoration still stored");
+is(s.getPropertyPriority("text-decoration"), "",
+ "text-decoration priority still stored");
+s.setProperty("text-decoration", "overline", "");
+is(s.getPropertyValue("text-decoration"), "overline",
+ "text-decoration stored");
+is(s.getPropertyPriority("text-decoration"), "",
+ "text-decoration priority stored");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index still stored");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority still stored");
+s.setProperty("text-decoration", "line-through", "important");
+is(s.getPropertyValue("text-decoration"), "line-through",
+ "text-decoration stored at new priority");
+is(s.getPropertyPriority("text-decoration"), "important",
+ "text-decoration priority overridden");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index still stored");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority still stored");
+
+ // also test setting a shorthand
+s.setProperty("font", "italic bold 12px/30px serif", "important");
+is(s.getPropertyValue("font-style"), "italic", "font-style stored");
+is(s.getPropertyPriority("font-style"), "important",
+ "font-style priority stored");
+is(s.getPropertyValue("font-weight"), "bold", "font-weight stored");
+is(s.getPropertyPriority("font-weight"), "important",
+ "font-weight priority stored");
+is(s.getPropertyValue("font-size"), "12px", "font-size stored");
+is(s.getPropertyPriority("font-size"), "important",
+ "font-size priority stored");
+is(s.getPropertyValue("line-height"), "30px", "line-height stored");
+is(s.getPropertyPriority("line-height"), "important",
+ "line-height priority stored");
+is(s.getPropertyValue("font-family"), "serif", "font-family stored");
+is(s.getPropertyPriority("font-family"), "important",
+ "font-family priority stored");
+
+is(s.getPropertyValue("text-decoration"), "line-through",
+ "text-decoration still stored");
+is(s.getPropertyPriority("text-decoration"), "important",
+ "text-decoration priority still stored");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index still stored");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority still stored");
+
+ // and overriding one element of that shorthand with some longhand
+ // test omitting the third argument to setProperty too (bug 655478)
+s.setProperty("font-style", "normal");
+
+is(s.getPropertyValue("font-style"), "normal", "font-style overridden");
+is(s.getPropertyPriority("font-style"), "", "font-style priority overridden");
+
+is(s.getPropertyValue("font-weight"), "bold", "font-weight unchanged");
+is(s.getPropertyPriority("font-weight"), "important",
+ "font-weight priority unchanged");
+is(s.getPropertyValue("font-size"), "12px", "font-size unchanged");
+is(s.getPropertyPriority("font-size"), "important",
+ "font-size priority unchanged");
+is(s.getPropertyValue("line-height"), "30px", "line-height unchanged");
+is(s.getPropertyPriority("line-height"), "important",
+ "line-height priority unchanged");
+is(s.getPropertyValue("font-family"), "serif", "font-family unchanged");
+is(s.getPropertyPriority("font-family"), "important",
+ "font-family priority unchanged");
+
+is(s.getPropertyValue("text-decoration"), "line-through",
+ "text-decoration still stored");
+is(s.getPropertyPriority("text-decoration"), "important",
+ "text-decoration priority still stored");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index still stored");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority still stored");
+
+s.setProperty("border-radius", "2em", "");
+is(s.getPropertyValue("border-radius"), "2em",
+ "border-radius serialization 1")
+
+s.setProperty("border-top-left-radius", "3em 4em", "");
+is(s.getPropertyValue("border-radius"),
+ "3em 2em 2em / 4em 2em 2em",
+ "border-radius serialization 2");
+
+s.setProperty("border-radius", "2em / 3em", "");
+is(s.getPropertyValue("border-radius"),
+ "2em / 3em",
+ "border-radius serialization 3")
+
+s.setProperty("border-top-left-radius", "4em", "");
+is(s.getPropertyValue("border-radius"),
+ "4em 2em 2em / 4em 3em 3em",
+ "border-radius serialization 3");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_property_database.html b/layout/style/test/test_property_database.html
new file mode 100644
index 0000000000..ba04ec341a
--- /dev/null
+++ b/layout/style/test/test_property_database.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test that property_database.js contains all supported CSS properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="css_properties.js"></script>
+ <script type="text/javascript" src="property_database.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 id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test that property_database.js contains all supported CSS properties **/
+
+/*
+ * Here we are testing the hand-written property_database.js against
+ * the autogenerated css_properties.js to make sure that everything in
+ * css_properties.js is in property_database.js.
+ *
+ * This prevents CSS properties from being added to the code without
+ * also being put under the minimal test coverage provided by the tests
+ * that use property_database.js.
+ */
+
+for (var idx in gLonghandProperties) {
+ var prop = gLonghandProperties[idx];
+ if (prop.pref && !IsCSSPropertyPrefEnabled(prop.pref)) {
+ continue;
+ }
+ var present = prop.name in gCSSProperties;
+ ok(present,
+ "'" + prop.name + "' listed in gCSSProperties");
+ if (present) {
+ is(gCSSProperties[prop.name].type, CSS_TYPE_LONGHAND,
+ "'" + prop.name + "' listed as CSS_TYPE_LONGHAND");
+ is(gCSSProperties[prop.name].domProp, prop.prop,
+ "'" + prop.name + "' listed with correct DOM property name");
+ }
+}
+for (var idx in gShorthandProperties) {
+ var prop = gShorthandProperties[idx];
+ if (prop.pref && !IsCSSPropertyPrefEnabled(prop.pref)) {
+ continue;
+ }
+ if (prop.name == "all") {
+ // "all" isn't listed in property_database.js.
+ continue;
+ }
+ var present = prop.name in gCSSProperties;
+ ok(present,
+ "'" + prop.name + "' listed in gCSSProperties");
+ if (present) {
+ ok(gCSSProperties[prop.name].type == CSS_TYPE_TRUE_SHORTHAND ||
+ gCSSProperties[prop.name].type == CSS_TYPE_LEGACY_SHORTHAND ||
+ gCSSProperties[prop.name].type == CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ "'" + prop.name + "' listed as CSS_TYPE_TRUE_SHORTHAND, CSS_TYPE_LEGACY_SHORTHAND, or CSS_TYPE_SHORTHAND_AND_LONGHAND");
+ ok(gCSSProperties[prop.name].domProp == prop.prop,
+ "'" + prop.name + "' listed with correct DOM property name");
+ }
+}
+for (var idx in gShorthandPropertiesLikeLonghand) {
+ var prop = gShorthandPropertiesLikeLonghand[idx];
+ if (prop.pref && !IsCSSPropertyPrefEnabled(prop.pref)) {
+ continue;
+ }
+ var present = prop.name in gCSSProperties;
+ ok(present,
+ "'" + prop.name + "' listed in gCSSProperties");
+ if (present) {
+ ok(gCSSProperties[prop.name].type == CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ "'" + prop.name + "' listed as CSS_TYPE_SHORTHAND_AND_LONGHAND");
+ ok(gCSSProperties[prop.name].domProp == prop.prop,
+ "'" + prop.name + "' listed with correct DOM property name");
+ }
+}
+
+/*
+ * Test that all shorthand properties have a subproperty list and all
+ * longhand properties do not.
+ */
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ if (entry.pref && !IsCSSPropertyPrefEnabled(entry.pref)) {
+ continue;
+ }
+ if (entry.type == CSS_TYPE_LONGHAND) {
+ ok(!("subproperties" in entry),
+ "longhand property '" + prop + "' must not have subproperty list");
+ } else if (entry.type == CSS_TYPE_TRUE_SHORTHAND ||
+ entry.type == CSS_TYPE_SHORTHAND_AND_LONGHAND) {
+ ok("subproperties" in entry,
+ "shorthand property '" + prop + "' must have subproperty list");
+ }
+
+ if ("subproperties" in entry) {
+ var good = true;
+ if (entry.subproperties.length < 1) {
+ info("subproperty list for '" + prop + "' is empty");
+ good = false;
+ }
+ for (var idx in entry.subproperties) {
+ var subprop = entry.subproperties[idx];
+ if (!(subprop in gCSSProperties)) {
+ info("subproperty list for '" + prop + "' lists nonexistent subproperty '" + subprop + "'");
+ good = false;
+ }
+ }
+ ok(good, "property '" + prop + "' has a good subproperty list");
+ }
+
+ ok("initial_values" in entry && entry.initial_values.length >= 1,
+ "must have initial values for property '" + prop + "'");
+ ok("other_values" in entry && entry.other_values.length >= 1,
+ "must have non-initial values for property '" + prop + "'");
+}
+
+/*
+ * Test that only longhand properties or its aliases are listed as logical
+ * properties.
+ */
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ if (entry.logical) {
+ ok(entry.type == CSS_TYPE_LONGHAND ||
+ (entry.alias_for && gCSSProperties[entry.alias_for].logical),
+ "property '" + prop + "' is listed as CSS_TYPE_LONGHAND or is an alias due to it " +
+ "being a logical property");
+ }
+}
+
+/*
+ * Test that axis is only specified for logical properties.
+ */
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ if (entry.axis) {
+ ok(entry.logical,
+ "property '" + prop + "' is listed as an logical property due to its " +
+ "being listed as an axis-related property");
+ }
+}
+
+/*
+ * Test that DOM properties match the expected rules.
+ */
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ var expectedDOMProp = prop.replace(/-([a-z])/g,
+ function(m, p1, offset, str) {
+ return p1.toUpperCase();
+ });
+ if (expectedDOMProp == "float") {
+ expectedDOMProp = "cssFloat";
+ } else if (prop.startsWith("-webkit")) {
+ // Our DOM accessors for webkit-prefixed properties start with lowercase w,
+ // not uppercase like standard DOM accessors.
+ expectedDOMProp = expectedDOMProp.replace(/^W/, "w");
+ }
+ is(entry.domProp, expectedDOMProp, "DOM property for " + prop);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_property_syntax_errors.html b/layout/style/test/test_property_syntax_errors.html
new file mode 100644
index 0000000000..1e3f382036
--- /dev/null
+++ b/layout/style/test/test_property_syntax_errors.html
@@ -0,0 +1,155 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test that we reject syntax errors listed in property_database.js</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<p id="display"></p>
+<iframe id="quirks" src="data:text/html,<div id='testnode'></div>"></iframe>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
+
+function check_not_accepted(decl, property, info, badval)
+{
+ decl.setProperty(property, badval, "");
+
+ is(decl.getPropertyValue(property), "",
+ "invalid value '" + badval + "' not accepted for '" + property +
+ "' property");
+
+ if ("subproperties" in info) {
+ for (var sidx in info.subproperties) {
+ var subprop = info.subproperties[sidx];
+ is(decl.getPropertyValue(subprop), "",
+ "invalid value '" + badval + "' not accepted for '" + property +
+ "' property when testing subproperty '" + subprop + "'");
+ }
+ }
+
+ decl.removeProperty(property);
+}
+
+function check_value_balanced(decl, property, badval)
+{
+ var goodProp =
+ (property == "background-color") ? "color" : "background-color";
+ decl.cssText = goodProp + ": red; " + property + ": " + badval + "; " +
+ goodProp + ": green";
+ is(decl.getPropertyValue(goodProp), "green",
+ "invalid value '" + property + ": " + badval +
+ "' is balanced and does not lead to parsing errors afterwards");
+ decl.cssText = "";
+}
+
+function check_value_unbalanced(decl, property, badval)
+{
+ var goodProp =
+ (property == "background-color") ? "color" : "background-color";
+ decl.cssText = goodProp + ": green; " + property + ": " + badval + "; " +
+ goodProp + ": red";
+ is(decl.getPropertyValue(goodProp), "green",
+ "invalid value '" + property + ": " + badval +
+ "' is unbalanced and absorbs what follows it");
+ decl.cssText = "";
+}
+
+function check_empty_value_rejected(decl, emptyval, property)
+{
+ var goodProp =
+ (property == "background-color") ? "color" : "background-color";
+ decl.cssText = goodProp + ": red; " + property + ":" + emptyval + "; " +
+ goodProp + ": green";
+ is(decl.length, 1,
+ "empty value '" + property + ":" + emptyval +
+ "' is not accepted");
+ is(decl.getPropertyValue(goodProp), "green",
+ "empty value '" + property + ":" + emptyval +
+ "' is balanced and does not lead to parsing errors afterwards");
+ decl.cssText = "";
+}
+
+function run()
+{
+ var gDeclaration = document.getElementById("testnode").style;
+ var quirksFrame = document.getElementById("quirks");
+ var wrappedFrame = SpecialPowers.wrap(quirksFrame);
+ var gQuirksDeclaration = wrappedFrame.contentDocument
+ .getElementById("testnode").style;
+
+ for (var property in gCSSProperties) {
+ var info = gCSSProperties[property];
+
+ check_empty_value_rejected(gDeclaration, "", property);
+ check_empty_value_rejected(gDeclaration, " ", property);
+
+ for (var idx in info.invalid_values) {
+ check_not_accepted(gDeclaration, property, info,
+ info.invalid_values[idx]);
+ check_not_accepted(gQuirksDeclaration, property, info,
+ info.invalid_values[idx]);
+ check_value_balanced(gDeclaration, property,
+ info.invalid_values[idx]);
+ }
+
+ if ("quirks_values" in info) {
+ for (var quirkval in info.quirks_values) {
+ var standardval = info.quirks_values[quirkval];
+ check_not_accepted(gDeclaration, property, info, quirkval);
+ check_value_balanced(gDeclaration, property, quirkval);
+
+ gQuirksDeclaration.setProperty(property, quirkval, "");
+ gDeclaration.setProperty(property, standardval, "");
+ var quirkret = gQuirksDeclaration.getPropertyValue(property);
+ var standardret = gDeclaration.getPropertyValue(property);
+ isnot(quirkret, "", property + ": " + quirkval +
+ " should be accepted in quirks mode");
+ is(quirkret, standardret, property + ": " + quirkval + " result");
+
+ if ("subproperties" in info) {
+ for (var sidx in info.subproperties) {
+ var subprop = info.subproperties[sidx];
+ var quirksub = gQuirksDeclaration.getPropertyValue(subprop);
+ var standardsub = gDeclaration.getPropertyValue(subprop);
+ isnot(quirksub, "", property + ": " + quirkval +
+ " should be accepted in quirks mode" +
+ " when testing subproperty " + subprop);
+ is(quirksub, standardsub, property + ": " + quirkval + " result" +
+ " when testing subproperty " + subprop);
+ }
+ }
+
+ gQuirksDeclaration.removeProperty(property);
+ gDeclaration.removeProperty(property);
+ }
+ }
+
+ for (var idx in info.unbalanced_values) {
+ check_not_accepted(gDeclaration, property, info,
+ info.invalid_values[idx]);
+ check_not_accepted(gQuirksDeclaration, property, info,
+ info.invalid_values[idx]);
+ check_value_unbalanced(gDeclaration, property,
+ info.unbalanced_values[idx]);
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_pseudo_display_fixup.html b/layout/style/test/test_pseudo_display_fixup.html
new file mode 100644
index 0000000000..de4dd0f810
--- /dev/null
+++ b/layout/style/test/test_pseudo_display_fixup.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test item blockification of pseudo-elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #test {
+ display: flex;
+ }
+ #test::before, #test::after {
+ content: "test";
+ display: inline-block;
+ color: green;
+ /*
+ * NOTE(emilio): The transition rule is very intentional, to avoid testing
+ * the eagerly resolved style.
+ */
+ transition: color 1s ease;
+ }
+</style>
+<div id="test"></div>
+<script>
+test(function() {
+ document.body.offsetTop;
+ let test = document.getElementById("test");
+ assert_equals(getComputedStyle(test, "::before").display, "block");
+ assert_equals(getComputedStyle(test, "::after").display, "block");
+}, "::before and ::after pseudo-elements are blockified");
+</script>
diff --git a/layout/style/test/test_pseudoelement_parsing.html b/layout/style/test/test_pseudoelement_parsing.html
new file mode 100644
index 0000000000..b6fcf783f7
--- /dev/null
+++ b/layout/style/test/test_pseudoelement_parsing.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Test for Bug 922669</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+
+<style></style>
+
+<script>
+var style = document.querySelector("style");
+
+var gValidTests = [
+ "::-moz-progress-bar",
+ "::-moz-progress-bar:hover",
+ "::-moz-progress-bar:active",
+ "::-moz-progress-bar:focus",
+ "::-moz-progress-bar:hover:focus",
+ "#a::-moz-progress-bar:hover",
+ ":hover::-moz-progress-bar"
+];
+
+var gInvalidTests = [
+ "::foo",
+ "::-moz-progress-bar::-moz-progress-bar",
+ "::-moz-progress-bar::first-line",
+ "::-moz-progress-bar#a",
+ "::-moz-progress-bar:invalid",
+ "::-moz-focus-inner:active"
+];
+
+gValidTests.forEach(function(aTest) {
+ style.textContent = aTest + "{}";
+ is(style.sheet.cssRules.length, 1, aTest);
+ style.textContent = "";
+});
+
+gInvalidTests.forEach(function(aTest) {
+ style.textContent = aTest + "{}";
+ is(style.sheet.cssRules.length, 0, aTest);
+ style.textContent = "";
+});
+</script>
diff --git a/layout/style/test/test_pseudoelement_state.html b/layout/style/test/test_pseudoelement_state.html
new file mode 100644
index 0000000000..0a8c3d52f6
--- /dev/null
+++ b/layout/style/test/test_pseudoelement_state.html
@@ -0,0 +1,185 @@
+<!DOCTYPE html>
+<title>Test for Bug 922669</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+
+<iframe srcdoc="<!DOCTYPE html><style></style><div></div>"></iframe>
+
+<script>
+var gIsAndroid = navigator.appVersion.includes("Android");
+
+var gTests = [
+ // Interact with the ::-moz-progress-bar.
+ { markup: '<progress value="75" max="100"></progress>',
+ pseudoelement: '::-moz-progress-bar',
+ common_style: 'progress { -moz-appearance: none; } progress::-moz-progress-bar { background: black; }',
+ hover_test_style: 'progress::-moz-progress-bar:hover { background: green; }',
+ hover_reference_style: 'progress::-moz-progress-bar { background: green; }',
+ active_test_style: 'progress::-moz-progress-bar:active { background: lime; }',
+ active_reference_style: 'progress::-moz-progress-bar { background: lime; }' },
+
+ // Interact with the part of the <progress> not covered by the ::-moz-progress-bar.
+ { markup: '<progress value="25" max="100"></progress>',
+ pseudoelement: '::-moz-progress-bar',
+ common_style: 'progress { -moz-appearance: none; } progress::-moz-progress-bar { background: black; }',
+ hover_test_style: 'progress::-moz-progress-bar { background: green; } progress::-moz-progress-bar:hover { background: red; }',
+ hover_reference_style: 'progress::-moz-progress-bar { background: green; }',
+ active_test_style: 'progress::-moz-progress-bar { background: lime; } progress::-moz-progress-bar:active { background: red; }',
+ active_reference_style: 'progress::-moz-progress-bar { background: lime; }' },
+
+ // Interact with the ::-moz-range-thumb.
+ { markup: '<input type="range" value="50" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }',
+ hover_test_style: 'input::-moz-range-thumb:hover { background: green; }',
+ hover_reference_style: 'input::-moz-range-thumb { background: green; }',
+ active_test_style: 'input::-moz-range-thumb:active { background: lime; }',
+ active_reference_style: 'input::-moz-range-thumb { background: lime; }' },
+
+ // Interact with the part of the <input type="range"> not covered by the ::-moz-range-thumb.
+ { markup: '<input type="range" value="25" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }',
+ hover_test_style: 'input::-moz-range-thumb { background: green; } input::-moz-range-thumb:hover { background: red; }',
+ hover_reference_style: 'input::-moz-range-thumb { background: green; }',
+ active_test_style: 'input::-moz-range-thumb { background: lime; } input::-moz-range-thumb:active { background: red; }',
+ active_reference_style: 'input::-moz-range-thumb { background: lime; }' },
+
+ // Interact with the ::-moz-meter-bar.
+ { markup: '<meter value="75" min="0" max="100"></meter>',
+ pseudoelement: '::-moz-meter-bar',
+ common_style: 'meter { -moz-appearance: none; } meter::-moz-meter-bar { background: black; }',
+ hover_test_style: 'meter::-moz-meter-bar:hover { background: green; }',
+ hover_reference_style: 'meter::-moz-meter-bar { background: green; }',
+ active_test_style: 'meter::-moz-meter-bar:active { background: lime; }',
+ active_reference_style: 'meter::-moz-meter-bar { background: lime; }' },
+
+ // Interact with the part of the <meter> not covered by the ::-moz-meter-bar.
+ { markup: '<meter value="25" min="0" max="100"></meter>',
+ pseudoelement: '::-moz-meter-bar',
+ common_style: 'meter { -moz-appearance: none; } meter::-moz-meter-bar { background: black; }',
+ hover_test_style: 'meter::-moz-meter-bar { background: green; } meter::-moz-meter-bar:hover { background: red; }',
+ hover_reference_style: 'meter::-moz-meter-bar { background: green; }',
+ active_test_style: 'meter::-moz-meter-bar { background: lime; } meter::-moz-meter-bar:active { background: red; }',
+ active_reference_style: 'meter::-moz-meter-bar { background: lime; }' },
+
+ // Do the same as the "Interact with the ::-moz-range-thumb" subtest above,
+ // but with selectors that include descendant operators.
+ { markup: '<input type="range" value="50" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'body input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }',
+ hover_test_style: 'body input::-moz-range-thumb:hover { background: green; }',
+ hover_reference_style: 'body input::-moz-range-thumb { background: green; }',
+ active_test_style: 'body input::-moz-range-thumb:active { background: lime; }',
+ active_reference_style: 'body input::-moz-range-thumb { background: lime; }' },
+
+ // Do the same as above, but using :is instead.
+ { markup: '<input type="range" value="50" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'body input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }',
+ hover_test_style: 'body input::-moz-range-thumb:is(:hover) { background: green; }',
+ hover_reference_style: 'body input::-moz-range-thumb { background: green; }',
+ active_test_style: 'body input::-moz-range-thumb:is(:active) { background: lime; }',
+ active_reference_style: 'body input::-moz-range-thumb { background: lime; }' },
+
+ // Do the same as above, but using :not instead.
+ { markup: '<input type="range" value="50" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'input { -moz-appearance: none; } input::-moz-range-thumb { background: green } input::-moz-range-thumb:not(:hover) { background: black; }',
+ hover_test_style: '',
+ hover_reference_style: 'input::-moz-range-thumb { background: green !important; }',
+ active_test_style: 'input::-moz-range-thumb:active { background: lime !important; }',
+ active_reference_style: 'input::-moz-range-thumb { background: lime !important; }' },
+
+ // ::placeholder can't be tested, since the UA style sheet sets it to
+ // be pointer-events:none.
+];
+
+function countPixelDifferences(aCanvas1, aCanvas2) {
+ var ctx1 = aCanvas1.getContext("2d");
+ var ctx2 = aCanvas2.getContext("2d");
+ var data1 = ctx1.getImageData(0, 0, aCanvas1.width, aCanvas1.height);
+ var data2 = ctx2.getImageData(0, 0, aCanvas2.width, aCanvas2.height);
+ var n = 0;
+ for (var i = 0; i < data1.width * data2.height * 4; i += 4) {
+ if (data1.data[i] != data2.data[i] ||
+ data1.data[i + 1] != data2.data[i + 1] ||
+ data1.data[i + 2] != data2.data[i + 2] ||
+ data1.data[i + 3] != data2.data[i + 3]) {
+ n++;
+ }
+ }
+ return n;
+}
+
+function runTests() {
+ var iframe = document.querySelector("iframe");
+ var style = iframe.contentDocument.querySelector("style");
+ var div = iframe.contentDocument.querySelector("div");
+ var canvas1, canvas2;
+
+ function runTestPart1(aIndex) {
+ var test = gTests[aIndex];
+ div.innerHTML = test.markup;
+ style.textContent = test.common_style;
+ is(getComputedStyle(div.firstChild, test.pseudoelement).backgroundColor, "rgb(0, 0, 0)", "subtest #" + aIndex + ", computed style");
+ style.textContent += test.hover_test_style;
+ synthesizeMouseAtCenter(div.lastChild, { type: 'mouseover' }, iframe.contentWindow);
+ }
+
+ function runTestPart2(aIndex) {
+ var test = gTests[aIndex];
+ canvas1 = SpecialPowers.snapshotWindow(iframe.contentWindow, false);
+ style.textContent = test.common_style + test.hover_reference_style;
+ }
+
+ function runTestPart3(aIndex) {
+ var test = gTests[aIndex];
+ canvas2 = SpecialPowers.snapshotWindow(iframe.contentWindow, false);
+ ok(canvas1.width == canvas2.width && canvas1.height == canvas2.height, "hover subtest #" + aIndex + ", canvas sizes equal");
+ is(countPixelDifferences(canvas1, canvas2), 0, "hover subtest #" + aIndex + ", number of different pixels");
+ is(getComputedStyle(div.firstChild, test.pseudoelement).backgroundColor, "rgb(0, 128, 0)", "hover subtest #" + aIndex + ", computed style");
+
+ if (!gIsAndroid) {
+ style.textContent = test.common_style + test.active_test_style;
+ synthesizeMouseAtCenter(div.lastChild, { type: 'mousedown' }, iframe.contentWindow);
+ }
+ }
+
+ function runTestPart4(aIndex) {
+ if (!gIsAndroid) {
+ var test = gTests[aIndex];
+ canvas1 = SpecialPowers.snapshotWindow(iframe.contentWindow, false);
+ synthesizeMouseAtCenter(div.lastChild, { type: 'mouseup' }, iframe.contentWindow);
+ style.textContent = test.common_style + test.active_reference_style;
+ }
+ }
+
+ function runTestPart5(aIndex) {
+ if (!gIsAndroid) {
+ var test = gTests[aIndex];
+ canvas2 = SpecialPowers.snapshotWindow(iframe.contentWindow, false);
+ ok(canvas1.width == canvas2.width && canvas1.height == canvas2.height, "active subtest #" + aIndex + ", canvas sizes equal");
+ is(countPixelDifferences(canvas1, canvas2), 0, "active subtest #" + aIndex + ", number of different pixels");
+ is(getComputedStyle(div.firstChild, test.pseudoelement).backgroundColor, "rgb(0, 255, 0)", "active subtest #" + aIndex + ", computed style");
+ }
+ }
+
+ for (var i = 0; i < gTests.length; i++) {
+ setTimeout(runTestPart1, 0, i);
+ setTimeout(runTestPart2, 0, i);
+ setTimeout(runTestPart3, 0, i);
+ setTimeout(runTestPart4, 0, i);
+ setTimeout(runTestPart5, 0, i);
+ }
+ setTimeout(function() { SimpleTest.finish(); }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+window.addEventListener("load", async function() {
+ await SpecialPowers.contentTransformsReceived(window);
+ runTests();
+});
+</script>
diff --git a/layout/style/test/test_query_container_for.html b/layout/style/test/test_query_container_for.html
new file mode 100644
index 0000000000..90dca9fab9
--- /dev/null
+++ b/layout/style/test/test_query_container_for.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<title>Test for bug 1789191</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<style>
+@container (min-width: 0px) {
+}
+
+@container name (min-width: 0px) {
+}
+
+@container (min-height: 0px) {
+}
+
+div {
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+ margin: 10px;
+}
+
+.container-height {
+ container-type: size;
+}
+
+.container-unnamed {
+ container-type: inline-size;
+}
+
+.container-named {
+ container-type: inline-size;
+ container-name: name;
+}
+</style>
+
+<div id="child1"></div>
+
+<div class="container-height" id="container1">
+ <div class="container-named" id="container2">
+ <div class="container-unnamed" id="container3">
+ <div id="child2"></div>
+ </div>
+ </div>
+</div>
+
+<script>
+ let sheet = document.querySelector("style").sheet;
+ let withoutFilter = SpecialPowers.wrap(sheet.cssRules[0]);
+ let withFilter = SpecialPowers.wrap(sheet.cssRules[1]);
+ let heightQuery = SpecialPowers.wrap(sheet.cssRules[2]);
+
+ // Container query selection requires up-to-date layout information.
+ document.body.getBoundingClientRect();
+
+ is(withoutFilter.queryContainerFor(child1), null, "No filter, no container");
+ is(withFilter.queryContainerFor(child1), null, "Filter, no container");
+ is(heightQuery.queryContainerFor(child1), null, "Height, no container");
+
+ is(SpecialPowers.unwrap(withoutFilter.queryContainerFor(child2)), container3, "No filter, container");
+ is(SpecialPowers.unwrap(withFilter.queryContainerFor(child2)), container2, "Filter");
+ is(SpecialPowers.unwrap(heightQuery.queryContainerFor(child2)), container1, "Height");
+</script>
diff --git a/layout/style/test/test_redundant_font_download.html b/layout/style/test/test_redundant_font_download.html
new file mode 100644
index 0000000000..c6930ae401
--- /dev/null
+++ b/layout/style/test/test_redundant_font_download.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=879963 -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for bug 879963</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+ <!-- Two <style> elements with identical @font-face rules.
+ Although multiple @font-face at-rules for the same family are legal,
+ and add faces to the family, we should NOT download the same resource
+ twice just because we have a duplicate face entry. -->
+ <style type="text/css">
+ @font-face {
+ font-family: foo;
+ src: url("redundant_font_download.sjs?q=font");
+ }
+ .test {
+ font-family: foo;
+ }
+ </style>
+
+ <style type="text/css">
+ @font-face {
+ font-family: foo;
+ src: url("redundant_font_download.sjs?q=font");
+ }
+ .test {
+ font-family: foo;
+ }
+ </style>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=879963">Mozilla Bug 879963</a>
+
+ <div>
+ <!-- the 'init' request returns an image (just so we can see it's working)
+ and initializes the request logging in our sjs server -->
+ <img src="redundant_font_download.sjs?q=init">
+ </div>
+
+ <div id="test">
+ Test
+ </div>
+
+ <div>
+ <img id="image2" src="">
+ </div>
+
+ <script type="application/javascript">
+ // helper to retrieve the server's request log as text, synchronously
+ function getRequestLog() {
+ var xmlHttp = new XMLHttpRequest();
+ xmlHttp.open("GET", "redundant_font_download.sjs?q=report", false);
+ xmlHttp.send(null);
+ return xmlHttp.responseText;
+ }
+
+ // retrieve just the most recent request the server has seen
+ function getLastRequest() {
+ return getRequestLog().split(";").pop();
+ }
+
+ // poll the server at intervals of animation frame callback until it has
+ // seen a specific request, or until maxTime ms have passed
+ function waitForRequest(request, maxTime, func) {
+ timeLimit = Date.now() + maxTime;
+ requestAnimationFrame(function rAF() {
+ var req = getLastRequest();
+ if (req == request || Date.now() > timeLimit) {
+ func();
+ return;
+ }
+ requestAnimationFrame(rAF);
+ });
+ }
+
+ // initially disable the second of the <style> elements,
+ // so we only have a single copy of the font-face
+ document.getElementsByTagName("style")[1].disabled = true;
+
+ SimpleTest.waitForExplicitFinish();
+
+ // We perform a series of actions that trigger requests to the .sjs server,
+ // and poll the server's request log at each stage to check that it has
+ // seen the request we expected before we proceed to the next step.
+ function startTest() {
+ is(getRequestLog(), "init", "request log has been initialized");
+
+ // this should trigger a font download
+ document.getElementById("test").className = "test";
+
+ // wait to confirm that the server has received the request
+ waitForRequest("font", 5000, continueTest1);
+ }
+
+ function continueTest1() {
+ is(getRequestLog(), "init;font", "server received font request");
+
+ // trigger a request for the second image, to provide an explicit
+ // delimiter in the log before we enable the second @font-face rule
+ document.getElementById("image2").src = "redundant_font_download.sjs?q=image";
+
+ waitForRequest("image", 5000, continueTest2);
+ }
+
+ function continueTest2() {
+ is(getRequestLog(), "init;font;image", "server received image request");
+
+ // enable the second <style> element: we should NOT see a second font request,
+ // we expect waitForRequest to time out instead
+ document.getElementsByTagName("style")[1].disabled = false;
+
+ waitForRequest("font", 1000, continueTest3);
+ }
+
+ function continueTest3() {
+ is(getRequestLog(), "init;font;image", "should NOT have re-requested the font");
+
+ SimpleTest.finish();
+ }
+
+ waitForRequest("init", 5000, startTest);
+
+ </script>
+
+</body>
+
+</html>
diff --git a/layout/style/test/test_reframe_cb.html b/layout/style/test/test_reframe_cb.html
new file mode 100644
index 0000000000..549d04c5c0
--- /dev/null
+++ b/layout/style/test/test_reframe_cb.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1519371: We don't reframe for changes that affect our containing
+ block status unless whether we're a containing block really changed.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div id="content"></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+
+function expectReframe(shouldHaveReframed, callback) {
+ document.documentElement.offsetTop;
+ const previousConstructCount = utils.framesConstructed;
+ const previousRestyleGeneration = utils.restyleGeneration;
+
+ callback();
+
+ document.documentElement.offsetTop;
+ isnot(previousRestyleGeneration, utils.restyleGeneration,
+ "We should have restyled");
+ (shouldHaveReframed ? isnot : is)(previousConstructCount,
+ utils.framesConstructed,
+ `We should ${shouldHaveReframed ? "" : "not"} have reframed`);
+}
+
+const content = document.getElementById("content");
+
+// Without fixed-pos descendants, we should not reframe.
+expectReframe(false, () => {
+ content.style.transform = "scale(1)";
+});
+
+content.style.transform = "";
+content.innerHTML = `<div style="position: fixed"></div>`;
+
+// With fixed-pos descendants, got to reframe.
+expectReframe(true, () => {
+ content.style.transform = "scale(1)";
+});
+
+// If our containing-block-ness didn't change, we should not need to reframe.
+expectReframe(false, () => {
+ content.style.willChange = "transform";
+});
+
+// If it does change, we need to reframe.
+expectReframe(true, () => {
+ content.style.willChange = "";
+ content.style.transform = "";
+});
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_reframe_image_loading.html b/layout/style/test/test_reframe_image_loading.html
new file mode 100644
index 0000000000..2f8a44c361
--- /dev/null
+++ b/layout/style/test/test_reframe_image_loading.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1395964: We don't reframe for image loading state changes.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<img id="content" src="support/1x1-transparent.png"></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+
+const content = document.getElementById("content");
+
+onload = function() {
+ content.offsetTop;
+
+ const previousConstructCount = utils.framesConstructed;
+
+ content.addEventListener("load", function() {
+ content.offsetTop;
+ is(previousConstructCount, utils.framesConstructed,
+ "We should not have reframed");
+ SimpleTest.finish();
+ });
+
+ content.src = "support/blue-100x100.png";
+}
+</script>
diff --git a/layout/style/test/test_reframe_input.html b/layout/style/test/test_reframe_input.html
new file mode 100644
index 0000000000..2887548abf
--- /dev/null
+++ b/layout/style/test/test_reframe_input.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for bug 1658302: We don't reframe for placeholder attribute value changes.</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<input id="input">
+<textarea id="textarea"></textarea>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.DOMWindowUtils;
+
+function expectReframe(shouldHaveReframed, callback) {
+ document.documentElement.offsetTop;
+ const previousConstructCount = utils.framesConstructed;
+ const previousReflowCount = utils.framesReflowed;
+
+ callback();
+
+ document.documentElement.offsetTop;
+ isnot(previousReflowCount, utils.framesReflowed, "We should have reflowed");
+ (shouldHaveReframed ? isnot : is)(previousConstructCount,
+ utils.framesConstructed,
+ `We should ${shouldHaveReframed ? "" : "not"} have reframed`);
+}
+
+for (const control of document.querySelectorAll("input, textarea")) {
+ // Creating the placeholder attribute reframes right now.
+ //
+ // TODO: Could be avoided with some more work.
+ expectReframe(true, () => {
+ control.placeholder = "foo";
+ });
+
+ // Incrementally changing it should not reframe, just reflow.
+ expectReframe(false, () => {
+ control.placeholder = "bar";
+ });
+
+ // Removing the placeholder attribute reframes right now.
+ //
+ // TODO: Could maybe be avoided with some more work.
+ expectReframe(true, () => {
+ control.removeAttribute("placeholder");
+ });
+}
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_reframe_pseudo_element.html b/layout/style/test/test_reframe_pseudo_element.html
new file mode 100644
index 0000000000..0e0b418e81
--- /dev/null
+++ b/layout/style/test/test_reframe_pseudo_element.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1376352: We don't reframe all the time a replaced element that
+ matches generated content rules.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+#flex::before,
+input::before {
+ content: "Foo";
+}
+</style>
+<input type="text">
+<div id="flex"></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+
+function testNoReframe(callback) {
+ document.documentElement.offsetTop;
+ const previousConstructCount = utils.framesConstructed;
+ const previousRestyleGeneration = utils.restyleGeneration;
+
+ callback();
+
+ document.documentElement.offsetTop;
+ isnot(previousRestyleGeneration, utils.restyleGeneration,
+ "We should have restyled");
+ is(previousConstructCount, utils.framesConstructed,
+ "We shouldn't have reframed");
+}
+
+testNoReframe(function() {
+ const input = document.querySelector('input');
+ input.style.color = "blue";
+});
+
+testNoReframe(function() {
+ const flex = document.getElementById('flex');
+ flex.style.color = "blue";
+});
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_rem_unit.html b/layout/style/test/test_rem_unit.html
new file mode 100644
index 0000000000..bd46524798
--- /dev/null
+++ b/layout/style/test/test_rem_unit.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=478321
+-->
+<head>
+ <title>Test for CSS 'rem' unit</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478321">Mozilla Bug 478321</a>
+<p id="display"></p>
+<p id="display2"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for CSS 'rem' unit **/
+
+function px_to_num(str)
+{
+ return Number(String(str).match(/^([\d.]+)px$/)[1]);
+}
+
+function fs(elt)
+{
+ return px_to_num(getComputedStyle(elt, "").fontSize);
+}
+
+var html = document.documentElement;
+var body = document.body;
+var p = document.getElementById("display");
+var p2 = document.getElementById("display2");
+
+html.style.font = "initial";
+
+var defaultFontSize = fs(html);
+
+// NOTE: This test assumes that the default font size is an
+// integral number of pixels (which is always the case at present).
+// If that ever becomes false, the calls to "is" may need to be replaced by
+// calls to "isapprox" that allows errors of up to some small fraction
+// of a pixel.
+
+html.style.fontSize = "3rem";
+is(fs(html), 3 * defaultFontSize,
+ "3rem on root should triple root's font size");
+body.style.font = "initial";
+is(fs(body), defaultFontSize,
+ "initial should produce initial font size");
+body.style.fontSize = "1em";
+is(fs(body), 3 * defaultFontSize, "1em should inherit from parent");
+body.style.fontSize = "200%";
+is(fs(body), 6 * defaultFontSize, "200% should double parent");
+body.style.fontSize = "2rem";
+is(fs(body), 6 * defaultFontSize, "2rem should double root");
+p.style.font = "inherit";
+is(fs(p), 6 * defaultFontSize, "inherit should inherit from parent");
+p2.style.fontSize = "2rem";
+is(fs(p2), 6 * defaultFontSize, "2rem should double root");
+body.style.font = "initial";
+is(fs(p), defaultFontSize, "inherit should inherit from parent");
+is(fs(p2), 6 * defaultFontSize, "2rem should double root");
+body.style.fontSize = "5em";
+html.style.fontSize = "200%";
+is(fs(p), 10 * defaultFontSize, "inherit should inherit from parent");
+is(fs(p2), 4 * defaultFontSize, "2rem should double root");
+
+
+// Make things readable again.
+html.style.fontSize = "1em";
+body.style.fontSize = "1em";
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_restyle_table_wrapper.html b/layout/style/test/test_restyle_table_wrapper.html
new file mode 100644
index 0000000000..d8049b142e
--- /dev/null
+++ b/layout/style/test/test_restyle_table_wrapper.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1371955: We don't incorrectly think that a table wrapper style
+ is the main table element style.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+/* Test implicitly ::before and ::after too */
+span::before, span::after {
+ content: "";
+ display: table;
+}
+</style>
+<table id="realTable" style="margin: 10px"></table>
+<span id="spanTable" style="display: table; padding: 10px;"></span>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+document.documentElement.offsetTop;
+for (const element of [realTable, spanTable]) {
+ const previousReflowCount = utils.framesReflowed;
+ const previousRestyleGeneration = utils.restyleGeneration;
+ element.style.color = "blue";
+ document.documentElement.offsetTop;
+ isnot(previousRestyleGeneration, utils.restyleGeneration,
+ "We should have restyled");
+ is(previousReflowCount, utils.framesReflowed,
+ "We shouldn't have reflowed");
+}
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_restyles_in_smil_animation.html b/layout/style/test/test_restyles_in_smil_animation.html
new file mode 100644
index 0000000000..17c1ebb2ab
--- /dev/null
+++ b/layout/style/test/test_restyles_in_smil_animation.html
@@ -0,0 +1,137 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Tests restyles in smil animation</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+
+<div id="target-div">
+ <svg>
+ <rect id="svg-rect" width="100%" height="100%" fill="lime"/>
+ </svg>
+</div>
+
+<script>
+"use strict";
+
+// Waits for |frameCount| requestAnimationFrame callbacks.
+// Returns the number of frame actually waited.
+function waitForAnimationFrames(frameCount) {
+ return new Promise(function(resolve, reject) {
+ let previousTime = document.timeline.currentTime;
+ let framesWaited = 0;
+ function handleFrame() {
+ // SMIL uses a time resolution of 1ms but our software-based vsync timer
+ // sometimes produces ticks with an interval of less than 1ms. In such
+ // cases we will skip restyling for SMIL animations since the SMIL time
+ // will not change.
+ //
+ // In the following we attempt to detect such situations and wait an
+ // additional frame when we detect it. However, the detection is not
+ // completely accurate since it uses the timeline time which is based on
+ // the navigation start time whereas the SMIL start time is based on the
+ // refresh driver time.
+ //
+ // To account for this inaccuracy the Promise returned by this method
+ // resolves with the number of frames waited with the additional frames.
+ // This can be used by the call site to add a suitable tolerance to the
+ // number of restylings it expects to happen. For example, if a call site
+ // is anticipating each animation frame to cause restyling, then the
+ // number of restylings, x, it should expect is frameCount <= x <=
+ // framesWaited.
+ const difference = document.timeline.currentTime - previousTime;
+ framesWaited++;
+ if (difference >= 1.0 && --frameCount <= 0) {
+ resolve(framesWaited);
+ return;
+ }
+
+ previousTime = document.timeline.currentTime;
+ window.requestAnimationFrame(handleFrame); // wait another frame
+ }
+ window.requestAnimationFrame(handleFrame);
+ });
+}
+
+// Returns an object consisting of observed styling count and the number of
+// frames actually waited because we detected a possibly overflapping SMIL
+// time.
+function observeStyling(frameCount) {
+ let priorAnimationTriggeredRestyles = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles;
+
+ return new Promise(function(resolve) {
+ return waitForAnimationFrames(frameCount).then(framesWaited => {
+ const restyleCount = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles - priorAnimationTriggeredRestyles;
+ resolve({
+ stylingCount: restyleCount,
+ framesWaited: framesWaited,
+ });
+ });
+ });
+}
+
+function ensureElementRemoval(aElement) {
+ return new Promise(function(resolve) {
+ aElement.remove();
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+function waitForPaintFlushed() {
+ return new Promise(function(resolve) {
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+add_task(async function smil_is_in_display_none_subtree() {
+ await waitForPaintFlushed();
+
+ var animate =
+ document.createElementNS("http://www.w3.org/2000/svg", "animate");
+ animate.setAttribute("attributeType", "XML");
+ animate.setAttribute("attributeName", "fill");
+ animate.setAttribute("values", "red;lime");
+ animate.setAttribute("dur", "1s");
+ animate.setAttribute("repeatCount", "indefinite");
+ document.getElementById("svg-rect").appendChild(animate);
+
+ await waitForAnimationFrames(2);
+
+ let result = await observeStyling(5);
+ // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
+ // element is newly associated with an nsIFrame.
+ ok(result.stylingCount >= 4 &&
+ result.stylingCount <= result.framesWaited,
+ `should restyle in most frames (got ${result.stylingCount} restyles ` +
+ `over ${result.framesWaited} frames, expected 4~${result.framesWaited})`);
+
+ var div = document.getElementById("target-div");
+
+ div.style.display = "none";
+ getComputedStyle(div).display;
+ await waitForPaintFlushed();
+
+ result = await observeStyling(5);
+ is(result.stylingCount, 0, "should never restyle if display:none");
+
+ div.style.display = "";
+ getComputedStyle(div).display;
+ await waitForAnimationFrames(2);
+
+ result = await observeStyling(5);
+ // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
+ // element is newly associated with an nsIFrame.
+ ok(result.stylingCount >= 4 &&
+ result.stylingCount <= result.framesWaited,
+ `should restyle again (got ${result.stylingCount} restyles over ` +
+ `${result.framesWaited} frames, expected 4~${result.framesWaited})`);
+
+ await ensureElementRemoval(animate);
+});
+</script>
+</body>
diff --git a/layout/style/test/test_revert.html b/layout/style/test/test_revert.html
new file mode 100644
index 0000000000..48897a75ca
--- /dev/null
+++ b/layout/style/test/test_revert.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<title>Test for computation of CSS 'revert' on all properties</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="property_database.js"></script>
+<style id="stylesheet"></style>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<div id="inheritanceParent">
+ <div id="inherited"></div>
+</div>
+<div id="nonInherited"></div>
+<div id="noAuthorStyleApplied"></div>
+<pre id="test">
+<script class="testbody">
+
+const kSheet = document.getElementById("stylesheet").sheet;
+const kAllDifferentFromInitialRule = kSheet.cssRules[kSheet.insertRule("#inheritanceParent {}", kSheet.cssRules.length)];
+const kPrerequisites = kSheet.cssRules[kSheet.insertRule("#inherited, #inheritanceParent, #nonInherited, #noAuthorStyleApplied {}", kSheet.cssRules.length)];
+const kResetPropRule = kSheet.cssRules[kSheet.insertRule("#nonInherited {}", kSheet.cssRules.length)];
+
+const kInheritedDiv = document.getElementById("inherited");
+const kResetDiv = document.getElementById("nonInherited");
+const kNoAuthorStylesDiv = document.getElementById("noAuthorStyleApplied");
+
+function computedValue(node, property) {
+ return get_computed_value(getComputedStyle(node), property);
+}
+
+function getInitialValue(node, property) {
+ node.style.setProperty(property, "initial");
+ const initial = computedValue(node, property);
+ node.style.removeProperty(property);
+ return initial;
+}
+
+function testResetProperty(property, info) {
+ kResetPropRule.style.setProperty(property, info.other_values[0]);
+
+ const div = kResetDiv;
+ const initial = getInitialValue(div, property);
+ const computed = computedValue(div, property);
+
+ isnot(computed, initial, `${property}: Should get something non-initial to begin with`);
+
+ const defaultValue = computedValue(kNoAuthorStylesDiv, property);
+
+ div.style.setProperty(property, "revert");
+ const reverted = computedValue(div, property);
+ is(reverted, defaultValue, `${property}: Should behave as if there was no author style`);
+ div.style.removeProperty(property);
+ kResetPropRule.style.removeProperty(property);
+}
+
+function testInheritedProperty(property, info) {
+ // Given how line-height works, and that it always returns the used value, we
+ // cannot test it. The prerequisites for line-height makes getComputedStyle
+ // and getDefaultComputedStyle return the same, even though the computed value
+ // is different (normal vs. 19px).
+ if (property == "line-height")
+ return;
+
+ const div = kInheritedDiv;
+ const initial = getInitialValue(div, property);
+ const parentValue = computedValue(div.parentNode, property);
+
+ isnot(parentValue, initial, `${property}: Should inherit something non-initial to begin with`);
+
+ const inheritedValue = computedValue(div, property);
+ const hasUARule = inheritedValue != parentValue;
+
+ const defaultValue = computedValue(kNoAuthorStylesDiv, property);
+ (hasUARule ? is : isnot)(defaultValue, inheritedValue, `${property}: Should get the non-inherited value from somewhere (expected ${hasUARule ? "UA Rule" : "inheritance"} - inherited: ${inheritedValue} - parent: ${parentValue} - default: ${defaultValue})`);
+
+ div.style.setProperty(property, "revert");
+ const reverted = computedValue(div, property);
+ if (hasUARule)
+ is(reverted, defaultValue, `${property}: Should behave as if there was no author style`);
+ else
+ is(reverted, inheritedValue, `${property}: Should behave as if there was no author style`);
+ div.style.removeProperty(property);
+}
+
+function testProperty(property, info) {
+ if (info.prerequisites)
+ for (const prereq in info.prerequisites)
+ kPrerequisites.style.setProperty(prereq, info.prerequisites[prereq]);
+ if (info.inherited)
+ testInheritedProperty(property, info);
+ else
+ testResetProperty(property, info);
+ kPrerequisites.style = "";
+}
+
+for (const prop in gCSSProperties) {
+ const info = gCSSProperties[prop];
+ if (info.type != CSS_TYPE_LONGHAND)
+ continue;
+ kAllDifferentFromInitialRule.style.setProperty(prop, info.other_values[0]);
+}
+
+for (const prop in gCSSProperties)
+ testProperty(prop, gCSSProperties[prop]);
+</script>
+</pre>
diff --git a/layout/style/test/test_root_node_display.html b/layout/style/test/test_root_node_display.html
new file mode 100644
index 0000000000..54dd9c222c
--- /dev/null
+++ b/layout/style/test/test_root_node_display.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=969460
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 969460</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.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=969460">Mozilla Bug 969460</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="float" style="float: left"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 969460: Test that "display" on the root node is computed
+ using the same conversion that we use for display on floated elements **/
+
+function test_display_value(val)
+{
+ var floatElem = document.getElementById("float");
+ floatElem.style.display = val;
+ var floatConversion = window.getComputedStyle(floatElem).display;
+ floatElem.style.display = "";
+
+ var rootNode = document.documentElement;
+ rootNode.style.display = val;
+ rootNode.offsetHeight; // (Flush layout, to be sure layout can handle 'val')
+ var rootConversion = window.getComputedStyle(rootNode).display;
+ rootNode.style.display = "";
+
+ // Special case: "display:list-item" does not get modified by 'float',
+ // but the spec allows us to convert it to 'block' on the root node
+ // (and we do convert it, so that we don't have to support documents whose
+ // root node is a list-item).
+ if (val == "list-item") {
+ is(floatConversion, val, "'float' shouldn't affect 'display:list-item'");
+ is(rootConversion, "block",
+ "We traditionally convert '" + val + "' on the root node to " +
+ "'display:block' (though if that changes, it's not technically a bug, " +
+ "as long as we support it properly).");
+} else if (val == "inline list-item" ||
+ val == "inline flow-root list-item") {
+ is(floatConversion, "list-item", "'float' should blockify '" + val + "'");
+ is(rootConversion, "block",
+ "We traditionally convert '" + val + "' on the root node to " +
+ "'display:block' (though if that changes, it's not technically a bug, " +
+ "as long as we support it properly).");
+ } else if (val == "contents") {
+ is(floatConversion, val, "'float' shouldn't affect 'display:contents'");
+ is(rootConversion, "block",
+ "'display:contents' on the root node computes to block-level per" +
+ "http://dev.w3.org/csswg/css-display/#transformations");
+ } else {
+ is(rootConversion, floatConversion,
+ "root node should make 'display:" + val + "' compute to the same " +
+ "value that it computes to on a floated element");
+ }
+}
+
+var displayInfo = gCSSProperties.display;
+displayInfo.initial_values.forEach(test_display_value);
+displayInfo.other_values.forEach(test_display_value);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_rule_insertion.html b/layout/style/test/test_rule_insertion.html
new file mode 100644
index 0000000000..e3103309aa
--- /dev/null
+++ b/layout/style/test/test_rule_insertion.html
@@ -0,0 +1,240 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=816720
+-->
+<head>
+ <title>Test for Bug 816720</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css" id="style"></style>
+</head>
+<body>
+
+<pre id="test"></pre>
+
+<p><span id=control-serif>........</span></p>
+<p><span id=control-monospace>........</span></p>
+<p><span id=test-font>........</span></p>
+
+<style id=other-styles>
+ #test { font-size: 16px; animation: test 1s both }
+ #control-serif { font: 16px serif }
+ #test-font { font: 16px UnlikelyFontName, serif }
+</style>
+
+<p><span id=control-decimal></span></p>
+<p><span id=control-cjk-decimal></span></p>
+<p><span id=test-counter-style></span></p>
+
+<style>
+ #control-decimal::before { content: counter(a, decimal); }
+ #control-cjk-decimal::before { content: counter(a, cjk-decimal); }
+ #test-counter-style::before { content: counter(a, unlikely-counter-style); }
+</style>
+
+<script type="application/javascript">
+
+// Monospace fonts available on all the platforms we're testing on.
+//
+// XXX Once bug 817220 is fixed we could instead use the value of
+// font.name.monospace.x-western as the monospace font to use.
+var MONOSPACE_FONTS = [
+ "Courier",
+ "Courier New",
+ "Monaco",
+ "DejaVu Sans Mono",
+ "Droid Sans Mono"
+];
+
+var test = document.getElementById("test");
+var controlSerif = document.getElementById("control-serif");
+var controlMonospace = document.getElementById("control-monospace");
+var testFont = document.getElementById("test-font");
+var otherStyles = document.getElementById("other-styles");
+
+otherStyles.sheet.insertRule("#control-monospace { font: 16px " +
+ MONOSPACE_FONTS + ", serif }", 0);
+
+var monospaceWidth = controlMonospace.getBoundingClientRect().width;
+var serifWidth = controlSerif.getBoundingClientRect().width;
+
+var controlDecimal = document.getElementById("control-decimal");
+var controlCJKDecimal = document.getElementById("control-cjk-decimal");
+var testCounterStyle = document.getElementById("test-counter-style");
+
+var decimalWidth = controlDecimal.getBoundingClientRect().width;
+var cjkDecimalWidth = controlCJKDecimal.getBoundingClientRect().width;
+
+// [at-rule type, passing condition, failing condition]
+var outerRuleInfo = [
+ ["@media", "all", "not all"],
+ ["@supports", "(color: green)", "(unknown: unknown)"]
+];
+
+// [rule, function to test whether the rule was successfully inserted and applied]
+var innerRuleInfo = [
+ ["#test { text-decoration: underline; }",
+ function(aApplied, aParent, aException) {
+ return !aException &&
+ window.getComputedStyle(test).textDecorationLine ==
+ (aApplied ? "underline" : "none");
+ }],
+ ["@page { margin: 4cm; }",
+ function(aApplied, aParent, aException) {
+ // just test whether it threw
+ return !aException;
+ }],
+ ["@keyframes test { from { font-size: 100px; } to { font-size: 100px; } }",
+ function(aApplied, aParent, aException) {
+ return !aException &&
+ window.getComputedStyle(test).fontSize ==
+ (aApplied ? "100px" : "16px")
+ }],
+ ["@font-face { font-family: UnlikelyFontName; src: " +
+ MONOSPACE_FONTS.map(function(s) { return "local('" + s + "')" }).join(", ") + "; }",
+ function(aApplied, aParent, aException) {
+ var width = testFont.getBoundingClientRect().width;
+ if (aException) {
+ return false;
+ }
+ if (navigator.oscpu.match(/Linux/) ||
+ navigator.oscpu.match(/Android/) ||
+ SpecialPowers.Services.appinfo.name == "B2G") {
+ return true;
+ }
+ return Math.abs(width - (aApplied ? monospaceWidth : serifWidth)) <= 1; // bug 769194 prevents local()
+ // fonts working on Android
+ }],
+ ["@import url(nothing.css);",
+ function(aApplied, aParent, aException) {
+ // just test whether it threw
+ return aParent instanceof CSSRule ? aException : !aException;
+ }],
+ ["@namespace test url(http://example.org);",
+ function(aApplied, aParent, aException) {
+ // just test whether it threw
+ return aParent instanceof CSSRule ? aException : !aException;
+ }],
+ ["@counter-style unlikely-counter-style { system: extends cjk-decimal; }",
+ function (aApplied, aParent, aException) {
+ var width = testCounterStyle.getBoundingClientRect().width;
+ if (aException) {
+ return false;
+ }
+ return width == (aApplied ? cjkDecimalWidth : decimalWidth);
+ }],
+];
+
+function runTest()
+{
+ // First, assert that our assumed available fonts are indeed available
+ // and have expected metrics.
+ ok(monospaceWidth > 0, "monospace text has width");
+ ok(serifWidth > 0, "serif text has width");
+ ok(Math.abs(monospaceWidth - serifWidth) > 1, "monospace and serif text have sufficiently different widths");
+
+ // And that the #test-font element starts off using the "serif" font.
+ var initialFontTestWidth = testFont.getBoundingClientRect().width;
+ is(initialFontTestWidth, serifWidth);
+
+ ok(decimalWidth > 0, "decimal counter has width");
+ ok(cjkDecimalWidth > 0, "cjk-decimal counter has width");
+ ok(decimalWidth != cjkDecimalWidth, "decimal and cjk-decimal counter have different width")
+
+ var initialCounterStyleWidth = testCounterStyle.getBoundingClientRect().width;
+ is(initialCounterStyleWidth, decimalWidth, "initial counter style is decimal");
+
+ // We construct a style sheet with zero, one or two levels of conditional
+ // grouping rules (taken from outerRuleInfo), with one of the inner rules
+ // at the deepest level.
+ var style = document.getElementById("style");
+
+ // For each of the outer rule types...
+ for (var outerRule1 = 0; outerRule1 < outerRuleInfo.length; outerRule1++) {
+ // For each of { 0 = don't create an outer rule,
+ // 1 = create an outer rule with a passing condition,
+ // 2 = create an outer rule with a failing condition }...
+ for (var outerRuleCondition1 = 0; outerRuleCondition1 <= 2; outerRuleCondition1++) {
+
+ // For each of the outer rule types again...
+ for (var outerRule2 = 0; outerRule2 < outerRuleInfo.length; outerRule2++) {
+ // For each of { 0 = don't create an outer rule,
+ // 1 = create an outer rule with a passing condition,
+ // 2 = create an outer rule with a failing condition } again...
+ for (var outerRuleCondition2 = 0; outerRuleCondition2 <= 2; outerRuleCondition2++) {
+
+ // For each of the inner rule types...
+ for (var innerRule = 0; innerRule < innerRuleInfo.length; innerRule++) {
+
+ // Clear rules
+ var object = style.sheet;
+ while (object.cssRules.length) {
+ object.deleteRule(0);
+ }
+
+ // We'll record whether the inner rule should have been applied,
+ // according to whether we put passing or failing conditional
+ // grouping rules around it.
+ var applied = true;
+
+ if (outerRuleCondition1) {
+ // Create an outer conditional rule.
+ object.insertRule([outerRuleInfo[outerRule1][0],
+ outerRuleInfo[outerRule1][outerRuleCondition1],
+ "{}"].join(" "), 0);
+ object = object.cssRules[0];
+
+ if (outerRuleCondition1 == 2) {
+ // If we used a failing condition, we don't expect the inner
+ // rule to be applied.
+ applied = false;
+ }
+ }
+
+ if (outerRuleCondition2) {
+ // Create another outer conditional rule as a child of the first
+ // outer conditional rule (or the style sheet, if we didn't create
+ // a first outer conditional rule).
+ object.insertRule([outerRuleInfo[outerRule2][0],
+ outerRuleInfo[outerRule2][outerRuleCondition2],
+ "{}"].join(" "), 0);
+ object = object.cssRules[0];
+
+ if (outerRuleCondition2 == 2) {
+ // If we used a failing condition, we don't expect the inner
+ // rule to be applied.
+ applied = false;
+ }
+ }
+
+ var outer = object instanceof CSSRule ? object.cssText : "style sheet";
+ var inner = innerRuleInfo[innerRule][0];
+
+ // Insert the inner rule.
+ var exception = null;
+ try {
+ object.insertRule(inner, 0);
+ } catch (e) {
+ exception = e;
+ }
+
+ ok(innerRuleInfo[innerRule][1](applied, object, exception),
+ "<" + [outerRule1, outerRuleCondition1, outerRule2,
+ outerRuleCondition2, innerRule].join(",") + "> " +
+ "inserting " + inner + " into " + outer.replace(/ *\n */g, ' '));
+ }
+ }
+ }
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+runTest();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_rules_out_of_sheets.html b/layout/style/test/test_rules_out_of_sheets.html
new file mode 100644
index 0000000000..2ca53d31b7
--- /dev/null
+++ b/layout/style/test/test_rules_out_of_sheets.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=634373
+-->
+<head>
+ <title>Test for Bug 634373</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=634373">Mozilla Bug 634373</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 634373 **/
+
+function make_rule_and_remove_sheet(text, getter) {
+ var style = document.createElement("style");
+ style.setAttribute("type", "text/css");
+ style.appendChild(document.createTextNode(text));
+ document.head.appendChild(style);
+ var result = style.sheet.cssRules[0];
+ if (getter) {
+ result = getter(result);
+ }
+ document.head.removeChild(style);
+ style = null;
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+ return result;
+}
+
+var gDisplayCS = getComputedStyle(document.getElementById("display"), "");
+
+function keep_rule_alive_by_matching(rule) {
+ // It's the caller's job to guarantee that the rule matches a p.
+ // This just causes a style flush, which in turn keeps the rule alive
+ // until the next style flush.
+ var color = gDisplayCS.color;
+ return rule;
+}
+
+function get_rule_and_child(rule) {
+ return [rule, rule.cssRules[0]];
+}
+
+function get_only_child(rule) {
+ return rule.cssRules[0];
+}
+
+var rule;
+
+// In this case, the rule goes away immediately, so we're testing
+// the DOM wrapper's handling of a null rule, rather than the rule's
+// handling of a null sheet.
+rule = make_rule_and_remove_sheet("p { color: blue }");
+rule.style.color = "";
+try {
+ rule.style.color = "fuchsia";
+} catch(ex) {}
+
+rule = make_rule_and_remove_sheet("p { color: blue }",
+ keep_rule_alive_by_matching);
+try {
+ rule.style.color = "";
+} catch(ex) {}
+try {
+ rule.style.color = "fuchsia";
+} catch(ex) {}
+
+rule = make_rule_and_remove_sheet("@media screen { p { color: blue } }",
+ get_rule_and_child);
+rule[1].style.color = "";
+try {
+ rule[1].style.color = "fuchsia";
+} catch(ex) {}
+
+// In this case, the rule goes away immediately, so we're testing
+// the DOM wrapper's handling of a null rule, rather than the rule's
+// handling of a null sheet.
+rule = make_rule_and_remove_sheet("@media screen { p { color: blue } }",
+ get_only_child);
+rule.style.color = "";
+try {
+ rule.style.color = "fuchsia";
+} catch(ex) {}
+
+rule = make_rule_and_remove_sheet("@media screen { p { color: blue } }",
+ function(ruleInner) {
+ return keep_rule_alive_by_matching(
+ get_only_child(ruleInner));
+ });
+try {
+ rule.style.color = "";
+} catch(ex) {}
+try {
+ rule.style.color = "fuchsia";
+} catch(ex) {}
+
+rule = make_rule_and_remove_sheet("@keyframes a { from { color: blue } }");
+rule.appendRule("from { color: fuchsia}");
+rule.deleteRule("from");
+rule.name = "b";
+rule.cssRules[0].keyText = "50%";
+
+ok(true, "didn't crash");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_selectors.html b/layout/style/test/test_selectors.html
new file mode 100644
index 0000000000..d688139d3c
--- /dev/null
+++ b/layout/style/test/test_selectors.html
@@ -0,0 +1,1348 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS Selectors</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<p id="display"><iframe id="iframe" src="about:blank"></iframe><iframe id="cloneiframe" src="about:blank"></iframe></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+var cloneiframe;
+
+function run() {
+ SpecialPowers.pushPrefEnv({ set: [["layout.css.xul-tree-pseudos.content.enabled", true]] }, runTests);
+}
+function runTests() {
+ var iframe = document.getElementById("iframe");
+ var ifwin = iframe.contentWindow;
+ var ifdoc = iframe.contentDocument;
+
+ cloneiframe = document.getElementById("cloneiframe");
+
+ var style_elem = ifdoc.createElement("style");
+ style_elem.setAttribute("type", "text/css");
+ ifdoc.getElementsByTagName("head")[0].appendChild(style_elem);
+ var style_text = ifdoc.createTextNode("");
+ style_elem.appendChild(style_text);
+
+ var gCounter = 0;
+
+ /*
+ * selector: the selector to test
+ * body_contents: what to set the body's innerHTML to
+ * match_fn: a function that, given the document object into which
+ * body_contents has been inserted, produces an array of nodes that
+ * should match selector
+ * notmatch_fn: likewise, but for nodes that should not match
+ * namespaces (optional): @namespace rules to be included in the sheet
+ */
+ function test_selector_in_html(selector, body_contents, match_fn, notmatch_fn, namespaces)
+ {
+ var zi = ++gCounter;
+ if (typeof(body_contents) == "string") {
+ ifdoc.body.innerHTML = body_contents;
+ } else {
+ // It's a function.
+ ifdoc.body.innerHTML = "";
+ body_contents(ifdoc.body);
+ }
+ if (!namespaces) {
+ namespaces = "";
+ }
+ style_text.data = namespaces + selector + "{ z-index: " + zi + " }";
+
+ var idx = style_text.parentNode.sheet.cssRules.length - 1;
+ if (namespaces == "") {
+ is(idx, 0, "unexpected rule index for " + selector);
+ }
+ if (idx < 0 ||
+ style_text.parentNode.sheet.cssRules[idx].type !=
+ CSSRule.STYLE_RULE)
+ {
+ ok(false, "selector " + selector + " could not be parsed");
+ return;
+ }
+
+ var should_match = match_fn(ifdoc);
+ var should_not_match = notmatch_fn(ifdoc);
+ if (should_match.length + should_not_match.length == 0) {
+ ok(false, "nothing to check");
+ }
+
+ for (let i = 0; i < should_match.length; ++i) {
+ let e = should_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched " + selector);
+ }
+ for (let i = 0; i < should_not_match.length; ++i) {
+ let e = should_not_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match " + selector);
+ }
+
+ // Now, since we're here, may as well make sure serialization
+ // works correctly. It need not produce the exact same text,
+ // but it should produce a selector that matches the same
+ // elements.
+ zi = ++gCounter;
+ var ser1 = style_text.parentNode.sheet.cssRules[idx].selectorText;
+ style_text.data = namespaces + ser1 + "{ z-index: " + zi + " }";
+ for (let i = 0; i < should_match.length; ++i) {
+ let e = should_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched " + ser1 +
+ " which is the reserialization of " + selector);
+ }
+ for (let i = 0; i < should_not_match.length; ++i) {
+ let e = should_not_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match " + ser1 +
+ " which is the reserialization of " + selector);
+ }
+
+ // But when we serialize the serialized result, we should get
+ // the same text.
+ isnot(style_text.parentNode.sheet.cssRules[idx], undefined,
+ "parse of selector \"" + ser1 + "\" failed");
+
+ if (style_text.parentNode.sheet.cssRules[idx] !== undefined) {
+ var ser2 = style_text.parentNode.sheet.cssRules[idx].selectorText;
+ is(ser2, ser1, "parse+serialize of selector \"" + selector +
+ "\" is idempotent");
+ }
+
+ ifdoc.body.innerHTML = "";
+ style_text.data = "";
+
+ // And now test that when we clone the style sheet, we end up
+ // with the same selector (serializes to same string, and
+ // matches the same things).
+ zi = ++gCounter;
+ var style_sheet = "data:text/css," +
+ escape(namespaces + selector + "{ z-index: " + zi + " }");
+ var style_sheet_link =
+ "<link rel='stylesheet' href='" + style_sheet + "'>";
+ var html_doc = "<!DOCTYPE HTML>" +
+ style_sheet_link + style_sheet_link +
+ "<body>";
+ if (typeof(body_contents) == "string") {
+ html_doc += body_contents;
+ }
+ var docurl = "data:text/html," + escape(html_doc);
+ defer_clonedoc_tests(docurl, function() {
+ var wrappedCloneFrame = SpecialPowers.wrap(cloneiframe);
+ var clonedoc = wrappedCloneFrame.contentDocument;
+ var clonewin = wrappedCloneFrame.contentWindow;
+
+ if (typeof(body_contents) != "string") {
+ body_contents(clonedoc.body);
+ }
+
+ var links = clonedoc.getElementsByTagName("link");
+ // cause a clone
+ links[1].sheet.insertRule("#nonexistent { color: purple}", idx + 1);
+ // remove the uncloned sheet
+ links[0].remove();
+
+ var should_match1 = match_fn(clonedoc);
+ var should_not_match1 = notmatch_fn(clonedoc);
+
+ if (should_match1.length + should_not_match1.length == 0) {
+ ok(false, "nothing to check");
+ }
+
+ for (let i = 0; i < should_match1.length; ++i) {
+ let e = should_match1[i];
+ is(clonewin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched clone of " +
+ selector);
+ }
+ for (let i = 0; i < should_not_match1.length; ++i) {
+ let e = should_not_match1[i];
+ is(clonewin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match clone of " +
+ selector);
+ }
+
+ var ser3 = links[0].sheet.cssRules[idx].selectorText;
+ is(ser3, ser1,
+ "selector " + selector + " serializes correctly after cloning");
+ });
+ }
+
+ function should_serialize_to(selector, serialization)
+ {
+ style_text.data = selector + "{ z-index: 0 }";
+ is(style_text.parentNode.sheet.cssRules[0].selectorText,
+ serialization,
+ "selector '" + selector + "' should serialize to '" +
+ serialization + "'.");
+ }
+
+ function test_parseable(selector)
+ {
+ ifdoc.body.innerHTML = "<p></p>";
+
+ var zi = ++gCounter;
+ style_text.data = "p, " + selector + "{ z-index: " + zi + " }";
+ var should_match = ifdoc.getElementsByTagName("p")[0];
+ var parsed = ifwin.getComputedStyle(should_match).zIndex == zi;
+ ok(parsed, "selector " + selector + " was parsed");
+ if (!parsed) {
+ return;
+ }
+
+ // Test that it serializes to something that is also parseable.
+ var ser1 = style_elem.sheet.cssRules[0].selectorText;
+ zi = ++gCounter;
+ style_text.data = ser1 + "{ z-index: " + zi + " }";
+ is(ifwin.getComputedStyle(should_match).zIndex, String(zi),
+ "serialization " + ser1 + " of selector p, " + selector +
+ " was parsed");
+ var ser2 = style_elem.sheet.cssRules[0].selectorText;
+ is(ser2, ser1,
+ "parse+serialize of selector " + selector + " is idempotent");
+
+ ifdoc.body.innerHTML = "";
+ style_text.data = "";
+
+ // Test that it clones to the same thing it serializes to.
+ zi = ++gCounter;
+ var style_sheet = "data:text/css," +
+ escape("p, " + selector + "{ z-index: " + zi + " }");
+ var style_sheet_link =
+ "<link rel='stylesheet' href='" + style_sheet + "'>";
+ var html_doc = "<!DOCTYPE HTML>" +
+ style_sheet_link + style_sheet_link +
+ "<p></p>";
+ var docurl = "data:text/html," + escape(html_doc);
+
+ defer_clonedoc_tests(docurl, function() {
+ var wrappedCloneFrame = SpecialPowers.wrap(cloneiframe);
+ var clonedoc = wrappedCloneFrame.contentDocument;
+ var clonewin = wrappedCloneFrame.contentWindow;
+ var links = clonedoc.getElementsByTagName("link");
+ // cause a clone
+ links[1].sheet.insertRule("#nonexistent { color: purple}", 0);
+ // remove the uncloned sheet
+ links[0].remove();
+
+ should_match = clonedoc.getElementsByTagName("p")[0];
+ is(clonewin.getComputedStyle(should_match).zIndex, String(zi),
+ "selector " + selector + " was cloned correctly");
+ var ser3 = links[0].sheet.cssRules[1].selectorText;
+ is(ser3, ser1,
+ "selector " + selector + " serializes correctly after cloning");
+ });
+ }
+
+ function test_unparseable_via_api(selector)
+ {
+ try {
+ // Test that it is also unparseable when followed by EOF.
+ ifdoc.body.matches(selector);
+ ok(false, "selector '" + selector + "' plus EOF is parse error");
+ } catch(ex) {
+ is(ex.name, "SyntaxError",
+ "selector '" + selector + "' plus EOF is parse error");
+ is(ex.code, DOMException.SYNTAX_ERR,
+ "selector '" + selector + "' plus EOF is parse error");
+ }
+ }
+
+ function test_parseable_via_api(selector)
+ {
+ var threw = false;
+ try {
+ // Test that a selector is parseable when followed by EOF.
+ ifdoc.body.matches(selector);
+ } catch(ex) {
+ threw = true;
+ }
+ ok(!threw, "selector '" + selector + "' was parsed");
+ }
+
+ function test_balanced_unparseable(selector)
+ {
+ var zi1 = ++gCounter;
+ var zi2 = ++gCounter;
+ ifdoc.body.innerHTML = "<p></p><div></div>";
+ style_text.data = "p, " + selector + "{ z-index: " + zi1 + " }" +
+ "div { z-index: " + zi2 + " }";
+ var should_not_match = ifdoc.getElementsByTagName("p")[0];
+ var should_match = ifdoc.getElementsByTagName("div")[0];
+ is(ifwin.getComputedStyle(should_not_match).zIndex, "auto",
+ "selector " + selector + " was a parser error");
+ is(ifwin.getComputedStyle(should_match).zIndex, String(zi2),
+ "selector " + selector + " error was recovered from");
+ ifdoc.body.innerHTML = "";
+ style_text.data = "";
+ test_unparseable_via_api(selector);
+ }
+
+ function test_unbalanced_unparseable(selector)
+ {
+ var zi1 = ++gCounter;
+ var zi2 = ++gCounter;
+ ifdoc.body.innerHTML = "<p></p>";
+ style_text.data = "p, " + selector + "{ z-index: " + zi1 + " }";
+ var should_not_match = ifdoc.getElementsByTagName("p")[0];
+ is(ifwin.getComputedStyle(should_not_match).zIndex, "auto",
+ "selector " + selector + " was a parser error");
+ is(style_text.parentNode.sheet.cssRules.length, 0,
+ "sheet should have no rules since " + selector + " is parse error");
+ ifdoc.body.innerHTML = "";
+ style_text.data = "";
+ test_unparseable_via_api(selector);
+ }
+
+ // [attr] selector
+ test_parseable("[attr]")
+ test_parseable_via_api("[attr");
+ test_parseable("[ATTR]")
+ should_serialize_to("[attr]", "[attr]");
+ should_serialize_to("[ATTR]", "[ATTR]");
+
+ // Whether we should drop the bar is debatable. This matches Edge
+ // and Safari at the time of writing.
+ should_serialize_to("[|attr]", "[attr]");
+ should_serialize_to("[|ATTR]", "[ATTR]");
+
+ // [attr= ] selector
+ test_parseable("[attr=\"x\"]");
+ test_parseable("[attr='x']");
+ test_parseable("[attr=x]");
+ test_parseable("[attr=\"\"]");
+ test_parseable("[attr='']");
+ test_parseable("[attr=\"foo bar\"]");
+ test_parseable_via_api("[attr=x");
+
+ test_balanced_unparseable("[attr=]");
+ test_balanced_unparseable("[attr=foo bar]");
+
+ test_selector_in_html(
+ '[title=""]',
+ '<p title=""></p>'
+ + '<div lang=" "></div><div lang="\t"></div><div lang="\n"></div>',
+ function(doc) { return doc.getElementsByTagName("p"); },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+
+ // [attr~= ] selector
+ test_parseable("[attr~=\"x\"]");
+ test_parseable("[attr~='x']");
+ test_parseable("[attr~=x]");
+ test_parseable("[attr~=\"\"]");
+ test_parseable("[attr~='']");
+ test_parseable("[attr~=\"foo bar\"]");
+ test_parseable_via_api("[attr~=x");
+
+ test_balanced_unparseable("[attr~=]");
+ test_balanced_unparseable("[attr~=foo bar]");
+
+ test_selector_in_html(
+ '[class~="x x"]',
+ '<div class="x x"></div><div class="x"></div><div class="x\tx"></div>div class="x\nx"></div>',
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+
+ // [attr|="x"]
+ test_parseable('[attr|="x"]');
+ test_parseable("[attr|='x']");
+ test_parseable('[attr|=x]');
+ test_parseable_via_api("[attr|=x");
+
+ test_parseable('[attr|=""]');
+ test_parseable("[attr|='']");
+ test_balanced_unparseable('[attr|=]');
+
+ test_selector_in_html(
+ '[lang|=""]',
+ '<p lang=""></p><p lang="-"></p><p lang="-GB"></p>'
+ + '<div lang="en-GB"></div><div lang="en-"></div>',
+ function(doc) { return doc.getElementsByTagName("p"); },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+
+ // [attr$= ] selector
+ test_parseable("[attr$=\"x\"]");
+ test_parseable("[attr$='x']");
+ test_parseable("[attr$=x]");
+ test_parseable("[attr$=\"\"]");
+ test_parseable("[attr$='']");
+ test_parseable("[attr$=\"foo bar\"]");
+ test_parseable_via_api("[attr$=x");
+
+ test_balanced_unparseable("[attr$=]");
+ test_balanced_unparseable("[attr$=foo bar]");
+
+ // [attr^= ] selector
+ test_parseable("[attr^=\"x\"]");
+ test_parseable("[attr^='x']");
+ test_parseable("[attr^=x]");
+ test_parseable("[attr^=\"\"]");
+ test_parseable("[attr^='']");
+ test_parseable("[attr^=\"foo bar\"]");
+ test_parseable_via_api("[attr^=x");
+
+ test_balanced_unparseable("[attr^=]");
+ test_balanced_unparseable("[attr^=foo bar]");
+
+ // attr[*= ] selector
+ test_parseable("[attr*=\"x\"]");
+ test_parseable("[attr*='x']");
+ test_parseable("[attr*=x]");
+ test_parseable("[attr*=\"\"]");
+ test_parseable("[attr*='']");
+ test_parseable("[attr*=\"foo bar\"]");
+ test_parseable_via_api("[attr^=x");
+
+ test_balanced_unparseable("[attr*=]");
+ test_balanced_unparseable("[attr*=foo bar]");
+
+ // And now tests for correctness of matching of attr selectors.
+ var attrTestBody =
+ // Paragraphs 1-5
+ "<p attr></p> <p attr=''></p> <p attr='foo'></p> <p att></p> <p></p>" +
+ // Paragraphs 6-8
+ "<p attr='foo bar'></p> <p attr='foo-bar'></p> <p attr='foobar'></p>" +
+ // Paragraphs 9-10
+ "<p attr='foo bar baz'></p> <p attr='foo-bar-baz'></p>" +
+ // Paragraphs 11-12
+ "<p attr='foo-bar baz'></p> <p attr=' foo-bar '></p> " +
+ // Paragraph 13-15
+ "<p attr=' foo '></p> <p attr='fo'></p> <p attr='bar baz-foo'></p>";
+ test_selector_in_html(
+ "[attr]", attrTestBody,
+ pset([1,2,3,6,7,8,9,10,11,12,13,14,15]), pset([4,5]));
+ test_selector_in_html(
+ "[attr=foo]", attrTestBody,
+ pset([3]), pset([1,2,4,5,6,7,8,9,10,11,12,13,14,15]));
+ test_selector_in_html(
+ "[attr~=foo]", attrTestBody,
+ pset([3,6,9,13]), pset([1,2,4,5,7,8,10,11,12,14,15]));
+ test_selector_in_html(
+ "[attr~=bar]", attrTestBody,
+ pset([6,9,15]), pset([1,2,3,4,5,7,8,10,11,12,13,14]));
+ test_selector_in_html(
+ "[attr~=baz]", attrTestBody,
+ pset([9,11]), pset([1,2,3,4,5,6,7,8,10,12,13,14,15]));
+ test_selector_in_html(
+ "[attr|=foo]", attrTestBody,
+ pset([3,7,10,11]), pset([1,2,4,5,6,8,9,12,13,14,15]));
+ test_selector_in_html(
+ "[attr|='bar baz']", attrTestBody,
+ pset([15]), pset([1,2,3,4,5,6,7,8,9,10,11,12,13,14]));
+ test_selector_in_html(
+ "[attr$=foo]", attrTestBody,
+ pset([3,15]), pset([1,2,4,5,6,7,8,9,10,11,12,13,14]));
+ test_selector_in_html(
+ "[attr$=bar]", attrTestBody,
+ pset([6,7,8]), pset([1,2,3,4,5,9,10,11,12,13,14,15]));
+ test_selector_in_html(
+ "[attr^=foo]", attrTestBody,
+ pset([3,6,7,8,9,10,11]), pset([1,2,4,5,12,13,14,15]));
+ test_selector_in_html(
+ "[attr*=foo]", attrTestBody,
+ pset([3,6,7,8,9,10,11,12,13,15]), pset([1,2,4,5,14]));
+
+ // Bug 420814
+ test_selector_in_html(
+ "div ~ div p",
+ "<div></div><div><div><p>match</p></div></div>",
+ function(doc) { return doc.getElementsByTagName("p"); },
+ function(doc) { return []; }
+ );
+
+ // Bug 420245
+ test_selector_in_html(
+ "p[attr$=\"\"]",
+ "<p attr=\"foo\">This should not match</p>",
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("p"); }
+ );
+ test_selector_in_html(
+ "div + p[attr~=\"\"]",
+ "<div>Dummy</div><p attr=\"foo\">This should not match</p>",
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("p"); }
+ );
+ test_selector_in_html(
+ "div[attr^=\"\"]",
+ "<div attr=\"dummy1\">Dummy</div><div attr=\"dummy2\">Dummy</div>",
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+ test_selector_in_html(
+ "div[attr*=\"\"]",
+ "<div attr=\"dummy1\">Dummy</div><div attr=\"dummy2\">Dummy</div>",
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+
+ // :nth-child(), etc.
+ // Follow the whitespace rules as proposed in
+ // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
+ test_balanced_unparseable(":nth-child()");
+ test_balanced_unparseable(":nth-of-type( )");
+ test_parseable(":nth-last-child( odd)");
+ test_parseable(":nth-last-of-type(even )");
+ test_parseable(":nth-child(n )");
+ test_parseable(":nth-of-type( 2n)");
+ test_parseable(":nth-last-child( -n)");
+ test_parseable(":nth-last-of-type(-2n )");
+ test_balanced_unparseable(":nth-child(- n)");
+ test_balanced_unparseable(":nth-of-type(-2 n)");
+ test_balanced_unparseable(":nth-last-of-type(2n1)");
+ test_balanced_unparseable(":nth-child(2n++1)");
+ test_balanced_unparseable(":nth-of-type(2n-+1)");
+ test_balanced_unparseable(":nth-last-child(2n+-1)");
+ test_balanced_unparseable(":nth-last-of-type(2n--1)");
+ test_parseable(":nth-child( 3n + 1 )");
+ test_parseable(":nth-child( +3n - 2 )");
+ test_parseable(":nth-child( -n+ 6)");
+ test_parseable(":nth-child( +6 )");
+ test_balanced_unparseable(":nth-child(3 n)");
+ test_balanced_unparseable(":nth-child(+ 2n)");
+ test_balanced_unparseable(":nth-child(+ 2)");
+ test_parseable(":nth-child(3)");
+ test_parseable(":nth-of-type(-3)");
+ test_parseable(":nth-last-child(+3)");
+ test_parseable(":nth-last-of-type(0)");
+ test_parseable(":nth-child(-0)");
+ test_parseable(":nth-of-type(3n)");
+ test_parseable(":nth-last-child(-3n)");
+ test_parseable(":nth-last-of-type(+3n)");
+ test_parseable(":nth-last-of-type(0n)");
+ test_parseable(":nth-child(-0n)");
+ test_parseable(":nth-of-type(n)");
+ test_parseable(":nth-last-child(-n)");
+ test_parseable(":nth-last-of-type(2n+1)");
+ test_parseable(":nth-child(2n-1)");
+ test_parseable(":nth-of-type(2n+0)");
+ test_parseable(":nth-last-child(2n-0)");
+ test_parseable(":nth-child(-0n+0)");
+ test_parseable(":nth-of-type(n+1)");
+ test_parseable(":nth-last-child(n-1)");
+ test_parseable(":nth-last-of-type(-n+1)");
+ test_parseable(":nth-child(-n-1)");
+ test_balanced_unparseable(":nth-child(2-n)");
+ test_balanced_unparseable(":nth-child(2-n-1)");
+ test_balanced_unparseable(":nth-child(n-2-1)");
+ // Bug 750388
+ test_parseable(":nth-child(+n)");
+ test_balanced_unparseable(":nth-child(+ n)");
+ test_parseable(":nth-child(+n+2)");
+ test_parseable(":nth-child(+n-2)");
+ test_parseable(":nth-child(+n + 2)");
+ test_parseable(":nth-child(+n - 2)");
+ test_balanced_unparseable(":nth-child(+ n+2)");
+ test_balanced_unparseable(":nth-child(+ n-2)");
+ test_balanced_unparseable(":nth-child(+ n + 2)");
+ test_balanced_unparseable(":nth-child(+ n - 2)");
+ test_parseable(":nth-child(+n-100)");
+ test_parseable(":nth-child(+n - 100)");
+ test_balanced_unparseable(":nth-child(+ n-100)");
+ test_balanced_unparseable(":nth-child(+-n+2)");
+ test_balanced_unparseable(":nth-child(+ -n+2)");
+ test_balanced_unparseable(":nth-child(+-n-100)");
+ test_balanced_unparseable(":nth-child(+ -n-100)");
+ test_balanced_unparseable(":nth-child(++n-100)");
+ test_balanced_unparseable(":nth-child(-+n-100)");
+ test_balanced_unparseable(":nth-child(++2n - 100)");
+ test_balanced_unparseable(":nth-child(+-2n - 100)");
+ test_balanced_unparseable(":nth-child(-+2n - 100)");
+ test_balanced_unparseable(":nth-child(--2n - 100)");
+ test_balanced_unparseable(":nth-child(+/**/+2n - 100)");
+ test_balanced_unparseable(":nth-child(+/**/-2n - 100)");
+ test_balanced_unparseable(":nth-child(-/**/+2n - 100)");
+ test_balanced_unparseable(":nth-child(-/**/-2n - 100)");
+ test_balanced_unparseable(":nth-child(+/**/+/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(+/**/-/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(-/**/+/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(-/**/-/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(++/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(+-/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(-+/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(--/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(-even)");
+ test_balanced_unparseable(":nth-child(-odd)");
+ test_balanced_unparseable(":nth-child(+even)");
+ test_balanced_unparseable(":nth-child(+odd)");
+ test_balanced_unparseable(":nth-child(+ even)");
+ test_balanced_unparseable(":nth-child(+ odd)");
+ test_balanced_unparseable(":nth-child(+-n)");
+ test_balanced_unparseable(":nth-child(+-n-)");
+ test_balanced_unparseable(":nth-child(-+n)");
+ test_balanced_unparseable(":nth-child(+n--)");
+ test_parseable(":nth-child(n+2)");
+ test_parseable(":nth-child(n/**/+/**/2)");
+ test_parseable(":nth-child(n-2)");
+ test_parseable(":nth-child(n/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(n++2)");
+ test_balanced_unparseable(":nth-child(n+-2)");
+ test_balanced_unparseable(":nth-child(n-+2)");
+ test_balanced_unparseable(":nth-child(n--2)");
+ test_balanced_unparseable(":nth-child(n/**/++2)");
+ test_balanced_unparseable(":nth-child(n/**/+-2)");
+ test_balanced_unparseable(":nth-child(n/**/-+2)");
+ test_balanced_unparseable(":nth-child(n/**/--2)");
+ test_balanced_unparseable(":nth-child(n/**/+/**/+2)");
+ test_balanced_unparseable(":nth-child(n/**/+/**/-2)");
+ test_balanced_unparseable(":nth-child(n/**/-/**/+2)");
+ test_balanced_unparseable(":nth-child(n/**/-/**/-2)");
+ test_balanced_unparseable(":nth-child(n+/**/+2)");
+ test_balanced_unparseable(":nth-child(n+/**/-2)");
+ test_balanced_unparseable(":nth-child(n-/**/+2)");
+ test_balanced_unparseable(":nth-child(n-/**/-2)");
+ test_balanced_unparseable(":nth-child(n++/**/2)");
+ test_balanced_unparseable(":nth-child(n+-/**/2)");
+ test_balanced_unparseable(":nth-child(n-+/**/2)");
+ test_balanced_unparseable(":nth-child(n--/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/++/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/+-/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/-+/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/--/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/+/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/+/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/-/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/-/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(n+/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(n+/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(n-/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(n-/**/-/**/2)");
+ test_parseable(":nth-child(2n+2)");
+ test_parseable(":nth-child(2n/**/+/**/2)");
+ test_parseable(":nth-child(2n-2)");
+ test_parseable(":nth-child(2n/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(2n++2)");
+ test_balanced_unparseable(":nth-child(2n+-2)");
+ test_balanced_unparseable(":nth-child(2n-+2)");
+ test_balanced_unparseable(":nth-child(2n--2)");
+ test_balanced_unparseable(":nth-child(2n/**/++2)");
+ test_balanced_unparseable(":nth-child(2n/**/+-2)");
+ test_balanced_unparseable(":nth-child(2n/**/-+2)");
+ test_balanced_unparseable(":nth-child(2n/**/--2)");
+ test_balanced_unparseable(":nth-child(2n/**/+/**/+2)");
+ test_balanced_unparseable(":nth-child(2n/**/+/**/-2)");
+ test_balanced_unparseable(":nth-child(2n/**/-/**/+2)");
+ test_balanced_unparseable(":nth-child(2n/**/-/**/-2)");
+ test_balanced_unparseable(":nth-child(2n+/**/+2)");
+ test_balanced_unparseable(":nth-child(2n+/**/-2)");
+ test_balanced_unparseable(":nth-child(2n-/**/+2)");
+ test_balanced_unparseable(":nth-child(2n-/**/-2)");
+ test_balanced_unparseable(":nth-child(2n++/**/2)");
+ test_balanced_unparseable(":nth-child(2n+-/**/2)");
+ test_balanced_unparseable(":nth-child(2n-+/**/2)");
+ test_balanced_unparseable(":nth-child(2n--/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/++/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/+-/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/-+/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/--/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/+/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/+/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/-/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/-/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(2n+/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(2n+/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(2n-/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(2n-/**/-/**/2)");
+ test_parseable(":nth-child(+/**/n+2)");
+ test_parseable(":nth-child(+n/**/+2)");
+ test_parseable(":nth-child(+n/**/+2)");
+ test_parseable(":nth-child(+n+/**/2)");
+ test_parseable(":nth-child(+n+2/**/)");
+ test_balanced_unparseable(":nth-child(+1/**/n+2)");
+ test_parseable(":nth-child(+1n/**/+2)");
+ test_parseable(":nth-child(+1n/**/+2)");
+ test_parseable(":nth-child(+1n+/**/2)");
+ test_parseable(":nth-child(+1n+2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/n+2)");
+ test_parseable(":nth-child(-n/**/+2)");
+ test_parseable(":nth-child(-n/**/+2)");
+ test_parseable(":nth-child(-n+/**/2)");
+ test_parseable(":nth-child(-n+2/**/)");
+ test_balanced_unparseable(":nth-child(-1/**/n+2)");
+ test_parseable(":nth-child(-1n/**/+2)");
+ test_parseable(":nth-child(-1n/**/+2)");
+ test_parseable(":nth-child(-1n+/**/2)");
+ test_parseable(":nth-child(-1n+2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/ n+2)");
+ test_balanced_unparseable(":nth-child(- /**/n+2)");
+ test_balanced_unparseable(":nth-child(+/**/ n+2)");
+ test_balanced_unparseable(":nth-child(+ /**/n+2)");
+ test_parseable(":nth-child(+/**/n-2)");
+ test_parseable(":nth-child(+n/**/-2)");
+ test_parseable(":nth-child(+n/**/-2)");
+ test_parseable(":nth-child(+n-/**/2)");
+ test_parseable(":nth-child(+n-2/**/)");
+ test_balanced_unparseable(":nth-child(+1/**/n-2)");
+ test_parseable(":nth-child(+1n/**/-2)");
+ test_parseable(":nth-child(+1n/**/-2)");
+ test_parseable(":nth-child(+1n-/**/2)");
+ test_parseable(":nth-child(+1n-2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/n-2)");
+ test_parseable(":nth-child(-n/**/-2)");
+ test_parseable(":nth-child(-n/**/-2)");
+ test_parseable(":nth-child(-n-/**/2)");
+ test_parseable(":nth-child(-n-2/**/)");
+ test_balanced_unparseable(":nth-child(-1/**/n-2)");
+ test_parseable(":nth-child(-1n/**/-2)");
+ test_parseable(":nth-child(-1n/**/-2)");
+ test_parseable(":nth-child(-1n-/**/2)");
+ test_parseable(":nth-child(-1n-2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/ n-2)");
+ test_balanced_unparseable(":nth-child(- /**/n-2)");
+ test_balanced_unparseable(":nth-child(+/**/ n-2)");
+ test_balanced_unparseable(":nth-child(+ /**/n-2)");
+ test_parseable(":nth-child(+/**/N-2)");
+ test_parseable(":nth-child(+N/**/-2)");
+ test_parseable(":nth-child(+N/**/-2)");
+ test_parseable(":nth-child(+N-/**/2)");
+ test_parseable(":nth-child(+N-2/**/)");
+ test_balanced_unparseable(":nth-child(+1/**/N-2)");
+ test_parseable(":nth-child(+1N/**/-2)");
+ test_parseable(":nth-child(+1N/**/-2)");
+ test_parseable(":nth-child(+1N-/**/2)");
+ test_parseable(":nth-child(+1N-2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/N-2)");
+ test_parseable(":nth-child(-N/**/-2)");
+ test_parseable(":nth-child(-N/**/-2)");
+ test_parseable(":nth-child(-N-/**/2)");
+ test_parseable(":nth-child(-N-2/**/)");
+ test_balanced_unparseable(":nth-child(-1/**/N-2)");
+ test_parseable(":nth-child(-1N/**/-2)");
+ test_parseable(":nth-child(-1N/**/-2)");
+ test_parseable(":nth-child(-1N-/**/2)");
+ test_parseable(":nth-child(-1N-2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/ N-2)");
+ test_balanced_unparseable(":nth-child(- /**/N-2)");
+ test_balanced_unparseable(":nth-child(+/**/ N-2)");
+ test_balanced_unparseable(":nth-child(+ /**/N-2)");
+ test_parseable(":nth-child( +n + 1 )");
+ test_parseable(":nth-child( +/**/n + 1 )");
+ test_balanced_unparseable(":nth-child( -/**/2/**/n/**/+/**/4 )");
+ test_parseable(":nth-child( -2n/**/ + /**/4 )");
+ test_parseable(":nth-child( -2n/**/+/**/4 )");
+ test_parseable(":nth-child( -2n /**/+/**/4 )");
+ test_balanced_unparseable(":nth-child( -/**/n /**/+ /**/ 4 )");
+ test_parseable(":nth-child( +/**/n /**/+ /**/ 4 )");
+ test_balanced_unparseable(":nth-child(+1/**/n-1)");
+ test_balanced_unparseable(":nth-child(1/**/n-1)");
+ // bug 876570
+ test_balanced_unparseable(":nth-child(+2n-)");
+ test_balanced_unparseable(":nth-child(+n-)");
+ test_balanced_unparseable(":nth-child(-2n-)");
+ test_balanced_unparseable(":nth-child(-n-)");
+ test_balanced_unparseable(":nth-child(2n-)");
+ test_balanced_unparseable(":nth-child(n-)");
+ test_balanced_unparseable(":nth-child(+2n+)");
+ test_balanced_unparseable(":nth-child(+n+)");
+ test_balanced_unparseable(":nth-child(-2n+)");
+ test_balanced_unparseable(":nth-child(-n+)");
+ test_balanced_unparseable(":nth-child(2n+)");
+ test_balanced_unparseable(":nth-child(n+)");
+
+ // exercise the an+b matching logic particularly hard for
+ // :nth-child() (since we know we use the same code for all 4)
+ var seven_ps = "<p></p><p></p><p></p><p></p><p></p><p></p><p></p>";
+ function pset(indices) { // takes an array of 1-based indices
+ return function pset_filter(doc) {
+ var a = doc.getElementsByTagName("p");
+ var result = [];
+ for (var i in indices)
+ result.push(a[indices[i] - 1]);
+ return result;
+ }
+ }
+ test_selector_in_html(":nth-child(0)", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(-3)", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(3)", seven_ps,
+ pset([3]), pset([1, 2, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(0n+3)", seven_ps,
+ pset([3]), pset([1, 2, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(-0n+3)", seven_ps,
+ pset([3]), pset([1, 2, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(8)", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(odd)", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child(even)", seven_ps,
+ pset([2, 4, 6]), pset([1, 3, 5, 7]));
+ test_selector_in_html(":nth-child(2n-1)", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child( 2n - 1 )", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child(2n+1)", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child( 2n + 1 )", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child(2n+0)", seven_ps,
+ pset([2, 4, 6]), pset([1, 3, 5, 7]));
+ test_selector_in_html(":nth-child(2n-0)", seven_ps,
+ pset([2, 4, 6]), pset([1, 3, 5, 7]));
+ test_selector_in_html(":nth-child(-n+3)", seven_ps,
+ pset([1, 2, 3]), pset([4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(-n-3)", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(n)", seven_ps,
+ pset([1, 2, 3, 4, 5, 6, 7]), pset([]));
+ test_selector_in_html(":nth-child(n-3)", seven_ps,
+ pset([1, 2, 3, 4, 5, 6, 7]), pset([]));
+ test_selector_in_html(":nth-child(n+3)", seven_ps,
+ pset([3, 4, 5, 6, 7]), pset([1, 2]));
+ test_selector_in_html(":nth-child(2n+3)", seven_ps,
+ pset([3, 5, 7]), pset([1, 2, 4, 6]));
+ test_selector_in_html(":nth-child(2n)", seven_ps,
+ pset([2, 4, 6]), pset([1, 3, 5, 7]));
+ test_selector_in_html(":nth-child(2n-3)", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child(-1n+3)", seven_ps,
+ pset([1, 2, 3]), pset([4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(-2n+3)", seven_ps,
+ pset([1, 3]), pset([2, 4, 5, 6, 7]));
+ // And a few spot-checks for the other :nth-* selectors
+ test_selector_in_html(":nth-child(4n+1)", seven_ps,
+ pset([1, 5]), pset([2, 3, 4, 6, 7]));
+ test_selector_in_html(":nth-last-child(4n+1)", seven_ps,
+ pset([3, 7]), pset([1, 2, 4, 5, 6]));
+ test_selector_in_html(":nth-of-type(4n+1)", seven_ps,
+ pset([1, 5]), pset([2, 3, 4, 6, 7]));
+ test_selector_in_html(":nth-last-of-type(4n+1)", seven_ps,
+ pset([3, 7]), pset([1, 2, 4, 5, 6]));
+ test_selector_in_html(":nth-child(6)", seven_ps,
+ pset([6]), pset([1, 2, 3, 4, 5, 7]));
+ test_selector_in_html(":nth-last-child(6)", seven_ps,
+ pset([2]), pset([1, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-of-type(6)", seven_ps,
+ pset([6]), pset([1, 2, 3, 4, 5, 7]));
+ test_selector_in_html(":nth-last-of-type(6)", seven_ps,
+ pset([2]), pset([1, 3, 4, 5, 6, 7]));
+
+ // Test [first|last|only]-[child|node|of-type]
+ var interesting_doc = "<!----> <div id='p1'> <!---->x<p id='s1'></p> <!----><p id='s2'></p> <!----></div> <!----><p id='p2'> <!----><span id='s3'></span> <!----><span id='s4'></span> <!---->x</p> <!----><div id='p3'> <!----><p id='s5'></p> <!----></div> <!---->";
+ function idset(ids) { // takes an array of ids
+ return function idset_filter(doc) {
+ var result = [];
+ for (var id of ids)
+ result.push(doc.getElementById(id));
+ return result;
+ }
+ }
+ function classset(classes) { // takes an array of classes
+ return function classset_filter(doc) {
+ var i, j, els;
+ var result = [];
+ for (i = 0; i < classes.length; i++) {
+ els = doc.getElementsByClassName(classes[i]);
+ for (j = 0; j < els.length; j++) {
+ result.push(els[j]);
+ }
+ }
+ return result;
+ }
+ }
+ function emptyset(doc) { return []; }
+ test_parseable(":first-child");
+ test_parseable(":last-child");
+ test_parseable(":only-child");
+ test_parseable(":-moz-first-node");
+ test_parseable(":-moz-last-node");
+ test_parseable(":first-of-type");
+ test_parseable(":last-of-type");
+ test_parseable(":only-of-type");
+ test_selector_in_html(":first-child", seven_ps,
+ pset([1]), pset([2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":first-child", interesting_doc,
+ idset(["p1", "s1", "s3", "s5"]),
+ idset(["s2", "p2", "s4", "p3"]));
+ test_selector_in_html(":-moz-first-node", interesting_doc,
+ idset(["p1", "s3", "s5"]),
+ idset(["s1", "s2", "p2", "s4", "p3"]));
+ test_selector_in_html(":last-child", seven_ps,
+ pset([7]), pset([1, 2, 3, 4, 5, 6]));
+ test_selector_in_html(":last-child", interesting_doc,
+ idset(["s2", "s4", "p3", "s5"]),
+ idset(["p1", "s1", "p2", "s3"]));
+ test_selector_in_html(":-moz-last-node", interesting_doc,
+ idset(["s2", "p3", "s5"]),
+ idset(["p1", "s1", "p2", "s3", "s4"]));
+ test_selector_in_html(":only-child", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":only-child", interesting_doc,
+ idset(["s5"]),
+ idset(["p1", "s1", "s2", "p2", "s3", "s4", "p3"]));
+ test_selector_in_html(":first-of-type", seven_ps,
+ pset([1]), pset([2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":first-of-type", interesting_doc,
+ idset(["p1", "s1", "p2", "s3", "s5"]),
+ idset(["s2", "s4", "p3"]));
+ test_selector_in_html(":last-of-type", seven_ps,
+ pset([7]), pset([1, 2, 3, 4, 5, 6]));
+ test_selector_in_html(":last-of-type", interesting_doc,
+ idset(["s2", "p2", "s4", "p3", "s5"]),
+ idset(["p1", "s1", "s3"]));
+ test_selector_in_html(":only-of-type", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":only-of-type", interesting_doc,
+ idset(["p2", "s5"]),
+ idset(["p1", "s1", "s2", "s3", "s4", "p3"]));
+
+ // And a bunch of tests for the of-type aspect of :nth-of-type() and
+ // :nth-last-of-type(). Note that the last div here contains two
+ // children.
+ var mixed_elements="<p></p><p></p><div></div><p></p><div><p></p><address></address></div><address></address>";
+ function pdaset(ps, divs, addresses) { // takes an array of 1-based indices
+ var l = { p: ps, div: divs, address: addresses };
+ return function pdaset_filter(doc) {
+ var result = [];
+ for (var tag in l) {
+ var a = doc.getElementsByTagName(tag);
+ var indices = l[tag];
+ for (var i in indices)
+ result.push(a[indices[i] - 1]);
+ }
+ return result;
+ }
+ }
+ test_selector_in_html(":nth-of-type(odd)", mixed_elements,
+ pdaset([1, 3, 4], [1], [1, 2]),
+ pdaset([2], [2], []));
+ test_selector_in_html(":nth-of-type(2n-0)", mixed_elements,
+ pdaset([2], [2], []),
+ pdaset([1, 3, 4], [1], [1, 2]));
+ test_selector_in_html(":nth-last-of-type(even)", mixed_elements,
+ pdaset([2], [1], []),
+ pdaset([1, 3, 4], [2], [1, 2]));
+
+ // Test greediness of descendant combinators.
+ var four_children="<div id='a'><div id='b'><div id='c'><div id='d'><\/div><\/div><\/div><\/div>";
+ test_selector_in_html("#a > div div", four_children,
+ idset(["c", "d"]), idset(["a", "b"]));
+ test_selector_in_html("#a > #b div", four_children,
+ idset(["c", "d"]), idset(["a", "b"]));
+ test_selector_in_html("#a div > div", four_children,
+ idset(["c", "d"]), idset(["a", "b"]));
+ test_selector_in_html("#a #b > div", four_children,
+ idset(["c"]), idset(["a", "b", "d"]));
+ test_selector_in_html("#a > #b div", four_children,
+ idset(["c", "d"]), idset(["a", "b"]));
+ test_selector_in_html("#a #c > div", four_children,
+ idset(["d"]), idset(["a", "b", "c"]));
+ test_selector_in_html("#a > #c div", four_children,
+ idset([]), idset(["a", "b", "c", "d"]));
+
+ // More descendant combinator greediness (bug 511147)
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b"]));
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="x"></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b", "x"]));
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"><p>filler filler <i>filler</i> filler</p></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b", "x"]));
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="x"><p>filler filler <i>filler</i> filler</p></div><div></div><div class="b"></div><div></div><div class="x"><p>filler filler <i>filler</i> filler</p></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b", "x"]));
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="match"></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b"]));
+
+ test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div><div class="b"></div><div class="nomatch"></div></div></div>',
+ emptyset, classset(["a", "b", "nomatch"]));
+ test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div><div class="b"></div><div class="nomatch"></div></div><div class="nomatch"></div></div>',
+ emptyset, classset(["a", "b", "nomatch"]));
+ test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div class="b"></div><div><div class="nomatch"></div></div><div></div></div>',
+ emptyset, classset(["a", "b", "nomatch"]));
+ test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div class="b"></div></div><div class="nomatch"></div>',
+ emptyset, classset(["a", "b", "nomatch"]));
+
+ // Test serialization of pseudo-elements.
+ should_serialize_to("p::first-letter", "p::first-letter");
+ should_serialize_to("p:first-letter", "p::first-letter");
+ should_serialize_to("div>p:first-letter", "div > p::first-letter");
+ should_serialize_to("span +div:first-line", "span + div::first-line");
+ should_serialize_to("input::placeholder", "input::placeholder");
+ should_serialize_to("input:placeholder-shown", "input:placeholder-shown");
+
+ // Test serialization of ::-moz-placeholder.
+ should_serialize_to("input::-moz-placeholder", "input::placeholder");
+
+ should_serialize_to(':lang("foo\\"bar")', ':lang(foo\\"bar)');
+
+ // Test default namespaces, including inside :not().
+ var html_default_ns = "@namespace url(http://www.w3.org/1999/xhtml);";
+ var html_ns = "@namespace html url(http://www.w3.org/1999/xhtml);";
+ var xul_default_ns = "@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);";
+ var single_a = "<a id='a' href='data:text/plain,this_better_be_unvisited'></a>";
+ var set_single = idset(['a']);
+ var empty_set = idset([]);
+ test_selector_in_html("a", single_a, set_single, empty_set,
+ html_default_ns);
+ test_selector_in_html("a", single_a, empty_set, set_single,
+ xul_default_ns);
+ test_selector_in_html("*|a", single_a, set_single, empty_set,
+ xul_default_ns);
+ test_selector_in_html("html|a", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ // Type selectors inside :not() bring in default namespaces, but
+ // non-type selectors don't.
+ test_selector_in_html("*|a:not(*)", single_a, set_single, empty_set,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(a)", single_a, set_single, empty_set,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(*|*)", single_a, empty_set, set_single,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(*|a)", single_a, empty_set, set_single,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(:link)", single_a + "<a id='b'></a>",
+ idset(["b"]), set_single,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(:visited)", single_a + "<a id='b'></a>",
+ idset(["a", "b"]), empty_set,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(html|*)", single_a, empty_set, set_single,
+ xul_default_ns + html_ns);
+ test_selector_in_html("*|a:not(html|a)", single_a, empty_set, set_single,
+ xul_default_ns + html_ns);
+ test_selector_in_html("*|a:not(|*)", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ test_selector_in_html("*|a:not(|a)", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ test_selector_in_html("html|a:not(|*)", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ test_selector_in_html("html|a:not(|a)", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ test_selector_in_html("html|a:not(*|*)", single_a, empty_set, set_single,
+ xul_default_ns + html_ns);
+ test_selector_in_html("html|a:not(*|a)", single_a, empty_set, set_single,
+ xul_default_ns + html_ns);
+
+ // Test -moz-locale-dir
+ test_balanced_unparseable(":-moz-locale-dir(ltr)");
+ test_balanced_unparseable(":-moz-locale-dir(rtl)");
+ test_balanced_unparseable(":-moz-locale-dir(rTl)");
+ test_balanced_unparseable(":-moz-locale-dir(LTR)");
+ test_balanced_unparseable(":-moz-locale-dir(other)");
+
+ test_balanced_unparseable(":-moz-locale-dir()");
+ test_balanced_unparseable(":-moz-locale-dir(())");
+ test_balanced_unparseable(":-moz-locale-dir(3())");
+ test_balanced_unparseable(":-moz-locale-dir(f{})");
+ test_balanced_unparseable(":-moz-locale-dir('ltr')");
+ test_balanced_unparseable(":-moz-locale-dir(ltr, other)");
+ test_balanced_unparseable(":-moz-locale-dir(ltr other)");
+ test_balanced_unparseable(":-moz-locale-dir");
+
+ // Test :dir()
+ test_parseable(":dir(ltr)");
+ test_parseable(":dir(rtl)");
+ test_parseable(":dir(rTl)");
+ test_parseable(":dir(LTR)");
+ test_parseable(":dir(other)");
+ if (document.body.matches(":dir(ltr)")) {
+ test_selector_in_html("a:dir(LTr)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(ltR)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(LTR)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(RTl)", single_a,
+ empty_set, set_single);
+ } else {
+ test_selector_in_html("a:dir(RTl)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(rtL)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(RTL)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(LTr)", single_a,
+ empty_set, set_single);
+ }
+ test_selector_in_html("a:dir(other)", single_a,
+ empty_set, set_single);
+
+ test_balanced_unparseable(":dir()");
+ test_balanced_unparseable(":dir(())");
+ test_balanced_unparseable(":dir(3())");
+ test_balanced_unparseable(":dir(f{})");
+ test_balanced_unparseable(":dir('ltr')");
+ test_balanced_unparseable(":dir(ltr, other)");
+ test_balanced_unparseable(":dir(ltr other)");
+ test_balanced_unparseable(":dir");
+
+ // Test chrome-only -moz-lwtheme
+ test_balanced_unparseable(":-moz-lwtheme");
+ test_balanced_unparseable(":-moz-broken");
+
+ test_balanced_unparseable(":-moz-tree-row(selected)");
+ test_balanced_unparseable("::-moz-tree-row(selected)");
+ test_balanced_unparseable("::-MoZ-trEE-RoW(sElEcTeD)");
+ test_balanced_unparseable(":-moz-tree-row(selected focus)");
+ test_balanced_unparseable(":-moz-tree-row(selected , focus)");
+ test_balanced_unparseable("::-moz-tree-row(selected ,focus)");
+ test_balanced_unparseable(":-moz-tree-row(selected, focus)");
+ test_balanced_unparseable("::-moz-tree-row(selected,focus)");
+ test_balanced_unparseable(":-moz-tree-row(selected focus)");
+ test_balanced_unparseable("::-moz-tree-row(selected , focus)");
+ test_balanced_unparseable("::-moz-tree-twisty( hover open )");
+ test_balanced_unparseable("::-moz-tree-row(selected {[]} )");
+ test_balanced_unparseable(":-moz-tree-twisty(open())");
+ test_balanced_unparseable("::-moz-tree-twisty(hover ())");
+
+ test_parseable(":-moz-window-inactive");
+ test_parseable("div p:-moz-window-inactive:hover span");
+
+ // Chrome-only
+ test_unbalanced_unparseable(":-moz-browser-frame");
+
+ // Plugin pseudoclasses are chrome-only:
+ test_unbalanced_unparseable(":-moz-type-unsupported");
+ test_unbalanced_unparseable(":-moz-type-unsupported-platform");
+ test_unbalanced_unparseable(":-moz-handler-clicktoplay");
+ test_unbalanced_unparseable(":-moz-handler-vulnerable-updatable");
+ test_unbalanced_unparseable(":-moz-handler-vulnerable-no-update");
+ test_unbalanced_unparseable(":-moz-handler-disabled");
+ test_unbalanced_unparseable(":-moz-handler-blocked");
+ test_unbalanced_unparseable(":-moz-handler-crashed");
+
+ // We're not in a UA sheet, so this should be invalid.
+ test_balanced_unparseable(":-moz-inert");
+ test_balanced_unparseable(":-moz-native-anonymous");
+ test_balanced_unparseable(":-moz-table-border-nonzero");
+
+ // Case sensitivity of tag selectors
+ function setup_cased_spans(body) {
+ var data = [
+ { tag: "span" },
+ { tag: "sPaN" },
+ { tag: "Span" },
+ { tag: "SPAN" },
+ { ns: "http://www.w3.org/1999/xhtml", tag: "span" },
+ { ns: "http://www.w3.org/1999/xhtml", tag: "sPaN" },
+ { ns: "http://www.w3.org/1999/xhtml", tag: "Span" },
+ { ns: "http://www.w3.org/1999/xhtml", tag: "SPAN" },
+ { ns: "http://example.com/useless", tag: "span" },
+ { ns: "http://example.com/useless", tag: "sPaN" },
+ { ns: "http://example.com/useless", tag: "Span" },
+ { ns: "http://example.com/useless", tag: "SPAN" },
+ ]
+ for (var i in data) {
+ var ent = data[i];
+ var elem;
+ if ("ns" in ent) {
+ elem = body.ownerDocument.createElementNS(ent.ns, ent.tag);
+ } else {
+ elem = body.ownerDocument.createElement(ent.tag);
+ }
+ body.appendChild(elem);
+ }
+ }
+ function bodychildset(indices) {
+ return function bodychildset_filter(doc) {
+ var body = doc.body;
+ var result = [];
+ for (var i in indices) {
+ result.push(body.childNodes[indices[i]]);
+ }
+ return result;
+ }
+ }
+ test_selector_in_html("span", setup_cased_spans,
+ bodychildset([0, 1, 2, 3, 4, 8]),
+ bodychildset([5, 6, 7, 9, 10, 11]));
+ test_selector_in_html("sPaN", setup_cased_spans,
+ bodychildset([0, 1, 2, 3, 4, 9]),
+ bodychildset([5, 6, 7, 8, 10, 11]));
+ test_selector_in_html("Span", setup_cased_spans,
+ bodychildset([0, 1, 2, 3, 4, 10]),
+ bodychildset([5, 6, 7, 8, 9, 11]));
+ test_selector_in_html("SPAN", setup_cased_spans,
+ bodychildset([0, 1, 2, 3, 4, 11]),
+ bodychildset([5, 6, 7, 8, 9, 10]));
+
+ // bug 528096 (tree pseudos)
+ test_unbalanced_unparseable(":-moz-tree-column((){} a");
+ test_unbalanced_unparseable(":-moz-tree-column(x(){} a");
+ test_unbalanced_unparseable(":-moz-tree-column(a b (){} a");
+ test_unbalanced_unparseable(":-moz-tree-column(a, b (){} a");
+
+ // Bug 543428 (escaping)
+ test_selector_in_html("\\32|a", single_a, set_single, empty_set,
+ "@namespace \\32 url(http://www.w3.org/1999/xhtml);");
+ test_selector_in_html("-\\32|a", single_a, set_single, empty_set,
+ "@namespace -\\32 url(http://www.w3.org/1999/xhtml);");
+ test_selector_in_html("\\2|a", single_a, set_single, empty_set,
+ "@namespace \\0002 url(http://www.w3.org/1999/xhtml);");
+ test_selector_in_html("-\\2|a", single_a, set_single, empty_set,
+ "@namespace -\\000002 url(http://www.w3.org/1999/xhtml);");
+ var spans = "<span class='2'></span><span class='&#x2;'></span>" +
+ "<span id='2'></span><span id='&#x2;'></span>"
+ test_selector_in_html(".\\32", spans,
+ bodychildset([0]), bodychildset([1, 2, 3]));
+ test_selector_in_html("[class=\\32]", spans,
+ bodychildset([0]), bodychildset([1, 2, 3]));
+ test_selector_in_html(".\\2", spans,
+ bodychildset([1]), bodychildset([0, 2, 3]));
+ test_selector_in_html("[class=\\2]", spans,
+ bodychildset([1]), bodychildset([0, 2, 3]));
+ test_selector_in_html("#\\32", spans,
+ bodychildset([2]), bodychildset([0, 1, 3]));
+ test_selector_in_html("[id=\\32]", spans,
+ bodychildset([2]), bodychildset([0, 1, 3]));
+ test_selector_in_html("#\\2", spans,
+ bodychildset([3]), bodychildset([0, 1, 2]));
+ test_selector_in_html("[id=\\2]", spans,
+ bodychildset([3]), bodychildset([0, 1, 2]));
+ test_balanced_unparseable("#2");
+
+ // Bug 553805: :not() containing nothing is forbidden
+ test_balanced_unparseable(":not()");
+ test_balanced_unparseable(":not( )");
+ test_balanced_unparseable(":not( \t\n )");
+ test_balanced_unparseable(":not(/*comment*/)");
+ test_balanced_unparseable(":not( /*comment*/ /* comment */ )");
+ test_balanced_unparseable("p :not()");
+ test_balanced_unparseable("p :not( )");
+ test_balanced_unparseable("p :not( \t\n )");
+ test_balanced_unparseable("p :not(/*comment*/)");
+ test_balanced_unparseable("p :not( /*comment*/ /* comment */ )");
+ test_balanced_unparseable("p:not()");
+ test_balanced_unparseable("p:not( )");
+ test_balanced_unparseable("p:not( \t\n )");
+ test_balanced_unparseable("p:not(/*comment*/)");
+ test_balanced_unparseable("p:not( /*comment*/ /* comment */ )");
+
+ test_balanced_unparseable(":not(:nth-child(2k))");
+ test_balanced_unparseable(":not(:nth-child(()))");
+
+ // Bug 1685621 - Serialization of :not()
+ should_serialize_to(":not([disabled][selected])", ":not([disabled][selected])");
+ should_serialize_to(":not([disabled],[selected])", ":not([disabled], [selected])");
+
+ // :-moz-any()
+ test_parseable(":-moz-any()");
+ test_parseable(":-moz-any('foo')");
+ test_parseable(":-moz-any(div p)");
+ test_parseable(":-moz-any(div ~ p)");
+ test_parseable(":-moz-any(div~p)");
+ test_parseable(":-moz-any(div + p)");
+ test_parseable(":-moz-any(div+p)");
+ test_parseable(":-moz-any(div > p)");
+ test_parseable(":-moz-any(div>p)");
+ test_parseable(":-moz-any(div, p)");
+ test_parseable(":-moz-any( div , p )");
+ test_parseable(":-moz-any(div,p)");
+ test_parseable(":-moz-any(div)");
+ test_parseable(":-moz-any(div,p,:link,span:focus)");
+ test_parseable(":-moz-any(:active,:focus)");
+ test_parseable(":-moz-any(:active,:link:focus)");
+ test_parseable(":-moz-any(div,:nonexistentpseudo)");
+ var any_elts = "<input type='text'><a href='http://www.example.com/'></a><div></div><a name='foo'>";
+ test_selector_in_html(":-moz-any(a,input)", any_elts,
+ bodychildset([0, 1, 3]), bodychildset([2]));
+ test_selector_in_html(":-moz-any(:link,:not(a))", any_elts,
+ bodychildset([0, 1, 2]), bodychildset([3]));
+ test_selector_in_html(":-moz-any([href],input[type],input[name])", any_elts,
+ bodychildset([0, 1]), bodychildset([2, 3]));
+ test_selector_in_html(":-moz-any(div,a):-moz-any([type],[href],[name])",
+ any_elts,
+ bodychildset([1, 3]), bodychildset([0, 2]));
+
+ // Test that we don't tokenize an empty HASH.
+ test_balanced_unparseable("#");
+ test_balanced_unparseable("# ");
+ test_balanced_unparseable("#, p");
+ test_balanced_unparseable("# , p");
+ test_balanced_unparseable("p #");
+ test_balanced_unparseable("p # ");
+ test_balanced_unparseable("p #, p");
+ test_balanced_unparseable("p # , p");
+
+ // Test that a backslash alone at EOF outside of a string is treated
+ // as U+FFFD.
+ test_parseable_via_api("#a\\");
+ test_parseable_via_api("#\\");
+ test_parseable_via_api("\\");
+
+ // Test that newline escapes are only supported in strings.
+ test_balanced_unparseable("di\\\nv");
+ test_balanced_unparseable("div \\\n p");
+ test_balanced_unparseable("div\\\n p");
+ test_balanced_unparseable("div \\\np");
+ test_balanced_unparseable("div\\\np");
+
+ // Test that :-moz-placeholder is parsable.
+ test_parseable(":-moz-placeholder");
+
+ // Test that things other than user-action pseudo-classes are
+ // rejected after pseudo-elements. Some of these tests rely on
+ // using a pseudo-element that supports a user-action pseudo-class
+ // after it, so we need to use the prefixed ::-moz-color-swatch,
+ // which is one of the ones with
+ // CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE (none of which are
+ // unprefixed).
+ test_parseable("::-moz-color-swatch:hover");
+ test_parseable("::-moz-color-swatch:is(:hover)");
+ test_parseable("::-moz-color-swatch:not(:hover)");
+ test_parseable("::-moz-color-swatch:where(:hover)");
+ test_balanced_unparseable("::-moz-color-swatch:not(.foo)");
+ test_balanced_unparseable("::-moz-color-swatch:first-child");
+ test_balanced_unparseable("::-moz-color-swatch:host");
+ test_balanced_unparseable("::-moz-color-swatch:host(div)");
+ test_balanced_unparseable("::-moz-color-swatch:nth-child(1)");
+ test_balanced_unparseable("::-moz-color-swatch:hover#foo");
+ test_balanced_unparseable(".foo::after:not(.bar) ~ h3");
+
+ for (let selector of [
+ "::-moz-color-swatch:where(.foo)",
+ "::-moz-color-swatch:is(.foo)",
+ "::-moz-color-swatch:where(p, :hover)",
+ "::-moz-color-swatch:is(p, :hover)",
+ ]) {
+ test_parseable(selector);
+ should_serialize_to(selector, selector);
+ ok(!CSS.supports(`selector(${selector})`), "supports should report false for forging selector parse failure");
+ }
+
+ run_deferred_tests();
+}
+
+var deferred_tests = [];
+
+function defer_clonedoc_tests(docurl, onloadfunc)
+{
+ deferred_tests.push( { docurl: docurl, onloadfunc: onloadfunc } );
+}
+
+function run_deferred_tests()
+{
+ if (deferred_tests.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+
+ cloneiframe.onload = deferred_tests_onload;
+ cloneiframe.src = deferred_tests[0].docurl;
+}
+
+function deferred_tests_onload(event)
+{
+ if (event.target != cloneiframe)
+ return;
+
+ deferred_tests[0].onloadfunc();
+ deferred_tests.shift();
+
+ run_deferred_tests();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_setPropertyWithNull.html b/layout/style/test/test_setPropertyWithNull.html
new file mode 100644
index 0000000000..d54fd03cdd
--- /dev/null
+++ b/layout/style/test/test_setPropertyWithNull.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=830260
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 830260</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 830260 **/
+ var div = document.createElement("div");
+ div.style.color = "green";
+ div.style.color = "null";
+ is(div.style.color, "green", 'Assigning "null" as a color should not parse');
+ div.style.setProperty("color", "null");
+ is(div.style.color, "green",
+ 'Passing "null" as a color to setProperty should not parse');
+
+ div.style.setProperty("color", null);
+ is(div.style.color, "",
+ 'Passing null as a color to setProperty should remove the property');
+
+ div.style.color = "green";
+ is(div.style.color, "green", 'Assigning "green" as a color should parse');
+
+ div.style.color = null;
+ is(div.style.color, "",
+ 'Assigning null as a color should remove the property');
+
+
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=830260">Mozilla Bug 830260</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_shape_outside_CORS.html b/layout/style/test/test_shape_outside_CORS.html
new file mode 100644
index 0000000000..2d2ee1c32f
--- /dev/null
+++ b/layout/style/test/test_shape_outside_CORS.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: shape-outside with a CORS violation</title>
+<link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-outside-property"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+
+<style>
+.container {
+ clear: both;
+ width: 500px;
+}
+.shaper {
+ width: 50px;
+ height: 50px;
+ float: left;
+ background-color: green;
+}
+.shapeAllow {
+ shape-outside: url("support/1x1-transparent.png");
+}
+.shapeRefuse {
+ shape-outside: url("http://example.com/layout/style/test/support/1x1-transparent.png");
+}
+.sibling {
+ display: inline-block;
+}
+</style>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function runTests() {
+ let divAllow = document.getElementById("allow");
+ let divAllowSib = divAllow.nextElementSibling;
+ ok(divAllowSib.getBoundingClientRect().left == divAllow.getBoundingClientRect().left,
+ "Test 1: Sibling should be at same left offset as div (shape-outside should be allowed), and onload should only fire after layout is complete.");
+
+ let divRefuse = document.getElementById("refuse");
+ let divRefuseSib = divRefuse.nextElementSibling;
+ ok(divRefuseSib.getBoundingClientRect().left != divRefuse.getBoundingClientRect().left,
+ "Test 2: Sibling should be at different left offset from div (shape-outside should be refused).");
+
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="runTests()">
+ <div class="container">
+ <div id="allow" class="shaper shapeAllow"></div><div class="sibling">allow (image is blank, so text is flush left)</div>
+ </div>
+ <div class="container">
+ <div id="refuse" class="shaper shapeRefuse"></div><div class="sibling">refuse (image unread, so text is moved to box edge)</div>
+ </div>
+</body>
+</html>
diff --git a/layout/style/test/test_shared_sheet_caching.html b/layout/style/test/test_shared_sheet_caching.html
new file mode 100644
index 0000000000..e59c0c139e
--- /dev/null
+++ b/layout/style/test/test_shared_sheet_caching.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ function childWindowLoaded(childWin) {
+ is(
+ childWin.getComputedStyle(childWin.document.documentElement).backgroundColor,
+ "rgb(0, 255, 0)",
+ "Sheet should apply",
+ )
+ is(
+ SpecialPowers.getDOMWindowUtils(childWin).parsedStyleSheets,
+ 0,
+ `Shouldn't need to parse the stylesheet ${childWin.parent == window ? "frame" : "top"}`
+ );
+ if (childWin.top == childWin) {
+ SimpleTest.finish();
+ }
+ }
+</script>
+<link rel="stylesheet" href="file_shared_sheet_caching.css">
+<iframe src="file_shared_sheet_caching.html"></iframe>
+<a rel="opener" href="file_shared_sheet_caching.html" target="_blank">Navigation</a>
+<script>
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+ info("Sheets parsed: " + SpecialPowers.DOMWindowUtils.parsedStyleSheets);
+ is(
+ getComputedStyle(document.documentElement).backgroundColor,
+ "rgb(0, 255, 0)",
+ "Sheet should apply",
+ )
+ document.querySelector("a").click();
+}
+</script>
diff --git a/layout/style/test/test_sheet_privilege.html b/layout/style/test/test_sheet_privilege.html
new file mode 100644
index 0000000000..d089fdc137
--- /dev/null
+++ b/layout/style/test/test_sheet_privilege.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>Test whether it can access a filed in MediaList with normal privilege after accessing with chrome privilege.</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<style>
+@media screen and (max-width: 800px) {
+ body {
+ background-color: lime;
+ }
+}
+</style>
+<script>
+"use strict";
+
+function assertMediaText(rule, description) {
+ is(rule.media.mediaText, "screen and (max-width: 800px)", description);
+}
+
+add_task(function testCssRuleMedia() {
+ assertMediaText(SpecialPowers.wrap(document).styleSheets[0].cssRules[0], "privileged document");
+ assertMediaText(document.styleSheets[0].cssRules[0], "unprivileged document");
+});
+
+add_task(function testSheetFromCache() {
+ for (let i = 0; i < 2; ++i) {
+ const style = document.createElement("style");
+ style.textContent = `@media screen and (max-width: 800px) {}`;
+ document.head.append(style);
+ assertMediaText(SpecialPowers.wrap(style).sheet.cssRules[0], "privileged sheet " + i);
+ assertMediaText(style.sheet.cssRules[0], "unprivileged sheet " + i);
+ }
+});
+</script>
diff --git a/layout/style/test/test_shorthand_property_getters.html b/layout/style/test/test_shorthand_property_getters.html
new file mode 100644
index 0000000000..cade526183
--- /dev/null
+++ b/layout/style/test/test_shorthand_property_getters.html
@@ -0,0 +1,248 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=376075
+-->
+<head>
+ <title>Test for Bug 376075</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=376075">Mozilla Bug 376075</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 376075 **/
+
+var e = document.getElementById("display");
+
+var borderExtras = "; border-image: none";
+
+// Test that we only serialize the 'border' shorthand when appropriate.
+e.setAttribute("style", "border-left: medium solid blue; border-right: medium solid blue; border-top: medium blue solid; border-bottom: blue medium solid" + borderExtras);
+isnot(e.style.border, "", "should be able to serialize border");
+e.setAttribute("style", "border-left: medium solid blue; border-right: medium solid blue; border-top: medium blue solid; border-bottom: green medium solid" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: green" + borderExtras);
+isnot(e.style.border, "", "should be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: green blue blue blue" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: blue green blue blue" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: blue blue green blue" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: blue blue blue green" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 2px 3px 3px; border-style: solid; border-color: green" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid dashed; border-color: green" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+
+// Test suppression of currentcolor in border shorthands.
+e.setAttribute("style", "border: medium solid");
+is(e.style.border, "solid", "implied default color omitted serializing border");
+is(e.style.borderLeft, "solid", "implied default color omitted serializing border-left");
+is(e.style.cssText, "border: solid;", "implied default color omitted serializing declaration");
+e.setAttribute("style", "border-right: medium solid");
+is(e.style.borderRight, "solid", "implied default color omitted serializing border-right");
+is(e.style.cssText, "border-right: solid;", "implied default color omitted serializing declaration");
+
+// Test that we shorten box properties to the shortest possible.
+e.setAttribute("style", "margin: 7px");
+is(e.style.margin, "7px", "should condense to shortest possible");
+is(e.style.cssText, "margin: 7px;", "should condense to shortest possible");
+e.setAttribute("style", "padding: 7px 7px 7px");
+is(e.style.padding, "7px", "should condense to shortest possible");
+is(e.style.cssText, "padding: 7px;", "should condense to shortest possible");
+e.setAttribute("style", "border-width: 7px 7px 7px 7px");
+is(e.style.borderWidth, "7px", "should condense to shortest possible");
+is(e.style.cssText, "border-width: 7px;", "should condense to shortest possible");
+e.setAttribute("style", "margin: 7px 7px 7px 6px");
+is(e.style.margin, "7px 7px 7px 6px", "should not condense");
+is(e.style.cssText, "margin: 7px 7px 7px 6px;", "should not condense");
+e.setAttribute("style", "border-style: solid dotted none dotted");
+is(e.style.borderStyle, "solid dotted none", "should condense");
+is(e.style.cssText, "border-style: solid dotted none;", "should condense");
+e.setAttribute("style", "border-color: green blue");
+is(e.style.borderColor, "green blue", "should condense");
+is(e.style.cssText, "border-color: green blue;", "should condense");
+e.setAttribute("style", "border-color: green blue green");
+is(e.style.borderColor, "green blue", "should condense");
+is(e.style.cssText, "border-color: green blue;", "should condense");
+e.setAttribute("style", "border-color: green blue green blue");
+is(e.style.borderColor, "green blue", "should condense");
+is(e.style.cssText, "border-color: green blue;", "should condense");
+e.setAttribute("style", "border-color: currentColor currentColor currentcolor CURRENTcolor");
+is(e.style.borderColor, "currentcolor", "should condense to canonical case");
+is(e.style.cssText, "border-color: currentcolor;", "should condense to canonical case");
+e.setAttribute("style", "border-style: ridge none none none");
+is(e.style.borderStyle, "ridge none none", "should condense");
+is(e.style.cssText, "border-style: ridge none none;", "should condense");
+e.setAttribute("style", "border-radius: 1px 1px 1px 1px");
+is(e.style.borderRadius, "1px", "should condense to shortest possible");
+is(e.style.cssText, "border-radius: 1px;", "should condense to shortest possible");
+e.setAttribute("style", "border-radius: 1px 1px 1px 1px / 2px 2px 2px 2px");
+is(e.style.borderRadius, "1px / 2px", "should condense to shortest possible");
+is(e.style.cssText, "border-radius: 1px / 2px;", "should condense to shortest possible");
+e.setAttribute("style", "border-radius: 1px 2px 1px 2px / 1% 2% 3% 2%");
+is(e.style.borderRadius, "1px 2px / 1% 2% 3%", "should condense to shortest possible");
+is(e.style.cssText, "border-radius: 1px 2px / 1% 2% 3%;", "should condense to shortest possible");
+
+// Test that we refuse to serialize the 'background' and 'font'
+// shorthands when some subproperties that can't be expressed in the
+// shorthand syntax are present.
+e.setAttribute("style", "font: medium serif");
+isnot(e.style.font, "", "should have font shorthand");
+e.setAttribute("style", "font: medium serif; font-size-adjust: 0.45");
+is(e.style.font, "", "should not have font shorthand");
+e.setAttribute("style", "font: medium serif; font-feature-settings: 'liga' off");
+is(e.style.font, "", "should not have font shorthand");
+
+// Test that all combinations of background-clip and background-origin
+// can be expressed in the shorthand (which wasn't the case previously).
+e.setAttribute("style", "background: red");
+isnot(e.style.background, "", "should have background shorthand");
+e.setAttribute("style", "background: red; background-origin: border-box");
+isnot(e.style.background, "", "should have background shorthand (origin:border-box)");
+e.setAttribute("style", "background: red; background-clip: padding-box");
+isnot(e.style.background, "", "should have background shorthand (clip:padding-box)");
+e.setAttribute("style", "background: red; background-origin: content-box");
+isnot(e.style.background, "", "should have background shorthand (origin:content-box)");
+e.setAttribute("style", "background: red; background-clip: content-box");
+isnot(e.style.background, "", "should have background shorthand (clip:content-box)");
+e.setAttribute("style", "background: red; background-clip: content-box; background-origin: content-box;");
+isnot(e.style.background, "", "should have background shorthand (clip:content-box;origin:content-box)");
+
+// Test background-size in the background shorthand.
+e.setAttribute("style", "background: red; background-size: 100% 100%");
+isnot(e.style.background, "", "should have background shorthand (size:100% 100%)");
+e.setAttribute("style", "background: red; background-size: 100% auto");
+isnot(e.style.background, "", "should have background shorthand (size:100% auto)");
+e.setAttribute("style", "background: red; background-size: auto 100%");
+isnot(e.style.background, "", "should have background shorthand (size:auto 100%)");
+
+// Check that we only serialize background when the lists (of layers) for
+// the subproperties are the same length.
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+isnot(e.style.background, "", "should have background shorthand (all lists length 3)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-clip too long)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-origin too short)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png), none; background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-image too long)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-attachment too short)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px, bottom; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-position too long)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat");
+is(e.style.background, "", "should not have background shorthand (background-repeat too short)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-size too long)");
+
+// Check that we only serialize background-position when the lists (of layers) for
+// the -x/-y subproperties are the same length.
+e.setAttribute("style", "background-position-x: 10%, left 2em, right; background-position-y: top 2em, bottom, 10%");
+is(e.style.backgroundPosition, "left 10% top 2em, left 2em bottom, right 10%", "should have background-position shorthand (both lists length 3)");
+e.setAttribute("style", "background-position-x: 10%, left 2em; background-position-y: top 2em, bottom, 10%");
+is(e.style.backgroundPosition, "", "should not have background-position shorthand (background-position-x too short)");
+e.setAttribute("style", "background-position-x: 10%, left 2em, right; background-position-y: top 2em");
+is(e.style.backgroundPosition, "", "should not have background-position shorthand (background-position-y too short)");
+
+// Check that background-position serialization doesn't produce invalid values.
+e.setAttribute("style", "background-position: 0px");
+is(e.style.backgroundPosition, "0px center", "1-value form should be accepted, with implied center value for background-position-y");
+e.setAttribute("style", "background-position: 0px center");
+is(e.style.backgroundPosition, "0px center", "2-value form 'x-offset' 'y-edge' should be accepted, and serialize to 2-value form");
+e.setAttribute("style", "background-position: left 0px center");
+is(e.style.backgroundPosition, "left 0px center", "3-value form 'x-edge' 'x-offset' 'y-edge' should be accepted and serialize to 3-value form");
+e.setAttribute("style", "background-position: left top 0px");
+is(e.style.backgroundPosition, "left top 0px", "3-value form 'x-edge' 'y-edge' 'y-offset' should be accepted and serialize to 3-value form");
+e.setAttribute("style", "background-position: left 0px top 0px");
+is(e.style.backgroundPosition, "left 0px top 0px", "4-value form should be accepted and serialize to 4-value form");
+e.setAttribute("style", "background-position-x: 0px; background-position-y: center");
+is(e.style.backgroundPosition, "0px center", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: 0px; background-position-y: 0px");
+is(e.style.backgroundPosition, "0px 0px", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: center; background-position-y: 0px");
+is(e.style.backgroundPosition, "center 0px", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: left; background-position-y: top");
+is(e.style.backgroundPosition, "left top", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: left 0px; background-position-y: center");
+is(e.style.backgroundPosition, "left 0px center", "should always serialize to 3-value form if both -x and -y specified an edge");
+e.setAttribute("style", "background-position-x: right; background-position-y: top 0px");
+is(e.style.backgroundPosition, "right top 0px", "should always serialize to 3-value form if both -x and -y specified an edge");
+e.setAttribute("style", "background-position-x: left 0px; background-position-y: 0px");
+is(e.style.backgroundPosition, "left 0px top 0px", "should serialize to 4-value form if 3-value form would only have one edge");
+e.setAttribute("style", "background-position-x: 0px; background-position-y: top 0px");
+is(e.style.backgroundPosition, "left 0px top 0px", "should serialize to 4-value form if 3-value form would only have one edge");
+
+// Check that we only serialize transition when the lists are the same length.
+e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
+isnot(e.style.transition, "", "should have transition shorthand (lists same length)");
+e.setAttribute("style", "transition-property: color, width, left; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition-property: all; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms, 300ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear, ease-out; transition-delay: 0s, 1s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s, 0s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition: color, width; transition-delay: 0s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+isnot(e.style.animation, "", "should have animation shorthand (lists same length)");
+e.setAttribute("style", "animation-name: bounce, roll, left; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms, 300ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear, ease-out; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s, 0s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards, both; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2, 1; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+
+// Check that the 'border' shorthand resets 'border-image' and
+// but that other 'border-*' shorthands don't
+// (bug 482692).
+e.setAttribute("style", 'border-image: url("foo.png") 5 5 5 5 / 5 5 5 5 / 5 5 5 5 repeat repeat; border-left: medium solid green');
+is(e.style.cssText,
+ 'border-image: url("foo.png") 5 / 5 / 5 repeat; border-left: solid green;',
+ "border-left does NOT reset border-image");
+e.setAttribute("style", 'border-image: url("foo.png") 5 5 5 5; border: solid green');
+is(e.style.cssText, 'border: solid green;', "border DOES reset border-image");
+
+// Test that the color goes at the beginning of the last item of the
+// background shorthand.
+e.setAttribute("style", "background: url(foo.png) blue");
+is(e.style.cssText,
+ "background: blue url(\"foo.png\");",
+ "color should be at start of single-item background");
+e.setAttribute("style", "background: url(bar.png), url(foo.png) blue");
+is(e.style.cssText,
+ "background: url(\"bar.png\"), blue url(\"foo.png\");",
+ "color should be at start of single-item background");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_specified_value_serialization.html b/layout/style/test/test_specified_value_serialization.html
new file mode 100644
index 0000000000..5624b7398a
--- /dev/null
+++ b/layout/style/test/test_specified_value_serialization.html
@@ -0,0 +1,281 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test for miscellaneous specified value issues</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+(function test_bug_721136() {
+ // Test for transform property serialization.
+ [
+ [" mAtRiX(1, 2,3,4, 5,6 ) ", "matrix(1, 2, 3, 4, 5, 6)"],
+ [" mAtRiX3d( 1,2,3,0,4 ,5,6,0,7,8 , 9,0,10, 11,12,1 ) ",
+ "matrix3d(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 10, 11, 12, 1)"],
+ [" pErSpEcTiVe( 400Px ) ", "perspective(400px)"],
+ [" rOtAtE( 90dEg ) ", "rotate(90deg)"],
+ [" rOtAtE3d( 0,0 , 1 ,180DeG ) ", "rotate3d(0, 0, 1, 180deg)"],
+ [" rOtAtEx( 100GrAD ) ", "rotateX(100grad)"],
+ [" rOtAtEy( 1.57RaD ) ", "rotateY(1.57rad)"],
+ [" rOtAtEz( 0.25TuRn ) ", "rotateZ(0.25turn)"],
+ [" sCaLe( 2 ) ", "scale(2)"],
+ [" sCaLe( 2,3 ) ", "scale(2, 3)"],
+ [" sCaLe3D( 2,4 , -9 ) ", "scale3d(2, 4, -9)"],
+ [" sCaLeX( 2 ) ", "scaleX(2)"],
+ [" sCaLeY( 2 ) ", "scaleY(2)"],
+ [" sCaLeZ( 2 ) ", "scaleZ(2)"],
+ [" sKeW( 45dEg ) ", "skew(45deg)"],
+ [" sKeW( 45dEg,45DeG ) ", "skew(45deg, 45deg)"],
+ [" sKeWx( 45DeG ) ", "skewX(45deg)"],
+ [" sKeWy( 45DeG ) ", "skewY(45deg)"],
+ [" tRaNsLaTe( 1Px ) ", "translate(1px)"],
+ [" tRaNsLaTe( 1Px,3Pt ) ", "translate(1px, 3pt)"],
+ [" tRaNsLaTe3D( 21pX,-6pX , 4pX ) ", "translate3d(21px, -6px, 4px)"],
+ [" tRaNsLaTeX( 1pT ) ", "translateX(1pt)"],
+ [" tRaNsLaTeY( 1iN ) ", "translateY(1in)"],
+ [" tRaNsLaTeZ( 15.4pX ) ", "translateZ(15.4px)"],
+ ["tranSlatex( 16px )rotatez(-90deg) rotate(100grad)\ttranslate3d(12pt, 0pc, 0.0em)",
+ "translateX(16px) rotateZ(-90deg) rotate(100grad) translate3d(12pt, 0pc, 0em)"],
+ ].forEach(function(arr) {
+ document.documentElement.style.transform = arr[0];
+ is(document.documentElement.style.transform, arr[1],
+ "bug-721136 incorrect serialization");
+ });
+
+ var elt = document.documentElement;
+
+ elt.setAttribute("style",
+ "transform: tRANslatEX(5px) TRanslATey(10px) translatez(2px) ROTATEX(30deg) rotateY(30deg) rotatez(5deg) SKEWx(10deg) skewy(10deg) scaleX(2) SCALEY(0.5) scalez(2)");
+ is(elt.style.getPropertyValue("transform"),
+ "translateX(5px) translateY(10px) translateZ(2px) rotateX(30deg) rotateY(30deg) rotateZ(5deg) skewX(10deg) skewY(10deg) scaleX(2) scaleY(0.5) scaleZ(2)",
+ "bug-721136 expected case canonicalization of transform functions");
+
+ elt.setAttribute("style",
+ "font-variant-alternates: SWASH(fOo) stYLIStiC(Bar)");
+ is(elt.style.getPropertyValue("font-variant-alternates"),
+ "stylistic(Bar) swash(fOo)",
+ "expected case and ordering canonicalization of font-variant-alternates functions");
+
+ elt.setAttribute("style", ""); // leave the page in a useful state
+})();
+
+(function test_bug_1347164() {
+ // Test that specified color values are serialized as "rgb()"
+ // IFF they're fully-opaque (and otherwise as "rgba()").
+ var color = [
+ ["rgba(0, 0, 0, 1)", "rgb(0, 0, 0)"],
+ ["rgba(0, 0, 0, 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["hsla(0, 0%, 0%, 1)", "rgb(0, 0, 0)"],
+ ["hsla(0, 0%, 0%, 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ];
+
+ var css_color_4 = [
+ ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgba(0 0 0 / 0.1)", "rgba(0, 0, 0, 0.1)"],
+ ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgb(0 0 0 / 0.2)", "rgba(0, 0, 0, 0.2)"],
+ ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsla(0deg 0% 0% / 0.3)", "rgba(0, 0, 0, 0.3)"],
+ ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsl(0 0% 0% / 0.4)", "rgba(0, 0, 0, 0.4)"],
+ ];
+
+ var frame_container = document.getElementById("display");
+ var p = document.createElement("p");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < color.length; ++i) {
+ var test = color[i];
+ p.style.color = test[0];
+ is(p.style.color, test[1], "serialization value of " + test[0]);
+ }
+ for (var i = 0; i < css_color_4.length; ++i) {
+ var test = css_color_4[i];
+ p.style.color = test[0];
+ is(p.style.color, test[1], "css-color-4 serialization value of " + test[0]);
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1357117() {
+ // Test that vendor-prefixed gradient styles round-trip with the same prefix,
+ // or with no prefix.
+ var backgroundImages = [
+ // [ specified style,
+ // expected serialization,
+ // descriptionOfTestcase ],
+ // Linear gradient with legacy-gradient-line (needs prefixed syntax):
+ [ "-webkit-linear-gradient(10deg, red, blue)",
+ "-webkit-linear-gradient(10deg, red, blue)",
+ "-webkit-linear-gradient with angled legacy-gradient-line" ],
+
+ // Linear gradient with box corner (needs prefixed syntax):
+ [ "-webkit-linear-gradient(top left, red, blue)",
+ "-webkit-linear-gradient(left top, red, blue)",
+ "-webkit-linear-gradient with box corner" ],
+
+ // Servo keeps the original prefix form which is closer to other impls.
+ [ "-webkit-radial-gradient(contain, red, blue)",
+ "-webkit-radial-gradient(closest-side, red, blue)",
+ "-webkit-radial-gradient with legacy 'contain' keyword" ],
+ ];
+
+ var frame_container = document.getElementById("display");
+ var p = document.createElement("p");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundImages.length; ++i) {
+ var test = backgroundImages[i];
+ p.style.backgroundImage = test[0];
+ is(p.style.backgroundImage, test[1],
+ "serialization value of prefixed gradient expression (" + test[2] + ")");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1357932() {
+ // Test for box-position keyword ordering, in serialization of specified CSS
+ // gradients.
+ var backgroundImages = [
+ // [ specified style,
+ // expected serialization,
+ // descriptionOfTestcase ],
+ // Linear gradient to box position ordering:
+ [ "linear-gradient(to right top, gray, pink)",
+ "linear-gradient(to right top, gray, pink)",
+ "linear-gradient ordering to right top" ],
+ [ "linear-gradient(to top right, yellow, teal)",
+ "linear-gradient(to right top, yellow, teal)",
+ "linear-gradient ordering to top right" ],
+ ];
+
+ var frame_container = document.getElementById("display");
+ var p = document.createElement("p");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundImages.length; ++i) {
+ var test = backgroundImages[i];
+ p.style.backgroundImage = test[0];
+ is(p.style.backgroundImage, test[1],
+ "serialization value of gradient expression (" + test[2] + ")");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1367028() {
+ const borderImageSubprops = [
+ "border-image-slice",
+ "border-image-outset",
+ "border-image-width"
+ ];
+ const rectValues = [
+ {
+ values: ["5 5 5 5", "5 5 5", "5 5", "5"],
+ expected: "5",
+ desc: "identical four sides",
+ },
+ {
+ values: ["5 6 5 6", "5 6 5", "5 6"],
+ expected: "5 6",
+ desc: "identical values on each axis",
+ },
+ {
+ values: ["5 6 7 6", "5 6 7"],
+ expected: "5 6 7",
+ desc: "identical values on left and right",
+ },
+ {
+ values: ["5 6 5 7"],
+ desc: "identical values on top and bottom",
+ },
+ {
+ values: ["5 5 6 6", "5 6 6 5"],
+ desc: "identical values on unrelated sides",
+ },
+ {
+ values: ["5 6 7 8"],
+ desc: "different values on all sides",
+ },
+ ];
+
+ let frameContainer = document.getElementById("display");
+ let p = document.createElement("p");
+ frameContainer.appendChild(p);
+
+ for (let prop of borderImageSubprops) {
+ for (let {values, expected, desc} of rectValues) {
+ for (let value of values) {
+ p.style.setProperty(prop, value);
+ is(p.style.getPropertyValue(prop),
+ expected ? expected : value, `${desc} for ${prop}`);
+ p.style.removeProperty(prop);
+ }
+ }
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1397619() {
+ var borderRadius = [
+ // [ specified style,
+ // expected serialization,
+ // descriptionOfTestcase ],
+ [ "10px 10px",
+ "10px",
+ "Width and height specified for " ],
+ [ "10px",
+ "10px",
+ "Only width specified for " ],
+ ];
+
+
+ var frame_container = document.getElementById("display");
+ var p = document.createElement("p");
+ frame_container.appendChild(p);
+
+ [
+ "border-top-left-radius",
+ "border-bottom-left-radius",
+ "border-top-right-radius",
+ "border-bottom-right-radius",
+ "border-spacing",
+ ].forEach(function(property) {
+ for (var i = 0; i < borderRadius.length; ++i) {
+ var test = borderRadius[i];
+ p.style.setProperty(property, test[0]);
+ is(p.style.getPropertyValue(property), test[1], test[2] + property);
+ }
+ });
+
+ p.style.paintOrder = "markers";
+ is(p.style.paintOrder, "markers",
+ "specified value serialization for paint-order doesn't contain repetitive values");
+
+ p.remove();
+})();
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ set: [['layout.css.individual-transform.enabled', true]],
+ },
+ () =>
+ window.open('file_specified_value_serialization_individual_transforms.html')
+);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_style_attr_listener.html b/layout/style/test/test_style_attr_listener.html
new file mode 100644
index 0000000000..b824fe7ff4
--- /dev/null
+++ b/layout/style/test/test_style_attr_listener.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for Bug 338679 (from bug 1340341)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div style=""></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+// Run the test first with mutation events enabled and then disabled.
+SpecialPowers.pushPrefEnv(
+ { 'set': [['dom.mutation-events.cssom.disabled', false]] },
+ runTest
+);
+
+function runTest(stop) {
+ let div = document.querySelector('div');
+ let expectation;
+ let count = 0;
+ div.style.color = "red";
+ div.addEventListener('DOMAttrModified', function(evt) {
+ count++;
+ is(evt.prevValue, expectation.prevValue, `Previous value for event ${count}`);
+ is(evt.newValue, expectation.newValue, `New value for event ${count}`);
+ });
+ expectation = { prevValue: 'color: red;', newValue: 'color: green;' };
+ div.style.color = "green";
+ expectation = { prevValue: 'color: green;', newValue: '' };
+ div.style.color = '';
+ if (SpecialPowers.getBoolPref("dom.mutation-events.cssom.disabled")) {
+ is(count, 0, "No DOMAttrModified event should be triggered");
+ } else {
+ is(count, 2, "DOMAttrModified events should have been triggered");
+ }
+
+ if (!stop) {
+ div.setAttribute("style", "");
+ SpecialPowers.pushPrefEnv(
+ { 'set': [['dom.mutation-events.cssom.disabled', true]] },
+ function() {
+ runTest(true);
+ SimpleTest.finish();
+ }
+ );
+ }
+}
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_style_attribute_quirks.html b/layout/style/test/test_style_attribute_quirks.html
new file mode 100644
index 0000000000..5a5b87e122
--- /dev/null
+++ b/layout/style/test/test_style_attribute_quirks.html
@@ -0,0 +1,18 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=915093
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 915093</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="style_attribute_tests.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=915093">Mozilla Bug 915093</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_style_attribute_standards.html b/layout/style/test/test_style_attribute_standards.html
new file mode 100644
index 0000000000..e6e64afc24
--- /dev/null
+++ b/layout/style/test/test_style_attribute_standards.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=915093
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 915093</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="style_attribute_tests.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=915093">Mozilla Bug 915093</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_style_struct_copy_constructors.html b/layout/style/test/test_style_struct_copy_constructors.html
new file mode 100644
index 0000000000..95f727a58d
--- /dev/null
+++ b/layout/style/test/test_style_struct_copy_constructors.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for style struct copy constructors</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"><span id="one"></span><span id="two"></span><span id="parent"><span id="child"></span></span></p>
+<div id="content" style="display: none">
+
+<div id="testnode"><span id="element"></span></div>
+
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for style struct copy constructors **/
+
+/**
+ * XXX Why doesn't putting a bug in the nsStyleFont copy-constructor for
+ * font-weight (initializing to normal) trigger a failure of this test?
+ * It works for leaving -moz-image-region uninitialized (both halves),
+ * overwriting text-decoration (only the first half, since it's not
+ * inherited), and leaving visibility uninitialized (only the second
+ * half; passes the first half ok).
+ */
+
+var gElementOne = document.getElementById("one");
+var gElementTwo = document.getElementById("two");
+var gElementParent = document.getElementById("parent");
+var gElementChild = document.getElementById("child");
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#one, #two, #parent {}", gStyleSheet.cssRules.length)];
+var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#two, #child {}", gStyleSheet.cssRules.length)];
+
+/** Test using aStartStruct **/
+
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (!("subproperties" in info)) {
+ gRule1.style.setProperty(prop, info.other_values[0], "");
+ gRule2.style.setProperty(prop, info.other_values[0], "");
+ }
+}
+
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (!("subproperties" in info)) {
+ gRule2.style.removeProperty(prop);
+
+ var one = getComputedStyle(gElementOne, "").getPropertyValue(prop);
+ var two = getComputedStyle(gElementTwo, "").getPropertyValue(prop);
+ is(two, one,
+ "property '" + prop + "' was copy-constructed correctly (aStartStruct)");
+
+ gRule2.style.setProperty(prop, info.other_values[0], "");
+ }
+}
+
+/** Test using inheritance **/
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.inherited && !("subproperties" in info)) {
+ gRule2.style.removeProperty(prop);
+
+ var parent = getComputedStyle(gElementParent, "").getPropertyValue(prop);
+ var child = getComputedStyle(gElementChild, "").getPropertyValue(prop);
+
+ is(child, parent,
+ "property '" + prop + "' was copy-constructed correctly (inheritance)");
+
+ gRule2.style.setProperty(prop, info.other_values[0], "");
+ }
+}
+
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (!("subproperties" in info)) {
+ gRule1.style.removeProperty(prop);
+ gRule2.style.removeProperty(prop);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_stylesheet_additions.html b/layout/style/test/test_stylesheet_additions.html
new file mode 100644
index 0000000000..0e115e91f4
--- /dev/null
+++ b/layout/style/test/test_stylesheet_additions.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1273303: Stylesheet additions and removals known to not
+ affect the document don't trigger restyles
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div class="classScope">
+ <div></div>
+</div>
+<div id="idScope">
+ <div></div>
+</div>
+<!--
+ We do it this way, using `disabled`, because appending stylesheets to the
+ document marks a restyle for that <style> element as needed, so we can't
+ accurately measure whether we've restyled or not.
+-->
+<style id="target" disabled></style>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+const TESTS = [
+ { selector: ".nonexistentClassScope", restyle: false },
+ { selector: ".nonexistentClassScope + div", restyle: true },
+ { selector: ".nonexistentClassScope div + div", restyle: false },
+ { selector: ".classScope", restyle: true },
+ { selector: ".classScope div", restyle: true },
+ { selector: "#idScope", restyle: true },
+ { selector: "#nonexistentIdScope", restyle: false },
+ { selector: "#nonexistentIdScope div + bar", restyle: false },
+ { selector: "baz", restyle: false },
+ { cssText: " ", restyle: false },
+ { cssText: "@keyframes foo { from { color: green } to { color: red } } #whatever { animation-name: foo; }", restyle: false },
+];
+
+for (const test of TESTS) {
+ let cssText = test.cssText ? test.cssText : (test.selector + " { color: green; }");
+ target.innerHTML = cssText;
+
+ document.body.offsetWidth;
+ const prevGeneration = utils.restyleGeneration;
+
+ target.disabled = true;
+
+ document.body.offsetWidth;
+ (test.restyle ? isnot : is)(utils.restyleGeneration, prevGeneration,
+ `Stylesheet removal with ${cssText} should ${test.restyle ? "have" : "not have"} caused a restyle`);
+
+ target.disabled = false; // Make the stylesheet effective.
+
+ if (test.selector) {
+ let element = document.querySelector(test.selector);
+ if (element) {
+ is(test.restyle, true, "How could we not expect a restyle?");
+ is(getComputedStyle(element).color, "rgb(0, 128, 0)",
+ "Element style should've changed appropriately");
+ }
+ }
+
+ document.body.offsetWidth;
+ (test.restyle ? isnot : is)(utils.restyleGeneration, prevGeneration,
+ `Stylesheet addition with ${cssText} should ${test.restyle ? "have" : "not have"} caused a restyle`);
+}
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_stylesheet_clone_font_face.html b/layout/style/test/test_stylesheet_clone_font_face.html
new file mode 100644
index 0000000000..e50bfec661
--- /dev/null
+++ b/layout/style/test/test_stylesheet_clone_font_face.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<link rel="stylesheet" href="data:text/css,@font-face { font-family: 'MarkA'; src: url(../fonts/markA.ttf); }">
+<link rel="stylesheet" href="data:text/css,@font-face { font-family: 'MarkA'; src: url(../fonts/markA.ttf); }">
+
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style>
+ body { font-family: "MarkA"; }
+</style>
+<div>ABC</div>
+<script>
+ function runTest() {
+ var links = document.getElementsByTagName("link");
+ links[0].sheet.cssRules[0].style.src = "../fonts/markB.ttf";
+
+ // Test that the cloned sheet is unaffected.
+ isnot(links[1].sheet.cssRules[0].style.src, "../fonts/markB.ttf", "Cloned sheet left unchanged.");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+</script>
+</html>
diff --git a/layout/style/test/test_supports_rules.html b/layout/style/test/test_supports_rules.html
new file mode 100644
index 0000000000..8b88fd7836
--- /dev/null
+++ b/layout/style/test/test_supports_rules.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=649740
+-->
+<head>
+ <title>Test for Bug 649740</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=649740">Mozilla Bug 649740</a>
+<p id="display1"></p>
+<p id="display2"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 649740 **/
+
+function condition(s) {
+ return s.replace(/^@supports\s*/, '').replace(/ \s*{\s*}\s*$/, '');
+}
+
+var styleSheetText =
+ "@supports(color: green){ }\n" +
+ "@supports (color: green) { }\n" +
+ "@supports ((color: green)) { }\n" +
+ "@supports (color: green) and (color: blue) { }\n" +
+ "@supports ( Font: 20px serif ! Important) { }";
+
+function runTest() {
+ var style = document.getElementById("style");
+ style.textContent = styleSheetText;
+
+ var sheet = style.sheet;
+
+ is(condition(sheet.cssRules[0].cssText), "(color: green)");
+ is(condition(sheet.cssRules[1].cssText), "(color: green)");
+ is(condition(sheet.cssRules[2].cssText), "((color: green))");
+ is(condition(sheet.cssRules[3].cssText), "(color: green) and (color: blue)");
+ is(condition(sheet.cssRules[4].cssText), "( Font: 20px serif ! Important)");
+
+ var cs1 = getComputedStyle(document.getElementById("display1"), "");
+ var cs2 = getComputedStyle(document.getElementById("display2"), "");
+ function check_balanced_condition(condition_inner, expected_match) {
+ style.textContent = "#display1, #display2 { text-decoration: overline }\n" +
+ "@supports " + condition_inner + "{\n" +
+ " #display1 { text-decoration: line-through }\n" +
+ "}\n" +
+ "#display2 { text-decoration: underline }\n";
+ is(cs1.textDecorationLine,
+ expected_match ? "line-through" : "overline",
+ "@supports condition \"" + condition_inner + "\" should " +
+ (expected_match ? "" : "NOT ") + "match");
+ is(cs2.textDecorationLine, "underline",
+ "@supports condition \"" + condition_inner + "\" should be balanced");
+ }
+
+ check_balanced_condition("not (color: green)", false);
+ check_balanced_condition("not (colour: green)", true);
+ check_balanced_condition("not(color: green)", false);
+ check_balanced_condition("not(colour: green)", false);
+ check_balanced_condition("not/* */(color: green)", false);
+ check_balanced_condition("not/* */(colour: green)", true);
+ check_balanced_condition("not /* */ (color: green)", false);
+ check_balanced_condition("not /* */ (colour: green)", true);
+ check_balanced_condition("(color: green) and (color: blue)", true);
+ check_balanced_condition("(color: green) /* */ /* */ and /* */ /* */ (color: blue)", true);
+ check_balanced_condition("(color: green) and(color: blue)", false);
+ check_balanced_condition("(color: green) and/* */(color: blue)", true);
+ check_balanced_condition("(color: green)and (color: blue)", true);
+ check_balanced_condition("(color: green) or (color: blue)", true);
+ check_balanced_condition("(color: green) /* */ /* */ or /* */ /* */ (color: blue)", true);
+ check_balanced_condition("(color: green) or(color: blue)", false);
+ check_balanced_condition("(color: green) or/* */(color: blue)", true);
+ check_balanced_condition("(color: green)or (color: blue)", true);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_system_font_serialization.html b/layout/style/test/test_system_font_serialization.html
new file mode 100644
index 0000000000..10fb54c45a
--- /dev/null
+++ b/layout/style/test/test_system_font_serialization.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=475214
+-->
+<head>
+ <title>Test for Bug 475214</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=475214">Mozilla Bug 475214</a>
+<p id="display"></p>
+<div id="content">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/* Helper copied from property_database.js */
+function IsCSSPropertyPrefEnabled(prefName)
+{
+ try {
+ if (SpecialPowers.getBoolPref(prefName)) {
+ return true;
+ }
+ } catch (ex) {
+ ok(false, "Failed to look up property-controlling pref '" +
+ prefName + "' (" + ex + ")");
+ }
+
+ return false;
+}
+
+/** Test for Bug 475214 **/
+
+var e = document.getElementById("content");
+var s = e.style;
+
+e.style.font = "menu";
+is(e.style.cssText, "font: menu;", "serialize system font alone");
+is(e.style.font, "menu", "font getter returns value");
+
+e.style.fontFamily = "inherit";
+is(e.style.font, "", "font getter should be empty");
+
+e.style.font = "message-box";
+is(e.style.cssText, "font: message-box;", "serialize system font alone");
+is(e.style.font, "message-box", "font getter returns value");
+
+e.setAttribute("style", "font-weight:bold;font:caption;line-height:3;");
+is(e.style.font, "", "font getter should be empty");
+
+e.setAttribute("style", "font: menu; font-weight: -moz-use-system-font");
+is(e.style.cssText, "font: menu;", "serialize system font alone");
+is(e.style.font, "menu", "font getter returns value");
+
+e.setAttribute("style", "font: inherit; font-family: Helvetica;");
+EXPECTED_DECLS = [
+ "font-family: Helvetica;",
+ "font-feature-settings: inherit;",
+ "font-kerning: inherit;",
+ "font-language-override: inherit;",
+ "font-size-adjust: inherit;",
+ "font-size: inherit;",
+ "font-stretch: inherit;",
+ "font-style: inherit;",
+ "font-variant: inherit;",
+ "font-weight: inherit;",
+ "line-height: inherit;",
+];
+if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) {
+ EXPECTED_DECLS.push("font-optical-sizing: inherit;");
+ EXPECTED_DECLS.push("font-variation-settings: inherit;");
+}
+EXPECTED_DECLS = EXPECTED_DECLS.sort().join(" ");
+let sortedDecls = e.style.cssText.split(/ (?=font-|line-)/).sort().join(" ");
+is(sortedDecls, EXPECTED_DECLS, "don't serialize system font for font:inherit");
+is(e.style.font, "", "font getter returns nothing");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_text_decoration_shorthands.html b/layout/style/test/test_text_decoration_shorthands.html
new file mode 100644
index 0000000000..d2cfed6667
--- /dev/null
+++ b/layout/style/test/test_text_decoration_shorthands.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test parsing of text-decoration shorthands</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+
+<script>
+
+var initial_values = {
+ textDecorationColor: "rgb(255, 0, 255)",
+ textDecorationLine: "none",
+ textDecorationStyle: "solid",
+ textDecorationThickness: "auto",
+};
+
+// For various specified values of the text-decoration shorthand,
+// test the computed values of the corresponding longhands.
+var text_decoration_test_cases = [
+ {
+ specified: "none",
+ },
+ {
+ specified: "red",
+ textDecorationColor: "rgb(255, 0, 0)",
+ },
+ {
+ specified: "line-through",
+ textDecorationLine: "line-through",
+ },
+ {
+ specified: "dotted",
+ textDecorationStyle: "dotted",
+ },
+ {
+ specified: "20px",
+ textDecorationThickness: "20px",
+ },
+ {
+ specified: "auto",
+ textDecorationThickness: "auto",
+ },
+ {
+ specified: "from-font",
+ textDecorationThickness: "from-font",
+ },
+ {
+ specified: "#00ff00 underline",
+ textDecorationColor: "rgb(0, 255, 0)",
+ textDecorationLine: "underline",
+ },
+ {
+ specified: "#ffff00 wavy",
+ textDecorationColor: "rgb(255, 255, 0)",
+ textDecorationStyle: "wavy",
+ },
+ {
+ specified: "overline double",
+ textDecorationLine: "overline",
+ textDecorationStyle: "double",
+ },
+ {
+ specified: "red underline solid",
+ textDecorationColor: "rgb(255, 0, 0)",
+ textDecorationLine: "underline",
+ textDecorationStyle: "solid",
+ },
+ {
+ specified: "double overline blue",
+ textDecorationColor: "rgb(0, 0, 255)",
+ textDecorationLine: "overline",
+ textDecorationStyle: "double",
+ },
+ {
+ specified: "solid underline 10px",
+ textDecorationStyle: "solid",
+ textDecorationLine: "underline",
+ textDecorationThickness: "10px",
+ },
+ {
+ specified: "line-through blue 200px",
+ textDecorationLine: "line-through",
+ textDecorationColor: "rgb(0, 0, 255)",
+ textDecorationThickness: "200px",
+ },
+ {
+ specified: "underline wavy red 0",
+ textDecorationLine: "underline",
+ textDecorationStyle: "wavy",
+ textDecorationColor: "rgb(255, 0, 0)",
+ textDecorationThickness: "0px",
+ },
+ {
+ specified: "overline -30px",
+ textDecorationLine: "overline",
+ textDecorationThickness: "-30px",
+ },
+ {
+ specified: "underline red from-font",
+ textDecorationLine: "underline",
+ textDecorationColor: "rgb(255, 0, 0)",
+ textDecorationThickness: "from-font",
+ },
+];
+
+function run_tests(test_cases, shorthand, subproperties) {
+ test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ // Set text color to test initial value of text-decoration-color
+ // (currentColor).
+ element.style.color = "#ff00ff";
+ element.style[shorthand] = test_case.specified;
+ var computed = window.getComputedStyle(element);
+ subproperties.forEach(function(longhand) {
+ assert_equals(
+ computed[longhand],
+ test_case[longhand] || initial_values[longhand],
+ longhand
+ );
+ });
+ }, "test parsing of 'text-decoration: " + test_case.specified + "'");
+ });
+}
+
+run_tests(text_decoration_test_cases, "textDecoration", [
+ "textDecorationColor", "textDecorationLine", "textDecorationStyle", "textDecorationThickness"]);
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions.html b/layout/style/test/test_transitions.html
new file mode 100644
index 0000000000..c26ba9be8f
--- /dev/null
+++ b/layout/style/test/test_transitions.html
@@ -0,0 +1,787 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display p { margin-top: 0; margin-bottom: 0; }
+ #display .before, #display .after {
+ width: -moz-fit-content; border: 1px solid black;
+ }
+ #display .before::before, #display .after::after {
+ display: block;
+ width: 0;
+ text-indent: 0;
+ }
+ #display .before.started::before, #display .after.started::after {
+ width: 100px;
+ text-indent: 100px;
+ transition: 8s width ease-in-out, 8s text-indent ease-in-out;
+ }
+ #display .before::before {
+ content: "Before";
+ }
+ #display .after::after {
+ content: "After";
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>
+<div id="display">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 435441 **/
+
+// Run tests simultaneously so we don't have to take up too much time.
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+var gTestsRunning = 0;
+function TestStarted() { ++gTestsRunning; }
+function TestFinished() { if (--gTestsRunning == 0) SimpleTest.finish(); }
+
+// An array of arrays of functions to be called at the outer index number
+// of seconds after the present.
+var gFutureCalls = [];
+
+function add_future_call(index, func)
+{
+ if (!(index in gFutureCalls)) {
+ gFutureCalls[index] = [];
+ }
+ gFutureCalls[index].push(func);
+ TestStarted();
+}
+var gStartTime1, gStartTime2;
+var gCurrentTime;
+var gSetupComplete = false;
+
+function process_future_calls(index)
+{
+ var calls = gFutureCalls[index];
+ if (!calls)
+ return;
+ gCurrentTime = Date.now();
+ for (var i = 0; i < calls.length; ++i) {
+ calls[i]();
+ TestFinished();
+ }
+}
+
+var timingFunctions = {
+ // a map from the value of 'transition-timing-function' to an array of
+ // the portions this function yields at 0 (always 0), 1/4, 1/2, and
+ // 3/4 and all (always 1) of the way through the time of the
+ // transition. Each portion is represented as a value and an
+ // acceptable error tolerance (based on a time error of 1%) for that
+ // value.
+
+ // ease
+ "ease": bezier(0.25, 0.1, 0.25, 1),
+ "cubic-bezier(0.25, 0.1, 0.25, 1.0)": bezier(0.25, 0.1, 0.25, 1),
+
+ // linear and various synonyms for it
+ "linear": function(x) { return x; },
+ "cubic-bezier(0.0, 0.0, 1.0, 1.0)": function(x) { return x; },
+ "cubic-bezier(0, 0, 1, 1)": function(x) { return x; },
+ "cubic-bezier(0, 0, 0, 0.0)": function(x) { return x; },
+ "cubic-bezier(1.0, 1, 0, 0)": function(x) { return x; },
+
+ // ease-in
+ "ease-in": bezier(0.42, 0, 1, 1),
+ "cubic-bezier(0.42, 0, 1.0, 1.0)": bezier(0.42, 0, 1, 1),
+
+ // ease-out
+ "ease-out": bezier(0, 0, 0.58, 1),
+ "cubic-bezier(0, 0, 0.58, 1.0)": bezier(0, 0, 0.58, 1),
+
+ // ease-in-out
+ "ease-in-out": bezier(0.42, 0, 0.58, 1),
+ "cubic-bezier(0.42, 0, 0.58, 1.0)": bezier(0.42, 0, 0.58, 1),
+
+ // other cubic-bezier values
+ "cubic-bezier(0.4, 0.1, 0.7, 0.95)": bezier(0.4, 0.1, 0.7, 0.95),
+ "cubic-bezier(1, 0, 0, 1)": bezier(1, 0, 0, 1),
+ "cubic-bezier(0, 1, 1, 0)": bezier(0, 1, 1, 0),
+
+};
+
+var div = document.getElementById("display");
+
+// Set up all the elements on which we are going to initiate transitions.
+
+// We have two reference elements to check the expected timing range.
+// They both have 16s linear transitions from 0 to 1000px.
+// This means they move through 62.5 pixels per second.
+const REF_PX_PER_SEC = 62.5;
+function make_reference_p() {
+ let p = document.createElement("p");
+ p.appendChild(document.createTextNode("reference"));
+ p.style.textIndent = "0px";
+ p.style.transition = "16s text-indent linear";
+ div.appendChild(p);
+ return p;
+}
+var earlyref = make_reference_p();
+var earlyrefcs = getComputedStyle(earlyref, "");
+
+// Test all timing functions using a set of 8-second transitions, which
+// we check at times 0, 2s, 4s, 6s, and 8s.
+var tftests = [];
+for (let tf in timingFunctions) {
+ let p = document.createElement("p");
+ let t = document.createTextNode("transition-timing-function: " + tf);
+ p.appendChild(t);
+ p.style.textIndent = "0px";
+ p.style.transition = "8s text-indent linear";
+ p.style.transitionTimingFunction = tf;
+ div.appendChild(p);
+ is(getComputedStyle(p, "").textIndent, "0px",
+ "should be zero before changing value");
+ tftests.push([ p, tf ]);
+}
+
+// Check that the timing function continues even when we restyle in the
+// middle.
+var interrupt_tests = [];
+for (var restyleParent of [true, false]) {
+ for (let itime = 2; itime < 8; itime += 2) {
+ let p = document.createElement("p");
+ let t = document.createTextNode("interrupt on " +
+ (restyleParent ? "parent" : "node itself") +
+ " at " + itime + "s");
+ p.appendChild(t);
+ p.style.textIndent = "0px";
+ p.style.transition = "8s text-indent cubic-bezier(0, 1, 1, 0)";
+ if (restyleParent) {
+ let d = document.createElement("div");
+ d.appendChild(p);
+ div.appendChild(d);
+ } else {
+ div.appendChild(p);
+ }
+ is(getComputedStyle(p, "").textIndent, "0px",
+ "should be zero before changing value");
+ setTimeout("interrupt_tests[" + interrupt_tests.length + "]" +
+ "[0]" + (restyleParent ? ".parentNode" : "") +
+ ".style.color = 'blue';" +
+ "check_interrupt_tests()", itime*1000);
+ interrupt_tests.push([ p, itime ]);
+ }
+}
+
+// Test transition-delay values of -4s through 4s on a 4s transition
+// with 'ease-out' timing function.
+var delay_tests = {};
+for (let d = -4; d <= 4; ++d) {
+ let p = document.createElement("p");
+ let delay = d + "s";
+ let t = document.createTextNode("transition-delay: " + delay);
+ p.appendChild(t);
+ p.style.marginLeft = "0px";
+ p.style.transition = "4s margin-left ease-out " + delay;
+ div.appendChild(p);
+ is(getComputedStyle(p, "").marginLeft, "0px",
+ "should be zero before changing value");
+ delay_tests[d] = p;
+}
+
+// Test transition-delay values of -4s through 4s on a 4s transition
+// with duration of zero.
+var delay_zero_tests = {};
+for (let d = -4; d <= 4; ++d) {
+ let p = document.createElement("p");
+ let delay = d + "s";
+ let t = document.createTextNode("transition-delay: " + delay);
+ p.appendChild(t);
+ p.style.marginLeft = "0px";
+ p.style.transition = "0s margin-left linear " + delay;
+ div.appendChild(p);
+ is(getComputedStyle(p, "").marginLeft, "0px",
+ "should be zero before changing value");
+ delay_zero_tests[d] = p;
+}
+
+// Test that changing the value on an already-running transition to the
+// value it currently happens to have resets the transition.
+function make_reset_test(transition, description)
+{
+ let p = document.createElement("p");
+ let t = document.createTextNode(description);
+ p.appendChild(t);
+ p.style.marginLeft = "0px";
+ p.style.transition = transition;
+ div.appendChild(p);
+ is(getComputedStyle(p, "").marginLeft, "0px",
+ "should be zero before changing value");
+ return p;
+}
+var reset_test = make_reset_test("4s margin-left ease-out 4s", "transition-delay reset to starting point");
+var reset_test_reference = make_reset_test("4s margin-left linear -3s", "reference for previous test (reset test)");
+
+// Test that transitions on descendants start correctly when the
+// inherited value is itself transitioning. In other words, when
+// ancestor and descendant both have a transition for the same property,
+// and the descendant inherits the property from the ancestor, the
+// descendant's transition starts as specified, based on the concepts of
+// the before-change style, the after-change style, and the
+// after-transition style.
+var descendant_tests = [
+ { parent_transition: "",
+ child_transition: "4s text-indent" },
+ { parent_transition: "4s text-indent",
+ child_transition: "" },
+ { parent_transition: "4s text-indent",
+ child_transition: "16s text-indent" },
+ { parent_transition: "4s text-indent",
+ child_transition: "1s text-indent" },
+ { parent_transition: "8s letter-spacing",
+ child_transition: "4s text-indent" },
+ { parent_transition: "4s text-indent",
+ child_transition: "8s letter-spacing" },
+ { parent_transition: "4s text-indent",
+ child_transition: "8s all" },
+ { parent_transition: "8s text-indent",
+ child_transition: "4s all" },
+ // examples with positive and negative delay
+ { parent_transition: "4s text-indent 1s",
+ child_transition: "8s text-indent" },
+ { parent_transition: "4s text-indent -1s",
+ child_transition: "8s text-indent" }
+];
+
+for (let i in descendant_tests) {
+ let test = descendant_tests[i];
+ test.parentNode = document.createElement("div");
+ test.childNode = document.createElement("p");
+ test.parentNode.appendChild(test.childNode);
+ test.childNode.appendChild(document.createTextNode(
+ "parent with \"" + test.parent_transition + "\" and " +
+ "child with \"" + test.child_transition + "\""));
+ test.parentNode.style.transition = test.parent_transition;
+ test.childNode.style.transition = test.child_transition;
+ test.parentNode.style.textIndent = "50px"; // transition from 50 to 150
+ test.parentNode.style.letterSpacing = "10px"; // transition from 10 to 5
+ div.appendChild(test.parentNode);
+ var parentCS = getComputedStyle(test.parentNode, "");
+ var childCS = getComputedStyle(test.childNode, "");
+ is(parentCS.textIndent, "50px",
+ "parent text-indent should be 50px before changing");
+ is(parentCS.letterSpacing, "10px",
+ "parent letter-spacing should be 10px before changing");
+ is(childCS.textIndent, "50px",
+ "child text-indent should be 50px before changing");
+ is(childCS.letterSpacing, "10px",
+ "child letter-spacing should be 10px before changing");
+ test.childCS = childCS;
+}
+
+// For all of these transitions, the transition for margin-left should
+// have a duration of 8s, and the default timing function (ease) and
+// delay (0).
+// This is because we're implementing the proposal in
+// http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
+var number_tests = [
+ { style: "transition: 4s margin, 8s margin-left" },
+ { style: "transition: 4s margin-left, 8s margin" },
+ { style: "transition-property: margin-left; " +
+ "transition-duration: 8s, 2s" },
+ { style: "transition-property: margin-left, margin-left; " +
+ "transition-duration: 2s, 8s" },
+ { style: "transition-property: margin-left, margin-left, margin-left; " +
+ "transition-duration: 8s, 2s" },
+ { style: "transition-property: margin-left; " +
+ "transition-duration: 8s, 16s" },
+ { style: "transition-property: margin-left, margin-left; " +
+ "transition-duration: 16s, 8s" },
+ { style: "transition-property: margin-left, margin-left, margin-left; " +
+ "transition-duration: 8s, 16s" },
+ { style: "transition-property: text-indent,word-spacing,margin-left; " +
+ "transition-duration: 8s; " +
+ "transition-delay: 0, 8s" },
+ { style: "transition-property: text-indent,word-spacing,margin-left; " +
+ "transition-duration: 8s, 16s; " +
+ "transition-delay: 8s, 8s, 0, 8s, 8s, 8s" },
+];
+
+for (let i in number_tests) {
+ let test = number_tests[i];
+ let p = document.createElement("p");
+ p.setAttribute("style", test.style);
+ let t = document.createTextNode(test.style);
+ p.appendChild(t);
+ p.style.marginLeft = "100px";
+ div.appendChild(p);
+ is(getComputedStyle(p, "").marginLeft, "100px",
+ "should be 100px before changing value");
+ test.node = p;
+}
+
+// Test transitions that are also from-display:none, to-display:none, and
+// display:none throughout.
+var from_none_test, to_none_test, always_none_test;
+function make_display_test(initially_none, text)
+{
+ let p = document.createElement("p");
+ p.appendChild(document.createTextNode(text));
+ p.style.textIndent = "0px";
+ p.style.transition = "8s text-indent ease-in-out";
+ if (initially_none)
+ p.style.display = "none";
+ div.appendChild(p);
+ return p;
+}
+from_none_test = make_display_test(true, "transition from display:none");
+to_none_test = make_display_test(false, "transition to display:none");
+always_none_test = make_display_test(true, "transition always display:none");
+var display_tests = [ from_none_test, to_none_test, always_none_test ];
+
+// Test transitions on pseudo-elements
+var before_test, after_test;
+function make_pseudo_elem_test(pseudo)
+{
+ let p = document.createElement("p");
+ p.className = pseudo;
+ div.appendChild(p);
+ return {"pseudo": pseudo, element: p};
+}
+before_test = make_pseudo_elem_test("before");
+after_test = make_pseudo_elem_test("after");
+var pseudo_element_tests = [ before_test, after_test ];
+
+// FIXME (Bug 522599): Test a transition that reverses partway through.
+
+var lateref = make_reference_p();
+var laterefcs = getComputedStyle(lateref, "");
+
+// flush style changes
+var x = getComputedStyle(div, "").width;
+
+// Start our timer as close as possible to when we start the first
+// transition.
+// Do not use setInterval because once it gets off in time, it stays off.
+for (let i = 1; i <= 8; ++i) {
+ setTimeout(process_future_calls, i * 1000, i);
+}
+gStartTime1 = Date.now(); // set before any transitions have started
+
+// Start all the transitions.
+earlyref.style.textIndent = "1000px";
+for (let test in tftests) {
+ let p = tftests[test][0];
+ p.style.textIndent = "100px";
+}
+for (let test in interrupt_tests) {
+ let p = interrupt_tests[test][0];
+ p.style.textIndent = "100px";
+}
+for (let d in delay_tests) {
+ let p = delay_tests[d];
+ p.style.marginLeft = "100px";
+}
+for (let d in delay_zero_tests) {
+ let p = delay_zero_tests[d];
+ p.style.marginLeft = "100px";
+}
+reset_test.style.marginLeft = "100px";
+reset_test_reference.style.marginLeft = "100px";
+for (let i in descendant_tests) {
+ let test = descendant_tests[i];
+ test.parentNode.style.textIndent = "150px";
+ test.parentNode.style.letterSpacing = "5px";
+}
+for (let i in number_tests) {
+ let test = number_tests[i];
+ test.node.style.marginLeft = "50px";
+}
+from_none_test.style.textIndent = "100px";
+from_none_test.style.display = "";
+to_none_test.style.textIndent = "100px";
+to_none_test.style.display = "none";
+always_none_test.style.textIndent = "100px";
+for (let i in pseudo_element_tests) {
+ let test = pseudo_element_tests[i];
+ test.element.classList.add("started");
+}
+lateref.style.textIndent = "1000px";
+
+// flush style changes
+x = getComputedStyle(div, "").width;
+
+gStartTime2 = Date.now(); // set after all transitions have started
+gCurrentTime = gStartTime2;
+
+/**
+ * Assert that a transition whose timing function yields the bezier
+ * |func|, running from |start_time| to |end_time| (both in seconds
+ * relative to when the transitions were started) should have produced
+ * computed value |cval| given that the transition was from
+ * |start_value| to |end_value| (both numbers in CSS pixels).
+ */
+function check_transition_value(func, start_time, end_time,
+ start_value, end_value, cval, desc,
+ xfail)
+{
+ /**
+ * Compute the value at a given time |elapsed|, by normalizing the
+ * input to the timing function using start_time and end_time and
+ * then turning the output into a value using start_value and
+ * end_value.
+ *
+ * The |error_direction| argument should be either -1, 0, or 1,
+ * suggesting adding on a little bit of error, to allow for the
+ * cubic-bezier calculation being an approximation. The amount of
+ * error is proportional to the slope of the timing function, since
+ * the error is added to the *input* of the timing function (after
+ * normalization to 0-1 based on start_time and end_time).
+ */
+ function value_at(elapsed, error_direction) {
+ var time_portion = (elapsed - start_time) / (end_time - start_time);
+ if (time_portion < 0)
+ time_portion = 0;
+ else if (time_portion > 1)
+ time_portion = 1;
+ // Assume a small error since bezier computation can be off slightly.
+ // (This test's computation is probably more accurate than Mozilla's.)
+ var value_portion = func(time_portion + error_direction * 0.0005);
+ if (value_portion < 0)
+ value_portion = 0;
+ else if (value_portion > 1)
+ value_portion = 1;
+ var value = (1 - value_portion) * start_value + value_portion * end_value;
+ if (start_value > end_value)
+ error_direction = -error_direction;
+ // Computed values get rounded to 1/60th of a pixel.
+ return value + error_direction * 0.02;
+ }
+
+ var time_range; // in seconds
+ var uns_range; // |range| before being sorted (so errors give it
+ // in the original order
+ if (!gSetupComplete) {
+ // No timers involved
+ time_range = [0, 0];
+ if (start_time < 0) {
+ uns_range = [ value_at(0, -1), value_at(0, 1) ];
+ } else {
+ var val = value_at(0, 0);
+ uns_range = [val, val];
+ }
+ } else {
+ time_range = [ px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC,
+ px_to_num(laterefcs.textIndent) / REF_PX_PER_SEC ];
+ // seconds
+ uns_range = [ value_at(time_range[0], -1),
+ value_at(time_range[1], 1) ];
+ }
+ var range = uns_range.concat(). /* concat to clone array */
+ sort(function compareNumbers(a,b) { return a - b; });
+ var actual = px_to_num(cval);
+
+ var fn = ok;
+ if (xfail && xfail(range))
+ fn = todo;
+
+ fn(range[0] <= actual && actual <= range[1],
+ desc + ": computed value " + cval + " should be between " +
+ uns_range[0].toFixed(6) + "px and " + uns_range[1].toFixed(6) +
+ "px at time between " + time_range[0] + "s and " + time_range[1] + "s.");
+}
+
+function check_ref_range()
+{
+ // This is the only test where we compare the progress of the
+ // transitions to an actual time; we need considerable tolerance at
+ // the low end (we are using half a second).
+ var expected_range = [ (gCurrentTime - gStartTime2 - 40) / 16,
+ (Date.now() - gStartTime1 + 20) / 16 ];
+ if (expected_range[0] > 1000) {
+ expected_range[0] = 1000;
+ }
+ if (expected_range[1] > 1000) {
+ expected_range[1] = 1000;
+ }
+ function check(desc, value) {
+ // The timing on the unit test VMs is not reliable, so make this
+ // test report PASS when it succeeds and TODO when it fails.
+ var passed = expected_range[0] <= value && value <= expected_range[1];
+ (passed ? ok : todo)(passed,
+ desc + ": computed value " + value + "px should be between " +
+ expected_range[0].toFixed(6) + "px and " +
+ expected_range[1].toFixed(6) + "px at time between " +
+ expected_range[0]/REF_PX_PER_SEC + "s and " +
+ expected_range[1]/REF_PX_PER_SEC + "s.");
+ }
+ check("early reference", px_to_num(earlyrefcs.textIndent));
+ check("late reference", px_to_num(laterefcs.textIndent));
+}
+
+for (let i = 1; i <= 8; ++i) {
+ add_future_call(i, check_ref_range);
+}
+
+function check_tf_test()
+{
+ for (var test in tftests) {
+ var p = tftests[test][0];
+ var tf = tftests[test][1];
+
+ check_transition_value(timingFunctions[tf], 0, 8, 0, 100,
+ getComputedStyle(p, "").textIndent,
+ "timing function test for timing function " + tf);
+
+ }
+
+ check_interrupt_tests();
+}
+
+check_tf_test();
+add_future_call(2, check_tf_test);
+add_future_call(4, check_tf_test);
+add_future_call(6, check_tf_test);
+add_future_call(8, check_tf_test);
+
+function check_interrupt_tests()
+{
+ for (let test in interrupt_tests) {
+ var p = interrupt_tests[test][0];
+ var itime = interrupt_tests[test][1];
+
+ check_transition_value(timingFunctions["cubic-bezier(0, 1, 1, 0)"],
+ 0, 8, 0, 100,
+ getComputedStyle(p, "").textIndent,
+ "interrupt " +
+ (p.parentNode == div ? "" : "on parent ") +
+ "test for time " + itime + "s");
+ }
+}
+
+// check_interrupt_tests is called from check_tf_test and from
+// where we reset the interrupts
+
+function check_delay_test(time)
+{
+ let tf = timingFunctions["ease-out"];
+ for (let d in delay_tests) {
+ let p = delay_tests[d];
+
+ check_transition_value(tf, Number(d), Number(d) + 4, 0, 100,
+ getComputedStyle(p, "").marginLeft,
+ "delay test for delay " + d + "s");
+ }
+}
+
+check_delay_test(0);
+for (let i = 1; i <= 8; ++i) {
+ add_future_call(i, check_delay_test);
+}
+
+function check_delay_zero_test(time)
+{
+ for (let d in delay_zero_tests) {
+ let p = delay_zero_tests[d];
+
+ time_range = [ px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC,
+ px_to_num(laterefcs.textIndent) / REF_PX_PER_SEC ];
+ var m = getComputedStyle(p, "").marginLeft;
+ var desc = "delay_zero test for delay " + d + "s";
+ if (time_range[0] < d && time_range[1] < d) {
+ is(m, "0px", desc);
+ } else if ((time_range[0] > d && time_range[1] > d) ||
+ (d == 0 && time == 0)) {
+ is(m, "100px", desc);
+ }
+ }
+}
+
+check_delay_zero_test(0);
+for (let i = 1; i <= 8; ++i) {
+ add_future_call(i, check_delay_zero_test);
+}
+
+function reset_reset_test(time)
+{
+ reset_test.style.marginLeft = "0px";
+}
+function check_reset_test(time)
+{
+ is(getComputedStyle(reset_test, "").marginLeft, "0px",
+ "reset test value at time " + time + "s.");
+}
+check_reset_test(0);
+// reset the reset test right now so we don't have to worry about clock skew
+// To make sure that this is valid, check that a pretty-much-identical test is
+// already transitioning.
+is(getComputedStyle(reset_test_reference, "").marginLeft, "75px",
+ "reset test reference value");
+reset_reset_test();
+check_reset_test(0);
+for (let i = 1; i <= 8; ++i) {
+ (function(j) {
+ add_future_call(j, function() { check_reset_test(j); });
+ })(i);
+}
+
+check_descendant_tests();
+add_future_call(2, check_descendant_tests);
+add_future_call(6, check_descendant_tests);
+
+function check_descendant_tests() {
+ // text-indent: transition from 50px to 150px
+ // letter-spacing: transition from 10px to 5px
+ var values = {};
+ values["text-indent"] = [ 50, 150 ];
+ values["letter-spacing"] = [ 10, 5 ];
+ let tf = timingFunctions.ease;
+
+ var time = px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC;
+
+ for (let i in descendant_tests) {
+ let test = descendant_tests[i];
+
+ /* ti=text-indent, ls=letter-spacing */
+ var child_ti_duration = 0;
+ var child_ls_duration = 0;
+ var child_ti_delay = 0;
+ var child_ls_delay = 0;
+
+ if (test.parent_transition != "") {
+ var props = test.parent_transition.split(" ");
+ var duration = parseInt(props[0]);
+ var delay = (props.length > 2) ? parseInt(props[2]) : 0;
+ var property = props[1];
+ if (property == "text-indent") {
+ child_ti_duration = duration;
+ child_ti_delay = delay;
+ } else if (property == "letter-spacing") {
+ child_ls_duration = duration;
+ child_ls_delay = delay;
+ } else {
+ ok(false, "fix this test (unexpected transition-property " +
+ property + " on parent)");
+ }
+ }
+
+ if (test.child_transition != "") {
+ var props = test.child_transition.split(" ");
+ var duration = parseInt(props[0]);
+ var delay = (props.length > 2) ? parseInt(props[2]) : 0;
+ var property = props[1];
+ if (property != "text-indent" && property != "letter-spacing" &&
+ property != "all") {
+ ok(false, "fix this test (unexpected transition-property " +
+ property + " on child)");
+ }
+
+ // Override the parent's transition with the child's as long
+ // as the child transition is still running.
+ if (property != "letter-spacing" && duration + delay > time) {
+ child_ti_duration = duration;
+ child_ti_delay = delay;
+ }
+ if (property != "text-indent" && duration + delay > time) {
+ child_ls_duration = duration;
+ child_ls_delay = delay;
+ }
+ }
+
+ var time_portions = {
+ "text-indent":
+ { duration: child_ti_duration, delay: child_ti_delay },
+ "letter-spacing":
+ { duration: child_ls_duration, delay: child_ls_delay },
+ };
+
+ for (var prop in {"text-indent": true, "letter-spacing": true}) {
+ var time_portion = time_portions[prop];
+
+ if (time_portion.duration == 0) {
+ time_portion.duration = 0.01;
+ time_portion.delay = -1;
+ }
+
+ check_transition_value(tf, time_portion.delay,
+ time_portion.delay + time_portion.duration,
+ values[prop][0], values[prop][1],
+ test.childCS.getPropertyValue(prop),
+ `descendant test #${Number(i)+1}, property ${prop}`);
+ }
+ }
+}
+
+function check_number_tests()
+{
+ let tf = timingFunctions.ease;
+ for (let d in number_tests) {
+ let test = number_tests[d];
+ let p = test.node;
+
+ check_transition_value(tf, 0, 8, 100, 50,
+ getComputedStyle(p, "").marginLeft,
+ "number of transitions test for style " +
+ test.style);
+ }
+}
+
+check_number_tests(0);
+add_future_call(2, check_number_tests);
+add_future_call(4, check_number_tests);
+add_future_call(6, check_number_tests);
+add_future_call(8, check_number_tests);
+
+function check_display_tests(time)
+{
+ for (let i in display_tests) {
+ let p = display_tests[i];
+
+ // There is no transition if the old or new style is display:none, so
+ // the computed value is always the end value.
+ var computedValue = getComputedStyle(p, "").textIndent;
+ is(computedValue, "100px",
+ "display test for test with " + p.childNodes[0].data +
+ ": computed value " + computedValue + " should be 100px.");
+ }
+}
+
+check_display_tests(0);
+add_future_call(2, function() { check_display_tests(2); });
+add_future_call(4, function() { check_display_tests(4); });
+add_future_call(6, function() { check_display_tests(6); });
+add_future_call(8, function() { check_display_tests(8); });
+
+function check_pseudo_element_tests(time)
+{
+ let tf = timingFunctions["ease-in-out"];
+ for (let i in pseudo_element_tests) {
+ let test = pseudo_element_tests[i];
+
+ check_transition_value(tf, 0, 8, 0, 100,
+ getComputedStyle(test.element, "").width,
+ "::"+test.pseudo+" test");
+ check_transition_value(tf, 0, 8, 0, 100,
+ getComputedStyle(test.element,
+ "::"+test.pseudo).textIndent,
+ "::"+test.pseudo+" indent test");
+ }
+}
+check_pseudo_element_tests(0);
+add_future_call(2, function() { check_pseudo_element_tests(2); });
+add_future_call(4, function() { check_pseudo_element_tests(4); });
+add_future_call(6, function() { check_pseudo_element_tests(6); });
+add_future_call(8, function() { check_pseudo_element_tests(8); });
+
+gSetupComplete = true;
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_and_reframes.html b/layout/style/test/test_transitions_and_reframes.html
new file mode 100644
index 0000000000..dfc16e54d1
--- /dev/null
+++ b/layout/style/test/test_transitions_and_reframes.html
@@ -0,0 +1,298 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=625289
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 625289</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ :root,
+ #e1, #e2 > div,
+ #b1::before, #b2 > div::before,
+ #a1::after, #a2 > div::after {
+ margin-left: 0;
+ }
+ :root.t,
+ #e1.t, #e2.t > div,
+ #b1.t::before, #b2.t > div::before,
+ #a1.t::after, #a2.t > div::after {
+ transition: margin-left linear 1s;
+ }
+ #b1::before, #b2 > div::before,
+ #a1::after, #a2 > div::after {
+ content: "x";
+ display: block;
+ }
+ :root.m,
+ #e1.m, #e2.m > div,
+ #b1.m::before, #b2.m > div::before,
+ #a1.m::after, #a2.m > div::after {
+ margin-left: 100px;
+ }
+ .o { overflow: hidden }
+ .n { display: none }
+
+ #fline { color: blue; font-size: 20px; width: 800px; }
+ #fline::first-line { color: yellow }
+ #fline.narrow { width: 50px }
+ #fline i { transition: color linear 1s }
+
+ #flexboxtest #flex { display: flex; flex-direction: column }
+ #flexboxtest #flextransition { color: blue; transition: color 5s linear }
+
+ #flexboxtest #flexkid[newstyle] { resize: both }
+ #flexboxtest #flextransition[newstyle] { color: yellow }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=625289">Mozilla Bug 625289</a>
+<div id="container"></div>
+<div id="fline">
+ This text has an <i>i element</i> in it.
+</div>
+<div id="flexboxtest">
+ <div id="flex">
+ hello
+ <span id="flexkid">this appears</span>
+ hello
+ <div id="flextransition">color transition</div>
+ </div>
+</div>
+<pre id="test">
+<script>
+"use strict";
+
+function advance_clock(milliseconds) {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
+}
+
+var container = document.getElementById("container");
+
+function make_elements(idName, child) {
+ var e = document.createElement("div");
+ e.setAttribute("id", idName);
+ if (child) {
+ e.appendChild(document.createElement("div"));
+ }
+ container.appendChild(e);
+ return e;
+}
+
+function assert_margin_at_quarter(element, pseudo, passes)
+{
+ var desc;
+ var useParent = false;
+ if (element == document.documentElement) {
+ desc = "root element";
+ } else if (element.id) {
+ desc = "element " + element.id;
+ } else {
+ desc = "child of element " + element.parentNode.id;
+ useParent = true;
+ }
+ var classes = (useParent ? element.parentNode : element).getAttribute("class");
+ if (classes) {
+ desc += " (classes: " + classes + ")";
+ }
+ if (pseudo) {
+ desc += " " + pseudo + " pseudo-element";
+ }
+ (passes ? is : todo_is)(getComputedStyle(element, pseudo).marginLeft, "25px",
+ "margin of " + desc);
+}
+
+function do_test(test)
+{
+ var expected_props = [ "element", "test_child", "pseudo", "passes",
+ "dynamic_change_transition", "start_from_none" ];
+ for (var propidx in expected_props) {
+ if (! expected_props[propidx] in test) {
+ ok(false, "expected " + expected_props[propidx] + " on test object");
+ }
+ }
+
+ var e;
+ if (typeof(test.element) == "string") {
+ e = make_elements(test.element, test.test_child);
+ } else {
+ if (test.test_child) {
+ ok(false, "test_child unexpected");
+ }
+ e = test.element;
+ }
+
+ var target = test.test_child ? e.firstChild : e;
+
+ if (!test.dynamic_change_transition) {
+ e.classList.add("t");
+ }
+ if (test.start_from_none) {
+ e.classList.add("n");
+ }
+
+ advance_clock(100);
+ e.classList.add("m");
+ e.classList.add("o");
+ if (test.dynamic_change_transition) {
+ e.classList.add("t");
+ }
+ if (test.start_from_none) {
+ e.classList.remove("n");
+ }
+ advance_clock(0);
+ advance_clock(250);
+ assert_margin_at_quarter(target, test.pseudo, test.passes);
+ if (typeof(test.element) == "string") {
+ e.remove();
+ } else {
+ target.style.transition = "";
+ target.removeAttribute("class");
+ }
+}
+
+advance_clock(0);
+
+var tests = [
+ { element:"e1", test_child:false, pseudo:"", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"e2", test_child:true, pseudo:"", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"b1", test_child:false, pseudo:"::before", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"b2", test_child:true, pseudo:"::before", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"a1", test_child:false, pseudo:"::after", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"a2", test_child:true, pseudo:"::after", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:document.documentElement, test_child:false, pseudo:"", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ // Recheck with a dynamic change in transition
+ { element:"e1", test_child:false, pseudo:"", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"e2", test_child:true, pseudo:"", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"b1", test_child:false, pseudo:"::before", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"b2", test_child:true, pseudo:"::before", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"a1", test_child:false, pseudo:"::after", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"a2", test_child:true, pseudo:"::after", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:document.documentElement, test_child:false, pseudo:"", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ // Recheck starting from display:none. Note that these tests all fail,
+ // although we could get *some* of them to pass by calling
+ // RestyleManager::TryInitiatingTransition from
+ // ElementRestyler::RestyleUndisplayedChildren.
+ { element:"e1", test_child:false, pseudo:"", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"e2", test_child:true, pseudo:"", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"b1", test_child:false, pseudo:"::before", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"b2", test_child:true, pseudo:"::before", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"a1", test_child:false, pseudo:"::after", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"a2", test_child:true, pseudo:"::after", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:document.documentElement, test_child:false, pseudo:"", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ // Recheck with a dynamic change in transition and starting from display:none
+ { element:"e1", test_child:false, pseudo:"", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"e2", test_child:true, pseudo:"", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"b1", test_child:false, pseudo:"::before", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"b2", test_child:true, pseudo:"::before", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"a1", test_child:false, pseudo:"::after", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"a2", test_child:true, pseudo:"::after", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:document.documentElement, test_child:false, pseudo:"", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+];
+
+for (var testidx in tests) {
+ do_test(tests[testidx]);
+}
+
+var fline = document.getElementById("fline");
+var fline_i_cs = getComputedStyle(fline.firstElementChild, "");
+// Note that the color in the ::first-line is never used in the test
+// since we avoid reporting ::first-line data in getComputedStyle.
+// However, if we were to start a transition (incorrectly), that would
+// show up in getComputedStyle.
+var COLOR_IN_LATER_LINES = "rgb(0, 0, 255)";
+
+function do_firstline_test(test) {
+ if (test.widening) {
+ fline.classList.add("narrow");
+ is (fline_i_cs.color, COLOR_IN_LATER_LINES, "initial color");
+ } else {
+ is (fline_i_cs.color, COLOR_IN_LATER_LINES, "initial color");
+ }
+
+ if (test.widening) {
+ fline.classList.remove("narrow");
+ } else {
+ fline.classList.add("narrow");
+ }
+
+ if (test.set_overflow) {
+ fline.classList.add("o");
+ }
+
+ advance_clock(100);
+
+ if (test.widening) {
+ is (fline_i_cs.color, COLOR_IN_LATER_LINES,
+ "::first-line changes don't trigger transitions");
+ } else {
+ is (fline_i_cs.color, COLOR_IN_LATER_LINES,
+ "::first-line changes don't trigger transitions");
+ }
+
+ fline.removeAttribute("class");
+}
+
+var firstline_tests = [
+ { widening: true, set_overflow: false },
+ { widening: false, set_overflow: false },
+ { widening: true, set_overflow: true },
+ { widening: false, set_overflow: true },
+];
+
+for (var firstline_test_idx in firstline_tests) {
+ do_firstline_test(firstline_tests[firstline_test_idx]);
+}
+
+function do_flexbox_reframe_test()
+{
+ var flextransition = document.getElementById("flextransition");
+ var cs = getComputedStyle(flextransition, "");
+ cs.backgroundColor;
+ flextransition.setAttribute("newstyle", "");
+ document.getElementById("flexkid").setAttribute("newstyle", "");
+ is(cs.color, "rgb(0, 0, 255)",
+ "color at start of wrapped flexbox transition");
+ advance_clock(1000);
+ is(cs.color, "rgb(51, 51, 204)",
+ "color one second in to wrapped flexbox transition");
+}
+
+do_flexbox_reframe_test();
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_and_restyles.html b/layout/style/test/test_transitions_and_restyles.html
new file mode 100644
index 0000000000..35fc608c20
--- /dev/null
+++ b/layout/style/test/test_transitions_and_restyles.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1030993
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1030993</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #display {
+ background: blue; height: 10px; width: 0; color: black;
+ transition: width linear 1s, color linear 1s;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1030993">Mozilla Bug 1030993</a>
+<p id="display"></p>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+
+/** Test for Bug 1030993 **/
+
+function advance_clock(milliseconds) {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
+}
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+advance_clock(0);
+cs.width; // flush
+p.style.width = "1000px"; // initiate transition
+is(cs.width, "0px", "transition at 0ms"); // flush (and test)
+advance_clock(100);
+is(cs.width, "100px", "transition at 100ms"); // flush
+// restyle *and* trigger new transitions
+p.style.color = "blue";
+// flush again, at the same timestamp
+is(cs.width, "100px", "transition at 100ms, after restyle");
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_and_zoom.html b/layout/style/test/test_transitions_and_zoom.html
new file mode 100644
index 0000000000..e95581be32
--- /dev/null
+++ b/layout/style/test/test_transitions_and_zoom.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=583219
+-->
+<head>
+ <title>Test for Bug 583219</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display {
+ transition: margin-left 1s linear;
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=583219">Mozilla Bug 583219</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 583219 **/
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+cs.marginLeft;
+
+p.addEventListener("transitionend", TransitionEndHandler);
+p.style.marginLeft = "100px";
+cs.marginLeft;
+
+SpecialPowers.setFullZoom(window, 2.0)
+
+SimpleTest.waitForExplicitFinish();
+
+function TransitionEndHandler(event) {
+ ok(true, "transition has completed");
+ is(event.propertyName, "margin-left", "event.propertyName");
+ is(cs.marginLeft, "100px", "value of margin-left");
+ SpecialPowers.setFullZoom(window, 1.0)
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_at_start.html b/layout/style/test/test_transitions_at_start.html
new file mode 100644
index 0000000000..bad36e7673
--- /dev/null
+++ b/layout/style/test/test_transitions_at_start.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1380133
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for transition value at start (Bug 1380133)</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="stylesheet" href="/resources/testharness.css">
+ <style>
+ a {
+ color: red;
+ transition: 100s color;
+ }
+ a.active {
+ color: blue;
+ }
+ </style>
+</head>
+<body>
+<a id=anchor><span id=span>Test</span></a>
+</body>
+<script>
+'use strict';
+
+const anchor = document.getElementById('anchor');
+const span = document.getElementById('span');
+
+test(() => {
+ anchor.getBoundingClientRect();
+ anchor.classList.add('active');
+ assert_equals(getComputedStyle(span).color, 'rgb(255, 0, 0)',
+ 'The child of a transitioning element should inherit its'
+ + ' parent\'s transition start value');
+}, 'Transition start value should be inherited');
+</script>
+</html>
diff --git a/layout/style/test/test_transitions_bug537151.html b/layout/style/test/test_transitions_bug537151.html
new file mode 100644
index 0000000000..8d3b84a5fc
--- /dev/null
+++ b/layout/style/test/test_transitions_bug537151.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=537151
+-->
+<head>
+ <title>Test for Bug 537151</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display {
+ transition: margin-left 200ms;
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=537151">Mozilla Bug 537151</a>
+<p id="display">Paragraph</p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 537151 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var p = document.getElementById("display");
+p.addEventListener("transitionend", listener);
+var ignored = getComputedStyle(p, "").marginLeft;
+p.style.marginLeft = "150px";
+
+var event_count = 0;
+function listener(event)
+{
+ ++event_count;
+ setTimeout(finish, 400);
+ p.style.color = "blue";
+}
+
+function finish()
+{
+ is(event_count, 1, "should have gotten only 1 transitionend event");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_cancel_near_end.html b/layout/style/test/test_transitions_cancel_near_end.html
new file mode 100644
index 0000000000..496d95e6a1
--- /dev/null
+++ b/layout/style/test/test_transitions_cancel_near_end.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=613888
+-->
+<head>
+ <title>Test for Bug 613888</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ #animated-elements-container > span {
+ color: black;
+ background: white;
+ transition:
+ color 10s ease-out,
+ background 1s ease-out;
+ }
+ #animated-elements-container > span.another {
+ color: white;
+ background: black;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=613888">Mozilla Bug 613888</a>
+<pre id="animated-elements-container">
+ <span should-restyle="true">canceled on a half of the animation</span>
+ <span should-restyle="true">canceled too fast, and restyled on transitionend</span>
+ <span>canceled too fast, but not restyled on transitionend</span>
+</pre>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 613888: that we don't cancel transitions when they're
+ about to end (current interpolated value rounds to ending value) and
+ they get an unrelated style change. **/
+
+var count_remaining = 6;
+
+window.addEventListener('load', function() {
+ var cases = Array.from(document.querySelectorAll('#animated-elements-container > span'));
+
+ cases.forEach(function(aTarget) {
+ aTarget.addEventListener('transitionend', function(aEvent) {
+ if (aTarget.hasAttribute('should-restyle'))
+ aTarget.style.outline = '1px solid';
+ var attr = 'transitionend-' + aEvent.propertyName;
+ if (aTarget.hasAttribute(attr)) {
+ // It's possible, given bad timers, that we might get a
+ // transition that completed before we reversed it, which could
+ // lead to two transitionend events for the same thing. We
+ // don't want to decrement count_remaining in this case.
+ return;
+ }
+ aTarget.setAttribute(attr, "true");
+ if (--count_remaining == 0) {
+ cases.forEach(function(aCase, aIndex) {
+ ok(aCase.hasAttribute('transitionend-color'),
+ "transitionend for color was fired for case "+aIndex);
+ ok(aCase.hasAttribute('transitionend-background-color'),
+ "transitionend for background-color was fired for case "+aIndex);
+ });
+ SimpleTest.finish();
+ }
+ });
+ });
+
+ cases.forEach(aCase => aCase.className = 'another' );
+
+ window.setTimeout(() => cases[0].className = '', 500);
+ window.setTimeout(() => cases[1].className = cases[2].className = '', 250);
+
+});
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_computed_value_combinations.html b/layout/style/test/test_transitions_computed_value_combinations.html
new file mode 100644
index 0000000000..3dfad41e58
--- /dev/null
+++ b/layout/style/test/test_transitions_computed_value_combinations.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 435441 **/
+
+
+/**
+ * I want to test a reasonable number of combinations rather than all of
+ * them, but I also want the test results to be reproducable. So use a
+ * simple random number generator with my own seed. See
+ * http://en.wikipedia.org/wiki/Linear_congruential_generator
+ * (Using the numbers from Numerical Recipes.)
+ */
+var rand_state = 1938266273; // a randomly (once) generated number in [0,2^32)
+var all_integers = true;
+function myrand()
+{
+ rand_state = ((rand_state * 1664525) + 1013904223) % 0x100000000;
+ all_integers = all_integers &&
+ Math.ceil(rand_state) == Math.floor(rand_state);
+ return rand_state / 0x100000000; // return value in [0,1)
+}
+
+// We want to test a bunch of values for each property.
+// Each of these values will also have a "computed" property filled in
+// below, so that we ensure it always computes to the same value.
+var values = {
+ "transition-duration":
+ [
+ { lone: true, specified: "initial" },
+ { lone: false, specified: "2s" },
+ { lone: false, specified: "0s" },
+ { lone: false, specified: "430ms" },
+ { lone: false, specified: "1s" },
+ ],
+ "transition-property":
+ [
+ { lone: true, specified: "initial" },
+ { lone: true, specified: "none" },
+ { lone: true, specified: "all" },
+ { lone: false, specified: "color" },
+ { lone: false, specified: "border-spacing" },
+ // Make sure to test the "unknown property" case.
+ { lone: false, specified: "unsupported-property" },
+ { lone: false, specified: "-other-unsupported-property" },
+ ],
+ "transition-timing-function":
+ [
+ { lone: true, specified: "initial" },
+ { lone: false, specified: "linear" },
+ { lone: false, specified: "ease" },
+ { lone: false, specified: "ease-in-out" },
+ { lone: false, specified: "cubic-bezier(0, 0, 0.63, 1.00)" },
+ ],
+ "transition-delay":
+ [
+ { lone: true, specified: "initial" },
+ { lone: false, specified: "2s" },
+ { lone: false, specified: "0s" },
+ { lone: false, specified: "430ms" },
+ { lone: false, specified: "-1s" },
+ ],
+};
+
+var elt = document.getElementById("content");
+var cs = getComputedStyle(elt, "");
+
+// Add the "computed" property to all of the above values.
+for (var prop in values) {
+ var valueset = values[prop];
+ for (var index in valueset) {
+ var item = valueset[index];
+ elt.style.setProperty(prop, item.specified, "");
+ item.computed = cs.getPropertyValue(prop);
+ elt.style.removeProperty(prop);
+ isnot(item.computed, "", "computed value must not be empty");
+ if (index != 0) {
+ isnot(item.computed, valueset[index-1].computed,
+ "computed value must not be the same as the last one");
+ }
+ }
+}
+
+var child = document.createElement("div");
+elt.appendChild(child);
+var child_cs = getComputedStyle(child, "");
+
+// Now test a hundred random combinations of values on the parent and
+// child.
+for (var iteration = 0; iteration < 100; ++iteration) {
+ // Figure out values on the parent.
+ var parent_vals = {};
+ for (var prop in values) {
+ var valueset = values[prop];
+ var list_length = Math.ceil(Math.pow(myrand(), 2) * 6);
+ // 41% chance of length 1
+ var specified = [];
+ var computed = [];
+ for (var i = 0; i < list_length; ++i) {
+ var index;
+ do {
+ index = Math.floor(myrand() * valueset.length);
+ } while (list_length != 1 && valueset[index].lone);
+ specified.push(valueset[index].specified);
+ computed.push(valueset[index].computed);
+ }
+ parent_vals[prop] = { specified: specified.join(", "),
+ computed: computed.join(", ") };
+ elt.style.setProperty(prop, parent_vals[prop].specified, "");
+ }
+
+ // Figure out values on the child.
+ var child_vals = {};
+ for (var prop in values) {
+ var valueset = values[prop];
+ // Use 0 as a magic value for "inherit".
+ var list_length = Math.floor(Math.pow(myrand(), 1.5) * 7);
+ // 27% chance of inherit
+ // 16% chance of length 1
+ if (list_length == 0) {
+ child_vals[prop] = { specified: "inherit",
+ computed: parent_vals[prop].computed };
+ } else {
+ var specified = [];
+ var computed = [];
+ for (var i = 0; i < list_length; ++i) {
+ var index;
+ do {
+ index = Math.floor(myrand() * valueset.length);
+ } while (list_length != 1 && valueset[index].lone);
+ specified.push(valueset[index].specified);
+ computed.push(valueset[index].computed);
+ }
+ child_vals[prop] = { specified: specified.join(", "),
+ computed: computed.join(", ") };
+ }
+ child.style.setProperty(prop, child_vals[prop].specified, "");
+ }
+
+ // Test computed values
+ for (var prop in values) {
+ is(cs.getPropertyValue(prop), parent_vals[prop].computed,
+ "computed value of " + prop + ": " + parent_vals[prop].specified +
+ " on parent.");
+ is(child_cs.getPropertyValue(prop), child_vals[prop].computed,
+ "computed value of " + prop + ": " + child_vals[prop].specified +
+ " on child.");
+ }
+}
+
+ok(all_integers, "pseudo-random number generator kept its numbers " +
+ "as integers throughout run");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_computed_values.html b/layout/style/test/test_transitions_computed_values.html
new file mode 100644
index 0000000000..7b350de6b2
--- /dev/null
+++ b/layout/style/test/test_transitions_computed_values.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 435441 **/
+
+
+/*
+ * test that when transition properties are inherited, the length of the
+ * computed value stays the same
+ */
+
+var p = document.getElementById("content");
+var c = document.createElement("div");
+p.appendChild(c);
+var cs = getComputedStyle(c, "");
+
+p.style.transitionProperty = "margin-left, margin-right";
+c.style.transitionProperty = "inherit";
+is(cs.transitionProperty, "margin-left, margin-right",
+ "computed style match with no other properties");
+c.style.transitionDuration = "5s";
+is(cs.transitionProperty, "margin-left, margin-right",
+ "computed style match with shorter property");
+is(cs.transitionDuration, "5s",
+ "shorter property not extended");
+c.style.transitionDuration = "5s, 4s, 3s, 2000ms";
+is(cs.transitionProperty, "margin-left, margin-right",
+ "computed style match with longer property");
+is(cs.transitionDuration, "5s, 4s, 3s, 2s",
+ "longer property computed correctly");
+p.style.transitionProperty = "";
+c.style.transitionProperty = "";
+c.style.transitionDuration = "";
+
+// and repeat the above set of tests with property and duration swapped
+p.style.transitionDuration = "5s, 4s";
+c.style.transitionDuration = "inherit";
+is(cs.transitionDuration, "5s, 4s",
+ "computed style match with no other properties");
+c.style.transitionProperty = "margin-left";
+is(cs.transitionDuration, "5s, 4s",
+ "computed style match with shorter property");
+is(cs.transitionProperty, "margin-left",
+ "shorter property not extended");
+c.style.transitionProperty =
+ "margin-left, margin-right, margin-top, margin-bottom";
+is(cs.transitionDuration, "5s, 4s",
+ "computed style match with longer property");
+is(cs.transitionProperty,
+ "margin-left, margin-right, margin-top, margin-bottom",
+ "longer property computed correctly");
+p.style.transitionDuration = "";
+c.style.transitionDuration = "";
+c.style.transitionProperty = "";
+
+// And do the same pair of tests for animations:
+
+p.style.animationName = "bounce, roll";
+c.style.animationName = "inherit";
+is(cs.animationName, "bounce, roll",
+ "computed style match with no other properties");
+c.style.animationDuration = "5s";
+is(cs.animationName, "bounce, roll",
+ "computed style match with shorter property");
+is(cs.animationDuration, "5s",
+ "shorter property not extended");
+c.style.animationDuration = "5s, 4s, 3s, 2000ms";
+is(cs.animationName, "bounce, roll",
+ "computed style match with longer property");
+is(cs.animationDuration, "5s, 4s, 3s, 2s",
+ "longer property computed correctly");
+p.style.animationName = "";
+c.style.animationName = "";
+c.style.animationDuration = "";
+
+// and repeat the above set of tests with name and duration swapped
+p.style.animationDuration = "5s, 4s";
+c.style.animationDuration = "inherit";
+is(cs.animationDuration, "5s, 4s",
+ "computed style match with no other properties");
+c.style.animationName = "bounce";
+is(cs.animationDuration, "5s, 4s",
+ "computed style match with shorter property");
+is(cs.animationName, "bounce",
+ "shorter property not extended");
+c.style.animationName =
+ "bounce, roll, wiggle, spin";
+is(cs.animationDuration, "5s, 4s",
+ "computed style match with longer property");
+is(cs.animationName,
+ "bounce, roll, wiggle, spin",
+ "longer property computed correctly");
+p.style.animationDuration = "";
+c.style.animationDuration = "";
+c.style.animationName = "";
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_dynamic_changes.html b/layout/style/test/test_transitions_dynamic_changes.html
new file mode 100644
index 0000000000..4d49db1e3a
--- /dev/null
+++ b/layout/style/test/test_transitions_dynamic_changes.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=525530
+-->
+<head>
+ <title>Test for Bug 525530</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525530">Mozilla Bug 525530</a>
+<p id="display" style="text-indent: 100px"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 525530 **/
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+var utils = SpecialPowers.DOMWindowUtils;
+
+p.style.transitionProperty = "all";
+p.style.transitionDuration = "4s";
+p.style.transitionDelay = "-2s";
+p.style.transitionTimingFunction = "linear";
+
+is(cs.textIndent, "100px", "initial value");
+
+p.style.textIndent = "0";
+is(cs.textIndent, "50px", "transition is halfway");
+p.style.transitionDuration = "0s";
+is(cs.textIndent, "50px", "changing duration doesn't change transitioning");
+p.style.transitionDelay = "0s";
+is(cs.textIndent, "50px", "changing delay doesn't change transitioning");
+p.style.transitionProperty = "text-indent";
+is(cs.textIndent, "50px",
+ "irrelevant change to transition property doesn't change transitioning");
+p.style.transitionProperty = "letter-spacing";
+is(cs.textIndent, "0px",
+ "relevant change to transition property does change transitioning");
+
+/** Test for Bug 522643 */
+p.style.transitionDuration = "4s";
+p.style.transitionDelay = "-2s";
+p.style.transitionProperty = "text-indent";
+p.style.textIndent = "100px";
+is(cs.textIndent, "50px", "transition is halfway");
+p.style.transitionDuration = "0s";
+p.style.transitionDelay = "0s";
+is(cs.textIndent, "50px",
+ "changing duration and delay doesn't change transitioning");
+p.style.textIndent = "0px";
+is(cs.textIndent, "0px",
+ "changing property after changing duration and delay stops transition");
+
+/** Test for Bug 1133375 */
+p.style.transitionDuration = "1s";
+p.style.transitionDelay = "-1s";
+p.style.transitionProperty = "text-indent";
+var endCount = 0;
+function incrementEndCount(event) { ++endCount; }
+p.addEventListener("transitionend", incrementEndCount);
+utils.advanceTimeAndRefresh(0);
+p.style.textIndent = "100px";
+is(cs.textIndent, "100px", "value should now be 100px");
+utils.advanceTimeAndRefresh(10);
+is(endCount, 0, "should not have started transition when combined duration less than or equal to 0");
+p.style.transitionDelay = "-2s";
+p.style.textIndent = "0";
+is(cs.textIndent, "0px", "value should now be 0px");
+utils.advanceTimeAndRefresh(10);
+is(endCount, 0, "should not have started transition when combined duration less than or equal to 0");
+utils.restoreNormalRefresh();
+p.style.textIndent = "";
+
+/** Test for bug 1144410 */
+utils.advanceTimeAndRefresh(0);
+p.style.transition = "opacity 200ms linear";
+p.style.opacity = "1";
+is(cs.opacity, "1", "bug 1144410 test - initial opacity");
+p.style.opacity = "0";
+is(cs.opacity, "1", "bug 1144410 test - opacity after starting transition");
+utils.advanceTimeAndRefresh(100);
+is(cs.opacity, "0.5", "bug 1144410 test - opacity during transition");
+utils.advanceTimeAndRefresh(200);
+is(cs.opacity, "0", "bug 1144410 test - opacity after transition");
+document.body.style.display = "none";
+is(cs.opacity, "0", "bug 1144410 test - opacity after display:none");
+p.style.opacity = "1";
+document.body.style.display = "";
+is(cs.opacity, "1", "bug 1144410 test - second transition, initial opacity");
+p.style.opacity = "0";
+is(cs.opacity, "1", "bug 1144410 test - opacity after starting second transition");
+utils.advanceTimeAndRefresh(100);
+is(cs.opacity, "0.5", "bug 1144410 test - opacity during second transition");
+utils.advanceTimeAndRefresh(200);
+is(cs.opacity, "0", "bug 1144410 test - opacity after second transition");
+utils.restoreNormalRefresh();
+p.style.opacity = "";
+p.style.transition = "";
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_events.html b/layout/style/test/test_transitions_events.html
new file mode 100644
index 0000000000..d04348bc0a
--- /dev/null
+++ b/layout/style/test/test_transitions_events.html
@@ -0,0 +1,294 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=531585
+-->
+<head>
+ <title>Test for Bug 531585 (transitionend event)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<style type="text/css">
+
+.bar { margin: 10px; }
+
+#one { transition-duration: 500ms; transition-property: all; }
+#two { transition: margin-left 1s; }
+#three { transition: margin 0.5s 0.25s; }
+
+#four, #five, #six, #seven::before, #seven::after {
+ transition: 500ms color;
+ border-color: black; /* don't derive from color */
+ column-rule-color: black; /* don't derive from color */
+ text-decoration-color: black; /* don't derive from color */
+ outline-color: black; /* don't derive from color */
+}
+
+#four {
+ /* give the reversing transition a long duration; the reversing will
+ still be quick */
+ transition-duration: 30s;
+ transition-timing-function: cubic-bezier(0, 1, 1, 0);
+}
+
+#seven::before, #seven::after {
+ content: "x";
+ transition-duration: 50ms;
+}
+#seven[foo]::before, #seven[foo]::after { color: lime; }
+
+</style>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=531585">Mozilla Bug 531585</a>
+<p id="display">
+
+<span id="one" style="color:blue"></span>
+<span id="two"></span>
+<span id="three"></span>
+<span id="four" style="color: blue"></span>
+<span id="five" style="color: blue"></span>
+<span id="six" style="color: blue"></span>
+<span id="seven" style="color: blue"></span>
+
+</p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 531585 (transitionend event) **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+var gTestCount = 0;
+function started_test() { ++gTestCount; }
+function finished_test() { if (--gTestCount == 0) { SimpleTest.finish(); } }
+
+function $(id) { return document.getElementById(id); }
+function cs(id) { return getComputedStyle($(id), ""); }
+
+var got_one_root = false;
+var got_one_target = false;
+var got_two_target = false;
+var got_three_top = false;
+var got_three_right = false;
+var got_three_bottom = false;
+var got_three_left = false;
+var got_four_root = false;
+var got_body = false;
+var did_finish_five = false;
+var did_finish_six = false;
+var got_before = false;
+var got_after = false;
+
+// Flush layout to guarantee consistent transitions.
+document.body.getBoundingClientRect();
+
+document.documentElement.addEventListener("transitionend",
+ function(event) {
+ if (event.target == $("one")) {
+ ok(!got_one_root, "transitionend on one on root");
+ is(event.propertyName, "border-right-color",
+ "propertyName for transitionend on one");
+ is(event.elapsedTime, 0.5,
+ "elapsedTime for transitionend on one");
+ is(cs("one").borderRightColor, "rgb(0, 255, 0)",
+ "computed style for transitionend on one");
+ got_one_root = true;
+ finished_test();
+ } else if (event.target == $("four")) {
+ ok(!got_four_root, "transitionend on four on root");
+ is(event.propertyName, "color",
+ "propertyName for transitionend on four");
+ // Reported time should (really?) be shortened by reversing.
+ ok(event.elapsedTime < 30,
+ "elapsedTime for transitionend on four");
+ is(cs("four").color, "rgb(0, 0, 255)",
+ "computed style for transitionend on four (end of reverse transition)");
+ got_four_root = true;
+ finished_test();
+ } else if (event.target == document.body) {
+ // A synthesized event.
+ ok(!got_body, "transitionend on body on root");
+ is(event.propertyName, "some-unknown-prop",
+ "propertyName for transitionend on body");
+ // Reported time should (really?) be shortened by reversing.
+ is(event.elapsedTime, 0.5,
+ "elapsedTime for transitionend on body");
+ got_body = true;
+ finished_test();
+ } else if (event.target == $("seven")) {
+ if (!got_before) {
+ got_before = true;
+ is(event.pseudoElement, "::before");
+ } else {
+ ok(!got_after, "transitionend on #seven::after");
+ got_after = true;
+ is(event.pseudoElement, "::after");
+ }
+ is(event.propertyName, "color");
+ is(event.isTrusted, true);
+ finished_test();
+ } else {
+ if ((event.target == $("five") && did_finish_five) ||
+ (event.target == $("six") && did_finish_six)) {
+ todo(false,
+ "it seems that transitionstart and transitionend had been " +
+ "processed in the same frame");
+ return;
+ }
+ ok(false,
+ "unexpected event on " + event.target.nodeName +
+ " element with id '" + event.target.id + "' " +
+ "elapsedTime=" + event.elapsedTime +
+ " propertyName='" + event.propertyName + "'");
+ }
+ });
+
+$("one").addEventListener("transitionend",
+ function(event) {
+ is(event.propertyName, "color", "unexpected " +
+ "property name for transitionend on one on target");
+ ok(!got_one_target,
+ "transitionend on one on target (color)");
+ got_one_target = true;
+ event.stopPropagation();
+ is(event.elapsedTime, 0.5,
+ "elapsedTime for transitionend on one");
+ is(cs("one").getPropertyValue(event.propertyName), "rgb(0, 255, 0)",
+ "computed style of " + event.propertyName + " for transitionend on one");
+ finished_test();
+ });
+
+started_test(); // color on #one
+$("one").style.color = "lime";
+
+
+$("two").addEventListener("transitionend",
+ function(event) {
+ event.stopPropagation();
+
+ ok(!got_two_target, "transitionend on two on target");
+ is(event.propertyName, "margin-left",
+ "propertyName for transitionend on two");
+ is(event.elapsedTime, 1,
+ "elapsedTime for transitionend on two");
+ is(event.bubbles, true,
+ "transitionend events should bubble");
+ is(event.cancelable, false,
+ "transitionend events should not be cancelable");
+ is(cs("two").marginLeft, "10px",
+ "computed style for transitionend on two");
+ got_two_target = true;
+ finished_test();
+ });
+
+started_test(); // #two
+$("two").className = "bar";
+
+$("three").addEventListener("transitionend",
+ function(event) {
+ event.stopPropagation();
+
+ switch (event.propertyName) {
+ case "margin-top":
+ ok(!got_three_top, "should only get margin-top once");
+ got_three_top = true;
+ break;
+ case "margin-right":
+ ok(!got_three_right, "should only get margin-right once");
+ got_three_right = true;
+ break;
+ case "margin-bottom":
+ ok(!got_three_bottom, "should only get margin-bottom once");
+ got_three_bottom = true;
+ break;
+ case "margin-left":
+ ok(!got_three_left, "should only get margin-left once");
+ got_three_left = true;
+ break;
+ default:
+ ok(false, "unexpected property name " + event.propertyName +
+ " for transitionend on three");
+ }
+ is(event.elapsedTime, 0.5,
+ "elapsedTime for transitionend on three");
+ is(cs("three").getPropertyValue(event.propertyName), "10px",
+ "computed style for transitionend on three");
+ finished_test();
+ }, true);
+
+started_test(); // margin-top on #three
+started_test(); // margin-right on #three
+started_test(); // margin-bottom on #three
+started_test(); // margin-left on #three
+$("three").className = "bar";
+
+// We reverse the transition on four, and we should only get an event
+// at the end of the second transition.
+started_test(); // #four (listener on root)
+$("four").style.color = "lime";
+
+// We cancel the transition on five by changing 'transition-property',
+// and should thus get no event.
+$("five").style.color = "lime";
+
+// We cancel the transition on six by changing 'transition-duration' and
+// then changing the value, so we should get no event.
+$("six").style.color = "lime";
+
+started_test(); // #seven::before (listener on root)
+started_test(); // #seven::after (listener on root)
+$("seven").setAttribute("foo", "bar");
+
+$("five").addEventListener("transitionstart", function() {
+ if (cs("five").color == "rgb(0, 255, 0)") {
+ // The transition has finished already.
+ did_finish_five = true;
+ }
+ $("five").style.transitionProperty = "margin-left";
+});
+
+$("six").addEventListener("transitionstart", function() {
+ if (cs("six").color == "rgb(0, 255, 0)") {
+ // The transition has finished already.
+ did_finish_six = true;
+ }
+ $("six").style.transitionDuration = "0s";
+ $("six").style.transitionDelay = "0s";
+ $("six").style.color = "blue";
+});
+
+function poll_start_reversal() {
+ if (cs("four").color != "rgb(0, 0, 255)") {
+ // The forward transition has started.
+ $("four").style.color = "blue";
+ } else {
+ // The forward transition has not started yet.
+ setTimeout(poll_start_reversal, 20);
+ }
+}
+setTimeout(poll_start_reversal, 200);
+
+// And make our own event to dispatch to the body.
+started_test(); // synthesized event to body (listener on root)
+
+var e = new TransitionEvent("transitionend",
+ {
+ bubbles: true,
+ cancelable: true,
+ propertyName: "some-unknown-prop",
+ elapsedTime: 0.5,
+ pseudoElement: "pseudo"
+ });
+is(e.bubbles, true);
+is(e.cancelable, true);
+is(e.propertyName, "some-unknown-prop");
+is(e.elapsedTime, 0.5);
+is(e.pseudoElement, "pseudo");
+is(e.isTrusted, false)
+
+document.body.dispatchEvent(e);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_per_property.html b/layout/style/test/test_transitions_per_property.html
new file mode 100644
index 0000000000..058152adfb
--- /dev/null
+++ b/layout/style/test/test_transitions_per_property.html
@@ -0,0 +1,3245 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <meta charset=utf-8>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <script type="text/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display > p { margin-top: 0; margin-bottom: 0; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>
+
+<!--
+ fixed-height container so percentage heights compute to different
+ (i.e., nonzero) values
+ fixed-width container so that percentages for margin-top and
+ margin-bottom are all relative to the same size container (rather than
+ one that depends on whether we're tall enough to need a scrollbar)
+
+ Use a 20px font size and line-height so that percentage line-height
+ and vertical-align doesn't accumulate rounding error.
+ -->
+<div style="height: 50px; width: 300px; font-size: 20px; line-height: 20px">
+
+<div id="display">
+</div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/* eslint no-shadow: ["error", {"allow": ["prop", "div"]}] */
+/* eslint-disable dot-notation */
+
+
+/** Test for Bug 435441 **/
+
+SimpleTest.requestLongerTimeout(2);
+SimpleTest.waitForExplicitFinish();
+
+function has_num(str)
+{
+ return !!String(str).match(/^([\d.]+)/);
+}
+
+function any_unit_to_num(str)
+{
+ return Number(String(str).match(/^([\d.]+)/)[1]);
+}
+
+var FUNC_NEGATIVE = "cubic-bezier(0.25, -2, 0.75, 1)";
+var FUNC_OVERONE = "cubic-bezier(0.25, 0, 0.75, 3)";
+
+var supported_properties = {
+ "aspect-ratio" : [ test_aspect_ratio_transition ],
+ "border-bottom-left-radius": [ test_radius_transition ],
+ "border-bottom-right-radius": [ test_radius_transition ],
+ "border-top-left-radius": [ test_radius_transition ],
+ "border-top-right-radius": [ test_radius_transition ],
+ "border-start-start-radius": [ test_radius_transition ],
+ "border-start-end-radius": [ test_radius_transition ],
+ "border-end-start-radius": [ test_radius_transition ],
+ "border-end-end-radius": [ test_radius_transition ],
+ "-moz-box-flex": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition,
+ test_float_zeroToOne_clamped ],
+ "box-shadow": [ test_shadow_transition ],
+ "column-count": [ test_pos_integer_or_auto_transition,
+ test_integer_at_least_one_clamping ],
+ "column-rule-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "column-rule-width": [ test_length_transition,
+ test_length_clamped ],
+ "column-width": [ test_length_transition,
+ test_length_clamped ],
+ "cx": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "cy": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "background-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "background-position": [ test_background_position_transition,
+ test_length_percent_pair_unclamped ],
+ "background-position-x": [ test_background_position_coord_transition,
+ test_length_transition,
+ test_percent_transition,
+ // FIXME: We don't currently test clamping,
+ // since background-position-x uses calc() as
+ // an intermediate form.
+ /* test_length_percent_pair_unclamped */ ],
+ "background-position-y": [ test_background_position_coord_transition,
+ test_length_transition,
+ test_percent_transition,
+ // FIXME: We don't currently test clamping,
+ // since background-position-y uses calc() as
+ // an intermediate form.
+ /* test_length_percent_pair_unclamped */ ],
+ "background-size": [ test_background_size_transition,
+ test_length_percent_pair_clamped ],
+ "border-bottom-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "border-bottom-width": [ test_length_transition,
+ test_length_clamped ],
+ "border-left-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "border-left-width": [ test_length_transition,
+ test_length_clamped ],
+ "border-right-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "border-right-width": [ test_length_transition,
+ test_length_clamped ],
+ "border-spacing": [ test_length_pair_transition,
+ test_length_pair_transition_clamped ],
+ "border-top-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "border-top-width": [ test_length_transition,
+ test_length_clamped ],
+ "bottom": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "accent-color": [ test_color_transition,
+ test_currentcolor_transition,
+ test_auto_color_transition ],
+ "caret-color": [ test_color_transition,
+ test_currentcolor_transition,
+ test_auto_color_transition ],
+ "clip": [ test_rect_transition ],
+ "clip-path": [ test_basic_shape_or_url_transition,
+ test_path_function ],
+ "color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "d": [ test_path_function ],
+ "fill": [ test_color_transition,
+ test_currentcolor_transition ],
+ "fill-opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "filter" : [ test_filter_transition ],
+ "flex-basis": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ test_flex_basis_content_transition ],
+ "flex-grow": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition ],
+ "flex-shrink": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition ],
+ "flood-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "flood-opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "font-size": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "font-size-adjust": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition,
+ /* FIXME: font-size-adjust treats zero specially */
+ /* test_float_zeroToOne_clamped */ ],
+ "font-stretch": [ test_percent_transition, test_percent_clamped ],
+ "font-weight": [ test_font_weight ],
+ "column-gap": [ test_grid_gap ],
+ "row-gap": [ test_grid_gap ],
+ "height": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "left": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "letter-spacing": [ test_length_transition, test_length_unclamped ],
+ "lighting-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ // NOTE: when calc() is supported on 'line-height', we should add
+ // test_length_percent_calc_transition.
+ "line-height": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "margin-bottom": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "margin-left": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "margin-right": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "margin-top": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "mask-position": [ test_background_position_transition,
+ test_length_percent_pair_unclamped ],
+ "mask-position-x": [ test_background_position_coord_transition,
+ test_length_transition,
+ test_percent_transition,
+ // FIXME: We don't currently test clamping,
+ // since background-position-x uses calc() as
+ // an intermediate form.
+ /* test_length_percent_pair_unclamped */ ],
+ "mask-position-y": [ test_background_position_coord_transition,
+ test_length_transition,
+ test_percent_transition,
+ // FIXME: We don't currently test clamping,
+ // since background-position-y uses calc() as
+ // an intermediate form.
+ /* test_length_percent_pair_unclamped */ ],
+ "mask-size": [ test_background_size_transition,
+ test_length_percent_pair_clamped ],
+ "max-height": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "max-width": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "min-height": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "min-width": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "object-position": [ test_background_position_transition ],
+ "overflow-clip-margin": [ test_length_transition ],
+ "opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "order": [ test_integer_transition ],
+ "outline-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "outline-offset": [ test_length_transition, test_length_unclamped ],
+ "outline-width": [ test_length_transition, test_length_clamped ],
+ "padding-bottom": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "padding-left": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "padding-right": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "padding-top": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "perspective": [ test_length_transition ],
+ "perspective-origin": [ test_length_pair_transition,
+ test_length_percent_pair_transition,
+ test_length_percent_pair_unclamped ],
+ "right": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "r": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "rx": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "ry": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "shape-image-threshold": [ test_float_zeroToOne_transition,
+ // shape-image-threshold (like opacity) is
+ // clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "shape-margin": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "shape-outside": [ test_basic_shape_or_url_transition ],
+ "stop-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "stop-opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "stroke": [ test_color_transition,
+ test_currentcolor_transition ],
+ "stroke-dasharray": [ test_dasharray_transition ],
+ "stroke-dashoffset": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped, ],
+ "stroke-miterlimit": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition,
+ test_float_aboveZero_clamped ],
+ "stroke-opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "stroke-width": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped, ],
+ "tab-size": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition, test_length_clamped ],
+ "text-decoration": [ test_color_shorthand_transition,
+ test_currentcolor_shorthand_transition ],
+ "text-decoration-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "text-emphasis-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "text-indent": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "text-shadow": [ test_shadow_transition ],
+ "top": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "transform": [ test_transform_transition ],
+ "transform-origin": [ test_length_pair_transition,
+ test_length_percent_pair_transition,
+ test_length_percent_pair_unclamped ],
+ "vertical-align": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "visibility": [ test_visibility_transition ],
+ "width": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "word-spacing": [ test_length_transition, test_length_unclamped ],
+ "x": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "y": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "z-index": [ test_integer_transition, test_pos_integer_or_auto_transition ],
+ "-webkit-line-clamp": [ test_pos_integer_or_none_transition ],
+ "-webkit-text-fill-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "-webkit-text-stroke-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "text-underline-offset": [ test_length_transition ],
+ "text-decoration-thickness": [ test_length_transition ],
+ "scroll-margin-top": [
+ test_length_transition,
+ ],
+ "scroll-margin-right": [
+ test_length_transition,
+ ],
+ "scroll-margin-bottom": [
+ test_length_transition,
+ ],
+ "scroll-margin-left": [
+ test_length_transition,
+ ],
+ "scroll-padding-top": [
+ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ ],
+ "scroll-padding-right": [
+ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ ],
+ "scroll-padding-bottom": [
+ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ ],
+ "scroll-padding-left": [
+ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ ],
+ "scrollbar-color": [ test_scrollbar_color_transition ],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.backdrop-filter.enabled")) {
+ supported_properties["backdrop-filter"] = [ test_filter_transition ];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) {
+ supported_properties["font-variation-settings"] = [ test_font_variations_transition ];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.individual-transform.enabled")) {
+ supported_properties["rotate"] = [ test_rotate_transition ];
+ supported_properties["scale"] = [ test_scale_transition ];
+ supported_properties["translate"] = [ test_translate_transition ];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) {
+ supported_properties["contain-intrinsic-width"] = [ test_length_transition, test_auto_with_length_transition ];
+ supported_properties["contain-intrinsic-height"] = supported_properties["contain-intrinsic-width"];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.content-visibility.enabled")) {
+ supported_properties["content-visibility"] = [test_content_visibility_transition];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.zoom.enabled")) {
+ Object.assign(supported_properties, {
+ "zoom": [ test_number_transition, test_percent_transition ],
+ });
+}
+
+// For properties which are well-tested by web-platform-tests, we don't need to
+// test animations/transitions again on them.
+var skipped_transitionable_properties = [
+ "border-image-outset",
+ "border-image-slice",
+ "border-image-width",
+ "font-style", // Tests being added in https://github.com/web-platform-tests/wpt/pull/37570
+ "grid-template-columns",
+ "grid-template-rows",
+ "offset-path",
+ "offset-distance",
+ "offset-rotate",
+ "offset-anchor",
+ "offset-position",
+]
+
+// Logical properties.
+for (const logical_side of ["inline-start", "inline-end", "block-start", "block-end"]) {
+ supported_properties["border-" + logical_side + "-color"] = supported_properties["border-top-color"];
+ supported_properties["border-" + logical_side + "-width"] = supported_properties["border-top-width"];
+ supported_properties["margin-" + logical_side] = supported_properties["margin-top"];
+ supported_properties["padding-" + logical_side] = supported_properties["padding-top"];
+ supported_properties["inset-" + logical_side] = supported_properties["top"];
+ supported_properties["scroll-margin-" + logical_side] = supported_properties["scroll-margin-top"];
+ supported_properties["scroll-padding-" + logical_side] = supported_properties["scroll-padding-top"];
+}
+
+for (const logical_size of ["inline", "block"]) {
+ supported_properties[logical_size + "-size"] = supported_properties["width"];
+ supported_properties["min-" + logical_size + "-size"] = supported_properties["min-width"];
+ supported_properties["max-" + logical_size + "-size"] = supported_properties["max-width"];
+ if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) {
+ supported_properties["contain-intrinsic-" + logical_size + "-size"] = supported_properties["contain-intrinsic-width"];
+ }
+}
+
+var div = document.getElementById("display");
+var cs = getComputedStyle(div, "");
+var winUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function computeMatrix(v) {
+ div.style.setProperty("transform", v, "");
+ var result = cs.getPropertyValue("transform");
+ div.style.removeProperty("transform");
+ return result;
+}
+var c_rot_15 = computeMatrix("rotate(15deg)");
+is(c_rot_15.substring(0,6), "matrix", "should compute to matrix value");
+var c_rot_60 = computeMatrix("rotate(60deg)");
+is(c_rot_60.substring(0,6), "matrix", "should compute to matrix value");
+
+var transformTests = [
+ // rotate
+ { start: 'none', end: 'rotate(60deg)',
+ expected_uncomputed: 'rotate(15deg)',
+ expected: c_rot_15 },
+ { start: 'rotate(0)', end: 'rotate(60deg)',
+ expected_uncomputed: 'rotate(15deg)',
+ expected: c_rot_15 },
+ { start: 'rotate(0deg)', end: 'rotate(60deg)',
+ expected_uncomputed: 'rotate(15deg)',
+ expected: c_rot_15 },
+ { start: 'none', end: c_rot_60,
+ expected: c_rot_15 },
+ { start: 'none', end: 'rotate(360deg)',
+ expected_uncomputed: 'rotate(90deg)',
+ expected: computeMatrix('rotate(90deg)') },
+ { start: 'none', end: 'rotatez(360deg)',
+ expected_uncomputed: 'rotate(90deg)',
+ expected: computeMatrix('rotate(90deg)') },
+ { start: 'none', end: 'rotate(720deg)',
+ expected_uncomputed: 'rotate(180deg)',
+ expected: computeMatrix('rotate(180deg)') },
+ { start: 'none', end: 'rotate(720deg)',
+ expected_uncomputed: 'rotatez(180deg)',
+ expected: computeMatrix('rotate(180deg)') },
+ { start: 'none', end: 'rotate(1080deg)',
+ expected_uncomputed: 'rotate(270deg)',
+ expected: computeMatrix('rotate(270deg)') },
+ { start: 'none', end: 'rotate(1080deg)',
+ expected_uncomputed: 'rotate(270deg)',
+ expected: computeMatrix('rotatez(270deg)') },
+ { start: 'none', end: 'rotate(1440deg)',
+ expected_uncomputed: 'rotate(360deg)',
+ expected: computeMatrix('scale(1)'),
+ round_error_ok: true },
+ { start: 'none', end: 'rotatey(60deg)',
+ expected_uncomputed: 'rotatey(15deg)',
+ expected: computeMatrix('rotatey(15deg)') },
+ { start: 'none', end: 'rotatey(720deg)',
+ expected_uncomputed: 'rotatey(180deg)',
+ expected: computeMatrix('rotatey(180deg)') },
+ { start: 'none', end: 'rotatex(60deg)',
+ expected_uncomputed: 'rotatex(15deg)',
+ expected: computeMatrix('rotatex(15deg)') },
+ { start: 'none', end: 'rotatex(720deg)',
+ expected_uncomputed: 'rotatex(180deg)',
+ expected: computeMatrix('rotatex(180deg)') },
+
+ // translate
+ { start: 'translate(20px)', end: 'none',
+ expected_uncomputed: 'translate(15px)',
+ expected: 'matrix(1, 0, 0, 1, 15, 0)' },
+ { start: 'translate(20px, 12px)', end: 'none',
+ expected_uncomputed: 'translate(15px, 9px)',
+ expected: 'matrix(1, 0, 0, 1, 15, 9)' },
+ { start: 'translateX(-20px)', end: 'none',
+ expected_uncomputed: 'translateX(-15px)',
+ expected: 'matrix(1, 0, 0, 1, -15, 0)' },
+ { start: 'translateY(-40px)', end: 'none',
+ expected_uncomputed: 'translateY(-30px)',
+ expected: 'matrix(1, 0, 0, 1, 0, -30)' },
+ { start: 'translateZ(40px)', end: 'none',
+ expected_uncomputed: 'translateZ(30px)',
+ expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 30, 1)' },
+ { start: 'none', end: 'translate3D(40px, 60px, -40px)',
+ expected_uncomputed: 'translate3D(10px, 15px, -10px)',
+ expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 15, -10, 1)' },
+ // percentages are relative to 300px (width) and 50px (height)
+ // per the prerequisites in property_database.js
+ { start: 'translate(20%)', end: 'none',
+ expected_uncomputed: 'translate(15%)',
+ expected: 'matrix(1, 0, 0, 1, 45, 0)',
+ round_error_ok: true },
+ { start: 'translate(20%, 12%)', end: 'none',
+ expected_uncomputed: 'translate(15%, 9%)',
+ expected: 'matrix(1, 0, 0, 1, 45, 4.5)',
+ round_error_ok: true },
+ { start: 'translateX(-20%)', end: 'none',
+ expected_uncomputed: 'translateX(-15%)',
+ expected: 'matrix(1, 0, 0, 1, -45, 0)',
+ round_error_ok: true },
+ { start: 'translateY(-40%)', end: 'none',
+ expected_uncomputed: 'translateY(-30%)',
+ expected: 'matrix(1, 0, 0, 1, 0, -15)',
+ round_error_ok: true },
+ { start: 'none', end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
+ expected_uncomputed: 'rotate(22.5deg) translate(5%, 5%) rotate(-22.5deg)',
+ round_error_ok: true },
+ { start: 'none', end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)',
+ expected_uncomputed: 'rotate(-22.5deg) translate(5%, 5%) rotate(22.5deg)',
+ round_error_ok: true },
+ // test percent translation using matrix decomposition
+ { start: 'matrix(1, 0, 0, 1, 0, 0)',
+ end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
+ expected: 'matrix(1, 0, 0, 1, -2.5, 15)',
+ round_error_ok: true },
+ { start: 'matrix(1, 0, 0, 1, 0, 0)',
+ end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)',
+ expected: 'matrix(1, 0, 0, 1, 2.5, -15)',
+ round_error_ok: true },
+ // test calc() in translate
+ // Note that font-size: is 20px, and that percentages are relative
+ // to 300px (width) and 50px (height) per the prerequisites in
+ // property_database.js
+ { start: 'translateX(20%)', /* 60px */
+ end: 'translateX(calc(10% + 1em))', /* 30px + 20px = 50px */
+ expected_uncomputed: 'translateX(calc(17.5% + 0.25em))',
+ expected: 'matrix(1, 0, 0, 1, 57.5, 0)' },
+ { start: 'translate(calc(0.75 * 3em + 1.5 * 10%), calc(0.5 * 5em + 0.5 * 8%))', /* 90px, 52px */
+ end: 'rotate(90deg) translateY(20%) rotate(90deg) translateY(calc(10% + 0.5em)) rotate(180deg)', /* -10px, -15px */
+ expected: 'matrix(1, 0, 0, 1, 65, 35.25)' },
+
+ // scale
+ { start: 'scale(2)', end: 'none',
+ expected_uncomputed: 'scale(1.75)',
+ expected: 'matrix(1.75, 0, 0, 1.75, 0, 0)' },
+ { start: 'none', end: 'scale(0.4)',
+ expected_uncomputed: 'scale(0.85)',
+ expected: 'matrix(0.85, 0, 0, 0.85, 0, 0)',
+ round_error_ok: true },
+ { start: 'scale(2)', end: 'scale(-2)',
+ expected_uncomputed: 'scale(1)',
+ expected: 'matrix(1, 0, 0, 1, 0, 0)' },
+ { start: 'scale(2)', end: 'scale(-6)',
+ expected_uncomputed: 'scale(0)',
+ expected: 'matrix(0, 0, 0, 0, 0, 0)' },
+ { start: 'scale(2, 0.4)', end: 'none',
+ expected_uncomputed: 'scale(1.75, 0.55)',
+ expected: 'matrix(1.75, 0, 0, 0.55, 0, 0)',
+ round_error_ok: true },
+ { start: 'scaleX(3)', end: 'none',
+ expected_uncomputed: 'scaleX(2.5)',
+ expected: 'matrix(2.5, 0, 0, 1, 0, 0)' },
+ { start: 'scaleY(5)', end: 'none',
+ expected_uncomputed: 'scaleY(4)',
+ expected: 'matrix(1, 0, 0, 4, 0, 0)' },
+ { start: 'scaleZ(5)', end: 'none',
+ expected_uncomputed: 'scaleZ(4)',
+ expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1)' },
+ { start: 'none', end: 'scale3D(5, 5, 5)',
+ expected_uncomputed: 'scale3D(2, 2, 2)',
+ expected: 'matrix3d(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1)' },
+
+ // skew
+ { start: 'skewX(45deg)', end: 'none',
+ expected_uncomputed: 'skewX(33.75deg)' },
+ { start: 'skewY(45deg)', end: 'none',
+ expected_uncomputed: 'skewY(33.75deg)' },
+ { start: 'skew(45deg)', end: 'none',
+ expected_uncomputed: 'skew(33.75deg)' },
+ { start: 'skew(45deg, 45deg)', end: 'none',
+ expected_uncomputed: 'skew(33.75deg, 33.75deg)' },
+ { start: 'skewX(45deg)', end: 'skewX(-45deg)',
+ expected_uncomputed: 'skewX(22.5deg)' },
+ { start: 'skewX(0)', end: 'skewX(-45deg)',
+ expected_uncomputed: 'skewX(-11.25deg)' },
+ { start: 'skewY(45deg)', end: 'skewY(-45deg)',
+ expected_uncomputed: 'skewY(22.5deg)' },
+
+ // matrix : skewX
+ { start: 'matrix(1, 0, 3, 1, 0, 0)', end: 'none',
+ expected: 'matrix(1, 0, ' + 3 * 0.75 + ', 1, 0, 0)',
+ round_error_ok: true },
+ { start: 'skewX(0)', end: 'skewX(-45deg) translate(0)',
+ expected_uncomputed: 'skewX(-11.25deg) translate(0)' },
+ // matrix : rotate
+ { start: 'rotate(-30deg)', end: 'matrix(0, 1, -1, 0, 0, 0)',
+ expected: 'matrix(1, 0, 0, 1, 0, 0)',
+ round_error_ok: true },
+ { start: 'rotate(-30deg) translateX(0)',
+ end: 'translateX(0) rotate(-90deg)',
+ expected: computeMatrix('rotate(-45deg)'),
+ round_error_ok: true },
+ // extended shorter transform list
+ { start: 'skewY(60deg)', end: 'skewY(-60deg) translateX(0)',
+ expected_uncomputed: 'skewY(30deg) translateX(0)' },
+
+
+ // matrix decomposition
+
+ // Four pairs of the same matrix expressed different ways.
+ { start: 'matrix(-1, 0, 0, -1, 0, 0)', /* rotate(180deg) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(135deg)') },
+ { start: 'scale(-1)', end: 'none',
+ expected_uncomputed: 'scale(-0.5)',
+ expected: 'matrix(-0.5, 0, 0, -0.5, 0, 0)' },
+ { start: 'rotate(180deg)', end: 'none',
+ expected_uncomputed: 'rotate(135deg)' },
+ { start: 'rotate(-180deg)', end: 'none',
+ expected_uncomputed: 'rotate(-135deg)',
+ expected: computeMatrix('rotate(225deg)') },
+
+ // matrix followed by scale
+ { start: 'matrix(2, 0, 0, 2, 10, 20) scale(2)',
+ end: 'none',
+ expected: 'matrix(3.0625, 0, 0, 3.0625, 7.5, 15)' },
+
+ // ... and a bunch of similar possibilities. The spec isn't settled
+ // here; there are multiple options. See:
+ // http://lists.w3.org/Archives/Public/www-style/2010Jun/0602.html
+ { start: 'matrix(-1, 0, 0, 1, 0, 0)', /* scaleX(-1) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('scaleX(-0.5)') },
+
+ { start: 'matrix(1, 0, 0, -1, 0, 0)', /* rotate(-180deg) scaleX(-1) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(-135deg) scaleX(-0.5)') },
+
+ { start: 'matrix(0, 1, 1, 0, 0, 0)', /* rotate(-90deg) scaleX(-1) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(-67.5deg) scaleX(-0.5)') },
+
+ { start: 'matrix(0, -1, 1, 0, 0, 0)', /* rotate(-90deg) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(-67.5deg)') },
+
+ { start: 'matrix(0, 1, -1, 0, 0, 0)', /* rotate(90deg) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(67.5deg)') },
+
+ { start: 'matrix(0, -1, -1, 0, 0, 0)', /* rotate(90deg) scaleX(-1) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(67.5deg) scaleX(-0.5)') },
+
+ // Similar decomposition tests, but with skewX. I checked visually
+ // that the sign of the skew was correct by checking visually that
+ // the animations in
+ // https://dbaron.org/css/test/2010/transition-negative-determinant
+ // don't flip when they finish, and then wrote tests corresponding
+ // to the current code's behavior.
+ // ... start with four with positive determinants
+ { start: 'none',
+ end: 'matrix(1, 0, 1.5, 1, 0, 0)',
+ /* skewX(atan(1.5)) */
+ expected: 'matrix(1, 0, ' + 1.5 * 0.25 + ', 1, 0, 0)',
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(-1, 0, 2, -1, 0, 0)',
+ /* rotate(180deg) skewX(atan(-2)) */
+ expected: computeMatrix('rotate(45deg) matrix(1, 0, ' + -2 * 0.25 + ', 1, 0, 0)'),
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(0, -1, 1, -3, 0, 0)',
+ /* rotate(-90deg) skewX(atan(3)) */
+ expected: computeMatrix('rotate(-22.5deg) matrix(1, 0, ' + 3 * 0.25 + ', 1, 0, 0)'),
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(0, 1, -1, 4, 0, 0)',
+ /* rotate(90deg) skewX(atan(4)) */
+ expected: computeMatrix('rotate(22.5deg) matrix(1, 0, ' + 4 * 0.25 + ', 1, 0, 0)'),
+ round_error_ok: true },
+ // and then four with negative determinants
+ { start: 'none',
+ end: 'matrix(1, 0, 1, -1, 0, 0)',
+ /* rotate(-180deg) skewX(atan(-1)) scaleX(-1) */
+ expected: computeMatrix('rotate(-45deg) matrix(1, 0, ' + -1 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(-1, 0, -1, 1, 0, 0)',
+ /* skewX(atan(-1)) scaleX(-1) */
+ expected: computeMatrix('matrix(1, 0, ' + -1 * 0.25 + ', 1, 0, 0) scaleX(0.5)') },
+ { start: 'none',
+ end: 'matrix(0, 1, 1, -2, 0, 0)',
+ /* rotate(-90deg) skewX(atan(2)) scaleX(-1) */
+ expected: computeMatrix('rotate(-22.5deg) matrix(1, 0, ' + 2 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(0, -1, -1, 0.5, 0, 0)',
+ /* rotate(90deg) skewX(atan(0.5)) scaleX(-1) */
+ expected: computeMatrix('rotate(22.5deg) matrix(1, 0, ' + 0.5 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
+ round_error_ok: true },
+
+ // lists
+ { start: 'translate(10px) skewY(45deg)',
+ end: 'translate(30px) skewY(-45deg)',
+ expected_uncomputed: 'translate(15px) skewY(22.5deg)' },
+ { start: 'skewY(45deg) rotate(90deg)',
+ end: 'skewY(-45deg) rotate(90deg)',
+ expected_uncomputed: 'skewY(22.5deg) rotate(90deg)' },
+ { start: 'skewX(45deg) rotate(90deg)',
+ end: 'skewX(-45deg) rotate(90deg)',
+ expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' },
+
+ // extended lists
+ { start: 'skewY(45deg) rotate(90deg) translate(0)',
+ end: 'skewY(-45deg) rotate(90deg)',
+ expected_uncomputed: 'skewY(22.5deg) rotate(90deg) translate(0)' },
+ { start: 'skewX(-60deg) rotate(90deg) translate(0)',
+ end: 'skewX(60deg) rotate(90deg)',
+ expected_uncomputed: 'skewX(-30deg) rotate(90deg) translate(0)' },
+];
+
+// We intentionally use a non-default reference-box so we always serialize it.
+// Therefore, we can reuse these tests for clip-path and shape-outside.
+// Bug 1313619: Add some tests for two basic shapes with an explicit
+// reference-box and a default one, for each property (because they use
+// different default reference-box).
+const basicShapesTests = [
+ { start: "none", end: "none",
+ expected: ["none"] },
+ // none to shape
+ { start: "none",
+ end: "circle(500px at 500px 500px) content-box",
+ expected: ["circle", ["500px at 500px 500px"], "content-box"]
+ },
+ { start: "none",
+ end: "ellipse(500px 500px at 500px 500px) content-box",
+ expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"]
+ },
+ { start: "none",
+ end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
+ expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "content-box"]
+ },
+ { start: "none",
+ end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
+ expected: ["inset", ["500px round 500px"], "content-box"]
+ },
+ // matching functions
+ { start: "circle(100px)", end: "circle(500px)",
+ expected: ["circle", ["200px"]] },
+ { start: "ellipse(100px 100px)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["200px 200px"]] },
+ { start: "circle(100px at 100px 100px) content-box",
+ end: "circle(500px at 500px 500px) content-box",
+ expected: ["circle", ["200px at 200px 200px"], "content-box"]
+ },
+ { start: "ellipse(100px 100px at 100px 100px) content-box",
+ end: "ellipse(500px 500px at 500px 500px) content-box",
+ expected: ["ellipse", ["200px 200px at 200px 200px"], "content-box"]
+ },
+ { start: "polygon(evenodd, 100px 100px, 100px 100px) content-box",
+ end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
+ expected: ["polygon", ["evenodd, 200px 200px, 200px 200px"], "content-box"]
+ },
+ { start: "inset(100px 100px 100px 100px round 100px 100px) content-box",
+ end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
+ expected: ["inset", ["200px round 200px"], "content-box"]
+ },
+ // matching functions percentage
+ { start: "circle(100%)", end: "circle(500%)",
+ expected: ["circle", ["200%"]] },
+ { start: "ellipse(100% 100%)", end: "ellipse(500% 500%)",
+ expected: ["ellipse", ["200% 200%"]] },
+ { start: "circle(100% at 100% 100%) content-box",
+ end: "circle(500% at 500% 500%) content-box",
+ expected: ["circle", ["200% at 200% 200%"], "content-box"]
+ },
+ { start: "ellipse(100% 100% at 100% 100%) content-box",
+ end: "ellipse(500% 500% at 500% 500%) content-box",
+ expected: ["ellipse", ["200% 200% at 200% 200%"], "content-box"]
+ },
+ { start: "polygon(evenodd, 100% 100%, 100% 100%) content-box",
+ end: "polygon(evenodd, 500% 500%, 500% 500%) content-box",
+ expected: ["polygon", ["evenodd, 200% 200%, 200% 200%"], "content-box"]
+ },
+ { start: "inset(100% 100% 100% 100% round 100% 100%) content-box",
+ end: "inset(500% 500% 500% 500% round 500% 500%) content-box",
+ expected: ["inset", ["200% round 200%"], "content-box"] },
+ // matching functions with calc() values
+ { start: "circle(calc(80px + 20px))", end: "circle(calc(200px + 300px))",
+ expected: ["circle", ["200px"]] },
+ { start: "circle(calc(80% + 20%))", end: "circle(calc(200% + 300%))",
+ expected: ["circle", ["200%"]] },
+ { start: "circle(calc(10px + 20%))", end: "circle(calc(50px + 40%))",
+ expected: ["circle", ["calc(25% + 20px)"]] },
+ // matching functions with interpolation between percentage/pixel values
+ { start: "circle(20px)", end: "circle(100%)",
+ expected: ["circle", ["calc(25% + 15px)"]] },
+ { start: "ellipse(100% 100px at 8px 20%) content-box",
+ end: "ellipse(40px 4% at 80% 60px) content-box",
+ expected: ["ellipse", ["calc(75% + 10px) calc(1% + 75px) at " +
+ "calc(20% + 6px) calc(15% + 15px)"],
+ "content-box"] },
+ // no interpolation for keywords
+ { start: "circle()", end: "circle(50px)",
+ expected: ["circle", ["50px"]] },
+ { start: "circle(closest-side)", end: "circle(500px)",
+ expected: ["circle", ["500px"]] },
+ { start: "circle(farthest-side)", end: "circle(500px)",
+ expected: ["circle", ["500px"]] },
+ { start: "circle(500px)", end: "circle(farthest-side)",
+ expected: ["circle", ["farthest-side"]]},
+ { start: "circle(500px)", end: "circle(closest-side)",
+ expected: ["circle", [""]]},
+ { start: "ellipse()", end: "ellipse(50px 50px)",
+ expected: ["ellipse", ["50px 50px"]] },
+ { start: "ellipse(closest-side closest-side)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px"]] },
+ { start: "ellipse(farthest-side closest-side)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px"]] },
+ { start: "ellipse(farthest-side farthest-side)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px"]] },
+ { start: "ellipse(500px 500px)", end: "ellipse(farthest-side farthest-side)",
+ expected: ["ellipse", ["farthest-side farthest-side"]] },
+ { start: "ellipse(500px 500px)", end: "ellipse(closest-side closest-side)",
+ expected: ["ellipse", [""]] },
+ // mismatching boxes
+ { start: "circle(100px at 100px 100px) border-box",
+ end: "circle(500px at 500px 500px) content-box",
+ expected: ["circle", ["500px at 500px 500px"], "content-box"]
+ },
+ { start: "ellipse(100px 100px at 100px 100px) border-box",
+ end: "ellipse(500px 500px at 500px 500px) content-box",
+ expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"]
+ },
+ { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box",
+ end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
+ expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "content-box"]
+ },
+ { start: "inset(100px 100px 100px 100px round 100px 100px) border-box",
+ end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
+ expected: ["inset", ["500px round 500px"], "content-box"]
+ },
+ // mismatching functions
+ { start: "circle(100px at 100px 100px) content-box",
+ end: "ellipse(500px 500px at 500px 500px) content-box",
+ expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"]
+ },
+ { start: "inset(0px round 20px)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px"]]
+ },
+ // shape to reference box
+ { start: "circle(20px)", end: "content-box", expected: ["content-box"] },
+ { start: "content-box", end: "circle(20px)", expected: ["circle", ["20px"]] },
+ // url to shape
+ { start: "circle(20px)", end: "url(http://localhost/a.png)", expected: ["url", ["\"http://localhost/a.png\""]] },
+ { start: "url(http://localhost/a.png)", end: "circle(20px)", expected: ["circle", ["20px"]] },
+ // url to none
+ { start: "none", end: "url(http://localhost/a.png)", expected: ["url", ["\"http://localhost/a.png\""]] },
+ { start: "http://localhost/a.png", end: "none", expected: ["none"] },
+];
+
+const basicShapesWithFragmentUrlTests = [
+ // Fragment url to shape
+ { start: "circle(20px)", end: "url('#a')", expected: ["url", ["\"#a\""]] },
+ { start: "url('#a')", end: "circle(20px)", expected: ["circle", ["20px"]] },
+ // Fragment url to none
+ { start: "none", end: "url('#a')", expected: ["url", ["\"#a\""]] },
+ { start: "url('#a')", end: "none", expected: ["none"] },
+];
+
+// We have a lot of tests in web-platform-tests already, so here we only test
+// basic interpolation cases.
+const pathFunctionTests = [
+ { start: "none", end: "none",
+ expected: ["none"] },
+ // none to path
+ { start: "none",
+ end: "path('M 100 100')",
+ expected: ["path", '"M 100 100"']
+ },
+ // path to none
+ { start: "path('M 100 100')",
+ end: "none",
+ expected: ["none"]
+ },
+ // mismatch
+ {
+ start: "path('M 0 0 H 100 H 200')",
+ end: "path('M 0 0 H 500')",
+ expected: ["path", '"M 0 0 H 500"']
+ },
+ {
+ start: "path('M 0 0 V 100')",
+ end: "path('M 0 0 H 500')",
+ expected: ["path", '"M 0 0 H 500"']
+ },
+ // match
+ {
+ start: "path('M 100 100')",
+ end: "path('M 100 500')",
+ expected: ["path", '"M 100 200"']
+ },
+ {
+ start: "path('M 10 10 L 100 100')",
+ end: "path('M 10 10 L 100 500')",
+ expected: ["path", '"M 10 10 L 100 200"']
+ },
+ {
+ start: "path('M 10 10 H 100')",
+ end: "path('M 10 10 H 500')",
+ expected: ["path", '"M 10 10 H 200"']
+ },
+ {
+ start: "path('M 10 10 V 100')",
+ end: "path('M 10 10 V 500')",
+ expected: ["path", '"M 10 10 V 200"']
+ },
+ {
+ start: "path('M 10 10 C 32 42 52 62 120 2200')",
+ end: "path('M 10 10 C 40 50 60 70 200 3000')",
+ expected: ["path", '"M 10 10 C 34 44 54 64 140 2400"']
+ },
+ {
+ start: "path('M 10 10 S 45 67 89 123')",
+ end: "path('M 10 10 S 61 51 113 99')",
+ expected: ["path", '"M 10 10 S 49 63 95 117"']
+ },
+ {
+ start: "path('M 10 10 Q 32 42 120 2200')",
+ end: "path('M 10 10 Q 40 50 200 3000')",
+ expected: ["path", '"M 10 10 Q 34 44 140 2400"']
+ },
+ {
+ start: "path('M 10 10 T 100 200')",
+ end: "path('M 10 10 T 500 280')",
+ expected: ["path", '"M 10 10 T 200 220"']
+ },
+ {
+ start: "path('M 10 10 A 10 20 30 0 1 140 450')",
+ end: "path('M 10 10 A 50 60 70 0 1 380 290')",
+ expected: ["path", '"M 10 10 A 20 30 40 0 1 200 410"']
+ },
+ {
+ start: "path('M 10 10 A 10 20 30 1 0 140 450')",
+ end: "path('M 10 10 A 50 60 70 0 1 380 290')",
+ expected: ["path", '"M 10 10 A 20 30 40 1 0 200 410"']
+ },
+ // mix relative and absolute coordinates
+ {
+ start: "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')",
+ // =="path('M 10 20 H 40 V 80 H 50 V 70 L 160 130')"
+ end: "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')",
+ expected: ["path", '"M 40 50 H 60 V 100 H 70 V 90 L 170 140"']
+ },
+];
+
+const clipPathPathFunctionTests = [
+ // match fill-rule
+ {
+ start: "path(nonzero, 'M 100 100')",
+ end: "path(nonzero, 'M 100 500')",
+ expected: ["path", '"M 100 200"']
+ },
+ {
+ start: "path(evenodd, 'M 100 100')",
+ end: "path(evenodd, 'M 100 500')",
+ expected: ["path", 'evenodd, "M 100 200"']
+ },
+ // mismatch fill-rule
+ {
+ start: "path(nonzero, 'M 100 100')",
+ end: "path(evenodd, 'M 100 500')",
+ expected: ["path", 'evenodd, "M 100 500"']
+ },
+];
+
+var filterTests = [
+ { start: "none", end: "none",
+ expected: ["none"] },
+ // function from none (number/length)
+ { start: "none", end: "brightness(0.5)",
+ expected: ["brightness", 0.875] },
+ { start: "none", end: "contrast(0.5)",
+ expected: ["contrast", 0.875] },
+ { start: "none", end: "grayscale(0.5)",
+ expected: ["grayscale", 0.125] },
+ { start: "none", end: "invert(0.5)",
+ expected: ["invert", 0.125] },
+ { start: "none", end: "opacity(0.5)",
+ expected: ["opacity", 0.875] },
+ { start: "none", end: "saturate(0.5)",
+ expected: ["saturate", 0.875] },
+ { start: "none", end: "sepia(0.5)",
+ expected: ["sepia", 0.125] },
+ { start: "none", end: "blur(50px)",
+ expected: ["blur", 12.5] },
+ // function to none (number/length)
+ { start: "brightness(0.5)", end: "none",
+ expected: ["brightness", 0.625] },
+ { start: "contrast(0.5)", end: "none",
+ expected: ["contrast", 0.625] },
+ { start: "grayscale(0.5)", end: "none",
+ expected: ["grayscale", 0.375] },
+ { start: "invert(0.5)", end: "none",
+ expected: ["invert", 0.375] },
+ { start: "opacity(0.5)", end: "none",
+ expected: ["opacity", 0.625] },
+ { start: "saturate(0.5)", end: "none",
+ expected: ["saturate", 0.625] },
+ { start: "sepia(0.5)", end: "none",
+ expected: ["sepia", 0.375] },
+ { start: "blur(50px)", end: "none",
+ expected: ["blur", 37.5] },
+ // function to same function (number/length)
+ { start: "brightness(0.25)", end: "brightness(0.75)",
+ expected: ["brightness", 0.375] },
+ { start: "contrast(0.25)", end: "contrast(0.75)",
+ expected: ["contrast", 0.375] },
+ { start: "grayscale(0.25)", end: "grayscale(0.75)",
+ expected: ["grayscale", 0.375] },
+ { start: "invert(0.25)", end: "invert(0.75)",
+ expected: ["invert", 0.375] },
+ { start: "opacity(0.25)", end: "opacity(0.75)",
+ expected: ["opacity", 0.375] },
+ { start: "saturate(0.25)", end: "saturate(0.75)",
+ expected: ["saturate", 0.375] },
+ { start: "sepia(0.25)", end: "sepia(0.75)",
+ expected: ["sepia", 0.375] },
+ { start: "blur(25px)", end: "blur(75px)",
+ expected: ["blur", 37.5] },
+ // function to same function (percent)
+ { start: "brightness(25%)", end: "brightness(75%)",
+ expected: ["brightness", 0.375] },
+ { start: "contrast(25%)", end: "contrast(75%)",
+ expected: ["contrast", 0.375] },
+ { start: "grayscale(25%)", end: "grayscale(75%)",
+ expected: ["grayscale", 0.375] },
+ { start: "invert(25%)", end: "invert(75%)",
+ expected: ["invert", 0.375] },
+ { start: "opacity(25%)", end: "opacity(75%)",
+ expected: ["opacity", 0.375] },
+ { start: "saturate(25%)", end: "saturate(75%)",
+ expected: ["saturate", 0.375] },
+ { start: "sepia(25%)", end: "sepia(75%)",
+ expected: ["sepia", 0.375] },
+ // function to same function (percent, number/length)
+ { start: "brightness(0.25)", end: "brightness(75%)",
+ expected: ["brightness", 0.375] },
+ { start: "contrast(25%)", end: "contrast(0.75)",
+ expected: ["contrast", 0.375] },
+ // hue-rotate with different angle values
+ { start: "hue-rotate(0deg)", end: "hue-rotate(720deg)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0rad)", end: "hue-rotate("+4*Math.PI+"rad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0grad)", end: "hue-rotate(800grad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0turn)", end: "hue-rotate(2turn)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0deg)", end: "hue-rotate("+4*Math.PI+"rad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0turn)", end: "hue-rotate(800grad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0grad)", end: "hue-rotate("+4*Math.PI+"rad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0grad)", end: "hue-rotate(0turn)",
+ expected: ["hue-rotate", "0deg"] },
+ // multiple matching functions, same length
+ { start: "contrast(25%) brightness(0.25) blur(25px) sepia(75%)",
+ end: "contrast(75%) brightness(0.75) blur(75px) sepia(25%)",
+ expected: ["contrast", 0.375, "brightness", 0.375, "blur", 37.5, "sepia", 0.625] },
+ { start: "invert(25%) brightness(0.25) blur(25px) invert(50%) brightness(0.5) blur(50px)",
+ end: "invert(75%) brightness(0.75) blur(75px)",
+ expected: ["invert", 0.375, "brightness", 0.375, "blur", 37.5, "invert", 0.375, "brightness", 0.625, "blur", 37.5] },
+ // multiple matching functions, different length
+ { start: "contrast(25%) brightness(0.5) blur(50px)",
+ end: "contrast(75%)",
+ expected: ["contrast", 0.375, "brightness", 0.625, "blur", 37.5] },
+ // mismatching filter functions
+ { start: "contrast(0%)", end: "blur(10px)",
+ expected: ["blur", 10] },
+ // not supported interpolations
+ { start: "none", end: "url('#b')",
+ expected: ["url", "\"#b\""] },
+ { start: "url('#a')", end: "none",
+ expected: ["none"] },
+ { start: "url('#a')", end: "url('#b')",
+ expected: ["url", "\"#b\""] },
+ { start: "url('#a')", end: "blur(10px)",
+ expected: ["blur", 10] },
+ { start: "blur(10px)", end: "url('#a')",
+ expected: ["url", "\"#a\""] },
+ { start: "blur(0px) url('#a')", end: "blur(20px)",
+ expected: ["blur", 20] },
+ { start: "blur(0px)", end: "blur(20px) url('#a')",
+ expected: ["blur", 20, "url", "\"#a\""] },
+ { start: "contrast(0.25) brightness(0.25) blur(25px)",
+ end: "contrast(0.75) url('#a')",
+ expected: ["contrast", 0.75, "url", "\"#a\""] },
+ { start: "contrast(0.25) brightness(0.25) blur(75px)",
+ end: "brightness(0.75) contrast(0.75) blur(25px)",
+ expected: ["brightness", 0.75, "contrast", 0.75, "blur", 25] },
+ { start: "contrast(0.25) brightness(0.25) blur(25px)",
+ end: "contrast(0.75) brightness(0.75) contrast(0.75)",
+ expected: ["contrast", 0.75, "brightness", 0.75, "contrast", 0.75] },
+ // drop-shadow animation
+ { start: "none",
+ end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)",
+ expected: ["drop-shadow", "rgba(0, 0, 0, 0.25) 1px 1px 0px"] },
+ { start: "drop-shadow(rgb(0, 0, 0) 0px 0px 0px)",
+ end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)",
+ expected: ["drop-shadow", "rgb(0, 0, 0) 1px 1px 0px"] },
+ { start: "drop-shadow(#038000 4px 4px)",
+ end: "drop-shadow(8px 8px 8px red)",
+ expected: ["drop-shadow", "rgb(66, 96, 0) 5px 5px 2px"] },
+ { start: "blur(25px) drop-shadow(8px 8px)",
+ end: "blur(75px)",
+ expected: ["blur", 37.5, "drop-shadow", "rgba(0, 0, 0, 0.75) 6px 6px 0px"] },
+ { start: "blur(75px)",
+ end: "blur(25px) drop-shadow(8px 8px)",
+ expected: ["blur", 62.5, "drop-shadow", "rgba(0, 0, 0, 0.25) 2px 2px 0px"] },
+ { start: "drop-shadow(2px 2px blue)",
+ end: "none",
+ expected: ["drop-shadow", "rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px"] },
+];
+
+var prop;
+for (prop in supported_properties) {
+ // Test that prop is in the property database.
+ ok(prop in gCSSProperties, "property " + prop + " in gCSSProperties");
+
+ // Test that the entry has at least one test function.
+ ok(supported_properties[prop].length > 0,
+ "property " + prop + " must have at least one test function");
+}
+
+// Return a consistent sampling of |count| values out of |array|.
+function sample_array(array, count) {
+ if (count <= 0) {
+ ok(false, "unexpected count");
+ return [];
+ }
+ var ratio = array.length / count;
+ if (ratio <= 1) {
+ return array;
+ }
+ var result = new Array(count);
+ for (let i = 0; i < count; ++i) {
+ result[i] = array[Math.floor(i * ratio)];
+ }
+ return result;
+}
+
+// Test that transitions don't do anything (i.e., aren't supported) on
+// the properties not in our test list above (and not transition
+// properties themselves).
+for (prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (!(prop in supported_properties) &&
+ !skipped_transitionable_properties.includes(prop) &&
+ info.type != CSS_TYPE_TRUE_SHORTHAND &&
+ info.type != CSS_TYPE_LEGACY_SHORTHAND &&
+ !("alias_for" in info) &&
+ !prop.match(/^transition-/) &&
+ prop != "mask") {
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ div.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+
+ var all_values = info.initial_values.concat(info.other_values);
+
+ if (all_values.length > 50) {
+ // Since we're using an O(N^2) algorithm here, reduce the list of
+ // values that we want to test. (This test is really only testing
+ // that somebody didn't make a property animatable without
+ // modifying this test. The odds of somebody doing that without
+ // making at least one of the many pairs of values we have left
+ // animatable seems pretty low, at least relative to the chance
+ // that any pair of the values listed in property_database.js is
+ // animatable.)
+ //
+ // That said, we still try to use all of the start of the list on
+ // the assumption that the more basic values are likely to be at
+ // the beginning of the list.
+ all_values = [].concat(info.initial_values.slice(0,2),
+ sample_array(info.initial_values.slice(2), 6),
+ info.other_values.slice(0, 10),
+ sample_array(info.other_values.slice(10), 40));
+ }
+
+ var all_computed = [];
+ for (var idx in all_values) {
+ let val = all_values[idx];
+ div.style.setProperty(prop, val, "");
+ all_computed.push(cs.getPropertyValue(prop));
+ }
+ div.style.removeProperty(prop);
+
+ div.style.setProperty("transition", prop + " 20s linear", "");
+ for (let i = 0; i < all_values.length; ++i) {
+ for (let j = i + 1; j < all_values.length; ++j) {
+ div.style.setProperty(prop, all_values[i], "");
+ is(cs.getPropertyValue(prop), all_computed[i],
+ "transitions not supported for property " + prop +
+ " value " + all_values[i]);
+ div.style.setProperty(prop, all_values[j], "");
+ is(cs.getPropertyValue(prop), all_computed[j],
+ "transitions not supported for property " + prop +
+ " value " + all_values[j]);
+ }
+ }
+
+ div.style.removeProperty("transition");
+ div.style.removeProperty(prop);
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ div.style.removeProperty(prereq);
+ }
+ }
+ }
+}
+
+// Do 4-second linear transitions with -1 second transition delay and
+// linear timing function so that we can expect the transition to be
+// one quarter of the way through the value space right after changing
+// the property.
+div.style.setProperty("transition-duration", "4s", "");
+div.style.setProperty("transition-delay", "-1s", "");
+div.style.setProperty("transition-timing-function", "linear", "");
+for (prop in supported_properties) {
+ var tinfo = supported_properties[prop];
+ var info = gCSSProperties[prop];
+
+ isnot(info.type, CSS_TYPE_TRUE_SHORTHAND,
+ prop + " must not be a shorthand");
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ // We don't want the 19px font-size prereq of line-height, since we
+ // want to leave it 20px.
+ if (prop != "line-height" || prereq != "font-size") {
+ div.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+ }
+
+ for (var idx in tinfo) {
+ tinfo[idx](prop);
+ }
+
+ // Make sure to unset the property and stop transitions on it.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.removeProperty(prop);
+ cs.getPropertyValue(prop);
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ div.style.removeProperty(prereq);
+ }
+ }
+}
+div.style.removeProperty("transition");
+
+function get_distance(prop, v1, v2)
+{
+ return SpecialPowers.DOMWindowUtils
+ .computeAnimationDistance(div, prop, v1, v2);
+}
+
+function check_distance(prop, start, quarter, end)
+{
+ var sq = get_distance(prop, start, quarter);
+ var se = get_distance(prop, start, end);
+ var qe = get_distance(prop, quarter, end);
+
+ ok(Math.abs((sq * 4 - se) / se) < 0.0001, "property '" + prop + "': distance " + sq + " from start '" + start + "' to quarter '" + quarter + "' should be quarter distance " + se + " from start '" + start + "' to end '" + end + "'");
+ ok(Math.abs((qe * 4 - se * 3) / se) < 0.0001, "property '" + prop + "': distance " + qe + " from quarter '" + quarter + "' to end '" + end + "' should be three quarters distance " + se + " from start '" + start + "' to end '" + end + "'");
+}
+
+function test_length_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px", "");
+ is(cs.getPropertyValue(prop), "4px",
+ "length-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px", "");
+ is(cs.getPropertyValue(prop), "6px",
+ "length-valued property " + prop + ": interpolation of lengths");
+ check_distance(prop, "4px", "6px", "12px");
+}
+
+function test_length_clamped(prop) {
+ test_length_clamped_or_unclamped(prop, true);
+}
+
+function test_length_unclamped(prop) {
+ test_length_clamped_or_unclamped(prop, false);
+}
+
+function test_length_clamped_or_unclamped(prop, is_clamped) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px", "");
+ let zero_val = cs.getPropertyValue(prop); // Flushes
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100px", "");
+ (is_clamped ? is : isnot)(cs.getPropertyValue(prop), zero_val,
+ "length-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+// Test transition to/from the special 'flex-basis: content' keyword.
+function test_flex_basis_content_transition(prop) {
+ is(prop, "flex-basis", "this test function should only be called for 'flex-basis'");
+
+ // Test transition from length to 'content':
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "8px", "");
+ is(cs.getPropertyValue(prop), "8px",
+ "property " + prop + ": computed value before transition to 'content'");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "content", "");
+ is(cs.getPropertyValue(prop), "content",
+ "property " + prop + ": transition to 'content' (should be discrete)");
+
+ // Test transition from 'content' to length:
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "content", "");
+ is(cs.getPropertyValue(prop), "content",
+ "property " + prop + ": computed value before transition from 'content'");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "6px", "");
+ is(cs.getPropertyValue(prop), "6px",
+ "property " + prop + ": transition from 'content' (should be discrete)");
+}
+
+// Test using float values in the range [0, 1] (e.g. opacity)
+function test_float_zeroToOne_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0.3", "");
+ is(cs.getPropertyValue(prop), "0.3",
+ "float-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "0.8", "");
+ is(cs.getPropertyValue(prop), "0.425",
+ "float-valued property " + prop + ": interpolation of floats");
+ check_distance(prop, "0.3", "0.425", "0.8");
+}
+
+function test_float_zeroToOne_clamped(prop) {
+ test_float_zeroToOne_clamped_or_unclamped(prop, true);
+}
+function test_float_zeroToOne_unclamped(prop) {
+ test_float_zeroToOne_clamped_or_unclamped(prop, false);
+}
+
+function test_float_zeroToOne_clamped_or_unclamped(prop, is_clamped) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0", "");
+ is(cs.getPropertyValue(prop), "0",
+ "float-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "1", "");
+ (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0",
+ "float-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+// Test using float values in the range [1, infinity)
+function test_float_aboveOne_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "1", "");
+ is(cs.getPropertyValue(prop), "1",
+ "float-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "2.1", "");
+ is(cs.getPropertyValue(prop), "1.275",
+ "float-valued property " + prop + ": interpolation of floats");
+ check_distance(prop, "1", "1.275", "2.1");
+}
+
+function test_float_aboveZero_clamped(prop) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0", "");
+ is(cs.getPropertyValue(prop), "0",
+ "float-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "5", "");
+ is(cs.getPropertyValue(prop), "0",
+ "float-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_percent_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "25%", "");
+ var av = cs.getPropertyValue(prop);
+ var a = any_unit_to_num(av);
+ div.style.setProperty(prop, "75%", "");
+ var bv = cs.getPropertyValue(prop);
+ var b = any_unit_to_num(bv);
+ isnot(b, a, "different percentages (" + av + " and " + bv +
+ ") should be different for " + prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "25%", "");
+ var res = cs.getPropertyValue(prop);
+ is(any_unit_to_num(res) * 4, 3 * b + a,
+ "percent-valued property " + prop + ": interpolation of percents: " +
+ res + " should be a quarter of the way between " + bv + " and " + av);
+ ok(has_num(res),
+ "percent-valued property " + prop + ": percent computes to number");
+ check_distance(prop, "25%", "37.5%", "75%");
+}
+
+function test_percent_clamped(prop) {
+ test_percent_clamped_or_unclamped(prop, true);
+}
+
+function test_percent_unclamped(prop) {
+ test_percent_clamped_or_unclamped(prop, false);
+}
+
+function test_percent_clamped_or_unclamped(prop, is_clamped) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0%", "");
+ var zero_val = cs.getPropertyValue(prop); // flushes too
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "150%", "");
+ (is_clamped ? is : isnot)(cs.getPropertyValue(prop), zero_val,
+ "percent-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+// FIXME: This doesn't deal well with properties for which the resolved value
+// is not the used value, like stroke-dashoffset or stroke-width.
+function test_length_percent_calc_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0%", "");
+ var av = cs.getPropertyValue(prop);
+ var a = any_unit_to_num(av);
+ div.style.setProperty(prop, "100%", "");
+ var bv = cs.getPropertyValue(prop);
+ var b = any_unit_to_num(bv);
+ div.style.setProperty(prop, "100px", "");
+ var cv = cs.getPropertyValue(prop);
+ var c = any_unit_to_num(cv);
+ isnot(b, a, "different percentages (" + av + " and " + bv +
+ ") should be different for " + prop);
+
+ div.style.setProperty(prop, "50%", "");
+ var v1v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v1v) * 2, a + b,
+ "computed value before transition for " + prop + ": '" +
+ v1v + "' should be halfway " +
+ "between '" + av + "' + and '" + bv + "'.");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "200px", "");
+ var v2v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v2v) * 8, 5*a + 3*b + 4*c,
+ "interpolation between length and percent for " + prop + ": '"
+ + v2v + "'");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(25% + 100px)", "");
+ v1v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v1v) * 4, b + 4*c,
+ "computed value before transition for " + prop + ": '" + v1v + "'");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "75%", "");
+ v2v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v2v) * 8, 5*a + 3*b + 6*c,
+ "interpolation between calc() and percent for " + prop + ": '" +
+ v2v + "'");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "150px", "");
+ v1v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v1v) * 2, c * 3,
+ "computed value before transition for " + prop + ": '" + v1v + "'");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(50% + 50px)", "");
+ v2v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v2v) * 8, 7 * a + b + 10*c,
+ "interpolation between length and calc() for " + prop + ": '" +
+ v2v + "'");
+
+ check_distance(prop, "50%", "calc(37.5% + 50px)", "200px");
+ check_distance(prop, "calc(25% + 100px)", "calc(37.5% + 75px)",
+ "75%");
+ check_distance(prop, "150px", "calc(125px + 12.5%)",
+ "calc(50% + 50px)");
+}
+
+// This can deal well with properties for which the computed value
+// is not the used value, e.g. translate.
+function test_calc_wrapped_calc_transition(prop) {
+ // Test interpolation that computes to calc() (transition from % to px)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "20%", "");
+ is(cs.getPropertyValue(prop), "20%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 3px)",
+ "property " + prop + ": interpolation that computes to calc()");
+
+ check_distance(prop, "20%", "calc(15% + 3px)", "12px");
+
+ // Test interpolation that computes to calc() (transition from px to %)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "12px", "");
+ is(cs.getPropertyValue(prop), "12px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "20%", "");
+ is(cs.getPropertyValue(prop), "calc(5% + 9px)",
+ "property " + prop + ": interpolation that computes to calc()");
+
+ check_distance(prop, "12px", "calc(5% + 9px)", "20%");
+
+ // Test interpolation between calc() and non-calc()
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(40px + 10%)", "");
+ is(cs.getPropertyValue(prop), "calc(10% + 40px)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "30%", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 30px)",
+ "property " + prop + ": interpolation between calc() and non-calc()");
+
+ check_distance(prop, "calc(40px + 10%)", "calc(30px + 15%)", "30%");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "16px", "");
+ is(cs.getPropertyValue(prop), "16px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(8px + 60%)", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 14px)",
+ "property " + prop + ": interpolation between calc() and non-calc()");
+
+ check_distance(prop, "16px", "calc(14px + 15%)", "calc(8px + 60%)");
+}
+
+function test_number_transition(prop) {
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '10';
+ is(cs[prop], '10',
+ `number property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '50';
+ is(cs[prop], '20', `number property ${prop}: interpolation of numbers`);
+ check_distance(prop, '10', '20', '50');
+}
+
+function test_angle_transition(prop) {
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '45deg';
+ is(cs[prop], '45deg',
+ `angle property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '145deg';
+ is(cs[prop], '70deg',
+ `angle property ${prop}: interpolation of angles`);
+ check_distance(prop, '45deg', '70deg', '145deg');
+}
+
+function get_color_options(options) {
+ let {
+ get_color = x => x,
+ set_color = x => x,
+ is_shorthand = false,
+ } = options;
+ return { get_color, set_color, is_shorthand };
+}
+
+function test_color_transition(prop, options={}) {
+ let { get_color, set_color, is_shorthand } = get_color_options(options);
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, set_color("rgb(255, 28, 0)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(255, 28, 0)",
+ "color-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color("rgb(75, 84, 128)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(210, 42, 32)",
+ "color-valued property " + prop + ": interpolation of colors");
+
+ if (!is_shorthand) {
+ check_distance(prop, set_color("rgb(255, 28, 0)"),
+ set_color("rgb(210, 42, 32)"),
+ set_color("rgb(75, 84, 128)"));
+ }
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, set_color("rgb(0, 255, 0)"), "");
+ var color = get_color(cs.getPropertyValue(prop));
+ var vals = color.match(/rgb\(([^, ]*), ([^, ]*), ([^, ]*)\)/);
+ is(vals.length, 4,
+ "color-valued property " + prop + ": flush before clamping test (length)");
+ is(vals[1], "0",
+ "color-valued property " + prop + ": flush before clamping test (red)");
+ is(vals[2], "255",
+ "color-valued property " + prop + ": flush before clamping test (green)");
+ is(vals[3], "0",
+ "color-valued property " + prop + ": flush before clamping test (blue)");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color("rgb(255, 0, 128)"), "");
+ // FIXME: Once we support non-sRGB colors, these tests will need fixing.
+ color = get_color(cs.getPropertyValue(prop));
+ vals = color.match(/rgb\(([^, ]*), ([^, ]*), ([^, ]*)\)/);
+ is(vals.length, 4,
+ "color-valued property " + prop + ": clamping of negatives (length)");
+ is(vals[1], "0",
+ "color-valued property " + prop + ": clamping of negatives (red)");
+ is(vals[2], "255",
+ "color-valued property " + prop + ": clamping of above-range (green)");
+ is(vals[3], "0",
+ "color-valued property " + prop + ": clamping of negatives (blue)");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_currentcolor_transition(prop, options={}) {
+ let { get_color, set_color } = get_color_options(options);
+
+ const msg_prefix = `color-valued property ${prop}: `;
+ div.style.setProperty("transition-property", "none", "");
+ (prop == "color" ? div.parentNode : div).style.
+ setProperty("color", "rgb(128, 0, 0)", "");
+ div.style.setProperty(prop, set_color("rgb(0, 0, 128)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(0, 0, 128)",
+ msg_prefix + "computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color("currentcolor"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(32, 0, 96)",
+ msg_prefix + "interpolation of rgb color and currentcolor");
+
+ if (prop != "color") {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty("color", "rgb(128, 0, 0)", "");
+ div.style.setProperty(prop, set_color("rgb(0, 0, 128)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(0, 0, 128)",
+ msg_prefix + "computed value before transition");
+ div.style.setProperty("transition-property", `color, ${prop}`, "");
+ div.style.setProperty("color", "rgb(0, 128, 0)", "");
+ div.style.setProperty(prop, set_color("currentcolor"), "");
+ is(cs.getPropertyValue("color"), "rgb(96, 32, 0)",
+ "interpolation of rgb color property");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(24, 8, 96)",
+ msg_prefix + "interpolation of rgb color and interpolated currentcolor");
+ }
+
+ div.style.setProperty("transition-property", "none", "");
+ (prop == "color" ? div.parentNode : div).style.
+ setProperty("color", "rgba(128, 0, 0, 0.6)", "");
+ div.style.setProperty(prop, set_color("rgba(0, 0, 128, 0.8)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgba(0, 0, 128, 0.8)",
+ msg_prefix + "computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color("currentcolor"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgba(26, 0, 102, 0.75)",
+ msg_prefix + "interpolation of rgba color and currentcolor");
+
+ // It is not possible to check distance, because there is a hidden
+ // dimension for ratio of currentcolor.
+
+ (prop == "color" ? div.parentNode : div).style.removeProperty("color");
+}
+
+function test_auto_color_transition(prop, options={}) {
+ let { get_color, set_color } = get_color_options(options);
+
+ const msg_prefix = `color-valued property ${prop}: `;
+ const test_color = "rgb(51, 102, 153)";
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "auto", "");
+ if (prop == "scrollbar-color") {
+ is(cs.getPropertyValue(prop), "auto",
+ msg_prefix + "auto should not be resolved to rgb color");
+ } else {
+ let used_value_of_auto = get_color(cs.getPropertyValue(prop));
+ isnot(used_value_of_auto, test_color,
+ msg_prefix + "ensure used auto value is different than our test color");
+ }
+
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color(test_color), "");
+ is(get_color(cs.getPropertyValue(prop)), test_color,
+ msg_prefix + "not interpolatable between auto and rgb color");
+}
+
+function get_color_from_shorthand_value(value) {
+ var m = value.match(/rgba?\([^, ]*, [^, ]*, [^, ]*(?:, [^, ]*)?\)/);
+ isnot(m, null, "shorthand property value should contain color");
+ return m[0];
+}
+
+function test_color_shorthand_transition(prop) {
+ test_color_transition(prop, {
+ get_color: get_color_from_shorthand_value,
+ is_shorthand: true,
+ });
+}
+
+function test_currentcolor_shorthand_transition(prop) {
+ test_currentcolor_transition(prop, {
+ get_color: get_color_from_shorthand_value,
+ is_shorthand: true,
+ });
+}
+
+function test_scrollbar_color_transition(prop) {
+ function split_colors(value) {
+ const colors = value.match(/^(rgba?\(.+?\)) (rgba?\(.+?\))$/);
+ isnot(colors, null, "scrollbar-color should consist of two colors");
+ return { thumb: colors[1], track: colors[2] };
+ }
+ const TEST_FUNCS = [
+ test_color_transition,
+ test_currentcolor_transition,
+ test_auto_color_transition,
+ ];
+ for (let test_func of TEST_FUNCS) {
+ test_func(prop, {
+ get_color: value => split_colors(value).thumb,
+ set_color: value => value + " blue",
+ });
+ test_func(prop, {
+ get_color: value => split_colors(value).track,
+ set_color: value => "blue " + value,
+ });
+ }
+}
+
+function test_shape_or_url_equals(computedValStr, expected)
+{
+ // Check simple case "none"
+ if (computedValStr == "none" && computedValStr == expected[0]) {
+ return true;
+ }
+ // We will update the expected list in this function for checking the result,
+ // so we clone it first to avoid affecting the input parameter.
+ var expectedList = expected.slice();
+
+ var start = String(computedValStr);
+
+ var regBox = /\s*(content\-box|padding\-box|border\-box|margin\-box|view\-box|stroke\-box|fill\-box)/
+ var matches = computedValStr.split(regBox);
+ var expectRefBox = typeof expectedList[expectedList.length - 1] == "string" &&
+ expectedList[expectedList.length - 1].match(regBox) !== null;
+
+ // Found a reference box? Format: "shape()" or "shape() reference-box"
+ if (matches.length > 1) {
+ // Our split() did actually split the string, which means computedValStr
+ // contains a reference box. That reference box should be at the end,
+ // which means split() will have produced an empty string as the final
+ // entry in |matches|. Let's first ditch that empty string.
+ var trailingJunk = matches.pop();
+ is(trailingJunk, "", "reference box shouldn't have anything after it");
+
+ // Do we expect a reference box?
+ if (!expectRefBox) {
+ ok(false, "unexpected reference box found");
+ matches.pop(); // Get rid of it, so we can test the rest...
+ } else {
+ is(matches.pop(), expectedList.pop(), "Reference boxes should match");
+ }
+ } else {
+ // No reference box found. Did we expect one?
+ if (expectRefBox) {
+ ok(false, "expected reference box");
+ return false;
+ }
+ }
+ computedValStr = matches[0];
+ if (expectedList.length == 0) {
+ if (computedValStr == "") {
+ return true;
+ }
+ ok(false, "expected basic shape");
+ return false;
+ }
+
+ // The regular expression does not filter out the last parenthesis.
+ // Remove last character for now.
+ is(computedValStr.substring(computedValStr.length - 1, computedValStr.length),
+ ')', "Function should have close-paren");
+ computedValStr = computedValStr.substring(0, computedValStr.length - 1);
+
+ var regShape = /\)*\s*(circle|ellipse|polygon|inset|url)\(/
+ matches = computedValStr.split(regShape);
+ // First item must be empty. All other items are of functionName, functionValue.
+ if (!matches || matches.shift() != "") {
+ ok(false, "invalid value or unknown shape function");
+ return false;
+ }
+
+ // Check argument values.
+ if (matches[1] != expectedList[1]) {
+ ok(false, "function parameters mismatch");
+ return false;
+ }
+
+ return true;
+}
+
+function test_path_function_equals(computedValStr, expectedList)
+{
+ // Check simple case "none"
+ if (expectedList.length === 1 && computedValStr === expectedList[0]) {
+ return true;
+ }
+
+ var regex = /([a-z]+)\((.*)\)/;
+ matches = computedValStr.match(regex)
+ if (!matches || matches[0] != computedValStr) {
+ ok(false, "Invalid function value");
+ return false;
+ }
+
+ // Bug 1480665: Support ray() for motion path. For now, only path(...) is
+ // acceptable.
+ if (matches[1] != "path") {
+ ok(false, "Only support path function");
+ return false;
+ }
+
+ // Check argument values.
+ if (matches[2] != expectedList[1]) {
+ ok(false, "Function parameters mismatch");
+ return false;
+ }
+
+ return true;
+}
+
+function filter_function_list_equals(computedValStr, expectedList)
+{
+ // Check simple case "none"
+ if (computedValStr == "none" && computedValStr == expectedList[0]) {
+ return true;
+ }
+
+ // The regular expression does not filter out the last parenthesis.
+ // Remove last character for now.
+ is(computedValStr.substring(computedValStr.length - 1, computedValStr.length),
+ ')', "Last character should be close-paren");
+ computedValStr = computedValStr.substring(0, computedValStr.length - 1);
+
+ var reg = /\)*\s*(blur|brightness|contrast|grayscale|hue\-rotate|invert|opacity|saturate|sepia|drop\-shadow|url)\(/
+ var matches = computedValStr.split(reg);
+ // First item must be empty. All other items are of functionName, functionValue.
+ if (!matches || matches.shift() != "") {
+ ok(false, "computed style of 'filter' isn't in the format we expect");
+ return false;
+ }
+
+ // Odd items are the function name, even items the function value.
+ if (!matches.length || matches.length % 2 ||
+ expectedList.length != matches.length) {
+ ok(false, "computed style of 'filter' isn't in the format we expect");
+ return false;
+ }
+ for (let i = 0; i < matches.length; i += 2) {
+ var functionName = matches[i];
+ var functionValue = matches[i+1];
+ var expected = expectedList[i+1]
+ var tolerance = 0;
+ // Check if we have the expected function.
+ if (functionName != expectedList[i]) {
+ return false;
+ }
+ if (functionName == "blur") {
+ // Last two characters must be "px".
+ if (functionValue.search("px") != functionValue.length - 2) {
+ return false;
+ }
+ functionValue = functionValue.substring(0, functionValue.length - 2);
+ } else if (functionName == "hue-rotate") {
+ // Just check for string equality.
+ return functionValue == expected;
+ } else if (functionName == "drop-shadow" || functionName == "url") {
+ if (functionValue != expected) {
+ return false;
+ }
+ continue;
+ }
+ // Check if string is not a number or difference is not in tolerance level.
+ if (isNaN(functionValue) ||
+ Math.abs(parseFloat(functionValue) - expected) > tolerance) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function test_basic_shape_or_url_transition(prop) {
+ let tests = basicShapesTests;
+ if (prop === "clip-path") {
+ // Clip-path won't resolve fragment URLs.
+ tests = tests.concat(basicShapesWithFragmentUrlTests);
+ }
+
+ for (let test of tests) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, test.start, "");
+ cs.getPropertyValue(prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, test.end, "");
+ var actual = cs.getPropertyValue(prop);
+ ok(test_shape_or_url_equals(actual, test.expected),
+ prop + " property is " + actual + " expected values of " +
+ test.expected);
+ }
+}
+
+function test_path_function(prop) {
+ let tests = pathFunctionTests;
+ if (prop === "clip-path") {
+ // The syntax of path() in clip-path has fill-rule, so we have to test more.
+ tests = tests.concat(clipPathPathFunctionTests);
+ }
+
+ for (const test of tests) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, test.start, "");
+ cs.getPropertyValue(prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, test.end, "");
+ const actual = cs.getPropertyValue(prop);
+ ok(test_path_function_equals(actual, test.expected),
+ prop + " property is " + actual + " expected values of " +
+ test.expected[0] + "(" + test.expected[1] + ")");
+ }
+}
+
+function test_filter_transition(prop) {
+ for (let i in filterTests) {
+ var test = filterTests[i];
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, test.start, "");
+ cs.getPropertyValue(prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, test.end, "");
+ var actual = cs.getPropertyValue(prop);
+ ok(filter_function_list_equals(actual, test.expected),
+ "Filter property is " + actual + " expected values of " +
+ test.expected);
+ }
+}
+
+function test_shadow_transition(prop) {
+ var origTimingFunc = div.style.getPropertyValue("transition-timing-function");
+ var spreadStr = (prop == "box-shadow") ? " 0px" : "";
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "4px 8px 3px red", "");
+ is(cs.getPropertyValue(prop), "rgba(255, 0, 0, 0.25) 1px 2px 0.75px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation of shadows");
+ check_distance(prop, "none", "rgba(255, 0, 0, 0.25) 1px 2px 0.75px",
+ "4px 8px 3px red");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "#038000 4px 4px, 2px 2px blue", "");
+ is(cs.getPropertyValue(prop), "rgb(3, 128, 0) 4px 4px 0px" + spreadStr + ", rgb(0, 0, 255) 2px 2px 0px" + spreadStr,
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "8px 8px 8px red", "");
+ is(cs.getPropertyValue(prop), "rgb(66, 96, 0) 5px 5px 2px" + spreadStr + ", rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation of shadows");
+ check_distance(prop, "#038000 4px 4px, 2px 2px blue",
+ "rgb(66, 96, 0) 5px 5px 2px, rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px",
+ "8px 8px 8px red");
+
+ if (prop == "box-shadow") {
+ div.style.setProperty(prop, "8px 8px 8px red inset", "");
+ is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px inset",
+ "shadow-valued property " + prop + ": non-interpolable cases");
+ div.style.setProperty(prop, "8px 8px 8px 8px red inset", "");
+ is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 2px inset",
+ "shadow-valued property " + prop + ": interpolation of spread");
+ // Leave in same state whether in the |if| or not.
+ div.style.setProperty(prop, "8px 8px 8px red", "");
+ is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px",
+ "shadow-valued property " + prop + ": non-interpolable cases");
+ check_distance(prop, "8px 8px 8px red inset",
+ "rgb(255, 0, 0) 8px 8px 8px 2px inset",
+ "8px 8px 8px 8px red inset");
+ }
+
+ // Transition beween values with color and without color.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty("color", "rgb(3, 0, 0)", "");
+ div.style.setProperty(prop, "2px 2px 2px", "");
+ is(cs.getPropertyValue(prop), "rgb(3, 0, 0) 2px 2px 2px" + spreadStr,
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "8px 8px 8px red", "");
+ is(cs.getPropertyValue(prop), "rgb(66, 0, 0) 3.5px 3.5px 3.5px" + spreadStr,
+ "shadow-valued property " + prop +
+ ": interpolation values with/without color");
+
+ // Transition beween values without color.
+ var defaultColor = cs.getPropertyValue("color") + " ";
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "2px 2px 2px", "");
+ is(cs.getPropertyValue(prop), defaultColor + "2px 2px 2px" + spreadStr,
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "6px 14px 10px", "");
+ is(cs.getPropertyValue(prop), defaultColor + "3px 5px 4px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation without color");
+ check_distance(prop, "2px 2px 2px", "3px 5px 4px", "6px 14px 10px");
+
+ // Transition between values with currentcolor transitioning.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty("color", "rgb(0, 255, 0)", "");
+ div.style.setProperty(prop, "2px 2px 2px", "");
+ is(cs.getPropertyValue(prop), "rgb(0, 255, 0) 2px 2px 2px" + spreadStr,
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", "color, " + prop, "");
+ div.style.setProperty("color", "rgb(0, 0, 255)", "");
+ div.style.setProperty(prop, "6px 10px 14px red", "");
+ is(cs.getPropertyValue(prop), "rgb(64, 143, 48) 3px 4px 5px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation with interpolating" +
+ "currentcolor");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0px 0px black", "");
+ is(cs.getPropertyValue(prop), "rgb(0, 0, 0) 0px 0px 0px" + spreadStr,
+ "shadow-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "10px 10px 10px black", "");
+ var vals = cs.getPropertyValue(prop).split(" ");
+ is(vals.length, 6 + (prop == "box-shadow"), "unexpected number of values");
+ is(vals.slice(0, 3).join(" "), "rgb(0, 0, 0)",
+ "shadow-valued property " + prop + " (color): clamping of negatives");
+ isnot(vals[3], "0px",
+ "shadow-valued property " + prop + " (x): clamping of negatives");
+ isnot(vals[4], "0px",
+ "shadow-valued property " + prop + " (y): clamping of negatives");
+ is(vals[5], "0px",
+ "shadow-valued property " + prop + " (radius): clamping of negatives");
+ if (prop == "box-shadow") {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0px 0px 0px black", "");
+ is(cs.getPropertyValue(prop), "rgb(0, 0, 0) 0px 0px 0px 0px",
+ "shadow-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "10px 10px 10px 10px black", "");
+ var vals = cs.getPropertyValue(prop).split(" ");
+ is(vals.length, 7, "unexpected number of values");
+ is(vals.slice(0, 3).join(" "), "rgb(0, 0, 0)",
+ "shadow-valued property " + prop + " (color): clamping of negatives");
+ isnot(vals[3], "0px",
+ "shadow-valued property " + prop + " (x): clamping of negatives");
+ isnot(vals[4], "0px",
+ "shadow-valued property " + prop + " (y): clamping of negatives");
+ is(vals[5], "0px",
+ "shadow-valued property " + prop + " (radius): clamping of negatives");
+ isnot(vals[6], "0px",
+ "shadow-valued property " + prop + " (spread): clamping of negatives");
+ }
+
+ // A test case that timing function produces values greater than 1.0.
+ div.style.setProperty("transition-timing-function",
+ // This function produces 1.2989961788069297 at 25%.
+ "cubic-bezier(0, 1.5, 0, 1.5)", "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "0px 0px 0px rgba(100, 100, 100, 0.5)", "");
+ // The alpha value, 0.5 * 1.2989961788069297 * 255, is 165.622012798, and then
+ // converted to 0.649.
+ is(cs.getPropertyValue(prop), "rgba(100, 100, 100, 0.649) 0px 0px 0px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation of shadows with " +
+ "timing function which produces values greater than 1.0");
+
+ div.style.setProperty("transition-timing-function", origTimingFunc, "");
+}
+
+function test_dasharray_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "3", "");
+ is(cs.getPropertyValue(prop), "3px",
+ "dasharray-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "15px", "");
+ is(cs.getPropertyValue(prop), "6px",
+ "dasharray-valued property " + prop + ": interpolation of dasharray");
+ check_distance(prop, "3", "6", "15px");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "dasharray-valued property " + prop + ": non-interpolability of none");
+ div.style.setProperty(prop, "6,8px,4,4", "");
+ is(cs.getPropertyValue(prop), "6px, 8px, 4px, 4px",
+ "dasharray-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty(prop, "14, 12,16,16px", "");
+ is(cs.getPropertyValue(prop), "8px, 9px, 7px, 7px",
+ "dasharray-valued property " + prop + ": interpolation of dasharray");
+ check_distance(prop, "6,8px,4,4", "8,9,7,7", "14, 12,16,16px");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "dasharray-valued property " + prop + ": non-interpolability of none");
+ div.style.setProperty(prop, "8,16,4", "");
+ is(cs.getPropertyValue(prop), "8px, 16px, 4px",
+ "dasharray-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty(prop, "4,8,12,16", "");
+ is(cs.getPropertyValue(prop), "7px, 14px, 6px, 10px, 13px, 5px, 9px, 16px, 4px, 8px, 15px, 7px",
+ "dasharray-valued property " + prop + ": interpolation of dasharray");
+ check_distance(prop, "8,16,4", "7, 14, 6, 10, 13, 5, 9, 16, 4, 8, 15, 7",
+ "4,8,12,16");
+ div.style.setProperty(prop, "2,50%,6,10", "");
+ is(cs.getPropertyValue(prop),
+ "5.75px, calc(12.5% + 10.5px), 6px, 10px, 10.25px, calc(12.5% + 3.75px), 8.25px, 14.5px, 3.5px, calc(12.5% + 6px), 12.75px, 7.75px",
+ "dasharray-valued property " + prop + ": interpolability of mixed units");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "dasharray-valued property " + prop + ": non-interpolability of none");
+ div.style.setProperty(prop, "2,50%,6,10", "");
+ is(cs.getPropertyValue(prop), "2px, 50%, 6px, 10px",
+ "dasharray-valued property " + prop + ": non-interpolability of none");
+ div.style.setProperty(prop, "6,30%,2,2", "");
+ is(cs.getPropertyValue(prop), "3px, 45%, 5px, 8px",
+ "dasharray-valued property " + prop + ": interpolation of dasharray");
+ check_distance(prop, "2,50%,6,10", "3, 45%, 5, 8", "6,30%,2,2");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0,0%", "");
+ is(cs.getPropertyValue(prop), "0px, 0%",
+ "dasharray-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "5px, 25%", "");
+ is(cs.getPropertyValue(prop), "0px, 0%",
+ "dasharray-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_radius_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+
+ // FIXME: Test a square for now, since we haven't updated to the spec
+ // for vertical components being relative to the height.
+ // Note: We use powers of two here so the floating-point math comes out
+ // nicely.
+ div.style.setProperty("width", "256px", "");
+ div.style.setProperty("height", "256px", "");
+ div.style.setProperty("border", "none", "");
+ div.style.setProperty("padding", "0", "");
+
+ div.style.setProperty(prop, "3px", "");
+ is(cs.getPropertyValue(prop), "3px",
+ "radius-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "15px", "");
+ is(cs.getPropertyValue(prop), "6px",
+ "radius-valued property " + prop + ": interpolation of radius");
+ check_distance(prop, "3px", "6px", "15px");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "12.5%", "");
+ is(cs.getPropertyValue(prop), "12.5%",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "25%", "");
+ is(cs.getPropertyValue(prop), "15.625%",
+ "radius-valued property " + prop + ": interpolation of radius");
+ check_distance(prop, "12.5%", "15.625%", "25%");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "3px 8px", "");
+ is(cs.getPropertyValue(prop), "3px 8px",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "15px 12px", "");
+ is(cs.getPropertyValue(prop), "6px 9px",
+ "radius-valued property " + prop + ": interpolation of radius");
+ check_distance(prop, "3px 8px", "6px 9px", "15px 12px");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "12.5% 6.25%", "");
+ is(cs.getPropertyValue(prop), "12.5% 6.25%",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "25%", "");
+ is(cs.getPropertyValue(prop), "15.625% 10.9375%",
+ "radius-valued property " + prop + ": interpolation of radius");
+ check_distance(prop, "12.5% 6.25%", "15.625% 10.9375%", "25%");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "6.25% 12.5%", "");
+ is(cs.getPropertyValue(prop), "6.25% 12.5%",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "64px 16px", "");
+ is(cs.getPropertyValue(prop), "calc(4.6875% + 16px) calc(9.375% + 4px)",
+ "radius-valued property " + prop + ": interpolation of radius with mixed units");
+ check_distance(prop, "6.25% 12.5%",
+ "calc(4.6875% + 16px) calc(9.375% + 4px)",
+ "64px 16px");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(5px) 10px", "");
+ is(cs.getPropertyValue(prop), "5px 10px",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(25px) calc(50px)", "");
+ is(cs.getPropertyValue(prop), "10px 20px",
+ "radius-valued property " + prop + ": interpolation of radius with calc() units");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0px", "");
+ is(cs.getPropertyValue(prop), "0px",
+ "radius-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "10px 20px", "");
+ is(cs.getPropertyValue(prop), "0px",
+ "radius-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+
+ div.style.removeProperty("width");
+ div.style.removeProperty("height");
+ div.style.removeProperty("border");
+ div.style.removeProperty("padding");
+}
+
+function test_integer_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4", "");
+ is(cs.getPropertyValue(prop), "4",
+ "integer-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "-14", "");
+ is(cs.getPropertyValue(prop), "0",
+ "integer-valued property " + prop + ": interpolation of integers");
+ check_distance(prop, "6", "1", "-14");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "-4", "");
+ is(cs.getPropertyValue(prop), "-4",
+ "integer-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "8", "");
+ is(cs.getPropertyValue(prop), "-1",
+ "integer-valued property " + prop + ": interpolation of integers");
+ check_distance(prop, "-4", "-1", "8");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0", "");
+ is(cs.getPropertyValue(prop), "0",
+ "integer-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100", "");
+ isnot(cs.getPropertyValue(prop), "0",
+ "integer-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_font_weight(prop) {
+ is(prop, "font-weight", "only designed for one property");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "normal", "");
+ is(cs.getPropertyValue(prop), "400",
+ "font-weight property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "900", "");
+ is(cs.getPropertyValue(prop), "525",
+ "font-weight property " + prop + ": interpolation of font-weights");
+ check_distance(prop, "400", "500", "800");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "900", "");
+ is(cs.getPropertyValue(prop), "900",
+ "font-weight property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100", "");
+ is(cs.getPropertyValue(prop), "700",
+ "font-weight property " + prop + ": interpolation of font-weights");
+ check_distance(prop, "900", "700", "100");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "1", "");
+ is(cs.getPropertyValue(prop), "1",
+ "font-weight property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "1000", "");
+ is(cs.getPropertyValue(prop), "1",
+ "font-weight property " + prop + ": clamping of values");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "1000", "");
+ is(cs.getPropertyValue(prop), "1000",
+ "font-weight property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "1", "");
+ is(cs.getPropertyValue(prop), "1000",
+ "font-weight property " + prop + ": clamping of values");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_grid_gap(prop) {
+ test_length_transition(prop);
+ test_length_clamped(prop);
+ test_percent_transition(prop);
+ test_percent_clamped(prop);
+}
+
+function test_pos_integer_or_keyword_transition(prop, keyword) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4", "");
+ is(cs.getPropertyValue(prop), "4",
+ "integer-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "11", "");
+ is(cs.getPropertyValue(prop), "6",
+ "integer-valued property " + prop + ": interpolation of integers");
+ check_distance(prop, "4", "6", "12");
+ div.style.setProperty(prop, keyword, "");
+ is(cs.getPropertyValue(prop), keyword,
+ "integer-valued property " + prop + ": " + keyword + " not interpolable");
+ div.style.setProperty(prop, "8", "");
+ is(cs.getPropertyValue(prop), "8",
+ "integer-valued property " + prop + ": computed value before transition");
+ div.style.setProperty(prop, "4", "");
+ is(cs.getPropertyValue(prop), "7",
+ "integer-valued property " + prop + ": interpolation of integers");
+ check_distance(prop, "8", "7", "4");
+}
+
+function test_pos_integer_or_auto_transition(prop) {
+ return test_pos_integer_or_keyword_transition(prop, "auto");
+}
+
+function test_pos_integer_or_none_transition(prop) {
+ return test_pos_integer_or_keyword_transition(prop, "none");
+}
+
+function test_integer_at_least_one_clamping(prop) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "1", "");
+ is(cs.getPropertyValue(prop), "1",
+ "integer-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "5", "");
+ is(cs.getPropertyValue(prop), "1",
+ "integer-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_length_pair_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px 6px", "");
+ is(cs.getPropertyValue(prop), "4px 6px",
+ "length-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 10px", "");
+ is(cs.getPropertyValue(prop), "6px 7px",
+ "length-valued property " + prop + ": interpolation of lengths");
+ check_distance(prop, "4px 6px", "6px 7px", "12px 10px");
+}
+
+function test_length_pair_transition_clamped(prop) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0px", "");
+ is(cs.getPropertyValue(prop), "0px 0px",
+ "length-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "30px 50px", "");
+ is(cs.getPropertyValue(prop), "0px 0px",
+ "length-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_length_percent_pair_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px 50%", "");
+ is(cs.getPropertyValue(prop), "4px 5px",
+ "length-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 70%", "");
+ is(cs.getPropertyValue(prop), "6px 5.5px",
+ "length-valued property " + prop + ": interpolation of lengths");
+ check_distance(prop, "4px 50%", "6px 55%", "12px 70%");
+}
+
+function test_length_percent_pair_clamped(prop) {
+ test_length_percent_pair_clamped_or_unclamped(prop, true);
+}
+
+function test_length_percent_pair_unclamped(prop) {
+ test_length_percent_pair_clamped_or_unclamped(prop, false);
+}
+
+function test_length_percent_pair_clamped_or_unclamped(prop, is_clamped) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0%", "");
+ var is_zero = function(val) {
+ if (prop == "transform-origin" || prop == "perspective-origin") {
+ // These two properties resolve percentages to pixels.
+ return val == "0px 0px";
+ }
+ return val == "0px 0%";
+ }
+ ok(is_zero(cs.getPropertyValue(prop)),
+ "length+percent-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "30px 25%", "");
+ is(is_zero(cs.getPropertyValue(prop)), is_clamped,
+ "length+percent-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_rect_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "rect(4px, 16px, 12px, 6px)", "");
+ is(cs.getPropertyValue(prop), "rect(4px, 16px, 12px, 6px)",
+ "rect-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "rect(0px, 4px, 4px, 2px)", "");
+ is(cs.getPropertyValue(prop), "rect(3px, 13px, 10px, 5px)",
+ "rect-valued property " + prop + ": interpolation of rects");
+ check_distance(prop, "rect(4px, 16px, 12px, 6px)",
+ "rect(3px, 13px, 10px, 5px)",
+ "rect(0px, 4px, 4px, 2px)");
+ div.style.setProperty(prop, "rect(0px, 6px, 4px, auto)", "");
+ is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)",
+ "rect-valued property " + prop + ": can't interpolate auto components");
+ div.style.setProperty(prop, "rect(0px, 6px, 4px, 2px)", "");
+ div.style.setProperty(prop, "auto", "");
+ is(cs.getPropertyValue(prop), "auto",
+ "rect-valued property " + prop + ": can't interpolate auto components");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "rect(-10px, 30px, 0px, 0px)", "");
+ var vals = cs.getPropertyValue(prop).match(/rect\(([^, ]*), ([^, ]*), ([^, ]*), ([^, ]*)\)/);
+ is(vals.length, 5,
+ "rect-valued property " + prop + ": flush before clamping test (length)");
+ is(vals[1], "-10px",
+ "rect-valued property " + prop + ": flush before clamping test (top)");
+ is(vals[2], "30px",
+ "rect-valued property " + prop + ": flush before clamping test (right)");
+ is(vals[3], "0px",
+ "rect-valued property " + prop + ": flush before clamping test (bottom)");
+ is(vals[4], "0px",
+ "rect-valued property " + prop + ": flush before clamping test (left)");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "rect(0px, 40px, 10px, 10px)", "");
+ vals = cs.getPropertyValue(prop).match(/rect\(([^, ]*), ([^, ]*), ([^, ]*), ([^, ]*)\)/);
+ is(vals.length, 5,
+ "rect-valued property " + prop + ": clamping of negatives (length)");
+ isnot(vals[1], "-10px",
+ "rect-valued property " + prop + ": clamping of negatives (top)");
+ isnot(vals[2], "30px",
+ "rect-valued property " + prop + ": clamping of negatives (right)");
+ isnot(vals[3], "0px",
+ "rect-valued property " + prop + ": clamping of negatives (bottom)");
+ isnot(vals[4], "0px",
+ "rect-valued property " + prop + ": clamping of negatives (left)");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function do_test(prop, from_value, to_value, interp_value) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, from_value, "");
+ is(cs.getPropertyValue(prop), from_value,
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, to_value, "");
+ is(cs.getPropertyValue(prop), interp_value,
+ "property " + prop + ": interpolation of " + prop);
+}
+
+function do_negative_test(prop, from_value, to_value, interpolable) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, from_value, "");
+ is(cs.getPropertyValue(prop), from_value,
+ "property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, to_value, "");
+ is(cs.getPropertyValue(prop), interpolable ? from_value : to_value,
+ "property " + prop + ": clamping of negatives");
+}
+
+function do_overone_test(prop, from_value, to_value) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, from_value, "");
+ is(cs.getPropertyValue(prop), from_value,
+ "property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, to_value, "");
+ is(cs.getPropertyValue(prop), to_value,
+ "property " + prop + ": clamping of over-ones");
+}
+
+function test_visibility_transition(prop) {
+ do_test(prop, "visible", "hidden", "visible");
+ do_test(prop, "hidden", "visible", "visible");
+ do_test(prop, "hidden", "collapse", "collapse"); /* not interpolable */
+ do_test(prop, "collapse", "hidden", "hidden"); /* not interpolable */
+ do_test(prop, "visible", "collapse", "visible");
+ do_test(prop, "collapse", "visible", "visible");
+
+ isnot(get_distance(prop, "visible", "hidden"), 0,
+ "distance between visible and hidden should not be zero");
+ isnot(get_distance(prop, "visible", "collapse"), 0,
+ "distance between visible and collapse should not be zero");
+ is(get_distance(prop, "visible", "visible"), 0,
+ "distance between visible and visible should be zero");
+ is(get_distance(prop, "hidden", "hidden"), 0,
+ "distance between hidden and hidden should be zero");
+ is(get_distance(prop, "collapse", "collapse"), 0,
+ "distance between collapse and collapse should be zero");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ do_negative_test(prop, "visible", "hidden", true);
+ do_negative_test(prop, "hidden", "visible", true);
+ do_negative_test(prop, "hidden", "collapse", false);
+ do_negative_test(prop, "collapse", "hidden", false);
+ do_negative_test(prop, "visible", "collapse", true);
+ do_negative_test(prop, "collapse", "visible", true);
+
+ div.style.setProperty("transition-delay", "-3s", "");
+ div.style.setProperty("transition-timing-function", FUNC_OVERONE, "");
+ do_overone_test(prop, "visible", "hidden");
+ do_overone_test(prop, "hidden", "visible");
+ do_overone_test(prop, "hidden", "collapse");
+ do_overone_test(prop, "collapse", "hidden");
+ do_overone_test(prop, "visible", "collapse");
+ do_overone_test(prop, "collapse", "visible");
+
+ div.style.setProperty("transition-delay", "-1s", "");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_content_visibility_transition(prop) {
+ do_test(prop, "visible", "hidden", "visible");
+ do_test(prop, "hidden", "visible", "visible");
+ do_test(prop, "hidden", "auto", "auto");
+ do_test(prop, "auto", "hidden", "auto");
+ do_test(prop, "visible", "auto", "auto"); /* not interpolable */
+ do_test(prop, "auto", "visible", "visible"); /* not interpolable */
+
+ isnot(get_distance(prop, "visible", "hidden"), 0,
+ "distance between visible and hidden should not be zero");
+ isnot(get_distance(prop, "auto", "hidden"), 0,
+ "distance between auto and hidden should not be zero");
+ is(get_distance(prop, "visible", "visible"), 0,
+ "distance between visible and visible should be zero");
+ is(get_distance(prop, "hidden", "hidden"), 0,
+ "distance between hidden and hidden should be zero");
+ is(get_distance(prop, "auto", "auto"), 0,
+ "distance between auto and auto should be zero");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ do_negative_test(prop, "visible", "hidden", true);
+ do_negative_test(prop, "hidden", "visible", true);
+ do_negative_test(prop, "hidden", "auto", true);
+ do_negative_test(prop, "auto", "hidden", true);
+ do_negative_test(prop, "visible", "auto", false);
+ do_negative_test(prop, "auto", "visible", false);
+
+ div.style.setProperty("transition-delay", "-3s", "");
+ div.style.setProperty("transition-timing-function", FUNC_OVERONE, "");
+ do_overone_test(prop, "visible", "hidden");
+ do_overone_test(prop, "hidden", "visible");
+ do_overone_test(prop, "hidden", "auto");
+ do_overone_test(prop, "auto", "hidden");
+ do_overone_test(prop, "visible", "auto");
+ do_overone_test(prop, "auto", "visible");
+
+ div.style.setProperty("transition-delay", "-1s", "");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_background_size_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "50% 80%", "");
+ is(cs.getPropertyValue(prop), "50% 80%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100% 100%", "");
+ is(cs.getPropertyValue(prop), "62.5% 85%",
+ "property " + prop + ": interpolation of percents");
+ check_distance(prop, "50% 80%", "62.5% 85%", "100% 100%");
+ div.style.setProperty(prop, "contain", "");
+ is(cs.getPropertyValue(prop), "contain",
+ "property " + prop + ": can't interpolate 'contain'");
+ test_background_position_size_common(prop, true, true);
+}
+
+function test_background_position_transition(prop) {
+ var doesPropTakeListValues = (prop == "background-position") ||
+ (prop == "mask-position");
+ var doesPropHaveDistanceComputation = (prop != "background-position") &&
+ (prop != "mask-position");
+
+ // Test interpolation between edge keywords, and between edge keyword and a
+ // percent value. (Note: edge keywords are really aliases for percent vals.)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "center 80%", "");
+ is(cs.getPropertyValue(prop), "50% 80%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "bottom right", "");
+ is(cs.getPropertyValue(prop), "62.5% 85%",
+ "property " + prop + ": interpolation of edge keywords & percents");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "center 80%", "62.5% 85%", "bottom right");
+ }
+
+ // Test interpolation between edge keyword *with an offset* and non-keyword
+ // values.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "right 20px bottom 30%", "");
+ is(cs.getPropertyValue(prop), "calc(100% - 20px) 70%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(40px + 20%) calc(12px + 30%)", "");
+ is(cs.getPropertyValue(prop), "calc(80% - 5px) calc(60% + 3px)",
+ "property " + prop + ": interpolation of edge keywords w/ offsets & calc");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "right 20px bottom 30%",
+ "calc(-5px + 80%) calc(3px + 60%)",
+ "calc(40px + 20%) calc(12px + 30%)");
+ }
+
+ test_background_position_size_common(prop, doesPropTakeListValues,
+ doesPropHaveDistanceComputation);
+}
+
+function test_background_position_coord_transition(prop) {
+ var endEdge = prop.endsWith("-x") ? "right" : "bottom";
+
+ // Test interpolation between edge keywords, and between edge keyword and a
+ // percent value. (Note: edge keywords are really aliases for percent vals.)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "center", "");
+ is(cs.getPropertyValue(prop), "50%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, endEdge, "");
+ is(cs.getPropertyValue(prop), "62.5%",
+ "property " + prop + ": interpolation of edge keywords & percents");
+ check_distance(prop, "center", "62.5%", endEdge);
+
+ // Test interpolation between edge keyword *with an offset* and non-keyword
+ // values.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, `${endEdge} 20px`, "");
+ is(cs.getPropertyValue(prop), "calc(100% - 20px)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(40px + 20%)", "");
+ is(cs.getPropertyValue(prop), "calc(80% - 5px)",
+ "property " + prop + ": interpolation of edge keywords w/ offsets & calc");
+ check_distance(prop, `${endEdge} 20px`,
+ "calc(-5px + 80%)",
+ "calc(40px + 20%)");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px, 50px, 30px", "");
+ is(cs.getPropertyValue(prop), "10px, 50px, 30px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px, 70px, 30px", "");
+ is(cs.getPropertyValue(prop), "20px, 55px, 30px",
+ "property " + prop + ": interpolation of lists of lengths");
+ check_distance(prop, "10px, 50px, 30px",
+ "20px, 55px, 30px",
+ "50px, 70px, 30px");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px, 50%, 30%, 5px", "");
+ is(cs.getPropertyValue(prop), "10px, 50%, 30%, 5px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px, 70%, 30%, 25px", "");
+ is(cs.getPropertyValue(prop), "20px, 55%, 30%, 10px",
+ "property " + prop + ": interpolation of lists of lengths and percents");
+ check_distance(prop, "10px, 50%, 30%, 5px",
+ "20px, 55%, 30%, 10px",
+ "50px, 70%, 30%, 25px");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "20%, 8px", "");
+ is(cs.getPropertyValue(prop), "20%, 8px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px, 40%", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 3px), calc(10% + 6px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ check_distance(prop, "20%, 8px",
+ "calc(3px + 15%), calc(6px + 10%)",
+ "12px, 40%");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(20% + 40px), 8px, calc(20px + 12%)", "");
+ is(cs.getPropertyValue(prop), "calc(20% + 40px), 8px, calc(12% + 20px)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px, calc(20%), calc(8px + 20%)", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 33px), calc(5% + 6px), calc(14% + 17px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ check_distance(prop, "calc(20% + 40px), 8px, calc(20px + 12%)",
+ "calc(33px + 15%), calc(6px + 5%), calc(17px + 14%)",
+ "12px, calc(20%), calc(8px + 20%)");
+}
+
+/**
+ * Common tests for 'background-position', 'background-size', and other
+ * properties that take CSS value-type 'position' or 'bg-size'.
+ *
+ * @arg prop The name of the property
+ * @arg doesPropTakeListValues
+ * If false, the property is assumed to just take a single 'position' or
+ * 'bg-size' value. If true, the property is assumed to also accept
+ * comma-separated list of such values.
+ */
+function test_background_position_size_common(prop, doesPropTakeListValues,
+ doesPropHaveDistanceComputation) {
+ // Test non-list values
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "40% 0%", "");
+ is(cs.getPropertyValue(prop), "40% 0%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "0% 0%", "");
+ is(cs.getPropertyValue(prop), "30% 0%",
+ "property " + prop + ": interpolation of percentages");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "40% 0%", "30% 0%", "0% 0%");
+ }
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0% 40%", "");
+ is(cs.getPropertyValue(prop), "0% 40%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "0% 0%", "");
+ is(cs.getPropertyValue(prop), "0% 30%",
+ "property " + prop + ": interpolation of percentages");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "0% 40%", "0% 30%", "0% 0%");
+ }
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px 40px", "");
+ is(cs.getPropertyValue(prop), "10px 40px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px 0", "");
+ is(cs.getPropertyValue(prop), "20px 30px",
+ "property " + prop + ": interpolation of lengths");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "10px 40px", "20px 30px", "50px 0");
+ }
+
+ // Test interpolation that computes to to calc() (transition from % to px)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "20% 40%", "");
+ is(cs.getPropertyValue(prop), "20% 40%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 20px", "");
+ is(cs.getPropertyValue(prop),
+ "calc(15% + 3px) calc(30% + 5px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "20% 40%",
+ "calc(3px + 15%) calc(5px + 30%)",
+ "12px 20px");
+ }
+
+ // Test interpolation that computes to to calc() (transition from px to %)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "12px 20px", "");
+ is(cs.getPropertyValue(prop), "12px 20px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "20% 40%", "");
+ is(cs.getPropertyValue(prop),
+ "calc(5% + 9px) calc(10% + 15px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "12px 20px",
+ "calc(9px + 5%) calc(15px + 10%)",
+ "20% 40%");
+ }
+
+ // Test interpolation between calc() and non-calc()
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(40px + 10%) 16px", "");
+ is(cs.getPropertyValue(prop), "calc(10% + 40px) 16px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "30% calc(8px + 60%)", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 30px) calc(15% + 14px)",
+ "property " + prop + ": interpolation between calc() and non-calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "calc(40px + 10%) 16px",
+ "calc(30px + 15%) calc(14px + 15%)",
+ "30% calc(8px + 60%)");
+ }
+
+ // Test list values, if appropriate
+ if (doesPropTakeListValues) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px 40px, 50px 50px, 30px 20px", "");
+ is(cs.getPropertyValue(prop), "10px 40px, 50px 50px, 30px 20px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px 20px, 70px 50px, 30px 40px", "");
+ is(cs.getPropertyValue(prop), "20px 35px, 55px 50px, 30px 25px",
+ "property " + prop + ": interpolation of lists of lengths");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "10px 40px, 50px 50px, 30px 20px",
+ "20px 35px, 55px 50px, 30px 25px",
+ "50px 20px, 70px 50px, 30px 40px");
+ }
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px 40%, 50% 50px, 30% 20%, 5px 10px", "");
+ is(cs.getPropertyValue(prop), "10px 40%, 50% 50px, 30% 20%, 5px 10px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px 20%, 70% 50px, 30% 40%, 25px 50px", "");
+ is(cs.getPropertyValue(prop), "20px 35%, 55% 50px, 30% 25%, 10px 20px",
+ "property " + prop + ": interpolation of lists of lengths and percents");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "10px 40%, 50% 50px, 30% 20%, 5px 10px",
+ "20px 35%, 55% 50px, 30% 25%, 10px 20px",
+ "50px 20%, 70% 50px, 30% 40%, 25px 50px");
+ }
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "20% 40%, 8px 12px", "");
+ is(cs.getPropertyValue(prop), "20% 40%, 8px 12px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 20px, 40% 16%", "");
+ is(cs.getPropertyValue(prop),
+ "calc(15% + 3px) calc(30% + 5px), calc(10% + 6px) calc(4% + 9px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "20% 40%, 8px 12px",
+ "calc(3px + 15%) calc(5px + 30%), calc(6px + 10%) calc(9px + 4%)",
+ "12px 20px, 40% 16%");
+ }
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)", "");
+ is(cs.getPropertyValue(prop),
+ "calc(20% + 40px) calc(40% + 40px), 8px 12%, calc(12% + 20px) calc(8% + 24px)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)", "");
+ is(cs.getPropertyValue(prop),
+ "calc(15% + 33px) calc(35% + 30px), calc(5% + 6px) calc(24% + 4px), calc(14% + 17px) calc(10% + 28px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)",
+ "calc(33px + 15%) calc(30px + 35%), calc(6px + 5%) calc(4px + 24%), calc(17px + 14%) calc(28px + 10%)",
+ "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)");
+ }
+ }
+}
+
+function test_transform_transition(prop) {
+ is(prop, "transform", "Unexpected transform property! Test needs to be fixed");
+ var matrix_re = /^matrix\(([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*)\)$/;
+ for (let i in transformTests) {
+ var test = transformTests[i];
+ if (!("expected" in test)) {
+ var v = test.expected_uncomputed;
+ if (v.match(matrix_re) && !test.force_compute) {
+ test.expected = v;
+ } else {
+ test.expected = computeMatrix(v);
+ }
+ }
+ }
+
+ for (let i in transformTests) {
+ var test = transformTests[i];
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, test.start, "");
+ cs.getPropertyValue(prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, test.end, "");
+ var actual = cs.getPropertyValue(prop);
+ if (!test.round_error_ok || actual == test.expected) {
+ // In most cases, we'll get an exact match, but in some cases
+ // there can be a small amount of rounding error.
+ is(actual, test.expected,
+ "interpolation of transitions: " + test.start + " to " + test.end);
+ } else {
+ function s(mat) {
+ return mat.match(matrix_re).slice(1,7);
+ }
+ var pass = true;
+ var actual_split = s(actual);
+ var expected_split = s(test.expected);
+ for (let j = 0; j < 6; ++j) {
+ // Allow differences of 1 at the sixth decimal place, and allow
+ // a drop extra for floating point error from the subtraction.
+ if (Math.abs(Number(actual_split[j]) - Number(expected_split[j])) >
+ 0.0000011) {
+ pass = false;
+ }
+ }
+ ok(pass,
+ "interpolation of transitions: " + test.start + " to " + test.end +
+ ": " + actual + " should approximately equal " + test.expected);
+ }
+ }
+
+ // FIXME: should perhaps test that no clamping occurs
+
+ runOMTATest(runAsyncTests, SimpleTest.finish);
+}
+
+function test_rotate_transition(prop) {
+ // One value: <angle>
+ test_angle_transition(prop);
+
+ // With axis: <number> <number> <number> <angle>
+ //
+ // We don't test for interpolation of the numbers here since it's quite
+ // complicated and this is tested by the web-platform tests for this property.
+ // Now that we have web-platform tests for animation properties the main
+ // purpose of the tests in this file is to check that transitions run on the
+ // properties we expect them to.
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '0 1 0 45deg';
+ is(cs[prop], 'y 45deg',
+ `rotate property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '0 1 0 145deg';
+ is(cs[prop], 'y 70deg',
+ `rotate property ${prop}: interpolation of angles`);
+ check_distance(prop, '0 1 0 45deg', '0 1 0 70deg', '0 1 0 145deg');
+}
+
+function test_scale_transition(prop) {
+ // One value: <number>
+ test_number_transition(prop);
+
+ // Two values: <number> <number>
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '10 20';
+ is(cs[prop], '10 20',
+ `number property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '50 60';
+ is(cs[prop], '20 30', `number property ${prop}: interpolation of numbers`);
+ check_distance(prop, '10 20', '20 30', '50 60');
+
+ // Three values: <number> <number> <number>
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '10 20 30';
+ is(cs[prop], '10 20 30',
+ `number property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '50 60 70';
+ is(cs[prop], '20 30 40', `number property ${prop}: interpolation of numbers`);
+ check_distance(prop, '10 20 30', '20 30 40', '50 60 70');
+}
+
+function test_translate_transition(prop) {
+ // One value: <length-percentage>
+ test_length_transition(prop);
+ test_length_unclamped(prop);
+ test_percent_transition(prop);
+ test_percent_unclamped(prop);
+ test_calc_wrapped_calc_transition(prop);
+
+ // Two values: <length-percentage> <length-percentage>
+ // Note: Cannot use test_length_percent_pair_transition(prop) because we
+ // don't resolve the percentage.
+ test_length_pair_transition(prop);
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px 50%", "");
+ is(cs.getPropertyValue(prop), "4px 50%",
+ `length-valued property ${prop}: computed value before transition`);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 70%", "");
+ is(cs.getPropertyValue(prop), "6px 55%",
+ `length-valued property ${prop}: interpolation of lengths`);
+ check_distance(prop, "4px 50%", "6px 55%", "12px 70%");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px 50%", "");
+ is(cs.getPropertyValue(prop), "4px 50%",
+ `length-valued property ${prop}: computed value before transition`);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "20% 20px", "");
+ is(cs.getPropertyValue(prop), "calc(5% + 3px) calc(37.5% + 5px)",
+ `length-valued property ${prop}: interpolation of lengths`);
+ check_distance(prop, "4px 50%", "calc(5% + 3px) calc(37.5% + 5px)",
+ "20% 20px");
+ // We can't use test_length_percent_pair_unclamped here since
+ // it assumes that "0px 0px" is serialized as "0px 0px" but
+ // translate should serialize it as "0px".
+
+ // Three values: <length-percentage> <length-percentage> <length>
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '10px 200% 30px';
+ is(cs[prop], '10px 200% 30px',
+ `translate property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '50px 600% 70px';
+ is(cs[prop], '20px 300% 40px',
+ `translate property ${prop}: interpolation of three values`);
+ check_distance(prop, '10px 20px 30px', '20px 30px 40px', '50px 60px 70px');
+}
+
+function test_font_variations_transition(prop) {
+ is(prop, "font-variation-settings", "only designed for one property");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "\"wght\" 0, \"wdth\" 1.5", "");
+ // Note that computed-style returns the tags in sorted order.
+ is(cs.getPropertyValue(prop), "\"wdth\" 1.5, \"wght\" 0",
+ "font-variation-settings property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "\"wght\" 2, \"wdth\" 0.5", "");
+ is(cs.getPropertyValue(prop), "\"wdth\" 1.25, \"wght\" 0.5",
+ "font-variation-settings property " + prop + ": interpolation of font-variation-settings");
+ check_distance(prop, "\"wght\" 0, \"wdth\" 1.5", "\"wght\" 0.5, \"wdth\" 1.25", "\"wght\" 2, \"wdth\" 0.5");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "\"wght\" 2, \"wdth\" 0.5", "");
+ is(cs.getPropertyValue(prop), "\"wdth\" 0.5, \"wght\" 2",
+ "font-variation-settings property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "\"wght\" 0, \"wdth\" 1.5", "");
+ is(cs.getPropertyValue(prop), "\"wdth\" 0.75, \"wght\" 1.5",
+ "font-variation-settings property " + prop + ": interpolation of font-variation-settings");
+ check_distance(prop, "\"wght\" 2, \"wdth\" 0.5", "\"wght\" 1.5, \"wdth\" 0.75", "\"wght\" 0, \"wdth\" 1.5");
+}
+
+function test_aspect_ratio_transition(prop) {
+ [
+ // No transition between auto and <ratio>.
+ { start: "auto", end: "1 / 1",
+ expected: "1 / 1" },
+ // No transition between auto && <ratio> and <ratio>.
+ { start: "auto 1 / 1", end: "1 / 1",
+ expected: "1 / 1" },
+ // No transition between auto && <ratio> and auto.
+ { start: "auto 1 / 1", end: "auto",
+ expected: "auto" },
+ { start: "1 / 2", end: "8 / 1",
+ expected: "1 / 1" },
+ { start: "auto 1 / 2", end: "auto 8 / 1",
+ expected: "auto 1 / 1" },
+ ].forEach(test => {
+ div.style.transitionProperty = 'none';
+ div.style[prop] = test.start;
+ is(cs[prop], test.start,
+ `aspect-ratio: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = test.end;
+ is(cs[prop], test.expected,
+ `aspect-ratio: interpolation of aspect-ratio`);
+ // We check distance only if there is a transition.
+ if (test.end != test.expected) {
+ check_distance(prop, test.start, test.expected, test.end);
+ }
+ });
+}
+
+function test_auto_with_length_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "auto 4px", "");
+ is(cs.getPropertyValue(prop), "auto 4px",
+ "auto+length-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "auto 12px", "");
+ is(cs.getPropertyValue(prop), "auto 6px",
+ "auto+length-valued property " + prop + ": interpolation of lengths");
+ check_distance(prop, "auto 4px", "auto 6px", "auto 12px");
+}
+
+var OMTAdiv;
+var OMTACs;
+
+function prepareForOMTATest() {
+ if (OMTAdiv) {
+ OMTAdiv.remove();
+ }
+ OMTAdiv = document.createElement("div");
+ OMTAdiv.style = "height:100px; width:100px; background-color:blue;";
+ OMTAdiv.style.setProperty("transition-duration", "300s", "");
+ OMTAdiv.style.setProperty("transition-timing-function", "linear", "");
+ document.body.appendChild(OMTAdiv);
+
+ OMTACs = getComputedStyle(OMTAdiv, "");
+}
+
+function runAsyncTests() {
+ // These tests check the value on the compositor 2/3rds of the way through
+ // the transition.
+ // For the transform tests we simply compare the value on the compositor
+ // with the computed value, but for the opacity test we check the absolute
+ // value as well.
+ addAsyncTransformTests();
+ addAsyncOpacityTest();
+ addAsyncDelayTest();
+
+ runAllAsyncAnimTests().then(function() {
+ OMTAdiv.style.removeProperty("transition");
+ SimpleTest.finish();
+ });
+}
+
+function addAsyncTransformTests() {
+ transformTests.forEach(function(test) {
+ addAsyncAnimTest(function () { return runTransformTest(test); } );
+ });
+}
+
+async function runTransformTest(test) {
+ prepareForOMTATest();
+
+ OMTAdiv.style.setProperty("transition-property", "none", "");
+ OMTAdiv.style.setProperty("transform", test.start, "");
+ OMTACs.getPropertyValue("transform");
+ OMTAdiv.style.setProperty("transition-property", "transform", "");
+ OMTAdiv.style.setProperty("transform", test.end, "");
+ OMTACs.getPropertyValue("transform");
+ await waitForPaints();
+
+ // If the start value produced a non-invertible matrix the layer won't be
+ // created yet so we need to force an extra sample.
+ if (!isTransformInvertible(test.start)) {
+ winUtils.advanceTimeAndRefresh(100000);
+ await waitForPaints();
+ winUtils.advanceTimeAndRefresh(100000);
+ await waitForPaints();
+ } else {
+ winUtils.advanceTimeAndRefresh(200000);
+ await waitForPaints();
+ }
+
+ omta_is_approx(OMTAdiv, "transform", OMTACs.getPropertyValue("transform"),
+ 0.0001, RunningOn.Compositor,
+ "compositor transform transition " +
+ "from '" + test.start + "' " +
+ "to '" + test.end + "' " +
+ "at 2/3rds duration matches computed style");
+}
+
+function addAsyncOpacityTest() {
+ addAsyncAnimTest(async function() {
+ prepareForOMTATest();
+
+ OMTAdiv.style.setProperty("transition-property", "none", "");
+ OMTAdiv.style.setProperty("opacity", 0, "");
+ OMTACs.getPropertyValue("opacity");
+ OMTAdiv.style.setProperty("transition-property", "opacity", "");
+ OMTAdiv.style.setProperty("opacity", 1, "");
+ OMTACs.getPropertyValue("opacity");
+
+ await waitForPaints();
+
+ winUtils.advanceTimeAndRefresh(200000);
+
+ omta_is_approx(OMTAdiv, "opacity", 2/3, 0.00001, RunningOn.Compositor,
+ "compositor opacity transition at 2/3rds duration");
+ });
+}
+
+function addAsyncDelayTest() {
+ addAsyncAnimTest(async function() {
+ prepareForOMTATest();
+
+ OMTAdiv.style.setProperty("transition-property", "none", "");
+ OMTAdiv.style.setProperty("transition-delay", "100s", "");
+ OMTAdiv.style.setProperty("transition-duration", "200s", "");
+ OMTAdiv.style.setProperty("transform", "", "");
+ OMTACs.getPropertyValue("transform");
+ OMTAdiv.style.setProperty("transition-property", "transform", "");
+ OMTAdiv.style.setProperty("transform", "translate(100px)", "");
+ OMTACs.getPropertyValue("transform");
+
+ winUtils.advanceTimeAndRefresh(200000);
+ await waitForPaints();
+
+ omta_is_approx(OMTAdiv, "transform", { tx: 50 }, 0.0001,
+ RunningOn.Compositor,
+ "compositor transform transition with delay at 1/2"
+ + " duration");
+ });
+}
+
+function isTransformInvertible(transformStr) {
+ var computedStr = transformStrToComputedStr(transformStr);
+ if (!transformStr)
+ return false;
+ var matrix = convertTo3dMatrix(computedStr);
+ if (matrix === null)
+ return false;
+ return isInvertible(matrix);
+}
+
+function transformStrToComputedStr(transformStr) {
+ var div = document.createElement("div");
+ div.style.transform = transformStr;
+ return window.getComputedStyle(div).transform;
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_replacement_on_busy_frame.html b/layout/style/test/test_transitions_replacement_on_busy_frame.html
new file mode 100644
index 0000000000..527c98ae85
--- /dev/null
+++ b/layout/style/test/test_transitions_replacement_on_busy_frame.html
@@ -0,0 +1,100 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1167519
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for bug 1167519</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style>
+ #target {
+ height: 100px;
+ width: 100px;
+ background: green;
+ transition: transform 100s linear;
+ }
+ </style>
+</head>
+<body>
+<div id="target"></div>
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
+const omtaEnabled =
+ SpecialPowers.DOMWindowUtils.layerManagerRemote &&
+ SpecialPowers.getBoolPref(OMTAPrefKey);
+
+window.addEventListener('load', async function() {
+ if (!omtaEnabled) {
+ ok(true, 'Skipping the test since OMTA is disabled');
+ SimpleTest.finish();
+ return;
+ }
+
+ const div = document.getElementById('target');
+
+ // Start first transition
+ div.style.transform = 'translateX(300px)';
+ const firstTransition = div.getAnimations()[0];
+
+ // Wait for first transition to start running on the main thread and
+ // compositor.
+ await firstTransition.ready;
+ await waitForPaints();
+
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ // Start second transition
+ div.style.transform = 'translateX(600px)';
+ const secondTransition = div.getAnimations()[0];
+
+ const originalProperties = SpecialPowers.wrap(
+ secondTransition.effect
+ ).getProperties();
+ const previousPropertyValue = originalProperties[0].values[0].value;
+ const previousKeyframeValue = secondTransition.effect.getKeyframes()[0]
+ .transform;
+
+ // Tie up main thread for 300ms. In the meantime, the first transition
+ // will continue running on the compositor. If we don't update the start
+ // point of the second transition, it will appear to jump when it starts.
+ const startTime = performance.now();
+ while (performance.now() - startTime < 300);
+
+ // Ensure that our paint process has been done.
+ //
+ // Note that requestAnimationFrame is not suitable here since on Android
+ // there is a case where the paint process has not completed even when the
+ // requestAnimationFrame callback is run (and it is during the paint
+ // process that we update the transition start point).
+ await waitForPaints();
+
+ const updatedProperties = SpecialPowers.wrap(
+ secondTransition.effect
+ ).getProperties();
+ const currentPropertyValue = updatedProperties[0].values[0].value;
+ isnot(
+ currentPropertyValue,
+ previousPropertyValue,
+ 'From value of transition is updated since the moment when ' +
+ 'it was generated'
+ );
+ isnot(
+ secondTransition.effect.getKeyframes()[0].transform,
+ previousKeyframeValue,
+ 'Keyframe value of transition is updated since the moment when ' +
+ 'it was generated'
+ );
+ SimpleTest.finish();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_replacement_with_setKeyframes.html b/layout/style/test/test_transitions_replacement_with_setKeyframes.html
new file mode 100644
index 0000000000..85e9e40127
--- /dev/null
+++ b/layout/style/test/test_transitions_replacement_with_setKeyframes.html
@@ -0,0 +1,88 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1292001
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for bug 1292001</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style>
+ #target {
+ height: 100px;
+ width: 100px;
+ background: green;
+ transition: transform 100s linear;
+ }
+ </style>
+</head>
+<body>
+<div id="target"></div>
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
+const omtaEnabled =
+ SpecialPowers.DOMWindowUtils.layerManagerRemote &&
+ SpecialPowers.getBoolPref(OMTAPrefKey);
+
+window.addEventListener('load', async function() {
+ if (!omtaEnabled) {
+ ok(true, 'Skipping the test since OMTA is disabled');
+ SimpleTest.finish();
+ return;
+ }
+
+ const div = document.getElementById('target');
+
+ // Start first transition
+ div.style.transform = 'translateX(300px)';
+ const firstTransition = div.getAnimations()[0];
+
+ // But then change its keyframes to something completely different.
+ firstTransition.effect.setKeyframes({ 'opacity': ['0', '1'] });
+
+ // Wait for the transition to start running on the main thread and
+ // compositor.
+ await firstTransition.ready;
+ await waitForPaints();
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ // Start second transition
+ div.style.transform = 'translateX(600px)';
+ const secondTransition = div.getAnimations()[0];
+
+ const previousKeyframeValue = secondTransition.effect.getKeyframes()[0]
+ .transform;
+
+ // Tie up main thread for 300ms. In the meantime, the first transition
+ // will continue running on the compositor. If we don't update the start
+ // point of the second transition, it will appear to jump when it starts.
+ const startTime = performance.now();
+ while (performance.now() - startTime < 300);
+
+ // Ensure that our paint process has been done.
+ //
+ // (See explanation in test_transitions_replacement_on_busy_frame.html for
+ // why we don't use requestAnimationFrame here.)
+ await waitForPaints();
+
+ // Now check that the keyframes are NOT updated.
+ is(
+ secondTransition.effect.getKeyframes()[0].transform,
+ previousKeyframeValue,
+ 'Keyframe value of transition is NOT updated since the moment when ' +
+ 'it was generated'
+ );
+
+ SimpleTest.finish();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_step_functions.html b/layout/style/test/test_transitions_step_functions.html
new file mode 100644
index 0000000000..c0205a8d2f
--- /dev/null
+++ b/layout/style/test/test_transitions_step_functions.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ p.transition {
+ transition: margin-top 100s linear;
+ }
+
+ </style>
+</head>
+<body>
+<div id="display">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for transition step functions **/
+
+var display = document.getElementById("display");
+
+function run_test(tf, percent, value)
+{
+ var p = document.createElement("p");
+ p.className = "transition";
+ p.style.marginTop = "0px";
+ // be this percent of the way through a 100s transition
+ p.style.transitionDelay = (percent * -100) + "s";
+ p.style.transitionTimingFunction = tf;
+ display.appendChild(p);
+ var cs = getComputedStyle(p, "");
+ var flush1 = cs.marginTop;
+
+ p.style.marginTop = "1000px";
+ var result = px_to_num(cs.marginTop) / 1000
+
+ is(result, value, 100 * percent + "% of the way through " + tf);
+
+ display.removeChild(p);
+}
+
+run_test("step-start", 0, 1);
+run_test("step-start", 0.001, 1);
+run_test("step-start", 0.999, 1);
+run_test("step-start", 1, 1);
+run_test("step-end", 0, 0);
+run_test("step-end", 0.001, 0);
+run_test("step-end", 0.999, 0);
+run_test("step-end", 1, 1);
+
+run_test("steps(2)", 0.00, 0.0);
+run_test("steps(2)", 0.49, 0.0);
+run_test("steps(2)", 0.50, 0.5);
+run_test("steps(2)", 0.99, 0.5);
+run_test("steps(2)", 1.00, 1.0);
+
+run_test("steps(2, end)", 0.00, 0.0);
+run_test("steps(2, end)", 0.49, 0.0);
+run_test("steps(2, end)", 0.50, 0.5);
+run_test("steps(2, end)", 0.99, 0.5);
+run_test("steps(2, end)", 1.00, 1.0);
+
+run_test("steps(2, start)", 0.00, 0.5);
+run_test("steps(2, start)", 0.49, 0.5);
+run_test("steps(2, start)", 0.50, 1.0);
+run_test("steps(2, start)", 0.99, 1.0);
+run_test("steps(2, start)", 1.00, 1.0);
+
+run_test("steps(8,end)", 0.00, 0.0);
+run_test("steps(8,end)", 0.10, 0.0);
+run_test("steps(8,end)", 0.20, 0.125);
+run_test("steps(8,end)", 0.30, 0.25);
+run_test("steps(8,end)", 0.40, 0.375);
+run_test("steps(8,end)", 0.49, 0.375);
+run_test("steps(8,end)", 0.50, 0.5);
+run_test("steps(8,end)", 0.60, 0.5);
+run_test("steps(8,end)", 0.70, 0.625);
+run_test("steps(8,end)", 0.80, 0.75);
+run_test("steps(8,end)", 0.90, 0.875);
+run_test("steps(8,end)", 1.00, 1.0);
+
+// steps(_, jump-*)
+run_test("steps(2, jump-start)", 0.00, 0.5);
+run_test("steps(2, jump-start)", 0.49, 0.5);
+run_test("steps(2, jump-start)", 0.50, 1.0);
+run_test("steps(2, jump-start)", 0.99, 1.0);
+run_test("steps(2, jump-start)", 1.00, 1.0);
+
+run_test("steps(2, jump-end)", 0.00, 0.0);
+run_test("steps(2, jump-end)", 0.49, 0.0);
+run_test("steps(2, jump-end)", 0.50, 0.5);
+run_test("steps(2, jump-end)", 0.99, 0.5);
+run_test("steps(2, jump-end)", 1.00, 1.0);
+
+run_test("steps(1, jump-both)", 0.00, 0.5);
+run_test("steps(1, jump-both)", 0.10, 0.5);
+run_test("steps(1, jump-both)", 0.99, 0.5);
+run_test("steps(1, jump-both)", 1.00, 1.0);
+
+run_test("steps(3, jump-both)", 0.00, 0.25);
+run_test("steps(3, jump-both)", 0.33, 0.25);
+run_test("steps(3, jump-both)", 0.34, 0.5);
+run_test("steps(3, jump-both)", 0.66, 0.5);
+run_test("steps(3, jump-both)", 0.67, 0.75);
+run_test("steps(3, jump-both)", 0.99, 0.75);
+run_test("steps(3, jump-both)", 1.00, 1.0);
+
+run_test("steps(2, jump-none)", 0.00, 0.0);
+run_test("steps(2, jump-none)", 0.49, 0.0);
+run_test("steps(2, jump-none)", 0.50, 1.0);
+run_test("steps(2, jump-none)", 1.00, 1.0);
+
+run_test("steps(3, jump-none)", 0.00, 0.0);
+run_test("steps(3, jump-none)", 0.33, 0.0);
+run_test("steps(3, jump-none)", 0.34, 0.5);
+run_test("steps(3, jump-none)", 0.66, 0.5);
+run_test("steps(3, jump-none)", 0.67, 1.0);
+run_test("steps(3, jump-none)", 1.00, 1.0);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_unclosed_parentheses.html b/layout/style/test/test_unclosed_parentheses.html
new file mode 100644
index 0000000000..7e8052892c
--- /dev/null
+++ b/layout/style/test/test_unclosed_parentheses.html
@@ -0,0 +1,262 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575672
+-->
+<head>
+ <title>Test for Bug 575672</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style type="text/css" id="style"></style>
+ <style type="text/css">
+ #display { position: relative }
+ </style>
+ <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=575672">Mozilla Bug 575672</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for unclosed parentheses in CSS values. **/
+
+// Each of the following semicolon-terminated @-rules should have a
+// single missing ')' in the value.
+var semirules = [
+ "@import (",
+ "@import url(",
+ "@import url(foo",
+ "@import url('foo'",
+ "@import foo(",
+];
+
+// Each of the following declarations should have a single missing ')'
+// in the value.
+var declarations = [
+ "content: url(",
+ "content: url( ",
+ "content: url(http://www.foo.com",
+ "content: url('http://www.foo.com'",
+ "content: foobar(",
+ "content: foobar( ",
+ "content: foobar(http://www.foo.com",
+ "content: foobar('http://www.foo.com'",
+ "color: url(",
+ "color: url( ",
+ "color: url(http://www.foo.com",
+ "color: url('http://www.foo.com'",
+ "background-image: linear-gradient(",
+ "background-image: linear-gradient( ",
+ "background-image: linear-gradient(to",
+ "background-image: linear-gradient(to top",
+ "background-image: linear-gradient(to top left",
+ "background-image: linear-gradient(to top left,",
+ "background-image: repeating-linear-gradient(to top left, red, blue",
+ "background-image: linear-gradient(to top left, red, yellow, blue",
+ "background-image: linear-gradient(to top left, red 1px, yellow 5px, blue 10px",
+ "background-image: linear-gradient(to top left, red, yellow, rgb(0, 0, 255)",
+ "background-image: linear-gradient(red, blue",
+ "background-image: linear-gradient(red, yellow, blue",
+ "background-image: linear-gradient(red 1px, yellow 5px, blue 10px",
+ "background-image: linear-gradient(red, yellow, rgb(0, 0, 255)",
+ "background-image: radial-gradient(",
+ "background-image: radial-gradient( ",
+ "background-image: radial-gradient(at",
+ "background-image: radial-gradient(at ",
+ "background-image: radial-gradient(at center",
+ "background-image: radial-gradient(at center,",
+ "background-image: radial-gradient(at center ",
+ "background-image: radial-gradient(closest-corner",
+ "background-image: radial-gradient(farthest-side ",
+ "background-image: radial-gradient(closest-corner ellipse",
+ "background-image: radial-gradient(farthest-side circle ",
+ "background-image: radial-gradient(closest-corner ellipse at",
+ "background-image: radial-gradient(farthest-side circle at ",
+ "background-image: radial-gradient(closest-corner ellipse at center",
+ "background-image: radial-gradient(farthest-side circle at center ",
+ "background-image: radial-gradient(50px",
+ "background-image: radial-gradient(50px,",
+ "background-image: radial-gradient(50px ",
+ "background-image: radial-gradient(50px at",
+ "background-image: radial-gradient(50px at ",
+ "background-image: radial-gradient(50px at center",
+ "background-image: radial-gradient(50px at center ",
+ "background-image: radial-gradient(50px at center,",
+ "background-image: radial-gradient(50px 50px",
+ "background-image: radial-gradient(50px 50px,",
+ "background-image: radial-gradient(50px 50px ",
+ "background-image: radial-gradient(50px 50px at",
+ "background-image: radial-gradient(50px 50px at ",
+ "background-image: radial-gradient(50px 50px at center",
+ "background-image: radial-gradient(50px 50px at center ",
+ "background-image: radial-gradient(50px 50px at center,",
+ "background-image: radial-gradient(50px 50px at center, red, blue",
+ "background-image: radial-gradient(ellipse at",
+ "background-image: radial-gradient(ellipse at ",
+ "background-image: radial-gradient(circle",
+ "background-image: radial-gradient(circle ",
+ "background-image: radial-gradient(circle closest-corner",
+ "background-image: radial-gradient(circle farthest-side ",
+ "background-image: radial-gradient(ellipse closest-corner at center",
+ "background-image: radial-gradient(ellipse farthest-side at center,",
+ "background-image: radial-gradient(circle at center",
+ "background-image: radial-gradient(circle at center,",
+ "background-image: radial-gradient(circle at center ",
+ "background-image: radial-gradient(circle at 50px center",
+ "background-image: radial-gradient(circle at 50px center ",
+ "background-image: radial-gradient(ellipse 50px",
+ "background-image: radial-gradient(ellipse 50px ",
+ "background-image: radial-gradient(ellipse 50px 50px",
+ "background-image: radial-gradient(ellipse 50px 50px,",
+ "background-image: radial-gradient(ellipse 50px 50px ",
+ "background-image: radial-gradient(ellipse 50px 50px at",
+ "background-image: radial-gradient(ellipse 50px 50px at ",
+ "background-image: radial-gradient(ellipse 50px 50px at center",
+ "background-image: radial-gradient(ellipse 50px 50px at center ",
+ "background-image: radial-gradient(ellipse 50px 50px at center,",
+ "background-image: radial-gradient(ellipse 50px 50px at center, red, blue",
+ "background-image: radial-gradient(at top left, red, blue",
+ "background-image: radial-gradient(farthest-corner, red, blue",
+ "background-image: radial-gradient(ellipse closest-corner, red, hsl(240, 50%, 50%)",
+ "background-image: radial-gradient(farthest-side circle, red, blue",
+ "background-image: repeating-radial-gradient(50%",
+ "background-image: repeating-radial-gradient(50% ",
+ "background-image: repeating-radial-gradient(50% 50%",
+ "background-image: repeating-radial-gradient(50% 50%,",
+ "background-image: repeating-radial-gradient(50% 50%, red, blue",
+ "background-image: repeating-radial-gradient(circle, red, blue",
+ "color: rgb(",
+ "color: rgb( ",
+ "color: rgb(128, 0",
+ "color: rgb(128, 0, 128",
+ "color: rgb(128, 0, 128, 128",
+ "color: rgba(",
+ "color: rgba( ",
+ "color: rgba(128, 0",
+ "color: rgba(128, 0, 128",
+ "color: rgba(128, 0, 128, 1",
+ "color: rgba(128, 0, 128, 1, 1",
+ "color: hsl(",
+ "color: hsl( ",
+ "color: hsl(240, 50%",
+ "color: hsl(240, 50%, 50%",
+ "color: hsl(240, 50%, 50%, 50%",
+ "color: hsla(",
+ "color: hsla( ",
+ "color: hsla(240, 50%",
+ "color: hsla(240, 50%, 50%",
+ "color: hsla(240, 50%, 50%, 1",
+ "color: hsla(240, 50%, 50%, 1, 1",
+ "content: counter(",
+ "content: counter( ",
+ "content: counter(foo",
+ "content: counter(foo ",
+ "content: counter(foo,",
+ "content: counter(foo, ",
+ "content: counter(foo, upper-roman",
+ "content: counter(foo, upper-roman ",
+ "content: counter(foo, upper-roman,",
+ "content: counter(foo, upper-roman, ",
+ "content: counters(",
+ "content: counters( ",
+ "content: counters(foo, ','",
+ "content: counters(foo, ',' ",
+ "content: counters(foo, ',',",
+ "content: counters(foo, ',', ",
+ "content: counters(foo, ',', upper-roman",
+ "content: counters(foo, ',', upper-roman ",
+ "content: counters(foo, ',', upper-roman,",
+ "content: counters(foo, ',', upper-roman, ",
+ "content: attr(",
+ "content: attr( ",
+ "content: attr(href",
+ "content: attr(href ",
+ "content: attr(html",
+ "content: attr(html ",
+ "content: attr(html|",
+ "content: attr(html| ",
+ "content: attr(html|href",
+ "content: attr(html|href ",
+ "content: attr(|",
+ "content: attr(| ",
+ "content: attr(|href",
+ "content: attr(|href ",
+ "transition-timing-function: cubic-bezier(",
+ "transition-timing-function: cubic-bezier( ",
+ "transition-timing-function: cubic-bezier(0, 0, 1",
+ "transition-timing-function: cubic-bezier(0, 0, 1 ",
+ "transition-timing-function: cubic-bezier(0, 0, 1,",
+ "transition-timing-function: cubic-bezier(0, 0, 1, ",
+ "transition-timing-function: cubic-bezier(0, 0, 1, 1",
+ "transition-timing-function: cubic-bezier(0, 0, 1, 1 ",
+ "transition-timing-function: cubic-bezier(0, 0, 1, 1,",
+ "transition-timing-function: cubic-bezier(0, 0, 1, 1, ",
+ "border-top-width: calc(",
+ "border-top-width: calc( ",
+ "border-top-width: calc(2em",
+ "border-top-width: calc(2em ",
+ "border-top-width: calc(2em +",
+ "border-top-width: calc(2em + ",
+ "border-top-width: calc(2em *",
+ "border-top-width: calc(2em * ",
+ "border-top-width: calc((2em)",
+ "border-top-width: calc((2em) ",
+];
+
+var selectors = [
+ ":not(",
+ ":not( ",
+ ":not(-",
+ ":not(- ",
+ ":not(>",
+ ":not(> ",
+ ":not(div p",
+ ":not(div p ",
+ ":not(div >",
+ ":not(div > ",
+];
+
+var textNode = document.createTextNode("");
+document.getElementById("style").appendChild(textNode);
+var cs = getComputedStyle(document.getElementById("display"), "");
+
+for (var i = 0; i < semirules.length; ++i) {
+ var sheet = semirules[i] +
+ "p#display { color: red } ) ; p { color: green; z-index: " + (i + 1) + " }";
+ textNode.data = sheet;
+ is(cs.color, "rgb(0, 128, 0)",
+ "color for rule '" + semirules[i] + "'");
+ is(cs.zIndex, String(i + 1),
+ "z-index for rule '" + semirules[i] + "'");
+}
+
+for (var i = 0; i < declarations.length; ++i) {
+ var sheet = "@namespace html url(http://www.w3.org/1999/xhtml);\n" +
+ "#display { color: green; " + declarations[i] +
+ " x x x x x x x ; color: red; ) ; z-index: " + (i + 1) + " }";
+ textNode.data = sheet;
+ is(cs.color, "rgb(0, 128, 0)",
+ "color for declaration '" + declarations[i] + "'");
+ is(cs.zIndex, String(i + 1),
+ "z-index for declaration '" + declarations[i] + "'");
+}
+
+for (var i = 0; i < selectors.length; ++i) {
+ var sheet = "@namespace html url(http://www.w3.org/1999/xhtml);\n" +
+ "#display { color: green } " +
+ selectors[i] + " x x x x x x x , #display { color: red } #display { color: red } ) , #display { color: red } " +
+ "#display { z-index: " + (i + 1) + " }";
+ textNode.data = sheet;
+ is(cs.color, "rgb(0, 128, 0)",
+ "color for selector '" + selectors[i] + "'");
+ is(cs.zIndex, String(i + 1),
+ "z-index for selector '" + selectors[i] + "'");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_unicode_range_loading.html b/layout/style/test/test_unicode_range_loading.html
new file mode 100644
index 0000000000..43622e2ae5
--- /dev/null
+++ b/layout/style/test/test_unicode_range_loading.html
@@ -0,0 +1,366 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>unicode-range load tests using font loading api</title>
+ <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com">
+ <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#unicode-range-desc" />
+ <link rel="help" href="http://dev.w3.org/csswg/css-font-loading/" />
+ <meta name="assert" content="unicode-range descriptor defines precisely which fonts should be loaded" />
+ <script type="text/javascript" src="/resources/testharness.js"></script>
+ <script type="text/javascript" src="/resources/testharnessreport.js"></script>
+ <style type="text/css">
+ </style>
+</head>
+<body>
+<div id="log"></div>
+<pre id="display"></pre>
+<style id="testfonts"></style>
+<style id="teststyle"></style>
+<div id="testcontent"></div>
+
+<script>
+
+const kSheetFonts = 1;
+const kSheetStyles = 2;
+
+const redSquDataURL = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' width='100%' height='100%'><rect fill='red' x='0' y='0' width='10' height='10'/></svg>";
+
+var unicodeRangeTests = [
+ { test: "simple load sanity check, unused fonts not loaded",
+ fonts: [{ family: "a", src: "markA", descriptors: { }, loaded: false}],
+ content: "AAA", style: { "font-family": "unused" } },
+ { test: "simple load sanity check, font for a used character loaded",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true}],
+ content: "AAA" },
+ { test: "simple load sanity check, font for an unused character not loaded",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false}],
+ content: "BBB" },
+ { test: "simple load sanity check, with two fonts only font for used character loaded A",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}],
+ content: "AAA" },
+ { test: "simple load sanity check, with two fonts only font for used character loaded B",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}],
+ content: "BBB" },
+ { test: "simple load sanity check, two fonts but neither supports characters used",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}],
+ content: "CCC" },
+ { test: "simple load sanity check, two fonts and both are used",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}],
+ content: "ABC" },
+ { test: "simple load sanity check, one with Han ranges",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+3???,u+5???,u+7???,u+8???" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}],
+ content: "納豆嫌い" },
+ { test: "simple load sanity check, two fonts with different styles A",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { weight: "bold", unicodeRange: "u+42" }, loaded: false}],
+ content: "ABC" },
+ { test: "simple load sanity check, two fonts with different styles B",
+ fonts: [{ family: "a", src: "markA", descriptors: { weight: "bold", unicodeRange: "u+41" }, loaded: false},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}],
+ content: "ABC" },
+ { test: "multiple fonts with overlapping ranges, all with default ranges, only last one supports character used",
+ fonts: [{ family: "a", src: "markC", descriptors: { }, loaded: true},
+ { family: "a", src: "markA", descriptors: { }, loaded: true},
+ { family: "a", src: "markB", descriptors: { }, loaded: true}],
+ content: "CCC" },
+ { test: "multiple fonts with overlapping ranges, all with default ranges, first one supports character used",
+ fonts: [{ family: "a", src: "markB", descriptors: { }, loaded: false},
+ { family: "a", src: "markA", descriptors: { }, loaded: false},
+ { family: "a", src: "markC", descriptors: { }, loaded: true}],
+ content: "CCC" },
+ { test: "multiple fonts with overlapping ranges, one with default value in the fallback position",
+ fonts: [{ family: "a", src: "markC", descriptors: { }, loaded: true},
+ { family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}],
+ content: "ABC" },
+ { test: "multiple fonts with overlapping ranges, one with default value in the primary use position, fallback to one",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false},
+ { family: "a", src: "markC", descriptors: { }, loaded: true}],
+ content: "AAA" },
+ { test: "multiple fonts with overlapping ranges, one with default value in the primary use position, fallback to two",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true},
+ { family: "a", src: "markC", descriptors: { }, loaded: true}],
+ content: "ABC" },
+ { test: "multiple fonts with overlapping ranges, one with default value in the primary use position, no fallback",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false},
+ { family: "a", src: "markC", descriptors: { }, loaded: true}],
+ content: "CCC" },
+ { test: "metrics only case, ex-sized image, single font with space in range",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+0??" }, loaded: true}],
+ content: "<img style='width: 2ex' src=\"" + redSquDataURL + "\">" },
+ { test: "metrics only case, ex-sized image, single font with space outside range",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+1??" }, loaded: false}],
+ content: "<img style='width: 2ex' src=\"" + redSquDataURL + "\">" },
+ { test: "metrics only case, ch-sized image, single font with space in range",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+0??" }, loaded: true}],
+ content: "<img style='width: 2ch' src=\"" + redSquDataURL + "\">" },
+ { test: "metrics only case, ch-sized image, single font with space outside range",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+1??" }, loaded: false}],
+ content: "<img style='width: 2ch' src=\"" + redSquDataURL + "\">" },
+];
+
+// map font loading descriptor names to @font-face rule descriptor names
+var mapDescriptorNames = {
+ style: "font-style",
+ weight: "font-weight",
+ stretch: "font-stretch",
+ unicodeRange: "unicode-range",
+ variant: "font-variant",
+ featureSettings: "font-feature-settings"
+};
+
+var kBaseFontURL;
+if ("SpecialPowers" in window) {
+ kBaseFontURL = "";
+} else {
+ kBaseFontURL = "fonts/";
+}
+
+var mapFontURLs = {
+ markA: "url(" + kBaseFontURL + "markA.woff" + ")",
+ markB: "url(" + kBaseFontURL + "markB.woff" + ")",
+ markC: "url(" + kBaseFontURL + "markC.woff" + ")",
+ markD: "url(" + kBaseFontURL + "markD.woff" + ")",
+
+ /* twourl versions include a bogus url followed by a valid url */
+ markAtwourl: "url(" + kBaseFontURL + "bogus-markA.woff" + "), url(" + kBaseFontURL + "markA.woff" + ")",
+ markBtwourl: "url(" + kBaseFontURL + "bogus-markB.woff" + "), url(" + kBaseFontURL + "markB.woff" + ")",
+ markCtwourl: "url(" + kBaseFontURL + "bogus-markC.woff" + "), url(" + kBaseFontURL + "markC.woff" + ")",
+ markDtwourl: "url(" + kBaseFontURL + "bogus-markD.woff" + "), url(" + kBaseFontURL + "markD.woff" + ")",
+
+ /* localfont versions include a bogus local ref followed by a valid url */
+ markAlocalfirst: "local(bogus-markA), url(" + kBaseFontURL + "markA.woff" + ")",
+ markBlocalfirst: "local(bogus-markB), url(" + kBaseFontURL + "markB.woff" + ")",
+ markClocalfirst: "local(bogus-markC), url(" + kBaseFontURL + "markC.woff" + ")",
+ markDlocalfirst: "local(bogus-markD), url(" + kBaseFontURL + "markD.woff" + ")",
+};
+
+function familyName(name, i) {
+ return "test" + i + "-" + name;
+}
+
+function fontFaceRule(name, fontdata, ft) {
+ var desc = [];
+ desc.push("font-family: " + name);
+ var srckey = fontdata.src + ft;
+ desc.push("src: " + mapFontURLs[srckey]);
+ for (var d in fontdata.descriptors) {
+ desc.push(mapDescriptorNames[d] + ": " + fontdata.descriptors[d]);
+ }
+ return "@font-face { " + desc.join(";") + " }";
+}
+
+function clearRules(sheetIndex) {
+ var sheet = document.styleSheets[sheetIndex];
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+}
+
+function clearAllRulesAndFonts() {
+ clearRules(kSheetFonts);
+ clearRules(kSheetStyles);
+ document.fonts.clear();
+}
+
+function addStyleRulesAndText(testdata, i) {
+ // add style rules for testcontent
+ var sheet = document.styleSheets[kSheetStyles];
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+ var rule = [];
+ var family = familyName(testdata.fonts[0].family, i);
+ rule.push("#testcontent { font-family: " + family);
+ if ("style" in testdata) {
+ for (s in testdata.style) {
+ rule.push(s + ": " + testdata.style[s]);
+ }
+ }
+ rule.push("}");
+ sheet.insertRule(rule.join("; "), 0);
+
+ var content = document.getElementById("testcontent");
+ content.innerHTML = testdata.content;
+ content.offsetHeight;
+}
+
+// work arounds
+function getFonts() {
+ if ("forEach" in document.fonts) {
+ return document.fonts;
+ }
+ return Array.from(document.fonts);
+}
+
+function getSize() {
+ if ("size" in document.fonts) {
+ return document.fonts.size;
+ }
+ return getFonts().length;
+}
+
+function getReady() {
+ if (typeof(document.fonts.ready) == "function") {
+ return document.fonts.ready();
+ }
+ return document.fonts.ready;
+}
+
+function setTimeoutPromise(aDelay) {
+ return new Promise(function(aResolve, aReject) {
+ setTimeout(aResolve, aDelay);
+ });
+}
+
+function addFontFaceRules(testdata, i, ft) {
+ var sheet = document.styleSheets[kSheetFonts];
+ var createdFonts = [];
+ testdata.fonts.forEach(function(f) {
+ var n = sheet.cssRules.length;
+ var fn = familyName(f.family, i);
+ sheet.insertRule(fontFaceRule(fn, f, ft), n);
+ var newfont;
+ var fonts = getFonts();
+ try {
+ fonts.forEach(function(font) { newfont = font; });
+ createdFonts.push({family: fn, data: f, font: newfont});
+ } catch (e) {
+ console.log(e);
+ }
+ });
+ return createdFonts;
+}
+
+function addDocumentFonts(testdata, i, ft) {
+ var createdFonts = [];
+ testdata.fonts.forEach(function(fd) {
+ var fn = familyName(fd.family, i);
+ var srckey = fd.src + ft;
+ var f = new FontFace(fn, mapFontURLs[srckey], fd.descriptors);
+ document.fonts.add(f);
+ createdFonts.push({family: fn, data: fd, font: f});
+ });
+ return createdFonts;
+}
+
+var q = Promise.resolve();
+
+function runTests() {
+ function setupTests() {
+ setup({explicit_done: true});
+ }
+
+ function checkFontsBeforeLoad(name, testdata, fd) {
+ test(function() {
+ assert_equals(document.fonts.status, "loaded", "before initializing test, no fonts should be loading - found: " + document.fonts.status);
+ var size = getSize();
+ assert_equals(size, testdata.fonts.length,
+ "fonts where not added to the font set object");
+ var i = 0;
+ fonts = getFonts();
+ fonts.forEach(function(ff) {
+ assert_equals(ff.status, "unloaded", "added fonts should be in unloaded state");
+ });
+ }, name + " before load");
+ }
+
+ function checkFontsAfterLoad(name, testdata, fd, afterTimeout) {
+ test(function() {
+ assert_equals(document.fonts.status, "loaded", "after ready promise resolved, no fonts should be loading");
+ var i = 0;
+ fd.forEach(function(f) {
+ assert_true(f.font instanceof FontFace, "font needs to be an instance of FontFace object");
+ if (f.data.loaded) {
+ assert_equals(f.font.status, "loaded", "font not loaded - font " + i + " " + f.data.src + " "
+ + JSON.stringify(f.data.descriptors) + " for content " + testdata.content);
+ } else {
+ assert_equals(f.font.status, "unloaded", "font loaded - font " + i + " " + f.data.src + " "
+ + JSON.stringify(f.data.descriptors) + " for content " + testdata.content);
+ }
+ i++;
+ });
+ }, name + " after load" + (afterTimeout ? " and timeout" : ""));
+ }
+
+ function testFontLoads(testdata, i, name, fd) {
+ checkFontsBeforeLoad(name, testdata, fd);
+ addStyleRulesAndText(testdata, i);
+
+ var ready = getReady();
+ return ready.then(function() {
+ checkFontsAfterLoad(name, testdata, fd, false);
+ }).then(function() {
+ return setTimeoutPromise(0).then(function() {
+ checkFontsAfterLoad(name, testdata, fd, true);
+ });
+ }).then(function() {
+ var ar = getReady();
+ return ar.then(function() {
+ test(function() {
+ assert_equals(document.fonts.status, "loaded", "after ready promise fulfilled once, fontset should not be loading");
+ var fonts = getFonts();
+ fonts.forEach(function(f) {
+ assert_not_equals(f.status, "loading", "after ready promise fulfilled once, no font should be loading");
+ });
+ }, name + " test done check");
+ });
+ }).then(function() {
+ clearAllRulesAndFonts();
+ });
+ }
+
+ function testUnicodeRangeFontFace(testdata, i, ft) {
+ var name = "TEST " + i + " " + testdata.test + " (@font-face rules)" + (ft != "" ? " " + ft : ft);
+
+ var fd = addFontFaceRules(testdata, i, ft);
+ return testFontLoads(testdata, i, name, fd);
+ }
+
+ function testUnicodeRangeDocumentFonts(testdata, i, ft) {
+ var name = "TEST " + i + " " + testdata.test + " (document.fonts)" + (ft != "" ? " " + ft : ft);
+
+ var fd = addDocumentFonts(testdata, i, ft);
+ return testFontLoads(testdata, i, name, fd);
+ }
+
+ q = q.then(function() {
+ setupTests();
+ });
+
+ var fontTypes = ["", "twourl", "localfirst"];
+
+ unicodeRangeTests.forEach(function(testdata, i) {
+ fontTypes.forEach(function(ft) {
+ q = q.then(function() {
+ return testUnicodeRangeFontFace(testdata, i, ft);
+ }).then(function() {
+ return testUnicodeRangeDocumentFonts(testdata, i, ft);
+ });
+ });
+ });
+
+ q = q.then(function() {
+ done();
+ });
+}
+
+if ("fonts" in document) {
+ runTests();
+} else {
+ test(function() {
+ assert_true(true, "CSS Font Loading API is not enabled.");
+ }, "CSS Font Loading API is not enabled");
+}
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_units_angle.html b/layout/style/test/test_units_angle.html
new file mode 100644
index 0000000000..a4432b7650
--- /dev/null
+++ b/layout/style/test/test_units_angle.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for serialization and equivalence of angle units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for serialization and equivalence of angle units **/
+
+/**
+ * We test that for each of the following:
+ * + they reserialize to exactly what is given
+ * + if a mapping is provided, they compute to the same result as the mapping
+ */
+var tests = {
+ "45deg": "50grad",
+ "150grad": "135deg",
+ "1rad": null
+};
+
+var p = document.getElementById("display");
+
+for (var test in tests) {
+ p.setAttribute("style", "transform: rotate(" + test + ")");
+ is(p.style.getPropertyValue("transform"), "rotate(" + test + ")",
+ test + " serializes to exactly itself");
+ // We can't test any equivalence since we don't have any properties
+ // with angle values that we compute. (transform doesn't help.)
+/*
+ var equiv = tests[test];
+ if (equiv) {
+ var cm1 = getComputedStyle(p, "").elevation;
+ p.style.elevation = equiv;
+ var cm2 = getComputedStyle(p, "").elevation;
+ is(cm1, cm2, test + " should compute to the same as " + equiv);
+ }
+*/
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_units_frequency.html b/layout/style/test/test_units_frequency.html
new file mode 100644
index 0000000000..cb5c0de20d
--- /dev/null
+++ b/layout/style/test/test_units_frequency.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for serialization and equivalence of frequency units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for serialization and equivalence of frequency units **/
+
+/**
+ * We test that for each of the following:
+ * + they reserialize to exactly what is given
+ * + if a mapping is provided, they compute to the same result as the mapping
+ */
+var tests = {
+ "7kHz": "7000Hz",
+ "300Hz": "0.3khz"
+};
+
+var p = document.getElementById("display");
+
+for (var test in tests) {
+ // We can't test this because we no longer support any properties
+ // with frequency values.
+ todo(false, "no tests to run, for now");
+ /*
+ p.setAttribute("style", "pitch: " + test);
+ is(p.style.getPropertyValue("pitch"), test,
+ test + " serializes to exactly itself");
+ */
+ // We can't test any equivalence since we don't have any properties
+ // with frequency values that we compute.
+/*
+ var equiv = tests[test];
+ if (equiv) {
+ var cm1 = getComputedStyle(p, "").pitch;
+ p.style.pitch = equiv;
+ var cm2 = getComputedStyle(p, "").pitch;
+ is(cm1, cm2, test + " should compute to the same as " + equiv);
+ }
+*/
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_units_length.html b/layout/style/test/test_units_length.html
new file mode 100644
index 0000000000..623f13df33
--- /dev/null
+++ b/layout/style/test/test_units_length.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for serialization and equivalence of length units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for serialization and equivalence of length units **/
+
+/**
+ * We test that for each of the following:
+ * + they reserialize to exactly what is given
+ * + if a mapping is provided, they compute to the same result as the mapping
+ */
+var tests = {
+ "1in": "72pt",
+ "20mm": "2cm",
+ "2.54cm": "1in",
+ "36pt": "0.5in",
+ "4pc": "48pt",
+ "1em": null,
+ "3ex": null,
+ "57px": null,
+ "5rem": null
+};
+
+var p = document.getElementById("display");
+
+for (var test in tests) {
+ p.setAttribute("style", "margin-left: " + test);
+ is(p.style.getPropertyValue("margin-left"), test,
+ test + " serializes to exactly itself");
+ var equiv = tests[test];
+ if (equiv) {
+ var cm1 = getComputedStyle(p, "").marginLeft;
+ p.style.marginLeft = equiv;
+ var cm2 = getComputedStyle(p, "").marginLeft;
+
+ ok(Math.abs(parseFloat(cm1, 10) - parseFloat(cm2, 10)) <= 0.0001, test + " should compute to the same as " + equiv + ", modulo floating point math error");
+ }
+}
+
+// Bug 393910
+p.setAttribute("style", "margin-left: 0");
+is(p.style.getPropertyValue("margin-left"), "0px",
+ "0 serializes to 0px");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_units_time.html b/layout/style/test/test_units_time.html
new file mode 100644
index 0000000000..16211c0207
--- /dev/null
+++ b/layout/style/test/test_units_time.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for serialization and equivalence of time units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for serialization and equivalence of time units **/
+
+/**
+ * We test that for each of the following:
+ * + they reserialize to exactly what is given
+ * + if a mapping is provided, they compute to the same result as the mapping
+ */
+var tests = {
+ "3s": "3000ms",
+ "500ms": "0.5s"
+};
+
+var p = document.getElementById("display");
+
+for (var test in tests) {
+ p.setAttribute("style", "transition-duration: " + test);
+ is(p.style.getPropertyValue("transition-duration"), test,
+ test + " serializes to exactly itself");
+ var equiv = tests[test];
+ if (equiv) {
+ var cm1 = getComputedStyle(p, "").transitionDuration;
+ p.style.transitionDuration = equiv;
+ var cm2 = getComputedStyle(p, "").transitionDuration;
+ is(cm1, cm2, test + " should compute to the same as " + equiv);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_use_counters.html b/layout/style/test/test_use_counters.html
new file mode 100644
index 0000000000..0706c9702d
--- /dev/null
+++ b/layout/style/test/test_use_counters.html
@@ -0,0 +1,159 @@
+<!doctype html>
+<title>Test for Bug 1425700: CSS properties use-counters</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<body>
+<iframe id="iframe"></iframe>
+<iframe id="second-iframe"></iframe>
+<script>
+const iframe = document.getElementById("iframe");
+
+function iframe_reload(frame = iframe) {
+ return new Promise(resolve => {
+ frame.addEventListener("load", _ => resolve());
+ frame.contentWindow.location.reload();
+ });
+}
+
+function assert_recorded(win, recorded, properties, desc) {
+ const utils = SpecialPowers.getDOMWindowUtils(win);
+ isnot(properties.length, 0, "Sanity check");
+ for (const prop of properties) {
+ try {
+ is(utils.isCssPropertyRecordedInUseCounter(prop), recorded,
+ `${desc} - ${prop}`)
+ } catch(ex) {
+ ok(false, "Threw: " + prop);
+ }
+ }
+}
+
+// NOTE(emilio): This is no longer meaningful now we always record in the style
+// system itself, which is what this tests. But we could conceivably change
+// it so it doesn't hurt.
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ "set": [
+ ["layout.css.use-counters.enabled", true],
+ ["layout.css.use-counters-unimplemented.enabled", true]
+ ]
+ });
+});
+
+// TODO(emilio): Make work (and test) inline style and maybe even CSSOM and
+// such?
+//
+// Make sure that something on the lines of the following passes:
+//
+// element.style.webkitTransform = "rotate(1deg)"
+// assert_recorded(true, ["-webkit-transform"]);
+// assert_recorded(false, ["transform"]);
+//
+const IMPLEMENTED_PROPERTIES = {
+ description: "unimplemented properties",
+ css: `
+ * {
+ grid-gap: 1px; /* shorthand alias */
+ -webkit-background-size: 100px 100px; /* longhand alias */
+ transform-origin: top left; /* longhand */
+ background: green; /* shorthand */
+ }
+ `,
+ recorded: [
+ "grid-gap",
+ "-webkit-background-size",
+ "transform-origin",
+ "background",
+ ],
+ // Should only record the aliases, not the non-aliased property.
+ // Should only record shorthands, not the longhands it expands to.
+ not_recorded: [
+ "gap",
+ "background-size",
+ "-moz-transform-origin",
+ "-webkit-transform-origin",
+ "background-color",
+ ],
+};
+
+const UNIMPLEMENTED_PROPERTIES = {
+ description: "unimplemented properties",
+ css: `
+ * {
+ grid-gap: 1px; /* shorthand alias */
+ -webkit-background-size: 100px 100px; /* longhand alias */
+ transform-origin: top left; /* longhand */
+ background: green; /* shorthand */
+ -webkit-font-smoothing: auto; /* counted unknown */
+ }
+ `,
+ recorded: [
+ "grid-gap",
+ "-webkit-background-size",
+ "transform-origin",
+ "background",
+ "-webkit-font-smoothing",
+ ],
+ not_recorded: [
+ "size",
+ "speak",
+ ],
+};
+
+// Test on regular <style> elements.
+add_task(async () => {
+ for (let test of [IMPLEMENTED_PROPERTIES, UNIMPLEMENTED_PROPERTIES]) {
+ await iframe_reload();
+
+ const win = iframe.contentWindow;
+ const style = document.createElement('style');
+ style.textContent = test.css;
+
+ iframe.contentDocument.body.appendChild(style);
+
+ assert_recorded(win, true, test.recorded, `Test ${test.description} in <style> elements`);
+ assert_recorded(win, false, test.not_recorded, `Test ${test.description} in <style> elements`);
+ }
+});
+
+// Test on constructable stylesheets.
+add_task(async () => {
+ for (let test of [IMPLEMENTED_PROPERTIES, UNIMPLEMENTED_PROPERTIES]) {
+ for (let method of ["replace", "replaceSync"]) {
+ await iframe_reload();
+ const win = iframe.contentWindow;
+
+ const sheet = new win.CSSStyleSheet();
+ await sheet[method](test.css);
+
+ assert_recorded(win, true, test.recorded, `Test ${test.description} in constructed sheet`);
+ assert_recorded(win, false, test.not_recorded, `Test ${test.description} in constructed sheet`);
+ }
+ }
+});
+
+add_task(async () => {
+ // Test for <link rel="stylesheet">. One iteration for the uncached version, one for the cached one.
+ for (let test of [IMPLEMENTED_PROPERTIES, UNIMPLEMENTED_PROPERTIES]) {
+ const uri = "data:text/css," + encodeURIComponent(test.css);
+ for (let frame of [iframe, document.getElementById("second-iframe")]) {
+ await iframe_reload(frame);
+ const win = frame.contentWindow;
+ const doc = frame.contentDocument;
+
+ const link = doc.createElement("link");
+ link.rel = "stylesheet";
+ const linkLoaded = new Promise(resolve => {
+ link.onload = resolve;
+ });
+ link.href = uri;
+ doc.body.appendChild(link);
+ await linkLoaded;
+ assert_recorded(win, true, test.recorded, `Test ${test.description} in <link> ${frame.id}`);
+ assert_recorded(win, false, test.not_recorded, `Test ${test.description} in <link> ${frame.id}`);
+ }
+ }
+});
+
+</script>
+</body>
diff --git a/layout/style/test/test_user_sheet_shadow_dom.html b/layout/style/test/test_user_sheet_shadow_dom.html
new file mode 100644
index 0000000000..cd7a44b308
--- /dev/null
+++ b/layout/style/test/test_user_sheet_shadow_dom.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<title>Test for bug 1576229 - Nodes in Shadow DOM react properly to dynamic changes in user sheets</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<div></div>
+<span id="host" style="display: block"></span>
+<script>
+const gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService)
+
+const gSSService = SpecialPowers.Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(SpecialPowers.Ci.nsIStyleSheetService);
+
+const windowUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function loadUserSheet(style) {
+ const uri = gIOService.newURI("data:text/css," + style);
+ windowUtils.loadSheet(uri, windowUtils.USER_SHEET);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+onload = function() {
+ loadUserSheet(`
+ div {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+ }
+ .foo {
+ background-color: green;
+ }
+ `);
+ let host = document.querySelector("#host");
+ host.attachShadow({ mode: "open" }).innerHTML = `
+ <div></div>
+ `;
+ let light = document.querySelector('div');
+ let shadow = host.shadowRoot.querySelector('div');
+ is(getComputedStyle(light).backgroundColor, "rgb(255, 0, 0)", "User sheet works in light DOM");
+ is(getComputedStyle(shadow).backgroundColor, "rgb(255, 0, 0)", "User sheet works in shadow DOM");
+ light.classList.add("foo");
+ shadow.classList.add("foo");
+ is(getComputedStyle(light).backgroundColor, "rgb(0, 128, 0)", "Dynamic change for user sheet works in light DOM");
+ is(getComputedStyle(shadow).backgroundColor, "rgb(0, 128, 0)", "Dynamic change for user sheet works in shadow DOM");
+ SimpleTest.finish();
+}
+</script>
diff --git a/layout/style/test/test_value_cloning.html b/layout/style/test/test_value_cloning.html
new file mode 100644
index 0000000000..ceaa9d3c66
--- /dev/null
+++ b/layout/style/test/test_value_cloning.html
@@ -0,0 +1,181 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375363
+-->
+<head>
+ <title>Test for cloning of CSS property values (including 'inherit', 'initial' and 'unset')</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"><iframe id="iframe" src="about:blank"></iframe></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for cloning of CSS property values (including 'inherit', 'initial' and 'unset') **/
+var test_queue = [];
+var iframe = document.getElementById("iframe");
+
+SimpleTest.waitForExplicitFinish();
+
+for (var prop in gCSSProperties) {
+ let info = gCSSProperties[prop];
+
+ test_queue.push({ prop: prop, value: "inherit",
+ inherited_value: info.initial_values[0] });
+ test_queue.push({ prop: prop, value: "inherit",
+ inherited_value: info.other_values[0] });
+ test_queue.push({ prop: prop, value: "initial" });
+ if (info.inherited) {
+ test_queue.push({ prop: prop, value: "unset",
+ inherited_value: info.initial_values[0] });
+ test_queue.push({ prop: prop, value: "unset",
+ inherited_value: info.other_values[0] });
+ } else {
+ test_queue.push({ prop: prop, value: "unset" });
+ }
+ for (let idx in info.initial_values) {
+ test_queue.push({ prop: prop, value: info.initial_values[idx] });
+ }
+ for (let idx in info.other_values) {
+ test_queue.push({ prop: prop, value: info.other_values[idx] });
+ }
+}
+
+test_queue.reverse();
+
+doTest();
+
+function doTest()
+{
+ var sheet_data = "";
+
+ for (let idx = 0; idx < test_queue.length; ++idx) {
+ var current_item = test_queue[idx];
+
+ let info = gCSSProperties[current_item.prop];
+
+ sheet_data += "#parent"+idx+", #test"+idx+" { ";
+ for (var prereq in info.prereqs) {
+ sheet_data += prereq + ": " + info.prereqs[prereq] + ";";
+ }
+ sheet_data += " }";
+
+ sheet_data += "#parent"+idx+" { ";
+ if ("inherited_value" in current_item) {
+ sheet_data += current_item.prop + ": " + current_item.inherited_value;
+ }
+ sheet_data += "}";
+
+ sheet_data += "#test"+idx+" { ";
+ sheet_data += current_item.prop + ": " + current_item.value;
+ sheet_data += "}";
+ }
+
+ var sheet_url = "data:text/css," + escape(sheet_data);
+
+ var doc_data =
+ "<!DOCTYPE HTML>\n" +
+ "<link rel='stylesheet' type='text/css' href='" + sheet_url + "'>\n" +
+ "<link rel='stylesheet' type='text/css' href='" + sheet_url + "'>\n" +
+ "<body>\n";
+
+
+ for (let idx = 0; idx < test_queue.length; ++idx) {
+ var current_item = test_queue[idx];
+
+ if ("inherited_value" in current_item) {
+ doc_data += "<span id='parent"+idx+"'>";
+ }
+ doc_data += "<span id='test"+idx+"'></span>";
+ if ("inherited_value" in current_item) {
+ doc_data += "</span>";
+ }
+ }
+
+ var doc_url = "data:text/html," + escape(doc_data);
+ iframe.onload = iframe_loaded;
+ iframe.src = doc_url;
+}
+
+function iframe_loaded(event)
+{
+ if (event.target != iframe)
+ return;
+
+ var start_ser = [];
+ var start_compute = [];
+ var test_cs = [];
+ var wrappedFrame = SpecialPowers.wrap(iframe);
+ var ifdoc = wrappedFrame.contentDocument;
+ var ifwin = wrappedFrame.contentWindow;
+
+ for (let idx = 0; idx < test_queue.length; ++idx) {
+ var current_item = test_queue[idx];
+ var info = gCSSProperties[current_item.prop];
+
+ var test = ifdoc.getElementById("test" + idx);
+ var cur_cs = ifwin.getComputedStyle(test);
+ test_cs.push(cur_cs);
+ var cur_ser = ifdoc.styleSheets[0].cssRules[3*idx+2].style.getPropertyValue(current_item.prop);
+ if (cur_ser == "") {
+ isnot(cur_ser, "",
+ "serialization should be nonempty for " +
+ current_item.prop + ": " + current_item.value);
+ }
+ start_ser.push(cur_ser);
+
+ var cur_compute = get_computed_value(cur_cs, current_item.prop);
+ if (cur_compute == "") {
+ isnot(cur_compute, "",
+ "computed value should be nonempty for " +
+ current_item.prop + ": " + current_item.value);
+ }
+ start_compute.push(cur_compute);
+ }
+
+ // In case the above access didn't force a clone already (though it
+ // currently does), clone the second style sheet's inner and then
+ // remove the first.
+ ifdoc.styleSheets[1].insertRule("#nonexistent { color: red }", 0);
+ var firstlink = ifdoc.getElementsByTagName("link")[0];
+ firstlink.remove();
+
+ // Force a flush
+ ifdoc.body.style.display="none";
+ var ow = ifdoc.body.offsetWidth;
+ ifdoc.body.style.display="";
+
+ for (let idx = 0; idx < test_queue.length; ++idx) {
+ var current_item = test_queue[idx];
+ var info = gCSSProperties[current_item.prop];
+
+ var end_ser =
+ ifdoc.styleSheets[0].cssRules[3*idx+3].style.getPropertyValue(current_item.prop);
+ is(end_ser, start_ser[idx],
+ "serialization should match when cloning " +
+ current_item.prop + ": " + current_item.value);
+
+ var end_compute = get_computed_value(test_cs[idx], current_item.prop);
+ // Output computed values only when the test failed.
+ // Computed values may be very long.
+ if (end_compute == start_compute[idx]) {
+ ok(true,
+ "computed values should match when cloning " +
+ current_item.prop + ": " + current_item.value);
+ } else {
+ is(end_compute, start_compute[idx],
+ "computed values should match when cloning " +
+ current_item.prop + ": " + current_item.value);
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_value_computation.html b/layout/style/test/test_value_computation.html
new file mode 100644
index 0000000000..df38a24b9b
--- /dev/null
+++ b/layout/style/test/test_value_computation.html
@@ -0,0 +1,236 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for computation of values in property database</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <style type="text/css">
+ /* For 'width', 'height', etc., need a constant size container. */
+ #display { width: 500px; height: 200px }
+ </style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(2);
+
+ var load_count = 0;
+ function load_done() {
+ if (++load_count == 3)
+ run_tests();
+ }
+ </script>
+</head>
+<body>
+<iframe id="unstyledn" src="unstyled.xml" height="10" width="10" onload="load_done()"></iframe>
+<iframe id="unstyledf" src="unstyled-frame.xml" height="10" width="10" onload="load_done()"></iframe>
+<p id="display"><span><span id="elementf"></span></span></p>
+<div id="content" style="display: none">
+
+<div><span id="elementn"></span></div>
+
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for computation of values in property database **/
+
+var gBadComputedNoFrame = {
+ // These are probably bogus tests...
+ "-moz-margin-end": [ "0%", "calc(0% + 0px)" ],
+ "-moz-margin-start": [ "0%", "calc(0% + 0px)" ],
+ "-moz-padding-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "-moz-padding-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "margin": [ "0% 0px 0em 0pt" ],
+ "margin-block-end": [ "0%", "calc(0% + 0px)" ],
+ "margin-block-start": [ "0%", "calc(0% + 0px)" ],
+ "margin-bottom": [ "0%", "calc(0% + 0px)" ],
+ "margin-inline-end": [ "0%", "calc(0% + 0px)" ],
+ "margin-inline-start": [ "0%", "calc(0% + 0px)" ],
+ "margin-left": [ "0%", "calc(0% + 0px)" ],
+ "margin-right": [ "0%", "calc(0% + 0px)" ],
+ "margin-top": [ "0%", "calc(0% + 0px)" ],
+ "padding": [ "0% 0px 0em 0pt", "calc(0px) calc(0em) calc(-2px) calc(-1%)" ],
+ "padding-block-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-block-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-bottom": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-inline-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-inline-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-left": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-right": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-top": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+};
+
+function xfail_value(property, value, is_initial, has_frame) {
+ if (!has_frame && (property in gBadComputedNoFrame) &&
+ gBadComputedNoFrame[property].includes(value))
+ return true;
+
+ return false;
+}
+
+var gSwapInitialWhenHaveFrame = {
+ // When there's a frame, '-moz-available' works out to the same as
+ // 'auto' given the prerequisites of only 'display: block'.
+ "width": [ "-moz-available" ],
+ // When there's a frame, these keywords works out to the same as the initial
+ // value, i.e. `auto`, given the prerequisites of only 'display: block'.
+ "height": [ "-moz-max-content", "-moz-min-content", "-moz-fit-content",
+ "-moz-available", "max-content", "min-content", "fit-content",
+ "fit-content(100px)", "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))" ],
+ "block-size": [ "-moz-max-content", "-moz-min-content", "-moz-fit-content",
+ "-moz-available", "max-content", "min-content", "fit-content",
+ "fit-content(100px)", "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))" ],
+};
+
+function swap_when_frame(property, value) {
+ return (property in gSwapInitialWhenHaveFrame) &&
+ gSwapInitialWhenHaveFrame[property].includes(value);
+}
+
+var gDisplayTree = document.getElementById("display");
+var gElementN = document.getElementById("elementn");
+var gElementF = document.getElementById("elementf");
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)];
+var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)];
+
+var gInitialValuesN;
+var gInitialValuesF;
+var gInitialPrereqsRuleN;
+var gInitialPrereqsRuleF;
+
+function setup_initial_values(id, ivalprop, prereqprop) {
+ var iframe = document.getElementById(id);
+ window[ivalprop] = iframe.contentWindow.getComputedStyle(
+ iframe.contentDocument.documentElement.firstChild);
+ var sheet = iframe.contentDocument.styleSheets[0];
+ // For 'width', 'height', etc., need a constant size container.
+ sheet.insertRule(":root { height: 200px; width: 500px }", sheet.cssRules.length);
+
+ window[prereqprop] = sheet.cssRules[sheet.insertRule(":root > * {}", sheet.cssRules.length)];
+}
+
+function test_value(property, val, is_initial)
+{
+ var info = gCSSProperties[property];
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gRule1.style.setProperty(prereq, prereqs[prereq], "");
+ gInitialPrereqsRuleN.style.setProperty(prereq, prereqs[prereq], "");
+ gInitialPrereqsRuleF.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+ if (info.inherited && is_initial) {
+ gElementN.parentNode.style.setProperty(property, info.other_values[0], "");
+ gElementF.parentNode.style.setProperty(property, info.other_values[0], "");
+ }
+
+ var initial_computed_n = get_computed_value(gInitialValuesN, property);
+ var initial_computed_f = get_computed_value(gInitialValuesF, property);
+ if (is_initial) {
+ gRule1.style.setProperty(property, info.other_values[0], "");
+ var other_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property);
+ var other_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property);
+ isnot(other_computed_n, initial_computed_n,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ isnot(other_computed_f, initial_computed_f,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ }
+ // It used to be important for values that are supposed to compute to the
+ // initial value (given the design of the old rule tree, nsRuleNode) that
+ // we're modifying the most specific rule that matches the element, and
+ // that we've already requested style while that rule was empty. This
+ // means we'd have a cached aStartStruct from the parent in the rule
+ // tree (caching the "other" value), so we'd make sure we don't get the
+ // initial value from the luck of default-initialization. This means
+ // that it would've been important that we set the prereqs on
+ // gRule1.style rather than on gElement.style.
+ //
+ // However, the rule tree no longer stores cached structs, and we only
+ // temporarily cache reset structs during a single restyle. So the
+ // particular failure mode this was designed to test for isn't as
+ // likely to eventuate.
+ gRule2.style.setProperty(property, val, "");
+ var val_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property);
+ var val_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property);
+ isnot(val_computed_n, "",
+ "should not get empty value for '" + property + ":" + val + "'");
+ isnot(val_computed_f, "",
+ "should not get empty value for '" + property + ":" + val + "'");
+ if (is_initial) {
+ (xfail_value(property, val, is_initial, false) ? todo_is : is)(
+ val_computed_n, initial_computed_n,
+ "should get initial value for '" + property + ":" + val + "'");
+ (xfail_value(property, val, is_initial, true) ? todo_is : is)(
+ val_computed_f, initial_computed_f,
+ "should get initial value for '" + property + ":" + val + "'");
+ } else {
+ (xfail_value(property, val, is_initial, false) ? todo_isnot : isnot)(
+ val_computed_n, initial_computed_n,
+ "should not get initial value for '" + property + ":" + val + "' on elementn.");
+ var swap = swap_when_frame(property, val);
+ (xfail_value(property, val, is_initial, true) ? todo_isnot : (swap ? is : isnot))(
+ val_computed_f, initial_computed_f,
+ "should " + (swap ? "" : "not ") +
+ "get initial value for '" + property + ":" + val + "' on elementf.");
+ }
+ if (is_initial)
+ gRule1.style.removeProperty(property);
+ gRule2.style.removeProperty(property);
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gRule1.style.removeProperty(prereq);
+ gInitialPrereqsRuleN.style.removeProperty(prereq);
+ gInitialPrereqsRuleF.style.removeProperty(prereq);
+ }
+ }
+ if (info.inherited && is_initial) {
+ gElementN.parentNode.style.removeProperty(property);
+ gElementF.parentNode.style.removeProperty(property);
+ }
+}
+
+function test_property(prop) {
+ var info = gCSSProperties[prop];
+ for (var idx in info.initial_values)
+ test_value(prop, info.initial_values[idx], true);
+ for (var idx in info.other_values)
+ test_value(prop, info.other_values[idx], false);
+}
+
+function run_tests() {
+ setup_initial_values("unstyledn", "gInitialValuesN", "gInitialPrereqsRuleN");
+ setup_initial_values("unstyledf", "gInitialValuesF", "gInitialPrereqsRuleF");
+ var props = [];
+ for (var prop in gCSSProperties)
+ props.push(prop);
+ props = props.reverse();
+ function do_one() {
+ if (props.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ test_property(props.pop());
+ SimpleTest.executeSoon(do_one);
+ }
+ SimpleTest.executeSoon(do_one);
+}
+
+load_done();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_value_storage.html b/layout/style/test/test_value_storage.html
new file mode 100644
index 0000000000..542cad91ed
--- /dev/null
+++ b/layout/style/test/test_value_storage.html
@@ -0,0 +1,365 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS values</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css" id="prereqsheet">
+ #testnode {}
+ </style>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for parsing, storage, and serialization of CSS values **/
+
+/*
+ * The idempotence tests here deserve a little bit of explanation. What
+ * we're testing here are the following operations:
+ * parse: string -> CSS rule
+ * serialize: CSS rule -> string (normalization 1)
+ * (this actually has two variants that go through partly different
+ * codepaths, which we exercise with getPropertyValue and cssText)
+ * compute: CSS rule -> computed style
+ * cserialize: computed style -> string (normalization 2)
+ *
+ * Both serialize and cserialize do some normalization, so we can't test
+ * for pure round-tripping, and we also can't compare their output since
+ * they could normalize differently. (We might at some point in the
+ * future want to guarantee that any output of cserialize is
+ * untouched by going through parse+serialize, though.)
+ *
+ * So we test idempotence of parse + serialize by running the whole
+ * operation twice. Likewise for parse + compute + cserialize.
+ *
+ * Slightly more interestingly, we test that serialize + parse is the
+ * identity transform by comparing the output of parse + compute +
+ * cserialize to the output of parse + serialize + parse + compute +
+ * cserialize.
+ */
+
+var gSystemFont = [
+ "caption",
+ "icon",
+ "menu",
+ "message-box",
+ "small-caption",
+ "status-bar",
+ "-moz-button",
+ "-moz-pull-down-menu",
+ "-moz-list",
+ "-moz-field",
+];
+
+var gBadCompute = {
+ // output wrapped around to positive, in exponential notation
+ "-moz-box-ordinal-group": [ "-1", "-1000" ],
+};
+
+function xfail_compute(property, value)
+{
+ if (property in gBadCompute &&
+ gBadCompute[property].includes(value))
+ return true;
+
+ return false;
+}
+
+// constructed to map longhands ==> list of containing shorthands
+var gPropertyShorthands = {};
+
+var gElement = document.getElementById("testnode");
+var gDeclaration = gElement.style;
+var gComputedStyle = window.getComputedStyle(gElement);
+
+var gPrereqDeclaration =
+ document.getElementById("prereqsheet").sheet.cssRules[0].style;
+
+// On Android, avoid most 'TEST-PASS' logging by overriding
+// SimpleTest.is/isnot, to improve performance
+if (navigator.appVersion.includes("Android")) {
+ is = function is(a, b, name)
+ {
+ var pass = Object.is(a, b);
+ if (!pass)
+ SimpleTest.is(a, b, name);
+ }
+
+ isnot = function isnot(a, b, name)
+ {
+ var pass = !Object.is(a, b);
+ if (!pass)
+ SimpleTest.isnot(a, b, name);
+ }
+}
+
+// Returns true if propA and propB are equivalent, considering aliasing.
+// (i.e. if one is an alias of the other, or if they're both aliases of
+// the same 3rd property)
+function are_properties_aliased(propA, propB)
+{
+ // If either property is an alias, replace it with the property it aliases.
+ if ("alias_for" in gCSSProperties[propA]) {
+ propA = gCSSProperties[propA].alias_for;
+ }
+ if ("alias_for" in gCSSProperties[propB]) {
+ propB = gCSSProperties[propB].alias_for;
+ }
+
+ return propA == propB;
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ // can all properties be removed from the style?
+ function test_remove_all_properties(propName, value) {
+ var i, p = [];
+ for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]);
+ for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]);
+ var errstr = "when setting property " + propName + " to " + value;
+ is(gDeclaration.length, 0, "unremovable properties " + errstr);
+ is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr);
+ }
+
+ function test_other_shorthands_empty(value, subprop) {
+ if (!(subprop in gPropertyShorthands)) return;
+ var shorthands = gPropertyShorthands[subprop];
+ for (idx in shorthands) {
+ var sh = shorthands[idx];
+ if (are_properties_aliased(sh, property)) {
+ continue;
+ }
+ is(gDeclaration.getPropertyValue(sh), "",
+ "setting '" + value + "' on '" + property + "' (for shorthand '" + sh + "')");
+ }
+ }
+
+ function test_value(value, resolved_value) {
+ var value_has_variable_reference = resolved_value != null;
+ var is_system_font = property == "font" && gSystemFont.includes(value);
+
+ var colon = ": ";
+ gDeclaration.setProperty(property, value, "");
+
+ var idx;
+
+ var step1val = gDeclaration.getPropertyValue(property);
+ var step1vals = [];
+ var step1ser = gDeclaration.cssText;
+ if ("subproperties" in info)
+ for (idx in info.subproperties)
+ step1vals.push(gDeclaration.getPropertyValue(info.subproperties[idx]));
+ var step1comp;
+ var step1comps = [];
+ if (info.type != CSS_TYPE_TRUE_SHORTHAND)
+ step1comp = gComputedStyle.getPropertyValue(property);
+ if ("subproperties" in info)
+ for (idx in info.subproperties)
+ step1comps.push(gComputedStyle.getPropertyValue(info.subproperties[idx]));
+
+ SimpleTest.isnot(step1val, "", "setting '" + value + "' on '" + property + "'");
+ if ("subproperties" in info &&
+ // System font doesn't produce meaningful value for subproperties.
+ !is_system_font)
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ if (value_has_variable_reference &&
+ (!info.alias_for || info.type == CSS_TYPE_TRUE_SHORTHAND ||
+ info.type == CSS_TYPE_LEGACY_SHORTHAND)) {
+ is(gDeclaration.getPropertyValue(subprop), "",
+ "setting '" + value + "' on '" + property + "' (for '" + subprop + "')");
+ test_other_shorthands_empty(value, subprop);
+ } else {
+ isnot(gDeclaration.getPropertyValue(subprop), "",
+ "setting '" + value + "' on '" + property + "' (for '" + subprop + "')");
+ }
+ }
+
+ // We don't care particularly about the whitespace or the placement of
+ // semicolons, but for simplicity we'll test the current behavior.
+ var expected_serialization = "";
+ if (step1val != "") {
+ if ("alias_for" in info) {
+ let newValue = info.legacy_mapping && info.legacy_mapping[step1val]
+ ? info.legacy_mapping[step1val] : step1val;
+ // FIXME(emilio): This is a bit unfortunate:
+ // https://github.com/w3c/csswg-drafts/issues/3332
+ if (info.type == CSS_TYPE_LEGACY_SHORTHAND && value_has_variable_reference)
+ newValue = "";
+ expected_serialization = info.alias_for + colon + newValue + ";";
+ } else if (info.type == CSS_TYPE_LEGACY_SHORTHAND) {
+ is(property, "zoom", "Zoom is a bit special because it never " +
+ "serializes as-is, we always serialize the longhands, " +
+ "but it doesn't just map to a single property " +
+ "(and thus we can't use the 'alias_for' mechanism)");
+ let transform = step1val == "1" ? "none" : "scale(" + step1val + ")";
+ let origin = step1val == "1" ? "50% 50% 0px" : "0px 0px 0px";
+ if (value_has_variable_reference) { // See above.
+ transform = "";
+ origin = "";
+ }
+ expected_serialization = "transform" + colon + transform + "; transform-origin" + colon + origin + ";";
+ } else {
+ expected_serialization = property + colon + step1val + ";";
+ }
+ }
+ is(step1ser, expected_serialization,
+ "serialization should match property value");
+
+ gDeclaration.removeProperty(property);
+ gDeclaration.setProperty(property, step1val, "");
+
+ is(gDeclaration.getPropertyValue(property), step1val,
+ "parse+serialize should be idempotent for '" +
+ property + colon + value + "'");
+ if (info.type != CSS_TYPE_TRUE_SHORTHAND) {
+ is(gComputedStyle.getPropertyValue(property), step1comp,
+ "serialize+parse should be identity transform for '" +
+ property + ": " + value + "'");
+ }
+
+ if ("subproperties" in info &&
+ // Using setProperty over subproperties is not sufficient for
+ // system fonts, since the shorthand does more than its parts.
+ !is_system_font &&
+ !value_has_variable_reference) {
+ gDeclaration.removeProperty(property);
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ gDeclaration.setProperty(subprop, step1vals[idx], "");
+ }
+
+ // Now that all the subprops are set, check their values. Note that we
+ // need this in a separate loop, in case parts of the shorthand affect
+ // the computed values of other parts.
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ is(gComputedStyle.getPropertyValue(subprop), step1comps[idx],
+ "serialize(" + subprop + ")+parse should be the identity " +
+ "transform for '" + property + ": " + value + "'");
+ }
+ is(gDeclaration.getPropertyValue(property), step1val,
+ "parse+split+serialize should be idempotent for '" +
+ property + colon + value + "'");
+ }
+
+ // FIXME(emilio): Why is mask special?
+ if (info.type != CSS_TYPE_TRUE_SHORTHAND &&
+ property != "mask") {
+ gDeclaration.removeProperty(property);
+ gDeclaration.setProperty(property, step1comp, "");
+ var func = xfail_compute(property, value) ? todo_is : is;
+ func(gComputedStyle.getPropertyValue(property), step1comp,
+ "parse+compute+serialize should be idempotent for '" +
+ property + ": " + value + "'");
+ }
+ if ("subproperties" in info && !is_system_font) {
+ gDeclaration.removeProperty(property);
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ gDeclaration.setProperty(subprop, step1comps[idx], "");
+ }
+
+ // Now that all the subprops are set, check their values. Note that we
+ // need this in a separate loop, in case parts of the shorthand affect
+ // the computed values of other parts.
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ is(gComputedStyle.getPropertyValue(subprop), step1comps[idx],
+ "parse+compute+serialize(" + subprop + ") should be idempotent for '" +
+ property + ": " + value + "'");
+ }
+ }
+
+ // sanity check shorthands to make sure disabled props aren't exposed
+ if (info.type != CSS_TYPE_LONGHAND) {
+ gDeclaration.setProperty(property, value, "");
+ test_remove_all_properties(property, value);
+ }
+
+ gDeclaration.removeProperty(property);
+ }
+
+ function test_value_without_variable(value) {
+ test_value(value, null);
+ }
+
+ function test_value_with_variable(value) {
+ gPrereqDeclaration.setProperty("--a", value, "");
+ test_value("var(--a)", value);
+ gPrereqDeclaration.removeProperty("--a");
+ }
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gPrereqDeclaration.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+
+ var idx;
+ for (idx in info.initial_values) {
+ test_value_without_variable(info.initial_values[idx]);
+ test_value_with_variable(info.initial_values[idx]);
+ }
+ for (idx in info.other_values) {
+ test_value_without_variable(info.other_values[idx]);
+ test_value_with_variable(info.other_values[idx]);
+ }
+
+ if ("prerequisites" in info) {
+ for (var prereq in info.prerequisites) {
+ gPrereqDeclaration.removeProperty(prereq);
+ }
+ }
+
+}
+
+function runTest() {
+ // To avoid triggering the slow script dialog, we have to test one
+ // property at a time.
+ var props = [];
+ for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if ("subproperties" in info) {
+ for (var idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ if (!(subprop in gPropertyShorthands)) {
+ gPropertyShorthands[subprop] = [];
+ }
+ gPropertyShorthands[subprop].push(prop);
+ }
+ }
+ props.push(prop);
+ }
+ props = props.reverse();
+ function do_one() {
+ if (props.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ test_property(props.pop());
+ SimpleTest.executeSoon(do_one);
+ }
+ SimpleTest.executeSoon(do_one);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(7);
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_variable_serialization_computed.html b/layout/style/test/test_variable_serialization_computed.html
new file mode 100644
index 0000000000..2814e4ab93
--- /dev/null
+++ b/layout/style/test/test_variable_serialization_computed.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<title>Test serialization of computed CSS variable values</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+
+<div>
+ <span></span>
+</div>
+
+<script>
+// Each entry is an entire declaration followed by the property to check and
+// its expected computed value.
+var values = [
+ ["", "--z", "an-inherited-value"],
+ ["--a: ", "--a", ""],
+ ["--a: initial", "--a", ""],
+ ["--z: initial", "--z", ""],
+ ["--a: inherit", "--a", ""],
+ ["--z: inherit", "--z", "an-inherited-value"],
+ ["--a: unset", "--a", ""],
+ ["--z: unset", "--z", "an-inherited-value"],
+ ["--a: 1px", "--a", "1px"],
+ ["--a: var(--a)", "--a", ""],
+ ["--a: var(--b)", "--a", ""],
+ ["--a: var(--b); --b: 1px", "--a", "1px"],
+ ["--a: var(--b, 1px)", "--a", "1px"],
+ ["--a: var(--a, 1px)", "--a", ""],
+ ["--a: something 3px url(whereever) calc(var(--a) + 1px)", "--a", ""],
+ ["--a: something 3px url(whereever) calc(var(--b,1em) + 1px)", "--a", "something 3px url(whereever) calc(1em + 1px)"],
+ ["--a: var(--b, var(--c, var(--d, Black)))", "--a", "Black"],
+ ["--a: a var(--b) c; --b:b", "--a", "a b c"],
+ ["--a: a var(--b,b var(--c) d) e; --c:c", "--a", "a b c d e"],
+ ["--a: var(--b)red; --b:orange;", "--a", "orange/**/red"],
+ ["--a: var(--b)var(--c); --b:orange; --c:red;", "--a", "orange/**/red"],
+ ["--a: var(--b)var(--c,red); --b:orange;", "--a", "orange/**/red"],
+ ["--a: var(--b,orange)var(--c); --c:red;", "--a", "orange/**/red"],
+ ["--a: var(--b)-; --b:-;", "--a", "-/**/-"],
+ ["--a: var(--b)--; --b:-;", "--a", "-/**/--"],
+ ["--a: var(--b)--x; --b:-;", "--a", "-/**/--x"],
+ ["--a: var(--b)var(--c); --b:-; --c:-;", "--a", "-/**/-"],
+ ["--a: var(--b)var(--c); --b:--; --c:-;", "--a", "--/**/-"],
+ ["--a: var(--b)var(--c); --b:--x; --c:-;", "--a", "--x/**/-"],
+ ["counter-reset: var(--a)red; --a:orange;", "counter-reset", "orange 0 red 0"],
+ ["--a: var(--b)var(--c); --c:[c]; --b:('ab", "--a", "('ab')[c]"],
+ ["--a: '", "--a", "''"],
+ ["--a: '\\", "--a", "''"],
+ ["--a: \\", "--a", "\\\ufffd"],
+ ["--a: \"", "--a", "\"\""],
+ ["--a: \"\\", "--a", "\"\""],
+ ["--a: url(http://example.org/", "--a", "url(http://example.org/)"],
+ ["--a: url(http://example.org/\\", "--a", "url(http://example.org/\\\ufffd)"],
+ ["--a: url('http://example.org/", "--a", "url('http://example.org/')"],
+ ["--a: url('http://example.org/\\", "--a", "url('http://example.org/')"],
+ ["--a: url(\"http://example.org/", "--a", "url(\"http://example.org/\")"],
+ ["--a: url(\"http://example.org/\\", "--a", "url(\"http://example.org/\")"]
+];
+
+function runTest() {
+ var div = document.querySelector("div");
+ var span = document.querySelector("span");
+
+ div.setAttribute("style", "--z:an-inherited-value");
+
+ values.forEach(function(entry, i) {
+ var declaration = entry[0];
+ var property = entry[1];
+ var expected = entry[2];
+ span.setAttribute("style", declaration);
+ var cs = getComputedStyle(span, "");
+ is(cs.getPropertyValue(property), expected, `subtest #${i}: ${declaration}`);
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
diff --git a/layout/style/test/test_variable_serialization_specified.html b/layout/style/test/test_variable_serialization_specified.html
new file mode 100644
index 0000000000..cae8871cb2
--- /dev/null
+++ b/layout/style/test/test_variable_serialization_specified.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<title>Test serialization of specified CSS variable values</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+
+<style id=style1>#test { }</style>
+<style id=style2></style>
+
+<script>
+// Values that should be serialized back to the same string.
+var values_with_unchanged_specified_value_serialization = [
+ "var(--a)",
+ "var(--a)",
+ "var(--a) ",
+ "var( --a ) ",
+ "var(--a, )",
+ "var(--a,/**/a)",
+ "1px var(--a)",
+ "var(--a) 1px",
+ "something 3px url(whereever) calc(var(--a) + 1px)",
+ "var(--a)",
+ "var(--a)var(--b)",
+ "var(--a, var(--b, var(--c, black)))",
+ "var(--a) <!--",
+ "--> var(--a)",
+ "{ [ var(--a) ] }",
+ "[;] var(--a)",
+ "var(--a,(;))",
+ "VAR(--a)",
+ "var(--0)",
+ "var(--\\30)",
+ "var(--\\d800)",
+ "var(--\\ffffff)",
+];
+
+// Values that serialize differently, due to additional implied closing
+// characters at EOF.
+var values_with_changed_specified_value_serialization = [
+ ["var(--a", "var(--a)"],
+ ["var(--a , ", "var(--a , )"],
+ ["var(--a, ", "var(--a, )"],
+ ["var(--a, var(--b", "var(--a, var(--b))"],
+ ["var(--a /* unclosed comment", "var(--a /* unclosed comment*/)"],
+ ["var(--a /* unclosed comment *", "var(--a /* unclosed comment */)"],
+ ["[{(((var(--a", "[{(((var(--a))))}]"],
+ ["var(--a, \"unclosed string", "var(--a, \"unclosed string\")"],
+ ["var(--a, 'unclosed string", "var(--a, 'unclosed string')"],
+ ["var(--a) \"unclosed string\\", "var(--a) \"unclosed string\""],
+ ["var(--a) 'unclosed string\\", "var(--a) 'unclosed string'"],
+ ["var(--a) \\", "var(--a) \\\ufffd"],
+ ["var(--a) url(unclosedurl", "var(--a) url(unclosedurl)"],
+ ["var(--a) url('unclosedurl", "var(--a) url('unclosedurl')"],
+ ["var(--a) url(\"unclosedurl", "var(--a) url(\"unclosedurl\")"],
+ ["var(--a) url(unclosedurl\\", "var(--a) url(unclosedurl\\\ufffd)"],
+ ["var(--a) url('unclosedurl\\", "var(--a) url('unclosedurl')"],
+ ["var(--a) url(\"unclosedurl\\", "var(--a) url(\"unclosedurl\")"],
+];
+
+var style1 = document.getElementById("style1");
+var style2 = document.getElementById("style2");
+
+var decl = style1.sheet.cssRules[0].style;
+
+function test_specified_value_serialization(value, expected) {
+ // Test setting value on a custom property with setProperty.
+ decl.setProperty("--test", value, "");
+ is(decl.getPropertyValue("--test"), expected,
+ "value with identical serialization set on custom property with setProperty");
+
+ // Test setting value on a custom property via style sheet parsing.
+ style2.textContent = "#test { --test:" + value;
+ is(style2.sheet.cssRules[0].style.getPropertyValue("--test"), expected,
+ "value with identical serialization set on custom property via parsing");
+
+ // Test setting value on a non-custom longhand property with setProperty.
+ decl.setProperty("color", value, "");
+ is(decl.getPropertyValue("color"), expected,
+ "value with identical serialization set on non-custom longhand property with setProperty");
+
+ // Test setting value on a non-custom longhand property via style sheet parsing.
+ style2.textContent = "#test { color:" + value;
+ is(style2.sheet.cssRules[0].style.getPropertyValue("color"), expected,
+ "value with identical serialization set on non-custom longhand property via parsing");
+
+ // Test setting value on a non-custom shorthand property with setProperty.
+ decl.setProperty("margin", value, "");
+ is(decl.getPropertyValue("margin"), expected,
+ "value with identical serialization set on non-custom shorthand property with setProperty");
+
+ // Test setting value on a non-custom shorthand property via style sheet parsing.
+ style2.textContent = "#test { margin:" + value;
+ is(style2.sheet.cssRules[0].style.getPropertyValue("margin"), expected,
+ "value with identical serialization set on non-custom shorthand property via parsing");
+
+ // Clean up.
+ decl.removeProperty("--test");
+ decl.removeProperty("color");
+ decl.removeProperty("margin");
+}
+
+function runTest() {
+ values_with_unchanged_specified_value_serialization.forEach(function(value) {
+ test_specified_value_serialization(value, value);
+ });
+
+ values_with_changed_specified_value_serialization.forEach(function(pair) {
+ test_specified_value_serialization(pair[0], pair[1]);
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
diff --git a/layout/style/test/test_variables.html b/layout/style/test/test_variables.html
new file mode 100644
index 0000000000..e26dc5a0f7
--- /dev/null
+++ b/layout/style/test/test_variables.html
@@ -0,0 +1,129 @@
+<!DOCTYPE type>
+<title>Assorted CSS variable tests</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+
+<style id="test1">
+</style>
+
+<style id="test2">
+</style>
+
+<style id="test3">
+</style>
+
+<style id="test4">
+</style>
+
+<div id="t4"></div>
+
+<style id="test5">
+</style>
+
+<div id="t5"></div>
+
+<style id="test6">
+</style>
+
+<style id="test7">
+</style>
+
+<style id="test8">
+</style>
+
+<script>
+var tests = [
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=773296#c121
+ var test1 = document.getElementById("test1");
+ test1.textContent = "p { --a:123!important; }";
+ var declaration = test1.sheet.cssRules[0].style;
+ declaration.cssText;
+ declaration.setProperty("color", "black");
+ is(declaration.getPropertyValue("--a"), "123");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=773296#c121
+ var test2 = document.getElementById("test2");
+ test2.textContent = "p { --a: a !important; }";
+ var declaration = test2.sheet.cssRules[0].style;
+ is(declaration.getPropertyPriority("--a"), "important");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=955913
+ var test3 = document.getElementById("test3");
+ test3.textContent = "p { border-left-style: inset; padding: 1px; --decoration: line-through; }";
+ var declaration = test3.sheet.cssRules[0].style;
+ is(declaration[declaration.length - 1], "--decoration");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=959973
+ var test4 = document.getElementById("test4");
+ test4.textContent = "#t4 { background-image: var(--a); }";
+
+ var element = document.getElementById("t4");
+ var path = window.location.pathname;
+ var currentDir = path.substring(0, path.lastIndexOf('/'));
+ var imageURL = "http://mochi.test:8888" + currentDir + "/image.png";
+
+ is(window.getComputedStyle(element).getPropertyValue("background-image"), "url(\"" + imageURL +"\")");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1043713
+ var test5 = document.getElementById("test5");
+ test5.textContent = "#t5 { --SomeVariableName: a; }";
+
+ var declaration = test5.sheet.cssRules[0].style;
+ is(declaration.item(0), "--SomeVariableName", "custom property name returned by item() on style declaration");
+ is(declaration[0], "--SomeVariableName", "custom property name returned by indexed getter on style declaration");
+
+ var element = document.getElementById("t5");
+ var cs = window.getComputedStyle(element);
+
+ is(cs.item(cs.length - 1), "--SomeVariableName", "custom property name returned by item() on computed style");
+ is(cs[cs.length - 1], "--SomeVariableName", "custom property name returned by indexed getter on style declaration");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1154356
+ var test7 = document.getElementById("test7");
+ test7.textContent = "p { --weird\\;name: green; }";
+ is(test7.sheet.cssRules[0].style.cssText, "--weird\\;name: green;");
+ test7.textContent = "p { --0: green; }";
+ is(test7.sheet.cssRules[0].style.cssText, "--0: green;");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1330172
+ var test8 = document.getElementById("test8");
+ test8.textContent = "p { --a:inHerit; }";
+ is(test8.sheet.cssRules[0].style.cssText, "--a: inherit;");
+ test8.textContent = "p { --b: initial!important; }";
+ is(test8.sheet.cssRules[0].style.cssText, "--b: initial !important;");
+ test8.textContent = "p { --c: UNSET !important }";
+ is(test8.sheet.cssRules[0].style.cssText, "--c: unset !important;");
+ },
+];
+
+function prepareTest() {
+ // Load an external style sheet for test 4.
+ var e = document.createElement("link");
+ e.addEventListener("load", runTest);
+ e.setAttribute("rel", "stylesheet");
+ e.setAttribute("href", "support/external-variable-url.css");
+ document.head.appendChild(e);
+}
+
+function runTest() {
+ tests.forEach(function(fn) { fn(); });
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+prepareTest();
+</script>
diff --git a/layout/style/test/test_variables_loop.html b/layout/style/test/test_variables_loop.html
new file mode 100644
index 0000000000..76e97e26f3
--- /dev/null
+++ b/layout/style/test/test_variables_loop.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>CSS variables loop resolving</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+<style id="test">
+ #outer {
+ --a: a;
+ --b: b;
+ --c: c;
+ --d: d;
+ --e: e;
+ }
+ #inner {
+ --a: var(--d, ad);
+ --b: var(--d, ad);
+ --c: var(--d, ad);
+ --d: var(--e, de);
+ --e: var(--a, ea) var(--b, eb) var(--c, ec);
+ }
+</style>
+<div id="outer">
+ <div id="inner"></div>
+</div>
+<script>
+let inner_cs = getComputedStyle(document.getElementById("inner"));
+for (let v of ['a', 'b', 'c', 'd', 'e']) {
+ is(inner_cs.getPropertyValue(`--${v}`), "",
+ `Variable --${v} should be eliminated`);
+}
+</script>
diff --git a/layout/style/test/test_variables_order.html b/layout/style/test/test_variables_order.html
new file mode 100644
index 0000000000..5adb9edf75
--- /dev/null
+++ b/layout/style/test/test_variables_order.html
@@ -0,0 +1,53 @@
+<!DOCTYPE type>
+<title>CSS variables order tests</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+
+<style id="test">
+</style>
+
+<div id="t4"></div>
+
+<script>
+
+/*
+ * Although the spec does not enforce any specific order, Gecko and Servo
+ * implement a consistent ordering for CSSDeclaration objects in the DOM.
+ * CSSDeclarations expose property names as indexed properties, which need
+ * to be stable. This order is the order that properties are cascaded in.
+ *
+ * We have this test just to prevent regressions, rather than testing specific
+ * mandated behavior.
+ */
+
+function prepareTest() {
+ var e = document.createElement("link");
+ e.addEventListener("load", runTest);
+ e.setAttribute("rel", "stylesheet");
+ e.setAttribute("href", "support/external-variable-url.css");
+ document.head.appendChild(e);
+}
+
+function runTest() {
+ var test = document.getElementById("test");
+ test.textContent = "div { --SomeVariableName: a; }";
+
+ var declaration = test.sheet.cssRules[0].style;
+ is(declaration.item(0), "--SomeVariableName", "custom property name returned by item() on style declaration");
+ is(declaration[0], "--SomeVariableName", "custom property name returned by indexed getter on style declaration");
+
+ var element = document.getElementById("t4");
+ var cs = window.getComputedStyle(element);
+
+ ["--SomeVariableName", "--a"].forEach((varName, index) => {
+ is(cs.item(cs.length - (index + 1)), varName, "custom property name returned by item() on computed style");
+ is(cs[cs.length - (index + 1)], varName, "custom property name returned by indexed getter on style declaration");
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+prepareTest();
+</script>
diff --git a/layout/style/test/test_video_object_fit.html b/layout/style/test/test_video_object_fit.html
new file mode 100644
index 0000000000..e673ba204e
--- /dev/null
+++ b/layout/style/test/test_video_object_fit.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1065766
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1065766</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1065766">Mozilla Bug 1065766</a>
+<div id="content" style="display: none">
+ <video id="myVideo"></video>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/**
+ * Test for Bug 1065766
+ *
+ * This test verifies that <video> has 'object-fit:contain' by default, set via
+ * a UA stylesheet. (This is different from the property's initial value, which
+ * is "fill".)
+ *
+ * Spec reference:
+ * https://html.spec.whatwg.org/multipage/rendering.html#video-object-fit
+ */
+
+function checkStyle(elem, expectedVal, message) {
+ is(window.getComputedStyle(elem).objectFit, expectedVal, message);
+}
+
+function main() {
+ const videoElem = document.getElementById("myVideo");
+
+ checkStyle(videoElem, "contain",
+ "<video> should have 'object-fit:contain' by default");
+
+ // Make sure we can override this behavior (i.e. that the UA stylesheet
+ // doesn't use "!important" to make this style mandatory):
+ videoElem.style.objectFit = "cover";
+ checkStyle(videoElem, "cover",
+ "<video> should honor 'object-fit:cover' in inline style");
+}
+
+main();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_viewport_scrollbar_causing_reflow.html b/layout/style/test/test_viewport_scrollbar_causing_reflow.html
new file mode 100644
index 0000000000..ced14f352a
--- /dev/null
+++ b/layout/style/test/test_viewport_scrollbar_causing_reflow.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1367568
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1367568</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug 1367568</a>
+<div id="content">
+ <!-- Some fixed-width divs that we shouldn't have to reflow when the viewport
+ changes. More than 5 so that our leeway for scrollbar parts doesn't
+ accidentally cause the test to pass -->
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="position: absolute; width: 150px">
+ abs-fixed-width
+ <div>(child)</div>
+ </div>
+</div>
+<pre id="test">
+<script>
+"use strict";
+
+/** Test for Bug 1367568 **/
+
+/**
+ * This test verifies that "overflow" changes on the <body> don't cause
+ * an unnecessarily large amount of reflow.
+ */
+
+// Vars used in setStyleAndMeasure that we really only have to look up once:
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function setStyleAndMeasure(initialStyle, finalStyle) {
+ is(document.body.style.length, 0,
+ "Bug in test - body should start with empty style");
+ let unusedVal = document.body.offsetHeight; // flush layout
+ let constructCount = gUtils.framesConstructed;
+
+ document.body.style = initialStyle;
+ unusedVal = document.body.offsetHeight; // flush layout
+ let reflowCountBeforeTweak = gUtils.framesReflowed;
+
+ document.body.style = finalStyle;
+ unusedVal = document.body.offsetHeight; // flush layout
+ let reflowCountAfterTweak = gUtils.framesReflowed;
+
+ // Clean up:
+ document.body.style = "";
+
+ is(gUtils.framesConstructed, constructCount,
+ "Style tweak shouldn't have triggered frame construction");
+
+ // ...and return the delta:
+ return reflowCountAfterTweak - reflowCountBeforeTweak;
+}
+
+function main() {
+ // First, we sanity-check that our measurement make sense -- if we leave
+ // styles unchanged, we should measure no frames being reflowed:
+ let count = setStyleAndMeasure("width: 50px; height: 80px",
+ "width: 50px; height: 80px");
+ is(count, 0,
+ "Shouldn't reflow anything when we leave 'width' & 'height' unchanged");
+
+ // Now: see how many frames are reflowed when the "width" & "height" change.
+ // We'll use this as the reference when measuring reflow counts for various
+ // changes to "overflow" below.
+ count = setStyleAndMeasure("width: 50px; height: 80px",
+ "width: 90px; height: 60px");
+ ok(count > 0,
+ "Should reflow some frames when 'width' & 'height' change");
+
+ const scrollbarsHaveButtons = navigator.platform.includes("Win");
+ // This is to allow for reflowing scrollbar parts themselves.
+ const scrollbarReflows = scrollbarsHaveButtons ? 5 : 2;
+ // Expected maximum number of frames reflowed for "overflow" changes
+ const expectedMax = count + scrollbarReflows;
+
+ // Shared ending for messages in all ok() checks below:
+ const messageSuffix =
+ " shouldn't be greater than count for tweaking width/height on body (" +
+ expectedMax + ")";
+
+ // OK, here is where the relevant tests actually begin!!
+ // See how many frames we reflow for various tweaks to "overflow" on
+ // the body -- we expect the count to be no larger than |expectedMax|.
+ count = setStyleAndMeasure("", "overflow: scroll");
+ ok(count <= expectedMax,
+ "Reflow count when setting 'overflow: scroll' on body (" + count + ")" +
+ messageSuffix);
+
+ count = setStyleAndMeasure("", "overflow: hidden");
+ ok(count <= expectedMax,
+ "Reflow count when setting 'overflow: hidden' on body (" + count + ")" +
+ messageSuffix);
+
+ // Test removal of "overflow: scroll":
+ count = setStyleAndMeasure("overflow: scroll", "");
+ ok(count <= expectedMax,
+ "Reflow count when removing 'overflow: scroll' from body (" + count + ")" +
+ messageSuffix);
+
+ count = setStyleAndMeasure("overflow: hidden", "");
+ ok(count <= expectedMax,
+ "Reflow count when removing 'overflow: hidden' from body (" + count + ")" +
+ messageSuffix);
+
+ // Test change between two non-'visible' overflow values:
+ count = setStyleAndMeasure("overflow: scroll", "overflow: hidden");
+ ok(count <= expectedMax,
+ "Reflow count when changing 'overflow' on body (" + count + ")" +
+ messageSuffix);
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_viewport_units.html b/layout/style/test/test_viewport_units.html
new file mode 100644
index 0000000000..fa7df88eb6
--- /dev/null
+++ b/layout/style/test/test_viewport_units.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=804970
+-->
+<head>
+ <title>Test for dynamic changes to CSS 'vh', 'vw', 'vmin', and 'vmax' units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=804970">Mozilla Bug 804970</a>
+<iframe id="iframe" src="viewport_units_iframe.html"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for CSS vh/vw/vmin/vmax units **/
+
+function px_to_num(str)
+{
+ return Number(String(str).match(/^([\d.]+)px$/)[1]);
+}
+
+function width(elt)
+{
+ return px_to_num(elt.ownerDocument.defaultView.getComputedStyle(elt).width);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var iframe = document.getElementById("iframe");
+ var idoc = iframe.contentDocument;
+ var vh = idoc.getElementById("vh");
+ var vw = idoc.getElementById("vw");
+ var vmin = idoc.getElementById("vmin");
+ var vmax = idoc.getElementById("vmax");
+
+ iframe.style.width = "100px";
+ iframe.style.height = "250px";
+ is(width(vh), 250, "vh should be 250px");
+ is(width(vw), 100, "vw should be 100px");
+ is(width(vmin), 100, "vmin should be 100px");
+ is(width(vmax), 250, "vmax should be 250px");
+
+ iframe.style.width = "300px";
+ is(width(vh), 250, "vh should be 250px");
+ is(width(vw), 300, "vw should be 300px");
+ is(width(vmin), 250, "vmin should be 250px");
+ is(width(vmax), 300, "vmax should be 300px");
+
+ iframe.style.height = "200px";
+ is(width(vh), 200, "vh should be 200px");
+ is(width(vw), 300, "vw should be 300px");
+ is(width(vmin), 200, "vmin should be 200px");
+ is(width(vmax), 300, "vmax should be 300px");
+
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", run);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_image_loading.html b/layout/style/test/test_visited_image_loading.html
new file mode 100644
index 0000000000..09aae8e53c
--- /dev/null
+++ b/layout/style/test/test_visited_image_loading.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557287
+-->
+<head>
+ <title>Test for Bug 557287</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557287">Mozilla Bug 147777</a>
+<pre id="test">
+<script type="application/ecmascript" src="visited_image_loading.sjs?reset"></script>
+<script type="application/javascript">
+
+/** Test for Bug 557287 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var subdoc, subwin;
+
+window.addEventListener("load", run);
+
+function run()
+{
+ subwin = window.open("visited_image_loading_frame.html", "_blank");
+ subwin.addEventListener("load", function() {
+ subdoc = subwin.document;
+ setTimeout(check_link_styled, 50);
+ });
+}
+
+function visitedDependentComputedStyle(win, elem, property) {
+ return SpecialPowers.DOMWindowUtils
+ .getVisitedDependentComputedStyle(elem, "", property);
+}
+
+function check_link_styled()
+{
+ var vislink = subdoc.getElementById("visited");
+ var bgcolor =
+ visitedDependentComputedStyle(subwin, vislink, "background-color");
+ if (bgcolor == "rgb(128, 0, 128)") {
+ // We've done our async :visited processing and restyled accordingly.
+ // Make sure that we've actually painted before finishing the test.
+ subwin.addEventListener("MozAfterPaint", paint_listener);
+ // do something that forces a paint
+ subdoc.body.appendChild(subdoc.createTextNode("new text node"));
+ } else {
+ setTimeout(check_link_styled, 50);
+ }
+}
+
+function paint_listener(event)
+{
+ subwin.removeEventListener("MozAfterPaint", paint_listener);
+ var s = document.createElement("script");
+ s.src = "visited_image_loading.sjs?waitforresult";
+ document.body.appendChild(s);
+ subwin.close();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_image_loading_empty.html b/layout/style/test/test_visited_image_loading_empty.html
new file mode 100644
index 0000000000..6687254720
--- /dev/null
+++ b/layout/style/test/test_visited_image_loading_empty.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557287
+-->
+<head>
+ <title>Test for Bug 557287</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557287">Mozilla Bug 147777</a>
+<pre id="test">
+<script type="application/ecmascript" src="visited_image_loading.sjs?reset"></script>
+<script type="application/javascript">
+
+/** Test for Bug 557287 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var subdoc, subwin;
+
+window.addEventListener("load", run);
+
+function run()
+{
+ subwin = window.open("visited_image_loading_frame_empty.html", "_blank");
+ subwin.addEventListener("load", function() {
+ subdoc = subwin.document;
+ setTimeout(check_link_styled, 50);
+ });
+}
+
+function visitedDependentComputedStyle(win, elem, property) {
+ return SpecialPowers.DOMWindowUtils
+ .getVisitedDependentComputedStyle(elem, "", property);
+}
+
+function check_link_styled()
+{
+ var vislink = subdoc.getElementById("visited");
+ var bgcolor =
+ visitedDependentComputedStyle(subwin, vislink, "background-color");
+ if (bgcolor == "rgb(128, 0, 128)") {
+ // We've done our async :visited processing and restyled accordingly.
+ // Make sure that we've actually painted before finishing the test.
+ subwin.addEventListener("MozAfterPaint", paint_listener);
+ // do something that forces a paint
+ subdoc.body.appendChild(subdoc.createTextNode("new text node"));
+ } else {
+ setTimeout(check_link_styled, 50);
+ }
+}
+
+function paint_listener(event)
+{
+ subwin.removeEventListener("MozAfterPaint", paint_listener);
+ var s = document.createElement("script");
+ s.src = "visited_image_loading.sjs?waitforresult";
+ document.body.appendChild(s);
+ subwin.close();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_lying.html b/layout/style/test/test_visited_lying.html
new file mode 100644
index 0000000000..116d301cf1
--- /dev/null
+++ b/layout/style/test/test_visited_lying.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=147777
+-->
+<head>
+ <title>Test for Bug 147777</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=147777">Mozilla Bug 147777</a>
+<iframe id="iframe" src="visited-lying-inner.html" style="width: 20em; height: 5em"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 147777 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+window.addEventListener("load", start);
+
+var iframe;
+var visitedlink, unvisitedlink;
+var snapshot1;
+
+function start()
+{
+ // Our load event has fired, so we know our iframe is loaded.
+ iframe = document.getElementById("iframe");
+ visitedlink = iframe.contentDocument.getElementById("visitedlink");
+ unvisitedlink = iframe.contentDocument.getElementById("unvisitedlink");
+
+ // First, take a snapshot of it with both links unvisited.
+ snapshot1 = snapshotWindow(iframe.contentWindow, false);
+
+ // Then, change one of the links in the iframe to being visited.
+ visitedlink.href = top.location;
+
+ // Then, start polling to see when the history has updated the display.
+ setTimeout(poll_for_restyle, 100);
+}
+
+function poll_for_restyle()
+{
+ var snapshot2 = snapshotWindow(iframe.contentWindow, false);
+ var equal = compareSnapshots(snapshot1, snapshot2, true)[0];
+ if (equal) {
+ // keep polling
+ setTimeout(poll_for_restyle, 100);
+ } else {
+ // We now know that the link is visited, so we're ready to run
+ // tests.
+ run_tests();
+ }
+}
+
+function run_tests()
+{
+ // Test querySelector and querySelectorAll.
+ var subdoc = iframe.contentDocument;
+ is(subdoc.querySelector(":link"), unvisitedlink,
+ "first :link should be the unvisited link");
+ is(subdoc.querySelector(":visited"), null,
+ "querySelector should not find anything :visited");
+ var qsr = subdoc.querySelectorAll(":link");
+ is(qsr.length, 2, "querySelectorAll(:link) should find 2 results");
+ is(qsr[0], unvisitedlink, "querySelectorAll(:link)[0]");
+ is(qsr[1], visitedlink, "querySelectorAll(:link)[1]");
+ qsr = subdoc.querySelectorAll(":visited");
+ is(qsr.length, 0, "querySelectorAll(:visited) should find 0 results");
+
+ // Test getComputedStyle.
+ var subwin = iframe.contentWindow;
+ is(subwin.getComputedStyle(unvisitedlink).color, "rgb(0, 0, 255)",
+ "getComputedStyle on unvisited link should report color is blue");
+ is(subwin.getComputedStyle(visitedlink).color, "rgb(0, 0, 255)",
+ "getComputedStyle on visited link should report color is blue");
+
+ // Test matches.
+ is(unvisitedlink.matches(":link"), true,
+ "unvisited link matches :link");
+ is(visitedlink.matches(":link"), true,
+ "visited link matches :link");
+ is(unvisitedlink.matches(":visited"), false,
+ "unvisited link does not match :visited");
+ is(visitedlink.matches(":visited"), false,
+ "visited link does not match :visited");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_pref.html b/layout/style/test/test_visited_pref.html
new file mode 100644
index 0000000000..481a71bfef
--- /dev/null
+++ b/layout/style/test/test_visited_pref.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=147777
+-->
+<head>
+ <title>Test for visited link coloring pref Bug 147777</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ :link { float: left; }
+
+ :visited { float: right; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=147777">Mozilla Bug 147777</a>
+<iframe id="iframe" src="visited-pref-iframe.html" style="width: 10em; height: 5em"></iframe>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 147777 **/
+
+function reinsert_node(e) {
+ var sib = e.nextSibling;
+ var par = e.parentNode;
+ par.removeChild(e);
+ par.insertBefore(e, sib);
+}
+
+function get_pref()
+{
+ return SpecialPowers.getBoolPref("layout.css.visited_links_enabled");
+}
+
+function snapshotsEqual(snap1, snap2)
+{
+ return compareSnapshots(snap1, snap2, true)[0];
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+window.addEventListener("load", step1);
+
+var iframe, subdoc, subwin;
+var link;
+var start;
+var timeout;
+
+var unvisref; // reference image for unvisited style
+
+function step1()
+{
+ is(get_pref(), true, "pref defaults to true");
+
+ iframe = document.getElementById("iframe");
+ subdoc = iframe.contentDocument;
+ subwin = iframe.contentWindow;
+ link = subdoc.getElementById("link");
+
+ unvisref = snapshotWindow(subwin, false);
+
+ // Now set the href of the link to a location that's actually visited.
+ link.href = top.location;
+
+ start = Date.now();
+
+ // And wait for the link to get restyled when the history lets us
+ // know it is (asynchronously).
+ setTimeout(poll_for_visited_style, 100);
+}
+
+function poll_for_visited_style()
+{
+ var snapshot = snapshotWindow(subwin, false);
+ if (snapshotsEqual(unvisref, snapshot)) {
+ // hasn't been styled yet
+ setTimeout(poll_for_visited_style, 100);
+
+ // If it never gets styled correctly, this test will fail because
+ // this loop will never complete.
+ } else {
+ var end = Date.now();
+ timeout = 3 * Math.max(end - start, 300);
+ SpecialPowers.pushPrefEnv({"set":[["layout.css.visited_links_enabled", false]]}, step2);
+ }
+}
+
+function step2()
+{
+ // we don't handle dynamic changes of this pref; it only takes effect
+ // when a new page loads
+ reinsert_node(link);
+
+ setTimeout(step3, timeout);
+}
+
+function step3()
+{
+ var snapshot = snapshotWindow(subwin, false);
+ ok(snapshotsEqual(unvisref, snapshot),
+ ":visited selector does not apply given false preference");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_reftests.html b/layout/style/test/test_visited_reftests.html
new file mode 100644
index 0000000000..0353d948fc
--- /dev/null
+++ b/layout/style/test/test_visited_reftests.html
@@ -0,0 +1,210 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=147777
+-->
+<head>
+ <title>Test for Bug 147777</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=147777">Mozilla Bug 147777</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 147777 **/
+
+// Because link-coloring for visited links is asynchronous, running
+// reftests that involve link coloring requires that we poll for the
+// correct result until all links are styled correctly.
+
+// A requirement of these reftests is that the reference rendering is
+// styled correctly when loaded. We only poll for the tests.
+
+var gTests = [
+ // there's also an implicit "load visited-page.html" at the start,
+ // thanks to the code below.
+
+ // IMPORTANT NOTE: For these tests, the test and reference are not
+ // snapshotted in the same way. The REFERENCE (second file) is
+ // assumed to be complete when loaded, but we poll for visited link
+ // coloring on the TEST (first file) until the test passes.
+ "== pseudo-classes-02.svg pseudo-classes-02-ref.svg",
+ "needs-focus == caret-color-on-visited-1.html caret-color-on-visited-1-ref.html",
+ "!= color-on-link-1-ref.html color-on-visited-1-ref.html",
+ "== color-on-link-1.html color-on-link-1-ref.html",
+ "== color-on-link-before-1.html color-on-link-1-ref.html",
+ "== color-on-visited-1.html color-on-visited-1-ref.html",
+ "== color-on-visited-before-1.html color-on-visited-1-ref.html",
+ "== color-on-visited-text-1.html color-on-visited-text-1-ref.html",
+ "!= content-color-on-link-before-1-ref.html content-color-on-visited-before-1-ref.html",
+ "== content-color-on-link-before-1.html content-color-on-link-before-1-ref.html",
+ "== content-color-on-visited-before-1.html content-color-on-visited-before-1-ref.html",
+ "== content-on-link-before-1.html content-before-1-ref.html",
+ "== content-on-visited-before-1.html content-before-1-ref.html",
+ "== color-on-text-decoration-1.html color-on-text-decoration-1-ref.html",
+ "== color-on-bullets-1.html color-on-bullets-1-ref.html",
+ // NOTE: background-color is tested by all the selector tests (and
+ // also color-choice-1) and therefore doesn't have its own tests.
+ // FIXME: Maybe add a test for selection colors (foreground and
+ // background), if possible.
+ "== width-on-link-1.html width-1-ref.html",
+ "== width-on-visited-1.html width-1-ref.html",
+ "== border-1.html border-1-ref.html",
+ "== border-2a.html border-2-ref.html",
+ "== border-2b.html border-2-ref.html",
+ // FIXME: Commented out because of dynamic change handling bugs in
+ // border-collapse tables that mean we get an incorrect rendering when
+ // the asynchronous restyle-from-history arrives.
+ //"== border-collapse-1.html border-collapse-1-ref.html",
+ "== outline-1.html outline-1-ref.html",
+ "== column-rule-1.html column-rule-1-ref.html",
+ "!= column-rule-1.html column-rule-1-notref.html",
+ "== color-choice-1.html color-choice-1-ref.html",
+ "== selector-descendant-1.html selector-descendant-1-ref.html",
+ "== selector-descendant-2.xhtml selector-descendant-2-ref.xhtml",
+ "== selector-child-1.html selector-child-1-ref.html",
+ "== selector-child-2.xhtml selector-child-2-ref.xhtml",
+ "== selector-adj-sibling-1.html selector-adj-sibling-1-ref.html",
+ "== selector-adj-sibling-2.html selector-adj-sibling-2-ref.html",
+ "== selector-adj-sibling-3.xhtml selector-adj-sibling-3-ref.xhtml",
+ "== selector-any-sibling-1.html selector-any-sibling-1-ref.html",
+ "== selector-any-sibling-2.html selector-any-sibling-2-ref.html",
+ "== subject-of-selector-descendant-1.html subject-of-selector-1-ref.html",
+ "== subject-of-selector-descendant-2.xhtml subject-of-selector-descendant-2-ref.xhtml",
+ "== subject-of-selector-child-1.html subject-of-selector-1-ref.html",
+ "== subject-of-selector-adj-sibling-1.html subject-of-selector-1-ref.html",
+ "== subject-of-selector-any-sibling-1.html subject-of-selector-1-ref.html",
+ "== inherit-keyword-1.xhtml inherit-keyword-1-ref.html",
+ "== svg-image-visited-1a.html svg-image-visited-1-ref.html",
+ "== svg-image-visited-1b.html svg-image-visited-1-ref.html",
+ "== svg-image-visited-1c.html svg-image-visited-1-ref.html",
+ "== svg-image-visited-1d.html svg-image-visited-1-ref.html",
+ // FIXME: commented out because dynamic changes on the non-first-line
+ // part of the test don't work right when the link becomes visited.
+ //"== first-line-1.html first-line-1-ref.html",
+ "== white-to-transparent-1.html white-to-transparent-1-ref.html",
+ "== link-root-1.xhtml link-root-1-ref.xhtml",
+ "== mathml-links.html mathml-links-ref.html",
+ "== placeholder-1.html placeholder-1-ref.html",
+ "== visited-inherit-1.html visited-inherit-1-ref.html",
+ "== transition-on-visited.html transition-on-visited-ref.html",
+ "== logical-box-border-color-visited-link-001.html logical-box-border-color-visited-link-ref.html",
+ "== logical-box-border-color-visited-link-002.html logical-box-border-color-visited-link-ref.html",
+ "== logical-box-border-color-visited-link-003.html logical-box-border-color-visited-link-ref.html",
+ "== svg-paint-currentcolor-visited.svg svg-paint-currentcolor-visited-ref.svg",
+ "== variables-visited.html variables-visited-ref.html",
+];
+
+// We record the maximum number of times we had to look at a test before
+// it switched to the passing state (though we assume it's 10 to start
+// rather than 0 so that we have a reasonable default). Then we make a
+// test "time out" if it takes more than gTimeoutFactor times that
+// amount of time. This allows us to report a test failure rather than
+// making a test failure just show up as a timeout.
+var gMaxPassingTries = 10;
+var gTimeoutFactor = 10;
+
+function startIframe(url) {
+ return new Promise(resolve => {
+ var element = document.createElement("iframe");
+ element.addEventListener("load", () => {
+ element.contentDocument.fonts.ready.then(() => {
+ resolve(element.contentWindow);
+ });
+ }, {once: true});
+ // smaller than normal reftests, but enough for these
+ element.setAttribute("style", "width: 30em; height: 10em");
+ element.src = "css-visited/" + url;
+ document.body.appendChild(element);
+ });
+}
+
+async function runTests() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("async link coloring");
+ // Set caret to a known size, for tests of :visited caret-color styling
+ await SpecialPowers.pushPrefEnv({'set': [['ui.caretWidth', 16]]});
+ info("opening visited page");
+ let win = window.open("css-visited/visited-page.html", "_blank");
+ await new Promise(resolve => {
+ win.onload = resolve;
+ });
+ info("running tests");
+ await Promise.all(gTests.map(runTest));
+ win.close();
+ SimpleTest.finish();
+}
+
+function passes(equal, shot1, shot2)
+{
+ let [correct] = compareSnapshots(shot1, shot2, equal);
+ return correct;
+}
+
+function waitFor100msAndIdle() {
+ return new Promise(resolve => setTimeout(function() {
+ requestIdleCallback(resolve);
+ }, 100));
+}
+
+async function runTest(testLine) {
+ let splitData = testLine.split(" ");
+ let isEqual;
+ let needsFocus = false;
+ while (true) {
+ let op = splitData.shift();
+ if (op == "needs-focus") {
+ needsFocus = true;
+ } else if (op == "==" || op == "!=") {
+ isEqual = op == "==";
+ break;
+ } else {
+ ok(false, "Unknown syntax");
+ return;
+ }
+ }
+ let [testFile, refFile] = splitData;
+
+ let promiseTestWin = startIframe(testFile);
+ let promiseRefWin = startIframe(refFile);
+ let refSnapshot = snapshotWindow(await promiseRefWin);
+ let testWindow = await promiseTestWin;
+ // Always wait at least 100ms, so that any test that switches
+ // from passing to failing when the asynchronous link coloring
+ // happens should fail at least some of the time.
+ await waitFor100msAndIdle();
+
+ let tries;
+ let testSnapshot;
+ for (tries = 0; tries < gMaxPassingTries * gTimeoutFactor; ++tries) {
+ if (needsFocus) {
+ await SimpleTest.promiseFocus(testWindow, false);
+ }
+ testSnapshot = snapshotWindow(testWindow, true);
+ if (passes(isEqual, testSnapshot, refSnapshot)) {
+ if (tries > gMaxPassingTries) {
+ gMaxPassingTries = tries;
+ }
+ break;
+ }
+ // Links might not have been colored yet. Try again in 100ms.
+ await waitFor100msAndIdle();
+ }
+
+ let result = assertSnapshots(testSnapshot, refSnapshot,
+ isEqual, null, testFile, refFile);
+ if (!result) {
+ info(`Gave up after ${tries} tries, ` +
+ `maxp=${gMaxPassingTries}, fact=${gTimeoutFactor}`);
+ }
+}
+
+runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_webkit_device_pixel_ratio.html b/layout/style/test/test_webkit_device_pixel_ratio.html
new file mode 100644
index 0000000000..69e50c58ff
--- /dev/null
+++ b/layout/style/test/test_webkit_device_pixel_ratio.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1176968
+-->
+<head>
+ <title>Test for Bug 1176968</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>.zoom-test { visibility: hidden; }</style>
+ <style><!-- placeholder for dynamic additions --></style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1176968">Mozilla Bug 1176968</a>
+<div id="content" style="display: none">
+
+</div>
+<script type="text/javascript">
+</script>
+<pre id="test">
+<div id="zoom1" class="zoom-test"></div>
+<div id="zoom2" class="zoom-test"></div>
+<div id="zoom3" class="zoom-test"></div>
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 1176968 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ function zoom(factor) {
+ var previous = SpecialPowers.getFullZoom(window);
+ SpecialPowers.setFullZoom(window, factor);
+ return previous;
+ }
+
+ function isVisible(divName) {
+ return window.getComputedStyle(document.getElementById(divName)).visibility == "visible";
+ }
+
+ var screenPixelsPerCSSPixel = window.devicePixelRatio;
+ var baseRatio = 1.0 * screenPixelsPerCSSPixel;
+ var doubleRatio = 2.0 * screenPixelsPerCSSPixel;
+ var halfRatio = 0.5 * screenPixelsPerCSSPixel;
+ var styleElem = document.getElementsByTagName("style")[1];
+ styleElem.textContent =
+ ["@media all and (-webkit-device-pixel-ratio: " + baseRatio + ") {",
+ "#zoom1 { visibility: visible; }",
+ "}",
+ "@media all and (-webkit-device-pixel-ratio: " + doubleRatio + ") {",
+ "#zoom2 { visibility: visible; }",
+ "}",
+ "@media all and (-webkit-device-pixel-ratio: " + halfRatio + ") {",
+ "#zoom3 { visibility: visible; }",
+ "}"
+ ].join("\n");
+
+ ok(isVisible("zoom1"), "Base ratio rule should apply at base zoom level");
+ ok(!isVisible("zoom2") && !isVisible("zoom3"), "no other rules should apply");
+ var origZoom = zoom(2);
+ ok(isVisible("zoom2"), "Double ratio rule should apply at double zoom level");
+ ok(!isVisible("zoom1") && !isVisible("zoom3"), "no other rules should apply");
+ zoom(0.5);
+ ok(isVisible("zoom3"), "Half ratio rule should apply at half zoom level");
+ ok(!isVisible("zoom1") && !isVisible("zoom2"), "no other rules should apply");
+ zoom(origZoom);
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_webkit_flex_display.html b/layout/style/test/test_webkit_flex_display.html
new file mode 100644
index 0000000000..2f329ed67d
--- /dev/null
+++ b/layout/style/test/test_webkit_flex_display.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1274096
+-->
+<head>
+ <title>Test for Bug 1274096</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1274096">Mozilla Bug 1274096</a>
+<div id="content" style="display: none">
+ <div id="testElem"></div>
+</div>
+<script type="text/javascript">
+</script>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 1274096 **/
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+function runTest() {
+ testValue("display", "-webkit-flex", "flex");
+ testValue("display", "-webkit-inline-flex", "inline-flex");
+
+ SimpleTest.finish();
+}
+
+function testValue(propName, specifiedVal, serializedVal) {
+ var testElem = document.getElementById("testElem");
+ testElem.style[propName] = specifiedVal;
+
+ is(testElem.style[propName], serializedVal,
+ `CSS '${propName}:${specifiedVal} should serialize as '${serializedVal}'`);
+ is(window.getComputedStyle(testElem)[propName], serializedVal,
+ `CSS 'display:${specifiedVal} should compute to '${serializedVal}'`);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/unstyled-frame.css b/layout/style/test/unstyled-frame.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/layout/style/test/unstyled-frame.css
diff --git a/layout/style/test/unstyled-frame.xml b/layout/style/test/unstyled-frame.xml
new file mode 100644
index 0000000000..833b4f112f
--- /dev/null
+++ b/layout/style/test/unstyled-frame.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="unstyled-frame.css" type="text/css"?>
+<!-- The root element is forced to display:block, so look at its child -->
+<root><child/></root>
diff --git a/layout/style/test/unstyled.css b/layout/style/test/unstyled.css
new file mode 100644
index 0000000000..82767f9b2f
--- /dev/null
+++ b/layout/style/test/unstyled.css
@@ -0,0 +1,2 @@
+/* we're testing computed style on elements without frames */
+root { display: none }
diff --git a/layout/style/test/unstyled.xml b/layout/style/test/unstyled.xml
new file mode 100644
index 0000000000..86b7c54acd
--- /dev/null
+++ b/layout/style/test/unstyled.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="unstyled.css" type="text/css"?>
+<root><child/></root>
diff --git a/layout/style/test/viewport_units_iframe.html b/layout/style/test/viewport_units_iframe.html
new file mode 100644
index 0000000000..fd71a3cd3e
--- /dev/null
+++ b/layout/style/test/viewport_units_iframe.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<title>viewport units test</title>
+<div id="vh" style="width: 100vh"></div>
+<div id="vw" style="width: 100vw"></div>
+<div id="vmin" style="width: 100vmin"></div>
+<div id="vmax" style="width: 100vmax"></div>
diff --git a/layout/style/test/visited-lying-inner.html b/layout/style/test/visited-lying-inner.html
new file mode 100644
index 0000000000..ad1dac7587
--- /dev/null
+++ b/layout/style/test/visited-lying-inner.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<title>Test document for test_visited_lying.html</title>
+<style>
+:link { color: blue }
+:visited { color: purple }
+</style>
+<div><a id="unvisitedlink" href="http://www.example.com/url-that-was-never-visited">unvisited link</a></div>
+<div><a id="visitedlink" href="http://www.example.com/url-that-was-never-visited">visited link</a></div>
diff --git a/layout/style/test/visited-pref-iframe.html b/layout/style/test/visited-pref-iframe.html
new file mode 100644
index 0000000000..31da176e44
--- /dev/null
+++ b/layout/style/test/visited-pref-iframe.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<title>iframe for test_visited_pref.html</title>
+<style>
+:link { color: blue }
+:visited { color: purple }
+</style>
+<a href="http://www.example.com/url-that-has-not-been-visited" id="link">link</a>
diff --git a/layout/style/test/visited_image_loading.sjs b/layout/style/test/visited_image_loading.sjs
new file mode 100644
index 0000000000..79c03d7b54
--- /dev/null
+++ b/layout/style/test/visited_image_loading.sjs
@@ -0,0 +1,83 @@
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ var query = request.queryString;
+ switch (query) {
+ case "reset":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ setState("1l", "");
+ setState("1v", "");
+ setState("2l", "");
+ setState("2v", "");
+ break;
+ case "1l":
+ case "1v":
+ case "2l":
+ case "2v":
+ setState(query, getState(query) + "load");
+ response.setStatusLine("1.1", 302, "Found");
+ // redirect to a solid blue image
+ response.setHeader(
+ "Location",
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12NgYPgPAAEDAQDZqt2zAAAAAElFTkSuQmCC"
+ );
+ response.setHeader("Content-Type", "text/plain", false);
+ break;
+
+ case "waitforresult":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ response.write("var start = Date.now();\n");
+ // fall through!
+
+ case "waitforresult-internal":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ response.write(
+ "if ('" +
+ getState("1l") +
+ "' == 'load' && '" +
+ getState("1v") +
+ "' == '' && '" +
+ getState("2l") +
+ "' == 'load' && '" +
+ getState("2v") +
+ "' == '') { \n"
+ );
+ response.write("setTimeout(function() {\n");
+ response.write("var s = document.createElement('script');\n");
+ response.write("s.src = 'visited_image_loading.sjs?result';\n");
+ response.write("document.body.appendChild(s);");
+ response.write("}, Math.max(100, 2 * (Date.now() - start)));\n");
+ response.write("} else setTimeout(function() {\n");
+ response.write("var s = document.createElement('script');\n");
+ response.write(
+ "s.src = 'visited_image_loading.sjs?waitforresult-internal';\n"
+ );
+ response.write("document.body.appendChild(s);");
+ response.write("}, 10);\n");
+ break;
+
+ case "result":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ response.write(
+ "is('" +
+ getState("1l") +
+ "', 'load', 'image 1l should have been loaded once')\n"
+ );
+ response.write(
+ "is('" +
+ getState("1v") +
+ "', '', 'image 1v should not have been loaded')\n"
+ );
+ response.write(
+ "is('" +
+ getState("2l") +
+ "', 'load', 'image 2l should have been loaded once')\n"
+ );
+ response.write(
+ "is('" +
+ getState("2v") +
+ "', '', 'image 2v should not have been loaded')\n"
+ );
+ response.write("SimpleTest.finish()");
+ break;
+ }
+}
diff --git a/layout/style/test/visited_image_loading_frame.html b/layout/style/test/visited_image_loading_frame.html
new file mode 100644
index 0000000000..f919f5eb75
--- /dev/null
+++ b/layout/style/test/visited_image_loading_frame.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<title>Test for :visited image loading</title>
+<style type="text/css">
+
+:link { background-color: blue }
+:visited { background-color: purple }
+
+#link:link { background-image: url("visited_image_loading.sjs?1l"); }
+#link:visited { background-image: url("visited_image_loading.sjs?1v"); }
+#visited:link { background-image: url("visited_image_loading.sjs?2l"); }
+#visited:visited { background-image: url("visited_image_loading.sjs?2v"); }
+
+</style>
+<a id="link" href="do-not-visit-this-link.html">unvisited link</a>
+<a id="visited" href="visited_image_loading_frame.html">visited link</a>
diff --git a/layout/style/test/visited_image_loading_frame_empty.html b/layout/style/test/visited_image_loading_frame_empty.html
new file mode 100644
index 0000000000..21579bb9ca
--- /dev/null
+++ b/layout/style/test/visited_image_loading_frame_empty.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<title>Test for :visited image loading</title>
+<style type="text/css">
+
+:link { background-color: blue }
+:visited { background-color: purple }
+
+#link:link { background-image: url("visited_image_loading.sjs?1l"); }
+#link:visited { background-image: url("visited_image_loading.sjs?1v"); }
+#visited:link { background-image: url("visited_image_loading.sjs?2l"); }
+#visited:visited { background-image: url("visited_image_loading.sjs?2v"); }
+
+</style>
+<a id="link" href="do-not-visit-this-link.html"></a>
+<a id="visited" href="visited_image_loading_frame_empty.html"></a>
diff --git a/layout/style/tools/cleanup_computed_getters.py b/layout/style/tools/cleanup_computed_getters.py
new file mode 100644
index 0000000000..301986751e
--- /dev/null
+++ b/layout/style/tools/cleanup_computed_getters.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+"""
+Script to remove unused getters in nsComputedDOMStyle.
+
+It needs to be run from the topsrcdir, and it requires passing in the objdir
+as first argument. It can only be run after nsComputedDOMStyleGenerated.inc
+is generated in the objdir.
+"""
+
+import re
+import sys
+
+from pathlib import Path
+
+if len(sys.argv) != 2:
+ print("Usage: {} objdir".format(sys.argv[0]))
+ exit(1)
+
+generated = Path(sys.argv[1]) / "layout" / "style"
+generated = generated / "nsComputedDOMStyleGenerated.inc"
+RE_GENERATED = re.compile(r"DoGet\w+")
+keeping = set()
+with generated.open() as f:
+ for line in f:
+ m = RE_GENERATED.search(line)
+ if m is not None:
+ keeping.add(m.group(0))
+
+HEADER = "layout/style/nsComputedDOMStyle.h"
+SOURCE = "layout/style/nsComputedDOMStyle.cpp"
+
+# We need to keep functions invoked by others
+RE_DEF = re.compile(r"nsComputedDOMStyle::(DoGet\w+)\(\)")
+RE_SRC = re.compile(r"\b(DoGet\w+)\(\)")
+with open(SOURCE, "r") as f:
+ for line in f:
+ m = RE_DEF.search(line)
+ if m is not None:
+ continue
+ m = RE_SRC.search(line)
+ if m is not None:
+ keeping.add(m.group(1))
+
+removing = set()
+remaining_lines = []
+with open(HEADER, "r") as f:
+ for line in f:
+ m = RE_SRC.search(line)
+ if m is not None:
+ name = m.group(1)
+ if name not in keeping:
+ print("Removing " + name)
+ removing.add(name)
+ continue
+ remaining_lines.append(line)
+
+with open(HEADER, "w", newline="") as f:
+ f.writelines(remaining_lines)
+
+remaining_lines = []
+is_removing = False
+with open(SOURCE, "r") as f:
+ for line in f:
+ if is_removing:
+ if line == "}\n":
+ is_removing = False
+ continue
+ m = RE_DEF.search(line)
+ if m is not None:
+ name = m.group(1)
+ if name in removing:
+ remaining_lines.pop()
+ if remaining_lines[-1] == "\n":
+ remaining_lines.pop()
+ is_removing = True
+ continue
+ remaining_lines.append(line)
+
+with open(SOURCE, "w", newline="") as f:
+ f.writelines(remaining_lines)