summaryrefslogtreecommitdiffstats
path: root/layout/style
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style')
-rw-r--r--layout/style/AnimationCollection.cpp172
-rw-r--r--layout/style/AnimationCollection.h147
-rw-r--r--layout/style/AnimationCommon.cpp54
-rw-r--r--layout/style/AnimationCommon.h256
-rw-r--r--layout/style/CSS.cpp107
-rw-r--r--layout/style/CSS.h44
-rw-r--r--layout/style/CSSCalc.h361
-rw-r--r--layout/style/CSSEnabledState.h39
-rw-r--r--layout/style/CSSLexer.cpp166
-rw-r--r--layout/style/CSSLexer.h39
-rw-r--r--layout/style/CSSRuleList.cpp34
-rw-r--r--layout/style/CSSRuleList.h66
-rw-r--r--layout/style/CSSStyleSheet.cpp2026
-rw-r--r--layout/style/CSSStyleSheet.h281
-rw-r--r--layout/style/CSSUnprefixingService.js342
-rw-r--r--layout/style/CSSUnprefixingService.manifest2
-rw-r--r--layout/style/CSSValue.h47
-rw-r--r--layout/style/CSSVariableDeclarations.cpp195
-rw-r--r--layout/style/CSSVariableDeclarations.h139
-rw-r--r--layout/style/CSSVariableImageTable.h190
-rw-r--r--layout/style/CSSVariableResolver.cpp266
-rw-r--r--layout/style/CSSVariableResolver.h148
-rw-r--r--layout/style/CSSVariableValues.cpp147
-rw-r--r--layout/style/CSSVariableValues.h147
-rw-r--r--layout/style/CounterStyleManager.cpp2135
-rw-r--r--layout/style/CounterStyleManager.h192
-rw-r--r--layout/style/Declaration.cpp1988
-rw-r--r--layout/style/Declaration.h417
-rw-r--r--layout/style/DeclarationBlock.h146
-rw-r--r--layout/style/DeclarationBlockInlines.h114
-rw-r--r--layout/style/ErrorReporter.cpp382
-rw-r--r--layout/style/ErrorReporter.h113
-rw-r--r--layout/style/FontFace.cpp808
-rw-r--r--layout/style/FontFace.h271
-rw-r--r--layout/style/FontFaceSet.cpp1848
-rw-r--r--layout/style/FontFaceSet.h356
-rw-r--r--layout/style/FontFaceSetIterator.cpp80
-rw-r--r--layout/style/FontFaceSetIterator.h44
-rw-r--r--layout/style/GenerateCSSPropsGenerated.py104
-rw-r--r--layout/style/GroupRule.h101
-rw-r--r--layout/style/HandleRefPtr.h133
-rw-r--r--layout/style/ImageDocument.css31
-rw-r--r--layout/style/ImageLoader.cpp529
-rw-r--r--layout/style/ImageLoader.h124
-rw-r--r--layout/style/ImportRule.h72
-rw-r--r--layout/style/IncrementalClearCOMRuleArray.cpp81
-rw-r--r--layout/style/IncrementalClearCOMRuleArray.h28
-rw-r--r--layout/style/LayerAnimationInfo.cpp53
-rw-r--r--layout/style/LayerAnimationInfo.h33
-rw-r--r--layout/style/Loader.cpp2694
-rw-r--r--layout/style/Loader.h598
-rw-r--r--layout/style/Makefile.in16
-rw-r--r--layout/style/MediaQueryList.cpp207
-rw-r--r--layout/style/MediaQueryList.h93
-rw-r--r--layout/style/NameSpaceRule.h70
-rw-r--r--layout/style/PythonCSSProps.h41
-rw-r--r--layout/style/Rule.h141
-rw-r--r--layout/style/RuleNodeCacheConditions.cpp53
-rw-r--r--layout/style/RuleNodeCacheConditions.h156
-rw-r--r--layout/style/RuleProcessorCache.cpp286
-rw-r--r--layout/style/RuleProcessorCache.h150
-rw-r--r--layout/style/SVGAttrAnimationRuleProcessor.cpp122
-rw-r--r--layout/style/SVGAttrAnimationRuleProcessor.h68
-rw-r--r--layout/style/ServoBindingList.h146
-rw-r--r--layout/style/ServoBindingTypes.h144
-rw-r--r--layout/style/ServoBindings.cpp1083
-rw-r--r--layout/style/ServoBindings.h285
-rw-r--r--layout/style/ServoDeclarationBlock.cpp110
-rw-r--r--layout/style/ServoDeclarationBlock.h72
-rw-r--r--layout/style/ServoElementSnapshot.cpp46
-rw-r--r--layout/style/ServoElementSnapshot.h169
-rw-r--r--layout/style/ServoStyleSet.cpp479
-rw-r--r--layout/style/ServoStyleSet.h175
-rw-r--r--layout/style/ServoStyleSheet.cpp143
-rw-r--r--layout/style/ServoStyleSheet.h90
-rw-r--r--layout/style/ServoTypes.h40
-rw-r--r--layout/style/ServoUtils.h82
-rw-r--r--layout/style/SheetParsingMode.h43
-rw-r--r--layout/style/SheetType.h38
-rw-r--r--layout/style/StyleAnimationValue.cpp4990
-rw-r--r--layout/style/StyleAnimationValue.h602
-rw-r--r--layout/style/StyleBackendType.h23
-rw-r--r--layout/style/StyleComplexColor.h45
-rw-r--r--layout/style/StyleContextSource.h161
-rw-r--r--layout/style/StyleRule.cpp1577
-rw-r--r--layout/style/StyleRule.h369
-rw-r--r--layout/style/StyleSetHandle.h219
-rw-r--r--layout/style/StyleSetHandleInlines.h267
-rw-r--r--layout/style/StyleSheet.cpp329
-rw-r--r--layout/style/StyleSheet.h210
-rw-r--r--layout/style/StyleSheetInfo.h52
-rw-r--r--layout/style/StyleSheetInlines.h176
-rw-r--r--layout/style/StyleStructContext.h121
-rw-r--r--layout/style/TopLevelImageDocument.css40
-rw-r--r--layout/style/TopLevelVideoDocument.css32
-rw-r--r--layout/style/contenteditable.css343
-rw-r--r--layout/style/crashtests/1017798-1.css84
-rw-r--r--layout/style/crashtests/1017798-1.html124
-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/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/1227501-1.html8
-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/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/1264396-1.html14
-rw-r--r--layout/style/crashtests/1264949.html23
-rw-r--r--layout/style/crashtests/1265611-1.html16
-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/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/1321357-1.html12
-rw-r--r--layout/style/crashtests/1356601-1.html18
-rw-r--r--layout/style/crashtests/147777-1.html6
-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.html36
-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.xul6
-rw-r--r--layout/style/crashtests/418007-1.xhtml24
-rw-r--r--layout/style/crashtests/431705-1.xul6
-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/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/621596-1.html18
-rw-r--r--layout/style/crashtests/622314-1.xhtml26
-rw-r--r--layout/style/crashtests/637242.xhtml27
-rw-r--r--layout/style/crashtests/645142.html11
-rw-r--r--layout/style/crashtests/645951-1-ref.html4
-rw-r--r--layout/style/crashtests/645951-1.css1
-rw-r--r--layout/style/crashtests/645951-1.html11
-rw-r--r--layout/style/crashtests/652976-1.svg10
-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/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/812824.html1
-rw-r--r--layout/style/crashtests/822766-1.html31
-rw-r--r--layout/style/crashtests/822842.html13
-rw-r--r--layout/style/crashtests/827591-1.html19
-rw-r--r--layout/style/crashtests/829817.html20
-rw-r--r--layout/style/crashtests/840898.html17
-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/880862.html28
-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/crashtests.list166
-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/long-url-list-stack-overflow.html23
-rw-r--r--layout/style/designmode.css8
-rwxr-xr-xlayout/style/generate-stylestructlist.py165
-rw-r--r--layout/style/jar.mn35
-rw-r--r--layout/style/moz.build264
-rw-r--r--layout/style/nsAnimationManager.cpp1082
-rw-r--r--layout/style/nsAnimationManager.h361
-rw-r--r--layout/style/nsCSSAnonBoxList.h103
-rw-r--r--layout/style/nsCSSAnonBoxes.cpp52
-rw-r--r--layout/style/nsCSSAnonBoxes.h34
-rw-r--r--layout/style/nsCSSCounterDescList.h15
-rw-r--r--layout/style/nsCSSDataBlock.cpp802
-rw-r--r--layout/style/nsCSSDataBlock.h372
-rw-r--r--layout/style/nsCSSFontDescList.h14
-rw-r--r--layout/style/nsCSSKeywordList.h799
-rw-r--r--layout/style/nsCSSKeywords.cpp87
-rw-r--r--layout/style/nsCSSKeywords.h42
-rw-r--r--layout/style/nsCSSParser.cpp18324
-rw-r--r--layout/style/nsCSSParser.h346
-rw-r--r--layout/style/nsCSSPropAliasList.h498
-rw-r--r--layout/style/nsCSSPropList.h4635
-rw-r--r--layout/style/nsCSSPropLogicalGroupList.h56
-rw-r--r--layout/style/nsCSSPropertyID.h118
-rw-r--r--layout/style/nsCSSPropertyIDSet.h107
-rw-r--r--layout/style/nsCSSProps.cpp3428
-rw-r--r--layout/style/nsCSSProps.h895
-rw-r--r--layout/style/nsCSSPropsGenerated.inc.in17
-rw-r--r--layout/style/nsCSSPseudoClassList.h253
-rw-r--r--layout/style/nsCSSPseudoClasses.cpp131
-rw-r--r--layout/style/nsCSSPseudoClasses.h90
-rw-r--r--layout/style/nsCSSPseudoElementList.h87
-rw-r--r--layout/style/nsCSSPseudoElements.cpp120
-rw-r--r--layout/style/nsCSSPseudoElements.h128
-rw-r--r--layout/style/nsCSSRuleProcessor.cpp4061
-rw-r--r--layout/style/nsCSSRuleProcessor.h292
-rw-r--r--layout/style/nsCSSRules.cpp3312
-rw-r--r--layout/style/nsCSSRules.h673
-rw-r--r--layout/style/nsCSSScanner.cpp1380
-rw-r--r--layout/style/nsCSSScanner.h397
-rw-r--r--layout/style/nsCSSValue.cpp3237
-rw-r--r--layout/style/nsCSSValue.h1943
-rw-r--r--layout/style/nsComputedDOMStyle.cpp6698
-rw-r--r--layout/style/nsComputedDOMStyle.h749
-rw-r--r--layout/style/nsComputedDOMStylePropertyList.h361
-rw-r--r--layout/style/nsDOMCSSAttrDeclaration.cpp204
-rw-r--r--layout/style/nsDOMCSSAttrDeclaration.h57
-rw-r--r--layout/style/nsDOMCSSDeclaration.cpp406
-rw-r--r--layout/style/nsDOMCSSDeclaration.h174
-rw-r--r--layout/style/nsDOMCSSRGBColor.cpp38
-rw-r--r--layout/style/nsDOMCSSRGBColor.h67
-rw-r--r--layout/style/nsDOMCSSRect.cpp77
-rw-r--r--layout/style/nsDOMCSSRect.h52
-rw-r--r--layout/style/nsDOMCSSValueList.cpp129
-rw-r--r--layout/style/nsDOMCSSValueList.h73
-rw-r--r--layout/style/nsFontFaceLoader.cpp306
-rw-r--r--layout/style/nsFontFaceLoader.h63
-rw-r--r--layout/style/nsFontFaceUtils.cpp151
-rw-r--r--layout/style/nsFontFaceUtils.h23
-rw-r--r--layout/style/nsHTMLCSSStyleSheet.cpp217
-rw-r--r--layout/style/nsHTMLCSSStyleSheet.h80
-rw-r--r--layout/style/nsHTMLStyleSheet.cpp592
-rw-r--r--layout/style/nsHTMLStyleSheet.h181
-rw-r--r--layout/style/nsICSSDeclaration.h174
-rw-r--r--layout/style/nsICSSLoaderObserver.h44
-rw-r--r--layout/style/nsICSSPseudoComparator.h19
-rw-r--r--layout/style/nsICSSStyleRuleDOMWrapper.h32
-rw-r--r--layout/style/nsICSSUnprefixingService.idl76
-rw-r--r--layout/style/nsIMediaList.h318
-rw-r--r--layout/style/nsIStyleRule.h96
-rw-r--r--layout/style/nsIStyleRuleProcessor.h140
-rw-r--r--layout/style/nsLayoutStylesheetCache.cpp1000
-rw-r--r--layout/style/nsLayoutStylesheetCache.h135
-rw-r--r--layout/style/nsMediaFeatures.cpp813
-rw-r--r--layout/style/nsMediaFeatures.h91
-rw-r--r--layout/style/nsNthIndexCache.cpp155
-rw-r--r--layout/style/nsNthIndexCache.h110
-rw-r--r--layout/style/nsROCSSPrimitiveValue.cpp722
-rw-r--r--layout/style/nsROCSSPrimitiveValue.h135
-rw-r--r--layout/style/nsRuleData.cpp55
-rw-r--r--layout/style/nsRuleData.h128
-rw-r--r--layout/style/nsRuleNode.cpp10736
-rw-r--r--layout/style/nsRuleNode.h1090
-rw-r--r--layout/style/nsRuleProcessorData.h595
-rw-r--r--layout/style/nsRuleWalker.h108
-rw-r--r--layout/style/nsStyleConsts.h1289
-rw-r--r--layout/style/nsStyleContext.cpp1836
-rw-r--r--layout/style/nsStyleContext.h853
-rw-r--r--layout/style/nsStyleCoord.cpp421
-rw-r--r--layout/style/nsStyleCoord.h649
-rw-r--r--layout/style/nsStyleSet.cpp2537
-rw-r--r--layout/style/nsStyleSet.h600
-rw-r--r--layout/style/nsStyleStruct.cpp4261
-rw-r--r--layout/style/nsStyleStruct.h4002
-rw-r--r--layout/style/nsStyleStructFwd.h64
-rw-r--r--layout/style/nsStyleStructInlines.h264
-rw-r--r--layout/style/nsStyleTransformMatrix.cpp1061
-rw-r--r--layout/style/nsStyleTransformMatrix.h226
-rw-r--r--layout/style/nsStyleUtil.cpp783
-rw-r--r--layout/style/nsStyleUtil.h207
-rw-r--r--layout/style/nsTransitionManager.cpp1047
-rw-r--r--layout/style/nsTransitionManager.h447
-rw-r--r--layout/style/res/accessiblecaret-normal@1.5x.pngbin0 -> 16477 bytes
-rw-r--r--layout/style/res/accessiblecaret-normal@1x.pngbin0 -> 15965 bytes
-rw-r--r--layout/style/res/accessiblecaret-normal@2.25x.pngbin0 -> 17193 bytes
-rw-r--r--layout/style/res/accessiblecaret-normal@2x.pngbin0 -> 17190 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-left@1.5x.pngbin0 -> 16342 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-left@1x.pngbin0 -> 15894 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-left@2.25x.pngbin0 -> 17063 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-left@2x.pngbin0 -> 16756 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-right@1.5x.pngbin0 -> 16360 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-right@1x.pngbin0 -> 15886 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-right@2.25x.pngbin0 -> 17087 bytes
-rw-r--r--layout/style/res/accessiblecaret-tilt-right@2x.pngbin0 -> 16763 bytes
-rw-r--r--layout/style/res/arrow-left.gifbin0 -> 57 bytes
-rw-r--r--layout/style/res/arrow-right.gifbin0 -> 57 bytes
-rw-r--r--layout/style/res/arrow.gifbin0 -> 56 bytes
-rw-r--r--layout/style/res/arrowd-left.gifbin0 -> 60 bytes
-rw-r--r--layout/style/res/arrowd-right.gifbin0 -> 59 bytes
-rw-r--r--layout/style/res/arrowd.gifbin0 -> 59 bytes
-rw-r--r--layout/style/res/counterstyles.css365
-rw-r--r--layout/style/res/forms.css1137
-rw-r--r--layout/style/res/html.css863
-rw-r--r--layout/style/res/noframes.css13
-rw-r--r--layout/style/res/noscript.css9
-rw-r--r--layout/style/res/number-control.css18
-rw-r--r--layout/style/res/plaintext.css9
-rw-r--r--layout/style/res/quirk.css203
-rw-r--r--layout/style/res/ua.css473
-rw-r--r--layout/style/res/viewsource.css101
-rw-r--r--layout/style/test/BitPattern.woffbin0 -> 6248 bytes
-rw-r--r--layout/style/test/ListCSSProperties.cpp193
-rw-r--r--layout/style/test/ParseCSS.cpp89
-rw-r--r--layout/style/test/TestCSSPropertyLookup.cpp172
-rw-r--r--layout/style/test/additional_sheets_helper.html7
-rw-r--r--layout/style/test/animation_utils.js707
-rw-r--r--layout/style/test/browser.ini8
-rw-r--r--layout/style/test/browser_bug453896.js13
-rw-r--r--layout/style/test/browser_newtab_share_rule_processors.js38
-rw-r--r--layout/style/test/bug453896_iframe.html66
-rw-r--r--layout/style/test/bug517224.sjs24
-rw-r--r--layout/style/test/bug732209-css.sjs19
-rw-r--r--layout/style/test/ccd-quirks.html124
-rw-r--r--layout/style/test/ccd-standards.html123
-rw-r--r--layout/style/test/ccd.sjs73
-rw-r--r--layout/style/test/chrome/bug418986-2.js314
-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.xul8
-rw-r--r--layout/style/test/chrome/chrome.ini21
-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/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_author_specified_style.html55
-rw-r--r--layout/style/test/chrome/test_bug1157097.html31
-rw-r--r--layout/style/test/chrome/test_bug1160724.xul76
-rw-r--r--layout/style/test/chrome/test_bug418986-2.xul30
-rw-r--r--layout/style/test/chrome/test_bug535806.xul43
-rw-r--r--layout/style/test/chrome/test_display_mode.html94
-rw-r--r--layout/style/test/chrome/test_display_mode_reflow.html74
-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/css_properties_like_longhand.js3
-rw-r--r--layout/style/test/descriptor_database.js72
-rw-r--r--layout/style/test/display_mode_reflow_iframe.html23
-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_effect_timing_duration.html86
-rw-r--r--layout/style/test/file_animations_effect_timing_enddelay.html146
-rw-r--r--layout/style/test/file_animations_effect_timing_iterations.html73
-rw-r--r--layout/style/test/file_animations_iterationstart.html60
-rw-r--r--layout/style/test/file_animations_pausing.html85
-rw-r--r--layout/style/test/file_animations_playbackrate.html102
-rw-r--r--layout/style/test/file_animations_styles_on_event.html70
-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_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_font_loading_api_vframe.html2
-rw-r--r--layout/style/test/file_transitions_replacement_on_busy_frame.html93
-rw-r--r--layout/style/test/file_transitions_with_disabled_properties.html46
-rw-r--r--layout/style/test/flexbox_layout_testcases.js1398
-rw-r--r--layout/style/test/gen-css-properties.py24
-rw-r--r--layout/style/test/media_queries_dynamic_xbl_binding.xml13
-rw-r--r--layout/style/test/media_queries_dynamic_xbl_iframe.html5
-rw-r--r--layout/style/test/media_queries_dynamic_xbl_style.css6
-rw-r--r--layout/style/test/media_queries_iframe.html15
-rw-r--r--layout/style/test/mochitest.ini312
-rw-r--r--layout/style/test/moz.build126
-rw-r--r--layout/style/test/neverending_font_load.sjs6
-rw-r--r--layout/style/test/neverending_stylesheet_load.sjs6
-rw-r--r--layout/style/test/newtab_share_rule_processors.html22
-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.js7917
-rw-r--r--layout/style/test/redirect.sjs5
-rw-r--r--layout/style/test/redundant_font_download.sjs60
-rw-r--r--layout/style/test/style_attribute_tests.js27
-rw-r--r--layout/style/test/support/external-variable-url.css3
-rw-r--r--layout/style/test/test_acid3_test46.html141
-rw-r--r--layout/style/test/test_addSheet.html46
-rw-r--r--layout/style/test/test_additional_sheets.html314
-rw-r--r--layout/style/test/test_align_justify_computed_values.html529
-rw-r--r--layout/style/test/test_align_shorthand_serialization.html123
-rw-r--r--layout/style/test/test_all_shorthand.html159
-rw-r--r--layout/style/test/test_animations.html2047
-rw-r--r--layout/style/test/test_animations_async_tests.html26
-rw-r--r--layout/style/test/test_animations_dynamic_changes.html65
-rw-r--r--layout/style/test/test_animations_effect_timing_duration.html24
-rw-r--r--layout/style/test/test_animations_effect_timing_enddelay.html24
-rw-r--r--layout/style/test/test_animations_effect_timing_iterations.html24
-rw-r--r--layout/style/test/test_animations_event_handler_attribute.html147
-rw-r--r--layout/style/test/test_animations_event_order.html585
-rw-r--r--layout/style/test/test_animations_iterationstart.html28
-rw-r--r--layout/style/test/test_animations_omta.html2392
-rw-r--r--layout/style/test/test_animations_omta_start.html189
-rw-r--r--layout/style/test/test_animations_pausing.html28
-rw-r--r--layout/style/test/test_animations_playbackrate.html28
-rw-r--r--layout/style/test/test_animations_styles_on_event.html28
-rw-r--r--layout/style/test/test_animations_with_disabled_properties.html34
-rw-r--r--layout/style/test/test_any_dynamic.html49
-rw-r--r--layout/style/test/test_asyncopen2.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_background_blend_mode.html58
-rw-r--r--layout/style/test/test_box_size_keywords.html172
-rw-r--r--layout/style/test/test_bug1055933.html42
-rw-r--r--layout/style/test/test_bug1089417.html47
-rw-r--r--layout/style/test/test_bug1112014.html127
-rw-r--r--layout/style/test/test_bug1203766.html112
-rw-r--r--layout/style/test/test_bug1232829.html38
-rw-r--r--layout/style/test/test_bug1292447.html377
-rw-r--r--layout/style/test/test_bug160403.html73
-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.html89
-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.html91
-rw-r--r--layout/style/test/test_bug373293.html29
-rw-r--r--layout/style/test/test_bug377947.html107
-rw-r--r--layout/style/test/test_bug379440.html72
-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.html78
-rw-r--r--layout/style/test/test_bug418986-2.html33
-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_bug453896_deck.html42
-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_bug511909.html205
-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.html212
-rw-r--r--layout/style/test/test_bug657143.html132
-rw-r--r--layout/style/test/test_bug664955.html37
-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.html192
-rw-r--r--layout/style/test/test_bug74880.html125
-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_bug798567.html26
-rw-r--r--layout/style/test/test_bug798843_pref.html57
-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.html61
-rw-r--r--layout/style/test/test_change_hint_optimizations.html57
-rw-r--r--layout/style/test/test_clip-path_polygon.html41
-rw-r--r--layout/style/test/test_compute_data_with_start_struct.html109
-rw-r--r--layout/style/test/test_computed_style.html413
-rw-r--r--layout/style/test/test_computed_style_min_size_auto.html133
-rw-r--r--layout/style/test/test_computed_style_no_pseudo.html44
-rw-r--r--layout/style/test/test_computed_style_prefs.html99
-rw-r--r--layout/style/test/test_condition_text.html93
-rw-r--r--layout/style/test/test_condition_text_assignment.html59
-rw-r--r--layout/style/test/test_contain_formatting_context.html40
-rw-r--r--layout/style/test/test_counter_descriptor_storage.html267
-rw-r--r--layout/style/test/test_counter_style.html121
-rw-r--r--layout/style/test/test_css_cross_domain.html99
-rw-r--r--layout/style/test/test_css_eof_handling.html278
-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_supports.html134
-rw-r--r--layout/style/test/test_css_supports_variables.html247
-rw-r--r--layout/style/test/test_csslexer.js171
-rw-r--r--layout/style/test/test_default_bidi_css.html79
-rw-r--r--layout/style/test/test_default_computed_style.html58
-rw-r--r--layout/style/test/test_descriptor_storage.html119
-rw-r--r--layout/style/test/test_descriptor_syntax_errors.html53
-rw-r--r--layout/style/test/test_dont_use_document_colors.html186
-rw-r--r--layout/style/test/test_dynamic_change_causing_reflow.html173
-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_flexbox_child_display_values.xhtml189
-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_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.html137
-rw-r--r--layout/style/test/test_font_face_parser.html382
-rw-r--r--layout/style/test/test_font_family_parsing.html276
-rw-r--r--layout/style/test/test_font_feature_values_parsing.html356
-rw-r--r--layout/style/test/test_font_loading_api.html1897
-rw-r--r--layout/style/test/test_garbage_at_end_of_declarations.html151
-rw-r--r--layout/style/test/test_grid_computed_values.html106
-rw-r--r--layout/style/test/test_grid_container_shorthands.html282
-rw-r--r--layout/style/test/test_grid_item_shorthands.html153
-rw-r--r--layout/style/test/test_grid_shorthand_serialization.html236
-rw-r--r--layout/style/test/test_group_insertRule.html243
-rw-r--r--layout/style/test/test_hover_quirk.html82
-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_inherit_computation.html159
-rw-r--r--layout/style/test/test_inherit_storage.html116
-rw-r--r--layout/style/test/test_initial_computation.html163
-rw-r--r--layout/style/test/test_initial_storage.html130
-rw-r--r--layout/style/test/test_keyframes_rules.html134
-rw-r--r--layout/style/test/test_load_events_on_stylesheets.html152
-rw-r--r--layout/style/test/test_logical_properties.html422
-rw-r--r--layout/style/test/test_media_queries.html845
-rw-r--r--layout/style/test/test_media_queries_dynamic.html213
-rw-r--r--layout/style/test/test_media_queries_dynamic_xbl.html40
-rw-r--r--layout/style/test/test_media_query_list.html367
-rw-r--r--layout/style/test/test_moz_device_pixel_ratio.html77
-rw-r--r--layout/style/test/test_namespace_rule.html462
-rw-r--r--layout/style/test/test_of_type_selectors.xhtml98
-rw-r--r--layout/style/test/test_page_parser.html107
-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.html256
-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.html75
-rw-r--r--layout/style/test/test_pointer-events.html114
-rw-r--r--layout/style/test/test_position_float_display.html107
-rw-r--r--layout/style/test/test_position_sticky.html89
-rw-r--r--layout/style/test/test_priority_preservation.html141
-rw-r--r--layout/style/test/test_property_database.html170
-rw-r--r--layout/style/test/test_property_syntax_errors.html153
-rw-r--r--layout/style/test/test_pseudoelement_parsing.html43
-rw-r--r--layout/style/test/test_pseudoelement_state.html164
-rw-r--r--layout/style/test/test_redundant_font_download.html130
-rw-r--r--layout/style/test/test_rem_unit.html80
-rw-r--r--layout/style/test/test_restyles_in_smil_animation.html113
-rw-r--r--layout/style/test/test_root_node_display.html67
-rw-r--r--layout/style/test/test_rule_insertion.html240
-rw-r--r--layout/style/test/test_rule_serialization.html53
-rw-r--r--layout/style/test/test_rules_out_of_sheets.html115
-rw-r--r--layout/style/test/test_selectors.html1297
-rw-r--r--layout/style/test/test_selectors_on_anonymous_content.html78
-rw-r--r--layout/style/test/test_setPropertyWithNull.html47
-rw-r--r--layout/style/test/test_shorthand_property_getters.html268
-rw-r--r--layout/style/test/test_specified_value_serialization.html105
-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_supports_rules.html88
-rw-r--r--layout/style/test/test_system_font_serialization.html59
-rw-r--r--layout/style/test/test_text_decoration_shorthands.html93
-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_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.html282
-rw-r--r--layout/style/test/test_transitions_per_property.html2565
-rw-r--r--layout/style/test/test_transitions_replacement_on_busy_frame.html30
-rw-r--r--layout/style/test/test_transitions_step_functions.html93
-rw-r--r--layout/style/test/test_transitions_with_disabled_properties.html28
-rw-r--r--layout/style/test/test_unclosed_parentheses.html289
-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.html59
-rw-r--r--layout/style/test/test_units_time.html47
-rw-r--r--layout/style/test/test_unprefixing_service.html93
-rw-r--r--layout/style/test/test_unprefixing_service_prefs.html132
-rw-r--r--layout/style/test/test_value_cloning.html182
-rw-r--r--layout/style/test/test_value_computation.html249
-rw-r--r--layout/style/test/test_value_storage.html352
-rw-r--r--layout/style/test/test_variable_serialization_computed.html82
-rw-r--r--layout/style/test/test_variable_serialization_specified.html117
-rw-r--r--layout/style/test/test_variables.html125
-rw-r--r--layout/style/test/test_video_object_fit.html53
-rw-r--r--layout/style/test/test_viewport_units.html66
-rw-r--r--layout/style/test/test_visited_image_loading.html67
-rw-r--r--layout/style/test/test_visited_image_loading_empty.html67
-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.html77
-rw-r--r--layout/style/test/test_webkit_flex_display.html48
-rw-r--r--layout/style/test/unprefixing_service_iframe.html394
-rw-r--r--layout/style/test/unprefixing_service_utils.js87
-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.sjs60
-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/test/xbl_bindings.xml9
-rw-r--r--layout/style/test/xpcshell.ini5
-rw-r--r--layout/style/xbl-marquee/jar.mn8
-rw-r--r--layout/style/xbl-marquee/moz.build7
-rw-r--r--layout/style/xbl-marquee/xbl-marquee.css12
-rw-r--r--layout/style/xbl-marquee/xbl-marquee.xml733
713 files changed, 189097 insertions, 0 deletions
diff --git a/layout/style/AnimationCollection.cpp b/layout/style/AnimationCollection.cpp
new file mode 100644
index 000000000..f7826f5ed
--- /dev/null
+++ b/layout/style/AnimationCollection.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/AnimationCollection.h"
+
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "nsAnimationManager.h" // For dom::CSSAnimation
+#include "nsPresContext.h"
+#include "nsTransitionManager.h" // For dom::CSSTransition
+
+namespace mozilla {
+
+template <class AnimationType>
+/* static */ void
+AnimationCollection<AnimationType>::PropertyDtor(void* aObject,
+ nsIAtom* aPropertyName,
+ void* aPropertyValue,
+ void* aData)
+{
+ AnimationCollection* collection =
+ static_cast<AnimationCollection*>(aPropertyValue);
+#ifdef DEBUG
+ MOZ_ASSERT(!collection->mCalledPropertyDtor, "can't call dtor twice");
+ collection->mCalledPropertyDtor = true;
+#endif
+ {
+ nsAutoAnimationMutationBatch mb(collection->mElement->OwnerDoc());
+
+ for (size_t animIdx = collection->mAnimations.Length(); animIdx-- != 0; ) {
+ collection->mAnimations[animIdx]->CancelFromStyle();
+ }
+ }
+ delete collection;
+}
+
+template <class AnimationType>
+/* static */ AnimationCollection<AnimationType>*
+AnimationCollection<AnimationType>::GetAnimationCollection(
+ dom::Element *aElement,
+ CSSPseudoElementType aPseudoType)
+{
+ if (!aElement->MayHaveAnimations()) {
+ // Early return for the most common case.
+ return nullptr;
+ }
+
+ nsIAtom* propName = GetPropertyAtomForPseudoType(aPseudoType);
+ if (!propName) {
+ return nullptr;
+ }
+
+ return
+ static_cast<AnimationCollection<AnimationType>*>(aElement->
+ GetProperty(propName));
+}
+
+template <class AnimationType>
+/* static */ AnimationCollection<AnimationType>*
+AnimationCollection<AnimationType>::GetAnimationCollection(
+ const nsIFrame* aFrame)
+{
+ Maybe<NonOwningAnimationTarget> pseudoElement =
+ EffectCompositor::GetAnimationElementAndPseudoForFrame(aFrame);
+ if (!pseudoElement) {
+ return nullptr;
+ }
+
+ if (!pseudoElement->mElement->MayHaveAnimations()) {
+ return nullptr;
+ }
+
+ return GetAnimationCollection(pseudoElement->mElement,
+ pseudoElement->mPseudoType);
+}
+
+template <class AnimationType>
+/* static */ AnimationCollection<AnimationType>*
+AnimationCollection<AnimationType>::GetOrCreateAnimationCollection(
+ dom::Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ bool* aCreatedCollection)
+{
+ MOZ_ASSERT(aCreatedCollection);
+ *aCreatedCollection = false;
+
+ nsIAtom* propName = GetPropertyAtomForPseudoType(aPseudoType);
+ MOZ_ASSERT(propName, "Should only try to create animations for one of the"
+ " recognized pseudo types");
+
+ auto collection = static_cast<AnimationCollection<AnimationType>*>(
+ aElement->GetProperty(propName));
+ if (!collection) {
+ // FIXME: Consider arena-allocating?
+ collection = new AnimationCollection<AnimationType>(aElement, propName);
+ nsresult rv =
+ aElement->SetProperty(propName, collection,
+ &AnimationCollection<AnimationType>::PropertyDtor,
+ false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("SetProperty failed");
+ // The collection must be destroyed via PropertyDtor, otherwise
+ // mCalledPropertyDtor assertion is triggered in destructor.
+ AnimationCollection<AnimationType>::PropertyDtor(aElement, propName,
+ collection, nullptr);
+ return nullptr;
+ }
+
+ *aCreatedCollection = true;
+ aElement->SetMayHaveAnimations();
+ }
+
+ return collection;
+}
+
+template <class AnimationType>
+/* static */ nsString
+AnimationCollection<AnimationType>::PseudoTypeAsString(
+ CSSPseudoElementType aPseudoType)
+{
+ switch (aPseudoType) {
+ case CSSPseudoElementType::before:
+ return NS_LITERAL_STRING("::before");
+ case CSSPseudoElementType::after:
+ return NS_LITERAL_STRING("::after");
+ default:
+ MOZ_ASSERT(aPseudoType == CSSPseudoElementType::NotPseudo,
+ "Unexpected pseudo type");
+ return EmptyString();
+ }
+}
+
+template <class AnimationType>
+void
+AnimationCollection<AnimationType>::UpdateCheckGeneration(
+ nsPresContext* aPresContext)
+{
+ if (aPresContext->RestyleManager()->IsServo()) {
+ // stylo: ServoRestyleManager does not support animations yet.
+ return;
+ }
+ mCheckGeneration =
+ aPresContext->RestyleManager()->AsGecko()->GetAnimationGeneration();
+}
+
+template<class AnimationType>
+/*static*/ nsIAtom*
+AnimationCollection<AnimationType>::GetPropertyAtomForPseudoType(
+ CSSPseudoElementType aPseudoType)
+{
+ nsIAtom* propName = nullptr;
+
+ if (aPseudoType == CSSPseudoElementType::NotPseudo) {
+ propName = TraitsType::ElementPropertyAtom();
+ } else if (aPseudoType == CSSPseudoElementType::before) {
+ propName = TraitsType::BeforePropertyAtom();
+ } else if (aPseudoType == CSSPseudoElementType::after) {
+ propName = TraitsType::AfterPropertyAtom();
+ }
+
+ return propName;
+}
+
+// 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 000000000..96163fcc8
--- /dev/null
+++ b/layout/style/AnimationCollection.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AnimationCollection_h
+#define mozilla_AnimationCollection_h
+
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/RefPtr.h"
+#include "nsCSSPseudoElements.h"
+#include "nsDOMMutationObserver.h"
+#include "nsTArray.h"
+
+class nsIAtom;
+class nsPresContext;
+
+namespace mozilla {
+
+// Traits class to define the specific atoms used when storing specializations
+// of AnimationCollection as a property on an Element (e.g. which atom
+// to use when storing an AnimationCollection<CSSAnimation> for a ::before
+// pseudo-element).
+template <class AnimationType>
+struct AnimationTypeTraits { };
+
+template <class AnimationType>
+class AnimationCollection
+ : public LinkedListElement<AnimationCollection<AnimationType>>
+{
+ typedef AnimationCollection<AnimationType> SelfType;
+ typedef AnimationTypeTraits<AnimationType> TraitsType;
+
+ AnimationCollection(dom::Element* aElement, nsIAtom* aElementProperty)
+ : mElement(aElement)
+ , mElementProperty(aElementProperty)
+ , mCheckGeneration(0)
+#ifdef DEBUG
+ , mCalledPropertyDtor(false)
+#endif
+ {
+ MOZ_COUNT_CTOR(AnimationCollection);
+ }
+
+public:
+ ~AnimationCollection()
+ {
+ MOZ_ASSERT(mCalledPropertyDtor,
+ "must call destructor through element property dtor");
+ MOZ_COUNT_DTOR(AnimationCollection);
+ LinkedListElement<SelfType>::remove();
+ }
+
+ void Destroy()
+ {
+ // This will call our destructor.
+ mElement->DeleteProperty(mElementProperty);
+ }
+
+ static void PropertyDtor(void *aObject, nsIAtom *aPropertyName,
+ void *aPropertyValue, void *aData);
+
+ // Get the collection of animations for the given |aElement| and
+ // |aPseudoType|.
+ static AnimationCollection<AnimationType>*
+ GetAnimationCollection(dom::Element* aElement,
+ CSSPseudoElementType aPseudoType);
+
+ // 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<AnimationType>* GetAnimationCollection(
+ const nsIFrame* aFrame);
+
+ // Get the collection of animations for the given |aElement| and
+ // |aPseudoType| or create it if it does not already exist.
+ //
+ // We'll set the outparam |aCreatedCollection| to true if we have
+ // to create the collection and we successfully do so. Otherwise,
+ // we'll set it to false.
+ static AnimationCollection<AnimationType>*
+ GetOrCreateAnimationCollection(dom::Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ bool* aCreatedCollection);
+
+ bool IsForElement() const { // rather than for a pseudo-element
+ return mElementProperty == TraitsType::ElementPropertyAtom();
+ }
+
+ bool IsForBeforePseudo() const {
+ return mElementProperty == TraitsType::BeforePropertyAtom();
+ }
+
+ bool IsForAfterPseudo() const {
+ return mElementProperty == TraitsType::AfterPropertyAtom();
+ }
+
+ CSSPseudoElementType PseudoElementType() const
+ {
+ if (IsForElement()) {
+ return CSSPseudoElementType::NotPseudo;
+ }
+ if (IsForBeforePseudo()) {
+ return CSSPseudoElementType::before;
+ }
+ MOZ_ASSERT(IsForAfterPseudo(),
+ "::before & ::after should be the only pseudo-elements here");
+ return CSSPseudoElementType::after;
+ }
+
+ static nsString PseudoTypeAsString(CSSPseudoElementType aPseudoType);
+
+ dom::Element *mElement;
+
+ // the atom we use in mElement's prop table (must be a static atom,
+ // i.e., in an atom list)
+ nsIAtom *mElementProperty;
+
+ InfallibleTArray<RefPtr<AnimationType>> mAnimations;
+
+ // For CSS transitions only, we record the most recent generation
+ // for which we've done the transition update, so that we avoid doing
+ // it more than once per style change.
+ // (Note that we also store an animation generation on each EffectSet in
+ // order to track when we need to update animations on layers.)
+ uint64_t mCheckGeneration;
+
+ // Update mCheckGeneration to RestyleManager's count
+ void UpdateCheckGeneration(nsPresContext* aPresContext);
+
+private:
+ static nsIAtom* GetPropertyAtomForPseudoType(
+ CSSPseudoElementType aPseudoType);
+
+#ifdef DEBUG
+ bool mCalledPropertyDtor;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AnimationCollection_h
diff --git a/layout/style/AnimationCommon.cpp b/layout/style/AnimationCommon.cpp
new file mode 100644
index 000000000..f4ebc2a93
--- /dev/null
+++ b/layout/style/AnimationCommon.cpp
@@ -0,0 +1,54 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AnimationCommon.h"
+#include "nsTransitionManager.h"
+#include "nsAnimationManager.h"
+
+#include "ActiveLayerTracker.h"
+#include "gfxPlatform.h"
+#include "nsCSSPropertyIDSet.h"
+#include "nsCSSValue.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsStyleContext.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "FrameLayerBuilder.h"
+#include "nsDisplayList.h"
+#include "mozilla/AnimationUtils.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+#include "nsRuleProcessorData.h"
+#include "nsStyleSet.h"
+#include "nsStyleChangeList.h"
+
+using mozilla::dom::Animation;
+using mozilla::dom::KeyframeEffectReadOnly;
+
+namespace mozilla {
+
+nsPresContext*
+OwningElementRef::GetRenderedPresContext() const
+{
+ if (!mElement) {
+ return nullptr;
+ }
+
+ nsIDocument* doc = mElement->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ return nullptr;
+ }
+
+ return shell->GetPresContext();
+}
+
+} // namespace mozilla
diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h
new file mode 100644
index 000000000..37030411c
--- /dev/null
+++ b/layout/style/AnimationCommon.h
@@ -0,0 +1,256 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_css_AnimationCommon_h
+#define mozilla_css_AnimationCommon_h
+
+#include <algorithm> // For <std::stable_sort>
+#include "mozilla/AnimationCollection.h"
+#include "mozilla/AnimationComparator.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/Attributes.h" // For MOZ_NON_OWNING_REF
+#include "mozilla/Assertions.h"
+#include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIFrame;
+class nsPresContext;
+
+namespace mozilla {
+
+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;
+ }
+
+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()
+ : mElement(nullptr)
+ , mPseudoType(CSSPseudoElementType::NotPseudo)
+ { }
+
+ OwningElementRef(dom::Element& aElement,
+ CSSPseudoElementType aPseudoType)
+ : mElement(&aElement)
+ , mPseudoType(aPseudoType)
+ { }
+
+ bool Equals(const OwningElementRef& aOther) const
+ {
+ return mElement == aOther.mElement &&
+ mPseudoType == aOther.mPseudoType;
+ }
+
+ bool LessThan(const OwningElementRef& aOther) const
+ {
+ MOZ_ASSERT(mElement && aOther.mElement,
+ "Elements to compare should not be null");
+
+ if (mElement != aOther.mElement) {
+ return nsContentUtils::PositionIsBefore(mElement, aOther.mElement);
+ }
+
+ return mPseudoType == CSSPseudoElementType::NotPseudo ||
+ (mPseudoType == CSSPseudoElementType::before &&
+ aOther.mPseudoType == CSSPseudoElementType::after);
+ }
+
+ bool IsSet() const { return !!mElement; }
+
+ void GetElement(dom::Element*& aElement,
+ CSSPseudoElementType& aPseudoType) const {
+ aElement = mElement;
+ aPseudoType = mPseudoType;
+ }
+
+ nsPresContext* GetRenderedPresContext() const;
+
+private:
+ dom::Element* MOZ_NON_OWNING_REF mElement;
+ CSSPseudoElementType mPseudoType;
+};
+
+template <class EventInfo>
+class DelayedEventDispatcher
+{
+public:
+ DelayedEventDispatcher() : mIsSorted(true) { }
+
+ void QueueEvent(EventInfo&& aEventInfo)
+ {
+ mPendingEvents.AppendElement(Forward<EventInfo>(aEventInfo));
+ mIsSorted = false;
+ }
+
+ // This is exposed as a separate method so that when we are dispatching
+ // *both* transition events and animation events we can sort both lists
+ // once using the current state of the document before beginning any
+ // dispatch.
+ void SortEvents()
+ {
+ if (mIsSorted) {
+ return;
+ }
+
+ // FIXME: Replace with mPendingEvents.StableSort when bug 1147091 is
+ // fixed.
+ std::stable_sort(mPendingEvents.begin(), mPendingEvents.end(),
+ EventInfoLessThan());
+ mIsSorted = true;
+ }
+
+ // Takes a reference to the owning manager's pres context so it can
+ // detect if the pres context is destroyed while dispatching one of
+ // the events.
+ //
+ // This will call SortEvents automatically if it has not already been
+ // called.
+ void DispatchEvents(nsPresContext* const & aPresContext)
+ {
+ if (!aPresContext || mPendingEvents.IsEmpty()) {
+ return;
+ }
+
+ SortEvents();
+
+ EventArray events;
+ mPendingEvents.SwapElements(events);
+ // mIsSorted will be set to true by SortEvents above, and we leave it
+ // that way since mPendingEvents is now empty
+ for (EventInfo& info : events) {
+ EventDispatcher::Dispatch(info.mElement, aPresContext, &info.mEvent);
+
+ if (!aPresContext) {
+ break;
+ }
+ }
+ }
+
+ void ClearEventQueue()
+ {
+ mPendingEvents.Clear();
+ mIsSorted = true;
+ }
+ bool HasQueuedEvents() const { return !mPendingEvents.IsEmpty(); }
+
+ // Methods for supporting cycle-collection
+ void Traverse(nsCycleCollectionTraversalCallback* aCallback,
+ const char* aName)
+ {
+ for (EventInfo& info : mPendingEvents) {
+ ImplCycleCollectionTraverse(*aCallback, info.mElement, aName);
+ ImplCycleCollectionTraverse(*aCallback, info.mAnimation, aName);
+ }
+ }
+ void Unlink() { ClearEventQueue(); }
+
+protected:
+ class EventInfoLessThan
+ {
+ public:
+ bool operator()(const EventInfo& a, const EventInfo& b) const
+ {
+ if (a.mTimeStamp != b.mTimeStamp) {
+ // Null timestamps sort first
+ if (a.mTimeStamp.IsNull() || b.mTimeStamp.IsNull()) {
+ return a.mTimeStamp.IsNull();
+ } else {
+ return a.mTimeStamp < b.mTimeStamp;
+ }
+ }
+
+ AnimationPtrComparator<RefPtr<dom::Animation>> comparator;
+ return comparator.LessThan(a.mAnimation, b.mAnimation);
+ }
+ };
+
+ typedef nsTArray<EventInfo> EventArray;
+ EventArray mPendingEvents;
+ bool mIsSorted;
+};
+
+template <class EventInfo>
+inline void
+ImplCycleCollectionUnlink(DelayedEventDispatcher<EventInfo>& aField)
+{
+ aField.Unlink();
+}
+
+template <class EventInfo>
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ DelayedEventDispatcher<EventInfo>& aField,
+ const char* aName,
+ uint32_t aFlags = 0)
+{
+ aField.Traverse(&aCallback, aName);
+}
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_css_AnimationCommon_h) */
diff --git a/layout/style/CSS.cpp b/layout/style/CSS.cpp
new file mode 100644
index 000000000..b70f67a50
--- /dev/null
+++ b/layout/style/CSS.cpp
@@ -0,0 +1,107 @@
+/* -*- 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/. */
+
+/* DOM object holding utility CSS functions */
+
+#include "CSS.h"
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ServoBindings.h"
+#include "nsCSSParser.h"
+#include "nsGlobalWindow.h"
+#include "nsIDocument.h"
+#include "nsIURI.h"
+#include "nsStyleUtil.h"
+#include "xpcpublic.h"
+
+namespace mozilla {
+namespace dom {
+
+struct SupportsParsingInfo
+{
+ nsIURI* mDocURI;
+ nsIURI* mBaseURI;
+ nsIPrincipal* mPrincipal;
+ StyleBackendType mStyleBackendType;
+};
+
+static nsresult
+GetParsingInfo(const GlobalObject& aGlobal,
+ SupportsParsingInfo& aInfo)
+{
+ nsGlobalWindow* win = xpc::WindowOrNull(aGlobal.Get());
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc = win->GetDoc();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aInfo.mDocURI = nsCOMPtr<nsIURI>(doc->GetDocumentURI()).get();
+ aInfo.mBaseURI = nsCOMPtr<nsIURI>(doc->GetBaseURI()).get();
+ aInfo.mPrincipal = win->GetPrincipal();
+ aInfo.mStyleBackendType = doc->GetStyleBackendType();
+ return NS_OK;
+}
+
+/* static */ bool
+CSS::Supports(const GlobalObject& aGlobal,
+ const nsAString& aProperty,
+ const nsAString& aValue,
+ ErrorResult& aRv)
+{
+ SupportsParsingInfo info;
+
+ nsresult rv = GetParsingInfo(aGlobal, info);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return false;
+ }
+
+ if (info.mStyleBackendType == StyleBackendType::Servo) {
+ NS_ConvertUTF16toUTF8 property(aProperty);
+ NS_ConvertUTF16toUTF8 value(aValue);
+ return Servo_CSSSupports(&property, &value);
+ }
+
+ nsCSSParser parser;
+ return parser.EvaluateSupportsDeclaration(aProperty, aValue, info.mDocURI,
+ info.mBaseURI, info.mPrincipal);
+}
+
+/* static */ bool
+CSS::Supports(const GlobalObject& aGlobal,
+ const nsAString& aCondition,
+ ErrorResult& aRv)
+{
+ SupportsParsingInfo info;
+
+ nsresult rv = GetParsingInfo(aGlobal, info);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return false;
+ }
+
+ if (info.mStyleBackendType == StyleBackendType::Servo) {
+ MOZ_CRASH("stylo: CSS.supports() with arguments is not yet implemented");
+ }
+
+ nsCSSParser parser;
+ return parser.EvaluateSupportsCondition(aCondition, info.mDocURI,
+ info.mBaseURI, info.mPrincipal);
+}
+
+/* static */ void
+CSS::Escape(const GlobalObject& aGlobal,
+ const nsAString& aIdent,
+ nsAString& aReturn)
+{
+ nsStyleUtil::AppendEscapedCSSIdent(aIdent, aReturn);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/CSS.h b/layout/style/CSS.h
new file mode 100644
index 000000000..ce3e38da4
--- /dev/null
+++ b/layout/style/CSS.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object holding utility CSS functions */
+
+#ifndef mozilla_dom_CSS_h_
+#define mozilla_dom_CSS_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+class CSS {
+private:
+ CSS() = delete;
+
+public:
+ static bool Supports(const GlobalObject& aGlobal,
+ const nsAString& aProperty,
+ const nsAString& aValue,
+ ErrorResult& aRv);
+
+ static bool Supports(const GlobalObject& aGlobal,
+ const nsAString& aDeclaration,
+ ErrorResult& aRv);
+
+ static void Escape(const GlobalObject& aGlobal,
+ const nsAString& aIdent,
+ nsAString& aReturn);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CSS_h_
diff --git a/layout/style/CSSCalc.h b/layout/style/CSSCalc.h
new file mode 100644
index 000000000..141ca9c0a
--- /dev/null
+++ b/layout/style/CSSCalc.h
@@ -0,0 +1,361 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef CSSCalc_h_
+#define CSSCalc_h_
+
+#include "nsCSSValue.h"
+#include "nsStyleCoord.h"
+#include <math.h>
+
+namespace mozilla {
+
+namespace css {
+
+/**
+ * ComputeCalc computes the result of a calc() expression tree.
+ *
+ * It is templatized over a CalcOps class that is expected to provide:
+ *
+ * // input_type and input_array_type have a bunch of very specific
+ * // expectations (which happen to be met by two classes (nsCSSValue
+ * // and nsStyleCoord). There must be methods (roughly):
+ * // input_array_type* input_type::GetArrayValue();
+ * // uint32_t input_array_type::Count() const;
+ * // input_type& input_array_type::Item(uint32_t);
+ * typedef ... input_type;
+ * typedef ... input_array_type;
+ *
+ * typedef ... result_type;
+ *
+ * // GetUnit(avalue) must return the correct nsCSSUnit for any
+ * // value that represents a calc tree node (eCSSUnit_Calc*). For
+ * // other nodes, it may return any non eCSSUnit_Calc* unit.
+ * static nsCSSUnit GetUnit(const input_type& aValue);
+ *
+ * result_type
+ * MergeAdditive(nsCSSUnit aCalcFunction,
+ * result_type aValue1, result_type aValue2);
+ *
+ * result_type
+ * MergeMultiplicativeL(nsCSSUnit aCalcFunction,
+ * float aValue1, result_type aValue2);
+ *
+ * result_type
+ * MergeMultiplicativeR(nsCSSUnit aCalcFunction,
+ * result_type aValue1, float aValue2);
+ *
+ * result_type
+ * ComputeLeafValue(const input_type& aValue);
+ *
+ * float
+ * ComputeNumber(const input_type& aValue);
+ *
+ * The CalcOps methods might compute the calc() expression down to a
+ * number, reduce some parts of it to a number but replicate other
+ * parts, or produce a tree with a different data structure (for
+ * example, nsCSS* for specified values vs nsStyle* for computed
+ * values).
+ *
+ * For each leaf in the calc() expression, ComputeCalc will call either
+ * ComputeNumber (when the leaf is the left side of a Times_L or the
+ * right side of a Times_R or Divided) or ComputeLeafValue (otherwise).
+ * (The CalcOps in the CSS parser that reduces purely numeric
+ * expressions in turn calls ComputeCalc on numbers; other ops can
+ * presume that expressions in the number positions have already been
+ * normalized to a single numeric value and derive from
+ * NumbersAlreadyNormalizedCalcOps.)
+ *
+ * For non-leaves, one of the Merge functions will be called:
+ * MergeAdditive for Plus and Minus
+ * MergeMultiplicativeL for Times_L (number * value)
+ * MergeMultiplicativeR for Times_R (value * number) and Divided
+ */
+template <class CalcOps>
+static typename CalcOps::result_type
+ComputeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
+{
+ switch (CalcOps::GetUnit(aValue)) {
+ case eCSSUnit_Calc: {
+ typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
+ MOZ_ASSERT(arr->Count() == 1, "unexpected length");
+ return ComputeCalc(arr->Item(0), aOps);
+ }
+ case eCSSUnit_Calc_Plus:
+ case eCSSUnit_Calc_Minus: {
+ typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
+ MOZ_ASSERT(arr->Count() == 2, "unexpected length");
+ typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps),
+ rhs = ComputeCalc(arr->Item(1), aOps);
+ return aOps.MergeAdditive(CalcOps::GetUnit(aValue), lhs, rhs);
+ }
+ case eCSSUnit_Calc_Times_L: {
+ typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
+ MOZ_ASSERT(arr->Count() == 2, "unexpected length");
+ float lhs = aOps.ComputeNumber(arr->Item(0));
+ typename CalcOps::result_type rhs = ComputeCalc(arr->Item(1), aOps);
+ return aOps.MergeMultiplicativeL(CalcOps::GetUnit(aValue), lhs, rhs);
+ }
+ case eCSSUnit_Calc_Times_R:
+ case eCSSUnit_Calc_Divided: {
+ typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
+ MOZ_ASSERT(arr->Count() == 2, "unexpected length");
+ typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps);
+ float rhs = aOps.ComputeNumber(arr->Item(1));
+ return aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs);
+ }
+ default: {
+ return aOps.ComputeLeafValue(aValue);
+ }
+ }
+}
+
+/**
+ * The input unit operation for input_type being nsCSSValue.
+ */
+struct CSSValueInputCalcOps
+{
+ typedef nsCSSValue input_type;
+ typedef nsCSSValue::Array input_array_type;
+
+ static nsCSSUnit GetUnit(const nsCSSValue& aValue)
+ {
+ return aValue.GetUnit();
+ }
+
+};
+
+/**
+ * Basic*CalcOps provide a partial implementation of the CalcOps
+ * template parameter to ComputeCalc, for those callers whose merging
+ * just consists of mathematics (rather than tree construction).
+ */
+
+struct BasicCoordCalcOps
+{
+ typedef nscoord result_type;
+
+ result_type
+ MergeAdditive(nsCSSUnit aCalcFunction,
+ result_type aValue1, result_type aValue2)
+ {
+ if (aCalcFunction == eCSSUnit_Calc_Plus) {
+ return NSCoordSaturatingAdd(aValue1, aValue2);
+ }
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
+ "unexpected unit");
+ return NSCoordSaturatingSubtract(aValue1, aValue2, 0);
+ }
+
+ result_type
+ MergeMultiplicativeL(nsCSSUnit aCalcFunction,
+ float aValue1, result_type aValue2)
+ {
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
+ "unexpected unit");
+ return NSCoordSaturatingMultiply(aValue2, aValue1);
+ }
+
+ result_type
+ MergeMultiplicativeR(nsCSSUnit aCalcFunction,
+ result_type aValue1, float aValue2)
+ {
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_R ||
+ aCalcFunction == eCSSUnit_Calc_Divided,
+ "unexpected unit");
+ if (aCalcFunction == eCSSUnit_Calc_Divided) {
+ aValue2 = 1.0f / aValue2;
+ }
+ return NSCoordSaturatingMultiply(aValue1, aValue2);
+ }
+};
+
+struct BasicFloatCalcOps
+{
+ typedef float result_type;
+
+ result_type
+ MergeAdditive(nsCSSUnit aCalcFunction,
+ result_type aValue1, result_type aValue2)
+ {
+ if (aCalcFunction == eCSSUnit_Calc_Plus) {
+ return aValue1 + aValue2;
+ }
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
+ "unexpected unit");
+ return aValue1 - aValue2;
+ }
+
+ result_type
+ MergeMultiplicativeL(nsCSSUnit aCalcFunction,
+ float aValue1, result_type aValue2)
+ {
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
+ "unexpected unit");
+ return aValue1 * aValue2;
+ }
+
+ result_type
+ MergeMultiplicativeR(nsCSSUnit aCalcFunction,
+ result_type aValue1, float aValue2)
+ {
+ if (aCalcFunction == eCSSUnit_Calc_Times_R) {
+ return aValue1 * aValue2;
+ }
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided,
+ "unexpected unit");
+ return aValue1 / aValue2;
+ }
+};
+
+/**
+ * A ComputeNumber implementation for callers that can assume numbers
+ * are already normalized (i.e., anything past the parser).
+ */
+struct NumbersAlreadyNormalizedOps : public CSSValueInputCalcOps
+{
+ float ComputeNumber(const nsCSSValue& aValue)
+ {
+ MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
+ return aValue.GetFloatValue();
+ }
+};
+
+/**
+ * SerializeCalc appends the serialization of aValue to a string.
+ *
+ * It is templatized over a CalcOps class that is expected to provide:
+ *
+ * // input_type and input_array_type have a bunch of very specific
+ * // expectations (which happen to be met by two classes (nsCSSValue
+ * // and nsStyleCoord). There must be methods (roughly):
+ * // input_array_type* input_type::GetArrayValue();
+ * // uint32_t input_array_type::Count() const;
+ * // input_type& input_array_type::Item(uint32_t);
+ * typedef ... input_type;
+ * typedef ... input_array_type;
+ *
+ * static nsCSSUnit GetUnit(const input_type& aValue);
+ *
+ * void Append(const char* aString);
+ * void AppendLeafValue(const input_type& aValue);
+ * void AppendNumber(const input_type& aValue);
+ *
+ * Data structures given may or may not have a toplevel eCSSUnit_Calc
+ * node representing a calc whose toplevel is not min() or max().
+ */
+
+template <class CalcOps>
+static void
+SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps);
+
+// Serialize the toplevel value in a calc() tree. See big comment
+// above.
+template <class CalcOps>
+static void
+SerializeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
+{
+ aOps.Append("calc(");
+ nsCSSUnit unit = CalcOps::GetUnit(aValue);
+ if (unit == eCSSUnit_Calc) {
+ const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
+ MOZ_ASSERT(array->Count() == 1, "unexpected length");
+ SerializeCalcInternal(array->Item(0), aOps);
+ } else {
+ SerializeCalcInternal(aValue, aOps);
+ }
+ aOps.Append(")");
+}
+
+static inline bool
+IsCalcAdditiveUnit(nsCSSUnit aUnit)
+{
+ return aUnit == eCSSUnit_Calc_Plus ||
+ aUnit == eCSSUnit_Calc_Minus;
+}
+
+static inline bool
+IsCalcMultiplicativeUnit(nsCSSUnit aUnit)
+{
+ return aUnit == eCSSUnit_Calc_Times_L ||
+ aUnit == eCSSUnit_Calc_Times_R ||
+ aUnit == eCSSUnit_Calc_Divided;
+}
+
+// Serialize a non-toplevel value in a calc() tree. See big comment
+// above.
+template <class CalcOps>
+/* static */ void
+SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps)
+{
+ nsCSSUnit unit = CalcOps::GetUnit(aValue);
+ if (IsCalcAdditiveUnit(unit)) {
+ const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
+ MOZ_ASSERT(array->Count() == 2, "unexpected length");
+
+ SerializeCalcInternal(array->Item(0), aOps);
+
+ if (eCSSUnit_Calc_Plus == unit) {
+ aOps.Append(" + ");
+ } else {
+ MOZ_ASSERT(eCSSUnit_Calc_Minus == unit, "unexpected unit");
+ aOps.Append(" - ");
+ }
+
+ bool needParens = IsCalcAdditiveUnit(CalcOps::GetUnit(array->Item(1)));
+ if (needParens) {
+ aOps.Append("(");
+ }
+ SerializeCalcInternal(array->Item(1), aOps);
+ if (needParens) {
+ aOps.Append(")");
+ }
+ } else if (IsCalcMultiplicativeUnit(unit)) {
+ const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
+ MOZ_ASSERT(array->Count() == 2, "unexpected length");
+
+ bool needParens = IsCalcAdditiveUnit(CalcOps::GetUnit(array->Item(0)));
+ if (needParens) {
+ aOps.Append("(");
+ }
+ if (unit == eCSSUnit_Calc_Times_L) {
+ aOps.AppendNumber(array->Item(0));
+ } else {
+ SerializeCalcInternal(array->Item(0), aOps);
+ }
+ if (needParens) {
+ aOps.Append(")");
+ }
+
+ if (eCSSUnit_Calc_Times_L == unit || eCSSUnit_Calc_Times_R == unit) {
+ aOps.Append(" * ");
+ } else {
+ MOZ_ASSERT(eCSSUnit_Calc_Divided == unit, "unexpected unit");
+ aOps.Append(" / ");
+ }
+
+ nsCSSUnit subUnit = CalcOps::GetUnit(array->Item(1));
+ needParens = IsCalcAdditiveUnit(subUnit) ||
+ IsCalcMultiplicativeUnit(subUnit);
+ if (needParens) {
+ aOps.Append("(");
+ }
+ if (unit == eCSSUnit_Calc_Times_L) {
+ SerializeCalcInternal(array->Item(1), aOps);
+ } else {
+ aOps.AppendNumber(array->Item(1));
+ }
+ if (needParens) {
+ aOps.Append(")");
+ }
+ } else {
+ aOps.AppendLeafValue(aValue);
+ }
+}
+
+} // namespace css
+
+} // namespace mozilla
+
+#endif /* !defined(CSSCalc_h_) */
diff --git a/layout/style/CSSEnabledState.h b/layout/style/CSSEnabledState.h
new file mode 100644
index 000000000..650397072
--- /dev/null
+++ b/layout/style/CSSEnabledState.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/. */
+
+/*
+ * 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.
+ eForAllContent = 0,
+ // Enable features available in UA sheets.
+ eInUASheets = 0x01,
+ // Enable features available in chrome code.
+ eInChrome = 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.
+ eIgnoreEnabledState = 0xff
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CSSEnabledState)
+
+} // namespace mozilla
+
+#endif // mozilla_CSSEnabledState_h
diff --git a/layout/style/CSSLexer.cpp b/layout/style/CSSLexer.cpp
new file mode 100644
index 000000000..f465981e7
--- /dev/null
+++ b/layout/style/CSSLexer.cpp
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSLexer.h"
+#include "js/Value.h"
+#include "mozilla/dom/CSSLexerBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+
+namespace mozilla {
+namespace dom {
+
+// Ensure that constants are consistent.
+
+#define CHECK(X, Y) \
+ static_assert(static_cast<int>(X) == static_cast<int>(Y), \
+ "nsCSSToken and CSSTokenType should have identical values")
+
+CHECK(eCSSToken_Whitespace, CSSTokenType::Whitespace);
+CHECK(eCSSToken_Comment, CSSTokenType::Comment);
+CHECK(eCSSToken_Ident, CSSTokenType::Ident);
+CHECK(eCSSToken_Function, CSSTokenType::Function);
+CHECK(eCSSToken_AtKeyword, CSSTokenType::At);
+CHECK(eCSSToken_ID, CSSTokenType::Id);
+CHECK(eCSSToken_Hash, CSSTokenType::Hash);
+CHECK(eCSSToken_Number, CSSTokenType::Number);
+CHECK(eCSSToken_Dimension, CSSTokenType::Dimension);
+CHECK(eCSSToken_Percentage, CSSTokenType::Percentage);
+CHECK(eCSSToken_String, CSSTokenType::String);
+CHECK(eCSSToken_Bad_String, CSSTokenType::Bad_string);
+CHECK(eCSSToken_URL, CSSTokenType::Url);
+CHECK(eCSSToken_Bad_URL, CSSTokenType::Bad_url);
+CHECK(eCSSToken_Symbol, CSSTokenType::Symbol);
+CHECK(eCSSToken_Includes, CSSTokenType::Includes);
+CHECK(eCSSToken_Dashmatch, CSSTokenType::Dashmatch);
+CHECK(eCSSToken_Beginsmatch, CSSTokenType::Beginsmatch);
+CHECK(eCSSToken_Endsmatch, CSSTokenType::Endsmatch);
+CHECK(eCSSToken_Containsmatch, CSSTokenType::Containsmatch);
+CHECK(eCSSToken_URange, CSSTokenType::Urange);
+CHECK(eCSSToken_HTMLComment, CSSTokenType::Htmlcomment);
+
+#undef CHECK
+
+CSSLexer::CSSLexer(const nsAString& aText)
+ : mInput(aText)
+ , mScanner(mInput, 1)
+{
+}
+
+CSSLexer::~CSSLexer()
+{
+}
+
+bool
+CSSLexer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector)
+{
+ return CSSLexerBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+uint32_t
+CSSLexer::LineNumber()
+{
+ // The scanner uses 1-based line numbers, but our callers expect
+ // 0-based.
+ return mScanner.GetLineNumber() - 1;
+}
+
+uint32_t
+CSSLexer::ColumnNumber()
+{
+ return mScanner.GetColumnNumber();
+}
+
+void
+CSSLexer::PerformEOFFixup(const nsAString& aInputString, bool aPreserveBackslash,
+ nsAString& aResult)
+{
+ aResult.Append(aInputString);
+ uint32_t eofChars = mScanner.GetEOFCharacters();
+
+ if (aPreserveBackslash &&
+ (eofChars & (nsCSSScanner::eEOFCharacters_DropBackslash |
+ nsCSSScanner::eEOFCharacters_ReplacementChar)) != 0) {
+ eofChars &= ~(nsCSSScanner::eEOFCharacters_DropBackslash |
+ nsCSSScanner::eEOFCharacters_ReplacementChar);
+ aResult.Append('\\');
+ }
+
+ if ((eofChars & nsCSSScanner::eEOFCharacters_DropBackslash) != 0 &&
+ aResult.Length() > 0 && aResult.Last() == '\\') {
+ aResult.Truncate(aResult.Length() - 1);
+ }
+
+ nsCSSScanner::AppendImpliedEOFCharacters(nsCSSScanner::EOFCharacters(eofChars),
+ aResult);
+}
+
+void
+CSSLexer::NextToken(Nullable<CSSToken>& aResult)
+{
+ nsCSSToken token;
+ if (!mScanner.Next(token, eCSSScannerExclude_None)) {
+ return;
+ }
+
+ CSSToken& resultToken(aResult.SetValue());
+
+ resultToken.mTokenType = static_cast<CSSTokenType>(token.mType);
+ resultToken.mStartOffset = mScanner.GetTokenOffset();
+ resultToken.mEndOffset = mScanner.GetTokenEndOffset();
+
+ switch (token.mType) {
+ case eCSSToken_Whitespace:
+ break;
+
+ case eCSSToken_Ident:
+ case eCSSToken_Function:
+ case eCSSToken_AtKeyword:
+ case eCSSToken_ID:
+ case eCSSToken_Hash:
+ resultToken.mText.Construct(token.mIdent);
+ break;
+
+ case eCSSToken_Dimension:
+ resultToken.mText.Construct(token.mIdent);
+ MOZ_FALLTHROUGH;
+ case eCSSToken_Number:
+ case eCSSToken_Percentage:
+ resultToken.mNumber.Construct(token.mNumber);
+ resultToken.mHasSign.Construct(token.mHasSign);
+ resultToken.mIsInteger.Construct(token.mIntegerValid);
+ break;
+
+ case eCSSToken_String:
+ case eCSSToken_Bad_String:
+ case eCSSToken_URL:
+ case eCSSToken_Bad_URL:
+ resultToken.mText.Construct(token.mIdent);
+ /* Don't bother emitting the delimiter, as it is readily extracted
+ from the source string when needed. */
+ break;
+
+ case eCSSToken_Symbol:
+ resultToken.mText.Construct(nsString(&token.mSymbol, 1));
+ break;
+
+ case eCSSToken_Includes:
+ case eCSSToken_Dashmatch:
+ case eCSSToken_Beginsmatch:
+ case eCSSToken_Endsmatch:
+ case eCSSToken_Containsmatch:
+ case eCSSToken_URange:
+ break;
+
+ case eCSSToken_Comment:
+ case eCSSToken_HTMLComment:
+ /* The comment text is easily extracted from the source string,
+ and is rarely useful. */
+ break;
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/CSSLexer.h b/layout/style/CSSLexer.h
new file mode 100644
index 000000000..8b41d2778
--- /dev/null
+++ b/layout/style/CSSLexer.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CSSLexer_h___
+#define CSSLexer_h___
+
+#include "mozilla/UniquePtr.h"
+#include "nsCSSScanner.h"
+#include "mozilla/dom/CSSLexerBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class CSSLexer : public NonRefcountedDOMObject
+{
+public:
+ explicit CSSLexer(const nsAString&);
+ ~CSSLexer();
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ uint32_t LineNumber();
+ uint32_t ColumnNumber();
+ void PerformEOFFixup(const nsAString& aInputString, bool aPreserveBackslash,
+ nsAString& aResult);
+ void NextToken(Nullable<CSSToken>& aResult);
+
+private:
+ nsString mInput;
+ nsCSSScanner mScanner;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* CSSLexer_h___ */
diff --git a/layout/style/CSSRuleList.cpp b/layout/style/CSSRuleList.cpp
new file mode 100644
index 000000000..8493e8a37
--- /dev/null
+++ b/layout/style/CSSRuleList.cpp
@@ -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/. */
+
+#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(CSSRuleList)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRuleList)
+ 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 CSSRuleListBinding::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 000000000..786cc1b88
--- /dev/null
+++ b/layout/style/CSSRuleList.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CSSRuleList_h
+#define mozilla_dom_CSSRuleList_h
+
+#include "mozilla/StyleSheetInlines.h"
+#include "nsIDOMCSSRule.h"
+#include "nsIDOMCSSRuleList.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+// IID for the CSSRuleList interface
+#define NS_ICSSRULELIST_IID \
+{ 0x56ac8d1c, 0xc1ed, 0x45fe, \
+ { 0x9a, 0x4d, 0x3a, 0xdc, 0xf9, 0xd1, 0xb9, 0x3f } }
+
+class CSSRuleList : public nsIDOMCSSRuleList
+ , public nsWrapperCache
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICSSRULELIST_IID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CSSRuleList)
+
+ virtual CSSStyleSheet* GetParentObject() = 0;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ NS_IMETHOD
+ GetLength(uint32_t* aLength) override final
+ {
+ *aLength = Length();
+ return NS_OK;
+ }
+ NS_IMETHOD
+ Item(uint32_t aIndex, nsIDOMCSSRule** aReturn) override final
+ {
+ NS_IF_ADDREF(*aReturn = Item(aIndex));
+ return NS_OK;
+ }
+
+ // WebIDL API
+ nsIDOMCSSRule* Item(uint32_t aIndex)
+ {
+ bool unused;
+ return IndexedGetter(aIndex, unused);
+ }
+
+ virtual nsIDOMCSSRule* IndexedGetter(uint32_t aIndex, bool& aFound) = 0;
+ virtual uint32_t Length() = 0;
+
+protected:
+ virtual ~CSSRuleList() {}
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CSSRuleList, NS_ICSSRULELIST_IID)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_CSSRuleList_h */
diff --git a/layout/style/CSSStyleSheet.cpp b/layout/style/CSSStyleSheet.cpp
new file mode 100644
index 000000000..71ca6e3f2
--- /dev/null
+++ b/layout/style/CSSStyleSheet.cpp
@@ -0,0 +1,2026 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:tabstop=2:expandtab:shiftwidth=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/. */
+
+/* representation of a CSS style sheet */
+
+#include "mozilla/CSSStyleSheet.h"
+
+#include "nsIAtom.h"
+#include "nsCSSRuleProcessor.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/MediaListBinding.h"
+#include "mozilla/css/NameSpaceRule.h"
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/css/ImportRule.h"
+#include "nsCSSRules.h"
+#include "nsIMediaList.h"
+#include "nsIDocument.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsQueryObject.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIDOMCSSStyleSheet.h"
+#include "mozilla/dom/CSSRuleList.h"
+#include "nsIDOMMediaList.h"
+#include "nsIDOMNode.h"
+#include "nsError.h"
+#include "nsCSSParser.h"
+#include "mozilla/css/Loader.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsNameSpaceManager.h"
+#include "nsXMLNameSpaceMap.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "mozAutoDocUpdate.h"
+#include "nsRuleNode.h"
+#include "nsMediaFeatures.h"
+#include "nsDOMClassInfoID.h"
+#include "mozilla/Likely.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNullPrincipal.h"
+#include "mozilla/RuleProcessorCache.h"
+#include "nsIStyleSheetLinkingElement.h"
+#include "nsDOMWindowUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// -------------------------------
+// Style Rule List for the DOM
+//
+class CSSRuleListImpl final : public CSSRuleList
+{
+public:
+ explicit CSSRuleListImpl(CSSStyleSheet *aStyleSheet);
+
+ virtual CSSStyleSheet* GetParentObject() override;
+
+ virtual nsIDOMCSSRule*
+ IndexedGetter(uint32_t aIndex, bool& aFound) override;
+ virtual uint32_t
+ Length() override;
+
+ void DropReference() { mStyleSheet = nullptr; }
+
+protected:
+ virtual ~CSSRuleListImpl();
+
+ CSSStyleSheet* mStyleSheet;
+};
+
+CSSRuleListImpl::CSSRuleListImpl(CSSStyleSheet *aStyleSheet)
+{
+ // Not reference counted to avoid circular references.
+ // The style sheet will tell us when its going away.
+ mStyleSheet = aStyleSheet;
+}
+
+CSSRuleListImpl::~CSSRuleListImpl()
+{
+}
+
+CSSStyleSheet*
+CSSRuleListImpl::GetParentObject()
+{
+ return mStyleSheet;
+}
+
+uint32_t
+CSSRuleListImpl::Length()
+{
+ if (!mStyleSheet) {
+ return 0;
+ }
+
+ return AssertedCast<uint32_t>(mStyleSheet->StyleRuleCount());
+}
+
+nsIDOMCSSRule*
+CSSRuleListImpl::IndexedGetter(uint32_t aIndex, bool& aFound)
+{
+ aFound = false;
+
+ if (mStyleSheet) {
+ // ensure rules have correct parent
+ mStyleSheet->EnsureUniqueInner();
+ css::Rule* rule = mStyleSheet->GetStyleRuleAt(aIndex);
+ if (rule) {
+ aFound = true;
+ return rule->GetDOMRule();
+ }
+ }
+
+ // Per spec: "Return Value ... null if ... not a valid index."
+ return nullptr;
+}
+
+template <class Numeric>
+int32_t DoCompare(Numeric a, Numeric b)
+{
+ if (a == b)
+ return 0;
+ if (a < b)
+ return -1;
+ return 1;
+}
+
+bool
+nsMediaExpression::Matches(nsPresContext *aPresContext,
+ const nsCSSValue& aActualValue) const
+{
+ const nsCSSValue& actual = aActualValue;
+ const nsCSSValue& required = mValue;
+
+ // If we don't have the feature, the match fails.
+ if (actual.GetUnit() == eCSSUnit_Null) {
+ return false;
+ }
+
+ // If the expression had no value to match, the match succeeds,
+ // unless the value is an integer 0 or a zero length.
+ if (required.GetUnit() == eCSSUnit_Null) {
+ if (actual.GetUnit() == eCSSUnit_Integer)
+ return actual.GetIntValue() != 0;
+ if (actual.IsLengthUnit())
+ return actual.GetFloatValue() != 0;
+ return true;
+ }
+
+ NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxAllowed ||
+ mRange == nsMediaExpression::eEqual, "yikes");
+ int32_t cmp; // -1 (actual < required)
+ // 0 (actual == required)
+ // 1 (actual > required)
+ switch (mFeature->mValueType) {
+ case nsMediaFeature::eLength:
+ {
+ NS_ASSERTION(actual.IsLengthUnit(), "bad actual value");
+ NS_ASSERTION(required.IsLengthUnit(), "bad required value");
+ nscoord actualCoord = nsRuleNode::CalcLengthWithInitialFont(
+ aPresContext, actual);
+ nscoord requiredCoord = nsRuleNode::CalcLengthWithInitialFont(
+ aPresContext, required);
+ cmp = DoCompare(actualCoord, requiredCoord);
+ }
+ break;
+ case nsMediaFeature::eInteger:
+ case nsMediaFeature::eBoolInteger:
+ {
+ NS_ASSERTION(actual.GetUnit() == eCSSUnit_Integer,
+ "bad actual value");
+ NS_ASSERTION(required.GetUnit() == eCSSUnit_Integer,
+ "bad required value");
+ NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger ||
+ actual.GetIntValue() == 0 || actual.GetIntValue() == 1,
+ "bad actual bool integer value");
+ NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger ||
+ required.GetIntValue() == 0 || required.GetIntValue() == 1,
+ "bad required bool integer value");
+ cmp = DoCompare(actual.GetIntValue(), required.GetIntValue());
+ }
+ break;
+ case nsMediaFeature::eFloat:
+ {
+ NS_ASSERTION(actual.GetUnit() == eCSSUnit_Number,
+ "bad actual value");
+ NS_ASSERTION(required.GetUnit() == eCSSUnit_Number,
+ "bad required value");
+ cmp = DoCompare(actual.GetFloatValue(), required.GetFloatValue());
+ }
+ break;
+ case nsMediaFeature::eIntRatio:
+ {
+ NS_ASSERTION(actual.GetUnit() == eCSSUnit_Array &&
+ actual.GetArrayValue()->Count() == 2 &&
+ actual.GetArrayValue()->Item(0).GetUnit() ==
+ eCSSUnit_Integer &&
+ actual.GetArrayValue()->Item(1).GetUnit() ==
+ eCSSUnit_Integer,
+ "bad actual value");
+ NS_ASSERTION(required.GetUnit() == eCSSUnit_Array &&
+ required.GetArrayValue()->Count() == 2 &&
+ required.GetArrayValue()->Item(0).GetUnit() ==
+ eCSSUnit_Integer &&
+ required.GetArrayValue()->Item(1).GetUnit() ==
+ eCSSUnit_Integer,
+ "bad required value");
+ // Convert to int64_t so we can multiply without worry. Note
+ // that while the spec requires that both halves of |required|
+ // be positive, the numerator or denominator of |actual| might
+ // be zero (e.g., when testing 'aspect-ratio' on a 0-width or
+ // 0-height iframe).
+ int64_t actualNum = actual.GetArrayValue()->Item(0).GetIntValue(),
+ actualDen = actual.GetArrayValue()->Item(1).GetIntValue(),
+ requiredNum = required.GetArrayValue()->Item(0).GetIntValue(),
+ requiredDen = required.GetArrayValue()->Item(1).GetIntValue();
+ cmp = DoCompare(actualNum * requiredDen, requiredNum * actualDen);
+ }
+ break;
+ case nsMediaFeature::eResolution:
+ {
+ NS_ASSERTION(actual.GetUnit() == eCSSUnit_Inch ||
+ actual.GetUnit() == eCSSUnit_Pixel ||
+ actual.GetUnit() == eCSSUnit_Centimeter,
+ "bad actual value");
+ NS_ASSERTION(required.GetUnit() == eCSSUnit_Inch ||
+ required.GetUnit() == eCSSUnit_Pixel ||
+ required.GetUnit() == eCSSUnit_Centimeter,
+ "bad required value");
+ float actualDPI = actual.GetFloatValue();
+ float overrideDPPX = aPresContext->GetOverrideDPPX();
+
+ if (overrideDPPX > 0) {
+ actualDPI = overrideDPPX * 96.0f;
+ } else if (actual.GetUnit() == eCSSUnit_Centimeter) {
+ actualDPI = actualDPI * 2.54f;
+ } else if (actual.GetUnit() == eCSSUnit_Pixel) {
+ actualDPI = actualDPI * 96.0f;
+ }
+ float requiredDPI = required.GetFloatValue();
+ if (required.GetUnit() == eCSSUnit_Centimeter) {
+ requiredDPI = requiredDPI * 2.54f;
+ } else if (required.GetUnit() == eCSSUnit_Pixel) {
+ requiredDPI = requiredDPI * 96.0f;
+ }
+ cmp = DoCompare(actualDPI, requiredDPI);
+ }
+ break;
+ case nsMediaFeature::eEnumerated:
+ {
+ NS_ASSERTION(actual.GetUnit() == eCSSUnit_Enumerated,
+ "bad actual value");
+ NS_ASSERTION(required.GetUnit() == eCSSUnit_Enumerated,
+ "bad required value");
+ NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxNotAllowed,
+ "bad range"); // we asserted above about mRange
+ // We don't really need DoCompare, but it doesn't hurt (and
+ // maybe the compiler will condense this case with eInteger).
+ cmp = DoCompare(actual.GetIntValue(), required.GetIntValue());
+ }
+ break;
+ case nsMediaFeature::eIdent:
+ {
+ NS_ASSERTION(actual.GetUnit() == eCSSUnit_Ident,
+ "bad actual value");
+ NS_ASSERTION(required.GetUnit() == eCSSUnit_Ident,
+ "bad required value");
+ NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxNotAllowed,
+ "bad range");
+ cmp = !(actual == required); // string comparison
+ }
+ break;
+ }
+ switch (mRange) {
+ case nsMediaExpression::eMin:
+ return cmp != -1;
+ case nsMediaExpression::eMax:
+ return cmp != 1;
+ case nsMediaExpression::eEqual:
+ return cmp == 0;
+ }
+ NS_NOTREACHED("unexpected mRange");
+ return false;
+}
+
+void
+nsMediaQueryResultCacheKey::AddExpression(const nsMediaExpression* aExpression,
+ bool aExpressionMatches)
+{
+ const nsMediaFeature *feature = aExpression->mFeature;
+ FeatureEntry *entry = nullptr;
+ for (uint32_t i = 0; i < mFeatureCache.Length(); ++i) {
+ if (mFeatureCache[i].mFeature == feature) {
+ entry = &mFeatureCache[i];
+ break;
+ }
+ }
+ if (!entry) {
+ entry = mFeatureCache.AppendElement();
+ if (!entry) {
+ return; /* out of memory */
+ }
+ entry->mFeature = feature;
+ }
+
+ ExpressionEntry eentry = { *aExpression, aExpressionMatches };
+ entry->mExpressions.AppendElement(eentry);
+}
+
+bool
+nsMediaQueryResultCacheKey::Matches(nsPresContext* aPresContext) const
+{
+ if (aPresContext->Medium() != mMedium) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < mFeatureCache.Length(); ++i) {
+ const FeatureEntry *entry = &mFeatureCache[i];
+ nsCSSValue actual;
+ nsresult rv =
+ (entry->mFeature->mGetter)(aPresContext, entry->mFeature, actual);
+ NS_ENSURE_SUCCESS(rv, false); // any better ideas?
+
+ for (uint32_t j = 0; j < entry->mExpressions.Length(); ++j) {
+ const ExpressionEntry &eentry = entry->mExpressions[j];
+ if (eentry.mExpression.Matches(aPresContext, actual) !=
+ eentry.mExpressionMatches) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+nsDocumentRuleResultCacheKey::AddMatchingRule(css::DocumentRule* aRule)
+{
+ MOZ_ASSERT(!mFinalized);
+ return mMatchingRules.AppendElement(aRule);
+}
+
+void
+nsDocumentRuleResultCacheKey::Finalize()
+{
+ mMatchingRules.Sort();
+#ifdef DEBUG
+ mFinalized = true;
+#endif
+}
+
+#ifdef DEBUG
+static bool
+ArrayIsSorted(const nsTArray<css::DocumentRule*>& aRules)
+{
+ for (size_t i = 1; i < aRules.Length(); i++) {
+ if (aRules[i - 1] > aRules[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+bool
+nsDocumentRuleResultCacheKey::Matches(
+ nsPresContext* aPresContext,
+ const nsTArray<css::DocumentRule*>& aRules) const
+{
+ MOZ_ASSERT(mFinalized);
+ MOZ_ASSERT(ArrayIsSorted(mMatchingRules));
+ MOZ_ASSERT(ArrayIsSorted(aRules));
+
+#ifdef DEBUG
+ for (css::DocumentRule* rule : mMatchingRules) {
+ MOZ_ASSERT(aRules.BinaryIndexOf(rule) != aRules.NoIndex,
+ "aRules must contain all rules in mMatchingRules");
+ }
+#endif
+
+ // First check that aPresContext matches all the rules listed in
+ // mMatchingRules.
+ for (css::DocumentRule* rule : mMatchingRules) {
+ if (!rule->UseForPresentation(aPresContext)) {
+ return false;
+ }
+ }
+
+ // Then check that all the rules in aRules that aren't also in
+ // mMatchingRules do not match.
+
+ // pointer to matching rules
+ auto pm = mMatchingRules.begin();
+ auto pm_end = mMatchingRules.end();
+
+ // pointer to all rules
+ auto pr = aRules.begin();
+ auto pr_end = aRules.end();
+
+ // mMatchingRules and aRules are both sorted by their pointer values,
+ // so we can iterate over the two lists simultaneously.
+ while (pr < pr_end) {
+ while (pm < pm_end && *pm < *pr) {
+ ++pm;
+ }
+ if (pm >= pm_end || *pm != *pr) {
+ if ((*pr)->UseForPresentation(aPresContext)) {
+ return false;
+ }
+ }
+ ++pr;
+ }
+ return true;
+}
+
+#ifdef DEBUG
+void
+nsDocumentRuleResultCacheKey::List(FILE* aOut, int32_t aIndent) const
+{
+ for (css::DocumentRule* rule : mMatchingRules) {
+ nsCString str;
+
+ for (int32_t i = 0; i < aIndent; i++) {
+ str.AppendLiteral(" ");
+ }
+ str.AppendLiteral("{ ");
+
+ nsString condition;
+ rule->GetConditionText(condition);
+ AppendUTF16toUTF8(condition, str);
+
+ str.AppendLiteral(" }\n");
+ fprintf_stderr(aOut, "%s", str.get());
+ }
+}
+#endif
+
+size_t
+nsDocumentRuleResultCacheKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ n += mMatchingRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return n;
+}
+
+void
+nsMediaQuery::AppendToString(nsAString& aString) const
+{
+ if (mHadUnknownExpression) {
+ aString.AppendLiteral("not all");
+ return;
+ }
+
+ NS_ASSERTION(!mNegated || !mHasOnly, "can't have not and only");
+ NS_ASSERTION(!mTypeOmitted || (!mNegated && !mHasOnly),
+ "can't have not or only when type is omitted");
+ if (!mTypeOmitted) {
+ if (mNegated) {
+ aString.AppendLiteral("not ");
+ } else if (mHasOnly) {
+ aString.AppendLiteral("only ");
+ }
+ aString.Append(nsDependentAtomString(mMediaType));
+ }
+
+ for (uint32_t i = 0, i_end = mExpressions.Length(); i < i_end; ++i) {
+ if (i > 0 || !mTypeOmitted)
+ aString.AppendLiteral(" and ");
+ aString.Append('(');
+
+ const nsMediaExpression &expr = mExpressions[i];
+ const nsMediaFeature *feature = expr.mFeature;
+ if (feature->mReqFlags & nsMediaFeature::eHasWebkitPrefix) {
+ aString.AppendLiteral("-webkit-");
+ }
+ if (expr.mRange == nsMediaExpression::eMin) {
+ aString.AppendLiteral("min-");
+ } else if (expr.mRange == nsMediaExpression::eMax) {
+ aString.AppendLiteral("max-");
+ }
+
+ aString.Append(nsDependentAtomString(*feature->mName));
+
+ if (expr.mValue.GetUnit() != eCSSUnit_Null) {
+ aString.AppendLiteral(": ");
+ switch (feature->mValueType) {
+ case nsMediaFeature::eLength:
+ NS_ASSERTION(expr.mValue.IsLengthUnit(), "bad unit");
+ // Use 'width' as a property that takes length values
+ // written in the normal way.
+ expr.mValue.AppendToString(eCSSProperty_width, aString,
+ nsCSSValue::eNormalized);
+ break;
+ case nsMediaFeature::eInteger:
+ case nsMediaFeature::eBoolInteger:
+ NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Integer,
+ "bad unit");
+ // Use 'z-index' as a property that takes integer values
+ // written without anything extra.
+ expr.mValue.AppendToString(eCSSProperty_z_index, aString,
+ nsCSSValue::eNormalized);
+ break;
+ case nsMediaFeature::eFloat:
+ {
+ NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Number,
+ "bad unit");
+ // Use 'line-height' as a property that takes float values
+ // written in the normal way.
+ expr.mValue.AppendToString(eCSSProperty_line_height, aString,
+ nsCSSValue::eNormalized);
+ }
+ break;
+ case nsMediaFeature::eIntRatio:
+ {
+ NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Array,
+ "bad unit");
+ nsCSSValue::Array *array = expr.mValue.GetArrayValue();
+ NS_ASSERTION(array->Count() == 2, "unexpected length");
+ NS_ASSERTION(array->Item(0).GetUnit() == eCSSUnit_Integer,
+ "bad unit");
+ NS_ASSERTION(array->Item(1).GetUnit() == eCSSUnit_Integer,
+ "bad unit");
+ array->Item(0).AppendToString(eCSSProperty_z_index, aString,
+ nsCSSValue::eNormalized);
+ aString.Append('/');
+ array->Item(1).AppendToString(eCSSProperty_z_index, aString,
+ nsCSSValue::eNormalized);
+ }
+ break;
+ case nsMediaFeature::eResolution:
+ {
+ aString.AppendFloat(expr.mValue.GetFloatValue());
+ if (expr.mValue.GetUnit() == eCSSUnit_Inch) {
+ aString.AppendLiteral("dpi");
+ } else if (expr.mValue.GetUnit() == eCSSUnit_Pixel) {
+ aString.AppendLiteral("dppx");
+ } else {
+ NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Centimeter,
+ "bad unit");
+ aString.AppendLiteral("dpcm");
+ }
+ }
+ break;
+ case nsMediaFeature::eEnumerated:
+ NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Enumerated,
+ "bad unit");
+ AppendASCIItoUTF16(
+ nsCSSProps::ValueToKeyword(expr.mValue.GetIntValue(),
+ feature->mData.mKeywordTable),
+ aString);
+ break;
+ case nsMediaFeature::eIdent:
+ NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Ident,
+ "bad unit");
+ aString.Append(expr.mValue.GetStringBufferValue());
+ break;
+ }
+ }
+
+ aString.Append(')');
+ }
+}
+
+nsMediaQuery*
+nsMediaQuery::Clone() const
+{
+ return new nsMediaQuery(*this);
+}
+
+bool
+nsMediaQuery::Matches(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey* aKey) const
+{
+ if (mHadUnknownExpression)
+ return false;
+
+ bool match =
+ mMediaType == aPresContext->Medium() || mMediaType == nsGkAtoms::all;
+ for (uint32_t i = 0, i_end = mExpressions.Length(); match && i < i_end; ++i) {
+ const nsMediaExpression &expr = mExpressions[i];
+ nsCSSValue actual;
+ nsresult rv =
+ (expr.mFeature->mGetter)(aPresContext, expr.mFeature, actual);
+ NS_ENSURE_SUCCESS(rv, false); // any better ideas?
+
+ match = expr.Matches(aPresContext, actual);
+ if (aKey) {
+ aKey->AddExpression(&expr, match);
+ }
+ }
+
+ return match == !mNegated;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMediaList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMMediaList)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMediaList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMediaList)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsMediaList)
+
+nsMediaList::nsMediaList()
+ : mStyleSheet(nullptr)
+{
+}
+
+nsMediaList::~nsMediaList()
+{
+}
+
+/* virtual */ JSObject*
+nsMediaList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+nsMediaList::GetText(nsAString& aMediaText)
+{
+ aMediaText.Truncate();
+
+ for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
+ nsMediaQuery* query = mArray[i];
+
+ query->AppendToString(aMediaText);
+
+ if (i + 1 < i_end) {
+ aMediaText.AppendLiteral(", ");
+ }
+ }
+}
+
+// XXXbz this is so ill-defined in the spec, it's not clear quite what
+// it should be doing....
+void
+nsMediaList::SetText(const nsAString& aMediaText)
+{
+ nsCSSParser parser;
+
+ bool htmlMode = mStyleSheet && mStyleSheet->GetOwnerNode();
+
+ parser.ParseMediaList(aMediaText, nullptr, 0, this, htmlMode);
+}
+
+bool
+nsMediaList::Matches(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey* aKey)
+{
+ for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
+ if (mArray[i]->Matches(aPresContext, aKey)) {
+ return true;
+ }
+ }
+ return mArray.IsEmpty();
+}
+
+void
+nsMediaList::SetStyleSheet(CSSStyleSheet* aSheet)
+{
+ NS_ASSERTION(aSheet == mStyleSheet || !aSheet || !mStyleSheet,
+ "multiple style sheets competing for one media list");
+ mStyleSheet = aSheet;
+}
+
+already_AddRefed<nsMediaList>
+nsMediaList::Clone()
+{
+ RefPtr<nsMediaList> result = new nsMediaList();
+ result->mArray.AppendElements(mArray.Length());
+ for (uint32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
+ result->mArray[i] = mArray[i]->Clone();
+ MOZ_ASSERT(result->mArray[i]);
+ }
+ return result.forget();
+}
+
+NS_IMETHODIMP
+nsMediaList::GetMediaText(nsAString& aMediaText)
+{
+ GetText(aMediaText);
+ return NS_OK;
+}
+
+// "sheet" should be a CSSStyleSheet and "doc" should be an
+// nsCOMPtr<nsIDocument>
+#define BEGIN_MEDIA_CHANGE(sheet, doc) \
+ if (sheet) { \
+ doc = sheet->GetOwningDocument(); \
+ } \
+ mozAutoDocUpdate updateBatch(doc, UPDATE_STYLE, true); \
+ if (sheet) { \
+ sheet->WillDirty(); \
+ }
+
+#define END_MEDIA_CHANGE(sheet, doc) \
+ if (sheet) { \
+ sheet->DidDirty(); \
+ } \
+ /* XXXldb Pass something meaningful? */ \
+ if (doc) { \
+ doc->StyleRuleChanged(sheet, nullptr); \
+ }
+
+
+NS_IMETHODIMP
+nsMediaList::SetMediaText(const nsAString& aMediaText)
+{
+ nsCOMPtr<nsIDocument> doc;
+
+ BEGIN_MEDIA_CHANGE(mStyleSheet, doc)
+
+ SetText(aMediaText);
+
+ END_MEDIA_CHANGE(mStyleSheet, doc)
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMediaList::GetLength(uint32_t* aLength)
+{
+ NS_ENSURE_ARG_POINTER(aLength);
+
+ *aLength = Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMediaList::Item(uint32_t aIndex, nsAString& aReturn)
+{
+ bool dummy;
+ IndexedGetter(aIndex, dummy, aReturn);
+ return NS_OK;
+}
+
+void
+nsMediaList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aReturn)
+{
+ if (aIndex < Length()) {
+ aFound = true;
+ aReturn.Truncate();
+ mArray[aIndex]->AppendToString(aReturn);
+ } else {
+ aFound = false;
+ SetDOMStringToNull(aReturn);
+ }
+}
+
+NS_IMETHODIMP
+nsMediaList::DeleteMedium(const nsAString& aOldMedium)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIDocument> doc;
+
+ BEGIN_MEDIA_CHANGE(mStyleSheet, doc)
+
+ rv = Delete(aOldMedium);
+ if (NS_FAILED(rv))
+ return rv;
+
+ END_MEDIA_CHANGE(mStyleSheet, doc)
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMediaList::AppendMedium(const nsAString& aNewMedium)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIDocument> doc;
+
+ BEGIN_MEDIA_CHANGE(mStyleSheet, doc)
+
+ rv = Append(aNewMedium);
+ if (NS_FAILED(rv))
+ return rv;
+
+ END_MEDIA_CHANGE(mStyleSheet, doc)
+
+ return rv;
+}
+
+nsresult
+nsMediaList::Delete(const nsAString& aOldMedium)
+{
+ if (aOldMedium.IsEmpty())
+ return NS_ERROR_DOM_NOT_FOUND_ERR;
+
+ for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
+ nsMediaQuery* query = mArray[i];
+
+ nsAutoString buf;
+ query->AppendToString(buf);
+ if (buf == aOldMedium) {
+ mArray.RemoveElementAt(i);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_DOM_NOT_FOUND_ERR;
+}
+
+nsresult
+nsMediaList::Append(const nsAString& aNewMedium)
+{
+ if (aNewMedium.IsEmpty())
+ return NS_ERROR_DOM_NOT_FOUND_ERR;
+
+ Delete(aNewMedium);
+
+ nsresult rv = NS_OK;
+ nsTArray<nsAutoPtr<nsMediaQuery> > buf;
+ mArray.SwapElements(buf);
+ SetText(aNewMedium);
+ if (mArray.Length() == 1) {
+ nsMediaQuery *query = mArray[0].forget();
+ if (!buf.AppendElement(query)) {
+ delete query;
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mArray.SwapElements(buf);
+ return rv;
+}
+
+namespace mozilla {
+
+// -------------------------------
+// CSS Style Sheet Inner Data Container
+//
+
+
+CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const SRIMetadata& aIntegrity)
+ : StyleSheetInfo(aCORSMode, aReferrerPolicy, aIntegrity)
+{
+ MOZ_COUNT_CTOR(CSSStyleSheetInner);
+ mSheets.AppendElement(aPrimarySheet);
+}
+
+static bool SetStyleSheetReference(css::Rule* aRule, void* aSheet)
+{
+ if (aRule) {
+ aRule->SetStyleSheet(static_cast<CSSStyleSheet*>(aSheet));
+ }
+ return true;
+}
+
+struct ChildSheetListBuilder {
+ RefPtr<CSSStyleSheet>* sheetSlot;
+ CSSStyleSheet* parent;
+
+ void SetParentLinks(CSSStyleSheet* aSheet) {
+ aSheet->mParent = parent;
+ aSheet->SetOwningDocument(parent->mDocument);
+ }
+
+ static void ReparentChildList(CSSStyleSheet* aPrimarySheet,
+ CSSStyleSheet* aFirstChild)
+ {
+ for (CSSStyleSheet *child = aFirstChild; child; child = child->mNext) {
+ child->mParent = aPrimarySheet;
+ child->SetOwningDocument(aPrimarySheet->mDocument);
+ }
+ }
+};
+
+bool
+CSSStyleSheet::RebuildChildList(css::Rule* aRule, void* aBuilder)
+{
+ int32_t type = aRule->GetType();
+ if (type < css::Rule::IMPORT_RULE) {
+ // Keep going till we get to the import rules.
+ return true;
+ }
+
+ if (type != css::Rule::IMPORT_RULE) {
+ // We're past all the import rules; stop the enumeration.
+ return false;
+ }
+
+ ChildSheetListBuilder* builder =
+ static_cast<ChildSheetListBuilder*>(aBuilder);
+
+ // XXXbz We really need to decomtaminate all this stuff. Is there a reason
+ // that I can't just QI to ImportRule and get a CSSStyleSheet
+ // directly from it?
+ nsCOMPtr<nsIDOMCSSImportRule> importRule(do_QueryInterface(aRule));
+ NS_ASSERTION(importRule, "GetType lied");
+
+ nsCOMPtr<nsIDOMCSSStyleSheet> childSheet;
+ importRule->GetStyleSheet(getter_AddRefs(childSheet));
+
+ // Have to do this QI to be safe, since XPConnect can fake
+ // nsIDOMCSSStyleSheets
+ RefPtr<CSSStyleSheet> cssSheet = do_QueryObject(childSheet);
+ if (!cssSheet) {
+ return true;
+ }
+
+ (*builder->sheetSlot) = cssSheet;
+ builder->SetParentLinks(*builder->sheetSlot);
+ builder->sheetSlot = &(*builder->sheetSlot)->mNext;
+ return true;
+}
+
+size_t
+CSSStyleSheet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ const CSSStyleSheet* s = this;
+ while (s) {
+ n += aMallocSizeOf(s);
+
+ // Each inner can be shared by multiple sheets. So we only count the inner
+ // if this sheet is the last one in the list of those sharing it. As a
+ // result, the last such sheet takes all the blame for the memory
+ // consumption of the inner, which isn't ideal but it's better than
+ // double-counting the inner. We use last instead of first since the first
+ // sheet may be held in the nsXULPrototypeCache and not used in a window at
+ // all.
+ if (s->mInner->mSheets.LastElement() == s) {
+ n += s->mInner->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - s->mTitle
+ // - s->mMedia
+ // - s->mRuleCollection
+ // - s->mRuleProcessors
+ //
+ // The following members are not measured:
+ // - s->mOwnerRule, because it's non-owning
+
+ s = s->mNext;
+ }
+ return n;
+}
+
+CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
+ CSSStyleSheet* aPrimarySheet)
+ : StyleSheetInfo(aCopy)
+{
+ MOZ_COUNT_CTOR(CSSStyleSheetInner);
+ AddSheet(aPrimarySheet);
+ aCopy.mOrderedRules.EnumerateForwards(css::GroupRule::CloneRuleInto, &mOrderedRules);
+ mOrderedRules.EnumerateForwards(SetStyleSheetReference, aPrimarySheet);
+
+ ChildSheetListBuilder builder = { &mFirstChild, aPrimarySheet };
+ mOrderedRules.EnumerateForwards(CSSStyleSheet::RebuildChildList, &builder);
+
+ RebuildNameSpaces();
+}
+
+CSSStyleSheetInner::~CSSStyleSheetInner()
+{
+ MOZ_COUNT_DTOR(CSSStyleSheetInner);
+ mOrderedRules.EnumerateForwards(SetStyleSheetReference, nullptr);
+}
+
+CSSStyleSheetInner*
+CSSStyleSheetInner::CloneFor(CSSStyleSheet* aPrimarySheet)
+{
+ return new CSSStyleSheetInner(*this, aPrimarySheet);
+}
+
+void
+CSSStyleSheetInner::AddSheet(CSSStyleSheet* aSheet)
+{
+ mSheets.AppendElement(aSheet);
+}
+
+void
+CSSStyleSheetInner::RemoveSheet(CSSStyleSheet* aSheet)
+{
+ if (1 == mSheets.Length()) {
+ NS_ASSERTION(aSheet == mSheets.ElementAt(0), "bad parent");
+ delete this;
+ return;
+ }
+ if (aSheet == mSheets.ElementAt(0)) {
+ mSheets.RemoveElementAt(0);
+ NS_ASSERTION(mSheets.Length(), "no parents");
+ mOrderedRules.EnumerateForwards(SetStyleSheetReference,
+ mSheets.ElementAt(0));
+
+ ChildSheetListBuilder::ReparentChildList(mSheets[0], mFirstChild);
+ }
+ else {
+ mSheets.RemoveElement(aSheet);
+ }
+}
+
+static void
+AddNamespaceRuleToMap(css::Rule* aRule, nsXMLNameSpaceMap* aMap)
+{
+ NS_ASSERTION(aRule->GetType() == css::Rule::NAMESPACE_RULE, "Bogus rule type");
+
+ RefPtr<css::NameSpaceRule> nameSpaceRule = do_QueryObject(aRule);
+
+ nsAutoString urlSpec;
+ nameSpaceRule->GetURLSpec(urlSpec);
+
+ aMap->AddPrefix(nameSpaceRule->GetPrefix(), urlSpec);
+}
+
+static bool
+CreateNameSpace(css::Rule* aRule, void* aNameSpacePtr)
+{
+ int32_t type = aRule->GetType();
+ if (css::Rule::NAMESPACE_RULE == type) {
+ AddNamespaceRuleToMap(aRule,
+ static_cast<nsXMLNameSpaceMap*>(aNameSpacePtr));
+ return true;
+ }
+ // stop if not namespace, import or charset because namespace can't follow
+ // anything else
+ return (css::Rule::CHARSET_RULE == type || css::Rule::IMPORT_RULE == type);
+}
+
+void
+CSSStyleSheetInner::RebuildNameSpaces()
+{
+ // Just nuke our existing namespace map, if any
+ if (NS_SUCCEEDED(CreateNamespaceMap())) {
+ mOrderedRules.EnumerateForwards(CreateNameSpace, mNameSpaceMap);
+ }
+}
+
+nsresult
+CSSStyleSheetInner::CreateNamespaceMap()
+{
+ mNameSpaceMap = nsXMLNameSpaceMap::Create(false);
+ NS_ENSURE_TRUE(mNameSpaceMap, NS_ERROR_OUT_OF_MEMORY);
+ // Override the default namespace map behavior for the null prefix to
+ // return the wildcard namespace instead of the null namespace.
+ mNameSpaceMap->AddPrefix(nullptr, kNameSpaceID_Unknown);
+ return NS_OK;
+}
+
+size_t
+CSSStyleSheetInner::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ n += mOrderedRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mOrderedRules.Length(); i++) {
+ n += mOrderedRules[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ n += mFirstChild ? mFirstChild->SizeOfIncludingThis(aMallocSizeOf) : 0;
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mSheetURI
+ // - mOriginalSheetURI
+ // - mBaseURI
+ // - mPrincipal
+ // - mNameSpaceMap
+ //
+ // The following members are not measured:
+ // - mSheets, because it's non-owning
+
+ return n;
+}
+
+// -------------------------------
+// CSS Style Sheet
+//
+
+CSSStyleSheet::CSSStyleSheet(css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy)
+ : StyleSheet(StyleBackendType::Gecko, aParsingMode),
+ mParent(nullptr),
+ mOwnerRule(nullptr),
+ mDirty(false),
+ mInRuleProcessorCache(false),
+ mScopeElement(nullptr),
+ mRuleProcessors(nullptr)
+{
+ mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
+ SRIMetadata());
+}
+
+CSSStyleSheet::CSSStyleSheet(css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const SRIMetadata& aIntegrity)
+ : StyleSheet(StyleBackendType::Gecko, aParsingMode),
+ mParent(nullptr),
+ mOwnerRule(nullptr),
+ mDirty(false),
+ mInRuleProcessorCache(false),
+ mScopeElement(nullptr),
+ mRuleProcessors(nullptr)
+{
+ mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
+ aIntegrity);
+}
+
+CSSStyleSheet::CSSStyleSheet(const CSSStyleSheet& aCopy,
+ CSSStyleSheet* aParentToUse,
+ css::ImportRule* aOwnerRuleToUse,
+ nsIDocument* aDocumentToUse,
+ nsINode* aOwningNodeToUse)
+ : StyleSheet(aCopy, aDocumentToUse, aOwningNodeToUse),
+ mParent(aParentToUse),
+ mOwnerRule(aOwnerRuleToUse),
+ mDirty(aCopy.mDirty),
+ mInRuleProcessorCache(false),
+ mScopeElement(nullptr),
+ mInner(aCopy.mInner),
+ mRuleProcessors(nullptr)
+{
+
+ mInner->AddSheet(this);
+
+ if (mDirty) { // CSSOM's been there, force full copy now
+ NS_ASSERTION(mInner->mComplete, "Why have rules been accessed on an incomplete sheet?");
+ // FIXME: handle failure?
+ EnsureUniqueInner();
+ }
+
+ if (aCopy.mMedia) {
+ // XXX This is wrong; we should be keeping @import rules and
+ // sheets in sync!
+ mMedia = aCopy.mMedia->Clone();
+ }
+}
+
+CSSStyleSheet::~CSSStyleSheet()
+{
+ for (CSSStyleSheet* child = mInner->mFirstChild;
+ child;
+ child = child->mNext) {
+ // XXXbz this is a little bogus; see the XXX comment where we
+ // declare mFirstChild.
+ if (child->mParent == this) {
+ child->mParent = nullptr;
+ child->mDocument = nullptr;
+ }
+ }
+ DropRuleCollection();
+ DropMedia();
+ mInner->RemoveSheet(this);
+ // XXX The document reference is not reference counted and should
+ // not be released. The document will let us know when it is going
+ // away.
+ if (mRuleProcessors) {
+ NS_ASSERTION(mRuleProcessors->Length() == 0, "destructing sheet with rule processor reference");
+ delete mRuleProcessors; // weak refs, should be empty here anyway
+ }
+ if (mInRuleProcessorCache) {
+ RuleProcessorCache::RemoveSheet(this);
+ }
+}
+
+void
+CSSStyleSheet::DropRuleCollection()
+{
+ if (mRuleCollection) {
+ mRuleCollection->DropReference();
+ mRuleCollection = nullptr;
+ }
+}
+
+void
+CSSStyleSheet::DropMedia()
+{
+ if (mMedia) {
+ mMedia->SetStyleSheet(nullptr);
+ mMedia = nullptr;
+ }
+}
+
+void
+CSSStyleSheet::UnlinkInner()
+{
+ // 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) {
+ return;
+ }
+
+ mInner->mOrderedRules.EnumerateForwards(SetStyleSheetReference, nullptr);
+ mInner->mOrderedRules.Clear();
+
+ // Have to be a bit careful with child sheets, because we want to
+ // drop their mNext pointers and null out their mParent and
+ // mDocument, but don't want to work with deleted objects. And we
+ // don't want to do any addrefing in the process, just to make sure
+ // we don't confuse the cycle collector (though on the face of it,
+ // addref/release pairs during unlink should probably be ok).
+ RefPtr<CSSStyleSheet> child;
+ child.swap(mInner->mFirstChild);
+ while (child) {
+ MOZ_ASSERT(child->mParent == this, "We have a unique inner!");
+ child->mParent = nullptr;
+ child->mDocument = nullptr;
+ RefPtr<CSSStyleSheet> next;
+ // Null out child->mNext, but don't let it die yet
+ next.swap(child->mNext);
+ // Switch to looking at the old value of child->mNext next iteration
+ child.swap(next);
+ // "next" is now our previous value of child; it'll get released
+ // as we loop around.
+ }
+}
+
+void
+CSSStyleSheet::TraverseInner(nsCycleCollectionTraversalCallback &cb)
+{
+ // 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) {
+ return;
+ }
+
+ RefPtr<CSSStyleSheet>* childSheetSlot = &mInner->mFirstChild;
+ while (*childSheetSlot) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "child sheet");
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMCSSStyleSheet*, childSheetSlot->get()));
+ childSheetSlot = &(*childSheetSlot)->mNext;
+ }
+
+ const nsCOMArray<css::Rule>& rules = mInner->mOrderedRules;
+ for (int32_t i = 0, count = rules.Count(); i < count; ++i) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mOrderedRules[i]");
+ cb.NoteXPCOMChild(rules[i]->GetExistingDOMRule());
+ }
+}
+
+// QueryInterface implementation for CSSStyleSheet
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(CSSStyleSheet)
+ NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, StyleSheet)
+ if (aIID.Equals(NS_GET_IID(CSSStyleSheet)))
+ foundInterface = reinterpret_cast<nsISupports*>(this);
+ else
+NS_INTERFACE_MAP_END_INHERITING(StyleSheet)
+
+NS_IMPL_ADDREF_INHERITED(CSSStyleSheet, StyleSheet)
+NS_IMPL_RELEASE_INHERITED(CSSStyleSheet, StyleSheet)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CSSStyleSheet)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CSSStyleSheet)
+ tmp->DropMedia();
+ // We do not unlink mNext; our parent will handle that. If we
+ // unlinked it here, our parent would not be able to walk its list
+ // of child sheets and null out the back-references to it, if we got
+ // unlinked before it does.
+ tmp->DropRuleCollection();
+ tmp->UnlinkInner();
+ tmp->mScopeElement = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(StyleSheet)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSStyleSheet, StyleSheet)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMedia)
+ // We do not traverse mNext; our parent will handle that. See
+ // comments in Unlink for why.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleCollection)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScopeElement)
+ tmp->TraverseInner(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+nsresult
+CSSStyleSheet::AddRuleProcessor(nsCSSRuleProcessor* aProcessor)
+{
+ if (! mRuleProcessors) {
+ mRuleProcessors = new AutoTArray<nsCSSRuleProcessor*, 8>();
+ if (!mRuleProcessors)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ASSERTION(mRuleProcessors->NoIndex == mRuleProcessors->IndexOf(aProcessor),
+ "processor already registered");
+ mRuleProcessors->AppendElement(aProcessor); // weak ref
+ return NS_OK;
+}
+
+nsresult
+CSSStyleSheet::DropRuleProcessor(nsCSSRuleProcessor* aProcessor)
+{
+ if (!mRuleProcessors)
+ return NS_ERROR_FAILURE;
+ return mRuleProcessors->RemoveElement(aProcessor)
+ ? NS_OK
+ : NS_ERROR_FAILURE;
+}
+
+void
+CSSStyleSheet::AddStyleSet(nsStyleSet* aStyleSet)
+{
+ NS_ASSERTION(!mStyleSets.Contains(aStyleSet),
+ "style set already registered");
+ mStyleSets.AppendElement(aStyleSet);
+}
+
+void
+CSSStyleSheet::DropStyleSet(nsStyleSet* aStyleSet)
+{
+ DebugOnly<bool> found = mStyleSets.RemoveElement(aStyleSet);
+ NS_ASSERTION(found, "didn't find style set");
+}
+
+bool
+CSSStyleSheet::UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey) const
+{
+ if (mMedia) {
+ return mMedia->Matches(aPresContext, &aKey);
+ }
+ return true;
+}
+
+
+void
+CSSStyleSheet::SetMedia(nsMediaList* aMedia)
+{
+ mMedia = aMedia;
+}
+
+bool
+CSSStyleSheet::HasRules() const
+{
+ return StyleRuleCount() != 0;
+}
+
+void
+CSSStyleSheet::SetEnabled(bool aEnabled)
+{
+ // Internal method, so callers must handle BeginUpdate/EndUpdate
+ bool oldDisabled = mDisabled;
+ mDisabled = !aEnabled;
+
+ if (mInner->mComplete && oldDisabled != mDisabled) {
+ ClearRuleCascades();
+
+ if (mDocument) {
+ mDocument->SetStyleSheetApplicableState(this, !mDisabled);
+ }
+ }
+}
+
+CSSStyleSheet*
+CSSStyleSheet::GetParentSheet() const
+{
+ return mParent;
+}
+
+void
+CSSStyleSheet::SetOwningDocument(nsIDocument* aDocument)
+{ // not ref counted
+ mDocument = aDocument;
+ // Now set the same document on all our child sheets....
+ // XXXbz this is a little bogus; see the XXX comment where we
+ // declare mFirstChild.
+ for (CSSStyleSheet* child = mInner->mFirstChild;
+ child; child = child->mNext) {
+ if (child->mParent == this) {
+ child->SetOwningDocument(aDocument);
+ }
+ }
+}
+
+uint64_t
+CSSStyleSheet::FindOwningWindowInnerID() const
+{
+ uint64_t windowID = 0;
+ if (mDocument) {
+ windowID = mDocument->InnerWindowID();
+ }
+
+ if (windowID == 0 && mOwningNode) {
+ windowID = mOwningNode->OwnerDoc()->InnerWindowID();
+ }
+
+ if (windowID == 0 && mOwnerRule) {
+ RefPtr<CSSStyleSheet> sheet = static_cast<css::Rule*>(mOwnerRule)->GetStyleSheet();
+ if (sheet) {
+ windowID = sheet->FindOwningWindowInnerID();
+ }
+ }
+
+ if (windowID == 0 && mParent) {
+ windowID = mParent->FindOwningWindowInnerID();
+ }
+
+ return windowID;
+}
+
+void
+CSSStyleSheet::AppendStyleSheet(CSSStyleSheet* aSheet)
+{
+ NS_PRECONDITION(nullptr != aSheet, "null arg");
+
+ WillDirty();
+ RefPtr<CSSStyleSheet>* tail = &mInner->mFirstChild;
+ while (*tail) {
+ tail = &(*tail)->mNext;
+ }
+ *tail = aSheet;
+
+ // This is not reference counted. Our parent tells us when
+ // it's going away.
+ aSheet->mParent = this;
+ aSheet->mDocument = mDocument;
+ DidDirty();
+}
+
+void
+CSSStyleSheet::AppendStyleRule(css::Rule* aRule)
+{
+ NS_PRECONDITION(nullptr != aRule, "null arg");
+
+ WillDirty();
+ mInner->mOrderedRules.AppendObject(aRule);
+ aRule->SetStyleSheet(this);
+ DidDirty();
+
+ if (css::Rule::NAMESPACE_RULE == aRule->GetType()) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ RegisterNamespaceRule(aRule);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "RegisterNamespaceRule returned error");
+ }
+}
+
+int32_t
+CSSStyleSheet::StyleRuleCount() const
+{
+ return mInner->mOrderedRules.Count();
+}
+
+css::Rule*
+CSSStyleSheet::GetStyleRuleAt(int32_t aIndex) const
+{
+ // Important: If this function is ever made scriptable, we must add
+ // a security check here. See GetCssRules below for an example.
+ return mInner->mOrderedRules.SafeObjectAt(aIndex);
+}
+
+void
+CSSStyleSheet::EnsureUniqueInner()
+{
+ mDirty = true;
+
+ MOZ_ASSERT(mInner->mSheets.Length() != 0,
+ "unexpected number of outers");
+ if (mInner->mSheets.Length() == 1) {
+ // already unique
+ return;
+ }
+ CSSStyleSheetInner* clone = mInner->CloneFor(this);
+ MOZ_ASSERT(clone);
+ mInner->RemoveSheet(this);
+ mInner = clone;
+
+ // otherwise the rule processor has pointers to the old rules
+ ClearRuleCascades();
+
+ // let our containing style sets know that if we call
+ // nsPresContext::EnsureSafeToHandOutCSSRules we will need to restyle the
+ // document
+ for (nsStyleSet* styleSet : mStyleSets) {
+ styleSet->SetNeedsRestyleAfterEnsureUniqueInner();
+ }
+}
+
+void
+CSSStyleSheet::AppendAllChildSheets(nsTArray<CSSStyleSheet*>& aArray)
+{
+ for (CSSStyleSheet* child = mInner->mFirstChild; child;
+ child = child->mNext) {
+ aArray.AppendElement(child);
+ }
+}
+
+already_AddRefed<CSSStyleSheet>
+CSSStyleSheet::Clone(CSSStyleSheet* aCloneParent,
+ css::ImportRule* aCloneOwnerRule,
+ nsIDocument* aCloneDocument,
+ nsINode* aCloneOwningNode) const
+{
+ RefPtr<CSSStyleSheet> clone = new CSSStyleSheet(*this,
+ aCloneParent,
+ aCloneOwnerRule,
+ aCloneDocument,
+ aCloneOwningNode);
+ return clone.forget();
+}
+
+#ifdef DEBUG
+static void
+ListRules(const nsCOMArray<css::Rule>& aRules, FILE* aOut, int32_t aIndent)
+{
+ for (int32_t index = aRules.Count() - 1; index >= 0; --index) {
+ aRules.ObjectAt(index)->List(aOut, aIndent);
+ }
+}
+
+struct ListEnumData {
+ ListEnumData(FILE* aOut, int32_t aIndent)
+ : mOut(aOut),
+ mIndent(aIndent)
+ {
+ }
+ FILE* mOut;
+ int32_t mIndent;
+};
+
+void
+CSSStyleSheet::List(FILE* out, int32_t aIndent) const
+{
+
+ int32_t index;
+
+ // Indent
+ nsAutoCString str;
+ for (index = aIndent; --index >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+
+ str.AppendLiteral("CSS Style Sheet: ");
+ nsAutoCString urlSpec;
+ nsresult rv = mInner->mSheetURI->GetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv) && !urlSpec.IsEmpty()) {
+ str.Append(urlSpec);
+ }
+
+ if (mMedia) {
+ str.AppendLiteral(" media: ");
+ nsAutoString buffer;
+ mMedia->GetText(buffer);
+ AppendUTF16toUTF8(buffer, str);
+ }
+ str.Append('\n');
+ fprintf_stderr(out, "%s", str.get());
+
+ for (const CSSStyleSheet* child = mInner->mFirstChild;
+ child;
+ child = child->mNext) {
+ child->List(out, aIndent + 1);
+ }
+
+ fprintf_stderr(out, "%s", "Rules in source order:\n");
+ ListRules(mInner->mOrderedRules, out, aIndent);
+}
+#endif
+
+void
+CSSStyleSheet::ClearRuleCascades()
+{
+ // We might be in ClearRuleCascades because we had a modification
+ // to the sheet that resulted in an nsCSSSelector being destroyed.
+ // Tell the RestyleManager for each document we're used in
+ // so that they can drop any nsCSSSelector pointers (used for
+ // eRestyle_SomeDescendants) in their mPendingRestyles.
+ for (nsStyleSet* styleSet : mStyleSets) {
+ styleSet->ClearSelectors();
+ }
+
+ bool removedSheetFromRuleProcessorCache = false;
+ if (mRuleProcessors) {
+ nsCSSRuleProcessor **iter = mRuleProcessors->Elements(),
+ **end = iter + mRuleProcessors->Length();
+ for(; iter != end; ++iter) {
+ if (!removedSheetFromRuleProcessorCache && (*iter)->IsShared()) {
+ // Since the sheet has been modified, we need to remove all
+ // RuleProcessorCache entries that contain this sheet, as the
+ // list of @-moz-document rules might have changed.
+ RuleProcessorCache::RemoveSheet(this);
+ removedSheetFromRuleProcessorCache = true;
+ }
+ (*iter)->ClearRuleCascades();
+ }
+ }
+ if (mParent) {
+ CSSStyleSheet* parent = (CSSStyleSheet*)mParent;
+ parent->ClearRuleCascades();
+ }
+}
+
+void
+CSSStyleSheet::WillDirty()
+{
+ if (mInner->mComplete) {
+ EnsureUniqueInner();
+ }
+}
+
+void
+CSSStyleSheet::DidDirty()
+{
+ MOZ_ASSERT(!mInner->mComplete || mDirty,
+ "caller must have called WillDirty()");
+ ClearRuleCascades();
+}
+
+nsresult
+CSSStyleSheet::RegisterNamespaceRule(css::Rule* aRule)
+{
+ if (!mInner->mNameSpaceMap) {
+ nsresult rv = mInner->CreateNamespaceMap();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ AddNamespaceRuleToMap(aRule, mInner->mNameSpaceMap);
+ return NS_OK;
+}
+
+nsMediaList*
+CSSStyleSheet::Media()
+{
+ if (!mMedia) {
+ mMedia = new nsMediaList();
+ mMedia->SetStyleSheet(this);
+ }
+
+ return mMedia;
+}
+
+nsIDOMCSSRule*
+CSSStyleSheet::GetDOMOwnerRule() const
+{
+ return mOwnerRule ? mOwnerRule->GetDOMRule() : nullptr;
+}
+
+CSSRuleList*
+CSSStyleSheet::GetCssRulesInternal(ErrorResult& aRv)
+{
+ if (!mRuleCollection) {
+ mRuleCollection = new CSSRuleListImpl(this);
+ }
+ return mRuleCollection;
+}
+
+static bool
+RuleHasPendingChildSheet(css::Rule *cssRule)
+{
+ nsCOMPtr<nsIDOMCSSImportRule> importRule(do_QueryInterface(cssRule));
+ NS_ASSERTION(importRule, "Rule which has type IMPORT_RULE and does not implement nsIDOMCSSImportRule!");
+ nsCOMPtr<nsIDOMCSSStyleSheet> childSheet;
+ importRule->GetStyleSheet(getter_AddRefs(childSheet));
+ RefPtr<CSSStyleSheet> cssSheet = do_QueryObject(childSheet);
+ return cssSheet != nullptr && !cssSheet->IsComplete();
+}
+
+uint32_t
+CSSStyleSheet::InsertRuleInternal(const nsAString& aRule,
+ uint32_t aIndex,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(mInner->mComplete);
+
+ WillDirty();
+
+ if (aIndex > uint32_t(mInner->mOrderedRules.Count())) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return 0;
+ }
+
+ NS_ASSERTION(uint32_t(mInner->mOrderedRules.Count()) <= INT32_MAX,
+ "Too many style rules!");
+
+ // Hold strong ref to the CSSLoader in case the document update
+ // kills the document
+ RefPtr<css::Loader> loader;
+ if (mDocument) {
+ loader = mDocument->CSSLoader();
+ NS_ASSERTION(loader, "Document with no CSS loader!");
+ }
+
+ nsCSSParser css(loader, this);
+
+ mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
+
+ RefPtr<css::Rule> rule;
+ aRv = css.ParseRule(aRule, mInner->mSheetURI, mInner->mBaseURI,
+ mInner->mPrincipal, getter_AddRefs(rule));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return 0;
+ }
+
+ // Hierarchy checking.
+ int32_t newType = rule->GetType();
+
+ // check that we're not inserting before a charset rule
+ css::Rule* nextRule = mInner->mOrderedRules.SafeObjectAt(aIndex);
+ if (nextRule) {
+ int32_t nextType = nextRule->GetType();
+ if (nextType == css::Rule::CHARSET_RULE) {
+ aRv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return 0;
+ }
+
+ if (nextType == css::Rule::IMPORT_RULE &&
+ newType != css::Rule::CHARSET_RULE &&
+ newType != css::Rule::IMPORT_RULE) {
+ aRv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return 0;
+ }
+
+ if (nextType == css::Rule::NAMESPACE_RULE &&
+ newType != css::Rule::CHARSET_RULE &&
+ newType != css::Rule::IMPORT_RULE &&
+ newType != css::Rule::NAMESPACE_RULE) {
+ aRv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return 0;
+ }
+ }
+
+ if (aIndex != 0) {
+ // no inserting charset at nonzero position
+ if (newType == css::Rule::CHARSET_RULE) {
+ aRv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return 0;
+ }
+
+ css::Rule* prevRule = mInner->mOrderedRules.SafeObjectAt(aIndex - 1);
+ int32_t prevType = prevRule->GetType();
+
+ if (newType == css::Rule::IMPORT_RULE &&
+ prevType != css::Rule::CHARSET_RULE &&
+ prevType != css::Rule::IMPORT_RULE) {
+ aRv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return 0;
+ }
+
+ if (newType == css::Rule::NAMESPACE_RULE &&
+ prevType != css::Rule::CHARSET_RULE &&
+ prevType != css::Rule::IMPORT_RULE &&
+ prevType != css::Rule::NAMESPACE_RULE) {
+ aRv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return 0;
+ }
+ }
+
+ if (!mInner->mOrderedRules.InsertObjectAt(rule, aIndex)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return 0;
+ }
+
+ DidDirty();
+
+ rule->SetStyleSheet(this);
+
+ int32_t type = rule->GetType();
+ if (type == css::Rule::NAMESPACE_RULE) {
+ // XXXbz does this screw up when inserting a namespace rule before
+ // another namespace rule that binds the same prefix to a different
+ // namespace?
+ aRv = RegisterNamespaceRule(rule);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return 0;
+ }
+ }
+
+ // We don't notify immediately for @import rules, but rather when
+ // the sheet the rule is importing is loaded (see StyleSheetLoaded)
+ if ((type != css::Rule::IMPORT_RULE || !RuleHasPendingChildSheet(rule)) &&
+ mDocument) {
+ mDocument->StyleRuleAdded(this, rule);
+ }
+
+ return aIndex;
+}
+
+void
+CSSStyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv)
+{
+ // XXX TBI: handle @rule types
+ mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
+
+ WillDirty();
+
+ if (aIndex >= uint32_t(mInner->mOrderedRules.Count())) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ NS_ASSERTION(uint32_t(mInner->mOrderedRules.Count()) <= INT32_MAX,
+ "Too many style rules!");
+
+ // Hold a strong ref to the rule so it doesn't die when we RemoveObjectAt
+ RefPtr<css::Rule> rule = mInner->mOrderedRules.ObjectAt(aIndex);
+ if (rule) {
+ mInner->mOrderedRules.RemoveObjectAt(aIndex);
+ if (mDocument && mDocument->StyleSheetChangeEventsEnabled()) {
+ // Force creation of the DOM rule, so that it can be put on the
+ // StyleRuleRemoved event object.
+ rule->GetDOMRule();
+ }
+ rule->SetStyleSheet(nullptr);
+ DidDirty();
+
+ if (mDocument) {
+ mDocument->StyleRuleRemoved(this, rule);
+ }
+ }
+}
+
+nsresult
+CSSStyleSheet::DeleteRuleFromGroup(css::GroupRule* aGroup, uint32_t aIndex)
+{
+ NS_ENSURE_ARG_POINTER(aGroup);
+ NS_ASSERTION(mInner->mComplete, "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;
+ }
+
+ mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
+
+ WillDirty();
+
+ nsresult result = aGroup->DeleteStyleRuleAt(aIndex);
+ NS_ENSURE_SUCCESS(result, result);
+
+ rule->SetStyleSheet(nullptr);
+
+ DidDirty();
+
+ if (mDocument) {
+ mDocument->StyleRuleRemoved(this, rule);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CSSStyleSheet::InsertRuleIntoGroup(const nsAString & aRule,
+ css::GroupRule* aGroup,
+ uint32_t aIndex,
+ uint32_t* _retval)
+{
+ NS_ASSERTION(mInner->mComplete, "No inserting into an incomplete sheet!");
+ // check that the group actually belongs to this sheet!
+ if (this != aGroup->GetStyleSheet()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Hold strong ref to the CSSLoader in case the document update
+ // kills the document
+ RefPtr<css::Loader> loader;
+ if (mDocument) {
+ loader = mDocument->CSSLoader();
+ NS_ASSERTION(loader, "Document with no CSS loader!");
+ }
+
+ nsCSSParser css(loader, this);
+
+ // parse and grab the rule
+ mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
+
+ WillDirty();
+
+ RefPtr<css::Rule> rule;
+ nsresult result = css.ParseRule(aRule, mInner->mSheetURI, mInner->mBaseURI,
+ mInner->mPrincipal, getter_AddRefs(rule));
+ if (NS_FAILED(result))
+ return result;
+
+ switch (rule->GetType()) {
+ case css::Rule::STYLE_RULE:
+ case css::Rule::MEDIA_RULE:
+ case css::Rule::FONT_FACE_RULE:
+ case css::Rule::PAGE_RULE:
+ case css::Rule::KEYFRAMES_RULE:
+ case css::Rule::COUNTER_STYLE_RULE:
+ case css::Rule::DOCUMENT_RULE:
+ case css::Rule::SUPPORTS_RULE:
+ // these types are OK to insert into a group
+ break;
+ case css::Rule::CHARSET_RULE:
+ case css::Rule::IMPORT_RULE:
+ case css::Rule::NAMESPACE_RULE:
+ // these aren't
+ return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
+ default:
+ NS_NOTREACHED("unexpected rule type");
+ return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
+ }
+
+ result = aGroup->InsertStyleRuleAt(aIndex, rule);
+ NS_ENSURE_SUCCESS(result, result);
+ DidDirty();
+
+ if (mDocument) {
+ mDocument->StyleRuleAdded(this, rule);
+ }
+
+ *_retval = aIndex;
+ return NS_OK;
+}
+
+// nsICSSLoaderObserver implementation
+NS_IMETHODIMP
+CSSStyleSheet::StyleSheetLoaded(StyleSheet* aSheet,
+ bool aWasAlternate,
+ nsresult aStatus)
+{
+ MOZ_ASSERT(aSheet->IsGecko(),
+ "why we were called back with a ServoStyleSheet?");
+
+ CSSStyleSheet* sheet = aSheet->AsGecko();
+
+ if (sheet->GetParentSheet() == nullptr) {
+ return NS_OK; // ignore if sheet has been detached already (see parseSheet)
+ }
+ NS_ASSERTION(this == sheet->GetParentSheet(),
+ "We are being notified of a sheet load for a sheet that is not our child!");
+
+ if (mDocument && NS_SUCCEEDED(aStatus)) {
+ mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
+
+ // XXXldb @import rules shouldn't even implement nsIStyleRule (but
+ // they do)!
+ mDocument->StyleRuleAdded(this, sheet->GetOwnerRule());
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CSSStyleSheet::ReparseSheet(const nsAString& aInput)
+{
+ // Not doing this if the sheet is not complete!
+ if (!mInner->mComplete) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ // Hold strong ref to the CSSLoader in case the document update
+ // kills the document
+ RefPtr<css::Loader> loader;
+ if (mDocument) {
+ loader = mDocument->CSSLoader();
+ NS_ASSERTION(loader, "Document with no CSS loader!");
+ } else {
+ loader = new css::Loader(StyleBackendType::Gecko);
+ }
+
+ mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
+
+ WillDirty();
+
+ // detach existing rules (including child sheets via import rules)
+ css::LoaderReusableStyleSheets reusableSheets;
+ int ruleCount;
+ while ((ruleCount = mInner->mOrderedRules.Count()) != 0) {
+ RefPtr<css::Rule> rule = mInner->mOrderedRules.ObjectAt(ruleCount - 1);
+ mInner->mOrderedRules.RemoveObjectAt(ruleCount - 1);
+ rule->SetStyleSheet(nullptr);
+ if (rule->GetType() == css::Rule::IMPORT_RULE) {
+ nsCOMPtr<nsIDOMCSSImportRule> importRule(do_QueryInterface(rule));
+ NS_ASSERTION(importRule, "GetType lied");
+
+ nsCOMPtr<nsIDOMCSSStyleSheet> childSheet;
+ importRule->GetStyleSheet(getter_AddRefs(childSheet));
+
+ RefPtr<CSSStyleSheet> cssSheet = do_QueryObject(childSheet);
+ if (cssSheet && cssSheet->GetOriginalURI()) {
+ reusableSheets.AddReusableSheet(cssSheet);
+ }
+ }
+ if (mDocument) {
+ mDocument->StyleRuleRemoved(this, rule);
+ }
+ }
+
+ // nuke child sheets list and current namespace map
+ for (CSSStyleSheet* child = mInner->mFirstChild; child; ) {
+ NS_ASSERTION(child->mParent == this, "Child sheet is not parented to this!");
+ CSSStyleSheet* next = child->mNext;
+ child->mParent = nullptr;
+ child->mDocument = nullptr;
+ child->mNext = nullptr;
+ child = next;
+ }
+ mInner->mFirstChild = nullptr;
+ mInner->mNameSpaceMap = nullptr;
+
+ uint32_t lineNumber = 1;
+ if (mOwningNode) {
+ nsCOMPtr<nsIStyleSheetLinkingElement> link = do_QueryInterface(mOwningNode);
+ if (link) {
+ lineNumber = link->GetLineNumber();
+ }
+ }
+
+ nsCSSParser parser(loader, this);
+ nsresult rv = parser.ParseSheet(aInput, mInner->mSheetURI, mInner->mBaseURI,
+ mInner->mPrincipal, lineNumber, &reusableSheets);
+ DidDirty(); // we are always 'dirty' here since we always remove rules first
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // notify document of all new rules
+ if (mDocument) {
+ for (int32_t index = 0; index < mInner->mOrderedRules.Count(); ++index) {
+ RefPtr<css::Rule> rule = mInner->mOrderedRules.ObjectAt(index);
+ if (rule->GetType() == css::Rule::IMPORT_RULE &&
+ RuleHasPendingChildSheet(rule)) {
+ continue; // notify when loaded (see StyleSheetLoaded)
+ }
+ mDocument->StyleRuleAdded(this, rule);
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/layout/style/CSSStyleSheet.h b/layout/style/CSSStyleSheet.h
new file mode 100644
index 000000000..74e12291e
--- /dev/null
+++ b/layout/style/CSSStyleSheet.h
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:tabstop=2:expandtab:shiftwidth=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/. */
+
+/* representation of a CSS style sheet */
+
+#ifndef mozilla_CSSStyleSheet_h
+#define mozilla_CSSStyleSheet_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/IncrementalClearCOMRuleArray.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInfo.h"
+#include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/dom/Element.h"
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsString.h"
+#include "mozilla/CORSMode.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/dom/SRIMetadata.h"
+
+class CSSRuleListImpl;
+class nsCSSRuleProcessor;
+class nsIURI;
+class nsMediaList;
+class nsMediaQueryResultCacheKey;
+class nsStyleSet;
+class nsPresContext;
+class nsXMLNameSpaceMap;
+
+namespace mozilla {
+struct ChildSheetListBuilder;
+class CSSStyleSheet;
+
+namespace css {
+class Rule;
+class GroupRule;
+class ImportRule;
+} // namespace css
+namespace dom {
+class CSSRuleList;
+} // namespace dom
+
+ // -------------------------------
+// CSS Style Sheet Inner Data Container
+//
+
+struct CSSStyleSheetInner : public StyleSheetInfo
+{
+ CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const dom::SRIMetadata& aIntegrity);
+ CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
+ CSSStyleSheet* aPrimarySheet);
+ ~CSSStyleSheetInner();
+
+ CSSStyleSheetInner* CloneFor(CSSStyleSheet* aPrimarySheet);
+ void AddSheet(CSSStyleSheet* aSheet);
+ void RemoveSheet(CSSStyleSheet* aSheet);
+
+ void RebuildNameSpaces();
+
+ // Create a new namespace map
+ nsresult CreateNamespaceMap();
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ AutoTArray<CSSStyleSheet*, 8> mSheets;
+ IncrementalClearCOMRuleArray mOrderedRules;
+ nsAutoPtr<nsXMLNameSpaceMap> mNameSpaceMap;
+ // Linked list of child sheets. This is al 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 ts hands on a
+ // child sheet that means we've already ensured unique inners throughout its
+ // parent chain and things are good.
+ RefPtr<CSSStyleSheet> mFirstChild;
+};
+
+
+// -------------------------------
+// CSS Style Sheet
+//
+
+// CID for the CSSStyleSheet class
+// 7985c7ac-9ddc-444d-9899-0c86ec122f54
+#define NS_CSS_STYLE_SHEET_IMPL_CID \
+{ 0x7985c7ac, 0x9ddc, 0x444d, \
+ { 0x98, 0x99, 0x0c, 0x86, 0xec, 0x12, 0x2f, 0x54 } }
+
+
+class CSSStyleSheet final : public StyleSheet
+ , public nsICSSLoaderObserver
+{
+public:
+ typedef net::ReferrerPolicy ReferrerPolicy;
+ CSSStyleSheet(css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy);
+ CSSStyleSheet(css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy,
+ const dom::SRIMetadata& aIntegrity);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CSSStyleSheet, StyleSheet)
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_CSS_STYLE_SHEET_IMPL_CID)
+
+ bool HasRules() const;
+
+ /**
+ * Set the stylesheet to be enabled. This may or may not make it
+ * applicable. Note that this WILL inform the sheet's document of
+ * its new applicable state if the state changes but WILL NOT call
+ * BeginUpdate() or EndUpdate() on the document -- calling those is
+ * the caller's responsibility. This allows use of SetEnabled when
+ * batched updates are desired. If you want updates handled for
+ * you, see nsIDOMStyleSheet::SetDisabled().
+ */
+ void SetEnabled(bool aEnabled);
+
+ // style sheet owner info
+ CSSStyleSheet* GetParentSheet() const; // may be null
+ void SetOwningDocument(nsIDocument* aDocument);
+
+ // Find the ID of the owner inner window.
+ uint64_t FindOwningWindowInnerID() const;
+#ifdef DEBUG
+ void List(FILE* out = stdout, int32_t aIndent = 0) const;
+#endif
+
+ void AppendStyleSheet(CSSStyleSheet* aSheet);
+
+ // XXX do these belong here or are they generic?
+ void AppendStyleRule(css::Rule* aRule);
+
+ int32_t StyleRuleCount() const;
+ css::Rule* GetStyleRuleAt(int32_t aIndex) const;
+
+ nsresult DeleteRuleFromGroup(css::GroupRule* aGroup, uint32_t aIndex);
+ nsresult InsertRuleIntoGroup(const nsAString& aRule, css::GroupRule* aGroup, uint32_t aIndex, uint32_t* _retval);
+
+ void SetTitle(const nsAString& aTitle) { mTitle = aTitle; }
+ void SetMedia(nsMediaList* aMedia);
+
+ void SetOwnerRule(css::ImportRule* aOwnerRule) { mOwnerRule = aOwnerRule; /* Not ref counted */ }
+ css::ImportRule* GetOwnerRule() const { return mOwnerRule; }
+ // Workaround overloaded-virtual warning in GCC.
+ using StyleSheet::GetOwnerRule;
+
+ nsXMLNameSpaceMap* GetNameSpaceMap() const { return mInner->mNameSpaceMap; }
+
+ already_AddRefed<CSSStyleSheet> Clone(CSSStyleSheet* aCloneParent,
+ css::ImportRule* aCloneOwnerRule,
+ nsIDocument* aCloneDocument,
+ nsINode* aCloneOwningNode) const;
+
+ bool IsModified() const { return mDirty; }
+
+ void SetModifiedByChildRule() {
+ NS_ASSERTION(mDirty,
+ "sheet must be marked dirty before handing out child rules");
+ DidDirty();
+ }
+
+ nsresult AddRuleProcessor(nsCSSRuleProcessor* aProcessor);
+ nsresult DropRuleProcessor(nsCSSRuleProcessor* aProcessor);
+
+ void AddStyleSet(nsStyleSet* aStyleSet);
+ void DropStyleSet(nsStyleSet* aStyleSet);
+
+ // nsICSSLoaderObserver interface
+ NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasAlternate,
+ nsresult aStatus) override;
+
+ void EnsureUniqueInner();
+
+ // Append all of this sheet's child sheets to aArray.
+ void AppendAllChildSheets(nsTArray<CSSStyleSheet*>& aArray);
+
+ bool UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey) const;
+
+ nsresult ReparseSheet(const nsAString& aInput);
+
+ void SetInRuleProcessorCache() { mInRuleProcessorCache = true; }
+
+ // Function used as a callback to rebuild our inner's child sheet
+ // list after we clone a unique inner for ourselves.
+ static bool RebuildChildList(css::Rule* aRule, void* aBuilder);
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ dom::Element* GetScopeElement() const { return mScopeElement; }
+ void SetScopeElement(dom::Element* aScopeElement)
+ {
+ mScopeElement = aScopeElement;
+ }
+
+ // WebIDL StyleSheet API
+ nsMediaList* Media() final;
+
+ // 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.
+ nsIDOMCSSRule* GetDOMOwnerRule() const final;
+
+ void WillDirty();
+ void DidDirty();
+
+private:
+ CSSStyleSheet(const CSSStyleSheet& aCopy,
+ CSSStyleSheet* aParentToUse,
+ css::ImportRule* aOwnerRuleToUse,
+ nsIDocument* aDocumentToUse,
+ nsINode* aOwningNodeToUse);
+
+ CSSStyleSheet(const CSSStyleSheet& aCopy) = delete;
+ CSSStyleSheet& operator=(const CSSStyleSheet& aCopy) = delete;
+
+protected:
+ virtual ~CSSStyleSheet();
+
+ void ClearRuleCascades();
+
+ // Add the namespace mapping from this @namespace rule to our namespace map
+ nsresult RegisterNamespaceRule(css::Rule* aRule);
+
+ // Drop our reference to mRuleCollection
+ void DropRuleCollection();
+
+ // Drop our reference to mMedia
+ void DropMedia();
+
+ // Unlink our inner, if needed, for cycle collection
+ void UnlinkInner();
+ // Traverse our inner, if needed, for cycle collection
+ void TraverseInner(nsCycleCollectionTraversalCallback &);
+
+protected:
+ // Internal methods which do not have security check and completeness check.
+ dom::CSSRuleList* GetCssRulesInternal(ErrorResult& aRv);
+ uint32_t InsertRuleInternal(const nsAString& aRule,
+ uint32_t aIndex, ErrorResult& aRv);
+ void DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv);
+
+ RefPtr<nsMediaList> mMedia;
+ RefPtr<CSSStyleSheet> mNext;
+ CSSStyleSheet* mParent; // weak ref
+ css::ImportRule* mOwnerRule; // weak ref
+
+ RefPtr<CSSRuleListImpl> mRuleCollection;
+ bool mDirty; // has been modified
+ bool mInRuleProcessorCache;
+ RefPtr<dom::Element> mScopeElement;
+
+ CSSStyleSheetInner* mInner;
+
+ AutoTArray<nsCSSRuleProcessor*, 8>* mRuleProcessors;
+ nsTArray<nsStyleSet*> mStyleSets;
+
+ friend class ::nsMediaList;
+ friend class ::nsCSSRuleProcessor;
+ friend class mozilla::StyleSheet;
+ friend struct mozilla::ChildSheetListBuilder;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CSSStyleSheet, NS_CSS_STYLE_SHEET_IMPL_CID)
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_CSSStyleSheet_h) */
diff --git a/layout/style/CSSUnprefixingService.js b/layout/style/CSSUnprefixingService.js
new file mode 100644
index 000000000..801ea198c
--- /dev/null
+++ b/layout/style/CSSUnprefixingService.js
@@ -0,0 +1,342 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Implementation of a service that converts certain vendor-prefixed CSS
+ properties to their unprefixed equivalents, for sites on a whitelist. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function CSSUnprefixingService() {
+}
+
+CSSUnprefixingService.prototype = {
+ // Boilerplate:
+ classID: Components.ID("{f0729490-e15c-4a2f-a3fb-99e1cc946b42}"),
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(CSSUnprefixingService),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICSSUnprefixingService]),
+
+ // See documentation in nsICSSUnprefixingService.idl
+ generateUnprefixedDeclaration: function(aPropName, aRightHalfOfDecl,
+ aUnprefixedDecl /*out*/) {
+
+ // Convert our input strings to lower-case, for easier string-matching.
+ // (NOTE: If we ever need to add support for unprefixing properties that
+ // have case-sensitive parts, then we should do these toLowerCase()
+ // conversions in a more targeted way, to avoid breaking those properties.)
+ aPropName = aPropName.toLowerCase();
+ aRightHalfOfDecl = aRightHalfOfDecl.toLowerCase();
+
+ // We have several groups of supported properties:
+ // FIRST GROUP: Properties that can just be handled as aliases:
+ // ============================================================
+ const propertiesThatAreJustAliases = {
+ "-webkit-background-size": "background-size",
+ "-webkit-box-flex": "flex-grow",
+ "-webkit-box-ordinal-group": "order",
+ "-webkit-box-sizing": "box-sizing",
+ "-webkit-transform": "transform",
+ "-webkit-transform-origin": "transform-origin",
+ };
+
+ let unprefixedPropName = propertiesThatAreJustAliases[aPropName];
+ if (unprefixedPropName !== undefined) {
+ aUnprefixedDecl.value = unprefixedPropName + ":" + aRightHalfOfDecl;
+ return true;
+ }
+
+ // SECOND GROUP: Properties that take a single keyword, where the
+ // unprefixed version takes a different (but analogous) set of keywords:
+ // =====================================================================
+ const propertiesThatNeedKeywordMapping = {
+ "-webkit-box-align" : {
+ unprefixedPropName : "align-items",
+ valueMap : {
+ "start" : "flex-start",
+ "center" : "center",
+ "end" : "flex-end",
+ "baseline" : "baseline",
+ "stretch" : "stretch"
+ }
+ },
+ "-webkit-box-orient" : {
+ unprefixedPropName : "flex-direction",
+ valueMap : {
+ "horizontal" : "row",
+ "inline-axis" : "row",
+ "vertical" : "column",
+ "block-axis" : "column"
+ }
+ },
+ "-webkit-box-pack" : {
+ unprefixedPropName : "justify-content",
+ valueMap : {
+ "start" : "flex-start",
+ "center" : "center",
+ "end" : "flex-end",
+ "justify" : "space-between"
+ }
+ },
+ };
+
+ let propInfo = propertiesThatNeedKeywordMapping[aPropName];
+ if (typeof(propInfo) != "undefined") {
+ // Regexp for parsing the right half of a declaration, for keyword-valued
+ // properties. Divides the right half of the declaration into:
+ // 1) any leading whitespace
+ // 2) the property value (one or more alphabetical character or hyphen)
+ // 3) anything after that (e.g. "!important", ";")
+ // Then we can look up the appropriate unprefixed-property value for the
+ // value (part 2), and splice that together with the other parts and with
+ // the unprefixed property-name to make the final declaration.
+ const keywordValuedPropertyRegexp = /^(\s*)([a-z\-]+)(.*)/;
+ let parts = keywordValuedPropertyRegexp.exec(aRightHalfOfDecl);
+ if (!parts) {
+ // Failed to parse a keyword out of aRightHalfOfDecl. (It probably has
+ // no alphabetical characters.)
+ return false;
+ }
+
+ let mappedKeyword = propInfo.valueMap[parts[2]];
+ if (mappedKeyword === undefined) {
+ // We found a keyword in aRightHalfOfDecl, but we don't have a mapping
+ // to an equivalent keyword for the unprefixed version of the property.
+ return false;
+ }
+
+ aUnprefixedDecl.value = propInfo.unprefixedPropName + ":" +
+ parts[1] + // any leading whitespace
+ mappedKeyword +
+ parts[3]; // any trailing text (e.g. !important, semicolon, etc)
+
+ return true;
+ }
+
+ // THIRD GROUP: Properties that may need arbitrary string-replacement:
+ // ===================================================================
+ const propertiesThatNeedStringReplacement = {
+ // "-webkit-transition" takes a multi-part value. If "-webkit-transform"
+ // appears as part of that value, replace it w/ "transform".
+ // And regardless, we unprefix the "-webkit-transition" property-name.
+ // (We could handle other prefixed properties in addition to 'transform'
+ // here, but in practice "-webkit-transform" is the main one that's
+ // likely to be transitioned & that we're concerned about supporting.)
+ "-webkit-transition": {
+ unprefixedPropName : "transition",
+ stringMap : {
+ "-webkit-transform" : "transform",
+ }
+ },
+ };
+
+ propInfo = propertiesThatNeedStringReplacement[aPropName];
+ if (typeof(propInfo) != "undefined") {
+ let newRightHalf = aRightHalfOfDecl;
+ for (let strToReplace in propInfo.stringMap) {
+ let replacement = propInfo.stringMap[strToReplace];
+ newRightHalf = newRightHalf.split(strToReplace).join(replacement);
+ }
+ aUnprefixedDecl.value = propInfo.unprefixedPropName + ":" + newRightHalf;
+
+ return true;
+ }
+
+ // No known mapping for property aPropName.
+ return false;
+ },
+
+ // See documentation in nsICSSUnprefixingService.idl
+ generateUnprefixedGradientValue: function(aPrefixedFuncName,
+ aPrefixedFuncBody,
+ aUnprefixedFuncName, /*[out]*/
+ aUnprefixedFuncBody /*[out]*/) {
+ var unprefixedFuncName, newValue;
+ if (aPrefixedFuncName == "-webkit-gradient") {
+ // Create expression for oldGradientParser:
+ var parts = this.oldGradientParser(aPrefixedFuncBody);
+ var type = parts[0].name;
+ newValue = this.standardizeOldGradientArgs(type, parts.slice(1));
+ unprefixedFuncName = type + "-gradient";
+ }else{ // we're dealing with more modern syntax - should be somewhat easier, at least for linear gradients.
+ // Fix three things: remove -webkit-, add 'to ' before reversed top/bottom keywords (linear) or 'at ' before position keywords (radial), recalculate deg-values
+ // -webkit-linear-gradient( [ [ <angle> | [top | bottom] || [left | right] ],]? <color-stop>[, <color-stop>]+);
+ if (aPrefixedFuncName != "-webkit-linear-gradient" &&
+ aPrefixedFuncName != "-webkit-radial-gradient") {
+ // Unrecognized prefixed gradient type
+ return false;
+ }
+ unprefixedFuncName = aPrefixedFuncName.replace(/-webkit-/, '');
+
+ // Keywords top, bottom, left, right: can be stand-alone or combined pairwise but in any order ('top left' or 'left top')
+ // These give the starting edge or corner in the -webkit syntax. The standardised equivalent is 'to ' plus opposite values for linear gradients, 'at ' plus same values for radial gradients
+ if(unprefixedFuncName.indexOf('linear') > -1){
+ newValue = aPrefixedFuncBody.replace(/(top|bottom|left|right)+\s*(top|bottom|left|right)*/, function(str){
+ var words = str.split(/\s+/);
+ for(var i=0; i<words.length; i++){
+ switch(words[i].toLowerCase()){
+ case 'top':
+ words[i] = 'bottom';
+ break;
+ case 'bottom':
+ words[i] = 'top';
+ break;
+ case 'left':
+ words[i] = 'right';
+ break;
+ case 'right':
+ words[i] = 'left';
+ }
+ }
+ str = words.join(' ');
+ return ( 'to ' + str);
+ });
+ }else{
+ newValue = aPrefixedFuncBody.replace(/(top|bottom|left|right)+\s/, 'at $1 ');
+ }
+
+ newValue = newValue.replace(/\d+deg/, function (val) {
+ return (360 - (parseInt(val)-90))+'deg';
+ });
+
+ }
+ aUnprefixedFuncName.value = unprefixedFuncName;
+ aUnprefixedFuncBody.value = newValue;
+ return true;
+ },
+
+ // Helpers for generateUnprefixedGradientValue():
+ // ----------------------------------------------
+ oldGradientParser : function(str){
+ /** This method takes a legacy -webkit-gradient() method call and parses it
+ to pull out the values, function names and their arguments.
+ It returns something like [{name:'-webkit-gradient',args:[{name:'linear'}, {name:'top left'} ... ]}]
+ */
+ var objs = [{}], path=[], current, word='', separator_chars = [',', '(', ')'];
+ current = objs[0], path[0] = objs;
+ //str = str.replace(/\s*\(/g, '('); // sorry, ws in front of ( would make parsing a lot harder
+ for(var i = 0; i < str.length; i++){
+ if(separator_chars.indexOf(str[i]) === -1){
+ word += str[i];
+ }else{ // now we have a "separator" - presumably we've also got a "word" or value
+ current.name = word.trim();
+ //GM_log(word+' '+path.length+' '+str[i])
+ word = '';
+ if(str[i] === '('){ // we assume the 'word' is a function, for example -webkit-gradient() or rgb(), so we create a place to record the arguments
+ if(!('args' in current)){
+ current.args = [];
+ }
+ current.args.push({});
+ path.push(current.args);
+ current = current.args[current.args.length - 1];
+ path.push(current);
+ }else if(str[i] === ')'){ // function is ended, no more arguments - go back to appending details to the previous branch of the tree
+ current = path.pop(); // drop 'current'
+ current = path.pop(); // drop 'args' reference
+ }else{
+ path.pop(); // remove 'current' object from path, we have no arguments to add
+ var current_parent = path[path.length - 1] || objs; // last object on current path refers to array that contained the previous "current"
+ current_parent.push({}); // we need a new object to hold this "word" or value
+ current = current_parent[current_parent.length - 1]; // that object is now the 'current'
+ path.push(current);
+//GM_log(path.length)
+ }
+ }
+ }
+
+ return objs;
+ },
+
+ /* Given an array of args for "-webkit-gradient(...)" returned by
+ * oldGradientParser(), this function constructs a string representing the
+ * equivalent arguments for a standard "linear-gradient(...)" or
+ * "radial-gradient(...)" expression.
+ *
+ * @param type Either 'linear' or 'radial'.
+ * @param args An array of args for a "-webkit-gradient(...)" expression,
+ * provided by oldGradientParser() (not including gradient type).
+ */
+ standardizeOldGradientArgs : function(type, args){
+ var stdArgStr = "";
+ var stops = [];
+ if(/^linear/.test(type)){
+ // linear gradient, args 1 and 2 tend to be start/end keywords
+ var points = [].concat(args[0].name.split(/\s+/), args[1].name.split(/\s+/)); // example: [left, top, right, top]
+ // Old webkit syntax "uses a two-point syntax that lets you explicitly state where a linear gradient starts and ends"
+ // if start/end keywords are percentages, let's massage the values a little more..
+ var rxPercTest = /\d+\%/;
+ if(rxPercTest.test(points[0]) || points[0] == 0){
+ var startX = parseInt(points[0]), startY = parseInt(points[1]), endX = parseInt(points[2]), endY = parseInt(points[3]);
+ stdArgStr += ((Math.atan2(endY- startY, endX - startX)) * (180 / Math.PI)+90) + 'deg';
+ }else{
+ if(points[1] === points[3]){ // both 'top' or 'bottom, this linear gradient goes left-right
+ stdArgStr += 'to ' + points[2];
+ }else if(points[0] === points[2]){ // both 'left' or 'right', this linear gradient goes top-bottom
+ stdArgStr += 'to ' + points[3];
+ }else if(points[1] === 'top'){ // diagonal gradient - from top left to opposite corner is 135deg
+ stdArgStr += '135deg';
+ }else{
+ stdArgStr += '45deg';
+ }
+ }
+
+ }else if(/^radial/i.test(type)){ // oooh, radial gradients..
+ stdArgStr += 'circle ' + args[3].name.replace(/(\d+)$/, '$1px') + ' at ' + args[0].name.replace(/(\d+) /, '$1px ').replace(/(\d+)$/, '$1px');
+ }
+
+ var toColor;
+ for(var j = type === 'linear' ? 2 : 4; j < args.length; j++){
+ var position, color, colorIndex;
+ if(args[j].name === 'color-stop'){
+ position = args[j].args[0].name;
+ colorIndex = 1;
+ }else if (args[j].name === 'to') {
+ position = '100%';
+ colorIndex = 0;
+ }else if (args[j].name === 'from') {
+ position = '0%';
+ colorIndex = 0;
+ };
+ if (position.indexOf('%') === -1) { // original Safari syntax had 0.5 equivalent to 50%
+ position = (parseFloat(position) * 100) +'%';
+ };
+ color = args[j].args[colorIndex].name;
+ if (args[j].args[colorIndex].args) { // the color is itself a function call, like rgb()
+ color += '(' + this.colorValue(args[j].args[colorIndex].args) + ')';
+ };
+ if (args[j].name === 'from'){
+ stops.unshift(color + ' ' + position);
+ }else if(args[j].name === 'to'){
+ toColor = color;
+ }else{
+ stops.push(color + ' ' + position);
+ }
+ }
+
+ // translating values to right syntax
+ for(var j = 0; j < stops.length; j++){
+ stdArgStr += ', ' + stops[j];
+ }
+ if(toColor){
+ stdArgStr += ', ' + toColor + ' 100%';
+ }
+ return stdArgStr;
+ },
+
+ colorValue: function(obj){
+ var ar = [];
+ for (var i = 0; i < obj.length; i++) {
+ ar.push(obj[i].name);
+ };
+ return ar.join(', ');
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CSSUnprefixingService]);
diff --git a/layout/style/CSSUnprefixingService.manifest b/layout/style/CSSUnprefixingService.manifest
new file mode 100644
index 000000000..08143d4ab
--- /dev/null
+++ b/layout/style/CSSUnprefixingService.manifest
@@ -0,0 +1,2 @@
+component {f0729490-e15c-4a2f-a3fb-99e1cc946b42} CSSUnprefixingService.js
+contract @mozilla.org/css-unprefixing-service;1 {f0729490-e15c-4a2f-a3fb-99e1cc946b42}
diff --git a/layout/style/CSSValue.h b/layout/style/CSSValue.h
new file mode 100644
index 000000000..ecba05e84
--- /dev/null
+++ b/layout/style/CSSValue.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsWrapperCache.h"
+#include "nsStringFwd.h"
+
+class nsROCSSPrimitiveValue;
+namespace mozilla {
+class ErrorResult;
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * CSSValue - a DOM object representing values in DOM computed style.
+ */
+class CSSValue : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ // CSSValue
+ virtual void GetCssText(nsString& aText, mozilla::ErrorResult& aRv) = 0;
+ virtual void SetCssText(const nsAString& aText, mozilla::ErrorResult& aRv) = 0;
+ virtual uint16_t CssValueType() const = 0;
+
+ // Downcasting
+
+ /**
+ * Return this as a nsROCSSPrimitiveValue* if its a primitive value, and null
+ * otherwise.
+ */
+ nsROCSSPrimitiveValue *AsPrimitiveValue();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/CSSVariableDeclarations.cpp b/layout/style/CSSVariableDeclarations.cpp
new file mode 100644
index 000000000..67bb8d732
--- /dev/null
+++ b/layout/style/CSSVariableDeclarations.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* CSS Custom Property assignments for a Declaration at a given priority */
+
+#include "CSSVariableDeclarations.h"
+
+#include "CSSVariableResolver.h"
+#include "nsCSSScanner.h"
+#include "nsRuleData.h"
+
+// These three special string values are used to represent specified values of
+// 'initial', 'inherit' and 'unset'. (Note that none of these are valid
+// variable values.)
+#define INITIAL_VALUE "!"
+#define INHERIT_VALUE ";"
+#define UNSET_VALUE ")"
+
+namespace mozilla {
+
+CSSVariableDeclarations::CSSVariableDeclarations()
+{
+ MOZ_COUNT_CTOR(CSSVariableDeclarations);
+}
+
+CSSVariableDeclarations::CSSVariableDeclarations(const CSSVariableDeclarations& aOther)
+{
+ MOZ_COUNT_CTOR(CSSVariableDeclarations);
+ CopyVariablesFrom(aOther);
+}
+
+#ifdef DEBUG
+CSSVariableDeclarations::~CSSVariableDeclarations()
+{
+ MOZ_COUNT_DTOR(CSSVariableDeclarations);
+}
+#endif
+
+CSSVariableDeclarations&
+CSSVariableDeclarations::operator=(const CSSVariableDeclarations& aOther)
+{
+ if (this == &aOther) {
+ return *this;
+ }
+
+ mVariables.Clear();
+ CopyVariablesFrom(aOther);
+ return *this;
+}
+
+void
+CSSVariableDeclarations::CopyVariablesFrom(const CSSVariableDeclarations& aOther)
+{
+ for (auto iter = aOther.mVariables.ConstIter(); !iter.Done(); iter.Next()) {
+ mVariables.Put(iter.Key(), iter.UserData());
+ }
+}
+
+bool
+CSSVariableDeclarations::Has(const nsAString& aName) const
+{
+ nsString value;
+ return mVariables.Get(aName, &value);
+}
+
+bool
+CSSVariableDeclarations::Get(const nsAString& aName,
+ Type& aType,
+ nsString& aTokenStream) const
+{
+ nsString value;
+ if (!mVariables.Get(aName, &value)) {
+ return false;
+ }
+ if (value.EqualsLiteral(INITIAL_VALUE)) {
+ aType = eInitial;
+ aTokenStream.Truncate();
+ } else if (value.EqualsLiteral(INHERIT_VALUE)) {
+ aType = eInitial;
+ aTokenStream.Truncate();
+ } else if (value.EqualsLiteral(UNSET_VALUE)) {
+ aType = eUnset;
+ aTokenStream.Truncate();
+ } else {
+ aType = eTokenStream;
+ aTokenStream = value;
+ }
+ return true;
+}
+
+void
+CSSVariableDeclarations::PutTokenStream(const nsAString& aName,
+ const nsString& aTokenStream)
+{
+ MOZ_ASSERT(!aTokenStream.EqualsLiteral(INITIAL_VALUE) &&
+ !aTokenStream.EqualsLiteral(INHERIT_VALUE) &&
+ !aTokenStream.EqualsLiteral(UNSET_VALUE));
+ mVariables.Put(aName, aTokenStream);
+}
+
+void
+CSSVariableDeclarations::PutInitial(const nsAString& aName)
+{
+ mVariables.Put(aName, NS_LITERAL_STRING(INITIAL_VALUE));
+}
+
+void
+CSSVariableDeclarations::PutInherit(const nsAString& aName)
+{
+ mVariables.Put(aName, NS_LITERAL_STRING(INHERIT_VALUE));
+}
+
+void
+CSSVariableDeclarations::PutUnset(const nsAString& aName)
+{
+ mVariables.Put(aName, NS_LITERAL_STRING(UNSET_VALUE));
+}
+
+void
+CSSVariableDeclarations::Remove(const nsAString& aName)
+{
+ mVariables.Remove(aName);
+}
+
+void
+CSSVariableDeclarations::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ if (!(aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Variables))) {
+ return;
+ }
+
+ if (!aRuleData->mVariables) {
+ aRuleData->mVariables = new CSSVariableDeclarations(*this);
+ } else {
+ for (auto iter = mVariables.Iter(); !iter.Done(); iter.Next()) {
+ nsDataHashtable<nsStringHashKey, nsString>& variables =
+ aRuleData->mVariables->mVariables;
+ const nsAString& aName = iter.Key();
+ if (!variables.Contains(aName)) {
+ variables.Put(aName, iter.UserData());
+ }
+ }
+ }
+}
+
+void
+CSSVariableDeclarations::AddVariablesToResolver(
+ CSSVariableResolver* aResolver) const
+{
+ for (auto iter = mVariables.ConstIter(); !iter.Done(); iter.Next()) {
+ const nsAString& name = iter.Key();
+ nsString value = iter.UserData();
+ if (value.EqualsLiteral(INITIAL_VALUE)) {
+ // Values of 'initial' are treated the same as an invalid value in the
+ // variable resolver.
+ aResolver->Put(name, EmptyString(),
+ eCSSTokenSerialization_Nothing,
+ eCSSTokenSerialization_Nothing,
+ false);
+ } else if (value.EqualsLiteral(INHERIT_VALUE) ||
+ value.EqualsLiteral(UNSET_VALUE)) {
+ // Values of 'inherit' and 'unset' don't need any handling, since it means
+ // we just need to keep whatever value is currently in the resolver. This
+ // is because the specified variable declarations already have only the
+ // winning declaration for the variable and no longer have any of the
+ // others.
+ } else {
+ // At this point, we don't know what token types are at the start and end
+ // of the specified variable value. These will be determined later during
+ // the resolving process.
+ aResolver->Put(name, value,
+ eCSSTokenSerialization_Nothing,
+ eCSSTokenSerialization_Nothing,
+ false);
+ }
+ }
+}
+
+size_t
+CSSVariableDeclarations::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ n += mVariables.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mVariables.ConstIter(); !iter.Done(); iter.Next()) {
+ n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ n += iter.Data().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+ return n;
+}
+
+} // namespace mozilla
diff --git a/layout/style/CSSVariableDeclarations.h b/layout/style/CSSVariableDeclarations.h
new file mode 100644
index 000000000..ebc17285b
--- /dev/null
+++ b/layout/style/CSSVariableDeclarations.h
@@ -0,0 +1,139 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* CSS Custom Property assignments for a Declaration at a given priority */
+
+#ifndef mozilla_CSSVariableDeclarations_h
+#define mozilla_CSSVariableDeclarations_h
+
+#include "nsDataHashtable.h"
+
+namespace mozilla {
+class CSSVariableResolver;
+} // namespace mozilla
+struct nsRuleData;
+
+namespace mozilla {
+
+class CSSVariableDeclarations
+{
+public:
+ CSSVariableDeclarations();
+ CSSVariableDeclarations(const CSSVariableDeclarations& aOther);
+#ifdef DEBUG
+ ~CSSVariableDeclarations();
+#endif
+ CSSVariableDeclarations& operator=(const CSSVariableDeclarations& aOther);
+
+ /**
+ * Returns whether this set of variable declarations includes a variable
+ * with a given name.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name).
+ */
+ bool Has(const nsAString& aName) const;
+
+ /**
+ * Represents the type of a variable value.
+ */
+ enum Type {
+ eTokenStream, // a stream of CSS tokens (the usual type for variables)
+ eInitial, // 'initial'
+ eInherit, // 'inherit'
+ eUnset // 'unset'
+ };
+
+ /**
+ * Gets the value of a variable in this set of variable declarations.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name).
+ * @param aType Out parameter into which the type of the variable value will
+ * be stored.
+ * @param aValue Out parameter into which the value of the variable will
+ * be stored. If the variable is 'initial', 'inherit' or 'unset', this will
+ * be the empty string.
+ * @return Whether a variable with the given name was found. When false
+ * is returned, aType and aValue will not be modified.
+ */
+ bool Get(const nsAString& aName, Type& aType, nsString& aValue) const;
+
+ /**
+ * Adds or modifies an existing entry in this set of variable declarations
+ * to have the value 'initial'.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name) whose value is to be set.
+ */
+ void PutInitial(const nsAString& aName);
+
+ /**
+ * Adds or modifies an existing entry in this set of variable declarations
+ * to have the value 'inherit'.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name) whose value is to be set.
+ */
+ void PutInherit(const nsAString& aName);
+
+ /**
+ * Adds or modifies an existing entry in this set of variable declarations
+ * to have the value 'unset'.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name) whose value is to be set.
+ */
+ void PutUnset(const nsAString& aName);
+
+ /**
+ * Adds or modifies an existing entry in this set of variable declarations
+ * to have a token stream value.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name) whose value is to be set.
+ * @param aTokenStream The CSS token stream.
+ */
+ void PutTokenStream(const nsAString& aName, const nsString& aTokenStream);
+
+ /**
+ * Removes an entry in this set of variable declarations.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name) whose entry is to be removed.
+ */
+ void Remove(const nsAString& aName);
+
+ /**
+ * Returns the number of entries in this set of variable declarations.
+ */
+ uint32_t Count() const { return mVariables.Count(); }
+
+ /**
+ * Copies each variable value from this object into aRuleData, unless that
+ * variable already exists on aRuleData.
+ */
+ void MapRuleInfoInto(nsRuleData* aRuleData);
+
+ /**
+ * Copies the variables from this object into aResolver, marking them as
+ * specified values.
+ */
+ void AddVariablesToResolver(CSSVariableResolver* aResolver) const;
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ /**
+ * Adds all the variable declarations from aOther into this object.
+ */
+ void CopyVariablesFrom(const CSSVariableDeclarations& aOther);
+
+ nsDataHashtable<nsStringHashKey, nsString> mVariables;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/CSSVariableImageTable.h b/layout/style/CSSVariableImageTable.h
new file mode 100644
index 000000000..26c9c7507
--- /dev/null
+++ b/layout/style/CSSVariableImageTable.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* A global table that tracks images referenced by CSS variables. */
+
+#ifndef mozilla_CSSVariableImageTable_h
+#define mozilla_CSSVariableImageTable_h
+
+#include "nsClassHashtable.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSValue.h"
+#include "nsStyleContext.h"
+#include "nsTArray.h"
+
+/**
+ * CSSVariableImageTable maintains a global mapping
+ * (nsStyleContext, nsCSSPropertyID) -> nsTArray<ImageValue>
+ * which allows us to track the relationship between CSS property values
+ * involving variables and any images they may reference.
+ *
+ * When properties like background-image contain a normal url(), the
+ * Declaration's data block will hold a reference to the ImageValue. When a
+ * token stream is used, the Declaration only holds on to an
+ * nsCSSValueTokenStream object, and the ImageValue would only exist for the
+ * duration of nsRuleNode::WalkRuleTree, in the AutoCSSValueArray. So instead
+ * when we re-parse a token stream and get an ImageValue, we record it in the
+ * CSSVariableImageTable to keep the ImageValue alive. Such ImageValues are
+ * eventually freed the next time the token stream is re-parsed, or when the
+ * associated style context is destroyed.
+ *
+ * To add ImageValues to the CSSVariableImageTable, callers should pass a lambda
+ * to CSSVariableImageTable::ReplaceAll() that calls
+ * CSSVariableImageTable::Add() for each ImageValue that needs to be added to
+ * the table. When callers are sure that the ImageValues for a given
+ * nsStyleContext won't be needed anymore, they can call
+ * CSSVariableImageTable::RemoveAll() to release them.
+ */
+
+namespace mozilla {
+namespace CSSVariableImageTable {
+
+namespace detail {
+
+typedef nsTArray<RefPtr<css::ImageValue>> ImageValueArray;
+typedef nsClassHashtable<nsGenericHashKey<nsCSSPropertyID>, ImageValueArray>
+ PerPropertyImageHashtable;
+typedef nsClassHashtable<nsPtrHashKey<nsStyleContext>, PerPropertyImageHashtable>
+ CSSVariableImageHashtable;
+
+inline CSSVariableImageHashtable& GetTable()
+{
+ static CSSVariableImageHashtable imageTable;
+ return imageTable;
+}
+
+#ifdef DEBUG
+inline bool& IsReplacing()
+{
+ static bool isReplacing = false;
+ return isReplacing;
+}
+#endif
+
+} // namespace detail
+
+/**
+ * ReplaceAll() allows callers to replace the ImageValues associated with a
+ * (nsStyleContext, nsCSSPropertyID) pair. The memory used by the previous list of
+ * ImageValues is automatically released.
+ *
+ * @param aContext The style context the ImageValues are associated with.
+ * @param aProp The CSS property the ImageValues are associated with.
+ * @param aFunc A lambda that calls CSSVariableImageTable::Add() to add new
+ * ImageValues which will replace the old ones.
+ */
+template <typename Lambda>
+inline void ReplaceAll(nsStyleContext* aContext,
+ nsCSSPropertyID aProp,
+ Lambda aFunc)
+{
+ MOZ_ASSERT(aContext);
+
+ auto& imageTable = detail::GetTable();
+
+ // Clear the existing image array, if any, for this property.
+ {
+ auto* perPropertyImageTable = imageTable.Get(aContext);
+ auto* imageList = perPropertyImageTable ? perPropertyImageTable->Get(aProp)
+ : nullptr;
+ if (imageList) {
+ imageList->ClearAndRetainStorage();
+ }
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(!detail::IsReplacing());
+ detail::IsReplacing() = true;
+#endif
+
+ aFunc();
+
+#ifdef DEBUG
+ detail::IsReplacing() = false;
+#endif
+
+ // Clean up.
+ auto* perPropertyImageTable = imageTable.Get(aContext);
+ auto* imageList = perPropertyImageTable ? perPropertyImageTable->Get(aProp)
+ : nullptr;
+ if (imageList) {
+ if (imageList->IsEmpty()) {
+ // We used to have an image array for this property, but now we don't.
+ // Remove the entry in the per-property image table for this property.
+ // That may then allow us to remove the entire per-property image table.
+ perPropertyImageTable->Remove(aProp);
+ if (perPropertyImageTable->Count() == 0) {
+ imageTable.Remove(aContext);
+ }
+ } else {
+ // We still have a non-empty image array for this property. Compact the
+ // storage it's using if possible.
+ imageList->Compact();
+ }
+ }
+}
+
+/**
+ * Adds a new ImageValue @aValue to the CSSVariableImageTable, which will be
+ * associated with @aContext and @aProp.
+ *
+ * It's illegal to call this function outside of a lambda passed to
+ * CSSVariableImageTable::ReplaceAll().
+ */
+inline void
+Add(nsStyleContext* aContext, nsCSSPropertyID aProp, css::ImageValue* aValue)
+{
+ MOZ_ASSERT(aValue);
+ MOZ_ASSERT(aContext);
+ MOZ_ASSERT(detail::IsReplacing());
+
+ auto& imageTable = detail::GetTable();
+
+ // Ensure there's a per-property image table for this style context.
+ auto* perPropertyImageTable = imageTable.Get(aContext);
+ if (!perPropertyImageTable) {
+ perPropertyImageTable = new detail::PerPropertyImageHashtable();
+ imageTable.Put(aContext, perPropertyImageTable);
+ }
+
+ // Ensure there's an image array for this property.
+ auto* imageList = perPropertyImageTable->Get(aProp);
+ if (!imageList) {
+ imageList = new detail::ImageValueArray();
+ perPropertyImageTable->Put(aProp, imageList);
+ }
+
+ // Append the provided ImageValue to the list.
+ imageList->AppendElement(aValue);
+}
+
+/**
+ * Removes all ImageValues stored in the CSSVariableImageTable for the provided
+ * @aContext.
+ */
+inline void
+RemoveAll(nsStyleContext* aContext)
+{
+ // Move all ImageValue references into removedImageList so that we can
+ // release them outside of any hashtable methods. (If we just call
+ // Remove(aContext) on the table then we can end up calling back
+ // re-entrantly into hashtable methods, as other style contexts
+ // are released.)
+ detail::ImageValueArray removedImages;
+ auto& imageTable = detail::GetTable();
+ auto* perPropertyImageTable = imageTable.Get(aContext);
+ if (perPropertyImageTable) {
+ for (auto it = perPropertyImageTable->Iter(); !it.Done(); it.Next()) {
+ auto* imageList = it.UserData();
+ removedImages.AppendElements(Move(*imageList));
+ }
+ }
+ imageTable.Remove(aContext);
+}
+
+} // namespace CSSVariableImageTable
+} // namespace mozilla
+
+#endif // mozilla_CSSVariableImageTable_h
diff --git a/layout/style/CSSVariableResolver.cpp b/layout/style/CSSVariableResolver.cpp
new file mode 100644
index 000000000..0d25b747b
--- /dev/null
+++ b/layout/style/CSSVariableResolver.cpp
@@ -0,0 +1,266 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* object that resolves CSS variables using specified and inherited variable
+ * values
+ */
+
+#include "CSSVariableResolver.h"
+
+#include "CSSVariableDeclarations.h"
+#include "CSSVariableValues.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/UniquePtr.h"
+#include <algorithm>
+
+namespace mozilla {
+
+/**
+ * Data used by the EnumerateVariableReferences callback. Reset must be called
+ * on it before it is used.
+ */
+class EnumerateVariableReferencesData
+{
+public:
+ explicit EnumerateVariableReferencesData(CSSVariableResolver& aResolver)
+ : mResolver(aResolver)
+ , mReferences(MakeUnique<bool[]>(aResolver.mVariables.Length()))
+ {
+ }
+
+ /**
+ * Resets the data so that it can be passed to another call of
+ * EnumerateVariableReferences for a different variable.
+ */
+ void Reset()
+ {
+ PodZero(mReferences.get(), mResolver.mVariables.Length());
+ mReferencesNonExistentVariable = false;
+ }
+
+ void RecordVariableReference(const nsAString& aVariableName)
+ {
+ size_t id;
+ if (mResolver.mVariableIDs.Get(aVariableName, &id)) {
+ mReferences[id] = true;
+ } else {
+ mReferencesNonExistentVariable = true;
+ }
+ }
+
+ bool HasReferenceToVariable(size_t aID) const
+ {
+ return mReferences[aID];
+ }
+
+ bool ReferencesNonExistentVariable() const
+ {
+ return mReferencesNonExistentVariable;
+ }
+
+private:
+ CSSVariableResolver& mResolver;
+
+ // Array of booleans, where each index is a variable ID. If an element is
+ // true, it indicates that the variable we have called
+ // EnumerateVariableReferences for has a reference to the variable with
+ // that ID.
+ const UniquePtr<bool[]> mReferences;
+
+ // Whether the variable we have called EnumerateVariableReferences for
+ // references a variable that does not exist in the resolver.
+ bool mReferencesNonExistentVariable;
+};
+
+static void
+RecordVariableReference(const nsAString& aVariableName,
+ void* aData)
+{
+ static_cast<EnumerateVariableReferencesData*>(aData)->
+ RecordVariableReference(aVariableName);
+}
+
+void
+CSSVariableResolver::RemoveCycles(size_t v)
+{
+ mVariables[v].mIndex = mNextIndex;
+ mVariables[v].mLowLink = mNextIndex;
+ mVariables[v].mInStack = true;
+ mStack.AppendElement(v);
+ mNextIndex++;
+
+ for (size_t i = 0, n = mReferences[v].Length(); i < n; i++) {
+ size_t w = mReferences[v][i];
+ if (!mVariables[w].mIndex) {
+ RemoveCycles(w);
+ mVariables[v].mLowLink = std::min(mVariables[v].mLowLink,
+ mVariables[w].mLowLink);
+ } else if (mVariables[w].mInStack) {
+ mVariables[v].mLowLink = std::min(mVariables[v].mLowLink,
+ mVariables[w].mIndex);
+ }
+ }
+
+ if (mVariables[v].mLowLink == mVariables[v].mIndex) {
+ if (mStack.LastElement() == v) {
+ // A strongly connected component consisting of a single variable is not
+ // necessarily invalid. We handle variables that reference themselves
+ // earlier, in CSSVariableResolver::Resolve.
+ mVariables[mStack.LastElement()].mInStack = false;
+ mStack.TruncateLength(mStack.Length() - 1);
+ } else {
+ size_t w;
+ do {
+ w = mStack.LastElement();
+ mVariables[w].mValue.Truncate(0);
+ mVariables[w].mInStack = false;
+ mStack.TruncateLength(mStack.Length() - 1);
+ } while (w != v);
+ }
+ }
+}
+
+void
+CSSVariableResolver::ResolveVariable(size_t aID)
+{
+ if (mVariables[aID].mValue.IsEmpty() || mVariables[aID].mWasInherited) {
+ // The variable is invalid or was inherited. We can just copy the value
+ // and its first/last token information across.
+ mOutput->Put(mVariables[aID].mVariableName,
+ mVariables[aID].mValue,
+ mVariables[aID].mFirstToken,
+ mVariables[aID].mLastToken);
+ } else {
+ // Otherwise we need to resolve the variable references, after resolving
+ // all of our dependencies first. We do this even for variables that we
+ // know do not reference other variables so that we can find their
+ // first/last token.
+ //
+ // XXX We might want to do this first/last token finding during
+ // EnumerateVariableReferences, so that we can avoid calling
+ // ResolveVariableValue and parsing the value again.
+ for (size_t i = 0, n = mReferences[aID].Length(); i < n; i++) {
+ size_t j = mReferences[aID][i];
+ if (aID != j && !mVariables[j].mResolved) {
+ ResolveVariable(j);
+ }
+ }
+ nsString resolvedValue;
+ nsCSSTokenSerializationType firstToken, lastToken;
+ if (!mParser.ResolveVariableValue(mVariables[aID].mValue, mOutput,
+ resolvedValue, firstToken, lastToken)) {
+ resolvedValue.Truncate(0);
+ }
+ mOutput->Put(mVariables[aID].mVariableName, resolvedValue,
+ firstToken, lastToken);
+ }
+ mVariables[aID].mResolved = true;
+}
+
+void
+CSSVariableResolver::Resolve(const CSSVariableValues* aInherited,
+ const CSSVariableDeclarations* aSpecified)
+{
+ MOZ_ASSERT(!mResolved);
+
+ // The only time we would be worried about having a null aInherited is
+ // for the root, but in that case nsRuleNode::ComputeVariablesData will
+ // happen to pass in whatever we're using as mOutput for aInherited,
+ // which will initially be empty.
+ MOZ_ASSERT(aInherited);
+ MOZ_ASSERT(aSpecified);
+
+ aInherited->AddVariablesToResolver(this);
+ aSpecified->AddVariablesToResolver(this);
+
+ // First we look at each variable's value and record which other variables
+ // it references.
+ size_t n = mVariables.Length();
+ mReferences.SetLength(n);
+ EnumerateVariableReferencesData data(*this);
+ for (size_t id = 0; id < n; id++) {
+ data.Reset();
+ if (!mVariables[id].mWasInherited &&
+ !mVariables[id].mValue.IsEmpty()) {
+ if (mParser.EnumerateVariableReferences(mVariables[id].mValue,
+ RecordVariableReference,
+ &data)) {
+ // Convert the boolean array of dependencies in |data| to a list
+ // of dependencies.
+ for (size_t i = 0; i < n; i++) {
+ if (data.HasReferenceToVariable(i)) {
+ mReferences[id].AppendElement(i);
+ }
+ }
+ // If a variable references itself, it is invalid. (RemoveCycles
+ // does not check for cycles consisting of a single variable, so we
+ // check here.)
+ if (data.HasReferenceToVariable(id)) {
+ mVariables[id].mValue.Truncate();
+ }
+ // Also record whether it referenced any variables that don't exist
+ // in the resolver, so that we can ensure we still resolve its value
+ // in ResolveVariable, even though its mReferences list is empty.
+ mVariables[id].mReferencesNonExistentVariable =
+ data.ReferencesNonExistentVariable();
+ } else {
+ MOZ_ASSERT(false, "EnumerateVariableReferences should not have failed "
+ "if we previously parsed the specified value");
+ mVariables[id].mValue.Truncate(0);
+ }
+ }
+ }
+
+ // Next we remove any cycles in variable references using Tarjan's strongly
+ // connected components finding algorithm, setting variables in cycles to
+ // have an invalid value.
+ mNextIndex = 1;
+ for (size_t id = 0; id < n; id++) {
+ if (!mVariables[id].mIndex) {
+ RemoveCycles(id);
+ MOZ_ASSERT(mStack.IsEmpty());
+ }
+ }
+
+ // Finally we construct the computed value for the variable by substituting
+ // any variable references.
+ for (size_t id = 0; id < n; id++) {
+ if (!mVariables[id].mResolved) {
+ ResolveVariable(id);
+ }
+ }
+
+#ifdef DEBUG
+ mResolved = true;
+#endif
+}
+
+void
+CSSVariableResolver::Put(const nsAString& aVariableName,
+ nsString aValue,
+ nsCSSTokenSerializationType aFirstToken,
+ nsCSSTokenSerializationType aLastToken,
+ bool aWasInherited)
+{
+ MOZ_ASSERT(!mResolved);
+
+ size_t id;
+ if (mVariableIDs.Get(aVariableName, &id)) {
+ MOZ_ASSERT(mVariables[id].mWasInherited && !aWasInherited,
+ "should only overwrite inherited variables with specified "
+ "variables");
+ mVariables[id].mValue = aValue;
+ mVariables[id].mFirstToken = aFirstToken;
+ mVariables[id].mLastToken = aLastToken;
+ mVariables[id].mWasInherited = aWasInherited;
+ } else {
+ id = mVariables.Length();
+ mVariableIDs.Put(aVariableName, id);
+ mVariables.AppendElement(Variable(aVariableName, aValue,
+ aFirstToken, aLastToken, aWasInherited));
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/style/CSSVariableResolver.h b/layout/style/CSSVariableResolver.h
new file mode 100644
index 000000000..f9758ca3a
--- /dev/null
+++ b/layout/style/CSSVariableResolver.h
@@ -0,0 +1,148 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* object that resolves CSS variables using specified and inherited variable
+ * values
+ */
+
+#ifndef mozilla_CSSVariableResolver_h
+#define mozilla_CSSVariableResolver_h
+
+#include "mozilla/DebugOnly.h"
+#include "nsCSSParser.h"
+#include "nsCSSScanner.h"
+#include "nsDataHashtable.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class CSSVariableDeclarations;
+class CSSVariableValues;
+class EnumerateVariableReferencesData;
+
+class CSSVariableResolver
+{
+ friend class CSSVariableDeclarations;
+ friend class CSSVariableValues;
+ friend class EnumerateVariableReferencesData;
+public:
+ /**
+ * Creates a new CSSVariableResolver that will output a set of resolved,
+ * computed variables into aOutput.
+ */
+ explicit CSSVariableResolver(CSSVariableValues* aOutput)
+ : mOutput(aOutput)
+#ifdef DEBUG
+ , mResolved(false)
+#endif
+ {
+ MOZ_ASSERT(aOutput);
+ }
+
+ /**
+ * Resolves the set of inherited variables from aInherited and the
+ * set of specified variables from aSpecified. The resolved variables
+ * are written into mOutput.
+ */
+ void Resolve(const CSSVariableValues* aInherited,
+ const CSSVariableDeclarations* aSpecified);
+
+private:
+ struct Variable
+ {
+ Variable(const nsAString& aVariableName,
+ nsString aValue,
+ nsCSSTokenSerializationType aFirstToken,
+ nsCSSTokenSerializationType aLastToken,
+ bool aWasInherited)
+ : mVariableName(aVariableName)
+ , mValue(aValue)
+ , mFirstToken(aFirstToken)
+ , mLastToken(aLastToken)
+ , mWasInherited(aWasInherited)
+ , mResolved(false)
+ , mReferencesNonExistentVariable(false)
+ , mInStack(false)
+ , mIndex(0)
+ , mLowLink(0) { }
+
+ nsString mVariableName;
+ nsString mValue;
+ nsCSSTokenSerializationType mFirstToken;
+ nsCSSTokenSerializationType mLastToken;
+
+ // Whether this variable came from the set of inherited variables.
+ bool mWasInherited;
+
+ // Whether this variable has been resolved yet.
+ bool mResolved;
+
+ // Whether this variables includes any references to non-existent variables.
+ bool mReferencesNonExistentVariable;
+
+ // Bookkeeping for the cycle remover algorithm.
+ bool mInStack;
+ size_t mIndex;
+ size_t mLowLink;
+ };
+
+ /**
+ * Adds or modifies an existing entry in the set of variables to be resolved.
+ * This is intended to be called by the AddVariablesToResolver functions on
+ * the CSSVariableDeclarations and CSSVariableValues objects passed in to
+ * Resolve.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name) whose value is to be set.
+ * @param aValue The variable value.
+ * @param aFirstToken The type of token at the start of the variable value.
+ * @param aLastToken The type of token at the en of the variable value.
+ * @param aWasInherited Whether this variable came from the set of inherited
+ * variables.
+ */
+ void Put(const nsAString& aVariableName,
+ nsString aValue,
+ nsCSSTokenSerializationType aFirstToken,
+ nsCSSTokenSerializationType aLastToken,
+ bool aWasInherited);
+
+ // Helper functions for Resolve.
+ void RemoveCycles(size_t aID);
+ void ResolveVariable(size_t aID);
+
+ // A mapping of variable names to an ID that indexes into mVariables
+ // and mReferences.
+ nsDataHashtable<nsStringHashKey, size_t> mVariableIDs;
+
+ // The set of variables.
+ nsTArray<Variable> mVariables;
+
+ // The list of variables that each variable references.
+ nsTArray<nsTArray<size_t> > mReferences;
+
+ // The next index to assign to a variable found during the cycle removing
+ // algorithm's traversal of the variable reference graph.
+ size_t mNextIndex;
+
+ // Stack of variable IDs that we push to as we traverse the variable reference
+ // graph while looking for cycles. Variable::mInStack reflects whether a
+ // given variable has its ID in mStack.
+ nsTArray<size_t> mStack;
+
+ // CSS parser to use for parsing property values with variable references.
+ nsCSSParser mParser;
+
+ // The object to output the resolved variables into.
+ CSSVariableValues* mOutput;
+
+#ifdef DEBUG
+ // Whether Resolve has been called.
+ bool mResolved;
+#endif
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/CSSVariableValues.cpp b/layout/style/CSSVariableValues.cpp
new file mode 100644
index 000000000..e6f1faf26
--- /dev/null
+++ b/layout/style/CSSVariableValues.cpp
@@ -0,0 +1,147 @@
+/* -*- 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/. */
+
+/* computed CSS Variable values */
+
+#include "CSSVariableValues.h"
+
+#include "CSSVariableResolver.h"
+
+namespace mozilla {
+
+CSSVariableValues::CSSVariableValues()
+{
+ MOZ_COUNT_CTOR(CSSVariableValues);
+}
+
+CSSVariableValues::CSSVariableValues(const CSSVariableValues& aOther)
+{
+ MOZ_COUNT_CTOR(CSSVariableValues);
+ CopyVariablesFrom(aOther);
+}
+
+#ifdef DEBUG
+CSSVariableValues::~CSSVariableValues()
+{
+ MOZ_COUNT_DTOR(CSSVariableValues);
+}
+#endif
+
+CSSVariableValues&
+CSSVariableValues::operator=(const CSSVariableValues& aOther)
+{
+ if (this == &aOther) {
+ return *this;
+ }
+
+ mVariableIDs.Clear();
+ mVariables.Clear();
+ CopyVariablesFrom(aOther);
+ return *this;
+}
+
+bool
+CSSVariableValues::operator==(const CSSVariableValues& aOther) const
+{
+ if (mVariables.Length() != aOther.mVariables.Length()) {
+ return false;
+ }
+
+ for (size_t thisIndex = 0; thisIndex < mVariables.Length(); ++thisIndex) {
+ size_t otherIndex;
+ if (!aOther.mVariableIDs.Get(mVariables[thisIndex].mVariableName,
+ &otherIndex)) {
+ return false;
+ }
+ const nsString& otherValue = aOther.mVariables[otherIndex].mValue;
+ if (!mVariables[thisIndex].mValue.Equals(otherValue)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+size_t
+CSSVariableValues::Count() const
+{
+ return mVariables.Length();
+}
+
+bool
+CSSVariableValues::Get(const nsAString& aName, nsString& aValue) const
+{
+ size_t id;
+ if (!mVariableIDs.Get(aName, &id)) {
+ return false;
+ }
+ aValue = mVariables[id].mValue;
+ return true;
+}
+
+bool
+CSSVariableValues::Get(const nsAString& aName,
+ nsString& aValue,
+ nsCSSTokenSerializationType& aFirstToken,
+ nsCSSTokenSerializationType& aLastToken) const
+{
+ size_t id;
+ if (!mVariableIDs.Get(aName, &id)) {
+ return false;
+ }
+ aValue = mVariables[id].mValue;
+ aFirstToken = mVariables[id].mFirstToken;
+ aLastToken = mVariables[id].mLastToken;
+ return true;
+}
+
+void
+CSSVariableValues::GetVariableAt(size_t aIndex, nsAString& aName) const
+{
+ aName = mVariables[aIndex].mVariableName;
+}
+
+void
+CSSVariableValues::Put(const nsAString& aName,
+ nsString aValue,
+ nsCSSTokenSerializationType aFirstToken,
+ nsCSSTokenSerializationType aLastToken)
+{
+ size_t id;
+ if (mVariableIDs.Get(aName, &id)) {
+ mVariables[id].mValue = aValue;
+ mVariables[id].mFirstToken = aFirstToken;
+ mVariables[id].mLastToken = aLastToken;
+ } else {
+ id = mVariables.Length();
+ mVariableIDs.Put(aName, id);
+ mVariables.AppendElement(Variable(aName, aValue, aFirstToken, aLastToken));
+ }
+}
+
+void
+CSSVariableValues::CopyVariablesFrom(const CSSVariableValues& aOther)
+{
+ for (size_t i = 0, n = aOther.mVariables.Length(); i < n; i++) {
+ Put(aOther.mVariables[i].mVariableName,
+ aOther.mVariables[i].mValue,
+ aOther.mVariables[i].mFirstToken,
+ aOther.mVariables[i].mLastToken);
+ }
+}
+
+void
+CSSVariableValues::AddVariablesToResolver(CSSVariableResolver* aResolver) const
+{
+ for (size_t i = 0, n = mVariables.Length(); i < n; i++) {
+ aResolver->Put(mVariables[i].mVariableName,
+ mVariables[i].mValue,
+ mVariables[i].mFirstToken,
+ mVariables[i].mLastToken,
+ true);
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/style/CSSVariableValues.h b/layout/style/CSSVariableValues.h
new file mode 100644
index 000000000..e26566be1
--- /dev/null
+++ b/layout/style/CSSVariableValues.h
@@ -0,0 +1,147 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* computed CSS Variable values */
+
+#ifndef mozilla_CSSVariableValues_h
+#define mozilla_CSSVariableValues_h
+
+#include "nsCSSScanner.h"
+#include "nsDataHashtable.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class CSSVariableResolver;
+
+class CSSVariableValues
+{
+public:
+ CSSVariableValues();
+ CSSVariableValues(const CSSVariableValues& aOther);
+#ifdef DEBUG
+ ~CSSVariableValues();
+#endif
+ CSSVariableValues& operator=(const CSSVariableValues& aOther);
+
+ bool operator==(const CSSVariableValues& aOther) const;
+ bool operator!=(const CSSVariableValues& aOther) const
+ { return !(*this == aOther); }
+
+ /**
+ * Gets the value of a variable in this set of computed variables.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name).
+ * @param aValue Out parameter into which the value of the variable will
+ * be stored.
+ * @return Whether a variable with the given name was found. When false
+ * is returned, aValue will not be modified.
+ */
+ bool Get(const nsAString& aName, nsString& aValue) const;
+
+ /**
+ * Gets the value of a variable in this set of computed variables, along
+ * with information on the types of tokens that appear at the start and
+ * end of the token stream.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name).
+ * @param aValue Out parameter into which the value of the variable will
+ * be stored.
+ * @param aFirstToken The type of token at the start of the variable value.
+ * @param aLastToken The type of token at the en of the variable value.
+ * @return Whether a variable with the given name was found. When false
+ * is returned, aValue, aFirstToken and aLastToken will not be modified.
+ */
+ bool Get(const nsAString& aName,
+ nsString& aValue,
+ nsCSSTokenSerializationType& aFirstToken,
+ nsCSSTokenSerializationType& aLastToken) const;
+
+ /**
+ * Gets the name of the variable at the given index.
+ *
+ * Variables on this object are ordered, and that order is just determined
+ * based on the order that they are added to the object. A consistent
+ * ordering is required for CSSDeclaration objects in the DOM.
+ * CSSDeclarations expose property names as indexed properties, which need to
+ * be stable.
+ *
+ * @param aIndex The index of the variable to get.
+ * @param aName Out parameter into which the name of the variable will be
+ * stored.
+ */
+ void GetVariableAt(size_t aIndex, nsAString& aName) const;
+
+ /**
+ * Gets the number of variables stored on this object.
+ */
+ size_t Count() const;
+
+ /**
+ * Adds or modifies an existing entry in this set of variable values.
+ *
+ * @param aName The variable name (not including any "--" prefix that would
+ * be part of the custom property name) whose value is to be set.
+ * @param aValue The variable value.
+ * @param aFirstToken The type of token at the start of the variable value.
+ * @param aLastToken The type of token at the en of the variable value.
+ */
+ void Put(const nsAString& aName,
+ nsString aValue,
+ nsCSSTokenSerializationType aFirstToken,
+ nsCSSTokenSerializationType aLastToken);
+
+ /**
+ * Copies the variables from this object into aResolver, marking them as
+ * computed, inherited values.
+ */
+ void AddVariablesToResolver(CSSVariableResolver* aResolver) const;
+
+private:
+ struct Variable
+ {
+ Variable()
+ : mFirstToken(eCSSTokenSerialization_Nothing)
+ , mLastToken(eCSSTokenSerialization_Nothing)
+ {}
+
+ Variable(const nsAString& aVariableName,
+ nsString aValue,
+ nsCSSTokenSerializationType aFirstToken,
+ nsCSSTokenSerializationType aLastToken)
+ : mVariableName(aVariableName)
+ , mValue(aValue)
+ , mFirstToken(aFirstToken)
+ , mLastToken(aLastToken)
+ {}
+
+ nsString mVariableName;
+ nsString mValue;
+ nsCSSTokenSerializationType mFirstToken;
+ nsCSSTokenSerializationType mLastToken;
+ };
+
+ /**
+ * Adds all the variables from aOther into this object.
+ */
+ void CopyVariablesFrom(const CSSVariableValues& aOther);
+
+ /**
+ * Map of variable names to IDs. Variable IDs are indexes into
+ * mVariables.
+ */
+ nsDataHashtable<nsStringHashKey, size_t> mVariableIDs;
+
+ /**
+ * Array of variables, indexed by variable ID.
+ */
+ nsTArray<Variable> mVariables;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/CounterStyleManager.cpp b/layout/style/CounterStyleManager.cpp
new file mode 100644
index 000000000..bccc9b836
--- /dev/null
+++ b/layout/style/CounterStyleManager.cpp
@@ -0,0 +1,2135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/ArenaObjectID.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Types.h"
+#include "mozilla/WritingModes.h"
+#include "nsCSSRules.h"
+#include "nsString.h"
+#include "nsStyleSet.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsUnicodeProperties.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+
+namespace mozilla {
+
+struct AdditiveSymbol
+{
+ CounterValue weight;
+ nsString symbol;
+};
+
+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,
+ nsSubstring& aResult,
+ const nsTArray<nsString>& aSymbols)
+{
+ MOZ_ASSERT(aSymbols.Length() >= 1,
+ "No symbol available for cyclic counter.");
+ auto n = aSymbols.Length();
+ CounterValue index = (aOrdinal - 1) % n;
+ aResult = aSymbols[index >= 0 ? index : index + n];
+ return true;
+}
+
+static bool
+GetFixedCounterText(CounterValue aOrdinal,
+ nsSubstring& aResult,
+ CounterValue aStart,
+ const nsTArray<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,
+ nsSubstring& aResult,
+ const nsTArray<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,
+ nsSubstring& aResult,
+ const nsTArray<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,
+ nsSubstring& aResult,
+ const nsTArray<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,
+ nsSubstring& aResult,
+ const nsTArray<AdditiveSymbol>& aSymbols)
+{
+ MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
+
+ if (aOrdinal == 0) {
+ const AdditiveSymbol& last = aSymbols.LastElement();
+ 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, nsSubstring& 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 = {
+ { // digit
+ 0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
+ 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
+ },
+ { 0x5341, 0x767e, 0x5343 }, // unit
+ { 0x4e07, 0x5104 }, // unit10K
+ JAPANESE, // lang
+ true // informal
+};
+static const CJKIdeographicData gDataJapaneseFormal = {
+ { // digit
+ 0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db,
+ 0x4f0d, 0x516d, 0x4e03, 0x516b, 0x4e5d
+ },
+ { 0x62fe, 0x767e, 0x9621 }, // unit
+ { 0x842c, 0x5104 }, // unit10K
+ JAPANESE, // lang
+ false // informal
+};
+static const CJKIdeographicData gDataKoreanHangulFormal = {
+ { // digit
+ 0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac,
+ 0xc624, 0xc721, 0xce60, 0xd314, 0xad6c
+ },
+ { 0xc2ed, 0xbc31, 0xcc9c }, // unit
+ { 0xb9cc, 0xc5b5 }, // unit10K
+ KOREAN, // lang
+ false // informal
+};
+static const CJKIdeographicData gDataKoreanHanjaInformal = {
+ { // digit
+ 0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
+ 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
+ },
+ { 0x5341, 0x767e, 0x5343 }, // unit
+ { 0x842c, 0x5104 }, // unit10K
+ KOREAN, // lang
+ true // informal
+};
+static const CJKIdeographicData gDataKoreanHanjaFormal = {
+ { // digit
+ 0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db,
+ 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
+ },
+ { 0x62fe, 0x767e, 0x4edf }, // unit
+ { 0x842c, 0x5104 }, // unit10K
+ KOREAN, // lang
+ false // informal
+};
+static const CJKIdeographicData gDataSimpChineseInformal = {
+ { // digit
+ 0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
+ 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
+ },
+ { 0x5341, 0x767e, 0x5343 }, // unit
+ { 0x4e07, 0x4ebf }, // unit10K
+ CHINESE, // lang
+ true // informal
+};
+static const CJKIdeographicData gDataSimpChineseFormal = {
+ { // digit
+ 0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086,
+ 0x4f0d, 0x9646, 0x67d2, 0x634c, 0x7396
+ },
+ { 0x62fe, 0x4f70, 0x4edf }, // unit
+ { 0x4e07, 0x4ebf }, // unit10K
+ CHINESE, // lang
+ false // informal
+};
+static const CJKIdeographicData gDataTradChineseInformal = {
+ { // digit
+ 0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
+ 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
+ },
+ { 0x5341, 0x767e, 0x5343 }, // unit
+ { 0x842c, 0x5104 }, // unit10K
+ CHINESE, // lang
+ true // informal
+};
+static const CJKIdeographicData gDataTradChineseFormal = {
+ { // digit
+ 0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x8086,
+ 0x4f0d, 0x9678, 0x67d2, 0x634c, 0x7396
+ },
+ { 0x62fe, 0x4f70, 0x4edf }, // unit
+ { 0x842c, 0x5104 }, // unit10K
+ CHINESE, // lang
+ false // informal
+};
+
+static bool
+CJKIdeographicToText(CounterValue aOrdinal, nsSubstring& 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<MakeUnsigned<CounterValue>::Type>(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, nsSubstring& 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, nsSubstring& 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.Insert(NS_LITERAL_STRING("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 uint8_t
+GetDefaultSpeakAsForSystem(uint8_t aSystem)
+{
+ MOZ_ASSERT(aSystem != NS_STYLE_COUNTER_SYSTEM_EXTENDS,
+ "Extends system does not have static default speak-as");
+ switch (aSystem) {
+ case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+ return NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT;
+ case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+ return NS_STYLE_COUNTER_SPEAKAS_BULLETS;
+ default:
+ return NS_STYLE_COUNTER_SPEAKAS_NUMBERS;
+ }
+}
+
+static bool
+SystemUsesNegativeSign(uint8_t aSystem)
+{
+ MOZ_ASSERT(aSystem != NS_STYLE_COUNTER_SYSTEM_EXTENDS,
+ "Cannot check this for extending style");
+ switch (aSystem) {
+ case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+ case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+ case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+ case NS_STYLE_COUNTER_SYSTEM_ADDITIVE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+class BuiltinCounterStyle : public CounterStyle
+{
+public:
+ friend class CounterStyleManager;
+
+ // will be initialized by CounterStyleManager::InitializeBuiltinCounterStyles
+ constexpr BuiltinCounterStyle()
+ : CounterStyle(NS_STYLE_LIST_STYLE_NONE)
+ {
+ }
+
+protected:
+ constexpr explicit BuiltinCounterStyle(int32_t aStyle)
+ : CounterStyle(aStyle)
+ {
+ }
+
+public:
+ virtual void GetStyleName(nsSubstring& aResult) override;
+ virtual void GetPrefix(nsSubstring& aResult) override;
+ virtual void GetSuffix(nsSubstring& aResult) override;
+ virtual void GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& 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 uint8_t GetSpeakAs() override;
+ virtual bool UseNegativeSign() override;
+
+ virtual bool GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& aResult,
+ bool& aIsRTL) override;
+
+ // Builtin counter style does not need refcount at all
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() override { return 2; }
+ NS_IMETHOD_(MozExternalRefCountType) Release() override { return 2; }
+};
+
+/* virtual */ void
+BuiltinCounterStyle::GetStyleName(nsSubstring& aResult)
+{
+ MOZ_ASSERT(mStyle != NS_STYLE_LIST_STYLE_CUSTOM);
+ const nsAFlatCString& str =
+ nsCSSProps::ValueToKeyword(mStyle, nsCSSProps::kListStyleKTable);
+ MOZ_ASSERT(!str.IsEmpty());
+ aResult.Assign(NS_ConvertUTF8toUTF16(str));
+}
+
+/* virtual */ void
+BuiltinCounterStyle::GetPrefix(nsSubstring& aResult)
+{
+ aResult.Truncate();
+}
+
+/* virtual */ void
+BuiltinCounterStyle::GetSuffix(nsSubstring& aResult)
+{
+ switch (mStyle) {
+ case NS_STYLE_LIST_STYLE_NONE:
+ aResult.Truncate();
+ break;
+
+ case NS_STYLE_LIST_STYLE_DISC:
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
+ case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC:
+ aResult = ' ';
+ break;
+
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+ aResult = 0x3001;
+ break;
+
+ case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+ 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 = 0x25fe;
+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,
+ nsSubstring& aResult,
+ bool& aIsBullet)
+{
+ switch (mStyle) {
+ case NS_STYLE_LIST_STYLE_NONE:
+ case NS_STYLE_LIST_STYLE_DISC:
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN: {
+ // 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 NS_STYLE_LIST_STYLE_DISC:
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
+ 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 NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+ aResult.before = gJapaneseNegative;
+ break;
+
+ case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+ aResult.before = gKoreanNegative;
+ break;
+
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+ aResult.before = gSimpChineseNegative;
+ break;
+
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+ aResult.before = gTradChineseNegative;
+ break;
+
+ default:
+ aResult.before.AssignLiteral(u"-");
+ }
+ aResult.after.Truncate();
+}
+
+/* virtual */ bool
+BuiltinCounterStyle::IsOrdinalInRange(CounterValue aOrdinal)
+{
+ switch (mStyle) {
+ default:
+ // cyclic
+ case NS_STYLE_LIST_STYLE_NONE:
+ case NS_STYLE_LIST_STYLE_DISC:
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
+ // use DecimalToText
+ case NS_STYLE_LIST_STYLE_DECIMAL:
+ // use CJKIdeographicToText
+ case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+ return true;
+
+ // use EthiopicToText
+ case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC:
+ return aOrdinal >= 1;
+
+ // use HebrewToText
+ case NS_STYLE_LIST_STYLE_HEBREW:
+ return aOrdinal >= 1 && aOrdinal <= 999999;
+ }
+}
+
+/* virtual */ bool
+BuiltinCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal)
+{
+ switch (mStyle) {
+ // cyclic:
+ case NS_STYLE_LIST_STYLE_NONE:
+ case NS_STYLE_LIST_STYLE_DISC:
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
+ // numeric:
+ case NS_STYLE_LIST_STYLE_DECIMAL:
+ return true;
+
+ // additive:
+ case NS_STYLE_LIST_STYLE_HEBREW:
+ return aOrdinal >= 0;
+
+ // complex predefined:
+ case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC:
+ return IsOrdinalInRange(aOrdinal);
+
+ default:
+ NS_NOTREACHED("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 */ uint8_t
+BuiltinCounterStyle::GetSpeakAs()
+{
+ switch (mStyle) {
+ case NS_STYLE_LIST_STYLE_NONE:
+ case NS_STYLE_LIST_STYLE_DISC:
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
+ return NS_STYLE_COUNTER_SPEAKAS_BULLETS;
+ default:
+ return NS_STYLE_COUNTER_SPEAKAS_NUMBERS;
+ }
+}
+
+/* virtual */ bool
+BuiltinCounterStyle::UseNegativeSign()
+{
+ switch (mStyle) {
+ case NS_STYLE_LIST_STYLE_NONE:
+ case NS_STYLE_LIST_STYLE_DISC:
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
+ return false;
+ default:
+ return true;
+ }
+}
+
+/* virtual */ bool
+BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& 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 NS_STYLE_LIST_STYLE_NONE:
+ aResult.Truncate();
+ return true;
+ case NS_STYLE_LIST_STYLE_DISC:
+ aResult.Assign(kDiscCharacter);
+ return true;
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ aResult.Assign(kCircleCharacter);
+ return true;
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ aResult.Assign(kSquareCharacter);
+ return true;
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
+ if (aWritingMode.IsVertical()) {
+ aResult.Assign(kDownPointingCharacter);
+ } else if (aWritingMode.IsBidiLTR()) {
+ aResult.Assign(kRightPointingCharacter);
+ } else {
+ aResult.Assign(kLeftPointingCharacter);
+ }
+ return true;
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
+ if (!aWritingMode.IsVertical()) {
+ aResult.Assign(kDownPointingCharacter);
+ } else if (aWritingMode.IsVerticalLR()) {
+ aResult.Assign(kRightPointingCharacter);
+ } else {
+ aResult.Assign(kLeftPointingCharacter);
+ }
+ return true;
+
+ case NS_STYLE_LIST_STYLE_DECIMAL:
+ return DecimalToText(aOrdinal, aResult);
+
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseInformal);
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseFormal);
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseInformal);
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseFormal);
+ case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseInformal);
+ case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseFormal);
+ case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHangulFormal);
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaInformal);
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+ return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaFormal);
+
+ case NS_STYLE_LIST_STYLE_HEBREW:
+ aIsRTL = true;
+ return HebrewToText(aOrdinal, aResult);
+
+ case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC:
+ return EthiopicToText(aOrdinal, aResult);
+
+ default:
+ NS_NOTREACHED("Unknown builtin counter style");
+ return false;
+ }
+}
+
+class DependentBuiltinCounterStyle final : public BuiltinCounterStyle
+{
+private:
+ ~DependentBuiltinCounterStyle() {}
+public:
+ DependentBuiltinCounterStyle(int32_t aStyle, CounterStyleManager* aManager)
+ : BuiltinCounterStyle(aStyle),
+ mManager(aManager)
+ {
+ NS_ASSERTION(IsDependentStyle(), "Not a dependent builtin style");
+ MOZ_ASSERT(!IsCustomStyle(), "Not a builtin style");
+ }
+
+ virtual CounterStyle* GetFallback() override;
+
+ // DependentBuiltinCounterStyle is managed in the same way as
+ // CustomCounterStyle.
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() override;
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+
+ void* operator new(size_t sz, nsPresContext* aPresContext)
+ {
+ return aPresContext->PresShell()->AllocateByObjectID(
+ eArenaObjectID_DependentBuiltinCounterStyle, sz);
+ }
+
+private:
+ void Destroy()
+ {
+ nsIPresShell* shell = mManager->PresContext()->PresShell();
+ this->~DependentBuiltinCounterStyle();
+ shell->FreeByObjectID(eArenaObjectID_DependentBuiltinCounterStyle, this);
+ }
+
+ CounterStyleManager* mManager;
+
+ nsAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+};
+
+NS_IMPL_ADDREF(DependentBuiltinCounterStyle)
+NS_IMPL_RELEASE_WITH_DESTROY(DependentBuiltinCounterStyle, Destroy())
+
+/* virtual */ CounterStyle*
+DependentBuiltinCounterStyle::GetFallback()
+{
+ switch (GetStyle()) {
+ case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+ // 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->BuildCounterStyle(NS_LITERAL_STRING("cjk-decimal"));
+ default:
+ NS_NOTREACHED("Not a valid dependent builtin style");
+ return BuiltinCounterStyle::GetFallback();
+ }
+}
+
+class CustomCounterStyle final : public CounterStyle
+{
+private:
+ ~CustomCounterStyle() {}
+public:
+ CustomCounterStyle(const nsAString& aName,
+ CounterStyleManager* aManager,
+ nsCSSCounterStyleRule* aRule)
+ : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM),
+ mName(aName),
+ mManager(aManager),
+ mRule(aRule),
+ mRuleGeneration(aRule->GetGeneration()),
+ mSystem(aRule->GetSystem()),
+ 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();
+
+ nsCSSCounterStyleRule* GetRule() const { return mRule; }
+ uint32_t GetRuleGeneration() const { return mRuleGeneration; }
+
+ virtual void GetStyleName(nsSubstring& aResult) override;
+ virtual void GetPrefix(nsSubstring& aResult) override;
+ virtual void GetSuffix(nsSubstring& aResult) override;
+ virtual void GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& 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 uint8_t GetSpeakAs() override;
+ virtual bool UseNegativeSign() override;
+
+ virtual void CallFallbackStyle(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& aResult,
+ bool& aIsRTL) override;
+ virtual bool GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& aResult,
+ bool& aIsRTL) override;
+
+ bool IsExtendsSystem()
+ {
+ return mSystem == NS_STYLE_COUNTER_SYSTEM_EXTENDS;
+ }
+
+ // CustomCounterStyle should be reference-counted because it may be
+ // dereferenced from the manager but still referenced by nodes and
+ // frames before the style change is propagated.
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() override;
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+
+ void* operator new(size_t sz, nsPresContext* aPresContext)
+ {
+ return aPresContext->PresShell()->AllocateByObjectID(
+ eArenaObjectID_CustomCounterStyle, sz);
+ }
+
+private:
+ void Destroy()
+ {
+ nsIPresShell* shell = mManager->PresContext()->PresShell();
+ this->~CustomCounterStyle();
+ shell->FreeByObjectID(eArenaObjectID_CustomCounterStyle, this);
+ }
+
+ const nsTArray<nsString>& GetSymbols();
+ const nsTArray<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.
+ uint8_t GetSpeakAsAutoValue();
+ void ComputeRawSpeakAs(uint8_t& aSpeakAs,
+ CounterStyle*& aSpeakAsCounter);
+ CounterStyle* ComputeSpeakAs();
+
+ CounterStyle* ComputeExtends();
+ CounterStyle* GetExtends();
+ CounterStyle* GetExtendsRoot();
+
+ nsString mName;
+
+ // 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<nsCSSCounterStyleRule> mRule;
+ uint32_t mRuleGeneration;
+
+ uint8_t mSystem;
+ // GetSpeakAs will ensure that private member mSpeakAs is initialized before used
+ MOZ_INIT_OUTSIDE_CTOR uint8_t 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.
+ nsTArray<nsString> mSymbols;
+ nsTArray<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;
+
+ nsAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+};
+
+NS_IMPL_ADDREF(CustomCounterStyle)
+NS_IMPL_RELEASE_WITH_DESTROY(CustomCounterStyle, Destroy())
+
+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 = mRule->GetGeneration();
+}
+
+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::GetStyleName(nsSubstring& aResult)
+{
+ aResult.Assign(mName);
+}
+
+/* virtual */ void
+CustomCounterStyle::GetPrefix(nsSubstring& aResult)
+{
+ if (!(mFlags & FLAG_PREFIX_INITED)) {
+ mFlags |= FLAG_PREFIX_INITED;
+
+ const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Prefix);
+ if (value.UnitHasStringValue()) {
+ value.GetStringValue(mPrefix);
+ } else if (IsExtendsSystem()) {
+ GetExtends()->GetPrefix(mPrefix);
+ } else {
+ mPrefix.Truncate();
+ }
+ }
+ aResult = mPrefix;
+}
+
+/* virtual */ void
+CustomCounterStyle::GetSuffix(nsSubstring& aResult)
+{
+ if (!(mFlags & FLAG_SUFFIX_INITED)) {
+ mFlags |= FLAG_SUFFIX_INITED;
+
+ const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Suffix);
+ if (value.UnitHasStringValue()) {
+ value.GetStringValue(mSuffix);
+ } else if (IsExtendsSystem()) {
+ GetExtends()->GetSuffix(mSuffix);
+ } else {
+ mSuffix.AssignLiteral(u". ");
+ }
+ }
+ aResult = mSuffix;
+}
+
+/* virtual */ void
+CustomCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& aResult,
+ bool& aIsBullet)
+{
+ if (GetSpeakAs() != NS_STYLE_COUNTER_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 NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+ // Only use ::-moz-list-bullet for cyclic system
+ return true;
+ case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
+ return GetExtendsRoot()->IsBullet();
+ default:
+ return false;
+ }
+}
+
+/* virtual */ void
+CustomCounterStyle::GetNegative(NegativeType& aResult)
+{
+ if (!(mFlags & FLAG_NEGATIVE_INITED)) {
+ mFlags |= FLAG_NEGATIVE_INITED;
+ const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Negative);
+ switch (value.GetUnit()) {
+ case eCSSUnit_Ident:
+ case eCSSUnit_String:
+ value.GetStringValue(mNegative.before);
+ mNegative.after.Truncate();
+ break;
+ case eCSSUnit_Pair: {
+ const nsCSSValuePair& pair = value.GetPairValue();
+ pair.mXValue.GetStringValue(mNegative.before);
+ pair.mYValue.GetStringValue(mNegative.after);
+ break;
+ }
+ default: {
+ if (IsExtendsSystem()) {
+ GetExtends()->GetNegative(mNegative);
+ } else {
+ mNegative.before.AssignLiteral(u"-");
+ mNegative.after.Truncate();
+ }
+ }
+ }
+ }
+ aResult = mNegative;
+}
+
+static inline bool
+IsRangeValueInfinite(const nsCSSValue& aValue)
+{
+ return aValue.GetUnit() == eCSSUnit_Enumerated &&
+ aValue.GetIntValue() == NS_STYLE_COUNTER_RANGE_INFINITE;
+}
+
+/* virtual */ bool
+CustomCounterStyle::IsOrdinalInRange(CounterValue aOrdinal)
+{
+ const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Range);
+ if (value.GetUnit() == eCSSUnit_PairList) {
+ for (const nsCSSValuePairList* item = value.GetPairListValue();
+ item != nullptr; item = item->mNext) {
+ const nsCSSValue& lowerBound = item->mXValue;
+ const nsCSSValue& upperBound = item->mYValue;
+ if ((IsRangeValueInfinite(lowerBound) ||
+ aOrdinal >= lowerBound.GetIntValue()) &&
+ (IsRangeValueInfinite(upperBound) ||
+ aOrdinal <= upperBound.GetIntValue())) {
+ return true;
+ }
+ }
+ return false;
+ } else if (IsExtendsSystem() && value.GetUnit() == eCSSUnit_None) {
+ // Only use the range of extended style when 'range' is not specified.
+ return GetExtends()->IsOrdinalInRange(aOrdinal);
+ }
+ return IsOrdinalInAutoRange(aOrdinal);
+}
+
+/* virtual */ bool
+CustomCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal)
+{
+ switch (mSystem) {
+ case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+ case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+ case NS_STYLE_COUNTER_SYSTEM_FIXED:
+ return true;
+ case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+ case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+ return aOrdinal >= 1;
+ case NS_STYLE_COUNTER_SYSTEM_ADDITIVE:
+ return aOrdinal >= 0;
+ case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
+ return GetExtendsRoot()->IsOrdinalInAutoRange(aOrdinal);
+ default:
+ NS_NOTREACHED("Invalid system for computing auto value.");
+ return false;
+ }
+}
+
+/* virtual */ void
+CustomCounterStyle::GetPad(PadType& aResult)
+{
+ if (!(mFlags & FLAG_PAD_INITED)) {
+ mFlags |= FLAG_PAD_INITED;
+ const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Pad);
+ if (value.GetUnit() == eCSSUnit_Pair) {
+ const nsCSSValuePair& pair = value.GetPairValue();
+ mPad.width = pair.mXValue.GetIntValue();
+ pair.mYValue.GetStringValue(mPad.symbol);
+ } else if (IsExtendsSystem()) {
+ GetExtends()->GetPad(mPad);
+ } else {
+ mPad.width = 0;
+ mPad.symbol.Truncate();
+ }
+ }
+ aResult = mPad;
+}
+
+/* virtual */ CounterStyle*
+CustomCounterStyle::GetFallback()
+{
+ if (!mFallback) {
+ const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Fallback);
+ if (value.UnitHasStringValue()) {
+ mFallback = mManager->BuildCounterStyle(
+ nsDependentString(value.GetStringBufferValue()));
+ } else if (IsExtendsSystem()) {
+ mFallback = GetExtends()->GetFallback();
+ } else {
+ mFallback = CounterStyleManager::GetDecimalStyle();
+ }
+ }
+ return mFallback;
+}
+
+/* virtual */ uint8_t
+CustomCounterStyle::GetSpeakAs()
+{
+ if (!(mFlags & FLAG_SPEAKAS_INITED)) {
+ ComputeSpeakAs();
+ }
+ return mSpeakAs;
+}
+
+/* virtual */ bool
+CustomCounterStyle::UseNegativeSign()
+{
+ if (mSystem == NS_STYLE_COUNTER_SYSTEM_EXTENDS) {
+ return GetExtendsRoot()->UseNegativeSign();
+ }
+ return SystemUsesNegativeSign(mSystem);
+}
+
+/* virtual */ void
+CustomCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& 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,
+ nsSubstring& aResult,
+ bool& aIsRTL)
+{
+ switch (mSystem) {
+ case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+ return GetCyclicCounterText(aOrdinal, aResult, GetSymbols());
+ case NS_STYLE_COUNTER_SYSTEM_FIXED: {
+ int32_t start = mRule->GetSystemArgument().GetIntValue();
+ return GetFixedCounterText(aOrdinal, aResult, start, GetSymbols());
+ }
+ case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+ return GetSymbolicCounterText(aOrdinal, aResult, GetSymbols());
+ case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+ return GetAlphabeticCounterText(aOrdinal, aResult, GetSymbols());
+ case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+ return GetNumericCounterText(aOrdinal, aResult, GetSymbols());
+ case NS_STYLE_COUNTER_SYSTEM_ADDITIVE:
+ return GetAdditiveCounterText(aOrdinal, aResult, GetAdditiveSymbols());
+ case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
+ return GetExtendsRoot()->
+ GetInitialCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
+ default:
+ NS_NOTREACHED("Invalid system.");
+ return false;
+ }
+}
+
+const nsTArray<nsString>&
+CustomCounterStyle::GetSymbols()
+{
+ if (mSymbols.IsEmpty()) {
+ const nsCSSValue& values = mRule->GetDesc(eCSSCounterDesc_Symbols);
+ for (const nsCSSValueList* item = values.GetListValue();
+ item; item = item->mNext) {
+ nsString* symbol = mSymbols.AppendElement();
+ item->mValue.GetStringValue(*symbol);
+ }
+ mSymbols.Compact();
+ }
+ return mSymbols;
+}
+
+const nsTArray<AdditiveSymbol>&
+CustomCounterStyle::GetAdditiveSymbols()
+{
+ if (mAdditiveSymbols.IsEmpty()) {
+ const nsCSSValue& values = mRule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
+ for (const nsCSSValuePairList* item = values.GetPairListValue();
+ item; item = item->mNext) {
+ AdditiveSymbol* symbol = mAdditiveSymbols.AppendElement();
+ symbol->weight = item->mXValue.GetIntValue();
+ item->mYValue.GetStringValue(symbol->symbol);
+ }
+ mAdditiveSymbols.Compact();
+ }
+ return mAdditiveSymbols;
+}
+
+// This method is used to provide the computed value for 'auto'.
+uint8_t
+CustomCounterStyle::GetSpeakAsAutoValue()
+{
+ uint8_t 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(uint8_t& aSpeakAs,
+ CounterStyle*& aSpeakAsCounter)
+{
+ NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_INITED),
+ "ComputeRawSpeakAs is called with speak-as inited.");
+
+ const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_SpeakAs);
+ switch (value.GetUnit()) {
+ case eCSSUnit_Auto:
+ aSpeakAs = GetSpeakAsAutoValue();
+ break;
+ case eCSSUnit_Enumerated:
+ aSpeakAs = value.GetIntValue();
+ break;
+ case eCSSUnit_Ident:
+ aSpeakAs = NS_STYLE_COUNTER_SPEAKAS_OTHER;
+ aSpeakAsCounter = mManager->BuildCounterStyle(
+ nsDependentString(value.GetStringBufferValue()));
+ break;
+ case eCSSUnit_Null: {
+ 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:
+ NS_NOTREACHED("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 == NS_STYLE_COUNTER_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 != NS_STYLE_COUNTER_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;
+ }
+
+ const nsCSSValue& value = mRule->GetSystemArgument();
+ CounterStyle* nextCounter = mManager->BuildCounterStyle(
+ nsDependentString(value.GetStringBufferValue()));
+ 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 nsSubstring& aContent)
+ : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM)
+ , mSingleString(true)
+ , mSystem(NS_STYLE_COUNTER_SYSTEM_CYCLIC)
+{
+ mSymbols.SetCapacity(1);
+ mSymbols.AppendElement(aContent);
+}
+
+AnonymousCounterStyle::AnonymousCounterStyle(const nsCSSValue::Array* aParams)
+ : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM)
+ , mSingleString(false)
+ , mSystem(aParams->Item(0).GetIntValue())
+{
+ for (const nsCSSValueList* item = aParams->Item(1).GetListValue();
+ item; item = item->mNext) {
+ item->mValue.GetStringValue(*mSymbols.AppendElement());
+ }
+ mSymbols.Compact();
+}
+
+/* virtual */ void
+AnonymousCounterStyle::GetStyleName(nsAString& aResult)
+{
+ aResult.Truncate();
+}
+
+/* virtual */ void
+AnonymousCounterStyle::GetPrefix(nsAString& aResult)
+{
+ aResult.Truncate();
+}
+
+/* virtual */ void
+AnonymousCounterStyle::GetSuffix(nsAString& aResult)
+{
+ if (IsSingleString()) {
+ aResult.Truncate();
+ } else {
+ aResult = ' ';
+ }
+}
+
+/* virtual */ bool
+AnonymousCounterStyle::IsBullet()
+{
+ switch (mSystem) {
+ case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+ // Only use ::-moz-list-bullet for cyclic system
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* virtual */ void
+AnonymousCounterStyle::GetNegative(NegativeType& aResult)
+{
+ aResult.before.AssignLiteral(u"-");
+ aResult.after.Truncate();
+}
+
+/* virtual */ bool
+AnonymousCounterStyle::IsOrdinalInRange(CounterValue aOrdinal)
+{
+ switch (mSystem) {
+ case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+ case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+ case NS_STYLE_COUNTER_SYSTEM_FIXED:
+ return true;
+ case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+ case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+ return aOrdinal >= 1;
+ default:
+ NS_NOTREACHED("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();
+}
+
+/* virtual */ uint8_t
+AnonymousCounterStyle::GetSpeakAs()
+{
+ return GetDefaultSpeakAsForSystem(mSystem);
+}
+
+/* virtual */ bool
+AnonymousCounterStyle::UseNegativeSign()
+{
+ return SystemUsesNegativeSign(mSystem);
+}
+
+/* virtual */ bool
+AnonymousCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult,
+ bool& aIsRTL)
+{
+ switch (mSystem) {
+ case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+ return GetCyclicCounterText(aOrdinal, aResult, mSymbols);
+ case NS_STYLE_COUNTER_SYSTEM_FIXED:
+ return GetFixedCounterText(aOrdinal, aResult, 1, mSymbols);
+ case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+ return GetSymbolicCounterText(aOrdinal, aResult, mSymbols);
+ case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+ return GetAlphabeticCounterText(aOrdinal, aResult, mSymbols);
+ case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+ return GetNumericCounterText(aOrdinal, aResult, mSymbols);
+ default:
+ NS_NOTREACHED("Invalid system.");
+ return false;
+ }
+}
+
+bool
+CounterStyle::IsDependentStyle() const
+{
+ switch (mStyle) {
+ // CustomCounterStyle
+ case NS_STYLE_LIST_STYLE_CUSTOM:
+ // DependentBuiltinCounterStyle
+ case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+ case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+ case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+ return true;
+
+ // BuiltinCounterStyle
+ default:
+ return false;
+ }
+}
+
+void
+CounterStyle::GetCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& 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) {
+ PadType pad;
+ GetPad(pad);
+ // We have to calculate the difference here since suffix part of negative
+ // sign may be appended to initialText later.
+ int32_t diff = pad.width -
+ unicode::CountGraphemeClusters(initialText.Data(),
+ initialText.Length());
+ 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);
+ }
+ 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,
+ nsSubstring& aResult,
+ bool& aIsBullet)
+{
+ bool isRTL; // we don't care about direction for spoken text
+ aIsBullet = false;
+ switch (GetSpeakAs()) {
+ case NS_STYLE_COUNTER_SPEAKAS_BULLETS:
+ aResult.Assign(kDiscCharacter);
+ aIsBullet = true;
+ break;
+ case NS_STYLE_COUNTER_SPEAKAS_NUMBERS:
+ DecimalToText(aOrdinal, aResult);
+ break;
+ case NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT:
+ // we currently do not actually support 'spell-out',
+ // so 'words' is used instead.
+ case NS_STYLE_COUNTER_SPEAKAS_WORDS:
+ GetCounterText(aOrdinal, WritingMode(), aResult, isRTL);
+ break;
+ case NS_STYLE_COUNTER_SPEAKAS_OTHER:
+ // This should be processed by CustomCounterStyle
+ NS_NOTREACHED("Invalid speak-as value");
+ break;
+ default:
+ NS_NOTREACHED("Unknown speak-as value");
+ break;
+ }
+}
+
+/* virtual */ void
+CounterStyle::CallFallbackStyle(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsAString& aResult,
+ bool& aIsRTL)
+{
+ GetFallback()->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
+}
+
+static BuiltinCounterStyle gBuiltinStyleTable[NS_STYLE_LIST_STYLE__MAX];
+
+CounterStyleManager::CounterStyleManager(nsPresContext* aPresContext)
+ : mPresContext(aPresContext)
+{
+ // Insert the static styles into cache table
+ mCacheTable.Put(NS_LITERAL_STRING("none"), GetNoneStyle());
+ mCacheTable.Put(NS_LITERAL_STRING("decimal"), GetDecimalStyle());
+}
+
+CounterStyleManager::~CounterStyleManager()
+{
+ MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
+}
+
+/* static */ void
+CounterStyleManager::InitializeBuiltinCounterStyles()
+{
+ for (uint32_t i = 0; i < NS_STYLE_LIST_STYLE__MAX; ++i) {
+ gBuiltinStyleTable[i].mStyle = i;
+ }
+}
+
+void
+CounterStyleManager::Disconnect()
+{
+#ifdef DEBUG
+ for (auto iter = mCacheTable.Iter(); !iter.Done(); iter.Next()) {
+ CounterStyle* style = iter.UserData();
+ style->AddRef();
+ auto refcnt = style->Release();
+ NS_ASSERTION(!style->IsDependentStyle() || refcnt == 1,
+ "Counter style is still referenced by other objects.");
+ }
+#endif
+ mCacheTable.Clear();
+ mPresContext = nullptr;
+}
+
+CounterStyle*
+CounterStyleManager::BuildCounterStyle(const nsSubstring& aName)
+{
+ CounterStyle* data = mCacheTable.GetWeak(aName);
+ if (data) {
+ return data;
+ }
+
+ // It is intentional that the predefined names are case-insensitive
+ // but the user-defined names case-sensitive.
+ // XXXheycam ServoStyleSets do not support custom counter styles yet.
+ StyleSetHandle styleSet = mPresContext->StyleSet();
+ NS_ASSERTION(styleSet->IsGecko(),
+ "stylo: ServoStyleSets do not support custom counter "
+ "styles yet");
+ nsCSSCounterStyleRule* rule = styleSet->IsGecko() ?
+ styleSet->AsGecko()->CounterStyleRuleForName(aName) : nullptr;
+ if (rule) {
+ data = new (mPresContext) CustomCounterStyle(aName, this, rule);
+ } else {
+ int32_t type;
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aName);
+ if (nsCSSProps::FindKeyword(keyword, nsCSSProps::kListStyleKTable, type)) {
+ if (gBuiltinStyleTable[type].IsDependentStyle()) {
+ data = new (mPresContext) DependentBuiltinCounterStyle(type, this);
+ } else {
+ data = GetBuiltinStyle(type);
+ }
+ }
+ }
+ if (!data) {
+ data = GetDecimalStyle();
+ }
+ mCacheTable.Put(aName, data);
+ return data;
+}
+
+/* static */ CounterStyle*
+CounterStyleManager::GetBuiltinStyle(int32_t aStyle)
+{
+ MOZ_ASSERT(0 <= aStyle && aStyle < NS_STYLE_LIST_STYLE__MAX,
+ "Require a valid builtin style constant");
+ MOZ_ASSERT(!gBuiltinStyleTable[aStyle].IsDependentStyle(),
+ "Cannot get dependent builtin style");
+ return &gBuiltinStyleTable[aStyle];
+}
+
+bool
+CounterStyleManager::NotifyRuleChanged()
+{
+ bool changed = false;
+ nsTArray<RefPtr<CounterStyle>> kungFuDeathGrip;
+ for (auto iter = mCacheTable.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<CounterStyle>& style = iter.Data();
+ bool toBeUpdated = false;
+ bool toBeRemoved = false;
+ // XXXheycam ServoStyleSets do not support custom counter styles yet.
+ StyleSetHandle styleSet = mPresContext->StyleSet();
+ NS_ASSERTION(styleSet->IsGecko(),
+ "stylo: ServoStyleSets do not support custom counter "
+ "styles yet");
+ nsCSSCounterStyleRule* newRule = styleSet->IsGecko() ?
+ styleSet->AsGecko()->CounterStyleRuleForName(iter.Key()) : nullptr;
+ if (!newRule) {
+ if (style->IsCustomStyle()) {
+ toBeRemoved = true;
+ }
+ } else {
+ if (!style->IsCustomStyle()) {
+ toBeRemoved = true;
+ } else {
+ auto custom = static_cast<CustomCounterStyle*>(style.get());
+ if (custom->GetRule() != newRule) {
+ toBeRemoved = true;
+ } else if (custom->GetRuleGeneration() != newRule->GetGeneration()) {
+ toBeUpdated = true;
+ custom->ResetCachedData();
+ }
+ }
+ }
+ changed = changed || toBeUpdated || toBeRemoved;
+ if (toBeRemoved) {
+ if (style->IsDependentStyle()) {
+ if (style->IsCustomStyle()) {
+ // Since |style| is being removed from mCacheTable, it won't be
+ // visited by our post-removal iteration. So, we have to give it a
+ // manual ResetDependentData() call. (This only really matters if
+ // something else is holding a reference and keeping it alive.)
+ static_cast<CustomCounterStyle*>(style.get())->ResetDependentData();
+ }
+ // The object has to be held here so that it will not be released
+ // before all pointers that refer to it are reset. It will be released
+ // when kungFuDeathGrip goes out of scope at the end of this function.
+ kungFuDeathGrip.AppendElement(style);
+ }
+ iter.Remove();
+ }
+ }
+
+ if (changed) {
+ for (auto iter = mCacheTable.Iter(); !iter.Done(); iter.Next()) {
+ CounterStyle* style = iter.UserData();
+ 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;
+}
+
+} // namespace mozilla
diff --git a/layout/style/CounterStyleManager.h b/layout/style/CounterStyleManager.h
new file mode 100644
index 000000000..2f760f340
--- /dev/null
+++ b/layout/style/CounterStyleManager.h
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsStringFwd.h"
+#include "nsRefPtrHashtable.h"
+#include "nsHashKeys.h"
+
+#include "nsStyleConsts.h"
+
+#include "mozilla/Attributes.h"
+
+#include "nsCSSValue.h"
+
+class nsPresContext;
+
+namespace mozilla {
+
+class WritingMode;
+
+typedef int32_t CounterValue;
+
+class CounterStyleManager;
+class AnonymousCounterStyle;
+
+struct NegativeType;
+struct PadType;
+
+class CounterStyle
+{
+protected:
+ explicit constexpr CounterStyle(int32_t aStyle)
+ : mStyle(aStyle)
+ {
+ }
+
+private:
+ CounterStyle(const CounterStyle& aOther) = delete;
+ void operator=(const CounterStyle& other) = delete;
+
+public:
+ int32_t GetStyle() const { return mStyle; }
+ bool IsNone() const { return mStyle == NS_STYLE_LIST_STYLE_NONE; }
+ bool IsCustomStyle() const { return mStyle == NS_STYLE_LIST_STYLE_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 GetStyleName(nsSubstring& aResult) = 0;
+ virtual void GetPrefix(nsSubstring& aResult) = 0;
+ virtual void GetSuffix(nsSubstring& aResult) = 0;
+ void GetCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& aResult,
+ bool& aIsRTL);
+ virtual void GetSpokenCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& 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 uint8_t GetSpeakAs() = 0;
+ virtual bool UseNegativeSign() = 0;
+
+ virtual void CallFallbackStyle(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& aResult,
+ bool& aIsRTL);
+ virtual bool GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& aResult,
+ bool& aIsRTL) = 0;
+
+ virtual AnonymousCounterStyle* AsAnonymous() { return nullptr; }
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
+
+protected:
+ int32_t mStyle;
+};
+
+class AnonymousCounterStyle final : public CounterStyle
+{
+public:
+ explicit AnonymousCounterStyle(const nsSubstring& aContent);
+ explicit AnonymousCounterStyle(const nsCSSValue::Array* aValue);
+
+ virtual void GetStyleName(nsAString& aResult) override;
+ 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 uint8_t GetSpeakAs() override;
+ virtual bool UseNegativeSign() override;
+
+ virtual bool GetInitialCounterText(CounterValue aOrdinal,
+ WritingMode aWritingMode,
+ nsSubstring& aResult,
+ bool& aIsRTL) override;
+
+ virtual AnonymousCounterStyle* AsAnonymous() override { return this; }
+
+ bool IsSingleString() const { return mSingleString; }
+ uint8_t GetSystem() const { return mSystem; }
+ const nsTArray<nsString>& GetSymbols() const { return mSymbols; }
+
+ NS_INLINE_DECL_REFCOUNTING(AnonymousCounterStyle, override)
+
+private:
+ ~AnonymousCounterStyle() {}
+
+ bool mSingleString;
+ uint8_t mSystem;
+ nsTArray<nsString> mSymbols;
+};
+
+class CounterStyleManager final
+{
+private:
+ ~CounterStyleManager();
+public:
+ explicit CounterStyleManager(nsPresContext* aPresContext);
+
+ static void InitializeBuiltinCounterStyles();
+
+ void Disconnect();
+
+ bool IsInitial() const
+ {
+ // only 'none' and 'decimal'
+ return mCacheTable.Count() == 2;
+ }
+
+ CounterStyle* BuildCounterStyle(const nsSubstring& aName);
+
+ static CounterStyle* GetBuiltinStyle(int32_t aStyle);
+ static CounterStyle* GetNoneStyle()
+ {
+ return GetBuiltinStyle(NS_STYLE_LIST_STYLE_NONE);
+ }
+ static CounterStyle* GetDecimalStyle()
+ {
+ return GetBuiltinStyle(NS_STYLE_LIST_STYLE_DECIMAL);
+ }
+
+ // 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();
+
+ nsPresContext* PresContext() const { return mPresContext; }
+
+ NS_INLINE_DECL_REFCOUNTING(CounterStyleManager)
+
+private:
+ nsPresContext* mPresContext;
+ nsRefPtrHashtable<nsStringHashKey, CounterStyle> mCacheTable;
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_CounterStyleManager_h_) */
diff --git a/layout/style/Declaration.cpp b/layout/style/Declaration.cpp
new file mode 100644
index 000000000..c67f6b2a2
--- /dev/null
+++ b/layout/style/Declaration.cpp
@@ -0,0 +1,1988 @@
+/* -*- 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/. */
+
+/*
+ * representation of a declaration block (or style attribute) in a CSS
+ * stylesheet
+ */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "mozilla/css/Declaration.h"
+#include "nsPrintfCString.h"
+#include "gfxFontConstants.h"
+#include "nsStyleUtil.h"
+
+namespace mozilla {
+namespace css {
+
+NS_IMPL_QUERY_INTERFACE(ImportantStyleData, nsIStyleRule)
+NS_IMPL_ADDREF_USING_AGGREGATOR(ImportantStyleData, Declaration())
+NS_IMPL_RELEASE_USING_AGGREGATOR(ImportantStyleData, Declaration())
+
+/* virtual */ void
+ImportantStyleData::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ Declaration()->MapImportantRuleInfoInto(aRuleData);
+}
+
+/* virtual */ bool
+ImportantStyleData::MightMapInheritedStyleData()
+{
+ return Declaration()->MapsImportantInheritedStyleData();
+}
+
+/* virtual */ bool
+ImportantStyleData::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue)
+{
+ return Declaration()->GetDiscretelyAnimatedCSSValue(aProperty, aValue);
+}
+
+
+#ifdef DEBUG
+/* virtual */ void
+ImportantStyleData::List(FILE* out, int32_t aIndent) const
+{
+ // Indent
+ nsAutoCString str;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+
+ str.AppendLiteral("! important rule\n");
+ fprintf_stderr(out, "%s", str.get());
+}
+#endif
+
+Declaration::Declaration(const Declaration& aCopy)
+ : DeclarationBlock(aCopy),
+ mOrder(aCopy.mOrder),
+ mVariableOrder(aCopy.mVariableOrder),
+ mData(aCopy.mData ? aCopy.mData->Clone() : nullptr),
+ mImportantData(aCopy.mImportantData ?
+ aCopy.mImportantData->Clone() : nullptr),
+ mVariables(aCopy.mVariables ?
+ new CSSVariableDeclarations(*aCopy.mVariables) :
+ nullptr),
+ mImportantVariables(aCopy.mImportantVariables ?
+ new CSSVariableDeclarations(*aCopy.mImportantVariables) :
+ nullptr)
+{
+}
+
+Declaration::~Declaration()
+{
+}
+
+NS_INTERFACE_MAP_BEGIN(Declaration)
+ if (aIID.Equals(NS_GET_IID(mozilla::css::Declaration))) {
+ *aInstancePtr = this;
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+ else
+ NS_INTERFACE_MAP_ENTRY(nsIStyleRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStyleRule)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(Declaration)
+NS_IMPL_RELEASE(Declaration)
+
+/* virtual */ void
+Declaration::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ MOZ_ASSERT(mData, "must call only while compressed");
+ mData->MapRuleInfoInto(aRuleData);
+ if (mVariables) {
+ mVariables->MapRuleInfoInto(aRuleData);
+ }
+}
+
+/* virtual */ bool
+Declaration::MightMapInheritedStyleData()
+{
+ MOZ_ASSERT(mData, "must call only while compressed");
+ if (mVariables && mVariables->Count() != 0) {
+ return true;
+ }
+ return mData->HasInheritedStyleData();
+}
+
+/* virtual */ bool
+Declaration::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue)
+{
+ nsCSSCompressedDataBlock* data = GetPropertyIsImportantByID(aProperty)
+ ? mImportantData : mData;
+ const nsCSSValue* value = data->ValueFor(aProperty);
+ if (!value) {
+ return false;
+ }
+ *aValue = *value;
+ return true;
+}
+
+
+bool
+Declaration::MapsImportantInheritedStyleData() const
+{
+ MOZ_ASSERT(mData, "must call only while compressed");
+ MOZ_ASSERT(HasImportantData(), "must only be called for Declarations with "
+ "important data");
+ if (mImportantVariables && mImportantVariables->Count() != 0) {
+ return true;
+ }
+ return mImportantData ? mImportantData->HasInheritedStyleData() : false;
+}
+
+void
+Declaration::ValueAppended(nsCSSPropertyID aProperty)
+{
+ MOZ_ASSERT(!mData && !mImportantData,
+ "should only be called while expanded");
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
+ "shorthands forbidden");
+ // order IS important for CSS, so remove and add to the end
+ mOrder.RemoveElement(static_cast<uint32_t>(aProperty));
+ mOrder.AppendElement(static_cast<uint32_t>(aProperty));
+}
+
+template<typename PropFunc, typename CustomPropFunc>
+inline void
+DispatchPropertyOperation(const nsAString& aProperty,
+ PropFunc aPropFunc, CustomPropFunc aCustomPropFunc)
+{
+ nsCSSPropertyID propID =
+ nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent);
+ if (propID != eCSSProperty_UNKNOWN) {
+ if (propID != eCSSPropertyExtra_variable) {
+ aPropFunc(propID);
+ } else {
+ aCustomPropFunc(Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH));
+ }
+ }
+}
+
+void
+Declaration::GetPropertyValue(const nsAString& aProperty,
+ nsAString& aValue) const
+{
+ DispatchPropertyOperation(aProperty,
+ [&](nsCSSPropertyID propID) { GetPropertyValueByID(propID, aValue); },
+ [&](const nsAString& name) { GetVariableValue(name, aValue); });
+}
+
+void
+Declaration::GetPropertyValueByID(nsCSSPropertyID aPropID,
+ nsAString& aValue) const
+{
+ GetPropertyValueInternal(aPropID, aValue, nsCSSValue::eNormalized);
+}
+
+void
+Declaration::GetAuthoredPropertyValue(const nsAString& aProperty,
+ nsAString& aValue) const
+{
+ DispatchPropertyOperation(aProperty,
+ [&](nsCSSPropertyID propID) {
+ GetPropertyValueInternal(propID, aValue, nsCSSValue::eAuthorSpecified);
+ },
+ [&](const nsAString& name) { GetVariableValue(name, aValue); });
+}
+
+bool
+Declaration::GetPropertyIsImportant(const nsAString& aProperty) const
+{
+ bool r = false;
+ DispatchPropertyOperation(aProperty,
+ [&](nsCSSPropertyID propID) { r = GetPropertyIsImportantByID(propID); },
+ [&](const nsAString& name) { r = GetVariableIsImportant(name); });
+ return r;
+}
+
+void
+Declaration::RemoveProperty(const nsAString& aProperty)
+{
+ DispatchPropertyOperation(aProperty,
+ [&](nsCSSPropertyID propID) { RemovePropertyByID(propID); },
+ [&](const nsAString& name) { RemoveVariable(name); });
+}
+
+void
+Declaration::RemovePropertyByID(nsCSSPropertyID aProperty)
+{
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT);
+
+ nsCSSExpandedDataBlock data;
+ ExpandTo(&data);
+ MOZ_ASSERT(!mData && !mImportantData, "Expand didn't null things out");
+
+ if (nsCSSProps::IsShorthand(aProperty)) {
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty,
+ CSSEnabledState::eForAllContent) {
+ data.ClearLonghandProperty(*p);
+ mOrder.RemoveElement(static_cast<uint32_t>(*p));
+ }
+ } else {
+ data.ClearLonghandProperty(aProperty);
+ mOrder.RemoveElement(static_cast<uint32_t>(aProperty));
+ }
+
+ CompressFrom(&data);
+}
+
+bool
+Declaration::HasProperty(nsCSSPropertyID aProperty) const
+{
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
+ "property ID out of range");
+
+ nsCSSCompressedDataBlock *data = GetPropertyIsImportantByID(aProperty)
+ ? mImportantData : mData;
+ const nsCSSValue *val = data->ValueFor(aProperty);
+ return !!val;
+}
+
+bool
+Declaration::AppendValueToString(nsCSSPropertyID aProperty,
+ nsAString& aResult,
+ nsCSSValue::Serialization aSerialization) const
+{
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
+ "property ID out of range");
+
+ nsCSSCompressedDataBlock *data = GetPropertyIsImportantByID(aProperty)
+ ? mImportantData : mData;
+ const nsCSSValue *val = data->ValueFor(aProperty);
+ if (!val) {
+ return false;
+ }
+
+ val->AppendToString(aProperty, aResult, aSerialization);
+ return true;
+}
+
+static void
+AppendSingleImageLayerPositionValue(const nsCSSValue& aPositionX,
+ const nsCSSValue& aPositionY,
+ const nsCSSPropertyID aTable[],
+ nsAString& aValue,
+ nsCSSValue::Serialization aSerialization)
+{
+ // We need to make sure that we don't serialize to an invalid 3-value form.
+ // The 3-value form is only valid if both edges are present.
+ const nsCSSValue &xEdge = aPositionX.GetArrayValue()->Item(0);
+ const nsCSSValue &xOffset = aPositionX.GetArrayValue()->Item(1);
+ const nsCSSValue &yEdge = aPositionY.GetArrayValue()->Item(0);
+ const nsCSSValue &yOffset = aPositionY.GetArrayValue()->Item(1);
+ bool xHasEdge = (eCSSUnit_Enumerated == xEdge.GetUnit());
+ bool xHasBoth = xHasEdge && (eCSSUnit_Null != xOffset.GetUnit());
+ bool yHasEdge = (eCSSUnit_Enumerated == yEdge.GetUnit());
+ bool yHasBoth = yHasEdge && (eCSSUnit_Null != yOffset.GetUnit());
+
+ if (yHasBoth && !xHasEdge) {
+ // Output 4-value form by adding the x edge.
+ aValue.AppendLiteral("left ");
+ }
+ aPositionX.AppendToString(aTable[nsStyleImageLayers::positionX],
+ aValue, aSerialization);
+
+ aValue.Append(char16_t(' '));
+
+ if (xHasBoth && !yHasEdge) {
+ // Output 4-value form by adding the y edge.
+ aValue.AppendLiteral("top ");
+ }
+ aPositionY.AppendToString(aTable[nsStyleImageLayers::positionY],
+ aValue, aSerialization);
+}
+
+void
+Declaration::GetImageLayerValue(
+ nsCSSCompressedDataBlock *data,
+ nsAString& aValue,
+ nsCSSValue::Serialization aSerialization,
+ const nsCSSPropertyID aTable[]) const
+{
+ // We know from our caller that all subproperties were specified.
+ // However, we still can't represent that in the shorthand unless
+ // they're all lists of the same length. So if they're different
+ // lengths, we need to bail out.
+ // We also need to bail out if an item has background-clip and
+ // background-origin that are different and not the default
+ // values. (We omit them if they're both default.)
+
+ // Common CSS properties for both background & mask layer.
+ const nsCSSValueList *image =
+ data->ValueFor(aTable[nsStyleImageLayers::image])->GetListValue();
+ const nsCSSValuePairList *repeat =
+ data->ValueFor(aTable[nsStyleImageLayers::repeat])->GetPairListValue();
+ const nsCSSValueList *positionX =
+ data->ValueFor(aTable[nsStyleImageLayers::positionX])->GetListValue();
+ const nsCSSValueList *positionY =
+ data->ValueFor(aTable[nsStyleImageLayers::positionY])->GetListValue();
+ const nsCSSValueList *clip =
+ data->ValueFor(aTable[nsStyleImageLayers::clip])->GetListValue();
+ const nsCSSValueList *origin =
+ data->ValueFor(aTable[nsStyleImageLayers::origin])->GetListValue();
+ const nsCSSValuePairList *size =
+ data->ValueFor(aTable[nsStyleImageLayers::size])->GetPairListValue();
+
+ // Background layer property.
+ const nsCSSValueList *attachment =
+ (aTable[nsStyleImageLayers::attachment] == eCSSProperty_UNKNOWN)?
+ nullptr :
+ data->ValueFor(aTable[nsStyleImageLayers::attachment])->GetListValue();
+
+ // Mask layer properties.
+ const nsCSSValueList *composite =
+ (aTable[nsStyleImageLayers::composite] == eCSSProperty_UNKNOWN)?
+ nullptr :
+ data->ValueFor(aTable[nsStyleImageLayers::composite])->GetListValue();
+ const nsCSSValueList *mode =
+ (aTable[nsStyleImageLayers::maskMode] == eCSSProperty_UNKNOWN)?
+ nullptr :
+ data->ValueFor(aTable[nsStyleImageLayers::maskMode])->GetListValue();
+
+ for (;;) {
+ // Serialize background-color at the beginning of the last item.
+ if (!image->mNext) {
+ if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
+ AppendValueToString(aTable[nsStyleImageLayers::color], aValue,
+ aSerialization);
+ aValue.Append(char16_t(' '));
+ }
+ }
+
+ image->mValue.AppendToString(aTable[nsStyleImageLayers::image], aValue,
+ aSerialization);
+
+ aValue.Append(char16_t(' '));
+ repeat->mXValue.AppendToString(aTable[nsStyleImageLayers::repeat], aValue,
+ aSerialization);
+ if (repeat->mYValue.GetUnit() != eCSSUnit_Null) {
+ repeat->mYValue.AppendToString(aTable[nsStyleImageLayers::repeat], aValue,
+ aSerialization);
+ }
+
+ if (attachment) {
+ aValue.Append(char16_t(' '));
+ attachment->mValue.AppendToString(aTable[nsStyleImageLayers::attachment],
+ aValue, aSerialization);
+ }
+
+ aValue.Append(char16_t(' '));
+ AppendSingleImageLayerPositionValue(positionX->mValue, positionY->mValue,
+ aTable, aValue, aSerialization);
+
+ if (size->mXValue.GetUnit() != eCSSUnit_Auto ||
+ size->mYValue.GetUnit() != eCSSUnit_Auto) {
+ aValue.Append(char16_t(' '));
+ aValue.Append(char16_t('/'));
+ aValue.Append(char16_t(' '));
+ size->mXValue.AppendToString(aTable[nsStyleImageLayers::size], aValue,
+ aSerialization);
+ aValue.Append(char16_t(' '));
+ size->mYValue.AppendToString(aTable[nsStyleImageLayers::size], aValue,
+ aSerialization);
+ }
+
+ MOZ_ASSERT(clip->mValue.GetUnit() == eCSSUnit_Enumerated &&
+ origin->mValue.GetUnit() == eCSSUnit_Enumerated,
+ "should not have inherit/initial within list");
+
+ int32_t originDefaultValue =
+ (aTable == nsStyleImageLayers::kBackgroundLayerTable)
+ ? NS_STYLE_IMAGELAYER_ORIGIN_PADDING : NS_STYLE_IMAGELAYER_ORIGIN_BORDER;
+ if (clip->mValue.GetIntValue() != NS_STYLE_IMAGELAYER_CLIP_BORDER ||
+ origin->mValue.GetIntValue() != originDefaultValue) {
+#ifdef DEBUG
+ for (size_t i = 0; nsCSSProps::kImageLayerOriginKTable[i].mValue != -1; i++) {
+ // For each keyword & value in kOriginKTable, ensure that
+ // kBackgroundKTable has a matching entry at the same position.
+ MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mKeyword ==
+ nsCSSProps::kBackgroundClipKTable[i].mKeyword);
+ MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mValue ==
+ nsCSSProps::kBackgroundClipKTable[i].mValue);
+ }
+#endif
+ static_assert(NS_STYLE_IMAGELAYER_CLIP_BORDER ==
+ NS_STYLE_IMAGELAYER_ORIGIN_BORDER &&
+ NS_STYLE_IMAGELAYER_CLIP_PADDING ==
+ NS_STYLE_IMAGELAYER_ORIGIN_PADDING &&
+ NS_STYLE_IMAGELAYER_CLIP_CONTENT ==
+ NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
+ "mask-clip and mask-origin style constants must agree");
+ aValue.Append(char16_t(' '));
+ origin->mValue.AppendToString(aTable[nsStyleImageLayers::origin], aValue,
+ aSerialization);
+
+ if (clip->mValue != origin->mValue) {
+ aValue.Append(char16_t(' '));
+ clip->mValue.AppendToString(aTable[nsStyleImageLayers::clip], aValue,
+ aSerialization);
+ }
+ }
+
+ if (composite) {
+ aValue.Append(char16_t(' '));
+ composite->mValue.AppendToString(aTable[nsStyleImageLayers::composite],
+ aValue, aSerialization);
+ }
+
+ if (mode) {
+ aValue.Append(char16_t(' '));
+ mode->mValue.AppendToString(aTable[nsStyleImageLayers::maskMode],
+ aValue, aSerialization);
+ }
+
+ image = image->mNext;
+ repeat = repeat->mNext;
+ positionX = positionX->mNext;
+ positionY = positionY->mNext;
+ clip = clip->mNext;
+ origin = origin->mNext;
+ size = size->mNext;
+ attachment = attachment ? attachment->mNext : nullptr;
+ composite = composite ? composite->mNext : nullptr;
+ mode = mode ? mode->mNext : nullptr;
+
+ if (!image) {
+ // This layer is an background layer
+ if (aTable == nsStyleImageLayers::kBackgroundLayerTable) {
+ if (repeat || positionX || positionY || clip || origin || size ||
+ attachment) {
+ // Uneven length lists, so can't be serialized as shorthand.
+ aValue.Truncate();
+ return;
+ }
+ // This layer is an mask layer
+ } else {
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ MOZ_ASSERT(aTable == nsStyleImageLayers::kMaskLayerTable);
+#else
+ MOZ_ASSERT_UNREACHABLE("Should never get here when mask-as-shorthand is disable");
+#endif
+ if (repeat || positionX || positionY || clip || origin || size ||
+ composite || mode) {
+ // Uneven length lists, so can't be serialized as shorthand.
+ aValue.Truncate();
+ return;
+ }
+ }
+ break;
+ }
+
+ // This layer is an background layer
+ if (aTable == nsStyleImageLayers::kBackgroundLayerTable) {
+ if (!repeat || !positionX || !positionY || !clip || !origin || !size ||
+ !attachment) {
+ // Uneven length lists, so can't be serialized as shorthand.
+ aValue.Truncate();
+ return;
+ }
+ // This layer is an mask layer
+ } else {
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ MOZ_ASSERT(aTable == nsStyleImageLayers::kMaskLayerTable);
+#else
+ MOZ_ASSERT_UNREACHABLE("Should never get here when mask-as-shorthand is disable");
+#endif
+ if (!repeat || !positionX || !positionY || !clip || !origin || !size ||
+ !composite || !mode) {
+ // Uneven length lists, so can't be serialized as shorthand.
+ aValue.Truncate();
+ return;
+ }
+ }
+ aValue.Append(char16_t(','));
+ aValue.Append(char16_t(' '));
+ }
+}
+
+void
+Declaration::GetImageLayerPositionValue(
+ nsCSSCompressedDataBlock *data,
+ nsAString& aValue,
+ nsCSSValue::Serialization aSerialization,
+ const nsCSSPropertyID aTable[]) const
+{
+ // We know from above that all subproperties were specified.
+ // However, we still can't represent that in the shorthand unless
+ // they're all lists of the same length. So if they're different
+ // lengths, we need to bail out.
+ const nsCSSValueList *positionX =
+ data->ValueFor(aTable[nsStyleImageLayers::positionX])->GetListValue();
+ const nsCSSValueList *positionY =
+ data->ValueFor(aTable[nsStyleImageLayers::positionY])->GetListValue();
+ for (;;) {
+ AppendSingleImageLayerPositionValue(positionX->mValue, positionY->mValue,
+ aTable, aValue, aSerialization);
+ positionX = positionX->mNext;
+ positionY = positionY->mNext;
+
+ if (!positionX || !positionY) {
+ if (positionX || positionY) {
+ // Uneven length lists, so can't be serialized as shorthand.
+ aValue.Truncate();
+ }
+ return;
+ }
+ aValue.Append(char16_t(','));
+ aValue.Append(char16_t(' '));
+ }
+}
+
+void
+Declaration::GetPropertyValueInternal(
+ nsCSSPropertyID aProperty, nsAString& aValue,
+ nsCSSValue::Serialization aSerialization) const
+{
+ aValue.Truncate(0);
+
+ // simple properties are easy.
+ if (!nsCSSProps::IsShorthand(aProperty)) {
+ AppendValueToString(aProperty, aValue, aSerialization);
+ return;
+ }
+
+ // DOM Level 2 Style says (when describing CSS2Properties, although
+ // not CSSStyleDeclaration.getPropertyValue):
+ // However, if there is no shorthand declaration that could be added
+ // to the ruleset without changing in any way the rules already
+ // declared in the ruleset (i.e., by adding longhand rules that were
+ // previously not declared in the ruleset), then the empty string
+ // should be returned for the shorthand property.
+ // This means we need to check a number of cases:
+ // (1) Since a shorthand sets all sub-properties, if some of its
+ // subproperties were not specified, we must return the empty
+ // string.
+ // (2) Since 'inherit', 'initial' and 'unset' can only be specified
+ // as the values for entire properties, we need to return the
+ // empty string if some but not all of the subproperties have one
+ // of those values.
+ // (3) Since a single value only makes sense with or without
+ // !important, we return the empty string if some values are
+ // !important and some are not.
+ // Since we're doing this check for 'inherit' and 'initial' up front,
+ // we can also simplify the property serialization code by serializing
+ // those values up front as well.
+ //
+ // Additionally, if a shorthand property was set using a value with a
+ // variable reference and none of its component longhand properties were
+ // then overridden on the declaration, we return the token stream
+ // assigned to the shorthand.
+ const nsCSSValue* tokenStream = nullptr;
+ uint32_t totalCount = 0, importantCount = 0,
+ initialCount = 0, inheritCount = 0, unsetCount = 0,
+ matchingTokenStreamCount = 0, nonMatchingTokenStreamCount = 0;
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty,
+ CSSEnabledState::eForAllContent) {
+ if (*p == eCSSProperty__x_system_font) {
+ // The system-font subproperty doesn't count.
+ continue;
+ }
+ ++totalCount;
+ const nsCSSValue *val = mData->ValueFor(*p);
+ MOZ_ASSERT(!val || !mImportantData || !mImportantData->ValueFor(*p),
+ "can't be in both blocks");
+ if (!val && mImportantData) {
+ ++importantCount;
+ val = mImportantData->ValueFor(*p);
+ }
+ if (!val) {
+ // Case (1) above: some subproperties not specified.
+ return;
+ }
+ if (val->GetUnit() == eCSSUnit_Inherit) {
+ ++inheritCount;
+ } else if (val->GetUnit() == eCSSUnit_Initial) {
+ ++initialCount;
+ } else if (val->GetUnit() == eCSSUnit_Unset) {
+ ++unsetCount;
+ } else if (val->GetUnit() == eCSSUnit_TokenStream) {
+ if (val->GetTokenStreamValue()->mShorthandPropertyID == aProperty) {
+ tokenStream = val;
+ ++matchingTokenStreamCount;
+ } else {
+ ++nonMatchingTokenStreamCount;
+ }
+ }
+ }
+ if (importantCount != 0 && importantCount != totalCount) {
+ // Case (3), no consistent importance.
+ return;
+ }
+ if (initialCount == totalCount) {
+ // Simplify serialization below by serializing initial up-front.
+ nsCSSValue(eCSSUnit_Initial).AppendToString(eCSSProperty_UNKNOWN, aValue,
+ nsCSSValue::eNormalized);
+ return;
+ }
+ if (inheritCount == totalCount) {
+ // Simplify serialization below by serializing inherit up-front.
+ nsCSSValue(eCSSUnit_Inherit).AppendToString(eCSSProperty_UNKNOWN, aValue,
+ nsCSSValue::eNormalized);
+ return;
+ }
+ if (unsetCount == totalCount) {
+ // Simplify serialization below by serializing unset up-front.
+ nsCSSValue(eCSSUnit_Unset).AppendToString(eCSSProperty_UNKNOWN, aValue,
+ nsCSSValue::eNormalized);
+ return;
+ }
+ if (initialCount != 0 || inheritCount != 0 ||
+ unsetCount != 0 || nonMatchingTokenStreamCount != 0) {
+ // Case (2): partially initial, inherit, unset or token stream.
+ return;
+ }
+ if (tokenStream) {
+ if (matchingTokenStreamCount == totalCount) {
+ // Shorthand was specified using variable references and all of its
+ // longhand components were set by the shorthand.
+ aValue.Append(tokenStream->GetTokenStreamValue()->mTokenStream);
+ } else {
+ // In all other cases, serialize to the empty string.
+ }
+ return;
+ }
+
+ nsCSSCompressedDataBlock *data = importantCount ? mImportantData : mData;
+ switch (aProperty) {
+ case eCSSProperty_margin:
+ case eCSSProperty_padding:
+ case eCSSProperty_border_color:
+ case eCSSProperty_border_style:
+ case eCSSProperty_border_width: {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ MOZ_ASSERT(nsCSSProps::GetStringValue(subprops[0]).Find("-top") !=
+ kNotFound, "first subprop must be top");
+ MOZ_ASSERT(nsCSSProps::GetStringValue(subprops[1]).Find("-right") !=
+ kNotFound, "second subprop must be right");
+ MOZ_ASSERT(nsCSSProps::GetStringValue(subprops[2]).Find("-bottom") !=
+ kNotFound, "third subprop must be bottom");
+ MOZ_ASSERT(nsCSSProps::GetStringValue(subprops[3]).Find("-left") !=
+ kNotFound, "fourth subprop must be left");
+ const nsCSSValue* vals[4] = {
+ data->ValueFor(subprops[0]),
+ data->ValueFor(subprops[1]),
+ data->ValueFor(subprops[2]),
+ data->ValueFor(subprops[3])
+ };
+ nsCSSValue::AppendSidesShorthandToString(subprops, vals, aValue,
+ aSerialization);
+ break;
+ }
+ case eCSSProperty_border_radius:
+ case eCSSProperty__moz_outline_radius: {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ const nsCSSValue* vals[4] = {
+ data->ValueFor(subprops[0]),
+ data->ValueFor(subprops[1]),
+ data->ValueFor(subprops[2]),
+ data->ValueFor(subprops[3])
+ };
+ nsCSSValue::AppendBasicShapeRadiusToString(subprops, vals, aValue,
+ aSerialization);
+ break;
+ }
+ case eCSSProperty_border_image: {
+ // Even though there are some cases where we could omit
+ // 'border-image-source' (when it's none), it's probably not a
+ // good idea since it's likely to be confusing. It would also
+ // require adding the extra check that we serialize *something*.
+ AppendValueToString(eCSSProperty_border_image_source, aValue,
+ aSerialization);
+
+ bool sliceDefault = data->HasDefaultBorderImageSlice();
+ bool widthDefault = data->HasDefaultBorderImageWidth();
+ bool outsetDefault = data->HasDefaultBorderImageOutset();
+
+ if (!sliceDefault || !widthDefault || !outsetDefault) {
+ aValue.Append(char16_t(' '));
+ AppendValueToString(eCSSProperty_border_image_slice, aValue,
+ aSerialization);
+ if (!widthDefault || !outsetDefault) {
+ aValue.AppendLiteral(" /");
+ if (!widthDefault) {
+ aValue.Append(char16_t(' '));
+ AppendValueToString(eCSSProperty_border_image_width, aValue,
+ aSerialization);
+ }
+ if (!outsetDefault) {
+ aValue.AppendLiteral(" / ");
+ AppendValueToString(eCSSProperty_border_image_outset, aValue,
+ aSerialization);
+ }
+ }
+ }
+
+ bool repeatDefault = data->HasDefaultBorderImageRepeat();
+ if (!repeatDefault) {
+ aValue.Append(char16_t(' '));
+ AppendValueToString(eCSSProperty_border_image_repeat, aValue,
+ aSerialization);
+ }
+ break;
+ }
+ case eCSSProperty_border: {
+ // If we have a non-default value for any of the properties that
+ // this shorthand sets but cannot specify, we have to return the
+ // empty string.
+ if (data->ValueFor(eCSSProperty_border_image_source)->GetUnit() !=
+ eCSSUnit_None ||
+ !data->HasDefaultBorderImageSlice() ||
+ !data->HasDefaultBorderImageWidth() ||
+ !data->HasDefaultBorderImageOutset() ||
+ !data->HasDefaultBorderImageRepeat() ||
+ data->ValueFor(eCSSProperty_border_top_colors)->GetUnit() !=
+ eCSSUnit_None ||
+ data->ValueFor(eCSSProperty_border_right_colors)->GetUnit() !=
+ eCSSUnit_None ||
+ data->ValueFor(eCSSProperty_border_bottom_colors)->GetUnit() !=
+ eCSSUnit_None ||
+ data->ValueFor(eCSSProperty_border_left_colors)->GetUnit() !=
+ eCSSUnit_None) {
+ break;
+ }
+
+ const nsCSSPropertyID* subproptables[3] = {
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color),
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_style),
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_width)
+ };
+ bool match = true;
+ for (const nsCSSPropertyID** subprops = subproptables,
+ **subprops_end = ArrayEnd(subproptables);
+ subprops < subprops_end; ++subprops) {
+ const nsCSSValue *firstSide = data->ValueFor((*subprops)[0]);
+ for (int32_t side = 1; side < 4; ++side) {
+ const nsCSSValue *otherSide =
+ data->ValueFor((*subprops)[side]);
+ if (*firstSide != *otherSide)
+ match = false;
+ }
+ }
+ if (!match) {
+ // We can't express what we have in the border shorthand
+ break;
+ }
+ // tweak aProperty and fall through
+ aProperty = eCSSProperty_border_top;
+ MOZ_FALLTHROUGH;
+ }
+ case eCSSProperty_border_top:
+ case eCSSProperty_border_right:
+ case eCSSProperty_border_bottom:
+ case eCSSProperty_border_left:
+ case eCSSProperty_border_inline_start:
+ case eCSSProperty_border_inline_end:
+ case eCSSProperty_border_block_start:
+ case eCSSProperty_border_block_end:
+ case eCSSProperty_column_rule:
+ case eCSSProperty_outline: {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ MOZ_ASSERT(StringEndsWith(nsCSSProps::GetStringValue(subprops[2]),
+ NS_LITERAL_CSTRING("-color")),
+ "third subprop must be the color property");
+ const nsCSSValue *colorValue = data->ValueFor(subprops[2]);
+ bool isCurrentColor =
+ colorValue->GetUnit() == eCSSUnit_EnumColor &&
+ colorValue->GetIntValue() == NS_COLOR_CURRENTCOLOR;
+ if (!AppendValueToString(subprops[0], aValue, aSerialization) ||
+ !(aValue.Append(char16_t(' ')),
+ AppendValueToString(subprops[1], aValue, aSerialization)) ||
+ // Don't output a third value when it's currentcolor.
+ !(isCurrentColor ||
+ (aValue.Append(char16_t(' ')),
+ AppendValueToString(subprops[2], aValue, aSerialization)))) {
+ aValue.Truncate();
+ }
+ break;
+ }
+ case eCSSProperty_background: {
+ GetImageLayerValue(data, aValue, aSerialization,
+ nsStyleImageLayers::kBackgroundLayerTable);
+ break;
+ }
+ case eCSSProperty_background_position: {
+ GetImageLayerPositionValue(data, aValue, aSerialization,
+ nsStyleImageLayers::kBackgroundLayerTable);
+ break;
+ }
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ case eCSSProperty_mask: {
+ GetImageLayerValue(data, aValue, aSerialization,
+ nsStyleImageLayers::kMaskLayerTable);
+ break;
+ }
+ case eCSSProperty_mask_position: {
+ GetImageLayerPositionValue(data, aValue, aSerialization,
+ nsStyleImageLayers::kMaskLayerTable);
+ break;
+ }
+#endif
+ case eCSSProperty_font: {
+ // systemFont might not be present; other values are guaranteed to be
+ // available based on the shorthand check at the beginning of the
+ // function, as long as the prop is enabled
+ const nsCSSValue *systemFont =
+ data->ValueFor(eCSSProperty__x_system_font);
+ const nsCSSValue *style =
+ data->ValueFor(eCSSProperty_font_style);
+ const nsCSSValue *weight =
+ data->ValueFor(eCSSProperty_font_weight);
+ const nsCSSValue *size =
+ data->ValueFor(eCSSProperty_font_size);
+ const nsCSSValue *lh =
+ data->ValueFor(eCSSProperty_line_height);
+ const nsCSSValue *family =
+ data->ValueFor(eCSSProperty_font_family);
+ const nsCSSValue *stretch =
+ data->ValueFor(eCSSProperty_font_stretch);
+ const nsCSSValue *sizeAdjust =
+ data->ValueFor(eCSSProperty_font_size_adjust);
+ const nsCSSValue *featureSettings =
+ data->ValueFor(eCSSProperty_font_feature_settings);
+ const nsCSSValue *languageOverride =
+ data->ValueFor(eCSSProperty_font_language_override);
+ const nsCSSValue *fontKerning =
+ data->ValueFor(eCSSProperty_font_kerning);
+ const nsCSSValue *fontSynthesis =
+ data->ValueFor(eCSSProperty_font_synthesis);
+ const nsCSSValue *fontVariantAlternates =
+ data->ValueFor(eCSSProperty_font_variant_alternates);
+ const nsCSSValue *fontVariantCaps =
+ data->ValueFor(eCSSProperty_font_variant_caps);
+ const nsCSSValue *fontVariantEastAsian =
+ data->ValueFor(eCSSProperty_font_variant_east_asian);
+ const nsCSSValue *fontVariantLigatures =
+ data->ValueFor(eCSSProperty_font_variant_ligatures);
+ const nsCSSValue *fontVariantNumeric =
+ data->ValueFor(eCSSProperty_font_variant_numeric);
+ const nsCSSValue *fontVariantPosition =
+ data->ValueFor(eCSSProperty_font_variant_position);
+
+ if (systemFont &&
+ systemFont->GetUnit() != eCSSUnit_None &&
+ systemFont->GetUnit() != eCSSUnit_Null) {
+ if (style->GetUnit() != eCSSUnit_System_Font ||
+ weight->GetUnit() != eCSSUnit_System_Font ||
+ size->GetUnit() != eCSSUnit_System_Font ||
+ lh->GetUnit() != eCSSUnit_System_Font ||
+ family->GetUnit() != eCSSUnit_System_Font ||
+ stretch->GetUnit() != eCSSUnit_System_Font ||
+ sizeAdjust->GetUnit() != eCSSUnit_System_Font ||
+ featureSettings->GetUnit() != eCSSUnit_System_Font ||
+ languageOverride->GetUnit() != eCSSUnit_System_Font ||
+ fontKerning->GetUnit() != eCSSUnit_System_Font ||
+ fontSynthesis->GetUnit() != eCSSUnit_System_Font ||
+ fontVariantAlternates->GetUnit() != eCSSUnit_System_Font ||
+ fontVariantCaps->GetUnit() != eCSSUnit_System_Font ||
+ fontVariantEastAsian->GetUnit() != eCSSUnit_System_Font ||
+ fontVariantLigatures->GetUnit() != eCSSUnit_System_Font ||
+ fontVariantNumeric->GetUnit() != eCSSUnit_System_Font ||
+ fontVariantPosition->GetUnit() != eCSSUnit_System_Font) {
+ // This can't be represented as a shorthand.
+ return;
+ }
+ systemFont->AppendToString(eCSSProperty__x_system_font, aValue,
+ aSerialization);
+ } else {
+ // properties reset by this shorthand property to their
+ // initial values but not represented in its syntax
+ if (sizeAdjust->GetUnit() != eCSSUnit_None ||
+ featureSettings->GetUnit() != eCSSUnit_Normal ||
+ languageOverride->GetUnit() != eCSSUnit_Normal ||
+ fontKerning->GetIntValue() != NS_FONT_KERNING_AUTO ||
+ fontSynthesis->GetUnit() != eCSSUnit_Enumerated ||
+ fontSynthesis->GetIntValue() !=
+ (NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE) ||
+ fontVariantAlternates->GetUnit() != eCSSUnit_Normal ||
+ fontVariantEastAsian->GetUnit() != eCSSUnit_Normal ||
+ fontVariantLigatures->GetUnit() != eCSSUnit_Normal ||
+ fontVariantNumeric->GetUnit() != eCSSUnit_Normal ||
+ fontVariantPosition->GetUnit() != eCSSUnit_Normal) {
+ return;
+ }
+
+ // only a normal or small-caps values of font-variant-caps can
+ // be represented in the font shorthand
+ if (fontVariantCaps->GetUnit() != eCSSUnit_Normal &&
+ (fontVariantCaps->GetUnit() != eCSSUnit_Enumerated ||
+ fontVariantCaps->GetIntValue() != NS_FONT_VARIANT_CAPS_SMALLCAPS)) {
+ return;
+ }
+
+ if (style->GetUnit() != eCSSUnit_Enumerated ||
+ style->GetIntValue() != NS_FONT_STYLE_NORMAL) {
+ style->AppendToString(eCSSProperty_font_style, aValue,
+ aSerialization);
+ aValue.Append(char16_t(' '));
+ }
+ if (fontVariantCaps->GetUnit() != eCSSUnit_Normal) {
+ fontVariantCaps->AppendToString(eCSSProperty_font_variant_caps, aValue,
+ aSerialization);
+ aValue.Append(char16_t(' '));
+ }
+ if (weight->GetUnit() != eCSSUnit_Enumerated ||
+ weight->GetIntValue() != NS_FONT_WEIGHT_NORMAL) {
+ weight->AppendToString(eCSSProperty_font_weight, aValue,
+ aSerialization);
+ aValue.Append(char16_t(' '));
+ }
+ if (stretch->GetUnit() != eCSSUnit_Enumerated ||
+ stretch->GetIntValue() != NS_FONT_STRETCH_NORMAL) {
+ stretch->AppendToString(eCSSProperty_font_stretch, aValue,
+ aSerialization);
+ aValue.Append(char16_t(' '));
+ }
+ size->AppendToString(eCSSProperty_font_size, aValue, aSerialization);
+ if (lh->GetUnit() != eCSSUnit_Normal) {
+ aValue.Append(char16_t('/'));
+ lh->AppendToString(eCSSProperty_line_height, aValue, aSerialization);
+ }
+ aValue.Append(char16_t(' '));
+ family->AppendToString(eCSSProperty_font_family, aValue,
+ aSerialization);
+ }
+ break;
+ }
+ case eCSSProperty_font_variant: {
+ const nsCSSPropertyID *subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ const nsCSSValue *fontVariantLigatures =
+ data->ValueFor(eCSSProperty_font_variant_ligatures);
+
+ // all subproperty values normal? system font?
+ bool normalLigs = true, normalNonLigs = true, systemFont = true,
+ hasSystem = false;
+ for (const nsCSSPropertyID *sp = subprops; *sp != eCSSProperty_UNKNOWN; sp++) {
+ const nsCSSValue *spVal = data->ValueFor(*sp);
+ bool isNormal = (spVal->GetUnit() == eCSSUnit_Normal);
+ if (*sp == eCSSProperty_font_variant_ligatures) {
+ normalLigs = normalLigs && isNormal;
+ } else {
+ normalNonLigs = normalNonLigs && isNormal;
+ }
+ bool isSystem = (spVal->GetUnit() == eCSSUnit_System_Font);
+ systemFont = systemFont && isSystem;
+ hasSystem = hasSystem || isSystem;
+ }
+
+ bool ligsNone =
+ fontVariantLigatures->GetUnit() == eCSSUnit_None;
+
+ // normal, none, or system font ==> single value
+ if ((normalLigs && normalNonLigs) ||
+ (normalNonLigs && ligsNone) ||
+ systemFont) {
+ fontVariantLigatures->AppendToString(eCSSProperty_font_variant_ligatures,
+ aValue,
+ aSerialization);
+ } else if (ligsNone || hasSystem) {
+ // ligatures none but other values are non-normal ==> empty
+ // at least one but not all values are system font ==> empty
+ return;
+ } else {
+ // iterate over and append non-normal values
+ bool appendSpace = false;
+ for (const nsCSSPropertyID *sp = subprops;
+ *sp != eCSSProperty_UNKNOWN; sp++) {
+ const nsCSSValue *spVal = data->ValueFor(*sp);
+ if (spVal && spVal->GetUnit() != eCSSUnit_Normal) {
+ if (appendSpace) {
+ aValue.Append(char16_t(' '));
+ } else {
+ appendSpace = true;
+ }
+ spVal->AppendToString(*sp, aValue, aSerialization);
+ }
+ }
+ }
+ break;
+ }
+ case eCSSProperty_list_style:
+ if (AppendValueToString(eCSSProperty_list_style_position, aValue,
+ aSerialization)) {
+ aValue.Append(char16_t(' '));
+ }
+ if (AppendValueToString(eCSSProperty_list_style_image, aValue,
+ aSerialization)) {
+ aValue.Append(char16_t(' '));
+ }
+ AppendValueToString(eCSSProperty_list_style_type, aValue,
+ aSerialization);
+ break;
+ case eCSSProperty_overflow: {
+ const nsCSSValue &xValue =
+ *data->ValueFor(eCSSProperty_overflow_x);
+ const nsCSSValue &yValue =
+ *data->ValueFor(eCSSProperty_overflow_y);
+ if (xValue == yValue)
+ xValue.AppendToString(eCSSProperty_overflow_x, aValue, aSerialization);
+ break;
+ }
+ case eCSSProperty_text_decoration: {
+ const nsCSSValue *decorationColor =
+ data->ValueFor(eCSSProperty_text_decoration_color);
+ const nsCSSValue *decorationStyle =
+ data->ValueFor(eCSSProperty_text_decoration_style);
+
+ MOZ_ASSERT(decorationStyle->GetUnit() == eCSSUnit_Enumerated,
+ "bad text-decoration-style unit");
+
+ AppendValueToString(eCSSProperty_text_decoration_line, aValue,
+ aSerialization);
+ if (decorationStyle->GetIntValue() !=
+ NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
+ aValue.Append(char16_t(' '));
+ AppendValueToString(eCSSProperty_text_decoration_style, aValue,
+ aSerialization);
+ }
+ if (decorationColor->GetUnit() != eCSSUnit_EnumColor ||
+ decorationColor->GetIntValue() != NS_COLOR_CURRENTCOLOR) {
+ aValue.Append(char16_t(' '));
+ AppendValueToString(eCSSProperty_text_decoration_color, aValue,
+ aSerialization);
+ }
+ break;
+ }
+ case eCSSProperty_transition: {
+ const nsCSSValue *transProp =
+ data->ValueFor(eCSSProperty_transition_property);
+ const nsCSSValue *transDuration =
+ data->ValueFor(eCSSProperty_transition_duration);
+ const nsCSSValue *transTiming =
+ data->ValueFor(eCSSProperty_transition_timing_function);
+ const nsCSSValue *transDelay =
+ data->ValueFor(eCSSProperty_transition_delay);
+
+ MOZ_ASSERT(transDuration->GetUnit() == eCSSUnit_List ||
+ transDuration->GetUnit() == eCSSUnit_ListDep,
+ "bad t-duration unit");
+ MOZ_ASSERT(transTiming->GetUnit() == eCSSUnit_List ||
+ transTiming->GetUnit() == eCSSUnit_ListDep,
+ "bad t-timing unit");
+ MOZ_ASSERT(transDelay->GetUnit() == eCSSUnit_List ||
+ transDelay->GetUnit() == eCSSUnit_ListDep,
+ "bad t-delay unit");
+
+ const nsCSSValueList* dur = transDuration->GetListValue();
+ const nsCSSValueList* tim = transTiming->GetListValue();
+ const nsCSSValueList* del = transDelay->GetListValue();
+
+ if (transProp->GetUnit() == eCSSUnit_None ||
+ transProp->GetUnit() == eCSSUnit_All) {
+ // If any of the other three lists has more than one element,
+ // we can't use the shorthand.
+ if (!dur->mNext && !tim->mNext && !del->mNext) {
+ transProp->AppendToString(eCSSProperty_transition_property, aValue,
+ aSerialization);
+ aValue.Append(char16_t(' '));
+ dur->mValue.AppendToString(eCSSProperty_transition_duration,aValue,
+ aSerialization);
+ aValue.Append(char16_t(' '));
+ tim->mValue.AppendToString(eCSSProperty_transition_timing_function,
+ aValue, aSerialization);
+ aValue.Append(char16_t(' '));
+ del->mValue.AppendToString(eCSSProperty_transition_delay, aValue,
+ aSerialization);
+ aValue.Append(char16_t(' '));
+ } else {
+ aValue.Truncate();
+ }
+ } else {
+ MOZ_ASSERT(transProp->GetUnit() == eCSSUnit_List ||
+ transProp->GetUnit() == eCSSUnit_ListDep,
+ "bad t-prop unit");
+ const nsCSSValueList* pro = transProp->GetListValue();
+ for (;;) {
+ pro->mValue.AppendToString(eCSSProperty_transition_property,
+ aValue, aSerialization);
+ aValue.Append(char16_t(' '));
+ dur->mValue.AppendToString(eCSSProperty_transition_duration,
+ aValue, aSerialization);
+ aValue.Append(char16_t(' '));
+ tim->mValue.AppendToString(eCSSProperty_transition_timing_function,
+ aValue, aSerialization);
+ aValue.Append(char16_t(' '));
+ del->mValue.AppendToString(eCSSProperty_transition_delay,
+ aValue, aSerialization);
+ pro = pro->mNext;
+ dur = dur->mNext;
+ tim = tim->mNext;
+ del = del->mNext;
+ if (!pro || !dur || !tim || !del) {
+ break;
+ }
+ aValue.AppendLiteral(", ");
+ }
+ if (pro || dur || tim || del) {
+ // Lists not all the same length, can't use shorthand.
+ aValue.Truncate();
+ }
+ }
+ break;
+ }
+ case eCSSProperty_animation: {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_animation);
+ static const size_t numProps = 8;
+ MOZ_ASSERT(subprops[numProps] == eCSSProperty_UNKNOWN,
+ "unexpected number of subproperties");
+ const nsCSSValue* values[numProps];
+ const nsCSSValueList* lists[numProps];
+
+ for (uint32_t i = 0; i < numProps; ++i) {
+ values[i] = data->ValueFor(subprops[i]);
+ MOZ_ASSERT(values[i]->GetUnit() == eCSSUnit_List ||
+ values[i]->GetUnit() == eCSSUnit_ListDep,
+ "bad a-duration unit");
+ lists[i] = values[i]->GetListValue();
+ }
+
+ for (;;) {
+ // We must serialize 'animation-name' last in case it has
+ // a value that conflicts with one of the other keyword properties.
+ MOZ_ASSERT(subprops[numProps - 1] == eCSSProperty_animation_name,
+ "animation-name must be last");
+ bool done = false;
+ for (uint32_t i = 0;;) {
+ lists[i]->mValue.AppendToString(subprops[i], aValue, aSerialization);
+ lists[i] = lists[i]->mNext;
+ if (!lists[i]) {
+ done = true;
+ }
+ if (++i == numProps) {
+ break;
+ }
+ aValue.Append(char16_t(' '));
+ }
+ if (done) {
+ break;
+ }
+ aValue.AppendLiteral(", ");
+ }
+ for (uint32_t i = 0; i < numProps; ++i) {
+ if (lists[i]) {
+ // Lists not all the same length, can't use shorthand.
+ aValue.Truncate();
+ break;
+ }
+ }
+ break;
+ }
+ case eCSSProperty_marker: {
+ const nsCSSValue &endValue =
+ *data->ValueFor(eCSSProperty_marker_end);
+ const nsCSSValue &midValue =
+ *data->ValueFor(eCSSProperty_marker_mid);
+ const nsCSSValue &startValue =
+ *data->ValueFor(eCSSProperty_marker_start);
+ if (endValue == midValue && midValue == startValue)
+ AppendValueToString(eCSSProperty_marker_end, aValue, aSerialization);
+ break;
+ }
+ case eCSSProperty_columns: {
+ // Two values, column-count and column-width, separated by a space.
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ AppendValueToString(subprops[0], aValue, aSerialization);
+ aValue.Append(char16_t(' '));
+ AppendValueToString(subprops[1], aValue, aSerialization);
+ break;
+ }
+ case eCSSProperty_flex: {
+ // flex-grow, flex-shrink, flex-basis, separated by single space
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+
+ AppendValueToString(subprops[0], aValue, aSerialization);
+ aValue.Append(char16_t(' '));
+ AppendValueToString(subprops[1], aValue, aSerialization);
+ aValue.Append(char16_t(' '));
+ AppendValueToString(subprops[2], aValue, aSerialization);
+ break;
+ }
+ case eCSSProperty_flex_flow: {
+ // flex-direction, flex-wrap, separated by single space
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ MOZ_ASSERT(subprops[2] == eCSSProperty_UNKNOWN,
+ "must have exactly two subproperties");
+
+ AppendValueToString(subprops[0], aValue, aSerialization);
+ aValue.Append(char16_t(' '));
+ AppendValueToString(subprops[1], aValue, aSerialization);
+ break;
+ }
+ case eCSSProperty_grid_row:
+ case eCSSProperty_grid_column: {
+ // grid-{row,column}-start, grid-{row,column}-end, separated by a slash
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ MOZ_ASSERT(subprops[2] == eCSSProperty_UNKNOWN,
+ "must have exactly two subproperties");
+
+ // TODO: should we simplify when possible?
+ AppendValueToString(subprops[0], aValue, aSerialization);
+ aValue.AppendLiteral(" / ");
+ AppendValueToString(subprops[1], aValue, aSerialization);
+ break;
+ }
+ case eCSSProperty_grid_area: {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ MOZ_ASSERT(subprops[4] == eCSSProperty_UNKNOWN,
+ "must have exactly four subproperties");
+
+ // TODO: should we simplify when possible?
+ AppendValueToString(subprops[0], aValue, aSerialization);
+ aValue.AppendLiteral(" / ");
+ AppendValueToString(subprops[1], aValue, aSerialization);
+ aValue.AppendLiteral(" / ");
+ AppendValueToString(subprops[2], aValue, aSerialization);
+ aValue.AppendLiteral(" / ");
+ AppendValueToString(subprops[3], aValue, aSerialization);
+ break;
+ }
+
+ // The 'grid' shorthand has 3 different possibilities for syntax:
+ // #1 <'grid-template'>
+ // #2 <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
+ // #3 [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
+ case eCSSProperty_grid: {
+ const nsCSSValue& columnGapValue =
+ *data->ValueFor(eCSSProperty_grid_column_gap);
+ if (columnGapValue.GetUnit() != eCSSUnit_Pixel ||
+ columnGapValue.GetFloatValue() != 0.0f) {
+ return; // Not serializable, bail.
+ }
+ const nsCSSValue& rowGapValue =
+ *data->ValueFor(eCSSProperty_grid_row_gap);
+ if (rowGapValue.GetUnit() != eCSSUnit_Pixel ||
+ rowGapValue.GetFloatValue() != 0.0f) {
+ return; // Not serializable, bail.
+ }
+ const nsCSSValue& areasValue =
+ *data->ValueFor(eCSSProperty_grid_template_areas);
+ const nsCSSValue& columnsValue =
+ *data->ValueFor(eCSSProperty_grid_template_columns);
+ const nsCSSValue& rowsValue =
+ *data->ValueFor(eCSSProperty_grid_template_rows);
+
+ const nsCSSValue& autoFlowValue =
+ *data->ValueFor(eCSSProperty_grid_auto_flow);
+ const nsCSSValue& autoColumnsValue =
+ *data->ValueFor(eCSSProperty_grid_auto_columns);
+ const nsCSSValue& autoRowsValue =
+ *data->ValueFor(eCSSProperty_grid_auto_rows);
+
+ // grid-template-rows/areas:none + default grid-auto-columns +
+ // non-default row grid-auto-flow or grid-auto-rows.
+ // --> serialize as 'grid' syntax #3.
+ // (for default grid-auto-flow/rows we prefer to serialize to
+ // "none ['/' ...]" instead using syntax #2 or #1 below)
+ if (rowsValue.GetUnit() == eCSSUnit_None &&
+ areasValue.GetUnit() == eCSSUnit_None &&
+ autoColumnsValue.GetUnit() == eCSSUnit_Auto &&
+ autoFlowValue.GetUnit() == eCSSUnit_Enumerated &&
+ (autoFlowValue.GetIntValue() & NS_STYLE_GRID_AUTO_FLOW_ROW) &&
+ (autoFlowValue.GetIntValue() != NS_STYLE_GRID_AUTO_FLOW_ROW ||
+ autoRowsValue.GetUnit() != eCSSUnit_Auto)) {
+ aValue.AppendLiteral("auto-flow");
+ if (autoFlowValue.GetIntValue() & NS_STYLE_GRID_AUTO_FLOW_DENSE) {
+ aValue.AppendLiteral(" dense");
+ }
+ if (autoRowsValue.GetUnit() != eCSSUnit_Auto) {
+ aValue.Append(' ');
+ AppendValueToString(eCSSProperty_grid_auto_rows,
+ aValue, aSerialization);
+ }
+ aValue.AppendLiteral(" / ");
+ AppendValueToString(eCSSProperty_grid_template_columns,
+ aValue, aSerialization);
+ break;
+ }
+
+ // grid-template-columns/areas:none + column grid-auto-flow +
+ // default grid-auto-rows.
+ // --> serialize as 'grid' syntax #2.
+ if (columnsValue.GetUnit() == eCSSUnit_None &&
+ areasValue.GetUnit() == eCSSUnit_None &&
+ autoRowsValue.GetUnit() == eCSSUnit_Auto &&
+ autoFlowValue.GetUnit() == eCSSUnit_Enumerated &&
+ (autoFlowValue.GetIntValue() & NS_STYLE_GRID_AUTO_FLOW_COLUMN)) {
+ AppendValueToString(eCSSProperty_grid_template_rows,
+ aValue, aSerialization);
+ aValue.AppendLiteral(" / auto-flow ");
+ if (autoFlowValue.GetIntValue() & NS_STYLE_GRID_AUTO_FLOW_DENSE) {
+ aValue.AppendLiteral("dense ");
+ }
+ AppendValueToString(eCSSProperty_grid_auto_columns,
+ aValue, aSerialization);
+ break;
+ }
+
+ if (!(autoFlowValue.GetUnit() == eCSSUnit_Enumerated &&
+ autoFlowValue.GetIntValue() == NS_STYLE_GRID_AUTO_FLOW_ROW &&
+ autoColumnsValue.GetUnit() == eCSSUnit_Auto &&
+ autoRowsValue.GetUnit() == eCSSUnit_Auto)) {
+ // Not serializable, bail.
+ return;
+ }
+ // Fall through to eCSSProperty_grid_template (syntax #1)
+ MOZ_FALLTHROUGH;
+ }
+ case eCSSProperty_grid_template: {
+ const nsCSSValue& areasValue =
+ *data->ValueFor(eCSSProperty_grid_template_areas);
+ const nsCSSValue& columnsValue =
+ *data->ValueFor(eCSSProperty_grid_template_columns);
+ const nsCSSValue& rowsValue =
+ *data->ValueFor(eCSSProperty_grid_template_rows);
+ if (areasValue.GetUnit() == eCSSUnit_None) {
+ AppendValueToString(eCSSProperty_grid_template_rows,
+ aValue, aSerialization);
+ aValue.AppendLiteral(" / ");
+ AppendValueToString(eCSSProperty_grid_template_columns,
+ aValue, aSerialization);
+ break;
+ }
+ if (columnsValue.GetUnit() == eCSSUnit_List ||
+ columnsValue.GetUnit() == eCSSUnit_ListDep) {
+ const nsCSSValueList* columnsItem = columnsValue.GetListValue();
+ if (columnsItem->mValue.GetUnit() == eCSSUnit_Enumerated &&
+ columnsItem->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) {
+ // We have "grid-template-areas:[something]; grid-template-columns:subgrid"
+ // which isn't a value that the shorthand can express. Bail.
+ return;
+ }
+ }
+ if (rowsValue.GetUnit() != eCSSUnit_List &&
+ rowsValue.GetUnit() != eCSSUnit_ListDep) {
+ // We have "grid-template-areas:[something]; grid-template-rows:none"
+ // which isn't a value that the shorthand can express. Bail.
+ return;
+ }
+ const nsCSSValueList* rowsItem = rowsValue.GetListValue();
+ if (rowsItem->mValue.GetUnit() == eCSSUnit_Enumerated &&
+ rowsItem->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) {
+ // We have "grid-template-areas:[something]; grid-template-rows:subgrid"
+ // which isn't a value that the shorthand can express. Bail.
+ return;
+ }
+ const GridTemplateAreasValue* areas = areasValue.GetGridTemplateAreas();
+ uint32_t nRowItems = 0;
+ while (rowsItem) {
+ nRowItems++;
+ rowsItem = rowsItem->mNext;
+ }
+ MOZ_ASSERT(nRowItems % 2 == 1, "expected an odd number of items");
+ if ((nRowItems - 1) / 2 != areas->NRows()) {
+ // Not serializable, bail.
+ return;
+ }
+ rowsItem = rowsValue.GetListValue();
+ uint32_t row = 0;
+ for (;;) {
+ bool addSpaceSeparator = true;
+ nsCSSUnit unit = rowsItem->mValue.GetUnit();
+
+ if (unit == eCSSUnit_Null) {
+ // Empty or omitted <line-names>. Serializes to nothing.
+ addSpaceSeparator = false; // Avoid a double space.
+
+ } else if (unit == eCSSUnit_List || unit == eCSSUnit_ListDep) {
+ // Non-empty <line-names>
+ aValue.Append('[');
+ rowsItem->mValue.AppendToString(eCSSProperty_grid_template_rows,
+ aValue, aSerialization);
+ aValue.Append(']');
+
+ } else {
+ nsStyleUtil::AppendEscapedCSSString(areas->mTemplates[row++], aValue);
+ aValue.Append(char16_t(' '));
+
+ // <track-size>
+ if (unit == eCSSUnit_Pair) {
+ // 'repeat()' isn't allowed with non-default 'grid-template-areas'.
+ aValue.Truncate();
+ return;
+ }
+ rowsItem->mValue.AppendToString(eCSSProperty_grid_template_rows,
+ aValue, aSerialization);
+ if (rowsItem->mNext &&
+ rowsItem->mNext->mValue.GetUnit() == eCSSUnit_Null &&
+ !rowsItem->mNext->mNext) {
+ // Break out of the loop early to avoid a trailing space.
+ break;
+ }
+ }
+
+ rowsItem = rowsItem->mNext;
+ if (!rowsItem) {
+ break;
+ }
+
+ if (addSpaceSeparator) {
+ aValue.Append(char16_t(' '));
+ }
+ }
+ if (columnsValue.GetUnit() != eCSSUnit_None) {
+ const nsCSSValueList* colsItem = columnsValue.GetListValue();
+ colsItem = colsItem->mNext; // first value is <line-names>
+ for (; colsItem; colsItem = colsItem->mNext) {
+ if (colsItem->mValue.GetUnit() == eCSSUnit_Pair) {
+ // 'repeat()' isn't allowed with non-default 'grid-template-areas'.
+ aValue.Truncate();
+ return;
+ }
+ colsItem = colsItem->mNext; // skip <line-names>
+ }
+ aValue.AppendLiteral(" / ");
+ AppendValueToString(eCSSProperty_grid_template_columns,
+ aValue, aSerialization);
+ }
+ break;
+ }
+ case eCSSProperty_place_content:
+ case eCSSProperty_place_items:
+ case eCSSProperty_place_self: {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ MOZ_ASSERT(subprops[2] == eCSSProperty_UNKNOWN,
+ "must have exactly two subproperties");
+ auto IsSingleValue = [] (const nsCSSValue& aValue) {
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Auto:
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ return true;
+ case eCSSUnit_Enumerated:
+ // return false if there is a fallback value or <overflow-position>
+ return aValue.GetIntValue() <= NS_STYLE_JUSTIFY_SPACE_EVENLY;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected unit for CSS Align property val");
+ return false;
+ }
+ };
+ // Each value must be a single value (i.e. no fallback value and no
+ // <overflow-position>), otherwise it can't be represented as a shorthand
+ // value. ('first|last baseline' counts as a single value)
+ const nsCSSValue* align = data->ValueFor(subprops[0]);
+ const nsCSSValue* justify = data->ValueFor(subprops[1]);
+ if (!align || !IsSingleValue(*align) ||
+ !justify || !IsSingleValue(*justify)) {
+ return; // Not serializable, bail.
+ }
+ MOZ_FALLTHROUGH;
+ }
+ case eCSSProperty_grid_gap: {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ MOZ_ASSERT(subprops[2] == eCSSProperty_UNKNOWN,
+ "must have exactly two subproperties");
+
+ nsAutoString val1, val2;
+ AppendValueToString(subprops[0], val1, aSerialization);
+ AppendValueToString(subprops[1], val2, aSerialization);
+ if (val1 == val2) {
+ aValue.Append(val1);
+ } else {
+ aValue.Append(val1);
+ aValue.Append(' ');
+ aValue.Append(val2);
+ }
+ break;
+ }
+ case eCSSProperty_text_emphasis: {
+ const nsCSSValue* emphasisStyle =
+ data->ValueFor(eCSSProperty_text_emphasis_style);
+ const nsCSSValue* emphasisColor =
+ data->ValueFor(eCSSProperty_text_emphasis_color);
+ bool isDefaultColor = emphasisColor->GetUnit() == eCSSUnit_EnumColor &&
+ emphasisColor->GetIntValue() == NS_COLOR_CURRENTCOLOR;
+
+ if (emphasisStyle->GetUnit() != eCSSUnit_None || isDefaultColor) {
+ AppendValueToString(eCSSProperty_text_emphasis_style,
+ aValue, aSerialization);
+ if (!isDefaultColor) {
+ aValue.Append(char16_t(' '));
+ }
+ }
+ if (!isDefaultColor) {
+ AppendValueToString(eCSSProperty_text_emphasis_color,
+ aValue, aSerialization);
+ }
+ break;
+ }
+ case eCSSProperty__moz_transform: {
+ // shorthands that are just aliases with different parsing rules
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aProperty);
+ MOZ_ASSERT(subprops[1] == eCSSProperty_UNKNOWN,
+ "must have exactly one subproperty");
+ AppendValueToString(subprops[0], aValue, aSerialization);
+ break;
+ }
+ case eCSSProperty_scroll_snap_type: {
+ const nsCSSValue& xValue =
+ *data->ValueFor(eCSSProperty_scroll_snap_type_x);
+ const nsCSSValue& yValue =
+ *data->ValueFor(eCSSProperty_scroll_snap_type_y);
+ if (xValue == yValue) {
+ AppendValueToString(eCSSProperty_scroll_snap_type_x, aValue,
+ aSerialization);
+ }
+ // If scroll-snap-type-x and scroll-snap-type-y are not equal,
+ // we don't have a shorthand that can express. Bail.
+ break;
+ }
+ case eCSSProperty__webkit_text_stroke: {
+ const nsCSSValue* strokeWidth =
+ data->ValueFor(eCSSProperty__webkit_text_stroke_width);
+ const nsCSSValue* strokeColor =
+ data->ValueFor(eCSSProperty__webkit_text_stroke_color);
+ bool isDefaultColor = strokeColor->GetUnit() == eCSSUnit_EnumColor &&
+ strokeColor->GetIntValue() == NS_COLOR_CURRENTCOLOR;
+
+ if (strokeWidth->GetUnit() != eCSSUnit_Integer ||
+ strokeWidth->GetIntValue() != 0 || isDefaultColor) {
+ AppendValueToString(eCSSProperty__webkit_text_stroke_width,
+ aValue, aSerialization);
+ if (!isDefaultColor) {
+ aValue.Append(char16_t(' '));
+ }
+ }
+ if (!isDefaultColor) {
+ AppendValueToString(eCSSProperty__webkit_text_stroke_color,
+ aValue, aSerialization);
+ }
+ break;
+ }
+ case eCSSProperty_all:
+ // If we got here, then we didn't have all "inherit" or "initial" or
+ // "unset" values for all of the longhand property components of 'all'.
+ // There is no other possible value that is valid for all properties,
+ // so serialize as the empty string.
+ break;
+ default:
+ MOZ_ASSERT(false, "no other shorthands");
+ break;
+ }
+}
+
+bool
+Declaration::GetPropertyIsImportantByID(nsCSSPropertyID aProperty) const
+{
+ if (!mImportantData)
+ return false;
+
+ // Calling ValueFor is inefficient, but we can assume '!important' is rare.
+
+ if (!nsCSSProps::IsShorthand(aProperty)) {
+ return mImportantData->ValueFor(aProperty) != nullptr;
+ }
+
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty,
+ CSSEnabledState::eForAllContent) {
+ if (*p == eCSSProperty__x_system_font) {
+ // The system_font subproperty doesn't count.
+ continue;
+ }
+ if (!mImportantData->ValueFor(*p)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+Declaration::AppendPropertyAndValueToString(nsCSSPropertyID aProperty,
+ nsAutoString& aValue,
+ nsAString& aResult) const
+{
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
+ "property enum out of range");
+ MOZ_ASSERT((aProperty < eCSSProperty_COUNT_no_shorthands) == aValue.IsEmpty(),
+ "aValue should be given for shorthands but not longhands");
+ AppendASCIItoUTF16(nsCSSProps::GetStringValue(aProperty), aResult);
+ aResult.AppendLiteral(": ");
+ if (aValue.IsEmpty())
+ AppendValueToString(aProperty, aResult, nsCSSValue::eNormalized);
+ else
+ aResult.Append(aValue);
+ if (GetPropertyIsImportantByID(aProperty)) {
+ aResult.AppendLiteral(" ! important");
+ }
+ aResult.AppendLiteral("; ");
+}
+
+void
+Declaration::AppendVariableAndValueToString(const nsAString& aName,
+ nsAString& aResult) const
+{
+ nsAutoString localName;
+ localName.AppendLiteral("--");
+ localName.Append(aName);
+ nsStyleUtil::AppendEscapedCSSIdent(localName, aResult);
+ CSSVariableDeclarations::Type type;
+ nsString value;
+ bool important;
+
+ if (mImportantVariables && mImportantVariables->Get(aName, type, value)) {
+ important = true;
+ } else {
+ MOZ_ASSERT(mVariables);
+ MOZ_ASSERT(mVariables->Has(aName));
+ mVariables->Get(aName, type, value);
+ important = false;
+ }
+
+ switch (type) {
+ case CSSVariableDeclarations::eTokenStream:
+ if (value.IsEmpty()) {
+ aResult.Append(':');
+ } else {
+ aResult.AppendLiteral(": ");
+ aResult.Append(value);
+ }
+ break;
+
+ case CSSVariableDeclarations::eInitial:
+ aResult.AppendLiteral("initial");
+ break;
+
+ case CSSVariableDeclarations::eInherit:
+ aResult.AppendLiteral("inherit");
+ break;
+
+ case CSSVariableDeclarations::eUnset:
+ aResult.AppendLiteral("unset");
+ break;
+
+ default:
+ MOZ_ASSERT(false, "unexpected variable value type");
+ }
+
+ if (important) {
+ aResult.AppendLiteral("! important");
+ }
+ aResult.AppendLiteral("; ");
+}
+
+void
+Declaration::ToString(nsAString& aString) const
+{
+ // Someone cares about this declaration's contents, so don't let it
+ // change from under them. See e.g. bug 338679.
+ SetImmutable();
+
+ nsCSSCompressedDataBlock *systemFontData =
+ GetPropertyIsImportantByID(eCSSProperty__x_system_font) ? mImportantData
+ : mData;
+ const nsCSSValue *systemFont =
+ systemFontData->ValueFor(eCSSProperty__x_system_font);
+ const bool haveSystemFont = systemFont &&
+ systemFont->GetUnit() != eCSSUnit_None &&
+ systemFont->GetUnit() != eCSSUnit_Null;
+ bool didSystemFont = false;
+
+ int32_t count = mOrder.Length();
+ int32_t index;
+ AutoTArray<nsCSSPropertyID, 16> shorthandsUsed;
+ for (index = 0; index < count; index++) {
+ nsCSSPropertyID property = GetPropertyAt(index);
+
+ if (property == eCSSPropertyExtra_variable) {
+ uint32_t variableIndex = mOrder[index] - eCSSProperty_COUNT;
+ AppendVariableAndValueToString(mVariableOrder[variableIndex], aString);
+ continue;
+ }
+
+ if (!nsCSSProps::IsEnabled(property, CSSEnabledState::eForAllContent)) {
+ continue;
+ }
+ bool doneProperty = false;
+
+ // If we already used this property in a shorthand, skip it.
+ if (shorthandsUsed.Length() > 0) {
+ for (const nsCSSPropertyID *shorthands =
+ nsCSSProps::ShorthandsContaining(property);
+ *shorthands != eCSSProperty_UNKNOWN; ++shorthands) {
+ if (shorthandsUsed.Contains(*shorthands)) {
+ doneProperty = true;
+ break;
+ }
+ }
+ if (doneProperty)
+ continue;
+ }
+
+ // Try to use this property in a shorthand.
+ nsAutoString value;
+ for (const nsCSSPropertyID *shorthands =
+ nsCSSProps::ShorthandsContaining(property);
+ *shorthands != eCSSProperty_UNKNOWN; ++shorthands) {
+ // ShorthandsContaining returns the shorthands in order from those
+ // that contain the most subproperties to those that contain the
+ // least, which is exactly the order we want to test them.
+ nsCSSPropertyID shorthand = *shorthands;
+
+ GetPropertyValueByID(shorthand, value);
+
+ // in the system font case, skip over font-variant shorthand, since all
+ // subproperties are already dealt with via the font shorthand
+ if (shorthand == eCSSProperty_font_variant &&
+ value.EqualsLiteral("-moz-use-system-font")) {
+ continue;
+ }
+
+ // If GetPropertyValueByID gives us a non-empty string back, we can
+ // use that value; otherwise it's not possible to use this shorthand.
+ if (!value.IsEmpty()) {
+ AppendPropertyAndValueToString(shorthand, value, aString);
+ shorthandsUsed.AppendElement(shorthand);
+ doneProperty = true;
+ break;
+ }
+
+ if (shorthand == eCSSProperty_font) {
+ if (haveSystemFont && !didSystemFont) {
+ // Output the shorthand font declaration that we will
+ // partially override later. But don't add it to
+ // |shorthandsUsed|, since we will have to override it.
+ systemFont->AppendToString(eCSSProperty__x_system_font, value,
+ nsCSSValue::eNormalized);
+ AppendPropertyAndValueToString(eCSSProperty_font, value, aString);
+ value.Truncate();
+ didSystemFont = true;
+ }
+
+ // That we output the system font is enough for this property if:
+ // (1) it's the hidden system font subproperty (which either
+ // means we output it or we don't have it), or
+ // (2) its value is the hidden system font value and it matches
+ // the hidden system font subproperty in importance, and
+ // we output the system font subproperty.
+ const nsCSSValue *val = systemFontData->ValueFor(property);
+ if (property == eCSSProperty__x_system_font ||
+ (haveSystemFont && val && val->GetUnit() == eCSSUnit_System_Font)) {
+ doneProperty = true;
+ break;
+ }
+ }
+ }
+ if (doneProperty)
+ continue;
+
+ MOZ_ASSERT(value.IsEmpty(), "value should be empty now");
+ AppendPropertyAndValueToString(property, value, aString);
+ }
+ if (! aString.IsEmpty()) {
+ // if the string is not empty, we have trailing whitespace we
+ // should remove
+ aString.Truncate(aString.Length() - 1);
+ }
+}
+
+#ifdef DEBUG
+/* virtual */ void
+Declaration::List(FILE* out, int32_t aIndent) const
+{
+ const Rule* owningRule = GetOwningRule();
+ if (owningRule) {
+ // More useful to print the selector and sheet URI too.
+ owningRule->List(out, aIndent);
+ return;
+ }
+
+ nsAutoCString str;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+
+ str.AppendLiteral("{ ");
+ nsAutoString s;
+ ToString(s);
+ AppendUTF16toUTF8(s, str);
+ str.AppendLiteral("}\n");
+ fprintf_stderr(out, "%s", str.get());
+}
+#endif
+
+bool
+Declaration::GetNthProperty(uint32_t aIndex, nsAString& aReturn) const
+{
+ aReturn.Truncate();
+ if (aIndex < mOrder.Length()) {
+ nsCSSPropertyID property = GetPropertyAt(aIndex);
+ if (property == eCSSPropertyExtra_variable) {
+ GetCustomPropertyNameAt(aIndex, aReturn);
+ return true;
+ }
+ if (0 <= property) {
+ AppendASCIItoUTF16(nsCSSProps::GetStringValue(property), aReturn);
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+Declaration::InitializeEmpty()
+{
+ MOZ_ASSERT(!mData && !mImportantData, "already initialized");
+ mData = nsCSSCompressedDataBlock::CreateEmptyBlock();
+}
+
+size_t
+Declaration::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ n += mOrder.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mData ? mData ->SizeOfIncludingThis(aMallocSizeOf) : 0;
+ n += mImportantData ? mImportantData->SizeOfIncludingThis(aMallocSizeOf) : 0;
+ if (mVariables) {
+ n += mVariables->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mImportantVariables) {
+ n += mImportantVariables->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+void
+Declaration::GetVariableValue(const nsAString& aName, nsAString& aValue) const
+{
+ aValue.Truncate();
+
+ CSSVariableDeclarations::Type type;
+ nsString value;
+
+ if ((mImportantVariables && mImportantVariables->Get(aName, type, value)) ||
+ (mVariables && mVariables->Get(aName, type, value))) {
+ switch (type) {
+ case CSSVariableDeclarations::eTokenStream:
+ aValue.Append(value);
+ break;
+
+ case CSSVariableDeclarations::eInitial:
+ aValue.AppendLiteral("initial");
+ break;
+
+ case CSSVariableDeclarations::eInherit:
+ aValue.AppendLiteral("inherit");
+ break;
+
+ case CSSVariableDeclarations::eUnset:
+ aValue.AppendLiteral("unset");
+ break;
+
+ default:
+ MOZ_ASSERT(false, "unexpected variable value type");
+ }
+ }
+}
+
+void
+Declaration::AddVariable(const nsAString& aName,
+ CSSVariableDeclarations::Type aType,
+ const nsString& aValue,
+ bool aIsImportant,
+ bool aOverrideImportant)
+{
+ MOZ_ASSERT(IsMutable());
+
+ nsTArray<nsString>::index_type index = mVariableOrder.IndexOf(aName);
+ if (index == nsTArray<nsString>::NoIndex) {
+ index = mVariableOrder.Length();
+ mVariableOrder.AppendElement(aName);
+ }
+
+ if (!aIsImportant && !aOverrideImportant &&
+ mImportantVariables && mImportantVariables->Has(aName)) {
+ return;
+ }
+
+ CSSVariableDeclarations* variables;
+ if (aIsImportant) {
+ if (mVariables) {
+ mVariables->Remove(aName);
+ }
+ if (!mImportantVariables) {
+ mImportantVariables = new CSSVariableDeclarations;
+ }
+ variables = mImportantVariables;
+ } else {
+ if (mImportantVariables) {
+ mImportantVariables->Remove(aName);
+ }
+ if (!mVariables) {
+ mVariables = new CSSVariableDeclarations;
+ }
+ variables = mVariables;
+ }
+
+ switch (aType) {
+ case CSSVariableDeclarations::eTokenStream:
+ variables->PutTokenStream(aName, aValue);
+ break;
+
+ case CSSVariableDeclarations::eInitial:
+ MOZ_ASSERT(aValue.IsEmpty());
+ variables->PutInitial(aName);
+ break;
+
+ case CSSVariableDeclarations::eInherit:
+ MOZ_ASSERT(aValue.IsEmpty());
+ variables->PutInherit(aName);
+ break;
+
+ case CSSVariableDeclarations::eUnset:
+ MOZ_ASSERT(aValue.IsEmpty());
+ variables->PutUnset(aName);
+ break;
+
+ default:
+ MOZ_ASSERT(false, "unexpected aType value");
+ }
+
+ uint32_t propertyIndex = index + eCSSProperty_COUNT;
+ mOrder.RemoveElement(propertyIndex);
+ mOrder.AppendElement(propertyIndex);
+}
+
+void
+Declaration::RemoveVariable(const nsAString& aName)
+{
+ if (mVariables) {
+ mVariables->Remove(aName);
+ }
+ if (mImportantVariables) {
+ mImportantVariables->Remove(aName);
+ }
+ nsTArray<nsString>::index_type index = mVariableOrder.IndexOf(aName);
+ if (index != nsTArray<nsString>::NoIndex) {
+ mOrder.RemoveElement(index + eCSSProperty_COUNT);
+ }
+}
+
+bool
+Declaration::GetVariableIsImportant(const nsAString& aName) const
+{
+ return mImportantVariables && mImportantVariables->Has(aName);
+}
+
+} // namespace css
+} // namespace mozilla
diff --git a/layout/style/Declaration.h b/layout/style/Declaration.h
new file mode 100644
index 000000000..a18c38b5d
--- /dev/null
+++ b/layout/style/Declaration.h
@@ -0,0 +1,417 @@
+/* -*- 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/. */
+
+/*
+ * representation of a declaration block (or style attribute) in a CSS
+ * stylesheet
+ */
+
+#ifndef mozilla_css_Declaration_h
+#define mozilla_css_Declaration_h
+
+// This header is in EXPORTS because it's used in several places in content/,
+// but it's not really a public interface.
+#ifndef MOZILLA_INTERNAL_API
+#error "This file should only be included within libxul"
+#endif
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/MemoryReporting.h"
+#include "CSSVariableDeclarations.h"
+#include "nsCSSDataBlock.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSProps.h"
+#include "nsIStyleRule.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include <stdio.h>
+
+// feec07b8-3fe6-491e-90d5-cc93f853e048
+#define NS_CSS_DECLARATION_IMPL_CID \
+{ 0xfeec07b8, 0x3fe6, 0x491e, \
+ { 0x90, 0xd5, 0xcc, 0x93, 0xf8, 0x53, 0xe0, 0x48 } }
+
+class nsHTMLCSSStyleSheet;
+
+namespace mozilla {
+namespace css {
+
+class Rule;
+class Declaration;
+
+/**
+ * ImportantStyleData is the implementation of nsIStyleRule (a source of
+ * style data) representing the style data coming from !important rules;
+ * the !important declarations need a separate nsIStyleRule object since
+ * they fit at a different point in the cascade.
+ *
+ * ImportantStyleData is allocated only as part of a Declaration object.
+ */
+class ImportantStyleData final : public nsIStyleRule
+{
+public:
+
+ NS_DECL_ISUPPORTS
+
+ inline ::mozilla::css::Declaration* Declaration();
+
+ // nsIStyleRule interface
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+
+private:
+ ImportantStyleData() {}
+ ~ImportantStyleData() {}
+
+ friend class ::mozilla::css::Declaration;
+};
+
+// Declaration objects have unusual lifetime rules. Every declaration
+// begins life in an invalid state which ends when InitializeEmpty or
+// CompressFrom is called upon it. After that, it can be attached to
+// exactly one style rule, and will be destroyed when that style rule
+// is destroyed. A declaration becomes immutable (via a SetImmutable
+// call) when it is matched (put in the rule tree); after that, it must
+// be copied before it can be modified, which is taken care of by
+// |EnsureMutable|.
+
+class Declaration final : public DeclarationBlock
+ , public nsIStyleRule
+{
+public:
+ /**
+ * Construct an |Declaration| that is in an invalid state (null
+ * |mData|) and cannot be used until its |CompressFrom| method or
+ * |InitializeEmpty| method is called.
+ */
+ Declaration() : DeclarationBlock(StyleBackendType::Gecko) {}
+
+ Declaration(const Declaration& aCopy);
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_CSS_DECLARATION_IMPL_CID)
+
+ NS_DECL_ISUPPORTS
+
+private:
+ ~Declaration();
+
+public:
+
+ // nsIStyleRule implementation
+ virtual void MapRuleInfoInto(nsRuleData *aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+
+ /**
+ * |ValueAppended| must be called to maintain this declaration's
+ * |mOrder| whenever a property is parsed into an expanded data block
+ * for this declaration. aProperty must not be a shorthand.
+ */
+ void ValueAppended(nsCSSPropertyID aProperty);
+
+ void GetPropertyValue(const nsAString& aProperty, nsAString& aValue) const;
+ void GetPropertyValueByID(nsCSSPropertyID aPropID, nsAString& aValue) const;
+ void GetAuthoredPropertyValue(const nsAString& aProperty,
+ nsAString& aValue) const;
+ bool GetPropertyIsImportant(const nsAString& aProperty) const;
+ void RemoveProperty(const nsAString& aProperty);
+ void RemovePropertyByID(nsCSSPropertyID aProperty);
+
+ bool HasProperty(nsCSSPropertyID aProperty) const;
+
+ bool HasImportantData() const {
+ return mImportantData || mImportantVariables;
+ }
+
+ /**
+ * Adds a custom property declaration to this object.
+ *
+ * @param aName The variable name (i.e., without the "--" prefix).
+ * @param aType The type of value the variable has.
+ * @param aValue The value of the variable, if aType is
+ * CSSVariableDeclarations::eTokenStream.
+ * @param aIsImportant Whether the declaration is !important.
+ * @param aOverrideImportant When aIsImportant is false, whether an
+ * existing !important declaration will be overridden.
+ */
+ void AddVariable(const nsAString& aName,
+ CSSVariableDeclarations::Type aType,
+ const nsString& aValue,
+ bool aIsImportant,
+ bool aOverrideImportant);
+
+ /**
+ * Removes a custom property declaration from this object.
+ *
+ * @param aName The variable name (i.e., without the "--" prefix).
+ */
+ void RemoveVariable(const nsAString& aName);
+
+ /**
+ * Gets the string value for a custom property declaration of a variable
+ * with a given name.
+ *
+ * @param aName The variable name (i.e., without the "--" prefix).
+ * @param aValue Out parameter into which the variable's value will be
+ * stored. If the value is 'initial' or 'inherit', that exact string
+ * will be stored in aValue.
+ */
+ void GetVariableValue(const nsAString& aName, nsAString& aValue) const;
+
+ /**
+ * Returns whether the custom property declaration for a variable with
+ * the given name was !important.
+ */
+ bool GetVariableIsImportant(const nsAString& aName) const;
+
+ uint32_t Count() const {
+ return mOrder.Length();
+ }
+
+ // Returns whether we actually had a property at aIndex
+ bool GetNthProperty(uint32_t aIndex, nsAString& aReturn) const;
+
+ void ToString(nsAString& aString) const;
+
+ nsCSSCompressedDataBlock* GetNormalBlock() const { return mData; }
+ nsCSSCompressedDataBlock* GetImportantBlock() const { return mImportantData; }
+
+ void AssertNotExpanded() const {
+ MOZ_ASSERT(mData, "should only be called when not expanded");
+ }
+
+ /**
+ * Initialize this declaration as holding no data. Cannot fail.
+ */
+ void InitializeEmpty();
+
+ /**
+ * Transfer all of the state from |aExpandedData| into this declaration.
+ * After calling, |aExpandedData| should be in its initial state.
+ * Callers must make sure mOrder is updated as necessary.
+ */
+ void CompressFrom(nsCSSExpandedDataBlock *aExpandedData) {
+ MOZ_ASSERT(!mData, "oops");
+ MOZ_ASSERT(!mImportantData, "oops");
+ aExpandedData->Compress(getter_Transfers(mData),
+ getter_Transfers(mImportantData),
+ mOrder);
+ aExpandedData->AssertInitialState();
+ }
+
+ /**
+ * Transfer all of the state from this declaration into
+ * |aExpandedData| and put this declaration temporarily into an
+ * invalid state (ended by |CompressFrom| or |InitializeEmpty|) that
+ * should last only during parsing. During this time only
+ * |ValueAppended| should be called.
+ */
+ void ExpandTo(nsCSSExpandedDataBlock *aExpandedData) {
+ AssertMutable();
+ aExpandedData->AssertInitialState();
+
+ MOZ_ASSERT(mData, "oops");
+ aExpandedData->Expand(mData.forget(), mImportantData.forget());
+ }
+
+ void MapImportantRuleInfoInto(nsRuleData *aRuleData) const {
+ AssertNotExpanded();
+ MOZ_ASSERT(mImportantData || mImportantVariables,
+ "must have important data or variables");
+ if (mImportantData) {
+ mImportantData->MapRuleInfoInto(aRuleData);
+ }
+ if (mImportantVariables) {
+ mImportantVariables->MapRuleInfoInto(aRuleData);
+ }
+ }
+
+ bool MapsImportantInheritedStyleData() const;
+
+ /**
+ * Attempt to replace the value for |aProperty| stored in this
+ * declaration with the matching value from |aFromBlock|.
+ * This method may only be called on a mutable declaration.
+ * It will fail (returning false) if |aProperty| is shorthand,
+ * is not already in this declaration, or does not have the indicated
+ * importance level. If it returns true, it erases the value in
+ * |aFromBlock|. |aChanged| is set to true if the declaration
+ * changed as a result of the call, and to false otherwise.
+ */
+ bool TryReplaceValue(nsCSSPropertyID aProperty, bool aIsImportant,
+ nsCSSExpandedDataBlock& aFromBlock,
+ bool* aChanged)
+ {
+ AssertMutable();
+ AssertNotExpanded();
+
+ if (nsCSSProps::IsShorthand(aProperty)) {
+ *aChanged = false;
+ return false;
+ }
+ nsCSSCompressedDataBlock *block = aIsImportant ? mImportantData : mData;
+ // mImportantData might be null
+ if (!block) {
+ *aChanged = false;
+ return false;
+ }
+
+#ifdef DEBUG
+ {
+ nsCSSCompressedDataBlock *other = aIsImportant ? mData : mImportantData;
+ MOZ_ASSERT(!other || !other->ValueFor(aProperty) ||
+ !block->ValueFor(aProperty),
+ "Property both important and not?");
+ }
+#endif
+ return block->TryReplaceValue(aProperty, aFromBlock, aChanged);
+ }
+
+ bool HasNonImportantValueFor(nsCSSPropertyID aProperty) const {
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), "must be longhand");
+ return !!mData->ValueFor(aProperty);
+ }
+
+ /**
+ * Clear the data, in preparation for its replacement with entirely
+ * new data by a call to |CompressFrom|.
+ */
+ void ClearData() {
+ AssertMutable();
+ mData = nullptr;
+ mImportantData = nullptr;
+ mVariables = nullptr;
+ mImportantVariables = nullptr;
+ mOrder.Clear();
+ mVariableOrder.Clear();
+ }
+
+ ImportantStyleData* GetImportantStyleData() {
+ if (HasImportantData()) {
+ return &mImportantStyleData;
+ }
+ return nullptr;
+ }
+
+private:
+ Declaration& operator=(const Declaration& aCopy) = delete;
+ bool operator==(const Declaration& aCopy) const = delete;
+
+ void GetPropertyValueInternal(nsCSSPropertyID aProperty, nsAString& aValue,
+ nsCSSValue::Serialization aValueSerialization)
+ const;
+ bool GetPropertyIsImportantByID(nsCSSPropertyID aProperty) const;
+
+ static void AppendImportanceToString(bool aIsImportant, nsAString& aString);
+ // return whether there was a value in |aValue| (i.e., it had a non-null unit)
+ bool AppendValueToString(nsCSSPropertyID aProperty, nsAString& aResult) const;
+ bool AppendValueToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aValueSerialization) const;
+ // Helper for ToString with strange semantics regarding aValue.
+ void AppendPropertyAndValueToString(nsCSSPropertyID aProperty,
+ nsAutoString& aValue,
+ nsAString& aResult) const;
+ // helper for ToString that serializes a custom property declaration for
+ // a variable with the specified name
+ void AppendVariableAndValueToString(const nsAString& aName,
+ nsAString& aResult) const;
+
+ void GetImageLayerValue(nsCSSCompressedDataBlock *data,
+ nsAString& aValue,
+ nsCSSValue::Serialization aSerialization,
+ const nsCSSPropertyID aTable[]) const;
+
+ void GetImageLayerPositionValue(nsCSSCompressedDataBlock *data,
+ nsAString& aValue,
+ nsCSSValue::Serialization aSerialization,
+ const nsCSSPropertyID aTable[]) const;
+
+public:
+ /**
+ * Returns the property at the given index in the ordered list of
+ * declarations. For custom properties, eCSSPropertyExtra_variable
+ * is returned.
+ */
+ nsCSSPropertyID GetPropertyAt(uint32_t aIndex) const {
+ uint32_t value = mOrder[aIndex];
+ if (value >= eCSSProperty_COUNT) {
+ return eCSSPropertyExtra_variable;
+ }
+ return nsCSSPropertyID(value);
+ }
+
+ /**
+ * Gets the name of the custom property at the given index in the ordered
+ * list of declarations.
+ */
+ void GetCustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const {
+ MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT);
+ uint32_t variableIndex = mOrder[aIndex] - eCSSProperty_COUNT;
+ aResult.Truncate();
+ aResult.AppendLiteral("--");
+ aResult.Append(mVariableOrder[variableIndex]);
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ // The order of properties in this declaration. Longhand properties are
+ // represented by their nsCSSPropertyID value, and each custom property (--*)
+ // is represented by a value that begins at eCSSProperty_COUNT.
+ //
+ // Subtracting eCSSProperty_COUNT from those values that represent custom
+ // properties results in an index into mVariableOrder, which identifies the
+ // specific variable the custom property declaration is for.
+ AutoTArray<uint32_t, 8> mOrder;
+
+ // variable names of custom properties found in mOrder
+ nsTArray<nsString> mVariableOrder;
+
+ // never null, except while expanded, or before the first call to
+ // InitializeEmpty or CompressFrom.
+ nsAutoPtr<nsCSSCompressedDataBlock> mData;
+
+ // may be null
+ nsAutoPtr<nsCSSCompressedDataBlock> mImportantData;
+
+ // may be null
+ nsAutoPtr<CSSVariableDeclarations> mVariables;
+
+ // may be null
+ nsAutoPtr<CSSVariableDeclarations> mImportantVariables;
+
+ friend class ImportantStyleData;
+ ImportantStyleData mImportantStyleData;
+};
+
+inline ::mozilla::css::Declaration*
+ImportantStyleData::Declaration()
+{
+ union {
+ char* ch; /* for pointer arithmetic */
+ ::mozilla::css::Declaration* declaration;
+ ImportantStyleData* importantData;
+ } u;
+ u.importantData = this;
+ u.ch -= offsetof(::mozilla::css::Declaration, mImportantStyleData);
+ return u.declaration;
+}
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Declaration, NS_CSS_DECLARATION_IMPL_CID)
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_Declaration_h */
diff --git a/layout/style/DeclarationBlock.h b/layout/style/DeclarationBlock.h
new file mode 100644
index 000000000..c3ed663b4
--- /dev/null
+++ b/layout/style/DeclarationBlock.h
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ServoUtils.h"
+#include "mozilla/StyleBackendType.h"
+
+#include "nsCSSPropertyID.h"
+
+class nsHTMLCSSStyleSheet;
+
+namespace mozilla {
+
+class ServoDeclarationBlock;
+
+namespace css {
+class Declaration;
+class Rule;
+} // namespace css
+
+class DeclarationBlock
+{
+protected:
+ explicit DeclarationBlock(StyleBackendType aType)
+ : mImmutable(false), mType(aType) { mContainer.mRaw = 0; }
+
+ DeclarationBlock(const DeclarationBlock& aCopy)
+ : DeclarationBlock(aCopy.mType) {}
+
+public:
+ MOZ_DECL_STYLO_METHODS(css::Declaration, ServoDeclarationBlock)
+
+ inline MozExternalRefCountType AddRef();
+ inline MozExternalRefCountType Release();
+
+ inline already_AddRefed<DeclarationBlock> Clone() const;
+
+ /**
+ * Return whether |this| may be modified.
+ */
+ bool IsMutable() const {
+ return !mImmutable;
+ }
+
+ /**
+ * Crash if |this| cannot be modified.
+ */
+ void AssertMutable() const {
+ MOZ_ASSERT(IsMutable(), "someone forgot to call EnsureMutable");
+ }
+
+ /**
+ * Mark this declaration as unmodifiable. It's 'const' so it can
+ * be called from ToString.
+ */
+ void SetImmutable() const { mImmutable = true; }
+
+ /**
+ * Copy |this|, if necessary to ensure that it can be modified.
+ */
+ inline already_AddRefed<DeclarationBlock> EnsureMutable();
+
+ 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 SetHTMLCSSStyleSheet(nsHTMLCSSStyleSheet* aHTMLCSSStyleSheet) {
+ MOZ_ASSERT(!mContainer.mHTMLCSSStyleSheet || !aHTMLCSSStyleSheet,
+ "should never overwrite one sheet with another");
+ mContainer.mHTMLCSSStyleSheet = aHTMLCSSStyleSheet;
+ if (aHTMLCSSStyleSheet) {
+ mContainer.mRaw |= uintptr_t(1);
+ }
+ }
+
+ nsHTMLCSSStyleSheet* GetHTMLCSSStyleSheet() const {
+ if (!(mContainer.mRaw & 0x1)) {
+ return nullptr;
+ }
+ auto c = mContainer;
+ c.mRaw &= ~uintptr_t(1);
+ return c.mHTMLCSSStyleSheet;
+ }
+
+ inline void ToString(nsAString& aString) const;
+
+ inline uint32_t Count() const;
+ inline bool GetNthProperty(uint32_t aIndex, nsAString& aReturn) const;
+
+ inline void GetPropertyValue(const nsAString& aProperty,
+ nsAString& aValue) const;
+ inline void GetPropertyValueByID(nsCSSPropertyID aPropID,
+ nsAString& aValue) const;
+ inline void GetAuthoredPropertyValue(const nsAString& aProperty,
+ nsAString& aValue) const;
+ inline bool GetPropertyIsImportant(const nsAString& aProperty) const;
+ inline void RemoveProperty(const nsAString& aProperty);
+ inline void RemovePropertyByID(nsCSSPropertyID aProperty);
+
+private:
+ union {
+ // We only ever have one of these since we have an
+ // nsHTMLCSSStyleSheet only for style attributes, and style
+ // attributes never have an owning rule.
+
+ // It's an nsHTMLCSSStyleSheet if the low bit is set.
+
+ uintptr_t mRaw;
+
+ // The style rule that owns this declaration. May be null.
+ css::Rule* mOwningRule;
+
+ // The nsHTMLCSSStyleSheet that is responsible for this declaration.
+ // Only non-null for style attributes.
+ nsHTMLCSSStyleSheet* mHTMLCSSStyleSheet;
+ } mContainer;
+
+ // set when declaration put in the rule tree;
+ // also by ToString (hence the 'mutable').
+ mutable bool mImmutable;
+
+ const StyleBackendType mType;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_DeclarationBlock_h
diff --git a/layout/style/DeclarationBlockInlines.h b/layout/style/DeclarationBlockInlines.h
new file mode 100644
index 000000000..791d24498
--- /dev/null
+++ b/layout/style/DeclarationBlockInlines.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_DeclarationBlockInlines_h
+#define mozilla_DeclarationBlockInlines_h
+
+#include "mozilla/css/Declaration.h"
+#include "mozilla/ServoDeclarationBlock.h"
+
+namespace mozilla {
+
+MOZ_DEFINE_STYLO_METHODS(DeclarationBlock, css::Declaration, ServoDeclarationBlock)
+
+MozExternalRefCountType
+DeclarationBlock::AddRef()
+{
+ MOZ_STYLO_FORWARD(AddRef, ())
+}
+
+MozExternalRefCountType
+DeclarationBlock::Release()
+{
+ MOZ_STYLO_FORWARD(Release, ())
+}
+
+already_AddRefed<DeclarationBlock>
+DeclarationBlock::Clone() const
+{
+ RefPtr<DeclarationBlock> result;
+ if (IsGecko()) {
+ result = new css::Declaration(*AsGecko());
+ } else {
+ result = new ServoDeclarationBlock(*AsServo());
+ }
+ return result.forget();
+}
+
+already_AddRefed<DeclarationBlock>
+DeclarationBlock::EnsureMutable()
+{
+#ifdef DEBUG
+ if (IsGecko()) {
+ AsGecko()->AssertNotExpanded();
+ }
+#endif
+ if (!IsMutable()) {
+ return Clone();
+ }
+ return do_AddRef(this);
+}
+
+void
+DeclarationBlock::ToString(nsAString& aString) const
+{
+ MOZ_STYLO_FORWARD(ToString, (aString))
+}
+
+uint32_t
+DeclarationBlock::Count() const
+{
+ MOZ_STYLO_FORWARD(Count, ())
+}
+
+bool
+DeclarationBlock::GetNthProperty(uint32_t aIndex, nsAString& aReturn) const
+{
+ MOZ_STYLO_FORWARD(GetNthProperty, (aIndex, aReturn))
+}
+
+void
+DeclarationBlock::GetPropertyValue(const nsAString& aProperty,
+ nsAString& aValue) const
+{
+ MOZ_STYLO_FORWARD(GetPropertyValue, (aProperty, aValue))
+}
+
+void
+DeclarationBlock::GetPropertyValueByID(nsCSSPropertyID aPropID,
+ nsAString& aValue) const
+{
+ MOZ_STYLO_FORWARD(GetPropertyValueByID, (aPropID, aValue))
+}
+
+void
+DeclarationBlock::GetAuthoredPropertyValue(const nsAString& aProperty,
+ nsAString& aValue) const
+{
+ MOZ_STYLO_FORWARD(GetAuthoredPropertyValue, (aProperty, aValue))
+}
+
+bool
+DeclarationBlock::GetPropertyIsImportant(const nsAString& aProperty) const
+{
+ MOZ_STYLO_FORWARD(GetPropertyIsImportant, (aProperty))
+}
+
+void
+DeclarationBlock::RemoveProperty(const nsAString& aProperty)
+{
+ MOZ_STYLO_FORWARD(RemoveProperty, (aProperty))
+}
+
+void
+DeclarationBlock::RemovePropertyByID(nsCSSPropertyID aProperty)
+{
+ MOZ_STYLO_FORWARD(RemovePropertyByID, (aProperty))
+}
+
+} // namespace mozilla
+
+#endif // mozilla_DeclarationBlockInlines_h
diff --git a/layout/style/ErrorReporter.cpp b/layout/style/ErrorReporter.cpp
new file mode 100644
index 000000000..56a84da3e
--- /dev/null
+++ b/layout/style/ErrorReporter.cpp
@@ -0,0 +1,382 @@
+/* -*- 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/. */
+
+/* diagnostic reporting for CSS style sheet parser */
+
+#include "mozilla/css/ErrorReporter.h"
+
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsCSSScanner.h"
+#include "nsIConsoleService.h"
+#include "nsIDocument.h"
+#include "nsIFactory.h"
+#include "nsIScriptError.h"
+#include "nsIStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStyleUtil.h"
+#include "nsThreadUtils.h"
+
+#ifdef CSS_REPORT_PARSE_ERRORS
+
+using namespace mozilla;
+
+namespace {
+class ShortTermURISpecCache : public Runnable {
+public:
+ ShortTermURISpecCache() : mPending(false) {}
+
+ nsString const& GetSpec(nsIURI* aURI) {
+ if (mURI != aURI) {
+ mURI = aURI;
+
+ nsAutoCString cSpec;
+ nsresult rv = mURI->GetSpec(cSpec);
+ if (NS_FAILED(rv)) {
+ cSpec.AssignLiteral("[nsIURI::GetSpec failed]");
+ }
+ CopyUTF8toUTF16(cSpec, mSpec);
+ }
+ 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
+
+static bool sReportErrors;
+static nsIConsoleService *sConsoleService;
+static nsIFactory *sScriptErrorFactory;
+static nsIStringBundle *sStringBundle;
+static ShortTermURISpecCache *sSpecCache;
+
+#define CSS_ERRORS_PREF "layout.css.report_errors"
+
+static bool
+InitGlobals()
+{
+ MOZ_ASSERT(!sConsoleService && !sScriptErrorFactory && !sStringBundle,
+ "should not have been called");
+
+ if (NS_FAILED(Preferences::AddBoolVarCache(&sReportErrors, CSS_ERRORS_PREF,
+ true))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!cs) {
+ return false;
+ }
+
+ nsCOMPtr<nsIFactory> sf = do_GetClassObject(NS_SCRIPTERROR_CONTRACTID);
+ if (!sf) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStringBundleService> sbs = services::GetStringBundleService();
+ if (!sbs) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStringBundle> sb;
+ nsresult rv = sbs->CreateBundle("chrome://global/locale/css.properties",
+ getter_AddRefs(sb));
+ if (NS_FAILED(rv) || !sb) {
+ return false;
+ }
+
+ cs.forget(&sConsoleService);
+ sf.forget(&sScriptErrorFactory);
+ sb.forget(&sStringBundle);
+
+ return true;
+}
+
+static inline bool
+ShouldReportErrors()
+{
+ if (!sConsoleService) {
+ if (!InitGlobals()) {
+ return false;
+ }
+ }
+ return sReportErrors;
+}
+
+namespace mozilla {
+namespace css {
+
+/* static */ void
+ErrorReporter::ReleaseGlobals()
+{
+ NS_IF_RELEASE(sConsoleService);
+ NS_IF_RELEASE(sScriptErrorFactory);
+ NS_IF_RELEASE(sStringBundle);
+ NS_IF_RELEASE(sSpecCache);
+}
+
+ErrorReporter::ErrorReporter(const nsCSSScanner& aScanner,
+ const CSSStyleSheet* aSheet,
+ const Loader* aLoader,
+ nsIURI* aURI)
+ : mScanner(&aScanner), mSheet(aSheet), mLoader(aLoader), mURI(aURI),
+ mInnerWindowID(0), mErrorLineNumber(0), mPrevErrorLineNumber(0),
+ mErrorColNumber(0)
+{
+}
+
+ErrorReporter::~ErrorReporter()
+{
+ // 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()) {
+ if (NS_FAILED(NS_DispatchToCurrentThread(sSpecCache))) {
+ // Peform the "deferred" cleanup immediately if the dispatch fails.
+ sSpecCache->Run();
+ } else {
+ sSpecCache->SetPending();
+ }
+ }
+}
+
+void
+ErrorReporter::OutputError()
+{
+ if (mError.IsEmpty()) {
+ return;
+ }
+ if (!ShouldReportErrors()) {
+ ClearError();
+ return;
+ }
+
+ if (mInnerWindowID == 0 && (mSheet || mLoader)) {
+ if (mSheet) {
+ mInnerWindowID = mSheet->FindOwningWindowInnerID();
+ }
+ if (mInnerWindowID == 0 && mLoader) {
+ nsIDocument* doc = mLoader->GetDocument();
+ if (doc) {
+ mInnerWindowID = doc->InnerWindowID();
+ }
+ }
+ // don't attempt this again, even if we failed
+ mSheet = nullptr;
+ mLoader = nullptr;
+ }
+
+ if (mFileName.IsEmpty()) {
+ if (mURI) {
+ if (!sSpecCache) {
+ sSpecCache = new ShortTermURISpecCache;
+ NS_ADDREF(sSpecCache);
+ }
+ mFileName = sSpecCache->GetSpec(mURI);
+ mURI = nullptr;
+ } else {
+ mFileName.AssignLiteral("from DOM");
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIScriptError> errorObject =
+ do_CreateInstance(sScriptErrorFactory, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = errorObject->InitWithWindowID(mError,
+ mFileName,
+ mErrorLine,
+ mErrorLineNumber,
+ mErrorColNumber,
+ nsIScriptError::warningFlag,
+ "CSS Parser",
+ mInnerWindowID);
+ if (NS_SUCCEEDED(rv)) {
+ sConsoleService->LogMessage(errorObject);
+ }
+ }
+
+ ClearError();
+}
+
+void
+ErrorReporter::OutputError(uint32_t aLineNumber, uint32_t aLineOffset)
+{
+ mErrorLineNumber = aLineNumber;
+ mErrorColNumber = aLineOffset;
+ OutputError();
+}
+
+void
+ErrorReporter::ClearError()
+{
+ mError.Truncate();
+}
+
+void
+ErrorReporter::AddToError(const nsString &aErrorText)
+{
+ if (!ShouldReportErrors()) return;
+
+ if (mError.IsEmpty()) {
+ mError = aErrorText;
+ mErrorLineNumber = mScanner->GetLineNumber();
+ mErrorColNumber = mScanner->GetColumnNumber();
+ // Retrieve the error line once per line, and reuse the same nsString
+ // for all errors on that line. That causes the text of the line to
+ // be shared among all the nsIScriptError objects.
+ if (mErrorLine.IsEmpty() || mErrorLineNumber != mPrevErrorLineNumber) {
+ // Be careful here: the error line might be really long and OOM
+ // when we try to make a copy here. If so, just leave it empty.
+ if (!mErrorLine.Assign(mScanner->GetCurrentLine(), fallible)) {
+ mErrorLine.Truncate();
+ }
+ mPrevErrorLineNumber = mErrorLineNumber;
+ }
+ } else {
+ mError.AppendLiteral(" ");
+ mError.Append(aErrorText);
+ }
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString str;
+ sStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage,
+ const nsString &aParam)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString qparam;
+ nsStyleUtil::AppendEscapedCSSIdent(aParam, qparam);
+ const char16_t *params[1] = { qparam.get() };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage,
+ const nsCSSToken &aToken)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString tokenString;
+ aToken.AppendToString(tokenString);
+ const char16_t *params[1] = { tokenString.get() };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage,
+ const nsCSSToken &aToken,
+ char16_t aChar)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString tokenString;
+ aToken.AppendToString(tokenString);
+ const char16_t charStr[2] = { aChar, 0 };
+ const char16_t *params[2] = { tokenString.get(), charStr };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage,
+ const nsString &aParam,
+ const nsString &aValue)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString qparam;
+ nsStyleUtil::AppendEscapedCSSIdent(aParam, qparam);
+ const char16_t *params[2] = { qparam.get(), aValue.get() };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpectedEOF(const char *aMessage)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString innerStr;
+ sStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ getter_Copies(innerStr));
+ const char16_t *params[1] = { innerStr.get() };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(u"PEUnexpEOF2",
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpectedEOF(char16_t aExpected)
+{
+ if (!ShouldReportErrors()) return;
+
+ const char16_t expectedStr[] = {
+ char16_t('\''), aExpected, char16_t('\''), char16_t(0)
+ };
+ const char16_t *params[1] = { expectedStr };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(u"PEUnexpEOF2",
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+} // namespace css
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/ErrorReporter.h b/layout/style/ErrorReporter.h
new file mode 100644
index 000000000..f3d7fa1da
--- /dev/null
+++ b/layout/style/ErrorReporter.h
@@ -0,0 +1,113 @@
+/* -*- 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/. */
+
+/* diagnostic reporting for CSS style sheet parser */
+
+#ifndef mozilla_css_ErrorReporter_h_
+#define mozilla_css_ErrorReporter_h_
+
+// XXX turn this off for minimo builds
+#define CSS_REPORT_PARSE_ERRORS
+
+#include "nsString.h"
+
+struct nsCSSToken;
+class nsCSSScanner;
+class nsIURI;
+
+namespace mozilla {
+class CSSStyleSheet;
+
+namespace css {
+
+class Loader;
+
+// If CSS_REPORT_PARSE_ERRORS is not defined, all of this class's
+// methods become inline stubs.
+class MOZ_STACK_CLASS ErrorReporter {
+public:
+ ErrorReporter(const nsCSSScanner &aScanner,
+ const CSSStyleSheet *aSheet,
+ const Loader *aLoader,
+ nsIURI *aURI);
+ ~ErrorReporter();
+
+ static void ReleaseGlobals();
+
+ void OutputError();
+ void OutputError(uint32_t aLineNumber, uint32_t aLineOffset);
+ 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, a string
+ void ReportUnexpected(const char *aMessage, const nsString& aParam);
+ // one parameter, a token
+ void ReportUnexpected(const char *aMessage, const nsCSSToken& aToken);
+ // two parameters, a token and a character, in that order
+ void ReportUnexpected(const char *aMessage, const nsCSSToken& aToken,
+ char16_t aChar);
+ // two parameters, a param and a value
+ void ReportUnexpected(const char *aMessage, const nsString& aParam,
+ const nsString& aValue);
+
+ // for ReportUnexpectedEOF, aExpected can be either a stringbundle
+ // name or a single character. In the former case there may not be
+ // any format parameters.
+ void ReportUnexpectedEOF(const char *aExpected);
+ void ReportUnexpectedEOF(char16_t aExpected);
+
+private:
+ void AddToError(const nsString &aErrorText);
+
+#ifdef CSS_REPORT_PARSE_ERRORS
+ nsAutoString mError;
+ nsString mErrorLine;
+ nsString mFileName;
+ const nsCSSScanner *mScanner;
+ const CSSStyleSheet *mSheet;
+ const Loader *mLoader;
+ nsIURI *mURI;
+ uint64_t mInnerWindowID;
+ uint32_t mErrorLineNumber;
+ uint32_t mPrevErrorLineNumber;
+ uint32_t mErrorColNumber;
+#endif
+};
+
+#ifndef CSS_REPORT_PARSE_ERRORS
+inline ErrorReporter::ErrorReporter(const nsCSSScanner&,
+ const CSSStyleSheet*,
+ const Loader*,
+ nsIURI*) {}
+inline ErrorReporter::~ErrorReporter() {}
+
+inline void ErrorReporter::ReleaseGlobals() {}
+
+inline void ErrorReporter::OutputError() {}
+inline void ErrorReporter::ClearError() {}
+
+inline void ErrorReporter::ReportUnexpected(const char *) {}
+inline void ErrorReporter::ReportUnexpected(const char *, const nsString &) {}
+inline void ErrorReporter::ReportUnexpected(const char *, const nsCSSToken &) {}
+inline void ErrorReporter::ReportUnexpected(const char *, const nsCSSToken &,
+ char16_t) {}
+inline void ErrorReporter::ReportUnexpected(const char *, const nsString &,
+ const nsString &) {}
+
+inline void ErrorReporter::ReportUnexpectedEOF(const char *) {}
+inline void ErrorReporter::ReportUnexpectedEOF(char16_t) {}
+
+inline void ErrorReporter::AddToError(const nsString &) {}
+#endif
+
+} // 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 000000000..4558ab1a2
--- /dev/null
+++ b/layout/style/FontFace.cpp
@@ -0,0 +1,808 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/FontFace.h"
+
+#include <algorithm>
+#include "mozilla/dom/FontFaceBinding.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 "nsCSSParser.h"
+#include "nsCSSRules.h"
+#include "nsIDocument.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:
+ explicit FontFaceBufferSource(FontFace* aFontFace)
+ : mFontFace(aFontFace) {}
+ virtual void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength);
+
+private:
+ RefPtr<FontFace> mFontFace;
+};
+
+void
+FontFaceBufferSource::TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength)
+{
+ MOZ_ASSERT(mFontFace, "only call TakeBuffer once on a given "
+ "FontFaceBufferSource object");
+ mFontFace->TakeBuffer(aBuffer, aLength);
+ mFontFace = nullptr;
+}
+
+// -- Utility functions ------------------------------------------------------
+
+template<typename T>
+static void
+GetDataFrom(const T& aObject, uint8_t*& aBuffer, uint32_t& aLength)
+{
+ MOZ_ASSERT(!aBuffer);
+ aObject.ComputeLengthAndData();
+ // We use malloc here rather than a FallibleTArray or fallible
+ // operator new[] since the gfxUserFontEntry will be calling free
+ // on it.
+ aBuffer = (uint8_t*) malloc(aObject.Length());
+ if (!aBuffer) {
+ return;
+ }
+ memcpy((void*) aBuffer, aObject.Data(), aObject.Length());
+ aLength = aObject.Length();
+}
+
+// -- 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(mRule)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOtherFontFaceSets)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+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)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRule)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOtherFontFaceSets)
+ 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(nsISupports* aParent, FontFaceSet* aFontFaceSet)
+ : mParent(aParent)
+ , mLoadedRejection(NS_OK)
+ , mStatus(FontFaceLoadStatus::Unloaded)
+ , mSourceType(SourceType(0))
+ , mSourceBuffer(nullptr)
+ , mSourceBufferLength(0)
+ , mFontFaceSet(aFontFaceSet)
+ , mInFontFaceSet(false)
+{
+ MOZ_COUNT_CTOR(FontFace);
+}
+
+FontFace::~FontFace()
+{
+ MOZ_COUNT_DTOR(FontFace);
+
+ SetUserFontEntry(nullptr);
+
+ if (mSourceBuffer) {
+ free(mSourceBuffer);
+ }
+}
+
+JSObject*
+FontFace::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FontFaceBinding::Wrap(aCx, this, aGivenProto);
+}
+
+static FontFaceLoadStatus
+LoadStateToStatus(gfxUserFontEntry::UserFontLoadState aLoadState)
+{
+ switch (aLoadState) {
+ case gfxUserFontEntry::UserFontLoadState::STATUS_NOT_LOADED:
+ return FontFaceLoadStatus::Unloaded;
+ case gfxUserFontEntry::UserFontLoadState::STATUS_LOADING:
+ return FontFaceLoadStatus::Loading;
+ case gfxUserFontEntry::UserFontLoadState::STATUS_LOADED:
+ return FontFaceLoadStatus::Loaded;
+ case gfxUserFontEntry::UserFontLoadState::STATUS_FAILED:
+ return FontFaceLoadStatus::Error;
+ }
+ NS_NOTREACHED("invalid aLoadState value");
+ return FontFaceLoadStatus::Error;
+}
+
+already_AddRefed<FontFace>
+FontFace::CreateForRule(nsISupports* aGlobal,
+ FontFaceSet* aFontFaceSet,
+ nsCSSFontFaceRule* aRule)
+{
+ RefPtr<FontFace> obj = new FontFace(aGlobal, aFontFaceSet);
+ obj->mRule = aRule;
+ obj->mSourceType = eSourceType_FontFaceRule;
+ obj->mInFontFaceSet = true;
+ return obj.forget();
+}
+
+already_AddRefed<FontFace>
+FontFace::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aFamily,
+ const StringOrArrayBufferOrArrayBufferView& aSource,
+ const FontFaceDescriptors& aDescriptors,
+ ErrorResult& aRv)
+{
+ nsISupports* global = aGlobal.GetAsSupports();
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
+ nsIDocument* doc = window->GetDoc();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<FontFace> obj = new FontFace(global, doc->Fonts());
+ if (!obj->SetDescriptors(aFamily, aDescriptors)) {
+ return obj.forget();
+ }
+
+ obj->InitializeSource(aSource);
+ return obj.forget();
+}
+
+void
+FontFace::InitializeSource(const StringOrArrayBufferOrArrayBufferView& aSource)
+{
+ if (aSource.IsString()) {
+ if (!ParseDescriptor(eCSSFontDesc_Src,
+ aSource.GetAsString(),
+ mDescriptors->mSrc)) {
+ Reject(NS_ERROR_DOM_SYNTAX_ERR);
+
+ SetStatus(FontFaceLoadStatus::Error);
+ return;
+ }
+
+ mSourceType = eSourceType_URLs;
+ return;
+ }
+
+ mSourceType = FontFace::eSourceType_Buffer;
+
+ if (aSource.IsArrayBuffer()) {
+ GetDataFrom(aSource.GetAsArrayBuffer(),
+ mSourceBuffer, mSourceBufferLength);
+ } else {
+ MOZ_ASSERT(aSource.IsArrayBufferView());
+ GetDataFrom(aSource.GetAsArrayBufferView(),
+ mSourceBuffer, mSourceBufferLength);
+ }
+
+ SetStatus(FontFaceLoadStatus::Loading);
+ DoLoad();
+}
+
+void
+FontFace::GetFamily(nsString& aResult)
+{
+ mFontFaceSet->FlushUserFontSet();
+
+ // Serialize the same way as in nsCSSFontFaceStyleDecl::GetPropertyValue.
+ nsCSSValue value;
+ GetDesc(eCSSFontDesc_Family, value);
+
+ aResult.Truncate();
+
+ if (value.GetUnit() == eCSSUnit_Null) {
+ return;
+ }
+
+ nsDependentString family(value.GetStringBufferValue());
+ if (!family.IsEmpty()) {
+ // The string length can be zero when the author passed an invalid
+ // family name or an invalid descriptor to the JS FontFace constructor.
+ nsStyleUtil::AppendEscapedCSSString(family, aResult);
+ }
+}
+
+void
+FontFace::SetFamily(const nsAString& aValue, ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+ SetDescriptor(eCSSFontDesc_Family, aValue, aRv);
+}
+
+void
+FontFace::GetStyle(nsString& aResult)
+{
+ mFontFaceSet->FlushUserFontSet();
+ GetDesc(eCSSFontDesc_Style, eCSSProperty_font_style, aResult);
+}
+
+void
+FontFace::SetStyle(const nsAString& aValue, ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+ SetDescriptor(eCSSFontDesc_Style, aValue, aRv);
+}
+
+void
+FontFace::GetWeight(nsString& aResult)
+{
+ mFontFaceSet->FlushUserFontSet();
+ GetDesc(eCSSFontDesc_Weight, eCSSProperty_font_weight, aResult);
+}
+
+void
+FontFace::SetWeight(const nsAString& aValue, ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+ SetDescriptor(eCSSFontDesc_Weight, aValue, aRv);
+}
+
+void
+FontFace::GetStretch(nsString& aResult)
+{
+ mFontFaceSet->FlushUserFontSet();
+ GetDesc(eCSSFontDesc_Stretch, eCSSProperty_font_stretch, aResult);
+}
+
+void
+FontFace::SetStretch(const nsAString& aValue, ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+ SetDescriptor(eCSSFontDesc_Stretch, aValue, aRv);
+}
+
+void
+FontFace::GetUnicodeRange(nsString& aResult)
+{
+ mFontFaceSet->FlushUserFontSet();
+
+ // There is no eCSSProperty_unicode_range for us to pass in to GetDesc
+ // to get a serialized (possibly defaulted) value, but that function
+ // doesn't use the property ID for this descriptor anyway.
+ GetDesc(eCSSFontDesc_UnicodeRange, eCSSProperty_UNKNOWN, aResult);
+}
+
+void
+FontFace::SetUnicodeRange(const nsAString& aValue, ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+ SetDescriptor(eCSSFontDesc_UnicodeRange, aValue, aRv);
+}
+
+void
+FontFace::GetVariant(nsString& aResult)
+{
+ mFontFaceSet->FlushUserFontSet();
+
+ // XXX Just expose the font-variant descriptor as "normal" until we
+ // support it properly (bug 1055385).
+ aResult.AssignLiteral("normal");
+}
+
+void
+FontFace::SetVariant(const nsAString& aValue, ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+
+ // XXX Ignore assignments to variant until we support font-variant
+ // descriptors (bug 1055385).
+}
+
+void
+FontFace::GetFeatureSettings(nsString& aResult)
+{
+ mFontFaceSet->FlushUserFontSet();
+ GetDesc(eCSSFontDesc_FontFeatureSettings, eCSSProperty_font_feature_settings,
+ aResult);
+}
+
+void
+FontFace::SetFeatureSettings(const nsAString& aValue, ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+ SetDescriptor(eCSSFontDesc_FontFeatureSettings, aValue, aRv);
+}
+
+void
+FontFace::GetDisplay(nsString& aResult)
+{
+ mFontFaceSet->FlushUserFontSet();
+ GetDesc(eCSSFontDesc_Display, eCSSProperty_UNKNOWN, aResult);
+}
+
+void
+FontFace::SetDisplay(const nsAString& aValue, ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+ SetDescriptor(eCSSFontDesc_Display, aValue, aRv);
+}
+
+FontFaceLoadStatus
+FontFace::Status()
+{
+ return mStatus;
+}
+
+Promise*
+FontFace::Load(ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+
+ EnsurePromise();
+
+ if (!mLoaded) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // 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 mLoaded;
+ }
+
+ // 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();
+
+ return mLoaded;
+}
+
+gfxUserFontEntry*
+FontFace::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");
+
+ RefPtr<gfxUserFontEntry> newEntry =
+ mFontFaceSet->FindOrCreateUserFontEntryFromFontFace(this);
+ if (newEntry) {
+ SetUserFontEntry(newEntry);
+ }
+ }
+
+ return mUserFontEntry;
+}
+
+void
+FontFace::DoLoad()
+{
+ if (!CreateUserFontEntry()) {
+ return;
+ }
+ mUserFontEntry->Load();
+}
+
+Promise*
+FontFace::GetLoaded(ErrorResult& aRv)
+{
+ mFontFaceSet->FlushUserFontSet();
+
+ EnsurePromise();
+
+ if (!mLoaded) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return mLoaded;
+}
+
+void
+FontFace::SetStatus(FontFaceLoadStatus aStatus)
+{
+ 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 (FontFaceSet* otherSet : mOtherFontFaceSets) {
+ otherSet->OnFontFaceStatusChanged(this);
+ }
+
+ if (mStatus == FontFaceLoadStatus::Loaded) {
+ if (mLoaded) {
+ mLoaded->MaybeResolve(this);
+ }
+ } else if (mStatus == FontFaceLoadStatus::Error) {
+ if (mSourceType == eSourceType_Buffer) {
+ Reject(NS_ERROR_DOM_SYNTAX_ERR);
+ } else {
+ Reject(NS_ERROR_DOM_NETWORK_ERR);
+ }
+ }
+}
+
+bool
+FontFace::ParseDescriptor(nsCSSFontDesc aDescID,
+ const nsAString& aString,
+ nsCSSValue& aResult)
+{
+ nsCSSParser parser;
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
+ nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull();
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mParent);
+ nsCOMPtr<nsIURI> docURI = window->GetDocumentURI();
+ nsCOMPtr<nsIURI> base = window->GetDocBaseURI();
+
+ if (!parser.ParseFontFaceDescriptor(aDescID, aString,
+ docURI, // aSheetURL
+ base,
+ principal,
+ aResult)) {
+ aResult.Reset();
+ return false;
+ }
+
+ return true;
+}
+
+void
+FontFace::SetDescriptor(nsCSSFontDesc aFontDesc,
+ const nsAString& aValue,
+ ErrorResult& aRv)
+{
+ NS_ASSERTION(!HasRule(),
+ "we don't handle rule backed FontFace objects yet");
+ if (HasRule()) {
+ return;
+ }
+
+ nsCSSValue parsedValue;
+ if (!ParseDescriptor(aFontDesc, aValue, parsedValue)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ mDescriptors->Get(aFontDesc) = parsedValue;
+
+ // XXX Setting descriptors doesn't actually have any effect on FontFace
+ // objects that have started loading or have already been loaded.
+}
+
+bool
+FontFace::SetDescriptors(const nsAString& aFamily,
+ const FontFaceDescriptors& aDescriptors)
+{
+ MOZ_ASSERT(!HasRule());
+ MOZ_ASSERT(!mDescriptors);
+
+ mDescriptors = new CSSFontFaceDescriptors;
+
+ // Parse all of the mDescriptors in aInitializer, which are the values
+ // we got from the JS constructor.
+ if (!ParseDescriptor(eCSSFontDesc_Family,
+ aFamily,
+ mDescriptors->mFamily) ||
+ *mDescriptors->mFamily.GetStringBufferValue() == 0 ||
+ !ParseDescriptor(eCSSFontDesc_Style,
+ aDescriptors.mStyle,
+ mDescriptors->mStyle) ||
+ !ParseDescriptor(eCSSFontDesc_Weight,
+ aDescriptors.mWeight,
+ mDescriptors->mWeight) ||
+ !ParseDescriptor(eCSSFontDesc_Stretch,
+ aDescriptors.mStretch,
+ mDescriptors->mStretch) ||
+ !ParseDescriptor(eCSSFontDesc_UnicodeRange,
+ aDescriptors.mUnicodeRange,
+ mDescriptors->mUnicodeRange) ||
+ !ParseDescriptor(eCSSFontDesc_FontFeatureSettings,
+ aDescriptors.mFeatureSettings,
+ mDescriptors->mFontFeatureSettings) ||
+ !ParseDescriptor(eCSSFontDesc_Display,
+ aDescriptors.mDisplay,
+ mDescriptors->mDisplay)) {
+ // 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 = new CSSFontFaceDescriptors;
+
+ Reject(NS_ERROR_DOM_SYNTAX_ERR);
+
+ SetStatus(FontFaceLoadStatus::Error);
+ return false;
+ }
+
+ return true;
+}
+
+void
+FontFace::GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const
+{
+ if (HasRule()) {
+ MOZ_ASSERT(mRule);
+ MOZ_ASSERT(!mDescriptors);
+ mRule->GetDesc(aDescID, aResult);
+ } else {
+ aResult = mDescriptors->Get(aDescID);
+ }
+}
+
+void
+FontFace::GetDesc(nsCSSFontDesc aDescID,
+ nsCSSPropertyID aPropID,
+ nsString& aResult) const
+{
+ MOZ_ASSERT(aDescID == eCSSFontDesc_UnicodeRange ||
+ aDescID == eCSSFontDesc_Display ||
+ aPropID != eCSSProperty_UNKNOWN,
+ "only pass eCSSProperty_UNKNOWN for eCSSFontDesc_UnicodeRange");
+
+ nsCSSValue value;
+ GetDesc(aDescID, value);
+
+ aResult.Truncate();
+
+ // Fill in a default value for missing descriptors.
+ if (value.GetUnit() == eCSSUnit_Null) {
+ 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");
+ }
+ return;
+ }
+
+ if (aDescID == eCSSFontDesc_UnicodeRange) {
+ // Since there's no unicode-range property, we can't use
+ // nsCSSValue::AppendToString to serialize this descriptor.
+ nsStyleUtil::AppendUnicodeRange(value, aResult);
+ } else if (aDescID == eCSSFontDesc_Display) {
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(value.GetIntValue(),
+ nsCSSProps::kFontDisplayKTable),
+ aResult);
+ } else {
+ value.AppendToString(aPropID, aResult, nsCSSValue::eNormalized);
+ }
+}
+
+void
+FontFace::SetUserFontEntry(gfxUserFontEntry* aEntry)
+{
+ if (mUserFontEntry) {
+ mUserFontEntry->mFontFaces.RemoveElement(this);
+ }
+
+ mUserFontEntry = static_cast<Entry*>(aEntry);
+ if (mUserFontEntry) {
+ mUserFontEntry->mFontFaces.AppendElement(this);
+
+ MOZ_ASSERT(mUserFontEntry->GetUserFontSet() ==
+ mFontFaceSet->GetUserFontSet(),
+ "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
+FontFace::GetFamilyName(nsString& aResult)
+{
+ nsCSSValue value;
+ GetDesc(eCSSFontDesc_Family, value);
+
+ if (value.GetUnit() == eCSSUnit_String) {
+ nsString familyname;
+ value.GetStringValue(familyname);
+ aResult.Append(familyname);
+ }
+
+ return !aResult.IsEmpty();
+}
+
+void
+FontFace::DisconnectFromRule()
+{
+ MOZ_ASSERT(HasRule());
+
+ // Make a copy of the descriptors.
+ mDescriptors = new CSSFontFaceDescriptors;
+ mRule->GetDescriptors(*mDescriptors);
+ mRule = nullptr;
+ mInFontFaceSet = false;
+}
+
+bool
+FontFace::HasFontData() const
+{
+ return mSourceType == eSourceType_Buffer && mSourceBuffer;
+}
+
+void
+FontFace::TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength)
+{
+ MOZ_ASSERT(HasFontData());
+
+ aBuffer = mSourceBuffer;
+ aLength = mSourceBufferLength;
+
+ mSourceBuffer = nullptr;
+ mSourceBufferLength = 0;
+}
+
+already_AddRefed<gfxFontFaceBufferSource>
+FontFace::CreateBufferSource()
+{
+ RefPtr<FontFaceBufferSource> bufferSource = new FontFaceBufferSource(this);
+ return bufferSource.forget();
+}
+
+bool
+FontFace::IsInFontFaceSet(FontFaceSet* aFontFaceSet) const
+{
+ if (mFontFaceSet == aFontFaceSet) {
+ return mInFontFaceSet;
+ }
+ return mOtherFontFaceSets.Contains(aFontFaceSet);
+}
+
+void
+FontFace::AddFontFaceSet(FontFaceSet* aFontFaceSet)
+{
+ MOZ_ASSERT(!IsInFontFaceSet(aFontFaceSet));
+
+ if (mFontFaceSet == aFontFaceSet) {
+ mInFontFaceSet = true;
+ } else {
+ mOtherFontFaceSets.AppendElement(aFontFaceSet);
+ }
+}
+
+void
+FontFace::RemoveFontFaceSet(FontFaceSet* aFontFaceSet)
+{
+ MOZ_ASSERT(IsInFontFaceSet(aFontFaceSet));
+
+ if (mFontFaceSet == aFontFaceSet) {
+ mInFontFaceSet = false;
+ } else {
+ mOtherFontFaceSets.RemoveElement(aFontFaceSet);
+ }
+}
+
+void
+FontFace::Reject(nsresult aResult)
+{
+ if (mLoaded) {
+ mLoaded->MaybeReject(aResult);
+ } else if (mLoadedRejection == NS_OK) {
+ mLoadedRejection = aResult;
+ }
+}
+
+void
+FontFace::EnsurePromise()
+{
+ if (mLoaded) {
+ return;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
+
+ // If the pref is not set, don't create the Promise (which the page wouldn't
+ // be able to get to anyway) as it causes the window.FontFace constructor
+ // to be created.
+ if (global && FontFaceSet::PrefEnabled()) {
+ ErrorResult rv;
+ mLoaded = Promise::Create(global, rv);
+
+ if (mStatus == FontFaceLoadStatus::Loaded) {
+ mLoaded->MaybeResolve(this);
+ } else if (mLoadedRejection != NS_OK) {
+ mLoaded->MaybeReject(mLoadedRejection);
+ }
+ }
+}
+
+// -- FontFace::Entry --------------------------------------------------------
+
+/* virtual */ void
+FontFace::Entry::SetLoadState(UserFontLoadState aLoadState)
+{
+ gfxUserFontEntry::SetLoadState(aLoadState);
+
+ for (size_t i = 0; i < mFontFaces.Length(); i++) {
+ mFontFaces[i]->SetStatus(LoadStateToStatus(aLoadState));
+ }
+}
+
+/* virtual */ void
+FontFace::Entry::GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult)
+{
+ aResult.Clear();
+
+ for (FontFace* f : mFontFaces) {
+ if (f->mInFontFaceSet) {
+ aResult.AppendElement(f->mFontFaceSet->GetUserFontSet());
+ }
+ for (FontFaceSet* s : f->mOtherFontFaceSets) {
+ aResult.AppendElement(s->GetUserFontSet());
+ }
+ }
+
+ // Remove duplicates.
+ aResult.Sort();
+ auto it = std::unique(aResult.begin(), aResult.end());
+ aResult.TruncateLength(it - aResult.begin());
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/FontFace.h b/layout/style/FontFace.h
new file mode 100644
index 000000000..374aa8e3f
--- /dev/null
+++ b/layout/style/FontFace.h
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FontFace_h
+#define mozilla_dom_FontFace_h
+
+#include "mozilla/dom/FontFaceBinding.h"
+#include "gfxUserFontSet.h"
+#include "nsAutoPtr.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSValue.h"
+#include "nsWrapperCache.h"
+
+class gfxFontFaceBufferSource;
+class nsCSSFontFaceRule;
+
+namespace mozilla {
+struct CSSFontFaceDescriptors;
+namespace dom {
+class FontFaceBufferSource;
+struct FontFaceDescriptors;
+class FontFaceSet;
+class Promise;
+class StringOrArrayBufferOrArrayBufferView;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+class FontFace final : public nsISupports,
+ public nsWrapperCache
+{
+ friend class mozilla::dom::FontFaceBufferSource;
+ friend class Entry;
+
+public:
+ class Entry final : public gfxUserFontEntry {
+ friend class FontFace;
+
+ public:
+ Entry(gfxUserFontSet* aFontSet,
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay)
+ : gfxUserFontEntry(aFontSet, aFontFaceSrcList, aWeight, aStretch,
+ aStyle, aFeatureSettings, aLanguageOverride,
+ aUnicodeRanges, aFontDisplay) {}
+
+ virtual void SetLoadState(UserFontLoadState aLoadState) override;
+ virtual void GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult) override;
+ const AutoTArray<FontFace*,1>& GetFontFaces() { return mFontFaces; }
+
+ protected:
+ // 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<FontFace*,1> mFontFaces;
+ };
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FontFace)
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<FontFace>
+ CreateForRule(nsISupports* aGlobal, FontFaceSet* aFontFaceSet,
+ nsCSSFontFaceRule* aRule);
+
+ nsCSSFontFaceRule* GetRule() { return mRule; }
+
+ void GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const;
+
+ gfxUserFontEntry* CreateUserFontEntry();
+ gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; }
+ void SetUserFontEntry(gfxUserFontEntry* aEntry);
+
+ /**
+ * Returns whether this object is in the specified FontFaceSet.
+ */
+ bool IsInFontFaceSet(FontFaceSet* aFontFaceSet) const;
+
+ void AddFontFaceSet(FontFaceSet* aFontFaceSet);
+ void RemoveFontFaceSet(FontFaceSet* aFontFaceSet);
+
+ FontFaceSet* 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 whether a valid family name was available.
+ */
+ bool GetFamilyName(nsString& aResult);
+
+ /**
+ * 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;
+
+ /**
+ * Creates a gfxFontFaceBufferSource to represent the font data
+ * in this object.
+ */
+ already_AddRefed<gfxFontFaceBufferSource> CreateBufferSource();
+
+ /**
+ * 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);
+
+ // Web IDL
+ static already_AddRefed<FontFace>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aFamily,
+ const mozilla::dom::StringOrArrayBufferOrArrayBufferView& aSource,
+ const mozilla::dom::FontFaceDescriptors& aDescriptors,
+ ErrorResult& aRV);
+
+ void GetFamily(nsString& aResult);
+ void SetFamily(const nsAString& aValue, mozilla::ErrorResult& aRv);
+ void GetStyle(nsString& aResult);
+ void SetStyle(const nsAString& aValue, mozilla::ErrorResult& aRv);
+ void GetWeight(nsString& aResult);
+ void SetWeight(const nsAString& aValue, mozilla::ErrorResult& aRv);
+ void GetStretch(nsString& aResult);
+ void SetStretch(const nsAString& aValue, mozilla::ErrorResult& aRv);
+ void GetUnicodeRange(nsString& aResult);
+ void SetUnicodeRange(const nsAString& aValue, mozilla::ErrorResult& aRv);
+ void GetVariant(nsString& aResult);
+ void SetVariant(const nsAString& aValue, mozilla::ErrorResult& aRv);
+ void GetFeatureSettings(nsString& aResult);
+ void SetFeatureSettings(const nsAString& aValue, mozilla::ErrorResult& aRv);
+ void GetDisplay(nsString& aResult);
+ void SetDisplay(const nsAString& aValue, mozilla::ErrorResult& aRv);
+
+ mozilla::dom::FontFaceLoadStatus Status();
+ mozilla::dom::Promise* Load(mozilla::ErrorResult& aRv);
+ mozilla::dom::Promise* GetLoaded(mozilla::ErrorResult& aRv);
+
+private:
+ FontFace(nsISupports* aParent, FontFaceSet* aFontFaceSet);
+ ~FontFace();
+
+ void InitializeSource(const StringOrArrayBufferOrArrayBufferView& aSource);
+
+ // Helper function for Load.
+ void DoLoad();
+
+ /**
+ * Parses a @font-face descriptor value, storing the result in aResult.
+ * Returns whether the parsing was successful.
+ */
+ bool ParseDescriptor(nsCSSFontDesc aDescID, const nsAString& aString,
+ nsCSSValue& aResult);
+
+ // Helper function for the descriptor setter methods.
+ void SetDescriptor(nsCSSFontDesc aFontDesc,
+ const nsAString& aValue,
+ mozilla::ErrorResult& aRv);
+
+ /**
+ * Sets all of the descriptor values in mDescriptors using values passed
+ * to the JS constructor.
+ */
+ bool SetDescriptors(const nsAString& aFamily,
+ const FontFaceDescriptors& aDescriptors);
+
+ /**
+ * Sets the current loading status.
+ */
+ void SetStatus(mozilla::dom::FontFaceLoadStatus aStatus);
+
+ void GetDesc(nsCSSFontDesc aDescID,
+ nsCSSPropertyID aPropID,
+ nsString& aResult) const;
+
+ /**
+ * Returns and takes ownership of the buffer storing the font data.
+ */
+ void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength);
+
+ // Acts like mLoaded->MaybeReject(aResult), except it doesn't create mLoaded
+ // if it doesn't already exist.
+ void Reject(nsresult aResult);
+
+ // Creates mLoaded if it doesn't already exist. It may immediately resolve or
+ // reject mLoaded based on mStatus and mLoadedRejection.
+ void EnsurePromise();
+
+ nsCOMPtr<nsISupports> mParent;
+
+ // 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<mozilla::dom::Promise> mLoaded;
+
+ // Saves the rejection code for mLoaded if mLoaded hasn't been created yet.
+ nsresult mLoadedRejection;
+
+ // The @font-face rule this FontFace object is reflecting, if it is a
+ // rule backed FontFace.
+ RefPtr<nsCSSFontFaceRule> 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.
+ mozilla::dom::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.
+ uint8_t* mSourceBuffer;
+ uint32_t mSourceBufferLength;
+
+ // 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.
+ nsAutoPtr<mozilla::CSSFontFaceDescriptors> mDescriptors;
+
+ // The primary FontFaceSet this FontFace is associated with,
+ // regardless of whether it is currently "in" the set.
+ RefPtr<FontFaceSet> mFontFaceSet;
+
+ // Other FontFaceSets (apart from mFontFaceSet) that this FontFace
+ // appears in.
+ nsTArray<RefPtr<FontFaceSet>> mOtherFontFaceSets;
+
+ // Whether this FontFace appears in mFontFaceSet.
+ bool mInFontFaceSet;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_FontFace_h)
diff --git a/layout/style/FontFaceSet.cpp b/layout/style/FontFaceSet.cpp
new file mode 100644
index 000000000..59626fba4
--- /dev/null
+++ b/layout/style/FontFaceSet.cpp
@@ -0,0 +1,1848 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/css/Declaration.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/FontFaceSetBinding.h"
+#include "mozilla/dom/FontFaceSetIterator.h"
+#include "mozilla/dom/FontFaceSetLoadEvent.h"
+#include "mozilla/dom/FontFaceSetLoadEventBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SizePrintfMacros.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "nsAutoPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsCSSParser.h"
+#include "nsDeviceContext.h"
+#include "nsFontFaceLoader.h"
+#include "nsIConsoleService.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsINetworkPredictor.h"
+#include "nsIPresShell.h"
+#include "nsIPrincipal.h"
+#include "nsISupportsPriority.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+#include "nsIProtocolHandler.h"
+#include "nsIInputStream.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "nsStyleSet.h"
+#include "nsUTF8Utils.h"
+#include "nsDOMNavigationTiming.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)
+
+#define FONT_LOADING_API_ENABLED_PREF "layout.css.font-loading-api.enabled"
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FontFaceSet)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FontFaceSet, DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument);
+ 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);
+ }
+ if (tmp->mUserFontSet) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUserFontSet->mFontFaceSet);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FontFaceSet, DOMEventTargetHelper)
+ tmp->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument);
+ 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);
+ }
+ if (tmp->mUserFontSet) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mUserFontSet->mFontFaceSet);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mUserFontSet);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(FontFaceSet, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(FontFaceSet, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FontFaceSet)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+FontFaceSet::FontFaceSet(nsPIDOMWindowInner* aWindow, nsIDocument* aDocument)
+ : DOMEventTargetHelper(aWindow)
+ , mDocument(aDocument)
+ , mResolveLazilyCreatedReadyPromise(false)
+ , mStatus(FontFaceSetLoadStatus::Loaded)
+ , mNonRuleFacesDirty(false)
+ , mHasLoadingFontFaces(false)
+ , mHasLoadingFontFacesIsDirty(false)
+ , mDelayedLoadCheck(false)
+{
+ MOZ_COUNT_CTOR(FontFaceSet);
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aWindow);
+
+ // If the pref is not set, don't create the Promise (which the page wouldn't
+ // be able to get to anyway) as it causes the window.FontFaceSet constructor
+ // to be created.
+ if (global && PrefEnabled()) {
+ mResolveLazilyCreatedReadyPromise = true;
+ }
+
+ if (!mDocument->DidFireDOMContentLoaded()) {
+ mDocument->AddSystemEventListener(NS_LITERAL_STRING("DOMContentLoaded"),
+ this, false, false);
+ }
+
+ mDocument->CSSLoader()->AddObserver(this);
+
+ mUserFontSet = new UserFontSet(this);
+}
+
+FontFaceSet::~FontFaceSet()
+{
+ MOZ_COUNT_DTOR(FontFaceSet);
+
+ Disconnect();
+ for (auto it = mLoaders.Iter(); !it.Done(); it.Next()) {
+ it.Get()->GetKey()->Cancel();
+ }
+}
+
+JSObject*
+FontFaceSet::WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto)
+{
+ return FontFaceSetBinding::Wrap(aContext, this, aGivenProto);
+}
+
+void
+FontFaceSet::Disconnect()
+{
+ RemoveDOMContentLoadedListener();
+
+ if (mDocument && mDocument->CSSLoader()) {
+ // We're null checking CSSLoader() since FontFaceSet::Disconnect() might be
+ // being called during unlink, at which time the loader amy already have
+ // been unlinked from the document.
+ mDocument->CSSLoader()->RemoveObserver(this);
+ }
+}
+
+void
+FontFaceSet::RemoveDOMContentLoadedListener()
+{
+ if (mDocument) {
+ mDocument->RemoveSystemEventListener(NS_LITERAL_STRING("DOMContentLoaded"),
+ this, false);
+ }
+}
+
+void
+FontFaceSet::ParseFontShorthandForMatching(
+ const nsAString& aFont,
+ RefPtr<FontFamilyListRefCnt>& aFamilyList,
+ uint32_t& aWeight,
+ int32_t& aStretch,
+ uint8_t& aStyle,
+ ErrorResult& aRv)
+{
+ // Parse aFont as a 'font' property value.
+ RefPtr<Declaration> declaration = new Declaration;
+ declaration->InitializeEmpty();
+
+ bool changed = false;
+ nsCSSParser parser;
+ parser.ParseProperty(eCSSProperty_font,
+ aFont,
+ mDocument->GetDocumentURI(),
+ mDocument->GetDocumentURI(),
+ mDocument->NodePrincipal(),
+ declaration,
+ &changed,
+ /* aIsImportant */ false);
+
+ // All of the properties we are interested in should have been set at once.
+ MOZ_ASSERT(changed == (declaration->HasProperty(eCSSProperty_font_family) &&
+ declaration->HasProperty(eCSSProperty_font_style) &&
+ declaration->HasProperty(eCSSProperty_font_weight) &&
+ declaration->HasProperty(eCSSProperty_font_stretch)));
+
+ if (!changed) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ nsCSSCompressedDataBlock* data = declaration->GetNormalBlock();
+ MOZ_ASSERT(!declaration->GetImportantBlock());
+
+ const nsCSSValue* family = data->ValueFor(eCSSProperty_font_family);
+ if (family->GetUnit() != eCSSUnit_FontFamilyList) {
+ // We got inherit, initial, unset, a system font, or a token stream.
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ aFamilyList =
+ static_cast<FontFamilyListRefCnt*>(family->GetFontFamilyListValue());
+
+ int32_t weight = data->ValueFor(eCSSProperty_font_weight)->GetIntValue();
+
+ // Resolve relative font weights against the initial of font-weight
+ // (normal, which is equivalent to 400).
+ if (weight == NS_STYLE_FONT_WEIGHT_BOLDER) {
+ weight = NS_FONT_WEIGHT_BOLD;
+ } else if (weight == NS_STYLE_FONT_WEIGHT_LIGHTER) {
+ weight = NS_FONT_WEIGHT_THIN;
+ }
+
+ aWeight = weight;
+
+ aStretch = data->ValueFor(eCSSProperty_font_stretch)->GetIntValue();
+ aStyle = data->ValueFor(eCSSProperty_font_style)->GetIntValue();
+}
+
+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
+FontFaceSet::FindMatchingFontFaces(const nsAString& aFont,
+ const nsAString& aText,
+ nsTArray<FontFace*>& aFontFaces,
+ ErrorResult& aRv)
+{
+ RefPtr<FontFamilyListRefCnt> familyList;
+ uint32_t weight;
+ int32_t stretch;
+ uint8_t italicStyle;
+ ParseFontShorthandForMatching(aFont, familyList, weight, stretch, italicStyle,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ gfxFontStyle style;
+ style.style = italicStyle;
+ style.weight = weight;
+ style.stretch = stretch;
+
+ nsTArray<FontFaceRecord>* arrays[2];
+ arrays[0] = &mNonRuleFaces;
+ arrays[1] = &mRuleFaces;
+
+ // Set of FontFaces that we want to return.
+ nsTHashtable<nsPtrHashKey<FontFace>> matchingFaces;
+
+ for (const FontFamilyName& fontFamilyName : familyList->GetFontlist()) {
+ RefPtr<gfxFontFamily> family =
+ mUserFontSet->LookupFamily(fontFamilyName.mName);
+
+ if (!family) {
+ continue;
+ }
+
+ AutoTArray<gfxFontEntry*,4> entries;
+ bool needsBold;
+ family->FindAllFontsForStyle(style, entries, needsBold);
+
+ for (gfxFontEntry* e : entries) {
+ FontFace::Entry* entry = static_cast<FontFace::Entry*>(e);
+ if (HasAnyCharacterInUnicodeRange(entry, aText)) {
+ for (FontFace* f : entry->GetFontFaces()) {
+ matchingFaces.PutEntry(f);
+ }
+ }
+ }
+ }
+
+ // Add all FontFaces in matchingFaces to aFontFaces, in the order
+ // they appear in the FontFaceSet.
+ for (nsTArray<FontFaceRecord>* array : arrays) {
+ for (FontFaceRecord& record : *array) {
+ FontFace* f = record.mFontFace;
+ if (matchingFaces.Contains(f)) {
+ aFontFaces.AppendElement(f);
+ }
+ }
+ }
+}
+
+TimeStamp
+FontFaceSet::GetNavigationStartTimeStamp()
+{
+ TimeStamp navStart;
+ RefPtr<nsDOMNavigationTiming> timing(mDocument->GetNavigationTiming());
+ if (timing) {
+ navStart = timing->GetNavigationStartTimeStamp();
+ }
+ return navStart;
+}
+
+already_AddRefed<Promise>
+FontFaceSet::Load(JSContext* aCx,
+ const nsAString& aFont,
+ const nsAString& aText,
+ ErrorResult& aRv)
+{
+ FlushUserFontSet();
+
+ nsTArray<RefPtr<Promise>> promises;
+
+ nsTArray<FontFace*> faces;
+ 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;
+ }
+ }
+
+ nsIGlobalObject* globalObject = GetParentObject();
+ if (!globalObject) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> jsGlobal(aCx, globalObject->GetGlobalJSObject());
+ GlobalObject global(aCx, jsGlobal);
+
+ RefPtr<Promise> result = Promise::All(global, promises, aRv);
+ return result.forget();
+}
+
+bool
+FontFaceSet::Check(const nsAString& aFont,
+ const nsAString& aText,
+ ErrorResult& aRv)
+{
+ FlushUserFontSet();
+
+ nsTArray<FontFace*> faces;
+ FindMatchingFontFaces(aFont, aText, faces, aRv);
+ if (aRv.Failed()) {
+ return false;
+ }
+
+ for (FontFace* f : faces) {
+ if (f->Status() != FontFaceLoadStatus::Loaded) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+Promise*
+FontFaceSet::GetReady(ErrorResult& aRv)
+{
+ 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;
+ }
+ }
+
+ FlushUserFontSet();
+ return mReady;
+}
+
+FontFaceSetLoadStatus
+FontFaceSet::Status()
+{
+ FlushUserFontSet();
+ return mStatus;
+}
+
+#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
+
+FontFaceSet*
+FontFaceSet::Add(FontFace& aFontFace, ErrorResult& aRv)
+{
+ FlushUserFontSet();
+
+ if (aFontFace.IsInFontFaceSet(this)) {
+ return this;
+ }
+
+ if (aFontFace.HasRule()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
+ return nullptr;
+ }
+
+ 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->mSheetType = SheetType::Unknown; // unused for mNonRuleFaces
+ rec->mLoadEventShouldFire =
+ aFontFace.Status() == FontFaceLoadStatus::Unloaded ||
+ aFontFace.Status() == FontFaceLoadStatus::Loading;
+
+ mNonRuleFacesDirty = true;
+ RebuildUserFontSet();
+ mHasLoadingFontFacesIsDirty = true;
+ CheckLoadingStarted();
+ return this;
+}
+
+void
+FontFaceSet::Clear()
+{
+ FlushUserFontSet();
+
+ if (mNonRuleFaces.IsEmpty()) {
+ return;
+ }
+
+ for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
+ FontFace* f = mNonRuleFaces[i].mFontFace;
+ f->RemoveFontFaceSet(this);
+ }
+
+ mNonRuleFaces.Clear();
+ mNonRuleFacesDirty = true;
+ RebuildUserFontSet();
+ mHasLoadingFontFacesIsDirty = true;
+ CheckLoadingFinished();
+}
+
+bool
+FontFaceSet::Delete(FontFace& aFontFace)
+{
+ 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;
+ RebuildUserFontSet();
+ mHasLoadingFontFacesIsDirty = true;
+ CheckLoadingFinished();
+ return true;
+}
+
+bool
+FontFaceSet::HasAvailableFontFace(FontFace* aFontFace)
+{
+ return aFontFace->IsInFontFaceSet(this);
+}
+
+bool
+FontFaceSet::Has(FontFace& aFontFace)
+{
+ FlushUserFontSet();
+
+ return HasAvailableFontFace(&aFontFace);
+}
+
+FontFace*
+FontFaceSet::GetFontFaceAt(uint32_t aIndex)
+{
+ FlushUserFontSet();
+
+ if (aIndex < mRuleFaces.Length()) {
+ return mRuleFaces[aIndex].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 = 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 < Size(); i++) {
+ FontFace* face = GetFontFaceAt(i);
+ aCallback.Call(thisArg, *face, *face, *this, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+}
+
+void
+FontFaceSet::RemoveLoader(nsFontFaceLoader* aLoader)
+{
+ mLoaders.RemoveEntry(aLoader);
+}
+
+nsresult
+FontFaceSet::StartLoad(gfxUserFontEntry* aUserFontEntry,
+ const gfxFontFaceSrc* aFontFaceSrc)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamLoader> streamLoader;
+ nsCOMPtr<nsILoadGroup> loadGroup(mDocument->GetDocumentLoadGroup());
+
+ 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),
+ aFontFaceSrc->mURI,
+ mDocument,
+ aUserFontEntry->GetPrincipal(),
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
+ nsIContentPolicy::TYPE_FONT,
+ loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsFontFaceLoader> fontLoader =
+ new nsFontFaceLoader(aUserFontEntry, aFontFaceSrc->mURI, this, channel);
+
+ if (LOG_ENABLED()) {
+ LOG(("userfonts (%p) download start - font uri: (%s) "
+ "referrer uri: (%s)\n",
+ fontLoader.get(), aFontFaceSrc->mURI->GetSpecOrDefault().get(),
+ aFontFaceSrc->mReferrer
+ ? aFontFaceSrc->mReferrer->GetSpecOrDefault().get()
+ : ""));
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ httpChannel->SetReferrerWithPolicy(aFontFaceSrc->mReferrer,
+ mDocument->GetReferrerPolicy());
+ nsAutoCString accept("application/font-woff;q=0.9,*/*;q=0.8");
+ if (Preferences::GetBool(GFX_PREF_WOFF2_ENABLED)) {
+ accept.Insert(NS_LITERAL_CSTRING("application/font-woff2;q=1.0,"), 0);
+ }
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ accept, false);
+ // For WOFF and WOFF2, we should tell servers/proxies/etc NOT to try
+ // and apply additional compression at the content-encoding layer
+ if (aFontFaceSrc->mFormatFlags & (gfxUserFontSet::FLAG_FORMAT_WOFF |
+ gfxUserFontSet::FLAG_FORMAT_WOFF2)) {
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
+ NS_LITERAL_CSTRING("identity"), false);
+ }
+ }
+ nsCOMPtr<nsISupportsPriority> priorityChannel(do_QueryInterface(channel));
+ if (priorityChannel) {
+ priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGH);
+ }
+
+ rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::net::PredictorLearn(aFontFaceSrc->mURI, mDocument->GetDocumentURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
+ loadGroup);
+
+ rv = channel->AsyncOpen2(streamLoader);
+ if (NS_FAILED(rv)) {
+ fontLoader->DropChannel(); // explicitly need to break ref cycle
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ mLoaders.PutEntry(fontLoader);
+ fontLoader->StartedLoading(streamLoader);
+ aUserFontEntry->SetLoader(fontLoader); // let the font entry remember the
+ // loader, in case we need to cancel it
+ }
+
+ return rv;
+}
+
+bool
+FontFaceSet::UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules)
+{
+ MOZ_ASSERT(mUserFontSet);
+
+ // 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
+ nsDataHashtable<nsPtrHashKey<nsCSSFontFaceRule>, FontFace*> ruleFaceMap;
+ for (size_t i = 0, i_end = mRuleFaces.Length(); i < i_end; ++i) {
+ FontFace* f = mRuleFaces[i].mFontFace;
+ if (!f) {
+ continue;
+ }
+ ruleFaceMap.Put(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;
+ mRuleFaces.SwapElements(oldRecords);
+
+ // 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 (auto it = mUserFontSet->mFontFamilies.Iter(); !it.Done(); it.Next()) {
+ it.Data()->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.
+ nsTHashtable<nsPtrHashKey<nsCSSFontFaceRule>> 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.
+ if (handledRules.Contains(aRules[i].mRule)) {
+ continue;
+ }
+ nsCSSFontFaceRule* rule = aRules[i].mRule;
+ RefPtr<FontFace> f = ruleFaceMap.Get(rule);
+ if (!f.get()) {
+ f = FontFace::CreateForRule(GetParentObject(), this, rule);
+ }
+ InsertRuleFontFace(f, aRules[i].mSheetType, oldRecords, modified);
+ handledRules.PutEntry(aRules[i].mRule);
+ }
+
+ 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, modified);
+ }
+
+ // 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 = mUserFontSet->mFontFamilies.Iter(); !it.Done(); it.Next()) {
+ if (it.Data()->GetFontList().IsEmpty()) {
+ 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<FontFace> 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 (mUserFontSet->mRebuildLocalRules) {
+ mUserFontSet->mLocalRulesUsed = false;
+ mUserFontSet->mRebuildLocalRules = false;
+ }
+
+ if (LOG_ENABLED() && !mRuleFaces.IsEmpty()) {
+ LOG(("userfonts (%p) userfont rules update (%s) rule count: %d",
+ mUserFontSet.get(),
+ (modified ? "modified" : "not modified"),
+ (int)(mRuleFaces.Length())));
+ }
+
+ return modified;
+}
+
+static bool
+HasLocalSrc(const nsCSSValue::Array *aSrcArr)
+{
+ size_t numSrc = aSrcArr->Count();
+ for (size_t i = 0; i < numSrc; i++) {
+ if (aSrcArr->Item(i).GetUnit() == eCSSUnit_Local_Font) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+FontFaceSet::IncrementGeneration(bool aIsRebuild)
+{
+ MOZ_ASSERT(mUserFontSet);
+ mUserFontSet->IncrementGeneration(aIsRebuild);
+}
+
+void
+FontFaceSet::InsertNonRuleFontFace(FontFace* aFontFace,
+ bool& aFontSetModified)
+{
+ nsAutoString fontfamily;
+ if (!aFontFace->GetFamilyName(fontfamily)) {
+ // If there is no family name, this rule cannot contribute a
+ // usable font, so there is no point in processing it further.
+ return;
+ }
+
+ // Just create a new font entry if we haven't got one already.
+ if (!aFontFace->GetUserFontEntry()) {
+ // XXX Should we be checking mUserFontSet->mLocalRulesUsed like
+ // InsertRuleFontFace does?
+ RefPtr<gfxUserFontEntry> entry =
+ FindOrCreateUserFontEntryFromFontFace(fontfamily, aFontFace,
+ SheetType::Doc);
+ if (!entry) {
+ return;
+ }
+ aFontFace->SetUserFontEntry(entry);
+ }
+
+ aFontSetModified = true;
+ mUserFontSet->AddUserFontEntry(fontfamily, aFontFace->GetUserFontEntry());
+}
+
+void
+FontFaceSet::InsertRuleFontFace(FontFace* aFontFace, SheetType aSheetType,
+ nsTArray<FontFaceRecord>& aOldRecords,
+ bool& aFontSetModified)
+{
+ nsAutoString fontfamily;
+ if (!aFontFace->GetFamilyName(fontfamily)) {
+ // 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.mSheetType == aSheetType) {
+
+ // if local rules were used, don't use the old font entry
+ // for rules containing src local usage
+ if (mUserFontSet->mLocalRulesUsed &&
+ mUserFontSet->mRebuildLocalRules) {
+ nsCSSValue val;
+ aFontFace->GetDesc(eCSSFontDesc_Src, val);
+ nsCSSUnit unit = val.GetUnit();
+ if (unit == eCSSUnit_Array && HasLocalSrc(val.GetArrayValue())) {
+ // 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");
+
+ mUserFontSet->AddUserFontEntry(fontfamily, entry);
+
+ MOZ_ASSERT(!HasRuleFontFace(rec.mFontFace),
+ "FontFace should not occur in mRuleFaces twice");
+
+ mRuleFaces.AppendElement(rec);
+ aOldRecords.RemoveElementAt(i);
+ // 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:
+ RefPtr<gfxUserFontEntry> entry =
+ FindOrCreateUserFontEntryFromFontFace(fontfamily, aFontFace, 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.mSheetType = aSheetType;
+ rec.mLoadEventShouldFire =
+ aFontFace->Status() == FontFaceLoadStatus::Unloaded ||
+ aFontFace->Status() == FontFaceLoadStatus::Loading;
+
+ aFontFace->SetUserFontEntry(entry);
+
+ MOZ_ASSERT(!HasRuleFontFace(aFontFace),
+ "FontFace should not occur in mRuleFaces twice");
+
+ mRuleFaces.AppendElement(rec);
+
+ // 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.
+ mUserFontSet->AddUserFontEntry(fontfamily, entry);
+}
+
+/* static */ already_AddRefed<gfxUserFontEntry>
+FontFaceSet::FindOrCreateUserFontEntryFromFontFace(FontFace* aFontFace)
+{
+ nsAutoString fontfamily;
+ if (!aFontFace->GetFamilyName(fontfamily)) {
+ // If there is no family name, this rule cannot contribute a
+ // usable font, so there is no point in processing it further.
+ return nullptr;
+ }
+
+ return FindOrCreateUserFontEntryFromFontFace(fontfamily, aFontFace,
+ SheetType::Doc);
+}
+
+/* static */ already_AddRefed<gfxUserFontEntry>
+FontFaceSet::FindOrCreateUserFontEntryFromFontFace(const nsAString& aFamilyName,
+ FontFace* aFontFace,
+ SheetType aSheetType)
+{
+ FontFaceSet* set = aFontFace->GetPrimaryFontFaceSet();
+
+ nsCSSValue val;
+ nsCSSUnit unit;
+
+ uint32_t weight = NS_STYLE_FONT_WEIGHT_NORMAL;
+ int32_t stretch = NS_STYLE_FONT_STRETCH_NORMAL;
+ uint8_t italicStyle = NS_STYLE_FONT_STYLE_NORMAL;
+ uint32_t languageOverride = NO_FONT_LANGUAGE_OVERRIDE;
+ uint8_t fontDisplay = NS_FONT_DISPLAY_AUTO;
+
+ // set up weight
+ aFontFace->GetDesc(eCSSFontDesc_Weight, val);
+ unit = val.GetUnit();
+ if (unit == eCSSUnit_Integer || unit == eCSSUnit_Enumerated) {
+ weight = val.GetIntValue();
+ if (weight == 0) {
+ weight = NS_STYLE_FONT_WEIGHT_NORMAL;
+ }
+ } else if (unit == eCSSUnit_Normal) {
+ weight = NS_STYLE_FONT_WEIGHT_NORMAL;
+ } else {
+ NS_ASSERTION(unit == eCSSUnit_Null,
+ "@font-face weight has unexpected unit");
+ }
+
+ // set up stretch
+ aFontFace->GetDesc(eCSSFontDesc_Stretch, val);
+ unit = val.GetUnit();
+ if (unit == eCSSUnit_Enumerated) {
+ stretch = val.GetIntValue();
+ } else if (unit == eCSSUnit_Normal) {
+ stretch = NS_STYLE_FONT_STRETCH_NORMAL;
+ } else {
+ NS_ASSERTION(unit == eCSSUnit_Null,
+ "@font-face stretch has unexpected unit");
+ }
+
+ // set up font style
+ aFontFace->GetDesc(eCSSFontDesc_Style, val);
+ unit = val.GetUnit();
+ if (unit == eCSSUnit_Enumerated) {
+ italicStyle = val.GetIntValue();
+ } else if (unit == eCSSUnit_Normal) {
+ italicStyle = NS_STYLE_FONT_STYLE_NORMAL;
+ } else {
+ NS_ASSERTION(unit == eCSSUnit_Null,
+ "@font-face style has unexpected unit");
+ }
+
+ // set up font display
+ aFontFace->GetDesc(eCSSFontDesc_Display, val);
+ unit = val.GetUnit();
+ if (unit == eCSSUnit_Enumerated) {
+ fontDisplay = val.GetIntValue();
+ } else {
+ NS_ASSERTION(unit == eCSSUnit_Null,
+ "@font-face style has unexpected unit");
+ }
+
+ // set up font features
+ nsTArray<gfxFontFeature> featureSettings;
+ aFontFace->GetDesc(eCSSFontDesc_FontFeatureSettings, val);
+ unit = val.GetUnit();
+ if (unit == eCSSUnit_Normal) {
+ // empty list of features
+ } else if (unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep) {
+ nsRuleNode::ComputeFontFeatures(val.GetPairListValue(), featureSettings);
+ } else {
+ NS_ASSERTION(unit == eCSSUnit_Null,
+ "@font-face font-feature-settings has unexpected unit");
+ }
+
+ // set up font language override
+ aFontFace->GetDesc(eCSSFontDesc_FontLanguageOverride, val);
+ unit = val.GetUnit();
+ if (unit == eCSSUnit_Normal) {
+ // empty feature string
+ } else if (unit == eCSSUnit_String) {
+ nsString stringValue;
+ val.GetStringValue(stringValue);
+ languageOverride = gfxFontStyle::ParseFontLanguageOverride(stringValue);
+ } else {
+ NS_ASSERTION(unit == eCSSUnit_Null,
+ "@font-face font-language-override has unexpected unit");
+ }
+
+ // set up unicode-range
+ nsAutoPtr<gfxCharacterMap> unicodeRanges;
+ aFontFace->GetDesc(eCSSFontDesc_UnicodeRange, val);
+ unit = val.GetUnit();
+ if (unit == eCSSUnit_Array) {
+ unicodeRanges = new gfxCharacterMap();
+ const nsCSSValue::Array& sources = *val.GetArrayValue();
+ MOZ_ASSERT(sources.Count() % 2 == 0,
+ "odd number of entries in a unicode-range: array");
+
+ for (uint32_t i = 0; i < sources.Count(); i += 2) {
+ uint32_t min = sources[i].GetIntValue();
+ uint32_t max = sources[i+1].GetIntValue();
+ unicodeRanges->SetRange(min, max);
+ }
+ }
+
+ // 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->CreateBufferSource();
+ } else {
+ aFontFace->GetDesc(eCSSFontDesc_Src, val);
+ unit = val.GetUnit();
+ if (unit == eCSSUnit_Array) {
+ nsCSSValue::Array* srcArr = val.GetArrayValue();
+ size_t numSrc = srcArr->Count();
+
+ for (size_t i = 0; i < numSrc; i++) {
+ val = srcArr->Item(i);
+ unit = val.GetUnit();
+ gfxFontFaceSrc* face = srcArray.AppendElements(1);
+ if (!face)
+ return nullptr;
+
+ switch (unit) {
+
+ case eCSSUnit_Local_Font:
+ val.GetStringValue(face->mLocalName);
+ face->mSourceType = gfxFontFaceSrc::eSourceType_Local;
+ face->mURI = nullptr;
+ face->mFormatFlags = 0;
+ break;
+ case eCSSUnit_URL:
+ face->mSourceType = gfxFontFaceSrc::eSourceType_URL;
+ face->mURI = val.GetURLValue();
+ face->mReferrer = val.GetURLStructValue()->mReferrer;
+ face->mReferrerPolicy = set->mDocument->GetReferrerPolicy();
+ face->mOriginPrincipal = val.GetURLStructValue()->mOriginPrincipal;
+ NS_ASSERTION(face->mOriginPrincipal, "null origin principal in @font-face rule");
+
+ // 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
+ face->mUseOriginPrincipal = (aSheetType == SheetType::User ||
+ aSheetType == SheetType::Agent);
+
+ face->mLocalName.Truncate();
+ face->mFormatFlags = 0;
+ while (i + 1 < numSrc && (val = srcArr->Item(i+1),
+ val.GetUnit() == eCSSUnit_Font_Format)) {
+ nsDependentString valueString(val.GetStringBufferValue());
+ if (valueString.LowerCaseEqualsASCII("woff")) {
+ face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_WOFF;
+ } else if (Preferences::GetBool(GFX_PREF_WOFF2_ENABLED) &&
+ valueString.LowerCaseEqualsASCII("woff2")) {
+ face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_WOFF2;
+ } else if (valueString.LowerCaseEqualsASCII("opentype")) {
+ face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_OPENTYPE;
+ } else if (valueString.LowerCaseEqualsASCII("truetype")) {
+ face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_TRUETYPE;
+ } else if (valueString.LowerCaseEqualsASCII("truetype-aat")) {
+ face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_TRUETYPE_AAT;
+ } else if (valueString.LowerCaseEqualsASCII("embedded-opentype")) {
+ face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_EOT;
+ } else if (valueString.LowerCaseEqualsASCII("svg")) {
+ face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_SVG;
+ } else {
+ // unknown format specified, mark to distinguish from the
+ // case where no format hints are specified
+ face->mFormatFlags |= gfxUserFontSet::FLAG_FORMAT_UNKNOWN;
+ }
+ i++;
+ }
+ if (!face->mURI) {
+ // if URI not valid, omit from src array
+ srcArray.RemoveElementAt(srcArray.Length() - 1);
+ NS_WARNING("null url in @font-face rule");
+ continue;
+ }
+ break;
+ default:
+ NS_ASSERTION(unit == eCSSUnit_Local_Font || unit == eCSSUnit_URL,
+ "strange unit type in font-face src array");
+ break;
+ }
+ }
+ } else {
+ NS_ASSERTION(unit == eCSSUnit_Null, "@font-face src has unexpected unit");
+ }
+ }
+
+ if (srcArray.IsEmpty()) {
+ return nullptr;
+ }
+
+ RefPtr<gfxUserFontEntry> entry =
+ set->mUserFontSet->FindOrCreateUserFontEntry(aFamilyName, srcArray, weight,
+ stretch, italicStyle,
+ featureSettings,
+ languageOverride,
+ unicodeRanges, fontDisplay);
+ return entry.forget();
+}
+
+nsCSSFontFaceRule*
+FontFaceSet::FindRuleForEntry(gfxFontEntry* aFontEntry)
+{
+ NS_ASSERTION(!aFontEntry->mIsUserFontContainer, "only platform font entries");
+ for (uint32_t i = 0; i < mRuleFaces.Length(); ++i) {
+ FontFace* f = mRuleFaces[i].mFontFace;
+ gfxUserFontEntry* entry = f->GetUserFontEntry();
+ if (entry && entry->GetPlatformFontEntry() == aFontEntry) {
+ return f->GetRule();
+ }
+ }
+ return nullptr;
+}
+
+nsCSSFontFaceRule*
+FontFaceSet::FindRuleForUserFontEntry(gfxUserFontEntry* aUserFontEntry)
+{
+ for (uint32_t i = 0; i < mRuleFaces.Length(); ++i) {
+ FontFace* f = mRuleFaces[i].mFontFace;
+ if (f->GetUserFontEntry() == aUserFontEntry) {
+ return f->GetRule();
+ }
+ }
+ return nullptr;
+}
+
+nsresult
+FontFaceSet::LogMessage(gfxUserFontEntry* aUserFontEntry,
+ const char* aMessage,
+ uint32_t aFlags,
+ nsresult aStatus)
+{
+ nsCOMPtr<nsIConsoleService>
+ console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (!console) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString familyName;
+ nsAutoCString fontURI;
+ aUserFontEntry->GetFamilyNameAndURIForLogging(familyName, fontURI);
+
+ char weightKeywordBuf[8]; // plenty to sprintf() a uint16_t
+ const char* weightKeyword;
+ const nsAFlatCString& weightKeywordString =
+ nsCSSProps::ValueToKeyword(aUserFontEntry->Weight(),
+ nsCSSProps::kFontWeightKTable);
+ if (weightKeywordString.Length() > 0) {
+ weightKeyword = weightKeywordString.get();
+ } else {
+ SprintfLiteral(weightKeywordBuf, "%u", aUserFontEntry->Weight());
+ weightKeyword = weightKeywordBuf;
+ }
+
+ nsPrintfCString message
+ ("downloadable font: %s "
+ "(font-family: \"%s\" style:%s weight:%s stretch:%s src index:%d)",
+ aMessage,
+ familyName.get(),
+ aUserFontEntry->IsItalic() ? "italic" : "normal",
+ weightKeyword,
+ nsCSSProps::ValueToKeyword(aUserFontEntry->Stretch(),
+ nsCSSProps::kFontStretchKTable).get(),
+ aUserFontEntry->GetSrcIndex());
+
+ 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);
+
+ if (LOG_ENABLED()) {
+ LOG(("userfonts (%p) %s", mUserFontSet.get(), message.get()));
+ }
+
+ // try to give the user an indication of where the rule came from
+ nsCSSFontFaceRule* rule = FindRuleForUserFontEntry(aUserFontEntry);
+ nsString href;
+ nsString text;
+ nsresult rv;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ if (rule) {
+ rv = rule->GetCssText(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CSSStyleSheet* 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");
+ }
+ line = rule->GetLineNumber();
+ column = rule->GetColumnNumber();
+ }
+
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t innerWindowID = mDocument->InnerWindowID();
+ rv = scriptError->InitWithWindowID(NS_ConvertUTF8toUTF16(message),
+ href, // file
+ text, // src line
+ line,
+ column,
+ aFlags, // flags
+ "CSS Loader", // category (make separate?)
+ innerWindowID);
+ if (NS_SUCCEEDED(rv)) {
+ console->LogMessage(scriptError);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FontFaceSet::CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
+ nsIPrincipal** aPrincipal,
+ bool* aBypassCache)
+{
+ NS_ASSERTION(aFontFaceSrc &&
+ aFontFaceSrc->mSourceType == gfxFontFaceSrc::eSourceType_URL,
+ "bad font face url passed to fontloader");
+
+ // check same-site origin
+
+ NS_ASSERTION(aFontFaceSrc->mURI, "null font uri");
+ if (!aFontFaceSrc->mURI)
+ return NS_ERROR_FAILURE;
+
+ // use document principal, original principal if flag set
+ // this enables user stylesheets to load font files via
+ // @font-face rules
+ *aPrincipal = mDocument->NodePrincipal();
+
+ NS_ASSERTION(aFontFaceSrc->mOriginPrincipal,
+ "null origin principal in @font-face rule");
+ if (aFontFaceSrc->mUseOriginPrincipal) {
+ *aPrincipal = aFontFaceSrc->mOriginPrincipal;
+ }
+
+ *aBypassCache = false;
+
+ nsCOMPtr<nsIDocShell> docShell = mDocument->GetDocShell();
+ if (docShell) {
+ uint32_t loadType;
+ if (NS_SUCCEEDED(docShell->GetLoadType(&loadType))) {
+ if ((loadType >> 16) & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
+ *aBypassCache = true;
+ }
+ }
+ uint32_t flags;
+ if (NS_SUCCEEDED(docShell->GetDefaultLoadFlags(&flags))) {
+ if (flags & nsIRequest::LOAD_BYPASS_CACHE) {
+ *aBypassCache = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+// @arg aPrincipal: generally this is mDocument->NodePrincipal() but
+// might also be the original principal which enables user stylesheets
+// to load font files via @font-face rules.
+bool
+FontFaceSet::IsFontLoadAllowed(nsIURI* aFontLocation, nsIPrincipal* aPrincipal)
+{
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_FONT,
+ aFontLocation,
+ aPrincipal,
+ mDocument,
+ EmptyCString(), // mime type
+ nullptr, // aExtra
+ &shouldLoad,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+
+ return NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(shouldLoad);
+}
+
+nsresult
+FontFaceSet::SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ uint8_t*& aBuffer,
+ uint32_t& aBufferLength)
+{
+ nsresult rv;
+
+ 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.
+ // Further, we only get here for data: loads, so it doesn't really matter
+ // whether we use SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS or not, to be more
+ // restrictive we use SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS.
+ rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
+ aFontFaceSrc->mURI,
+ mDocument,
+ aFontToLoad->GetPrincipal(),
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
+ nsIContentPolicy::TYPE_FONT);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // blocking stream is OK for data URIs
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open2(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*> (moz_xmalloc(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;
+}
+
+bool
+FontFaceSet::GetPrivateBrowsing()
+{
+ nsCOMPtr<nsILoadContext> loadContext = mDocument->GetLoadContext();
+ return loadContext && loadContext->UsePrivateBrowsing();
+}
+
+void
+FontFaceSet::OnFontFaceStatusChanged(FontFace* aFontFace)
+{
+ 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;
+ nsCOMPtr<nsIRunnable> checkTask =
+ NewRunnableMethod(this, &FontFaceSet::CheckLoadingFinishedAfterDelay);
+ NS_DispatchToMainThread(checkTask);
+ }
+ }
+}
+
+void
+FontFaceSet::DidRefresh()
+{
+ CheckLoadingFinished();
+}
+
+void
+FontFaceSet::CheckLoadingFinishedAfterDelay()
+{
+ mDelayedLoadCheck = false;
+ CheckLoadingFinished();
+}
+
+void
+FontFaceSet::CheckLoadingStarted()
+{
+ 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;
+ (new AsyncEventDispatcher(this, NS_LITERAL_STRING("loading"),
+ false))->PostDOMEvent();
+
+ if (PrefEnabled()) {
+ if (mReady) {
+ if (GetParentObject()) {
+ ErrorResult rv;
+ mReady = Promise::Create(GetParentObject(), rv);
+ }
+ }
+ if (!mReady) {
+ mResolveLazilyCreatedReadyPromise = false;
+ }
+ }
+}
+
+void
+FontFaceSet::UpdateHasLoadingFontFaces()
+{
+ mHasLoadingFontFacesIsDirty = false;
+ mHasLoadingFontFaces = false;
+ for (size_t i = 0; i < mRuleFaces.Length(); i++) {
+ FontFace* f = mRuleFaces[i].mFontFace;
+ if (f->Status() == FontFaceLoadStatus::Loading) {
+ mHasLoadingFontFaces = true;
+ return;
+ }
+ }
+ for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
+ if (mNonRuleFaces[i].mFontFace->Status() == FontFaceLoadStatus::Loading) {
+ mHasLoadingFontFaces = true;
+ return;
+ }
+ }
+}
+
+bool
+FontFaceSet::HasLoadingFontFaces()
+{
+ if (mHasLoadingFontFacesIsDirty) {
+ UpdateHasLoadingFontFaces();
+ }
+ return mHasLoadingFontFaces;
+}
+
+bool
+FontFaceSet::MightHavePendingFontLoads()
+{
+ // Check for FontFace objects in the FontFaceSet that are still loading.
+ if (HasLoadingFontFaces()) {
+ 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;
+}
+
+void
+FontFaceSet::CheckLoadingFinished()
+{
+ if (mDelayedLoadCheck) {
+ // Wait until the runnable posted in OnFontFaceStatusChanged calls us.
+ return;
+ }
+
+ if (mStatus == FontFaceSetLoadStatus::Loaded) {
+ // We've already resolved mReady and dispatched the loadingdone/loadingerror
+ // events.
+ return;
+ }
+
+ if (MightHavePendingFontLoads()) {
+ // We're not finished loading yet.
+ return;
+ }
+
+ mStatus = FontFaceSetLoadStatus::Loaded;
+ if (mReady) {
+ mReady->MaybeResolve(this);
+ } else {
+ mResolveLazilyCreatedReadyPromise = true;
+ }
+
+ // Now dispatch the loadingdone/loadingerror events.
+ nsTArray<FontFace*> loaded;
+ nsTArray<FontFace*> failed;
+
+ for (size_t i = 0; i < mRuleFaces.Length(); i++) {
+ if (!mRuleFaces[i].mLoadEventShouldFire) {
+ continue;
+ }
+ FontFace* f = mRuleFaces[i].mFontFace;
+ if (f->Status() == FontFaceLoadStatus::Loaded) {
+ loaded.AppendElement(f);
+ mRuleFaces[i].mLoadEventShouldFire = false;
+ } else if (f->Status() == FontFaceLoadStatus::Error) {
+ failed.AppendElement(f);
+ mRuleFaces[i].mLoadEventShouldFire = false;
+ }
+ }
+
+ for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
+ if (!mNonRuleFaces[i].mLoadEventShouldFire) {
+ continue;
+ }
+ FontFace* f = mNonRuleFaces[i].mFontFace;
+ if (f->Status() == FontFaceLoadStatus::Loaded) {
+ loaded.AppendElement(f);
+ mNonRuleFaces[i].mLoadEventShouldFire = false;
+ } else if (f->Status() == FontFaceLoadStatus::Error) {
+ failed.AppendElement(f);
+ mNonRuleFaces[i].mLoadEventShouldFire = false;
+ }
+ }
+
+ DispatchLoadingFinishedEvent(NS_LITERAL_STRING("loadingdone"), loaded);
+
+ if (!failed.IsEmpty()) {
+ DispatchLoadingFinishedEvent(NS_LITERAL_STRING("loadingerror"), failed);
+ }
+}
+
+void
+FontFaceSet::DispatchLoadingFinishedEvent(
+ const nsAString& aType,
+ const nsTArray<FontFace*>& aFontFaces)
+{
+ FontFaceSetLoadEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ OwningNonNull<FontFace>* elements =
+ init.mFontfaces.AppendElements(aFontFaces.Length(), fallible);
+ MOZ_ASSERT(elements);
+ for (size_t i = 0; i < aFontFaces.Length(); i++) {
+ elements[i] = aFontFaces[i];
+ }
+ RefPtr<FontFaceSetLoadEvent> event =
+ FontFaceSetLoadEvent::Constructor(this, aType, init);
+ (new AsyncEventDispatcher(this, event))->PostDOMEvent();
+}
+
+// nsIDOMEventListener
+
+NS_IMETHODIMP
+FontFaceSet::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsString type;
+ aEvent->GetType(type);
+
+ if (!type.EqualsLiteral("DOMContentLoaded")) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RemoveDOMContentLoadedListener();
+ CheckLoadingFinished();
+
+ return NS_OK;
+}
+
+/* static */ bool
+FontFaceSet::PrefEnabled()
+{
+ static bool initialized = false;
+ static bool enabled;
+ if (!initialized) {
+ initialized = true;
+ Preferences::AddBoolVarCache(&enabled, FONT_LOADING_API_ENABLED_PREF);
+ }
+ return enabled;
+}
+
+// nsICSSLoaderObserver
+
+NS_IMETHODIMP
+FontFaceSet::StyleSheetLoaded(StyleSheet* aSheet,
+ bool aWasAlternate,
+ nsresult aStatus)
+{
+ CheckLoadingFinished();
+ return NS_OK;
+}
+
+void
+FontFaceSet::FlushUserFontSet()
+{
+ if (mDocument) {
+ mDocument->FlushUserFontSet();
+ }
+}
+
+void
+FontFaceSet::RebuildUserFontSet()
+{
+ if (mDocument) {
+ mDocument->RebuildUserFontSet();
+ }
+}
+
+nsPresContext*
+FontFaceSet::GetPresContext()
+{
+ if (!mDocument) {
+ return nullptr;
+ }
+
+ nsIPresShell* shell = mDocument->GetShell();
+ return shell ? shell->GetPresContext() : nullptr;
+}
+
+// -- FontFaceSet::UserFontSet ------------------------------------------------
+
+/* virtual */ nsresult
+FontFaceSet::UserFontSet::CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
+ nsIPrincipal** aPrincipal,
+ bool* aBypassCache)
+{
+ if (!mFontFaceSet) {
+ return NS_ERROR_FAILURE;
+ }
+ return mFontFaceSet->CheckFontLoad(aFontFaceSrc, aPrincipal, aBypassCache);
+}
+
+/* virtual */ bool
+FontFaceSet::UserFontSet::IsFontLoadAllowed(nsIURI* aFontLocation,
+ nsIPrincipal* aPrincipal)
+{
+ return mFontFaceSet &&
+ mFontFaceSet->IsFontLoadAllowed(aFontLocation, aPrincipal);
+}
+
+/* virtual */ nsresult
+FontFaceSet::UserFontSet::StartLoad(gfxUserFontEntry* aUserFontEntry,
+ const gfxFontFaceSrc* aFontFaceSrc)
+{
+ if (!mFontFaceSet) {
+ return NS_ERROR_FAILURE;
+ }
+ return mFontFaceSet->StartLoad(aUserFontEntry, aFontFaceSrc);
+}
+
+void
+FontFaceSet::UserFontSet::RecordFontLoadDone(uint32_t aFontSize,
+ TimeStamp aDoneTime)
+{
+ mDownloadCount++;
+ mDownloadSize += aFontSize;
+ Telemetry::Accumulate(Telemetry::WEBFONT_SIZE, aFontSize / 1024);
+
+ if (!mFontFaceSet) {
+ return;
+ }
+
+ TimeStamp navStart = mFontFaceSet->GetNavigationStartTimeStamp();
+ TimeStamp zero;
+ if (navStart != zero) {
+ Telemetry::AccumulateTimeDelta(Telemetry::WEBFONT_DOWNLOAD_TIME_AFTER_START,
+ navStart, aDoneTime);
+ }
+}
+
+/* virtual */ nsresult
+FontFaceSet::UserFontSet::LogMessage(gfxUserFontEntry* aUserFontEntry,
+ const char* aMessage,
+ uint32_t aFlags,
+ nsresult aStatus)
+{
+ if (!mFontFaceSet) {
+ return NS_ERROR_FAILURE;
+ }
+ return mFontFaceSet->LogMessage(aUserFontEntry, aMessage, aFlags, aStatus);
+}
+
+/* virtual */ nsresult
+FontFaceSet::UserFontSet::SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ uint8_t*& aBuffer,
+ uint32_t& aBufferLength)
+{
+ if (!mFontFaceSet) {
+ return NS_ERROR_FAILURE;
+ }
+ return mFontFaceSet->SyncLoadFontData(aFontToLoad, aFontFaceSrc,
+ aBuffer, aBufferLength);
+}
+
+/* virtual */ bool
+FontFaceSet::UserFontSet::GetPrivateBrowsing()
+{
+ return mFontFaceSet && mFontFaceSet->GetPrivateBrowsing();
+}
+
+/* virtual */ void
+FontFaceSet::UserFontSet::DoRebuildUserFontSet()
+{
+ if (!mFontFaceSet) {
+ return;
+ }
+ mFontFaceSet->RebuildUserFontSet();
+}
+
+/* virtual */ already_AddRefed<gfxUserFontEntry>
+FontFaceSet::UserFontSet::CreateUserFontEntry(
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay)
+{
+ RefPtr<gfxUserFontEntry> entry =
+ new FontFace::Entry(this, aFontFaceSrcList, aWeight, aStretch, aStyle,
+ aFeatureSettings, aLanguageOverride, aUnicodeRanges,
+ aFontDisplay);
+ return entry.forget();
+}
+
+#undef LOG_ENABLED
+#undef LOG
diff --git a/layout/style/FontFaceSet.h b/layout/style/FontFaceSet.h
new file mode 100644
index 000000000..eafbd514e
--- /dev/null
+++ b/layout/style/FontFaceSet.h
@@ -0,0 +1,356 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FontFaceSet_h
+#define mozilla_dom_FontFaceSet_h
+
+#include "mozilla/dom/FontFace.h"
+#include "mozilla/dom/FontFaceSetBinding.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "gfxUserFontSet.h"
+#include "nsCSSRules.h"
+#include "nsICSSLoaderObserver.h"
+
+struct gfxFontFaceSrc;
+class gfxUserFontEntry;
+class nsFontFaceLoader;
+class nsIPrincipal;
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace css {
+class FontFamilyListRefCnt;
+} // namespace css
+namespace dom {
+class FontFace;
+class Promise;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+class FontFaceSet final : public DOMEventTargetHelper
+ , public nsIDOMEventListener
+ , public nsICSSLoaderObserver
+{
+ friend class UserFontSet;
+
+public:
+ /**
+ * A gfxUserFontSet that integrates with the layout and style systems to
+ * manage @font-face rules and handle network requests for font loading.
+ *
+ * We would combine this class and FontFaceSet into the one class if it were
+ * possible; it's not because FontFaceSet is cycle collected and
+ * gfxUserFontSet isn't (and can't be, as gfx classes don't use the cycle
+ * collector). So UserFontSet exists just to override the needed virtual
+ * methods from gfxUserFontSet and to forward them on FontFaceSet.
+ */
+ class UserFontSet final : public gfxUserFontSet
+ {
+ friend class FontFaceSet;
+
+ public:
+ explicit UserFontSet(FontFaceSet* aFontFaceSet)
+ : mFontFaceSet(aFontFaceSet)
+ {
+ }
+
+ FontFaceSet* GetFontFaceSet() { return mFontFaceSet; }
+
+ virtual nsresult CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
+ nsIPrincipal** aPrincipal,
+ bool* aBypassCache) override;
+
+ virtual bool IsFontLoadAllowed(nsIURI* aFontLocation,
+ nsIPrincipal* aPrincipal) override;
+
+ virtual nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
+ const gfxFontFaceSrc* aFontFaceSrc) override;
+
+ void RecordFontLoadDone(uint32_t aFontSize,
+ mozilla::TimeStamp aDoneTime) override;
+
+ protected:
+ virtual bool GetPrivateBrowsing() override;
+ virtual nsresult SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ uint8_t*& aBuffer,
+ uint32_t& aBufferLength) override;
+ virtual nsresult LogMessage(gfxUserFontEntry* aUserFontEntry,
+ const char* aMessage,
+ uint32_t aFlags = nsIScriptError::errorFlag,
+ nsresult aStatus = NS_OK) override;
+ virtual void DoRebuildUserFontSet() override;
+ virtual already_AddRefed<gfxUserFontEntry> CreateUserFontEntry(
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay) override;
+
+ private:
+ RefPtr<FontFaceSet> mFontFaceSet;
+ };
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FontFaceSet, DOMEventTargetHelper)
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ FontFaceSet(nsPIDOMWindowInner* aWindow, nsIDocument* aDocument);
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ UserFontSet* GetUserFontSet() { return mUserFontSet; }
+
+ // Called by nsFontFaceLoader when the loader has completed normally.
+ // It's removed from the mLoaders set.
+ void RemoveLoader(nsFontFaceLoader* aLoader);
+
+ bool UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules);
+
+ nsPresContext* GetPresContext();
+
+ // search for @font-face rule that matches a platform font entry
+ nsCSSFontFaceRule* FindRuleForEntry(gfxFontEntry* aFontEntry);
+
+ void IncrementGeneration(bool aIsRebuild = false);
+
+ /**
+ * 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(FontFace* aFontFace);
+
+ /**
+ * Notification method called by a FontFace to indicate that its loading
+ * status has changed.
+ */
+ void OnFontFaceStatusChanged(FontFace* aFontFace);
+
+ /**
+ * Notification method called by the nsPresContext to indicate that the
+ * refresh driver ticked and flushed style and layout.
+ * were just flushed.
+ */
+ void DidRefresh();
+
+ /**
+ * Returns whether the "layout.css.font-loading-api.enabled" pref is true.
+ */
+ static bool PrefEnabled();
+
+ // nsICSSLoaderObserver
+ NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet,
+ bool aWasAlternate,
+ nsresult aStatus) override;
+
+ FontFace* GetFontFaceAt(uint32_t aIndex);
+
+ void FlushUserFontSet();
+
+ static nsPresContext* GetPresContextFor(gfxUserFontSet* aUserFontSet)
+ {
+ FontFaceSet* set = static_cast<UserFontSet*>(aUserFontSet)->mFontFaceSet;
+ return set ? set->GetPresContext() : nullptr;
+ }
+
+ // -- Web IDL --------------------------------------------------------------
+
+ IMPL_EVENT_HANDLER(loading)
+ IMPL_EVENT_HANDLER(loadingdone)
+ IMPL_EVENT_HANDLER(loadingerror)
+ already_AddRefed<mozilla::dom::Promise> Load(JSContext* aCx,
+ const nsAString& aFont,
+ const nsAString& aText,
+ mozilla::ErrorResult& aRv);
+ bool Check(const nsAString& aFont,
+ const nsAString& aText,
+ mozilla::ErrorResult& aRv);
+ mozilla::dom::Promise* GetReady(mozilla::ErrorResult& aRv);
+ mozilla::dom::FontFaceSetLoadStatus Status();
+
+ FontFaceSet* Add(FontFace& aFontFace, mozilla::ErrorResult& aRv);
+ void Clear();
+ bool Delete(FontFace& aFontFace);
+ bool Has(FontFace& aFontFace);
+ uint32_t Size();
+ already_AddRefed<mozilla::dom::FontFaceSetIterator> Entries();
+ already_AddRefed<mozilla::dom::FontFaceSetIterator> Values();
+ void ForEach(JSContext* aCx, FontFaceSetForEachCallback& aCallback,
+ JS::Handle<JS::Value> aThisArg,
+ mozilla::ErrorResult& aRv);
+
+private:
+ ~FontFaceSet();
+
+ /**
+ * Returns whether the given FontFace is currently "in" the FontFaceSet.
+ */
+ bool HasAvailableFontFace(FontFace* aFontFace);
+
+ /**
+ * Removes any listeners and observers.
+ */
+ void Disconnect();
+
+ void RemoveDOMContentLoadedListener();
+
+ /**
+ * Returns whether there might be any pending font loads, which should cause
+ * the mReady Promise not to be resolved yet.
+ */
+ bool MightHavePendingFontLoads();
+
+ /**
+ * Checks to see whether it is time to replace mReady and dispatch a
+ * "loading" event.
+ */
+ void CheckLoadingStarted();
+
+ /**
+ * Checks to see whether it is time to resolve mReady and dispatch any
+ * "loadingdone" and "loadingerror" events.
+ */
+ void CheckLoadingFinished();
+
+ /**
+ * Callback for invoking CheckLoadingFinished after going through the
+ * event loop. See OnFontFaceStatusChanged.
+ */
+ void CheckLoadingFinishedAfterDelay();
+
+ /**
+ * Dispatches a FontFaceSetLoadEvent to this object.
+ */
+ void DispatchLoadingFinishedEvent(
+ const nsAString& aType,
+ const nsTArray<FontFace*>& aFontFaces);
+
+ // 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;
+ SheetType mSheetType; // 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;
+ };
+
+ static already_AddRefed<gfxUserFontEntry> FindOrCreateUserFontEntryFromFontFace(
+ const nsAString& aFamilyName,
+ FontFace* aFontFace,
+ SheetType aSheetType);
+
+ // search for @font-face rule that matches a userfont font entry
+ nsCSSFontFaceRule* FindRuleForUserFontEntry(gfxUserFontEntry* aUserFontEntry);
+
+ nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
+ const gfxFontFaceSrc* aFontFaceSrc);
+ nsresult CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
+ nsIPrincipal** aPrincipal,
+ bool* aBypassCache);
+ bool IsFontLoadAllowed(nsIURI* aFontLocation, nsIPrincipal* aPrincipal);
+ bool GetPrivateBrowsing();
+ nsresult SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ uint8_t*& aBuffer,
+ uint32_t& aBufferLength);
+ nsresult LogMessage(gfxUserFontEntry* aUserFontEntry,
+ const char* aMessage,
+ uint32_t aFlags,
+ nsresult aStatus);
+ void RebuildUserFontSet();
+
+ void InsertRuleFontFace(FontFace* aFontFace, SheetType aSheetType,
+ nsTArray<FontFaceRecord>& aOldRecords,
+ bool& aFontSetModified);
+ void InsertNonRuleFontFace(FontFace* aFontFace, bool& aFontSetModified);
+
+#ifdef DEBUG
+ bool HasRuleFontFace(FontFace* aFontFace);
+#endif
+
+ /**
+ * Returns whether we have any loading FontFace objects in the FontFaceSet.
+ */
+ bool HasLoadingFontFaces();
+
+ // Helper function for HasLoadingFontFaces.
+ void UpdateHasLoadingFontFaces();
+
+ void ParseFontShorthandForMatching(
+ const nsAString& aFont,
+ RefPtr<mozilla::css::FontFamilyListRefCnt>& aFamilyList,
+ uint32_t& aWeight,
+ int32_t& aStretch,
+ uint8_t& aStyle,
+ ErrorResult& aRv);
+ void FindMatchingFontFaces(const nsAString& aFont,
+ const nsAString& aText,
+ nsTArray<FontFace*>& aFontFaces,
+ mozilla::ErrorResult& aRv);
+
+ TimeStamp GetNavigationStartTimeStamp();
+
+ RefPtr<UserFontSet> mUserFontSet;
+
+ // The document this is a FontFaceSet for.
+ nsCOMPtr<nsIDocument> mDocument;
+
+ // 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<mozilla::dom::Promise> mReady;
+ // Whether the ready promise must be resolved when it's created.
+ bool mResolveLazilyCreatedReadyPromise;
+
+ // 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;
+
+ // 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;
+
+ // The overall status of the loading or loaded fonts in the FontFaceSet.
+ mozilla::dom::FontFaceSetLoadStatus mStatus;
+
+ // Whether mNonRuleFaces has changed since last time UpdateRules ran.
+ bool mNonRuleFacesDirty;
+
+ // 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;
+
+ // This variable is only valid when mLoadingDirty is false.
+ bool mHasLoadingFontFacesIsDirty;
+
+ // Whether CheckLoadingFinished calls should be ignored. See comment in
+ // OnFontFaceStatusChanged.
+ bool mDelayedLoadCheck;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_FontFaceSet_h)
diff --git a/layout/style/FontFaceSetIterator.cpp b/layout/style/FontFaceSetIterator.cpp
new file mode 100644
index 000000000..74997eb1a
--- /dev/null
+++ b/layout/style/FontFaceSetIterator.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/FontFaceSetIterator.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION(FontFaceSetIterator, mFontFaceSet)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FontFaceSetIterator, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FontFaceSetIterator, Release)
+
+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 FontFaceSetIteratorBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+void
+FontFaceSetIterator::Next(JSContext* aCx, FontFaceSetIteratorResult& aResult,
+ ErrorResult& aRv)
+{
+ if (!mFontFaceSet) {
+ aResult.mDone = true;
+ return;
+ }
+
+ FontFace* face = mFontFaceSet->GetFontFaceAt(mNextIndex++);
+
+ 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::AutoValueArray<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 000000000..a764dfbf9
--- /dev/null
+++ b/layout/style/FontFaceSetIterator.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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(mozilla::dom::FontFaceSet* aFontFaceSet,
+ 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,
+ mozilla::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/GenerateCSSPropsGenerated.py b/layout/style/GenerateCSSPropsGenerated.py
new file mode 100644
index 000000000..5038e9afe
--- /dev/null
+++ b/layout/style/GenerateCSSPropsGenerated.py
@@ -0,0 +1,104 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 sys
+import string
+import argparse
+import subprocess
+import buildconfig
+from mozbuild import shellutil
+
+def get_properties(preprocessorHeader):
+ cpp = list(buildconfig.substs['CPP'])
+ cpp += shellutil.split(buildconfig.substs['ACDEFINES'])
+ cpp.append(preprocessorHeader)
+ preprocessed = subprocess.check_output(cpp)
+ properties = [{"name":p[0], "prop":p[1], "id":p[2],
+ "flags":p[3], "pref":p[4], "proptype":p[5]}
+ for (i, p) in enumerate(eval(preprocessed))]
+
+ # Sort the list so that longhand and logical properties are intermingled
+ # first, shorthand properties follow, then aliases appear last. This matches
+ # the order of the nsCSSPropertyID enum.
+
+ def property_compare(x, y):
+ property_order = {"longhand": 0, "logical": 0, "shorthand": 1, "alias": 2}
+ return property_order[x["proptype"]] - property_order[y["proptype"]]
+
+ properties = sorted(properties, cmp=property_compare)
+
+ for i, p in enumerate(properties):
+ p["index"] = i
+
+ # Record each property's IDL name.
+ for p in properties:
+ if "CSS_PROPERTY_INTERNAL" in p["flags"]:
+ p["idlname"] = None
+ else:
+ idl_name = p["prop"]
+ if not idl_name.startswith("Moz"):
+ idl_name = idl_name[0].lower() + idl_name[1:]
+ p["idlname"] = idl_name
+
+ return properties
+
+def generate_idl_names(properties):
+ names = []
+ for p in properties:
+ if p["proptype"] is "alias":
+ continue
+ if p["idlname"] is None:
+ names.append(" nullptr, // %s" % p["name"])
+ else:
+ names.append(' "%s",' % p["idlname"])
+ return "\n".join(names)
+
+def generate_assertions(properties):
+ def enum(p):
+ if p["proptype"] is "alias":
+ return "eCSSPropertyAlias_%s" % p["prop"]
+ else:
+ return "eCSSProperty_%s" % p["id"]
+ msg = ('static_assert(%s == %d, "GenerateCSSPropsGenerated.py did not list '
+ 'properties in nsCSSPropertyID order");')
+ return "\n".join(map(lambda p: msg % (enum(p), p["index"]), properties))
+
+def generate_idl_name_positions(properties):
+ # Skip aliases.
+ ps = filter(lambda p: p["proptype"] is not "alias", properties)
+
+ # Sort alphabetically by IDL name.
+ ps = sorted(ps, key=lambda p: p["idlname"])
+
+ # Annotate entries with the sorted position.
+ ps = [(p, position) for position, p in enumerate(ps)]
+
+ # Sort back to nsCSSPropertyID order.
+ ps = sorted(ps, key=lambda (p, position): p["index"])
+
+ return ",\n".join(map(lambda (p, position): " %d" % position, ps))
+
+def generate(output, cppTemplate, preprocessorHeader):
+ cppFile = open(cppTemplate, "r")
+ cppTemplate = cppFile.read()
+ cppFile.close()
+
+ properties = get_properties(preprocessorHeader)
+ substitutions = {
+ "idl_names": generate_idl_names(properties),
+ "assertions": generate_assertions(properties),
+ "idl_name_positions": generate_idl_name_positions(properties),
+ }
+ output.write("/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n" +
+ string.Template(cppTemplate).substitute(substitutions) + "\n")
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('cppTemplate', help='CSS property file template')
+ parser.add_argument('preprocessorHeader', help='Header file to pass through the preprocessor')
+ args = parser.parse_args()
+ generate(sys.stdout, args.cppTemplate, args.preprocessorHeader)
+
+if __name__ == '__main__':
+ main()
diff --git a/layout/style/GroupRule.h b/layout/style/GroupRule.h
new file mode 100644
index 000000000..ec781fae6
--- /dev/null
+++ b/layout/style/GroupRule.h
@@ -0,0 +1,101 @@
+/* -*- 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/. */
+
+/*
+ * 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/IncrementalClearCOMRuleArray.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/Rule.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsPresContext;
+class nsMediaQueryResultCacheKey;
+
+namespace mozilla {
+
+class CSSStyleSheet;
+
+namespace css {
+
+class GroupRuleRuleList;
+
+// inherits from Rule so it can be shared between
+// MediaRule and DocumentRule
+class GroupRule : public Rule
+{
+protected:
+ GroupRule(uint32_t aLineNumber, uint32_t aColumnNumber);
+ GroupRule(const GroupRule& aCopy);
+ virtual ~GroupRule();
+public:
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(GroupRule)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // implement part of Rule
+ DECL_STYLE_RULE_INHERIT_NO_DOMRULE
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual void SetStyleSheet(CSSStyleSheet* aSheet) override;
+
+public:
+ void AppendStyleRule(Rule* aRule);
+
+ int32_t StyleRuleCount() const { return mRules.Count(); }
+ Rule* GetStyleRuleAt(int32_t aIndex) const;
+
+ typedef IncrementalClearCOMRuleArray::nsCOMArrayEnumFunc RuleEnumFunc;
+ bool EnumerateRulesForwards(RuleEnumFunc aFunc, void * aData) const;
+
+ /*
+ * The next three methods should never be called unless you have first
+ * called WillDirty() on the parent stylesheet. After they are
+ * called, DidDirty() needs to be called on the sheet.
+ */
+ nsresult DeleteStyleRuleAt(uint32_t aIndex);
+ nsresult InsertStyleRuleAt(uint32_t aIndex, Rule* aRule);
+
+ virtual bool UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey) = 0;
+
+ // non-virtual -- it is only called by subclasses
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override = 0;
+
+ static bool
+ CloneRuleInto(Rule* aRule, void* aArray)
+ {
+ RefPtr<Rule> clone = aRule->Clone();
+ static_cast<IncrementalClearCOMRuleArray*>(aArray)->AppendObject(clone);
+ return true;
+ }
+
+protected:
+ // to help implement nsIDOMCSSRule
+ void AppendRulesToCssText(nsAString& aCssText);
+
+ // to implement common methods on nsIDOMCSSMediaRule and
+ // nsIDOMCSSMozDocumentRule
+ nsresult GetCssRules(nsIDOMCSSRuleList* *aRuleList);
+ nsresult InsertRule(const nsAString & aRule, uint32_t aIndex,
+ uint32_t* _retval);
+ nsresult DeleteRule(uint32_t aIndex);
+
+ IncrementalClearCOMRuleArray mRules;
+ RefPtr<GroupRuleRuleList> mRuleCollection; // lazily constructed
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_GroupRule_h__ */
diff --git a/layout/style/HandleRefPtr.h b/layout/style/HandleRefPtr.h
new file mode 100644
index 000000000..0a73a4cf7
--- /dev/null
+++ b/layout/style/HandleRefPtr.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=78:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* smart pointer for strong references to objects through pointer-like
+ * "handle" objects */
+
+#include <algorithm>
+#include "mozilla/Assertions.h"
+
+#ifndef mozilla_HandleRefPtr_h
+#define mozilla_HandleRefPtr_h
+
+namespace mozilla {
+
+/**
+ * A class for holding strong references to handle-managed objects.
+ *
+ * This is intended for use with objects like RestyleManagerHandle,
+ * where the handle type is not a pointer but which can still have
+ * ->AddRef() and ->Release() called on it.
+ */
+template<typename T>
+class HandleRefPtr
+{
+public:
+ HandleRefPtr() {}
+
+ HandleRefPtr(HandleRefPtr<T>& aRhs)
+ {
+ assign(aRhs.mHandle);
+ }
+
+ HandleRefPtr(HandleRefPtr<T>&& aRhs)
+ {
+ std::swap(mHandle, aRhs.mHandle);
+ }
+
+ MOZ_IMPLICIT HandleRefPtr(T aRhs)
+ {
+ assign(aRhs);
+ }
+
+ HandleRefPtr<T>& operator=(HandleRefPtr<T>& aRhs)
+ {
+ assign(aRhs.mHandle);
+ return *this;
+ }
+
+ HandleRefPtr<T>& operator=(T aRhs)
+ {
+ assign(aRhs);
+ return *this;
+ }
+
+ ~HandleRefPtr() { assign(nullptr); }
+
+ explicit operator bool() const { return !!mHandle; }
+ bool operator!() const { return !mHandle; }
+
+ operator T() const { return mHandle; }
+ T operator->() const { return mHandle; }
+
+ void swap(HandleRefPtr<T>& aOther)
+ {
+ std::swap(mHandle, aOther.mHandle);
+ }
+
+private:
+ void assign(T aPtr)
+ {
+ // AddRef early so |aPtr| can't disappear underneath us.
+ if (aPtr) {
+ aPtr->AddRef();
+ }
+
+ // Don't release |mHandle| yet: if |mHandle| indirectly owns |this|,
+ // releasing would invalidate |this|. Swap |aPtr| for |mHandle| so that
+ // |aPtr| lives as long as |this|, then release the new |aPtr| (really the
+ // original |mHandle|) so that if |this| is invalidated, we're not using it
+ // any more.
+ std::swap(mHandle, aPtr);
+
+ if (aPtr) {
+ aPtr->Release();
+ }
+ }
+
+ T mHandle;
+};
+
+template<typename T>
+inline bool operator==(const HandleRefPtr<T>& aLHS, const HandleRefPtr<T>& aRHS)
+{
+ return static_cast<T>(aLHS) == static_cast<T>(aRHS);
+}
+
+template<typename T>
+inline bool operator==(const HandleRefPtr<T>& aLHS, T aRHS)
+{
+ return static_cast<T>(aLHS) == aRHS;
+}
+
+template<typename T>
+inline bool operator==(T aLHS, const HandleRefPtr<T>& aRHS)
+{
+ return aLHS == static_cast<T>(aRHS);
+}
+
+template<typename T>
+inline bool operator!=(const HandleRefPtr<T>& aLHS, const HandleRefPtr<T>& aRHS)
+{
+ return !(aLHS == aRHS);
+}
+
+template<typename T>
+inline bool operator!=(const HandleRefPtr<T>& aLHS, T aRHS)
+{
+ return !(aLHS == aRHS);
+}
+
+template<typename T>
+inline bool operator!=(T aLHS, const HandleRefPtr<T>& aRHS)
+{
+ return !(aLHS == aRHS);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_HandleRefPtr_h
diff --git a/layout/style/ImageDocument.css b/layout/style/ImageDocument.css
new file mode 100644
index 000000000..a79d2213f
--- /dev/null
+++ b/layout/style/ImageDocument.css
@@ -0,0 +1,31 @@
+/* -*- 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.
+*/
+
+@media not print {
+ .overflowingVertical, .overflowingHorizontalOnly {
+ cursor: zoom-out;
+ }
+
+ .shrinkToFit {
+ cursor: zoom-in;
+ }
+}
+
+@media print {
+ /* We must declare the image as a block element. If we stay as
+ an inline element, our parent LineBox will be inline too and
+ ignore the available height during reflow.
+ This is bad during printing, it means tall image frames won't know
+ the size of the paper and cannot break into continuations along
+ multiple pages. */
+ img {
+ display: block;
+ }
+}
diff --git a/layout/style/ImageLoader.cpp b/layout/style/ImageLoader.cpp
new file mode 100644
index 000000000..0a605abc9
--- /dev/null
+++ b/layout/style/ImageLoader.cpp
@@ -0,0 +1,529 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsAutoPtr.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsError.h"
+#include "nsDisplayList.h"
+#include "FrameLayerBuilder.h"
+#include "nsSVGEffects.h"
+#include "imgIContainer.h"
+#include "Image.h"
+
+namespace mozilla {
+namespace css {
+
+void
+ImageLoader::DropDocumentReference()
+{
+ // 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());
+
+ for (auto it = mImages.Iter(); !it.Done(); it.Next()) {
+ ImageLoader::Image* image = it.Get()->GetKey();
+ imgIRequest* request = image->mRequests.GetWeak(mDocument);
+ if (request) {
+ request->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+ image->mRequests.Remove(mDocument);
+ }
+ mImages.Clear();
+
+ mDocument = nullptr;
+}
+
+void
+ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest,
+ nsIFrame* aFrame)
+{
+ 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 == this);
+
+ FrameSet* frameSet = nullptr;
+ if (mRequestToFrameMap.Get(aRequest, &frameSet)) {
+ NS_ASSERTION(frameSet, "This should never be null!");
+ }
+
+ if (!frameSet) {
+ nsAutoPtr<FrameSet> newFrameSet(new FrameSet());
+
+ mRequestToFrameMap.Put(aRequest, newFrameSet);
+ frameSet = newFrameSet.forget();
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ nsLayoutUtils::RegisterImageRequestIfAnimated(presContext,
+ aRequest,
+ nullptr);
+ }
+ }
+
+ RequestSet* requestSet = nullptr;
+ if (mFrameToRequestMap.Get(aFrame, &requestSet)) {
+ NS_ASSERTION(requestSet, "This should never be null");
+ }
+
+ if (!requestSet) {
+ nsAutoPtr<RequestSet> newRequestSet(new RequestSet());
+
+ mFrameToRequestMap.Put(aFrame, newRequestSet);
+ requestSet = newRequestSet.forget();
+ }
+
+ // Add these to the sets, but only if they're not already there.
+ uint32_t i = frameSet->IndexOfFirstElementGt(aFrame);
+ if (i == 0 || aFrame != frameSet->ElementAt(i-1)) {
+ frameSet->InsertElementAt(i, aFrame);
+ }
+ i = requestSet->IndexOfFirstElementGt(aRequest);
+ if (i == 0 || aRequest != requestSet->ElementAt(i-1)) {
+ requestSet->InsertElementAt(i, aRequest);
+ }
+}
+
+void
+ImageLoader::MaybeRegisterCSSImage(ImageLoader::Image* aImage)
+{
+ NS_ASSERTION(aImage, "This should never be null!");
+
+ bool found = false;
+ aImage->mRequests.GetWeak(mDocument, &found);
+ if (found) {
+ // This document already has a request.
+ return;
+ }
+
+ imgRequestProxy* canonicalRequest = aImage->mRequests.GetWeak(nullptr);
+ if (!canonicalRequest) {
+ // The image was blocked or something.
+ return;
+ }
+
+ RefPtr<imgRequestProxy> request;
+
+ // Ignore errors here. If cloning fails for some reason we'll put a null
+ // entry in the hash and we won't keep trying to clone.
+ mInClone = true;
+ canonicalRequest->Clone(this, getter_AddRefs(request));
+ mInClone = false;
+
+ aImage->mRequests.Put(mDocument, request);
+
+ AddImage(aImage);
+}
+
+void
+ImageLoader::DeregisterCSSImage(ImageLoader::Image* aImage)
+{
+ RemoveImage(aImage);
+}
+
+void
+ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest,
+ nsIFrame* aFrame)
+{
+ FrameSet* frameSet = nullptr;
+ RequestSet* requestSet = nullptr;
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<imgINotificationObserver> observer;
+ aRequest->GetNotificationObserver(getter_AddRefs(observer));
+ MOZ_ASSERT(!observer || observer == this);
+ }
+#endif
+
+ mRequestToFrameMap.Get(aRequest, &frameSet);
+ mFrameToRequestMap.Get(aFrame, &requestSet);
+
+ if (frameSet) {
+ frameSet->RemoveElementSorted(aFrame);
+ }
+ if (requestSet) {
+ requestSet->RemoveElementSorted(aRequest);
+ }
+
+ if (frameSet && !frameSet->Length()) {
+ mRequestToFrameMap.Remove(aRequest);
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ nsLayoutUtils::DeregisterImageRequest(presContext,
+ aRequest,
+ nullptr);
+ }
+ }
+
+ if (requestSet && !requestSet->Length()) {
+ mFrameToRequestMap.Remove(aFrame);
+ }
+}
+
+void
+ImageLoader::DropRequestsForFrame(nsIFrame* aFrame)
+{
+ RequestSet* requestSet = nullptr;
+ if (!mFrameToRequestMap.Get(aFrame, &requestSet)) {
+ return;
+ }
+
+ NS_ASSERTION(requestSet, "This should never be null");
+
+ RequestSet frozenRequestSet(*requestSet);
+ for (RequestSet::size_type i = frozenRequestSet.Length(); i != 0; --i) {
+ imgIRequest* request = frozenRequestSet.ElementAt(i - 1);
+
+ DisassociateRequestFromFrame(request, aFrame);
+ }
+}
+
+void
+ImageLoader::SetAnimationMode(uint16_t aMode)
+{
+ NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode ||
+ aMode == imgIContainer::kDontAnimMode ||
+ aMode == imgIContainer::kLoopOnceAnimMode,
+ "Wrong Animation Mode is being set!");
+
+ for (auto iter = mRequestToFrameMap.ConstIter(); !iter.Done(); iter.Next()) {
+ auto request = static_cast<imgIRequest*>(iter.Key());
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<imgIRequest> debugRequest = do_QueryInterface(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)
+{
+ for (auto iter = mRequestToFrameMap.ConstIter(); !iter.Done(); iter.Next()) {
+ auto request = static_cast<imgIRequest*>(iter.Key());
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<imgIRequest> debugRequest = do_QueryInterface(request);
+ NS_ASSERTION(debugRequest == request, "This is bad");
+ }
+#endif
+
+ if (aPresContext) {
+ nsLayoutUtils::DeregisterImageRequest(aPresContext,
+ request,
+ nullptr);
+ }
+ }
+
+ mRequestToFrameMap.Clear();
+ mFrameToRequestMap.Clear();
+}
+
+void
+ImageLoader::LoadImage(nsIURI* aURI, nsIPrincipal* aOriginPrincipal,
+ nsIURI* aReferrer, ImageLoader::Image* aImage)
+{
+ NS_ASSERTION(aImage->mRequests.Count() == 0, "Huh?");
+
+ aImage->mRequests.Put(nullptr, nullptr);
+
+ if (!aURI) {
+ return;
+ }
+
+ RefPtr<imgRequestProxy> request;
+ nsresult rv = nsContentUtils::LoadImage(aURI, mDocument, mDocument,
+ aOriginPrincipal, aReferrer,
+ mDocument->GetReferrerPolicy(),
+ nullptr, nsIRequest::LOAD_NORMAL,
+ NS_LITERAL_STRING("css"),
+ getter_AddRefs(request));
+
+ if (NS_FAILED(rv) || !request) {
+ return;
+ }
+
+ RefPtr<imgRequestProxy> clonedRequest;
+ mInClone = true;
+ rv = request->Clone(this, getter_AddRefs(clonedRequest));
+ mInClone = false;
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ aImage->mRequests.Put(nullptr, request);
+ aImage->mRequests.Put(mDocument, clonedRequest);
+
+ AddImage(aImage);
+}
+
+void
+ImageLoader::AddImage(ImageLoader::Image* aImage)
+{
+ NS_ASSERTION(!mImages.Contains(aImage), "Huh?");
+ mImages.PutEntry(aImage);
+}
+
+void
+ImageLoader::RemoveImage(ImageLoader::Image* aImage)
+{
+ NS_ASSERTION(mImages.Contains(aImage), "Huh?");
+ mImages.RemoveEntry(aImage);
+}
+
+nsPresContext*
+ImageLoader::GetPresContext()
+{
+ if (!mDocument) {
+ return nullptr;
+ }
+
+ nsIPresShell* shell = mDocument->GetShell();
+ if (!shell) {
+ return nullptr;
+ }
+
+ return shell->GetPresContext();
+}
+
+void InvalidateImagesCallback(nsIFrame* aFrame,
+ FrameLayerBuilder::DisplayItemData* aItem)
+{
+ nsDisplayItem::Type type = nsDisplayItem::GetDisplayItemTypeFromKey(aItem->GetDisplayItemKey());
+ uint8_t flags = nsDisplayItem::GetDisplayItemFlagsForType(type);
+
+ if (flags & nsDisplayItem::TYPE_RENDERS_NO_IMAGES) {
+ return;
+ }
+
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Invalidating display item(type=%d) based on frame %p \
+ because it might contain an invalidated image\n", type, aFrame);
+ }
+ aItem->Invalidate();
+ aFrame->SchedulePaint();
+}
+
+void
+ImageLoader::DoRedraw(FrameSet* aFrameSet, bool aForcePaint)
+{
+ NS_ASSERTION(aFrameSet, "Must have a frame set");
+ NS_ASSERTION(mDocument, "Should have returned earlier!");
+
+ FrameSet::size_type length = aFrameSet->Length();
+ for (FrameSet::size_type i = 0; i < length; i++) {
+ nsIFrame* frame = aFrameSet->ElementAt(i);
+
+ if (frame->StyleVisibility()->IsVisible()) {
+ if (frame->IsFrameOfType(nsIFrame::eTablePart)) {
+ // Tables don't necessarily build border/background display items
+ // for the individual table part frames, so IterateRetainedDataFor
+ // might not find the right display item.
+ frame->InvalidateFrame();
+ } else {
+ FrameLayerBuilder::IterateRetainedDataFor(frame, InvalidateImagesCallback);
+
+ // Update ancestor rendering observers (-moz-element etc)
+ nsIFrame *f = frame;
+ while (f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(f);
+ f = nsLayoutUtils::GetCrossDocParentFrame(f);
+ }
+
+ if (aForcePaint) {
+ frame->SchedulePaint();
+ }
+ }
+ }
+ }
+}
+
+NS_IMPL_ADDREF(ImageLoader)
+NS_IMPL_RELEASE(ImageLoader)
+
+NS_INTERFACE_MAP_BEGIN(ImageLoader)
+ NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
+ NS_INTERFACE_MAP_ENTRY(imgIOnloadBlocker)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+ImageLoader::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
+{
+ 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);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ImageLoader::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
+{
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_OK;
+ }
+
+ aImage->SetAnimationMode(presContext->ImageAnimationMode());
+
+ return NS_OK;
+}
+
+nsresult
+ImageLoader::OnImageIsAnimated(imgIRequest* aRequest)
+{
+ if (!mDocument) {
+ return NS_OK;
+ }
+
+ FrameSet* frameSet = nullptr;
+ if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
+ return NS_OK;
+ }
+
+ // Register with the refresh driver now that we are aware that
+ // we are animated.
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ nsLayoutUtils::RegisterImageRequest(presContext,
+ aRequest,
+ nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ImageLoader::OnFrameComplete(imgIRequest* aRequest)
+{
+ if (!mDocument || mInClone) {
+ return NS_OK;
+ }
+
+ FrameSet* frameSet = nullptr;
+ if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(frameSet, "This should never be null!");
+
+ // 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).
+ DoRedraw(frameSet, /* aForcePaint = */ true);
+
+ return NS_OK;
+}
+
+nsresult
+ImageLoader::OnFrameUpdate(imgIRequest* aRequest)
+{
+ if (!mDocument || mInClone) {
+ return NS_OK;
+ }
+
+ FrameSet* frameSet = nullptr;
+ if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(frameSet, "This should never be null!");
+
+ DoRedraw(frameSet, /* aForcePaint = */ false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImageLoader::BlockOnload(imgIRequest* aRequest)
+{
+ if (!mDocument) {
+ return NS_OK;
+ }
+
+ mDocument->BlockOnload();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImageLoader::UnblockOnload(imgIRequest* aRequest)
+{
+ if (!mDocument) {
+ return NS_OK;
+ }
+
+ mDocument->UnblockOnload(false);
+
+ return NS_OK;
+}
+
+void
+ImageLoader::FlushUseCounters()
+{
+ for (auto iter = mImages.Iter(); !iter.Done(); iter.Next()) {
+ nsPtrHashKey<Image>* key = iter.Get();
+ ImageLoader::Image* image = key->GetKey();
+
+ imgIRequest* request = image->mRequests.GetWeak(mDocument);
+
+ nsCOMPtr<imgIContainer> container;
+ request->GetImage(getter_AddRefs(container));
+ if (container) {
+ static_cast<image::Image*>(container.get())->ReportUseCounters();
+ }
+ }
+}
+
+} // namespace css
+} // namespace mozilla
diff --git a/layout/style/ImageLoader.h b/layout/style/ImageLoader.h
new file mode 100644
index 000000000..4e29da5ed
--- /dev/null
+++ b/layout/style/ImageLoader.h
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "imgIRequest.h"
+#include "imgIOnloadBlocker.h"
+#include "imgINotificationObserver.h"
+#include "mozilla/Attributes.h"
+
+class imgIContainer;
+class nsIFrame;
+class nsIDocument;
+class nsPresContext;
+class nsIURI;
+class nsIPrincipal;
+
+namespace mozilla {
+namespace css {
+
+struct ImageValue;
+
+class ImageLoader final : public imgINotificationObserver,
+ public imgIOnloadBlocker
+{
+public:
+ typedef mozilla::css::ImageValue Image;
+
+ explicit ImageLoader(nsIDocument* aDocument)
+ : mDocument(aDocument),
+ mInClone(false)
+ {
+ MOZ_ASSERT(mDocument);
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGIONLOADBLOCKER
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ void DropDocumentReference();
+
+ void MaybeRegisterCSSImage(Image* aImage);
+ void DeregisterCSSImage(Image* aImage);
+
+ void AssociateRequestToFrame(imgIRequest* aRequest,
+ nsIFrame* aFrame);
+
+ void DisassociateRequestFromFrame(imgIRequest* aRequest,
+ nsIFrame* aFrame);
+
+ void DropRequestsForFrame(nsIFrame* aFrame);
+
+ 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);
+
+ void LoadImage(nsIURI* aURI, nsIPrincipal* aPrincipal, nsIURI* aReferrer,
+ Image* aCSSValue);
+
+ void DestroyRequest(imgIRequest* aRequest);
+
+ void FlushUseCounters();
+
+private:
+ ~ImageLoader() {}
+
+ // 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.
+
+ typedef nsTArray<nsIFrame*> FrameSet;
+ typedef nsTArray<nsCOMPtr<imgIRequest> > RequestSet;
+ typedef nsTHashtable<nsPtrHashKey<Image> > ImageHashSet;
+ typedef nsClassHashtable<nsISupportsHashKey,
+ FrameSet> RequestToFrameMap;
+ typedef nsClassHashtable<nsPtrHashKey<nsIFrame>,
+ RequestSet> FrameToRequestMap;
+
+ void AddImage(Image* aCSSImage);
+ void RemoveImage(Image* aCSSImage);
+
+ nsPresContext* GetPresContext();
+
+ void DoRedraw(FrameSet* aFrameSet, bool aForcePaint);
+
+ nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
+ nsresult OnFrameComplete(imgIRequest* aRequest);
+ nsresult OnImageIsAnimated(imgIRequest* aRequest);
+ nsresult OnFrameUpdate(imgIRequest* aRequest);
+
+ // 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.
+ nsIDocument* mDocument;
+
+ // The set of all nsCSSValue::Images (whether they're associated a frame or
+ // not). We'll need this when we go away to remove any requests associated
+ // with our document from those Images.
+ ImageHashSet mImages;
+
+ // Are we cloning? If so, ignore any notifications we get.
+ bool mInClone;
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_ImageLoader_h___ */
diff --git a/layout/style/ImportRule.h b/layout/style/ImportRule.h
new file mode 100644
index 000000000..f0803ba9d
--- /dev/null
+++ b/layout/style/ImportRule.h
@@ -0,0 +1,72 @@
+/* -*- 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/. */
+
+/* class for CSS @import rules */
+
+#ifndef mozilla_css_ImportRule_h__
+#define mozilla_css_ImportRule_h__
+
+#include "mozilla/Attributes.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/Rule.h"
+#include "nsIDOMCSSImportRule.h"
+
+class nsMediaList;
+class nsString;
+
+namespace mozilla {
+
+class CSSStyleSheet;
+
+namespace css {
+
+class ImportRule final : public Rule,
+ public nsIDOMCSSImportRule
+{
+public:
+ ImportRule(nsMediaList* aMedia, const nsString& aURLSpec,
+ uint32_t aLineNumber, uint32_t aColumnNumber);
+private:
+ // for |Clone|
+ ImportRule(const ImportRule& aCopy);
+ ~ImportRule();
+public:
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ImportRule, mozilla::css::Rule)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ DECL_STYLE_RULE_INHERIT
+
+#ifdef HAVE_CPP_AMBIGUITY_RESOLVING_USING
+ using Rule::GetStyleSheet; // unhide since nsIDOMCSSImportRule has its own GetStyleSheet
+#endif
+
+ // Rule methods
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<Rule> Clone() const override;
+
+ void SetSheet(CSSStyleSheet*);
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSImportRule interface
+ NS_DECL_NSIDOMCSSIMPORTRULE
+
+private:
+ nsString mURLSpec;
+ RefPtr<nsMediaList> mMedia;
+ RefPtr<CSSStyleSheet> mChildSheet;
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_ImportRule_h__ */
diff --git a/layout/style/IncrementalClearCOMRuleArray.cpp b/layout/style/IncrementalClearCOMRuleArray.cpp
new file mode 100644
index 000000000..92cdced22
--- /dev/null
+++ b/layout/style/IncrementalClearCOMRuleArray.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/IncrementalClearCOMRuleArray.h"
+
+#include <algorithm> // For std::min
+#include "nsCycleCollector.h"
+#include "mozilla/DeferredFinalize.h"
+#include "nsTArray.h"
+#include "nsCCUncollectableMarker.h"
+
+using namespace mozilla;
+
+typedef nsCOMArray<css::Rule> RuleArray;
+typedef nsTArray<RuleArray> RuleArrayArray;
+
+
+// These methods are based on those in DeferredFinalizerImpl.
+
+static void*
+AppendRulesArrayPointer(void* aData, void* aObject)
+{
+ RuleArrayArray* rulesArray = static_cast<RuleArrayArray*>(aData);
+ RuleArray* oldRules = static_cast<RuleArray*>(aObject);
+
+ if (!rulesArray) {
+ rulesArray = new RuleArrayArray();
+ }
+
+ RuleArray* newRules = rulesArray->AppendElement();
+ newRules->SwapElements(*oldRules);
+
+ return rulesArray;
+}
+
+// Remove up to |aSliceBudget| css::Rules from the arrays, starting at
+// the end of the last array.
+static bool
+DeferredFinalizeRulesArray(uint32_t aSliceBudget, void* aData)
+{
+ MOZ_ASSERT(aSliceBudget > 0, "nonsensical/useless call with aSliceBudget == 0");
+ RuleArrayArray* rulesArray = static_cast<RuleArrayArray*>(aData);
+
+ size_t newOuterLen = rulesArray->Length();
+
+ while (aSliceBudget > 0 && newOuterLen > 0) {
+ RuleArray& lastArray = rulesArray->ElementAt(newOuterLen - 1);
+ uint32_t innerLen = lastArray.Length();
+ uint32_t currSlice = std::min(innerLen, aSliceBudget);
+ uint32_t newInnerLen = innerLen - currSlice;
+ lastArray.TruncateLength(newInnerLen);
+ aSliceBudget -= currSlice;
+ if (newInnerLen == 0) {
+ newOuterLen -= 1;
+ }
+ }
+
+ rulesArray->TruncateLength(newOuterLen);
+
+ if (newOuterLen == 0) {
+ delete rulesArray;
+ return true;
+ }
+ return false;
+}
+
+void
+IncrementalClearCOMRuleArray::Clear()
+{
+ // Destroy the array incrementally if it is long and we
+ // haven't started shutting down.
+ if (Length() > 10 && nsCCUncollectableMarker::sGeneration) {
+ DeferredFinalize(AppendRulesArrayPointer, DeferredFinalizeRulesArray, this);
+ } else {
+ nsCOMArray<css::Rule>::Clear();
+ }
+ MOZ_ASSERT(Length() == 0);
+}
diff --git a/layout/style/IncrementalClearCOMRuleArray.h b/layout/style/IncrementalClearCOMRuleArray.h
new file mode 100644
index 000000000..9934fd683
--- /dev/null
+++ b/layout/style/IncrementalClearCOMRuleArray.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_IncrementalClearCOMRuleArray_h
+#define mozilla_IncrementalClearCOMRuleArray_h
+
+#include "nsCOMArray.h"
+
+namespace mozilla {
+namespace css {
+class Rule;
+} // namespace css
+
+class IncrementalClearCOMRuleArray : public nsCOMArray<css::Rule>
+{
+public:
+ IncrementalClearCOMRuleArray() {}
+ ~IncrementalClearCOMRuleArray() { Clear(); }
+
+ void Clear();
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_IncrementalClearCOMRuleArray_h) */
diff --git a/layout/style/LayerAnimationInfo.cpp b/layout/style/LayerAnimationInfo.cpp
new file mode 100644
index 000000000..8a119512b
--- /dev/null
+++ b/layout/style/LayerAnimationInfo.cpp
@@ -0,0 +1,53 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LayerAnimationInfo.h"
+
+#include "nsCSSProps.h" // For nsCSSProps::PropHasFlags
+
+namespace mozilla {
+
+/* static */ const LayerAnimationInfo::Record LayerAnimationInfo::sRecords[] =
+ { { eCSSProperty_transform,
+ nsDisplayItem::TYPE_TRANSFORM,
+ nsChangeHint_UpdateTransformLayer },
+ { eCSSProperty_opacity,
+ nsDisplayItem::TYPE_OPACITY,
+ nsChangeHint_UpdateOpacityLayer } };
+
+#ifdef DEBUG
+/* static */ void
+LayerAnimationInfo::Initialize()
+{
+ for (const Record& record : sRecords) {
+ MOZ_ASSERT(nsCSSProps::PropHasFlags(record.mProperty,
+ CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
+ "CSS property with entry in LayerAnimation::sRecords does not "
+ "have the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR flag");
+ }
+
+ // Check that every property with the flag for animating on the
+ // compositor has an entry in LayerAnimationInfo::sRecords.
+ for (nsCSSPropertyID prop = nsCSSPropertyID(0);
+ prop < eCSSProperty_COUNT;
+ prop = nsCSSPropertyID(prop + 1)) {
+ if (nsCSSProps::PropHasFlags(prop,
+ CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) {
+ bool found = false;
+ for (const Record& record : sRecords) {
+ if (record.mProperty == prop) {
+ found = true;
+ break;
+ }
+ }
+ MOZ_ASSERT(found,
+ "CSS property with the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR "
+ "flag does not have an entry in LayerAnimationInfo::sRecords");
+ }
+ }
+}
+#endif
+
+} // namespace mozilla
diff --git a/layout/style/LayerAnimationInfo.h b/layout/style/LayerAnimationInfo.h
new file mode 100644
index 000000000..53c5ae9bf
--- /dev/null
+++ b/layout/style/LayerAnimationInfo.h
@@ -0,0 +1,33 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LayerAnimationInfo_h
+#define mozilla_LayerAnimationInfo_h
+
+#include "nsChangeHint.h"
+#include "nsCSSPropertyID.h"
+#include "nsDisplayList.h" // For nsDisplayItem::Type
+
+namespace mozilla {
+
+struct LayerAnimationInfo {
+#ifdef DEBUG
+ static void Initialize();
+#endif
+ // For CSS properties that may be animated on a separate layer, represents
+ // a record of the corresponding layer type and change hint.
+ struct Record {
+ nsCSSPropertyID mProperty;
+ nsDisplayItem::Type mLayerType;
+ nsChangeHint mChangeHint;
+ };
+
+ static const size_t kRecords = 2;
+ static const Record sRecords[kRecords];
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_LayerAnimationInfo_h) */
diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp
new file mode 100644
index 000000000..a1a0fcfd9
--- /dev/null
+++ b/layout/style/Loader.cpp
@@ -0,0 +1,2694 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are Copyright (c)
+ * International Business Machines Corporation, 2000. Modifications
+ * to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+/* loading of CSS style sheets using the network APIs */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "mozilla/css/Loader.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "nsIRunnable.h"
+#include "nsIUnicharStreamLoader.h"
+#include "nsSyncLoadService.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMDocument.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsIProtocolHandler.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 "nsIStyleSheetLinkingElement.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsCSSParser.h"
+#include "mozilla/css/ImportRule.h"
+#include "nsThreadUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIThreadInternal.h"
+#include "nsINetworkPredictor.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/URL.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/ConsoleReportCollector.h"
+
+#ifdef MOZ_XUL
+#include "nsXULPrototypeCache.h"
+#endif
+
+#include "nsIMediaList.h"
+#include "nsIDOMStyleSheet.h"
+#include "nsError.h"
+
+#include "nsIContentSecurityPolicy.h"
+#include "mozilla/dom/SRICheck.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+using mozilla::dom::EncodingUtils;
+
+using namespace mozilla::dom;
+
+/**
+ * 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:
+ * InsertSheetInDoc() 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.
+ */
+
+namespace mozilla {
+namespace 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");
+
+class SheetLoadData final : public nsIRunnable,
+ public nsIUnicharStreamLoaderObserver,
+ public nsIThreadObserver
+{
+protected:
+ virtual ~SheetLoadData(void);
+
+public:
+ // Data for loading a sheet linked from a document
+ SheetLoadData(Loader* aLoader,
+ const nsSubstring& aTitle,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ nsIStyleSheetLinkingElement* aOwningElement,
+ bool aIsAlternate,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode);
+
+ // Data for loading a sheet linked from an @import rule
+ SheetLoadData(Loader* aLoader,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ SheetLoadData* aParentData,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode);
+
+ // Data for loading a non-document sheet
+ SheetLoadData(Loader* aLoader,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ bool aSyncLoad,
+ bool aUseSystemPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode);
+
+ already_AddRefed<nsIURI> GetReferrerURI();
+
+ void ScheduleLoadEventIfNeeded(nsresult aStatus);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSITHREADOBSERVER
+ NS_DECL_NSIUNICHARSTREAMLOADEROBSERVER
+
+ // Hold a ref to the CSSLoader so we can call back to it to let it
+ // know the load finished
+ RefPtr<Loader> mLoader;
+
+ // Title needed to pull datas out of the pending datas table when
+ // the preferred title is changed
+ nsString mTitle;
+
+ // Charset we decided to use for the sheet
+ nsCString mCharset;
+
+ // URI we're loading. Null for inline sheets
+ nsCOMPtr<nsIURI> mURI;
+
+ // Should be 1 for non-inline sheets.
+ uint32_t mLineNumber;
+
+ // The sheet we're loading data for
+ RefPtr<StyleSheet> mSheet;
+
+ // Linked list of datas for the same URI as us
+ SheetLoadData* mNext; // strong ref
+
+ // Load data for the sheet that @import-ed us if we were @import-ed
+ // during the parse
+ RefPtr<SheetLoadData> mParentData;
+
+ // Number of sheets we @import-ed that are still loading
+ uint32_t mPendingChildren;
+
+ // mSyncLoad is true when the load needs to be synchronous -- right
+ // now only for LoadSheetSync and children of sync loads.
+ 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.
+ bool mIsNonDocumentSheet : 1;
+
+ // mIsLoading is true from the moment we are placed in the loader's
+ // "loading datas" table (right after the async channel is opened)
+ // to the moment we are removed from said table (due to the load
+ // completing or being cancelled).
+ 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
+ // mOwningElement.
+ bool mMustNotify : 1;
+
+ // mWasAlternate is true if the sheet was an alternate when the load data was
+ // created.
+ bool mWasAlternate : 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.
+ 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;
+
+ // This is the element that imported the sheet. Needed to get the
+ // charset set on it and to fire load/error events.
+ nsCOMPtr<nsIStyleSheetLinkingElement> mOwningElement;
+
+ // The observer that wishes to be notified of load completion
+ nsCOMPtr<nsICSSLoaderObserver> mObserver;
+
+ // The principal that identifies who started loading us.
+ nsCOMPtr<nsIPrincipal> mLoaderPrincipal;
+
+ // The node that identifies who started loading us.
+ nsCOMPtr<nsINode> mRequestingNode;
+
+ // The charset to use if the transport and sheet don't indicate one.
+ // May be empty. Must be empty if mOwningElement is non-null.
+ nsCString mCharsetHint;
+
+ // The status our load ended up with; this determines whether we
+ // should fire error events or load events. This gets initialized
+ // by ScheduleLoadEventIfNeeded, and is only used after that has
+ // been called.
+ MOZ_INIT_OUTSIDE_CTOR nsresult mStatus;
+
+private:
+ void FireLoadEvent(nsIThreadInternal* aThread);
+};
+
+#include "mozilla/Logging.h"
+
+static mozilla::LazyLogModule sCssLoaderLog("nsCSSLoader");
+
+static mozilla::LazyLogModule gSriPRLog("SRI");
+
+#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[] = {
+ "eSheetStateUnknown",
+ "eSheetNeedsParser",
+ "eSheetPending",
+ "eSheetLoading",
+ "eSheetComplete"
+};
+
+/********************************
+ * SheetLoadData implementation *
+ ********************************/
+NS_IMPL_ISUPPORTS(SheetLoadData, nsIUnicharStreamLoaderObserver, nsIRunnable,
+ nsIThreadObserver)
+
+SheetLoadData::SheetLoadData(Loader* aLoader,
+ const nsSubstring& aTitle,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ nsIStyleSheetLinkingElement* aOwningElement,
+ bool aIsAlternate,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode)
+ : mLoader(aLoader),
+ mTitle(aTitle),
+ mURI(aURI),
+ mLineNumber(1),
+ mSheet(aSheet),
+ mNext(nullptr),
+ mPendingChildren(0),
+ mSyncLoad(false),
+ mIsNonDocumentSheet(false),
+ mIsLoading(false),
+ mIsCancelled(false),
+ mMustNotify(false),
+ mWasAlternate(aIsAlternate),
+ mUseSystemPrincipal(false),
+ mSheetAlreadyComplete(false),
+ mOwningElement(aOwningElement),
+ mObserver(aObserver),
+ mLoaderPrincipal(aLoaderPrincipal),
+ mRequestingNode(aRequestingNode)
+{
+ NS_PRECONDITION(mLoader, "Must have a loader!");
+}
+
+SheetLoadData::SheetLoadData(Loader* aLoader,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ SheetLoadData* aParentData,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode)
+ : mLoader(aLoader),
+ mURI(aURI),
+ mLineNumber(1),
+ mSheet(aSheet),
+ mNext(nullptr),
+ mParentData(aParentData),
+ mPendingChildren(0),
+ mSyncLoad(false),
+ mIsNonDocumentSheet(false),
+ mIsLoading(false),
+ mIsCancelled(false),
+ mMustNotify(false),
+ mWasAlternate(false),
+ mUseSystemPrincipal(false),
+ mSheetAlreadyComplete(false),
+ mOwningElement(nullptr),
+ mObserver(aObserver),
+ mLoaderPrincipal(aLoaderPrincipal),
+ mRequestingNode(aRequestingNode)
+{
+ NS_PRECONDITION(mLoader, "Must have a loader!");
+ if (mParentData) {
+ mSyncLoad = mParentData->mSyncLoad;
+ mIsNonDocumentSheet = mParentData->mIsNonDocumentSheet;
+ mUseSystemPrincipal = mParentData->mUseSystemPrincipal;
+ ++(mParentData->mPendingChildren);
+ }
+
+ NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
+ "Shouldn't use system principal for async loads");
+}
+
+SheetLoadData::SheetLoadData(Loader* aLoader,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ bool aSyncLoad,
+ bool aUseSystemPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode)
+ : mLoader(aLoader),
+ mURI(aURI),
+ mLineNumber(1),
+ mSheet(aSheet),
+ mNext(nullptr),
+ mPendingChildren(0),
+ mSyncLoad(aSyncLoad),
+ mIsNonDocumentSheet(true),
+ mIsLoading(false),
+ mIsCancelled(false),
+ mMustNotify(false),
+ mWasAlternate(false),
+ mUseSystemPrincipal(aUseSystemPrincipal),
+ mSheetAlreadyComplete(false),
+ mOwningElement(nullptr),
+ mObserver(aObserver),
+ mLoaderPrincipal(aLoaderPrincipal),
+ mRequestingNode(aRequestingNode),
+ mCharsetHint(aCharset)
+{
+ NS_PRECONDITION(mLoader, "Must have a loader!");
+ NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
+ "Shouldn't use system principal for async loads");
+}
+
+SheetLoadData::~SheetLoadData()
+{
+ NS_CSS_NS_RELEASE_LIST_MEMBER(SheetLoadData, this, mNext);
+}
+
+NS_IMETHODIMP
+SheetLoadData::Run()
+{
+ mLoader->HandleLoadEvent(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SheetLoadData::OnDispatchedEvent(nsIThreadInternal* aThread)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
+ bool aMayWait)
+{
+ // XXXkhuey this is insane!
+ // We want to fire our load even before or after event processing,
+ // whichever comes first.
+ FireLoadEvent(aThread);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
+ bool aEventWasProcessed)
+{
+ // XXXkhuey this too!
+ // We want to fire our load even before or after event processing,
+ // whichever comes first.
+ FireLoadEvent(aThread);
+ return NS_OK;
+}
+
+void
+SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread)
+{
+
+ // First remove ourselves as a thread observer. But we need to keep
+ // ourselves alive while doing that!
+ RefPtr<SheetLoadData> kungFuDeathGrip(this);
+ aThread->RemoveObserver(this);
+
+ // Now fire the event
+ nsCOMPtr<nsINode> node = do_QueryInterface(mOwningElement);
+ NS_ASSERTION(node, "How did that happen???");
+
+ nsContentUtils::DispatchTrustedEvent(node->OwnerDoc(),
+ node,
+ NS_SUCCEEDED(mStatus) ?
+ NS_LITERAL_STRING("load") :
+ NS_LITERAL_STRING("error"),
+ false, false);
+
+ // And unblock onload
+ if (mLoader->mDocument) {
+ mLoader->mDocument->UnblockOnload(true);
+ }
+}
+
+void
+SheetLoadData::ScheduleLoadEventIfNeeded(nsresult aStatus)
+{
+ if (!mOwningElement) {
+ return;
+ }
+
+ mStatus = aStatus;
+
+ nsCOMPtr<nsIThread> thread = do_GetMainThread();
+ nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
+ if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
+ // Make sure to block onload here
+ if (mLoader->mDocument) {
+ mLoader->mDocument->BlockOnload();
+ }
+ }
+}
+
+/*********************
+ * Style sheet reuse *
+ *********************/
+
+bool
+LoaderReusableStyleSheets::FindReusableStyleSheet(nsIURI* aURL,
+ RefPtr<CSSStyleSheet>& 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(StyleBackendType aType)
+ : mDocument(nullptr)
+ , mDatasToNotifyOn(0)
+ , mCompatMode(eCompatibility_FullStandards)
+ , mStyleBackendType(Some(aType))
+ , mEnabled(true)
+ , mReporter(new ConsoleReportCollector())
+#ifdef DEBUG
+ , mSyncCallback(false)
+#endif
+{
+}
+
+Loader::Loader(nsIDocument* aDocument)
+ : mDocument(aDocument)
+ , mDatasToNotifyOn(0)
+ , mCompatMode(eCompatibility_FullStandards)
+ , mEnabled(true)
+ , mReporter(new ConsoleReportCollector())
+#ifdef DEBUG
+ , mSyncCallback(false)
+#endif
+{
+ // We can just use the preferred set, since there are no sheets in the
+ // document yet (if there are, how did they get there? _we_ load the sheets!)
+ // and hence the selected set makes no sense at this time.
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
+ if (domDoc) {
+ domDoc->GetPreferredStyleSheetSet(mPreferredSheet);
+ }
+}
+
+Loader::~Loader()
+{
+ NS_ASSERTION(!mSheets || mSheets->mLoadingDatas.Count() == 0,
+ "How did we get destroyed when there are loading data?");
+ NS_ASSERTION(!mSheets || mSheets->mPendingDatas.Count() == 0,
+ "How did we get destroyed when there are pending data?");
+ // 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.
+}
+
+void
+Loader::DropDocumentReference(void)
+{
+ mDocument = nullptr;
+ // Flush out pending datas just so we don't leak by accident. These
+ // loads should short-circuit through the mDocument check in
+ // LoadSheet and just end up in SheetComplete immediately
+ if (mSheets) {
+ StartAlternateLoads();
+ }
+}
+
+nsresult
+Loader::SetPreferredSheet(const nsAString& aTitle)
+{
+#ifdef DEBUG
+ nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(mDocument);
+ if (doc) {
+ nsAutoString currentPreferred;
+ doc->GetLastStyleSheetSet(currentPreferred);
+ if (DOMStringIsNull(currentPreferred)) {
+ doc->GetPreferredStyleSheetSet(currentPreferred);
+ }
+ NS_ASSERTION(currentPreferred.Equals(aTitle),
+ "Unexpected argument to SetPreferredSheet");
+ }
+#endif
+
+ mPreferredSheet = aTitle;
+
+ // start any pending alternates that aren't alternates anymore
+ if (mSheets) {
+ LoadDataArray arr(mSheets->mPendingDatas.Count());
+ for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
+ SheetLoadData* data = iter.Data();
+ MOZ_ASSERT(data, "Must have a data");
+
+ // Note that we don't want to affect what the selected style set is, so
+ // use true for aHasAlternateRel.
+ if (!data->mLoader->IsAlternate(data->mTitle, true)) {
+ arr.AppendElement(data);
+ iter.Remove();
+ }
+ }
+
+ mDatasToNotifyOn += arr.Length();
+ for (uint32_t i = 0; i < arr.Length(); ++i) {
+ --mDatasToNotifyOn;
+ LoadSheet(arr[i], eSheetNeedsParser, false);
+ }
+ }
+
+ return NS_OK;
+}
+
+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;
+}
+
+NS_IMETHODIMP
+SheetLoadData::OnDetermineCharset(nsIUnicharStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsACString const& aSegment,
+ nsACString& aCharset)
+{
+ NS_PRECONDITION(!mOwningElement || mCharsetHint.IsEmpty(),
+ "Can't have element _and_ charset hint");
+
+ LOG_URI("SheetLoadData::OnDetermineCharset for '%s'", mURI);
+
+ // The precedence is (per CSS3 Syntax 2012-11-08 ED):
+ // BOM
+ // Channel
+ // @charset rule
+ // charset attribute on the referrer
+ // encoding of the referrer
+ // UTF-8
+
+ aCharset.Truncate();
+
+ if (nsContentUtils::CheckForBOM((const unsigned char*)aSegment.BeginReading(),
+ aSegment.Length(),
+ aCharset)) {
+ // aCharset is now either "UTF-16BE", "UTF-16BE" or "UTF-8"
+ // which will swallow the BOM.
+ mCharset.Assign(aCharset);
+ LOG((" Setting from BOM to: %s", PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ nsAutoCString specified;
+ aLoader->GetChannel(getter_AddRefs(channel));
+ if (channel) {
+ channel->GetContentCharset(specified);
+ if (EncodingUtils::FindEncodingForLabel(specified, aCharset)) {
+ mCharset.Assign(aCharset);
+ LOG((" Setting from HTTP to: %s", PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+ }
+
+ if (GetCharsetFromData(aSegment.BeginReading(),
+ aSegment.Length(),
+ specified)) {
+ if (EncodingUtils::FindEncodingForLabel(specified, aCharset)) {
+ // FindEncodingForLabel currently never returns UTF-16LE but will
+ // probably change to never return UTF-16 instead, so check both here
+ // to avoid relying on the exact behavior.
+ if (aCharset.EqualsLiteral("UTF-16") ||
+ aCharset.EqualsLiteral("UTF-16BE") ||
+ aCharset.EqualsLiteral("UTF-16LE")) {
+ // Be consistent with HTML <meta> handling in face of impossibility.
+ // When the @charset rule itself evidently was not UTF-16-encoded,
+ // it saying UTF-16 has to be a lie.
+ aCharset.AssignLiteral("UTF-8");
+ }
+ mCharset.Assign(aCharset);
+ LOG((" Setting from @charset rule to: %s",
+ PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+ }
+
+ // Now try the charset on the <link> or processing instruction
+ // that loaded us
+ if (mOwningElement) {
+ nsAutoString specified16;
+ mOwningElement->GetCharset(specified16);
+ if (EncodingUtils::FindEncodingForLabel(specified16, aCharset)) {
+ mCharset.Assign(aCharset);
+ LOG((" Setting from charset attribute to: %s",
+ PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+ }
+
+ // In the preload case, the value of the charset attribute on <link> comes
+ // in via mCharsetHint instead.
+ if (EncodingUtils::FindEncodingForLabel(mCharsetHint, aCharset)) {
+ mCharset.Assign(aCharset);
+ LOG((" Setting from charset attribute (preload case) to: %s",
+ PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+
+ // Try charset from the parent stylesheet.
+ if (mParentData) {
+ aCharset = mParentData->mCharset;
+ if (!aCharset.IsEmpty()) {
+ mCharset.Assign(aCharset);
+ LOG((" Setting from parent sheet to: %s",
+ PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+ }
+
+ if (mLoader->mDocument) {
+ // no useful data on charset. Try the document charset.
+ aCharset = mLoader->mDocument->GetDocumentCharacterSet();
+ MOZ_ASSERT(!aCharset.IsEmpty());
+ mCharset.Assign(aCharset);
+ LOG((" Setting from document to: %s", PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+
+ aCharset.AssignLiteral("UTF-8");
+ mCharset = aCharset;
+ LOG((" Setting from default to: %s", PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+}
+
+already_AddRefed<nsIURI>
+SheetLoadData::GetReferrerURI()
+{
+ nsCOMPtr<nsIURI> uri;
+ if (mParentData)
+ uri = mParentData->mSheet->GetSheetURI();
+ if (!uri && mLoader->mDocument)
+ uri = mLoader->mDocument->GetDocumentURI();
+ return uri.forget();
+}
+
+/*
+ * 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.
+ */
+NS_IMETHODIMP
+SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ const nsAString& aBuffer)
+{
+ LOG(("SheetLoadData::OnStreamComplete"));
+ NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");
+
+ if (mIsCancelled) {
+ // Just return. Don't call SheetComplete -- it's already been
+ // called and calling it again will lead to an extra NS_RELEASE on
+ // this data and a likely crash.
+ return NS_OK;
+ }
+
+ if (!mLoader->mDocument && !mIsNonDocumentSheet) {
+ // Sorry, we don't care about this load anymore
+ LOG_WARN((" No document and not non-document sheet; dropping load"));
+ mLoader->SheetComplete(this, NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aStatus)) {
+ LOG_WARN((" Load failed: status 0x%x", aStatus));
+ // Handle sheet not loading error because source was a tracking URL.
+ // 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 (aStatus == NS_ERROR_TRACKING_URI) {
+ nsIDocument* doc = mLoader->GetDocument();
+ if (doc) {
+ for (SheetLoadData* data = this; data; data = data->mNext) {
+ // mOwningElement may be null but AddBlockTrackingNode can cope
+ nsCOMPtr<nsIContent> content = do_QueryInterface(data->mOwningElement);
+ doc->AddBlockedTrackingNode(content);
+ }
+ }
+ }
+ mLoader->SheetComplete(this, aStatus);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult result = aLoader->GetChannel(getter_AddRefs(channel));
+ if (NS_FAILED(result)) {
+ LOG_WARN((" No channel from loader"));
+ mLoader->SheetComplete(this, result);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> originalURI;
+ channel->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(channel, 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();
+ 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(channel, getter_AddRefs(principal));
+ }
+ }
+
+ if (NS_FAILED(result)) {
+ LOG_WARN((" Couldn't get principal"));
+ mLoader->SheetComplete(this, result);
+ return NS_OK;
+ }
+
+ mSheet->SetPrincipal(principal);
+
+ // If it's an HTTP channel, we want to make sure this is not an
+ // error document we got.
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ 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;
+ }
+ }
+
+ nsAutoCString contentType;
+ if (channel) {
+ channel->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;
+
+ if (mLoaderPrincipal) {
+ bool subsumed;
+ result = mLoaderPrincipal->Subsumes(principal, &subsumed);
+ if (NS_FAILED(result) || !subsumed) {
+ sameOrigin = false;
+ }
+ }
+
+ if (sameOrigin && mLoader->mCompatMode == eCompatibility_NavQuirks) {
+ errorMessage = "MimeNotCssWarn";
+ errorFlag = nsIScriptError::warningFlag;
+ } else {
+ errorMessage = "MimeNotCss";
+ errorFlag = nsIScriptError::errorFlag;
+ }
+
+ const nsAFlatString& specUTF16 =
+ NS_ConvertUTF8toUTF16(channelURI->GetSpecOrDefault());
+ const nsAFlatString& ctypeUTF16 = NS_ConvertASCIItoUTF16(contentType);
+ const char16_t *strings[] = { specUTF16.get(), ctypeUTF16.get() };
+
+ nsCOMPtr<nsIURI> referrer = GetReferrerURI();
+ nsContentUtils::ReportToConsole(errorFlag,
+ NS_LITERAL_CSTRING("CSS Loader"),
+ mLoader->mDocument,
+ nsContentUtils::eCSS_PROPERTIES,
+ errorMessage,
+ strings, ArrayLength(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()) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ if (loadInfo->GetEnforceSRI()) {
+ LOG((" Load was blocked by SRI"));
+ MOZ_LOG(gSriPRLog, mozilla::LogLevel::Debug,
+ ("css::Loader::OnStreamComplete, required SRI not found"));
+ mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
+ // log the failed load to web console
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
+ nsAutoCString spec;
+ mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(spec);
+ // line number unknown. mRequestingNode doesn't bear this info.
+ csp->LogViolationDetails(
+ nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_STYLE,
+ NS_ConvertUTF8toUTF16(spec), EmptyString(),
+ 0, EmptyString(), EmptyString());
+ return NS_OK;
+ }
+ } else {
+ nsAutoCString sourceUri;
+ if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
+ mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ nsresult rv = SRICheck::VerifyIntegrity(sriMetadata, aLoader, aBuffer,
+ sourceUri, mLoader->mReporter);
+ mLoader->mReporter->FlushConsoleReports(mLoader->mDocument);
+ if (NS_FAILED(rv)) {
+ LOG((" Load was blocked by SRI"));
+ MOZ_LOG(gSriPRLog, mozilla::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);
+
+ bool completed;
+ result = mLoader->ParseSheet(aBuffer, this, completed);
+ NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete");
+ return result;
+}
+
+bool
+Loader::IsAlternate(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 false;
+ }
+
+ if (!aHasAlternateRel && mDocument && mPreferredSheet.IsEmpty()) {
+ // There's no preferred set yet, and we now have a sheet with a title.
+ // Make that be the preferred set.
+ mDocument->SetHeaderData(nsGkAtoms::headerDefaultStyle, aTitle);
+ // We're definitely not an alternate
+ return false;
+ }
+
+ return !aTitle.Equals(mPreferredSheet);
+}
+
+nsresult
+Loader::ObsoleteSheet(nsIURI* aURI)
+{
+ if (!mSheets) {
+ return NS_OK;
+ }
+ if (!aURI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ for (auto iter = mSheets->mCompleteSheets.Iter(); !iter.Done(); iter.Next()) {
+ nsIURI* sheetURI = iter.Key()->GetURI();
+ bool areEqual;
+ nsresult rv = sheetURI->Equals(aURI, &areEqual);
+ if (NS_SUCCEEDED(rv) && areEqual) {
+ iter.Remove();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+Loader::CheckContentPolicy(nsIPrincipal* aSourcePrincipal,
+ nsIURI* aTargetURI,
+ nsISupports* aContext,
+ bool aIsPreload)
+{
+ // When performing a system load (e.g. aUseSystemPrincipal = true)
+ // then aSourcePrincipal == null; don't consult content policies.
+ if (!aSourcePrincipal) {
+ return NS_OK;
+ }
+
+ nsContentPolicyType contentPolicyType =
+ aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
+ : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentLoadPolicy(contentPolicyType,
+ aTargetURI,
+ aSourcePrincipal,
+ aContext,
+ NS_LITERAL_CSTRING("text/css"),
+ nullptr, //extra param
+ &shouldLoad,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+ return NS_OK;
+}
+
+/**
+ * CreateSheet() creates a CSSStyleSheet object for the given URI,
+ * if any. If there is no URI given, we just create a new style sheet
+ * object. Otherwise, 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().
+ */
+nsresult
+Loader::CreateSheet(nsIURI* aURI,
+ nsIContent* aLinkingContent,
+ nsIPrincipal* aLoaderPrincipal,
+ css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity,
+ bool aSyncLoad,
+ bool aHasAlternateRel,
+ const nsAString& aTitle,
+ StyleSheetState& aSheetState,
+ bool *aIsAlternate,
+ RefPtr<StyleSheet>* aSheet)
+{
+ LOG(("css::Loader::CreateSheet"));
+ NS_PRECONDITION(aSheet, "Null out param!");
+
+ if (!mSheets) {
+ mSheets = new Sheets();
+ }
+
+ *aSheet = nullptr;
+ aSheetState = eSheetStateUnknown;
+
+ // Check the alternate state before doing anything else, because it
+ // can mess with our hashtables.
+ *aIsAlternate = IsAlternate(aTitle, aHasAlternateRel);
+
+ // XXXheycam Cached sheets currently must be CSSStyleSheets.
+ if (aURI && GetStyleBackendType() == StyleBackendType::Gecko) {
+ aSheetState = eSheetComplete;
+ RefPtr<StyleSheet> sheet;
+
+ // First, the XUL cache
+#ifdef MOZ_XUL
+ if (IsChromeURI(aURI)) {
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (cache) {
+ if (cache->IsEnabled()) {
+ sheet = cache->GetStyleSheet(aURI);
+ LOG((" From XUL cache: %p", sheet.get()));
+ }
+ }
+ }
+#endif
+
+ bool fromCompleteSheets = false;
+ if (!sheet) {
+ // Then our per-document complete sheets.
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode, aReferrerPolicy);
+
+ StyleSheet* completeSheet = nullptr;
+ mSheets->mCompleteSheets.Get(&key, &completeSheet);
+ sheet = completeSheet;
+ LOG((" From completed: %p", sheet.get()));
+
+ fromCompleteSheets = !!sheet;
+ }
+
+ if (sheet) {
+ if (sheet->IsServo()) {
+ MOZ_CRASH("stylo: can't clone ServoStyleSheets yet");
+ }
+
+ // This sheet came from the XUL cache or our per-document hashtable; it
+ // better be a complete sheet.
+ NS_ASSERTION(sheet->AsGecko()->IsComplete(),
+ "Sheet thinks it's not complete while we think it is");
+
+ // Make sure it hasn't been modified; if it has, we can't use it
+ if (sheet->AsGecko()->IsModified()) {
+ LOG((" Not cloning completed sheet %p because it's been modified",
+ sheet.get()));
+ sheet = nullptr;
+ fromCompleteSheets = false;
+ }
+ }
+
+ // Then loading sheets
+ if (!sheet && !aSyncLoad) {
+ aSheetState = eSheetLoading;
+ SheetLoadData* loadData = nullptr;
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode, aReferrerPolicy);
+ mSheets->mLoadingDatas.Get(&key, &loadData);
+ if (loadData) {
+ sheet = loadData->mSheet;
+ LOG((" From loading: %p", sheet.get()));
+
+#ifdef DEBUG
+ bool debugEqual;
+ NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
+ (aLoaderPrincipal && loadData->mLoaderPrincipal &&
+ NS_SUCCEEDED(aLoaderPrincipal->
+ Equals(loadData->mLoaderPrincipal,
+ &debugEqual)) && debugEqual),
+ "Principals should be the same");
+#endif
+ }
+
+ // Then alternate sheets
+ if (!sheet) {
+ aSheetState = eSheetPending;
+ loadData = nullptr;
+ mSheets->mPendingDatas.Get(&key, &loadData);
+ if (loadData) {
+ sheet = loadData->mSheet;
+ LOG((" From pending: %p", sheet.get()));
+
+#ifdef DEBUG
+ bool debugEqual;
+ NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
+ (aLoaderPrincipal && loadData->mLoaderPrincipal &&
+ NS_SUCCEEDED(aLoaderPrincipal->
+ Equals(loadData->mLoaderPrincipal,
+ &debugEqual)) && debugEqual),
+ "Principals should be the same");
+#endif
+ }
+ }
+ }
+
+ if (sheet) {
+ // The sheet we have now should be either incomplete or unmodified
+ if (sheet->IsServo()) {
+ MOZ_CRASH("stylo: can't clone ServoStyleSheets yet");
+ }
+ NS_ASSERTION(!sheet->AsGecko()->IsModified() ||
+ !sheet->AsGecko()->IsComplete(),
+ "Unexpected modified complete sheet");
+ NS_ASSERTION(sheet->AsGecko()->IsComplete() ||
+ aSheetState != eSheetComplete,
+ "Sheet thinks it's not complete while we think it is");
+
+ RefPtr<CSSStyleSheet> clonedSheet =
+ sheet->AsGecko()->Clone(nullptr, nullptr, nullptr, nullptr);
+ *aSheet = Move(clonedSheet);
+ if (*aSheet && fromCompleteSheets &&
+ !sheet->AsGecko()->GetOwnerNode() &&
+ !sheet->AsGecko()->GetParentSheet()) {
+ // The sheet we're cloning isn't actually referenced by
+ // anyone. Replace it in the cache, so that if our CSSOM is
+ // later modified we don't end up with two copies of our inner
+ // hanging around.
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode, aReferrerPolicy);
+ NS_ASSERTION((*aSheet)->AsGecko()->IsComplete(),
+ "Should only be caching complete sheets");
+ mSheets->mCompleteSheets.Put(&key, *aSheet);
+ }
+ }
+ }
+
+ if (!*aSheet) {
+ aSheetState = eSheetNeedsParser;
+ nsIURI *sheetURI;
+ nsCOMPtr<nsIURI> baseURI;
+ nsIURI* originalURI;
+ if (!aURI) {
+ // Inline style. Use the document's base URL so that @import in
+ // the inline sheet picks up the right base.
+ NS_ASSERTION(aLinkingContent, "Inline stylesheet without linking content?");
+ baseURI = aLinkingContent->GetBaseURI();
+ sheetURI = aLinkingContent->OwnerDoc()->GetDocumentURI();
+ originalURI = nullptr;
+ } else {
+ baseURI = aURI;
+ sheetURI = aURI;
+ originalURI = aURI;
+ }
+
+ SRIMetadata sriMetadata;
+ if (!aIntegrity.IsEmpty()) {
+ MOZ_LOG(gSriPRLog, mozilla::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 (GetStyleBackendType() == StyleBackendType::Gecko) {
+ *aSheet = new CSSStyleSheet(aParsingMode, aCORSMode, aReferrerPolicy, sriMetadata);
+ } else {
+ *aSheet = new ServoStyleSheet(aParsingMode, aCORSMode, aReferrerPolicy, sriMetadata);
+ }
+ (*aSheet)->SetURIs(sheetURI, originalURI, baseURI);
+ }
+
+ NS_ASSERTION(*aSheet, "We should have a sheet by now!");
+ NS_ASSERTION(aSheetState != eSheetStateUnknown, "Have to set a state!");
+ LOG((" State: %s", gStateStrings[aSheetState]));
+
+ return NS_OK;
+}
+
+/**
+ * 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.
+ */
+void
+Loader::PrepareSheet(StyleSheet* aSheet,
+ const nsSubstring& aTitle,
+ const nsSubstring& aMediaString,
+ nsMediaList* aMediaList,
+ Element* aScopeElement,
+ bool isAlternate)
+{
+ NS_PRECONDITION(aSheet, "Must have a sheet!");
+
+ // XXXheycam Need to set media, title, etc. on ServoStyleSheets.
+ if (aSheet->IsServo()) {
+ NS_WARNING("stylo: should set metadata on ServoStyleSheets. See bug 1290209.");
+ return;
+ }
+
+ CSSStyleSheet* sheet = aSheet->AsGecko();
+
+ RefPtr<nsMediaList> mediaList(aMediaList);
+
+ if (!aMediaString.IsEmpty()) {
+ NS_ASSERTION(!aMediaList,
+ "must not provide both aMediaString and aMediaList");
+ mediaList = new nsMediaList();
+
+ nsCSSParser mediumParser(this);
+
+ // We have aMediaString only when linked from link elements, style
+ // elements, or PIs, so pass true.
+ mediumParser.ParseMediaList(aMediaString, nullptr, 0, mediaList, true);
+ }
+
+ sheet->SetMedia(mediaList);
+
+ sheet->SetTitle(aTitle);
+ sheet->SetEnabled(!isAlternate);
+ sheet->SetScopeElement(aScopeElement);
+}
+
+/**
+ * InsertSheetInDoc handles ordering of sheets in the document. Here
+ * we have two types of sheets -- those with linking elements and
+ * those without. The latter are loaded by Link: headers.
+ * 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.
+ */
+nsresult
+Loader::InsertSheetInDoc(StyleSheet* aSheet,
+ nsIContent* aLinkingContent,
+ nsIDocument* aDocument)
+{
+ LOG(("css::Loader::InsertSheetInDoc"));
+ NS_PRECONDITION(aSheet, "Nothing to insert");
+ NS_PRECONDITION(aDocument, "Must have a document to insert into");
+
+ // XXX Need to cancel pending sheet loads for this element, if any
+
+ int32_t sheetCount = aDocument->GetNumberOfStyleSheets();
+
+ /*
+ * 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;
+ for (insertionPoint = sheetCount - 1; insertionPoint >= 0; --insertionPoint) {
+ StyleSheet* curSheet = aDocument->GetStyleSheetAt(insertionPoint);
+ NS_ASSERTION(curSheet, "There must be a sheet here!");
+ nsCOMPtr<nsINode> sheetOwner = curSheet->GetOwnerNode();
+ if (sheetOwner && !aLinkingContent) {
+ // 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 linkingNode
+ break;
+ }
+
+ NS_ASSERTION(aLinkingContent != sheetOwner,
+ "Why do we still have our old sheet?");
+
+ // Have to compare
+ if (nsContentUtils::PositionIsBefore(sheetOwner, aLinkingContent)) {
+ // The current sheet comes before us, and it better be the first
+ // such, because now we break
+ break;
+ }
+ }
+
+ ++insertionPoint; // adjust the index to the spot we want to insert in
+
+ // XXX <meta> elements do not implement nsIStyleSheetLinkingElement;
+ // need to fix this for them to be ordered correctly.
+ nsCOMPtr<nsIStyleSheetLinkingElement>
+ linkingElement = do_QueryInterface(aLinkingContent);
+ if (linkingElement) {
+ linkingElement->SetStyleSheet(aSheet); // This sets the ownerNode on the sheet
+ }
+
+ aDocument->BeginUpdate(UPDATE_STYLE);
+ aDocument->InsertStyleSheetAt(aSheet, insertionPoint);
+ aDocument->EndUpdate(UPDATE_STYLE);
+ LOG((" Inserting into document at position %d", insertionPoint));
+
+ return NS_OK;
+}
+
+/**
+ * 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.)
+ */
+nsresult
+Loader::InsertChildSheet(StyleSheet* aSheet,
+ StyleSheet* aParentSheet,
+ ImportRule* aParentRule)
+{
+ LOG(("css::Loader::InsertChildSheet"));
+ NS_PRECONDITION(aSheet, "Nothing to insert");
+ NS_PRECONDITION(aParentSheet, "Need a parent to insert into");
+ NS_PRECONDITION(aParentSheet, "How did we get imported?");
+
+ // XXXheycam The InsertChildSheet API doesn't work with ServoStyleSheets,
+ // since they won't have Gecko ImportRules in them.
+ if (aSheet->IsServo()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // child sheets should always start out enabled, even if they got
+ // cloned off of top-level sheets which were disabled
+ aSheet->AsGecko()->SetEnabled(true);
+
+ aParentSheet->AppendStyleSheet(aSheet);
+ aParentRule->SetSheet(aSheet->AsGecko()); // This sets the ownerRule on the sheet
+
+ LOG((" Inserting into parent sheet"));
+ // LOG((" Inserting into parent sheet at position %d", insertionPoint));
+
+ return NS_OK;
+}
+
+/**
+ * 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,
+ StyleSheetState aSheetState,
+ bool aIsPreload)
+{
+ LOG(("css::Loader::LoadSheet"));
+ NS_PRECONDITION(aLoadData, "Need a load data");
+ NS_PRECONDITION(aLoadData->mURI, "Need a URI to load");
+ NS_PRECONDITION(aLoadData->mSheet, "Need a sheet to load into");
+ NS_PRECONDITION(aSheetState != eSheetComplete, "Why bother?");
+ NS_PRECONDITION(!aLoadData->mUseSystemPrincipal || aLoadData->mSyncLoad,
+ "Shouldn't use system principal for async loads");
+ NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");
+
+ LOG_URI(" Load from: '%s'", aLoadData->mURI);
+
+ nsresult rv = NS_OK;
+
+ 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;
+ }
+
+ SRIMetadata sriMetadata;
+ aLoadData->mSheet->GetIntegrity(sriMetadata);
+
+ if (aLoadData->mSyncLoad) {
+ LOG((" Synchronous load"));
+ NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
+ NS_ASSERTION(aSheetState == eSheetNeedsParser,
+ "Sync loads can't reuse existing async loads");
+
+ // Create a nsIUnicharStreamLoader instance to which we will feed
+ // the data from the sync load. Do this before creating the
+ // channel to make error recovery simpler.
+ nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
+ rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to create stream loader for sync load"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ if (mDocument) {
+ mozilla::net::PredictorLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
+ mDocument);
+ }
+
+ nsSecurityFlags securityFlags =
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_ALLOW_CHROME;
+
+ nsContentPolicyType contentPolicyType =
+ aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
+ : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
+
+ // 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 (aLoadData->mRequestingNode && aLoadData->mLoaderPrincipal) {
+ rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
+ aLoadData->mURI,
+ aLoadData->mRequestingNode,
+ aLoadData->mLoaderPrincipal,
+ securityFlags,
+ contentPolicyType);
+ }
+ else {
+ // either we are loading something inside a document, in which case
+ // we should always have a requestingNode, or we are loading something
+ // outside a document, in which case the loadingPrincipal and the
+ // triggeringPrincipal should always be the systemPrincipal.
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aLoadData->mURI,
+ nsContentUtils::GetSystemPrincipal(),
+ securityFlags,
+ contentPolicyType);
+ }
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to create channel"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open2(getter_AddRefs(stream));
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to open URI synchronously"));
+ 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(NS_LITERAL_CSTRING("UTF-8"));
+
+ // 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,
+ streamLoader,
+ channel);
+ }
+
+ SheetLoadData* existingData = nullptr;
+
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aLoadData->mURI,
+ aLoadData->mLoaderPrincipal,
+ aLoadData->mSheet->GetCORSMode(),
+ aLoadData->mSheet->GetReferrerPolicy());
+ if (aSheetState == eSheetLoading) {
+ mSheets->mLoadingDatas.Get(&key, &existingData);
+ NS_ASSERTION(existingData, "CreateSheet lied about the state");
+ }
+ else if (aSheetState == eSheetPending){
+ mSheets->mPendingDatas.Get(&key, &existingData);
+ NS_ASSERTION(existingData, "CreateSheet lied about the state");
+ }
+
+ if (existingData) {
+ LOG((" Glomming on to existing load"));
+ SheetLoadData* data = existingData;
+ while (data->mNext) {
+ data = data->mNext;
+ }
+ data->mNext = aLoadData; // transfer ownership
+ if (aSheetState == eSheetPending && !aLoadData->mWasAlternate) {
+ // Kick the load off; someone cares about it right away
+
+#ifdef DEBUG
+ SheetLoadData* removedData;
+ NS_ASSERTION(mSheets->mPendingDatas.Get(&key, &removedData) &&
+ removedData == existingData,
+ "Bad pending table.");
+#endif
+
+ mSheets->mPendingDatas.Remove(&key);
+
+ LOG((" Forcing load of pending data"));
+ return LoadSheet(existingData, eSheetNeedsParser, aIsPreload);
+ }
+ // All done here; once the load completes we'll be marked complete
+ // automatically
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ 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;
+ }
+ }
+#ifdef DEBUG
+ mSyncCallback = true;
+#endif
+
+ CORSMode ourCORSMode = aLoadData->mSheet->GetCORSMode();
+ nsSecurityFlags securityFlags =
+ ourCORSMode == CORS_NONE
+ ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
+ : nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+ if (ourCORSMode == CORS_ANONYMOUS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
+ } else if (ourCORSMode == CORS_USE_CREDENTIALS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+ securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
+
+ nsContentPolicyType contentPolicyType =
+ aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
+ : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
+
+ 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 (aLoadData->mRequestingNode && aLoadData->mLoaderPrincipal) {
+ rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
+ aLoadData->mURI,
+ aLoadData->mRequestingNode,
+ aLoadData->mLoaderPrincipal,
+ securityFlags,
+ contentPolicyType,
+ loadGroup,
+ nullptr, // aCallbacks
+ nsIChannel::LOAD_NORMAL |
+ nsIChannel::LOAD_CLASSIFY_URI);
+ }
+ else {
+ // either we are loading something inside a document, in which case
+ // we should always have a requestingNode, or we are loading something
+ // outside a document, in which case the loadingPrincipal and the
+ // triggeringPrincipal should always be the systemPrincipal.
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aLoadData->mURI,
+ nsContentUtils::GetSystemPrincipal(),
+ securityFlags,
+ contentPolicyType,
+ loadGroup,
+ nullptr, // aCallbacks
+ nsIChannel::LOAD_NORMAL |
+ nsIChannel::LOAD_CLASSIFY_URI);
+ }
+
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ mSyncCallback = false;
+#endif
+ LOG_ERROR((" Failed to create channel"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ if (!aLoadData->mWasAlternate) {
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Leader);
+ }
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ // Send a minimal Accept header for text/css
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ NS_LITERAL_CSTRING("text/css,*/*;q=0.1"),
+ false);
+ nsCOMPtr<nsIURI> referrerURI = aLoadData->GetReferrerURI();
+ if (referrerURI)
+ httpChannel->SetReferrerWithPolicy(referrerURI,
+ aLoadData->mSheet->GetReferrerPolicy());
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
+ if (internalChannel) {
+ internalChannel->SetIntegrityMetadata(sriMetadata.GetIntegrityString());
+ }
+
+ // Set the initiator type
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
+ if (timedChannel) {
+ if (aLoadData->mParentData) {
+ timedChannel->SetInitiatorType(NS_LITERAL_STRING("css"));
+ } else {
+ timedChannel->SetInitiatorType(NS_LITERAL_STRING("link"));
+ }
+ }
+ }
+
+ // 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(NS_LITERAL_CSTRING("text/css"));
+
+ // 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
+ nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
+ rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ mSyncCallback = false;
+#endif
+ LOG_ERROR((" Failed to create stream loader"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ if (mDocument) {
+ mozilla::net::PredictorLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
+ mDocument);
+ }
+
+ rv = channel->AsyncOpen2(streamLoader);
+
+#ifdef DEBUG
+ mSyncCallback = false;
+#endif
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to create stream loader"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ mSheets->mLoadingDatas.Put(&key, aLoadData);
+ aLoadData->mIsLoading = true;
+
+ return NS_OK;
+}
+
+/**
+ * ParseSheet handles parsing the data stream. The main idea here is
+ * to push the current load data onto the parse stack before letting
+ * the CSS parser at the data stream. That lets us handle @import
+ * correctly.
+ */
+nsresult
+Loader::ParseSheet(const nsAString& aInput,
+ SheetLoadData* aLoadData,
+ bool& aCompleted)
+{
+ LOG(("css::Loader::ParseSheet"));
+ NS_PRECONDITION(aLoadData, "Must have load data");
+ NS_PRECONDITION(aLoadData->mSheet, "Must have sheet to parse into");
+
+ aCompleted = false;
+
+ // Push our load data on the stack so any kids can pick it up
+ mParsingDatas.AppendElement(aLoadData);
+ nsIURI* sheetURI = aLoadData->mSheet->GetSheetURI();
+ nsIURI* baseURI = aLoadData->mSheet->GetBaseURI();
+
+ nsresult rv;
+
+ if (aLoadData->mSheet->IsGecko()) {
+ nsCSSParser parser(this, aLoadData->mSheet->AsGecko());
+ rv = parser.ParseSheet(aInput, sheetURI, baseURI,
+ aLoadData->mSheet->Principal(),
+ aLoadData->mLineNumber);
+ } else {
+ rv =
+ aLoadData->mSheet->AsServo()->ParseSheet(aInput, sheetURI, baseURI,
+ aLoadData->mSheet->Principal(),
+ aLoadData->mLineNumber);
+ }
+
+ mParsingDatas.RemoveElementAt(mParsingDatas.Length() - 1);
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Low-level error in parser!"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ NS_ASSERTION(aLoadData->mPendingChildren == 0 || !aLoadData->mSyncLoad,
+ "Sync load has leftover pending children!");
+
+ if (aLoadData->mPendingChildren == 0) {
+ LOG((" No pending kids from parse"));
+ aCompleted = true;
+ SheetComplete(aLoadData, NS_OK);
+ }
+ // Otherwise, the children are holding strong refs to the data and
+ // will call SheetComplete() on it when they complete.
+
+ return NS_OK;
+}
+
+/**
+ * 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"));
+
+ if (aLoadData->mSheet->IsServo() && NS_FAILED(aStatus)) {
+ aLoadData->mSheet->AsServo()->LoadFailed();
+ }
+
+ // 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<SheetLoadData>, 8> datasToNotify;
+ DoSheetComplete(aLoadData, aStatus, datasToNotify);
+
+ // Now it's safe to go ahead and notify observers
+ uint32_t count = datasToNotify.Length();
+ mDatasToNotifyOn += count;
+ for (uint32_t i = 0; i < count; ++i) {
+ --mDatasToNotifyOn;
+
+ SheetLoadData* data = datasToNotify[i];
+ NS_ASSERTION(data && data->mMustNotify, "How did this data get here?");
+ if (data->mObserver) {
+ LOG((" Notifying observer %p for data %p. wasAlternate: %d",
+ data->mObserver.get(), data, data->mWasAlternate));
+ data->mObserver->StyleSheetLoaded(data->mSheet, data->mWasAlternate,
+ aStatus);
+ }
+
+ nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver> >::ForwardIterator iter(mObservers);
+ nsCOMPtr<nsICSSLoaderObserver> obs;
+ while (iter.HasMore()) {
+ obs = iter.GetNext();
+ LOG((" Notifying global observer %p for data %p. wasAlternate: %d",
+ obs.get(), data, data->mWasAlternate));
+ obs->StyleSheetLoaded(data->mSheet, data->mWasAlternate, aStatus);
+ }
+ }
+
+ if (mSheets->mLoadingDatas.Count() == 0 && mSheets->mPendingDatas.Count() > 0) {
+ LOG((" No more loading sheets; starting alternates"));
+ StartAlternateLoads();
+ }
+}
+
+void
+Loader::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
+ LoadDataArray& aDatasToNotify)
+{
+ LOG(("css::Loader::DoSheetComplete"));
+ NS_PRECONDITION(aLoadData, "Must have a load data!");
+ NS_PRECONDITION(aLoadData->mSheet, "Must have a sheet");
+ NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");
+
+ LOG(("Load completed, status: 0x%x", aStatus));
+
+ // Twiddle the hashtables
+ if (aLoadData->mURI) {
+ LOG_URI(" Finished loading: '%s'", aLoadData->mURI);
+ // Remove the data from the list of loading datas
+ if (aLoadData->mIsLoading) {
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aLoadData->mURI,
+ aLoadData->mLoaderPrincipal,
+ aLoadData->mSheet->GetCORSMode(),
+ aLoadData->mSheet->GetReferrerPolicy());
+#ifdef DEBUG
+ SheetLoadData *loadingData;
+ NS_ASSERTION(mSheets->mLoadingDatas.Get(&key, &loadingData) &&
+ loadingData == aLoadData,
+ "Bad loading table");
+#endif
+
+ mSheets->mLoadingDatas.Remove(&key);
+ aLoadData->mIsLoading = false;
+ }
+ }
+
+ // Go through and deal with the whole linked list.
+ SheetLoadData* data = aLoadData;
+ while (data) {
+ 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.
+ MOZ_ASSERT(!(data->mSheet->IsGecko() &&
+ data->mSheet->AsGecko()->IsModified()),
+ "should not get marked modified during parsing");
+ data->mSheet->SetComplete();
+ data->ScheduleLoadEventIfNeeded(aStatus);
+ }
+ if (data->mMustNotify && (data->mObserver || !mObservers.IsEmpty())) {
+ // Don't notify here so we don't trigger script. Remember the
+ // info we need to notify, then do it later when it's safe.
+ aDatasToNotify.AppendElement(data);
+
+ // On append failure, just press on. We'll fail to notify the observer,
+ // but not much we can do about that....
+ }
+
+ 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 &&
+ !mParsingDatas.Contains(data->mParentData)) {
+ DoSheetComplete(data->mParentData, aStatus, aDatasToNotify);
+ }
+
+ data = data->mNext;
+ }
+
+ // Now that it's marked complete, put the sheet in our cache.
+ // If we ever start doing this for failure aStatus, we'll need to
+ // adjust the PostLoadEvent code that thinks anything already
+ // complete must have loaded succesfully.
+ if (NS_SUCCEEDED(aStatus) && aLoadData->mURI) {
+ // Pick our sheet to cache carefully. Ideally, we want to cache
+ // one of the sheets that will be kept alive by a document or
+ // parent sheet anyway, so that if someone then accesses it via
+ // CSSOM we won't have extra clones of the inner lying around.
+ if (aLoadData->mSheet->IsGecko()) {
+ data = aLoadData;
+ CSSStyleSheet* sheet = aLoadData->mSheet->AsGecko();
+ while (data) {
+ if (data->mSheet->GetParentSheet() || data->mSheet->GetOwnerNode()) {
+ sheet = data->mSheet->AsGecko();
+ break;
+ }
+ data = data->mNext;
+ }
+#ifdef MOZ_XUL
+ if (IsChromeURI(aLoadData->mURI)) {
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (cache && cache->IsEnabled()) {
+ if (!cache->GetStyleSheet(aLoadData->mURI)) {
+ LOG((" Putting sheet in XUL prototype cache"));
+ NS_ASSERTION(sheet->IsComplete(),
+ "Should only be caching complete sheets");
+ cache->PutStyleSheet(sheet);
+ }
+ }
+ }
+ else {
+#endif
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aLoadData->mURI,
+ aLoadData->mLoaderPrincipal,
+ aLoadData->mSheet->GetCORSMode(),
+ aLoadData->mSheet->GetReferrerPolicy());
+ NS_ASSERTION(sheet->IsComplete(),
+ "Should only be caching complete sheets");
+ mSheets->mCompleteSheets.Put(&key, sheet);
+#ifdef MOZ_XUL
+ }
+#endif
+ } else {
+ NS_WARNING("stylo: Stylesheet caching not yet supported - see bug 1290218.");
+ }
+ }
+
+ NS_RELEASE(aLoadData); // this will release parents and siblings and all that
+}
+
+nsresult
+Loader::LoadInlineStyle(nsIContent* aElement,
+ const nsAString& aBuffer,
+ uint32_t aLineNumber,
+ const nsAString& aTitle,
+ const nsAString& aMedia,
+ Element* aScopeElement,
+ nsICSSLoaderObserver* aObserver,
+ bool* aCompleted,
+ bool* aIsAlternate)
+{
+ LOG(("css::Loader::LoadInlineStyle"));
+ NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
+
+ *aCompleted = true;
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
+ NS_ASSERTION(owningElement, "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(). Also, OK to use CORS_NONE for the CORS
+ // mode and mDocument's ReferrerPolicy.
+ StyleSheetState state;
+ RefPtr<StyleSheet> sheet;
+ nsresult rv = CreateSheet(nullptr, aElement, nullptr, eAuthorSheetFeatures,
+ CORS_NONE, mDocument->GetReferrerPolicy(),
+ EmptyString(), // no inline integrity checks
+ false, false, aTitle, state, aIsAlternate,
+ &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(state == eSheetNeedsParser,
+ "Inline sheets should not be cached");
+
+ LOG((" Sheet is alternate: %d", *aIsAlternate));
+
+ PrepareSheet(sheet, aTitle, aMedia, nullptr, aScopeElement, *aIsAlternate);
+
+ if (aElement->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
+ ShadowRoot* containingShadow = aElement->GetContainingShadow();
+ MOZ_ASSERT(containingShadow);
+ containingShadow->InsertSheet(sheet, aElement);
+ } else {
+ rv = InsertSheetInDoc(sheet, aElement, mDocument);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ SheetLoadData* data = new SheetLoadData(this, aTitle, nullptr, sheet,
+ owningElement, *aIsAlternate,
+ aObserver, nullptr, static_cast<nsINode*>(aElement));
+
+ // We never actually load this, so just set its principal directly
+ sheet->SetPrincipal(aElement->NodePrincipal());
+
+ NS_ADDREF(data);
+ data->mLineNumber = aLineNumber;
+ // Parse completion releases the load data
+ rv = ParseSheet(aBuffer, data, *aCompleted);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If aCompleted is true, |data| may well be deleted by now.
+ if (!*aCompleted) {
+ data->mMustNotify = true;
+ }
+ return rv;
+}
+
+nsresult
+Loader::LoadStyleLink(nsIContent* aElement,
+ nsIURI* aURL,
+ const nsAString& aTitle,
+ const nsAString& aMedia,
+ bool aHasAlternateRel,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity,
+ nsICSSLoaderObserver* aObserver,
+ bool* aIsAlternate)
+{
+ LOG(("css::Loader::LoadStyleLink"));
+ NS_PRECONDITION(aURL, "Must have URL to load");
+ NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
+
+ LOG_URI(" Link uri: '%s'", aURL);
+ LOG((" Link title: '%s'", NS_ConvertUTF16toUTF8(aTitle).get()));
+ LOG((" Link media: '%s'", NS_ConvertUTF16toUTF8(aMedia).get()));
+ LOG((" Link alternate rel: %d", aHasAlternateRel));
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
+
+ nsIPrincipal* principal =
+ aElement ? aElement->NodePrincipal() : mDocument->NodePrincipal();
+
+ nsISupports* context = aElement;
+ if (!context) {
+ context = mDocument;
+ }
+
+ nsresult rv = CheckContentPolicy(principal, aURL, context, false);
+ 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 (aElement && !mDocument->IsLoadedAsData()) {
+ // Fire an async error event on it.
+ RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
+ new LoadBlockingAsyncEventDispatcher(aElement,
+ NS_LITERAL_STRING("error"),
+ false, false);
+ loadBlockingAsyncDispatcher->PostDOMEvent();
+ }
+ return rv;
+ }
+
+ StyleSheetState state;
+ RefPtr<StyleSheet> sheet;
+ rv = CreateSheet(aURL, aElement, principal, eAuthorSheetFeatures,
+ aCORSMode, aReferrerPolicy, aIntegrity, false,
+ aHasAlternateRel, aTitle, state, aIsAlternate,
+ &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG((" Sheet is alternate: %d", *aIsAlternate));
+
+ PrepareSheet(sheet, aTitle, aMedia, nullptr, nullptr, *aIsAlternate);
+
+ rv = InsertSheetInDoc(sheet, aElement, mDocument);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
+
+ if (state == eSheetComplete) {
+ LOG((" Sheet already complete: 0x%p", sheet.get()));
+ if (aObserver || !mObservers.IsEmpty() || owningElement) {
+ rv = PostLoadEvent(aURL, sheet, aObserver, *aIsAlternate,
+ owningElement);
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // Now we need to actually load it
+ nsCOMPtr<nsINode> requestingNode = do_QueryInterface(context);
+ SheetLoadData* data = new SheetLoadData(this, aTitle, aURL, sheet,
+ owningElement, *aIsAlternate,
+ aObserver, principal, requestingNode);
+ NS_ADDREF(data);
+
+ // If we have to parse and it's an alternate non-inline, defer it
+ if (aURL && state == eSheetNeedsParser && mSheets->mLoadingDatas.Count() != 0 &&
+ *aIsAlternate) {
+ LOG((" Deferring alternate sheet load"));
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(data->mURI,
+ data->mLoaderPrincipal,
+ data->mSheet->GetCORSMode(),
+ data->mSheet->GetReferrerPolicy());
+ mSheets->mPendingDatas.Put(&key, data);
+
+ data->mMustNotify = true;
+ return NS_OK;
+ }
+
+ // Load completion will free the data
+ rv = LoadSheet(data, state, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ data->mMustNotify = true;
+ return rv;
+}
+
+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.
+ while (aData) {
+ if (aData->mParentData &&
+ HaveAncestorDataWithURI(aData->mParentData, aURI)) {
+ return true;
+ }
+
+ aData = aData->mNext;
+ }
+
+ return false;
+}
+
+nsresult
+Loader::LoadChildSheet(StyleSheet* aParentSheet,
+ nsIURI* aURL,
+ nsMediaList* aMedia,
+ ImportRule* aParentRule,
+ LoaderReusableStyleSheets* aReusableSheets)
+{
+ LOG(("css::Loader::LoadChildSheet"));
+ NS_PRECONDITION(aURL, "Must have a URI to load");
+ NS_PRECONDITION(aParentSheet, "Must have a parent sheet");
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG_URI(" Child uri: '%s'", aURL);
+
+ nsCOMPtr<nsINode> owningNode;
+
+ // check for an owning document: if none, don't bother walking up the parent
+ // sheets
+ if (aParentSheet->GetOwningDocument()) {
+ StyleSheet* topSheet = aParentSheet;
+ while (StyleSheet* parent = topSheet->GetParentSheet()) {
+ topSheet = parent;
+ }
+ owningNode = topSheet->GetOwnerNode();
+ }
+
+ nsISupports* context = owningNode;
+ if (!context) {
+ context = mDocument;
+ }
+
+ nsIPrincipal* principal = aParentSheet->Principal();
+ nsresult rv = CheckContentPolicy(principal, aURL, context, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SheetLoadData* parentData = nullptr;
+ nsCOMPtr<nsICSSLoaderObserver> observer;
+
+ int32_t count = mParsingDatas.Length();
+ if (count > 0) {
+ LOG((" Have a parent load"));
+ parentData = mParsingDatas.ElementAt(count - 1);
+ // Check for cycles
+ if (HaveAncestorDataWithURI(parentData, 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(parentData->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.
+ // XXXheycam ServoStyleSheet doesn't implement nsICSSLoaderObserver yet.
+ MOZ_ASSERT(aParentSheet->IsGecko(),
+ "stylo: ServoStyleSheets don't support child sheet loading yet");
+ observer = aParentSheet->AsGecko();
+ }
+
+ // Now that we know it's safe to load this (passes security check and not a
+ // loop) do so.
+ RefPtr<StyleSheet> sheet;
+ RefPtr<CSSStyleSheet> reusableSheet;
+ StyleSheetState state;
+ if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, reusableSheet)) {
+ sheet = reusableSheet;
+ aParentRule->SetSheet(reusableSheet);
+ state = eSheetComplete;
+ } else {
+ bool isAlternate;
+ const nsSubstring& empty = EmptyString();
+ // For now, use CORS_NONE for child sheets
+ rv = CreateSheet(aURL, nullptr, principal,
+ aParentSheet->ParsingMode(),
+ CORS_NONE, aParentSheet->GetReferrerPolicy(),
+ EmptyString(), // integrity is only checked on main sheet
+ parentData ? parentData->mSyncLoad : false,
+ false, empty, state, &isAlternate, &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PrepareSheet(sheet, empty, empty, aMedia, nullptr, isAlternate);
+ }
+
+ rv = InsertChildSheet(sheet, aParentSheet, aParentRule);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (state == eSheetComplete) {
+ 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.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> requestingNode = do_QueryInterface(context);
+ SheetLoadData* data = new SheetLoadData(this, aURL, sheet, parentData,
+ observer, principal, requestingNode);
+
+ NS_ADDREF(data);
+ bool syncLoad = data->mSyncLoad;
+
+ // Load completion will release the data
+ rv = LoadSheet(data, state, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If syncLoad is true, |data| will be deleted by now.
+ if (!syncLoad) {
+ data->mMustNotify = true;
+ }
+ return rv;
+}
+
+nsresult
+Loader::LoadSheetSync(nsIURI* aURL,
+ SheetParsingMode aParsingMode,
+ bool aUseSystemPrincipal,
+ RefPtr<StyleSheet>* aSheet)
+{
+ LOG(("css::Loader::LoadSheetSync"));
+ return InternalLoadNonDocumentSheet(aURL,
+ false, aParsingMode, aUseSystemPrincipal,
+ nullptr, EmptyCString(),
+ aSheet, nullptr);
+}
+
+nsresult
+Loader::LoadSheet(nsIURI* aURL,
+ nsIPrincipal* aOriginPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ RefPtr<StyleSheet>* aSheet)
+{
+ LOG(("css::Loader::LoadSheet(aURL, aObserver, aSheet) api call"));
+ NS_PRECONDITION(aSheet, "aSheet is null");
+ return InternalLoadNonDocumentSheet(aURL,
+ false, eAuthorSheetFeatures, false,
+ aOriginPrincipal, aCharset,
+ aSheet, aObserver);
+}
+
+nsresult
+Loader::LoadSheet(nsIURI* aURL,
+ bool aIsPreload,
+ nsIPrincipal* aOriginPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity)
+{
+ LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
+ return InternalLoadNonDocumentSheet(aURL,
+ aIsPreload, eAuthorSheetFeatures, false,
+ aOriginPrincipal, aCharset,
+ nullptr, aObserver,
+ aCORSMode, aReferrerPolicy, aIntegrity);
+}
+
+nsresult
+Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
+ bool aIsPreload,
+ SheetParsingMode aParsingMode,
+ bool aUseSystemPrincipal,
+ nsIPrincipal* aOriginPrincipal,
+ const nsCString& aCharset,
+ RefPtr<StyleSheet>* aSheet,
+ nsICSSLoaderObserver* aObserver,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity)
+{
+ NS_PRECONDITION(aURL, "Must have a URI to load");
+ NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
+ NS_PRECONDITION(!aUseSystemPrincipal || !aObserver,
+ "Shouldn't load system-principal sheets async");
+ NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
+
+ LOG_URI(" Non-document sheet uri: '%s'", aURL);
+
+ if (aSheet) {
+ *aSheet = nullptr;
+ }
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = CheckContentPolicy(aOriginPrincipal, aURL, mDocument, aIsPreload);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StyleSheetState state;
+ bool isAlternate;
+ RefPtr<StyleSheet> sheet;
+ bool syncLoad = (aObserver == nullptr);
+ const nsSubstring& empty = EmptyString();
+
+ rv = CreateSheet(aURL, nullptr, aOriginPrincipal, aParsingMode,
+ aCORSMode, aReferrerPolicy, aIntegrity, syncLoad,
+ false, empty, state, &isAlternate, &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PrepareSheet(sheet, empty, empty, nullptr, nullptr, isAlternate);
+
+ if (state == eSheetComplete) {
+ LOG((" Sheet already complete"));
+ if (aObserver || !mObservers.IsEmpty()) {
+ rv = PostLoadEvent(aURL, sheet, aObserver, false, nullptr);
+ }
+ if (aSheet) {
+ sheet.swap(*aSheet);
+ }
+ return rv;
+ }
+
+ SheetLoadData* data =
+ new SheetLoadData(this, aURL, sheet, syncLoad,
+ aUseSystemPrincipal, aCharset, aObserver,
+ aOriginPrincipal, mDocument);
+
+ NS_ADDREF(data);
+ rv = LoadSheet(data, state, aIsPreload);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aSheet) {
+ sheet.swap(*aSheet);
+ }
+ if (aObserver) {
+ data->mMustNotify = true;
+ }
+
+ return rv;
+}
+
+nsresult
+Loader::PostLoadEvent(nsIURI* aURI,
+ StyleSheet* aSheet,
+ nsICSSLoaderObserver* aObserver,
+ bool aWasAlternate,
+ nsIStyleSheetLinkingElement* aElement)
+{
+ LOG(("css::Loader::PostLoadEvent"));
+ NS_PRECONDITION(aSheet, "Must have sheet");
+ NS_PRECONDITION(aObserver || !mObservers.IsEmpty() || aElement,
+ "Must have observer or element");
+
+ RefPtr<SheetLoadData> evt =
+ new SheetLoadData(this, EmptyString(), // title doesn't matter here
+ aURI,
+ aSheet,
+ aElement,
+ aWasAlternate,
+ aObserver,
+ nullptr,
+ mDocument);
+ NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY);
+
+ if (!mPostedEvents.AppendElement(evt)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = NS_DispatchToCurrentThread(evt);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to dispatch stylesheet load event");
+ mPostedEvents.RemoveElement(evt);
+ } else {
+ // We'll unblock onload when we handle the event.
+ if (mDocument) {
+ mDocument->BlockOnload();
+ }
+
+ // We want to notify the observer for this data.
+ evt->mMustNotify = true;
+ evt->mSheetAlreadyComplete = true;
+
+ // If we get to this code, aSheet loaded correctly at some point, so
+ // we can just use NS_OK for the status. Note that we do this here
+ // and not from inside our SheetComplete so that we don't end up
+ // running the load event async.
+ evt->ScheduleLoadEventIfNeeded(NS_OK);
+ }
+
+ return rv;
+}
+
+void
+Loader::HandleLoadEvent(SheetLoadData* aEvent)
+{
+ // XXXbz can't assert this yet.... May not have an observer because
+ // we're unblocking the parser
+ // NS_ASSERTION(aEvent->mObserver, "Must have observer");
+ NS_ASSERTION(aEvent->mSheet, "Must have sheet");
+
+ // Very important: this needs to come before the SheetComplete call
+ // below, so that HasPendingLoads() will test true as needed under
+ // notifications we send from that SheetComplete call.
+ mPostedEvents.RemoveElement(aEvent);
+
+ if (!aEvent->mIsCancelled) {
+ // SheetComplete will call Release(), so give it a reference to do
+ // that with.
+ NS_ADDREF(aEvent);
+ SheetComplete(aEvent, NS_OK);
+ }
+
+ if (mDocument) {
+ mDocument->UnblockOnload(true);
+ }
+}
+
+static void
+StopLoadingSheets(
+ nsDataHashtable<URIPrincipalReferrerPolicyAndCORSModeHashKey, SheetLoadData*>& aDatas,
+ Loader::LoadDataArray& aArr)
+{
+ for (auto iter = aDatas.Iter(); !iter.Done(); iter.Next()) {
+ SheetLoadData* data = iter.Data();
+ MOZ_ASSERT(data, "Must have a data!");
+
+ data->mIsLoading = false; // we will handle the removal right here
+ data->mIsCancelled = true;
+
+ aArr.AppendElement(data);
+
+ iter.Remove();
+ }
+}
+
+nsresult
+Loader::Stop()
+{
+ uint32_t pendingCount = mSheets ? mSheets->mPendingDatas.Count() : 0;
+ uint32_t loadingCount = mSheets ? mSheets->mLoadingDatas.Count() : 0;
+ LoadDataArray arr(pendingCount + loadingCount + mPostedEvents.Length());
+
+ if (pendingCount) {
+ StopLoadingSheets(mSheets->mPendingDatas, arr);
+ }
+ if (loadingCount) {
+ StopLoadingSheets(mSheets->mLoadingDatas, arr);
+ }
+
+ uint32_t i;
+ for (i = 0; i < mPostedEvents.Length(); ++i) {
+ SheetLoadData* data = mPostedEvents[i];
+ data->mIsCancelled = true;
+ if (arr.AppendElement(data)) {
+ // SheetComplete() calls Release(), so give this an extra ref.
+ NS_ADDREF(data);
+ }
+#ifdef DEBUG
+ else {
+ NS_NOTREACHED("We preallocated this memory... shouldn't really fail, "
+ "except we never check that preallocation succeeds.");
+ }
+#endif
+ }
+ mPostedEvents.Clear();
+
+ mDatasToNotifyOn += arr.Length();
+ for (i = 0; i < arr.Length(); ++i) {
+ --mDatasToNotifyOn;
+ SheetComplete(arr[i], NS_BINDING_ABORTED);
+ }
+ return NS_OK;
+}
+
+bool
+Loader::HasPendingLoads()
+{
+ return
+ (mSheets && mSheets->mLoadingDatas.Count() != 0) ||
+ (mSheets && mSheets->mPendingDatas.Count() != 0) ||
+ mPostedEvents.Length() != 0 ||
+ mDatasToNotifyOn != 0;
+}
+
+nsresult
+Loader::AddObserver(nsICSSLoaderObserver* aObserver)
+{
+ NS_PRECONDITION(aObserver, "Must have observer");
+ if (mObservers.AppendElementUnlessExists(aObserver)) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+void
+Loader::RemoveObserver(nsICSSLoaderObserver* aObserver)
+{
+ mObservers.RemoveElement(aObserver);
+}
+
+void
+Loader::StartAlternateLoads()
+{
+ NS_PRECONDITION(mSheets, "Don't call me!");
+ LoadDataArray arr(mSheets->mPendingDatas.Count());
+ for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
+ arr.AppendElement(iter.Data());
+ iter.Remove();
+ }
+
+ mDatasToNotifyOn += arr.Length();
+ for (uint32_t i = 0; i < arr.Length(); ++i) {
+ --mDatasToNotifyOn;
+ LoadSheet(arr[i], eSheetNeedsParser, false);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Loader)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Loader)
+ if (tmp->mSheets) {
+ for (auto iter = tmp->mSheets->mCompleteSheets.Iter();
+ !iter.Done();
+ iter.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "Sheet cache nsCSSLoader");
+ if (iter.UserData()->IsGecko()) {
+ CSSStyleSheet* sheet = iter.UserData()->AsGecko();
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMCSSStyleSheet*, sheet));
+ }
+ }
+ }
+ nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>>::ForwardIterator
+ it(tmp->mObservers);
+ while (it.HasMore()) {
+ ImplCycleCollectionTraverse(cb, it.GetNext(),
+ "mozilla::css::Loader.mObservers");
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
+ if (tmp->mSheets) {
+ tmp->mSheets->mCompleteSheets.Clear();
+ }
+ tmp->mObservers.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Loader, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Loader, Release)
+
+size_t
+Loader::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ if (mSheets) {
+ n += mSheets->mCompleteSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mSheets->mCompleteSheets.ConstIter();
+ !iter.Done();
+ iter.Next()) {
+ // If aSheet has a parent, then its parent will report it so we don't
+ // have to worry about it here. Likewise, if aSheet has an owning node,
+ // then the document that node is in will report it.
+ const StyleSheet* sheet = iter.UserData();
+ n += (sheet->GetOwnerNode() || sheet->GetParentSheet())
+ ? 0
+ : sheet->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+ n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mLoadingDatas: transient, and should be small
+ // - mPendingDatas: transient, and should be small
+ // - mParsingDatas: transient, and should be small
+ // - mPostedEvents: transient, and should be small
+ //
+ // The following members aren't measured:
+ // - mDocument, because it's a weak backpointer
+ // - mPreferredSheet, because it can be a shared string
+
+ return n;
+}
+
+StyleBackendType
+Loader::GetStyleBackendType() const
+{
+ MOZ_ASSERT(mStyleBackendType || mDocument,
+ "you must construct a Loader with a document or set a "
+ "StyleBackendType on it before calling GetStyleBackendType");
+ if (mStyleBackendType) {
+ return *mStyleBackendType;
+ }
+ return mDocument->GetStyleBackendType();
+}
+
+} // namespace css
+} // namespace mozilla
diff --git a/layout/style/Loader.h b/layout/style/Loader.h
new file mode 100644
index 000000000..209783a80
--- /dev/null
+++ b/layout/style/Loader.h
@@ -0,0 +1,598 @@
+/* -*- 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/. */
+
+/* loading of CSS style sheets using the network APIs */
+
+#ifndef mozilla_css_Loader_h
+#define mozilla_css_Loader_h
+
+#include "nsIPrincipal.h"
+#include "nsAutoPtr.h"
+#include "nsCompatibility.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDataHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsTObserverArray.h"
+#include "nsURIHashKey.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StyleBackendType.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/net/ReferrerPolicy.h"
+
+class nsICSSLoaderObserver;
+class nsIConsoleReportCollector;
+class nsIContent;
+class nsIDocument;
+class nsMediaList;
+class nsIStyleSheetLinkingElement;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+
+class URIPrincipalReferrerPolicyAndCORSModeHashKey : public nsURIHashKey
+{
+public:
+ typedef URIPrincipalReferrerPolicyAndCORSModeHashKey* KeyType;
+ typedef const URIPrincipalReferrerPolicyAndCORSModeHashKey* KeyTypePointer;
+ typedef mozilla::net::ReferrerPolicy ReferrerPolicy;
+
+ explicit URIPrincipalReferrerPolicyAndCORSModeHashKey(const URIPrincipalReferrerPolicyAndCORSModeHashKey* aKey)
+ : nsURIHashKey(aKey->mKey),
+ mPrincipal(aKey->mPrincipal),
+ mCORSMode(aKey->mCORSMode),
+ mReferrerPolicy(aKey->mReferrerPolicy)
+ {
+ MOZ_COUNT_CTOR(URIPrincipalReferrerPolicyAndCORSModeHashKey);
+ }
+
+ URIPrincipalReferrerPolicyAndCORSModeHashKey(nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy)
+ : nsURIHashKey(aURI),
+ mPrincipal(aPrincipal),
+ mCORSMode(aCORSMode),
+ mReferrerPolicy(aReferrerPolicy)
+ {
+ MOZ_COUNT_CTOR(URIPrincipalReferrerPolicyAndCORSModeHashKey);
+ }
+ URIPrincipalReferrerPolicyAndCORSModeHashKey(const URIPrincipalReferrerPolicyAndCORSModeHashKey& toCopy)
+ : nsURIHashKey(toCopy),
+ mPrincipal(toCopy.mPrincipal),
+ mCORSMode(toCopy.mCORSMode),
+ mReferrerPolicy(toCopy.mReferrerPolicy)
+ {
+ MOZ_COUNT_CTOR(URIPrincipalReferrerPolicyAndCORSModeHashKey);
+ }
+ ~URIPrincipalReferrerPolicyAndCORSModeHashKey()
+ {
+ MOZ_COUNT_DTOR(URIPrincipalReferrerPolicyAndCORSModeHashKey);
+ }
+
+ URIPrincipalReferrerPolicyAndCORSModeHashKey* GetKey() const {
+ return const_cast<URIPrincipalReferrerPolicyAndCORSModeHashKey*>(this);
+ }
+ const URIPrincipalReferrerPolicyAndCORSModeHashKey* GetKeyPointer() const { return this; }
+
+ bool KeyEquals(const URIPrincipalReferrerPolicyAndCORSModeHashKey* aKey) const {
+ if (!nsURIHashKey::KeyEquals(aKey->mKey)) {
+ return false;
+ }
+
+ if (!mPrincipal != !aKey->mPrincipal) {
+ // One or the other has a principal, but not both... not equal
+ return false;
+ }
+
+ if (mCORSMode != aKey->mCORSMode) {
+ // Different CORS modes; we don't match
+ return false;
+ }
+
+ if (mReferrerPolicy != aKey->mReferrerPolicy) {
+ // Different ReferrerPolicy; we don't match
+ return false;
+ }
+
+ bool eq;
+ return !mPrincipal ||
+ (NS_SUCCEEDED(mPrincipal->Equals(aKey->mPrincipal, &eq)) && eq);
+ }
+
+ static const URIPrincipalReferrerPolicyAndCORSModeHashKey*
+ KeyToPointer(URIPrincipalReferrerPolicyAndCORSModeHashKey* aKey) { return aKey; }
+ static PLDHashNumber HashKey(const URIPrincipalReferrerPolicyAndCORSModeHashKey* aKey) {
+ return nsURIHashKey::HashKey(aKey->mKey);
+ }
+
+ nsIURI* GetURI() const { return nsURIHashKey::GetKey(); }
+
+ enum { ALLOW_MEMMOVE = true };
+
+protected:
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ CORSMode mCORSMode;
+ ReferrerPolicy mReferrerPolicy;
+};
+
+
+
+namespace css {
+
+class SheetLoadData;
+class ImportRule;
+
+/*********************
+ * Style sheet reuse *
+ *********************/
+
+class MOZ_RAII LoaderReusableStyleSheets
+{
+public:
+ LoaderReusableStyleSheets()
+ {
+ }
+
+ /**
+ * 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<CSSStyleSheet>& 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(CSSStyleSheet* aSheet) {
+ mReusableSheets.AppendElement(aSheet);
+ }
+
+private:
+ LoaderReusableStyleSheets(const LoaderReusableStyleSheets&) = delete;
+ LoaderReusableStyleSheets& operator=(const LoaderReusableStyleSheets&) = delete;
+
+ // The sheets that can be reused.
+ nsTArray<RefPtr<CSSStyleSheet>> mReusableSheets;
+};
+
+/***********************************************************************
+ * Enum that describes the state of the sheet returned by CreateSheet. *
+ ***********************************************************************/
+enum StyleSheetState {
+ eSheetStateUnknown = 0,
+ eSheetNeedsParser,
+ eSheetPending,
+ eSheetLoading,
+ eSheetComplete
+};
+
+class Loader final {
+ typedef mozilla::net::ReferrerPolicy ReferrerPolicy;
+
+public:
+ explicit Loader(StyleBackendType aType);
+ explicit Loader(nsIDocument*);
+
+ 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 SetCompatibilityMode(nsCompatibility aCompatMode)
+ { mCompatMode = aCompatMode; }
+ nsCompatibility GetCompatibilityMode() { return mCompatMode; }
+ nsresult SetPreferredSheet(const nsAString& aTitle);
+
+ // 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
+ * *aCompleted is false, then aObserver is guaranteed to be notified
+ * asynchronously once the sheet is marked complete. If an error is
+ * returned, or if *aCompleted is true, 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 aElement the element linking to the stylesheet. This must not be
+ * null and must implement nsIStyleSheetLinkingElement.
+ * @param aBuffer the stylesheet data
+ * @param aLineNumber the line number at which the stylesheet data started.
+ * @param aTitle the title of the sheet.
+ * @param aMedia the media string for the sheet.
+ * @param aObserver the observer to notify when the load completes.
+ * May be null.
+ * @param [out] aCompleted whether parsing of the sheet completed.
+ * @param [out] aIsAlternate whether the stylesheet ended up being an
+ * alternate sheet.
+ */
+ nsresult LoadInlineStyle(nsIContent* aElement,
+ const nsAString& aBuffer,
+ uint32_t aLineNumber,
+ const nsAString& aTitle,
+ const nsAString& aMedia,
+ mozilla::dom::Element* aScopeElement,
+ nsICSSLoaderObserver* aObserver,
+ bool* aCompleted,
+ bool* aIsAlternate);
+
+ /**
+ * 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. 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 aElement the element linking to the the stylesheet. May be null.
+ * @param aURL the URL of the sheet.
+ * @param aTitle the title of the sheet.
+ * @param aMedia the media string for the sheet.
+ * @param aHasAlternateRel whether the rel for this link included
+ * "alternate".
+ * @param aCORSMode the CORS mode for this load.
+ * @param aObserver the observer to notify when the load completes.
+ * May be null.
+ * @param [out] aIsAlternate whether the stylesheet actually ended up beinga
+ * an alternate sheet. Note that this need not match
+ * aHasAlternateRel.
+ */
+ nsresult LoadStyleLink(nsIContent* aElement,
+ nsIURI* aURL,
+ const nsAString& aTitle,
+ const nsAString& aMedia,
+ bool aHasAlternateRel,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity,
+ nsICSSLoaderObserver* aObserver,
+ bool* aIsAlternate);
+
+ /**
+ * 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 aURL the URL of the child sheet
+ * @param aMedia the already-parsed media list for the child sheet
+ * @param aRule the @import rule importing this child. This is used to
+ * properly order the child sheet list of aParentSheet.
+ * @param aSavedSheets any saved style sheets which could be reused
+ * for this load
+ */
+ nsresult LoadChildSheet(StyleSheet* aParentSheet,
+ nsIURI* aURL,
+ nsMediaList* aMedia,
+ ImportRule* aRule,
+ LoaderReusableStyleSheets* aSavedSheets);
+
+ /**
+ * 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.
+ * @param [out] aSheet the loaded, complete sheet.
+ *
+ * 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.
+ */
+ nsresult LoadSheetSync(nsIURI* aURL,
+ SheetParsingMode aParsingMode,
+ bool aUseSystemPrincipal,
+ RefPtr<StyleSheet>* aSheet);
+
+ /**
+ * As above, but defaults aParsingMode to eAuthorSheetFeatures and
+ * aUseSystemPrincipal to false.
+ */
+ nsresult LoadSheetSync(nsIURI* aURL, RefPtr<StyleSheet>* aSheet) {
+ return LoadSheetSync(aURL, eAuthorSheetFeatures, false, aSheet);
+ }
+
+ /**
+ * 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. This method cannot be used to
+ * load user or agent sheets.
+ *
+ * @param aURL the URL of the sheet to load
+ * @param aOriginPrincipal the principal to use for security checks. This
+ * can be null to indicate that these checks should
+ * be skipped.
+ * @param aCharset the encoding to use for converting the sheet data
+ * from bytes to Unicode. May be empty to indicate that the
+ * charset of the CSSLoader's document should be used. This
+ * is only used if neither the network transport nor the
+ * sheet itself indicate an encoding.
+ * @param aObserver the observer to notify when the load completes.
+ * Must not be null.
+ * @param [out] aSheet the sheet to load. Note that the sheet may well
+ * not be loaded by the time this method returns.
+ */
+ nsresult LoadSheet(nsIURI* aURL,
+ nsIPrincipal* aOriginPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ RefPtr<StyleSheet>* aSheet);
+
+ /**
+ * Same as above, to be used when the caller doesn't care about the
+ * not-yet-loaded sheet.
+ */
+ nsresult LoadSheet(nsIURI* aURL,
+ bool aIsPreload,
+ nsIPrincipal* aOriginPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ CORSMode aCORSMode = CORS_NONE,
+ ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
+ const nsAString& aIntegrity = EmptyString());
+
+ /**
+ * Stop loading all sheets. All nsICSSLoaderObservers involved will be
+ * notified with NS_BINDING_ABORTED as the status, possibly synchronously.
+ */
+ nsresult Stop(void);
+
+ /**
+ * 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; }
+
+ /**
+ * Get the document we live for. May return null.
+ */
+ nsIDocument* GetDocument() const { return mDocument; }
+
+ /**
+ * 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.
+ */
+ nsresult 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.
+
+ // IsAlternate can change our currently selected style set if none
+ // is selected and aHasAlternateRel is false.
+ bool IsAlternate(const nsAString& aTitle, bool aHasAlternateRel);
+
+ typedef nsTArray<RefPtr<SheetLoadData> > LoadDataArray;
+
+ // Measure our size.
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // Marks all the sheets at the given URI obsolete, and removes them from the
+ // cache.
+ nsresult ObsoleteSheet(nsIURI* aURI);
+
+private:
+ friend class SheetLoadData;
+
+ nsresult CheckContentPolicy(nsIPrincipal* aSourcePrincipal,
+ nsIURI* aTargetURI,
+ nsISupports* aContext,
+ bool aIsPreload);
+
+ // 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.
+ // *aIsAlternate is set based on aTitle and aHasAlternateRel.
+ nsresult CreateSheet(nsIURI* aURI,
+ nsIContent* aLinkingContent,
+ nsIPrincipal* aLoaderPrincipal,
+ css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity,
+ bool aSyncLoad,
+ bool aHasAlternateRel,
+ const nsAString& aTitle,
+ StyleSheetState& aSheetState,
+ bool *aIsAlternate,
+ RefPtr<StyleSheet>* aSheet);
+
+ // Pass in either a media string or the nsMediaList from the
+ // CSSParser. Don't pass both.
+ // This method will set the sheet's enabled state based on isAlternate
+ void PrepareSheet(StyleSheet* aSheet,
+ const nsAString& aTitle,
+ const nsAString& aMediaString,
+ nsMediaList* aMediaList,
+ dom::Element* aScopeElement,
+ bool isAlternate);
+
+ nsresult InsertSheetInDoc(StyleSheet* aSheet,
+ nsIContent* aLinkingContent,
+ nsIDocument* aDocument);
+
+ nsresult InsertChildSheet(StyleSheet* aSheet,
+ StyleSheet* aParentSheet,
+ ImportRule* aParentRule);
+
+ nsresult InternalLoadNonDocumentSheet(nsIURI* aURL,
+ bool aIsPreload,
+ SheetParsingMode aParsingMode,
+ bool aUseSystemPrincipal,
+ nsIPrincipal* aOriginPrincipal,
+ const nsCString& aCharset,
+ RefPtr<StyleSheet>* aSheet,
+ nsICSSLoaderObserver* aObserver,
+ CORSMode aCORSMode = CORS_NONE,
+ ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
+ const nsAString& aIntegrity = EmptyString());
+
+ // Post a load event for aObserver to be notified about aSheet. The
+ // notification will be sent with status NS_OK unless the load event is
+ // canceled at some point (in which case it will be sent with
+ // NS_BINDING_ABORTED). aWasAlternate indicates the state when the load was
+ // initiated, not the state at some later time. aURI should be the URI the
+ // sheet was loaded from (may be null for inline sheets). aElement is the
+ // owning element for this sheet.
+ nsresult PostLoadEvent(nsIURI* aURI,
+ StyleSheet* aSheet,
+ nsICSSLoaderObserver* aObserver,
+ bool aWasAlternate,
+ nsIStyleSheetLinkingElement* aElement);
+
+ // Start the loads of all the sheets in mPendingDatas
+ void StartAlternateLoads();
+
+ // Handle an event posted by PostLoadEvent
+ void HandleLoadEvent(SheetLoadData* aEvent);
+
+ // Note: LoadSheet is responsible for releasing aLoadData and setting the
+ // sheet to complete on failure.
+ nsresult LoadSheet(SheetLoadData* aLoadData,
+ StyleSheetState aSheetState,
+ bool aIsPreLoad);
+
+ // Parse the stylesheet in aLoadData. The sheet data comes from aInput.
+ // Set aCompleted to true if the parse finished, false otherwise (e.g. if the
+ // sheet had an @import). If aCompleted is true when this returns, then
+ // ParseSheet also called SheetComplete on aLoadData.
+ nsresult ParseSheet(const nsAString& aInput,
+ SheetLoadData* aLoadData,
+ bool& aCompleted);
+
+ // The load of the sheet in aLoadData is done, one way or another. Do final
+ // cleanup, including releasing aLoadData.
+ void SheetComplete(SheetLoadData* aLoadData, nsresult aStatus);
+
+ // The guts of SheetComplete. This may be called recursively on parent datas
+ // or datas that had glommed on to a single load. The array is there so load
+ // datas whose observers need to be notified can be added to it.
+ void DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
+ LoadDataArray& aDatasToNotify);
+
+ StyleBackendType GetStyleBackendType() const;
+
+ struct Sheets {
+ nsBaseHashtable<URIPrincipalReferrerPolicyAndCORSModeHashKey,
+ RefPtr<StyleSheet>,
+ StyleSheet*> mCompleteSheets;
+ nsDataHashtable<URIPrincipalReferrerPolicyAndCORSModeHashKey, SheetLoadData*>
+ mLoadingDatas; // weak refs
+ nsDataHashtable<URIPrincipalReferrerPolicyAndCORSModeHashKey, SheetLoadData*>
+ mPendingDatas; // weak refs
+ };
+ nsAutoPtr<Sheets> mSheets;
+
+ // We're not likely to have many levels of @import... But likely to have
+ // some. Allocate some storage, what the hell.
+ AutoTArray<SheetLoadData*, 8> mParsingDatas;
+
+ // The array of posted stylesheet loaded events (SheetLoadDatas) we have.
+ // Note that these are rare.
+ LoadDataArray mPostedEvents;
+
+ // Our array of "global" observers
+ nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver> > mObservers;
+
+ // This reference is nulled by the Document in it's destructor through
+ // DropDocumentReference().
+ nsIDocument* MOZ_NON_OWNING_REF mDocument; // the document we live for
+
+
+ // Number of datas still waiting to be notified on if we're notifying on a
+ // whole bunch at once (e.g. in one of the stop methods). This is used to
+ // make sure that HasPendingLoads() won't return false until we're notifying
+ // on the last data we're working with.
+ uint32_t mDatasToNotifyOn;
+
+ nsCompatibility mCompatMode;
+ nsString mPreferredSheet; // title of preferred sheet
+
+ // Set explicitly when the Loader(StyleBackendType) constructor is used, or
+ // taken from the document when the Loader(nsIDocument*) constructor is used.
+ mozilla::Maybe<StyleBackendType> mStyleBackendType;
+
+ bool mEnabled; // is enabled to load new styles
+
+ nsCOMPtr<nsIConsoleReportCollector> mReporter;
+
+#ifdef DEBUG
+ bool mSyncCallback;
+#endif
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_Loader_h */
diff --git a/layout/style/Makefile.in b/layout/style/Makefile.in
new file mode 100644
index 000000000..c92d1360e
--- /dev/null
+++ b/layout/style/Makefile.in
@@ -0,0 +1,16 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# TODO This list should be emitted to a .pp file via
+# GenerateCSSPropsGenerated.py (bug 1281614)
+csspropsidlnames_dependencies = \
+ $(srcdir)/nsCSSPropList.h \
+ $(srcdir)/nsCSSPropAliasList.h \
+ $(srcdir)/nsCSSPropsGenerated.inc.in \
+ $(srcdir)/PythonCSSProps.h \
+ $(srcdir)/GenerateCSSPropsGenerated.py \
+ $(GLOBAL_DEPS) \
+ $(NULL)
+
+nsCSSPropsGenerated.inc : $(csspropsidlnames_dependencies)
diff --git a/layout/style/MediaQueryList.cpp b/layout/style/MediaQueryList.cpp
new file mode 100644
index 000000000..069e049c4
--- /dev/null
+++ b/layout/style/MediaQueryList.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* implements DOM interface for querying and observing media queries */
+
+#include "mozilla/dom/MediaQueryList.h"
+#include "nsPresContext.h"
+#include "nsIMediaList.h"
+#include "nsCSSParser.h"
+#include "nsIDocument.h"
+
+namespace mozilla {
+namespace dom {
+
+MediaQueryList::MediaQueryList(nsIDocument *aDocument,
+ const nsAString &aMediaQueryList)
+ : mDocument(aDocument),
+ mMediaList(new nsMediaList),
+ mMatchesValid(false)
+{
+ PR_INIT_CLIST(this);
+
+ nsCSSParser parser;
+ parser.ParseMediaList(aMediaQueryList, nullptr, 0, mMediaList, false);
+}
+
+MediaQueryList::~MediaQueryList()
+{
+ if (mDocument) {
+ PR_REMOVE_LINK(this);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaQueryList)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaQueryList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallbacks)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaQueryList)
+ if (tmp->mDocument) {
+ PR_REMOVE_LINK(tmp);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+ }
+ tmp->RemoveAllListeners();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(MediaQueryList)
+
+NS_INTERFACE_MAP_BEGIN(MediaQueryList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(MediaQueryList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaQueryList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaQueryList)
+
+void
+MediaQueryList::GetMedia(nsAString &aMedia)
+{
+ mMediaList->GetText(aMedia);
+}
+
+bool
+MediaQueryList::Matches()
+{
+ if (!mMatchesValid) {
+ MOZ_ASSERT(!HasListeners(),
+ "when listeners present, must keep mMatches current");
+ RecomputeMatches();
+ }
+
+ return mMatches;
+}
+
+void
+MediaQueryList::AddListener(MediaQueryListListener& aListener)
+{
+ if (!HasListeners()) {
+ // When we have listeners, the pres context owns a reference to
+ // this. This is a cyclic reference that can only be broken by
+ // cycle collection.
+ NS_ADDREF_THIS();
+ }
+
+ if (!mMatchesValid) {
+ MOZ_ASSERT(!HasListeners(),
+ "when listeners present, must keep mMatches current");
+ RecomputeMatches();
+ }
+
+ for (uint32_t i = 0; i < mCallbacks.Length(); ++i) {
+ if (aListener == *mCallbacks[i]) {
+ // Already registered
+ return;
+ }
+ }
+
+ if (!mCallbacks.AppendElement(&aListener, fallible)) {
+ if (!HasListeners()) {
+ // Append failed; undo the AddRef above.
+ NS_RELEASE_THIS();
+ }
+ }
+}
+
+void
+MediaQueryList::RemoveListener(MediaQueryListListener& aListener)
+{
+ for (uint32_t i = 0; i < mCallbacks.Length(); ++i) {
+ if (aListener == *mCallbacks[i]) {
+ mCallbacks.RemoveElementAt(i);
+ if (!HasListeners()) {
+ // See NS_ADDREF_THIS() in AddListener.
+ NS_RELEASE_THIS();
+ }
+ break;
+ }
+ }
+}
+
+void
+MediaQueryList::RemoveAllListeners()
+{
+ bool hadListeners = HasListeners();
+ mCallbacks.Clear();
+ if (hadListeners) {
+ // See NS_ADDREF_THIS() in AddListener.
+ NS_RELEASE_THIS();
+ }
+}
+
+void
+MediaQueryList::RecomputeMatches()
+{
+ if (!mDocument) {
+ return;
+ }
+
+ if (mDocument->GetParentDocument()) {
+ // Flush frames on the parent so our prescontext will get
+ // recreated as needed.
+ mDocument->GetParentDocument()->FlushPendingNotifications(Flush_Frames);
+ // That might have killed our document, so recheck that.
+ if (!mDocument) {
+ return;
+ }
+ }
+
+ nsIPresShell* shell = mDocument->GetShell();
+ if (!shell) {
+ // XXXbz What's the right behavior here? Spec doesn't say.
+ return;
+ }
+
+ nsPresContext* presContext = shell->GetPresContext();
+ if (!presContext) {
+ // XXXbz What's the right behavior here? Spec doesn't say.
+ return;
+ }
+
+ mMatches = mMediaList->Matches(presContext, nullptr);
+ mMatchesValid = true;
+}
+
+void
+MediaQueryList::MediumFeaturesChanged(
+ nsTArray<HandleChangeData>& aListenersToNotify)
+{
+ mMatchesValid = false;
+
+ if (HasListeners()) {
+ bool oldMatches = mMatches;
+ RecomputeMatches();
+ if (mMatches != oldMatches) {
+ for (uint32_t i = 0, i_end = mCallbacks.Length(); i != i_end; ++i) {
+ HandleChangeData *d = aListenersToNotify.AppendElement(fallible);
+ if (d) {
+ d->mql = this;
+ d->callback = mCallbacks[i];
+ }
+ }
+ }
+ }
+}
+
+nsISupports*
+MediaQueryList::GetParentObject() const
+{
+ return mDocument;
+}
+
+JSObject*
+MediaQueryList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaQueryListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/style/MediaQueryList.h b/layout/style/MediaQueryList.h
new file mode 100644
index 000000000..5ba568528
--- /dev/null
+++ b/layout/style/MediaQueryList.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* 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 "prclist.h"
+#include "mozilla/Attributes.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/MediaQueryListBinding.h"
+
+class nsIDocument;
+class nsMediaList;
+
+namespace mozilla {
+namespace dom {
+
+class MediaQueryList final : public nsISupports,
+ public nsWrapperCache,
+ public PRCList
+{
+public:
+ // The caller who constructs is responsible for calling Evaluate
+ // before calling any other methods.
+ MediaQueryList(nsIDocument *aDocument,
+ const nsAString &aMediaQueryList);
+private:
+ ~MediaQueryList();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaQueryList)
+
+ nsISupports* GetParentObject() const;
+
+ struct HandleChangeData {
+ RefPtr<MediaQueryList> mql;
+ RefPtr<mozilla::dom::MediaQueryListListener> callback;
+ };
+
+ // Appends listeners that need notification to aListenersToNotify
+ void MediumFeaturesChanged(nsTArray<HandleChangeData>& aListenersToNotify);
+
+ bool HasListeners() const { return !mCallbacks.IsEmpty(); }
+
+ void RemoveAllListeners();
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL methods
+ void GetMedia(nsAString& aMedia);
+ bool Matches();
+ void AddListener(mozilla::dom::MediaQueryListListener& aListener);
+ void RemoveListener(mozilla::dom::MediaQueryListListener& aListener);
+
+private:
+ 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.
+ nsCOMPtr<nsIDocument> mDocument;
+
+ RefPtr<nsMediaList> mMediaList;
+ bool mMatches;
+ bool mMatchesValid;
+ nsTArray<RefPtr<mozilla::dom::MediaQueryListListener>> mCallbacks;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* !defined(mozilla_dom_MediaQueryList_h) */
diff --git a/layout/style/NameSpaceRule.h b/layout/style/NameSpaceRule.h
new file mode 100644
index 000000000..92d910cf5
--- /dev/null
+++ b/layout/style/NameSpaceRule.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* class for CSS @namespace rules */
+
+#ifndef mozilla_css_NameSpaceRule_h__
+#define mozilla_css_NameSpaceRule_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/Rule.h"
+
+#include "nsIDOMCSSRule.h"
+
+class nsIAtom;
+
+// IID for the NameSpaceRule class {f0b0dbe1-5031-4a21-b06a-dc141ef2af98}
+#define NS_CSS_NAMESPACE_RULE_IMPL_CID \
+{0xf0b0dbe1, 0x5031, 0x4a21, {0xb0, 0x6a, 0xdc, 0x14, 0x1e, 0xf2, 0xaf, 0x98}}
+
+
+namespace mozilla {
+namespace css {
+
+class NameSpaceRule final : public Rule,
+ public nsIDOMCSSRule
+{
+public:
+ NameSpaceRule(nsIAtom* aPrefix, const nsString& aURLSpec,
+ uint32_t aLineNumber, uint32_t aColumnNumber);
+private:
+ // for |Clone|
+ NameSpaceRule(const NameSpaceRule& aCopy);
+ ~NameSpaceRule();
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_CSS_NAMESPACE_RULE_IMPL_CID)
+
+ NS_DECL_ISUPPORTS
+
+ // Rule methods
+ DECL_STYLE_RULE_INHERIT
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<Rule> Clone() const override;
+
+ nsIAtom* GetPrefix() const { return mPrefix; }
+
+ void GetURLSpec(nsString& aURLSpec) const { aURLSpec = mURLSpec; }
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ override MOZ_MUST_OVERRIDE;
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+private:
+ nsCOMPtr<nsIAtom> mPrefix;
+ nsString mURLSpec;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(NameSpaceRule, NS_CSS_NAMESPACE_RULE_IMPL_CID)
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_NameSpaceRule_h__ */
diff --git a/layout/style/PythonCSSProps.h b/layout/style/PythonCSSProps.h
new file mode 100644
index 000000000..843654218
--- /dev/null
+++ b/layout/style/PythonCSSProps.h
@@ -0,0 +1,41 @@
+/* A file meant as input to the preprocessor only */
+
+/* DO_PROP serves as an extra level of indirection to allow expansion
+ of CSS_PROP_DOMPROP_PREFIXED */
+
+[
+
+#define PROP_STRINGIFY_INTERNAL(X) #X
+#define PROP_STRINGIFY(X) PROP_STRINGIFY_INTERNAL(X)
+
+#define DO_PROP(name, method, id, flags, pref, proptype) \
+ [ #name, #method, #id, PROP_STRINGIFY(flags), pref, proptype ],
+#define CSS_PROP(name, id, method, flags, pref, parsevariant, kwtable, \
+ stylestruct, stylestructoffset, animtype) \
+ DO_PROP(name, method, id, flags, pref, "longhand")
+#define CSS_PROP_SHORTHAND(name, id, method, flags, pref) \
+ DO_PROP(name, method, id, flags, pref, "shorthand")
+#define CSS_PROP_LOGICAL(name, id, method, flags, pref, parsevariant, kwtable, \
+ group, stylestruct, stylestructoffset, animtype) \
+ DO_PROP(name, method, id, flags, pref, "logical")
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_
+
+#include "nsCSSPropList.h"
+
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+#undef CSS_PROP_LOGICAL
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP
+
+#define CSS_PROP_ALIAS(name, id, method, pref) \
+ DO_PROP(name, method, id, 0, pref, "alias")
+
+#include "nsCSSPropAliasList.h"
+
+#undef CSS_PROP_ALIAS
+
+#undef DO_PROP
+#undef PROP_STRINGIFY
+#undef PROP_STRINGIFY_INTERNAL
+
+]
diff --git a/layout/style/Rule.h b/layout/style/Rule.h
new file mode 100644
index 000000000..7abe71174
--- /dev/null
+++ b/layout/style/Rule.h
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for all rule types in a CSS style sheet */
+
+#ifndef mozilla_css_Rule_h___
+#define mozilla_css_Rule_h___
+
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsISupports.h"
+#include "nsIDOMCSSRule.h"
+
+class nsIDocument;
+struct nsRuleData;
+template<class T> struct already_AddRefed;
+class nsHTMLCSSStyleSheet;
+
+namespace mozilla {
+namespace css {
+class GroupRule;
+
+#define DECL_STYLE_RULE_INHERIT_NO_DOMRULE \
+ /* nothing */
+
+#define DECL_STYLE_RULE_INHERIT \
+ DECL_STYLE_RULE_INHERIT_NO_DOMRULE \
+ virtual nsIDOMCSSRule* GetDOMRule() override; \
+ virtual nsIDOMCSSRule* GetExistingDOMRule() override;
+
+class Rule : public nsISupports {
+protected:
+ Rule(uint32_t aLineNumber, uint32_t aColumnNumber)
+ : mSheet(nullptr),
+ mParentRule(nullptr),
+ mLineNumber(aLineNumber),
+ mColumnNumber(aColumnNumber)
+ {
+ }
+
+ Rule(const Rule& aCopy)
+ : mSheet(aCopy.mSheet),
+ mParentRule(aCopy.mParentRule),
+ mLineNumber(aCopy.mLineNumber),
+ mColumnNumber(aCopy.mColumnNumber)
+ {
+ }
+
+ virtual ~Rule() {}
+
+public:
+
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const = 0;
+#endif
+
+ // The constants in this list must maintain the following invariants:
+ // If a rule of type N must appear before a rule of type M in stylesheets
+ // then N < M
+ // Note that CSSStyleSheet::RebuildChildList assumes that no other kinds of
+ // rules can come between two rules of type IMPORT_RULE.
+ enum {
+ UNKNOWN_RULE = 0,
+ CHARSET_RULE,
+ IMPORT_RULE,
+ NAMESPACE_RULE,
+ STYLE_RULE,
+ MEDIA_RULE,
+ FONT_FACE_RULE,
+ PAGE_RULE,
+ KEYFRAME_RULE,
+ KEYFRAMES_RULE,
+ DOCUMENT_RULE,
+ SUPPORTS_RULE,
+ FONT_FEATURE_VALUES_RULE,
+ COUNTER_STYLE_RULE
+ };
+
+ virtual int32_t GetType() const = 0;
+
+ CSSStyleSheet* GetStyleSheet() const { return mSheet; }
+
+ // Return the document the rule lives in, if any
+ nsIDocument* GetDocument() const
+ {
+ CSSStyleSheet* sheet = GetStyleSheet();
+ return sheet ? sheet->GetDocument() : nullptr;
+ }
+
+ virtual void SetStyleSheet(CSSStyleSheet* aSheet);
+
+ void SetParentRule(GroupRule* aRule) {
+ // We don't reference count this up reference. The group rule
+ // will tell us when it's going away or when we're detached from
+ // it.
+ mParentRule = aRule;
+ }
+
+ uint32_t GetLineNumber() const { return mLineNumber; }
+ uint32_t GetColumnNumber() const { return mColumnNumber; }
+
+ /**
+ * Clones |this|. Never returns nullptr.
+ */
+ virtual already_AddRefed<Rule> Clone() const = 0;
+
+ // Note that this returns null for inline style rules since they aren't
+ // supposed to have a DOM rule representation (and our code wouldn't work).
+ virtual nsIDOMCSSRule* GetDOMRule() = 0;
+
+ // Like GetDOMRule(), but won't create one if we don't have one yet
+ virtual nsIDOMCSSRule* GetExistingDOMRule() = 0;
+
+ // to implement methods on nsIDOMCSSRule
+ nsresult GetParentRule(nsIDOMCSSRule** aParentRule);
+ nsresult GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet);
+ Rule* GetCSSRule();
+
+ // This is pure virtual because all of Rule's data members are non-owning and
+ // thus measured elsewhere.
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ const MOZ_MUST_OVERRIDE = 0;
+
+protected:
+ // This is sometimes null (e.g., for style attributes).
+ CSSStyleSheet* mSheet;
+ // When the parent GroupRule is destroyed, it will call SetParentRule(nullptr)
+ // on this object. (Through SetParentRuleReference);
+ GroupRule* 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/RuleNodeCacheConditions.cpp b/layout/style/RuleNodeCacheConditions.cpp
new file mode 100644
index 000000000..2dbb6c344
--- /dev/null
+++ b/layout/style/RuleNodeCacheConditions.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 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/. */
+
+/*
+ * an object that stores the result of determining whether a style struct that
+ * was computed can be cached in the rule tree, and if so, what the cache
+ * key is
+ */
+
+#include "RuleNodeCacheConditions.h"
+
+#include "nsStyleContext.h"
+#include "WritingModes.h"
+
+using namespace mozilla;
+
+bool
+RuleNodeCacheConditions::Matches(nsStyleContext* aStyleContext) const
+{
+ MOZ_ASSERT(Cacheable());
+ if ((mBits & eHaveFontSize) &&
+ mFontSize != aStyleContext->StyleFont()->mFont.size) {
+ return false;
+ }
+ if ((mBits & eHaveWritingMode) &&
+ (GetWritingMode() != WritingMode(aStyleContext).GetBits())) {
+ return false;
+ }
+ return true;
+}
+
+#ifdef DEBUG
+void
+RuleNodeCacheConditions::List() const
+{
+ printf("{ ");
+ bool first = true;
+ if (mBits & eHaveFontSize) {
+ printf("FontSize(%d)", mFontSize);
+ first = false;
+ }
+ if (mBits & eHaveWritingMode) {
+ if (!first) {
+ printf(", ");
+ }
+ printf("WritingMode(0x%x)", GetWritingMode());
+ }
+ printf(" }");
+}
+#endif
diff --git a/layout/style/RuleNodeCacheConditions.h b/layout/style/RuleNodeCacheConditions.h
new file mode 100644
index 000000000..4b1a2349a
--- /dev/null
+++ b/layout/style/RuleNodeCacheConditions.h
@@ -0,0 +1,156 @@
+/* -*- 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/. */
+
+/*
+ * an object that stores the result of determining whether a style struct that
+ * was computed can be cached in the rule tree, and if so, what the conditions
+ * it relies on are
+ */
+
+#ifndef RuleNodeCacheConditions_h_
+#define RuleNodeCacheConditions_h_
+
+#include "mozilla/Attributes.h"
+#include "nsCoord.h"
+#include "nsTArray.h"
+
+class nsStyleContext;
+
+namespace mozilla {
+
+/**
+ * nsRuleNodeCacheConditions is used to store information about whether
+ * we can store a style struct that we're computing in the rule tree.
+ *
+ * For inherited structs (i.e., structs with inherited properties), we
+ * cache the struct in the rule tree if it does not depend on any data
+ * in the style context tree, and otherwise store it in the style
+ * context tree. This means that for inherited structs, setting any
+ * conditions is equivalent to making the struct uncacheable.
+ *
+ * For reset structs (i.e., structs with non-inherited properties), we
+ * are also able to cache structs in the rule tree conditionally on
+ * certain common conditions. For these structs, setting conditions
+ * (SetFontSizeDependency, SetWritingModeDependency) instead causes the
+ * struct to be stored, with the condition, in the rule tree.
+ */
+class RuleNodeCacheConditions
+{
+public:
+ RuleNodeCacheConditions()
+ : mFontSize(0), mBits(0) {}
+ RuleNodeCacheConditions(const RuleNodeCacheConditions& aOther)
+ : mFontSize(aOther.mFontSize), mBits(aOther.mBits) {}
+ RuleNodeCacheConditions& operator=(const RuleNodeCacheConditions& aOther)
+ {
+ mFontSize = aOther.mFontSize;
+ mBits = aOther.mBits;
+ return *this;
+ }
+ bool operator==(const RuleNodeCacheConditions& aOther) const
+ {
+ return mFontSize == aOther.mFontSize &&
+ mBits == aOther.mBits;
+ }
+ bool operator!=(const RuleNodeCacheConditions& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ bool Matches(nsStyleContext* aStyleContext) const;
+
+ /**
+ * Record that the data being computed depend on the font-size
+ * property of the element for which they are being computed.
+ *
+ * Note that we sometimes actually call this when there is a
+ * dependency on the font-size property of the parent element, but we
+ * only do so while computing inherited structs (nsStyleFont), and we
+ * only store reset structs conditionally.
+ */
+ void SetFontSizeDependency(nscoord aCoord)
+ {
+ MOZ_ASSERT(!(mBits & eHaveFontSize) || mFontSize == aCoord);
+ mFontSize = aCoord;
+ mBits |= eHaveFontSize;
+ }
+
+ /**
+ * Record that the data being computed depend on the writing mode of
+ * the element for which they are being computed, which in turn
+ * depends on its 'writing-mode', 'direction', and 'text-orientation'
+ * properties.
+ */
+ void SetWritingModeDependency(uint8_t aWritingMode)
+ {
+ MOZ_ASSERT(!(mBits & eHaveWritingMode) || GetWritingMode() == aWritingMode);
+ mBits |= (static_cast<uint64_t>(aWritingMode) << eWritingModeShift) |
+ eHaveWritingMode;
+ }
+
+ void SetUncacheable()
+ {
+ mBits |= eUncacheable;
+ }
+
+ void Clear()
+ {
+ *this = RuleNodeCacheConditions();
+ }
+
+ bool Cacheable() const
+ {
+ return !(mBits & eUncacheable);
+ }
+
+ bool CacheableWithDependencies() const
+ {
+ return !(mBits & eUncacheable) &&
+ (mBits & eHaveBitsMask) != 0;
+ }
+
+ bool CacheableWithoutDependencies() const
+ {
+ // We're not uncacheable and we have don't have a font-size or
+ // writing mode value.
+ return (mBits & eHaveBitsMask) == 0;
+ }
+
+#ifdef DEBUG
+ void List() const;
+#endif
+
+private:
+ enum {
+ eUncacheable = 0x0001,
+ eHaveFontSize = 0x0002,
+ eHaveWritingMode = 0x0004,
+ eHaveBitsMask = 0x00ff,
+ eWritingModeMask = 0xff00,
+ eWritingModeShift = 8,
+ };
+
+ uint8_t GetWritingMode() const
+ {
+ return static_cast<uint8_t>(
+ (mBits & eWritingModeMask) >> eWritingModeShift);
+ }
+
+ // The font size from which em units are derived.
+ nscoord mFontSize;
+
+ // Values in mBits:
+ // bit 0: are we set to "uncacheable"?
+ // bit 1: do we have a font size value?
+ // bit 2: do we have a writing mode value?
+ // bits 3-7: unused
+ // bits 8-15: writing mode (uint8_t)
+ // bits 16-31: unused
+ uint32_t mBits;
+};
+
+} // namespace mozilla
+
+#endif // !defined(RuleNodeCacheConditions_h_)
diff --git a/layout/style/RuleProcessorCache.cpp b/layout/style/RuleProcessorCache.cpp
new file mode 100644
index 000000000..23832c230
--- /dev/null
+++ b/layout/style/RuleProcessorCache.cpp
@@ -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/. */
+
+/*
+ * cache of re-usable nsCSSRuleProcessors for given sets of style sheets
+ */
+
+#include "RuleProcessorCache.h"
+
+#include <algorithm>
+#include "nsCSSRuleProcessor.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(RuleProcessorCache, nsIMemoryReporter)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(RuleProcessorCacheMallocSizeOf)
+
+NS_IMETHODIMP
+RuleProcessorCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ MOZ_COLLECT_REPORT(
+ "explicit/layout/rule-processor-cache", KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(RuleProcessorCacheMallocSizeOf),
+ "Memory used for cached rule processors.");
+
+ return NS_OK;
+}
+
+RuleProcessorCache::~RuleProcessorCache()
+{
+ UnregisterWeakMemoryReporter(this);
+
+ for (Entry& e : mEntries) {
+ for (DocumentEntry& de : e.mDocumentEntries) {
+ if (de.mRuleProcessor->GetExpirationState()->IsTracked()) {
+ mExpirationTracker.RemoveObject(de.mRuleProcessor);
+ }
+ de.mRuleProcessor->SetInRuleProcessorCache(false);
+ }
+ }
+}
+
+void
+RuleProcessorCache::InitMemoryReporter()
+{
+ RegisterWeakMemoryReporter(this);
+}
+
+/* static */ bool
+RuleProcessorCache::EnsureGlobal()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gShutdown) {
+ return false;
+ }
+
+ if (!gRuleProcessorCache) {
+ gRuleProcessorCache = new RuleProcessorCache;
+ gRuleProcessorCache->InitMemoryReporter();
+ }
+ return true;
+}
+
+/* static */ void
+RuleProcessorCache::RemoveSheet(CSSStyleSheet* aSheet)
+{
+ if (!EnsureGlobal()) {
+ return;
+ }
+ gRuleProcessorCache->DoRemoveSheet(aSheet);
+}
+
+#ifdef DEBUG
+/* static */ bool
+RuleProcessorCache::HasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor)
+{
+ if (!EnsureGlobal()) {
+ return false;
+ }
+ return gRuleProcessorCache->DoHasRuleProcessor(aRuleProcessor);
+}
+#endif
+
+/* static */ void
+RuleProcessorCache::RemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor)
+{
+ if (!EnsureGlobal()) {
+ return;
+ }
+ gRuleProcessorCache->DoRemoveRuleProcessor(aRuleProcessor);
+}
+
+/* static */ nsCSSRuleProcessor*
+RuleProcessorCache::GetRuleProcessor(const nsTArray<CSSStyleSheet*>& aSheets,
+ nsPresContext* aPresContext)
+{
+ if (!EnsureGlobal()) {
+ return nullptr;
+ }
+ return gRuleProcessorCache->DoGetRuleProcessor(aSheets, aPresContext);
+}
+
+/* static */ void
+RuleProcessorCache::PutRuleProcessor(
+ const nsTArray<CSSStyleSheet*>& aSheets,
+ nsTArray<css::DocumentRule*>&& aDocumentRulesInSheets,
+ const nsDocumentRuleResultCacheKey& aCacheKey,
+ nsCSSRuleProcessor* aRuleProcessor)
+{
+ if (!EnsureGlobal()) {
+ return;
+ }
+ gRuleProcessorCache->DoPutRuleProcessor(aSheets, Move(aDocumentRulesInSheets),
+ aCacheKey, aRuleProcessor);
+}
+
+/* static */ void
+RuleProcessorCache::StartTracking(nsCSSRuleProcessor* aRuleProcessor)
+{
+ if (!EnsureGlobal()) {
+ return;
+ }
+ return gRuleProcessorCache->DoStartTracking(aRuleProcessor);
+}
+
+/* static */ void
+RuleProcessorCache::StopTracking(nsCSSRuleProcessor* aRuleProcessor)
+{
+ if (!EnsureGlobal()) {
+ return;
+ }
+ return gRuleProcessorCache->DoStopTracking(aRuleProcessor);
+}
+
+void
+RuleProcessorCache::DoRemoveSheet(CSSStyleSheet* aSheet)
+{
+ Entry* last = std::remove_if(mEntries.begin(), mEntries.end(),
+ HasSheet_ThenRemoveRuleProcessors(this, aSheet));
+ mEntries.TruncateLength(last - mEntries.begin());
+}
+
+nsCSSRuleProcessor*
+RuleProcessorCache::DoGetRuleProcessor(const nsTArray<CSSStyleSheet*>& aSheets,
+ nsPresContext* aPresContext)
+{
+ for (Entry& e : mEntries) {
+ if (e.mSheets == aSheets) {
+ for (DocumentEntry& de : e.mDocumentEntries) {
+ if (de.mCacheKey.Matches(aPresContext, e.mDocumentRulesInSheets)) {
+ return de.mRuleProcessor;
+ }
+ }
+ // Entry::mSheets is unique; if we matched aSheets but didn't
+ // find a matching DocumentEntry, we won't find one later in
+ // mEntries.
+ return nullptr;
+ }
+ }
+ return nullptr;
+}
+
+void
+RuleProcessorCache::DoPutRuleProcessor(
+ const nsTArray<CSSStyleSheet*>& aSheets,
+ nsTArray<css::DocumentRule*>&& aDocumentRulesInSheets,
+ const nsDocumentRuleResultCacheKey& aCacheKey,
+ nsCSSRuleProcessor* aRuleProcessor)
+{
+ MOZ_ASSERT(!aRuleProcessor->IsInRuleProcessorCache());
+
+ Entry* entry = nullptr;
+ for (Entry& e : mEntries) {
+ if (e.mSheets == aSheets) {
+ entry = &e;
+ break;
+ }
+ }
+
+ if (!entry) {
+ entry = mEntries.AppendElement();
+ entry->mSheets = aSheets;
+ entry->mDocumentRulesInSheets = aDocumentRulesInSheets;
+ for (CSSStyleSheet* sheet : aSheets) {
+ sheet->SetInRuleProcessorCache();
+ }
+ } else {
+ MOZ_ASSERT(entry->mDocumentRulesInSheets == aDocumentRulesInSheets,
+ "DocumentRule array shouldn't have changed");
+ }
+
+#ifdef DEBUG
+ for (DocumentEntry& de : entry->mDocumentEntries) {
+ MOZ_ASSERT(de.mCacheKey != aCacheKey,
+ "should not have duplicate document cache keys");
+ }
+#endif
+
+ DocumentEntry* documentEntry = entry->mDocumentEntries.AppendElement();
+ documentEntry->mCacheKey = aCacheKey;
+ documentEntry->mRuleProcessor = aRuleProcessor;
+ aRuleProcessor->SetInRuleProcessorCache(true);
+}
+
+#ifdef DEBUG
+bool
+RuleProcessorCache::DoHasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor)
+{
+ for (Entry& e : mEntries) {
+ for (DocumentEntry& de : e.mDocumentEntries) {
+ if (de.mRuleProcessor == aRuleProcessor) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+#endif
+
+void
+RuleProcessorCache::DoRemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor)
+{
+ MOZ_ASSERT(aRuleProcessor->IsInRuleProcessorCache());
+
+ aRuleProcessor->SetInRuleProcessorCache(false);
+ mExpirationTracker.RemoveObjectIfTracked(aRuleProcessor);
+ for (Entry& e : mEntries) {
+ for (size_t i = 0; i < e.mDocumentEntries.Length(); i++) {
+ if (e.mDocumentEntries[i].mRuleProcessor == aRuleProcessor) {
+ e.mDocumentEntries.RemoveElementAt(i);
+ return;
+ }
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("should have found rule processor");
+}
+
+void
+RuleProcessorCache::DoStartTracking(nsCSSRuleProcessor* aRuleProcessor)
+{
+ mExpirationTracker.AddObject(aRuleProcessor);
+}
+
+void
+RuleProcessorCache::DoStopTracking(nsCSSRuleProcessor* aRuleProcessor)
+{
+ mExpirationTracker.RemoveObjectIfTracked(aRuleProcessor);
+}
+
+size_t
+RuleProcessorCache::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+ size_t n = aMallocSizeOf(this);
+
+ int count = 0;
+ n += mEntries.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (Entry& e : mEntries) {
+ n += e.mDocumentEntries.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (DocumentEntry& de : e.mDocumentEntries) {
+ count++;
+ n += de.mRuleProcessor->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ return n;
+}
+
+void
+RuleProcessorCache::ExpirationTracker::RemoveObjectIfTracked(
+ nsCSSRuleProcessor* aRuleProcessor)
+{
+ if (aRuleProcessor->GetExpirationState()->IsTracked()) {
+ RemoveObject(aRuleProcessor);
+ }
+}
+
+bool RuleProcessorCache::gShutdown = false;
+mozilla::StaticRefPtr<RuleProcessorCache> RuleProcessorCache::gRuleProcessorCache;
diff --git a/layout/style/RuleProcessorCache.h b/layout/style/RuleProcessorCache.h
new file mode 100644
index 000000000..7eb808fc9
--- /dev/null
+++ b/layout/style/RuleProcessorCache.h
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * cache of re-usable nsCSSRuleProcessors for given sets of style sheets
+ */
+
+#ifndef mozilla_RuleProcessorCache_h
+#define mozilla_RuleProcessorCache_h
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsExpirationTracker.h"
+#include "nsIMediaList.h"
+#include "nsIMemoryReporter.h"
+#include "nsTArray.h"
+
+class nsCSSRuleProcessor;
+namespace mozilla {
+class CSSStyleSheet;
+namespace css {
+class DocumentRule;
+} // namespace css
+} // namespace mozilla
+
+namespace mozilla {
+
+/**
+ * The RuleProcessorCache is a singleton object that caches
+ * nsCSSRuleProcessors keyed off a list of style sheets and the result of
+ * evaluating all @-moz-documents in the style sheets. nsStyleSet gets and
+ * puts nsCSSRuleProcessors from/to the RuleProcessorCache.
+ *
+ * State bits on CSSStyleSheet and nsCSSRuleProcessor track whether they are in
+ * the RuleProcessorCache. This lets us remove them from the RuleProcessorCache
+ * when they're going away.
+ */
+class RuleProcessorCache final : public nsIMemoryReporter
+{
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+public:
+ static nsCSSRuleProcessor* GetRuleProcessor(
+ const nsTArray<CSSStyleSheet*>& aSheets,
+ nsPresContext* aPresContext);
+ static void PutRuleProcessor(
+ const nsTArray<CSSStyleSheet*>& aSheets,
+ nsTArray<css::DocumentRule*>&& aDocumentRulesInSheets,
+ const nsDocumentRuleResultCacheKey& aCacheKey,
+ nsCSSRuleProcessor* aRuleProcessor);
+ static void StartTracking(nsCSSRuleProcessor* aRuleProcessor);
+ static void StopTracking(nsCSSRuleProcessor* aRuleProcessor);
+
+#ifdef DEBUG
+ static bool HasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor);
+#endif
+ static void RemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor);
+ static void RemoveSheet(CSSStyleSheet* aSheet);
+
+ static void Shutdown() { gShutdown = true; gRuleProcessorCache = nullptr; }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+private:
+ class ExpirationTracker : public nsExpirationTracker<nsCSSRuleProcessor,3>
+ {
+ public:
+ explicit ExpirationTracker(RuleProcessorCache* aCache)
+ : nsExpirationTracker<nsCSSRuleProcessor,3>(
+ 10000, "RuleProcessorCache::ExpirationTracker")
+ , mCache(aCache) {}
+
+ void RemoveObjectIfTracked(nsCSSRuleProcessor* aRuleProcessor);
+
+ virtual void NotifyExpired(nsCSSRuleProcessor* aRuleProcessor) override {
+ mCache->RemoveRuleProcessor(aRuleProcessor);
+ }
+
+ private:
+ RuleProcessorCache* mCache;
+ };
+
+ RuleProcessorCache() : mExpirationTracker(this) {}
+ ~RuleProcessorCache();
+
+ void InitMemoryReporter();
+
+ static bool EnsureGlobal();
+ static StaticRefPtr<RuleProcessorCache> gRuleProcessorCache;
+ static bool gShutdown;
+
+ void DoRemoveSheet(CSSStyleSheet* aSheet);
+ nsCSSRuleProcessor* DoGetRuleProcessor(
+ const nsTArray<CSSStyleSheet*>& aSheets,
+ nsPresContext* aPresContext);
+ void DoPutRuleProcessor(const nsTArray<CSSStyleSheet*>& aSheets,
+ nsTArray<css::DocumentRule*>&& aDocumentRulesInSheets,
+ const nsDocumentRuleResultCacheKey& aCacheKey,
+ nsCSSRuleProcessor* aRuleProcessor);
+#ifdef DEBUG
+ bool DoHasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor);
+#endif
+ void DoRemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor);
+ void DoStartTracking(nsCSSRuleProcessor* aRuleProcessor);
+ void DoStopTracking(nsCSSRuleProcessor* aRuleProcessor);
+
+ struct DocumentEntry {
+ nsDocumentRuleResultCacheKey mCacheKey;
+ RefPtr<nsCSSRuleProcessor> mRuleProcessor;
+ };
+
+ struct Entry {
+ nsTArray<CSSStyleSheet*> mSheets;
+ nsTArray<css::DocumentRule*> mDocumentRulesInSheets;
+ nsTArray<DocumentEntry> mDocumentEntries;
+ };
+
+ // Function object to test whether an Entry object has a given sheet
+ // in its mSheets array. If it does, removes all of its rule processors
+ // before returning true.
+ struct HasSheet_ThenRemoveRuleProcessors {
+ HasSheet_ThenRemoveRuleProcessors(RuleProcessorCache* aCache,
+ CSSStyleSheet* aSheet)
+ : mCache(aCache), mSheet(aSheet) {}
+ bool operator()(Entry& aEntry) {
+ if (aEntry.mSheets.Contains(mSheet)) {
+ for (DocumentEntry& de : aEntry.mDocumentEntries) {
+ de.mRuleProcessor->SetInRuleProcessorCache(false);
+ mCache->mExpirationTracker.RemoveObjectIfTracked(de.mRuleProcessor);
+ }
+ return true;
+ }
+ return false;
+ }
+ RuleProcessorCache* mCache;
+ CSSStyleSheet* mSheet;
+ };
+
+ ExpirationTracker mExpirationTracker;
+ nsTArray<Entry> mEntries;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_RuleProcessorCache_h
diff --git a/layout/style/SVGAttrAnimationRuleProcessor.cpp b/layout/style/SVGAttrAnimationRuleProcessor.cpp
new file mode 100644
index 000000000..9eb31b1b2
--- /dev/null
+++ b/layout/style/SVGAttrAnimationRuleProcessor.cpp
@@ -0,0 +1,122 @@
+/* -*- 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/.
+ */
+
+/*
+ * style rule processor for rules from SMIL Animation of SVG mapped
+ * attributes (attributes whose values are mapped into style)
+ */
+
+#include "SVGAttrAnimationRuleProcessor.h"
+#include "nsRuleProcessorData.h"
+#include "nsSVGElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+SVGAttrAnimationRuleProcessor::SVGAttrAnimationRuleProcessor()
+{
+}
+
+SVGAttrAnimationRuleProcessor::~SVGAttrAnimationRuleProcessor()
+{
+}
+
+NS_IMPL_ISUPPORTS(SVGAttrAnimationRuleProcessor, nsIStyleRuleProcessor)
+
+/* virtual */ void
+SVGAttrAnimationRuleProcessor::RulesMatching(ElementRuleProcessorData* aData)
+{
+ ElementRulesMatching(aData->mElement, aData->mRuleWalker);
+}
+
+void
+SVGAttrAnimationRuleProcessor::ElementRulesMatching(Element* aElement,
+ nsRuleWalker* aRuleWalker)
+{
+ if (aElement->IsSVGElement()) {
+ static_cast<nsSVGElement*>(aElement)->
+ WalkAnimatedContentStyleRules(aRuleWalker);
+ }
+}
+
+/* virtual */ nsRestyleHint
+SVGAttrAnimationRuleProcessor::HasStateDependentStyle(StateRuleProcessorData* aData)
+{
+ return nsRestyleHint(0);
+}
+
+/* virtual */ nsRestyleHint
+SVGAttrAnimationRuleProcessor::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData)
+{
+ return nsRestyleHint(0);
+}
+
+/* virtual */ bool
+SVGAttrAnimationRuleProcessor::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
+{
+ return false;
+}
+
+/* virtual */ nsRestyleHint
+SVGAttrAnimationRuleProcessor::HasAttributeDependentStyle(
+ AttributeRuleProcessorData* aData,
+ RestyleHintData& aRestyleHintDataResult)
+{
+ return nsRestyleHint(0);
+}
+
+/* virtual */ bool
+SVGAttrAnimationRuleProcessor::MediumFeaturesChanged(nsPresContext* aPresContext)
+{
+ return false;
+}
+
+/* virtual */ void
+SVGAttrAnimationRuleProcessor::RulesMatching(PseudoElementRuleProcessorData* aData)
+{
+ // If SMIL Animation of SVG attributes can ever target
+ // pseudo-elements, we need to adjust either
+ // nsStyleSet::RuleNodeWithReplacement or the test in
+ // ElementRestyler::RestyleSelf (added in bug 977991 patch 4) to
+ // handle such styles.
+}
+
+/* virtual */ void
+SVGAttrAnimationRuleProcessor::RulesMatching(AnonBoxRuleProcessorData* aData)
+{
+ // If SMIL Animation of SVG attributes can ever target anonymous boxes,
+ // see comment in RulesMatching(PseudoElementRuleProcessorData*).
+}
+
+#ifdef MOZ_XUL
+/* virtual */ void
+SVGAttrAnimationRuleProcessor::RulesMatching(XULTreeRuleProcessorData* aData)
+{
+ // If SMIL Animation of SVG attributes can ever target XUL tree pseudos,
+ // see comment in RulesMatching(PseudoElementRuleProcessorData*).
+}
+#endif
+
+/* virtual */ size_t
+SVGAttrAnimationRuleProcessor::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return 0; // SVGAttrAnimationRuleProcessors are charged to the DOM, not layout
+}
+
+/* virtual */ size_t
+SVGAttrAnimationRuleProcessor::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return 0; // SVGAttrAnimationRuleProcessors are charged to the DOM, not layout
+}
+
+size_t
+SVGAttrAnimationRuleProcessor::DOMSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ return n;
+}
diff --git a/layout/style/SVGAttrAnimationRuleProcessor.h b/layout/style/SVGAttrAnimationRuleProcessor.h
new file mode 100644
index 000000000..1750c761b
--- /dev/null
+++ b/layout/style/SVGAttrAnimationRuleProcessor.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * style rule processor for rules from SMIL Animation of SVG mapped
+ * attributes (attributes whose values are mapped into style)
+ */
+
+#ifndef mozilla_SVGAttrAnimationRuleProcessor_h_
+#define mozilla_SVGAttrAnimationRuleProcessor_h_
+
+#include "nsIStyleRuleProcessor.h"
+
+class nsRuleWalker;
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+} // namespace dom
+
+class SVGAttrAnimationRuleProcessor final : public nsIStyleRuleProcessor
+{
+public:
+ SVGAttrAnimationRuleProcessor();
+
+private:
+ ~SVGAttrAnimationRuleProcessor();
+
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIStyleRuleProcessor API
+ virtual void RulesMatching(ElementRuleProcessorData* aData) override;
+ virtual void RulesMatching(PseudoElementRuleProcessorData* aData) override;
+ virtual void RulesMatching(AnonBoxRuleProcessorData* aData) override;
+#ifdef MOZ_XUL
+ virtual void RulesMatching(XULTreeRuleProcessorData* aData) override;
+#endif
+ virtual nsRestyleHint HasStateDependentStyle(StateRuleProcessorData* aData) override;
+ virtual nsRestyleHint HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData) override;
+ virtual bool HasDocumentStateDependentStyle(StateRuleProcessorData* aData) override;
+ virtual nsRestyleHint
+ HasAttributeDependentStyle(AttributeRuleProcessorData* aData,
+ RestyleHintData& aRestyleHintDataResult) override;
+ virtual bool MediumFeaturesChanged(nsPresContext* aPresContext) override;
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ const MOZ_MUST_OVERRIDE override;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ const MOZ_MUST_OVERRIDE override;
+
+ size_t DOMSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // A shortcut for nsStyleSet to call RulesMatching with less setup.
+ void ElementRulesMatching(mozilla::dom::Element* aElement,
+ nsRuleWalker* aRuleWalker);
+
+private:
+ SVGAttrAnimationRuleProcessor(const SVGAttrAnimationRuleProcessor& aCopy) = delete;
+ SVGAttrAnimationRuleProcessor& operator=(const SVGAttrAnimationRuleProcessor& aCopy) = delete;
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_SVGAttrAnimationRuleProcessor_h_) */
diff --git a/layout/style/ServoBindingList.h b/layout/style/ServoBindingList.h
new file mode 100644
index 000000000..3badfc37f
--- /dev/null
+++ b/layout/style/ServoBindingList.h
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 binding functions */
+
+/* This file contains the list of all Servo binding functions. Each
+ * entry is defined as a SERVO_BINDING_FUNC macro with the following
+ * parameters:
+ * - 'name_' the name of the binding function
+ * - 'return_' the return type of the binding function
+ * and the parameter list of the function.
+ *
+ * Users of this list should define a macro
+ * SERVO_BINDING_FUNC(name_, return_, ...)
+ * before including this file.
+ */
+
+// Node data
+SERVO_BINDING_FUNC(Servo_Node_ClearNodeData, void, RawGeckoNodeBorrowed node)
+
+// Styleset and Stylesheet management
+SERVO_BINDING_FUNC(Servo_StyleSheet_Empty, RawServoStyleSheetStrong,
+ mozilla::css::SheetParsingMode parsing_mode)
+SERVO_BINDING_FUNC(Servo_StyleSheet_FromUTF8Bytes, RawServoStyleSheetStrong,
+ const nsACString* data,
+ mozilla::css::SheetParsingMode parsing_mode,
+ const nsACString* base_url,
+ ThreadSafeURIHolder* base,
+ ThreadSafeURIHolder* referrer,
+ ThreadSafePrincipalHolder* principal)
+SERVO_BINDING_FUNC(Servo_StyleSheet_AddRef, void,
+ RawServoStyleSheetBorrowed sheet)
+SERVO_BINDING_FUNC(Servo_StyleSheet_Release, void,
+ RawServoStyleSheetBorrowed sheet)
+SERVO_BINDING_FUNC(Servo_StyleSheet_HasRules, bool,
+ RawServoStyleSheetBorrowed sheet)
+SERVO_BINDING_FUNC(Servo_StyleSet_Init, RawServoStyleSetOwned)
+SERVO_BINDING_FUNC(Servo_StyleSet_Drop, void, RawServoStyleSetOwned set)
+SERVO_BINDING_FUNC(Servo_StyleSet_AppendStyleSheet, void,
+ RawServoStyleSetBorrowed set, RawServoStyleSheetBorrowed sheet)
+SERVO_BINDING_FUNC(Servo_StyleSet_PrependStyleSheet, void,
+ RawServoStyleSetBorrowed set, RawServoStyleSheetBorrowed sheet)
+SERVO_BINDING_FUNC(Servo_StyleSet_RemoveStyleSheet, void,
+ RawServoStyleSetBorrowed set, RawServoStyleSheetBorrowed sheet)
+SERVO_BINDING_FUNC(Servo_StyleSet_InsertStyleSheetBefore, void,
+ RawServoStyleSetBorrowed set, RawServoStyleSheetBorrowed sheet,
+ RawServoStyleSheetBorrowed reference)
+
+// Animations API
+SERVO_BINDING_FUNC(Servo_ParseProperty,
+ RawServoDeclarationBlockStrong,
+ const nsACString* property, const nsACString* value,
+ const nsACString* base_url, ThreadSafeURIHolder* base,
+ ThreadSafeURIHolder* referrer,
+ ThreadSafePrincipalHolder* principal)
+SERVO_BINDING_FUNC(Servo_RestyleWithAddedDeclaration,
+ ServoComputedValuesStrong,
+ RawServoDeclarationBlockBorrowed declarations,
+ ServoComputedValuesBorrowed previous_style)
+
+// Style attribute
+SERVO_BINDING_FUNC(Servo_ParseStyleAttribute, RawServoDeclarationBlockStrong,
+ const nsACString* data)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_CreateEmpty,
+ RawServoDeclarationBlockStrong)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_Clone, RawServoDeclarationBlockStrong,
+ RawServoDeclarationBlockBorrowed declarations)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_AddRef, void,
+ RawServoDeclarationBlockBorrowed declarations)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_Release, void,
+ RawServoDeclarationBlockBorrowed declarations)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_Equals, bool,
+ RawServoDeclarationBlockBorrowed a,
+ RawServoDeclarationBlockBorrowed b)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_GetCssText, void,
+ RawServoDeclarationBlockBorrowed declarations,
+ nsAString* result)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_SerializeOneValue, void,
+ RawServoDeclarationBlockBorrowed declarations,
+ nsString* buffer)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_Count, uint32_t,
+ RawServoDeclarationBlockBorrowed declarations)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_GetNthProperty, bool,
+ RawServoDeclarationBlockBorrowed declarations,
+ uint32_t index, nsAString* result)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_GetPropertyValue, void,
+ RawServoDeclarationBlockBorrowed declarations,
+ nsIAtom* property, bool is_custom, nsAString* value)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_GetPropertyIsImportant, bool,
+ RawServoDeclarationBlockBorrowed declarations,
+ nsIAtom* property, bool is_custom)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_SetProperty, bool,
+ RawServoDeclarationBlockBorrowed declarations,
+ nsIAtom* property, bool is_custom,
+ nsACString* value, bool is_important)
+SERVO_BINDING_FUNC(Servo_DeclarationBlock_RemoveProperty, void,
+ RawServoDeclarationBlockBorrowed declarations,
+ nsIAtom* property, bool is_custom)
+
+// CSS supports()
+SERVO_BINDING_FUNC(Servo_CSSSupports, bool,
+ const nsACString* name, const nsACString* value)
+
+// Computed style data
+SERVO_BINDING_FUNC(Servo_ComputedValues_Get, ServoComputedValuesStrong,
+ RawGeckoNodeBorrowed node)
+SERVO_BINDING_FUNC(Servo_ComputedValues_GetForAnonymousBox,
+ ServoComputedValuesStrong,
+ ServoComputedValuesBorrowedOrNull parent_style_or_null,
+ nsIAtom* pseudoTag, RawServoStyleSetBorrowed set)
+SERVO_BINDING_FUNC(Servo_ComputedValues_GetForPseudoElement,
+ ServoComputedValuesStrong,
+ ServoComputedValuesBorrowed parent_style,
+ RawGeckoElementBorrowed match_element, nsIAtom* pseudo_tag,
+ RawServoStyleSetBorrowed set, bool is_probe)
+SERVO_BINDING_FUNC(Servo_ComputedValues_Inherit, ServoComputedValuesStrong,
+ ServoComputedValuesBorrowedOrNull parent_style)
+SERVO_BINDING_FUNC(Servo_ComputedValues_AddRef, void,
+ ServoComputedValuesBorrowed computed_values)
+SERVO_BINDING_FUNC(Servo_ComputedValues_Release, void,
+ ServoComputedValuesBorrowed computed_values)
+
+// Initialize Servo components. Should be called exactly once at startup.
+SERVO_BINDING_FUNC(Servo_Initialize, void)
+// Shut down Servo components. Should be called exactly once at shutdown.
+SERVO_BINDING_FUNC(Servo_Shutdown, void)
+
+// Restyle hints
+SERVO_BINDING_FUNC(Servo_ComputeRestyleHint, nsRestyleHint,
+ RawGeckoElementBorrowed element, ServoElementSnapshot* snapshot,
+ RawServoStyleSetBorrowed set)
+
+// Restyle the given subtree.
+SERVO_BINDING_FUNC(Servo_RestyleSubtree, void,
+ RawGeckoNodeBorrowed node, RawServoStyleSetBorrowed set)
+
+// Style-struct management.
+#define STYLE_STRUCT(name, checkdata_cb) \
+ struct nsStyle##name; \
+ SERVO_BINDING_FUNC(Servo_GetStyle##name, const nsStyle##name*, \
+ ServoComputedValuesBorrowedOrNull computed_values)
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
diff --git a/layout/style/ServoBindingTypes.h b/layout/style/ServoBindingTypes.h
new file mode 100644
index 000000000..b315d9b49
--- /dev/null
+++ b/layout/style/ServoBindingTypes.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ServoBindingTypes_h
+#define mozilla_ServoBindingTypes_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+struct ServoComputedValues;
+struct RawServoStyleSheet;
+struct RawServoStyleSet;
+struct RawServoDeclarationBlock;
+
+namespace mozilla {
+namespace dom {
+class Element;
+class StyleChildrenIterator;
+} // namespace dom
+} // namespace mozilla
+
+class nsCSSValue;
+class nsIDocument;
+class nsINode;
+
+using mozilla::dom::StyleChildrenIterator;
+
+typedef nsINode RawGeckoNode;
+typedef mozilla::dom::Element RawGeckoElement;
+typedef nsIDocument RawGeckoDocument;
+
+// We have these helper types so that we can directly generate
+// things like &T or Borrowed<T> on the Rust side in the function, providing
+// additional safety benefits.
+//
+// FFI has a problem with templated types, so we just use raw pointers here.
+//
+// The "Borrowed" types generate &T or Borrowed<T> in the nullable case.
+//
+// The "Owned" types generate Owned<T> or OwnedOrNull<T>. Some of these
+// are Servo-managed and can be converted to Box<ServoType> on the
+// Servo side.
+//
+// The "Arc" types are Servo-managed Arc<ServoType>s, which are passed
+// over FFI as Strong<T> (which is nullable).
+// Note that T != ServoType, rather T is ArcInner<ServoType>
+#define DECL_BORROWED_REF_TYPE_FOR(type_) typedef type_ const* type_##Borrowed;
+#define DECL_NULLABLE_BORROWED_REF_TYPE_FOR(type_) typedef type_ const* type_##BorrowedOrNull;
+#define DECL_BORROWED_MUT_REF_TYPE_FOR(type_) typedef type_* type_##BorrowedMut;
+#define DECL_NULLABLE_BORROWED_MUT_REF_TYPE_FOR(type_) typedef type_* type_##BorrowedMutOrNull;
+
+#define DECL_ARC_REF_TYPE_FOR(type_) \
+ DECL_NULLABLE_BORROWED_REF_TYPE_FOR(type_) \
+ DECL_BORROWED_REF_TYPE_FOR(type_) \
+ struct MOZ_MUST_USE_TYPE type_##Strong \
+ { \
+ type_* mPtr; \
+ already_AddRefed<type_> Consume(); \
+ };
+
+#define DECL_OWNED_REF_TYPE_FOR(type_) \
+ typedef type_* type_##Owned; \
+ DECL_BORROWED_REF_TYPE_FOR(type_) \
+ DECL_BORROWED_MUT_REF_TYPE_FOR(type_)
+
+#define DECL_NULLABLE_OWNED_REF_TYPE_FOR(type_) \
+ typedef type_* type_##OwnedOrNull; \
+ DECL_NULLABLE_BORROWED_REF_TYPE_FOR(type_) \
+ DECL_NULLABLE_BORROWED_MUT_REF_TYPE_FOR(type_)
+
+DECL_ARC_REF_TYPE_FOR(ServoComputedValues)
+DECL_ARC_REF_TYPE_FOR(RawServoStyleSheet)
+DECL_ARC_REF_TYPE_FOR(RawServoDeclarationBlock)
+// This is a reference to a reference of RawServoDeclarationBlock, which
+// corresponds to Option<&Arc<RawServoDeclarationBlock>> in Servo side.
+DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawServoDeclarationBlockStrong)
+
+DECL_OWNED_REF_TYPE_FOR(RawServoStyleSet)
+DECL_NULLABLE_OWNED_REF_TYPE_FOR(StyleChildrenIterator)
+DECL_OWNED_REF_TYPE_FOR(StyleChildrenIterator)
+
+// We don't use BorrowedMut because the nodes may alias
+// Servo itself doesn't directly read or mutate these;
+// it only asks Gecko to do so. In case we wish to in
+// the future, we should ensure that things being mutated
+// are protected from noalias violations by a cell type
+DECL_BORROWED_REF_TYPE_FOR(RawGeckoNode)
+DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoNode)
+DECL_BORROWED_REF_TYPE_FOR(RawGeckoElement)
+DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoElement)
+DECL_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
+DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
+DECL_BORROWED_MUT_REF_TYPE_FOR(StyleChildrenIterator)
+DECL_BORROWED_REF_TYPE_FOR(nsCSSValue)
+DECL_BORROWED_MUT_REF_TYPE_FOR(nsCSSValue)
+
+#undef DECL_ARC_REF_TYPE_FOR
+#undef DECL_OWNED_REF_TYPE_FOR
+#undef DECL_NULLABLE_OWNED_REF_TYPE_FOR
+#undef DECL_BORROWED_REF_TYPE_FOR
+#undef DECL_NULLABLE_BORROWED_REF_TYPE_FOR
+#undef DECL_BORROWED_MUT_REF_TYPE_FOR
+#undef DECL_NULLABLE_BORROWED_MUT_REF_TYPE_FOR
+
+#define DEFINE_REFPTR_TRAITS(name_, type_) \
+ extern "C" { \
+ void Servo_##name_##_AddRef(type_##Borrowed ptr); \
+ void Servo_##name_##_Release(type_##Borrowed ptr); \
+ } \
+ 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_REFPTR_TRAITS(StyleSheet, RawServoStyleSheet)
+DEFINE_REFPTR_TRAITS(ComputedValues, ServoComputedValues)
+DEFINE_REFPTR_TRAITS(DeclarationBlock, RawServoDeclarationBlock)
+
+#undef DEFINE_REFPTR_TRAITS
+
+extern "C" void Servo_StyleSet_Drop(RawServoStyleSetOwned ptr);
+
+namespace mozilla {
+template<>
+class DefaultDelete<RawServoStyleSet>
+{
+public:
+ void operator()(RawServoStyleSet* aPtr) const
+ {
+ Servo_StyleSet_Drop(aPtr);
+ }
+};
+}
+
+#endif // mozilla_ServoBindingTypes_h
diff --git a/layout/style/ServoBindings.cpp b/layout/style/ServoBindings.cpp
new file mode 100644
index 000000000..ad34633e9
--- /dev/null
+++ b/layout/style/ServoBindings.cpp
@@ -0,0 +1,1083 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ServoBindings.h"
+
+#include "ChildIterator.h"
+#include "StyleStructContext.h"
+#include "gfxFontFamilyList.h"
+#include "nsAttrValueInlines.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsContentUtils.h"
+#include "nsDOMTokenList.h"
+#include "nsIContentInlines.h"
+#include "nsIDOMNode.h"
+#include "nsIDocument.h"
+#include "nsIFrame.h"
+#include "nsINode.h"
+#include "nsIPrincipal.h"
+#include "nsNameSpaceManager.h"
+#include "nsRuleNode.h"
+#include "nsString.h"
+#include "nsStyleStruct.h"
+#include "nsStyleUtil.h"
+#include "nsTArray.h"
+
+#include "mozilla/EventStates.h"
+#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/ServoRestyleManager.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define IMPL_STRONG_REF_TYPE_FOR(type_) \
+ already_AddRefed<type_> \
+ type_##Strong::Consume() { \
+ RefPtr<type_> result; \
+ result.swap(mPtr); \
+ return result.forget(); \
+ }
+
+IMPL_STRONG_REF_TYPE_FOR(ServoComputedValues)
+IMPL_STRONG_REF_TYPE_FOR(RawServoStyleSheet)
+IMPL_STRONG_REF_TYPE_FOR(RawServoDeclarationBlock)
+
+#undef IMPL_STRONG_REF_TYPE_FOR
+
+uint32_t
+Gecko_ChildrenCount(RawGeckoNodeBorrowed aNode)
+{
+ return aNode->GetChildCount();
+}
+
+bool
+Gecko_NodeIsElement(RawGeckoNodeBorrowed aNode)
+{
+ return aNode->IsElement();
+}
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetParentNode(RawGeckoNodeBorrowed aNode)
+{
+ return aNode->GetFlattenedTreeParentNode();
+}
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetFirstChild(RawGeckoNodeBorrowed aNode)
+{
+ return aNode->GetFirstChild();
+}
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetLastChild(RawGeckoNodeBorrowed aNode)
+{
+ return aNode->GetLastChild();
+}
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetPrevSibling(RawGeckoNodeBorrowed aNode)
+{
+ return aNode->GetPreviousSibling();
+}
+
+RawGeckoNodeBorrowedOrNull
+Gecko_GetNextSibling(RawGeckoNodeBorrowed aNode)
+{
+ return aNode->GetNextSibling();
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetParentElement(RawGeckoElementBorrowed aElement)
+{
+ nsINode* parentNode = aElement->GetFlattenedTreeParentNode();
+ return parentNode->IsElement() ? parentNode->AsElement() : nullptr;
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetFirstChildElement(RawGeckoElementBorrowed aElement)
+{
+ return aElement->GetFirstElementChild();
+}
+
+RawGeckoElementBorrowedOrNull Gecko_GetLastChildElement(RawGeckoElementBorrowed aElement)
+{
+ return aElement->GetLastElementChild();
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetPrevSiblingElement(RawGeckoElementBorrowed aElement)
+{
+ return aElement->GetPreviousElementSibling();
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetNextSiblingElement(RawGeckoElementBorrowed aElement)
+{
+ return aElement->GetNextElementSibling();
+}
+
+RawGeckoElementBorrowedOrNull
+Gecko_GetDocumentElement(RawGeckoDocumentBorrowed aDoc)
+{
+ return aDoc->GetDocumentElement();
+}
+
+StyleChildrenIteratorOwnedOrNull
+Gecko_MaybeCreateStyleChildrenIterator(RawGeckoNodeBorrowed aNode)
+{
+ if (!aNode->IsElement()) {
+ return nullptr;
+ }
+
+ const Element* el = aNode->AsElement();
+ return StyleChildrenIterator::IsNeeded(el) ? new StyleChildrenIterator(el)
+ : nullptr;
+}
+
+void
+Gecko_DropStyleChildrenIterator(StyleChildrenIteratorOwned aIterator)
+{
+ MOZ_ASSERT(aIterator);
+ delete aIterator;
+}
+
+RawGeckoNodeBorrowed
+Gecko_GetNextStyleChild(StyleChildrenIteratorBorrowedMut aIterator)
+{
+ MOZ_ASSERT(aIterator);
+ return aIterator->GetNextChild();
+}
+
+EventStates::ServoType
+Gecko_ElementState(RawGeckoElementBorrowed aElement)
+{
+ return aElement->StyleState().ServoValue();
+}
+
+bool
+Gecko_IsHTMLElementInHTMLDocument(RawGeckoElementBorrowed aElement)
+{
+ return aElement->IsHTMLElement() && aElement->OwnerDoc()->IsHTMLDocument();
+}
+
+bool
+Gecko_IsLink(RawGeckoElementBorrowed aElement)
+{
+ return nsCSSRuleProcessor::IsLink(aElement);
+}
+
+bool
+Gecko_IsTextNode(RawGeckoNodeBorrowed aNode)
+{
+ return aNode->NodeInfo()->NodeType() == nsIDOMNode::TEXT_NODE;
+}
+
+bool
+Gecko_IsVisitedLink(RawGeckoElementBorrowed aElement)
+{
+ return aElement->StyleState().HasState(NS_EVENT_STATE_VISITED);
+}
+
+bool
+Gecko_IsUnvisitedLink(RawGeckoElementBorrowed aElement)
+{
+ return aElement->StyleState().HasState(NS_EVENT_STATE_UNVISITED);
+}
+
+bool
+Gecko_IsRootElement(RawGeckoElementBorrowed aElement)
+{
+ return aElement->OwnerDoc()->GetRootElement() == aElement;
+}
+
+nsIAtom*
+Gecko_LocalName(RawGeckoElementBorrowed aElement)
+{
+ return aElement->NodeInfo()->NameAtom();
+}
+
+nsIAtom*
+Gecko_Namespace(RawGeckoElementBorrowed aElement)
+{
+ int32_t id = aElement->NodeInfo()->NamespaceID();
+ return nsContentUtils::NameSpaceManager()->NameSpaceURIAtomForServo(id);
+}
+
+nsIAtom*
+Gecko_GetElementId(RawGeckoElementBorrowed aElement)
+{
+ const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::id);
+ return attr ? attr->GetAtomValue() : nullptr;
+}
+
+// Dirtiness tracking.
+uint32_t
+Gecko_GetNodeFlags(RawGeckoNodeBorrowed aNode)
+{
+ return aNode->GetFlags();
+}
+
+void
+Gecko_SetNodeFlags(RawGeckoNodeBorrowed aNode, uint32_t aFlags)
+{
+ const_cast<nsINode*>(aNode)->SetFlags(aFlags);
+}
+
+void
+Gecko_UnsetNodeFlags(RawGeckoNodeBorrowed aNode, uint32_t aFlags)
+{
+ const_cast<nsINode*>(aNode)->UnsetFlags(aFlags);
+}
+
+nsStyleContext*
+Gecko_GetStyleContext(RawGeckoNodeBorrowed aNode, nsIAtom* aPseudoTagOrNull)
+{
+ MOZ_ASSERT(aNode->IsContent());
+ nsIFrame* relevantFrame =
+ ServoRestyleManager::FrameForPseudoElement(aNode->AsContent(),
+ aPseudoTagOrNull);
+ if (!relevantFrame) {
+ return nullptr;
+ }
+
+ return relevantFrame->StyleContext();
+}
+
+nsChangeHint
+Gecko_CalcStyleDifference(nsStyleContext* aOldStyleContext,
+ ServoComputedValuesBorrowed aComputedValues)
+{
+ MOZ_ASSERT(aOldStyleContext);
+ MOZ_ASSERT(aComputedValues);
+
+ // Pass the safe thing, which causes us to miss a potential optimization. See
+ // bug 1289863.
+ nsChangeHint forDescendants = nsChangeHint_Hints_NotHandledForDescendants;
+
+ // Eventually, we should compute things out of these flags like
+ // ElementRestyler::RestyleSelf does and pass the result to the caller to
+ // potentially halt traversal. See bug 1289868.
+ uint32_t equalStructs, samePointerStructs;
+ nsChangeHint result =
+ aOldStyleContext->CalcStyleDifference(aComputedValues,
+ forDescendants,
+ &equalStructs,
+ &samePointerStructs);
+
+ return result;
+}
+
+void
+Gecko_StoreStyleDifference(RawGeckoNodeBorrowed aNode, nsChangeHint aChangeHintToStore)
+{
+#ifdef MOZ_STYLO
+ MOZ_ASSERT(aNode->IsElement());
+ MOZ_ASSERT(aNode->IsDirtyForServo(),
+ "Change hint stored in a not-dirty node");
+
+ const Element* aElement = aNode->AsElement();
+ nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
+ if (!primaryFrame) {
+ // If there's no primary frame, that means that either this content is
+ // undisplayed (so we only need to check at the restyling phase for the
+ // display value on the element), or is a display: contents element.
+ //
+ // In this second case, we should store it in the frame constructor display
+ // contents map. Note that while this operation looks hairy, this would be
+ // thread-safe because the content should be there already (we'd only need
+ // to read the map and modify our entry).
+ //
+ // That being said, we still don't support display: contents anyway, so it's
+ // probably not worth it to do all the roundtrip just yet until we have a
+ // more concrete plan.
+ return;
+ }
+
+ if ((aChangeHintToStore & nsChangeHint_ReconstructFrame) &&
+ aNode->IsInNativeAnonymousSubtree())
+ {
+ NS_WARNING("stylo: Removing forbidden frame reconstruction hint on native "
+ "anonymous content. Fix this in bug 1297857!");
+ aChangeHintToStore &= ~nsChangeHint_ReconstructFrame;
+ }
+
+ primaryFrame->StyleContext()->StoreChangeHint(aChangeHintToStore);
+#else
+ MOZ_CRASH("stylo: Shouldn't call Gecko_StoreStyleDifference in "
+ "non-stylo build");
+#endif
+}
+
+RawServoDeclarationBlockStrongBorrowedOrNull
+Gecko_GetServoDeclarationBlock(RawGeckoElementBorrowed aElement)
+{
+ const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::style);
+ if (!attr || attr->Type() != nsAttrValue::eCSSDeclaration) {
+ return nullptr;
+ }
+ DeclarationBlock* decl = attr->GetCSSDeclarationValue();
+ if (!decl) {
+ return nullptr;
+ }
+ if (decl->IsGecko()) {
+ // XXX This can happen at least when script sets style attribute
+ // since we haven't implemented Element.style for stylo. But
+ // we may want to turn it into an assertion after that's done.
+ NS_WARNING("stylo: requesting a Gecko declaration block?");
+ return nullptr;
+ }
+ return reinterpret_cast<const RawServoDeclarationBlockStrong*>
+ (decl->AsServo()->RefRaw());
+}
+
+void
+Gecko_FillAllBackgroundLists(nsStyleImageLayers* aLayers, uint32_t aMaxLen)
+{
+ nsRuleNode::FillAllBackgroundLists(*aLayers, aMaxLen);
+}
+
+void
+Gecko_FillAllMaskLists(nsStyleImageLayers* aLayers, uint32_t aMaxLen)
+{
+ nsRuleNode::FillAllMaskLists(*aLayers, aMaxLen);
+}
+
+template <typename Implementor>
+static nsIAtom*
+AtomAttrValue(Implementor* aElement, nsIAtom* aName)
+{
+ const nsAttrValue* attr = aElement->GetParsedAttr(aName);
+ return attr ? attr->GetAtomValue() : nullptr;
+}
+
+template <typename Implementor, typename MatchFn>
+static bool
+DoMatch(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName, MatchFn aMatch)
+{
+ if (aNS) {
+ int32_t ns = nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNS,
+ aElement->IsInChromeDocument());
+ NS_ENSURE_TRUE(ns != kNameSpaceID_Unknown, false);
+ const nsAttrValue* value = aElement->GetParsedAttr(aName, ns);
+ return value && aMatch(value);
+ }
+ // No namespace means any namespace - we have to check them all. :-(
+ BorrowedAttrInfo attrInfo;
+ for (uint32_t i = 0; (attrInfo = aElement->GetAttrInfoAt(i)); ++i) {
+ if (attrInfo.mName->LocalName() != aName) {
+ continue;
+ }
+ if (aMatch(attrInfo.mValue)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename Implementor>
+static bool
+HasAttr(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName)
+{
+ auto match = [](const nsAttrValue* aValue) { return true; };
+ return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrEquals(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName, nsIAtom* aStr,
+ bool aIgnoreCase)
+{
+ auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
+ return aValue->Equals(aStr, aIgnoreCase ? eIgnoreCase : eCaseMatters);
+ };
+ return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrDashEquals(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr)
+{
+ auto match = [aStr](const nsAttrValue* aValue) {
+ nsAutoString str;
+ aValue->ToString(str);
+ const nsDefaultStringComparator c;
+ return nsStyleUtil::DashMatchCompare(str, nsDependentAtomString(aStr), c);
+ };
+ return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrIncludes(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr)
+{
+ auto match = [aStr](const nsAttrValue* aValue) {
+ nsAutoString str;
+ aValue->ToString(str);
+ const nsDefaultStringComparator c;
+ return nsStyleUtil::ValueIncludes(str, nsDependentAtomString(aStr), c);
+ };
+ return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrHasSubstring(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr)
+{
+ auto match = [aStr](const nsAttrValue* aValue) {
+ nsAutoString str;
+ aValue->ToString(str);
+ return FindInReadable(str, nsDependentAtomString(aStr));
+ };
+ return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrHasPrefix(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr)
+{
+ auto match = [aStr](const nsAttrValue* aValue) {
+ nsAutoString str;
+ aValue->ToString(str);
+ return StringBeginsWith(str, nsDependentAtomString(aStr));
+ };
+ return DoMatch(aElement, aNS, aName, match);
+}
+
+template <typename Implementor>
+static bool
+AttrHasSuffix(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr)
+{
+ auto match = [aStr](const nsAttrValue* aValue) {
+ nsAutoString str;
+ aValue->ToString(str);
+ return StringEndsWith(str, nsDependentAtomString(aStr));
+ };
+ return DoMatch(aElement, aNS, aName, match);
+}
+
+/**
+ * Gets the class or class list (if any) of the implementor. The calling
+ * convention here is rather hairy, and is optimized for getting Servo the
+ * information it needs for hot calls.
+ *
+ * The return value indicates the number of classes. If zero, neither outparam
+ * is valid. If one, the class_ outparam is filled with the atom of the class.
+ * If two or more, the classList outparam is set to point to an array of atoms
+ * representing the class list.
+ *
+ * The array is borrowed and the atoms are not addrefed. These values can be
+ * invalidated by any DOM mutation. Use them in a tight scope.
+ */
+template <typename Implementor>
+static uint32_t
+ClassOrClassList(Implementor* aElement, nsIAtom** aClass, nsIAtom*** aClassList)
+{
+ const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::_class);
+ if (!attr) {
+ return 0;
+ }
+
+ // For class values with only whitespace, Gecko just stores a string. For the
+ // purposes of the style system, there is no class in this case.
+ if (attr->Type() == nsAttrValue::eString) {
+ MOZ_ASSERT(nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
+ attr->GetStringValue()).IsEmpty());
+ return 0;
+ }
+
+ // Single tokens are generally stored as an atom. Check that case.
+ if (attr->Type() == nsAttrValue::eAtom) {
+ *aClass = attr->GetAtomValue();
+ return 1;
+ }
+
+ // At this point we should have an atom array. It is likely, but not
+ // guaranteed, that we have two or more elements in the array.
+ MOZ_ASSERT(attr->Type() == nsAttrValue::eAtomArray);
+ nsTArray<nsCOMPtr<nsIAtom>>* atomArray = attr->GetAtomArrayValue();
+ uint32_t length = atomArray->Length();
+
+ // Special case: zero elements.
+ if (length == 0) {
+ return 0;
+ }
+
+ // Special case: one element.
+ if (length == 1) {
+ *aClass = atomArray->ElementAt(0);
+ return 1;
+ }
+
+ // General case: Two or more elements.
+ //
+ // Note: We could also expose this array as an array of nsCOMPtrs, since
+ // bindgen knows what those look like, and eliminate the reinterpret_cast.
+ // But it's not obvious that that would be preferable.
+ static_assert(sizeof(nsCOMPtr<nsIAtom>) == sizeof(nsIAtom*), "Bad simplification");
+ static_assert(alignof(nsCOMPtr<nsIAtom>) == alignof(nsIAtom*), "Bad simplification");
+
+ nsCOMPtr<nsIAtom>* elements = atomArray->Elements();
+ nsIAtom** rawElements = reinterpret_cast<nsIAtom**>(elements);
+ *aClassList = rawElements;
+ return atomArray->Length();
+}
+
+#define SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_) \
+ nsIAtom* prefix_##AtomAttrValue(implementor_ aElement, nsIAtom* aName) \
+ { \
+ return AtomAttrValue(aElement, aName); \
+ } \
+ bool prefix_##HasAttr(implementor_ aElement, nsIAtom* aNS, nsIAtom* aName) \
+ { \
+ return HasAttr(aElement, aNS, aName); \
+ } \
+ bool prefix_##AttrEquals(implementor_ aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr, bool aIgnoreCase) \
+ { \
+ return AttrEquals(aElement, aNS, aName, aStr, aIgnoreCase); \
+ } \
+ bool prefix_##AttrDashEquals(implementor_ aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+ { \
+ return AttrDashEquals(aElement, aNS, aName, aStr); \
+ } \
+ bool prefix_##AttrIncludes(implementor_ aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+ { \
+ return AttrIncludes(aElement, aNS, aName, aStr); \
+ } \
+ bool prefix_##AttrHasSubstring(implementor_ aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+ { \
+ return AttrHasSubstring(aElement, aNS, aName, aStr); \
+ } \
+ bool prefix_##AttrHasPrefix(implementor_ aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+ { \
+ return AttrHasPrefix(aElement, aNS, aName, aStr); \
+ } \
+ bool prefix_##AttrHasSuffix(implementor_ aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+ { \
+ return AttrHasSuffix(aElement, aNS, aName, aStr); \
+ } \
+ uint32_t prefix_##ClassOrClassList(implementor_ aElement, nsIAtom** aClass, \
+ nsIAtom*** aClassList) \
+ { \
+ return ClassOrClassList(aElement, aClass, aClassList); \
+ }
+
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, RawGeckoElementBorrowed)
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot, ServoElementSnapshot*)
+
+#undef SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS
+
+nsIAtom*
+Gecko_Atomize(const char* aString, uint32_t aLength)
+{
+ return NS_Atomize(nsDependentCSubstring(aString, aLength)).take();
+}
+
+void
+Gecko_AddRefAtom(nsIAtom* aAtom)
+{
+ NS_ADDREF(aAtom);
+}
+
+void
+Gecko_ReleaseAtom(nsIAtom* aAtom)
+{
+ NS_RELEASE(aAtom);
+}
+
+const uint16_t*
+Gecko_GetAtomAsUTF16(nsIAtom* aAtom, uint32_t* aLength)
+{
+ static_assert(sizeof(char16_t) == sizeof(uint16_t), "Servo doesn't know what a char16_t is");
+ MOZ_ASSERT(aAtom);
+ *aLength = aAtom->GetLength();
+
+ // We need to manually cast from char16ptr_t to const char16_t* to handle the
+ // MOZ_USE_CHAR16_WRAPPER we use on WIndows.
+ return reinterpret_cast<const uint16_t*>(static_cast<const char16_t*>(aAtom->GetUTF16String()));
+}
+
+bool
+Gecko_AtomEqualsUTF8(nsIAtom* aAtom, const char* aString, uint32_t aLength)
+{
+ // XXXbholley: We should be able to do this without converting, I just can't
+ // find the right thing to call.
+ nsDependentAtomString atomStr(aAtom);
+ NS_ConvertUTF8toUTF16 inStr(nsDependentCSubstring(aString, aLength));
+ return atomStr.Equals(inStr);
+}
+
+bool
+Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* aAtom, const char* aString, uint32_t aLength)
+{
+ // XXXbholley: We should be able to do this without converting, I just can't
+ // find the right thing to call.
+ nsDependentAtomString atomStr(aAtom);
+ NS_ConvertUTF8toUTF16 inStr(nsDependentCSubstring(aString, aLength));
+ return nsContentUtils::EqualsIgnoreASCIICase(atomStr, inStr);
+}
+
+void
+Gecko_Utf8SliceToString(nsString* aString,
+ const uint8_t* aBuffer,
+ size_t aBufferLen)
+{
+ MOZ_ASSERT(aString);
+ MOZ_ASSERT(aBuffer);
+
+ aString->Truncate();
+ AppendUTF8toUTF16(Substring(reinterpret_cast<const char*>(aBuffer),
+ aBufferLen), *aString);
+}
+
+void
+Gecko_FontFamilyList_Clear(FontFamilyList* aList) {
+ aList->Clear();
+}
+
+void
+Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName)
+{
+ // Servo doesn't record whether the name was quoted or unquoted, so just
+ // assume unquoted for now.
+ FontFamilyName family;
+ aName->ToString(family.mName);
+ aList->Append(family);
+}
+
+void
+Gecko_FontFamilyList_AppendGeneric(FontFamilyList* aList, FontFamilyType aType)
+{
+ aList->Append(FontFamilyName(aType));
+}
+
+void
+Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src)
+{
+ dst->fontlist = src->fontlist;
+}
+
+void
+Gecko_SetListStyleType(nsStyleList* style_struct, uint32_t type)
+{
+ // Builtin counter styles are static and use no-op refcounting, and thus are
+ // safe to use off-main-thread.
+ style_struct->SetCounterStyle(CounterStyleManager::GetBuiltinStyle(type));
+}
+
+void
+Gecko_CopyListStyleTypeFrom(nsStyleList* dst, const nsStyleList* src)
+{
+ dst->SetCounterStyle(src->GetCounterStyle());
+}
+
+NS_IMPL_HOLDER_FFI_REFCOUNTING(nsIPrincipal, Principal)
+NS_IMPL_HOLDER_FFI_REFCOUNTING(nsIURI, URI)
+
+void
+Gecko_SetMozBinding(nsStyleDisplay* aDisplay,
+ const uint8_t* aURLString, uint32_t aURLStringLength,
+ ThreadSafeURIHolder* aBaseURI,
+ ThreadSafeURIHolder* aReferrer,
+ ThreadSafePrincipalHolder* aPrincipal)
+{
+ MOZ_ASSERT(aDisplay);
+ MOZ_ASSERT(aURLString);
+ MOZ_ASSERT(aBaseURI);
+ MOZ_ASSERT(aReferrer);
+ MOZ_ASSERT(aPrincipal);
+
+ nsString url;
+ nsDependentCSubstring urlString(reinterpret_cast<const char*>(aURLString),
+ aURLStringLength);
+ AppendUTF8toUTF16(urlString, url);
+ RefPtr<nsStringBuffer> urlBuffer = nsCSSValue::BufferFromString(url);
+
+ aDisplay->mBinding =
+ new css::URLValue(urlBuffer, do_AddRef(aBaseURI),
+ do_AddRef(aReferrer), do_AddRef(aPrincipal));
+}
+
+void
+Gecko_CopyMozBindingFrom(nsStyleDisplay* aDest, const nsStyleDisplay* aSrc)
+{
+ aDest->mBinding = aSrc->mBinding;
+}
+
+
+void
+Gecko_SetNullImageValue(nsStyleImage* aImage)
+{
+ MOZ_ASSERT(aImage);
+ aImage->SetNull();
+}
+
+void
+Gecko_SetGradientImageValue(nsStyleImage* aImage, nsStyleGradient* aGradient)
+{
+ MOZ_ASSERT(aImage);
+ aImage->SetGradientData(aGradient);
+}
+
+static already_AddRefed<nsStyleImageRequest>
+CreateStyleImageRequest(nsStyleImageRequest::Mode aModeFlags,
+ const uint8_t* aURLString, uint32_t aURLStringLength,
+ ThreadSafeURIHolder* aBaseURI,
+ ThreadSafeURIHolder* aReferrer,
+ ThreadSafePrincipalHolder* aPrincipal)
+{
+ MOZ_ASSERT(aURLString);
+ MOZ_ASSERT(aBaseURI);
+ MOZ_ASSERT(aReferrer);
+ MOZ_ASSERT(aPrincipal);
+
+ nsString url;
+ nsDependentCSubstring urlString(reinterpret_cast<const char*>(aURLString),
+ aURLStringLength);
+ AppendUTF8toUTF16(urlString, url);
+ RefPtr<nsStringBuffer> urlBuffer = nsCSSValue::BufferFromString(url);
+
+ RefPtr<nsStyleImageRequest> req =
+ new nsStyleImageRequest(aModeFlags, urlBuffer, do_AddRef(aBaseURI),
+ do_AddRef(aReferrer), do_AddRef(aPrincipal));
+ return req.forget();
+}
+
+void
+Gecko_SetUrlImageValue(nsStyleImage* aImage,
+ const uint8_t* aURLString, uint32_t aURLStringLength,
+ ThreadSafeURIHolder* aBaseURI,
+ ThreadSafeURIHolder* aReferrer,
+ ThreadSafePrincipalHolder* aPrincipal)
+{
+ RefPtr<nsStyleImageRequest> req =
+ CreateStyleImageRequest(nsStyleImageRequest::Mode::Track,
+ aURLString, aURLStringLength,
+ aBaseURI, aReferrer, aPrincipal);
+ aImage->SetImageRequest(req.forget());
+}
+
+void
+Gecko_CopyImageValueFrom(nsStyleImage* aImage, const nsStyleImage* aOther)
+{
+ MOZ_ASSERT(aImage);
+ MOZ_ASSERT(aOther);
+
+ *aImage = *aOther;
+}
+
+nsStyleGradient*
+Gecko_CreateGradient(uint8_t aShape,
+ uint8_t aSize,
+ bool aRepeating,
+ bool aLegacySyntax,
+ uint32_t aStopCount)
+{
+ nsStyleGradient* result = new nsStyleGradient();
+
+ result->mShape = aShape;
+ result->mSize = aSize;
+ result->mRepeating = aRepeating;
+ result->mLegacySyntax = aLegacySyntax;
+
+ result->mAngle.SetNoneValue();
+ result->mBgPosX.SetNoneValue();
+ result->mBgPosY.SetNoneValue();
+ result->mRadiusX.SetNoneValue();
+ result->mRadiusY.SetNoneValue();
+
+ nsStyleGradientStop dummyStop;
+ dummyStop.mLocation.SetNoneValue();
+ dummyStop.mColor = NS_RGB(0, 0, 0);
+ dummyStop.mIsInterpolationHint = 0;
+
+ for (uint32_t i = 0; i < aStopCount; i++) {
+ result->mStops.AppendElement(dummyStop);
+ }
+
+ return result;
+}
+
+void
+Gecko_SetListStyleImageNone(nsStyleList* aList)
+{
+ aList->mListStyleImage = nullptr;
+}
+
+void
+Gecko_SetListStyleImage(nsStyleList* aList,
+ const uint8_t* aURLString, uint32_t aURLStringLength,
+ ThreadSafeURIHolder* aBaseURI,
+ ThreadSafeURIHolder* aReferrer,
+ ThreadSafePrincipalHolder* aPrincipal)
+{
+ aList->mListStyleImage =
+ CreateStyleImageRequest(nsStyleImageRequest::Mode(0),
+ aURLString, aURLStringLength,
+ aBaseURI, aReferrer, aPrincipal);
+}
+
+void
+Gecko_CopyListStyleImageFrom(nsStyleList* aList, const nsStyleList* aSource)
+{
+ aList->mListStyleImage = aSource->mListStyleImage;
+}
+
+void
+Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity, size_t aElemSize)
+{
+ auto base =
+ reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator,
+ nsTArray_CopyWithMemutils>*>(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_CopyWithMemutils>*>(aArray);
+
+ base->template ShiftData<nsTArrayInfallibleAllocator>(0, base->Length(), 0,
+ aElementSize, aElementAlign);
+}
+
+void
+Gecko_ClearStyleContents(nsStyleContent* aContent)
+{
+ aContent->AllocateContents(0);
+}
+
+void
+Gecko_CopyStyleContentsFrom(nsStyleContent* aContent, const nsStyleContent* aOther)
+{
+ uint32_t count = aOther->ContentCount();
+
+ aContent->AllocateContents(count);
+
+ for (uint32_t i = 0; i < count; ++i) {
+ aContent->ContentAt(i) = aOther->ContentAt(i);
+ }
+}
+
+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);
+ }
+}
+
+void
+Gecko_ResetStyleCoord(nsStyleUnit* aUnit, nsStyleUnion* aValue)
+{
+ nsStyleCoord::Reset(*aUnit, *aValue);
+}
+
+void
+Gecko_SetStyleCoordCalcValue(nsStyleUnit* aUnit, nsStyleUnion* aValue, nsStyleCoord::CalcValue aCalc)
+{
+ // Calc units should be cleaned up first
+ MOZ_ASSERT(*aUnit != nsStyleUnit::eStyleUnit_Calc);
+ nsStyleCoord::Calc* calcRef = new nsStyleCoord::Calc();
+ calcRef->mLength = aCalc.mLength;
+ calcRef->mPercent = aCalc.mPercent;
+ calcRef->mHasPercent = aCalc.mHasPercent;
+ *aUnit = nsStyleUnit::eStyleUnit_Calc;
+ aValue->mPointer = calcRef;
+ calcRef->AddRef();
+}
+
+void
+Gecko_CopyClipPathValueFrom(mozilla::StyleClipPath* aDst, const mozilla::StyleClipPath* aSrc)
+{
+ MOZ_ASSERT(aDst);
+ MOZ_ASSERT(aSrc);
+
+ *aDst = *aSrc;
+}
+
+void
+Gecko_DestroyClipPath(mozilla::StyleClipPath* aClip)
+{
+ aClip->~StyleClipPath();
+}
+
+mozilla::StyleBasicShape*
+Gecko_NewBasicShape(mozilla::StyleBasicShapeType aType)
+{
+ RefPtr<StyleBasicShape> ptr = new mozilla::StyleBasicShape(aType);
+ return ptr.forget().take();
+}
+
+void
+Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len)
+{
+ effects->mFilters.Clear();
+ effects->mFilters.SetLength(new_len);
+}
+
+void
+Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest)
+{
+ aDest->mFilters = aSrc->mFilters;
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsStyleCoord::Calc, Calc);
+
+nsCSSShadowArray*
+Gecko_NewCSSShadowArray(uint32_t aLen)
+{
+ RefPtr<nsCSSShadowArray> arr = new(aLen) nsCSSShadowArray(aLen);
+ return arr.forget().take();
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsCSSShadowArray, CSSShadowArray);
+
+nsStyleQuoteValues*
+Gecko_NewStyleQuoteValues(uint32_t aLen)
+{
+ RefPtr<nsStyleQuoteValues> values = new nsStyleQuoteValues;
+ values->mQuotePairs.SetLength(aLen);
+ return values.forget().take();
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsStyleQuoteValues, QuoteValues);
+
+nsCSSValueSharedList*
+Gecko_NewCSSValueSharedList(uint32_t aLen)
+{
+ RefPtr<nsCSSValueSharedList> list = new nsCSSValueSharedList;
+ if (aLen == 0) {
+ return list.forget().take();
+ }
+
+ list->mHead = new nsCSSValueList;
+ nsCSSValueList* cur = list->mHead;
+ for (uint32_t i = 0; i < aLen - 1; i++) {
+ cur->mNext = new nsCSSValueList;
+ cur = cur->mNext;
+ }
+
+ return list.forget().take();
+}
+
+void
+Gecko_CSSValue_SetAbsoluteLength(nsCSSValueBorrowedMut aCSSValue, nscoord aLen)
+{
+ aCSSValue->SetIntegerCoordValue(aLen);
+}
+
+void
+Gecko_CSSValue_SetNumber(nsCSSValueBorrowedMut aCSSValue, float aNumber)
+{
+ aCSSValue->SetFloatValue(aNumber, eCSSUnit_Number);
+}
+
+void
+Gecko_CSSValue_SetKeyword(nsCSSValueBorrowedMut aCSSValue, nsCSSKeyword aKeyword)
+{
+ aCSSValue->SetIntValue(aKeyword, eCSSUnit_Enumerated);
+}
+
+void
+Gecko_CSSValue_SetPercentage(nsCSSValueBorrowedMut aCSSValue, float aPercent)
+{
+ aCSSValue->SetFloatValue(aPercent, eCSSUnit_Number);
+}
+
+void
+Gecko_CSSValue_SetAngle(nsCSSValueBorrowedMut aCSSValue, float aRadians)
+{
+ aCSSValue->SetFloatValue(aRadians, eCSSUnit_Radian);
+}
+
+void
+Gecko_CSSValue_SetCalc(nsCSSValueBorrowedMut aCSSValue, nsStyleCoord::CalcValue aCalc)
+{
+ aCSSValue->SetCalcValue(&aCalc);
+}
+
+void
+Gecko_CSSValue_SetFunction(nsCSSValueBorrowedMut aCSSValue, int32_t aLen)
+{
+ nsCSSValue::Array* arr = nsCSSValue::Array::Create(aLen);
+ aCSSValue->SetArrayValue(arr, eCSSUnit_Function);
+}
+
+nsCSSValueBorrowedMut
+Gecko_CSSValue_GetArrayItem(nsCSSValueBorrowedMut aCSSValue, int32_t aIndex)
+{
+ return &aCSSValue->GetArrayValue()->Item(aIndex);
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(nsCSSValueSharedList, CSSValueSharedList);
+
+#define STYLE_STRUCT(name, checkdata_cb) \
+ \
+void \
+Gecko_Construct_nsStyle##name(nsStyle##name* ptr) \
+{ \
+ new (ptr) nsStyle##name(StyleStructContext::ServoContext()); \
+} \
+ \
+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
+
+#ifndef MOZ_STYLO
+#define SERVO_BINDING_FUNC(name_, return_, ...) \
+ return_ name_(__VA_ARGS__) { \
+ MOZ_CRASH("stylo: shouldn't be calling " #name_ "in a non-stylo build"); \
+ }
+#include "ServoBindingList.h"
+#undef SERVO_BINDING_FUNC
+#endif
+
+#ifdef MOZ_STYLO
+const nsStyleVariables*
+Servo_GetStyleVariables(ServoComputedValuesBorrowed aComputedValues)
+{
+ // Servo can't provide us with Variables structs yet, so instead of linking
+ // to a Servo_GetStyleVariables defined in Servo we define one here that
+ // always returns the same, empty struct.
+ static nsStyleVariables variables(StyleStructContext::ServoContext());
+ return &variables;
+}
+#endif
diff --git a/layout/style/ServoBindings.h b/layout/style/ServoBindings.h
new file mode 100644
index 000000000..81ea48d5b
--- /dev/null
+++ b/layout/style/ServoBindings.h
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ServoBindings_h
+#define mozilla_ServoBindings_h
+
+#include <stdint.h>
+
+#include "mozilla/ServoTypes.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/css/SheetParsingMode.h"
+#include "nsChangeHint.h"
+#include "nsStyleStruct.h"
+
+/*
+ * API for Servo to access Gecko data structures. This file must compile as valid
+ * C code in order for the binding generator to parse it.
+ *
+ * Functions beginning with Gecko_ are implemented in Gecko and invoked from Servo.
+ * Functions beginning with Servo_ are implemented in Servo and invoked from Gecko.
+ */
+
+class nsIAtom;
+class nsIPrincipal;
+class nsIURI;
+struct nsFont;
+namespace mozilla {
+ class FontFamilyList;
+ enum FontFamilyType : uint32_t;
+}
+using mozilla::FontFamilyList;
+using mozilla::FontFamilyType;
+using mozilla::ServoElementSnapshot;
+struct nsStyleList;
+struct nsStyleImage;
+struct nsStyleGradientStop;
+class nsStyleGradient;
+class nsStyleCoord;
+struct nsStyleDisplay;
+
+#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); }
+
+#define NS_DECL_HOLDER_FFI_REFCOUNTING(class_, name_) \
+ typedef nsMainThreadPtrHolder<class_> ThreadSafe##name_##Holder; \
+ void Gecko_AddRef##name_##ArbitraryThread(ThreadSafe##name_##Holder* aPtr); \
+ void Gecko_Release##name_##ArbitraryThread(ThreadSafe##name_##Holder* aPtr);
+#define NS_IMPL_HOLDER_FFI_REFCOUNTING(class_, name_) \
+ void Gecko_AddRef##name_##ArbitraryThread(ThreadSafe##name_##Holder* aPtr) \
+ { NS_ADDREF(aPtr); } \
+ void Gecko_Release##name_##ArbitraryThread(ThreadSafe##name_##Holder* aPtr) \
+ { NS_RELEASE(aPtr); } \
+
+extern "C" {
+
+// Object refcounting.
+NS_DECL_HOLDER_FFI_REFCOUNTING(nsIPrincipal, Principal)
+NS_DECL_HOLDER_FFI_REFCOUNTING(nsIURI, URI)
+
+// DOM Traversal.
+uint32_t Gecko_ChildrenCount(RawGeckoNodeBorrowed node);
+bool Gecko_NodeIsElement(RawGeckoNodeBorrowed node);
+RawGeckoNodeBorrowedOrNull Gecko_GetParentNode(RawGeckoNodeBorrowed node);
+RawGeckoNodeBorrowedOrNull Gecko_GetFirstChild(RawGeckoNodeBorrowed node);
+RawGeckoNodeBorrowedOrNull Gecko_GetLastChild(RawGeckoNodeBorrowed node);
+RawGeckoNodeBorrowedOrNull Gecko_GetPrevSibling(RawGeckoNodeBorrowed node);
+RawGeckoNodeBorrowedOrNull Gecko_GetNextSibling(RawGeckoNodeBorrowed node);
+RawGeckoElementBorrowedOrNull Gecko_GetParentElement(RawGeckoElementBorrowed element);
+RawGeckoElementBorrowedOrNull Gecko_GetFirstChildElement(RawGeckoElementBorrowed element);
+RawGeckoElementBorrowedOrNull Gecko_GetLastChildElement(RawGeckoElementBorrowed element);
+RawGeckoElementBorrowedOrNull Gecko_GetPrevSiblingElement(RawGeckoElementBorrowed element);
+RawGeckoElementBorrowedOrNull Gecko_GetNextSiblingElement(RawGeckoElementBorrowed element);
+RawGeckoElementBorrowedOrNull Gecko_GetDocumentElement(RawGeckoDocumentBorrowed document);
+
+// 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 heap-allocated iterator for nodes that need it. If the creation
+// method returns null, Servo falls back to the aforementioned simpler (and
+// faster) sibling traversal.
+StyleChildrenIteratorOwnedOrNull Gecko_MaybeCreateStyleChildrenIterator(RawGeckoNodeBorrowed node);
+void Gecko_DropStyleChildrenIterator(StyleChildrenIteratorOwned it);
+RawGeckoNodeBorrowedOrNull Gecko_GetNextStyleChild(StyleChildrenIteratorBorrowedMut it);
+
+// Selector Matching.
+uint8_t Gecko_ElementState(RawGeckoElementBorrowed element);
+bool Gecko_IsHTMLElementInHTMLDocument(RawGeckoElementBorrowed element);
+bool Gecko_IsLink(RawGeckoElementBorrowed element);
+bool Gecko_IsTextNode(RawGeckoNodeBorrowed node);
+bool Gecko_IsVisitedLink(RawGeckoElementBorrowed element);
+bool Gecko_IsUnvisitedLink(RawGeckoElementBorrowed element);
+bool Gecko_IsRootElement(RawGeckoElementBorrowed element);
+nsIAtom* Gecko_LocalName(RawGeckoElementBorrowed element);
+nsIAtom* Gecko_Namespace(RawGeckoElementBorrowed element);
+nsIAtom* Gecko_GetElementId(RawGeckoElementBorrowed element);
+
+// Attributes.
+#define SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_) \
+ nsIAtom* prefix_##AtomAttrValue(implementor_ element, nsIAtom* attribute); \
+ bool prefix_##HasAttr(implementor_ element, nsIAtom* ns, nsIAtom* name); \
+ bool prefix_##AttrEquals(implementor_ element, nsIAtom* ns, nsIAtom* name, \
+ nsIAtom* str, bool ignoreCase); \
+ bool prefix_##AttrDashEquals(implementor_ element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ bool prefix_##AttrIncludes(implementor_ element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ bool prefix_##AttrHasSubstring(implementor_ element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ bool prefix_##AttrHasPrefix(implementor_ element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ bool prefix_##AttrHasSuffix(implementor_ element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ uint32_t prefix_##ClassOrClassList(implementor_ element, nsIAtom** class_, \
+ nsIAtom*** classList);
+
+SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, RawGeckoElementBorrowed)
+SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot,
+ ServoElementSnapshot*)
+
+#undef SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS
+
+// Style attributes.
+RawServoDeclarationBlockStrongBorrowedOrNull
+Gecko_GetServoDeclarationBlock(RawGeckoElementBorrowed element);
+
+// Atoms.
+nsIAtom* Gecko_Atomize(const char* aString, uint32_t aLength);
+void Gecko_AddRefAtom(nsIAtom* aAtom);
+void Gecko_ReleaseAtom(nsIAtom* aAtom);
+const uint16_t* Gecko_GetAtomAsUTF16(nsIAtom* aAtom, uint32_t* aLength);
+bool Gecko_AtomEqualsUTF8(nsIAtom* aAtom, const char* aString, uint32_t aLength);
+bool Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* aAtom, const char* aString, uint32_t aLength);
+
+// Strings (temporary until bug 1294742)
+void Gecko_Utf8SliceToString(nsString* aString,
+ const uint8_t* aBuffer,
+ size_t aBufferLen);
+
+// Font style
+void Gecko_FontFamilyList_Clear(FontFamilyList* aList);
+void Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName);
+void Gecko_FontFamilyList_AppendGeneric(FontFamilyList* list, FontFamilyType familyType);
+void Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src);
+
+// Counter style.
+void Gecko_SetListStyleType(nsStyleList* style_struct, uint32_t type);
+void Gecko_CopyListStyleTypeFrom(nsStyleList* dst, const nsStyleList* src);
+
+// background-image style.
+// TODO: support element() and -moz-image()
+void Gecko_SetNullImageValue(nsStyleImage* image);
+void Gecko_SetGradientImageValue(nsStyleImage* image, nsStyleGradient* gradient);
+void Gecko_SetUrlImageValue(nsStyleImage* image,
+ const uint8_t* url_bytes,
+ uint32_t url_length,
+ ThreadSafeURIHolder* base_uri,
+ ThreadSafeURIHolder* referrer,
+ ThreadSafePrincipalHolder* principal);
+void Gecko_CopyImageValueFrom(nsStyleImage* image, const nsStyleImage* other);
+
+nsStyleGradient* Gecko_CreateGradient(uint8_t shape,
+ uint8_t size,
+ bool repeating,
+ bool legacy_syntax,
+ uint32_t stops);
+
+// list-style-image style.
+void Gecko_SetListStyleImageNone(nsStyleList* style_struct);
+void Gecko_SetListStyleImage(nsStyleList* style_struct,
+ const uint8_t* string_bytes, uint32_t string_length,
+ ThreadSafeURIHolder* base_uri,
+ ThreadSafeURIHolder* referrer,
+ ThreadSafePrincipalHolder* principal);
+void Gecko_CopyListStyleImageFrom(nsStyleList* dest, const nsStyleList* src);
+
+// Display style.
+void Gecko_SetMozBinding(nsStyleDisplay* style_struct,
+ const uint8_t* string_bytes, uint32_t string_length,
+ ThreadSafeURIHolder* base_uri,
+ ThreadSafeURIHolder* referrer,
+ ThreadSafePrincipalHolder* principal);
+void Gecko_CopyMozBindingFrom(nsStyleDisplay* des, const nsStyleDisplay* src);
+
+// Dirtiness tracking.
+uint32_t Gecko_GetNodeFlags(RawGeckoNodeBorrowed node);
+void Gecko_SetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
+void Gecko_UnsetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
+
+// Incremental restyle.
+// TODO: We would avoid a few ffi calls if we decide to make an API like the
+// former CalcAndStoreStyleDifference, but that would effectively mean breaking
+// some safety guarantees in the servo side.
+//
+// Also, we might want a ComputedValues to ComputedValues API for animations?
+// Not if we do them in Gecko...
+nsStyleContext* Gecko_GetStyleContext(RawGeckoNodeBorrowed node,
+ nsIAtom* aPseudoTagOrNull);
+nsChangeHint Gecko_CalcStyleDifference(nsStyleContext* oldstyle,
+ ServoComputedValuesBorrowed newstyle);
+void Gecko_StoreStyleDifference(RawGeckoNodeBorrowed node, nsChangeHint change);
+
+// `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);
+
+// Clear the mContents field in nsStyleContent. This is needed to run the
+// destructors, otherwise we'd leak the images (though we still don't support
+// those), strings, and whatnot.
+void Gecko_ClearStyleContents(nsStyleContent* content);
+void Gecko_CopyStyleContentsFrom(nsStyleContent* content, const nsStyleContent* other);
+
+void Gecko_EnsureImageLayersLength(nsStyleImageLayers* layers, size_t len,
+ nsStyleImageLayers::LayerType layer_type);
+
+// Clean up pointer-based coordinates
+void Gecko_ResetStyleCoord(nsStyleUnit* unit, nsStyleUnion* value);
+
+// Set an nsStyleCoord to a computed `calc()` value
+void Gecko_SetStyleCoordCalcValue(nsStyleUnit* unit, nsStyleUnion* value, nsStyleCoord::CalcValue calc);
+
+void Gecko_CopyClipPathValueFrom(mozilla::StyleClipPath* dst, const mozilla::StyleClipPath* src);
+
+void Gecko_DestroyClipPath(mozilla::StyleClipPath* clip);
+mozilla::StyleBasicShape* Gecko_NewBasicShape(mozilla::StyleBasicShapeType type);
+
+void Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len);
+void Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest);
+
+void Gecko_FillAllBackgroundLists(nsStyleImageLayers* layers, uint32_t max_len);
+void Gecko_FillAllMaskLists(nsStyleImageLayers* layers, uint32_t max_len);
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsStyleCoord::Calc, Calc);
+
+nsCSSShadowArray* Gecko_NewCSSShadowArray(uint32_t len);
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsCSSShadowArray, CSSShadowArray);
+
+nsStyleQuoteValues* Gecko_NewStyleQuoteValues(uint32_t len);
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsStyleQuoteValues, QuoteValues);
+
+nsCSSValueSharedList* Gecko_NewCSSValueSharedList(uint32_t len);
+void Gecko_CSSValue_SetAbsoluteLength(nsCSSValueBorrowedMut css_value, nscoord len);
+void Gecko_CSSValue_SetNumber(nsCSSValueBorrowedMut css_value, float number);
+void Gecko_CSSValue_SetKeyword(nsCSSValueBorrowedMut css_value, nsCSSKeyword keyword);
+void Gecko_CSSValue_SetPercentage(nsCSSValueBorrowedMut css_value, float percent);
+void Gecko_CSSValue_SetAngle(nsCSSValueBorrowedMut css_value, float radians);
+void Gecko_CSSValue_SetCalc(nsCSSValueBorrowedMut css_value, nsStyleCoord::CalcValue calc);
+void Gecko_CSSValue_SetFunction(nsCSSValueBorrowedMut css_value, int32_t len);
+nsCSSValueBorrowedMut Gecko_CSSValue_GetArrayItem(nsCSSValueBorrowedMut css_value, int32_t index);
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsCSSValueSharedList, CSSValueSharedList);
+
+// Style-struct management.
+#define STYLE_STRUCT(name, checkdata_cb) \
+ void Gecko_Construct_nsStyle##name(nsStyle##name* ptr); \
+ 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
+
+#define SERVO_BINDING_FUNC(name_, return_, ...) return_ name_(__VA_ARGS__);
+#include "mozilla/ServoBindingList.h"
+#undef SERVO_BINDING_FUNC
+
+} // extern "C"
+
+#endif // mozilla_ServoBindings_h
diff --git a/layout/style/ServoDeclarationBlock.cpp b/layout/style/ServoDeclarationBlock.cpp
new file mode 100644
index 000000000..1dc691bfb
--- /dev/null
+++ b/layout/style/ServoDeclarationBlock.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ServoDeclarationBlock.h"
+
+#include "mozilla/ServoBindings.h"
+
+#include "nsCSSProps.h"
+
+namespace mozilla {
+
+/* static */ already_AddRefed<ServoDeclarationBlock>
+ServoDeclarationBlock::FromCssText(const nsAString& aCssText)
+{
+ NS_ConvertUTF16toUTF8 value(aCssText);
+ RefPtr<RawServoDeclarationBlock>
+ raw = Servo_ParseStyleAttribute(&value).Consume();
+ RefPtr<ServoDeclarationBlock> decl = new ServoDeclarationBlock(raw.forget());
+ return decl.forget();
+}
+
+/**
+ * An RAII class holding an atom for the given property.
+ */
+class MOZ_STACK_CLASS PropertyAtomHolder
+{
+public:
+ explicit PropertyAtomHolder(const nsAString& aProperty)
+ {
+ nsCSSPropertyID propID =
+ nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent);
+ if (propID == eCSSPropertyExtra_variable) {
+ mIsCustomProperty = true;
+ mAtom = NS_Atomize(
+ Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH)).take();
+ } else {
+ mIsCustomProperty = false;
+ if (propID != eCSSProperty_UNKNOWN) {
+ mAtom = nsCSSProps::AtomForProperty(propID);
+ } else {
+ mAtom = nullptr;
+ }
+ }
+ }
+
+ ~PropertyAtomHolder()
+ {
+ if (mIsCustomProperty) {
+ NS_RELEASE(mAtom);
+ }
+ }
+
+ explicit operator bool() const { return !!mAtom; }
+ nsIAtom* Atom() const { MOZ_ASSERT(mAtom); return mAtom; }
+ bool IsCustomProperty() const { return mIsCustomProperty; }
+
+private:
+ nsIAtom* mAtom;
+ bool mIsCustomProperty;
+};
+
+void
+ServoDeclarationBlock::GetPropertyValue(const nsAString& aProperty,
+ nsAString& aValue) const
+{
+ if (PropertyAtomHolder holder{aProperty}) {
+ Servo_DeclarationBlock_GetPropertyValue(
+ mRaw, holder.Atom(), holder.IsCustomProperty(), &aValue);
+ }
+}
+
+void
+ServoDeclarationBlock::GetPropertyValueByID(nsCSSPropertyID aPropID,
+ nsAString& aValue) const
+{
+ nsIAtom* atom = nsCSSProps::AtomForProperty(aPropID);
+ Servo_DeclarationBlock_GetPropertyValue(mRaw, atom, false, &aValue);
+}
+
+bool
+ServoDeclarationBlock::GetPropertyIsImportant(const nsAString& aProperty) const
+{
+ if (PropertyAtomHolder holder{aProperty}) {
+ return Servo_DeclarationBlock_GetPropertyIsImportant(
+ mRaw, holder.Atom(), holder.IsCustomProperty());
+ }
+ return false;
+}
+
+void
+ServoDeclarationBlock::RemoveProperty(const nsAString& aProperty)
+{
+ AssertMutable();
+ if (PropertyAtomHolder holder{aProperty}) {
+ Servo_DeclarationBlock_RemoveProperty(mRaw, holder.Atom(),
+ holder.IsCustomProperty());
+ }
+}
+
+void
+ServoDeclarationBlock::RemovePropertyByID(nsCSSPropertyID aPropID)
+{
+ AssertMutable();
+ nsIAtom* atom = nsCSSProps::AtomForProperty(aPropID);
+ Servo_DeclarationBlock_RemoveProperty(mRaw, atom, false);
+}
+
+} // namespace mozilla
diff --git a/layout/style/ServoDeclarationBlock.h b/layout/style/ServoDeclarationBlock.h
new file mode 100644
index 000000000..2cf7b1619
--- /dev/null
+++ b/layout/style/ServoDeclarationBlock.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ServoDeclarationBlock_h
+#define mozilla_ServoDeclarationBlock_h
+
+#include "mozilla/ServoBindings.h"
+#include "mozilla/DeclarationBlock.h"
+
+namespace mozilla {
+
+class ServoDeclarationBlock final : public DeclarationBlock
+{
+public:
+ ServoDeclarationBlock()
+ : ServoDeclarationBlock(Servo_DeclarationBlock_CreateEmpty().Consume()) {}
+
+ ServoDeclarationBlock(const ServoDeclarationBlock& aCopy)
+ : DeclarationBlock(aCopy)
+ , mRaw(Servo_DeclarationBlock_Clone(aCopy.mRaw).Consume()) {}
+
+ NS_INLINE_DECL_REFCOUNTING(ServoDeclarationBlock)
+
+ static already_AddRefed<ServoDeclarationBlock>
+ FromCssText(const nsAString& aCssText);
+
+ RawServoDeclarationBlock* Raw() const { return mRaw; }
+ RawServoDeclarationBlock* const* RefRaw() const {
+ static_assert(sizeof(RefPtr<RawServoDeclarationBlock>) ==
+ sizeof(RawServoDeclarationBlock*),
+ "RefPtr should just be a pointer");
+ return reinterpret_cast<RawServoDeclarationBlock* const*>(&mRaw);
+ }
+
+ void ToString(nsAString& aResult) const {
+ Servo_DeclarationBlock_GetCssText(mRaw, &aResult);
+ }
+
+ uint32_t Count() const {
+ return Servo_DeclarationBlock_Count(mRaw);
+ }
+ bool GetNthProperty(uint32_t aIndex, nsAString& aReturn) const {
+ aReturn.Truncate();
+ return Servo_DeclarationBlock_GetNthProperty(mRaw, aIndex, &aReturn);
+ }
+
+ void GetPropertyValue(const nsAString& aProperty, nsAString& aValue) const;
+ void GetPropertyValueByID(nsCSSPropertyID aPropID, nsAString& aValue) const;
+ void GetAuthoredPropertyValue(const nsAString& aProperty,
+ nsAString& aValue) const {
+ GetPropertyValue(aProperty, aValue);
+ }
+ bool GetPropertyIsImportant(const nsAString& aProperty) const;
+ void RemoveProperty(const nsAString& aProperty);
+ void RemovePropertyByID(nsCSSPropertyID aPropID);
+
+protected:
+ explicit ServoDeclarationBlock(
+ already_AddRefed<RawServoDeclarationBlock> aRaw)
+ : DeclarationBlock(StyleBackendType::Servo), mRaw(aRaw) {}
+
+private:
+ ~ServoDeclarationBlock() {}
+
+ RefPtr<RawServoDeclarationBlock> mRaw;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoDeclarationBlock_h
diff --git a/layout/style/ServoElementSnapshot.cpp b/layout/style/ServoElementSnapshot.cpp
new file mode 100644
index 000000000..2b1dd840d
--- /dev/null
+++ b/layout/style/ServoElementSnapshot.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 "mozilla/ServoElementSnapshot.h"
+#include "mozilla/dom/Element.h"
+#include "nsIContentInlines.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+ServoElementSnapshot::ServoElementSnapshot(Element* aElement)
+ : mContains(Flags(0))
+ , mState(0)
+ , mExplicitRestyleHint(nsRestyleHint(0))
+ , mExplicitChangeHint(nsChangeHint(0))
+{
+ mIsHTMLElementInHTMLDocument =
+ aElement->IsHTMLElement() && aElement->IsInHTMLDocument();
+ mIsInChromeDocument =
+ nsContentUtils::IsChromeDoc(aElement->OwnerDoc());
+}
+
+void
+ServoElementSnapshot::AddAttrs(Element* aElement)
+{
+ MOZ_ASSERT(aElement);
+
+ if (HasAny(Flags::Attributes)) {
+ return;
+ }
+
+ uint32_t attrCount = aElement->GetAttrCount();
+ const nsAttrName* attrName;
+ for (uint32_t i = 0; i < attrCount; ++i) {
+ attrName = aElement->GetAttrNameAt(i);
+ const nsAttrValue* attrValue =
+ aElement->GetParsedAttr(attrName->LocalName(), attrName->NamespaceID());
+ mAttrs.AppendElement(ServoAttrSnapshot(*attrName, *attrValue));
+ }
+ mContains |= Flags::Attributes;
+}
+
+} // namespace mozilla
diff --git a/layout/style/ServoElementSnapshot.h b/layout/style/ServoElementSnapshot.h
new file mode 100644
index 000000000..638b2fd31
--- /dev/null
+++ b/layout/style/ServoElementSnapshot.h
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ServoElementSnapshot_h
+#define mozilla_ServoElementSnapshot_h
+
+#include "mozilla/EventStates.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsChangeHint.h"
+#include "nsIAtom.h"
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+} // namespace dom
+
+/**
+ * A structure representing a single attribute name and value.
+ *
+ * This is pretty similar to the private nsAttrAndChildArray::InternalAttr.
+ */
+struct ServoAttrSnapshot
+{
+ nsAttrName mName;
+ nsAttrValue mValue;
+
+ ServoAttrSnapshot(const nsAttrName& aName, const nsAttrValue& aValue)
+ : mName(aName)
+ , mValue(aValue)
+ {
+ }
+};
+
+/**
+ * A bitflags enum class used to determine what data does a ServoElementSnapshot
+ * contains.
+ */
+enum class ServoElementSnapshotFlags : uint8_t
+{
+ State = 1 << 0,
+ Attributes = 1 << 1,
+ All = State | Attributes
+};
+
+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;
+ typedef EventStates::ServoType ServoStateType;
+
+public:
+ typedef ServoElementSnapshotFlags Flags;
+
+ explicit ServoElementSnapshot(Element* aElement);
+
+ bool HasAttrs() { return HasAny(Flags::Attributes); }
+
+ bool HasState() { return HasAny(Flags::State); }
+
+ /**
+ * Captures the given state (if not previously captured).
+ */
+ void AddState(EventStates aState)
+ {
+ if (!HasAny(Flags::State)) {
+ mState = aState.ServoValue();
+ mContains |= Flags::State;
+ }
+ }
+
+ /**
+ * Captures the given element attributes (if not previously captured).
+ */
+ void AddAttrs(Element* aElement);
+
+ void AddExplicitChangeHint(nsChangeHint aMinChangeHint)
+ {
+ mExplicitChangeHint |= aMinChangeHint;
+ }
+
+ void AddExplicitRestyleHint(nsRestyleHint aRestyleHint)
+ {
+ mExplicitRestyleHint |= aRestyleHint;
+ }
+
+ nsRestyleHint ExplicitRestyleHint() { return mExplicitRestyleHint; }
+
+ nsChangeHint ExplicitChangeHint() { return mExplicitChangeHint; }
+
+ /**
+ * Needed methods for attribute matching.
+ */
+ BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const
+ {
+ if (aIndex >= mAttrs.Length()) {
+ return BorrowedAttrInfo(nullptr, nullptr);
+ }
+ return BorrowedAttrInfo(&mAttrs[aIndex].mName, &mAttrs[aIndex].mValue);
+ }
+
+ const nsAttrValue* GetParsedAttr(nsIAtom* aLocalName) const
+ {
+ return GetParsedAttr(aLocalName, kNameSpaceID_None);
+ }
+
+ const nsAttrValue* GetParsedAttr(nsIAtom* aLocalName,
+ int32_t aNamespaceID) const
+ {
+ 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 HasAny(Flags aFlags) { return bool(mContains & aFlags); }
+
+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.
+ Flags mContains;
+ nsTArray<ServoAttrSnapshot> mAttrs;
+ ServoStateType mState;
+ nsRestyleHint mExplicitRestyleHint;
+ nsChangeHint mExplicitChangeHint;
+ bool mIsHTMLElementInHTMLDocument;
+ bool mIsInChromeDocument;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp
new file mode 100644
index 000000000..519d17aa8
--- /dev/null
+++ b/layout/style/ServoStyleSet.cpp
@@ -0,0 +1,479 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ServoRestyleManager.h"
+#include "mozilla/dom/ChildIterator.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSPseudoElements.h"
+#include "nsIDocumentInlines.h"
+#include "nsPrintfCString.h"
+#include "nsStyleContext.h"
+#include "nsStyleSet.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+ServoStyleSet::ServoStyleSet()
+ : mPresContext(nullptr)
+ , mRawSet(Servo_StyleSet_Init())
+ , mBatching(0)
+{
+}
+
+void
+ServoStyleSet::Init(nsPresContext* aPresContext)
+{
+ mPresContext = aPresContext;
+}
+
+void
+ServoStyleSet::BeginShutdown()
+{
+}
+
+void
+ServoStyleSet::Shutdown()
+{
+ mRawSet = nullptr;
+}
+
+bool
+ServoStyleSet::GetAuthorStyleDisabled() const
+{
+ return false;
+}
+
+nsresult
+ServoStyleSet::SetAuthorStyleDisabled(bool aStyleDisabled)
+{
+ MOZ_CRASH("stylo: not implemented");
+}
+
+void
+ServoStyleSet::BeginUpdate()
+{
+ ++mBatching;
+}
+
+nsresult
+ServoStyleSet::EndUpdate()
+{
+ MOZ_ASSERT(mBatching > 0);
+ if (--mBatching > 0) {
+ return NS_OK;
+ }
+
+ // ... do something ...
+ return NS_OK;
+}
+
+already_AddRefed<nsStyleContext>
+ServoStyleSet::ResolveStyleFor(Element* aElement,
+ nsStyleContext* aParentContext)
+{
+ return GetContext(aElement, aParentContext, nullptr,
+ CSSPseudoElementType::NotPseudo);
+}
+
+already_AddRefed<nsStyleContext>
+ServoStyleSet::GetContext(nsIContent* aContent,
+ nsStyleContext* aParentContext,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType)
+{
+ RefPtr<ServoComputedValues> computedValues =
+ Servo_ComputedValues_Get(aContent).Consume();
+ MOZ_ASSERT(computedValues);
+ return GetContext(computedValues.forget(), aParentContext, aPseudoTag, aPseudoType);
+}
+
+already_AddRefed<nsStyleContext>
+ServoStyleSet::GetContext(already_AddRefed<ServoComputedValues> aComputedValues,
+ nsStyleContext* aParentContext,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType)
+{
+ // XXXbholley: nsStyleSet does visited handling here.
+
+ // XXXbholley: Figure out the correct thing to pass here. Does this fixup
+ // duplicate something that servo already does?
+ bool skipFixup = false;
+
+ return NS_NewStyleContext(aParentContext, mPresContext, aPseudoTag,
+ aPseudoType, Move(aComputedValues), skipFixup);
+}
+
+already_AddRefed<nsStyleContext>
+ServoStyleSet::ResolveStyleFor(Element* aElement,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext)
+{
+ // aTreeMatchContext is used to speed up selector matching,
+ // but if the element already has a ServoComputedValues computed in
+ // advance, then we shouldn't need to use it.
+ return ResolveStyleFor(aElement, aParentContext);
+}
+
+already_AddRefed<nsStyleContext>
+ServoStyleSet::ResolveStyleForText(nsIContent* aTextNode,
+ nsStyleContext* aParentContext)
+{
+ MOZ_ASSERT(aTextNode && aTextNode->IsNodeOfType(nsINode::eTEXT));
+ MOZ_ASSERT(aTextNode->GetParent());
+ MOZ_ASSERT(aParentContext);
+
+ // Gecko expects text node style contexts to be like elements that match no
+ // rules: inherit the inherit structs, reset the reset structs. This is cheap
+ // enough to do on the main thread, which means that the parallel style system
+ // can avoid worrying about text nodes.
+ const ServoComputedValues* parentComputedValues =
+ aParentContext->StyleSource().AsServoComputedValues();
+ RefPtr<ServoComputedValues> computedValues =
+ Servo_ComputedValues_Inherit(parentComputedValues).Consume();
+
+ return GetContext(computedValues.forget(), aParentContext,
+ nsCSSAnonBoxes::mozText, CSSPseudoElementType::AnonBox);
+}
+
+already_AddRefed<nsStyleContext>
+ServoStyleSet::ResolveStyleForOtherNonElement(nsStyleContext* aParentContext)
+{
+ // The parent context can be null if the non-element share a style context
+ // with the root of an anonymous subtree.
+ const ServoComputedValues* parent =
+ aParentContext ? aParentContext->StyleSource().AsServoComputedValues() : nullptr;
+ RefPtr<ServoComputedValues> computedValues =
+ Servo_ComputedValues_Inherit(parent).Consume();
+ MOZ_ASSERT(computedValues);
+
+ return GetContext(computedValues.forget(), aParentContext,
+ nsCSSAnonBoxes::mozOtherNonElement,
+ CSSPseudoElementType::AnonBox);
+}
+
+already_AddRefed<nsStyleContext>
+ServoStyleSet::ResolvePseudoElementStyle(Element* aParentElement,
+ CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ Element* aPseudoElement)
+{
+ if (aPseudoElement) {
+ NS_ERROR("stylo: We don't support CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE yet");
+ }
+ MOZ_ASSERT(aParentContext);
+ MOZ_ASSERT(aType < CSSPseudoElementType::Count);
+ nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(aType);
+
+ RefPtr<ServoComputedValues> computedValues =
+ Servo_ComputedValues_GetForPseudoElement(
+ aParentContext->StyleSource().AsServoComputedValues(),
+ aParentElement, pseudoTag, mRawSet.get(), /* is_probe = */ false).Consume();
+ MOZ_ASSERT(computedValues);
+
+ return GetContext(computedValues.forget(), aParentContext, pseudoTag, aType);
+}
+
+// aFlags is an nsStyleSet flags bitfield
+already_AddRefed<nsStyleContext>
+ServoStyleSet::ResolveAnonymousBoxStyle(nsIAtom* aPseudoTag,
+ nsStyleContext* aParentContext,
+ uint32_t aFlags)
+{
+ MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag));
+
+ MOZ_ASSERT(aFlags == 0 ||
+ aFlags == nsStyleSet::eSkipParentDisplayBasedStyleFixup);
+ bool skipFixup = aFlags & nsStyleSet::eSkipParentDisplayBasedStyleFixup;
+
+ const ServoComputedValues* parentStyle =
+ aParentContext ? aParentContext->StyleSource().AsServoComputedValues()
+ : nullptr;
+ RefPtr<ServoComputedValues> computedValues =
+ Servo_ComputedValues_GetForAnonymousBox(parentStyle, aPseudoTag,
+ mRawSet.get()).Consume();
+#ifdef DEBUG
+ if (!computedValues) {
+ nsString pseudo;
+ aPseudoTag->ToString(pseudo);
+ NS_ERROR(nsPrintfCString("stylo: could not get anon-box: %s",
+ NS_ConvertUTF16toUTF8(pseudo).get()).get());
+ MOZ_CRASH();
+ }
+#endif
+
+ return NS_NewStyleContext(aParentContext, mPresContext, aPseudoTag,
+ CSSPseudoElementType::AnonBox,
+ computedValues.forget(), skipFixup);
+}
+
+// manage the set of style sheets in the style set
+nsresult
+ServoStyleSet::AppendStyleSheet(SheetType aType,
+ ServoStyleSheet* aSheet)
+{
+ MOZ_ASSERT(aSheet);
+ MOZ_ASSERT(aSheet->IsApplicable());
+ MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType));
+
+ mSheets[aType].RemoveElement(aSheet);
+ mSheets[aType].AppendElement(aSheet);
+
+ // Maintain a mirrored list of sheets on the servo side.
+ Servo_StyleSet_AppendStyleSheet(mRawSet.get(), aSheet->RawSheet());
+
+ return NS_OK;
+}
+
+nsresult
+ServoStyleSet::PrependStyleSheet(SheetType aType,
+ ServoStyleSheet* aSheet)
+{
+ MOZ_ASSERT(aSheet);
+ MOZ_ASSERT(aSheet->IsApplicable());
+ MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType));
+
+ mSheets[aType].RemoveElement(aSheet);
+ mSheets[aType].InsertElementAt(0, aSheet);
+
+ // Maintain a mirrored list of sheets on the servo side.
+ Servo_StyleSet_PrependStyleSheet(mRawSet.get(), aSheet->RawSheet());
+
+ return NS_OK;
+}
+
+nsresult
+ServoStyleSet::RemoveStyleSheet(SheetType aType,
+ ServoStyleSheet* aSheet)
+{
+ MOZ_ASSERT(aSheet);
+ MOZ_ASSERT(aSheet->IsApplicable());
+ MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType));
+
+ mSheets[aType].RemoveElement(aSheet);
+
+ // Maintain a mirrored list of sheets on the servo side.
+ Servo_StyleSet_RemoveStyleSheet(mRawSet.get(), aSheet->RawSheet());
+
+ return NS_OK;
+}
+
+nsresult
+ServoStyleSet::ReplaceSheets(SheetType aType,
+ const nsTArray<RefPtr<ServoStyleSheet>>& aNewSheets)
+{
+ // Gecko uses a two-dimensional array keyed by sheet type, whereas Servo
+ // stores a flattened list. This makes ReplaceSheets a pretty clunky thing
+ // to express. If the need ever arises, we can easily make this more efficent,
+ // probably by aligning the representations better between engines.
+
+ for (ServoStyleSheet* sheet : mSheets[aType]) {
+ Servo_StyleSet_RemoveStyleSheet(mRawSet.get(), sheet->RawSheet());
+ }
+
+ mSheets[aType].Clear();
+ mSheets[aType].AppendElements(aNewSheets);
+
+ for (ServoStyleSheet* sheet : mSheets[aType]) {
+ Servo_StyleSet_AppendStyleSheet(mRawSet.get(), sheet->RawSheet());
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ServoStyleSet::InsertStyleSheetBefore(SheetType aType,
+ ServoStyleSheet* aNewSheet,
+ ServoStyleSheet* aReferenceSheet)
+{
+ MOZ_ASSERT(aNewSheet);
+ MOZ_ASSERT(aReferenceSheet);
+ MOZ_ASSERT(aNewSheet->IsApplicable());
+
+ mSheets[aType].RemoveElement(aNewSheet);
+ size_t idx = mSheets[aType].IndexOf(aReferenceSheet);
+ if (idx == mSheets[aType].NoIndex) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mSheets[aType].InsertElementAt(idx, aNewSheet);
+
+ // Maintain a mirrored list of sheets on the servo side.
+ Servo_StyleSet_InsertStyleSheetBefore(mRawSet.get(), aNewSheet->RawSheet(),
+ aReferenceSheet->RawSheet());
+
+ return NS_OK;
+}
+
+int32_t
+ServoStyleSet::SheetCount(SheetType aType) const
+{
+ MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType));
+ return mSheets[aType].Length();
+}
+
+ServoStyleSheet*
+ServoStyleSet::StyleSheetAt(SheetType aType,
+ int32_t aIndex) const
+{
+ MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType));
+ return mSheets[aType][aIndex];
+}
+
+nsresult
+ServoStyleSet::RemoveDocStyleSheet(ServoStyleSheet* aSheet)
+{
+ return RemoveStyleSheet(SheetType::Doc, aSheet);
+}
+
+nsresult
+ServoStyleSet::AddDocStyleSheet(ServoStyleSheet* aSheet,
+ nsIDocument* aDocument)
+{
+ RefPtr<StyleSheet> strong(aSheet);
+
+ mSheets[SheetType::Doc].RemoveElement(aSheet);
+
+ size_t index =
+ aDocument->FindDocStyleSheetInsertionPoint(mSheets[SheetType::Doc], aSheet);
+ mSheets[SheetType::Doc].InsertElementAt(index, aSheet);
+
+ // Maintain a mirrored list of sheets on the servo side.
+ ServoStyleSheet* followingSheet =
+ mSheets[SheetType::Doc].SafeElementAt(index + 1);
+ if (followingSheet) {
+ Servo_StyleSet_InsertStyleSheetBefore(mRawSet.get(), aSheet->RawSheet(),
+ followingSheet->RawSheet());
+ } else {
+ Servo_StyleSet_AppendStyleSheet(mRawSet.get(), aSheet->RawSheet());
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsStyleContext>
+ServoStyleSet::ProbePseudoElementStyle(Element* aParentElement,
+ CSSPseudoElementType aType,
+ nsStyleContext* aParentContext)
+{
+ MOZ_ASSERT(aParentContext);
+ MOZ_ASSERT(aType < CSSPseudoElementType::Count);
+ nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(aType);
+
+ RefPtr<ServoComputedValues> computedValues =
+ Servo_ComputedValues_GetForPseudoElement(
+ aParentContext->StyleSource().AsServoComputedValues(),
+ aParentElement, pseudoTag, mRawSet.get(), /* is_probe = */ true).Consume();
+
+ if (!computedValues) {
+ return nullptr;
+ }
+
+ // For :before and :after pseudo-elements, having display: none or no
+ // 'content' property is equivalent to not having the pseudo-element
+ // at all.
+ if (computedValues &&
+ (pseudoTag == nsCSSPseudoElements::before ||
+ pseudoTag == nsCSSPseudoElements::after)) {
+ const nsStyleDisplay *display = Servo_GetStyleDisplay(computedValues);
+ const nsStyleContent *content = Servo_GetStyleContent(computedValues);
+ // XXXldb What is contentCount for |content: ""|?
+ if (display->mDisplay == StyleDisplay::None ||
+ content->ContentCount() == 0) {
+ return nullptr;
+ }
+ }
+
+ return GetContext(computedValues.forget(), aParentContext, pseudoTag, aType);
+}
+
+already_AddRefed<nsStyleContext>
+ServoStyleSet::ProbePseudoElementStyle(Element* aParentElement,
+ CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext,
+ Element* aPseudoElement)
+{
+ if (aPseudoElement) {
+ NS_ERROR("stylo: We don't support CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE yet");
+ }
+ return ProbePseudoElementStyle(aParentElement, aType, aParentContext);
+}
+
+nsRestyleHint
+ServoStyleSet::HasStateDependentStyle(dom::Element* aElement,
+ EventStates aStateMask)
+{
+ NS_WARNING("stylo: HasStateDependentStyle always returns zero!");
+ return nsRestyleHint(0);
+}
+
+nsRestyleHint
+ServoStyleSet::HasStateDependentStyle(dom::Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ dom::Element* aPseudoElement,
+ EventStates aStateMask)
+{
+ NS_WARNING("stylo: HasStateDependentStyle always returns zero!");
+ return nsRestyleHint(0);
+}
+
+nsRestyleHint
+ServoStyleSet::ComputeRestyleHint(dom::Element* aElement,
+ ServoElementSnapshot* aSnapshot)
+{
+ return Servo_ComputeRestyleHint(aElement, aSnapshot, mRawSet.get());
+}
+
+static void
+ClearDirtyBits(nsIContent* aContent)
+{
+ bool traverseDescendants = aContent->HasDirtyDescendantsForServo();
+ aContent->UnsetIsDirtyAndHasDirtyDescendantsForServo();
+ if (!traverseDescendants) {
+ return;
+ }
+
+ StyleChildrenIterator it(aContent);
+ for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+ ClearDirtyBits(n);
+ }
+}
+
+void
+ServoStyleSet::StyleDocument(bool aLeaveDirtyBits)
+{
+ // Grab the root.
+ nsIDocument* doc = mPresContext->Document();
+ nsIContent* root = doc->GetRootElement();
+ MOZ_ASSERT(root);
+
+ // Restyle the document, clearing the dirty bits if requested.
+ Servo_RestyleSubtree(root, mRawSet.get());
+ if (!aLeaveDirtyBits) {
+ ClearDirtyBits(root);
+ doc->UnsetHasDirtyDescendantsForServo();
+ }
+}
+
+void
+ServoStyleSet::StyleNewSubtree(nsIContent* aContent)
+{
+ MOZ_ASSERT(aContent->IsDirtyForServo());
+ if (aContent->IsElement() || aContent->IsNodeOfType(nsINode::eTEXT)) {
+ Servo_RestyleSubtree(aContent, mRawSet.get());
+ }
+ ClearDirtyBits(aContent);
+}
+
+void
+ServoStyleSet::StyleNewChildren(nsIContent* aParent)
+{
+ MOZ_ASSERT(aParent->HasDirtyDescendantsForServo());
+ Servo_RestyleSubtree(aParent, mRawSet.get());
+ ClearDirtyBits(aParent);
+}
diff --git a/layout/style/ServoStyleSet.h b/layout/style/ServoStyleSet.h
new file mode 100644
index 000000000..26b515afd
--- /dev/null
+++ b/layout/style/ServoStyleSet.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 mozilla_ServoStyleSet_h
+#define mozilla_ServoStyleSet_h
+
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/SheetType.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCSSPseudoElements.h"
+#include "nsChangeHint.h"
+#include "nsIAtom.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+class CSSStyleSheet;
+class ServoRestyleManager;
+class ServoStyleSheet;
+} // namespace mozilla
+class nsIDocument;
+class nsStyleContext;
+class nsPresContext;
+struct TreeMatchContext;
+
+namespace mozilla {
+
+/**
+ * The set of style sheets that apply to a document, backed by a Servo
+ * Stylist. A ServoStyleSet contains ServoStyleSheets.
+ */
+class ServoStyleSet
+{
+ friend class ServoRestyleManager;
+public:
+ ServoStyleSet();
+
+ void Init(nsPresContext* aPresContext);
+ void BeginShutdown();
+ void Shutdown();
+
+ bool GetAuthorStyleDisabled() const;
+ nsresult SetAuthorStyleDisabled(bool aStyleDisabled);
+
+ void BeginUpdate();
+ nsresult EndUpdate();
+
+ already_AddRefed<nsStyleContext>
+ ResolveStyleFor(dom::Element* aElement,
+ nsStyleContext* aParentContext);
+
+ already_AddRefed<nsStyleContext>
+ ResolveStyleFor(dom::Element* aElement,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext);
+
+ already_AddRefed<nsStyleContext>
+ ResolveStyleForText(nsIContent* aTextNode,
+ nsStyleContext* aParentContext);
+
+ already_AddRefed<nsStyleContext>
+ ResolveStyleForOtherNonElement(nsStyleContext* aParentContext);
+
+ already_AddRefed<nsStyleContext>
+ ResolvePseudoElementStyle(dom::Element* aParentElement,
+ mozilla::CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ dom::Element* aPseudoElement);
+
+ // aFlags is an nsStyleSet flags bitfield
+ already_AddRefed<nsStyleContext>
+ ResolveAnonymousBoxStyle(nsIAtom* aPseudoTag, nsStyleContext* aParentContext,
+ uint32_t aFlags = 0);
+
+ // manage the set of style sheets in the style set
+ nsresult AppendStyleSheet(SheetType aType, ServoStyleSheet* aSheet);
+ nsresult PrependStyleSheet(SheetType aType, ServoStyleSheet* aSheet);
+ nsresult RemoveStyleSheet(SheetType aType, ServoStyleSheet* aSheet);
+ nsresult ReplaceSheets(SheetType aType,
+ const nsTArray<RefPtr<ServoStyleSheet>>& aNewSheets);
+ nsresult InsertStyleSheetBefore(SheetType aType,
+ ServoStyleSheet* aNewSheet,
+ ServoStyleSheet* aReferenceSheet);
+
+ int32_t SheetCount(SheetType aType) const;
+ ServoStyleSheet* StyleSheetAt(SheetType aType, int32_t aIndex) const;
+
+ nsresult RemoveDocStyleSheet(ServoStyleSheet* aSheet);
+ nsresult AddDocStyleSheet(ServoStyleSheet* aSheet, nsIDocument* aDocument);
+
+ // check whether there is ::before/::after style for an element
+ already_AddRefed<nsStyleContext>
+ ProbePseudoElementStyle(dom::Element* aParentElement,
+ mozilla::CSSPseudoElementType aType,
+ nsStyleContext* aParentContext);
+
+ already_AddRefed<nsStyleContext>
+ ProbePseudoElementStyle(dom::Element* aParentElement,
+ mozilla::CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext,
+ dom::Element* aPseudoElement = nullptr);
+
+ // Test if style is dependent on content state
+ nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
+ EventStates aStateMask);
+ nsRestyleHint HasStateDependentStyle(
+ dom::Element* aElement, mozilla::CSSPseudoElementType aPseudoType,
+ dom::Element* aPseudoElement, EventStates aStateMask);
+
+ /**
+ * Computes a restyle hint given a element and a previous element snapshot.
+ */
+ nsRestyleHint ComputeRestyleHint(dom::Element* aElement,
+ ServoElementSnapshot* aSnapshot);
+
+ /**
+ * Performs a Servo traversal to compute style for all dirty nodes in the
+ * document. The root element must be non-null.
+ *
+ * If aLeaveDirtyBits is true, the dirty/dirty-descendant bits are not
+ * cleared.
+ */
+ void StyleDocument(bool aLeaveDirtyBits);
+
+ /**
+ * Eagerly styles a subtree of dirty nodes that were just appended to the
+ * tree. This is used in situations where we need the style immediately and
+ * cannot wait for a future batch restyle.
+ *
+ * The subtree must have the root dirty bit set, which currently gets
+ * propagated to all descendants. The dirty bits are cleared before
+ * returning.
+ */
+ void StyleNewSubtree(nsIContent* aContent);
+
+ /**
+ * Like the above, but does not assume that the root node is dirty. When
+ * appending multiple children to a potentially-non-dirty node, it's
+ * preferable to call StyleNewChildren on the node rather than making multiple
+ * calls to StyleNewSubtree on each child, since it allows for more
+ * parallelism.
+ */
+ void StyleNewChildren(nsIContent* aParent);
+
+private:
+ already_AddRefed<nsStyleContext> GetContext(already_AddRefed<ServoComputedValues>,
+ nsStyleContext* aParentContext,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType);
+
+ already_AddRefed<nsStyleContext> GetContext(nsIContent* aContent,
+ nsStyleContext* aParentContext,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType);
+
+ nsPresContext* mPresContext;
+ UniquePtr<RawServoStyleSet> mRawSet;
+ EnumeratedArray<SheetType, SheetType::Count,
+ nsTArray<RefPtr<ServoStyleSheet>>> mSheets;
+ int32_t mBatching;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoStyleSet_h
diff --git a/layout/style/ServoStyleSheet.cpp b/layout/style/ServoStyleSheet.cpp
new file mode 100644
index 000000000..cfeae20d2
--- /dev/null
+++ b/layout/style/ServoStyleSheet.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/ServoStyleSheet.h"
+#include "mozilla/StyleBackendType.h"
+
+namespace mozilla {
+
+ServoStyleSheet::ServoStyleSheet(css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode,
+ net::ReferrerPolicy aReferrerPolicy,
+ const dom::SRIMetadata& aIntegrity)
+ : StyleSheet(StyleBackendType::Servo, aParsingMode)
+ , mSheetInfo(aCORSMode, aReferrerPolicy, aIntegrity)
+{
+}
+
+ServoStyleSheet::~ServoStyleSheet()
+{
+ DropSheet();
+}
+
+bool
+ServoStyleSheet::HasRules() const
+{
+ return mSheet && Servo_StyleSheet_HasRules(mSheet);
+}
+
+void
+ServoStyleSheet::SetOwningDocument(nsIDocument* aDocument)
+{
+ // XXXheycam: Traverse to child ServoStyleSheets to set this, like
+ // CSSStyleSheet::SetOwningDocument does.
+
+ mDocument = aDocument;
+}
+
+ServoStyleSheet*
+ServoStyleSheet::GetParentSheet() const
+{
+ // XXXheycam: When we implement support for child sheets, we'll have
+ // to fix SetOwningDocument to propagate the owning document down
+ // to the children.
+ MOZ_CRASH("stylo: not implemented");
+}
+
+void
+ServoStyleSheet::AppendStyleSheet(ServoStyleSheet* aSheet)
+{
+ // XXXheycam: When we implement support for child sheets, we'll have
+ // to fix SetOwningDocument to propagate the owning document down
+ // to the children.
+ MOZ_CRASH("stylo: not implemented");
+}
+
+nsresult
+ServoStyleSheet::ParseSheet(const nsAString& aInput,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ uint32_t aLineNumber)
+{
+ DropSheet();
+
+ RefPtr<ThreadSafeURIHolder> base = new ThreadSafeURIHolder(aBaseURI);
+ RefPtr<ThreadSafeURIHolder> referrer = new ThreadSafeURIHolder(aSheetURI);
+ RefPtr<ThreadSafePrincipalHolder> principal =
+ new ThreadSafePrincipalHolder(aSheetPrincipal);
+
+ nsCString baseString;
+ nsresult rv = aBaseURI->GetSpec(baseString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF16toUTF8 input(aInput);
+ mSheet = Servo_StyleSheet_FromUTF8Bytes(&input, mParsingMode, &baseString,
+ base, referrer, principal).Consume();
+
+ return NS_OK;
+}
+
+void
+ServoStyleSheet::LoadFailed()
+{
+ mSheet = Servo_StyleSheet_Empty(mParsingMode).Consume();
+}
+
+void
+ServoStyleSheet::DropSheet()
+{
+ mSheet = nullptr;
+}
+
+size_t
+ServoStyleSheet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ MOZ_CRASH("stylo: not implemented");
+}
+
+#ifdef DEBUG
+void
+ServoStyleSheet::List(FILE* aOut, int32_t aIndex) const
+{
+ MOZ_CRASH("stylo: not implemented");
+}
+#endif
+
+nsMediaList*
+ServoStyleSheet::Media()
+{
+ return nullptr;
+}
+
+nsIDOMCSSRule*
+ServoStyleSheet::GetDOMOwnerRule() const
+{
+ return nullptr;
+}
+
+CSSRuleList*
+ServoStyleSheet::GetCssRulesInternal(ErrorResult& aRv)
+{
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return nullptr;
+}
+
+uint32_t
+ServoStyleSheet::InsertRuleInternal(const nsAString& aRule,
+ uint32_t aIndex, ErrorResult& aRv)
+{
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return 0;
+}
+
+void
+ServoStyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv)
+{
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+} // namespace mozilla
diff --git a/layout/style/ServoStyleSheet.h b/layout/style/ServoStyleSheet.h
new file mode 100644
index 000000000..079f196eb
--- /dev/null
+++ b/layout/style/ServoStyleSheet.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ServoStyleSheet_h
+#define mozilla_ServoStyleSheet_h
+
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInfo.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+
+/**
+ * CSS style sheet object that is a wrapper for a Servo Stylesheet.
+ */
+class ServoStyleSheet : public StyleSheet
+{
+public:
+ ServoStyleSheet(css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode,
+ net::ReferrerPolicy aReferrerPolicy,
+ const dom::SRIMetadata& aIntegrity);
+
+ bool HasRules() const;
+
+ void SetOwningDocument(nsIDocument* aDocument);
+
+ ServoStyleSheet* GetParentSheet() const;
+ void AppendStyleSheet(ServoStyleSheet* aSheet);
+
+ MOZ_MUST_USE nsresult ParseSheet(const nsAString& aInput,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ uint32_t aLineNumber);
+
+ /**
+ * Called instead of ParseSheet to initialize the Servo stylesheet object
+ * for a failed load. Either ParseSheet or LoadFailed must be called before
+ * adding a ServoStyleSheet to a ServoStyleSet.
+ */
+ void LoadFailed();
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+#ifdef DEBUG
+ void List(FILE* aOut = stdout, int32_t aIndex = 0) const;
+#endif
+
+ RawServoStyleSheet* RawSheet() const { return mSheet; }
+
+ // WebIDL StyleSheet API
+ nsMediaList* Media() final;
+
+ // 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.
+ nsIDOMCSSRule* GetDOMOwnerRule() const final;
+
+ void WillDirty() {}
+ void DidDirty() {}
+
+protected:
+ virtual ~ServoStyleSheet();
+
+ // Internal methods which do not have security check and completeness check.
+ dom::CSSRuleList* GetCssRulesInternal(ErrorResult& aRv);
+ uint32_t InsertRuleInternal(const nsAString& aRule,
+ uint32_t aIndex, ErrorResult& aRv);
+ void DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv);
+
+private:
+ void DropSheet();
+
+ RefPtr<RawServoStyleSheet> mSheet;
+ StyleSheetInfo mSheetInfo;
+
+ friend class StyleSheet;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoStyleSheet_h
diff --git a/layout/style/ServoTypes.h b/layout/style/ServoTypes.h
new file mode 100644
index 000000000..6d1324b88
--- /dev/null
+++ b/layout/style/ServoTypes.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ServoTypes_h
+#define mozilla_ServoTypes_h
+
+/*
+ * Type definitions used to interact with Servo. This gets included by nsINode,
+ * so don't add significant include dependencies to this file.
+ */
+
+struct ServoNodeData;
+namespace mozilla {
+
+/*
+ * Replaced types. These get mapped to associated Servo types in bindgen.
+ */
+
+template<typename T>
+struct ServoUnsafeCell {
+ T value;
+
+ // Ensure that primitive types (i.e. pointers) get zero-initialized.
+ ServoUnsafeCell() : value() {};
+};
+
+template<typename T>
+struct ServoCell {
+ ServoUnsafeCell<T> value;
+ T Get() const { return value.value; }
+ void Set(T arg) { value.value = arg; }
+ ServoCell() : value() {};
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoTypes_h
diff --git a/layout/style/ServoUtils.h b/layout/style/ServoUtils.h
new file mode 100644
index 000000000..b116b9c4f
--- /dev/null
+++ b/layout/style/ServoUtils.h
@@ -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/. */
+
+/* some utilities for stylo */
+
+#ifndef mozilla_ServoUtils_h
+#define mozilla_ServoUtils_h
+
+#include "mozilla/TypeTraits.h"
+
+#ifdef MOZ_STYLO
+# define MOZ_DECL_STYLO_CHECK_METHODS \
+ bool IsGecko() const { return !IsServo(); } \
+ bool IsServo() const { return mType == StyleBackendType::Servo; }
+#else
+# define MOZ_DECL_STYLO_CHECK_METHODS \
+ bool IsGecko() const { return true; } \
+ bool IsServo() const { return false; }
+#endif
+
+/**
+ * Macro used in a base class of |geckotype_| and |servotype_|.
+ * The class should define |StyleBackendType mType;| itself.
+ */
+#define MOZ_DECL_STYLO_METHODS(geckotype_, servotype_) \
+ MOZ_DECL_STYLO_CHECK_METHODS \
+ inline geckotype_* AsGecko(); \
+ inline servotype_* AsServo(); \
+ inline const geckotype_* AsGecko() const; \
+ inline const servotype_* AsServo() const;
+
+/**
+ * Macro used in inline header of class |type_| with its Gecko and Servo
+ * subclasses named |geckotype_| and |servotype_| correspondingly for
+ * implementing the inline methods defined by MOZ_DECL_STYLO_METHODS.
+ */
+#define MOZ_DEFINE_STYLO_METHODS(type_, geckotype_, servotype_) \
+ geckotype_* type_::AsGecko() { \
+ MOZ_ASSERT(IsGecko()); \
+ return static_cast<geckotype_*>(this); \
+ } \
+ servotype_* type_::AsServo() { \
+ MOZ_ASSERT(IsServo()); \
+ return static_cast<servotype_*>(this); \
+ } \
+ const geckotype_* type_::AsGecko() const { \
+ MOZ_ASSERT(IsGecko()); \
+ return static_cast<const geckotype_*>(this); \
+ } \
+ const servotype_* type_::AsServo() const { \
+ MOZ_ASSERT(IsServo()); \
+ return static_cast<const servotype_*>(this); \
+ }
+
+#define MOZ_STYLO_THIS_TYPE mozilla::RemovePointer<decltype(this)>::Type
+#define MOZ_STYLO_GECKO_TYPE mozilla::RemovePointer<decltype(AsGecko())>::Type
+#define MOZ_STYLO_SERVO_TYPE mozilla::RemovePointer<decltype(AsServo())>::Type
+
+/**
+ * Macro used to forward a method call to the concrete method defined by
+ * the Servo or Gecko implementation. The class of the method using it
+ * should use MOZ_DECL_STYLO_METHODS to define basic stylo methods.
+ */
+#define MOZ_STYLO_FORWARD_CONCRETE(method_, geckoargs_, servoargs_) \
+ static_assert(!mozilla::IsSame<decltype(&MOZ_STYLO_THIS_TYPE::method_), \
+ decltype(&MOZ_STYLO_GECKO_TYPE::method_)> \
+ ::value, "Gecko subclass should define its own " #method_); \
+ static_assert(!mozilla::IsSame<decltype(&MOZ_STYLO_THIS_TYPE::method_), \
+ decltype(&MOZ_STYLO_SERVO_TYPE::method_)> \
+ ::value, "Servo subclass should define its own " #method_); \
+ if (IsServo()) { \
+ return AsServo()->method_ servoargs_; \
+ } \
+ return AsGecko()->method_ geckoargs_;
+
+#define MOZ_STYLO_FORWARD(method_, args_) \
+ MOZ_STYLO_FORWARD_CONCRETE(method_, args_, args_)
+
+#endif // mozilla_ServoUtils_h
diff --git a/layout/style/SheetParsingMode.h b/layout/style/SheetParsingMode.h
new file mode 100644
index 000000000..2eb6b2f64
--- /dev/null
+++ b/layout/style/SheetParsingMode.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_css_SheetParsingMode_h
+#define mozilla_css_SheetParsingMode_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.
+ *
+ * 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 {
+ eAuthorSheetFeatures = 0,
+ eUserSheetFeatures,
+ eAgentSheetFeatures
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif // mozilla_css_SheetParsingMode_h
diff --git a/layout/style/SheetType.h b/layout/style/SheetType.h
new file mode 100644
index 000000000..33d1efc1a
--- /dev/null
+++ b/layout/style/SheetType.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* enum to represent a level in the cascade */
+
+#ifndef mozilla_SheetType_h
+#define mozilla_SheetType_h
+
+#include <stdint.h>
+
+namespace mozilla {
+
+// The "origins" of the CSS cascade, from lowest precedence to
+// highest (for non-!important rules).
+//
+// Be sure to update NS_RULE_NODE_LEVEL_MASK when changing the number
+// of sheet types; static assertions enforce this.
+enum class SheetType : uint8_t {
+ Agent, // CSS
+ User, // CSS
+ PresHint,
+ SVGAttrAnimation,
+ Doc, // CSS
+ ScopedDoc,
+ StyleAttr,
+ Override, // CSS
+ Animation,
+ Transition,
+
+ Count,
+ Unknown = 0xff
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SheetType_h
diff --git a/layout/style/StyleAnimationValue.cpp b/layout/style/StyleAnimationValue.cpp
new file mode 100644
index 000000000..eb34c3d83
--- /dev/null
+++ b/layout/style/StyleAnimationValue.cpp
@@ -0,0 +1,4990 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ArrayUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/RuleNodeCacheConditions.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/UniquePtr.h"
+#include "nsStyleTransformMatrix.h"
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsIStyleRule.h"
+#include "mozilla/css/StyleRule.h"
+#include "nsString.h"
+#include "nsStyleContext.h"
+#include "nsStyleSet.h"
+#include "nsComputedDOMStyle.h"
+#include "nsCSSParser.h"
+#include "nsCSSPseudoElements.h"
+#include "mozilla/css/Declaration.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Likely.h"
+#include "mozilla/ServoBindings.h" // RawServoDeclarationBlock
+#include "gfxMatrix.h"
+#include "gfxQuaternion.h"
+#include "nsIDocument.h"
+#include "nsIFrame.h"
+#include "gfx2DGlue.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::gfx;
+using nsStyleTransformMatrix::Decompose2DMatrix;
+using nsStyleTransformMatrix::Decompose3DMatrix;
+using nsStyleTransformMatrix::ShearType;
+
+// HELPER METHODS
+// --------------
+/*
+ * Given two units, this method returns a common unit that they can both be
+ * converted into, if possible. This is intended to facilitate
+ * interpolation, distance-computation, and addition between "similar" units.
+ *
+ * The ordering of the arguments should not affect the output of this method.
+ *
+ * If there's no sensible common unit, this method returns eUnit_Null.
+ *
+ * @param aFirstUnit One unit to resolve.
+ * @param aFirstUnit The other unit to resolve.
+ * @return A "common" unit that both source units can be converted into, or
+ * eUnit_Null if that's not possible.
+ */
+static
+StyleAnimationValue::Unit
+GetCommonUnit(nsCSSPropertyID aProperty,
+ StyleAnimationValue::Unit aFirstUnit,
+ StyleAnimationValue::Unit aSecondUnit)
+{
+ if (aFirstUnit != aSecondUnit) {
+ if (nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_STORES_CALC) &&
+ (aFirstUnit == StyleAnimationValue::eUnit_Coord ||
+ aFirstUnit == StyleAnimationValue::eUnit_Percent ||
+ aFirstUnit == StyleAnimationValue::eUnit_Calc) &&
+ (aSecondUnit == StyleAnimationValue::eUnit_Coord ||
+ aSecondUnit == StyleAnimationValue::eUnit_Percent ||
+ aSecondUnit == StyleAnimationValue::eUnit_Calc)) {
+ // We can use calc() as the common unit.
+ return StyleAnimationValue::eUnit_Calc;
+ }
+ if ((aFirstUnit == StyleAnimationValue::eUnit_Color ||
+ aFirstUnit == StyleAnimationValue::eUnit_CurrentColor ||
+ aFirstUnit == StyleAnimationValue::eUnit_ComplexColor) &&
+ (aSecondUnit == StyleAnimationValue::eUnit_Color ||
+ aSecondUnit == StyleAnimationValue::eUnit_CurrentColor ||
+ aSecondUnit == StyleAnimationValue::eUnit_ComplexColor)) {
+ // We can use complex color as the common unit.
+ return StyleAnimationValue::eUnit_ComplexColor;
+ }
+ return StyleAnimationValue::eUnit_Null;
+ }
+ return aFirstUnit;
+}
+
+static
+nsCSSUnit
+GetCommonUnit(nsCSSPropertyID aProperty,
+ nsCSSUnit aFirstUnit,
+ nsCSSUnit aSecondUnit)
+{
+ if (aFirstUnit != aSecondUnit) {
+ if (nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_STORES_CALC) &&
+ (aFirstUnit == eCSSUnit_Pixel ||
+ aFirstUnit == eCSSUnit_Percent ||
+ aFirstUnit == eCSSUnit_Calc) &&
+ (aSecondUnit == eCSSUnit_Pixel ||
+ aSecondUnit == eCSSUnit_Percent ||
+ aSecondUnit == eCSSUnit_Calc)) {
+ // We can use calc() as the common unit.
+ return eCSSUnit_Calc;
+ }
+ return eCSSUnit_Null;
+ }
+ return aFirstUnit;
+}
+
+static nsCSSKeyword
+ToPrimitive(nsCSSKeyword aKeyword)
+{
+ switch (aKeyword) {
+ case eCSSKeyword_translatex:
+ case eCSSKeyword_translatey:
+ case eCSSKeyword_translatez:
+ case eCSSKeyword_translate:
+ return eCSSKeyword_translate3d;
+ case eCSSKeyword_scalex:
+ case eCSSKeyword_scaley:
+ case eCSSKeyword_scalez:
+ case eCSSKeyword_scale:
+ return eCSSKeyword_scale3d;
+ default:
+ return aKeyword;
+ }
+}
+
+static bool
+TransformFunctionsMatch(nsCSSKeyword func1, nsCSSKeyword func2)
+{
+ return ToPrimitive(func1) == ToPrimitive(func2);
+}
+
+static already_AddRefed<nsCSSValue::Array>
+AppendFunction(nsCSSKeyword aTransformFunction)
+{
+ uint32_t nargs;
+ switch (aTransformFunction) {
+ case eCSSKeyword_matrix3d:
+ nargs = 16;
+ break;
+ case eCSSKeyword_matrix:
+ nargs = 6;
+ break;
+ case eCSSKeyword_rotate3d:
+ nargs = 4;
+ break;
+ case eCSSKeyword_interpolatematrix:
+ case eCSSKeyword_translate3d:
+ case eCSSKeyword_scale3d:
+ nargs = 3;
+ break;
+ case eCSSKeyword_translate:
+ case eCSSKeyword_skew:
+ case eCSSKeyword_scale:
+ nargs = 2;
+ break;
+ default:
+ NS_ERROR("must be a transform function");
+ MOZ_FALLTHROUGH;
+ case eCSSKeyword_translatex:
+ case eCSSKeyword_translatey:
+ case eCSSKeyword_translatez:
+ case eCSSKeyword_scalex:
+ case eCSSKeyword_scaley:
+ case eCSSKeyword_scalez:
+ case eCSSKeyword_skewx:
+ case eCSSKeyword_skewy:
+ case eCSSKeyword_rotate:
+ case eCSSKeyword_rotatex:
+ case eCSSKeyword_rotatey:
+ case eCSSKeyword_rotatez:
+ case eCSSKeyword_perspective:
+ nargs = 1;
+ break;
+ }
+
+ RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(nargs + 1);
+ arr->Item(0).SetIntValue(aTransformFunction, eCSSUnit_Enumerated);
+
+ return arr.forget();
+}
+
+static already_AddRefed<nsCSSValue::Array>
+ToPrimitive(nsCSSValue::Array* aArray)
+{
+ nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(aArray);
+ nsCSSKeyword primitive = ToPrimitive(tfunc);
+ RefPtr<nsCSSValue::Array> arr = AppendFunction(primitive);
+
+ // FIXME: This would produce fewer calc() expressions if the
+ // zero were of compatible type (length vs. percent) when
+ // needed.
+
+ nsCSSValue zero(0.0f, eCSSUnit_Pixel);
+ nsCSSValue one(1.0f, eCSSUnit_Number);
+ switch(tfunc) {
+ case eCSSKeyword_translate:
+ {
+ MOZ_ASSERT(aArray->Count() == 2 || aArray->Count() == 3,
+ "unexpected count");
+ arr->Item(1) = aArray->Item(1);
+ arr->Item(2) = aArray->Count() == 3 ? aArray->Item(2) : zero;
+ arr->Item(3) = zero;
+ break;
+ }
+ case eCSSKeyword_translatex:
+ {
+ MOZ_ASSERT(aArray->Count() == 2, "unexpected count");
+ arr->Item(1) = aArray->Item(1);
+ arr->Item(2) = zero;
+ arr->Item(3) = zero;
+ break;
+ }
+ case eCSSKeyword_translatey:
+ {
+ MOZ_ASSERT(aArray->Count() == 2, "unexpected count");
+ arr->Item(1) = zero;
+ arr->Item(2) = aArray->Item(1);
+ arr->Item(3) = zero;
+ break;
+ }
+ case eCSSKeyword_translatez:
+ {
+ MOZ_ASSERT(aArray->Count() == 2, "unexpected count");
+ arr->Item(1) = zero;
+ arr->Item(2) = zero;
+ arr->Item(3) = aArray->Item(1);
+ break;
+ }
+ case eCSSKeyword_scale:
+ {
+ MOZ_ASSERT(aArray->Count() == 2 || aArray->Count() == 3,
+ "unexpected count");
+ arr->Item(1) = aArray->Item(1);
+ arr->Item(2) = aArray->Count() == 3 ? aArray->Item(2) : aArray->Item(1);
+ arr->Item(3) = one;
+ break;
+ }
+ case eCSSKeyword_scalex:
+ {
+ MOZ_ASSERT(aArray->Count() == 2, "unexpected count");
+ arr->Item(1) = aArray->Item(1);
+ arr->Item(2) = one;
+ arr->Item(3) = one;
+ break;
+ }
+ case eCSSKeyword_scaley:
+ {
+ MOZ_ASSERT(aArray->Count() == 2, "unexpected count");
+ arr->Item(1) = one;
+ arr->Item(2) = aArray->Item(1);
+ arr->Item(3) = one;
+ break;
+ }
+ case eCSSKeyword_scalez:
+ {
+ MOZ_ASSERT(aArray->Count() == 2, "unexpected count");
+ arr->Item(1) = one;
+ arr->Item(2) = one;
+ arr->Item(3) = aArray->Item(1);
+ break;
+ }
+ default:
+ arr = aArray;
+ }
+ return arr.forget();
+}
+
+static void
+AppendCSSShadowValue(const nsCSSShadowItem *aShadow,
+ nsCSSValueList **&aResultTail)
+{
+ MOZ_ASSERT(aShadow, "shadow expected");
+
+ // X, Y, Radius, Spread, Color, Inset
+ RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(6);
+ arr->Item(0).SetIntegerCoordValue(aShadow->mXOffset);
+ arr->Item(1).SetIntegerCoordValue(aShadow->mYOffset);
+ arr->Item(2).SetIntegerCoordValue(aShadow->mRadius);
+ // NOTE: This code sometimes stores mSpread: 0 even when
+ // the parser would be required to leave it null.
+ arr->Item(3).SetIntegerCoordValue(aShadow->mSpread);
+ if (aShadow->mHasColor) {
+ arr->Item(4).SetColorValue(aShadow->mColor);
+ }
+ if (aShadow->mInset) {
+ arr->Item(5).SetIntValue(uint8_t(StyleBoxShadowType::Inset),
+ eCSSUnit_Enumerated);
+ }
+
+ nsCSSValueList *resultItem = new nsCSSValueList;
+ resultItem->mValue.SetArrayValue(arr, eCSSUnit_Array);
+ *aResultTail = resultItem;
+ aResultTail = &resultItem->mNext;
+}
+
+// Like nsStyleCoord::CalcValue, but with length in float pixels instead
+// of nscoord.
+struct PixelCalcValue
+{
+ float mLength, mPercent;
+ bool mHasPercent;
+};
+
+// Requires a canonical calc() value that we generated.
+static PixelCalcValue
+ExtractCalcValueInternal(const nsCSSValue& aValue)
+{
+ MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Calc, "unexpected unit");
+ nsCSSValue::Array *arr = aValue.GetArrayValue();
+ MOZ_ASSERT(arr->Count() == 1, "unexpected length");
+
+ const nsCSSValue &topval = arr->Item(0);
+ PixelCalcValue result;
+ if (topval.GetUnit() == eCSSUnit_Pixel) {
+ result.mLength = topval.GetFloatValue();
+ result.mPercent = 0.0f;
+ result.mHasPercent = false;
+ } else {
+ MOZ_ASSERT(topval.GetUnit() == eCSSUnit_Calc_Plus,
+ "unexpected unit");
+ nsCSSValue::Array *arr2 = topval.GetArrayValue();
+ const nsCSSValue &len = arr2->Item(0);
+ const nsCSSValue &pct = arr2->Item(1);
+ MOZ_ASSERT(len.GetUnit() == eCSSUnit_Pixel, "unexpected unit");
+ MOZ_ASSERT(pct.GetUnit() == eCSSUnit_Percent, "unexpected unit");
+ result.mLength = len.GetFloatValue();
+ result.mPercent = pct.GetPercentValue();
+ result.mHasPercent = true;
+ }
+
+ return result;
+}
+
+// Requires a canonical calc() value that we generated.
+static PixelCalcValue
+ExtractCalcValue(const StyleAnimationValue& aValue)
+{
+ PixelCalcValue result;
+ if (aValue.GetUnit() == StyleAnimationValue::eUnit_Coord) {
+ result.mLength =
+ nsPresContext::AppUnitsToFloatCSSPixels(aValue.GetCoordValue());
+ result.mPercent = 0.0f;
+ result.mHasPercent = false;
+ return result;
+ }
+ if (aValue.GetUnit() == StyleAnimationValue::eUnit_Percent) {
+ result.mLength = 0.0f;
+ result.mPercent = aValue.GetPercentValue();
+ result.mHasPercent = true;
+ return result;
+ }
+ MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Calc,
+ "unexpected unit");
+ nsCSSValue *val = aValue.GetCSSValueValue();
+ return ExtractCalcValueInternal(*val);
+}
+
+static PixelCalcValue
+ExtractCalcValue(const nsCSSValue& aValue)
+{
+ PixelCalcValue result;
+ if (aValue.GetUnit() == eCSSUnit_Pixel) {
+ result.mLength = aValue.GetFloatValue();
+ result.mPercent = 0.0f;
+ result.mHasPercent = false;
+ return result;
+ }
+ if (aValue.GetUnit() == eCSSUnit_Percent) {
+ result.mLength = 0.0f;
+ result.mPercent = aValue.GetPercentValue();
+ result.mHasPercent = true;
+ return result;
+ }
+ return ExtractCalcValueInternal(aValue);
+}
+
+static void
+CalcValueToCSSValue(const nsStyleCoord::CalcValue* aCalc, nsCSSValue& aValue)
+{
+ aValue.SetCalcValue(aCalc);
+}
+
+static void
+CalcValueToCSSValue(const PixelCalcValue& aCalc, nsCSSValue& aValue)
+{
+ RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(1);
+ if (!aCalc.mHasPercent) {
+ arr->Item(0).SetFloatValue(aCalc.mLength, eCSSUnit_Pixel);
+ } else {
+ nsCSSValue::Array *arr2 = nsCSSValue::Array::Create(2);
+ arr->Item(0).SetArrayValue(arr2, eCSSUnit_Calc_Plus);
+ arr2->Item(0).SetFloatValue(aCalc.mLength, eCSSUnit_Pixel);
+ arr2->Item(1).SetPercentValue(aCalc.mPercent);
+ }
+
+ aValue.SetArrayValue(arr, eCSSUnit_Calc);
+}
+
+double
+CalcPositionSquareDistance(const nsCSSValue& aPos1,
+ const nsCSSValue& aPos2)
+{
+ NS_ASSERTION(aPos1.GetUnit() == eCSSUnit_Array &&
+ aPos2.GetUnit() == eCSSUnit_Array,
+ "Expected two arrays");
+
+ PixelCalcValue calcVal[4];
+
+ nsCSSValue::Array* posArray = aPos1.GetArrayValue();
+ MOZ_ASSERT(posArray->Count() == 4, "Invalid position value");
+ NS_ASSERTION(posArray->Item(0).GetUnit() == eCSSUnit_Null &&
+ posArray->Item(2).GetUnit() == eCSSUnit_Null,
+ "Invalid list used");
+ for (int i = 0; i < 2; ++i) {
+ MOZ_ASSERT(posArray->Item(i*2+1).GetUnit() != eCSSUnit_Null,
+ "Invalid position value");
+ calcVal[i] = ExtractCalcValue(posArray->Item(i*2+1));
+ }
+
+ posArray = aPos2.GetArrayValue();
+ MOZ_ASSERT(posArray->Count() == 4, "Invalid position value");
+ NS_ASSERTION(posArray->Item(0).GetUnit() == eCSSUnit_Null &&
+ posArray->Item(2).GetUnit() == eCSSUnit_Null,
+ "Invalid list used");
+ for (int i = 0; i < 2; ++i) {
+ MOZ_ASSERT(posArray->Item(i*2+1).GetUnit() != eCSSUnit_Null,
+ "Invalid position value");
+ calcVal[i+2] = ExtractCalcValue(posArray->Item(i*2+1));
+ }
+
+ double squareDistance = 0.0;
+ for (int i = 0; i < 2; ++i) {
+ float difflen = calcVal[i+2].mLength - calcVal[i].mLength;
+ float diffpct = calcVal[i+2].mPercent - calcVal[i].mPercent;
+ squareDistance += difflen * difflen + diffpct * diffpct;
+ }
+
+ return squareDistance;
+}
+
+static PixelCalcValue
+CalcBackgroundCoord(const nsCSSValue& aCoord)
+{
+ NS_ASSERTION(aCoord.GetUnit() == eCSSUnit_Array,
+ "Expected array");
+
+ nsCSSValue::Array* array = aCoord.GetArrayValue();
+ MOZ_ASSERT(array->Count() == 2 &&
+ array->Item(0).GetUnit() == eCSSUnit_Null &&
+ array->Item(1).GetUnit() != eCSSUnit_Null,
+ "Invalid position value");
+ return ExtractCalcValue(array->Item(1));
+}
+
+double
+CalcPositionCoordSquareDistance(const nsCSSValue& aPos1,
+ const nsCSSValue& aPos2)
+{
+ PixelCalcValue calcVal1 = CalcBackgroundCoord(aPos1);
+ PixelCalcValue calcVal2 = CalcBackgroundCoord(aPos2);
+
+ float difflen = calcVal2.mLength - calcVal1.mLength;
+ float diffpct = calcVal2.mPercent - calcVal1.mPercent;
+ return difflen * difflen + diffpct * diffpct;
+}
+
+// Ensure that a float/double value isn't NaN by returning zero instead
+// (NaN doesn't have a sign) as a general restriction for floating point
+// values in RestrictValue.
+template<typename T>
+MOZ_ALWAYS_INLINE T
+EnsureNotNan(T aValue)
+{
+ return aValue;
+}
+template<>
+MOZ_ALWAYS_INLINE float
+EnsureNotNan(float aValue)
+{
+ // This would benefit from a MOZ_FLOAT_IS_NaN if we had one.
+ return MOZ_LIKELY(!mozilla::IsNaN(aValue)) ? aValue : 0;
+}
+template<>
+MOZ_ALWAYS_INLINE double
+EnsureNotNan(double aValue)
+{
+ return MOZ_LIKELY(!mozilla::IsNaN(aValue)) ? aValue : 0;
+}
+
+template <typename T>
+T
+RestrictValue(uint32_t aRestrictions, T aValue)
+{
+ T result = EnsureNotNan(aValue);
+ switch (aRestrictions) {
+ case 0:
+ break;
+ case CSS_PROPERTY_VALUE_NONNEGATIVE:
+ if (result < 0) {
+ result = 0;
+ }
+ break;
+ case CSS_PROPERTY_VALUE_AT_LEAST_ONE:
+ if (result < 1) {
+ result = 1;
+ }
+ break;
+ default:
+ MOZ_ASSERT(false, "bad value restriction");
+ break;
+ }
+ return result;
+}
+
+template <typename T>
+T
+RestrictValue(nsCSSPropertyID aProperty, T aValue)
+{
+ return RestrictValue(nsCSSProps::ValueRestrictions(aProperty), aValue);
+}
+
+static void
+AddCSSValueAngle(double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ nsCSSValue &aResult)
+{
+ if (aValue1.GetUnit() == aValue2.GetUnit()) {
+ // To avoid floating point error, if the units match, maintain the unit.
+ aResult.SetFloatValue(
+ EnsureNotNan(aCoeff1 * aValue1.GetFloatValue() +
+ aCoeff2 * aValue2.GetFloatValue()),
+ aValue1.GetUnit());
+ } else {
+ aResult.SetFloatValue(
+ EnsureNotNan(aCoeff1 * aValue1.GetAngleValueInRadians() +
+ aCoeff2 * aValue2.GetAngleValueInRadians()),
+ eCSSUnit_Radian);
+ }
+}
+
+static inline void
+AddCSSValuePercent(double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ nsCSSValue &aResult, uint32_t aValueRestrictions = 0)
+{
+ MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Percent, "unexpected unit");
+ MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Percent, "unexpected unit");
+ aResult.SetPercentValue(RestrictValue(aValueRestrictions,
+ aCoeff1 * aValue1.GetPercentValue() +
+ aCoeff2 * aValue2.GetPercentValue()));
+}
+
+// Add two canonical-form calc values (eUnit_Calc) to make another
+// canonical-form calc value.
+static void
+AddCSSValueCanonicalCalc(double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ nsCSSValue &aResult)
+{
+ PixelCalcValue v1 = ExtractCalcValue(aValue1);
+ PixelCalcValue v2 = ExtractCalcValue(aValue2);
+ PixelCalcValue result;
+ result.mLength = aCoeff1 * v1.mLength + aCoeff2 * v2.mLength;
+ result.mPercent = aCoeff1 * v1.mPercent + aCoeff2 * v2.mPercent;
+ result.mHasPercent = v1.mHasPercent || v2.mHasPercent;
+ MOZ_ASSERT(result.mHasPercent || result.mPercent == 0.0f,
+ "can't have a nonzero percentage part without having percentages");
+ CalcValueToCSSValue(result, aResult);
+}
+
+static inline void
+AddCSSValuePixel(double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ nsCSSValue &aResult, uint32_t aValueRestrictions = 0)
+{
+ MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Pixel, "unexpected unit");
+ MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Pixel, "unexpected unit");
+ aResult.SetFloatValue(RestrictValue(aValueRestrictions,
+ aCoeff1 * aValue1.GetFloatValue() +
+ aCoeff2 * aValue2.GetFloatValue()),
+ eCSSUnit_Pixel);
+}
+
+static bool
+AddCSSValuePixelPercentCalc(const uint32_t aValueRestrictions,
+ const nsCSSUnit aCommonUnit,
+ double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ nsCSSValue &aResult)
+{
+ switch (aCommonUnit) {
+ case eCSSUnit_Pixel:
+ AddCSSValuePixel(aCoeff1, aValue1,
+ aCoeff2, aValue2,
+ aResult, aValueRestrictions);
+ break;
+ case eCSSUnit_Percent:
+ AddCSSValuePercent(aCoeff1, aValue1,
+ aCoeff2, aValue2,
+ aResult, aValueRestrictions);
+ break;
+ case eCSSUnit_Calc:
+ AddCSSValueCanonicalCalc(aCoeff1, aValue1,
+ aCoeff2, aValue2,
+ aResult);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static void
+AddTransformTranslate(double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ nsCSSValue &aResult)
+{
+ // Only three possible units: eCSSUnit_Pixel, eCSSUnit_Percent, or
+ // eCSSUnit_Calc.
+ MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Percent ||
+ aValue1.GetUnit() == eCSSUnit_Pixel ||
+ aValue1.IsCalcUnit(),
+ "unexpected unit");
+ MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Percent ||
+ aValue2.GetUnit() == eCSSUnit_Pixel ||
+ aValue2.IsCalcUnit(),
+ "unexpected unit");
+ AddCSSValuePixelPercentCalc(0,
+ (aValue1.GetUnit() != aValue2.GetUnit() ||
+ aValue1.IsCalcUnit())
+ ? eCSSUnit_Calc
+ : aValue1.GetUnit(),
+ aCoeff1, aValue1,
+ aCoeff2, aValue2,
+ aResult);
+}
+
+// CLASS METHODS
+// -------------
+
+static RGBAColorData
+ExtractColor(const nsCSSValue& aValue)
+{
+ MOZ_ASSERT(aValue.IsNumericColorUnit(), "The unit should be color");
+ // PercentageRGBColor and PercentageRGBAColor component value might be
+ // greater than 1.0 in case when the color value is accumulated, so we
+ // can't use nsCSSValue::GetColorValue() here because that function
+ // clamps its values.
+ if (aValue.GetUnit() == eCSSUnit_PercentageRGBColor ||
+ aValue.GetUnit() == eCSSUnit_PercentageRGBAColor) {
+ nsCSSValueFloatColor* floatColor = aValue.GetFloatColorValue();
+ return {
+ floatColor->Comp1(),
+ floatColor->Comp2(),
+ floatColor->Comp3(),
+ floatColor->Alpha()
+ };
+ }
+ return RGBAColorData(aValue.GetColorValue());
+}
+
+static RGBAColorData
+ExtractColor(const StyleAnimationValue& aValue)
+{
+ MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Color);
+ nsCSSValue* value = aValue.GetCSSValueValue();
+ MOZ_ASSERT(value, "CSS value must be valid");
+ return ExtractColor(*value);
+}
+
+static ComplexColorData
+ExtractComplexColor(const StyleAnimationValue& aValue)
+{
+ switch (aValue.GetUnit()) {
+ case StyleAnimationValue::eUnit_Color:
+ return ComplexColorData(ExtractColor(aValue), 0.0f);
+ case StyleAnimationValue::eUnit_CurrentColor:
+ return ComplexColorData({0, 0, 0, 0}, 1.0f);
+ case StyleAnimationValue::eUnit_ComplexColor:
+ return ComplexColorData(aValue.GetComplexColorData());
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown unit");
+ return ComplexColorData({0, 0, 0, 0}, 0.0f);
+ }
+}
+
+double
+StyleAnimationValue::ComputeColorDistance(const RGBAColorData& aStartColor,
+ const RGBAColorData& aEndColor)
+{
+ // http://www.w3.org/TR/smil-animation/#animateColorElement says
+ // that we should use Euclidean RGB cube distance. However, we
+ // have to extend that to RGBA. For now, we'll just use the
+ // Euclidean distance in the (part of the) 4-cube of premultiplied
+ // colors.
+ double startA = aStartColor.mA;
+ double startR = aStartColor.mR * startA;
+ double startG = aStartColor.mG * startA;
+ double startB = aStartColor.mB * startA;
+ double endA = aEndColor.mA;
+ double endR = aEndColor.mR * endA;
+ double endG = aEndColor.mG * endA;
+ double endB = aEndColor.mB * endA;
+
+ double diffA = startA - endA;
+ double diffR = startR - endR;
+ double diffG = startG - endG;
+ double diffB = startB - endB;
+ return sqrt(diffA * diffA + diffR * diffR + diffG * diffG + diffB * diffB);
+}
+
+enum class Restrictions {
+ Enable,
+ Disable
+};
+
+static already_AddRefed<nsCSSValue::Array>
+AddShapeFunction(nsCSSPropertyID aProperty,
+ double aCoeff1, const nsCSSValue::Array* aArray1,
+ double aCoeff2, const nsCSSValue::Array* aArray2,
+ Restrictions aRestriction = Restrictions::Enable);
+
+static double
+ComputeShapeDistance(nsCSSPropertyID aProperty,
+ const nsCSSValue::Array* aArray1,
+ const nsCSSValue::Array* aArray2)
+{
+ // Use AddShapeFunction to get the difference between two shape functions.
+ RefPtr<nsCSSValue::Array> diffShape =
+ AddShapeFunction(aProperty, 1.0, aArray2, -1.0, aArray1,
+ Restrictions::Disable);
+ if (!diffShape) {
+ return 0.0;
+ }
+
+ // A helper function to convert a calc() diff value into a double distance.
+ auto pixelCalcDistance = [](const PixelCalcValue& aValue) {
+ MOZ_ASSERT(aValue.mHasPercent || aValue.mPercent == 0.0f,
+ "can't have a nonzero percentage part without having percentages");
+ return aValue.mLength * aValue.mLength + aValue.mPercent * aValue.mPercent;
+ };
+
+ double squareDistance = 0.0;
+ const nsCSSValue::Array* func = diffShape->Item(0).GetArrayValue();
+ nsCSSKeyword shapeFuncName = func->Item(0).GetKeywordValue();
+ switch (shapeFuncName) {
+ case eCSSKeyword_ellipse:
+ case eCSSKeyword_circle: {
+ // Skip the first element which is the function keyword.
+ // Also, skip the last element which is an array for <position>
+ const size_t len = func->Count();
+ for (size_t i = 1; i < len - 1; ++i) {
+ squareDistance += pixelCalcDistance(ExtractCalcValue(func->Item(i)));
+ }
+ // Only iterate over elements 1 and 3. The <position> is 'uncomputed' to
+ // only those elements. See also the comment in SetPositionValue.
+ for (size_t i = 1; i < 4; i += 2) {
+ const nsCSSValue& value = func->Item(len - 1).GetArrayValue()->Item(i);
+ squareDistance += pixelCalcDistance(ExtractCalcValue(value));
+ }
+ break;
+ }
+ case eCSSKeyword_polygon: {
+ // Don't care about the first element which is the function keyword, and
+ // the second element which is the fill rule.
+ const nsCSSValuePairList* list = func->Item(2).GetPairListValue();
+ do {
+ squareDistance += pixelCalcDistance(ExtractCalcValue(list->mXValue)) +
+ pixelCalcDistance(ExtractCalcValue(list->mYValue));
+ list = list->mNext;
+ } while (list);
+ break;
+ }
+ case eCSSKeyword_inset: {
+ // Items 1-4 are respectively the top, right, bottom and left offsets
+ // from the reference box.
+ for (size_t i = 1; i <= 4; ++i) {
+ const nsCSSValue& value = func->Item(i);
+ squareDistance += pixelCalcDistance(ExtractCalcValue(value));
+ }
+ // Item 5 contains the radii of the rounded corners for the inset
+ // rectangle.
+ const nsCSSValue::Array* array = func->Item(5).GetArrayValue();
+ const size_t len = array->Count();
+ for (size_t i = 0; i < len; ++i) {
+ const nsCSSValuePair& pair = array->Item(i).GetPairValue();
+ squareDistance += pixelCalcDistance(ExtractCalcValue(pair.mXValue)) +
+ pixelCalcDistance(ExtractCalcValue(pair.mYValue));
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown shape type");
+ }
+ return sqrt(squareDistance);
+}
+
+static nsCSSValueList*
+AddTransformLists(double aCoeff1, const nsCSSValueList* aList1,
+ double aCoeff2, const nsCSSValueList* aList2);
+
+static double
+ComputeTransform2DMatrixDistance(const Matrix& aMatrix1,
+ const Matrix& aMatrix2)
+{
+ Point3D scale1(1, 1, 1);
+ Point3D translate1;
+ gfxQuaternion rotate1;
+ nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f};
+ Decompose2DMatrix(aMatrix1, scale1, shear1, rotate1, translate1);
+
+ Point3D scale2(1, 1, 1);
+ Point3D translate2;
+ gfxQuaternion rotate2;
+ nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f};
+ Decompose2DMatrix(aMatrix2, scale2, shear2, rotate2, translate2);
+
+ // Note:
+ // 1. Shear factor is the tangent value of shear angle, so we need to
+ // call atan() to get the angle. For 2D transform, we only have XYSHEAR.
+ // 2. The quaternion vector of the decomposed 2d matrix is got by
+ // "gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2))"
+ // ^^^^^^^^^^^^^ ^^^^^^^^^^^^^
+ // z w
+ // Therefore, we can get the rotate angle by 2 * atan2f(z, w).
+ //
+ // However, we can also get the rotate angle by the inner product of
+ // two quaternion vectors, just as what we do for eCSSKeyword_rotate3d.
+ // e.g.
+ // rotate3d(0, 0, 1, 60deg) => rotate3d(0, 0, 1, 120deg);
+ // quaternion 1: (0, 0, sin(30deg), cos(30deg)) = (0, 0, 1/2, sqrt(3)/2)
+ // quaternion 2: (0, 0, sin(60deg), cos(60deg)) = (0, 0, sqrt(3)/2, 1/2)
+ // inner product: sqrt(3)/4 + sqrt(3)/4 = sqrt(3)/2
+ // Finally, the rotate angle: 2 * acos(sqrt(3)/2) = 60deg
+ //
+ // I think doing atan() may be faster than doing inner product together
+ // with acos(), so let's adopt atan2f().
+ const Point3D diffTranslate = translate2 - translate1;
+ const Point3D diffScale = scale2 - scale1;
+ const double diffShear = atan(shear2[ShearType::XYSHEAR]) -
+ atan(shear1[ShearType::XYSHEAR]);
+ const double diffRotate = 2.0 * (atan2f(rotate2.z, rotate2.w) -
+ atan2f(rotate1.z, rotate1.w));
+ // Returns the sum of squares because we will take a square root in
+ // ComputeTransformListDistance.
+ return diffTranslate.DotProduct(diffTranslate) +
+ diffScale.DotProduct(diffScale) +
+ diffRotate * diffRotate +
+ diffShear * diffShear;
+}
+
+static double
+ComputeTransform3DMatrixDistance(const Matrix4x4& aMatrix1,
+ const Matrix4x4& aMatrix2)
+{
+ Point3D scale1(1, 1, 1);
+ Point3D translate1;
+ Point4D perspective1(0, 0, 0, 1);
+ gfxQuaternion rotate1;
+ nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f};
+ Decompose3DMatrix(aMatrix1, scale1, shear1, rotate1, translate1,
+ perspective1);
+
+ Point3D scale2(1, 1, 1);
+ Point3D translate2;
+ Point4D perspective2(0, 0, 0, 1);
+ gfxQuaternion rotate2;
+ nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f};
+ Decompose3DMatrix(aMatrix2, scale2, shear2, rotate2, translate2,
+ perspective2);
+
+ // Note:
+ // 1. Shear factor is the tangent value of shear angle, so we need to
+ // call atan() to get the angle.
+ // 2. We use the same way to get the rotate angle of two quaternion vectors as
+ // what we do for rotate3d.
+ const Point3D diffTranslate = translate2 - translate1;
+ const Point3D diffScale = scale2 - scale1;
+ const Point3D diffShear(atan(shear2[ShearType::XYSHEAR]) -
+ atan(shear1[ShearType::XYSHEAR]),
+ atan(shear2[ShearType::XZSHEAR]) -
+ atan(shear1[ShearType::XZSHEAR]),
+ atan(shear2[ShearType::YZSHEAR]) -
+ atan(shear1[ShearType::YZSHEAR]));
+ const Point4D diffPerspective = perspective2 - perspective1;
+ const double dot = clamped(rotate1.DotProduct(rotate2), -1.0, 1.0);
+ const double diffRotate = 2.0 * acos(dot);
+ // Returns the sum of squares because we will take a square root in
+ // ComputeTransformListDistance.
+ return diffTranslate.DotProduct(diffTranslate) +
+ diffScale.DotProduct(diffScale) +
+ diffPerspective.DotProduct(diffPerspective) +
+ diffShear.DotProduct(diffShear) +
+ diffRotate * diffRotate;
+}
+
+static double
+ComputeTransformDistance(nsCSSValue::Array* aArray1,
+ nsCSSValue::Array* aArray2)
+{
+ MOZ_ASSERT(aArray1, "aArray1 should be non-null.");
+ MOZ_ASSERT(aArray2, "aArray2 should be non-null.");
+
+ // Normalize translate and scale functions to equivalent "translate3d" and
+ // "scale3d" functions.
+ RefPtr<nsCSSValue::Array> a1 = ToPrimitive(aArray1),
+ a2 = ToPrimitive(aArray2);
+ nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(a1);
+ MOZ_ASSERT(nsStyleTransformMatrix::TransformFunctionOf(a2) == tfunc);
+
+ double distance = 0.0;
+ switch (tfunc) {
+ case eCSSKeyword_translate3d: {
+ MOZ_ASSERT(a1->Count() == 4, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 4, "unexpected count");
+
+ nsCSSValue x, y, z;
+ AddTransformTranslate(1.0, a2->Item(1), -1.0, a1->Item(1), x);
+ AddTransformTranslate(1.0, a2->Item(2), -1.0, a1->Item(2), y);
+ AddTransformTranslate(1.0, a2->Item(3), -1.0, a1->Item(3), z);
+ // Drop percent part because we only compute distance by computed values.
+ double c1 = ExtractCalcValue(x).mLength;
+ double c2 = ExtractCalcValue(y).mLength;
+ double c3 = z.GetFloatValue();
+ distance = c1 * c1 + c2 * c2 + c3 * c3;
+ break;
+ }
+ case eCSSKeyword_scale3d: {
+ MOZ_ASSERT(a1->Count() == 4, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 4, "unexpected count");
+
+ auto ComputeScaleDiff = [](const nsCSSValue& aValue1,
+ const nsCSSValue& aValue2) {
+ float v1 = aValue1.GetFloatValue();
+ float v2 = aValue2.GetFloatValue();
+ return EnsureNotNan(v2 - v1);
+ };
+
+ double c1 = ComputeScaleDiff(a1->Item(1), a2->Item(1));
+ double c2 = ComputeScaleDiff(a1->Item(2), a2->Item(2));
+ double c3 = ComputeScaleDiff(a1->Item(3), a2->Item(3));
+ distance = c1 * c1 + c2 * c2 + c3 * c3;
+ break;
+ }
+ case eCSSKeyword_skew: {
+ MOZ_ASSERT(a1->Count() == 2 || a1->Count() == 3, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 2 || a2->Count() == 3, "unexpected count");
+
+ const nsCSSValue zero(0.0f, eCSSUnit_Radian);
+ nsCSSValue x, y;
+ AddCSSValueAngle(1.0, a2->Item(1), -1.0, a1->Item(1), x);
+ AddCSSValueAngle(1.0, a2->Count() == 3 ? a2->Item(2) : zero,
+ -1.0, a1->Count() == 3 ? a1->Item(2) : zero,
+ y);
+ distance = x.GetAngleValueInRadians() * x.GetAngleValueInRadians() +
+ y.GetAngleValueInRadians() * y.GetAngleValueInRadians();
+ break;
+ }
+ case eCSSKeyword_skewx:
+ case eCSSKeyword_skewy:
+ case eCSSKeyword_rotate:
+ case eCSSKeyword_rotatex:
+ case eCSSKeyword_rotatey:
+ case eCSSKeyword_rotatez: {
+ MOZ_ASSERT(a1->Count() == 2, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 2, "unexpected count");
+
+ nsCSSValue angle;
+ AddCSSValueAngle(1.0, a2->Item(1), -1.0, a1->Item(1), angle);
+ distance = angle.GetAngleValueInRadians() *
+ angle.GetAngleValueInRadians();
+ break;
+ }
+ case eCSSKeyword_rotate3d: {
+ MOZ_ASSERT(a1->Count() == 5, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 5, "unexpected count");
+
+ Point3D vector1(a1->Item(1).GetFloatValue(),
+ a1->Item(2).GetFloatValue(),
+ a1->Item(3).GetFloatValue());
+ vector1.Normalize();
+ Point3D vector2(a2->Item(1).GetFloatValue(),
+ a2->Item(2).GetFloatValue(),
+ a2->Item(3).GetFloatValue());
+ vector2.Normalize();
+
+ if (vector1 == vector2) {
+ // Handle rotate3d with matched (normalized) vectors.
+ nsCSSValue angle;
+ AddCSSValueAngle(1.0, a2->Item(4), -1.0, a1->Item(4), angle);
+ distance = angle.GetAngleValueInRadians() *
+ angle.GetAngleValueInRadians();
+ } else {
+ // Use quaternion vectors to get the angle difference. Both q1 and q2
+ // are unit vectors, so we can get their angle difference by
+ // cos(theta/2) = (q1 dot q2) / (|q1| * |q2|) = q1 dot q2.
+ gfxQuaternion q1(vector1, a1->Item(4).GetAngleValueInRadians());
+ gfxQuaternion q2(vector2, a2->Item(4).GetAngleValueInRadians());
+ distance = 2.0 * acos(clamped(q1.DotProduct(q2), -1.0, 1.0));
+ distance = distance * distance;
+ }
+ break;
+ }
+ case eCSSKeyword_perspective: {
+ MOZ_ASSERT(a1->Count() == 2, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 2, "unexpected count");
+
+ // We convert a perspective function into an equivalent matrix3d, and
+ // then do matrix decomposition to get the distance.
+ // Why don't we just subtract one perspective depth from the other?
+ // I think it's better to follow the logic of our interpolation,
+ // which does linear interpolation between two decomposed perspective
+ // vectors.
+ // e.g.
+ // Do interpolation between perspective(100px) and perspective(1000px).
+ // 1) Convert them into matrix3d, and then do matrix decomposition:
+ // perspective vector 1: perspective(0, 0, -1/100, 1);
+ // perspective vector 2: perspective(0, 0, -1/1000, 1);
+ // 2) Do linear interpolation between these two vectors.
+ // Therefore, we use the same rule to get the distance as what we do for
+ // matrix3d.
+
+ using nsStyleTransformMatrix::ApplyPerspectiveToMatrix;
+ Matrix4x4 m1;
+ ApplyPerspectiveToMatrix(m1, a1->Item(1).GetFloatValue());
+ Matrix4x4 m2;
+ ApplyPerspectiveToMatrix(m2, a2->Item(1).GetFloatValue());
+
+ distance = ComputeTransform3DMatrixDistance(m1, m2);
+ break;
+ }
+ case eCSSKeyword_matrix: {
+ MOZ_ASSERT(a1->Count() == 7, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 7, "unexpected count");
+
+ distance = ComputeTransform2DMatrixDistance(
+ nsStyleTransformMatrix::CSSValueArrayTo2DMatrix(a1),
+ nsStyleTransformMatrix::CSSValueArrayTo2DMatrix(a2));
+ break;
+ }
+ case eCSSKeyword_matrix3d: {
+ MOZ_ASSERT(a1->Count() == 17, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 17, "unexpected count");
+
+ distance = ComputeTransform3DMatrixDistance(
+ nsStyleTransformMatrix::CSSValueArrayTo3DMatrix(a1),
+ nsStyleTransformMatrix::CSSValueArrayTo3DMatrix(a2));
+ break;
+ }
+ case eCSSKeyword_interpolatematrix:
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported transform function");
+ break;
+ }
+ return distance;
+}
+
+static double
+ComputeTransformListDistance(const nsCSSValueList* aList1,
+ const nsCSSValueList* aList2)
+{
+ MOZ_ASSERT(aList1, "aList1 should be non-null.");
+ MOZ_ASSERT(aList2, "aList2 should be non-null.");
+
+ double distance = 0.0;
+ do {
+ distance += ComputeTransformDistance(aList1->mValue.GetArrayValue(),
+ aList2->mValue.GetArrayValue());
+ aList1 = aList1->mNext;
+ aList2 = aList2->mNext;
+ MOZ_ASSERT(!aList1 == !aList2,
+ "aList1 and aList2 should have the same length.");
+ } while (aList1);
+ return sqrt(distance);
+}
+
+static double
+ComputeMismatchedTransfromListDistance(const nsCSSValueList* aList1,
+ const nsCSSValueList* aList2,
+ nsStyleContext* aStyleContext)
+{
+ // We need nsStyleContext and nsPresContext to compute calc() values while
+ // processing the translate part of transforms.
+ if (!aStyleContext) {
+ return 0.0;
+ }
+
+ RuleNodeCacheConditions dontCare;
+ bool dontCareBool;
+ nsStyleTransformMatrix::TransformReferenceBox emptyRefBox;
+
+ Matrix4x4 m1 = nsStyleTransformMatrix::ReadTransforms(
+ aList1,
+ aStyleContext,
+ aStyleContext->PresContext(),
+ dontCare,
+ emptyRefBox,
+ nsPresContext::AppUnitsPerCSSPixel(),
+ &dontCareBool);
+ Matrix4x4 m2 = nsStyleTransformMatrix::ReadTransforms(
+ aList2,
+ aStyleContext,
+ aStyleContext->PresContext(),
+ dontCare,
+ emptyRefBox,
+ nsPresContext::AppUnitsPerCSSPixel(),
+ &dontCareBool);
+ return sqrt(ComputeTransform3DMatrixDistance(m1, m2));
+}
+
+bool
+StyleAnimationValue::ComputeDistance(nsCSSPropertyID aProperty,
+ const StyleAnimationValue& aStartValue,
+ const StyleAnimationValue& aEndValue,
+ nsStyleContext* aStyleContext,
+ double& aDistance)
+{
+ Unit commonUnit =
+ GetCommonUnit(aProperty, aStartValue.GetUnit(), aEndValue.GetUnit());
+
+ switch (commonUnit) {
+ case eUnit_Null:
+ case eUnit_Auto:
+ case eUnit_None:
+ case eUnit_Normal:
+ case eUnit_UnparsedString:
+ case eUnit_URL:
+ case eUnit_DiscreteCSSValue:
+ return false;
+
+ case eUnit_Enumerated:
+ switch (aProperty) {
+ case eCSSProperty_font_stretch: {
+ // just like eUnit_Integer.
+ int32_t startInt = aStartValue.GetIntValue();
+ int32_t endInt = aEndValue.GetIntValue();
+ aDistance = Abs(endInt - startInt);
+ return true;
+ }
+ default:
+ return false;
+ }
+ case eUnit_Visibility: {
+ int32_t startEnum = aStartValue.GetIntValue();
+ int32_t endEnum = aEndValue.GetIntValue();
+ if (startEnum == endEnum) {
+ aDistance = 0;
+ return true;
+ }
+ if ((startEnum == NS_STYLE_VISIBILITY_VISIBLE) ==
+ (endEnum == NS_STYLE_VISIBILITY_VISIBLE)) {
+ return false;
+ }
+ aDistance = 1;
+ return true;
+ }
+ case eUnit_Integer: {
+ int32_t startInt = aStartValue.GetIntValue();
+ int32_t endInt = aEndValue.GetIntValue();
+ aDistance = Abs(double(endInt) - double(startInt));
+ return true;
+ }
+ case eUnit_Coord: {
+ nscoord startCoord = aStartValue.GetCoordValue();
+ nscoord endCoord = aEndValue.GetCoordValue();
+ aDistance = Abs(double(endCoord) - double(startCoord));
+ return true;
+ }
+ case eUnit_Percent: {
+ float startPct = aStartValue.GetPercentValue();
+ float endPct = aEndValue.GetPercentValue();
+ aDistance = Abs(double(endPct) - double(startPct));
+ return true;
+ }
+ case eUnit_Float: {
+ float startFloat = aStartValue.GetFloatValue();
+ float endFloat = aEndValue.GetFloatValue();
+ aDistance = Abs(double(endFloat) - double(startFloat));
+ return true;
+ }
+ case eUnit_Color: {
+ aDistance = ComputeColorDistance(ExtractColor(aStartValue),
+ ExtractColor(aEndValue));
+ return true;
+ }
+ case eUnit_CurrentColor: {
+ aDistance = 0;
+ return true;
+ }
+ case eUnit_ComplexColor: {
+ ComplexColorData color1 = ExtractComplexColor(aStartValue);
+ ComplexColorData color2 = ExtractComplexColor(aEndValue);
+ // Common case is interpolating between a color and a currentcolor
+ if (color1.IsNumericColor() && color2.IsCurrentColor()) {
+ double dist = ComputeColorDistance(color1.mColor, NS_RGBA(0, 0, 0, 0));
+ aDistance = sqrt(dist * dist + 1);
+ return true;
+ }
+ if (color1.IsCurrentColor() && color2.IsNumericColor()) {
+ double dist = ComputeColorDistance(NS_RGBA(0, 0, 0, 0), color2.mColor);
+ aDistance = sqrt(dist * dist + 1);
+ return true;
+ }
+ // If we ever reach here, we may want to use the code in
+ // bug 1299741 comment 79 to compute it.
+ MOZ_ASSERT_UNREACHABLE("We shouldn't get here as we only call "
+ "ComputeDistance on pre-interpolation values");
+ aDistance = 0.0;
+ return true;
+ }
+ case eUnit_Calc: {
+ PixelCalcValue v1 = ExtractCalcValue(aStartValue);
+ PixelCalcValue v2 = ExtractCalcValue(aEndValue);
+ float difflen = v2.mLength - v1.mLength;
+ float diffpct = v2.mPercent - v1.mPercent;
+ aDistance = sqrt(difflen * difflen + diffpct * diffpct);
+ return true;
+ }
+ case eUnit_ObjectPosition: {
+ const nsCSSValue* position1 = aStartValue.GetCSSValueValue();
+ const nsCSSValue* position2 = aEndValue.GetCSSValueValue();
+ double squareDistance =
+ CalcPositionSquareDistance(*position1,
+ *position2);
+ aDistance = sqrt(squareDistance);
+ return true;
+ }
+ case eUnit_CSSValuePair: {
+ const nsCSSValuePair *pair1 = aStartValue.GetCSSValuePairValue();
+ const nsCSSValuePair *pair2 = aEndValue.GetCSSValuePairValue();
+ nsCSSUnit unit[2];
+ unit[0] = GetCommonUnit(aProperty, pair1->mXValue.GetUnit(),
+ pair2->mXValue.GetUnit());
+ unit[1] = GetCommonUnit(aProperty, pair1->mYValue.GetUnit(),
+ pair2->mYValue.GetUnit());
+ if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null ||
+ unit[0] == eCSSUnit_URL || unit[0] == eCSSUnit_Enumerated) {
+ return false;
+ }
+
+ double squareDistance = 0.0;
+ static nsCSSValue nsCSSValuePair::* const pairValues[2] = {
+ &nsCSSValuePair::mXValue, &nsCSSValuePair::mYValue
+ };
+ for (uint32_t i = 0; i < 2; ++i) {
+ nsCSSValue nsCSSValuePair::*member = pairValues[i];
+ double diffsquared;
+ switch (unit[i]) {
+ case eCSSUnit_Pixel: {
+ float diff = (pair1->*member).GetFloatValue() -
+ (pair2->*member).GetFloatValue();
+ diffsquared = diff * diff;
+ break;
+ }
+ case eCSSUnit_Percent: {
+ float diff = (pair1->*member).GetPercentValue() -
+ (pair2->*member).GetPercentValue();
+ diffsquared = diff * diff;
+ break;
+ }
+ case eCSSUnit_Calc: {
+ PixelCalcValue v1 = ExtractCalcValue(pair1->*member);
+ PixelCalcValue v2 = ExtractCalcValue(pair2->*member);
+ float difflen = v2.mLength - v1.mLength;
+ float diffpct = v2.mPercent - v1.mPercent;
+ diffsquared = difflen * difflen + diffpct * diffpct;
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ return false;
+ }
+ squareDistance += diffsquared;
+ }
+
+ aDistance = sqrt(squareDistance);
+ return true;
+ }
+ case eUnit_CSSValueTriplet: {
+ const nsCSSValueTriplet *triplet1 = aStartValue.GetCSSValueTripletValue();
+ const nsCSSValueTriplet *triplet2 = aEndValue.GetCSSValueTripletValue();
+ nsCSSUnit unit[3];
+ unit[0] = GetCommonUnit(aProperty, triplet1->mXValue.GetUnit(),
+ triplet2->mXValue.GetUnit());
+ unit[1] = GetCommonUnit(aProperty, triplet1->mYValue.GetUnit(),
+ triplet2->mYValue.GetUnit());
+ unit[2] = GetCommonUnit(aProperty, triplet1->mZValue.GetUnit(),
+ triplet2->mZValue.GetUnit());
+ if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null ||
+ unit[2] == eCSSUnit_Null) {
+ return false;
+ }
+
+ double squareDistance = 0.0;
+ static nsCSSValue nsCSSValueTriplet::* const pairValues[3] = {
+ &nsCSSValueTriplet::mXValue, &nsCSSValueTriplet::mYValue, &nsCSSValueTriplet::mZValue
+ };
+ for (uint32_t i = 0; i < 3; ++i) {
+ nsCSSValue nsCSSValueTriplet::*member = pairValues[i];
+ double diffsquared;
+ switch (unit[i]) {
+ case eCSSUnit_Pixel: {
+ float diff = (triplet1->*member).GetFloatValue() -
+ (triplet2->*member).GetFloatValue();
+ diffsquared = diff * diff;
+ break;
+ }
+ case eCSSUnit_Percent: {
+ float diff = (triplet1->*member).GetPercentValue() -
+ (triplet2->*member).GetPercentValue();
+ diffsquared = diff * diff;
+ break;
+ }
+ case eCSSUnit_Calc: {
+ PixelCalcValue v1 = ExtractCalcValue(triplet1->*member);
+ PixelCalcValue v2 = ExtractCalcValue(triplet2->*member);
+ float difflen = v2.mLength - v1.mLength;
+ float diffpct = v2.mPercent - v1.mPercent;
+ diffsquared = difflen * difflen + diffpct * diffpct;
+ break;
+ }
+ case eCSSUnit_Null:
+ diffsquared = 0;
+ break;
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ return false;
+ }
+ squareDistance += diffsquared;
+ }
+
+ aDistance = sqrt(squareDistance);
+ return true;
+ }
+ case eUnit_CSSRect: {
+ const nsCSSRect *rect1 = aStartValue.GetCSSRectValue();
+ const nsCSSRect *rect2 = aEndValue.GetCSSRectValue();
+ if (rect1->mTop.GetUnit() != rect2->mTop.GetUnit() ||
+ rect1->mRight.GetUnit() != rect2->mRight.GetUnit() ||
+ rect1->mBottom.GetUnit() != rect2->mBottom.GetUnit() ||
+ rect1->mLeft.GetUnit() != rect2->mLeft.GetUnit()) {
+ // At least until we have calc()
+ return false;
+ }
+
+ double squareDistance = 0.0;
+ for (uint32_t i = 0; i < ArrayLength(nsCSSRect::sides); ++i) {
+ nsCSSValue nsCSSRect::*member = nsCSSRect::sides[i];
+ MOZ_ASSERT((rect1->*member).GetUnit() == (rect2->*member).GetUnit(),
+ "should have returned above");
+ double diff;
+ switch ((rect1->*member).GetUnit()) {
+ case eCSSUnit_Pixel:
+ diff = (rect1->*member).GetFloatValue() -
+ (rect2->*member).GetFloatValue();
+ break;
+ case eCSSUnit_Auto:
+ diff = 0;
+ break;
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ return false;
+ }
+ squareDistance += diff * diff;
+ }
+
+ aDistance = sqrt(squareDistance);
+ return true;
+ }
+ case eUnit_Dasharray: {
+ // NOTE: This produces results on substantially different scales
+ // for length values and percentage values, which might even be
+ // mixed in the same property value. This means the result isn't
+ // particularly useful for paced animation.
+
+ // Call AddWeighted to make us lists of the same length.
+ StyleAnimationValue normValue1, normValue2;
+ if (!AddWeighted(aProperty, 1.0, aStartValue, 0.0, aEndValue,
+ normValue1) ||
+ !AddWeighted(aProperty, 0.0, aStartValue, 1.0, aEndValue,
+ normValue2)) {
+ return false;
+ }
+
+ double squareDistance = 0.0;
+ const nsCSSValueList *list1 = normValue1.GetCSSValueListValue();
+ const nsCSSValueList *list2 = normValue2.GetCSSValueListValue();
+
+ MOZ_ASSERT(!list1 == !list2, "lists should be same length");
+ while (list1) {
+ const nsCSSValue &val1 = list1->mValue;
+ const nsCSSValue &val2 = list2->mValue;
+
+ MOZ_ASSERT(val1.GetUnit() == val2.GetUnit(),
+ "unit match should be assured by AddWeighted");
+ double diff;
+ switch (val1.GetUnit()) {
+ case eCSSUnit_Percent:
+ diff = val1.GetPercentValue() - val2.GetPercentValue();
+ break;
+ case eCSSUnit_Number:
+ diff = val1.GetFloatValue() - val2.GetFloatValue();
+ break;
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ return false;
+ }
+ squareDistance += diff * diff;
+
+ list1 = list1->mNext;
+ list2 = list2->mNext;
+ MOZ_ASSERT(!list1 == !list2, "lists should be same length");
+ }
+
+ aDistance = sqrt(squareDistance);
+ return true;
+ }
+ case eUnit_Shadow: {
+ // Call AddWeighted to make us lists of the same length.
+ StyleAnimationValue normValue1, normValue2;
+ if (!AddWeighted(aProperty, 1.0, aStartValue, 0.0, aEndValue,
+ normValue1) ||
+ !AddWeighted(aProperty, 0.0, aStartValue, 1.0, aEndValue,
+ normValue2)) {
+ return false;
+ }
+
+ const nsCSSValueList *shadow1 = normValue1.GetCSSValueListValue();
+ const nsCSSValueList *shadow2 = normValue2.GetCSSValueListValue();
+
+ double squareDistance = 0.0;
+ MOZ_ASSERT(!shadow1 == !shadow2, "lists should be same length");
+ while (shadow1) {
+ nsCSSValue::Array *array1 = shadow1->mValue.GetArrayValue();
+ nsCSSValue::Array *array2 = shadow2->mValue.GetArrayValue();
+ for (size_t i = 0; i < 4; ++i) {
+ MOZ_ASSERT(array1->Item(i).GetUnit() == eCSSUnit_Pixel,
+ "unexpected unit");
+ MOZ_ASSERT(array2->Item(i).GetUnit() == eCSSUnit_Pixel,
+ "unexpected unit");
+ double diff = array1->Item(i).GetFloatValue() -
+ array2->Item(i).GetFloatValue();
+ squareDistance += diff * diff;
+ }
+
+ const nsCSSValue &color1 = array1->Item(4);
+ const nsCSSValue &color2 = array2->Item(4);
+#ifdef DEBUG
+ {
+ const nsCSSValue &inset1 = array1->Item(5);
+ const nsCSSValue &inset2 = array2->Item(5);
+ // There are only two possible states of the inset value:
+ // (1) GetUnit() == eCSSUnit_Null
+ // (2) GetUnit() == eCSSUnit_Enumerated &&
+ // GetIntValue() == NS_STYLE_BOX_SHADOW_INSET
+ MOZ_ASSERT(((color1.IsNumericColorUnit() &&
+ color2.IsNumericColorUnit()) ||
+ (color1.GetUnit() == color2.GetUnit())) &&
+ inset1 == inset2,
+ "AddWeighted should have failed");
+ }
+#endif
+
+ if (color1.GetUnit() != eCSSUnit_Null) {
+ double colorDistance = ComputeColorDistance(color1.GetColorValue(),
+ color2.GetColorValue());
+ squareDistance += colorDistance * colorDistance;
+ }
+
+ shadow1 = shadow1->mNext;
+ shadow2 = shadow2->mNext;
+ MOZ_ASSERT(!shadow1 == !shadow2, "lists should be same length");
+ }
+ aDistance = sqrt(squareDistance);
+ return true;
+ }
+ case eUnit_Shape:
+ aDistance = ComputeShapeDistance(aProperty,
+ aStartValue.GetCSSValueArrayValue(),
+ aEndValue.GetCSSValueArrayValue());
+ return true;
+
+ case eUnit_Filter:
+ // Bug 1286151: Support paced animations for filter function
+ // interpolation.
+ return false;
+
+ case eUnit_Transform: {
+ // FIXME: We don't have an official spec to define the distance of
+ // two transform lists, but paced spacing (defined in Web Animations API)
+ // needs this, so we implement this according to the concept of the
+ // interpolation of two transform lists.
+ // Issue: https://www.w3.org/TR/web-animations-1/#issue-789f9fd1
+
+ const nsCSSValueList* list1 =
+ aStartValue.GetCSSValueSharedListValue()->mHead;
+ const nsCSSValueList* list2 =
+ aEndValue.GetCSSValueSharedListValue()->mHead;
+ MOZ_ASSERT(list1);
+ MOZ_ASSERT(list2);
+
+ if (list1->mValue.GetUnit() == eCSSUnit_None &&
+ list2->mValue.GetUnit() == eCSSUnit_None) {
+ // Both none, nothing happens.
+ aDistance = 0.0;
+ } else if (list1->mValue.GetUnit() == eCSSUnit_None) {
+ nsAutoPtr<nsCSSValueList> none(AddTransformLists(0, list2, 0, list2));
+ aDistance = ComputeTransformListDistance(none, list2);
+ } else if (list2->mValue.GetUnit() == eCSSUnit_None) {
+ nsAutoPtr<nsCSSValueList> none(AddTransformLists(0, list1, 0, list1));
+ aDistance = ComputeTransformListDistance(list1, none);
+ } else {
+ const nsCSSValueList *item1 = list1, *item2 = list2;
+ do {
+ nsCSSKeyword func1 = nsStyleTransformMatrix::TransformFunctionOf(
+ item1->mValue.GetArrayValue());
+ nsCSSKeyword func2 = nsStyleTransformMatrix::TransformFunctionOf(
+ item2->mValue.GetArrayValue());
+ if (!TransformFunctionsMatch(func1, func2)) {
+ break;
+ }
+
+ item1 = item1->mNext;
+ item2 = item2->mNext;
+ } while (item1 && item2);
+
+ if (item1 || item2) {
+ // Either the transform function types don't match or
+ // the lengths don't match.
+ aDistance =
+ ComputeMismatchedTransfromListDistance(list1, list2, aStyleContext);
+ } else {
+ aDistance = ComputeTransformListDistance(list1, list2);
+ }
+ }
+ return true;
+ }
+ case eUnit_BackgroundPositionCoord: {
+ const nsCSSValueList *position1 = aStartValue.GetCSSValueListValue();
+ const nsCSSValueList *position2 = aEndValue.GetCSSValueListValue();
+
+ double squareDistance = 0.0;
+ MOZ_ASSERT(!position1 == !position2, "lists should be same length");
+
+ while (position1 && position2) {
+ squareDistance += CalcPositionCoordSquareDistance(position1->mValue,
+ position2->mValue);
+ position1 = position1->mNext;
+ position2 = position2->mNext;
+ }
+ // fail if lists differ in length.
+ if (position1 || position2) {
+ return false;
+ }
+
+ aDistance = sqrt(squareDistance);
+ return true;
+ }
+ case eUnit_CSSValuePairList: {
+ const nsCSSValuePairList *list1 = aStartValue.GetCSSValuePairListValue();
+ const nsCSSValuePairList *list2 = aEndValue.GetCSSValuePairListValue();
+ double squareDistance = 0.0;
+ do {
+ static nsCSSValue nsCSSValuePairList::* const pairListValues[] = {
+ &nsCSSValuePairList::mXValue,
+ &nsCSSValuePairList::mYValue,
+ };
+ for (uint32_t i = 0; i < ArrayLength(pairListValues); ++i) {
+ const nsCSSValue &v1 = list1->*(pairListValues[i]);
+ const nsCSSValue &v2 = list2->*(pairListValues[i]);
+ nsCSSUnit unit =
+ GetCommonUnit(aProperty, v1.GetUnit(), v2.GetUnit());
+ if (unit == eCSSUnit_Null) {
+ return false;
+ }
+ double diffsquared = 0.0;
+ switch (unit) {
+ case eCSSUnit_Pixel: {
+ float diff = v1.GetFloatValue() - v2.GetFloatValue();
+ diffsquared = diff * diff;
+ break;
+ }
+ case eCSSUnit_Percent: {
+ float diff = v1.GetPercentValue() - v2.GetPercentValue();
+ diffsquared = diff * diff;
+ break;
+ }
+ case eCSSUnit_Calc: {
+ PixelCalcValue val1 = ExtractCalcValue(v1);
+ PixelCalcValue val2 = ExtractCalcValue(v2);
+ float difflen = val2.mLength - val1.mLength;
+ float diffpct = val2.mPercent - val1.mPercent;
+ diffsquared = difflen * difflen + diffpct * diffpct;
+ break;
+ }
+ default:
+ if (v1 != v2) {
+ return false;
+ }
+ break;
+ }
+ squareDistance += diffsquared;
+ }
+ list1 = list1->mNext;
+ list2 = list2->mNext;
+ } while (list1 && list2);
+ if (list1 || list2) {
+ // We can't interpolate lists of different lengths.
+ return false;
+ }
+ aDistance = sqrt(squareDistance);
+ return true;
+ }
+ }
+
+ MOZ_ASSERT(false, "Can't compute distance using the given common unit");
+ return false;
+}
+
+static inline void
+AddCSSValueNumber(double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ nsCSSValue &aResult, uint32_t aValueRestrictions = 0)
+{
+ MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Number, "unexpected unit");
+ MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Number, "unexpected unit");
+ aResult.SetFloatValue(RestrictValue(aValueRestrictions,
+ aCoeff1 * aValue1.GetFloatValue() +
+ aCoeff2 * aValue2.GetFloatValue()),
+ eCSSUnit_Number);
+}
+
+static inline float
+GetNumberOrPercent(const nsCSSValue &aValue)
+{
+ nsCSSUnit unit = aValue.GetUnit();
+ MOZ_ASSERT(unit == eCSSUnit_Number || unit == eCSSUnit_Percent,
+ "unexpected unit");
+ return (unit == eCSSUnit_Number) ?
+ aValue.GetFloatValue() : aValue.GetPercentValue();
+}
+
+static inline void
+AddCSSValuePercentNumber(const uint32_t aValueRestrictions,
+ double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ nsCSSValue &aResult, float aInitialVal)
+{
+ float n1 = GetNumberOrPercent(aValue1);
+ float n2 = GetNumberOrPercent(aValue2);
+
+ // Rather than interpolating aValue1 and aValue2 directly, we
+ // interpolate their *distances from aInitialVal* (the initial value,
+ // which is either 1 or 0 for "filter" functions). This matters in
+ // cases where aInitialVal is nonzero and the coefficients don't add
+ // up to 1. For example, if initialVal is 1, aCoeff1 is 0.5, and
+ // aCoeff2 is 0, then we'll return the value halfway between 1 and
+ // aValue1, rather than the value halfway between 0 and aValue1.
+ // Note that we do something similar in AddTransformScale().
+ float result = (n1 - aInitialVal) * aCoeff1 + (n2 - aInitialVal) * aCoeff2;
+ aResult.SetFloatValue(RestrictValue(aValueRestrictions, result + aInitialVal),
+ eCSSUnit_Number);
+}
+
+enum class ColorAdditionType {
+ Clamped, // Clamp each color channel after adding.
+ Unclamped // Do not clamp color channels after adding.
+};
+
+// Unclamped AddWeightedColors.
+static RGBAColorData
+AddWeightedColors(double aCoeff1, const RGBAColorData& aValue1,
+ double aCoeff2, const RGBAColorData& aValue2)
+{
+ float factor1 = aValue1.mA * aCoeff1;
+ float factor2 = aValue2.mA * aCoeff2;
+ float resultA = factor1 + factor2;
+ if (resultA <= 0.0) {
+ return {0, 0, 0, 0};
+ }
+
+ if (resultA > 1.0) {
+ resultA = 1.0;
+ }
+
+ float resultFactor = 1.0f / resultA;
+ return RGBAColorData(
+ (aValue1.mR * factor1 + aValue2.mR * factor2) * resultFactor,
+ (aValue1.mG * factor1 + aValue2.mG * factor2) * resultFactor,
+ (aValue1.mB * factor1 + aValue2.mB * factor2) * resultFactor,
+ resultA);
+}
+
+// Multiplies |aValue| color by |aDilutionRation|.
+static nscolor
+DiluteColor(const RGBAColorData& aValue, double aDilutionRatio)
+{
+ MOZ_ASSERT(aDilutionRatio >= 0.0 && aDilutionRatio <= 1.0,
+ "Dilution ratio should be in [0, 1]");
+ float resultA = aValue.mA * aDilutionRatio;
+ return resultA <= 0.0 ? NS_RGBA(0, 0, 0, 0)
+ : aValue.WithAlpha(resultA).ToColor();
+}
+
+// Clamped AddWeightedColors.
+static nscolor
+AddWeightedColorsAndClamp(double aCoeff1, const RGBAColorData& aValue1,
+ double aCoeff2, const RGBAColorData& aValue2)
+{
+ // We are using AddWeighted() with a zero aCoeff2 for colors to
+ // pretend AddWeighted() against transparent color, i.e. rgba(0, 0, 0, 0).
+ // But unpremultiplication in AddWeightedColors() does not work well
+ // for such cases, so we use another function named DiluteColor() which
+ // has a similar logic to AddWeightedColors().
+ return aCoeff2 == 0.0
+ ? DiluteColor(aValue1, aCoeff1)
+ : AddWeightedColors(aCoeff1, aValue1, aCoeff2, aValue2).ToColor();
+}
+
+void
+AppendToCSSValueList(UniquePtr<nsCSSValueList>& aHead,
+ UniquePtr<nsCSSValueList>&& aValueToAppend,
+ nsCSSValueList** aTail)
+{
+ MOZ_ASSERT(!aHead == !*aTail,
+ "Can't have head w/o tail, & vice versa");
+
+ if (!aHead) {
+ aHead = Move(aValueToAppend);
+ *aTail = aHead.get();
+ } else {
+ (*aTail) = (*aTail)->mNext = aValueToAppend.release();
+ }
+}
+
+static UniquePtr<nsCSSValueList>
+AddWeightedShadowItems(double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ ColorAdditionType aColorAdditionType)
+{
+ // X, Y, Radius, Spread, Color, Inset
+ MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Array,
+ "wrong unit");
+ MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Array,
+ "wrong unit");
+ nsCSSValue::Array *array1 = aValue1.GetArrayValue();
+ nsCSSValue::Array *array2 = aValue2.GetArrayValue();
+ RefPtr<nsCSSValue::Array> resultArray = nsCSSValue::Array::Create(6);
+
+ for (size_t i = 0; i < 4; ++i) {
+ AddCSSValuePixel(aCoeff1, array1->Item(i), aCoeff2, array2->Item(i),
+ resultArray->Item(i),
+ // blur radius must be nonnegative
+ (i == 2) ? CSS_PROPERTY_VALUE_NONNEGATIVE : 0);
+ }
+
+ const nsCSSValue& colorValue1 = array1->Item(4);
+ const nsCSSValue& colorValue2 = array2->Item(4);
+ const nsCSSValue& inset1 = array1->Item(5);
+ const nsCSSValue& inset2 = array2->Item(5);
+ if ((colorValue1.GetUnit() != colorValue2.GetUnit() &&
+ (!colorValue1.IsNumericColorUnit() ||
+ !colorValue2.IsNumericColorUnit())) ||
+ inset1.GetUnit() != inset2.GetUnit()) {
+ // We don't know how to animate between color and no-color, or
+ // between inset and not-inset.
+ // NOTE: In case when both colors' units are eCSSUnit_Null, that means
+ // neither color value was specified, so we can interpolate.
+ return nullptr;
+ }
+
+ if (colorValue1.GetUnit() != eCSSUnit_Null) {
+ RGBAColorData color1 = ExtractColor(colorValue1);
+ RGBAColorData color2 = ExtractColor(colorValue2);
+ if (aColorAdditionType == ColorAdditionType::Clamped) {
+ resultArray->Item(4).SetColorValue(
+ AddWeightedColorsAndClamp(aCoeff1, color1, aCoeff2, color2));
+ } else {
+ resultArray->Item(4).SetRGBAColorValue(
+ AddWeightedColors(aCoeff1, color1, aCoeff2, color2));
+ }
+ }
+
+ MOZ_ASSERT(inset1 == inset2, "should match");
+ resultArray->Item(5) = inset1;
+
+ auto resultItem = MakeUnique<nsCSSValueList>();
+ resultItem->mValue.SetArrayValue(resultArray, eCSSUnit_Array);
+ return resultItem;
+}
+
+static void
+AddTransformScale(double aCoeff1, const nsCSSValue &aValue1,
+ double aCoeff2, const nsCSSValue &aValue2,
+ nsCSSValue &aResult)
+{
+ // Handle scale, and the two matrix components where identity is 1, by
+ // subtracting 1, multiplying by the coefficients, and then adding 1
+ // back. This gets the right AddWeighted behavior and gets us the
+ // interpolation-against-identity behavior for free.
+ MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Number, "unexpected unit");
+ MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Number, "unexpected unit");
+
+ float v1 = aValue1.GetFloatValue() - 1.0f,
+ v2 = aValue2.GetFloatValue() - 1.0f;
+ float result = v1 * aCoeff1 + v2 * aCoeff2;
+ aResult.SetFloatValue(EnsureNotNan(result + 1.0f), eCSSUnit_Number);
+}
+
+/* static */ already_AddRefed<nsCSSValue::Array>
+StyleAnimationValue::AppendTransformFunction(nsCSSKeyword aTransformFunction,
+ nsCSSValueList**& aListTail)
+{
+ RefPtr<nsCSSValue::Array> arr = AppendFunction(aTransformFunction);
+ nsCSSValueList *item = new nsCSSValueList;
+ item->mValue.SetArrayValue(arr, eCSSUnit_Function);
+
+ *aListTail = item;
+ aListTail = &item->mNext;
+
+ return arr.forget();
+}
+
+template<typename T>
+T InterpolateNumerically(const T& aOne, const T& aTwo, double aCoeff)
+{
+ return aOne + (aTwo - aOne) * aCoeff;
+}
+
+
+/* static */ Matrix4x4
+StyleAnimationValue::InterpolateTransformMatrix(const Matrix4x4 &aMatrix1,
+ const Matrix4x4 &aMatrix2,
+ double aProgress)
+{
+ // Decompose both matrices
+
+ // TODO: What do we do if one of these returns false (singular matrix)
+ Point3D scale1(1, 1, 1), translate1;
+ Point4D perspective1(0, 0, 0, 1);
+ gfxQuaternion rotate1;
+ nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f};
+
+ Point3D scale2(1, 1, 1), translate2;
+ Point4D perspective2(0, 0, 0, 1);
+ gfxQuaternion rotate2;
+ nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f};
+
+ Matrix matrix2d1, matrix2d2;
+ if (aMatrix1.Is2D(&matrix2d1) && aMatrix2.Is2D(&matrix2d2)) {
+ Decompose2DMatrix(matrix2d1, scale1, shear1, rotate1, translate1);
+ Decompose2DMatrix(matrix2d2, scale2, shear2, rotate2, translate2);
+ } else {
+ Decompose3DMatrix(aMatrix1, scale1, shear1,
+ rotate1, translate1, perspective1);
+ Decompose3DMatrix(aMatrix2, scale2, shear2,
+ rotate2, translate2, perspective2);
+ }
+
+ // Interpolate each of the pieces
+ Matrix4x4 result;
+
+ Point4D perspective =
+ InterpolateNumerically(perspective1, perspective2, aProgress);
+ result.SetTransposedVector(3, perspective);
+
+ Point3D translate =
+ InterpolateNumerically(translate1, translate2, aProgress);
+ result.PreTranslate(translate.x, translate.y, translate.z);
+
+ gfxQuaternion q3 = rotate1.Slerp(rotate2, aProgress);
+ Matrix4x4 rotate = q3.ToMatrix();
+ if (!rotate.IsIdentity()) {
+ result = rotate * result;
+ }
+
+ // TODO: Would it be better to interpolate these as angles?
+ // How do we convert back to angles?
+ float yzshear =
+ InterpolateNumerically(shear1[ShearType::YZSHEAR],
+ shear2[ShearType::YZSHEAR],
+ aProgress);
+ if (yzshear != 0.0) {
+ result.SkewYZ(yzshear);
+ }
+
+ float xzshear =
+ InterpolateNumerically(shear1[ShearType::XZSHEAR],
+ shear2[ShearType::XZSHEAR],
+ aProgress);
+ if (xzshear != 0.0) {
+ result.SkewXZ(xzshear);
+ }
+
+ float xyshear =
+ InterpolateNumerically(shear1[ShearType::XYSHEAR],
+ shear2[ShearType::XYSHEAR],
+ aProgress);
+ if (xyshear != 0.0) {
+ result.SkewXY(xyshear);
+ }
+
+ Point3D scale =
+ InterpolateNumerically(scale1, scale2, aProgress);
+ if (scale != Point3D(1.0, 1.0, 1.0)) {
+ result.PreScale(scale.x, scale.y, scale.z);
+ }
+
+ return result;
+}
+
+static nsCSSValueList*
+AddDifferentTransformLists(double aCoeff1, const nsCSSValueList* aList1,
+ double aCoeff2, const nsCSSValueList* aList2)
+{
+ nsAutoPtr<nsCSSValueList> result;
+ nsCSSValueList **resultTail = getter_Transfers(result);
+
+ RefPtr<nsCSSValue::Array> arr;
+ arr =
+ StyleAnimationValue::AppendTransformFunction(eCSSKeyword_interpolatematrix,
+ resultTail);
+
+ // FIXME: We should change the other transform code to also only
+ // take a single progress value, as having values that don't
+ // sum to 1 doesn't make sense for these.
+ if (aList1 == aList2) {
+ arr->Item(1).Reset();
+ } else {
+ aList1->CloneInto(arr->Item(1).SetListValue());
+ }
+
+ aList2->CloneInto(arr->Item(2).SetListValue());
+ arr->Item(3).SetPercentValue(aCoeff2);
+
+ return result.forget();
+}
+
+static UniquePtr<nsCSSValueList>
+AddWeightedFilterFunctionImpl(double aCoeff1, const nsCSSValueList* aList1,
+ double aCoeff2, const nsCSSValueList* aList2,
+ ColorAdditionType aColorAdditionType)
+{
+ // AddWeightedFilterFunction should be our only caller, and it should ensure
+ // that both args are non-null.
+ MOZ_ASSERT(aList1, "expected filter list");
+ MOZ_ASSERT(aList2, "expected filter list");
+ MOZ_ASSERT(aList1->mValue.GetUnit() == eCSSUnit_Function,
+ "expected function");
+ MOZ_ASSERT(aList2->mValue.GetUnit() == eCSSUnit_Function,
+ "expected function");
+ RefPtr<nsCSSValue::Array> a1 = aList1->mValue.GetArrayValue(),
+ a2 = aList2->mValue.GetArrayValue();
+ nsCSSKeyword filterFunction = a1->Item(0).GetKeywordValue();
+ if (filterFunction != a2->Item(0).GetKeywordValue()) {
+ return nullptr; // Can't add two filters of different types.
+ }
+
+ auto resultList = MakeUnique<nsCSSValueList>();
+ nsCSSValue::Array* result =
+ resultList->mValue.InitFunction(filterFunction, 1);
+
+ // "hue-rotate" is the only filter-function that accepts negative values, and
+ // we don't use this "restrictions" variable in its clause below.
+ const uint32_t restrictions = CSS_PROPERTY_VALUE_NONNEGATIVE;
+ const nsCSSValue& funcArg1 = a1->Item(1);
+ const nsCSSValue& funcArg2 = a2->Item(1);
+ nsCSSValue& resultArg = result->Item(1);
+ float initialVal = 1.0f;
+ switch (filterFunction) {
+ case eCSSKeyword_blur: {
+ nsCSSUnit unit;
+ if (funcArg1.GetUnit() == funcArg2.GetUnit()) {
+ unit = funcArg1.GetUnit();
+ } else {
+ // If units differ, we'll just combine them with calc().
+ unit = eCSSUnit_Calc;
+ }
+ if (!AddCSSValuePixelPercentCalc(restrictions,
+ unit,
+ aCoeff1, funcArg1,
+ aCoeff2, funcArg2,
+ resultArg)) {
+ return nullptr;
+ }
+ break;
+ }
+ case eCSSKeyword_grayscale:
+ case eCSSKeyword_invert:
+ case eCSSKeyword_sepia:
+ initialVal = 0.0f;
+ MOZ_FALLTHROUGH;
+ case eCSSKeyword_brightness:
+ case eCSSKeyword_contrast:
+ case eCSSKeyword_opacity:
+ case eCSSKeyword_saturate:
+ AddCSSValuePercentNumber(restrictions,
+ aCoeff1, funcArg1,
+ aCoeff2, funcArg2,
+ resultArg,
+ initialVal);
+ break;
+ case eCSSKeyword_hue_rotate:
+ AddCSSValueAngle(aCoeff1, funcArg1,
+ aCoeff2, funcArg2,
+ resultArg);
+ break;
+ case eCSSKeyword_drop_shadow: {
+ MOZ_ASSERT(!funcArg1.GetListValue()->mNext &&
+ !funcArg2.GetListValue()->mNext,
+ "drop-shadow filter func doesn't support lists");
+ UniquePtr<nsCSSValueList> shadowValue =
+ AddWeightedShadowItems(aCoeff1,
+ funcArg1.GetListValue()->mValue,
+ aCoeff2,
+ funcArg2.GetListValue()->mValue,
+ aColorAdditionType);
+ if (!shadowValue) {
+ return nullptr;
+ }
+ resultArg.AdoptListValue(Move(shadowValue));
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "unknown filter function");
+ return nullptr;
+ }
+
+ return resultList;
+}
+
+static UniquePtr<nsCSSValueList>
+AddWeightedFilterFunction(double aCoeff1, const nsCSSValueList* aList1,
+ double aCoeff2, const nsCSSValueList* aList2,
+ ColorAdditionType aColorAdditionType)
+{
+ MOZ_ASSERT(aList1 || aList2,
+ "one function list item must not be null");
+ // Note that one of our arguments could be null, indicating that
+ // it's the initial value. Rather than adding special null-handling
+ // logic, we just check for null values and replace them with
+ // 0 * the other value. That way, AddWeightedFilterFunctionImpl can assume
+ // its args are non-null.
+ if (!aList1) {
+ return AddWeightedFilterFunctionImpl(aCoeff2, aList2, 0, aList2,
+ aColorAdditionType);
+ }
+ if (!aList2) {
+ return AddWeightedFilterFunctionImpl(aCoeff1, aList1, 0, aList1,
+ aColorAdditionType);
+ }
+
+ return AddWeightedFilterFunctionImpl(aCoeff1, aList1, aCoeff2, aList2,
+ aColorAdditionType);
+}
+
+static inline uint32_t
+ShapeArgumentCount(nsCSSKeyword aShapeFunction)
+{
+ switch (aShapeFunction) {
+ case eCSSKeyword_circle:
+ return 2; // radius and center point
+ case eCSSKeyword_polygon:
+ return 2; // fill rule and a list of points
+ case eCSSKeyword_ellipse:
+ return 3; // two radii and center point
+ case eCSSKeyword_inset:
+ return 5; // four edge offsets and a list of corner radii
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown shape type");
+ return 0;
+ }
+}
+
+static void
+AddPositions(double aCoeff1, const nsCSSValue& aPos1,
+ double aCoeff2, const nsCSSValue& aPos2,
+ nsCSSValue& aResultPos)
+{
+ MOZ_ASSERT(aPos1.GetUnit() == eCSSUnit_Array &&
+ aPos2.GetUnit() == eCSSUnit_Array,
+ "Args should be CSS <position>s, encoded as arrays");
+
+ const nsCSSValue::Array* posArray1 = aPos1.GetArrayValue();
+ const nsCSSValue::Array* posArray2 = aPos2.GetArrayValue();
+ MOZ_ASSERT(posArray1->Count() == 4 && posArray2->Count() == 4,
+ "CSSParserImpl::ParsePositionValue creates an array of length "
+ "4 - how did we get here?");
+
+ nsCSSValue::Array* resultPosArray = nsCSSValue::Array::Create(4);
+ aResultPos.SetArrayValue(resultPosArray, eCSSUnit_Array);
+
+ // Only iterate over elements 1 and 3. The <position> is 'uncomputed' to
+ // only those elements. See also the comment in SetPositionValue.
+ for (size_t i = 1; i < 4; i += 2) {
+ const nsCSSValue& v1 = posArray1->Item(i);
+ const nsCSSValue& v2 = posArray2->Item(i);
+ nsCSSValue& vr = resultPosArray->Item(i);
+ AddCSSValueCanonicalCalc(aCoeff1, v1,
+ aCoeff2, v2, vr);
+ }
+}
+
+static Maybe<nsCSSValuePair>
+AddCSSValuePair(nsCSSPropertyID aProperty, uint32_t aRestrictions,
+ double aCoeff1, const nsCSSValuePair* aPair1,
+ double aCoeff2, const nsCSSValuePair* aPair2)
+{
+ MOZ_ASSERT(aPair1, "expected pair");
+ MOZ_ASSERT(aPair2, "expected pair");
+
+ Maybe<nsCSSValuePair> result;
+ nsCSSUnit unit[2];
+ unit[0] = GetCommonUnit(aProperty, aPair1->mXValue.GetUnit(),
+ aPair2->mXValue.GetUnit());
+ unit[1] = GetCommonUnit(aProperty, aPair1->mYValue.GetUnit(),
+ aPair2->mYValue.GetUnit());
+ if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null ||
+ unit[0] == eCSSUnit_URL || unit[0] == eCSSUnit_Enumerated) {
+ return result; // Nothing() (returning |result| for RVO)
+ }
+
+ result.emplace();
+
+ static nsCSSValue nsCSSValuePair::* const pairValues[2] = {
+ &nsCSSValuePair::mXValue, &nsCSSValuePair::mYValue
+ };
+ for (uint32_t i = 0; i < 2; ++i) {
+ nsCSSValue nsCSSValuePair::*member = pairValues[i];
+ if (!AddCSSValuePixelPercentCalc(aRestrictions, unit[i],
+ aCoeff1, aPair1->*member,
+ aCoeff2, aPair2->*member,
+ result.ref().*member) ) {
+ MOZ_ASSERT(false, "unexpected unit");
+ result.reset();
+ return result; // Nothing() (returning |result| for RVO)
+ }
+ }
+
+ return result;
+}
+
+static UniquePtr<nsCSSValuePairList>
+AddCSSValuePairList(nsCSSPropertyID aProperty,
+ double aCoeff1, const nsCSSValuePairList* aList1,
+ double aCoeff2, const nsCSSValuePairList* aList2)
+{
+ MOZ_ASSERT(aList1, "Can't add a null list");
+ MOZ_ASSERT(aList2, "Can't add a null list");
+
+ auto result = MakeUnique<nsCSSValuePairList>();
+ nsCSSValuePairList* resultPtr = result.get();
+
+ do {
+ static nsCSSValue nsCSSValuePairList::* const pairListValues[] = {
+ &nsCSSValuePairList::mXValue,
+ &nsCSSValuePairList::mYValue,
+ };
+ uint32_t restrictions = nsCSSProps::ValueRestrictions(aProperty);
+ for (uint32_t i = 0; i < ArrayLength(pairListValues); ++i) {
+ const nsCSSValue& v1 = aList1->*(pairListValues[i]);
+ const nsCSSValue& v2 = aList2->*(pairListValues[i]);
+
+ nsCSSValue& vr = resultPtr->*(pairListValues[i]);
+ nsCSSUnit unit =
+ GetCommonUnit(aProperty, v1.GetUnit(), v2.GetUnit());
+ if (unit == eCSSUnit_Null) {
+ return nullptr;
+ }
+ if (!AddCSSValuePixelPercentCalc(restrictions, unit,
+ aCoeff1, v1,
+ aCoeff2, v2, vr)) {
+ if (v1 != v2) {
+ return nullptr;
+ }
+ vr = v1;
+ }
+ }
+ aList1 = aList1->mNext;
+ aList2 = aList2->mNext;
+ if (!aList1 || !aList2) {
+ break;
+ }
+ resultPtr->mNext = new nsCSSValuePairList;
+ resultPtr = resultPtr->mNext;
+ } while (aList1 && aList2);
+
+ if (aList1 || aList2) {
+ return nullptr; // We can't interpolate lists of different lengths
+ }
+
+ return result;
+}
+
+static already_AddRefed<nsCSSValue::Array>
+AddShapeFunction(nsCSSPropertyID aProperty,
+ double aCoeff1, const nsCSSValue::Array* aArray1,
+ double aCoeff2, const nsCSSValue::Array* aArray2,
+ Restrictions aRestriction)
+{
+ MOZ_ASSERT(aArray1 && aArray1->Count() == 2, "expected shape function");
+ MOZ_ASSERT(aArray2 && aArray2->Count() == 2, "expected shape function");
+ MOZ_ASSERT(aArray1->Item(0).GetUnit() == eCSSUnit_Function,
+ "expected function");
+ MOZ_ASSERT(aArray2->Item(0).GetUnit() == eCSSUnit_Function,
+ "expected function");
+ MOZ_ASSERT(aArray1->Item(1).GetUnit() == eCSSUnit_Enumerated,
+ "expected geometry-box");
+ MOZ_ASSERT(aArray2->Item(1).GetUnit() == eCSSUnit_Enumerated,
+ "expected geometry-box");
+
+ if (aArray1->Item(1).GetIntValue() != aArray2->Item(1).GetIntValue()) {
+ return nullptr; // Both shapes must use the same reference box.
+ }
+
+ const nsCSSValue::Array* func1 = aArray1->Item(0).GetArrayValue();
+ const nsCSSValue::Array* func2 = aArray2->Item(0).GetArrayValue();
+ nsCSSKeyword shapeFuncName = func1->Item(0).GetKeywordValue();
+ if (shapeFuncName != func2->Item(0).GetKeywordValue()) {
+ return nullptr; // Can't add two shapes of different types.
+ }
+
+ RefPtr<nsCSSValue::Array> result = nsCSSValue::Array::Create(2);
+
+ nsCSSValue::Array* resultFuncArgs =
+ result->Item(0).InitFunction(shapeFuncName,
+ ShapeArgumentCount(shapeFuncName));
+ switch (shapeFuncName) {
+ case eCSSKeyword_ellipse:
+ // Add ellipses' |ry| values (but fail if we encounter an enum):
+ if (!AddCSSValuePixelPercentCalc(aRestriction == Restrictions::Enable
+ ? CSS_PROPERTY_VALUE_NONNEGATIVE
+ : 0,
+ GetCommonUnit(aProperty,
+ func1->Item(2).GetUnit(),
+ func2->Item(2).GetUnit()),
+ aCoeff1, func1->Item(2),
+ aCoeff2, func2->Item(2),
+ resultFuncArgs->Item(2))) {
+ return nullptr;
+ }
+ MOZ_FALLTHROUGH; // to handle rx and center point
+ case eCSSKeyword_circle: {
+ // Add circles' |r| (or ellipses' |rx|) values:
+ if (!AddCSSValuePixelPercentCalc(aRestriction == Restrictions::Enable
+ ? CSS_PROPERTY_VALUE_NONNEGATIVE
+ : 0,
+ GetCommonUnit(aProperty,
+ func1->Item(1).GetUnit(),
+ func2->Item(1).GetUnit()),
+ aCoeff1, func1->Item(1),
+ aCoeff2, func2->Item(1),
+ resultFuncArgs->Item(1))) {
+ return nullptr;
+ }
+ // Add center points (defined as a <position>).
+ size_t posIndex = shapeFuncName == eCSSKeyword_circle ? 2 : 3;
+ AddPositions(aCoeff1, func1->Item(posIndex),
+ aCoeff2, func2->Item(posIndex),
+ resultFuncArgs->Item(posIndex));
+ break;
+ }
+ case eCSSKeyword_polygon: {
+ // Add polygons' corresponding points (if the fill rule matches):
+ int32_t fillRule = func1->Item(1).GetIntValue();
+ if (fillRule != func2->Item(1).GetIntValue()) {
+ return nullptr; // can't interpolate between different fill rules
+ }
+ resultFuncArgs->Item(1).SetIntValue(fillRule, eCSSUnit_Enumerated);
+
+ const nsCSSValuePairList* points1 = func1->Item(2).GetPairListValue();
+ const nsCSSValuePairList* points2 = func2->Item(2).GetPairListValue();
+ UniquePtr<nsCSSValuePairList> resultPoints =
+ AddCSSValuePairList(aProperty, aCoeff1, points1, aCoeff2, points2);
+ if (!resultPoints) {
+ return nullptr;
+ }
+ resultFuncArgs->Item(2).AdoptPairListValue(Move(resultPoints));
+ break;
+ }
+ case eCSSKeyword_inset: {
+ MOZ_ASSERT(func1->Count() == 6 && func2->Count() == 6,
+ "Update for CSSParserImpl::ParseInsetFunction changes");
+ // Items 1-4 are respectively the top, right, bottom and left offsets
+ // from the reference box.
+ for (size_t i = 1; i <= 4; ++i) {
+ if (!AddCSSValuePixelPercentCalc(aRestriction == Restrictions::Enable
+ ? CSS_PROPERTY_VALUE_NONNEGATIVE
+ : 0,
+ GetCommonUnit(aProperty,
+ func1->Item(i).GetUnit(),
+ func2->Item(i).GetUnit()),
+ aCoeff1, func1->Item(i),
+ aCoeff2, func2->Item(i),
+ resultFuncArgs->Item(i))) {
+ return nullptr;
+ }
+ }
+ // Item 5 contains the radii of the rounded corners for the inset
+ // rectangle.
+ MOZ_ASSERT(func1->Item(5).GetUnit() == eCSSUnit_Array &&
+ func2->Item(5).GetUnit() == eCSSUnit_Array,
+ "Expected two arrays");
+ const nsCSSValue::Array* radii1 = func1->Item(5).GetArrayValue();
+ const nsCSSValue::Array* radii2 = func2->Item(5).GetArrayValue();
+ MOZ_ASSERT(radii1->Count() == 4 && radii2->Count() == 4);
+ nsCSSValue::Array* resultRadii = nsCSSValue::Array::Create(4);
+ resultFuncArgs->Item(5).SetArrayValue(resultRadii, eCSSUnit_Array);
+ // We use an arbitrary border-radius property here to get the appropriate
+ // restrictions for radii since this is a <border-radius> value.
+ uint32_t restrictions =
+ aRestriction == Restrictions::Enable
+ ? nsCSSProps::ValueRestrictions(eCSSProperty_border_top_left_radius)
+ : 0;
+ for (size_t i = 0; i < 4; ++i) {
+ const nsCSSValuePair& pair1 = radii1->Item(i).GetPairValue();
+ const nsCSSValuePair& pair2 = radii2->Item(i).GetPairValue();
+ const Maybe<nsCSSValuePair> pairResult =
+ AddCSSValuePair(aProperty, restrictions,
+ aCoeff1, &pair1,
+ aCoeff2, &pair2);
+ if (!pairResult) {
+ return nullptr;
+ }
+ resultRadii->Item(i).SetPairValue(pairResult.ptr());
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown shape type");
+ return nullptr;
+ }
+
+ // set the geometry-box value
+ result->Item(1).SetIntValue(aArray1->Item(1).GetIntValue(),
+ eCSSUnit_Enumerated);
+
+ return result.forget();
+}
+
+static nsCSSValueList*
+AddTransformLists(double aCoeff1, const nsCSSValueList* aList1,
+ double aCoeff2, const nsCSSValueList* aList2)
+{
+ nsAutoPtr<nsCSSValueList> result;
+ nsCSSValueList **resultTail = getter_Transfers(result);
+
+ do {
+ RefPtr<nsCSSValue::Array> a1 = ToPrimitive(aList1->mValue.GetArrayValue()),
+ a2 = ToPrimitive(aList2->mValue.GetArrayValue());
+ MOZ_ASSERT(
+ TransformFunctionsMatch(nsStyleTransformMatrix::TransformFunctionOf(a1),
+ nsStyleTransformMatrix::TransformFunctionOf(a2)),
+ "transform function mismatch");
+ MOZ_ASSERT(!*resultTail,
+ "resultTail isn't pointing to the tail (may leak)");
+
+ nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(a1);
+ RefPtr<nsCSSValue::Array> arr;
+ if (tfunc != eCSSKeyword_matrix &&
+ tfunc != eCSSKeyword_matrix3d &&
+ tfunc != eCSSKeyword_interpolatematrix &&
+ tfunc != eCSSKeyword_rotate3d &&
+ tfunc != eCSSKeyword_perspective) {
+ arr = StyleAnimationValue::AppendTransformFunction(tfunc, resultTail);
+ }
+
+ switch (tfunc) {
+ case eCSSKeyword_translate3d: {
+ MOZ_ASSERT(a1->Count() == 4, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 4, "unexpected count");
+ AddTransformTranslate(aCoeff1, a1->Item(1), aCoeff2, a2->Item(1),
+ arr->Item(1));
+ AddTransformTranslate(aCoeff1, a1->Item(2), aCoeff2, a2->Item(2),
+ arr->Item(2));
+ AddTransformTranslate(aCoeff1, a1->Item(3), aCoeff2, a2->Item(3),
+ arr->Item(3));
+ break;
+ }
+ case eCSSKeyword_scale3d: {
+ MOZ_ASSERT(a1->Count() == 4, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 4, "unexpected count");
+
+ AddTransformScale(aCoeff1, a1->Item(1), aCoeff2, a2->Item(1),
+ arr->Item(1));
+ AddTransformScale(aCoeff1, a1->Item(2), aCoeff2, a2->Item(2),
+ arr->Item(2));
+ AddTransformScale(aCoeff1, a1->Item(3), aCoeff2, a2->Item(3),
+ arr->Item(3));
+
+ break;
+ }
+ // It would probably be nicer to animate skew in tangent space
+ // rather than angle space. However, it's easy to specify
+ // skews with infinite tangents, and behavior changes pretty
+ // drastically when crossing such skews (since the direction of
+ // animation flips), so interop is probably more important here.
+ case eCSSKeyword_skew: {
+ MOZ_ASSERT(a1->Count() == 2 || a1->Count() == 3,
+ "unexpected count");
+ MOZ_ASSERT(a2->Count() == 2 || a2->Count() == 3,
+ "unexpected count");
+
+ nsCSSValue zero(0.0f, eCSSUnit_Radian);
+ // Add Y component of skew.
+ AddCSSValueAngle(aCoeff1,
+ a1->Count() == 3 ? a1->Item(2) : zero,
+ aCoeff2,
+ a2->Count() == 3 ? a2->Item(2) : zero,
+ arr->Item(2));
+
+ // Add X component of skew (which can be merged with case below
+ // in non-DEBUG).
+ AddCSSValueAngle(aCoeff1, a1->Item(1), aCoeff2, a2->Item(1),
+ arr->Item(1));
+
+ break;
+ }
+ case eCSSKeyword_skewx:
+ case eCSSKeyword_skewy:
+ case eCSSKeyword_rotate:
+ case eCSSKeyword_rotatex:
+ case eCSSKeyword_rotatey:
+ case eCSSKeyword_rotatez: {
+ MOZ_ASSERT(a1->Count() == 2, "unexpected count");
+ MOZ_ASSERT(a2->Count() == 2, "unexpected count");
+
+ AddCSSValueAngle(aCoeff1, a1->Item(1), aCoeff2, a2->Item(1),
+ arr->Item(1));
+
+ break;
+ }
+ case eCSSKeyword_rotate3d: {
+ Point3D vector1(a1->Item(1).GetFloatValue(),
+ a1->Item(2).GetFloatValue(),
+ a1->Item(3).GetFloatValue());
+ vector1.Normalize();
+ Point3D vector2(a2->Item(1).GetFloatValue(),
+ a2->Item(2).GetFloatValue(),
+ a2->Item(3).GetFloatValue());
+ vector2.Normalize();
+
+ // Handle rotate3d with matched (normalized) vectors,
+ // otherwise fallthrough to the next switch statement
+ // and do matrix decomposition.
+ if (vector1 == vector2) {
+ // We skipped appending a transform function above for rotate3d,
+ // so do it now.
+ arr = StyleAnimationValue::AppendTransformFunction(tfunc, resultTail);
+ arr->Item(1).SetFloatValue(vector1.x, eCSSUnit_Number);
+ arr->Item(2).SetFloatValue(vector1.y, eCSSUnit_Number);
+ arr->Item(3).SetFloatValue(vector1.z, eCSSUnit_Number);
+
+ AddCSSValueAngle(aCoeff1, a1->Item(4), aCoeff2, a2->Item(4),
+ arr->Item(4));
+ break;
+ }
+ MOZ_FALLTHROUGH;
+ }
+ case eCSSKeyword_matrix:
+ case eCSSKeyword_matrix3d:
+ case eCSSKeyword_perspective:
+ if (aCoeff1 == 0.0 && aCoeff2 == 0.0) {
+ // Special case. If both coefficients are 0.0, we should apply an
+ // identity transform function.
+ arr = StyleAnimationValue::AppendTransformFunction(tfunc, resultTail);
+
+ if (tfunc == eCSSKeyword_rotate3d) {
+ arr->Item(1).SetFloatValue(0.0, eCSSUnit_Number);
+ arr->Item(2).SetFloatValue(0.0, eCSSUnit_Number);
+ arr->Item(3).SetFloatValue(1.0, eCSSUnit_Number);
+ arr->Item(4).SetFloatValue(0.0, eCSSUnit_Radian);
+ } else if (tfunc == eCSSKeyword_perspective) {
+ // The parameter of the identity perspective function is
+ // positive infinite.
+ arr->Item(1).SetFloatValue(std::numeric_limits<float>::infinity(),
+ eCSSUnit_Pixel);
+ } else {
+ nsStyleTransformMatrix::SetIdentityMatrix(arr);
+ }
+ break;
+ }
+ MOZ_FALLTHROUGH;
+ case eCSSKeyword_interpolatematrix: {
+ // FIXME: If the matrix contains only numbers then we could decompose
+ // here.
+
+ // Construct temporary lists with only this item in them.
+ nsCSSValueList tempList1, tempList2;
+ tempList1.mValue = aList1->mValue;
+ tempList2.mValue = aList2->mValue;
+
+ if (aList1 == aList2) {
+ *resultTail =
+ AddDifferentTransformLists(aCoeff1, &tempList1, aCoeff2, &tempList1);
+ } else {
+ *resultTail =
+ AddDifferentTransformLists(aCoeff1, &tempList1, aCoeff2, &tempList2);
+ }
+
+ // Now advance resultTail to point to the new tail slot.
+ while (*resultTail) {
+ resultTail = &(*resultTail)->mNext;
+ }
+
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "unknown transform function");
+ }
+
+ aList1 = aList1->mNext;
+ aList2 = aList2->mNext;
+ } while (aList1);
+ MOZ_ASSERT(!aList2, "list length mismatch");
+ MOZ_ASSERT(!*resultTail,
+ "resultTail isn't pointing to the tail");
+
+ return result.forget();
+}
+
+static void
+AddPositionCoords(double aCoeff1, const nsCSSValue& aPos1,
+ double aCoeff2, const nsCSSValue& aPos2,
+ nsCSSValue& aResultPos)
+{
+ const nsCSSValue::Array* posArray1 = aPos1.GetArrayValue();
+ const nsCSSValue::Array* posArray2 = aPos2.GetArrayValue();
+ nsCSSValue::Array* resultPosArray = nsCSSValue::Array::Create(2);
+ aResultPos.SetArrayValue(resultPosArray, eCSSUnit_Array);
+
+ /* Only compute element 1. The <position-coord> is
+ * 'uncomputed' to only that element.
+ */
+ const nsCSSValue& v1 = posArray1->Item(1);
+ const nsCSSValue& v2 = posArray2->Item(1);
+ nsCSSValue& vr = resultPosArray->Item(1);
+ AddCSSValueCanonicalCalc(aCoeff1, v1,
+ aCoeff2, v2, vr);
+}
+
+static UniquePtr<nsCSSValueList>
+AddWeightedShadowList(double aCoeff1,
+ const nsCSSValueList* aShadow1,
+ double aCoeff2,
+ const nsCSSValueList* aShadow2,
+ ColorAdditionType aColorAdditionType)
+{
+ // This is implemented according to:
+ // http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
+ // and the third item in the summary of:
+ // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html
+ UniquePtr<nsCSSValueList> result;
+ nsCSSValueList* tail = nullptr;
+ while (aShadow1 && aShadow2) {
+ UniquePtr<nsCSSValueList> shadowValue =
+ AddWeightedShadowItems(aCoeff1, aShadow1->mValue,
+ aCoeff2, aShadow2->mValue,
+ aColorAdditionType);
+ if (!shadowValue) {
+ return nullptr;
+ }
+ aShadow1 = aShadow1->mNext;
+ aShadow2 = aShadow2->mNext;
+ AppendToCSSValueList(result, Move(shadowValue), &tail);
+ }
+ if (aShadow1 || aShadow2) {
+ const nsCSSValueList *longShadow;
+ double longCoeff;
+ if (aShadow1) {
+ longShadow = aShadow1;
+ longCoeff = aCoeff1;
+ } else {
+ longShadow = aShadow2;
+ longCoeff = aCoeff2;
+ }
+
+ while (longShadow) {
+ // Passing coefficients that add to less than 1 produces the
+ // desired result of interpolating "0 0 0 transparent" with
+ // the current shadow.
+ UniquePtr<nsCSSValueList> shadowValue =
+ AddWeightedShadowItems(longCoeff, longShadow->mValue,
+ 0.0, longShadow->mValue,
+ aColorAdditionType);
+ if (!shadowValue) {
+ return nullptr;
+ }
+
+ longShadow = longShadow->mNext;
+ AppendToCSSValueList(result, Move(shadowValue), &tail);
+ }
+ }
+ return result;
+}
+
+static UniquePtr<nsCSSValueList>
+AddWeightedFilterList(double aCoeff1, const nsCSSValueList* aList1,
+ double aCoeff2, const nsCSSValueList* aList2,
+ ColorAdditionType aColorAdditionType)
+{
+ UniquePtr<nsCSSValueList> result;
+ nsCSSValueList* tail = nullptr;
+ while (aList1 || aList2) {
+ if ((aList1 && aList1->mValue.GetUnit() != eCSSUnit_Function) ||
+ (aList2 && aList2->mValue.GetUnit() != eCSSUnit_Function)) {
+ // If we don't have filter-functions, we must have filter-URLs, which
+ // we can't add or interpolate.
+ return nullptr;
+ }
+
+ UniquePtr<nsCSSValueList> resultFunction =
+ AddWeightedFilterFunction(aCoeff1, aList1, aCoeff2, aList2,
+ aColorAdditionType);
+ if (!resultFunction) {
+ // filter function mismatch
+ return nullptr;
+ }
+
+ AppendToCSSValueList(result, Move(resultFunction), &tail);
+
+ // move to next aList items
+ if (aList1) {
+ aList1 = aList1->mNext;
+ }
+ if (aList2) {
+ aList2 = aList2->mNext;
+ }
+ }
+
+ return result;
+}
+
+bool
+StyleAnimationValue::AddWeighted(nsCSSPropertyID aProperty,
+ double aCoeff1,
+ const StyleAnimationValue& aValue1,
+ double aCoeff2,
+ const StyleAnimationValue& aValue2,
+ StyleAnimationValue& aResultValue)
+{
+ Unit commonUnit =
+ GetCommonUnit(aProperty, aValue1.GetUnit(), aValue2.GetUnit());
+ // Maybe need a followup method to convert the inputs into the common
+ // unit-type, if they don't already match it. (Or would it make sense to do
+ // that in GetCommonUnit? in which case maybe ConvertToCommonUnit would be
+ // better.)
+
+ switch (commonUnit) {
+ case eUnit_Null:
+ case eUnit_Auto:
+ case eUnit_None:
+ case eUnit_Normal:
+ case eUnit_UnparsedString:
+ case eUnit_URL:
+ case eUnit_DiscreteCSSValue:
+ return false;
+
+ case eUnit_Enumerated:
+ switch (aProperty) {
+ case eCSSProperty_font_stretch: {
+ // Animate just like eUnit_Integer.
+ int32_t result = floor(aCoeff1 * double(aValue1.GetIntValue()) +
+ aCoeff2 * double(aValue2.GetIntValue()));
+ if (result < NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED) {
+ result = NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED;
+ } else if (result > NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED) {
+ result = NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED;
+ }
+ aResultValue.SetIntValue(result, eUnit_Enumerated);
+ return true;
+ }
+ default:
+ return false;
+ }
+ case eUnit_Visibility: {
+ int32_t enum1 = aValue1.GetIntValue();
+ int32_t enum2 = aValue2.GetIntValue();
+ if (enum1 == enum2) {
+ aResultValue.SetIntValue(enum1, eUnit_Visibility);
+ return true;
+ }
+ if ((enum1 == NS_STYLE_VISIBILITY_VISIBLE) ==
+ (enum2 == NS_STYLE_VISIBILITY_VISIBLE)) {
+ return false;
+ }
+ int32_t val1 = enum1 == NS_STYLE_VISIBILITY_VISIBLE;
+ int32_t val2 = enum2 == NS_STYLE_VISIBILITY_VISIBLE;
+ double interp = aCoeff1 * val1 + aCoeff2 * val2;
+ int32_t result = interp > 0.0 ? NS_STYLE_VISIBILITY_VISIBLE
+ : (val1 ? enum2 : enum1);
+ aResultValue.SetIntValue(result, eUnit_Visibility);
+ return true;
+ }
+ case eUnit_Integer: {
+ // http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
+ // says we should use floor
+ int32_t result = floor(aCoeff1 * double(aValue1.GetIntValue()) +
+ aCoeff2 * double(aValue2.GetIntValue()));
+ if (aProperty == eCSSProperty_font_weight) {
+ if (result < 100) {
+ result = 100;
+ } else if (result > 900) {
+ result = 900;
+ }
+ result -= result % 100;
+ } else {
+ result = RestrictValue(aProperty, result);
+ }
+ aResultValue.SetIntValue(result, eUnit_Integer);
+ return true;
+ }
+ case eUnit_Coord: {
+ aResultValue.SetCoordValue(RestrictValue(aProperty, NSToCoordRound(
+ aCoeff1 * aValue1.GetCoordValue() +
+ aCoeff2 * aValue2.GetCoordValue())));
+ return true;
+ }
+ case eUnit_Percent: {
+ aResultValue.SetPercentValue(RestrictValue(aProperty,
+ aCoeff1 * aValue1.GetPercentValue() +
+ aCoeff2 * aValue2.GetPercentValue()));
+ return true;
+ }
+ case eUnit_Float: {
+ aResultValue.SetFloatValue(RestrictValue(aProperty,
+ aCoeff1 * aValue1.GetFloatValue() +
+ aCoeff2 * aValue2.GetFloatValue()));
+ return true;
+ }
+ case eUnit_Color: {
+ RGBAColorData color1 = ExtractColor(aValue1);
+ RGBAColorData color2 = ExtractColor(aValue2);
+ auto resultColor = MakeUnique<nsCSSValue>();
+ resultColor->SetColorValue(
+ AddWeightedColorsAndClamp(aCoeff1, color1, aCoeff2, color2));
+ aResultValue.SetAndAdoptCSSValueValue(resultColor.release(), eUnit_Color);
+ return true;
+ }
+ case eUnit_CurrentColor: {
+ aResultValue.SetCurrentColorValue();
+ return true;
+ }
+ case eUnit_ComplexColor: {
+ ComplexColorData color1 = ExtractComplexColor(aValue1);
+ ComplexColorData color2 = ExtractComplexColor(aValue2);
+ RefPtr<ComplexColorValue> result = new ComplexColorValue;
+ // Common case is interpolating between a color and a currentcolor.
+ if (color1.IsNumericColor() && color2.IsCurrentColor()) {
+ result->mColor = color1.mColor;
+ result->mForegroundRatio = aCoeff2;
+ } else if (color1.IsCurrentColor() && color2.IsNumericColor()) {
+ result->mColor = color2.mColor;
+ result->mForegroundRatio = aCoeff1;
+ } else {
+ float ratio1 = 1.0f - color1.mForegroundRatio;
+ float ratio2 = 1.0f - color2.mForegroundRatio;
+ float alpha1 = color1.mColor.mA * ratio1;
+ float alpha2 = color2.mColor.mA * ratio2;
+ RGBAColorData resultColor =
+ AddWeightedColors(aCoeff1, color1.mColor.WithAlpha(alpha1),
+ aCoeff2, color2.mColor.WithAlpha(alpha2));
+ float resultRatio = color1.mForegroundRatio * aCoeff1 +
+ color2.mForegroundRatio * aCoeff2;
+ float resultAlpha = resultColor.mA / (1.0f - resultRatio);
+ result->mColor = resultColor.WithAlpha(resultAlpha);
+ result->mForegroundRatio = resultRatio;
+ }
+ aResultValue.SetComplexColorValue(result.forget());
+ return true;
+ }
+ case eUnit_Calc: {
+ PixelCalcValue v1 = ExtractCalcValue(aValue1);
+ PixelCalcValue v2 = ExtractCalcValue(aValue2);
+ double len = aCoeff1 * v1.mLength + aCoeff2 * v2.mLength;
+ double pct = aCoeff1 * v1.mPercent + aCoeff2 * v2.mPercent;
+ bool hasPct = (aCoeff1 != 0.0 && v1.mHasPercent) ||
+ (aCoeff2 != 0.0 && v2.mHasPercent);
+ nsCSSValue *val = new nsCSSValue();
+ nsCSSValue::Array *arr = nsCSSValue::Array::Create(1);
+ val->SetArrayValue(arr, eCSSUnit_Calc);
+ if (hasPct) {
+ nsCSSValue::Array *arr2 = nsCSSValue::Array::Create(2);
+ arr2->Item(0).SetFloatValue(len, eCSSUnit_Pixel);
+ arr2->Item(1).SetPercentValue(pct);
+ arr->Item(0).SetArrayValue(arr2, eCSSUnit_Calc_Plus);
+ } else {
+ arr->Item(0).SetFloatValue(len, eCSSUnit_Pixel);
+ }
+ aResultValue.SetAndAdoptCSSValueValue(val, eUnit_Calc);
+ return true;
+ }
+ case eUnit_ObjectPosition: {
+ const nsCSSValue* position1 = aValue1.GetCSSValueValue();
+ const nsCSSValue* position2 = aValue2.GetCSSValueValue();
+
+ nsAutoPtr<nsCSSValue> result(new nsCSSValue);
+ AddPositions(aCoeff1, *position1,
+ aCoeff2, *position2, *result);
+
+ aResultValue.SetAndAdoptCSSValueValue(result.forget(),
+ eUnit_ObjectPosition);
+ return true;
+ }
+ case eUnit_CSSValuePair: {
+ uint32_t restrictions = nsCSSProps::ValueRestrictions(aProperty);
+ Maybe<nsCSSValuePair> result =
+ AddCSSValuePair(aProperty, restrictions,
+ aCoeff1, aValue1.GetCSSValuePairValue(),
+ aCoeff2, aValue2.GetCSSValuePairValue());
+ if (!result) {
+ return false;
+ }
+
+ // We need a heap allocated object to adopt here:
+ auto heapResult = MakeUnique<nsCSSValuePair>(*result);
+ aResultValue.SetAndAdoptCSSValuePairValue(heapResult.release(),
+ eUnit_CSSValuePair);
+ return true;
+ }
+ case eUnit_CSSValueTriplet: {
+ nsCSSValueTriplet triplet1(*aValue1.GetCSSValueTripletValue());
+ nsCSSValueTriplet triplet2(*aValue2.GetCSSValueTripletValue());
+
+ nsCSSUnit unit[3];
+ unit[0] = GetCommonUnit(aProperty, triplet1.mXValue.GetUnit(),
+ triplet2.mXValue.GetUnit());
+ unit[1] = GetCommonUnit(aProperty, triplet1.mYValue.GetUnit(),
+ triplet2.mYValue.GetUnit());
+ unit[2] = GetCommonUnit(aProperty, triplet1.mZValue.GetUnit(),
+ triplet2.mZValue.GetUnit());
+ if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null ||
+ unit[2] == eCSSUnit_Null) {
+ return false;
+ }
+
+ nsAutoPtr<nsCSSValueTriplet> result(new nsCSSValueTriplet);
+ static nsCSSValue nsCSSValueTriplet::* const tripletValues[3] = {
+ &nsCSSValueTriplet::mXValue, &nsCSSValueTriplet::mYValue, &nsCSSValueTriplet::mZValue
+ };
+ uint32_t restrictions = nsCSSProps::ValueRestrictions(aProperty);
+ for (uint32_t i = 0; i < 3; ++i) {
+ nsCSSValue nsCSSValueTriplet::*member = tripletValues[i];
+ if (!AddCSSValuePixelPercentCalc(restrictions, unit[i],
+ aCoeff1, &triplet1->*member,
+ aCoeff2, &triplet2->*member,
+ result->*member) ) {
+ MOZ_ASSERT(false, "unexpected unit");
+ return false;
+ }
+ }
+
+ aResultValue.SetAndAdoptCSSValueTripletValue(result.forget(),
+ eUnit_CSSValueTriplet);
+ return true;
+ }
+ case eUnit_CSSRect: {
+ MOZ_ASSERT(nsCSSProps::ValueRestrictions(aProperty) == 0,
+ "must add code for handling value restrictions");
+ const nsCSSRect *rect1 = aValue1.GetCSSRectValue();
+ const nsCSSRect *rect2 = aValue2.GetCSSRectValue();
+ if (rect1->mTop.GetUnit() != rect2->mTop.GetUnit() ||
+ rect1->mRight.GetUnit() != rect2->mRight.GetUnit() ||
+ rect1->mBottom.GetUnit() != rect2->mBottom.GetUnit() ||
+ rect1->mLeft.GetUnit() != rect2->mLeft.GetUnit()) {
+ // At least until we have calc()
+ return false;
+ }
+
+ nsAutoPtr<nsCSSRect> result(new nsCSSRect);
+ for (uint32_t i = 0; i < ArrayLength(nsCSSRect::sides); ++i) {
+ nsCSSValue nsCSSRect::*member = nsCSSRect::sides[i];
+ MOZ_ASSERT((rect1->*member).GetUnit() == (rect2->*member).GetUnit(),
+ "should have returned above");
+ switch ((rect1->*member).GetUnit()) {
+ case eCSSUnit_Pixel:
+ AddCSSValuePixel(aCoeff1, rect1->*member, aCoeff2, rect2->*member,
+ result->*member);
+ break;
+ case eCSSUnit_Auto:
+ if (float(aCoeff1 + aCoeff2) != 1.0f) {
+ // Interpolating between two auto values makes sense;
+ // adding in other ratios does not.
+ return false;
+ }
+ (result->*member).SetAutoValue();
+ break;
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ return false;
+ }
+ }
+
+ aResultValue.SetAndAdoptCSSRectValue(result.forget(), eUnit_CSSRect);
+ return true;
+ }
+ case eUnit_Dasharray: {
+ const nsCSSValueList *list1 = aValue1.GetCSSValueListValue();
+ const nsCSSValueList *list2 = aValue2.GetCSSValueListValue();
+
+ uint32_t len1 = 0, len2 = 0;
+ for (const nsCSSValueList *v = list1; v; v = v->mNext) {
+ ++len1;
+ }
+ for (const nsCSSValueList *v = list2; v; v = v->mNext) {
+ ++len2;
+ }
+ MOZ_ASSERT(len1 > 0 && len2 > 0, "unexpected length");
+
+ nsAutoPtr<nsCSSValueList> result;
+ nsCSSValueList **resultTail = getter_Transfers(result);
+ for (uint32_t i = 0, i_end = EuclidLCM<uint32_t>(len1, len2); i != i_end; ++i) {
+ const nsCSSValue &v1 = list1->mValue;
+ const nsCSSValue &v2 = list2->mValue;
+ MOZ_ASSERT(v1.GetUnit() == eCSSUnit_Number ||
+ v1.GetUnit() == eCSSUnit_Percent, "unexpected");
+ MOZ_ASSERT(v2.GetUnit() == eCSSUnit_Number ||
+ v2.GetUnit() == eCSSUnit_Percent, "unexpected");
+ if (v1.GetUnit() != v2.GetUnit()) {
+ // Can't animate between lengths and percentages (until calc()).
+ return false;
+ }
+
+ nsCSSValueList *item = new nsCSSValueList;
+ *resultTail = item;
+ resultTail = &item->mNext;
+
+ if (v1.GetUnit() == eCSSUnit_Number) {
+ AddCSSValueNumber(aCoeff1, v1, aCoeff2, v2, item->mValue,
+ CSS_PROPERTY_VALUE_NONNEGATIVE);
+ } else {
+ AddCSSValuePercent(aCoeff1, v1, aCoeff2, v2, item->mValue,
+ CSS_PROPERTY_VALUE_NONNEGATIVE);
+ }
+
+ list1 = list1->mNext;
+ if (!list1) {
+ list1 = aValue1.GetCSSValueListValue();
+ }
+ list2 = list2->mNext;
+ if (!list2) {
+ list2 = aValue2.GetCSSValueListValue();
+ }
+ }
+
+ aResultValue.SetAndAdoptCSSValueListValue(result.forget(),
+ eUnit_Dasharray);
+ return true;
+ }
+ case eUnit_Shadow: {
+ UniquePtr<nsCSSValueList> result =
+ AddWeightedShadowList(aCoeff1,
+ aValue1.GetCSSValueListValue(),
+ aCoeff2,
+ aValue2.GetCSSValueListValue(),
+ ColorAdditionType::Clamped);
+ if (!result) {
+ return false;
+ }
+ aResultValue.SetAndAdoptCSSValueListValue(result.release(), eUnit_Shadow);
+ return true;
+ }
+ case eUnit_Shape: {
+ RefPtr<nsCSSValue::Array> result =
+ AddShapeFunction(aProperty,
+ aCoeff1, aValue1.GetCSSValueArrayValue(),
+ aCoeff2, aValue2.GetCSSValueArrayValue());
+ if (!result) {
+ return false;
+ }
+ aResultValue.SetCSSValueArrayValue(result, eUnit_Shape);
+ return true;
+ }
+ case eUnit_Filter: {
+ UniquePtr<nsCSSValueList> result =
+ AddWeightedFilterList(aCoeff1, aValue1.GetCSSValueListValue(),
+ aCoeff2, aValue2.GetCSSValueListValue(),
+ ColorAdditionType::Clamped);
+ if (!result) {
+ return false;
+ }
+
+ aResultValue.SetAndAdoptCSSValueListValue(result.release(),
+ eUnit_Filter);
+ return true;
+ }
+
+ case eUnit_Transform: {
+ const nsCSSValueList* list1 = aValue1.GetCSSValueSharedListValue()->mHead;
+ const nsCSSValueList* list2 = aValue2.GetCSSValueSharedListValue()->mHead;
+
+ MOZ_ASSERT(list1);
+ MOZ_ASSERT(list2);
+
+ // We want to avoid the matrix decomposition when we can, since
+ // avoiding it can produce better results both for compound
+ // transforms and for skew and skewY (see below). We can do this
+ // in two cases:
+ // (1) if one of the transforms is 'none'
+ // (2) if the lists have the same length and the transform
+ // functions match
+ nsAutoPtr<nsCSSValueList> result;
+ if (list1->mValue.GetUnit() == eCSSUnit_None) {
+ if (list2->mValue.GetUnit() == eCSSUnit_None) {
+ result = new nsCSSValueList;
+ if (result) {
+ result->mValue.SetNoneValue();
+ }
+ } else {
+ result = AddTransformLists(0, list2, aCoeff2, list2);
+ }
+ } else {
+ if (list2->mValue.GetUnit() == eCSSUnit_None) {
+ result = AddTransformLists(0, list1, aCoeff1, list1);
+ } else {
+ bool match = true;
+
+ {
+ const nsCSSValueList *item1 = list1, *item2 = list2;
+ do {
+ nsCSSKeyword func1 = nsStyleTransformMatrix::TransformFunctionOf(
+ item1->mValue.GetArrayValue());
+ nsCSSKeyword func2 = nsStyleTransformMatrix::TransformFunctionOf(
+ item2->mValue.GetArrayValue());
+
+ if (!TransformFunctionsMatch(func1, func2)) {
+ break;
+ }
+
+ item1 = item1->mNext;
+ item2 = item2->mNext;
+ } while (item1 && item2);
+ if (item1 || item2) {
+ // Either |break| above or length mismatch.
+ match = false;
+ }
+ }
+
+ if (match) {
+ result = AddTransformLists(aCoeff1, list1, aCoeff2, list2);
+ } else {
+ result = AddDifferentTransformLists(aCoeff1, list1, aCoeff2, list2);
+ }
+ }
+ }
+
+ aResultValue.SetTransformValue(new nsCSSValueSharedList(result.forget()));
+ return true;
+ }
+ case eUnit_BackgroundPositionCoord: {
+ const nsCSSValueList *position1 = aValue1.GetCSSValueListValue();
+ const nsCSSValueList *position2 = aValue2.GetCSSValueListValue();
+ nsAutoPtr<nsCSSValueList> result;
+ nsCSSValueList **resultTail = getter_Transfers(result);
+ while (position1 && position2) {
+ nsCSSValueList *item = new nsCSSValueList;
+ *resultTail = item;
+ resultTail = &item->mNext;
+
+ AddPositionCoords(aCoeff1, position1->mValue,
+ aCoeff2, position2->mValue, item->mValue);
+
+ position1 = position1->mNext;
+ position2 = position2->mNext;
+ }
+
+ // Check for different lengths
+ if (position1 || position2) {
+ return false;
+ }
+
+ aResultValue.SetAndAdoptCSSValueListValue(result.forget(),
+ eUnit_BackgroundPositionCoord);
+ return true;
+ }
+ case eUnit_CSSValuePairList: {
+ const nsCSSValuePairList *list1 = aValue1.GetCSSValuePairListValue();
+ const nsCSSValuePairList *list2 = aValue2.GetCSSValuePairListValue();
+ UniquePtr<nsCSSValuePairList> result =
+ AddCSSValuePairList(aProperty, aCoeff1, list1, aCoeff2, list2);
+ if (!result) {
+ return false;
+ }
+ aResultValue.SetAndAdoptCSSValuePairListValue(result.release());
+ return true;
+ }
+ }
+
+ MOZ_ASSERT(false, "Can't interpolate using the given common unit");
+ return false;
+}
+
+bool
+StyleAnimationValue::Accumulate(nsCSSPropertyID aProperty,
+ StyleAnimationValue& aDest,
+ const StyleAnimationValue& aValueToAccumulate,
+ uint64_t aCount)
+{
+ Unit commonUnit =
+ GetCommonUnit(aProperty, aDest.GetUnit(), aValueToAccumulate.GetUnit());
+ switch (commonUnit) {
+ case eUnit_Filter: {
+ UniquePtr<nsCSSValueList> result =
+ AddWeightedFilterList(1.0, aDest.GetCSSValueListValue(),
+ aCount, aValueToAccumulate.GetCSSValueListValue(),
+ ColorAdditionType::Unclamped);
+ if (!result) {
+ return false;
+ }
+
+ aDest.SetAndAdoptCSSValueListValue(result.release(), eUnit_Filter);
+ return true;
+ }
+ case eUnit_Shadow: {
+ UniquePtr<nsCSSValueList> result =
+ AddWeightedShadowList(1.0, aDest.GetCSSValueListValue(),
+ aCount, aValueToAccumulate.GetCSSValueListValue(),
+ ColorAdditionType::Unclamped);
+ if (!result) {
+ return false;
+ }
+ aDest.SetAndAdoptCSSValueListValue(result.release(), eUnit_Shadow);
+ return true;
+ }
+ case eUnit_Color: {
+ RGBAColorData color1 = ExtractColor(aDest);
+ RGBAColorData color2 = ExtractColor(aValueToAccumulate);
+ auto resultColor = MakeUnique<nsCSSValue>();
+ resultColor->SetRGBAColorValue(
+ AddWeightedColors(1.0, color1, aCount, color2));
+ aDest.SetAndAdoptCSSValueValue(resultColor.release(), eUnit_Color);
+ return true;
+ }
+ default:
+ return Add(aProperty, aDest, aValueToAccumulate, aCount);
+ }
+ MOZ_ASSERT_UNREACHABLE("Can't accumulate using the given common unit");
+ return false;
+}
+
+already_AddRefed<css::StyleRule>
+BuildStyleRule(nsCSSPropertyID aProperty,
+ dom::Element* aTargetElement,
+ const nsAString& aSpecifiedValue,
+ bool aUseSVGMode)
+{
+ // Set up an empty CSS Declaration
+ RefPtr<css::Declaration> declaration(new css::Declaration());
+ declaration->InitializeEmpty();
+
+ bool changed; // ignored, but needed as outparam for ParseProperty
+ nsIDocument* doc = aTargetElement->OwnerDoc();
+ nsCOMPtr<nsIURI> baseURI = aTargetElement->GetBaseURI();
+ nsCSSParser parser(doc->CSSLoader());
+
+ nsCSSPropertyID propertyToCheck = nsCSSProps::IsShorthand(aProperty) ?
+ nsCSSProps::SubpropertyEntryFor(aProperty)[0] : aProperty;
+
+ // Get a parser, parse the property, and check for CSS parsing errors.
+ // If this fails, we bail out and delete the declaration.
+ parser.ParseProperty(aProperty, aSpecifiedValue, doc->GetDocumentURI(),
+ baseURI, aTargetElement->NodePrincipal(), declaration,
+ &changed, false, aUseSVGMode);
+
+ // check whether property parsed without CSS parsing errors
+ if (!declaration->HasNonImportantValueFor(propertyToCheck)) {
+ return nullptr;
+ }
+
+ RefPtr<css::StyleRule> rule = new css::StyleRule(nullptr,
+ declaration,
+ 0, 0);
+ return rule.forget();
+}
+
+already_AddRefed<css::StyleRule>
+BuildStyleRule(nsCSSPropertyID aProperty,
+ dom::Element* aTargetElement,
+ const nsCSSValue& aSpecifiedValue,
+ bool aUseSVGMode)
+{
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
+ "Should be a longhand property");
+
+ // Check if longhand failed to parse correctly.
+ if (aSpecifiedValue.GetUnit() == eCSSUnit_Null) {
+ return nullptr;
+ }
+
+ // Set up an empty CSS Declaration
+ RefPtr<css::Declaration> declaration(new css::Declaration());
+ declaration->InitializeEmpty();
+
+ // Add our longhand value
+ nsCSSExpandedDataBlock block;
+ declaration->ExpandTo(&block);
+ block.AddLonghandProperty(aProperty, aSpecifiedValue);
+ declaration->ValueAppended(aProperty);
+ declaration->CompressFrom(&block);
+
+ RefPtr<css::StyleRule> rule = new css::StyleRule(nullptr, declaration, 0, 0);
+ return rule.forget();
+}
+
+static bool
+ComputeValuesFromStyleContext(
+ nsCSSPropertyID aProperty,
+ CSSEnabledState aEnabledState,
+ nsStyleContext* aStyleContext,
+ nsTArray<PropertyStyleAnimationValuePair>& aValues)
+{
+ // Extract computed value of our property (or all longhand components, if
+ // aProperty is a shorthand) from the temporary style context
+ if (nsCSSProps::IsShorthand(aProperty)) {
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty, aEnabledState) {
+ if (nsCSSProps::kAnimTypeTable[*p] == eStyleAnimType_None) {
+ // Skip non-animatable component longhands.
+ continue;
+ }
+ PropertyStyleAnimationValuePair* pair = aValues.AppendElement();
+ pair->mProperty = *p;
+ if (!StyleAnimationValue::ExtractComputedValue(*p, aStyleContext,
+ pair->mValue)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ PropertyStyleAnimationValuePair* pair = aValues.AppendElement();
+ pair->mProperty = aProperty;
+ return StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
+ pair->mValue);
+}
+
+static bool
+ComputeValuesFromStyleRule(nsCSSPropertyID aProperty,
+ CSSEnabledState aEnabledState,
+ nsStyleContext* aStyleContext,
+ css::StyleRule* aStyleRule,
+ nsTArray<PropertyStyleAnimationValuePair>& aValues,
+ bool* aIsContextSensitive)
+{
+ MOZ_ASSERT(aStyleContext);
+ if (!nsCSSProps::IsEnabled(aProperty, aEnabledState)) {
+ return false;
+ }
+
+ MOZ_ASSERT(aStyleContext->PresContext()->StyleSet()->IsGecko(),
+ "ServoStyleSet should not use StyleAnimationValue for animations");
+ nsStyleSet* styleSet = aStyleContext->PresContext()->StyleSet()->AsGecko();
+
+ RefPtr<nsStyleContext> tmpStyleContext;
+ if (aIsContextSensitive) {
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
+ "to correctly set aIsContextSensitive for shorthand properties, "
+ "this code must be adjusted");
+
+ nsCOMArray<nsIStyleRule> ruleArray;
+ ruleArray.AppendObject(styleSet->InitialStyleRule());
+ css::Declaration* declaration = aStyleRule->GetDeclaration();
+ ruleArray.AppendObject(declaration);
+ declaration->SetImmutable();
+ tmpStyleContext =
+ styleSet->ResolveStyleByAddingRules(aStyleContext, ruleArray);
+ if (!tmpStyleContext) {
+ return false;
+ }
+
+ // Force walk of rule tree
+ nsStyleStructID sid = nsCSSProps::kSIDTable[aProperty];
+ tmpStyleContext->StyleData(sid);
+
+ // The rule node will have unconditional cached style data if the value is
+ // not context-sensitive. So if there's nothing cached, it's not context
+ // sensitive.
+ *aIsContextSensitive =
+ !tmpStyleContext->RuleNode()->NodeHasCachedUnconditionalData(sid);
+ }
+
+ // If we're not concerned whether the property is context sensitive then just
+ // add the rule to a new temporary style context alongside the target
+ // element's style context.
+ // Also, if we previously discovered that this property IS context-sensitive
+ // then we need to throw the temporary style context out since the property's
+ // value may have been biased by the 'initial' values supplied.
+ if (!aIsContextSensitive || *aIsContextSensitive) {
+ nsCOMArray<nsIStyleRule> ruleArray;
+ css::Declaration* declaration = aStyleRule->GetDeclaration();
+ ruleArray.AppendObject(declaration);
+ declaration->SetImmutable();
+ tmpStyleContext =
+ styleSet->ResolveStyleByAddingRules(aStyleContext, ruleArray);
+ if (!tmpStyleContext) {
+ return false;
+ }
+ }
+
+ return ComputeValuesFromStyleContext(aProperty, aEnabledState,
+ tmpStyleContext, aValues);
+}
+
+/* static */ bool
+StyleAnimationValue::ComputeValue(nsCSSPropertyID aProperty,
+ dom::Element* aTargetElement,
+ nsStyleContext* aStyleContext,
+ const nsAString& aSpecifiedValue,
+ bool aUseSVGMode,
+ StyleAnimationValue& aComputedValue,
+ bool* aIsContextSensitive)
+{
+ MOZ_ASSERT(aTargetElement, "null target element");
+
+ // Parse specified value into a temporary css::StyleRule
+ // Note: BuildStyleRule needs an element's OwnerDoc, BaseURI, and Principal.
+ // If it is a pseudo element, use its parent element's OwnerDoc, BaseURI,
+ // and Principal.
+ RefPtr<css::StyleRule> styleRule =
+ BuildStyleRule(aProperty, aTargetElement, aSpecifiedValue, aUseSVGMode);
+ if (!styleRule) {
+ return false;
+ }
+
+ if (nsCSSProps::IsShorthand(aProperty) ||
+ nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
+ // Just capture the specified value
+ aComputedValue.SetUnparsedStringValue(nsString(aSpecifiedValue));
+ if (aIsContextSensitive) {
+ // Since we're just returning the string as-is, aComputedValue isn't going
+ // to change depending on the context
+ *aIsContextSensitive = false;
+ }
+ return true;
+ }
+
+ AutoTArray<PropertyStyleAnimationValuePair,1> values;
+ bool ok = ComputeValuesFromStyleRule(aProperty,
+ CSSEnabledState::eIgnoreEnabledState,
+ aStyleContext, styleRule,
+ values, aIsContextSensitive);
+ if (!ok) {
+ return false;
+ }
+
+ MOZ_ASSERT(values.Length() == 1);
+ MOZ_ASSERT(values[0].mProperty == aProperty);
+
+ aComputedValue = values[0].mValue;
+ return true;
+}
+
+template <class T>
+bool
+ComputeValuesFromSpecifiedValue(
+ nsCSSPropertyID aProperty,
+ CSSEnabledState aEnabledState,
+ dom::Element* aTargetElement,
+ nsStyleContext* aStyleContext,
+ T& aSpecifiedValue,
+ bool aUseSVGMode,
+ nsTArray<PropertyStyleAnimationValuePair>& aResult)
+{
+ MOZ_ASSERT(aTargetElement, "null target element");
+
+ // Parse specified value into a temporary css::StyleRule
+ // Note: BuildStyleRule needs an element's OwnerDoc, BaseURI, and Principal.
+ // If it is a pseudo element, use its parent element's OwnerDoc, BaseURI,
+ // and Principal.
+ RefPtr<css::StyleRule> styleRule =
+ BuildStyleRule(aProperty, aTargetElement, aSpecifiedValue, aUseSVGMode);
+ if (!styleRule) {
+ return false;
+ }
+
+ aResult.Clear();
+ return ComputeValuesFromStyleRule(aProperty, aEnabledState,
+ aStyleContext, styleRule, aResult,
+ /* aIsContextSensitive */ nullptr);
+}
+
+/* static */ bool
+StyleAnimationValue::ComputeValues(
+ nsCSSPropertyID aProperty,
+ CSSEnabledState aEnabledState,
+ dom::Element* aTargetElement,
+ nsStyleContext* aStyleContext,
+ const nsAString& aSpecifiedValue,
+ bool aUseSVGMode,
+ nsTArray<PropertyStyleAnimationValuePair>& aResult)
+{
+ return ComputeValuesFromSpecifiedValue(aProperty, aEnabledState,
+ aTargetElement, aStyleContext,
+ aSpecifiedValue, aUseSVGMode,
+ aResult);
+}
+
+/* static */ bool
+StyleAnimationValue::ComputeValues(
+ nsCSSPropertyID aProperty,
+ CSSEnabledState aEnabledState,
+ dom::Element* aTargetElement,
+ nsStyleContext* aStyleContext,
+ const nsCSSValue& aSpecifiedValue,
+ bool aUseSVGMode,
+ nsTArray<PropertyStyleAnimationValuePair>& aResult)
+{
+ return ComputeValuesFromSpecifiedValue(aProperty, aEnabledState,
+ aTargetElement, aStyleContext,
+ aSpecifiedValue, aUseSVGMode,
+ aResult);
+}
+
+/* static */ bool
+StyleAnimationValue::ComputeValues(
+ nsCSSPropertyID aProperty,
+ CSSEnabledState aEnabledState,
+ nsStyleContext* aStyleContext,
+ const RawServoDeclarationBlock& aDeclarations,
+ nsTArray<PropertyStyleAnimationValuePair>& aValues)
+{
+ MOZ_ASSERT(aStyleContext->PresContext()->StyleSet()->IsServo(),
+ "Should be using ServoStyleSet if we have a"
+ " RawServoDeclarationBlock");
+
+ if (!nsCSSProps::IsEnabled(aProperty, aEnabledState)) {
+ return false;
+ }
+
+ const ServoComputedValues* previousStyle =
+ aStyleContext->StyleSource().AsServoComputedValues();
+
+ // FIXME: Servo bindings don't yet represent const-ness so we just
+ // cast it away for now.
+ auto declarations = const_cast<RawServoDeclarationBlock*>(&aDeclarations);
+ RefPtr<ServoComputedValues> computedValues =
+ Servo_RestyleWithAddedDeclaration(declarations, previousStyle).Consume();
+ if (!computedValues) {
+ return false;
+ }
+
+ RefPtr<nsStyleContext> tmpStyleContext =
+ NS_NewStyleContext(aStyleContext, aStyleContext->PresContext(),
+ aStyleContext->GetPseudo(),
+ aStyleContext->GetPseudoType(),
+ computedValues.forget(),
+ false /* skipFixup */);
+
+ return ComputeValuesFromStyleContext(aProperty, aEnabledState,
+ tmpStyleContext, aValues);
+}
+
+bool
+StyleAnimationValue::UncomputeValue(nsCSSPropertyID aProperty,
+ const StyleAnimationValue& aComputedValue,
+ nsCSSValue& aSpecifiedValue)
+{
+ Unit unit = aComputedValue.GetUnit();
+ switch (unit) {
+ case eUnit_Normal:
+ aSpecifiedValue.SetNormalValue();
+ break;
+ case eUnit_Auto:
+ aSpecifiedValue.SetAutoValue();
+ break;
+ case eUnit_None:
+ aSpecifiedValue.SetNoneValue();
+ break;
+ case eUnit_Enumerated:
+ case eUnit_Visibility:
+ aSpecifiedValue.
+ SetIntValue(aComputedValue.GetIntValue(), eCSSUnit_Enumerated);
+ break;
+ case eUnit_Integer:
+ aSpecifiedValue.
+ SetIntValue(aComputedValue.GetIntValue(), eCSSUnit_Integer);
+ break;
+ case eUnit_Coord:
+ aSpecifiedValue.SetIntegerCoordValue(aComputedValue.GetCoordValue());
+ break;
+ case eUnit_Percent:
+ aSpecifiedValue.SetPercentValue(aComputedValue.GetPercentValue());
+ break;
+ case eUnit_Float:
+ aSpecifiedValue.
+ SetFloatValue(aComputedValue.GetFloatValue(), eCSSUnit_Number);
+ break;
+ case eUnit_CurrentColor:
+ aSpecifiedValue.SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ break;
+ case eUnit_Calc:
+ case eUnit_Color:
+ case eUnit_ObjectPosition:
+ case eUnit_URL:
+ case eUnit_DiscreteCSSValue: {
+ nsCSSValue* val = aComputedValue.GetCSSValueValue();
+ // Sanity-check that the underlying unit in the nsCSSValue is what we
+ // expect for our StyleAnimationValue::Unit:
+ MOZ_ASSERT((unit == eUnit_Calc && val->GetUnit() == eCSSUnit_Calc) ||
+ (unit == eUnit_Color &&
+ nsCSSValue::IsNumericColorUnit(val->GetUnit())) ||
+ (unit == eUnit_ObjectPosition &&
+ val->GetUnit() == eCSSUnit_Array) ||
+ (unit == eUnit_URL && val->GetUnit() == eCSSUnit_URL) ||
+ unit == eUnit_DiscreteCSSValue,
+ "unexpected unit");
+ aSpecifiedValue = *val;
+ break;
+ }
+ case eUnit_ComplexColor: {
+ aSpecifiedValue.SetComplexColorValue(
+ do_AddRef(aComputedValue.mValue.mComplexColor));
+ break;
+ }
+ case eUnit_CSSValuePair: {
+ // Rule node processing expects pair values to be collapsed to a
+ // single value if both halves would be equal, for most but not
+ // all properties. At present, all animatable properties that
+ // use pairs do expect collapsing.
+ const nsCSSValuePair* pair = aComputedValue.GetCSSValuePairValue();
+ if (pair->mXValue == pair->mYValue) {
+ aSpecifiedValue = pair->mXValue;
+ } else {
+ aSpecifiedValue.SetPairValue(pair);
+ }
+ } break;
+ case eUnit_CSSValueTriplet: {
+ // Rule node processing expects triplet values to be collapsed to a
+ // single value if both halves would be equal, for most but not
+ // all properties. At present, all animatable properties that
+ // use pairs do expect collapsing.
+ const nsCSSValueTriplet* triplet = aComputedValue.GetCSSValueTripletValue();
+ if (triplet->mXValue == triplet->mYValue && triplet->mYValue == triplet->mZValue) {
+ aSpecifiedValue = triplet->mXValue;
+ } else {
+ aSpecifiedValue.SetTripletValue(triplet);
+ }
+ } break;
+ case eUnit_CSSRect: {
+ nsCSSRect& rect = aSpecifiedValue.SetRectValue();
+ rect = *aComputedValue.GetCSSRectValue();
+ } break;
+ case eUnit_Dasharray:
+ case eUnit_Shadow:
+ case eUnit_Filter:
+ case eUnit_BackgroundPositionCoord:
+ {
+ nsCSSValueList* computedList = aComputedValue.GetCSSValueListValue();
+ if (computedList) {
+ aSpecifiedValue.SetDependentListValue(computedList);
+ } else {
+ aSpecifiedValue.SetNoneValue();
+ }
+ }
+ break;
+ case eUnit_Shape: {
+ nsCSSValue::Array* computedArray = aComputedValue.GetCSSValueArrayValue();
+ aSpecifiedValue.SetArrayValue(computedArray, eCSSUnit_Array);
+ break;
+ }
+ case eUnit_Transform:
+ aSpecifiedValue.
+ SetSharedListValue(aComputedValue.GetCSSValueSharedListValue());
+ break;
+ case eUnit_CSSValuePairList:
+ aSpecifiedValue.
+ SetDependentPairListValue(aComputedValue.GetCSSValuePairListValue());
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool
+StyleAnimationValue::UncomputeValue(nsCSSPropertyID aProperty,
+ StyleAnimationValue&& aComputedValue,
+ nsCSSValue& aSpecifiedValue)
+{
+ Unit unit = aComputedValue.GetUnit();
+ switch (unit) {
+ case eUnit_Dasharray:
+ case eUnit_Shadow:
+ case eUnit_Filter:
+ case eUnit_BackgroundPositionCoord:
+ {
+ UniquePtr<nsCSSValueList> computedList =
+ aComputedValue.TakeCSSValueListValue();
+ if (computedList) {
+ aSpecifiedValue.AdoptListValue(Move(computedList));
+ } else {
+ aSpecifiedValue.SetNoneValue();
+ }
+ }
+ break;
+ case eUnit_CSSValuePairList:
+ {
+ UniquePtr<nsCSSValuePairList> computedList =
+ aComputedValue.TakeCSSValuePairListValue();
+ MOZ_ASSERT(computedList, "Pair list should never be null");
+ aSpecifiedValue.AdoptPairListValue(Move(computedList));
+ }
+ break;
+ default:
+ return UncomputeValue(aProperty, aComputedValue, aSpecifiedValue);
+ }
+ return true;
+}
+
+bool
+StyleAnimationValue::UncomputeValue(nsCSSPropertyID aProperty,
+ const StyleAnimationValue& aComputedValue,
+ nsAString& aSpecifiedValue)
+{
+ aSpecifiedValue.Truncate(); // Clear outparam, if it's not already empty
+
+ if (aComputedValue.GetUnit() == eUnit_UnparsedString) {
+ aComputedValue.GetStringValue(aSpecifiedValue);
+ return true;
+ }
+ nsCSSValue val;
+ if (!StyleAnimationValue::UncomputeValue(aProperty, aComputedValue, val)) {
+ return false;
+ }
+
+ val.AppendToString(aProperty, aSpecifiedValue, nsCSSValue::eNormalized);
+ return true;
+}
+
+template<typename T>
+inline const T&
+StyleDataAtOffset(const void* aStyleStruct, ptrdiff_t aOffset)
+{
+ return *reinterpret_cast<const T*>(
+ reinterpret_cast<const uint8_t*>(aStyleStruct) + aOffset);
+}
+
+static bool
+StyleCoordToValue(const nsStyleCoord& aCoord, StyleAnimationValue& aValue)
+{
+ switch (aCoord.GetUnit()) {
+ case eStyleUnit_Normal:
+ aValue.SetNormalValue();
+ break;
+ case eStyleUnit_Auto:
+ aValue.SetAutoValue();
+ break;
+ case eStyleUnit_None:
+ aValue.SetNoneValue();
+ break;
+ case eStyleUnit_Percent:
+ aValue.SetPercentValue(aCoord.GetPercentValue());
+ break;
+ case eStyleUnit_Factor:
+ aValue.SetFloatValue(aCoord.GetFactorValue());
+ break;
+ case eStyleUnit_Coord:
+ aValue.SetCoordValue(aCoord.GetCoordValue());
+ break;
+ case eStyleUnit_Enumerated:
+ aValue.SetIntValue(aCoord.GetIntValue(),
+ StyleAnimationValue::eUnit_Enumerated);
+ break;
+ case eStyleUnit_Integer:
+ aValue.SetIntValue(aCoord.GetIntValue(),
+ StyleAnimationValue::eUnit_Integer);
+ break;
+ case eStyleUnit_Calc: {
+ nsAutoPtr<nsCSSValue> val(new nsCSSValue);
+ CalcValueToCSSValue(aCoord.GetCalcValue(), *val);
+ aValue.SetAndAdoptCSSValueValue(val.forget(),
+ StyleAnimationValue::eUnit_Calc);
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+}
+
+static bool
+StyleCoordToCSSValue(const nsStyleCoord& aCoord, nsCSSValue& aCSSValue)
+{
+ switch (aCoord.GetUnit()) {
+ case eStyleUnit_Coord:
+ aCSSValue.SetIntegerCoordValue(aCoord.GetCoordValue());
+ break;
+ case eStyleUnit_Factor:
+ aCSSValue.SetFloatValue(aCoord.GetFactorValue(), eCSSUnit_Number);
+ break;
+ case eStyleUnit_Percent:
+ aCSSValue.SetPercentValue(aCoord.GetPercentValue());
+ break;
+ case eStyleUnit_Calc:
+ CalcValueToCSSValue(aCoord.GetCalcValue(), aCSSValue);
+ break;
+ case eStyleUnit_Degree:
+ aCSSValue.SetFloatValue(aCoord.GetAngleValue(), eCSSUnit_Degree);
+ break;
+ case eStyleUnit_Grad:
+ aCSSValue.SetFloatValue(aCoord.GetAngleValue(), eCSSUnit_Grad);
+ break;
+ case eStyleUnit_Radian:
+ aCSSValue.SetFloatValue(aCoord.GetAngleValue(), eCSSUnit_Radian);
+ break;
+ case eStyleUnit_Turn:
+ aCSSValue.SetFloatValue(aCoord.GetAngleValue(), eCSSUnit_Turn);
+ break;
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ return false;
+ }
+ return true;
+}
+
+static void
+SetPositionValue(const Position& aPos, nsCSSValue& aCSSValue)
+{
+ RefPtr<nsCSSValue::Array> posArray = nsCSSValue::Array::Create(4);
+ aCSSValue.SetArrayValue(posArray.get(), eCSSUnit_Array);
+
+ // NOTE: Array entries #0 and #2 here are intentionally left untouched, with
+ // eCSSUnit_Null. The purpose of these entries in our specified-style
+ // <position> representation is to store edge names. But for values
+ // extracted from computed style (which is what we're dealing with here),
+ // we'll just have a normalized "x,y" position, with no edge names needed.
+ nsCSSValue& xValue = posArray->Item(1);
+ nsCSSValue& yValue = posArray->Item(3);
+
+ CalcValueToCSSValue(&aPos.mXPosition, xValue);
+ CalcValueToCSSValue(&aPos.mYPosition, yValue);
+}
+
+static void
+SetPositionCoordValue(const Position::Coord& aPosCoord,
+ nsCSSValue& aCSSValue)
+{
+ RefPtr<nsCSSValue::Array> posArray = nsCSSValue::Array::Create(2);
+ aCSSValue.SetArrayValue(posArray.get(), eCSSUnit_Array);
+
+ // NOTE: Array entry #0 here is intentionally left untouched, with
+ // eCSSUnit_Null. The purpose of this entry in our specified-style
+ // <position-coord> representation is to store edge names. But for values
+ // extracted from computed style (which is what we're dealing with here),
+ // we'll just have a normalized "x"/"y" position, with no edge names needed.
+ nsCSSValue& value = posArray->Item(1);
+
+ CalcValueToCSSValue(&aPosCoord, value);
+}
+
+/*
+ * Assign |aOutput = aInput|, except with any non-pixel lengths
+ * replaced with the equivalent in pixels, and any non-canonical calc()
+ * expressions replaced with canonical ones.
+ */
+static void
+SubstitutePixelValues(nsStyleContext* aStyleContext,
+ const nsCSSValue& aInput, nsCSSValue& aOutput)
+{
+ if (aInput.IsCalcUnit()) {
+ RuleNodeCacheConditions conditions;
+ nsRuleNode::ComputedCalc c =
+ nsRuleNode::SpecifiedCalcToComputedCalc(aInput, aStyleContext,
+ aStyleContext->PresContext(),
+ conditions);
+ nsStyleCoord::CalcValue c2;
+ c2.mLength = c.mLength;
+ c2.mPercent = c.mPercent;
+ c2.mHasPercent = true; // doesn't matter for transform translate
+ CalcValueToCSSValue(&c2, aOutput);
+ } else if (aInput.UnitHasArrayValue()) {
+ const nsCSSValue::Array *inputArray = aInput.GetArrayValue();
+ RefPtr<nsCSSValue::Array> outputArray =
+ nsCSSValue::Array::Create(inputArray->Count());
+ for (size_t i = 0, i_end = inputArray->Count(); i < i_end; ++i) {
+ SubstitutePixelValues(aStyleContext,
+ inputArray->Item(i), outputArray->Item(i));
+ }
+ aOutput.SetArrayValue(outputArray, aInput.GetUnit());
+ } else if (aInput.IsLengthUnit() &&
+ aInput.GetUnit() != eCSSUnit_Pixel) {
+ RuleNodeCacheConditions conditions;
+ nscoord len = nsRuleNode::CalcLength(aInput, aStyleContext,
+ aStyleContext->PresContext(),
+ conditions);
+ aOutput.SetFloatValue(nsPresContext::AppUnitsToFloatCSSPixels(len),
+ eCSSUnit_Pixel);
+ } else {
+ aOutput = aInput;
+ }
+}
+
+static void
+ExtractImageLayerPositionXList(const nsStyleImageLayers& aLayer,
+ StyleAnimationValue& aComputedValue)
+{
+ MOZ_ASSERT(aLayer.mPositionXCount > 0, "unexpected count");
+
+ nsAutoPtr<nsCSSValueList> result;
+ nsCSSValueList **resultTail = getter_Transfers(result);
+ for (uint32_t i = 0, i_end = aLayer.mPositionXCount; i != i_end; ++i) {
+ nsCSSValueList *item = new nsCSSValueList;
+ *resultTail = item;
+ resultTail = &item->mNext;
+ SetPositionCoordValue(aLayer.mLayers[i].mPosition.mXPosition,
+ item->mValue);
+ }
+
+ aComputedValue.SetAndAdoptCSSValueListValue(result.forget(),
+ StyleAnimationValue::eUnit_BackgroundPositionCoord);
+}
+
+static void
+ExtractImageLayerPositionYList(const nsStyleImageLayers& aLayer,
+ StyleAnimationValue& aComputedValue)
+{
+ MOZ_ASSERT(aLayer.mPositionYCount > 0, "unexpected count");
+
+ nsAutoPtr<nsCSSValueList> result;
+ nsCSSValueList **resultTail = getter_Transfers(result);
+ for (uint32_t i = 0, i_end = aLayer.mPositionYCount; i != i_end; ++i) {
+ nsCSSValueList *item = new nsCSSValueList;
+ *resultTail = item;
+ resultTail = &item->mNext;
+ SetPositionCoordValue(aLayer.mLayers[i].mPosition.mYPosition,
+ item->mValue);
+ }
+
+ aComputedValue.SetAndAdoptCSSValueListValue(result.forget(),
+ StyleAnimationValue::eUnit_BackgroundPositionCoord);
+}
+
+static void
+ExtractImageLayerSizePairList(const nsStyleImageLayers& aLayer,
+ StyleAnimationValue& aComputedValue)
+{
+ MOZ_ASSERT(aLayer.mSizeCount > 0, "unexpected count");
+
+ nsAutoPtr<nsCSSValuePairList> result;
+ nsCSSValuePairList **resultTail = getter_Transfers(result);
+ for (uint32_t i = 0, i_end = aLayer.mSizeCount; i != i_end; ++i) {
+ nsCSSValuePairList *item = new nsCSSValuePairList;
+ *resultTail = item;
+ resultTail = &item->mNext;
+
+ const nsStyleImageLayers::Size &size = aLayer.mLayers[i].mSize;
+ switch (size.mWidthType) {
+ case nsStyleImageLayers::Size::eContain:
+ case nsStyleImageLayers::Size::eCover:
+ item->mXValue.SetIntValue(size.mWidthType,
+ eCSSUnit_Enumerated);
+ break;
+ case nsStyleImageLayers::Size::eAuto:
+ item->mXValue.SetAutoValue();
+ break;
+ case nsStyleImageLayers::Size::eLengthPercentage:
+ // XXXbz is there a good reason we can't just
+ // CalcValueToCSSValue(&size.mWidth, item->mXValue) here?
+ if (!size.mWidth.mHasPercent &&
+ // negative values must have come from calc()
+ size.mWidth.mLength >= 0) {
+ MOZ_ASSERT(size.mWidth.mPercent == 0.0f,
+ "Shouldn't have mPercent");
+ item->mXValue.SetIntegerCoordValue(size.mWidth.mLength);
+ } else if (size.mWidth.mLength == 0 &&
+ // negative values must have come from calc()
+ size.mWidth.mPercent >= 0.0f) {
+ item->mXValue.SetPercentValue(size.mWidth.mPercent);
+ } else {
+ CalcValueToCSSValue(&size.mWidth, item->mXValue);
+ }
+ break;
+ }
+
+ switch (size.mHeightType) {
+ case nsStyleImageLayers::Size::eContain:
+ case nsStyleImageLayers::Size::eCover:
+ // leave it null
+ break;
+ case nsStyleImageLayers::Size::eAuto:
+ item->mYValue.SetAutoValue();
+ break;
+ case nsStyleImageLayers::Size::eLengthPercentage:
+ // XXXbz is there a good reason we can't just
+ // CalcValueToCSSValue(&size.mHeight, item->mYValue) here?
+ if (!size.mHeight.mHasPercent &&
+ // negative values must have come from calc()
+ size.mHeight.mLength >= 0) {
+ MOZ_ASSERT(size.mHeight.mPercent == 0.0f,
+ "Shouldn't have mPercent");
+ item->mYValue.SetIntegerCoordValue(size.mHeight.mLength);
+ } else if (size.mHeight.mLength == 0 &&
+ // negative values must have come from calc()
+ size.mHeight.mPercent >= 0.0f) {
+ item->mYValue.SetPercentValue(size.mHeight.mPercent);
+ } else {
+ CalcValueToCSSValue(&size.mHeight, item->mYValue);
+ }
+ break;
+ }
+ }
+
+ aComputedValue.SetAndAdoptCSSValuePairListValue(result.forget());
+}
+
+static bool
+StyleClipBasicShapeToCSSArray(const StyleClipPath& aClipPath,
+ nsCSSValue::Array* aResult)
+{
+ MOZ_ASSERT(aResult->Count() == 2,
+ "Expected array to be presized for a function and the sizing-box");
+
+ const StyleBasicShape* shape = aClipPath.GetBasicShape();
+ nsCSSKeyword functionName = shape->GetShapeTypeName();
+ RefPtr<nsCSSValue::Array> functionArray;
+ switch (shape->GetShapeType()) {
+ case StyleBasicShapeType::Circle:
+ case StyleBasicShapeType::Ellipse: {
+ const nsTArray<nsStyleCoord>& coords = shape->Coordinates();
+ MOZ_ASSERT(coords.Length() == ShapeArgumentCount(functionName) - 1,
+ "Unexpected radii count");
+ // The "+1" is for the center point:
+ functionArray = aResult->Item(0).InitFunction(functionName,
+ coords.Length() + 1);
+ for (size_t i = 0; i < coords.Length(); ++i) {
+ if (coords[i].GetUnit() == eStyleUnit_Enumerated) {
+ functionArray->Item(i + 1).SetIntValue(coords[i].GetIntValue(),
+ eCSSUnit_Enumerated);
+ } else if (!StyleCoordToCSSValue(coords[i],
+ functionArray->Item(i + 1))) {
+ return false;
+ }
+ }
+ // Set functionArray's last item to the circle or ellipse's center point:
+ SetPositionValue(shape->GetPosition(),
+ functionArray->Item(functionArray->Count() - 1));
+ break;
+ }
+ case StyleBasicShapeType::Polygon: {
+ functionArray =
+ aResult->Item(0).InitFunction(functionName,
+ ShapeArgumentCount(functionName));
+ functionArray->Item(1).SetIntValue(shape->GetFillRule(),
+ eCSSUnit_Enumerated);
+ nsCSSValuePairList* list = functionArray->Item(2).SetPairListValue();
+ const nsTArray<nsStyleCoord>& coords = shape->Coordinates();
+ MOZ_ASSERT((coords.Length() % 2) == 0);
+ for (size_t i = 0; i < coords.Length(); i += 2) {
+ if (i > 0) {
+ list->mNext = new nsCSSValuePairList;
+ list = list->mNext;
+ }
+ if (!StyleCoordToCSSValue(coords[i], list->mXValue) ||
+ !StyleCoordToCSSValue(coords[i + 1], list->mYValue)) {
+ return false;
+ }
+ }
+ break;
+ }
+ case StyleBasicShapeType::Inset: {
+ const nsTArray<nsStyleCoord>& coords = shape->Coordinates();
+ MOZ_ASSERT(coords.Length() == ShapeArgumentCount(functionName) - 1,
+ "Unexpected offset count");
+ functionArray =
+ aResult->Item(0).InitFunction(functionName, coords.Length() + 1);
+ for (size_t i = 0; i < coords.Length(); ++i) {
+ if (!StyleCoordToCSSValue(coords[i], functionArray->Item(i + 1))) {
+ return false;
+ }
+ }
+ RefPtr<nsCSSValue::Array> radiusArray = nsCSSValue::Array::Create(4);
+ const nsStyleCorners& radii = shape->GetRadius();
+ NS_FOR_CSS_FULL_CORNERS(corner) {
+ auto pair = MakeUnique<nsCSSValuePair>();
+ if (!StyleCoordToCSSValue(radii.Get(NS_FULL_TO_HALF_CORNER(corner, false)),
+ pair->mXValue) ||
+ !StyleCoordToCSSValue(radii.Get(NS_FULL_TO_HALF_CORNER(corner, true)),
+ pair->mYValue)) {
+ return false;
+ }
+ radiusArray->Item(corner).SetPairValue(pair.get());
+ }
+ // Set the last item in functionArray to the radius array:
+ functionArray->Item(functionArray->Count() - 1).
+ SetArrayValue(radiusArray, eCSSUnit_Array);
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown shape type");
+ return false;
+ }
+ aResult->Item(1).SetIntValue(aClipPath.GetReferenceBox(),
+ eCSSUnit_Enumerated);
+ return true;
+}
+
+bool
+StyleAnimationValue::ExtractComputedValue(nsCSSPropertyID aProperty,
+ nsStyleContext* aStyleContext,
+ StyleAnimationValue& aComputedValue)
+{
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
+ "bad property");
+ const void* styleStruct =
+ aStyleContext->StyleData(nsCSSProps::kSIDTable[aProperty]);
+ ptrdiff_t ssOffset = nsCSSProps::kStyleStructOffsetTable[aProperty];
+ nsStyleAnimType animType = nsCSSProps::kAnimTypeTable[aProperty];
+ MOZ_ASSERT(0 <= ssOffset ||
+ animType == eStyleAnimType_Custom ||
+ animType == eStyleAnimType_Discrete,
+ "all animation types other than Custom and Discrete must " \
+ "specify a style struct offset to extract values from");
+ switch (animType) {
+ case eStyleAnimType_Custom:
+ switch (aProperty) {
+ // For border-width, ignore the border-image business (which
+ // only exists until we update our implementation to the current
+ // spec) and use GetComputedBorder
+
+ #define BORDER_WIDTH_CASE(prop_, side_) \
+ case prop_: \
+ aComputedValue.SetCoordValue( \
+ static_cast<const nsStyleBorder*>(styleStruct)-> \
+ GetComputedBorder().side_); \
+ break;
+ BORDER_WIDTH_CASE(eCSSProperty_border_bottom_width, bottom)
+ BORDER_WIDTH_CASE(eCSSProperty_border_left_width, left)
+ BORDER_WIDTH_CASE(eCSSProperty_border_right_width, right)
+ BORDER_WIDTH_CASE(eCSSProperty_border_top_width, top)
+ #undef BORDER_WIDTH_CASE
+
+ case eCSSProperty_column_rule_width:
+ aComputedValue.SetCoordValue(
+ static_cast<const nsStyleColumn*>(styleStruct)->
+ GetComputedColumnRuleWidth());
+ break;
+
+ case eCSSProperty_column_count: {
+ const nsStyleColumn *styleColumn =
+ static_cast<const nsStyleColumn*>(styleStruct);
+ if (styleColumn->mColumnCount == NS_STYLE_COLUMN_COUNT_AUTO) {
+ aComputedValue.SetAutoValue();
+ } else {
+ aComputedValue.SetIntValue(styleColumn->mColumnCount,
+ eUnit_Integer);
+ }
+ break;
+ }
+
+ case eCSSProperty_order: {
+ const nsStylePosition *stylePosition =
+ static_cast<const nsStylePosition*>(styleStruct);
+ aComputedValue.SetIntValue(stylePosition->mOrder,
+ eUnit_Integer);
+ break;
+ }
+
+ case eCSSProperty_border_spacing: {
+ const nsStyleTableBorder *styleTableBorder =
+ static_cast<const nsStyleTableBorder*>(styleStruct);
+ nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
+ pair->mXValue.SetIntegerCoordValue(styleTableBorder->mBorderSpacingCol);
+ pair->mYValue.SetIntegerCoordValue(styleTableBorder->mBorderSpacingRow);
+ aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
+ eUnit_CSSValuePair);
+ break;
+ }
+
+ case eCSSProperty_transform_origin: {
+ const nsStyleDisplay *styleDisplay =
+ static_cast<const nsStyleDisplay*>(styleStruct);
+ nsAutoPtr<nsCSSValueTriplet> triplet(new nsCSSValueTriplet);
+ if (!StyleCoordToCSSValue(styleDisplay->mTransformOrigin[0],
+ triplet->mXValue) ||
+ !StyleCoordToCSSValue(styleDisplay->mTransformOrigin[1],
+ triplet->mYValue) ||
+ !StyleCoordToCSSValue(styleDisplay->mTransformOrigin[2],
+ triplet->mZValue)) {
+ return false;
+ }
+ aComputedValue.SetAndAdoptCSSValueTripletValue(triplet.forget(),
+ eUnit_CSSValueTriplet);
+ break;
+ }
+
+ case eCSSProperty_perspective_origin: {
+ const nsStyleDisplay *styleDisplay =
+ static_cast<const nsStyleDisplay*>(styleStruct);
+ nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
+ if (!StyleCoordToCSSValue(styleDisplay->mPerspectiveOrigin[0],
+ pair->mXValue) ||
+ !StyleCoordToCSSValue(styleDisplay->mPerspectiveOrigin[1],
+ pair->mYValue)) {
+ return false;
+ }
+ aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
+ eUnit_CSSValuePair);
+ break;
+ }
+
+ case eCSSProperty_stroke_dasharray: {
+ const nsStyleSVG *svg = static_cast<const nsStyleSVG*>(styleStruct);
+ if (!svg->mStrokeDasharray.IsEmpty()) {
+ nsAutoPtr<nsCSSValueList> result;
+ nsCSSValueList **resultTail = getter_Transfers(result);
+ for (uint32_t i = 0, i_end = svg->mStrokeDasharray.Length();
+ i != i_end; ++i) {
+ nsCSSValueList *item = new nsCSSValueList;
+ *resultTail = item;
+ resultTail = &item->mNext;
+
+ const nsStyleCoord &coord = svg->mStrokeDasharray[i];
+ nsCSSValue &value = item->mValue;
+ switch (coord.GetUnit()) {
+ case eStyleUnit_Coord:
+ // Number means the same thing as length; we want to
+ // animate them the same way. Normalize both to number
+ // since it has more accuracy (float vs nscoord).
+ value.SetFloatValue(nsPresContext::
+ AppUnitsToFloatCSSPixels(coord.GetCoordValue()),
+ eCSSUnit_Number);
+ break;
+ case eStyleUnit_Factor:
+ value.SetFloatValue(coord.GetFactorValue(),
+ eCSSUnit_Number);
+ break;
+ case eStyleUnit_Percent:
+ value.SetPercentValue(coord.GetPercentValue());
+ break;
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ return false;
+ }
+ }
+ aComputedValue.SetAndAdoptCSSValueListValue(result.forget(),
+ eUnit_Dasharray);
+ } else if (svg->StrokeDasharrayFromObject()) {
+ // An empty dasharray with StrokeDasharrayFromObject() == true
+ // corresponds to the "context-value" keyword.
+ aComputedValue.SetIntValue(NS_STYLE_STROKE_PROP_CONTEXT_VALUE,
+ eUnit_Enumerated);
+ } else {
+ // Otherwise, an empty dasharray corresponds to the "none" keyword.
+ aComputedValue.SetNoneValue();
+ }
+ break;
+ }
+
+ case eCSSProperty_font_stretch: {
+ int16_t stretch =
+ static_cast<const nsStyleFont*>(styleStruct)->mFont.stretch;
+ static_assert(NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED == -4 &&
+ NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED == 4,
+ "font stretch constants not as expected");
+ if (stretch < NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED ||
+ stretch > NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED) {
+ return false;
+ }
+ aComputedValue.SetIntValue(stretch, eUnit_Enumerated);
+ return true;
+ }
+
+ case eCSSProperty_font_weight: {
+ uint16_t weight =
+ static_cast<const nsStyleFont*>(styleStruct)->mFont.weight;
+ if (weight % 100 != 0) {
+ return false;
+ }
+ aComputedValue.SetIntValue(weight, eUnit_Integer);
+ return true;
+ }
+
+ case eCSSProperty_image_region: {
+ const nsStyleList *list =
+ static_cast<const nsStyleList*>(styleStruct);
+ const nsRect &srect = list->mImageRegion;
+ if (srect.IsEmpty()) {
+ aComputedValue.SetAutoValue();
+ break;
+ }
+
+ nsCSSRect *vrect = new nsCSSRect;
+ vrect->mLeft.SetIntegerCoordValue(srect.x);
+ vrect->mTop.SetIntegerCoordValue(srect.y);
+ vrect->mRight.SetIntegerCoordValue(srect.XMost());
+ vrect->mBottom.SetIntegerCoordValue(srect.YMost());
+ aComputedValue.SetAndAdoptCSSRectValue(vrect, eUnit_CSSRect);
+ break;
+ }
+
+ case eCSSProperty_clip: {
+ const nsStyleEffects* effects =
+ static_cast<const nsStyleEffects*>(styleStruct);
+ if (!(effects->mClipFlags & NS_STYLE_CLIP_RECT)) {
+ aComputedValue.SetAutoValue();
+ } else {
+ nsCSSRect *vrect = new nsCSSRect;
+ const nsRect &srect = effects->mClip;
+ if (effects->mClipFlags & NS_STYLE_CLIP_TOP_AUTO) {
+ vrect->mTop.SetAutoValue();
+ } else {
+ vrect->mTop.SetIntegerCoordValue(srect.y);
+ }
+ if (effects->mClipFlags & NS_STYLE_CLIP_RIGHT_AUTO) {
+ vrect->mRight.SetAutoValue();
+ } else {
+ vrect->mRight.SetIntegerCoordValue(srect.XMost());
+ }
+ if (effects->mClipFlags & NS_STYLE_CLIP_BOTTOM_AUTO) {
+ vrect->mBottom.SetAutoValue();
+ } else {
+ vrect->mBottom.SetIntegerCoordValue(srect.YMost());
+ }
+ if (effects->mClipFlags & NS_STYLE_CLIP_LEFT_AUTO) {
+ vrect->mLeft.SetAutoValue();
+ } else {
+ vrect->mLeft.SetIntegerCoordValue(srect.x);
+ }
+ aComputedValue.SetAndAdoptCSSRectValue(vrect, eUnit_CSSRect);
+ }
+ break;
+ }
+
+ case eCSSProperty_object_position: {
+ const nsStylePosition* stylePos =
+ static_cast<const nsStylePosition*>(styleStruct);
+
+ nsAutoPtr<nsCSSValue> val(new nsCSSValue);
+ SetPositionValue(stylePos->mObjectPosition, *val);
+
+ aComputedValue.SetAndAdoptCSSValueValue(val.forget(),
+ eUnit_ObjectPosition);
+ break;
+ }
+
+ case eCSSProperty_background_position_x: {
+ const nsStyleImageLayers& layers =
+ static_cast<const nsStyleBackground*>(styleStruct)->mImage;
+ ExtractImageLayerPositionXList(layers, aComputedValue);
+ break;
+ }
+ case eCSSProperty_background_position_y: {
+ const nsStyleImageLayers& layers =
+ static_cast<const nsStyleBackground*>(styleStruct)->mImage;
+ ExtractImageLayerPositionYList(layers, aComputedValue);
+ break;
+
+
+
+
+ }
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ case eCSSProperty_mask_position_x: {
+ const nsStyleImageLayers& layers =
+ static_cast<const nsStyleSVGReset*>(styleStruct)->mMask;
+ ExtractImageLayerPositionXList(layers, aComputedValue);
+ break;
+ }
+ case eCSSProperty_mask_position_y: {
+ const nsStyleImageLayers& layers =
+ static_cast<const nsStyleSVGReset*>(styleStruct)->mMask;
+ ExtractImageLayerPositionYList(layers, aComputedValue);
+
+ break;
+ }
+#endif
+ case eCSSProperty_background_size: {
+ const nsStyleImageLayers& layers =
+ static_cast<const nsStyleBackground*>(styleStruct)->mImage;
+ ExtractImageLayerSizePairList(layers, aComputedValue);
+ break;
+ }
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ case eCSSProperty_mask_size: {
+ const nsStyleImageLayers& layers =
+ static_cast<const nsStyleSVGReset*>(styleStruct)->mMask;
+ ExtractImageLayerSizePairList(layers, aComputedValue);
+ break;
+ }
+#endif
+
+ case eCSSProperty_clip_path: {
+ const nsStyleSVGReset* svgReset =
+ static_cast<const nsStyleSVGReset*>(styleStruct);
+ const StyleClipPath& clipPath = svgReset->mClipPath;
+ const StyleShapeSourceType type = clipPath.GetType();
+
+ if (type == StyleShapeSourceType::URL) {
+ auto result = MakeUnique<nsCSSValue>();
+ result->SetURLValue(clipPath.GetURL());
+ aComputedValue.SetAndAdoptCSSValueValue(result.release(), eUnit_URL);
+ } else if (type == StyleShapeSourceType::Box) {
+ aComputedValue.SetIntValue(clipPath.GetReferenceBox(),
+ eUnit_Enumerated);
+ } else if (type == StyleShapeSourceType::Shape) {
+ RefPtr<nsCSSValue::Array> result = nsCSSValue::Array::Create(2);
+ if (!StyleClipBasicShapeToCSSArray(clipPath, result)) {
+ return false;
+ }
+ aComputedValue.SetCSSValueArrayValue(result, eUnit_Shape);
+
+ } else {
+ MOZ_ASSERT(type == StyleShapeSourceType::None, "unknown type");
+ aComputedValue.SetNoneValue();
+ }
+ break;
+ }
+
+ case eCSSProperty_filter: {
+ const nsStyleEffects* effects =
+ static_cast<const nsStyleEffects*>(styleStruct);
+ const nsTArray<nsStyleFilter>& filters = effects->mFilters;
+ nsAutoPtr<nsCSSValueList> result;
+ nsCSSValueList **resultTail = getter_Transfers(result);
+ for (uint32_t i = 0; i < filters.Length(); ++i) {
+ nsCSSValueList *item = new nsCSSValueList;
+ *resultTail = item;
+ resultTail = &item->mNext;
+ const nsStyleFilter& filter = filters[i];
+ int32_t type = filter.GetType();
+ if (type == NS_STYLE_FILTER_URL) {
+ item->mValue.SetURLValue(filter.GetURL());
+ } else {
+ nsCSSKeyword functionName =
+ nsCSSProps::ValueToKeywordEnum(type,
+ nsCSSProps::kFilterFunctionKTable);
+ nsCSSValue::Array* filterArray =
+ item->mValue.InitFunction(functionName, 1);
+ if (type >= NS_STYLE_FILTER_BLUR && type <= NS_STYLE_FILTER_HUE_ROTATE) {
+ if (!StyleCoordToCSSValue(
+ filter.GetFilterParameter(),
+ filterArray->Item(1))) {
+ return false;
+ }
+ } else if (type == NS_STYLE_FILTER_DROP_SHADOW) {
+ nsCSSValueList* shadowResult = filterArray->Item(1).SetListValue();
+ nsAutoPtr<nsCSSValueList> tmpShadowValue;
+ nsCSSValueList **tmpShadowResultTail = getter_Transfers(tmpShadowValue);
+ nsCSSShadowArray* shadowArray = filter.GetDropShadow();
+ MOZ_ASSERT(shadowArray->Length() == 1,
+ "expected exactly one shadow");
+ AppendCSSShadowValue(shadowArray->ShadowAt(0), tmpShadowResultTail);
+ *shadowResult = *tmpShadowValue;
+ } else {
+ // We checked all possible nsStyleFilter types but
+ // NS_STYLE_FILTER_NULL before. We should never enter this path.
+ NS_NOTREACHED("no other filter functions defined");
+ return false;
+ }
+ }
+ }
+
+ aComputedValue.SetAndAdoptCSSValueListValue(result.forget(),
+ eUnit_Filter);
+ break;
+ }
+
+ case eCSSProperty_transform: {
+ const nsStyleDisplay *display =
+ static_cast<const nsStyleDisplay*>(styleStruct);
+ nsAutoPtr<nsCSSValueList> result;
+ if (display->mSpecifiedTransform) {
+ // Clone, and convert all lengths (not percents) to pixels.
+ nsCSSValueList **resultTail = getter_Transfers(result);
+ for (const nsCSSValueList *l = display->mSpecifiedTransform->mHead;
+ l; l = l->mNext) {
+ nsCSSValueList *clone = new nsCSSValueList;
+ *resultTail = clone;
+ resultTail = &clone->mNext;
+
+ SubstitutePixelValues(aStyleContext, l->mValue, clone->mValue);
+ }
+ } else {
+ result = new nsCSSValueList();
+ result->mValue.SetNoneValue();
+ }
+
+ aComputedValue.SetTransformValue(
+ new nsCSSValueSharedList(result.forget()));
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "missing property implementation");
+ return false;
+ };
+ return true;
+ case eStyleAnimType_Coord: {
+ const nsStyleCoord& coord =
+ StyleDataAtOffset<nsStyleCoord>(styleStruct, ssOffset);
+ if (nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_NUMBERS_ARE_PIXELS) &&
+ coord.GetUnit() == eStyleUnit_Coord) {
+ // For SVG properties where number means the same thing as length,
+ // we want to animate them the same way. Normalize both to number
+ // since it has more accuracy (float vs nscoord).
+ aComputedValue.SetFloatValue(nsPresContext::
+ AppUnitsToFloatCSSPixels(coord.GetCoordValue()));
+ return true;
+ }
+ return StyleCoordToValue(coord, aComputedValue);
+ }
+ case eStyleAnimType_Sides_Top:
+ case eStyleAnimType_Sides_Right:
+ case eStyleAnimType_Sides_Bottom:
+ case eStyleAnimType_Sides_Left: {
+ static_assert(
+ NS_SIDE_TOP == eStyleAnimType_Sides_Top -eStyleAnimType_Sides_Top &&
+ NS_SIDE_RIGHT == eStyleAnimType_Sides_Right -eStyleAnimType_Sides_Top &&
+ NS_SIDE_BOTTOM == eStyleAnimType_Sides_Bottom-eStyleAnimType_Sides_Top &&
+ NS_SIDE_LEFT == eStyleAnimType_Sides_Left -eStyleAnimType_Sides_Top,
+ "box side constants out of sync with animation side constants");
+
+ const nsStyleCoord &coord =
+ StyleDataAtOffset<nsStyleSides>(styleStruct, ssOffset).
+ Get(mozilla::css::Side(animType - eStyleAnimType_Sides_Top));
+ return StyleCoordToValue(coord, aComputedValue);
+ }
+ case eStyleAnimType_Corner_TopLeft:
+ case eStyleAnimType_Corner_TopRight:
+ case eStyleAnimType_Corner_BottomRight:
+ case eStyleAnimType_Corner_BottomLeft: {
+ static_assert(
+ NS_CORNER_TOP_LEFT == eStyleAnimType_Corner_TopLeft -
+ eStyleAnimType_Corner_TopLeft &&
+ NS_CORNER_TOP_RIGHT == eStyleAnimType_Corner_TopRight -
+ eStyleAnimType_Corner_TopLeft &&
+ NS_CORNER_BOTTOM_RIGHT == eStyleAnimType_Corner_BottomRight -
+ eStyleAnimType_Corner_TopLeft &&
+ NS_CORNER_BOTTOM_LEFT == eStyleAnimType_Corner_BottomLeft -
+ eStyleAnimType_Corner_TopLeft,
+ "box corner constants out of sync with animation corner constants");
+
+ const nsStyleCorners& corners =
+ StyleDataAtOffset<nsStyleCorners>(styleStruct, ssOffset);
+ uint8_t fullCorner = animType - eStyleAnimType_Corner_TopLeft;
+ const nsStyleCoord &horiz =
+ corners.Get(NS_FULL_TO_HALF_CORNER(fullCorner, false));
+ const nsStyleCoord &vert =
+ corners.Get(NS_FULL_TO_HALF_CORNER(fullCorner, true));
+ nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
+ if (!StyleCoordToCSSValue(horiz, pair->mXValue) ||
+ !StyleCoordToCSSValue(vert, pair->mYValue)) {
+ return false;
+ }
+ aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
+ eUnit_CSSValuePair);
+ return true;
+ }
+ case eStyleAnimType_nscoord:
+ aComputedValue.SetCoordValue(
+ StyleDataAtOffset<nscoord>(styleStruct, ssOffset));
+ return true;
+ case eStyleAnimType_float:
+ aComputedValue.SetFloatValue(
+ StyleDataAtOffset<float>(styleStruct, ssOffset));
+ if (aProperty == eCSSProperty_font_size_adjust &&
+ aComputedValue.GetFloatValue() == -1.0f) {
+ // In nsStyleFont, we set mFont.sizeAdjust to -1.0 to represent
+ // font-size-adjust: none. Here, we have to treat this as a keyword
+ // instead of a float value, to make sure we don't end up doing
+ // interpolation with it.
+ aComputedValue.SetNoneValue();
+ }
+ return true;
+ case eStyleAnimType_Color:
+ aComputedValue.SetColorValue(
+ StyleDataAtOffset<nscolor>(styleStruct, ssOffset));
+ return true;
+ case eStyleAnimType_ComplexColor: {
+ aComputedValue.SetComplexColorValue(
+ StyleDataAtOffset<StyleComplexColor>(styleStruct, ssOffset));
+ return true;
+ }
+ case eStyleAnimType_PaintServer: {
+ const nsStyleSVGPaint& paint =
+ StyleDataAtOffset<nsStyleSVGPaint>(styleStruct, ssOffset);
+ switch (paint.Type()) {
+ case eStyleSVGPaintType_Color:
+ aComputedValue.SetColorValue(paint.GetColor());
+ return true;
+ case eStyleSVGPaintType_Server: {
+ css::URLValue* url = paint.GetPaintServer();
+ if (!url) {
+ NS_WARNING("Null paint server");
+ return false;
+ }
+ nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
+ pair->mXValue.SetURLValue(url);
+ pair->mYValue.SetColorValue(paint.GetFallbackColor());
+ aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
+ eUnit_CSSValuePair);
+ return true;
+ }
+ case eStyleSVGPaintType_ContextFill:
+ case eStyleSVGPaintType_ContextStroke: {
+ nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
+ pair->mXValue.SetIntValue(paint.Type() == eStyleSVGPaintType_ContextFill ?
+ NS_COLOR_CONTEXT_FILL : NS_COLOR_CONTEXT_STROKE,
+ eCSSUnit_Enumerated);
+ pair->mYValue.SetColorValue(paint.GetFallbackColor());
+ aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
+ eUnit_CSSValuePair);
+ return true;
+ }
+ default:
+ MOZ_ASSERT(paint.Type() == eStyleSVGPaintType_None,
+ "Unexpected SVG paint type");
+ aComputedValue.SetNoneValue();
+ return true;
+ }
+ }
+ case eStyleAnimType_Shadow: {
+ const nsCSSShadowArray* shadowArray =
+ StyleDataAtOffset<RefPtr<nsCSSShadowArray>>(styleStruct, ssOffset);
+ if (!shadowArray) {
+ aComputedValue.SetAndAdoptCSSValueListValue(nullptr, eUnit_Shadow);
+ return true;
+ }
+ nsAutoPtr<nsCSSValueList> result;
+ nsCSSValueList **resultTail = getter_Transfers(result);
+ for (uint32_t i = 0, i_end = shadowArray->Length(); i < i_end; ++i) {
+ AppendCSSShadowValue(shadowArray->ShadowAt(i), resultTail);
+ }
+ aComputedValue.SetAndAdoptCSSValueListValue(result.forget(),
+ eUnit_Shadow);
+ return true;
+ }
+ case eStyleAnimType_Discrete: {
+ if (aProperty == eCSSProperty_visibility) {
+ aComputedValue.SetIntValue(
+ static_cast<const nsStyleVisibility*>(styleStruct)->mVisible,
+ eUnit_Visibility);
+ return true;
+ }
+ auto cssValue = MakeUnique<nsCSSValue>(eCSSUnit_Unset);
+ aStyleContext->RuleNode()->GetDiscretelyAnimatedCSSValue(aProperty,
+ cssValue.get());
+ aComputedValue.SetAndAdoptCSSValueValue(cssValue.release(),
+ eUnit_DiscreteCSSValue);
+ return true;
+ }
+ case eStyleAnimType_None:
+ NS_NOTREACHED("shouldn't use on non-animatable properties");
+ }
+ return false;
+}
+
+gfxSize
+StyleAnimationValue::GetScaleValue(const nsIFrame* aForFrame) const
+{
+ MOZ_ASSERT(aForFrame);
+ MOZ_ASSERT(GetUnit() == StyleAnimationValue::eUnit_Transform);
+
+ nsCSSValueSharedList* list = GetCSSValueSharedListValue();
+ MOZ_ASSERT(list->mHead);
+
+ RuleNodeCacheConditions dontCare;
+ bool dontCareBool;
+ nsStyleTransformMatrix::TransformReferenceBox refBox(aForFrame);
+ Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
+ list->mHead,
+ aForFrame->StyleContext(),
+ aForFrame->PresContext(), dontCare, refBox,
+ aForFrame->PresContext()->AppUnitsPerDevPixel(),
+ &dontCareBool);
+
+ Matrix transform2d;
+ bool canDraw2D = transform.CanDraw2D(&transform2d);
+ if (!canDraw2D) {
+ return gfxSize();
+ }
+
+ return ThebesMatrix(transform2d).ScaleFactors(true);
+}
+
+StyleAnimationValue::StyleAnimationValue(int32_t aInt, Unit aUnit,
+ IntegerConstructorType)
+{
+ NS_ASSERTION(IsIntUnit(aUnit), "unit must be of integer type");
+ mUnit = aUnit;
+ mValue.mInt = aInt;
+}
+
+StyleAnimationValue::StyleAnimationValue(nscoord aLength, CoordConstructorType)
+{
+ mUnit = eUnit_Coord;
+ mValue.mCoord = aLength;
+}
+
+StyleAnimationValue::StyleAnimationValue(float aPercent,
+ PercentConstructorType)
+{
+ mUnit = eUnit_Percent;
+ mValue.mFloat = aPercent;
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+}
+
+StyleAnimationValue::StyleAnimationValue(float aFloat, FloatConstructorType)
+{
+ mUnit = eUnit_Float;
+ mValue.mFloat = aFloat;
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+}
+
+StyleAnimationValue::StyleAnimationValue(nscolor aColor, ColorConstructorType)
+{
+ mUnit = eUnit_Color;
+ mValue.mCSSValue = new nsCSSValue();
+ mValue.mCSSValue->SetColorValue(aColor);
+}
+
+StyleAnimationValue&
+StyleAnimationValue::operator=(const StyleAnimationValue& aOther)
+{
+ if (this == &aOther) {
+ return *this;
+ }
+
+ FreeValue();
+
+ mUnit = aOther.mUnit;
+ switch (mUnit) {
+ case eUnit_Null:
+ case eUnit_Normal:
+ case eUnit_Auto:
+ case eUnit_None:
+ case eUnit_CurrentColor:
+ break;
+ case eUnit_Enumerated:
+ case eUnit_Visibility:
+ case eUnit_Integer:
+ mValue.mInt = aOther.mValue.mInt;
+ break;
+ case eUnit_Coord:
+ mValue.mCoord = aOther.mValue.mCoord;
+ break;
+ case eUnit_Percent:
+ case eUnit_Float:
+ mValue.mFloat = aOther.mValue.mFloat;
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+ break;
+ case eUnit_Calc:
+ case eUnit_Color:
+ case eUnit_ObjectPosition:
+ case eUnit_URL:
+ case eUnit_DiscreteCSSValue:
+ MOZ_ASSERT(IsCSSValueUnit(mUnit),
+ "This clause is for handling nsCSSValue-backed units");
+ MOZ_ASSERT(aOther.mValue.mCSSValue, "values may not be null");
+ mValue.mCSSValue = new nsCSSValue(*aOther.mValue.mCSSValue);
+ break;
+ case eUnit_CSSValuePair:
+ MOZ_ASSERT(aOther.mValue.mCSSValuePair,
+ "value pairs may not be null");
+ mValue.mCSSValuePair = new nsCSSValuePair(*aOther.mValue.mCSSValuePair);
+ break;
+ case eUnit_CSSValueTriplet:
+ MOZ_ASSERT(aOther.mValue.mCSSValueTriplet,
+ "value triplets may not be null");
+ mValue.mCSSValueTriplet = new nsCSSValueTriplet(*aOther.mValue.mCSSValueTriplet);
+ break;
+ case eUnit_CSSRect:
+ MOZ_ASSERT(aOther.mValue.mCSSRect, "rects may not be null");
+ mValue.mCSSRect = new nsCSSRect(*aOther.mValue.mCSSRect);
+ break;
+ case eUnit_Dasharray:
+ case eUnit_Shadow:
+ case eUnit_Filter:
+ case eUnit_BackgroundPositionCoord:
+ MOZ_ASSERT(mUnit == eUnit_Shadow || mUnit == eUnit_Filter ||
+ aOther.mValue.mCSSValueList,
+ "value lists other than shadows and filters may not be null");
+ if (aOther.mValue.mCSSValueList) {
+ mValue.mCSSValueList = aOther.mValue.mCSSValueList->Clone();
+ } else {
+ mValue.mCSSValueList = nullptr;
+ }
+ break;
+ case eUnit_Shape:
+ MOZ_ASSERT(aOther.mValue.mCSSValueArray,
+ "value arrays may not be null");
+ mValue.mCSSValueArray = aOther.mValue.mCSSValueArray;
+ mValue.mCSSValueArray->AddRef();
+ break;
+ case eUnit_Transform:
+ mValue.mCSSValueSharedList = aOther.mValue.mCSSValueSharedList;
+ mValue.mCSSValueSharedList->AddRef();
+ break;
+ case eUnit_CSSValuePairList:
+ MOZ_ASSERT(aOther.mValue.mCSSValuePairList,
+ "value pair lists may not be null");
+ mValue.mCSSValuePairList = aOther.mValue.mCSSValuePairList->Clone();
+ break;
+ case eUnit_UnparsedString:
+ MOZ_ASSERT(aOther.mValue.mString, "expecting non-null string");
+ mValue.mString = aOther.mValue.mString;
+ mValue.mString->AddRef();
+ break;
+ case eUnit_ComplexColor:
+ MOZ_ASSERT(aOther.mValue.mComplexColor);
+ mValue.mComplexColor = aOther.mValue.mComplexColor;
+ mValue.mComplexColor->AddRef();
+ break;
+ }
+
+ return *this;
+}
+
+void
+StyleAnimationValue::SetNormalValue()
+{
+ FreeValue();
+ mUnit = eUnit_Normal;
+}
+
+void
+StyleAnimationValue::SetAutoValue()
+{
+ FreeValue();
+ mUnit = eUnit_Auto;
+}
+
+void
+StyleAnimationValue::SetNoneValue()
+{
+ FreeValue();
+ mUnit = eUnit_None;
+}
+
+void
+StyleAnimationValue::SetIntValue(int32_t aInt, Unit aUnit)
+{
+ NS_ASSERTION(IsIntUnit(aUnit), "unit must be of integer type");
+ FreeValue();
+ mUnit = aUnit;
+ mValue.mInt = aInt;
+}
+
+void
+StyleAnimationValue::SetCoordValue(nscoord aLength)
+{
+ FreeValue();
+ mUnit = eUnit_Coord;
+ mValue.mCoord = aLength;
+}
+
+void
+StyleAnimationValue::SetPercentValue(float aPercent)
+{
+ FreeValue();
+ mUnit = eUnit_Percent;
+ mValue.mFloat = aPercent;
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+}
+
+void
+StyleAnimationValue::SetFloatValue(float aFloat)
+{
+ FreeValue();
+ mUnit = eUnit_Float;
+ mValue.mFloat = aFloat;
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+}
+
+void
+StyleAnimationValue::SetColorValue(nscolor aColor)
+{
+ FreeValue();
+ mUnit = eUnit_Color;
+ mValue.mCSSValue = new nsCSSValue();
+ mValue.mCSSValue->SetColorValue(aColor);
+}
+
+void
+StyleAnimationValue::SetCurrentColorValue()
+{
+ FreeValue();
+ mUnit = eUnit_CurrentColor;
+}
+
+void
+StyleAnimationValue::SetComplexColorValue(const StyleComplexColor& aColor)
+{
+ if (aColor.IsCurrentColor()) {
+ SetCurrentColorValue();
+ } else if (aColor.IsNumericColor()) {
+ SetColorValue(aColor.mColor);
+ } else {
+ SetComplexColorValue(do_AddRef(new ComplexColorValue(aColor)));
+ }
+}
+
+void
+StyleAnimationValue::SetComplexColorValue(
+ already_AddRefed<ComplexColorValue> aValue)
+{
+ FreeValue();
+ mUnit = eUnit_ComplexColor;
+ mValue.mComplexColor = aValue.take();
+}
+
+void
+StyleAnimationValue::SetUnparsedStringValue(const nsString& aString)
+{
+ FreeValue();
+ mUnit = eUnit_UnparsedString;
+ mValue.mString = nsCSSValue::BufferFromString(aString).take();
+}
+
+void
+StyleAnimationValue::SetAndAdoptCSSValueValue(nsCSSValue *aValue,
+ Unit aUnit)
+{
+ FreeValue();
+ MOZ_ASSERT(IsCSSValueUnit(aUnit), "bad unit");
+ MOZ_ASSERT(aValue != nullptr, "values may not be null");
+ mUnit = aUnit;
+ mValue.mCSSValue = aValue; // take ownership
+}
+
+void
+StyleAnimationValue::SetAndAdoptCSSValuePairValue(nsCSSValuePair *aValuePair,
+ Unit aUnit)
+{
+ FreeValue();
+ MOZ_ASSERT(IsCSSValuePairUnit(aUnit), "bad unit");
+ MOZ_ASSERT(aValuePair != nullptr, "value pairs may not be null");
+ mUnit = aUnit;
+ mValue.mCSSValuePair = aValuePair; // take ownership
+}
+
+void
+StyleAnimationValue::SetAndAdoptCSSValueTripletValue(
+ nsCSSValueTriplet *aValueTriplet, Unit aUnit)
+{
+ FreeValue();
+ MOZ_ASSERT(IsCSSValueTripletUnit(aUnit), "bad unit");
+ MOZ_ASSERT(aValueTriplet != nullptr, "value pairs may not be null");
+ mUnit = aUnit;
+ mValue.mCSSValueTriplet = aValueTriplet; // take ownership
+}
+
+void
+StyleAnimationValue::SetAndAdoptCSSRectValue(nsCSSRect *aRect, Unit aUnit)
+{
+ FreeValue();
+ MOZ_ASSERT(IsCSSRectUnit(aUnit), "bad unit");
+ MOZ_ASSERT(aRect != nullptr, "value pairs may not be null");
+ mUnit = aUnit;
+ mValue.mCSSRect = aRect; // take ownership
+}
+
+void
+StyleAnimationValue::SetCSSValueArrayValue(nsCSSValue::Array* aValue,
+ Unit aUnit)
+{
+ FreeValue();
+ MOZ_ASSERT(IsCSSValueArrayUnit(aUnit), "bad unit");
+ MOZ_ASSERT(aValue != nullptr,
+ "not currently expecting any arrays to be null");
+ mUnit = aUnit;
+ mValue.mCSSValueArray = aValue;
+ mValue.mCSSValueArray->AddRef();
+}
+
+void
+StyleAnimationValue::SetAndAdoptCSSValueListValue(nsCSSValueList *aValueList,
+ Unit aUnit)
+{
+ FreeValue();
+ MOZ_ASSERT(IsCSSValueListUnit(aUnit), "bad unit");
+ MOZ_ASSERT(aUnit == eUnit_Shadow || aUnit == eUnit_Filter ||
+ aValueList != nullptr,
+ "value lists other than shadows and filters may not be null");
+ mUnit = aUnit;
+ mValue.mCSSValueList = aValueList; // take ownership
+}
+
+void
+StyleAnimationValue::SetTransformValue(nsCSSValueSharedList* aList)
+{
+ FreeValue();
+ mUnit = eUnit_Transform;
+ mValue.mCSSValueSharedList = aList;
+ mValue.mCSSValueSharedList->AddRef();
+}
+
+void
+StyleAnimationValue::SetAndAdoptCSSValuePairListValue(
+ nsCSSValuePairList *aValuePairList)
+{
+ FreeValue();
+ MOZ_ASSERT(aValuePairList, "may not be null");
+ mUnit = eUnit_CSSValuePairList;
+ mValue.mCSSValuePairList = aValuePairList; // take ownership
+}
+
+void
+StyleAnimationValue::FreeValue()
+{
+ if (IsCSSValueUnit(mUnit)) {
+ delete mValue.mCSSValue;
+ } else if (IsCSSValueListUnit(mUnit)) {
+ delete mValue.mCSSValueList;
+ } else if (IsCSSValueSharedListValue(mUnit)) {
+ mValue.mCSSValueSharedList->Release();
+ } else if (IsCSSValuePairUnit(mUnit)) {
+ delete mValue.mCSSValuePair;
+ } else if (IsCSSValueTripletUnit(mUnit)) {
+ delete mValue.mCSSValueTriplet;
+ } else if (IsCSSRectUnit(mUnit)) {
+ delete mValue.mCSSRect;
+ } else if (IsCSSValuePairListUnit(mUnit)) {
+ delete mValue.mCSSValuePairList;
+ } else if (IsCSSValueArrayUnit(mUnit)) {
+ mValue.mCSSValueArray->Release();
+ } else if (IsStringUnit(mUnit)) {
+ MOZ_ASSERT(mValue.mString, "expecting non-null string");
+ mValue.mString->Release();
+ } else if (mUnit == eUnit_ComplexColor) {
+ mValue.mComplexColor->Release();
+ }
+}
+
+bool
+StyleAnimationValue::operator==(const StyleAnimationValue& aOther) const
+{
+ if (mUnit != aOther.mUnit) {
+ return false;
+ }
+
+ switch (mUnit) {
+ case eUnit_Null:
+ case eUnit_Normal:
+ case eUnit_Auto:
+ case eUnit_None:
+ case eUnit_CurrentColor:
+ return true;
+ case eUnit_Enumerated:
+ case eUnit_Visibility:
+ case eUnit_Integer:
+ return mValue.mInt == aOther.mValue.mInt;
+ case eUnit_Coord:
+ return mValue.mCoord == aOther.mValue.mCoord;
+ case eUnit_Percent:
+ case eUnit_Float:
+ return mValue.mFloat == aOther.mValue.mFloat;
+ case eUnit_Calc:
+ case eUnit_Color:
+ case eUnit_ObjectPosition:
+ case eUnit_URL:
+ case eUnit_DiscreteCSSValue:
+ MOZ_ASSERT(IsCSSValueUnit(mUnit),
+ "This clause is for handling nsCSSValue-backed units");
+ return *mValue.mCSSValue == *aOther.mValue.mCSSValue;
+ case eUnit_CSSValuePair:
+ return *mValue.mCSSValuePair == *aOther.mValue.mCSSValuePair;
+ case eUnit_CSSValueTriplet:
+ return *mValue.mCSSValueTriplet == *aOther.mValue.mCSSValueTriplet;
+ case eUnit_CSSRect:
+ return *mValue.mCSSRect == *aOther.mValue.mCSSRect;
+ case eUnit_Dasharray:
+ case eUnit_Shadow:
+ case eUnit_Filter:
+ case eUnit_BackgroundPositionCoord:
+ return nsCSSValueList::Equal(mValue.mCSSValueList,
+ aOther.mValue.mCSSValueList);
+ case eUnit_Shape:
+ return *mValue.mCSSValueArray == *aOther.mValue.mCSSValueArray;
+ case eUnit_Transform:
+ return *mValue.mCSSValueSharedList == *aOther.mValue.mCSSValueSharedList;
+ case eUnit_CSSValuePairList:
+ return nsCSSValuePairList::Equal(mValue.mCSSValuePairList,
+ aOther.mValue.mCSSValuePairList);
+ case eUnit_UnparsedString:
+ return (NS_strcmp(GetStringBufferValue(),
+ aOther.GetStringBufferValue()) == 0);
+ case eUnit_ComplexColor:
+ return *mValue.mComplexColor == *aOther.mValue.mComplexColor;
+ }
+
+ NS_NOTREACHED("incomplete case");
+ return false;
+}
diff --git a/layout/style/StyleAnimationValue.h b/layout/style/StyleAnimationValue.h
new file mode 100644
index 000000000..e318cecb6
--- /dev/null
+++ b/layout/style/StyleAnimationValue.h
@@ -0,0 +1,602 @@
+/* -*- 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/. */
+
+/* Utilities for animation of computed style values */
+
+#ifndef mozilla_StyleAnimationValue_h_
+#define mozilla_StyleAnimationValue_h_
+
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/UniquePtr.h"
+#include "nsStringFwd.h"
+#include "nsStringBuffer.h"
+#include "nsCoord.h"
+#include "nsColor.h"
+#include "nsCSSProps.h"
+#include "nsCSSValue.h"
+#include "nsStyleCoord.h"
+
+class nsIFrame;
+class nsStyleContext;
+class gfx3DMatrix;
+struct RawServoDeclarationBlock;
+
+namespace mozilla {
+
+namespace css {
+class StyleRule;
+} // namespace css
+
+namespace dom {
+class Element;
+} // namespace dom
+
+enum class CSSPseudoElementType : uint8_t;
+struct PropertyStyleAnimationValuePair;
+
+/**
+ * Utility class to handle animated style values
+ */
+class StyleAnimationValue {
+public:
+ // Mathematical methods
+ // --------------------
+ /**
+ * Adds |aCount| copies of |aValueToAdd| to |aDest|. The result of this
+ * addition is stored in aDest.
+ *
+ * Note that if |aCount| is 0, then |aDest| will be unchanged. Also, if
+ * this method fails, then |aDest| will be unchanged.
+ *
+ * @param aDest The value to add to.
+ * @param aValueToAdd The value to add.
+ * @param aCount The number of times to add aValueToAdd.
+ * @return true on success, false on failure.
+ */
+ static MOZ_MUST_USE bool
+ Add(nsCSSPropertyID aProperty, StyleAnimationValue& aDest,
+ const StyleAnimationValue& aValueToAdd, uint32_t aCount) {
+ return AddWeighted(aProperty, 1.0, aDest, aCount, aValueToAdd, aDest);
+ }
+
+ /**
+ * Calculates a measure of 'distance' between two colors.
+ *
+ * @param aStartColor The start of the interval for which the distance
+ * should be calculated.
+ * @param aEndColor The end of the interval for which the distance
+ * should be calculated.
+ * @return the result of the calculation.
+ */
+ static double ComputeColorDistance(const css::RGBAColorData& aStartColor,
+ const css::RGBAColorData& aEndColor);
+
+ /**
+ * Calculates a measure of 'distance' between two values.
+ *
+ * This measure of Distance is guaranteed to be proportional to
+ * portions passed to Interpolate, Add, or AddWeighted. However, for
+ * some types of StyleAnimationValue it may not produce sensible results
+ * for paced animation.
+ *
+ * If this method succeeds, the returned distance value is guaranteed to be
+ * non-negative.
+ *
+ * @param aStartValue The start of the interval for which the distance
+ * should be calculated.
+ * @param aEndValue The end of the interval for which the distance
+ * should be calculated.
+ * @param aStyleContext The style context to use for processing the
+ * translate part of transforms.
+ * @param aDistance The result of the calculation.
+ * @return true on success, false on failure.
+ */
+ static MOZ_MUST_USE bool
+ ComputeDistance(nsCSSPropertyID aProperty,
+ const StyleAnimationValue& aStartValue,
+ const StyleAnimationValue& aEndValue,
+ nsStyleContext* aStyleContext,
+ double& aDistance);
+
+ /**
+ * Calculates an interpolated value that is the specified |aPortion| between
+ * the two given values.
+ *
+ * This really just does the following calculation:
+ * aResultValue = (1.0 - aPortion) * aStartValue + aPortion * aEndValue
+ *
+ * @param aStartValue The value defining the start of the interval of
+ * interpolation.
+ * @param aEndValue The value defining the end of the interval of
+ * interpolation.
+ * @param aPortion A number in the range [0.0, 1.0] defining the
+ * distance of the interpolated value in the interval.
+ * @param [out] aResultValue The resulting interpolated value.
+ * @return true on success, false on failure.
+ */
+ static MOZ_MUST_USE bool
+ Interpolate(nsCSSPropertyID aProperty,
+ const StyleAnimationValue& aStartValue,
+ const StyleAnimationValue& aEndValue,
+ double aPortion,
+ StyleAnimationValue& aResultValue) {
+ return AddWeighted(aProperty, 1.0 - aPortion, aStartValue,
+ aPortion, aEndValue, aResultValue);
+ }
+
+ /**
+ * Does the calculation:
+ * aResultValue = aCoeff1 * aValue1 + aCoeff2 * aValue2
+ *
+ * @param [out] aResultValue The resulting interpolated value. May be
+ * the same as aValue1 or aValue2.
+ * @return true on success, false on failure.
+ *
+ * NOTE: Current callers always pass aCoeff1 and aCoeff2 >= 0. They
+ * are currently permitted to be negative; however, if, as we add
+ * support more value types types, we find that this causes
+ * difficulty, we might change this to restrict them to being
+ * positive.
+ */
+ static MOZ_MUST_USE bool
+ AddWeighted(nsCSSPropertyID aProperty,
+ double aCoeff1, const StyleAnimationValue& aValue1,
+ double aCoeff2, const StyleAnimationValue& aValue2,
+ StyleAnimationValue& aResultValue);
+
+ /**
+ * Accumulates |aValueToAccumulate| onto |aDest| |aCount| times.
+ * The result is stored in |aDest| on success.
+ *
+ * @param aDest The base value to be accumulated.
+ * @param aValueToAccumulate The value to accumulate.
+ * @param aCount The number of times to accumulate
+ * aValueToAccumulate.
+ * @return true on success, false on failure.
+ *
+ * NOTE: This function will work as a wrapper of StyleAnimationValue::Add()
+ * if |aProperty| isn't color or shadow or filter. For these properties,
+ * this function may return a color value that at least one of its components
+ * has a value which is outside the range [0, 1] so that we can calculate
+ * plausible values as interpolation with the return value.
+ */
+ static MOZ_MUST_USE bool
+ Accumulate(nsCSSPropertyID aProperty, StyleAnimationValue& aDest,
+ const StyleAnimationValue& aValueToAccumulate,
+ uint64_t aCount);
+
+ // Type-conversion methods
+ // -----------------------
+ /**
+ * Creates a computed value for the given specified value
+ * (property ID + string). A style context is needed in case the
+ * specified value depends on inherited style or on the values of other
+ * properties.
+ *
+ * @param aProperty The property whose value we're computing.
+ * @param aTargetElement The content node to which our computed value is
+ * applicable. For pseudo-elements, this is the parent
+ * element to which the pseudo is attached, not the
+ * generated content node.
+ * @param aStyleContext The style context used to compute values from the
+ * specified value. For pseudo-elements, this should
+ * be the style context corresponding to the pseudo
+ * element.
+ * @param aSpecifiedValue The specified value, from which we'll build our
+ * computed value.
+ * @param aUseSVGMode A flag to indicate whether we should parse
+ * |aSpecifiedValue| in SVG mode.
+ * @param [out] aComputedValue The resulting computed value.
+ * @param [out] aIsContextSensitive
+ * Set to true if |aSpecifiedValue| may produce
+ * a different |aComputedValue| depending on other CSS
+ * properties on |aTargetElement| or its ancestors.
+ * false otherwise.
+ * Note that the operation of this method is
+ * significantly faster when |aIsContextSensitive| is
+ * nullptr.
+ * @return true on success, false on failure.
+ */
+ static MOZ_MUST_USE bool
+ ComputeValue(nsCSSPropertyID aProperty,
+ mozilla::dom::Element* aTargetElement,
+ nsStyleContext* aStyleContext,
+ const nsAString& aSpecifiedValue,
+ bool aUseSVGMode,
+ StyleAnimationValue& aComputedValue,
+ bool* aIsContextSensitive = nullptr);
+
+ /**
+ * Like ComputeValue, but returns an array of StyleAnimationValues.
+ *
+ * On success, when aProperty is a longhand, aResult will have a single
+ * value in it. When aProperty is a shorthand, aResult will be filled with
+ * values for all of aProperty's longhand components. aEnabledState
+ * is used to filter the longhand components that will be appended
+ * to aResult. On failure, aResult might still have partial results
+ * in it.
+ */
+ static MOZ_MUST_USE bool
+ ComputeValues(nsCSSPropertyID aProperty,
+ mozilla::CSSEnabledState aEnabledState,
+ mozilla::dom::Element* aTargetElement,
+ nsStyleContext* aStyleContext,
+ const nsAString& aSpecifiedValue,
+ bool aUseSVGMode,
+ nsTArray<PropertyStyleAnimationValuePair>& aResult);
+
+ /**
+ * A variant on ComputeValues that takes an nsCSSValue as the specified
+ * value. Only longhand properties are supported.
+ */
+ static MOZ_MUST_USE bool
+ ComputeValues(nsCSSPropertyID aProperty,
+ mozilla::CSSEnabledState aEnabledState,
+ mozilla::dom::Element* aTargetElement,
+ nsStyleContext* aStyleContext,
+ const nsCSSValue& aSpecifiedValue,
+ bool aUseSVGMode,
+ nsTArray<PropertyStyleAnimationValuePair>& aResult);
+
+ /**
+ * A variant of ComputeValues that takes a RawServoDeclarationBlock
+ * as the specified value.
+ */
+ static MOZ_MUST_USE bool
+ ComputeValues(nsCSSPropertyID aProperty,
+ mozilla::CSSEnabledState aEnabledState,
+ nsStyleContext* aStyleContext,
+ const RawServoDeclarationBlock& aDeclarations,
+ nsTArray<PropertyStyleAnimationValuePair>& aValues);
+
+ /**
+ * Creates a specified value for the given computed value.
+ *
+ * The first two overloads fill in an nsCSSValue object; the third
+ * produces a string. For the overload that takes a const
+ * StyleAnimationValue& reference, the nsCSSValue result may depend on
+ * objects owned by the |aComputedValue| object, so users of that variant
+ * must keep |aComputedValue| alive longer than |aSpecifiedValue|.
+ * The overload that takes an rvalue StyleAnimationValue reference
+ * transfers ownership for some resources such that the |aComputedValue|
+ * does not depend on the lifetime of |aSpecifiedValue|.
+ *
+ * @param aProperty The property whose value we're uncomputing.
+ * @param aComputedValue The computed value to be converted.
+ * @param [out] aSpecifiedValue The resulting specified value.
+ * @return true on success, false on failure.
+ *
+ * These functions are not MOZ_MUST_USE because failing to check the return
+ * value is common and reasonable.
+ */
+ static MOZ_MUST_USE bool
+ UncomputeValue(nsCSSPropertyID aProperty,
+ const StyleAnimationValue& aComputedValue,
+ nsCSSValue& aSpecifiedValue);
+ static MOZ_MUST_USE bool
+ UncomputeValue(nsCSSPropertyID aProperty,
+ StyleAnimationValue&& aComputedValue,
+ nsCSSValue& aSpecifiedValue);
+ static MOZ_MUST_USE bool
+ UncomputeValue(nsCSSPropertyID aProperty,
+ const StyleAnimationValue& aComputedValue,
+ nsAString& aSpecifiedValue);
+
+ /**
+ * Gets the computed value for the given property from the given style
+ * context.
+ *
+ * Obtaining the computed value allows us to animate properties when the
+ * content author has specified a value like "inherit" or "initial" or some
+ * other keyword that isn't directly interpolatable, but which *computes* to
+ * something interpolatable.
+ *
+ * @param aProperty The property whose value we're looking up.
+ * @param aStyleContext The style context to check for the computed value.
+ * @param [out] aComputedValue The resulting computed value.
+ * @return true on success, false on failure.
+ */
+ static MOZ_MUST_USE bool ExtractComputedValue(
+ nsCSSPropertyID aProperty,
+ nsStyleContext* aStyleContext,
+ StyleAnimationValue& aComputedValue);
+
+ /**
+ * Interpolates between 2 matrices by decomposing them.
+ *
+ * @param aMatrix1 First matrix, using CSS pixel units.
+ * @param aMatrix2 Second matrix, using CSS pixel units.
+ * @param aProgress Interpolation value in the range [0.0, 1.0]
+ */
+ static gfx::Matrix4x4 InterpolateTransformMatrix(const gfx::Matrix4x4 &aMatrix1,
+ const gfx::Matrix4x4 &aMatrix2,
+ double aProgress);
+
+ static already_AddRefed<nsCSSValue::Array>
+ AppendTransformFunction(nsCSSKeyword aTransformFunction,
+ nsCSSValueList**& aListTail);
+
+ /**
+ * The types and values for the values that we extract and animate.
+ */
+ enum Unit {
+ eUnit_Null, // not initialized
+ eUnit_Normal,
+ eUnit_Auto,
+ eUnit_None,
+ eUnit_Enumerated,
+ eUnit_Visibility, // special case for transitions (which converts
+ // Enumerated to Visibility as needed)
+ eUnit_Integer,
+ eUnit_Coord,
+ eUnit_Percent,
+ eUnit_Float,
+ eUnit_Color, // nsCSSValue* (never null), always with an nscolor or
+ // an nsCSSValueFloatColor
+ eUnit_CurrentColor,
+ eUnit_ComplexColor, // ComplexColorValue* (never null)
+ eUnit_Calc, // nsCSSValue* (never null), always with a single
+ // calc() expression that's either length or length+percent
+ eUnit_ObjectPosition, // nsCSSValue* (never null), always with a
+ // 4-entry nsCSSValue::Array
+ eUnit_URL, // nsCSSValue* (never null), always with a css::URLValue
+ eUnit_DiscreteCSSValue, // nsCSSValue* (never null)
+ eUnit_CSSValuePair, // nsCSSValuePair* (never null)
+ eUnit_CSSValueTriplet, // nsCSSValueTriplet* (never null)
+ eUnit_CSSRect, // nsCSSRect* (never null)
+ eUnit_Dasharray, // nsCSSValueList* (never null)
+ eUnit_Shadow, // nsCSSValueList* (may be null)
+ eUnit_Shape, // nsCSSValue::Array* (never null)
+ eUnit_Filter, // nsCSSValueList* (may be null)
+ eUnit_Transform, // nsCSSValueList* (never null)
+ eUnit_BackgroundPositionCoord, // nsCSSValueList* (never null)
+ eUnit_CSSValuePairList, // nsCSSValuePairList* (never null)
+ eUnit_UnparsedString // nsStringBuffer* (never null)
+ };
+
+private:
+ Unit mUnit;
+ union {
+ int32_t mInt;
+ nscoord mCoord;
+ float mFloat;
+ nsCSSValue* mCSSValue;
+ nsCSSValuePair* mCSSValuePair;
+ nsCSSValueTriplet* mCSSValueTriplet;
+ nsCSSRect* mCSSRect;
+ nsCSSValue::Array* mCSSValueArray;
+ nsCSSValueList* mCSSValueList;
+ nsCSSValueSharedList* mCSSValueSharedList;
+ nsCSSValuePairList* mCSSValuePairList;
+ nsStringBuffer* mString;
+ css::ComplexColorValue* mComplexColor;
+ } mValue;
+
+public:
+ Unit GetUnit() const {
+ NS_ASSERTION(mUnit != eUnit_Null, "uninitialized");
+ return mUnit;
+ }
+
+ // Accessor to let us verify assumptions about presence of null unit,
+ // without tripping the assertion in GetUnit().
+ bool IsNull() const {
+ return mUnit == eUnit_Null;
+ }
+
+ int32_t GetIntValue() const {
+ NS_ASSERTION(IsIntUnit(mUnit), "unit mismatch");
+ return mValue.mInt;
+ }
+ nscoord GetCoordValue() const {
+ NS_ASSERTION(mUnit == eUnit_Coord, "unit mismatch");
+ return mValue.mCoord;
+ }
+ float GetPercentValue() const {
+ NS_ASSERTION(mUnit == eUnit_Percent, "unit mismatch");
+ return mValue.mFloat;
+ }
+ float GetFloatValue() const {
+ NS_ASSERTION(mUnit == eUnit_Float, "unit mismatch");
+ return mValue.mFloat;
+ }
+ nsCSSValue* GetCSSValueValue() const {
+ NS_ASSERTION(IsCSSValueUnit(mUnit), "unit mismatch");
+ return mValue.mCSSValue;
+ }
+ nsCSSValuePair* GetCSSValuePairValue() const {
+ NS_ASSERTION(IsCSSValuePairUnit(mUnit), "unit mismatch");
+ return mValue.mCSSValuePair;
+ }
+ nsCSSValueTriplet* GetCSSValueTripletValue() const {
+ NS_ASSERTION(IsCSSValueTripletUnit(mUnit), "unit mismatch");
+ return mValue.mCSSValueTriplet;
+ }
+ nsCSSRect* GetCSSRectValue() const {
+ NS_ASSERTION(IsCSSRectUnit(mUnit), "unit mismatch");
+ return mValue.mCSSRect;
+ }
+ nsCSSValue::Array* GetCSSValueArrayValue() const {
+ NS_ASSERTION(IsCSSValueArrayUnit(mUnit), "unit mismatch");
+ return mValue.mCSSValueArray;
+ }
+ nsCSSValueList* GetCSSValueListValue() const {
+ NS_ASSERTION(IsCSSValueListUnit(mUnit), "unit mismatch");
+ return mValue.mCSSValueList;
+ }
+ nsCSSValueSharedList* GetCSSValueSharedListValue() const {
+ NS_ASSERTION(IsCSSValueSharedListValue(mUnit), "unit mismatch");
+ return mValue.mCSSValueSharedList;
+ }
+ nsCSSValuePairList* GetCSSValuePairListValue() const {
+ NS_ASSERTION(IsCSSValuePairListUnit(mUnit), "unit mismatch");
+ return mValue.mCSSValuePairList;
+ }
+ const char16_t* GetStringBufferValue() const {
+ NS_ASSERTION(IsStringUnit(mUnit), "unit mismatch");
+ return GetBufferValue(mValue.mString);
+ }
+
+ void GetStringValue(nsAString& aBuffer) const {
+ NS_ASSERTION(IsStringUnit(mUnit), "unit mismatch");
+ aBuffer.Truncate();
+ uint32_t len = NS_strlen(GetBufferValue(mValue.mString));
+ mValue.mString->ToString(len, aBuffer);
+ }
+
+ /// @return the scale for this value, calculated with reference to @aForFrame.
+ gfxSize GetScaleValue(const nsIFrame* aForFrame) const;
+
+ const css::ComplexColorData& GetComplexColorData() const {
+ MOZ_ASSERT(mUnit == eUnit_ComplexColor, "unit mismatch");
+ return *mValue.mComplexColor;
+ }
+ StyleComplexColor GetStyleComplexColorValue() const {
+ return GetComplexColorData().ToComplexColor();
+ }
+
+ UniquePtr<nsCSSValueList> TakeCSSValueListValue() {
+ nsCSSValueList* list = GetCSSValueListValue();
+ mValue.mCSSValueList = nullptr;
+ mUnit = eUnit_Null;
+ return UniquePtr<nsCSSValueList>(list);
+ }
+ UniquePtr<nsCSSValuePairList> TakeCSSValuePairListValue() {
+ nsCSSValuePairList* list = GetCSSValuePairListValue();
+ mValue.mCSSValuePairList = nullptr;
+ mUnit = eUnit_Null;
+ return UniquePtr<nsCSSValuePairList>(list);
+ }
+
+ explicit StyleAnimationValue(Unit aUnit = eUnit_Null) : mUnit(aUnit) {
+ NS_ASSERTION(aUnit == eUnit_Null || aUnit == eUnit_Normal ||
+ aUnit == eUnit_Auto || aUnit == eUnit_None,
+ "must be valueless unit");
+ }
+ StyleAnimationValue(const StyleAnimationValue& aOther)
+ : mUnit(eUnit_Null) { *this = aOther; }
+ StyleAnimationValue(StyleAnimationValue&& aOther)
+ : mUnit(aOther.mUnit)
+ , mValue(aOther.mValue)
+ {
+ aOther.mUnit = eUnit_Null;
+ }
+ enum IntegerConstructorType { IntegerConstructor };
+ StyleAnimationValue(int32_t aInt, Unit aUnit, IntegerConstructorType);
+ enum CoordConstructorType { CoordConstructor };
+ StyleAnimationValue(nscoord aLength, CoordConstructorType);
+ enum PercentConstructorType { PercentConstructor };
+ StyleAnimationValue(float aPercent, PercentConstructorType);
+ enum FloatConstructorType { FloatConstructor };
+ StyleAnimationValue(float aFloat, FloatConstructorType);
+ enum ColorConstructorType { ColorConstructor };
+ StyleAnimationValue(nscolor aColor, ColorConstructorType);
+
+ ~StyleAnimationValue() { FreeValue(); }
+
+ void SetNormalValue();
+ void SetAutoValue();
+ void SetNoneValue();
+ void SetIntValue(int32_t aInt, Unit aUnit);
+ template<typename T,
+ typename = typename std::enable_if<std::is_enum<T>::value>::type>
+ void SetIntValue(T aInt, Unit aUnit)
+ {
+ static_assert(mozilla::EnumTypeFitsWithin<T, int32_t>::value,
+ "aValue must be an enum that fits within mValue.mInt");
+ SetIntValue(static_cast<int32_t>(aInt), aUnit);
+ }
+ void SetCoordValue(nscoord aCoord);
+ void SetPercentValue(float aPercent);
+ void SetFloatValue(float aFloat);
+ void SetColorValue(nscolor aColor);
+ void SetCurrentColorValue();
+ void SetComplexColorValue(const StyleComplexColor& aColor);
+ void SetComplexColorValue(already_AddRefed<css::ComplexColorValue> aValue);
+ void SetUnparsedStringValue(const nsString& aString);
+ void SetCSSValueArrayValue(nsCSSValue::Array* aValue, Unit aUnit);
+
+ // These setters take ownership of |aValue|, and are therefore named
+ // "SetAndAdopt*".
+ void SetAndAdoptCSSValueValue(nsCSSValue *aValue, Unit aUnit);
+ void SetAndAdoptCSSValuePairValue(nsCSSValuePair *aValue, Unit aUnit);
+ void SetAndAdoptCSSValueTripletValue(nsCSSValueTriplet *aValue, Unit aUnit);
+ void SetAndAdoptCSSRectValue(nsCSSRect *aValue, Unit aUnit);
+ void SetAndAdoptCSSValueListValue(nsCSSValueList *aValue, Unit aUnit);
+ void SetAndAdoptCSSValuePairListValue(nsCSSValuePairList *aValue);
+
+ void SetTransformValue(nsCSSValueSharedList* aList);
+
+ StyleAnimationValue& operator=(const StyleAnimationValue& aOther);
+ StyleAnimationValue& operator=(StyleAnimationValue&& aOther)
+ {
+ MOZ_ASSERT(this != &aOther, "Do not move itself");
+ if (this != &aOther) {
+ FreeValue();
+ mUnit = aOther.mUnit;
+ mValue = aOther.mValue;
+ aOther.mUnit = eUnit_Null;
+ }
+ return *this;
+ }
+
+ bool operator==(const StyleAnimationValue& aOther) const;
+ bool operator!=(const StyleAnimationValue& aOther) const
+ { return !(*this == aOther); }
+
+private:
+ void FreeValue();
+
+ static const char16_t* GetBufferValue(nsStringBuffer* aBuffer) {
+ return static_cast<char16_t*>(aBuffer->Data());
+ }
+
+ static bool IsIntUnit(Unit aUnit) {
+ return aUnit == eUnit_Enumerated || aUnit == eUnit_Visibility ||
+ aUnit == eUnit_Integer;
+ }
+ static bool IsCSSValueUnit(Unit aUnit) {
+ return aUnit == eUnit_Color ||
+ aUnit == eUnit_Calc ||
+ aUnit == eUnit_ObjectPosition ||
+ aUnit == eUnit_URL ||
+ aUnit == eUnit_DiscreteCSSValue;
+ }
+ static bool IsCSSValuePairUnit(Unit aUnit) {
+ return aUnit == eUnit_CSSValuePair;
+ }
+ static bool IsCSSValueTripletUnit(Unit aUnit) {
+ return aUnit == eUnit_CSSValueTriplet;
+ }
+ static bool IsCSSRectUnit(Unit aUnit) {
+ return aUnit == eUnit_CSSRect;
+ }
+ static bool IsCSSValueArrayUnit(Unit aUnit) {
+ return aUnit == eUnit_Shape;
+ }
+ static bool IsCSSValueListUnit(Unit aUnit) {
+ return aUnit == eUnit_Dasharray || aUnit == eUnit_Filter ||
+ aUnit == eUnit_Shadow ||
+ aUnit == eUnit_BackgroundPositionCoord;
+ }
+ static bool IsCSSValueSharedListValue(Unit aUnit) {
+ return aUnit == eUnit_Transform;
+ }
+ static bool IsCSSValuePairListUnit(Unit aUnit) {
+ return aUnit == eUnit_CSSValuePairList;
+ }
+ static bool IsStringUnit(Unit aUnit) {
+ return aUnit == eUnit_UnparsedString;
+ }
+};
+
+struct PropertyStyleAnimationValuePair
+{
+ nsCSSPropertyID mProperty;
+ StyleAnimationValue mValue;
+};
+} // namespace mozilla
+
+#endif
diff --git a/layout/style/StyleBackendType.h b/layout/style/StyleBackendType.h
new file mode 100644
index 000000000..f02b86213
--- /dev/null
+++ b/layout/style/StyleBackendType.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_StyleBackendType_h
+#define mozilla_StyleBackendType_h
+
+namespace mozilla {
+
+/**
+ * Enumeration that represents one of the two supported style system backends.
+ */
+enum class StyleBackendType : uint8_t
+{
+ Gecko = 1,
+ Servo
+};
+
+} // namespace mozilla
+
+#endif // mozilla_StyleBackendType_h
diff --git a/layout/style/StyleComplexColor.h b/layout/style/StyleComplexColor.h
new file mode 100644
index 000000000..4acf90a56
--- /dev/null
+++ b/layout/style/StyleComplexColor.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/. */
+
+/* represent a color combines a numeric color and currentcolor */
+
+#ifndef mozilla_StyleComplexColor_h_
+#define mozilla_StyleComplexColor_h_
+
+#include "nsColor.h"
+
+namespace mozilla {
+
+/**
+ * This struct represents a combined color from a numeric color and
+ * the current foreground color (currentcolor keyword).
+ * Conceptually, the formula is "color * (1 - p) + currentcolor * p"
+ * where p is mForegroundRatio. See mozilla::LinearBlendColors for
+ * the actual algorithm.
+ */
+struct StyleComplexColor
+{
+ nscolor mColor;
+ uint8_t mForegroundRatio;
+
+ static StyleComplexColor FromColor(nscolor aColor) { return {aColor, 0}; }
+ static StyleComplexColor CurrentColor() { return {NS_RGBA(0, 0, 0, 0), 255}; }
+
+ bool IsNumericColor() const { return mForegroundRatio == 0; }
+ bool IsCurrentColor() const { return mForegroundRatio == 255; }
+
+ bool operator==(const StyleComplexColor& aOther) const {
+ return mForegroundRatio == aOther.mForegroundRatio &&
+ (IsCurrentColor() || mColor == aOther.mColor);
+ }
+ bool operator!=(const StyleComplexColor& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+}
+
+#endif // mozilla_StyleComplexColor_h_
diff --git a/layout/style/StyleContextSource.h b/layout/style/StyleContextSource.h
new file mode 100644
index 000000000..7c6a00730
--- /dev/null
+++ b/layout/style/StyleContextSource.h
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StyleContextSource_h
+#define mozilla_StyleContextSource_h
+
+#include "mozilla/ServoBindingTypes.h"
+#include "nsRuleNode.h"
+
+namespace mozilla {
+
+// Tagged union between Gecko Rule Nodes and Servo Computed Values.
+//
+// The rule node is the node in the lexicographic tree of rule nodes
+// (the "rule tree") that indicates which style rules are used to
+// compute the style data, and in what cascading order. The least
+// specific rule matched is the one whose rule node is a child of the
+// root of the rule tree, and the most specific rule matched is the
+// |mRule| member of the rule node.
+//
+// In the Servo case, we hold an atomically-refcounted handle to a
+// Servo ComputedValues struct, which is more or less the Servo equivalent
+// of an nsStyleContext.
+
+// Underlying pointer without any strong ownership semantics.
+struct NonOwningStyleContextSource
+{
+ MOZ_IMPLICIT NonOwningStyleContextSource(nsRuleNode* aRuleNode)
+ : mBits(reinterpret_cast<uintptr_t>(aRuleNode)) {}
+ explicit NonOwningStyleContextSource(const ServoComputedValues* aComputedValues)
+ : mBits(reinterpret_cast<uintptr_t>(aComputedValues) | 1) {}
+
+ bool operator==(const NonOwningStyleContextSource& aOther) const {
+ MOZ_ASSERT(IsServoComputedValues() == aOther.IsServoComputedValues(),
+ "Comparing Servo to Gecko - probably a bug");
+ return mBits == aOther.mBits;
+ }
+ bool operator!=(const NonOwningStyleContextSource& aOther) const {
+ return !(*this == aOther);
+ }
+
+ // We intentionally avoid exposing IsGeckoRuleNode() here, because that would
+ // encourage callers to do:
+ //
+ // if (source.IsGeckoRuleNode()) {
+ // // Code that we would run unconditionally if it weren't for Servo.
+ // }
+ //
+ // We want these branches to compile away when MOZ_STYLO is disabled, but that
+ // won't happen if there's an implicit null-check.
+ bool IsNull() const { return !mBits; }
+ bool IsGeckoRuleNodeOrNull() const { return !IsServoComputedValues(); }
+ bool IsServoComputedValues() const {
+#ifdef MOZ_STYLO
+ return mBits & 1;
+#else
+ return false;
+#endif
+ }
+
+ nsRuleNode* AsGeckoRuleNode() const {
+ MOZ_ASSERT(IsGeckoRuleNodeOrNull() && !IsNull());
+ return reinterpret_cast<nsRuleNode*>(mBits);
+ }
+
+ const ServoComputedValues* AsServoComputedValues() const {
+ MOZ_ASSERT(IsServoComputedValues());
+ return reinterpret_cast<ServoComputedValues*>(mBits & ~1);
+ }
+
+ bool MatchesNoRules() const {
+ if (IsGeckoRuleNodeOrNull()) {
+ return AsGeckoRuleNode()->IsRoot();
+ }
+
+ // Just assume a Servo-backed StyleContextSource always matches some rules.
+ //
+ // MatchesNoRules is used to ensure style contexts that match no rules
+ // go into a separate mEmptyChild list on their parent. This is only used
+ // as an optimization so that calling FindChildWithRules for style context
+ // sharing is faster for text nodes (which match no rules, and are common).
+ // Since Servo will handle sharing for us, there's no need to split children
+ // into two lists.
+ return false;
+ }
+
+private:
+ uintptr_t mBits;
+};
+
+// Higher-level struct that owns a strong reference to the source. The source
+// is never null.
+struct OwningStyleContextSource
+{
+ explicit OwningStyleContextSource(already_AddRefed<nsRuleNode> aRuleNode)
+ : mRaw(aRuleNode.take())
+ {
+ MOZ_COUNT_CTOR(OwningStyleContextSource);
+ MOZ_ASSERT(!mRaw.IsNull());
+ };
+
+ explicit OwningStyleContextSource(already_AddRefed<ServoComputedValues> aComputedValues)
+ : mRaw(aComputedValues.take())
+ {
+ MOZ_COUNT_CTOR(OwningStyleContextSource);
+ MOZ_ASSERT(!mRaw.IsNull());
+ }
+
+ OwningStyleContextSource(OwningStyleContextSource&& aOther)
+ : mRaw(aOther.mRaw)
+ {
+ MOZ_COUNT_CTOR(OwningStyleContextSource);
+ aOther.mRaw = nullptr;
+ }
+
+ OwningStyleContextSource& operator=(OwningStyleContextSource&) = delete;
+ OwningStyleContextSource(OwningStyleContextSource&) = delete;
+
+ ~OwningStyleContextSource() {
+ MOZ_COUNT_DTOR(OwningStyleContextSource);
+ if (mRaw.IsNull()) {
+ // We must have invoked the move constructor.
+ } else if (IsGeckoRuleNode()) {
+ RefPtr<nsRuleNode> releaseme = dont_AddRef(AsGeckoRuleNode());
+ } else {
+ MOZ_ASSERT(IsServoComputedValues());
+ RefPtr<ServoComputedValues> releaseme =
+ dont_AddRef(AsServoComputedValues());
+ }
+ }
+
+ bool operator==(const OwningStyleContextSource& aOther) const {
+ return mRaw == aOther.mRaw;
+ }
+ bool operator!=(const OwningStyleContextSource& aOther) const {
+ return !(*this == aOther);
+ }
+ bool IsNull() const { return mRaw.IsNull(); }
+ bool IsGeckoRuleNode() const {
+ MOZ_ASSERT(!mRaw.IsNull());
+ return mRaw.IsGeckoRuleNodeOrNull();
+ }
+ bool IsServoComputedValues() const { return mRaw.IsServoComputedValues(); }
+
+ NonOwningStyleContextSource AsRaw() const { return mRaw; }
+ nsRuleNode* AsGeckoRuleNode() const { return mRaw.AsGeckoRuleNode(); }
+ ServoComputedValues* AsServoComputedValues() const {
+ return const_cast<ServoComputedValues*>(mRaw.AsServoComputedValues());
+ }
+
+ bool MatchesNoRules() const { return mRaw.MatchesNoRules(); }
+
+private:
+ NonOwningStyleContextSource mRaw;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_StyleContextSource_h
diff --git a/layout/style/StyleRule.cpp b/layout/style/StyleRule.cpp
new file mode 100644
index 000000000..6aade8897
--- /dev/null
+++ b/layout/style/StyleRule.cpp
@@ -0,0 +1,1577 @@
+/* -*- 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/. */
+
+/*
+ * representation of CSS style rules (selectors+declaration), CSS
+ * selectors, and DOM objects for style rules, selectors, and
+ * declarations
+ */
+
+#include "mozilla/css/StyleRule.h"
+
+#include "mozilla/DeclarationBlockInlines.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/css/Declaration.h"
+#include "nsIDocument.h"
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "nsStyleUtil.h"
+#include "nsICSSStyleRuleDOMWrapper.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsNameSpaceManager.h"
+#include "nsXMLNameSpaceMap.h"
+#include "nsCSSPseudoClasses.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsTArray.h"
+#include "nsDOMClassInfoID.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "mozAutoDocUpdate.h"
+
+class nsIDOMCSSStyleDeclaration;
+class nsIDOMCSSStyleSheet;
+
+using namespace mozilla;
+
+#define NS_IF_CLONE(member_) \
+ PR_BEGIN_MACRO \
+ if (member_) { \
+ result->member_ = member_->Clone(); \
+ if (!result->member_) { \
+ delete result; \
+ return nullptr; \
+ } \
+ } \
+ PR_END_MACRO
+
+#define NS_IF_DELETE(ptr) \
+ PR_BEGIN_MACRO \
+ delete ptr; \
+ ptr = nullptr; \
+ PR_END_MACRO
+
+/* ************************************************************************** */
+
+nsAtomList::nsAtomList(nsIAtom* aAtom)
+ : mAtom(aAtom),
+ mNext(nullptr)
+{
+ MOZ_COUNT_CTOR(nsAtomList);
+}
+
+nsAtomList::nsAtomList(const nsString& aAtomValue)
+ : mAtom(nullptr),
+ mNext(nullptr)
+{
+ MOZ_COUNT_CTOR(nsAtomList);
+ mAtom = NS_Atomize(aAtomValue);
+}
+
+nsAtomList*
+nsAtomList::Clone(bool aDeep) const
+{
+ nsAtomList *result = new nsAtomList(mAtom);
+ if (!result)
+ return nullptr;
+
+ if (aDeep)
+ NS_CSS_CLONE_LIST_MEMBER(nsAtomList, this, mNext, result, (false));
+ return result;
+}
+
+size_t
+nsAtomList::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ const nsAtomList* a = this;
+ while (a) {
+ n += aMallocSizeOf(a);
+
+ // The following members aren't measured:
+ // - a->mAtom, because it may be shared
+
+ a = a->mNext;
+ }
+ return n;
+}
+
+nsAtomList::~nsAtomList(void)
+{
+ MOZ_COUNT_DTOR(nsAtomList);
+ NS_CSS_DELETE_LIST_MEMBER(nsAtomList, this, mNext);
+}
+
+nsPseudoClassList::nsPseudoClassList(CSSPseudoClassType aType)
+ : mType(aType),
+ mNext(nullptr)
+{
+ NS_ASSERTION(!nsCSSPseudoClasses::HasStringArg(aType) &&
+ !nsCSSPseudoClasses::HasNthPairArg(aType),
+ "unexpected pseudo-class");
+ MOZ_COUNT_CTOR(nsPseudoClassList);
+ u.mMemory = nullptr;
+}
+
+nsPseudoClassList::nsPseudoClassList(CSSPseudoClassType aType,
+ const char16_t* aString)
+ : mType(aType),
+ mNext(nullptr)
+{
+ NS_ASSERTION(nsCSSPseudoClasses::HasStringArg(aType),
+ "unexpected pseudo-class");
+ NS_ASSERTION(aString, "string expected");
+ MOZ_COUNT_CTOR(nsPseudoClassList);
+ u.mString = NS_strdup(aString);
+}
+
+nsPseudoClassList::nsPseudoClassList(CSSPseudoClassType aType,
+ const int32_t* aIntPair)
+ : mType(aType),
+ mNext(nullptr)
+{
+ NS_ASSERTION(nsCSSPseudoClasses::HasNthPairArg(aType),
+ "unexpected pseudo-class");
+ NS_ASSERTION(aIntPair, "integer pair expected");
+ MOZ_COUNT_CTOR(nsPseudoClassList);
+ u.mNumbers =
+ static_cast<int32_t*>(nsMemory::Clone(aIntPair, sizeof(int32_t) * 2));
+}
+
+// adopts aSelectorList
+nsPseudoClassList::nsPseudoClassList(CSSPseudoClassType aType,
+ nsCSSSelectorList* aSelectorList)
+ : mType(aType),
+ mNext(nullptr)
+{
+ NS_ASSERTION(nsCSSPseudoClasses::HasSelectorListArg(aType),
+ "unexpected pseudo-class");
+ NS_ASSERTION(aSelectorList, "selector list expected");
+ MOZ_COUNT_CTOR(nsPseudoClassList);
+ u.mSelectors = aSelectorList;
+}
+
+nsPseudoClassList*
+nsPseudoClassList::Clone(bool aDeep) const
+{
+ nsPseudoClassList *result;
+ if (!u.mMemory) {
+ result = new nsPseudoClassList(mType);
+ } else if (nsCSSPseudoClasses::HasStringArg(mType)) {
+ result = new nsPseudoClassList(mType, u.mString);
+ } else if (nsCSSPseudoClasses::HasNthPairArg(mType)) {
+ result = new nsPseudoClassList(mType, u.mNumbers);
+ } else {
+ NS_ASSERTION(nsCSSPseudoClasses::HasSelectorListArg(mType),
+ "unexpected pseudo-class");
+ // This constructor adopts its selector list argument.
+ result = new nsPseudoClassList(mType, u.mSelectors->Clone());
+ }
+
+ if (aDeep)
+ NS_CSS_CLONE_LIST_MEMBER(nsPseudoClassList, this, mNext, result,
+ (false));
+
+ return result;
+}
+
+size_t
+nsPseudoClassList::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ const nsPseudoClassList* p = this;
+ while (p) {
+ n += aMallocSizeOf(p);
+ if (!p->u.mMemory) {
+ // do nothing
+
+ } else if (nsCSSPseudoClasses::HasStringArg(p->mType)) {
+ n += aMallocSizeOf(p->u.mString);
+
+ } else if (nsCSSPseudoClasses::HasNthPairArg(p->mType)) {
+ n += aMallocSizeOf(p->u.mNumbers);
+
+ } else {
+ NS_ASSERTION(nsCSSPseudoClasses::HasSelectorListArg(p->mType),
+ "unexpected pseudo-class");
+ n += p->u.mSelectors->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ p = p->mNext;
+ }
+ return n;
+}
+
+nsPseudoClassList::~nsPseudoClassList(void)
+{
+ MOZ_COUNT_DTOR(nsPseudoClassList);
+ if (nsCSSPseudoClasses::HasSelectorListArg(mType)) {
+ delete u.mSelectors;
+ } else if (u.mMemory) {
+ free(u.mMemory);
+ }
+ NS_CSS_DELETE_LIST_MEMBER(nsPseudoClassList, this, mNext);
+}
+
+nsAttrSelector::nsAttrSelector(int32_t aNameSpace, const nsString& aAttr)
+ : mValue(),
+ mNext(nullptr),
+ mLowercaseAttr(nullptr),
+ mCasedAttr(nullptr),
+ mNameSpace(aNameSpace),
+ mFunction(NS_ATTR_FUNC_SET),
+ // mValueCaseSensitivity doesn't matter; we have no value.
+ mValueCaseSensitivity(ValueCaseSensitivity::CaseSensitive)
+{
+ MOZ_COUNT_CTOR(nsAttrSelector);
+
+ nsAutoString lowercase;
+ nsContentUtils::ASCIIToLower(aAttr, lowercase);
+
+ mCasedAttr = NS_Atomize(aAttr);
+ mLowercaseAttr = NS_Atomize(lowercase);
+}
+
+nsAttrSelector::nsAttrSelector(int32_t aNameSpace, const nsString& aAttr, uint8_t aFunction,
+ const nsString& aValue,
+ ValueCaseSensitivity aValueCaseSensitivity)
+ : mValue(aValue),
+ mNext(nullptr),
+ mLowercaseAttr(nullptr),
+ mCasedAttr(nullptr),
+ mNameSpace(aNameSpace),
+ mFunction(aFunction),
+ mValueCaseSensitivity(aValueCaseSensitivity)
+{
+ MOZ_COUNT_CTOR(nsAttrSelector);
+
+ nsAutoString lowercase;
+ nsContentUtils::ASCIIToLower(aAttr, lowercase);
+
+ mCasedAttr = NS_Atomize(aAttr);
+ mLowercaseAttr = NS_Atomize(lowercase);
+}
+
+nsAttrSelector::nsAttrSelector(int32_t aNameSpace, nsIAtom* aLowercaseAttr,
+ nsIAtom* aCasedAttr, uint8_t aFunction,
+ const nsString& aValue,
+ ValueCaseSensitivity aValueCaseSensitivity)
+ : mValue(aValue),
+ mNext(nullptr),
+ mLowercaseAttr(aLowercaseAttr),
+ mCasedAttr(aCasedAttr),
+ mNameSpace(aNameSpace),
+ mFunction(aFunction),
+ mValueCaseSensitivity(aValueCaseSensitivity)
+{
+ MOZ_COUNT_CTOR(nsAttrSelector);
+}
+
+nsAttrSelector*
+nsAttrSelector::Clone(bool aDeep) const
+{
+ nsAttrSelector *result =
+ new nsAttrSelector(mNameSpace, mLowercaseAttr, mCasedAttr,
+ mFunction, mValue, mValueCaseSensitivity);
+
+ if (aDeep)
+ NS_CSS_CLONE_LIST_MEMBER(nsAttrSelector, this, mNext, result, (false));
+
+ return result;
+}
+
+nsAttrSelector::~nsAttrSelector(void)
+{
+ MOZ_COUNT_DTOR(nsAttrSelector);
+
+ NS_CSS_DELETE_LIST_MEMBER(nsAttrSelector, this, mNext);
+}
+
+size_t
+nsAttrSelector::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ const nsAttrSelector* p = this;
+ while (p) {
+ n += aMallocSizeOf(p);
+ n += p->mValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ p = p->mNext;
+ }
+ return n;
+}
+
+// -- nsCSSSelector -------------------------------
+
+nsCSSSelector::nsCSSSelector(void)
+ : mLowercaseTag(nullptr),
+ mCasedTag(nullptr),
+ mIDList(nullptr),
+ mClassList(nullptr),
+ mPseudoClassList(nullptr),
+ mAttrList(nullptr),
+ mNegations(nullptr),
+ mNext(nullptr),
+ mNameSpace(kNameSpaceID_Unknown),
+ mOperator(0),
+ mPseudoType(CSSPseudoElementType::NotPseudo)
+{
+ MOZ_COUNT_CTOR(nsCSSSelector);
+}
+
+nsCSSSelector*
+nsCSSSelector::Clone(bool aDeepNext, bool aDeepNegations) const
+{
+ nsCSSSelector *result = new nsCSSSelector();
+ if (!result)
+ return nullptr;
+
+ result->mNameSpace = mNameSpace;
+ result->mLowercaseTag = mLowercaseTag;
+ result->mCasedTag = mCasedTag;
+ result->mOperator = mOperator;
+ result->mPseudoType = mPseudoType;
+
+ NS_IF_CLONE(mIDList);
+ NS_IF_CLONE(mClassList);
+ NS_IF_CLONE(mPseudoClassList);
+ NS_IF_CLONE(mAttrList);
+
+ // No need to worry about multiple levels of recursion since an
+ // mNegations can't have an mNext.
+ NS_ASSERTION(!mNegations || !mNegations->mNext,
+ "mNegations can't have non-null mNext");
+ if (aDeepNegations) {
+ NS_CSS_CLONE_LIST_MEMBER(nsCSSSelector, this, mNegations, result,
+ (true, false));
+ }
+
+ if (aDeepNext) {
+ NS_CSS_CLONE_LIST_MEMBER(nsCSSSelector, this, mNext, result,
+ (false, true));
+ }
+
+ return result;
+}
+
+nsCSSSelector::~nsCSSSelector(void)
+{
+ MOZ_COUNT_DTOR(nsCSSSelector);
+ Reset();
+ // No need to worry about multiple levels of recursion since an
+ // mNegations can't have an mNext.
+ NS_CSS_DELETE_LIST_MEMBER(nsCSSSelector, this, mNext);
+}
+
+void nsCSSSelector::Reset(void)
+{
+ mNameSpace = kNameSpaceID_Unknown;
+ mLowercaseTag = nullptr;
+ mCasedTag = nullptr;
+ NS_IF_DELETE(mIDList);
+ NS_IF_DELETE(mClassList);
+ NS_IF_DELETE(mPseudoClassList);
+ NS_IF_DELETE(mAttrList);
+ // No need to worry about multiple levels of recursion since an
+ // mNegations can't have an mNext.
+ NS_ASSERTION(!mNegations || !mNegations->mNext,
+ "mNegations can't have non-null mNext");
+ NS_CSS_DELETE_LIST_MEMBER(nsCSSSelector, this, mNegations);
+ mOperator = char16_t(0);
+}
+
+void nsCSSSelector::SetNameSpace(int32_t aNameSpace)
+{
+ mNameSpace = aNameSpace;
+}
+
+void nsCSSSelector::SetTag(const nsString& aTag)
+{
+ if (aTag.IsEmpty()) {
+ mLowercaseTag = mCasedTag = nullptr;
+ return;
+ }
+
+ mCasedTag = NS_Atomize(aTag);
+
+ nsAutoString lowercase;
+ nsContentUtils::ASCIIToLower(aTag, lowercase);
+ mLowercaseTag = NS_Atomize(lowercase);
+}
+
+void nsCSSSelector::AddID(const nsString& aID)
+{
+ if (!aID.IsEmpty()) {
+ nsAtomList** list = &mIDList;
+ while (nullptr != *list) {
+ list = &((*list)->mNext);
+ }
+ *list = new nsAtomList(aID);
+ }
+}
+
+void nsCSSSelector::AddClass(const nsString& aClass)
+{
+ if (!aClass.IsEmpty()) {
+ nsAtomList** list = &mClassList;
+ while (nullptr != *list) {
+ list = &((*list)->mNext);
+ }
+ *list = new nsAtomList(aClass);
+ }
+}
+
+void nsCSSSelector::AddPseudoClass(CSSPseudoClassType aType)
+{
+ AddPseudoClassInternal(new nsPseudoClassList(aType));
+}
+
+void nsCSSSelector::AddPseudoClass(CSSPseudoClassType aType,
+ const char16_t* aString)
+{
+ AddPseudoClassInternal(new nsPseudoClassList(aType, aString));
+}
+
+void nsCSSSelector::AddPseudoClass(CSSPseudoClassType aType,
+ const int32_t* aIntPair)
+{
+ AddPseudoClassInternal(new nsPseudoClassList(aType, aIntPair));
+}
+
+void nsCSSSelector::AddPseudoClass(CSSPseudoClassType aType,
+ nsCSSSelectorList* aSelectorList)
+{
+ // Take ownership of nsCSSSelectorList instead of copying.
+ AddPseudoClassInternal(new nsPseudoClassList(aType, aSelectorList));
+}
+
+void nsCSSSelector::AddPseudoClassInternal(nsPseudoClassList *aPseudoClass)
+{
+ nsPseudoClassList** list = &mPseudoClassList;
+ while (nullptr != *list) {
+ list = &((*list)->mNext);
+ }
+ *list = aPseudoClass;
+}
+
+void nsCSSSelector::AddAttribute(int32_t aNameSpace, const nsString& aAttr)
+{
+ if (!aAttr.IsEmpty()) {
+ nsAttrSelector** list = &mAttrList;
+ while (nullptr != *list) {
+ list = &((*list)->mNext);
+ }
+ *list = new nsAttrSelector(aNameSpace, aAttr);
+ }
+}
+
+void nsCSSSelector::AddAttribute(int32_t aNameSpace, const nsString& aAttr, uint8_t aFunc,
+ const nsString& aValue,
+ nsAttrSelector::ValueCaseSensitivity aCaseSensitivity)
+{
+ if (!aAttr.IsEmpty()) {
+ nsAttrSelector** list = &mAttrList;
+ while (nullptr != *list) {
+ list = &((*list)->mNext);
+ }
+ *list = new nsAttrSelector(aNameSpace, aAttr, aFunc, aValue, aCaseSensitivity);
+ }
+}
+
+void nsCSSSelector::SetOperator(char16_t aOperator)
+{
+ mOperator = aOperator;
+}
+
+int32_t nsCSSSelector::CalcWeightWithoutNegations() const
+{
+ int32_t weight = 0;
+
+#ifdef MOZ_XUL
+ MOZ_ASSERT(!(IsPseudoElement() &&
+ PseudoType() != CSSPseudoElementType::XULTree &&
+ mClassList),
+ "If non-XUL-tree pseudo-elements can have class selectors "
+ "after them, specificity calculation must be updated");
+#else
+ MOZ_ASSERT(!(IsPseudoElement() && mClassList),
+ "If pseudo-elements can have class selectors "
+ "after them, specificity calculation must be updated");
+#endif
+ MOZ_ASSERT(!(IsPseudoElement() && (mIDList || mAttrList)),
+ "If pseudo-elements can have id or attribute selectors "
+ "after them, specificity calculation must be updated");
+
+ if (nullptr != mCasedTag) {
+ weight += 0x000001;
+ }
+ nsAtomList* list = mIDList;
+ while (nullptr != list) {
+ weight += 0x010000;
+ list = list->mNext;
+ }
+ list = mClassList;
+#ifdef MOZ_XUL
+ // XUL tree pseudo-elements abuse mClassList to store some private
+ // data; ignore that.
+ if (PseudoType() == CSSPseudoElementType::XULTree) {
+ list = nullptr;
+ }
+#endif
+ while (nullptr != list) {
+ weight += 0x000100;
+ list = list->mNext;
+ }
+ // FIXME (bug 561154): This is incorrect for :-moz-any(), which isn't
+ // really a pseudo-class. In order to handle :-moz-any() correctly,
+ // we need to compute specificity after we match, based on which
+ // option we matched with (and thus also need to try the
+ // highest-specificity options first).
+ nsPseudoClassList *plist = mPseudoClassList;
+ while (nullptr != plist) {
+ weight += 0x000100;
+ plist = plist->mNext;
+ }
+ nsAttrSelector* attr = mAttrList;
+ while (nullptr != attr) {
+ weight += 0x000100;
+ attr = attr->mNext;
+ }
+ return weight;
+}
+
+int32_t nsCSSSelector::CalcWeight() const
+{
+ // Loop over this selector and all its negations.
+ int32_t weight = 0;
+ for (const nsCSSSelector *n = this; n; n = n->mNegations) {
+ weight += n->CalcWeightWithoutNegations();
+ }
+ return weight;
+}
+
+//
+// Builds the textual representation of a selector. Called by DOM 2 CSS
+// StyleRule:selectorText
+//
+void
+nsCSSSelector::ToString(nsAString& aString, CSSStyleSheet* aSheet,
+ bool aAppend) const
+{
+ if (!aAppend)
+ aString.Truncate();
+
+ // selectors are linked from right-to-left, so the next selector in
+ // the linked list actually precedes this one in the resulting string
+ AutoTArray<const nsCSSSelector*, 8> stack;
+ for (const nsCSSSelector *s = this; s; s = s->mNext) {
+ stack.AppendElement(s);
+ }
+
+ while (!stack.IsEmpty()) {
+ uint32_t index = stack.Length() - 1;
+ const nsCSSSelector *s = stack.ElementAt(index);
+ stack.RemoveElementAt(index);
+
+ s->AppendToStringWithoutCombinators(aString, aSheet, false);
+
+ // Append the combinator, if needed.
+ if (!stack.IsEmpty()) {
+ const nsCSSSelector *next = stack.ElementAt(index - 1);
+ char16_t oper = s->mOperator;
+ if (next->IsPseudoElement()) {
+ NS_ASSERTION(oper == char16_t(':'),
+ "improperly chained pseudo element");
+ } else {
+ NS_ASSERTION(oper != char16_t(0),
+ "compound selector without combinator");
+
+ aString.Append(char16_t(' '));
+ if (oper != char16_t(' ')) {
+ aString.Append(oper);
+ aString.Append(char16_t(' '));
+ }
+ }
+ }
+ }
+}
+
+void
+nsCSSSelector::AppendToStringWithoutCombinators(
+ nsAString& aString,
+ CSSStyleSheet* aSheet,
+ bool aUseStandardNamespacePrefixes) const
+{
+ AppendToStringWithoutCombinatorsOrNegations(aString, aSheet, false,
+ aUseStandardNamespacePrefixes);
+
+ for (const nsCSSSelector* negation = mNegations; negation;
+ negation = negation->mNegations) {
+ aString.AppendLiteral(":not(");
+ negation->AppendToStringWithoutCombinatorsOrNegations(
+ aString, aSheet, true, aUseStandardNamespacePrefixes);
+ aString.Append(char16_t(')'));
+ }
+}
+
+#ifdef DEBUG
+nsCString
+nsCSSSelector::RestrictedSelectorToString() const
+{
+ MOZ_ASSERT(IsRestrictedSelector());
+
+ nsString result;
+ AppendToStringWithoutCombinators(result, nullptr, true);
+ return NS_ConvertUTF16toUTF8(result);
+}
+
+static bool
+AppendStandardNamespacePrefixToString(nsAString& aString, int32_t aNameSpace)
+{
+ if (aNameSpace == kNameSpaceID_Unknown) {
+ // Wildcard namespace; no prefix to write.
+ return false;
+ }
+ switch (aNameSpace) {
+ case kNameSpaceID_None:
+ break;
+ case kNameSpaceID_XML:
+ aString.AppendLiteral("xml");
+ break;
+ case kNameSpaceID_XHTML:
+ aString.AppendLiteral("html");
+ break;
+ case kNameSpaceID_XLink:
+ aString.AppendLiteral("xlink");
+ break;
+ case kNameSpaceID_XSLT:
+ aString.AppendLiteral("xsl");
+ break;
+ case kNameSpaceID_XBL:
+ aString.AppendLiteral("xbl");
+ break;
+ case kNameSpaceID_MathML:
+ aString.AppendLiteral("math");
+ break;
+ case kNameSpaceID_RDF:
+ aString.AppendLiteral("rdf");
+ break;
+ case kNameSpaceID_XUL:
+ aString.AppendLiteral("xul");
+ break;
+ case kNameSpaceID_SVG:
+ aString.AppendLiteral("svg");
+ break;
+ default:
+ aString.AppendLiteral("ns");
+ aString.AppendInt(aNameSpace);
+ break;
+ }
+ return true;
+}
+#endif
+
+void
+nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
+ (nsAString& aString, CSSStyleSheet* aSheet,
+ bool aIsNegated,
+ bool aUseStandardNamespacePrefixes) const
+{
+ nsAutoString temp;
+ bool isPseudoElement = IsPseudoElement();
+
+ // For non-pseudo-element selectors or for lone pseudo-elements, deal with
+ // namespace prefixes.
+ bool wroteNamespace = false;
+ if (!isPseudoElement || !mNext) {
+ // append the namespace prefix if needed
+ nsXMLNameSpaceMap *sheetNS = aSheet ? aSheet->GetNameSpaceMap() : nullptr;
+
+ // sheetNS is non-null if and only if we had an @namespace rule. If it's
+ // null, that means that the only namespaces we could have are the
+ // wildcard namespace (which can be implicit in this case) and the "none"
+ // namespace, which then needs to be explicitly specified.
+ if (aUseStandardNamespacePrefixes) {
+#ifdef DEBUG
+ // We have no sheet to look up prefix information from. This is
+ // only for debugging, so use some "standard" prefixes that
+ // are recognizable.
+ wroteNamespace =
+ AppendStandardNamespacePrefixToString(aString, mNameSpace);
+ if (wroteNamespace) {
+ aString.Append(char16_t('|'));
+ }
+#endif
+ } else if (!sheetNS) {
+ NS_ASSERTION(mNameSpace == kNameSpaceID_Unknown ||
+ mNameSpace == kNameSpaceID_None,
+ "How did we get this namespace?");
+ if (mNameSpace == kNameSpaceID_None) {
+ aString.Append(char16_t('|'));
+ wroteNamespace = true;
+ }
+ } else if (sheetNS->FindNameSpaceID(nullptr) == mNameSpace) {
+ // We have the default namespace (possibly including the wildcard
+ // namespace). Do nothing.
+ NS_ASSERTION(mNameSpace == kNameSpaceID_Unknown ||
+ CanBeNamespaced(aIsNegated),
+ "How did we end up with this namespace?");
+ } else if (mNameSpace == kNameSpaceID_None) {
+ NS_ASSERTION(CanBeNamespaced(aIsNegated),
+ "How did we end up with this namespace?");
+ aString.Append(char16_t('|'));
+ wroteNamespace = true;
+ } else if (mNameSpace != kNameSpaceID_Unknown) {
+ NS_ASSERTION(CanBeNamespaced(aIsNegated),
+ "How did we end up with this namespace?");
+ nsIAtom *prefixAtom = sheetNS->FindPrefix(mNameSpace);
+ NS_ASSERTION(prefixAtom, "how'd we get a non-default namespace "
+ "without a prefix?");
+ nsStyleUtil::AppendEscapedCSSIdent(nsDependentAtomString(prefixAtom),
+ aString);
+ aString.Append(char16_t('|'));
+ wroteNamespace = true;
+ } else {
+ // A selector for an element in any namespace, while the default
+ // namespace is something else. :not() is special in that the default
+ // namespace is not implied for non-type selectors, so if this is a
+ // negated non-type selector we don't need to output an explicit wildcard
+ // namespace here, since those default to a wildcard namespace.
+ if (CanBeNamespaced(aIsNegated)) {
+ aString.AppendLiteral("*|");
+ wroteNamespace = true;
+ }
+ }
+ }
+
+ if (!mLowercaseTag) {
+ // Universal selector: avoid writing the universal selector when we
+ // can avoid it, especially since we're required to avoid it for the
+ // inside of :not()
+ if (wroteNamespace ||
+ (!mIDList && !mClassList && !mPseudoClassList && !mAttrList &&
+ (aIsNegated || !mNegations))) {
+ aString.Append(char16_t('*'));
+ }
+ } else {
+ // Append the tag name
+ nsAutoString tag;
+ (isPseudoElement ? mLowercaseTag : mCasedTag)->ToString(tag);
+ if (isPseudoElement) {
+ if (!mNext) {
+ // Lone pseudo-element selector -- toss in a wildcard type selector
+ // XXXldb Why?
+ aString.Append(char16_t('*'));
+ }
+ // While our atoms use one colon, most pseudo-elements require two
+ // colons (those not in CSS level 2) and all pseudo-elements allow
+ // two colons. So serialize to the non-deprecated two colon syntax.
+ aString.Append(char16_t(':'));
+ // This should not be escaped since (a) the pseudo-element string
+ // has a ":" that can't be escaped and (b) all pseudo-elements at
+ // this point are known, and therefore we know they don't need
+ // escaping.
+ aString.Append(tag);
+ } else {
+ nsStyleUtil::AppendEscapedCSSIdent(tag, aString);
+ }
+ }
+
+ // Append the id, if there is one
+ if (mIDList) {
+ nsAtomList* list = mIDList;
+ while (list != nullptr) {
+ list->mAtom->ToString(temp);
+ aString.Append(char16_t('#'));
+ nsStyleUtil::AppendEscapedCSSIdent(temp, aString);
+ list = list->mNext;
+ }
+ }
+
+ // Append each class in the linked list
+ if (mClassList) {
+ if (isPseudoElement) {
+#ifdef MOZ_XUL
+ MOZ_ASSERT(nsCSSAnonBoxes::IsTreePseudoElement(mLowercaseTag),
+ "must be tree pseudo-element");
+
+ aString.Append(char16_t('('));
+ for (nsAtomList* list = mClassList; list; list = list->mNext) {
+ nsStyleUtil::AppendEscapedCSSIdent(nsDependentAtomString(list->mAtom), aString);
+ aString.Append(char16_t(','));
+ }
+ // replace the final comma with a close-paren
+ aString.Replace(aString.Length() - 1, 1, char16_t(')'));
+#else
+ NS_ERROR("Can't happen");
+#endif
+ } else {
+ nsAtomList* list = mClassList;
+ while (list != nullptr) {
+ list->mAtom->ToString(temp);
+ aString.Append(char16_t('.'));
+ nsStyleUtil::AppendEscapedCSSIdent(temp, aString);
+ list = list->mNext;
+ }
+ }
+ }
+
+ // Append each attribute selector in the linked list
+ if (mAttrList) {
+ nsAttrSelector* list = mAttrList;
+ while (list != nullptr) {
+ aString.Append(char16_t('['));
+ // Append the namespace prefix
+ if (list->mNameSpace == kNameSpaceID_Unknown) {
+ aString.Append(char16_t('*'));
+ aString.Append(char16_t('|'));
+ } else if (list->mNameSpace != kNameSpaceID_None) {
+ if (aUseStandardNamespacePrefixes) {
+#ifdef DEBUG
+ AppendStandardNamespacePrefixToString(aString, list->mNameSpace);
+ aString.Append(char16_t('|'));
+#endif
+ } else if (aSheet) {
+ nsXMLNameSpaceMap *sheetNS = aSheet->GetNameSpaceMap();
+ nsIAtom *prefixAtom = sheetNS->FindPrefix(list->mNameSpace);
+ // Default namespaces don't apply to attribute selectors, so
+ // we must have a useful prefix.
+ NS_ASSERTION(prefixAtom,
+ "How did we end up with a namespace if the prefix "
+ "is unknown?");
+ nsAutoString prefix;
+ prefixAtom->ToString(prefix);
+ nsStyleUtil::AppendEscapedCSSIdent(prefix, aString);
+ aString.Append(char16_t('|'));
+ }
+ }
+ // Append the attribute name
+ list->mCasedAttr->ToString(temp);
+ nsStyleUtil::AppendEscapedCSSIdent(temp, aString);
+
+ if (list->mFunction != NS_ATTR_FUNC_SET) {
+ // Append the function
+ if (list->mFunction == NS_ATTR_FUNC_INCLUDES)
+ aString.Append(char16_t('~'));
+ else if (list->mFunction == NS_ATTR_FUNC_DASHMATCH)
+ aString.Append(char16_t('|'));
+ else if (list->mFunction == NS_ATTR_FUNC_BEGINSMATCH)
+ aString.Append(char16_t('^'));
+ else if (list->mFunction == NS_ATTR_FUNC_ENDSMATCH)
+ aString.Append(char16_t('$'));
+ else if (list->mFunction == NS_ATTR_FUNC_CONTAINSMATCH)
+ aString.Append(char16_t('*'));
+
+ aString.Append(char16_t('='));
+
+ // Append the value
+ nsStyleUtil::AppendEscapedCSSString(list->mValue, aString);
+
+ if (list->mValueCaseSensitivity ==
+ nsAttrSelector::ValueCaseSensitivity::CaseInsensitive) {
+ aString.Append(NS_LITERAL_STRING(" i"));
+ }
+ }
+
+ aString.Append(char16_t(']'));
+
+ list = list->mNext;
+ }
+ }
+
+ // Append each pseudo-class in the linked list
+ for (nsPseudoClassList* list = mPseudoClassList; list; list = list->mNext) {
+ nsCSSPseudoClasses::PseudoTypeToString(list->mType, temp);
+ // This should not be escaped since (a) the pseudo-class string
+ // has a ":" that can't be escaped and (b) all pseudo-classes at
+ // this point are known, and therefore we know they don't need
+ // escaping.
+ aString.Append(temp);
+ if (list->u.mMemory) {
+ aString.Append(char16_t('('));
+ if (nsCSSPseudoClasses::HasStringArg(list->mType)) {
+ nsStyleUtil::AppendEscapedCSSIdent(
+ nsDependentString(list->u.mString), aString);
+ } else if (nsCSSPseudoClasses::HasNthPairArg(list->mType)) {
+ int32_t a = list->u.mNumbers[0],
+ b = list->u.mNumbers[1];
+ temp.Truncate();
+ if (a != 0) {
+ if (a == -1) {
+ temp.Append(char16_t('-'));
+ } else if (a != 1) {
+ temp.AppendInt(a);
+ }
+ temp.Append(char16_t('n'));
+ }
+ if (b != 0 || a == 0) {
+ if (b >= 0 && a != 0) // check a != 0 for whether we printed above
+ temp.Append(char16_t('+'));
+ temp.AppendInt(b);
+ }
+ aString.Append(temp);
+ } else {
+ NS_ASSERTION(nsCSSPseudoClasses::HasSelectorListArg(list->mType),
+ "unexpected pseudo-class");
+ nsString tmp;
+ list->u.mSelectors->ToString(tmp, aSheet);
+ aString.Append(tmp);
+ }
+ aString.Append(char16_t(')'));
+ }
+ }
+}
+
+bool
+nsCSSSelector::CanBeNamespaced(bool aIsNegated) const
+{
+ return !aIsNegated ||
+ (!mIDList && !mClassList && !mPseudoClassList && !mAttrList);
+}
+
+size_t
+nsCSSSelector::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ const nsCSSSelector* s = this;
+ while (s) {
+ n += aMallocSizeOf(s);
+
+ #define MEASURE(x) n += x ? x->SizeOfIncludingThis(aMallocSizeOf) : 0;
+
+ MEASURE(s->mIDList);
+ MEASURE(s->mClassList);
+ MEASURE(s->mPseudoClassList);
+ MEASURE(s->mNegations);
+ MEASURE(s->mAttrList);
+
+ // The following members aren't measured:
+ // - s->mLowercaseTag, because it's an atom and therefore shared
+ // - s->mCasedTag, because it's an atom and therefore shared
+
+ s = s->mNext;
+ }
+ return n;
+}
+
+// -- nsCSSSelectorList -------------------------------
+
+nsCSSSelectorList::nsCSSSelectorList(void)
+ : mSelectors(nullptr),
+ mWeight(0),
+ mNext(nullptr)
+{
+ MOZ_COUNT_CTOR(nsCSSSelectorList);
+}
+
+nsCSSSelectorList::~nsCSSSelectorList()
+{
+ MOZ_COUNT_DTOR(nsCSSSelectorList);
+ delete mSelectors;
+ NS_CSS_DELETE_LIST_MEMBER(nsCSSSelectorList, this, mNext);
+}
+
+nsCSSSelector*
+nsCSSSelectorList::AddSelector(char16_t aOperator)
+{
+ nsCSSSelector* newSel = new nsCSSSelector();
+
+ if (mSelectors) {
+ NS_ASSERTION(aOperator != char16_t(0), "chaining without combinator");
+ mSelectors->SetOperator(aOperator);
+ } else {
+ NS_ASSERTION(aOperator == char16_t(0), "combinator without chaining");
+ }
+
+ newSel->mNext = mSelectors;
+ mSelectors = newSel;
+ return newSel;
+}
+
+void
+nsCSSSelectorList::RemoveRightmostSelector()
+{
+ nsCSSSelector* current = mSelectors;
+ mSelectors = mSelectors->mNext;
+ MOZ_ASSERT(mSelectors,
+ "Rightmost selector has been removed, but now "
+ "mSelectors is null");
+ mSelectors->SetOperator(char16_t(0));
+
+ // Make sure that deleting current won't delete the whole list.
+ current->mNext = nullptr;
+ delete current;
+}
+
+void
+nsCSSSelectorList::ToString(nsAString& aResult, CSSStyleSheet* aSheet)
+{
+ aResult.Truncate();
+ nsCSSSelectorList *p = this;
+ for (;;) {
+ p->mSelectors->ToString(aResult, aSheet, true);
+ p = p->mNext;
+ if (!p)
+ break;
+ aResult.AppendLiteral(", ");
+ }
+}
+
+nsCSSSelectorList*
+nsCSSSelectorList::Clone(bool aDeep) const
+{
+ nsCSSSelectorList *result = new nsCSSSelectorList();
+ result->mWeight = mWeight;
+ NS_IF_CLONE(mSelectors);
+
+ if (aDeep) {
+ NS_CSS_CLONE_LIST_MEMBER(nsCSSSelectorList, this, mNext, result,
+ (false));
+ }
+ return result;
+}
+
+size_t
+nsCSSSelectorList::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ const nsCSSSelectorList* s = this;
+ while (s) {
+ n += aMallocSizeOf(s);
+ n += s->mSelectors ? s->mSelectors->SizeOfIncludingThis(aMallocSizeOf) : 0;
+ s = s->mNext;
+ }
+ return n;
+}
+
+// --------------------------------------------------------
+
+namespace mozilla {
+namespace css {
+class DOMCSSStyleRule;
+} // namespace css
+} // namespace mozilla
+
+class DOMCSSDeclarationImpl : public nsDOMCSSDeclaration
+{
+protected:
+ virtual ~DOMCSSDeclarationImpl(void);
+
+public:
+ explicit DOMCSSDeclarationImpl(css::StyleRule *aRule);
+
+ NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent) override;
+ void DropReference(void);
+ virtual DeclarationBlock* GetCSSDeclaration(Operation aOperation) override;
+ virtual nsresult SetCSSDeclaration(DeclarationBlock* aDecl) override;
+ virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+ virtual nsIDocument* DocToUpdate() override;
+
+ // Override |AddRef| and |Release| for being a member of
+ // |DOMCSSStyleRule|. Also, we need to forward QI for cycle
+ // collection things to DOMCSSStyleRule.
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual nsINode *GetParentObject() override
+ {
+ return mRule ? mRule->GetDocument() : nullptr;
+ }
+
+ friend class css::DOMCSSStyleRule;
+
+protected:
+ // This reference is not reference-counted. The rule object tells us
+ // when it's about to go away.
+ css::StyleRule *mRule;
+
+ inline css::DOMCSSStyleRule* DomRule();
+
+private:
+ // NOT TO BE IMPLEMENTED
+ // This object cannot be allocated on its own. It must be a member of
+ // DOMCSSStyleRule.
+ void* operator new(size_t size) CPP_THROW_NEW;
+};
+
+namespace mozilla {
+namespace css {
+
+class DOMCSSStyleRule : public nsICSSStyleRuleDOMWrapper
+{
+public:
+ explicit DOMCSSStyleRule(StyleRule *aRule);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMCSSStyleRule)
+ NS_DECL_NSIDOMCSSRULE
+ NS_DECL_NSIDOMCSSSTYLERULE
+
+ // nsICSSStyleRuleDOMWrapper
+ NS_IMETHOD GetCSSStyleRule(StyleRule **aResult) override;
+
+ DOMCSSDeclarationImpl* DOMDeclaration() { return &mDOMDeclaration; }
+
+ friend class ::DOMCSSDeclarationImpl;
+
+protected:
+ virtual ~DOMCSSStyleRule();
+
+ DOMCSSDeclarationImpl mDOMDeclaration;
+
+ StyleRule* Rule() {
+ return mDOMDeclaration.mRule;
+ }
+};
+
+} // namespace css
+} // namespace mozilla
+
+DOMCSSDeclarationImpl::DOMCSSDeclarationImpl(css::StyleRule *aRule)
+ : mRule(aRule)
+{
+ MOZ_COUNT_CTOR(DOMCSSDeclarationImpl);
+}
+
+DOMCSSDeclarationImpl::~DOMCSSDeclarationImpl(void)
+{
+ NS_ASSERTION(!mRule, "DropReference not called.");
+
+ MOZ_COUNT_DTOR(DOMCSSDeclarationImpl);
+}
+
+inline css::DOMCSSStyleRule* DOMCSSDeclarationImpl::DomRule()
+{
+ return reinterpret_cast<css::DOMCSSStyleRule*>
+ (reinterpret_cast<char*>(this) -
+ offsetof(css::DOMCSSStyleRule, mDOMDeclaration));
+}
+
+NS_IMPL_ADDREF_USING_AGGREGATOR(DOMCSSDeclarationImpl, DomRule())
+NS_IMPL_RELEASE_USING_AGGREGATOR(DOMCSSDeclarationImpl, DomRule())
+
+NS_INTERFACE_MAP_BEGIN(DOMCSSDeclarationImpl)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ // We forward the cycle collection interfaces to DomRule(), 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 DomRule()->QueryInterface(aIID, aInstancePtr);
+ }
+ else
+NS_IMPL_QUERY_TAIL_INHERITING(nsDOMCSSDeclaration)
+
+void
+DOMCSSDeclarationImpl::DropReference(void)
+{
+ mRule = nullptr;
+}
+
+DeclarationBlock*
+DOMCSSDeclarationImpl::GetCSSDeclaration(Operation aOperation)
+{
+ if (mRule) {
+ if (aOperation != eOperation_Read) {
+ RefPtr<CSSStyleSheet> sheet = mRule->GetStyleSheet();
+ if (sheet) {
+ sheet->WillDirty();
+ }
+ }
+ return mRule->GetDeclaration();
+ } else {
+ return nullptr;
+ }
+}
+
+void
+DOMCSSDeclarationImpl::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
+{
+ GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
+}
+
+NS_IMETHODIMP
+DOMCSSDeclarationImpl::GetParentRule(nsIDOMCSSRule **aParent)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+
+ if (!mRule) {
+ *aParent = nullptr;
+ return NS_OK;
+ }
+
+ NS_IF_ADDREF(*aParent = mRule->GetDOMRule());
+ return NS_OK;
+}
+
+nsresult
+DOMCSSDeclarationImpl::SetCSSDeclaration(DeclarationBlock* aDecl)
+{
+ NS_PRECONDITION(mRule,
+ "can only be called when |GetCSSDeclaration| returned a declaration");
+
+ nsCOMPtr<nsIDocument> owningDoc;
+ RefPtr<CSSStyleSheet> sheet = mRule->GetStyleSheet();
+ if (sheet) {
+ owningDoc = sheet->GetOwningDocument();
+ }
+
+ mozAutoDocUpdate updateBatch(owningDoc, UPDATE_STYLE, true);
+
+ mRule->SetDeclaration(aDecl->AsGecko());
+
+ if (sheet) {
+ sheet->DidDirty();
+ }
+
+ if (owningDoc) {
+ owningDoc->StyleRuleChanged(sheet, mRule);
+ }
+ return NS_OK;
+}
+
+nsIDocument*
+DOMCSSDeclarationImpl::DocToUpdate()
+{
+ return nullptr;
+}
+
+namespace mozilla {
+namespace css {
+
+DOMCSSStyleRule::DOMCSSStyleRule(StyleRule* aRule)
+ : mDOMDeclaration(aRule)
+{
+}
+
+DOMCSSStyleRule::~DOMCSSStyleRule()
+{
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMCSSStyleRule)
+ NS_INTERFACE_MAP_ENTRY(nsICSSStyleRuleDOMWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSStyleRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSStyleRule)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMCSSStyleRule)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMCSSStyleRule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(DOMCSSStyleRule)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMCSSStyleRule)
+ // 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->DOMDeclaration()->TraceWrapper(aCallbacks, aClosure);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMCSSStyleRule)
+ // Unlink the wrapper for our declaraton. This just expands out
+ // NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER which we can't use
+ // directly because the wrapper is on the declaration, not on us.
+ tmp->DOMDeclaration()->ReleaseWrapper(static_cast<nsISupports*>(p));
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMCSSStyleRule)
+ // Just NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS here: that will call
+ // into our Trace hook, where we do the right thing with declarations
+ // already.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMETHODIMP
+DOMCSSStyleRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::STYLE_RULE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMCSSStyleRule::GetCssText(nsAString& aCssText)
+{
+ if (!Rule()) {
+ aCssText.Truncate();
+ } else {
+ Rule()->GetCssText(aCssText);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMCSSStyleRule::SetCssText(const nsAString& aCssText)
+{
+ if (Rule()) {
+ Rule()->SetCssText(aCssText);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMCSSStyleRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ if (!Rule()) {
+ *aSheet = nullptr;
+ return NS_OK;
+ }
+ return Rule()->GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+DOMCSSStyleRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ if (!Rule()) {
+ *aParentRule = nullptr;
+ return NS_OK;
+ }
+ return Rule()->GetParentRule(aParentRule);
+}
+
+css::Rule*
+DOMCSSStyleRule::GetCSSRule()
+{
+ return Rule();
+}
+
+NS_IMETHODIMP
+DOMCSSStyleRule::GetSelectorText(nsAString& aSelectorText)
+{
+ if (!Rule()) {
+ aSelectorText.Truncate();
+ } else {
+ Rule()->GetSelectorText(aSelectorText);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMCSSStyleRule::SetSelectorText(const nsAString& aSelectorText)
+{
+ if (Rule()) {
+ Rule()->SetSelectorText(aSelectorText);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMCSSStyleRule::GetStyle(nsIDOMCSSStyleDeclaration** aStyle)
+{
+ *aStyle = &mDOMDeclaration;
+ NS_ADDREF(*aStyle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMCSSStyleRule::GetCSSStyleRule(StyleRule **aResult)
+{
+ *aResult = Rule();
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+} // namespace css
+} // namespace mozilla
+
+// -- StyleRule ------------------------------------
+
+namespace mozilla {
+namespace css {
+
+StyleRule::StyleRule(nsCSSSelectorList* aSelector,
+ Declaration* aDeclaration,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber)
+ : Rule(aLineNumber, aColumnNumber),
+ mSelector(aSelector),
+ mDeclaration(aDeclaration)
+{
+ NS_PRECONDITION(aDeclaration, "must have a declaration");
+
+ mDeclaration->SetOwningRule(this);
+}
+
+// for |Clone|
+StyleRule::StyleRule(const StyleRule& aCopy)
+ : Rule(aCopy),
+ mSelector(aCopy.mSelector ? aCopy.mSelector->Clone() : nullptr),
+ mDeclaration(new Declaration(*aCopy.mDeclaration))
+{
+ mDeclaration->SetOwningRule(this);
+ // rest is constructed lazily on existing data
+}
+
+StyleRule::~StyleRule()
+{
+ delete mSelector;
+ if (mDOMRule) {
+ mDOMRule->DOMDeclaration()->DropReference();
+ }
+
+ if (mDeclaration) {
+ mDeclaration->SetOwningRule(nullptr);
+ }
+}
+
+// QueryInterface implementation for StyleRule
+NS_INTERFACE_MAP_BEGIN(StyleRule)
+ if (aIID.Equals(NS_GET_IID(mozilla::css::StyleRule))) {
+ *aInstancePtr = this;
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+ else
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(StyleRule)
+NS_IMPL_RELEASE(StyleRule)
+
+/* virtual */ int32_t
+StyleRule::GetType() const
+{
+ return Rule::STYLE_RULE;
+}
+
+/* virtual */ already_AddRefed<Rule>
+StyleRule::Clone() const
+{
+ RefPtr<Rule> clone = new StyleRule(*this);
+ return clone.forget();
+}
+
+/* virtual */ nsIDOMCSSRule*
+StyleRule::GetDOMRule()
+{
+ if (!mDOMRule) {
+ if (!GetStyleSheet()) {
+ // Inline style rules aren't supposed to have a DOM rule object, only
+ // a declaration. But if we do have one already, from a style sheet
+ // rule that used to be in a document, we still want to return it.
+ return nullptr;
+ }
+ mDOMRule = new DOMCSSStyleRule(this);
+ }
+ return mDOMRule;
+}
+
+/* virtual */ nsIDOMCSSRule*
+StyleRule::GetExistingDOMRule()
+{
+ return mDOMRule;
+}
+
+void
+StyleRule::SetDeclaration(Declaration* aDecl)
+{
+ if (aDecl == mDeclaration) {
+ return;
+ }
+ mDeclaration->SetOwningRule(nullptr);
+ mDeclaration = aDecl;
+ mDeclaration->SetOwningRule(this);
+}
+
+#ifdef DEBUG
+/* virtual */ void
+StyleRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString str;
+ // Indent
+ for (int32_t index = aIndent; --index >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+
+ if (mSelector) {
+ nsAutoString buffer;
+ mSelector->ToString(buffer, GetStyleSheet());
+ AppendUTF16toUTF8(buffer, str);
+ str.Append(' ');
+ }
+
+ if (nullptr != mDeclaration) {
+ nsAutoString buffer;
+ str.AppendLiteral("{ ");
+ mDeclaration->ToString(buffer);
+ AppendUTF16toUTF8(buffer, str);
+ str.Append('}');
+ CSSStyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ nsIURI* uri = sheet->GetSheetURI();
+ if (uri) {
+ str.Append(" /* ");
+ str.Append(uri->GetSpecOrDefault());
+ str.Append(':');
+ str.AppendInt(mLineNumber);
+ str.Append(" */");
+ }
+ }
+ }
+ else {
+ str.AppendLiteral("{ null declaration }");
+ }
+ str.Append('\n');
+ fprintf_stderr(out, "%s", str.get());
+}
+#endif
+
+void
+StyleRule::GetCssText(nsAString& aCssText)
+{
+ if (mSelector) {
+ mSelector->ToString(aCssText, GetStyleSheet());
+ aCssText.Append(char16_t(' '));
+ }
+ aCssText.Append(char16_t('{'));
+ aCssText.Append(char16_t(' '));
+ if (mDeclaration)
+ {
+ nsAutoString tempString;
+ mDeclaration->ToString( tempString );
+ aCssText.Append( tempString );
+ }
+ aCssText.Append(char16_t(' '));
+ aCssText.Append(char16_t('}'));
+}
+
+void
+StyleRule::SetCssText(const nsAString& aCssText)
+{
+ // XXX TBI - need to re-parse rule & declaration
+}
+
+void
+StyleRule::GetSelectorText(nsAString& aSelectorText)
+{
+ if (mSelector)
+ mSelector->ToString(aSelectorText, GetStyleSheet());
+ else
+ aSelectorText.Truncate();
+}
+
+void
+StyleRule::SetSelectorText(const nsAString& aSelectorText)
+{
+ // XXX TBI - get a parser and re-parse the selectors,
+ // XXX then need to re-compute the cascade
+ // XXX and dirty sheet
+}
+
+/* virtual */ size_t
+StyleRule::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ n += mSelector ? mSelector->SizeOfIncludingThis(aMallocSizeOf) : 0;
+ n += mDeclaration ? mDeclaration->SizeOfIncludingThis(aMallocSizeOf) : 0;
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mDOMRule;
+
+ return n;
+}
+
+
+} // namespace css
+} // namespace mozilla
diff --git a/layout/style/StyleRule.h b/layout/style/StyleRule.h
new file mode 100644
index 000000000..907e55448
--- /dev/null
+++ b/layout/style/StyleRule.h
@@ -0,0 +1,369 @@
+/* -*- 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/. */
+
+/*
+ * representation of CSS style rules (selectors+declaration) and CSS
+ * selectors
+ */
+
+#ifndef mozilla_css_StyleRule_h__
+#define mozilla_css_StyleRule_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/Rule.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsCSSPseudoElements.h"
+#include "nsIStyleRule.h"
+
+class nsIAtom;
+struct nsCSSSelectorList;
+
+namespace mozilla {
+enum class CSSPseudoClassType : uint8_t;
+class CSSStyleSheet;
+} // namespace mozilla
+
+struct nsAtomList {
+public:
+ explicit nsAtomList(nsIAtom* aAtom);
+ explicit nsAtomList(const nsString& aAtomValue);
+ ~nsAtomList(void);
+
+ /** Do a deep clone. Should be used only on the first in the linked list. */
+ nsAtomList* Clone() const { return Clone(true); }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ nsCOMPtr<nsIAtom> mAtom;
+ nsAtomList* mNext;
+private:
+ nsAtomList* Clone(bool aDeep) const;
+
+ nsAtomList(const nsAtomList& aCopy) = delete;
+ nsAtomList& operator=(const nsAtomList& aCopy) = delete;
+};
+
+struct nsPseudoClassList {
+public:
+ typedef mozilla::CSSPseudoClassType CSSPseudoClassType;
+
+ explicit nsPseudoClassList(CSSPseudoClassType aType);
+ nsPseudoClassList(CSSPseudoClassType aType, const char16_t *aString);
+ nsPseudoClassList(CSSPseudoClassType aType, const int32_t *aIntPair);
+ nsPseudoClassList(CSSPseudoClassType aType,
+ nsCSSSelectorList *aSelectorList /* takes ownership */);
+ ~nsPseudoClassList(void);
+
+ /** Do a deep clone. Should be used only on the first in the linked list. */
+ nsPseudoClassList* Clone() const { return Clone(true); }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ union {
+ // For a given value of mType, we have either:
+ // a. no value, which means mMemory is always null
+ // (if none of the conditions for (b), (c), or (d) is true)
+ // b. a string value, which means mString/mMemory is non-null
+ // (if nsCSSPseudoClasses::HasStringArg(mType))
+ // c. an integer pair value, which means mNumbers/mMemory is non-null
+ // (if nsCSSPseudoClasses::HasNthPairArg(mType))
+ // d. a selector list, which means mSelectors is non-null
+ // (if nsCSSPseudoClasses::HasSelectorListArg(mType))
+ void* mMemory; // mString and mNumbers use moz_xmalloc/free
+ char16_t* mString;
+ int32_t* mNumbers;
+ nsCSSSelectorList* mSelectors;
+ } u;
+ CSSPseudoClassType mType;
+ nsPseudoClassList* mNext;
+private:
+ nsPseudoClassList* Clone(bool aDeep) const;
+
+ nsPseudoClassList(const nsPseudoClassList& aCopy) = delete;
+ nsPseudoClassList& operator=(const nsPseudoClassList& aCopy) = delete;
+};
+
+#define NS_ATTR_FUNC_SET 0 // [attr]
+#define NS_ATTR_FUNC_EQUALS 1 // [attr=value]
+#define NS_ATTR_FUNC_INCLUDES 2 // [attr~=value] (space separated)
+#define NS_ATTR_FUNC_DASHMATCH 3 // [attr|=value] ('-' truncated)
+#define NS_ATTR_FUNC_BEGINSMATCH 4 // [attr^=value] (begins with)
+#define NS_ATTR_FUNC_ENDSMATCH 5 // [attr$=value] (ends with)
+#define NS_ATTR_FUNC_CONTAINSMATCH 6 // [attr*=value] (contains substring)
+
+struct nsAttrSelector {
+public:
+ enum class ValueCaseSensitivity : uint8_t {
+ CaseSensitive,
+ CaseInsensitive,
+ CaseInsensitiveInHTML
+ };
+
+ nsAttrSelector(int32_t aNameSpace, const nsString& aAttr);
+ nsAttrSelector(int32_t aNameSpace, const nsString& aAttr, uint8_t aFunction,
+ const nsString& aValue,
+ ValueCaseSensitivity aValueCaseSensitivity);
+ nsAttrSelector(int32_t aNameSpace, nsIAtom* aLowercaseAttr,
+ nsIAtom* aCasedAttr, uint8_t aFunction,
+ const nsString& aValue,
+ ValueCaseSensitivity aValueCaseSensitivity);
+ ~nsAttrSelector(void);
+
+ /** Do a deep clone. Should be used only on the first in the linked list. */
+ nsAttrSelector* Clone() const { return Clone(true); }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ bool IsValueCaseSensitive(bool aInHTML) const {
+ return mValueCaseSensitivity == ValueCaseSensitivity::CaseSensitive ||
+ (!aInHTML &&
+ mValueCaseSensitivity == ValueCaseSensitivity::CaseInsensitiveInHTML);
+ }
+
+ nsString mValue;
+ nsAttrSelector* mNext;
+ nsCOMPtr<nsIAtom> mLowercaseAttr;
+ nsCOMPtr<nsIAtom> mCasedAttr;
+ int32_t mNameSpace;
+ uint8_t mFunction;
+ ValueCaseSensitivity mValueCaseSensitivity;
+
+private:
+ nsAttrSelector* Clone(bool aDeep) const;
+
+ nsAttrSelector(const nsAttrSelector& aCopy) = delete;
+ nsAttrSelector& operator=(const nsAttrSelector& aCopy) = delete;
+};
+
+struct nsCSSSelector {
+public:
+ typedef mozilla::CSSPseudoClassType CSSPseudoClassType;
+
+ nsCSSSelector(void);
+ ~nsCSSSelector(void);
+
+ /** Do a deep clone. Should be used only on the first in the linked list. */
+ nsCSSSelector* Clone() const { return Clone(true, true); }
+
+ void Reset(void);
+ void SetNameSpace(int32_t aNameSpace);
+ void SetTag(const nsString& aTag);
+ void AddID(const nsString& aID);
+ void AddClass(const nsString& aClass);
+ void AddPseudoClass(CSSPseudoClassType aType);
+ void AddPseudoClass(CSSPseudoClassType aType, const char16_t* aString);
+ void AddPseudoClass(CSSPseudoClassType aType, const int32_t* aIntPair);
+ // takes ownership of aSelectorList
+ void AddPseudoClass(CSSPseudoClassType aType,
+ nsCSSSelectorList* aSelectorList);
+ void AddAttribute(int32_t aNameSpace, const nsString& aAttr);
+ void AddAttribute(int32_t aNameSpace, const nsString& aAttr, uint8_t aFunc,
+ const nsString& aValue,
+ nsAttrSelector::ValueCaseSensitivity aValueCaseSensitivity);
+ void SetOperator(char16_t aOperator);
+
+ inline bool HasTagSelector() const {
+ return !!mCasedTag;
+ }
+
+ inline bool IsPseudoElement() const {
+ return mLowercaseTag && !mCasedTag;
+ }
+
+ // Calculate the specificity of this selector (not including its mNext!).
+ int32_t CalcWeight() const;
+
+ void ToString(nsAString& aString, mozilla::CSSStyleSheet* aSheet,
+ bool aAppend = false) const;
+
+ bool IsRestrictedSelector() const {
+ return PseudoType() == mozilla::CSSPseudoElementType::NotPseudo;
+ }
+
+#ifdef DEBUG
+ nsCString RestrictedSelectorToString() const;
+#endif
+
+private:
+ void AddPseudoClassInternal(nsPseudoClassList *aPseudoClass);
+ nsCSSSelector* Clone(bool aDeepNext, bool aDeepNegations) const;
+
+ void AppendToStringWithoutCombinators(
+ nsAString& aString,
+ mozilla::CSSStyleSheet* aSheet,
+ bool aUseStandardNamespacePrefixes) const;
+ void AppendToStringWithoutCombinatorsOrNegations(
+ nsAString& aString,
+ mozilla::CSSStyleSheet* aSheet,
+ bool aIsNegated,
+ bool aUseStandardNamespacePrefixes) const;
+ // Returns true if this selector can have a namespace specified (which
+ // happens if and only if the default namespace would apply to this
+ // selector).
+ bool CanBeNamespaced(bool aIsNegated) const;
+ // Calculate the specificity of this selector (not including its mNext
+ // or its mNegations).
+ int32_t CalcWeightWithoutNegations() const;
+
+public:
+ // Get and set the selector's pseudo type
+ mozilla::CSSPseudoElementType PseudoType() const { return mPseudoType; }
+ void SetPseudoType(mozilla::CSSPseudoElementType aType) {
+ mPseudoType = aType;
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // For case-sensitive documents, mLowercaseTag is the same as mCasedTag,
+ // but in case-insensitive documents (HTML) mLowercaseTag is lowercase.
+ // Also, for pseudo-elements mCasedTag will be null but mLowercaseTag
+ // contains their name.
+ nsCOMPtr<nsIAtom> mLowercaseTag;
+ nsCOMPtr<nsIAtom> mCasedTag;
+ nsAtomList* mIDList;
+ nsAtomList* mClassList;
+ nsPseudoClassList* mPseudoClassList; // atom for the pseudo, string for
+ // the argument to functional pseudos
+ nsAttrSelector* mAttrList;
+ nsCSSSelector* mNegations;
+ nsCSSSelector* mNext;
+ int32_t mNameSpace;
+ char16_t mOperator;
+private:
+ // The underlying type of CSSPseudoElementType is uint8_t and
+ // it packs well with mOperator. (char16_t + uint8_t is less than 32bits.)
+ mozilla::CSSPseudoElementType mPseudoType;
+
+ nsCSSSelector(const nsCSSSelector& aCopy) = delete;
+ nsCSSSelector& operator=(const nsCSSSelector& aCopy) = delete;
+};
+
+/**
+ * A selector list is the unit of selectors that each style rule has.
+ * For example, "P B, H1 B { ... }" would be a selector list with two
+ * items (where each |nsCSSSelectorList| object's |mSelectors| has
+ * an |mNext| for the P or H1). We represent them as linked lists.
+ */
+class inDOMUtils;
+
+struct nsCSSSelectorList {
+ nsCSSSelectorList(void);
+ ~nsCSSSelectorList(void);
+
+ /**
+ * Create a new selector and push it onto the beginning of |mSelectors|,
+ * setting its |mNext| to the current value of |mSelectors|. If there is an
+ * earlier selector, set its |mOperator| to |aOperator|; else |aOperator|
+ * must be char16_t(0).
+ * Returns the new selector.
+ * The list owns the new selector.
+ * The caller is responsible for updating |mWeight|.
+ */
+ nsCSSSelector* AddSelector(char16_t aOperator);
+
+ /**
+ * Point |mSelectors| to its |mNext|, and delete the first node in the old
+ * |mSelectors|.
+ * Should only be used on a list with more than one selector in it.
+ */
+ void RemoveRightmostSelector();
+
+ /**
+ * Should be used only on the first in the list
+ */
+ void ToString(nsAString& aResult, mozilla::CSSStyleSheet* aSheet);
+
+ /**
+ * Do a deep clone. Should be used only on the first in the list.
+ */
+ nsCSSSelectorList* Clone() const { return Clone(true); }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ nsCSSSelector* mSelectors;
+ int32_t mWeight;
+ nsCSSSelectorList* mNext;
+protected:
+ friend class inDOMUtils;
+ nsCSSSelectorList* Clone(bool aDeep) const;
+
+private:
+ nsCSSSelectorList(const nsCSSSelectorList& aCopy) = delete;
+ nsCSSSelectorList& operator=(const nsCSSSelectorList& aCopy) = delete;
+};
+
+// 464bab7a-2fce-4f30-ab44-b7a5f3aae57d
+#define NS_CSS_STYLE_RULE_IMPL_CID \
+{ 0x464bab7a, 0x2fce, 0x4f30, \
+ { 0xab, 0x44, 0xb7, 0xa5, 0xf3, 0xaa, 0xe5, 0x7d } }
+
+namespace mozilla {
+namespace css {
+
+class Declaration;
+class DOMCSSStyleRule;
+
+class StyleRule final : public Rule
+{
+ public:
+ StyleRule(nsCSSSelectorList* aSelector,
+ Declaration *aDeclaration,
+ uint32_t aLineNumber, uint32_t aColumnNumber);
+private:
+ // for |Clone|
+ StyleRule(const StyleRule& aCopy);
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_CSS_STYLE_RULE_IMPL_CID)
+
+ NS_DECL_ISUPPORTS
+
+ // null for style attribute
+ nsCSSSelectorList* Selector() { return mSelector; }
+
+ Declaration* GetDeclaration() const { return mDeclaration; }
+
+ void SetDeclaration(Declaration* aDecl);
+
+ // hooks for DOM rule
+ void GetCssText(nsAString& aCssText);
+ void SetCssText(const nsAString& aCssText);
+ void GetSelectorText(nsAString& aSelectorText);
+ void SetSelectorText(const nsAString& aSelectorText);
+
+ virtual int32_t GetType() const override;
+
+ virtual already_AddRefed<Rule> Clone() const override;
+
+ virtual nsIDOMCSSRule* GetDOMRule() override;
+
+ virtual nsIDOMCSSRule* GetExistingDOMRule() override;
+
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+private:
+ ~StyleRule();
+
+private:
+ nsCSSSelectorList* mSelector; // null for style attribute
+ RefPtr<Declaration> mDeclaration;
+ RefPtr<DOMCSSStyleRule> mDOMRule;
+
+private:
+ StyleRule& operator=(const StyleRule& aCopy) = delete;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(StyleRule, NS_CSS_STYLE_RULE_IMPL_CID)
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* mozilla_css_StyleRule_h__ */
diff --git a/layout/style/StyleSetHandle.h b/layout/style/StyleSetHandle.h
new file mode 100644
index 000000000..ad75f75ae
--- /dev/null
+++ b/layout/style/StyleSetHandle.h
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_StyleSetHandle_h
+#define mozilla_StyleSetHandle_h
+
+#include "mozilla/EventStates.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SheetType.h"
+#include "mozilla/StyleBackendType.h"
+#include "mozilla/StyleSheet.h"
+#include "nsChangeHint.h"
+#include "nsCSSPseudoElements.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+class CSSStyleSheet;
+class ServoStyleSet;
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+class nsIAtom;
+class nsIContent;
+class nsIDocument;
+class nsStyleContext;
+class nsStyleSet;
+class nsPresContext;
+struct TreeMatchContext;
+
+namespace mozilla {
+
+#define SERVO_BIT 0x1
+
+/**
+ * Smart pointer class that can hold a pointer to either an nsStyleSet
+ * or a ServoStyleSet.
+ */
+class StyleSetHandle
+{
+public:
+ // We define this Ptr class with a StyleSet API that forwards on to the
+ // wrapped pointer, rather than putting these methods on StyleSetHandle
+ // itself, so that we can have StyleSetHandle behave like a smart pointer and
+ // be dereferenced with operator->.
+ class Ptr
+ {
+ public:
+ friend class ::mozilla::StyleSetHandle;
+
+ bool IsGecko() const { return !IsServo(); }
+ bool IsServo() const
+ {
+ MOZ_ASSERT(mValue, "StyleSetHandle null pointer dereference");
+#ifdef MOZ_STYLO
+ return mValue & SERVO_BIT;
+#else
+ return false;
+#endif
+ }
+
+ StyleBackendType BackendType() const
+ {
+ return IsGecko() ? StyleBackendType::Gecko :
+ StyleBackendType::Servo;
+ }
+
+ nsStyleSet* AsGecko()
+ {
+ MOZ_ASSERT(IsGecko());
+ return reinterpret_cast<nsStyleSet*>(mValue);
+ }
+
+ ServoStyleSet* AsServo()
+ {
+ MOZ_ASSERT(IsServo());
+ return reinterpret_cast<ServoStyleSet*>(mValue & ~SERVO_BIT);
+ }
+
+ nsStyleSet* GetAsGecko() { return IsGecko() ? AsGecko() : nullptr; }
+ ServoStyleSet* GetAsServo() { return IsServo() ? AsServo() : nullptr; }
+
+ const nsStyleSet* AsGecko() const
+ {
+ return const_cast<Ptr*>(this)->AsGecko();
+ }
+
+ const ServoStyleSet* AsServo() const
+ {
+ MOZ_ASSERT(IsServo());
+ return const_cast<Ptr*>(this)->AsServo();
+ }
+
+ const nsStyleSet* GetAsGecko() const { return IsGecko() ? AsGecko() : nullptr; }
+ const ServoStyleSet* GetAsServo() const { return IsServo() ? AsServo() : nullptr; }
+
+ // These inline methods are defined in StyleSetHandleInlines.h.
+ inline void Delete();
+
+ // Style set interface. These inline methods are defined in
+ // StyleSetHandleInlines.h and just forward to the underlying
+ // nsStyleSet or ServoStyleSet. See corresponding comments in
+ // nsStyleSet.h for descriptions of these methods.
+
+ inline void Init(nsPresContext* aPresContext);
+ inline void BeginShutdown();
+ inline void Shutdown();
+ inline bool GetAuthorStyleDisabled() const;
+ inline nsresult SetAuthorStyleDisabled(bool aStyleDisabled);
+ inline void BeginUpdate();
+ inline nsresult EndUpdate();
+ inline already_AddRefed<nsStyleContext>
+ ResolveStyleFor(dom::Element* aElement,
+ nsStyleContext* aParentContext);
+ inline already_AddRefed<nsStyleContext>
+ ResolveStyleFor(dom::Element* aElement,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext);
+ inline already_AddRefed<nsStyleContext>
+ ResolveStyleForText(nsIContent* aTextNode,
+ nsStyleContext* aParentContext);
+ inline already_AddRefed<nsStyleContext>
+ ResolveStyleForOtherNonElement(nsStyleContext* aParentContext);
+ inline already_AddRefed<nsStyleContext>
+ ResolvePseudoElementStyle(dom::Element* aParentElement,
+ mozilla::CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ dom::Element* aPseudoElement);
+ inline already_AddRefed<nsStyleContext>
+ ResolveAnonymousBoxStyle(nsIAtom* aPseudoTag, nsStyleContext* aParentContext,
+ uint32_t aFlags = 0);
+ inline nsresult AppendStyleSheet(SheetType aType, StyleSheet* aSheet);
+ inline nsresult PrependStyleSheet(SheetType aType, StyleSheet* aSheet);
+ inline nsresult RemoveStyleSheet(SheetType aType, StyleSheet* aSheet);
+ inline nsresult ReplaceSheets(SheetType aType,
+ const nsTArray<RefPtr<StyleSheet>>& aNewSheets);
+ inline nsresult InsertStyleSheetBefore(SheetType aType,
+ StyleSheet* aNewSheet,
+ StyleSheet* aReferenceSheet);
+ inline int32_t SheetCount(SheetType aType) const;
+ inline StyleSheet* StyleSheetAt(SheetType aType, int32_t aIndex) const;
+ inline nsresult RemoveDocStyleSheet(StyleSheet* aSheet);
+ inline nsresult AddDocStyleSheet(StyleSheet* aSheet, nsIDocument* aDocument);
+ inline already_AddRefed<nsStyleContext>
+ ProbePseudoElementStyle(dom::Element* aParentElement,
+ mozilla::CSSPseudoElementType aType,
+ nsStyleContext* aParentContext);
+ inline already_AddRefed<nsStyleContext>
+ ProbePseudoElementStyle(dom::Element* aParentElement,
+ mozilla::CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext,
+ dom::Element* aPseudoElement = nullptr);
+ inline nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
+ EventStates aStateMask);
+ inline nsRestyleHint HasStateDependentStyle(
+ dom::Element* aElement,
+ mozilla::CSSPseudoElementType aPseudoType,
+ dom::Element* aPseudoElement,
+ EventStates aStateMask);
+
+ inline void RootStyleContextAdded();
+ inline void RootStyleContextRemoved();
+
+ private:
+ // Stores a pointer to an nsStyleSet or a ServoStyleSet. The least
+ // significant bit is 0 for the former, 1 for the latter. This is
+ // valid as the least significant bit will never be used for a pointer
+ // value on platforms we care about.
+ uintptr_t mValue;
+ };
+
+ StyleSetHandle() { mPtr.mValue = 0; }
+ StyleSetHandle(const StyleSetHandle& aOth) { mPtr.mValue = aOth.mPtr.mValue; }
+ MOZ_IMPLICIT StyleSetHandle(nsStyleSet* aSet) { *this = aSet; }
+ MOZ_IMPLICIT StyleSetHandle(ServoStyleSet* aSet) { *this = aSet; }
+
+ StyleSetHandle& operator=(nsStyleSet* aStyleSet)
+ {
+ MOZ_ASSERT(!(reinterpret_cast<uintptr_t>(aStyleSet) & SERVO_BIT),
+ "least significant bit shouldn't be set; we use it for state");
+ mPtr.mValue = reinterpret_cast<uintptr_t>(aStyleSet);
+ return *this;
+ }
+
+ StyleSetHandle& operator=(ServoStyleSet* aStyleSet)
+ {
+#ifdef MOZ_STYLO
+ MOZ_ASSERT(!(reinterpret_cast<uintptr_t>(aStyleSet) & SERVO_BIT),
+ "least significant bit shouldn't be set; we use it for state");
+ mPtr.mValue =
+ aStyleSet ? (reinterpret_cast<uintptr_t>(aStyleSet) | SERVO_BIT) : 0;
+ return *this;
+#else
+ MOZ_CRASH("should not have a ServoStyleSet object when MOZ_STYLO is "
+ "disabled");
+#endif
+ }
+
+ // Make StyleSetHandle usable in boolean contexts.
+ explicit operator bool() const { return !!mPtr.mValue; }
+ bool operator!() const { return !mPtr.mValue; }
+
+ // Make StyleSetHandle behave like a smart pointer.
+ Ptr* operator->() { return &mPtr; }
+ const Ptr* operator->() const { return &mPtr; }
+
+private:
+ Ptr mPtr;
+};
+
+#undef SERVO_BIT
+
+} // namespace mozilla
+
+#endif // mozilla_StyleSetHandle_h
diff --git a/layout/style/StyleSetHandleInlines.h b/layout/style/StyleSetHandleInlines.h
new file mode 100644
index 000000000..2bb32101f
--- /dev/null
+++ b/layout/style/StyleSetHandleInlines.h
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_StyleSetHandleInlines_h
+#define mozilla_StyleSetHandleInlines_h
+
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/ServoStyleSet.h"
+#include "nsStyleSet.h"
+
+#define FORWARD_CONCRETE(method_, geckoargs_, servoargs_) \
+ if (IsGecko()) { \
+ return AsGecko()->method_ geckoargs_; \
+ } else { \
+ return AsServo()->method_ servoargs_; \
+ }
+
+#define FORWARD(method_, args_) FORWARD_CONCRETE(method_, args_, args_)
+
+namespace mozilla {
+
+void
+StyleSetHandle::Ptr::Delete()
+{
+ if (mValue) {
+ if (IsGecko()) {
+ delete AsGecko();
+ } else {
+ delete AsServo();
+ }
+ }
+}
+
+void
+StyleSetHandle::Ptr::Init(nsPresContext* aPresContext)
+{
+ FORWARD(Init, (aPresContext));
+}
+
+void
+StyleSetHandle::Ptr::BeginShutdown()
+{
+ FORWARD(BeginShutdown, ());
+}
+
+void
+StyleSetHandle::Ptr::Shutdown()
+{
+ FORWARD(Shutdown, ());
+}
+
+bool
+StyleSetHandle::Ptr::GetAuthorStyleDisabled() const
+{
+ FORWARD(GetAuthorStyleDisabled, ());
+}
+
+nsresult
+StyleSetHandle::Ptr::SetAuthorStyleDisabled(bool aStyleDisabled)
+{
+ FORWARD(SetAuthorStyleDisabled, (aStyleDisabled));
+}
+
+void
+StyleSetHandle::Ptr::BeginUpdate()
+{
+ FORWARD(BeginUpdate, ());
+}
+
+nsresult
+StyleSetHandle::Ptr::EndUpdate()
+{
+ FORWARD(EndUpdate, ());
+}
+
+// resolve a style context
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ResolveStyleFor(dom::Element* aElement,
+ nsStyleContext* aParentContext)
+{
+ FORWARD(ResolveStyleFor, (aElement, aParentContext));
+}
+
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ResolveStyleFor(dom::Element* aElement,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext)
+{
+ FORWARD(ResolveStyleFor, (aElement, aParentContext, aTreeMatchContext));
+}
+
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ResolveStyleForText(nsIContent* aTextNode,
+ nsStyleContext* aParentContext)
+{
+ FORWARD(ResolveStyleForText, (aTextNode, aParentContext));
+}
+
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ResolveStyleForOtherNonElement(nsStyleContext* aParentContext)
+{
+ FORWARD(ResolveStyleForOtherNonElement, (aParentContext));
+}
+
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ResolvePseudoElementStyle(dom::Element* aParentElement,
+ CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ dom::Element* aPseudoElement)
+{
+ FORWARD(ResolvePseudoElementStyle, (aParentElement, aType, aParentContext,
+ aPseudoElement));
+}
+
+// aFlags is an nsStyleSet flags bitfield
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ResolveAnonymousBoxStyle(nsIAtom* aPseudoTag,
+ nsStyleContext* aParentContext,
+ uint32_t aFlags)
+{
+ FORWARD(ResolveAnonymousBoxStyle, (aPseudoTag, aParentContext, aFlags));
+}
+
+// manage the set of style sheets in the style set
+nsresult
+StyleSetHandle::Ptr::AppendStyleSheet(SheetType aType, StyleSheet* aSheet)
+{
+ FORWARD_CONCRETE(AppendStyleSheet, (aType, aSheet->AsGecko()),
+ (aType, aSheet->AsServo()));
+}
+
+nsresult
+StyleSetHandle::Ptr::PrependStyleSheet(SheetType aType, StyleSheet* aSheet)
+{
+ FORWARD_CONCRETE(PrependStyleSheet, (aType, aSheet->AsGecko()),
+ (aType, aSheet->AsServo()));
+}
+
+nsresult
+StyleSetHandle::Ptr::RemoveStyleSheet(SheetType aType, StyleSheet* aSheet)
+{
+ FORWARD_CONCRETE(RemoveStyleSheet, (aType, aSheet->AsGecko()),
+ (aType, aSheet->AsServo()));
+}
+
+nsresult
+StyleSetHandle::Ptr::ReplaceSheets(SheetType aType,
+ const nsTArray<RefPtr<StyleSheet>>& aNewSheets)
+{
+ if (IsGecko()) {
+ nsTArray<RefPtr<CSSStyleSheet>> newSheets(aNewSheets.Length());
+ for (auto& sheet : aNewSheets) {
+ newSheets.AppendElement(sheet->AsGecko());
+ }
+ return AsGecko()->ReplaceSheets(aType, newSheets);
+ } else {
+ nsTArray<RefPtr<ServoStyleSheet>> newSheets(aNewSheets.Length());
+ for (auto& sheet : aNewSheets) {
+ newSheets.AppendElement(sheet->AsServo());
+ }
+ return AsServo()->ReplaceSheets(aType, newSheets);
+ }
+}
+
+nsresult
+StyleSetHandle::Ptr::InsertStyleSheetBefore(SheetType aType,
+ StyleSheet* aNewSheet,
+ StyleSheet* aReferenceSheet)
+{
+ FORWARD_CONCRETE(
+ InsertStyleSheetBefore,
+ (aType, aNewSheet->AsGecko(), aReferenceSheet->AsGecko()),
+ (aType, aReferenceSheet->AsServo(), aReferenceSheet->AsServo()));
+}
+
+int32_t
+StyleSetHandle::Ptr::SheetCount(SheetType aType) const
+{
+ FORWARD(SheetCount, (aType));
+}
+
+StyleSheet*
+StyleSetHandle::Ptr::StyleSheetAt(SheetType aType, int32_t aIndex) const
+{
+ FORWARD(StyleSheetAt, (aType, aIndex));
+}
+
+nsresult
+StyleSetHandle::Ptr::RemoveDocStyleSheet(StyleSheet* aSheet)
+{
+ FORWARD_CONCRETE(RemoveDocStyleSheet, (aSheet->AsGecko()),
+ (aSheet->AsServo()));
+}
+
+nsresult
+StyleSetHandle::Ptr::AddDocStyleSheet(StyleSheet* aSheet,
+ nsIDocument* aDocument)
+{
+ FORWARD_CONCRETE(AddDocStyleSheet, (aSheet->AsGecko(), aDocument),
+ (aSheet->AsServo(), aDocument));
+}
+
+// check whether there is ::before/::after style for an element
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ProbePseudoElementStyle(dom::Element* aParentElement,
+ CSSPseudoElementType aType,
+ nsStyleContext* aParentContext)
+{
+ FORWARD(ProbePseudoElementStyle, (aParentElement, aType, aParentContext));
+}
+
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ProbePseudoElementStyle(dom::Element* aParentElement,
+ CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext,
+ dom::Element* aPseudoElement)
+{
+ FORWARD(ProbePseudoElementStyle, (aParentElement, aType, aParentContext,
+ aTreeMatchContext, aPseudoElement));
+}
+
+nsRestyleHint
+StyleSetHandle::Ptr::HasStateDependentStyle(dom::Element* aElement,
+ EventStates aStateMask)
+{
+ FORWARD(HasStateDependentStyle, (aElement, aStateMask));
+}
+
+nsRestyleHint
+StyleSetHandle::Ptr::HasStateDependentStyle(dom::Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ dom::Element* aPseudoElement,
+ EventStates aStateMask)
+{
+ FORWARD(HasStateDependentStyle, (aElement, aPseudoType, aPseudoElement,
+ aStateMask));
+}
+
+void
+StyleSetHandle::Ptr::RootStyleContextAdded()
+{
+ if (IsGecko()) {
+ AsGecko()->RootStyleContextAdded();
+ } else {
+ // Not needed.
+ }
+}
+
+void
+StyleSetHandle::Ptr::RootStyleContextRemoved()
+{
+ if (IsGecko()) {
+ RootStyleContextAdded();
+ } else {
+ // Not needed.
+ }
+}
+
+} // namespace mozilla
+
+#undef FORWARD
+
+#endif // mozilla_StyleSetHandleInlines_h
diff --git a/layout/style/StyleSheet.cpp b/layout/style/StyleSheet.cpp
new file mode 100644
index 000000000..9ff90b8d2
--- /dev/null
+++ b/layout/style/StyleSheet.cpp
@@ -0,0 +1,329 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/CSSRuleList.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/ServoStyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/CSSStyleSheet.h"
+
+#include "nsNullPrincipal.h"
+
+namespace mozilla {
+
+StyleSheet::StyleSheet(StyleBackendType aType, css::SheetParsingMode aParsingMode)
+ : mDocument(nullptr)
+ , mOwningNode(nullptr)
+ , mParsingMode(aParsingMode)
+ , mType(aType)
+ , mDisabled(false)
+{
+}
+
+StyleSheet::StyleSheet(const StyleSheet& aCopy,
+ nsIDocument* aDocumentToUse,
+ nsINode* aOwningNodeToUse)
+ : mTitle(aCopy.mTitle)
+ , mDocument(aDocumentToUse)
+ , mOwningNode(aOwningNodeToUse)
+ , mParsingMode(aCopy.mParsingMode)
+ , mType(aCopy.mType)
+ , mDisabled(aCopy.mDisabled)
+{
+}
+
+// QueryInterface implementation for StyleSheet
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StyleSheet)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMStyleSheet)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSStyleSheet)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StyleSheet)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(StyleSheet)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(StyleSheet)
+
+mozilla::dom::CSSStyleSheetParsingMode
+StyleSheet::ParsingModeDOM()
+{
+#define CHECK(X, Y) \
+ static_assert(static_cast<int>(X) == static_cast<int>(Y), \
+ "mozilla::dom::CSSStyleSheetParsingMode and mozilla::css::SheetParsingMode should have identical values");
+
+ CHECK(mozilla::dom::CSSStyleSheetParsingMode::Agent, css::eAgentSheetFeatures);
+ CHECK(mozilla::dom::CSSStyleSheetParsingMode::User, css::eUserSheetFeatures);
+ CHECK(mozilla::dom::CSSStyleSheetParsingMode::Author, css::eAuthorSheetFeatures);
+
+#undef CHECK
+
+ return static_cast<mozilla::dom::CSSStyleSheetParsingMode>(mParsingMode);
+}
+
+bool
+StyleSheet::IsComplete() const
+{
+ return SheetInfo().mComplete;
+}
+
+void
+StyleSheet::SetComplete()
+{
+ NS_ASSERTION(!IsGecko() || !AsGecko()->mDirty,
+ "Can't set a dirty sheet complete!");
+ SheetInfo().mComplete = true;
+ if (mDocument && !mDisabled) {
+ // Let the document know
+ mDocument->BeginUpdate(UPDATE_STYLE);
+ mDocument->SetStyleSheetApplicableState(this, true);
+ mDocument->EndUpdate(UPDATE_STYLE);
+ }
+
+ if (mOwningNode && !mDisabled &&
+ mOwningNode->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
+ mOwningNode->IsContent()) {
+ dom::ShadowRoot* shadowRoot = mOwningNode->AsContent()->GetContainingShadow();
+ shadowRoot->StyleSheetChanged();
+ }
+}
+
+StyleSheetInfo::StyleSheetInfo(CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const dom::SRIMetadata& aIntegrity)
+ : mPrincipal(nsNullPrincipal::Create())
+ , mCORSMode(aCORSMode)
+ , mReferrerPolicy(aReferrerPolicy)
+ , mIntegrity(aIntegrity)
+ , mComplete(false)
+#ifdef DEBUG
+ , mPrincipalSet(false)
+#endif
+{
+ if (!mPrincipal) {
+ NS_RUNTIMEABORT("nsNullPrincipal::Init failed");
+ }
+}
+
+// nsIDOMStyleSheet interface
+
+NS_IMETHODIMP
+StyleSheet::GetType(nsAString& aType)
+{
+ aType.AssignLiteral("text/css");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StyleSheet::GetDisabled(bool* aDisabled)
+{
+ *aDisabled = Disabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StyleSheet::SetDisabled(bool aDisabled)
+{
+ // DOM method, so handle BeginUpdate/EndUpdate
+ MOZ_AUTO_DOC_UPDATE(mDocument, UPDATE_STYLE, true);
+ if (IsGecko()) {
+ AsGecko()->SetEnabled(!aDisabled);
+ } else {
+ MOZ_CRASH("stylo: unimplemented SetEnabled");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StyleSheet::GetOwnerNode(nsIDOMNode** aOwnerNode)
+{
+ nsCOMPtr<nsIDOMNode> ownerNode = do_QueryInterface(GetOwnerNode());
+ ownerNode.forget(aOwnerNode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StyleSheet::GetHref(nsAString& aHref)
+{
+ if (nsIURI* sheetURI = SheetInfo().mOriginalSheetURI) {
+ nsAutoCString str;
+ nsresult rv = sheetURI->GetSpec(str);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(str, aHref);
+ } else {
+ SetDOMStringToNull(aHref);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StyleSheet::GetTitle(nsAString& aTitle)
+{
+ aTitle.Assign(mTitle);
+ return NS_OK;
+}
+
+// nsIDOMStyleSheet interface
+
+NS_IMETHODIMP
+StyleSheet::GetParentStyleSheet(nsIDOMStyleSheet** aParentStyleSheet)
+{
+ NS_ENSURE_ARG_POINTER(aParentStyleSheet);
+ NS_IF_ADDREF(*aParentStyleSheet = GetParentStyleSheet());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StyleSheet::GetMedia(nsIDOMMediaList** aMedia)
+{
+ NS_ADDREF(*aMedia = Media());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StyleSheet::GetOwnerRule(nsIDOMCSSRule** aOwnerRule)
+{
+ NS_IF_ADDREF(*aOwnerRule = GetDOMOwnerRule());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StyleSheet::GetCssRules(nsIDOMCSSRuleList** aCssRules)
+{
+ ErrorResult rv;
+ nsCOMPtr<nsIDOMCSSRuleList> rules =
+ GetCssRules(*nsContentUtils::SubjectPrincipal(), rv);
+ rules.forget(aCssRules);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+StyleSheet::InsertRule(const nsAString& aRule, uint32_t aIndex,
+ uint32_t* aReturn)
+{
+ ErrorResult rv;
+ *aReturn =
+ InsertRule(aRule, aIndex, *nsContentUtils::SubjectPrincipal(), rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+StyleSheet::DeleteRule(uint32_t aIndex)
+{
+ ErrorResult rv;
+ DeleteRule(aIndex, *nsContentUtils::SubjectPrincipal(), rv);
+ return rv.StealNSResult();
+}
+
+// WebIDL CSSStyleSheet API
+
+#define FORWARD_INTERNAL(method_, args_) \
+ if (IsServo()) { \
+ return AsServo()->method_ args_; \
+ } \
+ return AsGecko()->method_ args_;
+
+dom::CSSRuleList*
+StyleSheet::GetCssRules(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ if (!AreRulesAvailable(aSubjectPrincipal, aRv)) {
+ return nullptr;
+ }
+ FORWARD_INTERNAL(GetCssRulesInternal, (aRv))
+}
+
+uint32_t
+StyleSheet::InsertRule(const nsAString& aRule, uint32_t aIndex,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ if (!AreRulesAvailable(aSubjectPrincipal, aRv)) {
+ return 0;
+ }
+ FORWARD_INTERNAL(InsertRuleInternal, (aRule, aIndex, aRv))
+}
+
+void
+StyleSheet::DeleteRule(uint32_t aIndex,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ if (!AreRulesAvailable(aSubjectPrincipal, aRv)) {
+ return;
+ }
+ FORWARD_INTERNAL(DeleteRuleInternal, (aIndex, aRv))
+}
+
+#undef FORWARD_INTERNAL
+
+void
+StyleSheet::SubjectSubsumesInnerPrincipal(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ StyleSheetInfo& info = SheetInfo();
+
+ if (aSubjectPrincipal.Subsumes(info.mPrincipal)) {
+ return;
+ }
+
+ // Allow access only if CORS mode is not NONE
+ if (GetCORSMode() == CORS_NONE) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ 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 (!info.mComplete) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return;
+ }
+
+ WillDirty();
+
+ info.mPrincipal = &aSubjectPrincipal;
+
+ DidDirty();
+}
+
+bool
+StyleSheet::AreRulesAvailable(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ // Rules are not available on incomplete sheets.
+ if (!SheetInfo().mComplete) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ 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;
+}
+
+// nsWrapperCache
+
+JSObject*
+StyleSheet::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return CSSStyleSheetBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla
diff --git a/layout/style/StyleSheet.h b/layout/style/StyleSheet.h
new file mode 100644
index 000000000..f21a6d648
--- /dev/null
+++ b/layout/style/StyleSheet.h
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StyleSheet_h
+#define mozilla_StyleSheet_h
+
+#include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/dom/CSSStyleSheetBinding.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/StyleBackendType.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/ServoUtils.h"
+
+#include "nsIDOMCSSStyleSheet.h"
+#include "nsWrapperCache.h"
+
+class nsIDocument;
+class nsINode;
+class nsIPrincipal;
+class nsMediaList;
+
+namespace mozilla {
+
+class CSSStyleSheet;
+class ServoStyleSheet;
+struct StyleSheetInfo;
+
+namespace dom {
+class CSSRuleList;
+class SRIMetadata;
+} // namespace dom
+
+/**
+ * Superclass for data common to CSSStyleSheet and ServoStyleSheet.
+ */
+class StyleSheet : public nsIDOMCSSStyleSheet
+ , public nsWrapperCache
+{
+protected:
+ StyleSheet(StyleBackendType aType, css::SheetParsingMode aParsingMode);
+ StyleSheet(const StyleSheet& aCopy,
+ nsIDocument* aDocumentToUse,
+ nsINode* aOwningNodeToUse);
+ virtual ~StyleSheet() {}
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StyleSheet)
+
+ void SetOwningNode(nsINode* aOwningNode)
+ {
+ mOwningNode = aOwningNode;
+ }
+
+ css::SheetParsingMode ParsingMode() { return mParsingMode; }
+ mozilla::dom::CSSStyleSheetParsingMode ParsingModeDOM();
+
+ // The document this style sheet is associated with. May be null
+ nsIDocument* GetDocument() const { return mDocument; }
+
+ /**
+ * Whether the sheet is complete.
+ */
+ bool IsComplete() const;
+ void SetComplete();
+
+ MOZ_DECL_STYLO_METHODS(CSSStyleSheet, ServoStyleSheet)
+
+ // Whether the sheet is for an inline <style> element.
+ inline bool IsInline() const;
+
+ inline nsIURI* GetSheetURI() const;
+ /* Get the URI this sheet was originally loaded from, if any. Can
+ return null */
+ inline nsIURI* GetOriginalURI() const;
+ inline nsIURI* GetBaseURI() const;
+ /**
+ * 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
+ */
+ 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.
+ */
+ inline bool IsApplicable() const;
+ inline bool HasRules() const;
+
+ // style sheet owner info
+ nsIDocument* GetOwningDocument() const { return mDocument; }
+ inline void SetOwningDocument(nsIDocument* aDocument);
+ nsINode* GetOwnerNode() const { return mOwningNode; }
+ inline StyleSheet* GetParentSheet() const;
+
+ inline void AppendStyleSheet(StyleSheet* aSheet);
+
+ // Principal() never returns a null pointer.
+ inline nsIPrincipal* Principal() const;
+ /**
+ * 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.
+ */
+ inline void SetPrincipal(nsIPrincipal* aPrincipal);
+
+ // Get this style sheet's CORS mode
+ inline CORSMode GetCORSMode() const;
+ // Get this style sheet's Referrer Policy
+ inline net::ReferrerPolicy GetReferrerPolicy() const;
+ // Get this style sheet's integrity metadata
+ inline void GetIntegrity(dom::SRIMetadata& aResult) const;
+
+ inline size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+#ifdef DEBUG
+ inline void List(FILE* aOut = stdout, int32_t aIndex = 0) const;
+#endif
+
+ // WebIDL StyleSheet API
+ // The XPCOM GetType is fine for WebIDL.
+ // The XPCOM GetHref is fine for WebIDL
+ // GetOwnerNode is defined above.
+ inline StyleSheet* GetParentStyleSheet() const;
+ // The XPCOM GetTitle is fine for WebIDL.
+ virtual nsMediaList* Media() = 0;
+ bool Disabled() const { return mDisabled; }
+ // The XPCOM SetDisabled is fine for WebIDL.
+
+ // WebIDL CSSStyleSheet API
+ virtual nsIDOMCSSRule* GetDOMOwnerRule() const = 0;
+ dom::CSSRuleList* GetCssRules(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+ uint32_t InsertRule(const nsAString& aRule, uint32_t aIndex,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+ void DeleteRule(uint32_t aIndex,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ // WebIDL miscellaneous bits
+ inline dom::ParentObject GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ // nsIDOMStyleSheet interface
+ NS_IMETHOD GetType(nsAString& aType) final;
+ NS_IMETHOD GetDisabled(bool* aDisabled) final;
+ NS_IMETHOD SetDisabled(bool aDisabled) final;
+ NS_IMETHOD GetOwnerNode(nsIDOMNode** aOwnerNode) final;
+ NS_IMETHOD GetParentStyleSheet(nsIDOMStyleSheet** aParentStyleSheet) final;
+ NS_IMETHOD GetHref(nsAString& aHref) final;
+ NS_IMETHOD GetTitle(nsAString& aTitle) final;
+ NS_IMETHOD GetMedia(nsIDOMMediaList** aMedia) final;
+
+ // nsIDOMCSSStyleSheet
+ NS_IMETHOD GetOwnerRule(nsIDOMCSSRule** aOwnerRule) final;
+ NS_IMETHOD GetCssRules(nsIDOMCSSRuleList** aCssRules) final;
+ NS_IMETHOD InsertRule(const nsAString& aRule, uint32_t aIndex,
+ uint32_t* aReturn) final;
+ NS_IMETHOD DeleteRule(uint32_t aIndex) final;
+
+ // Changes to sheets should be inside of a WillDirty-DidDirty pair.
+ // However, the calls do not need to be matched; it's ok to call
+ // WillDirty and then make no change and skip the DidDirty call.
+ inline void WillDirty();
+ inline void DidDirty();
+
+private:
+ // Get a handle to the various stylesheet bits which live on the 'inner' for
+ // gecko stylesheets and live on the StyleSheet for Servo stylesheets.
+ inline StyleSheetInfo& SheetInfo();
+ inline const StyleSheetInfo& SheetInfo() const;
+
+ // 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);
+
+protected:
+ // Return success if the subject principal subsumes the principal of our
+ // inner, error otherwise. This will also succeed if the subject has
+ // UniversalXPConnect or if access is allowed by CORS. In the latter case,
+ // it will set the principal of the inner to the subject principal.
+ void SubjectSubsumesInnerPrincipal(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ nsString mTitle;
+ nsIDocument* mDocument; // weak ref; parents maintain this for their children
+ nsINode* mOwningNode; // weak ref
+
+ // 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.
+ css::SheetParsingMode mParsingMode;
+
+ const StyleBackendType mType;
+ bool mDisabled;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_StyleSheet_h
diff --git a/layout/style/StyleSheetInfo.h b/layout/style/StyleSheetInfo.h
new file mode 100644
index 000000000..9d085fa81
--- /dev/null
+++ b/layout/style/StyleSheetInfo.h
@@ -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/. */
+
+#ifndef mozilla_StyleSheetInfo_h
+#define mozilla_StyleSheetInfo_h
+
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/CORSMode.h"
+
+#include "nsIURI.h"
+
+namespace mozilla {
+class CSSStyleSheet;
+} // namespace mozilla
+class nsCSSRuleProcessor;
+class nsIPrincipal;
+
+namespace mozilla {
+
+/**
+ * Struct for data common to CSSStyleSheetInner and ServoStyleSheet.
+ */
+struct StyleSheetInfo
+{
+ typedef net::ReferrerPolicy ReferrerPolicy;
+
+ StyleSheetInfo(CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const dom::SRIMetadata& aIntegrity);
+
+ 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;
+ CORSMode mCORSMode;
+ // The Referrer Policy of a stylesheet is used for its child sheets, so it is
+ // stored here.
+ ReferrerPolicy mReferrerPolicy;
+ dom::SRIMetadata mIntegrity;
+ bool mComplete;
+#ifdef DEBUG
+ bool mPrincipalSet;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // mozilla_StyleSheetInfo_h
diff --git a/layout/style/StyleSheetInlines.h b/layout/style/StyleSheetInlines.h
new file mode 100644
index 000000000..d03a3741b
--- /dev/null
+++ b/layout/style/StyleSheetInlines.h
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StyleSheetInlines_h
+#define mozilla_StyleSheetInlines_h
+
+#include "mozilla/StyleSheetInfo.h"
+#include "mozilla/ServoStyleSheet.h"
+#include "mozilla/CSSStyleSheet.h"
+
+namespace mozilla {
+
+MOZ_DEFINE_STYLO_METHODS(StyleSheet, CSSStyleSheet, ServoStyleSheet)
+
+StyleSheetInfo&
+StyleSheet::SheetInfo()
+{
+ if (IsServo()) {
+ return AsServo()->mSheetInfo;
+ }
+ return *AsGecko()->mInner;
+}
+
+const StyleSheetInfo&
+StyleSheet::SheetInfo() const
+{
+ if (IsServo()) {
+ return AsServo()->mSheetInfo;
+ }
+ return *AsGecko()->mInner;
+}
+
+bool
+StyleSheet::IsInline() const
+{
+ return !SheetInfo().mOriginalSheetURI;
+}
+
+nsIURI*
+StyleSheet::GetSheetURI() const
+{
+ return SheetInfo().mSheetURI;
+}
+
+nsIURI*
+StyleSheet::GetOriginalURI() const
+{
+ return SheetInfo().mOriginalSheetURI;
+}
+
+nsIURI*
+StyleSheet::GetBaseURI() const
+{
+ return SheetInfo().mBaseURI;
+}
+
+void
+StyleSheet::SetURIs(nsIURI* aSheetURI, nsIURI* aOriginalSheetURI,
+ nsIURI* aBaseURI)
+{
+ NS_PRECONDITION(aSheetURI && aBaseURI, "null ptr");
+ StyleSheetInfo& info = SheetInfo();
+ MOZ_ASSERT(!HasRules() && !info.mComplete,
+ "Can't call SetURIs on sheets that are complete or have rules");
+ info.mSheetURI = aSheetURI;
+ info.mOriginalSheetURI = aOriginalSheetURI;
+ info.mBaseURI = aBaseURI;
+}
+
+bool
+StyleSheet::IsApplicable() const
+{
+ return !mDisabled && SheetInfo().mComplete;
+}
+
+bool
+StyleSheet::HasRules() const
+{
+ MOZ_STYLO_FORWARD(HasRules, ())
+}
+
+void
+StyleSheet::SetOwningDocument(nsIDocument* aDocument)
+{
+ MOZ_STYLO_FORWARD(SetOwningDocument, (aDocument))
+}
+
+StyleSheet*
+StyleSheet::GetParentSheet() const
+{
+ MOZ_STYLO_FORWARD(GetParentSheet, ())
+}
+
+StyleSheet*
+StyleSheet::GetParentStyleSheet() const
+{
+ return GetParentSheet();
+}
+
+dom::ParentObject
+StyleSheet::GetParentObject() const
+{
+ if (mOwningNode) {
+ return dom::ParentObject(mOwningNode);
+ }
+ return dom::ParentObject(GetParentSheet());
+}
+
+void
+StyleSheet::AppendStyleSheet(StyleSheet* aSheet)
+{
+ MOZ_STYLO_FORWARD_CONCRETE(AppendStyleSheet,
+ (aSheet->AsGecko()), (aSheet->AsServo()))
+}
+
+nsIPrincipal*
+StyleSheet::Principal() const
+{
+ return SheetInfo().mPrincipal;
+}
+
+void
+StyleSheet::SetPrincipal(nsIPrincipal* aPrincipal)
+{
+ StyleSheetInfo& info = SheetInfo();
+ NS_PRECONDITION(!info.mPrincipalSet, "Should only set principal once");
+ if (aPrincipal) {
+ info.mPrincipal = aPrincipal;
+#ifdef DEBUG
+ info.mPrincipalSet = true;
+#endif
+ }
+}
+
+CORSMode
+StyleSheet::GetCORSMode() const
+{
+ return SheetInfo().mCORSMode;
+}
+
+net::ReferrerPolicy
+StyleSheet::GetReferrerPolicy() const
+{
+ return SheetInfo().mReferrerPolicy;
+}
+
+void
+StyleSheet::GetIntegrity(dom::SRIMetadata& aResult) const
+{
+ aResult = SheetInfo().mIntegrity;
+}
+
+size_t
+StyleSheet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ MOZ_STYLO_FORWARD(SizeOfIncludingThis, (aMallocSizeOf))
+}
+
+#ifdef DEBUG
+void
+StyleSheet::List(FILE* aOut, int32_t aIndex) const
+{
+ MOZ_STYLO_FORWARD(List, (aOut, aIndex))
+}
+#endif
+
+void StyleSheet::WillDirty() { MOZ_STYLO_FORWARD(WillDirty, ()) }
+void StyleSheet::DidDirty() { MOZ_STYLO_FORWARD(DidDirty, ()) }
+
+
+}
+
+#endif // mozilla_StyleSheetInlines_h
diff --git a/layout/style/StyleStructContext.h b/layout/style/StyleStructContext.h
new file mode 100644
index 000000000..f4b59972e
--- /dev/null
+++ b/layout/style/StyleStructContext.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StyleStructContext_h
+#define mozilla_StyleStructContext_h
+
+#include "CounterStyleManager.h"
+#include "mozilla/LookAndFeel.h"
+#include "nsPresContext.h"
+
+class nsDeviceContext;
+
+/**
+ * Construction context for style structs.
+ *
+ * Certain Gecko style structs have historically taken an nsPresContext
+ * argument in their constructor, which is used to compute various things. This
+ * makes Gecko style structs document-specific (which Servo style structs are
+ * not), and means that the initial values for style-structs cannot be shared
+ * across the entire runtime (as is the case in Servo).
+ *
+ * We'd like to remove this constraint so that Gecko can get the benefits of the
+ * Servo model, and so that Gecko aligns better with the Servo style system when
+ * using it. Unfortunately, this may require a fair amount of work, especially
+ * related to text zoom.
+ *
+ * So as an intermediate step, we define a reduced API set of "things that are
+ * needed when constructing style structs". This just wraps and forwards to an
+ * nsPresContext in the Gecko case, and returns some default not-always-correct
+ * values in the Servo case. We can then focus on reducing this API surface to
+ * zero, at which point this can be removed.
+ *
+ * We don't put the type in namespace mozilla, since we expect it to be
+ * temporary, and the namespacing would clutter up nsStyleStruct.h.
+ */
+#ifdef MOZ_STYLO
+#define SERVO_DEFAULT(default_val) { if (!mPresContext) { return default_val; } }
+#else
+#define SERVO_DEFAULT(default_val) { MOZ_ASSERT(mPresContext); }
+#endif
+class StyleStructContext {
+public:
+ MOZ_IMPLICIT StyleStructContext(nsPresContext* aPresContext)
+ : mPresContext(aPresContext) { MOZ_ASSERT(aPresContext); }
+ static StyleStructContext ServoContext() { return StyleStructContext(); }
+
+ // XXXbholley: Need to make text zoom work with stylo. This probably means
+ // moving the zoom handling out of computed values and into a post-
+ // computation.
+ float TextZoom()
+ {
+ SERVO_DEFAULT(1.0);
+ return mPresContext->TextZoom();
+ }
+
+ const nsFont* GetDefaultFont(uint8_t aFontID)
+ {
+ // NB: The servo case only differ from the default case in terms of which
+ // font pref cache gets used. The distinction is probably unnecessary.
+ SERVO_DEFAULT(mozilla::StaticPresData::Get()->
+ GetDefaultFont(aFontID, GetLanguageFromCharset()));
+ return mPresContext->GetDefaultFont(aFontID, GetLanguageFromCharset());
+ }
+
+ uint32_t GetBidi()
+ {
+ SERVO_DEFAULT(0);
+ return mPresContext->GetBidi();
+ }
+
+ int32_t AppUnitsPerDevPixel();
+
+ nscoord DevPixelsToAppUnits(int32_t aPixels)
+ {
+ return NSIntPixelsToAppUnits(aPixels, AppUnitsPerDevPixel());
+ }
+
+ typedef mozilla::LookAndFeel LookAndFeel;
+ nscolor DefaultColor()
+ {
+ SERVO_DEFAULT(LookAndFeel::GetColor(LookAndFeel::eColorID_WindowForeground,
+ NS_RGB(0x00, 0x00, 0x00)));
+ return mPresContext->DefaultColor();
+ }
+
+ mozilla::CounterStyle* BuildCounterStyle(const nsSubstring& aName)
+ {
+ SERVO_DEFAULT(mozilla::CounterStyleManager::GetBuiltinStyle(NS_STYLE_LIST_STYLE_DISC));
+ return mPresContext->CounterStyleManager()->BuildCounterStyle(aName);
+ }
+
+ nsIAtom* GetLanguageFromCharset() const
+ {
+ SERVO_DEFAULT(nsGkAtoms::x_western);
+ return mPresContext->GetLanguageFromCharset();
+ }
+
+ already_AddRefed<nsIAtom> GetContentLanguage() const
+ {
+ SERVO_DEFAULT(do_AddRef(nsGkAtoms::x_western));
+ return mPresContext->GetContentLanguage();
+ }
+
+private:
+ nsDeviceContext* DeviceContext()
+ {
+ SERVO_DEFAULT(HackilyFindSomeDeviceContext());
+ return mPresContext->DeviceContext();
+ }
+
+ nsDeviceContext* HackilyFindSomeDeviceContext();
+
+ StyleStructContext() : mPresContext(nullptr) {}
+ nsPresContext* mPresContext;
+};
+
+#undef SERVO_DEFAULT
+
+#endif // mozilla_StyleStructContext_h
diff --git a/layout/style/TopLevelImageDocument.css b/layout/style/TopLevelImageDocument.css
new file mode 100644
index 000000000..bfbf0dcbf
--- /dev/null
+++ b/layout/style/TopLevelImageDocument.css
@@ -0,0 +1,40 @@
+/* -*- 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 {
+ body {
+ margin: 0;
+ }
+
+ img {
+ text-align: center;
+ position: absolute;
+ margin: auto;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ }
+
+ 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 000000000..0f6f26749
--- /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).
+*/
+
+body {
+ height: 100%;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+video {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+ max-width: 100%;
+ max-height: 100%;
+ -moz-user-select: none;
+}
+
+video:focus {
+ outline-width: 0;
+}
diff --git a/layout/style/contenteditable.css b/layout/style/contenteditable.css
new file mode 100644
index 000000000..c550bc7c9
--- /dev/null
+++ b/layout/style/contenteditable.css
@@ -0,0 +1,343 @@
+/* -*- 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 */
+
+*|*::-moz-canvas {
+ cursor: text;
+}
+
+*|*:-moz-read-write :-moz-read-only {
+ -moz-user-select: all;
+}
+
+*|*:-moz-read-only > :-moz-read-write {
+ /* override the above -moz-user-select: all rule. */
+ -moz-user-select: -moz-text;
+}
+
+input:-moz-read-write > .anonymous-div:-moz-read-only,
+textarea:-moz-read-write > .anonymous-div:-moz-read-only {
+ -moz-user-select: text;
+}
+
+/* Use default arrow over objects with size that
+ are selected when clicked on.
+ Override the browser's pointer cursor over links
+*/
+
+img:-moz-read-write, img:-moz-read-write[usemap], area:-moz-read-write,
+object:-moz-read-write, object:-moz-read-write[usemap],
+applet:-moz-read-write, hr:-moz-read-write, button:-moz-read-write,
+select:-moz-read-write,
+a:-moz-read-write:link img, a:-moz-read-write:visited img,
+a:-moz-read-write:active img, a:-moz-read-write:-moz-only-whitespace[name] {
+ cursor: default;
+}
+
+*|*:any-link:-moz-read-write {
+ cursor: text;
+}
+
+/* Prevent clicking on links from going to link */
+a:link:-moz-read-write img, a:visited:-moz-read-write img,
+a:active:-moz-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:-moz-read-write {
+ text-decoration: underline -moz-anchor-decoration;
+ color: -moz-hyperlinktext;
+}
+
+/* Allow double-clicks on these widgets to open properties dialogs
+ XXX except when the widget has disabled attribute */
+*|*:-moz-read-write > input:-moz-read-only,
+*|*:-moz-read-write > button:-moz-read-only,
+*|*:-moz-read-write > textarea:-moz-read-only {
+ -moz-user-select: all;
+ -moz-user-input: auto !important;
+ -moz-user-focus: none !important;
+}
+
+/* XXX Still need a better way of blocking other events to these widgets */
+select:-moz-read-write,
+*|*:-moz-read-write > input:disabled,
+*|*:-moz-read-write > input[type="checkbox"],
+*|*:-moz-read-write > input[type="radio"],
+*|*:-moz-read-write > input[type="file"],
+input[contenteditable="true"]:disabled,
+input[contenteditable="true"][type="checkbox"],
+input[contenteditable="true"][type="radio"],
+input[contenteditable="true"][type="file"] {
+ -moz-user-select: all;
+ -moz-user-input: none !important;
+ -moz-user-focus: none !important;
+}
+
+/* emulation of non-standard HTML <marquee> tag */
+marquee:-moz-read-write {
+ -moz-binding: url('chrome://xbl-marquee/content/xbl-marquee.xml#marquee-horizontal-editable');
+}
+
+marquee[direction="up"]:-moz-read-write, marquee[direction="down"]:-moz-read-write {
+ -moz-binding: url('chrome://xbl-marquee/content/xbl-marquee.xml#marquee-vertical-editable');
+}
+
+*|*:-moz-read-write > input[type="hidden"],
+input[contenteditable="true"][type="hidden"] {
+ border: 1px solid black !important;
+ visibility: visible !important;
+}
+
+label:-moz-read-write {
+ -moz-user-select: all;
+}
+
+*|*::-moz-display-comboboxcontrol-frame {
+ -moz-user-select: text;
+}
+
+option:-moz-read-write {
+ -moz-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;
+ -moz-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;
+ -moz-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;
+ -moz-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;
+ -moz-user-select: none;
+ -moz-user-focus: none !important;
+}
+
+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;
+ -moz-user-select: none;
+ -moz-user-focus: none !important;
+}
+
+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;
+ -moz-user-select: none;
+ -moz-user-focus: none !important;
+}
+
+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;
+ -moz-user-select: none;
+ -moz-user-focus: none !important;
+}
+
+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;
+ -moz-user-select: none;
+ -moz-user-focus: none !important;
+}
+
+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;
+ -moz-user-select: none;
+ -moz-user-focus: none !important;
+}
+
+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 000000000..feb77d9dc
--- /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 000000000..097217d18
--- /dev/null
+++ b/layout/style/crashtests/1017798-1.html
@@ -0,0 +1,124 @@
+
+<!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(function nextTick() {
+ this.shadowRoot.appendChild(style.cloneNode(true));
+ }.bind(this));
+ }
+
+ };
+
+ exports.ComponentUtils = ComponentUtils;
+
+}(window));
+</script>
+<script>
+'use strict';
+/* global ComponentUtils */
+
+window.GaiaSwitch = (function(win) {
+ // Extend from the HTMLElement prototype
+ var proto = Object.create(HTMLElement.prototype);
+
+ proto.createdCallback = function() {
+ var shadow = this.createShadowRoot();
+ 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( proto, 'checked', {
+ get: function() {
+ return this._input.checked;
+ },
+ set: function(value) {
+ this._input.checked = value;
+ }
+ });
+
+ /**
+ * Proxy the name property to the input element.
+ */
+ Object.defineProperty( proto, '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><content select="label"></content></span>' +
+ '</label>';
+
+ // Register and return the constructor
+ return document.registerElement('gaia-switch', { prototype: proto });
+})(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 000000000..bbe7f3ba5
--- /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 000000000..27746f29a
--- /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 000000000..019a20a7f
--- /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 000000000..b76855cc1
--- /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 000000000..f7a2f5961
--- /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.createShadowRoot();
+ 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 000000000..920ec9c7b
--- /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 000000000..bdf63f9c0
--- /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 000000000..e3f8f2aa3
--- /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 000000000..8035f1b21
--- /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/1161320-1.html b/layout/style/crashtests/1161320-1.html
new file mode 100644
index 000000000..3cb3a7b45
--- /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 000000000..71db694d0
--- /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 000000000..d4eacccdc
--- /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 000000000..a3ca0c44d
--- /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 000000000..ee5a60ffd
--- /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 000000000..4b41ea398
--- /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 000000000..22608557d
--- /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 000000000..e2dc9c09d
--- /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 000000000..88af39e53
--- /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 000000000..70f9d8579
--- /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 000000000..c4589884f
--- /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 000000000..dea15642c
--- /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/1227501-1.html b/layout/style/crashtests/1227501-1.html
new file mode 100644
index 000000000..03383813d
--- /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/1230408-1.html b/layout/style/crashtests/1230408-1.html
new file mode 100644
index 000000000..0a2588c8a
--- /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 000000000..58a82f304
--- /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 000000000..03e356ae1
--- /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/1238660-1.html b/layout/style/crashtests/1238660-1.html
new file mode 100644
index 000000000..2323d2cfa
--- /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 000000000..6f2dda995
--- /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 000000000..b7ec8ba60
--- /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/1264396-1.html b/layout/style/crashtests/1264396-1.html
new file mode 100644
index 000000000..abba4de3b
--- /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 000000000..780bff97a
--- /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 000000000..7685ad633
--- /dev/null
+++ b/layout/style/crashtests/1265611-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!--
+ 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-test-fill-color.
+
+ This test exercises some code that, prior to this bug, would fail because we
+ would initially create the transition on -webkit-test-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 000000000..c4262078e
--- /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 000000000..7960d2889
--- /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 000000000..6f9ca3bd0
--- /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.removeChild(root.firstChild); }
+ 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.MozPerspectiveOrigin = "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 000000000..4c5266826
--- /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 000000000..da3b976d8
--- /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/1282076-1.html b/layout/style/crashtests/1282076-1.html
new file mode 100644
index 000000000..d5d1f0c74
--- /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 000000000..1c6f98639
--- /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 000000000..d9d99a5b0
--- /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 000000000..e592b84ee
--- /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 000000000..76589f5a6
--- /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 000000000..4278856d0
--- /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 000000000..8e804643f
--- /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 000000000..29186fac1
--- /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 000000000..e0192460f
--- /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/1321357-1.html b/layout/style/crashtests/1321357-1.html
new file mode 100644
index 000000000..7b3a3c39c
--- /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/1356601-1.html b/layout/style/crashtests/1356601-1.html
new file mode 100644
index 000000000..4fc28bd58
--- /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/147777-1.html b/layout/style/crashtests/147777-1.html
new file mode 100644
index 000000000..2500fae9f
--- /dev/null
+++ b/layout/style/crashtests/147777-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<title>crashtest for NS_ABORT_IF_FALSE during development of 147777</title>
+<style type="text/css">
+:link::before { content: "link: " }
+</style>
+<a href="http://www.example.com/">example</a>
diff --git a/layout/style/crashtests/187671-1.html b/layout/style/crashtests/187671-1.html
new file mode 100644
index 000000000..7395c23b4
--- /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 000000000..bb75e4401
--- /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 000000000..db1d28d8d
--- /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 000000000..7485a9644
--- /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 000000000..01335a17c
--- /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 000000000..46522094e
--- /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, false);
+
+</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 000000000..46bfb2f9c
--- /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], null).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 000000000..c7eeddf8c
--- /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; -moz-column-width:1em; -moz-column-gap:1em; width: 500px;'>
+ <p>滾滾長江東逝水,浪花淘盡英雄。是非成敗轉頭空:青山依舊在,幾度夕陽紅。</p>
+ <p style='direction: rtl; -moz-column-width:1em; -moz-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 000000000..ed315dd44
--- /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, null).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 000000000..40e38a7cc
--- /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 000000000..9a3f8e673
--- /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 000000000..06f900e12
--- /dev/null
+++ b/layout/style/crashtests/383979-2.html
@@ -0,0 +1,36 @@
+<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 {
+ -moz-image-region: auto;
+ 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 000000000..679d2b27e
--- /dev/null
+++ b/layout/style/crashtests/386939-1.html
@@ -0,0 +1,24 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function rM(n)
+{
+ n.parentNode.removeChild(n);
+}
+
+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 000000000..315796d28
--- /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 000000000..ececc3fa5
--- /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 000000000..583de2c24
--- /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 000000000..481ba4233
--- /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/vnd.mozilla.xul+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 000000000..596565fbc
--- /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 000000000..561aed564
--- /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 000000000..19d8fab0f
--- /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.xul b/layout/style/crashtests/416461-1.xul
new file mode 100644
index 000000000..1986cda91
--- /dev/null
+++ b/layout/style/crashtests/416461-1.xul
@@ -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; -moz-box-ordinal-group: 2147483646;"/>
+</window> \ No newline at end of file
diff --git a/layout/style/crashtests/418007-1.xhtml b/layout/style/crashtests/418007-1.xhtml
new file mode 100644
index 000000000..f07a69344
--- /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.xul b/layout/style/crashtests/431705-1.xul
new file mode 100644
index 000000000..8b64d4b3b
--- /dev/null
+++ b/layout/style/crashtests/431705-1.xul
@@ -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 000000000..81bb082e4
--- /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 000000000..af6fa1d66
--- /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 000000000..52eefa530
--- /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 000000000..f22660726
--- /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 000000000..7eac32ed8
--- /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 000000000..d2c75d576
--- /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 000000000..dde700fae
--- /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 000000000..05f12bf72
--- /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 -moz-column-count and word-wrap</title>
+</head><body>
+<div style="border: 1px solid black; word-wrap: normal; -moz-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 000000000..ef200462b
--- /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 000000000..41dc7800f
--- /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 000000000..4ff411c6d
--- /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 000000000..ed82ca0d4
--- /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.MozTransform = 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 000000000..d78235738
--- /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 000000000..e5918ad6e
--- /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 000000000..0bae55aaf
--- /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 000000000..f15c934f1
--- /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 -moz-transform</title>
+<style>
+#a::first-line { -moz-transform: translate(50px);}
+</style>
+</head>
+<body style="position: absolute;">
+<span style="position: absolute;" id="a">
+<span style="-moz-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 000000000..9b11a88c4
--- /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 000000000..0eff97e46
--- /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 000000000..0d0e273db
--- /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 000000000..a29316181
--- /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 000000000..362e38a6d
--- /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 000000000..47a7a9898
--- /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 000000000..519a753cf
--- /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 000000000..654e11c10
--- /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/495269-1.html b/layout/style/crashtests/495269-1.html
new file mode 100644
index 000000000..40090edc8
--- /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 000000000..8deca08e8
--- /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 000000000..0128be749
--- /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 000000000..d21137332
--- /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 000000000..f75776a5d
--- /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 000000000..41a3cc559
--- /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 000000000..d772390cd
--- /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 000000000..65686e818
--- /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, null).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 000000000..86fcb344f
--- /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 000000000..386d7e127
--- /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 000000000..e3d978fd3
--- /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 000000000..19126e560
--- /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 000000000..18c8add08
--- /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 000000000..4dce42dc8
--- /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 000000000..72fbaf63e
--- /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 000000000..e77649ed7
--- /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 000000000..4cb015393
--- /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, null).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 000000000..17eec73b3
--- /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 000000000..04684950c
--- /dev/null
+++ b/layout/style/crashtests/588627-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="-moz-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 000000000..9dcab0e32
--- /dev/null
+++ b/layout/style/crashtests/592698-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <iframe id="x"
+ src="data:text/html;charset=utf-8,%3Cdiv%20id%3D%22a%22%3Eaaa"></iframe>
+
+ <script>
+ window.onload = function() {
+ window.frames[0].document.getElementById("a").setAttribute("style",
+ '-moz-transition-property: color;' +
+ '-moz-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 000000000..8b5efd6cc
--- /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 000000000..e76e4520e
--- /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 000000000..be02ac2e7
--- /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 000000000..f6affa0de
--- /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/621596-1.html b/layout/style/crashtests/621596-1.html
new file mode 100644
index 000000000..8bfd68185
--- /dev/null
+++ b/layout/style/crashtests/621596-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.style.MozColumnWidth = "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 000000000..4daf23169
--- /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/637242.xhtml b/layout/style/crashtests/637242.xhtml
new file mode 100644
index 000000000..a8d99a732
--- /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 000000000..290355c18
--- /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/645951-1-ref.html b/layout/style/crashtests/645951-1-ref.html
new file mode 100644
index 000000000..7259e9603
--- /dev/null
+++ b/layout/style/crashtests/645951-1-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<title>Test, bug 645951</title>
+<body>
+<p style="color: green">Should not crash, and should be green too.</p>
diff --git a/layout/style/crashtests/645951-1.css b/layout/style/crashtests/645951-1.css
new file mode 100644
index 000000000..25ccf7a26
--- /dev/null
+++ b/layout/style/crashtests/645951-1.css
@@ -0,0 +1 @@
+@import url(chrome:///browser/skin/);
diff --git a/layout/style/crashtests/645951-1.html b/layout/style/crashtests/645951-1.html
new file mode 100644
index 000000000..a977e1ed4
--- /dev/null
+++ b/layout/style/crashtests/645951-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<title>Test, bug 645951</title>
+<link rel=stylesheet href="645951-1.css">
+<link id="two" rel=stylesheet href="645951-1.css">
+<body onload="run()">
+<script>
+function run() {
+ document.getElementById("two").sheet.insertRule("p { color: green}", 1);
+}
+</script>
+<p>Should not crash, and should be green too.</p>
diff --git a/layout/style/crashtests/652976-1.svg b/layout/style/crashtests/652976-1.svg
new file mode 100644
index 000000000..1ca6ee28e
--- /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/665209-1.html b/layout/style/crashtests/665209-1.html
new file mode 100644
index 000000000..30e8055eb
--- /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 000000000..cc89495b3
--- /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 000000000..a8398680e
--- /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 000000000..19520e4f9
--- /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/696188-1.html b/layout/style/crashtests/696188-1.html
new file mode 100644
index 000000000..e52a26747
--- /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", "-moz-transform: rotate3d(2, 3, 4, 45deg) scale(10);");
+ e.offsetHeight;
+ e.setAttribute("style", "-moz-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 000000000..e85a882a6
--- /dev/null
+++ b/layout/style/crashtests/696869-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<body style="-moz-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 000000000..de35ce702
--- /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 000000000..a5c50abd0
--- /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 000000000..63533ee30
--- /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 000000000..2962e7117
--- /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 000000000..1b2892040
--- /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 000000000..938c21675
--- /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 000000000..505b41076
--- /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/812824.html b/layout/style/crashtests/812824.html
new file mode 100644
index 000000000..5367b6c30
--- /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 000000000..77bb1e25a
--- /dev/null
+++ b/layout/style/crashtests/822766-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+@-moz-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/822842.html b/layout/style/crashtests/822842.html
new file mode 100644
index 000000000..a8c7acfbf
--- /dev/null
+++ b/layout/style/crashtests/822842.html
@@ -0,0 +1,13 @@
+<html>
+ <head></head>
+ <body></body>
+ <script type="text/javascript">
+ window.x = window.getComputedStyle(document.documentElement, null).getPropertyCSSValue("transition-timing-function");
+ window.x = window.getComputedStyle(document.documentElement, null).getPropertyCSSValue("color");
+ x.z = x.getRGBColorValue().blue;
+ x.getRGBColor().blue.d = x;
+ x= null;
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ </script>
+</html>
diff --git a/layout/style/crashtests/827591-1.html b/layout/style/crashtests/827591-1.html
new file mode 100644
index 000000000..a0ab10091
--- /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 000000000..a81fb6b79
--- /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/840898.html b/layout/style/crashtests/840898.html
new file mode 100644
index 000000000..352bf3c3d
--- /dev/null
+++ b/layout/style/crashtests/840898.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var styleDeclaration = window.getComputedStyle(document.createElement('div'), null);
+ var cursorValue = styleDeclaration.getPropertyCSSValue('cursor');
+ cursorValue.item(1000);
+}
+
+</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 000000000..f5bab1214
--- /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 000000000..bb394cef5
--- /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 000000000..319132b78
--- /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 000000000..a259690d1
--- /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 000000000..1ffcc623a
--- /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/880862.html b/layout/style/crashtests/880862.html
new file mode 100644
index 000000000..d89e24d90
--- /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/915440.html b/layout/style/crashtests/915440.html
new file mode 100644
index 000000000..f3a291f1f
--- /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 000000000..bd99f9b9e
--- /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 000000000..cbdf5a9e3
--- /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 000000000..6240ca20e
--- /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.parentNode.removeChild(div);
+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 000000000..753efb66f
--- /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 000000000..a4e0de0d8
--- /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 000000000..a2879d973
--- /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 000000000..86a1d57d7
--- /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 000000000..deefd19b2
--- /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 000000000..b6e3ae5d7
--- /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/crashtests.list b/layout/style/crashtests/crashtests.list
new file mode 100644
index 000000000..1ddb01d6b
--- /dev/null
+++ b/layout/style/crashtests/crashtests.list
@@ -0,0 +1,166 @@
+load 105619-1.html
+load 147777-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
+load 416461-1.xul
+load 418007-1.xhtml
+load 431705-1.xul
+load 432561-1.html
+load 437170-1.html
+load 437532-1.html
+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 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 621596-1.html
+load 622314-1.xhtml
+load 637242.xhtml
+load 645142.html
+== 645951-1.html 645951-1-ref.html
+load 652976-1.svg
+load 665209-1.html
+load 671799-1.html
+load 671799-2.html
+load 690990-1.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 812824.html
+load 822766-1.html
+load 822842.html
+load 827591-1.html
+load 829817.html
+load 840898.html
+load 842134.html
+load 861489-1.html
+load 862113.html
+load 867487.html
+load 873222.html
+load 880862.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
+pref(dom.webcomponents.enabled,true) load 1017798-1.html
+load 1028514-1.html
+load 1066089-1.html
+load 1074651-1.html
+load 1135534.html
+pref(dom.webcomponents.enabled,true) load 1089463-1.html
+pref(layout.css.expensive-style-struct-assertions.enabled,true) load 1136010-1.html
+pref(layout.css.expensive-style-struct-assertions.enabled,true) load 1146101-1.html
+load 1153693-1.html
+load 1161320-1.html
+pref(dom.animations-api.core.enabled,true) 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 1227501-1.html
+load 1230408-1.html
+load 1233135-1.html
+load 1233135-2.html
+load 1238660-1.html
+load 1245260-1.html
+load 1247865-1.html
+load 1264396-1.html
+# The following test relies on -webkit-text-fill-color being behind the
+# layout.css.prefixes.webkit pref
+pref(layout.css.prefixes.webkit,false) load 1265611-1.html
+load border-image-visited-link.html
+load font-face-truncated-src.html
+load large_border_image_width.html
+load long-url-list-stack-overflow.html
+pref(layout.css.background-clip-text.enabled,true) load 1264949.html
+pref(layout.css.background-clip-text.enabled,true) load 1270795.html
+pref(layout.css.background-clip-text.enabled,true) load 1275026.html
+load 1278463-1.html
+pref(dom.animations-api.core.enabled,true) load 1277908-1.html
+load 1277908-2.html
+load 1282076-1.html
+pref(dom.animations-api.core.enabled,true) load 1282076-2.html
+pref(dom.animations-api.core.enabled,true) load 1290994-1.html
+pref(dom.animations-api.core.enabled,true) load 1290994-2.html
+pref(dom.animations-api.core.enabled,true) load 1290994-3.html
+load 1290994-4.html
+load 1314531.html
+load 1315889-1.html
+load 1315894-1.html
+load 1321357-1.html
+load 1356601-1.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 000000000..c3d7dbda5
--- /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 000000000..915d94eb1
--- /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: -moz-linear-gradient(0rad, blue 25px, green 25px); border-image-width: 5464618830153;"></div>
+</body>
+</html>
+
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 000000000..899e858df
--- /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/designmode.css b/layout/style/designmode.css
new file mode 100644
index 000000000..194586ee1
--- /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/generate-stylestructlist.py b/layout/style/generate-stylestructlist.py
new file mode 100755
index 000000000..346a7deae
--- /dev/null
+++ b/layout/style/generate-stylestructlist.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env 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/.
+
+# This script generates nsStyleStructList.h, which contains macro invocations
+# that can be used for three things:
+#
+# 1. To generate code for each inherited style struct.
+# 2. To generate code for each reset style struct.
+# 3. To generate the dependency of each style struct.
+
+from __future__ import print_function
+
+import math
+
+NORMAL_DEP = ["Variables"]
+COLOR_DEP = ["Color"]
+LENGTH_DEP = ["Font", "Visibility"]
+
+# List of style structs and their corresponding Check callback functions,
+# if any.
+STYLE_STRUCTS = [("INHERITED",) + x for x in [
+ # Inherited style structs.
+ ("Font", "CheckFontCallback", NORMAL_DEP + ["Visibility"]),
+ ("Color", "CheckColorCallback", NORMAL_DEP),
+ ("List", "nullptr", NORMAL_DEP + LENGTH_DEP),
+ ("Text", "CheckTextCallback", NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
+ ("Visibility", "nullptr", NORMAL_DEP),
+ ("UserInterface", "nullptr", NORMAL_DEP),
+ ("TableBorder", "nullptr", NORMAL_DEP + LENGTH_DEP),
+ ("SVG", "nullptr", NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
+ ("Variables", "CheckVariablesCallback",[]),
+]] + [("RESET",) + x for x in [
+ # Reset style structs.
+ ("Background", "nullptr", NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
+ ("Position", "nullptr", NORMAL_DEP + LENGTH_DEP),
+ ("TextReset", "nullptr", NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
+ ("Display", "nullptr", NORMAL_DEP + LENGTH_DEP),
+ ("Content", "nullptr", NORMAL_DEP + LENGTH_DEP),
+ ("UIReset", "nullptr", NORMAL_DEP),
+ ("Table", "nullptr", NORMAL_DEP),
+ ("Margin", "nullptr", NORMAL_DEP + LENGTH_DEP),
+ ("Padding", "nullptr", NORMAL_DEP + LENGTH_DEP),
+ ("Border", "nullptr", NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
+ ("Outline", "nullptr", NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
+ ("XUL", "nullptr", NORMAL_DEP),
+ ("SVGReset", "nullptr", NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
+ ("Column", "nullptr", NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
+ ("Effects", "nullptr", NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
+]]
+
+
+# ---- Generate nsStyleStructList.h ----
+
+count = len(STYLE_STRUCTS)
+
+# Check for problems with style struct dependencies
+resolved_items = []
+# This whole loop tries to sort the style structs in topological order
+# according to the dependencies. A topological order exists iff there
+# are no cyclic dependencies between the style structs. It resolves one
+# struct each iteration, and append the resolved one to |resolved_items|.
+for i in range(count):
+ # This inner loop picks one style struct which does not have
+ # unsolved dependencies. If nothing can be picked, then we
+ # must have some cyclic dependencies.
+ for j in range(count):
+ _, name, _, dependencies = STYLE_STRUCTS[j]
+ if name in resolved_items:
+ continue
+ # Check whether all dependencies of this item have been placed
+ for dep in dependencies:
+ if dep not in resolved_items:
+ break
+ else:
+ resolved_items.append(name)
+ break
+ else:
+ import sys
+ print("ERROR: Cannot resolve style struct dependencies", file=sys.stderr)
+ print("Resolved items:", " ".join(resolved_items), file=sys.stderr)
+ unsolved_items = [name for _, name, _, _ in STYLE_STRUCTS
+ if name not in resolved_items]
+ print("There exist cyclic dependencies between " +
+ "the following structs:", " ".join(unsolved_items), file=sys.stderr)
+ exit(1)
+
+def printEntry(header, i):
+ print("STYLE_STRUCT_%s(%s, %s)" % STYLE_STRUCTS[i][:3], file=header)
+ for dep in STYLE_STRUCTS[i][3]:
+ print("STYLE_STRUCT_DEP(%s)" % (dep,), file=header)
+ print("STYLE_STRUCT_END()", file=header)
+
+HEADER = """/* THIS FILE IS AUTOGENERATED BY generate-stylestructlist.py - DO NOT EDIT */
+
+// IWYU pragma: private, include "nsStyleStructFwd.h"
+
+/*
+ * list of structs that contain the data provided by nsStyleContext, 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, checkdata_cb) \\
+ STYLE_STRUCT(name, checkdata_cb)
+#define UNDEF_STYLE_STRUCT_INHERITED
+#endif
+
+#ifndef STYLE_STRUCT_RESET
+#define STYLE_STRUCT_RESET(name, checkdata_cb) \\
+ STYLE_STRUCT(name, checkdata_cb)
+#define UNDEF_STYLE_STRUCT_RESET
+#endif
+
+#ifndef STYLE_STRUCT_DEP
+#define STYLE_STRUCT_DEP(dep)
+#define UNDEF_STYLE_STRUCT_DEP
+#endif
+
+#ifndef STYLE_STRUCT_END
+#define STYLE_STRUCT_END()
+#define UNDEF_STYLE_STRUCT_END
+#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.
+
+"""
+FOOTER = """
+#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
+
+#ifdef UNDEF_STYLE_STRUCT_DEP
+#undef STYLE_STRUCT_DEP
+#undef UNDEF_STYLE_STRUCT_DEP
+#endif
+
+#ifdef UNDEF_STYLE_STRUCT_END
+#undef STYLE_STRUCT_END
+#undef UNDEF_STYLE_STRUCT_END
+#endif
+"""
+
+def main(header):
+ print(HEADER, file=header)
+ for i in range(count):
+ printEntry(header, i)
+ print(FOOTER, file=header)
diff --git a/layout/style/jar.mn b/layout/style/jar.mn
new file mode 100644
index 000000000..af8c15d4f
--- /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/plaintext.css (res/plaintext.css)
+ res/viewsource.css (res/viewsource.css)
+ res/counterstyles.css (res/counterstyles.css)
+ res/noscript.css (res/noscript.css)
+ res/noframes.css (res/noframes.css)
+* res/forms.css (res/forms.css)
+ res/number-control.css (res/number-control.css)
+ res/arrow.gif (res/arrow.gif)
+ res/arrow-left.gif (res/arrow-left.gif)
+ res/arrow-right.gif (res/arrow-right.gif)
+ res/arrowd.gif (res/arrowd.gif)
+ res/arrowd-left.gif (res/arrowd-left.gif)
+ res/arrowd-right.gif (res/arrowd-right.gif)
+ 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)
+
+% resource gre-resources %res/
diff --git a/layout/style/moz.build b/layout/style/moz.build
new file mode 100644
index 000000000..3dc2a19af
--- /dev/null
+++ b/layout/style/moz.build
@@ -0,0 +1,264 @@
+# -*- 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')
+
+DIRS += ['xbl-marquee']
+TEST_DIRS += ['test']
+
+XPIDL_SOURCES += [
+ 'nsICSSUnprefixingService.idl',
+]
+
+XPIDL_MODULE = 'layout_base'
+
+EXPORTS += [
+ '!nsStyleStructList.h',
+ 'AnimationCommon.h',
+ 'CounterStyleManager.h',
+ 'nsAnimationManager.h',
+ 'nsComputedDOMStylePropertyList.h',
+ 'nsCSSAnonBoxes.h',
+ 'nsCSSAnonBoxList.h',
+ 'nsCSSCounterDescList.h',
+ 'nsCSSFontDescList.h',
+ 'nsCSSKeywordList.h',
+ 'nsCSSKeywords.h',
+ 'nsCSSParser.h',
+ 'nsCSSPropAliasList.h',
+ 'nsCSSPropertyID.h',
+ 'nsCSSPropertyIDSet.h',
+ 'nsCSSPropList.h',
+ 'nsCSSPropLogicalGroupList.h',
+ 'nsCSSProps.h',
+ 'nsCSSPseudoClasses.h',
+ 'nsCSSPseudoClassList.h',
+ 'nsCSSPseudoElementList.h',
+ 'nsCSSPseudoElements.h',
+ 'nsCSSRuleProcessor.h',
+ 'nsCSSScanner.h',
+ 'nsCSSValue.h',
+ 'nsDOMCSSAttrDeclaration.h',
+ 'nsDOMCSSDeclaration.h',
+ 'nsDOMCSSRGBColor.h',
+ 'nsICSSDeclaration.h',
+ 'nsICSSLoaderObserver.h',
+ 'nsICSSPseudoComparator.h',
+ 'nsICSSStyleRuleDOMWrapper.h',
+ 'nsIStyleRule.h',
+ 'nsIStyleRuleProcessor.h',
+ 'nsLayoutStylesheetCache.h',
+ 'nsRuleData.h',
+ 'nsRuleNode.h',
+ 'nsRuleProcessorData.h',
+ 'nsRuleWalker.h',
+ 'nsStyleConsts.h',
+ 'nsStyleContext.h',
+ 'nsStyleCoord.h',
+ 'nsStyleSet.h',
+ 'nsStyleStruct.h',
+ 'nsStyleStructFwd.h',
+ 'nsStyleStructInlines.h',
+ 'nsStyleTransformMatrix.h',
+ 'nsStyleUtil.h',
+]
+
+EXPORTS.mozilla += [
+ 'AnimationCollection.h',
+ 'CSSEnabledState.h',
+ 'CSSStyleSheet.h',
+ 'CSSVariableDeclarations.h',
+ 'CSSVariableResolver.h',
+ 'CSSVariableValues.h',
+ 'DeclarationBlock.h',
+ 'DeclarationBlockInlines.h',
+ 'HandleRefPtr.h',
+ 'IncrementalClearCOMRuleArray.h',
+ 'LayerAnimationInfo.h',
+ 'RuleNodeCacheConditions.h',
+ 'RuleProcessorCache.h',
+ 'ServoBindingList.h',
+ 'ServoBindings.h',
+ 'ServoBindingTypes.h',
+ 'ServoDeclarationBlock.h',
+ 'ServoElementSnapshot.h',
+ 'ServoStyleSet.h',
+ 'ServoStyleSheet.h',
+ 'ServoTypes.h',
+ 'ServoUtils.h',
+ 'SheetType.h',
+ 'StyleAnimationValue.h',
+ 'StyleBackendType.h',
+ 'StyleComplexColor.h',
+ 'StyleContextSource.h',
+ 'StyleSetHandle.h',
+ 'StyleSetHandleInlines.h',
+ 'StyleSheet.h',
+ 'StyleSheetInfo.h',
+ 'StyleSheetInlines.h',
+ 'StyleStructContext.h',
+]
+
+EXPORTS.mozilla.dom += [
+ 'CSS.h',
+ 'CSSLexer.h',
+ 'CSSRuleList.h',
+ 'CSSValue.h',
+ 'FontFace.h',
+ 'FontFaceSet.h',
+ 'FontFaceSetIterator.h',
+ 'MediaQueryList.h',
+]
+
+EXPORTS.mozilla.css += [
+ 'Declaration.h',
+ 'ErrorReporter.h',
+ 'GroupRule.h',
+ 'ImageLoader.h',
+ 'ImportRule.h',
+ 'Loader.h',
+ 'NameSpaceRule.h',
+ 'Rule.h',
+ 'SheetParsingMode.h',
+ 'StyleRule.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AnimationCollection.cpp',
+ 'AnimationCommon.cpp',
+ 'CounterStyleManager.cpp',
+ 'CSS.cpp',
+ 'CSSLexer.cpp',
+ 'CSSRuleList.cpp',
+ 'CSSStyleSheet.cpp',
+ 'CSSVariableDeclarations.cpp',
+ 'CSSVariableResolver.cpp',
+ 'CSSVariableValues.cpp',
+ 'Declaration.cpp',
+ 'ErrorReporter.cpp',
+ 'FontFace.cpp',
+ 'FontFaceSet.cpp',
+ 'FontFaceSetIterator.cpp',
+ 'ImageLoader.cpp',
+ 'IncrementalClearCOMRuleArray.cpp',
+ 'LayerAnimationInfo.cpp',
+ 'Loader.cpp',
+ 'MediaQueryList.cpp',
+ 'nsAnimationManager.cpp',
+ 'nsComputedDOMStyle.cpp',
+ 'nsCSSAnonBoxes.cpp',
+ 'nsCSSDataBlock.cpp',
+ 'nsCSSKeywords.cpp',
+ 'nsCSSParser.cpp',
+ 'nsCSSProps.cpp',
+ 'nsCSSPseudoClasses.cpp',
+ 'nsCSSPseudoElements.cpp',
+ 'nsCSSRules.cpp',
+ 'nsCSSScanner.cpp',
+ 'nsCSSValue.cpp',
+ 'nsDOMCSSAttrDeclaration.cpp',
+ 'nsDOMCSSDeclaration.cpp',
+ 'nsDOMCSSRect.cpp',
+ 'nsDOMCSSRGBColor.cpp',
+ 'nsDOMCSSValueList.cpp',
+ 'nsFontFaceLoader.cpp',
+ 'nsFontFaceUtils.cpp',
+ 'nsHTMLCSSStyleSheet.cpp',
+ 'nsHTMLStyleSheet.cpp',
+ 'nsMediaFeatures.cpp',
+ 'nsNthIndexCache.cpp',
+ 'nsROCSSPrimitiveValue.cpp',
+ 'nsRuleData.cpp',
+ 'nsRuleNode.cpp',
+ 'nsStyleContext.cpp',
+ 'nsStyleCoord.cpp',
+ 'nsStyleSet.cpp',
+ 'nsStyleStruct.cpp',
+ 'nsStyleTransformMatrix.cpp',
+ 'nsStyleUtil.cpp',
+ 'nsTransitionManager.cpp',
+ 'RuleNodeCacheConditions.cpp',
+ 'RuleProcessorCache.cpp',
+ 'ServoBindings.cpp',
+ 'ServoDeclarationBlock.cpp',
+ 'ServoElementSnapshot.cpp',
+ 'ServoStyleSet.cpp',
+ 'ServoStyleSheet.cpp',
+ 'StyleAnimationValue.cpp',
+ 'StyleRule.cpp',
+ 'StyleSheet.cpp',
+ 'SVGAttrAnimationRuleProcessor.cpp',
+]
+
+# nsCSSRuleProcessor.cpp needs to be built separately because it uses plarena.h.
+# nsLayoutStylesheetCache.cpp needs to be built separately because it uses
+# nsExceptionHandler.h, which includes windows.h.
+SOURCES += [
+ 'nsCSSRuleProcessor.cpp',
+ 'nsLayoutStylesheetCache.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'CSSUnprefixingService.js',
+ 'CSSUnprefixingService.manifest',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '../base',
+ '../generic',
+ '../svg',
+ '../xul',
+ '/dom/base',
+ '/dom/html',
+ '/dom/xbl',
+ '/dom/xul',
+ '/image',
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+RESOURCE_FILES += [
+ 'contenteditable.css',
+ 'designmode.css',
+ 'ImageDocument.css',
+ 'TopLevelImageDocument.css',
+ 'TopLevelVideoDocument.css',
+]
+
+GENERATED_FILES += [
+ 'nsStyleStructList.h',
+]
+
+style_struct_list = GENERATED_FILES['nsStyleStructList.h']
+style_struct_list.script = 'generate-stylestructlist.py'
+
+if CONFIG['COMPILE_ENVIRONMENT']:
+ GENERATED_FILES += [
+ 'nsCSSPropsGenerated.inc',
+ ]
+ css_props = GENERATED_FILES['nsCSSPropsGenerated.inc']
+ css_props.script = 'GenerateCSSPropsGenerated.py:generate'
+ css_props.inputs = [
+ 'nsCSSPropsGenerated.inc.in',
+ 'PythonCSSProps.h',
+ ]
diff --git a/layout/style/nsAnimationManager.cpp b/layout/style/nsAnimationManager.cpp
new file mode 100644
index 000000000..ed2b5afc7
--- /dev/null
+++ b/layout/style/nsAnimationManager.cpp
@@ -0,0 +1,1082 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAnimationManager.h"
+#include "nsTransitionManager.h"
+#include "mozilla/dom/CSSAnimationBinding.h"
+
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+
+#include "nsPresContext.h"
+#include "nsStyleSet.h"
+#include "nsStyleChangeList.h"
+#include "nsCSSRules.h"
+#include "mozilla/RestyleManager.h"
+#include "nsLayoutUtils.h"
+#include "nsIFrame.h"
+#include "nsIDocument.h"
+#include "nsDOMMutationObserver.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::KeyframeEffectReadOnly;
+using mozilla::dom::CSSAnimation;
+
+namespace {
+
+// Pair of an event message and elapsed time used when determining the set of
+// events to queue.
+typedef Pair<EventMessage, StickyTimeDuration> EventPair;
+
+} // anonymous namespace
+
+////////////////////////// CSSAnimation ////////////////////////////
+
+JSObject*
+CSSAnimation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::CSSAnimationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+mozilla::dom::Promise*
+CSSAnimation::GetReady(ErrorResult& aRv)
+{
+ FlushStyle();
+ return Animation::GetReady(aRv);
+}
+
+void
+CSSAnimation::Play(ErrorResult &aRv, LimitBehavior aLimitBehavior)
+{
+ mPauseShouldStick = false;
+ Animation::Play(aRv, aLimitBehavior);
+}
+
+void
+CSSAnimation::Pause(ErrorResult& aRv)
+{
+ mPauseShouldStick = true;
+ Animation::Pause(aRv);
+}
+
+AnimationPlayState
+CSSAnimation::PlayStateFromJS() const
+{
+ // Flush style to ensure that any properties controlling animation state
+ // (e.g. animation-play-state) are fully updated.
+ FlushStyle();
+ return Animation::PlayStateFromJS();
+}
+
+void
+CSSAnimation::PlayFromJS(ErrorResult& aRv)
+{
+ // Note that flushing style below might trigger calls to
+ // PlayFromStyle()/PauseFromStyle() on this object.
+ FlushStyle();
+ Animation::PlayFromJS(aRv);
+}
+
+void
+CSSAnimation::PlayFromStyle()
+{
+ mIsStylePaused = false;
+ if (!mPauseShouldStick) {
+ ErrorResult rv;
+ PlayNoUpdate(rv, Animation::LimitBehavior::Continue);
+ // play() should not throw when LimitBehavior is Continue
+ MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing animation");
+ }
+}
+
+void
+CSSAnimation::PauseFromStyle()
+{
+ // Check if the pause state is being overridden
+ if (mIsStylePaused) {
+ return;
+ }
+
+ mIsStylePaused = true;
+ ErrorResult rv;
+ PauseNoUpdate(rv);
+ // pause() should only throw when *all* of the following conditions are true:
+ // - we are in the idle state, and
+ // - we have a negative playback rate, and
+ // - we have an infinitely repeating animation
+ // The first two conditions will never happen under regular style processing
+ // but could happen if an author made modifications to the Animation object
+ // and then updated animation-play-state. It's an unusual case and there's
+ // no obvious way to pass on the exception information so we just silently
+ // fail for now.
+ if (rv.Failed()) {
+ NS_WARNING("Unexpected exception pausing animation - silently failing");
+ }
+}
+
+void
+CSSAnimation::Tick()
+{
+ Animation::Tick();
+ QueueEvents();
+}
+
+bool
+CSSAnimation::HasLowerCompositeOrderThan(const CSSAnimation& aOther) const
+{
+ MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
+ "Should only be called for CSS animations that are sorted "
+ "as CSS animations (i.e. tied to CSS markup)");
+
+ // 0. Object-equality case
+ if (&aOther == this) {
+ return false;
+ }
+
+ // 1. Sort by document order
+ if (!mOwningElement.Equals(aOther.mOwningElement)) {
+ return mOwningElement.LessThan(aOther.mOwningElement);
+ }
+
+ // 2. (Same element and pseudo): Sort by position in animation-name
+ return mAnimationIndex < aOther.mAnimationIndex;
+}
+
+void
+CSSAnimation::QueueEvents()
+{
+ if (!mEffect) {
+ return;
+ }
+
+ // If the animation is pending, we ignore animation events until we finish
+ // pending.
+ if (mPendingState != PendingState::NotPending) {
+ return;
+ }
+
+ // CSS animations dispatch events at their owning element. This allows
+ // script to repurpose a CSS animation to target a different element,
+ // to use a group effect (which has no obvious "target element"), or
+ // to remove the animation effect altogether whilst still getting
+ // animation events.
+ //
+ // It does mean, however, that for a CSS animation that has no owning
+ // element (e.g. it was created using the CSSAnimation constructor or
+ // disassociated from CSS) no events are fired. If it becomes desirable
+ // for these animations to still fire events we should spec the concept
+ // of the "original owning element" or "event target" and allow script
+ // to set it when creating a CSSAnimation object.
+ if (!mOwningElement.IsSet()) {
+ return;
+ }
+
+ dom::Element* owningElement;
+ CSSPseudoElementType owningPseudoType;
+ mOwningElement.GetElement(owningElement, owningPseudoType);
+ MOZ_ASSERT(owningElement, "Owning element should be set");
+
+ // Get the nsAnimationManager so we can queue events on it
+ nsPresContext* presContext = mOwningElement.GetRenderedPresContext();
+ if (!presContext) {
+ return;
+ }
+ nsAnimationManager* manager = presContext->AnimationManager();
+
+ ComputedTiming computedTiming = mEffect->GetComputedTiming();
+
+ if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Null) {
+ return; // do nothing
+ }
+
+ // Note that script can change the start time, so we have to handle moving
+ // backwards through the animation as well as forwards. An 'animationstart'
+ // is dispatched if we enter the active phase (regardless if that is from
+ // before or after the animation's active phase). An 'animationend' is
+ // dispatched if we leave the active phase (regardless if that is to before
+ // or after the animation's active phase).
+
+ bool wasActive = mPreviousPhaseOrIteration != PREVIOUS_PHASE_BEFORE &&
+ mPreviousPhaseOrIteration != PREVIOUS_PHASE_AFTER;
+ bool isActive =
+ computedTiming.mPhase == ComputedTiming::AnimationPhase::Active;
+ bool isSameIteration =
+ computedTiming.mCurrentIteration == mPreviousPhaseOrIteration;
+ bool skippedActivePhase =
+ (mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE &&
+ computedTiming.mPhase == ComputedTiming::AnimationPhase::After) ||
+ (mPreviousPhaseOrIteration == PREVIOUS_PHASE_AFTER &&
+ computedTiming.mPhase == ComputedTiming::AnimationPhase::Before);
+ bool skippedFirstIteration =
+ isActive &&
+ mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE &&
+ computedTiming.mCurrentIteration > 0;
+
+ MOZ_ASSERT(!skippedActivePhase || (!isActive && !wasActive),
+ "skippedActivePhase only makes sense if we were & are inactive");
+
+ if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Before) {
+ mPreviousPhaseOrIteration = PREVIOUS_PHASE_BEFORE;
+ } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Active) {
+ mPreviousPhaseOrIteration = computedTiming.mCurrentIteration;
+ } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase::After) {
+ mPreviousPhaseOrIteration = PREVIOUS_PHASE_AFTER;
+ }
+
+ AutoTArray<EventPair, 2> events;
+ StickyTimeDuration initialAdvance = StickyTimeDuration(InitialAdvance());
+ StickyTimeDuration iterationStart = computedTiming.mDuration *
+ computedTiming.mCurrentIteration;
+ const StickyTimeDuration& activeDuration = computedTiming.mActiveDuration;
+
+ if (skippedFirstIteration) {
+ // Notify animationstart and animationiteration in same tick.
+ events.AppendElement(EventPair(eAnimationStart, initialAdvance));
+ events.AppendElement(EventPair(eAnimationIteration,
+ std::max(iterationStart, initialAdvance)));
+ } else if (!wasActive && isActive) {
+ events.AppendElement(EventPair(eAnimationStart, initialAdvance));
+ } else if (wasActive && !isActive) {
+ events.AppendElement(EventPair(eAnimationEnd, activeDuration));
+ } else if (wasActive && isActive && !isSameIteration) {
+ events.AppendElement(EventPair(eAnimationIteration, iterationStart));
+ } else if (skippedActivePhase) {
+ events.AppendElement(EventPair(eAnimationStart,
+ std::min(initialAdvance, activeDuration)));
+ events.AppendElement(EventPair(eAnimationEnd, activeDuration));
+ } else {
+ return; // No events need to be sent
+ }
+
+ for (const EventPair& pair : events){
+ manager->QueueEvent(
+ AnimationEventInfo(owningElement, owningPseudoType,
+ pair.first(), mAnimationName,
+ pair.second(),
+ ElapsedTimeToTimeStamp(pair.second()),
+ this));
+ }
+}
+
+void
+CSSAnimation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
+{
+ if (mNeedsNewAnimationIndexWhenRun &&
+ PlayState() != AnimationPlayState::Idle) {
+ mAnimationIndex = sNextAnimationIndex++;
+ mNeedsNewAnimationIndexWhenRun = false;
+ }
+
+ Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
+}
+
+////////////////////////// nsAnimationManager ////////////////////////////
+
+NS_IMPL_CYCLE_COLLECTION(nsAnimationManager, mEventDispatcher)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsAnimationManager, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsAnimationManager, Release)
+
+// 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 nsAString& 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;
+}
+
+static void
+UpdateOldAnimationPropertiesWithNew(
+ CSSAnimation& aOld,
+ TimingParams& aNewTiming,
+ nsTArray<Keyframe>& aNewKeyframes,
+ bool aNewIsStylePaused,
+ nsStyleContext* aStyleContext)
+{
+ 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()) {
+ AnimationEffectReadOnly* oldEffect = aOld.GetEffect();
+ animationChanged = oldEffect->SpecifiedTiming() != aNewTiming;
+ oldEffect->SetSpecifiedTiming(aNewTiming);
+
+ KeyframeEffectReadOnly* oldKeyframeEffect = oldEffect->AsKeyframeEffect();
+ if (oldKeyframeEffect) {
+ oldKeyframeEffect->SetKeyframes(Move(aNewKeyframes), aStyleContext);
+ }
+ }
+
+ // 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) {
+ // CSSAnimation takes care of override behavior so that,
+ // for example, if the author has called pause(), that will
+ // override the animation-play-state.
+ // (We should check aNew->IsStylePaused() but that requires
+ // downcasting to CSSAnimation and we happen to know that
+ // aNew will only ever be paused by calling PauseFromStyle
+ // making IsPausedOrPausing synonymous in this case.)
+ if (!aOld.IsStylePaused() && aNewIsStylePaused) {
+ aOld.PauseFromStyle();
+ animationChanged = true;
+ } else if (aOld.IsStylePaused() && !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()) {
+ nsNodeUtils::AnimationChanged(&aOld);
+ }
+}
+
+void
+nsAnimationManager::UpdateAnimations(nsStyleContext* aStyleContext,
+ mozilla::dom::Element* aElement)
+{
+ 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");
+
+ // 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.
+
+ const nsStyleDisplay* disp = aStyleContext->StyleDisplay();
+ CSSAnimationCollection* collection =
+ CSSAnimationCollection::GetAnimationCollection(aElement,
+ aStyleContext->
+ GetPseudoType());
+ if (!collection &&
+ disp->mAnimationNameCount == 1 &&
+ disp->mAnimations[0].GetName().IsEmpty()) {
+ return;
+ }
+
+ nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
+
+ // Build the updated animations list, extracting matching animations from
+ // the existing collection as we go.
+ OwningCSSAnimationPtrArray newAnimations;
+ if (!aStyleContext->IsInDisplayNoneSubtree()) {
+ BuildAnimations(aStyleContext, aElement, collection, newAnimations);
+ }
+
+ if (newAnimations.IsEmpty()) {
+ if (collection) {
+ collection->Destroy();
+ }
+ return;
+ }
+
+ if (!collection) {
+ bool createdCollection = false;
+ collection =
+ CSSAnimationCollection::GetOrCreateAnimationCollection(
+ aElement, aStyleContext->GetPseudoType(), &createdCollection);
+ if (!collection) {
+ MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
+ NS_WARNING("allocating collection failed");
+ return;
+ }
+
+ if (createdCollection) {
+ AddElementCollection(collection);
+ }
+ }
+ collection->mAnimations.SwapElements(newAnimations);
+
+ // Cancel removed animations
+ for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0; ) {
+ newAnimations[newAnimIdx]->CancelFromStyle();
+ }
+
+ // We don't actually dispatch the pending events now. We'll either
+ // dispatch them the next time we get a refresh driver notification
+ // or the next time somebody calls
+ // nsPresShell::FlushPendingNotifications.
+ if (mEventDispatcher.HasQueuedEvents()) {
+ mPresContext->Document()->SetNeedStyleFlush();
+ }
+}
+
+void
+nsAnimationManager::StopAnimationsForElement(
+ mozilla::dom::Element* aElement,
+ mozilla::CSSPseudoElementType aPseudoType)
+{
+ MOZ_ASSERT(aElement);
+ CSSAnimationCollection* collection =
+ CSSAnimationCollection::GetAnimationCollection(aElement, aPseudoType);
+ if (!collection) {
+ return;
+ }
+
+ nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
+ collection->Destroy();
+}
+
+class ResolvedStyleCache {
+public:
+ ResolvedStyleCache() : mCache() {}
+ nsStyleContext* Get(nsPresContext *aPresContext,
+ nsStyleContext *aParentStyleContext,
+ Declaration* aKeyframeDeclaration);
+
+private:
+ nsRefPtrHashtable<nsPtrHashKey<Declaration>, nsStyleContext> mCache;
+};
+
+nsStyleContext*
+ResolvedStyleCache::Get(nsPresContext *aPresContext,
+ nsStyleContext *aParentStyleContext,
+ Declaration* aKeyframeDeclaration)
+{
+ // FIXME (spec): The css3-animations spec isn't very clear about how
+ // properties are resolved when they have values that depend on other
+ // properties (e.g., values in 'em'). I presume that they're resolved
+ // relative to the other styles of the element. The question is
+ // whether they are resolved relative to other animations: I assume
+ // that they're not, since that would prevent us from caching a lot of
+ // data that we'd really like to cache (in particular, the
+ // StyleAnimationValue values in AnimationPropertySegment).
+ nsStyleContext *result = mCache.GetWeak(aKeyframeDeclaration);
+ if (!result) {
+ aKeyframeDeclaration->SetImmutable();
+ // The spec says that !important declarations should just be ignored
+ MOZ_ASSERT(!aKeyframeDeclaration->HasImportantData(),
+ "Keyframe rule has !important data");
+
+ nsCOMArray<nsIStyleRule> rules;
+ rules.AppendObject(aKeyframeDeclaration);
+ MOZ_ASSERT(aPresContext->StyleSet()->IsGecko(),
+ "ServoStyleSet should not use nsAnimationManager for "
+ "animations");
+ RefPtr<nsStyleContext> resultStrong = aPresContext->StyleSet()->AsGecko()->
+ ResolveStyleByAddingRules(aParentStyleContext, rules);
+ mCache.Put(aKeyframeDeclaration, resultStrong);
+ result = resultStrong;
+ }
+ return result;
+}
+
+class MOZ_STACK_CLASS CSSAnimationBuilder final {
+public:
+ CSSAnimationBuilder(nsStyleContext* aStyleContext,
+ dom::Element* aTarget,
+ nsAnimationManager::CSSAnimationCollection* aCollection)
+ : mStyleContext(aStyleContext)
+ , mTarget(aTarget)
+ , mCollection(aCollection)
+ {
+ MOZ_ASSERT(aStyleContext);
+ MOZ_ASSERT(aTarget);
+ mTimeline = mTarget->OwnerDoc()->Timeline();
+ }
+
+ // Returns a new animation set up with given StyleAnimation and
+ // keyframe rules.
+ // Or returns an existing animation matching StyleAnimation's name updated
+ // with the new StyleAnimation and keyframe rules.
+ already_AddRefed<CSSAnimation>
+ Build(nsPresContext* aPresContext,
+ const StyleAnimation& aSrc,
+ const nsCSSKeyframesRule* aRule);
+
+private:
+ nsTArray<Keyframe> BuildAnimationFrames(nsPresContext* aPresContext,
+ const StyleAnimation& aSrc,
+ const nsCSSKeyframesRule* aRule);
+ Maybe<ComputedTimingFunction> GetKeyframeTimingFunction(
+ nsPresContext* aPresContext,
+ nsCSSKeyframeRule* aKeyframeRule,
+ const Maybe<ComputedTimingFunction>& aInheritedTimingFunction);
+ nsTArray<PropertyValuePair> GetKeyframePropertyValues(
+ nsPresContext* aPresContext,
+ nsCSSKeyframeRule* aKeyframeRule,
+ nsCSSPropertyIDSet& aAnimatedProperties);
+ void FillInMissingKeyframeValues(
+ nsPresContext* aPresContext,
+ nsCSSPropertyIDSet aAnimatedProperties,
+ nsCSSPropertyIDSet aPropertiesSetAtStart,
+ nsCSSPropertyIDSet aPropertiesSetAtEnd,
+ const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
+ nsTArray<Keyframe>& aKeyframes);
+ void AppendProperty(nsPresContext* aPresContext,
+ nsCSSPropertyID aProperty,
+ nsTArray<PropertyValuePair>& aPropertyValues);
+ nsCSSValue GetComputedValue(nsPresContext* aPresContext,
+ nsCSSPropertyID aProperty);
+
+ static TimingParams TimingParamsFrom(
+ const StyleAnimation& aStyleAnimation)
+ {
+ TimingParams timing;
+
+ timing.mDuration.emplace(StickyTimeDuration::FromMilliseconds(
+ aStyleAnimation.GetDuration()));
+ timing.mDelay = TimeDuration::FromMilliseconds(aStyleAnimation.GetDelay());
+ timing.mIterations = aStyleAnimation.GetIterationCount();
+ MOZ_ASSERT(timing.mIterations >= 0.0 && !IsNaN(timing.mIterations),
+ "mIterations should be nonnegative & finite, as ensured by "
+ "CSSParser");
+ timing.mDirection = aStyleAnimation.GetDirection();
+ timing.mFill = aStyleAnimation.GetFillMode();
+
+ return timing;
+ }
+
+ RefPtr<nsStyleContext> mStyleContext;
+ RefPtr<dom::Element> mTarget;
+ RefPtr<dom::DocumentTimeline> mTimeline;
+
+ ResolvedStyleCache mResolvedStyles;
+ RefPtr<nsStyleContext> mStyleWithoutAnimation;
+ // Existing collection, nullptr if the target element has no animations.
+ nsAnimationManager::CSSAnimationCollection* mCollection;
+};
+
+static Maybe<ComputedTimingFunction>
+ConvertTimingFunction(const nsTimingFunction& aTimingFunction);
+
+already_AddRefed<CSSAnimation>
+CSSAnimationBuilder::Build(nsPresContext* aPresContext,
+ const StyleAnimation& aSrc,
+ const nsCSSKeyframesRule* aRule)
+{
+ MOZ_ASSERT(aPresContext);
+ MOZ_ASSERT(aRule);
+
+ TimingParams timing = TimingParamsFrom(aSrc);
+
+ nsTArray<Keyframe> keyframes =
+ BuildAnimationFrames(aPresContext, aSrc, aRule);
+
+ bool isStylePaused =
+ aSrc.GetPlayState() == NS_STYLE_ANIMATION_PLAY_STATE_PAUSED;
+
+ // 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(aSrc.GetName(), mCollection);
+
+ 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,
+ timing,
+ keyframes,
+ isStylePaused,
+ mStyleContext);
+ return oldAnim.forget();
+ }
+
+ // mTarget is non-null here, so we emplace it directly.
+ Maybe<OwningAnimationTarget> target;
+ target.emplace(mTarget, mStyleContext->GetPseudoType());
+ KeyframeEffectParams effectOptions;
+ RefPtr<KeyframeEffectReadOnly> effect =
+ new KeyframeEffectReadOnly(aPresContext->Document(), target, timing,
+ effectOptions);
+
+ effect->SetKeyframes(Move(keyframes), mStyleContext);
+
+ RefPtr<CSSAnimation> animation =
+ new CSSAnimation(aPresContext->Document()->GetScopeObject(),
+ aSrc.GetName());
+ animation->SetOwningElement(
+ OwningElementRef(*mTarget, mStyleContext->GetPseudoType()));
+
+ animation->SetTimelineNoUpdate(mTimeline);
+ animation->SetEffectNoUpdate(effect);
+
+ if (isStylePaused) {
+ animation->PauseFromStyle();
+ } else {
+ animation->PlayFromStyle();
+ }
+
+ return animation.forget();
+}
+
+nsTArray<Keyframe>
+CSSAnimationBuilder::BuildAnimationFrames(nsPresContext* aPresContext,
+ const StyleAnimation& aSrc,
+ const nsCSSKeyframesRule* aRule)
+{
+ // Ideally we'd like to build up a set of Keyframe objects that more-or-less
+ // reflect the keyframes as-specified in the @keyframes rule(s) so that
+ // authors get something intuitive when they call anim.effect.getKeyframes().
+ //
+ // That, however, proves to be difficult because the way CSS declarations are
+ // processed differs from how we are able to represent keyframes as
+ // JavaScript objects in the Web Animations API.
+ //
+ // For example,
+ //
+ // { margin: 10px; margin-left: 20px }
+ //
+ // could be represented as:
+ //
+ // { margin: '10px', marginLeft: '20px' }
+ //
+ // BUT:
+ //
+ // { margin-left: 20px; margin: 10px }
+ //
+ // would be represented as:
+ //
+ // { margin: '10px' }
+ //
+ // Likewise,
+ //
+ // { margin-left: 20px; margin-left: 30px }
+ //
+ // would be represented as:
+ //
+ // { marginLeft: '30px' }
+ //
+ // As such, the mapping between source @keyframes and the Keyframe objects
+ // becomes obscured. The deviation is even more significant when we consider
+ // cascading between @keyframes rules and variable references in shorthand
+ // properties.
+ //
+ // We could, perhaps, produce a mapping that makes sense most of the time
+ // but it would be complex and need to be specified and implemented
+ // interoperably. Instead, for now, for CSS Animations (and CSS Transitions,
+ // for that matter) we resolve values on @keyframes down to computed values
+ // (thereby expanding shorthands and variable references) and then pick up the
+ // last value for each longhand property at each offset.
+
+ // FIXME: There is a pending spec change to make multiple @keyframes
+ // rules with the same name cascade but we don't support that yet.
+
+ Maybe<ComputedTimingFunction> inheritedTimingFunction =
+ ConvertTimingFunction(aSrc.GetTimingFunction());
+
+ // First, make up Keyframe objects for each rule
+ nsTArray<Keyframe> keyframes;
+ nsCSSPropertyIDSet animatedProperties;
+
+ for (auto ruleIdx = 0, ruleEnd = aRule->StyleRuleCount();
+ ruleIdx != ruleEnd; ++ruleIdx) {
+ css::Rule* cssRule = aRule->GetStyleRuleAt(ruleIdx);
+ MOZ_ASSERT(cssRule, "must have rule");
+ MOZ_ASSERT(cssRule->GetType() == css::Rule::KEYFRAME_RULE,
+ "must be keyframe rule");
+ nsCSSKeyframeRule* keyframeRule = static_cast<nsCSSKeyframeRule*>(cssRule);
+
+ const nsTArray<float>& keys = keyframeRule->GetKeys();
+ for (float key : keys) {
+ if (key < 0.0f || key > 1.0f) {
+ continue;
+ }
+
+ Keyframe keyframe;
+ keyframe.mOffset.emplace(key);
+ keyframe.mTimingFunction =
+ GetKeyframeTimingFunction(aPresContext, keyframeRule,
+ inheritedTimingFunction);
+ keyframe.mPropertyValues =
+ GetKeyframePropertyValues(aPresContext, keyframeRule,
+ animatedProperties);
+
+ keyframes.AppendElement(Move(keyframe));
+ }
+ }
+
+ // Next, stable sort by offset
+ std::stable_sort(keyframes.begin(), keyframes.end(),
+ [](const Keyframe& a, const Keyframe& b)
+ {
+ return a.mOffset < b.mOffset;
+ });
+
+ // Then walk backwards through the keyframes and drop overridden properties.
+ nsCSSPropertyIDSet propertiesSetAtCurrentOffset;
+ nsCSSPropertyIDSet propertiesSetAtStart;
+ nsCSSPropertyIDSet propertiesSetAtEnd;
+ double currentOffset = -1.0;
+ for (size_t keyframeIdx = keyframes.Length();
+ keyframeIdx > 0;
+ --keyframeIdx) {
+ Keyframe& keyframe = keyframes[keyframeIdx - 1];
+ MOZ_ASSERT(keyframe.mOffset, "Should have filled in the offset");
+
+ if (keyframe.mOffset.value() != currentOffset) {
+ propertiesSetAtCurrentOffset.Empty();
+ currentOffset = keyframe.mOffset.value();
+ }
+
+ // Get the set of properties from this keyframe that have not
+ // already been set at this offset.
+ nsTArray<PropertyValuePair> uniquePropertyValues;
+ uniquePropertyValues.SetCapacity(keyframe.mPropertyValues.Length());
+ for (const PropertyValuePair& pair : keyframe.mPropertyValues) {
+ if (!propertiesSetAtCurrentOffset.HasProperty(pair.mProperty)) {
+ uniquePropertyValues.AppendElement(pair);
+ propertiesSetAtCurrentOffset.AddProperty(pair.mProperty);
+
+ if (currentOffset == 0.0) {
+ propertiesSetAtStart.AddProperty(pair.mProperty);
+ } else if (currentOffset == 1.0) {
+ propertiesSetAtEnd.AddProperty(pair.mProperty);
+ }
+ }
+ }
+
+ // If we have a keyframe at the same offset with the same timing
+ // function we should merge our (unique) values into it.
+ // Otherwise, we should update the existing keyframe with only the
+ // unique properties.
+ //
+ // Bug 1293490: We should also match composite modes here.
+ Keyframe* existingKeyframe = nullptr;
+ // Don't bother searching for an existing keyframe if we don't
+ // have anything to contribute to it.
+ if (!uniquePropertyValues.IsEmpty()) {
+ for (size_t i = keyframeIdx; i < keyframes.Length(); i++) {
+ Keyframe& kf = keyframes[i];
+ if (kf.mOffset.value() != currentOffset) {
+ break;
+ }
+ if (kf.mTimingFunction == keyframe.mTimingFunction) {
+ existingKeyframe = &kf;
+ break;
+ }
+ }
+ }
+
+ if (existingKeyframe) {
+ existingKeyframe->
+ mPropertyValues.AppendElements(Move(uniquePropertyValues));
+ keyframe.mPropertyValues.Clear();
+ } else {
+ keyframe.mPropertyValues.SwapElements(uniquePropertyValues);
+ }
+
+ // Check for a now-empty keyframe
+ if (keyframe.mPropertyValues.IsEmpty()) {
+ keyframes.RemoveElementAt(keyframeIdx - 1);
+ // existingKeyframe might dangle now
+ }
+ }
+
+ // Finally, we need to look for any animated properties that have an
+ // implicit 'to' or 'from' value and fill in the appropriate keyframe
+ // with the current computed style.
+ FillInMissingKeyframeValues(aPresContext, animatedProperties,
+ propertiesSetAtStart, propertiesSetAtEnd,
+ inheritedTimingFunction, keyframes);
+
+ return keyframes;
+}
+
+Maybe<ComputedTimingFunction>
+CSSAnimationBuilder::GetKeyframeTimingFunction(
+ nsPresContext* aPresContext,
+ nsCSSKeyframeRule* aKeyframeRule,
+ const Maybe<ComputedTimingFunction>& aInheritedTimingFunction)
+{
+ Maybe<ComputedTimingFunction> result;
+
+ if (aKeyframeRule->Declaration() &&
+ aKeyframeRule->Declaration()->HasProperty(
+ eCSSProperty_animation_timing_function)) {
+ RefPtr<nsStyleContext> keyframeRuleContext =
+ mResolvedStyles.Get(aPresContext, mStyleContext,
+ aKeyframeRule->Declaration());
+ const nsTimingFunction& tf = keyframeRuleContext->StyleDisplay()->
+ mAnimations[0].GetTimingFunction();
+ result = ConvertTimingFunction(tf);
+ } else {
+ result = aInheritedTimingFunction;
+ }
+
+ return result;
+}
+
+static Maybe<ComputedTimingFunction>
+ConvertTimingFunction(const nsTimingFunction& aTimingFunction)
+{
+ Maybe<ComputedTimingFunction> result;
+
+ if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
+ result.emplace();
+ result->Init(aTimingFunction);
+ }
+
+ return result;
+}
+
+nsTArray<PropertyValuePair>
+CSSAnimationBuilder::GetKeyframePropertyValues(
+ nsPresContext* aPresContext,
+ nsCSSKeyframeRule* aKeyframeRule,
+ nsCSSPropertyIDSet& aAnimatedProperties)
+{
+ nsTArray<PropertyValuePair> result;
+ RefPtr<nsStyleContext> styleContext =
+ mResolvedStyles.Get(aPresContext, mStyleContext,
+ aKeyframeRule->Declaration());
+
+ for (nsCSSPropertyID prop = nsCSSPropertyID(0);
+ prop < eCSSProperty_COUNT_no_shorthands;
+ prop = nsCSSPropertyID(prop + 1)) {
+ if (nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None ||
+ !aKeyframeRule->Declaration()->HasNonImportantValueFor(prop)) {
+ continue;
+ }
+
+ PropertyValuePair pair;
+ pair.mProperty = prop;
+
+ StyleAnimationValue computedValue;
+ if (!StyleAnimationValue::ExtractComputedValue(prop, styleContext,
+ computedValue)) {
+ continue;
+ }
+ DebugOnly<bool> uncomputeResult =
+ StyleAnimationValue::UncomputeValue(prop, Move(computedValue),
+ pair.mValue);
+ MOZ_ASSERT(uncomputeResult,
+ "Unable to get specified value from computed value");
+ MOZ_ASSERT(pair.mValue.GetUnit() != eCSSUnit_Null,
+ "Not expecting to read invalid properties");
+
+ result.AppendElement(Move(pair));
+ aAnimatedProperties.AddProperty(prop);
+ }
+
+ return result;
+}
+
+// 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 Maybe<ComputedTimingFunction>& aTimingFunctionToMatch,
+ size_t& aIndex)
+{
+ aIndex = 0;
+ for (Keyframe& keyframe : aIter) {
+ if (keyframe.mOffset.value() != aOffset) {
+ break;
+ }
+ if (keyframe.mTimingFunction == aTimingFunctionToMatch) {
+ return true;
+ }
+ ++aIndex;
+ }
+ return false;
+}
+
+void
+CSSAnimationBuilder::FillInMissingKeyframeValues(
+ nsPresContext* aPresContext,
+ nsCSSPropertyIDSet aAnimatedProperties,
+ nsCSSPropertyIDSet aPropertiesSetAtStart,
+ nsCSSPropertyIDSet aPropertiesSetAtEnd,
+ const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
+ nsTArray<Keyframe>& aKeyframes)
+{
+ static const size_t kNotSet = static_cast<size_t>(-1);
+
+ // Find/create the keyframe to add start values to
+ size_t startKeyframeIndex = kNotSet;
+ if (!aAnimatedProperties.Equals(aPropertiesSetAtStart) &&
+ !FindMatchingKeyframe(aKeyframes, 0.0, aInheritedTimingFunction,
+ startKeyframeIndex)) {
+ Keyframe newKeyframe;
+ newKeyframe.mOffset.emplace(0.0);
+ newKeyframe.mTimingFunction = aInheritedTimingFunction;
+ aKeyframes.InsertElementAt(startKeyframeIndex, Move(newKeyframe));
+ }
+
+ // Find/create the keyframe to add end values to
+ size_t endKeyframeIndex = kNotSet;
+ if (!aAnimatedProperties.Equals(aPropertiesSetAtEnd)) {
+ if (!FindMatchingKeyframe(Reversed(aKeyframes), 1.0,
+ aInheritedTimingFunction, endKeyframeIndex)) {
+ Keyframe newKeyframe;
+ newKeyframe.mOffset.emplace(1.0);
+ newKeyframe.mTimingFunction = aInheritedTimingFunction;
+ aKeyframes.AppendElement(Move(newKeyframe));
+ endKeyframeIndex = aKeyframes.Length() - 1;
+ } else {
+ // endKeyframeIndex is currently a count from the end of the array
+ // so we need to reverse it.
+ endKeyframeIndex = aKeyframes.Length() - 1 - endKeyframeIndex;
+ }
+ }
+
+ if (startKeyframeIndex == kNotSet && endKeyframeIndex == kNotSet) {
+ return;
+ }
+
+ // Now that we have finished manipulating aKeyframes, it is safe to
+ // take pointers to its elements.
+ Keyframe* startKeyframe = startKeyframeIndex == kNotSet
+ ? nullptr : &aKeyframes[startKeyframeIndex];
+ Keyframe* endKeyframe = endKeyframeIndex == kNotSet
+ ? nullptr : &aKeyframes[endKeyframeIndex];
+
+ // Iterate through all properties and fill-in missing values
+ for (nsCSSPropertyID prop = nsCSSPropertyID(0);
+ prop < eCSSProperty_COUNT_no_shorthands;
+ prop = nsCSSPropertyID(prop + 1)) {
+ if (!aAnimatedProperties.HasProperty(prop)) {
+ continue;
+ }
+
+ if (startKeyframe && !aPropertiesSetAtStart.HasProperty(prop)) {
+ AppendProperty(aPresContext, prop, startKeyframe->mPropertyValues);
+ }
+ if (endKeyframe && !aPropertiesSetAtEnd.HasProperty(prop)) {
+ AppendProperty(aPresContext, prop, endKeyframe->mPropertyValues);
+ }
+ }
+}
+
+void
+CSSAnimationBuilder::AppendProperty(
+ nsPresContext* aPresContext,
+ nsCSSPropertyID aProperty,
+ nsTArray<PropertyValuePair>& aPropertyValues)
+{
+ PropertyValuePair propertyValue;
+ propertyValue.mProperty = aProperty;
+ propertyValue.mValue = GetComputedValue(aPresContext, aProperty);
+
+ aPropertyValues.AppendElement(Move(propertyValue));
+}
+
+nsCSSValue
+CSSAnimationBuilder::GetComputedValue(nsPresContext* aPresContext,
+ nsCSSPropertyID aProperty)
+{
+ nsCSSValue result;
+ StyleAnimationValue computedValue;
+
+ if (!mStyleWithoutAnimation) {
+ MOZ_ASSERT(aPresContext->StyleSet()->IsGecko(),
+ "ServoStyleSet should not use nsAnimationManager for "
+ "animations");
+ mStyleWithoutAnimation = aPresContext->StyleSet()->AsGecko()->
+ ResolveStyleWithoutAnimation(mTarget, mStyleContext,
+ eRestyle_AllHintsWithAnimations);
+ }
+
+ if (StyleAnimationValue::ExtractComputedValue(aProperty,
+ mStyleWithoutAnimation,
+ computedValue)) {
+ DebugOnly<bool> uncomputeResult =
+ StyleAnimationValue::UncomputeValue(aProperty, Move(computedValue),
+ result);
+ MOZ_ASSERT(uncomputeResult,
+ "Unable to get specified value from computed value");
+ }
+
+ // If we hit this assertion, it probably means we are fetching a value from
+ // the computed style that we don't know how to represent as
+ // a StyleAnimationValue.
+ MOZ_ASSERT(result.GetUnit() != eCSSUnit_Null, "Got null computed value");
+
+ return result;
+}
+
+void
+nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext,
+ dom::Element* aTarget,
+ CSSAnimationCollection* aCollection,
+ OwningCSSAnimationPtrArray& aAnimations)
+{
+ MOZ_ASSERT(aAnimations.IsEmpty(), "expect empty array");
+
+ const nsStyleDisplay *disp = aStyleContext->StyleDisplay();
+
+ CSSAnimationBuilder builder(aStyleContext, aTarget, aCollection);
+
+ for (size_t animIdx = disp->mAnimationNameCount; animIdx-- != 0;) {
+ const StyleAnimation& src = disp->mAnimations[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.
+ MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
+ "ServoStyleSet should not use nsAnimationManager for "
+ "animations");
+ nsCSSKeyframesRule* rule =
+ src.GetName().IsEmpty()
+ ? nullptr
+ : mPresContext->StyleSet()->AsGecko()->KeyframesRuleForName(src.GetName());
+ if (!rule) {
+ continue;
+ }
+
+ RefPtr<CSSAnimation> dest = builder.Build(mPresContext, src, rule);
+ dest->SetAnimationIndex(static_cast<uint64_t>(animIdx));
+ aAnimations.AppendElement(dest);
+ }
+}
+
diff --git a/layout/style/nsAnimationManager.h b/layout/style/nsAnimationManager.h
new file mode 100644
index 000000000..abe3aeeb8
--- /dev/null
+++ b/layout/style/nsAnimationManager.h
@@ -0,0 +1,361 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsAnimationManager_h_
+#define nsAnimationManager_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventForwards.h"
+#include "AnimationCommon.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIGlobalObject;
+class nsStyleContext;
+
+namespace mozilla {
+namespace css {
+class Declaration;
+} /* namespace css */
+namespace dom {
+class KeyframeEffectReadOnly;
+class Promise;
+} /* namespace dom */
+
+enum class CSSPseudoElementType : uint8_t;
+
+struct AnimationEventInfo {
+ RefPtr<dom::Element> mElement;
+ RefPtr<dom::Animation> mAnimation;
+ InternalAnimationEvent mEvent;
+ TimeStamp mTimeStamp;
+
+ AnimationEventInfo(dom::Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ EventMessage aMessage,
+ const nsSubstring& aAnimationName,
+ const StickyTimeDuration& aElapsedTime,
+ const TimeStamp& aTimeStamp,
+ dom::Animation* aAnimation)
+ : mElement(aElement)
+ , mAnimation(aAnimation)
+ , mEvent(true, aMessage)
+ , mTimeStamp(aTimeStamp)
+ {
+ // XXX Looks like nobody initialize WidgetEvent::time
+ mEvent.mAnimationName = aAnimationName;
+ mEvent.mElapsedTime = aElapsedTime.ToSeconds();
+ mEvent.mPseudoElement =
+ AnimationCollection<dom::CSSAnimation>::PseudoTypeAsString(aPseudoType);
+ }
+
+ // InternalAnimationEvent doesn't support copy-construction, so we need
+ // to ourselves in order to work with nsTArray
+ AnimationEventInfo(const AnimationEventInfo& aOther)
+ : mElement(aOther.mElement)
+ , mAnimation(aOther.mAnimation)
+ , mEvent(true, aOther.mEvent.mMessage)
+ , mTimeStamp(aOther.mTimeStamp)
+ {
+ mEvent.AssignAnimationEventData(aOther.mEvent, false);
+ }
+};
+
+namespace dom {
+
+class CSSAnimation final : public Animation
+{
+public:
+ explicit CSSAnimation(nsIGlobalObject* aGlobal,
+ const nsSubstring& aAnimationName)
+ : dom::Animation(aGlobal)
+ , mAnimationName(aAnimationName)
+ , mIsStylePaused(false)
+ , mPauseShouldStick(false)
+ , mNeedsNewAnimationIndexWhenRun(false)
+ , mPreviousPhaseOrIteration(PREVIOUS_PHASE_BEFORE)
+ {
+ // We might need to drop this assertion once we add a script-accessible
+ // constructor but for animations generated from CSS markup the
+ // animation-name should never be empty.
+ MOZ_ASSERT(!mAnimationName.IsEmpty(), "animation-name should not be empty");
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ CSSAnimation* AsCSSAnimation() override { return this; }
+ const CSSAnimation* AsCSSAnimation() const override { return this; }
+
+ // CSSAnimation interface
+ void GetAnimationName(nsString& aRetVal) const { aRetVal = mAnimationName; }
+
+ // Alternative to GetAnimationName that returns a reference to the member
+ // for more efficient internal usage.
+ const nsString& AnimationName() const { return mAnimationName; }
+
+ // Animation interface overrides
+ virtual Promise* GetReady(ErrorResult& aRv) override;
+ virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior) override;
+ virtual void Pause(ErrorResult& aRv) override;
+
+ virtual AnimationPlayState PlayStateFromJS() const override;
+ virtual void PlayFromJS(ErrorResult& aRv) override;
+
+ void PlayFromStyle();
+ void PauseFromStyle();
+ void CancelFromStyle() override
+ {
+ mOwningElement = OwningElementRef();
+
+ // When an animation is disassociated with style it enters an odd state
+ // where its composite order is undefined until it first transitions
+ // out of the idle state.
+ //
+ // Even if the composite order isn't defined we don't want it to be random
+ // in case we need to determine the order to dispatch events associated
+ // with an animation in this state. To solve this we treat the animation as
+ // if it had been added to the end of the global animation list so that
+ // its sort order is defined. We'll update this index again once the
+ // animation leaves the idle state.
+ mAnimationIndex = sNextAnimationIndex++;
+ mNeedsNewAnimationIndexWhenRun = true;
+
+ Animation::CancelFromStyle();
+ }
+
+ void Tick() override;
+ void QueueEvents();
+
+ bool IsStylePaused() const { return mIsStylePaused; }
+
+ bool HasLowerCompositeOrderThan(const CSSAnimation& aOther) const;
+
+ void SetAnimationIndex(uint64_t aIndex)
+ {
+ MOZ_ASSERT(IsTiedToMarkup());
+ if (IsRelevant() &&
+ mAnimationIndex != aIndex) {
+ nsNodeUtils::AnimationChanged(this);
+ PostUpdate();
+ }
+ mAnimationIndex = aIndex;
+ }
+
+ // Sets the owning element which is used for determining the composite
+ // order of CSSAnimation objects generated from CSS markup.
+ //
+ // @see mOwningElement
+ void SetOwningElement(const OwningElementRef& aElement)
+ {
+ mOwningElement = aElement;
+ }
+ // True for animations that are generated from CSS markup and continue to
+ // reflect changes to that markup.
+ bool IsTiedToMarkup() const { return mOwningElement.IsSet(); }
+
+protected:
+ virtual ~CSSAnimation()
+ {
+ MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared "
+ "before a CSS animation is destroyed");
+ }
+
+ // Animation overrides
+ void UpdateTiming(SeekFlag aSeekFlag,
+ SyncNotifyFlag aSyncNotifyFlag) override;
+
+ // Returns the duration from the start of the animation's source effect's
+ // active interval to the point where the animation actually begins playback.
+ // This is zero unless the animation's source effect has a negative delay in
+ // which case it is the absolute value of that delay.
+ // This is used for setting the elapsedTime member of CSS AnimationEvents.
+ TimeDuration InitialAdvance() const {
+ return mEffect ?
+ std::max(TimeDuration(), mEffect->SpecifiedTiming().mDelay * -1) :
+ TimeDuration();
+ }
+
+ nsString mAnimationName;
+
+ // The (pseudo-)element whose computed animation-name refers to this
+ // animation (if any).
+ //
+ // This is used for determining the relative composite order of animations
+ // generated from CSS markup.
+ //
+ // Typically this will be the same as the target element of the keyframe
+ // effect associated with this animation. However, it can differ in the
+ // following circumstances:
+ //
+ // a) If script removes or replaces the effect of this animation,
+ // b) If this animation is cancelled (e.g. by updating the
+ // animation-name property or removing the owning element from the
+ // document),
+ // c) If this object is generated from script using the CSSAnimation
+ // constructor.
+ //
+ // For (b) and (c) the owning element will return !IsSet().
+ OwningElementRef mOwningElement;
+
+ // When combining animation-play-state with play() / pause() the following
+ // behavior applies:
+ // 1. pause() is sticky and always overrides the underlying
+ // animation-play-state
+ // 2. If animation-play-state is 'paused', play() will temporarily override
+ // it until animation-play-state next becomes 'running'.
+ // 3. Calls to play() trigger finishing behavior but setting the
+ // animation-play-state to 'running' does not.
+ //
+ // This leads to five distinct states:
+ //
+ // A. Running
+ // B. Running and temporarily overriding animation-play-state: paused
+ // C. Paused and sticky overriding animation-play-state: running
+ // D. Paused and sticky overriding animation-play-state: paused
+ // E. Paused by animation-play-state
+ //
+ // C and D may seem redundant but they differ in how to respond to the
+ // sequence: call play(), set animation-play-state: paused.
+ //
+ // C will transition to A then E leaving the animation paused.
+ // D will transition to B then B leaving the animation running.
+ //
+ // A state transition chart is as follows:
+ //
+ // A | B | C | D | E
+ // ---------------------------
+ // play() A | B | A | B | B
+ // pause() C | D | C | D | D
+ // 'running' A | A | C | C | A
+ // 'paused' E | B | D | D | E
+ //
+ // The base class, Animation already provides a boolean value,
+ // mIsPaused which gives us two states. To this we add a further two booleans
+ // to represent the states as follows.
+ //
+ // A. Running
+ // (!mIsPaused; !mIsStylePaused; !mPauseShouldStick)
+ // B. Running and temporarily overriding animation-play-state: paused
+ // (!mIsPaused; mIsStylePaused; !mPauseShouldStick)
+ // C. Paused and sticky overriding animation-play-state: running
+ // (mIsPaused; !mIsStylePaused; mPauseShouldStick)
+ // D. Paused and sticky overriding animation-play-state: paused
+ // (mIsPaused; mIsStylePaused; mPauseShouldStick)
+ // E. Paused by animation-play-state
+ // (mIsPaused; mIsStylePaused; !mPauseShouldStick)
+ //
+ // (That leaves 3 combinations of the boolean values that we never set because
+ // they don't represent valid states.)
+ bool mIsStylePaused;
+ bool mPauseShouldStick;
+
+ // When true, indicates that when this animation next leaves the idle state,
+ // its animation index should be updated.
+ bool mNeedsNewAnimationIndexWhenRun;
+
+ enum {
+ PREVIOUS_PHASE_BEFORE = uint64_t(-1),
+ PREVIOUS_PHASE_AFTER = uint64_t(-2)
+ };
+ // One of the PREVIOUS_PHASE_* constants, or an integer for the iteration
+ // whose start we last notified on.
+ uint64_t mPreviousPhaseOrIteration;
+};
+
+} /* namespace dom */
+
+template <>
+struct AnimationTypeTraits<dom::CSSAnimation>
+{
+ static nsIAtom* ElementPropertyAtom()
+ {
+ return nsGkAtoms::animationsProperty;
+ }
+ static nsIAtom* BeforePropertyAtom()
+ {
+ return nsGkAtoms::animationsOfBeforeProperty;
+ }
+ static nsIAtom* AfterPropertyAtom()
+ {
+ return nsGkAtoms::animationsOfAfterProperty;
+ }
+};
+
+} /* namespace mozilla */
+
+class nsAnimationManager final
+ : public mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation>
+{
+public:
+ explicit nsAnimationManager(nsPresContext *aPresContext)
+ : mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation>(aPresContext)
+ {
+ }
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsAnimationManager)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsAnimationManager)
+
+ typedef mozilla::AnimationCollection<mozilla::dom::CSSAnimation>
+ CSSAnimationCollection;
+ typedef nsTArray<RefPtr<mozilla::dom::CSSAnimation>>
+ OwningCSSAnimationPtrArray;
+
+ /**
+ * Update the set of animations on |aElement| based on |aStyleContext|.
+ * If necessary, this will notify the corresponding EffectCompositor so
+ * that it can update its animation rule.
+ *
+ * aStyleContext may be a style context for aElement or for its
+ * :before or :after pseudo-element.
+ */
+ void UpdateAnimations(nsStyleContext* aStyleContext,
+ mozilla::dom::Element* aElement);
+
+ /**
+ * Add a pending event.
+ */
+ void QueueEvent(mozilla::AnimationEventInfo&& aEventInfo)
+ {
+ mEventDispatcher.QueueEvent(
+ mozilla::Forward<mozilla::AnimationEventInfo>(aEventInfo));
+ }
+
+ /**
+ * Dispatch any pending events. We accumulate animationend and
+ * animationiteration events only during refresh driver notifications
+ * (and dispatch them at the end of such notifications), but we
+ * accumulate animationstart events at other points when style
+ * contexts are created.
+ */
+ void DispatchEvents()
+ {
+ RefPtr<nsAnimationManager> kungFuDeathGrip(this);
+ mEventDispatcher.DispatchEvents(mPresContext);
+ }
+ void SortEvents() { mEventDispatcher.SortEvents(); }
+ void ClearEventQueue() { mEventDispatcher.ClearEventQueue(); }
+
+ // Stop animations on the element. This method takes the real element
+ // rather than the element for the generated content for animations on
+ // ::before and ::after.
+ void StopAnimationsForElement(mozilla::dom::Element* aElement,
+ mozilla::CSSPseudoElementType aPseudoType);
+
+protected:
+ ~nsAnimationManager() override = default;
+
+private:
+
+ void BuildAnimations(nsStyleContext* aStyleContext,
+ mozilla::dom::Element* aTarget,
+ CSSAnimationCollection* aCollection,
+ OwningCSSAnimationPtrArray& aAnimations);
+
+ mozilla::DelayedEventDispatcher<mozilla::AnimationEventInfo> mEventDispatcher;
+};
+
+#endif /* !defined(nsAnimationManager_h_) */
diff --git a/layout/style/nsCSSAnonBoxList.h b/layout/style/nsCSSAnonBoxList.h
new file mode 100644
index 000000000..bb44215f1
--- /dev/null
+++ b/layout/style/nsCSSAnonBoxList.h
@@ -0,0 +1,103 @@
+/* -*- 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/. */
+
+/* atom list for CSS anonymous boxes */
+
+/*
+ * This file contains the list of nsIAtoms 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 macro CSS_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 is the C++ identifier of
+ * the atom. The second argument is the string value of the atom.
+ */
+
+// OUTPUT_CLASS=nsCSSAnonBoxes
+// MACRO_NAME=CSS_ANON_BOX
+
+// ::-moz-text and ::-moz-other-non-element are non-elements which no
+// rule will match.
+CSS_ANON_BOX(mozText, ":-moz-text")
+// This anonymous box has two uses:
+// 1. placeholder frames,
+// 2. nsFirstLetterFrames for content outside the ::first-letter.
+CSS_ANON_BOX(mozOtherNonElement, ":-moz-other-non-element")
+
+CSS_ANON_BOX(mozAnonymousBlock, ":-moz-anonymous-block")
+CSS_ANON_BOX(mozAnonymousPositionedBlock, ":-moz-anonymous-positioned-block")
+CSS_ANON_BOX(mozMathMLAnonymousBlock, ":-moz-mathml-anonymous-block")
+CSS_ANON_BOX(mozXULAnonymousBlock, ":-moz-xul-anonymous-block")
+
+// Framesets
+CSS_ANON_BOX(horizontalFramesetBorder, ":-moz-hframeset-border")
+CSS_ANON_BOX(verticalFramesetBorder, ":-moz-vframeset-border")
+
+CSS_ANON_BOX(mozLineFrame, ":-moz-line-frame")
+
+CSS_ANON_BOX(buttonContent, ":-moz-button-content")
+CSS_ANON_BOX(mozButtonLabel, ":-moz-buttonlabel")
+CSS_ANON_BOX(cellContent, ":-moz-cell-content")
+CSS_ANON_BOX(dropDownList, ":-moz-dropdown-list")
+CSS_ANON_BOX(fieldsetContent, ":-moz-fieldset-content")
+CSS_ANON_BOX(framesetBlank, ":-moz-frameset-blank")
+CSS_ANON_BOX(mozDisplayComboboxControlFrame, ":-moz-display-comboboxcontrol-frame")
+CSS_ANON_BOX(htmlCanvasContent, ":-moz-html-canvas-content")
+
+CSS_ANON_BOX(inlineTable, ":-moz-inline-table")
+CSS_ANON_BOX(table, ":-moz-table")
+CSS_ANON_BOX(tableCell, ":-moz-table-cell")
+CSS_ANON_BOX(tableColGroup, ":-moz-table-column-group")
+CSS_ANON_BOX(tableCol, ":-moz-table-column")
+CSS_ANON_BOX(tableWrapper, ":-moz-table-wrapper")
+CSS_ANON_BOX(tableRowGroup, ":-moz-table-row-group")
+CSS_ANON_BOX(tableRow, ":-moz-table-row")
+
+CSS_ANON_BOX(canvas, ":-moz-canvas")
+CSS_ANON_BOX(pageBreak, ":-moz-pagebreak")
+CSS_ANON_BOX(page, ":-moz-page")
+CSS_ANON_BOX(pageContent, ":-moz-pagecontent")
+CSS_ANON_BOX(pageSequence, ":-moz-page-sequence")
+CSS_ANON_BOX(scrolledContent, ":-moz-scrolled-content")
+CSS_ANON_BOX(scrolledCanvas, ":-moz-scrolled-canvas")
+CSS_ANON_BOX(scrolledPageSequence, ":-moz-scrolled-page-sequence")
+CSS_ANON_BOX(columnContent, ":-moz-column-content")
+CSS_ANON_BOX(viewport, ":-moz-viewport")
+CSS_ANON_BOX(viewportScroll, ":-moz-viewport-scroll")
+
+// Inside a flex container, a contiguous run of text gets wrapped in
+// an anonymous block, which is then treated as a flex item.
+CSS_ANON_BOX(anonymousFlexItem, ":-moz-anonymous-flex-item")
+
+// Inside a grid container, a contiguous run of text gets wrapped in
+// an anonymous block, which is then treated as a grid item.
+CSS_ANON_BOX(anonymousGridItem, ":-moz-anonymous-grid-item")
+
+CSS_ANON_BOX(ruby, ":-moz-ruby")
+CSS_ANON_BOX(rubyBase, ":-moz-ruby-base")
+CSS_ANON_BOX(rubyBaseContainer, ":-moz-ruby-base-container")
+CSS_ANON_BOX(rubyText, ":-moz-ruby-text")
+CSS_ANON_BOX(rubyTextContainer, ":-moz-ruby-text-container")
+
+#ifdef MOZ_XUL
+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(moztreeprogressmeter, ":-moz-tree-progressmeter")
+CSS_ANON_BOX(moztreedropfeedback, ":-moz-tree-drop-feedback")
+#endif
+
+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")
diff --git a/layout/style/nsCSSAnonBoxes.cpp b/layout/style/nsCSSAnonBoxes.cpp
new file mode 100644
index 000000000..3585c9e38
--- /dev/null
+++ b/layout/style/nsCSSAnonBoxes.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* atom list for CSS anonymous boxes */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCSSAnonBoxes.h"
+#include "nsAtomListUtils.h"
+#include "nsStaticAtom.h"
+
+using namespace mozilla;
+
+// define storage for all atoms
+#define CSS_ANON_BOX(_name, _value) \
+ nsICSSAnonBoxPseudo* nsCSSAnonBoxes::_name;
+#include "nsCSSAnonBoxList.h"
+#undef CSS_ANON_BOX
+
+#define CSS_ANON_BOX(name_, value_) \
+ NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
+#include "nsCSSAnonBoxList.h"
+#undef CSS_ANON_BOX
+
+static const nsStaticAtom CSSAnonBoxes_info[] = {
+#define CSS_ANON_BOX(name_, value_) \
+ NS_STATIC_ATOM(name_##_buffer, (nsIAtom**)&nsCSSAnonBoxes::name_),
+#include "nsCSSAnonBoxList.h"
+#undef CSS_ANON_BOX
+};
+
+void nsCSSAnonBoxes::AddRefAtoms()
+{
+ NS_RegisterStaticAtoms(CSSAnonBoxes_info);
+}
+
+bool nsCSSAnonBoxes::IsAnonBox(nsIAtom *aAtom)
+{
+ return nsAtomListUtils::IsMember(aAtom, CSSAnonBoxes_info,
+ ArrayLength(CSSAnonBoxes_info));
+}
+
+#ifdef MOZ_XUL
+/* static */ bool
+nsCSSAnonBoxes::IsTreePseudoElement(nsIAtom* aPseudo)
+{
+ return StringBeginsWith(nsDependentAtomString(aPseudo),
+ NS_LITERAL_STRING(":-moz-tree-"));
+}
+#endif
diff --git a/layout/style/nsCSSAnonBoxes.h b/layout/style/nsCSSAnonBoxes.h
new file mode 100644
index 000000000..f631f8801
--- /dev/null
+++ b/layout/style/nsCSSAnonBoxes.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+/* atom list for CSS anonymous boxes */
+
+#ifndef nsCSSAnonBoxes_h___
+#define nsCSSAnonBoxes_h___
+
+#include "nsIAtom.h"
+
+// Empty class derived from nsIAtom so that function signatures can
+// require an atom from this atom list.
+class nsICSSAnonBoxPseudo : public nsIAtom {};
+
+class nsCSSAnonBoxes {
+public:
+
+ static void AddRefAtoms();
+
+ static bool IsAnonBox(nsIAtom *aAtom);
+#ifdef MOZ_XUL
+ static bool IsTreePseudoElement(nsIAtom* aPseudo);
+#endif
+ static bool IsNonElement(nsIAtom* aPseudo)
+ { return aPseudo == mozText || aPseudo == mozOtherNonElement; }
+
+#define CSS_ANON_BOX(_name, _value) static nsICSSAnonBoxPseudo* _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 000000000..03431b469
--- /dev/null
+++ b/layout/style/nsCSSCounterDescList.h
@@ -0,0 +1,15 @@
+/* -*- 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/. */
+
+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/nsCSSDataBlock.cpp b/layout/style/nsCSSDataBlock.cpp
new file mode 100644
index 000000000..fe2dc621a
--- /dev/null
+++ b/layout/style/nsCSSDataBlock.cpp
@@ -0,0 +1,802 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * compact representation of the property-value pairs within a CSS
+ * declaration, and the code for expanding and compacting it
+ */
+
+#include "nsCSSDataBlock.h"
+
+#include "CSSVariableImageTable.h"
+#include "mozilla/css/Declaration.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/WritingModes.h"
+#include "nsAutoPtr.h"
+#include "nsIDocument.h"
+#include "nsRuleData.h"
+#include "nsStyleContext.h"
+#include "nsStyleSet.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+
+/**
+ * Does a fast move of aSource to aDest. The previous value in
+ * aDest is cleanly destroyed, and aSource is cleared. Returns
+ * true if, before the copy, the value at aSource compared unequal
+ * to the value at aDest; false otherwise.
+ */
+static bool
+MoveValue(nsCSSValue* aSource, nsCSSValue* aDest)
+{
+ bool changed = (*aSource != *aDest);
+ aDest->~nsCSSValue();
+ memcpy(aDest, aSource, sizeof(nsCSSValue));
+ new (aSource) nsCSSValue();
+ return changed;
+}
+
+static bool
+ShouldIgnoreColors(nsRuleData *aRuleData)
+{
+ return aRuleData->mLevel != SheetType::Agent &&
+ aRuleData->mLevel != SheetType::User &&
+ !aRuleData->mPresContext->UseDocumentColors();
+}
+
+/**
+ * Tries to call |nsCSSValue::StartImageLoad()| on an image source.
+ * Image sources are specified by |url()| or |-moz-image-rect()| function.
+ */
+static void
+TryToStartImageLoadOnValue(const nsCSSValue& aValue, nsIDocument* aDocument,
+ nsStyleContext* aContext, nsCSSPropertyID aProperty,
+ bool aForTokenStream)
+{
+ MOZ_ASSERT(aDocument);
+
+ if (aValue.GetUnit() == eCSSUnit_URL) {
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ // The 'mask-image' property accepts local reference URIs.
+ // For example,
+ // mask-image: url(#mask_id); // refer to a SVG mask element, whose id is
+ // // "mask_id", in the current document.
+ // For such 'mask-image' values (pointing to an in-document element),
+ // there is no need to trigger image download.
+ if (aProperty == eCSSProperty_mask_image) {
+ // Filter out all fragment URLs.
+ // Since nsCSSValue::GetURLStructValue runs much faster than
+ // nsIURI::EqualsExceptRef bellow, we get performance gain by this
+ // early return.
+ URLValue* urlValue = aValue.GetURLStructValue();
+ if (urlValue->IsLocalRef()) {
+ return;
+ }
+
+ // Even though urlValue is not a fragment URL, it might still refer to
+ // an internal resource.
+ // For example, aDocument base URL is "http://foo/index.html" and
+ // intentionally references a mask-image at
+ // url(http://foo/index.html#mask) which still refers to a resource in
+ // aDocument.
+ nsIURI* imageURI = aValue.GetURLValue();
+ if (imageURI) {
+ nsIURI* docURI = aDocument->GetDocumentURI();
+ bool isEqualExceptRef = false;
+ nsresult rv = imageURI->EqualsExceptRef(docURI, &isEqualExceptRef);
+ if (NS_SUCCEEDED(rv) && isEqualExceptRef) {
+ return;
+ }
+ }
+ }
+#endif
+ aValue.StartImageLoad(aDocument);
+ if (aForTokenStream && aContext) {
+ CSSVariableImageTable::Add(aContext, aProperty,
+ aValue.GetImageStructValue());
+ }
+ }
+ else if (aValue.GetUnit() == eCSSUnit_Image) {
+ // If we already have a request, see if this document needs to clone it.
+ imgIRequest* request = aValue.GetImageValue(nullptr);
+
+ if (request) {
+ ImageValue* imageValue = aValue.GetImageStructValue();
+ aDocument->StyleImageLoader()->MaybeRegisterCSSImage(imageValue);
+ if (aForTokenStream && aContext) {
+ CSSVariableImageTable::Add(aContext, aProperty, imageValue);
+ }
+ }
+ }
+ else if (aValue.EqualsFunction(eCSSKeyword__moz_image_rect)) {
+ nsCSSValue::Array* arguments = aValue.GetArrayValue();
+ MOZ_ASSERT(arguments->Count() == 6, "unexpected num of arguments");
+
+ const nsCSSValue& image = arguments->Item(1);
+ TryToStartImageLoadOnValue(image, aDocument, aContext, aProperty,
+ aForTokenStream);
+ }
+}
+
+static void
+TryToStartImageLoad(const nsCSSValue& aValue, nsIDocument* aDocument,
+ nsStyleContext* aContext, nsCSSPropertyID aProperty,
+ bool aForTokenStream)
+{
+ if (aValue.GetUnit() == eCSSUnit_List) {
+ for (const nsCSSValueList* l = aValue.GetListValue(); l; l = l->mNext) {
+ TryToStartImageLoad(l->mValue, aDocument, aContext, aProperty,
+ aForTokenStream);
+ }
+ } else if (nsCSSProps::PropHasFlags(aProperty,
+ CSS_PROPERTY_IMAGE_IS_IN_ARRAY_0)) {
+ if (aValue.GetUnit() == eCSSUnit_Array) {
+ TryToStartImageLoadOnValue(aValue.GetArrayValue()->Item(0), aDocument,
+ aContext, aProperty, aForTokenStream);
+ }
+ } else {
+ TryToStartImageLoadOnValue(aValue, aDocument, aContext, aProperty,
+ aForTokenStream);
+ }
+}
+
+static inline bool
+ShouldStartImageLoads(nsRuleData *aRuleData, nsCSSPropertyID aProperty)
+{
+ // Don't initiate image loads for if-visited styles. This is
+ // important because:
+ // (1) it's a waste of CPU and bandwidth
+ // (2) in some cases we'd start the image load on a style change
+ // where we wouldn't have started the load initially, which makes
+ // which links are visited detectable to Web pages (see bug
+ // 557287)
+ return !aRuleData->mStyleContext->IsStyleIfVisited() &&
+ nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_START_IMAGE_LOADS);
+}
+
+static void
+MapSinglePropertyInto(nsCSSPropertyID aTargetProp,
+ const nsCSSValue* aSrcValue,
+ nsCSSValue* aTargetValue,
+ nsRuleData* aRuleData)
+{
+ MOZ_ASSERT(!nsCSSProps::PropHasFlags(aTargetProp, CSS_PROPERTY_LOGICAL),
+ "Can't map into a logical property");
+ MOZ_ASSERT(aSrcValue->GetUnit() != eCSSUnit_Null, "oops");
+
+ // Although aTargetValue is the nsCSSValue we are going to write into,
+ // we also look at its value before writing into it. This is done
+ // when aTargetValue is a token stream value, which is the case when we
+ // have just re-parsed a property that had a variable reference (in
+ // nsCSSParser::ParsePropertyWithVariableReferences). TryToStartImageLoad
+ // then records any resulting ImageValue objects in the
+ // CSSVariableImageTable, to give them the appropriate lifetime.
+ MOZ_ASSERT(aTargetValue->GetUnit() == eCSSUnit_TokenStream ||
+ aTargetValue->GetUnit() == eCSSUnit_Null,
+ "aTargetValue must only be a token stream (when re-parsing "
+ "properties with variable references) or null");
+
+ if (ShouldStartImageLoads(aRuleData, aTargetProp)) {
+ nsIDocument* doc = aRuleData->mPresContext->Document();
+ TryToStartImageLoad(*aSrcValue, doc, aRuleData->mStyleContext,
+ aTargetProp,
+ aTargetValue->GetUnit() == eCSSUnit_TokenStream);
+ }
+ *aTargetValue = *aSrcValue;
+ if (nsCSSProps::PropHasFlags(aTargetProp,
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED) &&
+ ShouldIgnoreColors(aRuleData))
+ {
+ if (aTargetProp == eCSSProperty_background_color) {
+ // Force non-'transparent' background
+ // colors to the user's default.
+ if (aTargetValue->IsNonTransparentColor()) {
+ aTargetValue->SetColorValue(aRuleData->mPresContext->
+ DefaultBackgroundColor());
+ }
+ } else {
+ // Ignore 'color', 'border-*-color', etc.
+ *aTargetValue = nsCSSValue();
+ }
+ }
+}
+
+/**
+ * If aProperty is a logical property, converts it to the equivalent physical
+ * property based on writing mode information obtained from aRuleData's
+ * style context.
+ */
+static inline void
+EnsurePhysicalProperty(nsCSSPropertyID& aProperty, nsRuleData* aRuleData)
+{
+ bool isAxisProperty =
+ nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_AXIS);
+ bool isBlock =
+ nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_BLOCK_AXIS);
+
+ int index;
+
+ if (isAxisProperty) {
+ LogicalAxis logicalAxis = isBlock ? eLogicalAxisBlock : eLogicalAxisInline;
+ uint8_t wm = aRuleData->mStyleContext->StyleVisibility()->mWritingMode;
+ PhysicalAxis axis =
+ WritingMode::PhysicalAxisForLogicalAxis(wm, logicalAxis);
+
+ // We rely on physical axis constants values matching the order of the
+ // physical properties in the logical group array.
+ static_assert(eAxisVertical == 0 && eAxisHorizontal == 1,
+ "unexpected axis constant values");
+ index = axis;
+ } else {
+ bool isEnd =
+ nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_END_EDGE);
+
+ LogicalEdge edge = isEnd ? eLogicalEdgeEnd : eLogicalEdgeStart;
+
+ // We handle block axis logical properties separately to save a bit of
+ // work that the WritingMode constructor does that is unnecessary
+ // unless we have an inline axis property.
+ mozilla::css::Side side;
+ if (isBlock) {
+ uint8_t wm = aRuleData->mStyleContext->StyleVisibility()->mWritingMode;
+ side = WritingMode::PhysicalSideForBlockAxis(wm, edge);
+ } else {
+ WritingMode wm(aRuleData->mStyleContext);
+ side = wm.PhysicalSideForInlineAxis(edge);
+ }
+
+ // We rely on the physical side constant values matching the order of
+ // the physical properties in the logical group array.
+ static_assert(NS_SIDE_TOP == 0 && NS_SIDE_RIGHT == 1 &&
+ NS_SIDE_BOTTOM == 2 && NS_SIDE_LEFT == 3,
+ "unexpected side constant values");
+ index = side;
+ }
+
+ const nsCSSPropertyID* props = nsCSSProps::LogicalGroup(aProperty);
+ size_t len = isAxisProperty ? 2 : 4;
+#ifdef DEBUG
+ for (size_t i = 0; i < len; i++) {
+ MOZ_ASSERT(props[i] != eCSSProperty_UNKNOWN,
+ "unexpected logical group length");
+ }
+ MOZ_ASSERT(props[len] == eCSSProperty_UNKNOWN,
+ "unexpected logical group length");
+#endif
+
+ for (size_t i = 0; i < len; i++) {
+ if (aRuleData->ValueFor(props[i])->GetUnit() == eCSSUnit_Null) {
+ // A declaration of one of the logical properties in this logical
+ // group (but maybe not aProperty) would be the winning
+ // declaration in the cascade. This means that it's reasonably
+ // likely that this logical property could be the winning
+ // declaration in the cascade for some values of writing-mode,
+ // direction, and text-orientation. (It doesn't mean that for
+ // sure, though. For example, if this is a block-start logical
+ // property, and all but the bottom physical property were set.
+ // But the common case we want to hit here is logical declarations
+ // that are completely overridden by a shorthand.)
+ //
+ // If this logical property could be the winning declaration in
+ // the cascade for some values of writing-mode, direction, and
+ // text-orientation, then we have to fault the resulting style
+ // struct out of the rule tree. We can't cache anything on the
+ // rule tree if it depends on data from the style context, since
+ // data cached in the rule tree could be used with a style context
+ // with a different value of the depended-upon data.
+ uint8_t wm = WritingMode(aRuleData->mStyleContext).GetBits();
+ aRuleData->mConditions.SetWritingModeDependency(wm);
+ break;
+ }
+ }
+
+ aProperty = props[index];
+}
+
+void
+nsCSSCompressedDataBlock::MapRuleInfoInto(nsRuleData *aRuleData) const
+{
+ // If we have no data for these structs, then return immediately.
+ // This optimization should make us return most of the time, so we
+ // have to worry much less (although still some) about the speed of
+ // the rest of the function.
+ if (!(aRuleData->mSIDs & mStyleBits))
+ return;
+
+ // We process these in reverse order so that we end up mapping the
+ // right property when one can be expressed using both logical and
+ // physical property names.
+ for (uint32_t i = mNumProps; i-- > 0; ) {
+ nsCSSPropertyID iProp = PropertyAtIndex(i);
+ if (nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[iProp]) &
+ aRuleData->mSIDs) {
+ if (nsCSSProps::PropHasFlags(iProp, CSS_PROPERTY_LOGICAL)) {
+ EnsurePhysicalProperty(iProp, aRuleData);
+ }
+ nsCSSValue* target = aRuleData->ValueFor(iProp);
+ if (target->GetUnit() == eCSSUnit_Null) {
+ const nsCSSValue *val = ValueAtIndex(i);
+ // In order for variable resolution to have the right information
+ // about the stylesheet level of a value, that level needs to be
+ // stored on the token stream. We can't do that at creation time
+ // because the CSS parser (which creates the object) has no idea
+ // about the stylesheet level, so we do it here instead, where
+ // the rule walking will have just updated aRuleData.
+ if (val->GetUnit() == eCSSUnit_TokenStream) {
+ val->GetTokenStreamValue()->mLevel = aRuleData->mLevel;
+ }
+ MapSinglePropertyInto(iProp, val, target, aRuleData);
+ }
+ }
+ }
+}
+
+const nsCSSValue*
+nsCSSCompressedDataBlock::ValueFor(nsCSSPropertyID aProperty) const
+{
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
+ "Don't call for shorthands");
+
+ // If we have no data for this struct, then return immediately.
+ // This optimization should make us return most of the time, so we
+ // have to worry much less (although still some) about the speed of
+ // the rest of the function.
+ if (!(nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[aProperty]) &
+ mStyleBits))
+ return nullptr;
+
+ for (uint32_t i = 0; i < mNumProps; i++) {
+ if (PropertyAtIndex(i) == aProperty) {
+ return ValueAtIndex(i);
+ }
+ }
+
+ return nullptr;
+}
+
+bool
+nsCSSCompressedDataBlock::TryReplaceValue(nsCSSPropertyID aProperty,
+ nsCSSExpandedDataBlock& aFromBlock,
+ bool *aChanged)
+{
+ nsCSSValue* newValue = aFromBlock.PropertyAt(aProperty);
+ MOZ_ASSERT(newValue && newValue->GetUnit() != eCSSUnit_Null,
+ "cannot replace with empty value");
+
+ const nsCSSValue* oldValue = ValueFor(aProperty);
+ if (!oldValue) {
+ *aChanged = false;
+ return false;
+ }
+
+ *aChanged = MoveValue(newValue, const_cast<nsCSSValue*>(oldValue));
+ aFromBlock.ClearPropertyBit(aProperty);
+ return true;
+}
+
+nsCSSCompressedDataBlock*
+nsCSSCompressedDataBlock::Clone() const
+{
+ nsAutoPtr<nsCSSCompressedDataBlock>
+ result(new(mNumProps) nsCSSCompressedDataBlock(mNumProps));
+
+ result->mStyleBits = mStyleBits;
+
+ for (uint32_t i = 0; i < mNumProps; i++) {
+ result->SetPropertyAtIndex(i, PropertyAtIndex(i));
+ result->CopyValueToIndex(i, ValueAtIndex(i));
+ }
+
+ return result.forget();
+}
+
+nsCSSCompressedDataBlock::~nsCSSCompressedDataBlock()
+{
+ for (uint32_t i = 0; i < mNumProps; i++) {
+#ifdef DEBUG
+ (void)PropertyAtIndex(i); // this checks the property is in range
+#endif
+ const nsCSSValue* val = ValueAtIndex(i);
+ MOZ_ASSERT(val->GetUnit() != eCSSUnit_Null, "oops");
+ val->~nsCSSValue();
+ }
+}
+
+/* static */ nsCSSCompressedDataBlock*
+nsCSSCompressedDataBlock::CreateEmptyBlock()
+{
+ nsCSSCompressedDataBlock *result = new(0) nsCSSCompressedDataBlock(0);
+ return result;
+}
+
+size_t
+nsCSSCompressedDataBlock::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ for (uint32_t i = 0; i < mNumProps; i++) {
+ n += ValueAtIndex(i)->SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+bool
+nsCSSCompressedDataBlock::HasDefaultBorderImageSlice() const
+{
+ const nsCSSValueList *slice =
+ ValueFor(eCSSProperty_border_image_slice)->GetListValue();
+ return !slice->mNext &&
+ slice->mValue.GetRectValue().AllSidesEqualTo(
+ nsCSSValue(1.0f, eCSSUnit_Percent));
+}
+
+bool
+nsCSSCompressedDataBlock::HasDefaultBorderImageWidth() const
+{
+ const nsCSSRect &width =
+ ValueFor(eCSSProperty_border_image_width)->GetRectValue();
+ return width.AllSidesEqualTo(nsCSSValue(1.0f, eCSSUnit_Number));
+}
+
+bool
+nsCSSCompressedDataBlock::HasDefaultBorderImageOutset() const
+{
+ const nsCSSRect &outset =
+ ValueFor(eCSSProperty_border_image_outset)->GetRectValue();
+ return outset.AllSidesEqualTo(nsCSSValue(0.0f, eCSSUnit_Number));
+}
+
+bool
+nsCSSCompressedDataBlock::HasDefaultBorderImageRepeat() const
+{
+ const nsCSSValuePair &repeat =
+ ValueFor(eCSSProperty_border_image_repeat)->GetPairValue();
+ return repeat.BothValuesEqualTo(
+ nsCSSValue(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH, eCSSUnit_Enumerated));
+}
+
+/*****************************************************************************/
+
+nsCSSExpandedDataBlock::nsCSSExpandedDataBlock()
+{
+ AssertInitialState();
+}
+
+nsCSSExpandedDataBlock::~nsCSSExpandedDataBlock()
+{
+ AssertInitialState();
+}
+
+void
+nsCSSExpandedDataBlock::DoExpand(nsCSSCompressedDataBlock *aBlock,
+ bool aImportant)
+{
+ /*
+ * Save needless copying and allocation by copying the memory
+ * corresponding to the stored data in the compressed block.
+ */
+ for (uint32_t i = 0; i < aBlock->mNumProps; i++) {
+ nsCSSPropertyID iProp = aBlock->PropertyAtIndex(i);
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(iProp), "out of range");
+ MOZ_ASSERT(!HasPropertyBit(iProp),
+ "compressed block has property multiple times");
+ SetPropertyBit(iProp);
+ if (aImportant)
+ SetImportantBit(iProp);
+
+ const nsCSSValue* val = aBlock->ValueAtIndex(i);
+ nsCSSValue* dest = PropertyAt(iProp);
+ MOZ_ASSERT(val->GetUnit() != eCSSUnit_Null, "oops");
+ MOZ_ASSERT(dest->GetUnit() == eCSSUnit_Null,
+ "expanding into non-empty block");
+#ifdef NS_BUILD_REFCNT_LOGGING
+ dest->~nsCSSValue();
+#endif
+ memcpy(dest, val, sizeof(nsCSSValue));
+ }
+
+ // Set the number of properties to zero so that we don't destroy the
+ // remnants of what we just copied.
+ aBlock->SetNumPropsToZero();
+ delete aBlock;
+}
+
+void
+nsCSSExpandedDataBlock::Expand(nsCSSCompressedDataBlock *aNormalBlock,
+ nsCSSCompressedDataBlock *aImportantBlock)
+{
+ MOZ_ASSERT(aNormalBlock, "unexpected null block");
+ AssertInitialState();
+
+ DoExpand(aNormalBlock, false);
+ if (aImportantBlock) {
+ DoExpand(aImportantBlock, true);
+ }
+}
+
+void
+nsCSSExpandedDataBlock::ComputeNumProps(uint32_t* aNumPropsNormal,
+ uint32_t* aNumPropsImportant)
+{
+ *aNumPropsNormal = *aNumPropsImportant = 0;
+ for (size_t iHigh = 0; iHigh < nsCSSPropertyIDSet::kChunkCount; ++iHigh) {
+ if (!mPropertiesSet.HasPropertyInChunk(iHigh))
+ continue;
+ for (size_t iLow = 0; iLow < nsCSSPropertyIDSet::kBitsInChunk; ++iLow) {
+ if (!mPropertiesSet.HasPropertyAt(iHigh, iLow))
+ continue;
+#ifdef DEBUG
+ nsCSSPropertyID iProp = nsCSSPropertyIDSet::CSSPropertyAt(iHigh, iLow);
+#endif
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(iProp), "out of range");
+ MOZ_ASSERT(PropertyAt(iProp)->GetUnit() != eCSSUnit_Null,
+ "null value while computing size");
+ if (mPropertiesImportant.HasPropertyAt(iHigh, iLow))
+ (*aNumPropsImportant)++;
+ else
+ (*aNumPropsNormal)++;
+ }
+ }
+}
+
+void
+nsCSSExpandedDataBlock::Compress(nsCSSCompressedDataBlock **aNormalBlock,
+ nsCSSCompressedDataBlock **aImportantBlock,
+ const nsTArray<uint32_t>& aOrder)
+{
+ nsAutoPtr<nsCSSCompressedDataBlock> result_normal, result_important;
+ uint32_t i_normal = 0, i_important = 0;
+
+ uint32_t numPropsNormal, numPropsImportant;
+ ComputeNumProps(&numPropsNormal, &numPropsImportant);
+
+ result_normal =
+ new(numPropsNormal) nsCSSCompressedDataBlock(numPropsNormal);
+
+ if (numPropsImportant != 0) {
+ result_important =
+ new(numPropsImportant) nsCSSCompressedDataBlock(numPropsImportant);
+ } else {
+ result_important = nullptr;
+ }
+
+ /*
+ * Save needless copying and allocation by copying the memory
+ * corresponding to the stored data in the expanded block, and then
+ * clearing the data in the expanded block.
+ */
+ for (size_t i = 0; i < aOrder.Length(); i++) {
+ nsCSSPropertyID iProp = static_cast<nsCSSPropertyID>(aOrder[i]);
+ if (iProp >= eCSSProperty_COUNT) {
+ // a custom property
+ continue;
+ }
+ MOZ_ASSERT(mPropertiesSet.HasProperty(iProp),
+ "aOrder identifies a property not in the expanded "
+ "data block");
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(iProp), "out of range");
+ bool important = mPropertiesImportant.HasProperty(iProp);
+ nsCSSCompressedDataBlock *result =
+ important ? result_important : result_normal;
+ uint32_t* ip = important ? &i_important : &i_normal;
+ nsCSSValue* val = PropertyAt(iProp);
+ MOZ_ASSERT(val->GetUnit() != eCSSUnit_Null,
+ "Null value while compressing");
+ result->SetPropertyAtIndex(*ip, iProp);
+ result->RawCopyValueToIndex(*ip, val);
+ new (val) nsCSSValue();
+ (*ip)++;
+ result->mStyleBits |=
+ nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[iProp]);
+ }
+
+ MOZ_ASSERT(numPropsNormal == i_normal, "bad numProps");
+
+ if (result_important) {
+ MOZ_ASSERT(numPropsImportant == i_important, "bad numProps");
+ }
+
+#ifdef DEBUG
+ {
+ // assert that we didn't have any other properties on this expanded data
+ // block that we didn't find in aOrder
+ uint32_t numPropsInSet = 0;
+ for (size_t iHigh = 0; iHigh < nsCSSPropertyIDSet::kChunkCount; iHigh++) {
+ if (!mPropertiesSet.HasPropertyInChunk(iHigh)) {
+ continue;
+ }
+ for (size_t iLow = 0; iLow < nsCSSPropertyIDSet::kBitsInChunk; iLow++) {
+ if (mPropertiesSet.HasPropertyAt(iHigh, iLow)) {
+ numPropsInSet++;
+ }
+ }
+ }
+ MOZ_ASSERT(numPropsNormal + numPropsImportant == numPropsInSet,
+ "aOrder missing properties from the expanded data block");
+ }
+#endif
+
+ ClearSets();
+ AssertInitialState();
+ *aNormalBlock = result_normal.forget();
+ *aImportantBlock = result_important.forget();
+}
+
+void
+nsCSSExpandedDataBlock::AddLonghandProperty(nsCSSPropertyID aProperty,
+ const nsCSSValue& aValue)
+{
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
+ "property out of range");
+ nsCSSValue& storage = *static_cast<nsCSSValue*>(PropertyAt(aProperty));
+ storage = aValue;
+ SetPropertyBit(aProperty);
+}
+
+void
+nsCSSExpandedDataBlock::Clear()
+{
+ for (size_t iHigh = 0; iHigh < nsCSSPropertyIDSet::kChunkCount; ++iHigh) {
+ if (!mPropertiesSet.HasPropertyInChunk(iHigh))
+ continue;
+ for (size_t iLow = 0; iLow < nsCSSPropertyIDSet::kBitsInChunk; ++iLow) {
+ if (!mPropertiesSet.HasPropertyAt(iHigh, iLow))
+ continue;
+ nsCSSPropertyID iProp = nsCSSPropertyIDSet::CSSPropertyAt(iHigh, iLow);
+ ClearLonghandProperty(iProp);
+ }
+ }
+
+ AssertInitialState();
+}
+
+void
+nsCSSExpandedDataBlock::ClearProperty(nsCSSPropertyID aPropID)
+{
+ if (nsCSSProps::IsShorthand(aPropID)) {
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
+ p, aPropID, CSSEnabledState::eIgnoreEnabledState) {
+ ClearLonghandProperty(*p);
+ }
+ } else {
+ ClearLonghandProperty(aPropID);
+ }
+}
+
+void
+nsCSSExpandedDataBlock::ClearLonghandProperty(nsCSSPropertyID aPropID)
+{
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(aPropID), "out of range");
+
+ ClearPropertyBit(aPropID);
+ ClearImportantBit(aPropID);
+ PropertyAt(aPropID)->Reset();
+}
+
+bool
+nsCSSExpandedDataBlock::TransferFromBlock(nsCSSExpandedDataBlock& aFromBlock,
+ nsCSSPropertyID aPropID,
+ CSSEnabledState aEnabledState,
+ bool aIsImportant,
+ bool aOverrideImportant,
+ bool aMustCallValueAppended,
+ css::Declaration* aDeclaration,
+ nsIDocument* aSheetDocument)
+{
+ if (!nsCSSProps::IsShorthand(aPropID)) {
+ return DoTransferFromBlock(aFromBlock, aPropID,
+ aIsImportant, aOverrideImportant,
+ aMustCallValueAppended, aDeclaration,
+ aSheetDocument);
+ }
+
+ // We can pass CSSEnabledState::eIgnore (here, and in ClearProperty
+ // above) rather than a value corresponding to whether we're parsing
+ // a UA style sheet or certified app because we assert in nsCSSProps::
+ // AddRefTable that shorthand properties available in these contexts
+ // also have all of their subproperties available in these contexts.
+ bool changed = false;
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID, aEnabledState) {
+ changed |= DoTransferFromBlock(aFromBlock, *p,
+ aIsImportant, aOverrideImportant,
+ aMustCallValueAppended, aDeclaration,
+ aSheetDocument);
+ }
+ return changed;
+}
+
+bool
+nsCSSExpandedDataBlock::DoTransferFromBlock(nsCSSExpandedDataBlock& aFromBlock,
+ nsCSSPropertyID aPropID,
+ bool aIsImportant,
+ bool aOverrideImportant,
+ bool aMustCallValueAppended,
+ css::Declaration* aDeclaration,
+ nsIDocument* aSheetDocument)
+{
+ bool changed = false;
+ MOZ_ASSERT(aFromBlock.HasPropertyBit(aPropID), "oops");
+ if (aIsImportant) {
+ if (!HasImportantBit(aPropID))
+ changed = true;
+ SetImportantBit(aPropID);
+ } else {
+ if (HasImportantBit(aPropID)) {
+ // When parsing a declaration block, an !important declaration
+ // is not overwritten by an ordinary declaration of the same
+ // property later in the block. However, CSSOM manipulations
+ // come through here too, and in that case we do want to
+ // overwrite the property.
+ if (!aOverrideImportant) {
+ aFromBlock.ClearLonghandProperty(aPropID);
+ return false;
+ }
+ changed = true;
+ ClearImportantBit(aPropID);
+ }
+ }
+
+ if (aMustCallValueAppended || !HasPropertyBit(aPropID)) {
+ aDeclaration->ValueAppended(aPropID);
+ }
+
+ if (aSheetDocument) {
+ UseCounter useCounter = nsCSSProps::UseCounterFor(aPropID);
+ if (useCounter != eUseCounter_UNKNOWN) {
+ aSheetDocument->SetDocumentAndPageUseCounter(useCounter);
+ }
+ }
+
+ SetPropertyBit(aPropID);
+ aFromBlock.ClearPropertyBit(aPropID);
+
+ /*
+ * Save needless copying and allocation by calling the destructor in
+ * the destination, copying memory directly, and then using placement
+ * new.
+ */
+ changed |= MoveValue(aFromBlock.PropertyAt(aPropID), PropertyAt(aPropID));
+ return changed;
+}
+
+void
+nsCSSExpandedDataBlock::MapRuleInfoInto(nsCSSPropertyID aPropID,
+ nsRuleData* aRuleData) const
+{
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(aPropID));
+
+ const nsCSSValue* src = PropertyAt(aPropID);
+ MOZ_ASSERT(src->GetUnit() != eCSSUnit_Null);
+
+ nsCSSPropertyID physicalProp = aPropID;
+ if (nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_LOGICAL)) {
+ EnsurePhysicalProperty(physicalProp, aRuleData);
+ }
+
+ nsCSSValue* dest = aRuleData->ValueFor(physicalProp);
+ MOZ_ASSERT(dest->GetUnit() == eCSSUnit_TokenStream &&
+ dest->GetTokenStreamValue()->mPropertyID == aPropID);
+
+ CSSVariableImageTable::ReplaceAll(aRuleData->mStyleContext, aPropID, [=] {
+ MapSinglePropertyInto(physicalProp, src, dest, aRuleData);
+ });
+}
+
+#ifdef DEBUG
+void
+nsCSSExpandedDataBlock::DoAssertInitialState()
+{
+ mPropertiesSet.AssertIsEmpty("not initial state");
+ mPropertiesImportant.AssertIsEmpty("not initial state");
+
+ for (uint32_t i = 0; i < eCSSProperty_COUNT_no_shorthands; ++i) {
+ nsCSSPropertyID prop = nsCSSPropertyID(i);
+ MOZ_ASSERT(PropertyAt(prop)->GetUnit() == eCSSUnit_Null,
+ "not initial state");
+ }
+}
+#endif
diff --git a/layout/style/nsCSSDataBlock.h b/layout/style/nsCSSDataBlock.h
new file mode 100644
index 000000000..76deaa911
--- /dev/null
+++ b/layout/style/nsCSSDataBlock.h
@@ -0,0 +1,372 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * compact representation of the property-value pairs within a CSS
+ * declaration, and the code for expanding and compacting it
+ */
+
+#ifndef nsCSSDataBlock_h__
+#define nsCSSDataBlock_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "nsCSSProps.h"
+#include "nsCSSPropertyIDSet.h"
+#include "nsCSSValue.h"
+#include "nsStyleStruct.h"
+#include "imgRequestProxy.h"
+
+struct nsRuleData;
+class nsCSSExpandedDataBlock;
+class nsIDocument;
+
+namespace mozilla {
+namespace css {
+class Declaration;
+} // namespace css
+} // namespace mozilla
+
+/**
+ * An |nsCSSCompressedDataBlock| holds a usually-immutable chunk of
+ * property-value data for a CSS declaration block (which we misname a
+ * |css::Declaration|). Mutation is accomplished through
+ * |nsCSSExpandedDataBlock| or in some cases via direct slot access.
+ */
+class nsCSSCompressedDataBlock
+{
+private:
+ friend class nsCSSExpandedDataBlock;
+
+ // Only this class (via |CreateEmptyBlock|) or nsCSSExpandedDataBlock
+ // (in |Compress|) can create compressed data blocks.
+ explicit nsCSSCompressedDataBlock(uint32_t aNumProps)
+ : mStyleBits(0), mNumProps(aNumProps)
+ {}
+
+public:
+ ~nsCSSCompressedDataBlock();
+
+ /**
+ * Do what |nsIStyleRule::MapRuleInfoInto| needs to do for a style
+ * rule using this block for storage.
+ */
+ void MapRuleInfoInto(nsRuleData *aRuleData) const;
+
+ /**
+ * Return the location at which the *value* for the property is
+ * stored, or null if the block does not contain a value for the
+ * property.
+ *
+ * Inefficient (by design).
+ *
+ * Must not be called for shorthands.
+ */
+ const nsCSSValue* ValueFor(nsCSSPropertyID aProperty) const;
+
+ /**
+ * Attempt to replace the value for |aProperty| stored in this block
+ * with the matching value stored in |aFromBlock|.
+ * This method will fail (returning false) if |aProperty| is not
+ * already in this block. It will set |aChanged| to true if it
+ * actually made a change to the block, but regardless, if it
+ * returns true, the value in |aFromBlock| was erased.
+ */
+ bool TryReplaceValue(nsCSSPropertyID aProperty,
+ nsCSSExpandedDataBlock& aFromBlock,
+ bool* aChanged);
+
+ /**
+ * Clone this block, or return null on out-of-memory.
+ */
+ nsCSSCompressedDataBlock* Clone() const;
+
+ /**
+ * Create a new nsCSSCompressedDataBlock holding no declarations.
+ */
+ static nsCSSCompressedDataBlock* CreateEmptyBlock();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ bool HasDefaultBorderImageSlice() const;
+ bool HasDefaultBorderImageWidth() const;
+ bool HasDefaultBorderImageOutset() const;
+ bool HasDefaultBorderImageRepeat() const;
+
+ bool HasInheritedStyleData() const
+ {
+ return mStyleBits & NS_STYLE_INHERITED_STRUCT_MASK;
+ }
+
+private:
+ void* operator new(size_t aBaseSize, uint32_t aNumProps) {
+ MOZ_ASSERT(aBaseSize == sizeof(nsCSSCompressedDataBlock),
+ "unexpected size for nsCSSCompressedDataBlock");
+ return ::operator new(aBaseSize + DataSize(aNumProps));
+ }
+
+public:
+ // Ideally, |nsCSSPropertyID| would be |enum nsCSSPropertyID : int16_t|. But
+ // not all of the compilers we use are modern enough to support small
+ // enums. So we manually squeeze nsCSSPropertyID into 16 bits ourselves.
+ // The static assertion below ensures it fits.
+ typedef int16_t CompressedCSSProperty;
+ static const size_t MaxCompressedCSSProperty = INT16_MAX;
+
+private:
+ static size_t DataSize(uint32_t aNumProps) {
+ return size_t(aNumProps) *
+ (sizeof(nsCSSValue) + sizeof(CompressedCSSProperty));
+ }
+
+ int32_t mStyleBits; // the structs for which we have data, according to
+ // |nsCachedStyleData::GetBitForSID|.
+ uint32_t mNumProps;
+ // nsCSSValue elements are stored after these fields, and
+ // nsCSSPropertyID elements are stored -- each one compressed as a
+ // CompressedCSSProperty -- after the nsCSSValue elements. Space for them
+ // is allocated in |operator new| above. The static assertions following
+ // this class make sure that the value and property elements are aligned
+ // appropriately.
+
+ nsCSSValue* Values() const {
+ return (nsCSSValue*)(this + 1);
+ }
+
+ CompressedCSSProperty* CompressedProperties() const {
+ return (CompressedCSSProperty*)(Values() + mNumProps);
+ }
+
+ nsCSSValue* ValueAtIndex(uint32_t i) const {
+ MOZ_ASSERT(i < mNumProps, "value index out of range");
+ return Values() + i;
+ }
+
+ nsCSSPropertyID PropertyAtIndex(uint32_t i) const {
+ MOZ_ASSERT(i < mNumProps, "property index out of range");
+ nsCSSPropertyID prop = (nsCSSPropertyID)CompressedProperties()[i];
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(prop), "out of range");
+ return prop;
+ }
+
+ void CopyValueToIndex(uint32_t i, nsCSSValue* aValue) {
+ new (ValueAtIndex(i)) nsCSSValue(*aValue);
+ }
+
+ void RawCopyValueToIndex(uint32_t i, nsCSSValue* aValue) {
+ memcpy(ValueAtIndex(i), aValue, sizeof(nsCSSValue));
+ }
+
+ void SetPropertyAtIndex(uint32_t i, nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(i < mNumProps, "set property index out of range");
+ CompressedProperties()[i] = (CompressedCSSProperty)aProperty;
+ }
+
+ void SetNumPropsToZero() {
+ mNumProps = 0;
+ }
+};
+
+// Make sure the values and properties are aligned appropriately. (These
+// assertions are stronger than necessary to keep them simple.)
+static_assert(sizeof(nsCSSCompressedDataBlock) == 8,
+ "nsCSSCompressedDataBlock's size has changed");
+static_assert(NS_ALIGNMENT_OF(nsCSSValue) == 4 || NS_ALIGNMENT_OF(nsCSSValue) == 8,
+ "nsCSSValue doesn't align with nsCSSCompressedDataBlock");
+static_assert(NS_ALIGNMENT_OF(nsCSSCompressedDataBlock::CompressedCSSProperty) == 2,
+ "CompressedCSSProperty doesn't align with nsCSSValue");
+
+// Make sure that sizeof(CompressedCSSProperty) is big enough.
+static_assert(eCSSProperty_COUNT_no_shorthands <=
+ nsCSSCompressedDataBlock::MaxCompressedCSSProperty,
+ "nsCSSPropertyID doesn't fit in StoredSizeOfCSSProperty");
+
+class nsCSSExpandedDataBlock
+{
+ friend class nsCSSCompressedDataBlock;
+
+public:
+ nsCSSExpandedDataBlock();
+ ~nsCSSExpandedDataBlock();
+
+private:
+ /* Property storage may not be accessed directly; use AddLonghandProperty
+ * and friends.
+ */
+ nsCSSValue mValues[eCSSProperty_COUNT_no_shorthands];
+
+public:
+ /**
+ * Transfer all of the state from a pair of compressed data blocks
+ * to this expanded block. This expanded block must be clear
+ * beforehand.
+ *
+ * This method DELETES both of the compressed data blocks it is
+ * passed. (This is necessary because ownership of sub-objects
+ * is transferred to the expanded block.)
+ */
+ void Expand(nsCSSCompressedDataBlock *aNormalBlock,
+ nsCSSCompressedDataBlock *aImportantBlock);
+
+ /**
+ * Allocate new compressed blocks and transfer all of the state
+ * from this expanded block to the new blocks, clearing this
+ * expanded block. A normal block will always be allocated, but
+ * an important block will only be allocated if there are
+ * !important properties in the expanded block; otherwise
+ * |*aImportantBlock| will be set to null.
+ *
+ * aOrder is an array of nsCSSPropertyID values specifying the order
+ * to store values in the two data blocks.
+ */
+ void Compress(nsCSSCompressedDataBlock **aNormalBlock,
+ nsCSSCompressedDataBlock **aImportantBlock,
+ const nsTArray<uint32_t>& aOrder);
+
+ /**
+ * Copy a value into this expanded block. This does NOT destroy
+ * the source value object. |aProperty| cannot be a shorthand.
+ */
+ void AddLonghandProperty(nsCSSPropertyID aProperty, const nsCSSValue& aValue);
+
+ /**
+ * Clear the state of this expanded block.
+ */
+ void Clear();
+
+ /**
+ * Clear the data for the given property (including the set and
+ * important bits). Can be used with shorthand properties.
+ */
+ void ClearProperty(nsCSSPropertyID aPropID);
+
+ /**
+ * Same as ClearProperty, but faster and cannot be used with shorthands.
+ */
+ void ClearLonghandProperty(nsCSSPropertyID aPropID);
+
+ /**
+ * Transfer the state for |aPropID| (which may be a shorthand)
+ * from |aFromBlock| to this block. The property being transferred
+ * is !important if |aIsImportant| is true, and should replace an
+ * existing !important property regardless of its own importance
+ * if |aOverrideImportant| is true. |aEnabledState| is used to
+ * determine which longhand components of |aPropID| (if it is a
+ * shorthand) to transfer.
+ *
+ * Returns true if something changed, false otherwise. Calls
+ * |ValueAppended| on |aDeclaration| if the property was not
+ * previously set, or in any case if |aMustCallValueAppended| is true.
+ * Calls |SetDocumentAndPageUseCounter| on |aSheetDocument| if it is
+ * non-null and |aPropID| has a use counter.
+ */
+ bool TransferFromBlock(nsCSSExpandedDataBlock& aFromBlock,
+ nsCSSPropertyID aPropID,
+ mozilla::CSSEnabledState aEnabledState,
+ bool aIsImportant,
+ bool aOverrideImportant,
+ bool aMustCallValueAppended,
+ mozilla::css::Declaration* aDeclaration,
+ nsIDocument* aSheetDocument);
+
+ /**
+ * Copies the values for aPropID into the specified aRuleData object.
+ *
+ * This is used for copying parsed-at-computed-value-time properties
+ * that had variable references. aPropID must be a longhand property.
+ */
+ void MapRuleInfoInto(nsCSSPropertyID aPropID, nsRuleData* aRuleData) const;
+
+ void AssertInitialState() {
+#ifdef DEBUG
+ DoAssertInitialState();
+#endif
+ }
+
+private:
+ /**
+ * Compute the number of properties that will be present in the
+ * result of |Compress|.
+ */
+ void ComputeNumProps(uint32_t* aNumPropsNormal,
+ uint32_t* aNumPropsImportant);
+
+ void DoExpand(nsCSSCompressedDataBlock *aBlock, bool aImportant);
+
+ /**
+ * Worker for TransferFromBlock; cannot be used with shorthands.
+ */
+ bool DoTransferFromBlock(nsCSSExpandedDataBlock& aFromBlock,
+ nsCSSPropertyID aPropID,
+ bool aIsImportant,
+ bool aOverrideImportant,
+ bool aMustCallValueAppended,
+ mozilla::css::Declaration* aDeclaration,
+ nsIDocument* aSheetDocument);
+
+#ifdef DEBUG
+ void DoAssertInitialState();
+#endif
+
+ /*
+ * mPropertiesSet stores a bit for every property that is present,
+ * to optimize compression of blocks with small numbers of
+ * properties (the norm) and to allow quickly checking whether a
+ * property is set in this block.
+ */
+ nsCSSPropertyIDSet mPropertiesSet;
+ /*
+ * mPropertiesImportant indicates which properties are '!important'.
+ */
+ nsCSSPropertyIDSet mPropertiesImportant;
+
+ /*
+ * Return the storage location within |this| of the value of the
+ * property |aProperty|.
+ */
+ nsCSSValue* PropertyAt(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(0 <= aProperty &&
+ aProperty < eCSSProperty_COUNT_no_shorthands,
+ "property out of range");
+ return &mValues[aProperty];
+ }
+ const nsCSSValue* PropertyAt(nsCSSPropertyID aProperty) const {
+ MOZ_ASSERT(0 <= aProperty &&
+ aProperty < eCSSProperty_COUNT_no_shorthands,
+ "property out of range");
+ return &mValues[aProperty];
+ }
+
+ void SetPropertyBit(nsCSSPropertyID aProperty) {
+ mPropertiesSet.AddProperty(aProperty);
+ }
+
+ void ClearPropertyBit(nsCSSPropertyID aProperty) {
+ mPropertiesSet.RemoveProperty(aProperty);
+ }
+
+ bool HasPropertyBit(nsCSSPropertyID aProperty) {
+ return mPropertiesSet.HasProperty(aProperty);
+ }
+
+ void SetImportantBit(nsCSSPropertyID aProperty) {
+ mPropertiesImportant.AddProperty(aProperty);
+ }
+
+ void ClearImportantBit(nsCSSPropertyID aProperty) {
+ mPropertiesImportant.RemoveProperty(aProperty);
+ }
+
+ bool HasImportantBit(nsCSSPropertyID aProperty) {
+ return mPropertiesImportant.HasProperty(aProperty);
+ }
+
+ void ClearSets() {
+ mPropertiesSet.Empty();
+ mPropertiesImportant.Empty();
+ }
+};
+
+#endif /* !defined(nsCSSDataBlock_h__) */
diff --git a/layout/style/nsCSSFontDescList.h b/layout/style/nsCSSFontDescList.h
new file mode 100644
index 000000000..0c614c9b2
--- /dev/null
+++ b/layout/style/nsCSSFontDescList.h
@@ -0,0 +1,14 @@
+/* -*- 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/. */
+
+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-language-override, FontLanguageOverride)
+CSS_FONT_DESC(font-display, Display)
diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h
new file mode 100644
index 000000000..febdd32c6
--- /dev/null
+++ b/layout/style/nsCSSKeywordList.h
@@ -0,0 +1,799 @@
+/* -*- 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/. */
+
+/* keywords used within CSS property values */
+
+/******
+
+ This file contains the list of all parsed CSS keywords
+ See nsCSSKeywords.h for access to the enum values for keywords
+
+ It is designed to be used as inline input to nsCSSKeywords.cpp *only*
+ through the magic of C preprocessing.
+
+ All entries must be enclosed in the macro CSS_KEY which will have cruel
+ and unusual things done to it
+
+ It is recommended (but not strictly necessary) to keep all entries
+ in alphabetical order
+
+ Requirements:
+
+ Entries are in the form: (name, id). 'id' must always be the same as 'name'
+ except that all hyphens ('-') in 'name' are converted to underscores ('_')
+ in 'id'. This lets us do nice things with the macros without having to
+ copy/convert strings at runtime.
+
+ 'name' entries *must* use only lowercase characters.
+
+ ** Break these invariants and bad things will happen. **
+
+ ******/
+
+// OUTPUT_CLASS=nsCSSKeywords
+// MACRO_NAME=CSS_KEY
+
+CSS_KEY(-moz-activehyperlinktext, _moz_activehyperlinktext)
+CSS_KEY(-moz-all, _moz_all)
+CSS_KEY(-moz-alt-content, _moz_alt_content)
+CSS_KEY(-moz-anchor-decoration, _moz_anchor_decoration)
+CSS_KEY(-moz-available, _moz_available)
+CSS_KEY(-moz-box, _moz_box)
+CSS_KEY(-moz-button, _moz_button)
+CSS_KEY(-moz-buttondefault, _moz_buttondefault)
+CSS_KEY(-moz-buttonhoverface, _moz_buttonhoverface)
+CSS_KEY(-moz-buttonhovertext, _moz_buttonhovertext)
+CSS_KEY(-moz-cellhighlight, _moz_cellhighlight)
+CSS_KEY(-moz-cellhighlighttext, _moz_cellhighlighttext)
+CSS_KEY(-moz-center, _moz_center)
+CSS_KEY(-moz-combobox, _moz_combobox)
+CSS_KEY(-moz-comboboxtext, _moz_comboboxtext)
+CSS_KEY(-moz-block-height, _moz_block_height)
+CSS_KEY(-moz-deck, _moz_deck)
+CSS_KEY(-moz-default-background-color, _moz_default_background_color)
+CSS_KEY(-moz-default-color, _moz_default_color)
+CSS_KEY(-moz-desktop, _moz_desktop)
+CSS_KEY(-moz-dialog, _moz_dialog)
+CSS_KEY(-moz-dialogtext, _moz_dialogtext)
+CSS_KEY(-moz-document, _moz_document)
+CSS_KEY(-moz-dragtargetzone, _moz_dragtargetzone)
+CSS_KEY(-moz-element, _moz_element)
+CSS_KEY(-moz-eventreerow, _moz_eventreerow)
+CSS_KEY(-moz-field, _moz_field)
+CSS_KEY(-moz-fieldtext, _moz_fieldtext)
+CSS_KEY(-moz-fit-content, _moz_fit_content)
+CSS_KEY(-moz-fixed, _moz_fixed)
+CSS_KEY(-moz-grabbing, _moz_grabbing)
+CSS_KEY(-moz-grab, _moz_grab)
+CSS_KEY(-moz-grid-group, _moz_grid_group)
+CSS_KEY(-moz-grid-line, _moz_grid_line)
+CSS_KEY(-moz-grid, _moz_grid)
+CSS_KEY(-moz-groupbox, _moz_groupbox)
+CSS_KEY(-moz-gtk-info-bar, _moz_gtk_info_bar)
+CSS_KEY(-moz-gtk-info-bar-text, _moz_gtk_info_bar_text)
+CSS_KEY(-moz-hidden-unscrollable, _moz_hidden_unscrollable)
+CSS_KEY(-moz-hyperlinktext, _moz_hyperlinktext)
+CSS_KEY(-moz-html-cellhighlight, _moz_html_cellhighlight)
+CSS_KEY(-moz-html-cellhighlighttext, _moz_html_cellhighlighttext)
+CSS_KEY(-moz-image-rect, _moz_image_rect)
+CSS_KEY(-moz-info, _moz_info)
+CSS_KEY(-moz-inline-box, _moz_inline_box)
+CSS_KEY(-moz-inline-grid, _moz_inline_grid)
+CSS_KEY(-moz-inline-stack, _moz_inline_stack)
+CSS_KEY(-moz-isolate, _moz_isolate)
+CSS_KEY(-moz-isolate-override, _moz_isolate_override)
+CSS_KEY(-moz-left, _moz_left)
+CSS_KEY(-moz-list, _moz_list)
+CSS_KEY(-moz-mac-buttonactivetext, _moz_mac_buttonactivetext)
+CSS_KEY(-moz-mac-chrome-active, _moz_mac_chrome_active)
+CSS_KEY(-moz-mac-chrome-inactive, _moz_mac_chrome_inactive)
+CSS_KEY(-moz-mac-defaultbuttontext, _moz_mac_defaultbuttontext)
+CSS_KEY(-moz-mac-focusring, _moz_mac_focusring)
+CSS_KEY(-moz-mac-fullscreen-button, _moz_mac_fullscreen_button)
+CSS_KEY(-moz-mac-menuselect, _moz_mac_menuselect)
+CSS_KEY(-moz-mac-menushadow, _moz_mac_menushadow)
+CSS_KEY(-moz-mac-menutextdisable, _moz_mac_menutextdisable)
+CSS_KEY(-moz-mac-menutextselect, _moz_mac_menutextselect)
+CSS_KEY(-moz-mac-disabledtoolbartext, _moz_mac_disabledtoolbartext)
+CSS_KEY(-moz-mac-secondaryhighlight, _moz_mac_secondaryhighlight)
+CSS_KEY(-moz-max-content, _moz_max_content)
+CSS_KEY(-moz-menuhover, _moz_menuhover)
+CSS_KEY(-moz-menuhovertext, _moz_menuhovertext)
+CSS_KEY(-moz-menubartext, _moz_menubartext)
+CSS_KEY(-moz-menubarhovertext, _moz_menubarhovertext)
+CSS_KEY(-moz-middle-with-baseline, _moz_middle_with_baseline)
+CSS_KEY(-moz-min-content, _moz_min_content)
+CSS_KEY(-moz-nativehyperlinktext, _moz_nativehyperlinktext)
+CSS_KEY(-moz-none, _moz_none)
+CSS_KEY(-moz-oddtreerow, _moz_oddtreerow)
+CSS_KEY(-moz-plaintext, _moz_plaintext)
+CSS_KEY(-moz-popup, _moz_popup)
+CSS_KEY(-moz-pre-space, _moz_pre_space)
+CSS_KEY(-moz-pull-down-menu, _moz_pull_down_menu)
+CSS_KEY(-moz-right, _moz_right)
+CSS_KEY(-moz-scrollbars-horizontal, _moz_scrollbars_horizontal)
+CSS_KEY(-moz-scrollbars-none, _moz_scrollbars_none)
+CSS_KEY(-moz-scrollbars-vertical, _moz_scrollbars_vertical)
+CSS_KEY(-moz-stack, _moz_stack)
+CSS_KEY(-moz-text, _moz_text)
+CSS_KEY(-moz-use-system-font, _moz_use_system_font)
+CSS_KEY(-moz-visitedhyperlinktext, _moz_visitedhyperlinktext)
+CSS_KEY(-moz-window, _moz_window)
+CSS_KEY(-moz-workspace, _moz_workspace)
+CSS_KEY(-moz-zoom-in, _moz_zoom_in)
+CSS_KEY(-moz-zoom-out, _moz_zoom_out)
+CSS_KEY(-webkit-box, _webkit_box)
+CSS_KEY(-webkit-flex, _webkit_flex)
+CSS_KEY(-webkit-inline-box, _webkit_inline_box)
+CSS_KEY(-webkit-inline-flex, _webkit_inline_flex)
+CSS_KEY(absolute, absolute)
+CSS_KEY(active, active)
+CSS_KEY(activeborder, activeborder)
+CSS_KEY(activecaption, activecaption)
+CSS_KEY(add, add)
+CSS_KEY(additive, additive)
+CSS_KEY(alias, alias)
+CSS_KEY(all, all)
+CSS_KEY(all-petite-caps, all_petite_caps)
+CSS_KEY(all-scroll, all_scroll)
+CSS_KEY(all-small-caps, all_small_caps)
+CSS_KEY(alpha, alpha)
+CSS_KEY(alternate, alternate)
+CSS_KEY(alternate-reverse, alternate_reverse)
+CSS_KEY(always, always)
+CSS_KEY(annotation, annotation)
+CSS_KEY(appworkspace, appworkspace)
+CSS_KEY(auto, auto)
+CSS_KEY(auto-fill, auto_fill)
+CSS_KEY(auto-fit, auto_fit)
+CSS_KEY(auto-flow, auto_flow)
+CSS_KEY(avoid, avoid)
+CSS_KEY(background, background)
+CSS_KEY(backwards, backwards)
+CSS_KEY(balance, balance)
+CSS_KEY(baseline, baseline)
+CSS_KEY(bidi-override, bidi_override)
+CSS_KEY(blink, blink)
+CSS_KEY(block, block)
+CSS_KEY(block-axis, block_axis)
+CSS_KEY(blur, blur)
+CSS_KEY(bold, bold)
+CSS_KEY(bold-fraktur, bold_fraktur)
+CSS_KEY(bold-italic, bold_italic)
+CSS_KEY(bold-sans-serif, bold_sans_serif)
+CSS_KEY(bold-script, bold_script)
+CSS_KEY(bolder, bolder)
+CSS_KEY(border-box, border_box)
+CSS_KEY(both, both)
+CSS_KEY(bottom, bottom)
+CSS_KEY(bottom-outside, bottom_outside)
+CSS_KEY(break-all, break_all)
+CSS_KEY(break-word, break_word)
+CSS_KEY(brightness, brightness)
+CSS_KEY(browser, browser)
+CSS_KEY(bullets, bullets)
+CSS_KEY(button, button)
+CSS_KEY(buttonface, buttonface)
+CSS_KEY(buttonhighlight, buttonhighlight)
+CSS_KEY(buttonshadow, buttonshadow)
+CSS_KEY(buttontext, buttontext)
+CSS_KEY(capitalize, capitalize)
+CSS_KEY(caption, caption)
+CSS_KEY(captiontext, captiontext)
+CSS_KEY(cell, cell)
+CSS_KEY(center, center)
+CSS_KEY(ch, ch)
+CSS_KEY(character-variant, character_variant)
+CSS_KEY(circle, circle)
+CSS_KEY(cjk-decimal, cjk_decimal)
+CSS_KEY(clip, clip)
+CSS_KEY(clone, clone)
+CSS_KEY(close-quote, close_quote)
+CSS_KEY(closest-corner, closest_corner)
+CSS_KEY(closest-side, closest_side)
+CSS_KEY(cm, cm)
+CSS_KEY(col-resize, col_resize)
+CSS_KEY(collapse, collapse)
+CSS_KEY(color, color)
+CSS_KEY(color-burn, color_burn)
+CSS_KEY(color-dodge, color_dodge)
+CSS_KEY(common-ligatures, common_ligatures)
+CSS_KEY(column, column)
+CSS_KEY(column-reverse, column_reverse)
+CSS_KEY(condensed, condensed)
+CSS_KEY(contain, contain)
+CSS_KEY(content-box, content_box)
+CSS_KEY(contents, contents)
+CSS_KEY(context-fill, context_fill)
+CSS_KEY(context-fill-opacity, context_fill_opacity)
+CSS_KEY(context-menu, context_menu)
+CSS_KEY(context-stroke, context_stroke)
+CSS_KEY(context-stroke-opacity, context_stroke_opacity)
+CSS_KEY(context-value, context_value)
+CSS_KEY(continuous, continuous)
+CSS_KEY(contrast, contrast)
+CSS_KEY(copy, copy)
+CSS_KEY(contextual, contextual)
+CSS_KEY(cover, cover)
+CSS_KEY(crop, crop)
+CSS_KEY(cross, cross)
+CSS_KEY(crosshair, crosshair)
+CSS_KEY(currentcolor, currentcolor)
+CSS_KEY(cursive, cursive)
+CSS_KEY(cyclic, cyclic)
+CSS_KEY(darken, darken)
+CSS_KEY(dashed, dashed)
+CSS_KEY(dense, dense)
+CSS_KEY(decimal, decimal)
+CSS_KEY(default, default)
+CSS_KEY(deg, deg)
+CSS_KEY(diagonal-fractions, diagonal_fractions)
+CSS_KEY(dialog, dialog)
+CSS_KEY(difference, difference)
+CSS_KEY(digits, digits)
+CSS_KEY(disabled, disabled)
+CSS_KEY(disc, disc)
+CSS_KEY(disclosure-closed, disclosure_closed)
+CSS_KEY(disclosure-open, disclosure_open)
+CSS_KEY(discretionary-ligatures, discretionary_ligatures)
+CSS_KEY(dot, dot)
+CSS_KEY(dotted, dotted)
+CSS_KEY(double, double)
+CSS_KEY(double-circle, double_circle)
+CSS_KEY(double-struck, double_struck)
+CSS_KEY(drag, drag)
+CSS_KEY(drop-shadow, drop_shadow)
+CSS_KEY(e-resize, e_resize)
+CSS_KEY(ease, ease)
+CSS_KEY(ease-in, ease_in)
+CSS_KEY(ease-in-out, ease_in_out)
+CSS_KEY(ease-out, ease_out)
+CSS_KEY(economy, economy)
+CSS_KEY(element, element)
+CSS_KEY(elements, elements)
+CSS_KEY(ellipse, ellipse)
+CSS_KEY(ellipsis, ellipsis)
+CSS_KEY(em, em)
+CSS_KEY(embed, embed)
+CSS_KEY(enabled, enabled)
+CSS_KEY(end, end)
+CSS_KEY(ethiopic-numeric, ethiopic_numeric)
+CSS_KEY(ex, ex)
+CSS_KEY(exact, exact)
+CSS_KEY(exclude, exclude)
+CSS_KEY(exclusion, exclusion)
+CSS_KEY(expanded, expanded)
+CSS_KEY(extends, extends)
+CSS_KEY(extra-condensed, extra_condensed)
+CSS_KEY(extra-expanded, extra_expanded)
+CSS_KEY(ew-resize, ew_resize)
+CSS_KEY(fallback, fallback)
+CSS_KEY(fantasy, fantasy)
+CSS_KEY(farthest-side, farthest_side)
+CSS_KEY(farthest-corner, farthest_corner)
+CSS_KEY(fill, fill)
+CSS_KEY(filled, filled)
+CSS_KEY(fill-box, fill_box)
+CSS_KEY(first, first)
+CSS_KEY(fit-content, fit_content)
+CSS_KEY(fixed, fixed)
+CSS_KEY(flat, flat)
+CSS_KEY(flex, flex)
+CSS_KEY(flex-end, flex_end)
+CSS_KEY(flex-start, flex_start)
+CSS_KEY(flip, flip)
+CSS_KEY(forwards, forwards)
+CSS_KEY(fraktur, fraktur)
+CSS_KEY(from-image, from_image)
+CSS_KEY(full-width, full_width)
+CSS_KEY(fullscreen, fullscreen)
+CSS_KEY(grab, grab)
+CSS_KEY(grabbing, grabbing)
+CSS_KEY(grad, grad)
+CSS_KEY(grayscale, grayscale)
+CSS_KEY(graytext, graytext)
+CSS_KEY(grid, grid)
+CSS_KEY(groove, groove)
+CSS_KEY(hard-light, hard_light)
+CSS_KEY(hebrew, hebrew)
+CSS_KEY(help, help)
+CSS_KEY(hidden, hidden)
+CSS_KEY(hide, hide)
+CSS_KEY(highlight, highlight)
+CSS_KEY(highlighttext, highlighttext)
+CSS_KEY(historical-forms, historical_forms)
+CSS_KEY(historical-ligatures, historical_ligatures)
+CSS_KEY(horizontal, horizontal)
+CSS_KEY(horizontal-tb, horizontal_tb)
+CSS_KEY(hue, hue)
+CSS_KEY(hue-rotate, hue_rotate)
+CSS_KEY(hz, hz)
+CSS_KEY(icon, icon)
+CSS_KEY(ignore, ignore)
+CSS_KEY(in, in)
+CSS_KEY(interlace, interlace)
+CSS_KEY(inactive, inactive)
+CSS_KEY(inactiveborder, inactiveborder)
+CSS_KEY(inactivecaption, inactivecaption)
+CSS_KEY(inactivecaptiontext, inactivecaptiontext)
+CSS_KEY(infinite, infinite)
+CSS_KEY(infobackground, infobackground)
+CSS_KEY(infotext, infotext)
+CSS_KEY(inherit, inherit)
+CSS_KEY(initial, initial)
+CSS_KEY(inline, inline)
+CSS_KEY(inline-axis, inline_axis)
+CSS_KEY(inline-block, inline_block)
+CSS_KEY(inline-end, inline_end)
+CSS_KEY(inline-flex, inline_flex)
+CSS_KEY(inline-grid, inline_grid)
+CSS_KEY(inline-start, inline_start)
+CSS_KEY(inline-table, inline_table)
+CSS_KEY(inset, inset)
+CSS_KEY(inside, inside)
+// CSS_KEY(inter-character, inter_character) // TODO see bug 1055672
+CSS_KEY(interpolatematrix, interpolatematrix)
+CSS_KEY(intersect, intersect)
+CSS_KEY(isolate, isolate)
+CSS_KEY(isolate-override, isolate_override)
+CSS_KEY(invert, invert)
+CSS_KEY(italic, italic)
+CSS_KEY(japanese-formal, japanese_formal)
+CSS_KEY(japanese-informal, japanese_informal)
+CSS_KEY(jis78, jis78)
+CSS_KEY(jis83, jis83)
+CSS_KEY(jis90, jis90)
+CSS_KEY(jis04, jis04)
+CSS_KEY(justify, justify)
+CSS_KEY(keep-all, keep_all)
+CSS_KEY(khz, khz)
+CSS_KEY(korean-hangul-formal, korean_hangul_formal)
+CSS_KEY(korean-hanja-formal, korean_hanja_formal)
+CSS_KEY(korean-hanja-informal, korean_hanja_informal)
+CSS_KEY(landscape, landscape)
+CSS_KEY(large, large)
+CSS_KEY(larger, larger)
+CSS_KEY(last, last)
+CSS_KEY(last baseline, last_baseline) // only used for DevTools auto-completion
+CSS_KEY(layout, layout)
+CSS_KEY(left, left)
+CSS_KEY(legacy, legacy)
+CSS_KEY(lighten, lighten)
+CSS_KEY(lighter, lighter)
+CSS_KEY(line-through, line_through)
+CSS_KEY(linear, linear)
+CSS_KEY(lining-nums, lining_nums)
+CSS_KEY(list-item, list_item)
+CSS_KEY(local, local)
+CSS_KEY(logical, logical)
+CSS_KEY(looped, looped)
+CSS_KEY(lowercase, lowercase)
+CSS_KEY(lr, lr)
+CSS_KEY(lr-tb, lr_tb)
+CSS_KEY(ltr, ltr)
+CSS_KEY(luminance, luminance)
+CSS_KEY(luminosity, luminosity)
+CSS_KEY(mandatory, mandatory)
+CSS_KEY(manipulation, manipulation)
+CSS_KEY(manual, manual)
+CSS_KEY(margin-box, margin_box)
+CSS_KEY(markers, markers)
+CSS_KEY(match-parent, match_parent)
+CSS_KEY(match-source, match_source)
+CSS_KEY(matrix, matrix)
+CSS_KEY(matrix3d, matrix3d)
+CSS_KEY(max-content, max_content)
+CSS_KEY(medium, medium)
+CSS_KEY(menu, menu)
+CSS_KEY(menutext, menutext)
+CSS_KEY(message-box, message_box)
+CSS_KEY(middle, middle)
+CSS_KEY(min-content, min_content)
+CSS_KEY(minmax, minmax)
+CSS_KEY(mix, mix)
+CSS_KEY(mixed, mixed)
+CSS_KEY(mm, mm)
+CSS_KEY(monospace, monospace)
+CSS_KEY(move, move)
+CSS_KEY(ms, ms)
+CSS_KEY(multiply, multiply)
+CSS_KEY(n-resize, n_resize)
+CSS_KEY(narrower, narrower)
+CSS_KEY(ne-resize, ne_resize)
+CSS_KEY(nesw-resize, nesw_resize)
+CSS_KEY(no-close-quote, no_close_quote)
+CSS_KEY(no-common-ligatures, no_common_ligatures)
+CSS_KEY(no-contextual, no_contextual)
+CSS_KEY(no-discretionary-ligatures, no_discretionary_ligatures)
+CSS_KEY(no-drag, no_drag)
+CSS_KEY(no-drop, no_drop)
+CSS_KEY(no-historical-ligatures, no_historical_ligatures)
+CSS_KEY(no-open-quote, no_open_quote)
+CSS_KEY(no-repeat, no_repeat)
+CSS_KEY(none, none)
+CSS_KEY(normal, normal)
+CSS_KEY(not-allowed, not_allowed)
+CSS_KEY(nowrap, nowrap)
+CSS_KEY(numeric, numeric)
+CSS_KEY(ns-resize, ns_resize)
+CSS_KEY(nw-resize, nw_resize)
+CSS_KEY(nwse-resize, nwse_resize)
+CSS_KEY(oblique, oblique)
+CSS_KEY(oldstyle-nums, oldstyle_nums)
+CSS_KEY(opacity, opacity)
+CSS_KEY(open, open)
+CSS_KEY(open-quote, open_quote)
+CSS_KEY(optional, optional)
+CSS_KEY(ordinal, ordinal)
+CSS_KEY(ornaments, ornaments)
+CSS_KEY(outset, outset)
+CSS_KEY(outside, outside)
+CSS_KEY(over, over)
+CSS_KEY(overlay, overlay)
+CSS_KEY(overline, overline)
+CSS_KEY(paint, paint)
+CSS_KEY(padding-box, padding_box)
+CSS_KEY(painted, painted)
+CSS_KEY(pan-x, pan_x)
+CSS_KEY(pan-y, pan_y)
+CSS_KEY(paused, paused)
+CSS_KEY(pc, pc)
+CSS_KEY(perspective, perspective)
+CSS_KEY(petite-caps, petite_caps)
+CSS_KEY(physical, physical)
+CSS_KEY(plaintext, plaintext)
+CSS_KEY(pointer, pointer)
+CSS_KEY(polygon, polygon)
+CSS_KEY(portrait, portrait)
+CSS_KEY(pre, pre)
+CSS_KEY(pre-wrap, pre_wrap)
+CSS_KEY(pre-line, pre_line)
+CSS_KEY(preserve-3d, preserve_3d)
+CSS_KEY(progress, progress)
+CSS_KEY(progressive, progressive)
+CSS_KEY(proportional-nums, proportional_nums)
+CSS_KEY(proportional-width, proportional_width)
+CSS_KEY(proximity, proximity)
+CSS_KEY(pt, pt)
+CSS_KEY(px, px)
+CSS_KEY(rad, rad)
+CSS_KEY(read-only, read_only)
+CSS_KEY(read-write, read_write)
+CSS_KEY(relative, relative)
+CSS_KEY(repeat, repeat)
+CSS_KEY(repeat-x, repeat_x)
+CSS_KEY(repeat-y, repeat_y)
+CSS_KEY(reverse, reverse)
+CSS_KEY(ridge, ridge)
+CSS_KEY(right, right)
+CSS_KEY(rl, rl)
+CSS_KEY(rl-tb, rl_tb)
+CSS_KEY(rotate, rotate)
+CSS_KEY(rotate3d, rotate3d)
+CSS_KEY(rotatex, rotatex)
+CSS_KEY(rotatey, rotatey)
+CSS_KEY(rotatez, rotatez)
+CSS_KEY(round, round)
+CSS_KEY(row, row)
+CSS_KEY(row-resize, row_resize)
+CSS_KEY(row-reverse, row_reverse)
+CSS_KEY(rtl, rtl)
+CSS_KEY(ruby, ruby)
+CSS_KEY(ruby-base, ruby_base)
+CSS_KEY(ruby-base-container, ruby_base_container)
+CSS_KEY(ruby-text, ruby_text)
+CSS_KEY(ruby-text-container, ruby_text_container)
+CSS_KEY(running, running)
+CSS_KEY(s, s)
+CSS_KEY(s-resize, s_resize)
+CSS_KEY(safe, safe)
+CSS_KEY(saturate, saturate)
+CSS_KEY(saturation, saturation)
+CSS_KEY(scale, scale)
+CSS_KEY(scale-down, scale_down)
+CSS_KEY(scale3d, scale3d)
+CSS_KEY(scalex, scalex)
+CSS_KEY(scaley, scaley)
+CSS_KEY(scalez, scalez)
+CSS_KEY(screen, screen)
+CSS_KEY(script, script)
+CSS_KEY(scroll, scroll)
+CSS_KEY(scrollbar, scrollbar)
+CSS_KEY(scrollbar-small, scrollbar_small)
+CSS_KEY(scrollbar-horizontal, scrollbar_horizontal)
+CSS_KEY(scrollbar-vertical, scrollbar_vertical)
+CSS_KEY(se-resize, se_resize)
+CSS_KEY(select-after, select_after)
+CSS_KEY(select-all, select_all)
+CSS_KEY(select-before, select_before)
+CSS_KEY(select-menu, select_menu)
+CSS_KEY(select-same, select_same)
+CSS_KEY(self-end, self_end)
+CSS_KEY(self-start, self_start)
+CSS_KEY(semi-condensed, semi_condensed)
+CSS_KEY(semi-expanded, semi_expanded)
+CSS_KEY(separate, separate)
+CSS_KEY(sepia, sepia)
+CSS_KEY(serif, serif)
+CSS_KEY(sesame, sesame)
+CSS_KEY(show, show)
+CSS_KEY(sideways, sideways)
+CSS_KEY(sideways-lr, sideways_lr)
+CSS_KEY(sideways-right, sideways_right) /* alias for 'sideways' */
+CSS_KEY(sideways-rl, sideways_rl)
+CSS_KEY(simp-chinese-formal, simp_chinese_formal)
+CSS_KEY(simp-chinese-informal, simp_chinese_informal)
+CSS_KEY(simplified, simplified)
+CSS_KEY(skew, skew)
+CSS_KEY(skewx, skewx)
+CSS_KEY(skewy, skewy)
+CSS_KEY(slashed-zero, slashed_zero)
+CSS_KEY(slice, slice)
+CSS_KEY(small, small)
+CSS_KEY(small-caps, small_caps)
+CSS_KEY(small-caption, small_caption)
+CSS_KEY(smaller, smaller)
+CSS_KEY(smooth, smooth)
+CSS_KEY(soft, soft)
+CSS_KEY(soft-light, soft_light)
+CSS_KEY(solid, solid)
+CSS_KEY(space-around, space_around)
+CSS_KEY(space-between, space_between)
+CSS_KEY(space-evenly, space_evenly)
+CSS_KEY(span, span)
+CSS_KEY(spell-out, spell_out)
+CSS_KEY(square, square)
+CSS_KEY(stacked-fractions, stacked_fractions)
+CSS_KEY(start, start)
+CSS_KEY(static, static)
+CSS_KEY(standalone, standalone)
+CSS_KEY(status-bar, status_bar)
+CSS_KEY(step-end, step_end)
+CSS_KEY(step-start, step_start)
+CSS_KEY(sticky, sticky)
+CSS_KEY(stretch, stretch)
+CSS_KEY(stretch-to-fit, stretch_to_fit)
+CSS_KEY(stretched, stretched)
+CSS_KEY(strict, strict)
+CSS_KEY(stroke, stroke)
+CSS_KEY(stroke-box, stroke_box)
+CSS_KEY(style, style)
+CSS_KEY(styleset, styleset)
+CSS_KEY(stylistic, stylistic)
+CSS_KEY(sub, sub)
+CSS_KEY(subgrid, subgrid)
+CSS_KEY(subtract, subtract)
+CSS_KEY(super, super)
+CSS_KEY(sw-resize, sw_resize)
+CSS_KEY(swash, swash)
+CSS_KEY(swap, swap)
+CSS_KEY(table, table)
+CSS_KEY(table-caption, table_caption)
+CSS_KEY(table-cell, table_cell)
+CSS_KEY(table-column, table_column)
+CSS_KEY(table-column-group, table_column_group)
+CSS_KEY(table-footer-group, table_footer_group)
+CSS_KEY(table-header-group, table_header_group)
+CSS_KEY(table-row, table_row)
+CSS_KEY(table-row-group, table_row_group)
+CSS_KEY(tabular-nums, tabular_nums)
+CSS_KEY(tailed, tailed)
+CSS_KEY(tb, tb)
+CSS_KEY(tb-rl, tb_rl)
+CSS_KEY(text, text)
+CSS_KEY(text-bottom, text_bottom)
+CSS_KEY(text-top, text_top)
+CSS_KEY(thick, thick)
+CSS_KEY(thin, thin)
+CSS_KEY(threeddarkshadow, threeddarkshadow)
+CSS_KEY(threedface, threedface)
+CSS_KEY(threedhighlight, threedhighlight)
+CSS_KEY(threedlightshadow, threedlightshadow)
+CSS_KEY(threedshadow, threedshadow)
+CSS_KEY(titling-caps, titling_caps)
+CSS_KEY(toggle, toggle)
+CSS_KEY(top, top)
+CSS_KEY(top-outside, top_outside)
+CSS_KEY(trad-chinese-formal, trad_chinese_formal)
+CSS_KEY(trad-chinese-informal, trad_chinese_informal)
+CSS_KEY(traditional, traditional)
+CSS_KEY(translate, translate)
+CSS_KEY(translate3d, translate3d)
+CSS_KEY(translatex, translatex)
+CSS_KEY(translatey, translatey)
+CSS_KEY(translatez, translatez)
+CSS_KEY(transparent, transparent) // for nsComputedDOMStyle only
+CSS_KEY(triangle, triangle)
+CSS_KEY(tri-state, tri_state)
+CSS_KEY(ultra-condensed, ultra_condensed)
+CSS_KEY(ultra-expanded, ultra_expanded)
+CSS_KEY(under, under)
+CSS_KEY(underline, underline)
+CSS_KEY(unicase, unicase)
+CSS_KEY(unsafe, unsafe)
+CSS_KEY(unset, unset)
+CSS_KEY(uppercase, uppercase)
+CSS_KEY(upright, upright)
+CSS_KEY(vertical, vertical)
+CSS_KEY(vertical-lr, vertical_lr)
+CSS_KEY(vertical-rl, vertical_rl)
+CSS_KEY(vertical-text, vertical_text)
+CSS_KEY(view-box, view_box)
+CSS_KEY(visible, visible)
+CSS_KEY(visiblefill, visiblefill)
+CSS_KEY(visiblepainted, visiblepainted)
+CSS_KEY(visiblestroke, visiblestroke)
+CSS_KEY(w-resize, w_resize)
+CSS_KEY(wait, wait)
+CSS_KEY(wavy, wavy)
+CSS_KEY(weight, weight)
+CSS_KEY(wider, wider)
+CSS_KEY(window, window)
+CSS_KEY(windowframe, windowframe)
+CSS_KEY(windowtext, windowtext)
+CSS_KEY(words, words)
+CSS_KEY(wrap, wrap)
+CSS_KEY(wrap-reverse, wrap_reverse)
+CSS_KEY(write-only, write_only)
+CSS_KEY(x-large, x_large)
+CSS_KEY(x-small, x_small)
+CSS_KEY(xx-large, xx_large)
+CSS_KEY(xx-small, xx_small)
+CSS_KEY(zoom-in, zoom_in)
+CSS_KEY(zoom-out, zoom_out)
+
+// Appearance keywords for widget styles
+CSS_KEY(radio, radio)
+CSS_KEY(checkbox, checkbox)
+CSS_KEY(button-bevel, button_bevel)
+CSS_KEY(toolbox, toolbox)
+CSS_KEY(toolbar, toolbar)
+CSS_KEY(toolbarbutton, toolbarbutton)
+CSS_KEY(toolbargripper, toolbargripper)
+CSS_KEY(dualbutton, dualbutton)
+CSS_KEY(toolbarbutton-dropdown, toolbarbutton_dropdown)
+CSS_KEY(button-arrow-up, button_arrow_up)
+CSS_KEY(button-arrow-down, button_arrow_down)
+CSS_KEY(button-arrow-next, button_arrow_next)
+CSS_KEY(button-arrow-previous, button_arrow_previous)
+CSS_KEY(separator, separator)
+CSS_KEY(splitter, splitter)
+CSS_KEY(statusbar, statusbar)
+CSS_KEY(statusbarpanel, statusbarpanel)
+CSS_KEY(resizerpanel, resizerpanel)
+CSS_KEY(resizer, resizer)
+CSS_KEY(listbox, listbox)
+CSS_KEY(listitem, listitem)
+CSS_KEY(numbers, numbers)
+CSS_KEY(number-input, number_input)
+CSS_KEY(treeview, treeview)
+CSS_KEY(treeitem, treeitem)
+CSS_KEY(treetwisty, treetwisty)
+CSS_KEY(treetwistyopen, treetwistyopen)
+CSS_KEY(treeline, treeline)
+CSS_KEY(treeheader, treeheader)
+CSS_KEY(treeheadercell, treeheadercell)
+CSS_KEY(treeheadersortarrow, treeheadersortarrow)
+CSS_KEY(progressbar, progressbar)
+CSS_KEY(progressbar-vertical, progressbar_vertical)
+CSS_KEY(progresschunk, progresschunk)
+CSS_KEY(progresschunk-vertical, progresschunk_vertical)
+CSS_KEY(tab, tab)
+CSS_KEY(tabpanels, tabpanels)
+CSS_KEY(tabpanel, tabpanel)
+CSS_KEY(tab-scroll-arrow-back, tab_scroll_arrow_back)
+CSS_KEY(tab-scroll-arrow-forward, tab_scroll_arrow_forward)
+CSS_KEY(tooltip, tooltip)
+CSS_KEY(spinner, spinner)
+CSS_KEY(spinner-upbutton, spinner_upbutton)
+CSS_KEY(spinner-downbutton, spinner_downbutton)
+CSS_KEY(spinner-textfield, spinner_textfield)
+CSS_KEY(scrollbarbutton-up, scrollbarbutton_up)
+CSS_KEY(scrollbarbutton-down, scrollbarbutton_down)
+CSS_KEY(scrollbarbutton-left, scrollbarbutton_left)
+CSS_KEY(scrollbarbutton-right, scrollbarbutton_right)
+CSS_KEY(scrollbartrack-horizontal, scrollbartrack_horizontal)
+CSS_KEY(scrollbartrack-vertical, scrollbartrack_vertical)
+CSS_KEY(scrollbarthumb-horizontal, scrollbarthumb_horizontal)
+CSS_KEY(scrollbarthumb-vertical, scrollbarthumb_vertical)
+CSS_KEY(sheet, sheet)
+CSS_KEY(textfield, textfield)
+CSS_KEY(textfield-multiline, textfield_multiline)
+CSS_KEY(caret, caret)
+CSS_KEY(searchfield, searchfield)
+CSS_KEY(menubar, menubar)
+CSS_KEY(menupopup, menupopup)
+CSS_KEY(menuitem, menuitem)
+CSS_KEY(checkmenuitem, checkmenuitem)
+CSS_KEY(radiomenuitem, radiomenuitem)
+CSS_KEY(menucheckbox, menucheckbox)
+CSS_KEY(menuradio, menuradio)
+CSS_KEY(menuseparator, menuseparator)
+CSS_KEY(menuarrow, menuarrow)
+CSS_KEY(menuimage, menuimage)
+CSS_KEY(menuitemtext, menuitemtext)
+CSS_KEY(menulist, menulist)
+CSS_KEY(menulist-button, menulist_button)
+CSS_KEY(menulist-text, menulist_text)
+CSS_KEY(menulist-textfield, menulist_textfield)
+CSS_KEY(meterbar, meterbar)
+CSS_KEY(meterchunk, meterchunk)
+CSS_KEY(minimal-ui, minimal_ui)
+CSS_KEY(range, range)
+CSS_KEY(range-thumb, range_thumb)
+CSS_KEY(sans-serif, sans_serif)
+CSS_KEY(sans-serif-bold-italic, sans_serif_bold_italic)
+CSS_KEY(sans-serif-italic, sans_serif_italic)
+CSS_KEY(scale-horizontal, scale_horizontal)
+CSS_KEY(scale-vertical, scale_vertical)
+CSS_KEY(scalethumb-horizontal, scalethumb_horizontal)
+CSS_KEY(scalethumb-vertical, scalethumb_vertical)
+CSS_KEY(scalethumbstart, scalethumbstart)
+CSS_KEY(scalethumbend, scalethumbend)
+CSS_KEY(scalethumbtick, scalethumbtick)
+CSS_KEY(groupbox, groupbox)
+CSS_KEY(checkbox-container, checkbox_container)
+CSS_KEY(radio-container, radio_container)
+CSS_KEY(checkbox-label, checkbox_label)
+CSS_KEY(radio-label, radio_label)
+CSS_KEY(button-focus, button_focus)
+CSS_KEY(-moz-win-media-toolbox, _moz_win_media_toolbox)
+CSS_KEY(-moz-win-communications-toolbox, _moz_win_communications_toolbox)
+CSS_KEY(-moz-win-browsertabbar-toolbox, _moz_win_browsertabbar_toolbox)
+CSS_KEY(-moz-win-mediatext, _moz_win_mediatext)
+CSS_KEY(-moz-win-communicationstext, _moz_win_communicationstext)
+CSS_KEY(-moz-win-glass, _moz_win_glass)
+CSS_KEY(-moz-win-borderless-glass, _moz_win_borderless_glass)
+CSS_KEY(-moz-window-titlebar, _moz_window_titlebar)
+CSS_KEY(-moz-window-titlebar-maximized, _moz_window_titlebar_maximized)
+CSS_KEY(-moz-window-frame-left, _moz_window_frame_left)
+CSS_KEY(-moz-window-frame-right, _moz_window_frame_right)
+CSS_KEY(-moz-window-frame-bottom, _moz_window_frame_bottom)
+CSS_KEY(-moz-window-button-close, _moz_window_button_close)
+CSS_KEY(-moz-window-button-minimize, _moz_window_button_minimize)
+CSS_KEY(-moz-window-button-maximize, _moz_window_button_maximize)
+CSS_KEY(-moz-window-button-restore, _moz_window_button_restore)
+CSS_KEY(-moz-window-button-box, _moz_window_button_box)
+CSS_KEY(-moz-window-button-box-maximized, _moz_window_button_box_maximized)
+CSS_KEY(-moz-mac-help-button, _moz_mac_help_button)
+CSS_KEY(-moz-win-exclude-glass, _moz_win_exclude_glass)
+CSS_KEY(-moz-mac-vibrancy-light, _moz_mac_vibrancy_light)
+CSS_KEY(-moz-mac-vibrancy-dark, _moz_mac_vibrancy_dark)
+CSS_KEY(-moz-mac-disclosure-button-closed, _moz_mac_disclosure_button_closed)
+CSS_KEY(-moz-mac-disclosure-button-open, _moz_mac_disclosure_button_open)
+CSS_KEY(-moz-mac-source-list, _moz_mac_source_list)
+CSS_KEY(-moz-mac-source-list-selection, _moz_mac_source_list_selection)
+CSS_KEY(-moz-mac-active-source-list-selection, _moz_mac_active_source_list_selection)
+CSS_KEY(alphabetic, alphabetic)
+CSS_KEY(bevel, bevel)
+CSS_KEY(butt, butt)
+CSS_KEY(central, central)
+CSS_KEY(crispedges, crispedges)
+CSS_KEY(evenodd, evenodd)
+CSS_KEY(geometricprecision, geometricprecision)
+CSS_KEY(hanging, hanging)
+CSS_KEY(ideographic, ideographic)
+CSS_KEY(linearrgb, linearrgb)
+CSS_KEY(mathematical, mathematical)
+//CSS_KEY(middle, middle)
+CSS_KEY(miter, miter)
+CSS_KEY(no-change, no_change)
+CSS_KEY(non-scaling-stroke, non_scaling_stroke)
+CSS_KEY(nonzero, nonzero)
+CSS_KEY(optimizelegibility, optimizelegibility)
+CSS_KEY(optimizequality, optimizequality)
+CSS_KEY(optimizespeed, optimizespeed)
+CSS_KEY(reset-size, reset_size)
+//CSS_KEY(square, square)
+//CSS_KEY(start, start)
+CSS_KEY(srgb, srgb)
+CSS_KEY(symbolic, symbolic)
+CSS_KEY(symbols, symbols)
+CSS_KEY(text-after-edge, text_after_edge)
+CSS_KEY(text-before-edge, text_before_edge)
+CSS_KEY(use-script, use_script)
+CSS_KEY(-moz-crisp-edges, _moz_crisp_edges)
+CSS_KEY(space, space)
+
diff --git a/layout/style/nsCSSKeywords.cpp b/layout/style/nsCSSKeywords.cpp
new file mode 100644
index 000000000..8dd362e7c
--- /dev/null
+++ b/layout/style/nsCSSKeywords.cpp
@@ -0,0 +1,87 @@
+/* -*- 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/. */
+
+/* keywords used within CSS property values */
+
+#include "nsCSSKeywords.h"
+#include "nsString.h"
+#include "nsStaticNameTable.h"
+
+// required to make the symbol external, so that TestCSSPropertyLookup.cpp can link with it
+extern const char* const kCSSRawKeywords[];
+
+// define an array of all CSS keywords
+#define CSS_KEY(_name,_id) #_name,
+const char* const kCSSRawKeywords[] = {
+#include "nsCSSKeywordList.h"
+};
+#undef CSS_KEY
+
+static int32_t gKeywordTableRefCount;
+static nsStaticCaseInsensitiveNameTable* gKeywordTable;
+
+void
+nsCSSKeywords::AddRefTable(void)
+{
+ if (0 == gKeywordTableRefCount++) {
+ NS_ASSERTION(!gKeywordTable, "pre existing array!");
+ gKeywordTable =
+ new nsStaticCaseInsensitiveNameTable(kCSSRawKeywords, eCSSKeyword_COUNT);
+#ifdef DEBUG
+ // Partially verify the entries.
+ int32_t index = 0;
+ for (; index < eCSSKeyword_COUNT && kCSSRawKeywords[index]; ++index) {
+ nsAutoCString temp(kCSSRawKeywords[index]);
+ NS_ASSERTION(-1 == temp.FindChar('_'), "underscore char in table");
+ }
+ NS_ASSERTION(index == eCSSKeyword_COUNT, "kCSSRawKeywords and eCSSKeyword_COUNT are out of sync");
+#endif
+ }
+}
+
+void
+nsCSSKeywords::ReleaseTable(void)
+{
+ if (0 == --gKeywordTableRefCount) {
+ if (gKeywordTable) {
+ delete gKeywordTable;
+ gKeywordTable = nullptr;
+ }
+ }
+}
+
+nsCSSKeyword
+nsCSSKeywords::LookupKeyword(const nsACString& aKeyword)
+{
+ NS_ASSERTION(gKeywordTable, "no lookup table, needs addref");
+ if (gKeywordTable) {
+ return nsCSSKeyword(gKeywordTable->Lookup(aKeyword));
+ }
+ return eCSSKeyword_UNKNOWN;
+}
+
+nsCSSKeyword
+nsCSSKeywords::LookupKeyword(const nsAString& aKeyword)
+{
+ NS_ASSERTION(gKeywordTable, "no lookup table, needs addref");
+ if (gKeywordTable) {
+ return nsCSSKeyword(gKeywordTable->Lookup(aKeyword));
+ }
+ return eCSSKeyword_UNKNOWN;
+}
+
+const nsAFlatCString&
+nsCSSKeywords::GetStringValue(nsCSSKeyword aKeyword)
+{
+ NS_ASSERTION(gKeywordTable, "no lookup table, needs addref");
+ NS_ASSERTION(0 <= aKeyword && aKeyword < eCSSKeyword_COUNT, "out of range");
+ if (gKeywordTable) {
+ return gKeywordTable->GetStringValue(int32_t(aKeyword));
+ } else {
+ static nsDependentCString kNullStr("");
+ return kNullStr;
+ }
+}
+
diff --git a/layout/style/nsCSSKeywords.h b/layout/style/nsCSSKeywords.h
new file mode 100644
index 000000000..9e7aeadba
--- /dev/null
+++ b/layout/style/nsCSSKeywords.h
@@ -0,0 +1,42 @@
+/* -*- 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/. */
+
+/* keywords used within CSS property values */
+
+#ifndef nsCSSKeywords_h___
+#define nsCSSKeywords_h___
+
+#include "nsStringFwd.h"
+
+/*
+ Declare the enum list using the magic of preprocessing
+ enum values are "eCSSKeyword_foo" (where foo is the keyword)
+
+ To change the list of keywords, see nsCSSKeywordList.h
+
+ */
+#define CSS_KEY(_name,_id) eCSSKeyword_##_id,
+enum nsCSSKeyword : int16_t {
+ eCSSKeyword_UNKNOWN = -1,
+#include "nsCSSKeywordList.h"
+ eCSSKeyword_COUNT
+};
+#undef CSS_KEY
+
+
+class nsCSSKeywords {
+public:
+ static void AddRefTable(void);
+ static void ReleaseTable(void);
+
+ // Given a keyword string, return the enum value
+ static nsCSSKeyword LookupKeyword(const nsACString& aKeyword);
+ static nsCSSKeyword LookupKeyword(const nsAString& aKeyword);
+
+ // Given a keyword enum, get the string value
+ static const nsAFlatCString& GetStringValue(nsCSSKeyword aKeyword);
+};
+
+#endif /* nsCSSKeywords_h___ */
diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp
new file mode 100644
index 000000000..1108ce5b5
--- /dev/null
+++ b/layout/style/nsCSSParser.cpp
@@ -0,0 +1,18324 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 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/. */
+
+/* parsing of CSS stylesheets, based on a token stream from the CSS scanner */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Move.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/TypedEnumBits.h"
+
+#include <algorithm> // for std::stable_sort
+#include <limits> // for std::numeric_limits
+
+#include "nsCSSParser.h"
+#include "nsAlgorithm.h"
+#include "nsCSSProps.h"
+#include "nsCSSKeywords.h"
+#include "nsCSSScanner.h"
+#include "mozilla/css/ErrorReporter.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/css/StyleRule.h"
+#include "mozilla/css/ImportRule.h"
+#include "nsCSSRules.h"
+#include "mozilla/css/NameSpaceRule.h"
+#include "nsTArray.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Declaration.h"
+#include "nsStyleConsts.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIAtom.h"
+#include "nsColor.h"
+#include "nsCSSPseudoClasses.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsNameSpaceManager.h"
+#include "nsXMLNameSpaceMap.h"
+#include "nsError.h"
+#include "nsIMediaList.h"
+#include "nsStyleUtil.h"
+#include "nsIPrincipal.h"
+#include "nsICSSUnprefixingService.h"
+#include "mozilla/Sprintf.h"
+#include "nsContentUtils.h"
+#include "nsAutoPtr.h"
+#include "CSSCalc.h"
+#include "nsMediaFeatures.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsRuleData.h"
+#include "mozilla/CSSVariableValues.h"
+#include "mozilla/dom/AnimationEffectReadOnlyBinding.h"
+#include "mozilla/dom/URL.h"
+#include "gfxFontFamilyList.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+
+typedef nsCSSProps::KTableEntry KTableEntry;
+
+// pref-backed bool values (hooked up in nsCSSParser::Startup)
+static bool sOpentypeSVGEnabled;
+static bool sWebkitPrefixedAliasesEnabled;
+static bool sWebkitDevicePixelRatioEnabled;
+static bool sUnprefixingServiceEnabled;
+#ifdef NIGHTLY_BUILD
+static bool sUnprefixingServiceGloballyWhitelisted;
+#endif
+static bool sMozGradientsEnabled;
+static bool sControlCharVisibility;
+
+const uint32_t
+nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = {
+#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
+ stylestruct_, stylestructoffset_, animtype_) \
+ parsevariant_,
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+};
+
+// Maximum number of repetitions for the repeat() function
+// in the grid-template-rows and grid-template-columns properties,
+// to limit high memory usage from small stylesheets.
+// Must be a positive integer. Should be large-ish.
+#define GRID_TEMPLATE_MAX_REPETITIONS 10000
+
+// End-of-array marker for mask arguments to ParseBitmaskValues
+#define MASK_END_VALUE (-1)
+
+enum class CSSParseResult : int32_t {
+ // Parsed something successfully:
+ Ok,
+ // Did not find what we were looking for, but did not consume any token:
+ NotFound,
+ // Unexpected token or token value, too late for UngetToken() to be enough:
+ Error
+};
+
+enum class GridTrackSizeFlags {
+ eDefaultTrackSize = 0x0,
+ eFixedTrackSize = 0x1, // parse a <fixed-size> instead of <track-size>
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackSizeFlags)
+
+enum class GridTrackListFlags {
+ eDefaultTrackList = 0x0, // parse a <track-list>
+ eExplicitTrackList = 0x1, // parse an <explicit-track-list> instead
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackListFlags)
+
+namespace {
+
+// Rule processing function
+typedef void (* RuleAppendFunc) (css::Rule* aRule, void* aData);
+static void AssignRuleToPointer(css::Rule* aRule, void* aPointer);
+static void AppendRuleToSheet(css::Rule* aRule, void* aParser);
+
+struct CSSParserInputState {
+ nsCSSScannerPosition mPosition;
+ nsCSSToken mToken;
+ bool mHavePushBack;
+};
+
+static_assert(css::eAuthorSheetFeatures == 0 &&
+ css::eUserSheetFeatures == 1 &&
+ css::eAgentSheetFeatures == 2,
+ "sheet parsing mode constants won't fit "
+ "in CSSParserImpl::mParsingMode");
+
+// Your basic top-down recursive descent style parser
+// The exposed methods and members of this class are precisely those
+// needed by nsCSSParser, far below.
+class CSSParserImpl {
+public:
+ CSSParserImpl();
+ ~CSSParserImpl();
+
+ nsresult SetStyleSheet(CSSStyleSheet* aSheet);
+
+ nsIDocument* GetDocument();
+
+ nsresult SetQuirkMode(bool aQuirkMode);
+
+ nsresult SetChildLoader(mozilla::css::Loader* aChildLoader);
+
+ // Clears everything set by the above Set*() functions.
+ void Reset();
+
+ nsresult ParseSheet(const nsAString& aInput,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ uint32_t aLineNumber,
+ css::LoaderReusableStyleSheets* aReusableSheets);
+
+ already_AddRefed<css::Declaration>
+ ParseStyleAttribute(const nsAString& aAttributeValue,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aNodePrincipal);
+
+ nsresult ParseDeclarations(const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ css::Declaration* aDeclaration,
+ bool* aChanged);
+
+ nsresult ParseRule(const nsAString& aRule,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ css::Rule** aResult);
+
+ void ParseProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ css::Declaration* aDeclaration,
+ bool* aChanged,
+ bool aIsImportant,
+ bool aIsSVGMode);
+ void ParseLonghandProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue);
+
+ bool ParseTransformProperty(const nsAString& aPropValue,
+ bool aDisallowRelativeValues,
+ nsCSSValue& aResult);
+
+ void ParseMediaList(const nsSubstring& aBuffer,
+ nsIURI* aURL, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsMediaList* aMediaList,
+ bool aHTMLMode);
+
+ bool ParseSourceSizeList(const nsAString& aBuffer,
+ nsIURI* aURI, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
+ InfallibleTArray<nsCSSValue>& aValues,
+ bool aHTMLMode);
+
+ void ParseVariable(const nsAString& aVariableName,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ css::Declaration* aDeclaration,
+ bool* aChanged,
+ bool aIsImportant);
+
+ bool ParseFontFamilyListString(const nsSubstring& aBuffer,
+ nsIURI* aURL, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsCSSValue& aValue);
+
+ bool ParseColorString(const nsSubstring& aBuffer,
+ nsIURI* aURL, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsCSSValue& aValue,
+ bool aSuppressErrors /* false */);
+
+ bool ParseMarginString(const nsSubstring& aBuffer,
+ nsIURI* aURL, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsCSSValue& aValue,
+ bool aSuppressErrors /* false */);
+
+ nsresult ParseSelectorString(const nsSubstring& aSelectorString,
+ nsIURI* aURL, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsCSSSelectorList **aSelectorList);
+
+ already_AddRefed<nsCSSKeyframeRule>
+ ParseKeyframeRule(const nsSubstring& aBuffer,
+ nsIURI* aURL,
+ uint32_t aLineNumber);
+
+ bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
+ nsIURI* aURL, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ InfallibleTArray<float>& aSelectorList);
+
+ bool EvaluateSupportsDeclaration(const nsAString& aProperty,
+ const nsAString& aValue,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal);
+
+ bool EvaluateSupportsCondition(const nsAString& aCondition,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal);
+
+ bool ParseCounterStyleName(const nsAString& aBuffer,
+ nsIURI* aURL,
+ nsAString& aName);
+
+ bool ParseCounterDescriptor(nsCSSCounterDesc aDescID,
+ const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue);
+
+ bool ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
+ const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue);
+
+ bool IsValueValidForProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue);
+
+ typedef nsCSSParser::VariableEnumFunc VariableEnumFunc;
+
+ /**
+ * Parses a CSS token stream value and invokes a callback function for each
+ * variable reference that is encountered.
+ *
+ * @param aPropertyValue The CSS token stream value.
+ * @param aFunc The callback function to invoke; its parameters are the
+ * variable name found and the aData argument passed in to this function.
+ * @param aData User data to pass in to the callback.
+ * @return Whether aPropertyValue could be parsed as a valid CSS token stream
+ * value (e.g., without syntactic errors in variable references).
+ */
+ bool EnumerateVariableReferences(const nsAString& aPropertyValue,
+ VariableEnumFunc aFunc,
+ void* aData);
+
+ /**
+ * Parses aPropertyValue as a CSS token stream value and resolves any
+ * variable references using the variables in aVariables.
+ *
+ * @param aPropertyValue The CSS token stream value.
+ * @param aVariables The set of variable values to use when resolving variable
+ * references.
+ * @param aResult Out parameter that gets the resolved value.
+ * @param aFirstToken Out parameter that gets the type of the first token in
+ * aResult.
+ * @param aLastToken Out parameter that gets the type of the last token in
+ * aResult.
+ * @return Whether aResult could be parsed successfully and variable reference
+ * substitution succeeded.
+ */
+ bool ResolveVariableValue(const nsAString& aPropertyValue,
+ const CSSVariableValues* aVariables,
+ nsString& aResult,
+ nsCSSTokenSerializationType& aFirstToken,
+ nsCSSTokenSerializationType& aLastToken);
+
+ /**
+ * Parses a string as a CSS token stream value for particular property,
+ * resolving any variable references. The parsed property value is stored
+ * in the specified nsRuleData object. If aShorthandPropertyID has a value
+ * other than eCSSProperty_UNKNOWN, this is the property that will be parsed;
+ * otherwise, aPropertyID will be parsed. Either way, only aPropertyID,
+ * a longhand property, will be copied over to the rule data.
+ *
+ * If the property cannot be parsed, it will be treated as if 'initial' or
+ * 'inherit' were specified, for non-inherited and inherited properties
+ * respectively.
+ *
+ * @param aPropertyID The ID of the longhand property whose value is to be
+ * copied to the rule data.
+ * @param aShorthandPropertyID The ID of the shorthand property to be parsed.
+ * If a longhand property is to be parsed, aPropertyID is that property,
+ * and aShorthandPropertyID must be eCSSProperty_UNKNOWN.
+ * @param aValue The CSS token stream value.
+ * @param aVariables The set of variable values to use when resolving variable
+ * references.
+ * @param aRuleData The rule data object into which parsed property value for
+ * aPropertyID will be stored.
+ */
+ void ParsePropertyWithVariableReferences(nsCSSPropertyID aPropertyID,
+ nsCSSPropertyID aShorthandPropertyID,
+ const nsAString& aValue,
+ const CSSVariableValues* aVariables,
+ nsRuleData* aRuleData,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal,
+ CSSStyleSheet* aSheet,
+ uint32_t aLineNumber,
+ uint32_t aLineOffset);
+
+ bool AgentRulesEnabled() const {
+ return mParsingMode == css::eAgentSheetFeatures;
+ }
+ bool ChromeRulesEnabled() const {
+ return mIsChrome;
+ }
+ bool UserRulesEnabled() const {
+ return mParsingMode == css::eAgentSheetFeatures ||
+ mParsingMode == css::eUserSheetFeatures;
+ }
+
+ CSSEnabledState EnabledState() const {
+ static_assert(int(CSSEnabledState::eForAllContent) == 0,
+ "CSSEnabledState::eForAllContent should be zero for "
+ "this bitfield to work");
+ CSSEnabledState enabledState = CSSEnabledState::eForAllContent;
+ if (AgentRulesEnabled()) {
+ enabledState |= CSSEnabledState::eInUASheets;
+ }
+ if (mIsChrome) {
+ enabledState |= CSSEnabledState::eInChrome;
+ }
+ return enabledState;
+ }
+
+ nsCSSPropertyID LookupEnabledProperty(const nsAString& aProperty) {
+ return nsCSSProps::LookupProperty(aProperty, EnabledState());
+ }
+
+protected:
+ class nsAutoParseCompoundProperty;
+ friend class nsAutoParseCompoundProperty;
+
+ class nsAutoFailingSupportsRule;
+ friend class nsAutoFailingSupportsRule;
+
+ class nsAutoSuppressErrors;
+ friend class nsAutoSuppressErrors;
+
+ void AppendRule(css::Rule* aRule);
+ friend void AppendRuleToSheet(css::Rule*, void*); // calls AppendRule
+
+ /**
+ * This helper class automatically calls SetParsingCompoundProperty in its
+ * constructor and takes care of resetting it to false in its destructor.
+ */
+ class nsAutoParseCompoundProperty {
+ public:
+ explicit nsAutoParseCompoundProperty(CSSParserImpl* aParser) : mParser(aParser)
+ {
+ NS_ASSERTION(!aParser->IsParsingCompoundProperty(),
+ "already parsing compound property");
+ NS_ASSERTION(aParser, "Null parser?");
+ aParser->SetParsingCompoundProperty(true);
+ }
+
+ ~nsAutoParseCompoundProperty()
+ {
+ mParser->SetParsingCompoundProperty(false);
+ }
+ private:
+ CSSParserImpl* mParser;
+ };
+
+ /**
+ * This helper class conditionally sets mInFailingSupportsRule to
+ * true if aCondition = false, and resets it to its original value in its
+ * destructor. If we are already somewhere within a failing @supports
+ * rule, passing in aCondition = true does not change mInFailingSupportsRule.
+ */
+ class nsAutoFailingSupportsRule {
+ public:
+ nsAutoFailingSupportsRule(CSSParserImpl* aParser,
+ bool aCondition)
+ : mParser(aParser),
+ mOriginalValue(aParser->mInFailingSupportsRule)
+ {
+ if (!aCondition) {
+ mParser->mInFailingSupportsRule = true;
+ }
+ }
+
+ ~nsAutoFailingSupportsRule()
+ {
+ mParser->mInFailingSupportsRule = mOriginalValue;
+ }
+
+ private:
+ CSSParserImpl* mParser;
+ bool mOriginalValue;
+ };
+
+ /**
+ * Auto class to set aParser->mSuppressErrors to the specified value
+ * and restore it to its original value later.
+ */
+ class nsAutoSuppressErrors {
+ public:
+ explicit nsAutoSuppressErrors(CSSParserImpl* aParser,
+ bool aSuppressErrors = true)
+ : mParser(aParser),
+ mOriginalValue(aParser->mSuppressErrors)
+ {
+ mParser->mSuppressErrors = aSuppressErrors;
+ }
+
+ ~nsAutoSuppressErrors()
+ {
+ mParser->mSuppressErrors = mOriginalValue;
+ }
+
+ private:
+ CSSParserImpl* mParser;
+ bool mOriginalValue;
+ };
+
+ /**
+ * RAII class to set aParser->mInSupportsCondition to true and restore it
+ * to false later.
+ */
+ class MOZ_RAII nsAutoInSupportsCondition
+ {
+ public:
+ explicit nsAutoInSupportsCondition(CSSParserImpl* aParser)
+ : mParser(aParser)
+ {
+ MOZ_ASSERT(!aParser->mInSupportsCondition,
+ "nsAutoInSupportsCondition is not designed to be used "
+ "re-entrantly");
+ mParser->mInSupportsCondition = true;
+ }
+
+ ~nsAutoInSupportsCondition()
+ {
+ mParser->mInSupportsCondition = false;
+ }
+
+ private:
+ CSSParserImpl* const mParser;
+ };
+
+ // the caller must hold on to aString until parsing is done
+ void InitScanner(nsCSSScanner& aScanner,
+ css::ErrorReporter& aReporter,
+ nsIURI* aSheetURI, nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal);
+ void ReleaseScanner(void);
+
+ /**
+ * This is a RAII class which behaves like an "AutoRestore<>" for our parser
+ * input state. When instantiated, this class saves the current parser input
+ * state (in a CSSParserInputState object), and it restores the parser to
+ * that state when destructed, unless "DoNotRestore()" has been called.
+ */
+ class MOZ_RAII nsAutoCSSParserInputStateRestorer {
+ public:
+ explicit nsAutoCSSParserInputStateRestorer(CSSParserImpl* aParser
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mParser(aParser),
+ mShouldRestore(true)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ mParser->SaveInputState(mSavedState);
+ }
+
+ void DoNotRestore()
+ {
+ mShouldRestore = false;
+ }
+
+ ~nsAutoCSSParserInputStateRestorer()
+ {
+ if (mShouldRestore) {
+ mParser->RestoreSavedInputState(mSavedState);
+ }
+ }
+
+ private:
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+ CSSParserImpl* mParser;
+ CSSParserInputState mSavedState;
+ bool mShouldRestore;
+ };
+
+ /**
+ * This is a RAII class which creates a temporary nsCSSScanner for the given
+ * string, and reconfigures aParser to use *that* scanner instead of its
+ * existing scanner, until we go out of scope. (This allows us to rewrite
+ * a portion of a stylesheet using a temporary string, and switch to parsing
+ * that rewritten section, and then resume parsing the original stylesheet.)
+ *
+ * aParser must have a non-null nsCSSScanner (which we'll be temporarily
+ * replacing) and ErrorReporter (which this class will co-opt for the
+ * temporary parser). While we're in scope, we also suppress error reporting,
+ * so it doesn't really matter which reporter we use. We suppress reporting
+ * because this class is only used with CSS that is synthesized & didn't
+ * come directly from an author, and it would be confusing if we reported
+ * syntax errors for CSS that an author didn't provide.
+ *
+ * XXXdholbert we could also change this & report errors, if needed. Might
+ * want to customize the error reporting somehow though.
+ */
+ class MOZ_RAII nsAutoScannerChanger {
+ public:
+ nsAutoScannerChanger(CSSParserImpl* aParser,
+ const nsAString& aStringToScan
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mParser(aParser),
+ mOriginalScanner(aParser->mScanner),
+ mStringScanner(aStringToScan, 0),
+ mParserStateRestorer(aParser),
+ mErrorSuppresser(aParser)
+ {
+ MOZ_ASSERT(mOriginalScanner,
+ "Shouldn't use nsAutoScannerChanger unless we already "
+ "have a scanner");
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+ // Set & setup the new scanner:
+ mParser->mScanner = &mStringScanner;
+ mStringScanner.SetErrorReporter(mParser->mReporter);
+
+ // We might've had push-back on our original scanner (and if we did,
+ // that fact is saved via mParserStateRestorer). But we don't have
+ // push-back in mStringScanner, so clear that flag.
+ mParser->mHavePushBack = false;
+ }
+
+ ~nsAutoScannerChanger()
+ {
+ // Restore original scanner. All other cleanup is done by RAII members.
+ mParser->mScanner = mOriginalScanner;
+ }
+
+ private:
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+ CSSParserImpl* mParser;
+ nsCSSScanner *mOriginalScanner;
+ nsCSSScanner mStringScanner;
+ nsAutoCSSParserInputStateRestorer mParserStateRestorer;
+ nsAutoSuppressErrors mErrorSuppresser;
+ };
+
+
+ bool IsSVGMode() const {
+ return mScanner->IsSVGMode();
+ }
+
+ /**
+ * Saves the current input state, which includes any currently pushed
+ * back token, and the current position of the scanner.
+ */
+ void SaveInputState(CSSParserInputState& aState);
+
+ /**
+ * Restores the saved input state by pushing back any saved pushback
+ * token and calling RestoreSavedPosition on the scanner.
+ */
+ void RestoreSavedInputState(const CSSParserInputState& aState);
+
+ bool GetToken(bool aSkipWS);
+ void UngetToken();
+ bool GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum);
+ void AssertNextTokenAt(uint32_t aLine, uint32_t aCol)
+ {
+ // Beware that this method will call GetToken/UngetToken (in
+ // GetNextTokenLocation) in DEBUG builds, but not in non-DEBUG builds.
+ DebugOnly<uint32_t> lineAfter, colAfter;
+ MOZ_ASSERT(GetNextTokenLocation(true, &lineAfter, &colAfter) &&
+ lineAfter == aLine && colAfter == aCol,
+ "shouldn't have consumed any tokens");
+ }
+
+ bool ExpectSymbol(char16_t aSymbol, bool aSkipWS);
+ bool ExpectEndProperty();
+ bool CheckEndProperty();
+ nsSubstring* NextIdent();
+
+ // returns true when the stop symbol is found, and false for EOF
+ bool SkipUntil(char16_t aStopSymbol);
+ void SkipUntilOneOf(const char16_t* aStopSymbolChars);
+ // For each character in aStopSymbolChars from the end of the array
+ // to the start, calls SkipUntil with that character.
+ typedef AutoTArray<char16_t, 16> StopSymbolCharStack;
+ void SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars);
+ // returns true if the stop symbol or EOF is found, and false for an
+ // unexpected ')', ']' or '}'; this not safe to call outside variable
+ // resolution, as it doesn't handle mismatched content
+ bool SkipBalancedContentUntil(char16_t aStopSymbol);
+
+ void SkipRuleSet(bool aInsideBraces);
+ bool SkipAtRule(bool aInsideBlock);
+ bool SkipDeclaration(bool aCheckForBraces);
+
+ void PushGroup(css::GroupRule* aRule);
+ void PopGroup();
+
+ bool ParseRuleSet(RuleAppendFunc aAppendFunc, void* aProcessData,
+ bool aInsideBraces = false);
+ bool ParseAtRule(RuleAppendFunc aAppendFunc, void* aProcessData,
+ bool aInAtRule);
+ bool ParseCharsetRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ bool ParseImportRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ bool ParseURLOrString(nsString& aURL);
+ bool GatherMedia(nsMediaList* aMedia, bool aInAtRule);
+
+ enum eMediaQueryType { eMediaQueryNormal,
+ // Parsing an at rule
+ eMediaQueryAtRule,
+ // Attempt to consume a single media-condition and
+ // stop. Note that the spec defines "expression and/or
+ // expression" as one condition but "expression,
+ // expression" as two.
+ eMediaQuerySingleCondition };
+ bool ParseMediaQuery(eMediaQueryType aMode, nsMediaQuery **aQuery,
+ bool *aHitStop);
+ bool ParseMediaQueryExpression(nsMediaQuery* aQuery);
+ void ProcessImport(const nsString& aURLSpec,
+ nsMediaList* aMedia,
+ RuleAppendFunc aAppendFunc,
+ void* aProcessData,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber);
+ bool ParseGroupRule(css::GroupRule* aRule, RuleAppendFunc aAppendFunc,
+ void* aProcessData);
+ bool ParseMediaRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ bool ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ bool ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ void ProcessNameSpace(const nsString& aPrefix,
+ const nsString& aURLSpec, RuleAppendFunc aAppendFunc,
+ void* aProcessData,
+ uint32_t aLineNumber, uint32_t aColumnNumber);
+
+ bool ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ bool ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc,
+ void* aProcessData);
+ bool ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule *aRule);
+ bool ParseFontDescriptor(nsCSSFontFaceRule* aRule);
+ bool ParseFontDescriptorValue(nsCSSFontDesc aDescID,
+ nsCSSValue& aValue);
+
+ bool ParsePageRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ bool ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ already_AddRefed<nsCSSKeyframeRule> ParseKeyframeRule();
+ bool ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList);
+
+ bool ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ bool ParseSupportsCondition(bool& aConditionMet);
+ bool ParseSupportsConditionNegation(bool& aConditionMet);
+ bool ParseSupportsConditionInParens(bool& aConditionMet);
+ bool ParseSupportsMozBoolPrefName(bool& aConditionMet);
+ bool ParseSupportsConditionInParensInsideParens(bool& aConditionMet);
+ bool ParseSupportsConditionTerms(bool& aConditionMet);
+ enum SupportsConditionTermOperator { eAnd, eOr };
+ bool ParseSupportsConditionTermsAfterOperator(
+ bool& aConditionMet,
+ SupportsConditionTermOperator aOperator);
+
+ bool ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+ bool ParseCounterStyleName(nsAString& aName, bool aForDefinition);
+ bool ParseCounterStyleNameValue(nsCSSValue& aValue);
+ bool ParseCounterDescriptor(nsCSSCounterStyleRule *aRule);
+ bool ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
+ nsCSSValue& aValue);
+ bool ParseCounterRange(nsCSSValuePair& aPair);
+
+ /**
+ * Parses the current input stream for a CSS token stream value and resolves
+ * any variable references using the variables in aVariables.
+ *
+ * @param aVariables The set of variable values to use when resolving variable
+ * references.
+ * @param aResult Out parameter that, if the function returns true, will be
+ * replaced with the resolved value.
+ * @return Whether aResult could be parsed successfully and variable reference
+ * substitution succeeded.
+ */
+ bool ResolveValueWithVariableReferences(
+ const CSSVariableValues* aVariables,
+ nsString& aResult,
+ nsCSSTokenSerializationType& aResultFirstToken,
+ nsCSSTokenSerializationType& aResultLastToken);
+ // Helper function for ResolveValueWithVariableReferences.
+ bool ResolveValueWithVariableReferencesRec(
+ nsString& aResult,
+ nsCSSTokenSerializationType& aResultFirstToken,
+ nsCSSTokenSerializationType& aResultLastToken,
+ const CSSVariableValues* aVariables);
+
+ enum nsSelectorParsingStatus {
+ // we have parsed a selector and we saw a token that cannot be
+ // part of a selector:
+ eSelectorParsingStatus_Done,
+ // we should continue parsing the selector:
+ eSelectorParsingStatus_Continue,
+ // we saw an unexpected token or token value,
+ // or we saw end-of-file with an unfinished selector:
+ eSelectorParsingStatus_Error
+ };
+ nsSelectorParsingStatus ParseIDSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector);
+
+ nsSelectorParsingStatus ParseClassSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector);
+
+ // aPseudoElement and aPseudoElementArgs are the location where
+ // pseudo-elements (as opposed to pseudo-classes) are stored;
+ // pseudo-classes are stored on aSelector. aPseudoElement and
+ // aPseudoElementArgs must be non-null iff !aIsNegated.
+ nsSelectorParsingStatus ParsePseudoSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector,
+ bool aIsNegated,
+ nsIAtom** aPseudoElement,
+ nsAtomList** aPseudoElementArgs,
+ CSSPseudoElementType* aPseudoElementType);
+
+ nsSelectorParsingStatus ParseAttributeSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector);
+
+ nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector,
+ bool aIsNegated);
+
+ nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
+ CSSPseudoClassType aType);
+
+ nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
+ CSSPseudoClassType aType);
+
+ nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
+ CSSPseudoClassType aType);
+
+ nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector);
+
+ // If aStopChar is non-zero, the selector list is done when we hit
+ // aStopChar. Otherwise, it's done when we hit EOF.
+ bool ParseSelectorList(nsCSSSelectorList*& aListHead,
+ char16_t aStopChar);
+ bool ParseSelectorGroup(nsCSSSelectorList*& aListHead);
+ bool ParseSelector(nsCSSSelectorList* aList, char16_t aPrevCombinator);
+
+ enum {
+ eParseDeclaration_InBraces = 1 << 0,
+ eParseDeclaration_AllowImportant = 1 << 1,
+ // The declaration we're parsing was generated by the CSSUnprefixingService:
+ eParseDeclaration_FromUnprefixingSvc = 1 << 2
+ };
+ enum nsCSSContextType {
+ eCSSContext_General,
+ eCSSContext_Page
+ };
+
+ already_AddRefed<css::Declaration>
+ ParseDeclarationBlock(uint32_t aFlags,
+ nsCSSContextType aContext = eCSSContext_General);
+ bool ParseDeclaration(css::Declaration* aDeclaration,
+ uint32_t aFlags,
+ bool aMustCallValueAppended,
+ bool* aChanged,
+ nsCSSContextType aContext = eCSSContext_General);
+
+ // A "prefix-aware" wrapper for nsCSSKeywords::LookupKeyword().
+ // Use this instead of LookupKeyword() if you might be parsing an unprefixed
+ // property (like "display") for which we emulate a vendor-prefixed value
+ // (like "-webkit-box").
+ nsCSSKeyword LookupKeywordPrefixAware(nsAString& aKeywordStr,
+ const KTableEntry aKeywordTable[]);
+
+ bool ShouldUseUnprefixingService() const;
+ bool ParsePropertyWithUnprefixingService(const nsAString& aPropertyName,
+ css::Declaration* aDeclaration,
+ uint32_t aFlags,
+ bool aMustCallValueAppended,
+ bool* aChanged,
+ nsCSSContextType aContext);
+ // When we detect a webkit-prefixed gradient expression, this function can be
+ // used to parse its body into outparam |aValue|, with the help of the
+ // CSSUnprefixingService.
+ // Only call if ShouldUseUnprefixingService() returns true.
+ bool ParseWebkitPrefixedGradientWithService(nsAString& aPrefixedFuncName,
+ nsCSSValue& aValue);
+
+ bool ParseProperty(nsCSSPropertyID aPropID);
+ bool ParsePropertyByFunction(nsCSSPropertyID aPropID);
+ CSSParseResult ParseSingleValueProperty(nsCSSValue& aValue,
+ nsCSSPropertyID aPropID);
+ bool ParseSingleValuePropertyByFunction(nsCSSValue& aValue,
+ nsCSSPropertyID aPropID);
+
+ // This is similar to ParseSingleValueProperty but only works for
+ // properties that are parsed with ParseBoxProperties or
+ // ParseGroupedBoxProperty.
+ //
+ // Only works with variants with the following flags:
+ // A, C, H, K, L, N, P, CALC.
+ CSSParseResult ParseBoxProperty(nsCSSValue& aValue,
+ nsCSSPropertyID aPropID);
+
+ enum PriorityParsingStatus {
+ ePriority_None,
+ ePriority_Important,
+ ePriority_Error
+ };
+ PriorityParsingStatus ParsePriority();
+
+#ifdef MOZ_XUL
+ bool ParseTreePseudoElement(nsAtomList **aPseudoElementArgs);
+#endif
+
+ // Property specific parsing routines
+ bool ParseImageLayers(const nsCSSPropertyID aTable[]);
+
+ struct ImageLayersShorthandParseState {
+ nsCSSValue& mColor;
+ nsCSSValueList* mImage;
+ nsCSSValuePairList* mRepeat;
+ nsCSSValueList* mAttachment; // A property for background layer only
+ nsCSSValueList* mClip;
+ nsCSSValueList* mOrigin;
+ nsCSSValueList* mPositionX;
+ nsCSSValueList* mPositionY;
+ nsCSSValuePairList* mSize;
+ nsCSSValueList* mComposite; // A property for mask layer only
+ nsCSSValueList* mMode; // A property for mask layer only
+ ImageLayersShorthandParseState(
+ nsCSSValue& aColor, nsCSSValueList* aImage, nsCSSValuePairList* aRepeat,
+ nsCSSValueList* aAttachment, nsCSSValueList* aClip,
+ nsCSSValueList* aOrigin,
+ nsCSSValueList* aPositionX, nsCSSValueList* aPositionY,
+ nsCSSValuePairList* aSize, nsCSSValueList* aComposite,
+ nsCSSValueList* aMode) :
+ mColor(aColor), mImage(aImage), mRepeat(aRepeat),
+ mAttachment(aAttachment), mClip(aClip), mOrigin(aOrigin),
+ mPositionX(aPositionX), mPositionY(aPositionY),
+ mSize(aSize), mComposite(aComposite),
+ mMode(aMode) {};
+ };
+
+ bool IsFunctionTokenValidForImageLayerImage(const nsCSSToken& aToken) const;
+ bool ParseImageLayersItem(ImageLayersShorthandParseState& aState,
+ const nsCSSPropertyID aTable[]);
+
+ bool ParseValueList(nsCSSPropertyID aPropID); // a single value prop-id
+ bool ParseImageLayerRepeat(nsCSSPropertyID aPropID);
+ bool ParseImageLayerRepeatValues(nsCSSValuePair& aValue);
+ bool ParseImageLayerPosition(const nsCSSPropertyID aTable[]);
+ bool ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal);
+
+ // ParseBoxPositionValues parses the CSS 2.1 background-position syntax,
+ // which is still used by some properties. See ParsePositionValue
+ // for the css3-background syntax.
+ bool ParseBoxPositionValues(nsCSSValuePair& aOut, bool aAcceptsInherit,
+ bool aAllowExplicitCenter = true); // deprecated
+
+ // ParsePositionValue parses a CSS <position> value, which is used by
+ // the 'background-position' property.
+ bool ParsePositionValue(nsCSSValue& aOut);
+ bool ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY);
+
+ bool ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal);
+ bool ParseImageLayerSize(nsCSSPropertyID aPropID);
+ bool ParseImageLayerSizeValues(nsCSSValuePair& aOut);
+ bool ParseBorderColor();
+ bool ParseBorderColors(nsCSSPropertyID aProperty);
+ void SetBorderImageInitialValues();
+ bool ParseBorderImageRepeat(bool aAcceptsInherit);
+ // If ParseBorderImageSlice returns false, aConsumedTokens indicates
+ // whether or not any tokens were consumed (in other words, was the property
+ // in error or just not present). If ParseBorderImageSlice returns true
+ // aConsumedTokens is always true.
+ bool ParseBorderImageSlice(bool aAcceptsInherit, bool* aConsumedTokens);
+ bool ParseBorderImageWidth(bool aAcceptsInherit);
+ bool ParseBorderImageOutset(bool aAcceptsInherit);
+ bool ParseBorderImage();
+ bool ParseBorderSpacing();
+ bool ParseBorderSide(const nsCSSPropertyID aPropIDs[],
+ bool aSetAllSides);
+ bool ParseBorderStyle();
+ bool ParseBorderWidth();
+
+ bool ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask);
+ bool ParseCalcAdditiveExpression(nsCSSValue& aValue,
+ uint32_t& aVariantMask);
+ bool ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
+ uint32_t& aVariantMask,
+ bool *aHadFinalWS);
+ bool ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask);
+ bool RequireWhitespace();
+
+ // For "flex" shorthand property, defined in CSS Flexbox spec
+ bool ParseFlex();
+ // For "flex-flow" shorthand property, defined in CSS Flexbox spec
+ bool ParseFlexFlow();
+
+ // CSS Grid
+ bool ParseGridAutoFlow();
+
+ // Parse a <line-names> expression.
+ // If successful, either leave aValue untouched,
+ // to indicate that we parsed the empty list,
+ // or set it to a eCSSUnit_List of eCSSUnit_Ident.
+ //
+ // To parse an optional <line-names> (ie. if not finding an open bracket
+ // is considered the same as an empty list),
+ // treat CSSParseResult::NotFound the same as CSSParseResult::Ok.
+ //
+ // If aValue is already a eCSSUnit_List, append to that list.
+ CSSParseResult ParseGridLineNames(nsCSSValue& aValue);
+ bool ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr);
+ bool ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue);
+
+ CSSParseResult ParseGridTrackBreadth(nsCSSValue& aValue);
+ // eFixedTrackSize in aFlags makes it parse a <fixed-size>.
+ CSSParseResult ParseGridTrackSize(nsCSSValue& aValue,
+ GridTrackSizeFlags aFlags = GridTrackSizeFlags::eDefaultTrackSize);
+
+ bool ParseGridAutoColumnsRows(nsCSSPropertyID aPropID);
+ bool ParseGridTrackListRepeat(nsCSSValueList** aTailPtr);
+ bool ParseGridTrackRepeatIntro(bool aForSubgrid,
+ int32_t* aRepetitions,
+ Maybe<int32_t>* aRepeatAutoEnum);
+
+ // Assuming a [ <line-names>? ] has already been parsed,
+ // parse the rest of a <track-list>.
+ //
+ // This exists because [ <line-names>? ] is ambiguous in the 'grid-template'
+ // shorthand: it can be either the start of a <track-list> (in
+ // a <'grid-template-rows'>) or of the intertwined syntax that sets both
+ // grid-template-rows and grid-template-areas.
+ //
+ // On success, |aValue| will be a list of odd length >= 3,
+ // starting with a <line-names> (which is itself a list)
+ // and alternating between that and <track-size>.
+ bool ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue,
+ const nsCSSValue& aFirstLineNames,
+ GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList);
+
+ bool ParseGridTrackList(nsCSSPropertyID aPropID,
+ GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList);
+ bool ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID);
+
+ // |aAreaIndices| is a lookup table to help us parse faster,
+ // mapping area names to indices in |aResult.mNamedAreas|.
+ bool ParseGridTemplateAreasLine(const nsAutoString& aInput,
+ css::GridTemplateAreasValue* aResult,
+ nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices);
+ bool ParseGridTemplateAreas();
+ bool ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand);
+ bool ParseGridTemplate(bool aForGridShorthand = false);
+ bool ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames);
+ bool ParseGrid();
+ CSSParseResult ParseGridShorthandAutoProps(int32_t aAutoFlowAxis);
+ bool ParseGridLine(nsCSSValue& aValue);
+ bool ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID);
+ bool ParseGridColumnRow(nsCSSPropertyID aStartPropID,
+ nsCSSPropertyID aEndPropID);
+ bool ParseGridArea();
+ bool ParseGridGap();
+
+ bool ParseInitialLetter();
+
+ // parsing 'align/justify-items/self' from the css-align spec
+ bool ParseAlignJustifyPosition(nsCSSValue& aResult,
+ const KTableEntry aTable[]);
+ bool ParseJustifyItems();
+ bool ParseAlignItems();
+ bool ParseAlignJustifySelf(nsCSSPropertyID aPropID);
+ // parsing 'align/justify-content' from the css-align spec
+ bool ParseAlignJustifyContent(nsCSSPropertyID aPropID);
+ bool ParsePlaceContent();
+ bool ParsePlaceItems();
+ bool ParsePlaceSelf();
+
+ // for 'clip' and '-moz-image-region'
+ bool ParseRect(nsCSSPropertyID aPropID);
+ bool ParseColumns();
+ bool ParseContain(nsCSSValue& aValue);
+ bool ParseContent();
+ bool ParseCounterData(nsCSSPropertyID aPropID);
+ bool ParseCursor();
+ bool ParseFont();
+ bool ParseFontSynthesis(nsCSSValue& aValue);
+ bool ParseSingleAlternate(int32_t& aWhichFeature, nsCSSValue& aValue);
+ bool ParseFontVariantAlternates(nsCSSValue& aValue);
+ bool MergeBitmaskValue(int32_t aNewValue, const int32_t aMasks[],
+ int32_t& aMergedValue);
+ bool ParseBitmaskValues(nsCSSValue& aValue,
+ const KTableEntry aKeywordTable[],
+ const int32_t aMasks[]);
+ bool ParseFontVariantEastAsian(nsCSSValue& aValue);
+ bool ParseFontVariantLigatures(nsCSSValue& aValue);
+ bool ParseFontVariantNumeric(nsCSSValue& aValue);
+ bool ParseFontVariant();
+ bool ParseFontWeight(nsCSSValue& aValue);
+ bool ParseOneFamily(nsAString& aFamily, bool& aOneKeyword, bool& aQuoted);
+ bool ParseFamily(nsCSSValue& aValue);
+ bool ParseFontFeatureSettings(nsCSSValue& aValue);
+ bool ParseFontSrc(nsCSSValue& aValue);
+ bool ParseFontSrcFormat(InfallibleTArray<nsCSSValue>& values);
+ bool ParseFontRanges(nsCSSValue& aValue);
+ bool ParseListStyle();
+ bool ParseListStyleType(nsCSSValue& aValue);
+ bool ParseMargin();
+ bool ParseClipPath(nsCSSValue& aValue);
+ bool ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues = false);
+ bool ParseObjectPosition();
+ bool ParseOutline();
+ bool ParseOverflow();
+ bool ParsePadding();
+ bool ParseQuotes();
+ bool ParseTextAlign(nsCSSValue& aValue,
+ const KTableEntry aTable[]);
+ bool ParseTextAlign(nsCSSValue& aValue);
+ bool ParseTextAlignLast(nsCSSValue& aValue);
+ bool ParseTextDecoration();
+ bool ParseTextDecorationLine(nsCSSValue& aValue);
+ bool ParseTextEmphasis();
+ bool ParseTextEmphasisPosition(nsCSSValue& aValue);
+ bool ParseTextEmphasisStyle(nsCSSValue& aValue);
+ bool ParseTextCombineUpright(nsCSSValue& aValue);
+ bool ParseTextOverflow(nsCSSValue& aValue);
+ bool ParseTouchAction(nsCSSValue& aValue);
+
+ bool ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow);
+ bool ParseShadowList(nsCSSPropertyID aProperty);
+ bool ParseShapeOutside(nsCSSValue& aValue);
+ bool ParseTransitionProperty();
+ bool ParseTransitionTimingFunctionValues(nsCSSValue& aValue);
+ bool ParseTransitionTimingFunctionValueComponent(float& aComponent,
+ char aStop,
+ bool aIsXPoint);
+ bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue);
+ enum ParseAnimationOrTransitionShorthandResult {
+ eParseAnimationOrTransitionShorthand_Values,
+ eParseAnimationOrTransitionShorthand_Inherit,
+ eParseAnimationOrTransitionShorthand_Error
+ };
+ ParseAnimationOrTransitionShorthandResult
+ ParseAnimationOrTransitionShorthand(const nsCSSPropertyID* aProperties,
+ const nsCSSValue* aInitialValues,
+ nsCSSValue* aValues,
+ size_t aNumProperties);
+ bool ParseTransition();
+ bool ParseAnimation();
+ bool ParseWillChange();
+
+ bool ParsePaint(nsCSSPropertyID aPropID);
+ bool ParseDasharray();
+ bool ParseMarker();
+ bool ParsePaintOrder();
+ bool ParseAll();
+ bool ParseScrollSnapType();
+ bool ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID);
+ bool ParseScrollSnapDestination(nsCSSValue& aValue);
+ bool ParseScrollSnapCoordinate(nsCSSValue& aValue);
+ bool ParseWebkitTextStroke();
+
+ /**
+ * Parses a variable value from a custom property declaration.
+ *
+ * @param aType Out parameter into which will be stored the type of variable
+ * value, indicating whether the parsed value was a token stream or one of
+ * the CSS-wide keywords.
+ * @param aValue Out parameter into which will be stored the token stream
+ * as a string, if the parsed custom property did take a token stream.
+ * @return Whether parsing succeeded.
+ */
+ bool ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
+ nsString& aValue);
+
+ /**
+ * Parses a CSS variable value. This could be 'initial', 'inherit', 'unset'
+ * or a token stream, which may or may not include variable references.
+ *
+ * @param aType Out parameter into which the type of the variable value
+ * will be stored.
+ * @param aDropBackslash Out parameter indicating whether during variable
+ * value parsing there was a trailing backslash before EOF that must
+ * be dropped when serializing the variable value.
+ * @param aImpliedCharacters Out parameter appended to which will be any
+ * characters that were implied when encountering EOF and which
+ * must be included at the end of the serialized variable value.
+ * @param aFunc A callback function to invoke when a variable reference
+ * is encountered. May be null. Arguments are the variable name
+ * and the aData argument passed in to this function.
+ * @param User data to pass in to the callback.
+ * @return Whether parsing succeeded.
+ */
+ bool ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
+ bool* aDropBackslash,
+ nsString& aImpliedCharacters,
+ void (*aFunc)(const nsAString&, void*),
+ void* aData);
+
+ /**
+ * Returns whether the scanner dropped a backslash just before EOF.
+ */
+ bool BackslashDropped();
+
+ /**
+ * Calls AppendImpliedEOFCharacters on mScanner.
+ */
+ void AppendImpliedEOFCharacters(nsAString& aResult);
+
+ // Reused utility parsing routines
+ void AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue);
+ bool ParseBoxProperties(const nsCSSPropertyID aPropIDs[]);
+ bool ParseGroupedBoxProperty(int32_t aVariantMask,
+ nsCSSValue& aValue,
+ uint32_t aRestrictions);
+ bool ParseBoxCornerRadius(const nsCSSPropertyID aPropID);
+ bool ParseBoxCornerRadiiInternals(nsCSSValue array[]);
+ bool ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[]);
+
+ int32_t ParseChoice(nsCSSValue aValues[],
+ const nsCSSPropertyID aPropIDs[], int32_t aNumIDs);
+
+ CSSParseResult ParseColor(nsCSSValue& aValue);
+
+ template<typename ComponentType>
+ bool ParseRGBColor(ComponentType& aR,
+ ComponentType& aG,
+ ComponentType& aB,
+ ComponentType& aA);
+ bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
+ float& aOpacity);
+
+ // The ParseColorOpacityAndCloseParen methods below attempt to parse an
+ // optional [ separator <alpha-value> ] expression, followed by a
+ // close-parenthesis, at the end of a css color function (e.g. "rgba()" or
+ // "hsla()"). If these functions simply encounter a close-parenthesis (without
+ // any [separator <alpha-value>]), they will still succeed (i.e. return true),
+ // with outparam 'aOpacity' set to a default opacity value (fully-opaque).
+ //
+ // The range of opacity component is [0, 255], and the default opacity value
+ // is 255 (fully-opaque) for this function.
+ bool ParseColorOpacityAndCloseParen(uint8_t& aOpacity,
+ char aSeparator);
+ // Similar to the previous one, but the range of opacity component is
+ // [0.0f, 1.0f] and the default opacity value is 1.0f (fully-opaque).
+ bool ParseColorOpacityAndCloseParen(float& aOpacity,
+ char aSeparator);
+
+ // Parse a <number> color component. The range of color component is [0, 255].
+ // If |aSeparator| is provided, this function will also attempt to parse that
+ // character after parsing the color component.
+ bool ParseColorComponent(uint8_t& aComponent, Maybe<char> aSeparator);
+ // Similar to the previous one, but parse a <percentage> color component.
+ // The range of color component is [0.0f, 1.0f].
+ bool ParseColorComponent(float& aComponent, Maybe<char> aSeparator);
+
+ // Parse a <hue> component.
+ // <hue> = <number> | <angle>
+ // The unit of outparam 'aAngle' is degree. Assume the unit to be degree if an
+ // unitless <number> is parsed.
+ bool ParseHue(float& aAngle);
+
+ bool ParseEnum(nsCSSValue& aValue,
+ const KTableEntry aKeywordTable[]);
+
+ // A special ParseEnum for the CSS Box Alignment properties that have
+ // 'baseline' values. In addition to the keywords in aKeywordTable, it also
+ // parses 'first baseline' and 'last baseline' as a single value.
+ // (aKeywordTable must contain 'baseline')
+ bool ParseAlignEnum(nsCSSValue& aValue, const KTableEntry aKeywordTable[]);
+
+ // Variant parsing methods
+ CSSParseResult ParseVariant(nsCSSValue& aValue,
+ uint32_t aVariantMask,
+ const KTableEntry aKeywordTable[]);
+ CSSParseResult ParseVariantWithRestrictions(nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[],
+ uint32_t aRestrictions);
+ CSSParseResult ParseNonNegativeVariant(nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[]);
+ CSSParseResult ParseOneOrLargerVariant(nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[]);
+
+ // Variant parsing methods that are guaranteed to UngetToken any token
+ // consumed on failure
+ bool ParseSingleTokenVariant(nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[])
+ {
+ MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
+ "use ParseVariant for variants in VARIANT_MULTIPLE_TOKENS");
+ CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
+ MOZ_ASSERT(result != CSSParseResult::Error);
+ return result == CSSParseResult::Ok;
+ }
+ bool ParseSingleTokenVariantWithRestrictions(
+ nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[],
+ uint32_t aRestrictions)
+ {
+ MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
+ "use ParseVariantWithRestrictions for variants in "
+ "VARIANT_MULTIPLE_TOKENS");
+ CSSParseResult result =
+ ParseVariantWithRestrictions(aValue, aVariantMask, aKeywordTable,
+ aRestrictions);
+ MOZ_ASSERT(result != CSSParseResult::Error);
+ return result == CSSParseResult::Ok;
+ }
+ bool ParseSingleTokenNonNegativeVariant(nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[])
+ {
+ MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
+ "use ParseNonNegativeVariant for variants in "
+ "VARIANT_MULTIPLE_TOKENS");
+ CSSParseResult result =
+ ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable);
+ MOZ_ASSERT(result != CSSParseResult::Error);
+ return result == CSSParseResult::Ok;
+ }
+ bool ParseSingleTokenOneOrLargerVariant(nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[])
+ {
+ MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
+ "use ParseOneOrLargerVariant for variants in "
+ "VARIANT_MULTIPLE_TOKENS");
+ CSSParseResult result =
+ ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable);
+ MOZ_ASSERT(result != CSSParseResult::Error);
+ return result == CSSParseResult::Ok;
+ }
+
+ // Helpers for some common ParseSingleTokenNonNegativeVariant calls.
+ bool ParseNonNegativeInteger(nsCSSValue& aValue)
+ {
+ return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_INTEGER, nullptr);
+ }
+ bool ParseNonNegativeNumber(nsCSSValue& aValue)
+ {
+ return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_NUMBER, nullptr);
+ }
+
+ // Helpers for some common ParseSingleTokenOneOrLargerVariant calls.
+ bool ParseOneOrLargerInteger(nsCSSValue& aValue)
+ {
+ return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_INTEGER, nullptr);
+ }
+ bool ParseOneOrLargerNumber(nsCSSValue& aValue)
+ {
+ return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_NUMBER, nullptr);
+ }
+
+ // http://dev.w3.org/csswg/css-values/#custom-idents
+ // Parse an identifier that is none of:
+ // * a CSS-wide keyword
+ // * "default"
+ // * a keyword in |aExcludedKeywords|
+ // * a keyword in |aPropertyKTable|
+ //
+ // |aExcludedKeywords| is an array of nsCSSKeyword
+ // that ends with a eCSSKeyword_UNKNOWN marker.
+ //
+ // |aPropertyKTable| can be used if some of the keywords to exclude
+ // also appear in an existing nsCSSProps::KTableEntry,
+ // to avoid duplicating them.
+ bool ParseCustomIdent(nsCSSValue& aValue,
+ const nsAutoString& aIdentValue,
+ const nsCSSKeyword aExcludedKeywords[] = nullptr,
+ const nsCSSProps::KTableEntry aPropertyKTable[] = nullptr);
+ bool ParseCounter(nsCSSValue& aValue);
+ bool ParseAttr(nsCSSValue& aValue);
+ bool ParseSymbols(nsCSSValue& aValue);
+ bool SetValueToURL(nsCSSValue& aValue, const nsString& aURL);
+ bool TranslateDimension(nsCSSValue& aValue, uint32_t aVariantMask,
+ float aNumber, const nsString& aUnit);
+ bool ParseImageOrientation(nsCSSValue& aAngle);
+ bool ParseImageRect(nsCSSValue& aImage);
+ bool ParseElement(nsCSSValue& aValue);
+ bool ParseColorStop(nsCSSValueGradient* aGradient);
+
+ enum GradientParsingFlags {
+ eGradient_Repeating = 1 << 0, // repeating-{linear|radial}-gradient
+ eGradient_MozLegacy = 1 << 1, // -moz-{linear|radial}-gradient
+ eGradient_WebkitLegacy = 1 << 2, // -webkit-{linear|radial}-gradient
+
+ // Mask to catch both "legacy" flags:
+ eGradient_AnyLegacy = eGradient_MozLegacy | eGradient_WebkitLegacy
+ };
+ bool ParseLinearGradient(nsCSSValue& aValue, uint8_t aFlags);
+ bool ParseRadialGradient(nsCSSValue& aValue, uint8_t aFlags);
+ bool IsLegacyGradientLine(const nsCSSTokenType& aType,
+ const nsString& aId);
+ bool ParseGradientColorStops(nsCSSValueGradient* aGradient,
+ nsCSSValue& aValue);
+
+ // For the ancient "-webkit-gradient(linear|radial, ...)" syntax:
+ bool ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
+ bool aIsHorizontal);
+ bool ParseWebkitGradientPoint(nsCSSValuePair& aPoint);
+ bool ParseWebkitGradientRadius(float& aRadius);
+ bool ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient);
+ bool ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient);
+ void FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient,
+ const nsCSSValuePair& aStartPoint,
+ const nsCSSValuePair& aSecondPoint);
+ void FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient,
+ const nsCSSValuePair& aFirstCenter,
+ const nsCSSValuePair& aSecondCenter,
+ const float aFirstRadius,
+ const float aSecondRadius);
+ bool ParseWebkitGradient(nsCSSValue& aValue);
+
+ void SetParsingCompoundProperty(bool aBool) {
+ mParsingCompoundProperty = aBool;
+ }
+ bool IsParsingCompoundProperty(void) const {
+ return mParsingCompoundProperty;
+ }
+
+ /* Functions for basic shapes */
+ bool ParseReferenceBoxAndBasicShape(nsCSSValue& aValue,
+ const KTableEntry aBoxKeywordTable[]);
+ bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens);
+ bool ParsePolygonFunction(nsCSSValue& aValue);
+ bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue);
+ bool ParseInsetFunction(nsCSSValue& aValue);
+ // We parse position values differently for basic-shape, by expanding defaults
+ // and replacing keywords with percentages
+ bool ParsePositionValueForBasicShape(nsCSSValue& aOut);
+
+
+ /* Functions for transform Parsing */
+ bool ParseSingleTransform(bool aIsPrefixed, bool aDisallowRelativeValues,
+ nsCSSValue& aValue);
+ bool ParseFunction(nsCSSKeyword aFunction, const uint32_t aAllowedTypes[],
+ uint32_t aVariantMaskAll, uint16_t aMinElems,
+ uint16_t aMaxElems, nsCSSValue &aValue);
+ bool ParseFunctionInternals(const uint32_t aVariantMask[],
+ uint32_t aVariantMaskAll,
+ uint16_t aMinElems,
+ uint16_t aMaxElems,
+ InfallibleTArray<nsCSSValue>& aOutput);
+
+ /* Functions for transform-origin/perspective-origin Parsing */
+ bool ParseTransformOrigin(bool aPerspective);
+
+ /* Functions for filter parsing */
+ bool ParseFilter();
+ bool ParseSingleFilter(nsCSSValue* aValue);
+ bool ParseDropShadow(nsCSSValue* aValue);
+
+ /* Find and return the namespace ID associated with aPrefix.
+ If aPrefix has not been declared in an @namespace rule, returns
+ kNameSpaceID_Unknown. */
+ int32_t GetNamespaceIdForPrefix(const nsString& aPrefix);
+
+ /* Find the correct default namespace, and set it on aSelector. */
+ void SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector);
+
+ // Current token. The value is valid after calling GetToken and invalidated
+ // by UngetToken.
+ nsCSSToken mToken;
+
+ // Our scanner.
+ nsCSSScanner* mScanner;
+
+ // Our error reporter.
+ css::ErrorReporter* mReporter;
+
+ // The URI to be used as a base for relative URIs.
+ nsCOMPtr<nsIURI> mBaseURI;
+
+ // The URI to be used as an HTTP "Referer" and for error reporting.
+ nsCOMPtr<nsIURI> mSheetURI;
+
+ // The principal of the sheet involved
+ nsCOMPtr<nsIPrincipal> mSheetPrincipal;
+
+ // The sheet we're parsing into
+ RefPtr<CSSStyleSheet> mSheet;
+
+ // Used for @import rules
+ css::Loader* mChildLoader; // not ref counted, it owns us
+
+ // Any sheets we may reuse when parsing an @import.
+ css::LoaderReusableStyleSheets* mReusableSheets;
+
+ // Sheet section we're in. This is used to enforce correct ordering of the
+ // various rule types (eg the fact that a @charset rule must come before
+ // anything else). Note that there are checks of similar things in various
+ // places in CSSStyleSheet.cpp (e.g in insertRule, RebuildChildList).
+ enum nsCSSSection {
+ eCSSSection_Charset,
+ eCSSSection_Import,
+ eCSSSection_NameSpace,
+ eCSSSection_General
+ };
+ nsCSSSection mSection;
+
+ nsXMLNameSpaceMap *mNameSpaceMap; // weak, mSheet owns it
+
+ // After an UngetToken is done this flag is true. The next call to
+ // GetToken clears the flag.
+ bool mHavePushBack : 1;
+
+ // True if we are in quirks mode; false in standards or almost standards mode
+ bool mNavQuirkMode : 1;
+
+ // True when the hashless color quirk applies.
+ bool mHashlessColorQuirk : 1;
+
+ // True when the unitless length quirk applies.
+ bool mUnitlessLengthQuirk : 1;
+
+ // 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. The only values stored in this field are
+ // 0, 1, and 2; three bits are allocated to avoid issues should the
+ // enum type be signed.
+ css::SheetParsingMode mParsingMode : 3;
+
+ // True if we are in parsing rules for the chrome.
+ bool mIsChrome : 1;
+
+ // True if viewport units should be allowed.
+ bool mViewportUnitsEnabled : 1;
+
+ // True for parsing media lists for HTML attributes, where we have to
+ // ignore CSS comments.
+ bool mHTMLMediaMode : 1;
+
+ // This flag is set when parsing a non-box shorthand; it's used to not apply
+ // some quirks during shorthand parsing
+ bool mParsingCompoundProperty : 1;
+
+ // True if we are in the middle of parsing an @supports condition.
+ // This is used to avoid recording the input stream when variable references
+ // are encountered in a property declaration in the @supports condition.
+ bool mInSupportsCondition : 1;
+
+ // True if we are somewhere within a @supports rule whose condition is
+ // false.
+ bool mInFailingSupportsRule : 1;
+
+ // True if we will suppress all parse errors (except unexpected EOFs).
+ // This is used to prevent errors for declarations inside a failing
+ // @supports rule.
+ bool mSuppressErrors : 1;
+
+ // True if any parsing of URL values requires a sheet principal to have
+ // been passed in the nsCSSScanner constructor. This is usually the case.
+ // It can be set to false, for example, when we create an nsCSSParser solely
+ // to parse a property value to test it for syntactic correctness. When
+ // false, an assertion that mSheetPrincipal is non-null is skipped. Should
+ // not be set to false if any nsCSSValues created during parsing can escape
+ // out of the parser.
+ bool mSheetPrincipalRequired;
+
+ // This enum helps us track whether we've unprefixed "display: -webkit-box"
+ // (treating it as "display: flex") in an earlier declaration within a series
+ // of declarations. (This only impacts behavior when the function
+ // "ShouldUseUnprefixingService()" returns true, and that should only happen
+ // for a short whitelist of origins.)
+ enum WebkitBoxUnprefixState : uint8_t {
+ eNotParsingDecls, // We are *not* currently parsing a sequence of
+ // CSS declarations. (default state)
+
+ // The next two enum values indicate that we *are* currently parsing a
+ // sequence of declarations (in ParseDeclarations or ParseDeclarationBlock)
+ // and...
+ eHaveNotUnprefixed, // ...we have not unprefixed 'display:-webkit-box' in
+ // this sequence of CSS declarations.
+ eHaveUnprefixed // ...we *have* unprefixed 'display:-webkit-box' earlier in
+ // this sequence of CSS declarations.
+ };
+ WebkitBoxUnprefixState mWebkitBoxUnprefixState;
+
+ // Stack of rule groups; used for @media and such.
+ InfallibleTArray<RefPtr<css::GroupRule> > mGroupStack;
+
+ // During the parsing of a property (which may be a shorthand), the data
+ // are stored in |mTempData|. (It is needed to ensure that parser
+ // errors cause the data to be ignored, and to ensure that a
+ // non-'!important' declaration does not override an '!important'
+ // one.)
+ nsCSSExpandedDataBlock mTempData;
+
+ // All data from successfully parsed properties are placed into |mData|.
+ nsCSSExpandedDataBlock mData;
+
+public:
+ // Used from nsCSSParser constructors and destructors
+ CSSParserImpl* mNextFree;
+};
+
+static void AssignRuleToPointer(css::Rule* aRule, void* aPointer)
+{
+ css::Rule **pointer = static_cast<css::Rule**>(aPointer);
+ NS_ADDREF(*pointer = aRule);
+}
+
+static void AppendRuleToSheet(css::Rule* aRule, void* aParser)
+{
+ CSSParserImpl* parser = (CSSParserImpl*) aParser;
+ parser->AppendRule(aRule);
+}
+
+#define REPORT_UNEXPECTED(msg_) \
+ { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_); }
+
+#define REPORT_UNEXPECTED_P(msg_, param_) \
+ { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_); }
+
+#define REPORT_UNEXPECTED_P_V(msg_, param_, value_) \
+ { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_, value_); }
+
+#define REPORT_UNEXPECTED_TOKEN(msg_) \
+ { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken); }
+
+#define REPORT_UNEXPECTED_TOKEN_CHAR(msg_, ch_) \
+ { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken, ch_); }
+
+#define REPORT_UNEXPECTED_EOF(lf_) \
+ mReporter->ReportUnexpectedEOF(#lf_)
+
+#define REPORT_UNEXPECTED_EOF_CHAR(ch_) \
+ mReporter->ReportUnexpectedEOF(ch_)
+
+#define OUTPUT_ERROR() \
+ mReporter->OutputError()
+
+#define OUTPUT_ERROR_WITH_POSITION(linenum_, lineoff_) \
+ mReporter->OutputError(linenum_, lineoff_)
+
+#define CLEAR_ERROR() \
+ mReporter->ClearError()
+
+CSSParserImpl::CSSParserImpl()
+ : mToken(),
+ mScanner(nullptr),
+ mReporter(nullptr),
+ mChildLoader(nullptr),
+ mReusableSheets(nullptr),
+ mSection(eCSSSection_Charset),
+ mNameSpaceMap(nullptr),
+ mHavePushBack(false),
+ mNavQuirkMode(false),
+ mHashlessColorQuirk(false),
+ mUnitlessLengthQuirk(false),
+ mParsingMode(css::eAuthorSheetFeatures),
+ mIsChrome(false),
+ mViewportUnitsEnabled(true),
+ mHTMLMediaMode(false),
+ mParsingCompoundProperty(false),
+ mInSupportsCondition(false),
+ mInFailingSupportsRule(false),
+ mSuppressErrors(false),
+ mSheetPrincipalRequired(true),
+ mWebkitBoxUnprefixState(eNotParsingDecls),
+ mNextFree(nullptr)
+{
+}
+
+CSSParserImpl::~CSSParserImpl()
+{
+ mData.AssertInitialState();
+ mTempData.AssertInitialState();
+}
+
+nsresult
+CSSParserImpl::SetStyleSheet(CSSStyleSheet* aSheet)
+{
+ if (aSheet != mSheet) {
+ // Switch to using the new sheet, if any
+ mGroupStack.Clear();
+ mSheet = aSheet;
+ if (mSheet) {
+ mNameSpaceMap = mSheet->GetNameSpaceMap();
+ } else {
+ mNameSpaceMap = nullptr;
+ }
+ } else if (mSheet) {
+ mNameSpaceMap = mSheet->GetNameSpaceMap();
+ }
+
+ return NS_OK;
+}
+
+nsIDocument*
+CSSParserImpl::GetDocument()
+{
+ if (!mSheet) {
+ return nullptr;
+ }
+ return mSheet->GetDocument();
+}
+
+nsresult
+CSSParserImpl::SetQuirkMode(bool aQuirkMode)
+{
+ mNavQuirkMode = aQuirkMode;
+ return NS_OK;
+}
+
+nsresult
+CSSParserImpl::SetChildLoader(mozilla::css::Loader* aChildLoader)
+{
+ mChildLoader = aChildLoader; // not ref counted, it owns us
+ return NS_OK;
+}
+
+void
+CSSParserImpl::Reset()
+{
+ NS_ASSERTION(!mScanner, "resetting with scanner active");
+ SetStyleSheet(nullptr);
+ SetQuirkMode(false);
+ SetChildLoader(nullptr);
+}
+
+void
+CSSParserImpl::InitScanner(nsCSSScanner& aScanner,
+ css::ErrorReporter& aReporter,
+ nsIURI* aSheetURI, nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal)
+{
+ NS_PRECONDITION(!mHTMLMediaMode, "Bad initial state");
+ NS_PRECONDITION(!mParsingCompoundProperty, "Bad initial state");
+ NS_PRECONDITION(!mScanner, "already have scanner");
+
+ mScanner = &aScanner;
+ mReporter = &aReporter;
+ mScanner->SetErrorReporter(mReporter);
+
+ mBaseURI = aBaseURI;
+ mSheetURI = aSheetURI;
+ mSheetPrincipal = aSheetPrincipal;
+ mHavePushBack = false;
+}
+
+void
+CSSParserImpl::ReleaseScanner()
+{
+ mScanner = nullptr;
+ mReporter = nullptr;
+ mBaseURI = nullptr;
+ mSheetURI = nullptr;
+ mSheetPrincipal = nullptr;
+}
+
+nsresult
+CSSParserImpl::ParseSheet(const nsAString& aInput,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ uint32_t aLineNumber,
+ css::LoaderReusableStyleSheets* aReusableSheets)
+{
+ NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
+ NS_PRECONDITION(aBaseURI, "need base URI");
+ NS_PRECONDITION(aSheetURI, "need sheet URI");
+ NS_PRECONDITION(mSheet, "Must have sheet to parse into");
+ NS_ENSURE_STATE(mSheet);
+
+#ifdef DEBUG
+ nsIURI* uri = mSheet->GetSheetURI();
+ bool equal;
+ NS_ASSERTION(NS_SUCCEEDED(aSheetURI->Equals(uri, &equal)) && equal,
+ "Sheet URI does not match passed URI");
+ NS_ASSERTION(NS_SUCCEEDED(mSheet->Principal()->Equals(aSheetPrincipal,
+ &equal)) &&
+ equal,
+ "Sheet principal does not match passed principal");
+#endif
+
+ nsCSSScanner scanner(aInput, aLineNumber);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
+ InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
+
+ int32_t ruleCount = mSheet->StyleRuleCount();
+ if (0 < ruleCount) {
+ const css::Rule* lastRule = mSheet->GetStyleRuleAt(ruleCount - 1);
+ if (lastRule) {
+ switch (lastRule->GetType()) {
+ case css::Rule::CHARSET_RULE:
+ case css::Rule::IMPORT_RULE:
+ mSection = eCSSSection_Import;
+ break;
+ case css::Rule::NAMESPACE_RULE:
+ mSection = eCSSSection_NameSpace;
+ break;
+ default:
+ mSection = eCSSSection_General;
+ break;
+ }
+ }
+ }
+ else {
+ mSection = eCSSSection_Charset; // sheet is empty, any rules are fair
+ }
+
+ mParsingMode = mSheet->ParsingMode();
+ mIsChrome = dom::IsChromeURI(aSheetURI);
+ mReusableSheets = aReusableSheets;
+
+ nsCSSToken* tk = &mToken;
+ for (;;) {
+ // Get next non-whitespace token
+ if (!GetToken(true)) {
+ OUTPUT_ERROR();
+ break;
+ }
+ if (eCSSToken_HTMLComment == tk->mType) {
+ continue; // legal here only
+ }
+ if (eCSSToken_AtKeyword == tk->mType) {
+ ParseAtRule(AppendRuleToSheet, this, false);
+ continue;
+ }
+ UngetToken();
+ if (ParseRuleSet(AppendRuleToSheet, this)) {
+ mSection = eCSSSection_General;
+ }
+ }
+ ReleaseScanner();
+
+ mParsingMode = css::eAuthorSheetFeatures;
+ mIsChrome = false;
+ mReusableSheets = nullptr;
+
+ return NS_OK;
+}
+
+/**
+ * Determines whether the identifier contained in the given string is a
+ * vendor-specific identifier, as described in CSS 2.1 section 4.1.2.1.
+ */
+static bool
+NonMozillaVendorIdentifier(const nsAString& ident)
+{
+ return (ident.First() == char16_t('-') &&
+ !StringBeginsWith(ident, NS_LITERAL_STRING("-moz-"))) ||
+ ident.First() == char16_t('_');
+
+}
+
+already_AddRefed<css::Declaration>
+CSSParserImpl::ParseStyleAttribute(const nsAString& aAttributeValue,
+ nsIURI* aDocURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aNodePrincipal)
+{
+ NS_PRECONDITION(aNodePrincipal, "Must have principal here!");
+ NS_PRECONDITION(aBaseURI, "need base URI");
+
+ // XXX line number?
+ nsCSSScanner scanner(aAttributeValue, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURI);
+ InitScanner(scanner, reporter, aDocURI, aBaseURI, aNodePrincipal);
+
+ mSection = eCSSSection_General;
+
+ uint32_t parseFlags = eParseDeclaration_AllowImportant;
+
+ RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags);
+
+ ReleaseScanner();
+
+ return declaration.forget();
+}
+
+nsresult
+CSSParserImpl::ParseDeclarations(const nsAString& aBuffer,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ css::Declaration* aDeclaration,
+ bool* aChanged)
+{
+ *aChanged = false;
+
+ NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
+
+ nsCSSScanner scanner(aBuffer, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
+ InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
+
+ MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls,
+ "Someone forgot to clear mWebkitBoxUnprefixState!");
+ AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState);
+ mWebkitBoxUnprefixState = eHaveNotUnprefixed;
+
+ mSection = eCSSSection_General;
+
+ mData.AssertInitialState();
+ aDeclaration->ClearData();
+ // We could check if it was already empty, but...
+ *aChanged = true;
+
+ for (;;) {
+ // If we cleared the old decl, then we want to be calling
+ // ValueAppended as we parse.
+ if (!ParseDeclaration(aDeclaration, eParseDeclaration_AllowImportant,
+ true, aChanged)) {
+ if (!SkipDeclaration(false)) {
+ break;
+ }
+ }
+ }
+
+ aDeclaration->CompressFrom(&mData);
+ ReleaseScanner();
+ return NS_OK;
+}
+
+nsresult
+CSSParserImpl::ParseRule(const nsAString& aRule,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ css::Rule** aResult)
+{
+ NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
+ NS_PRECONDITION(aBaseURI, "need base URI");
+
+ *aResult = nullptr;
+
+ nsCSSScanner scanner(aRule, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
+ InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
+
+ mSection = eCSSSection_Charset; // callers are responsible for rejecting invalid rules.
+
+ nsCSSToken* tk = &mToken;
+ // Get first non-whitespace token
+ nsresult rv = NS_OK;
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED(PEParseRuleWSOnly);
+ OUTPUT_ERROR();
+ rv = NS_ERROR_DOM_SYNTAX_ERR;
+ } else {
+ if (eCSSToken_AtKeyword == tk->mType) {
+ // FIXME: perhaps aInsideBlock should be true when we are?
+ ParseAtRule(AssignRuleToPointer, aResult, false);
+ } else {
+ UngetToken();
+ ParseRuleSet(AssignRuleToPointer, aResult);
+ }
+
+ if (*aResult && GetToken(true)) {
+ // garbage after rule
+ REPORT_UNEXPECTED_TOKEN(PERuleTrailing);
+ NS_RELEASE(*aResult);
+ }
+
+ if (!*aResult) {
+ rv = NS_ERROR_DOM_SYNTAX_ERR;
+ OUTPUT_ERROR();
+ }
+ }
+
+ ReleaseScanner();
+ return rv;
+}
+
+void
+CSSParserImpl::ParseLonghandProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue)
+{
+ MOZ_ASSERT(aPropID < eCSSProperty_COUNT_no_shorthands,
+ "ParseLonghandProperty must only take a longhand property");
+
+ RefPtr<css::Declaration> declaration = new css::Declaration;
+ declaration->InitializeEmpty();
+
+ bool changed;
+ ParseProperty(aPropID, aPropValue, aSheetURL, aBaseURL, aSheetPrincipal,
+ declaration, &changed,
+ /* aIsImportant */ false,
+ /* aIsSVGMode */ false);
+
+ if (changed) {
+ aValue = *declaration->GetNormalBlock()->ValueFor(aPropID);
+ } else {
+ aValue.Reset();
+ }
+}
+
+bool
+CSSParserImpl::ParseTransformProperty(const nsAString& aPropValue,
+ bool aDisallowRelativeValues,
+ nsCSSValue& aValue)
+{
+ RefPtr<css::Declaration> declaration = new css::Declaration();
+ declaration->InitializeEmpty();
+
+ mData.AssertInitialState();
+ mTempData.AssertInitialState();
+
+ nsCSSScanner scanner(aPropValue, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
+ InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
+
+ bool parsedOK = ParseTransform(false, aDisallowRelativeValues);
+ // We should now be at EOF
+ if (parsedOK && GetToken(true)) {
+ parsedOK = false;
+ }
+
+ bool changed = false;
+ if (parsedOK) {
+ declaration->ExpandTo(&mData);
+ changed = mData.TransferFromBlock(mTempData, eCSSProperty_transform,
+ EnabledState(), false,
+ true, false, declaration,
+ GetDocument());
+ declaration->CompressFrom(&mData);
+ }
+
+ if (changed) {
+ aValue = *declaration->GetNormalBlock()->ValueFor(eCSSProperty_transform);
+ } else {
+ aValue.Reset();
+ }
+
+ ReleaseScanner();
+
+ return parsedOK;
+}
+
+void
+CSSParserImpl::ParseProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ css::Declaration* aDeclaration,
+ bool* aChanged,
+ bool aIsImportant,
+ bool aIsSVGMode)
+{
+ NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
+ NS_PRECONDITION(aBaseURI, "need base URI");
+ NS_PRECONDITION(aDeclaration, "Need declaration to parse into!");
+ MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable);
+
+ mData.AssertInitialState();
+ mTempData.AssertInitialState();
+ aDeclaration->AssertMutable();
+
+ nsCSSScanner scanner(aPropValue, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
+ InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
+ mSection = eCSSSection_General;
+ scanner.SetSVGMode(aIsSVGMode);
+
+ *aChanged = false;
+
+ // Check for unknown or preffed off properties
+ if (eCSSProperty_UNKNOWN == aPropID ||
+ !nsCSSProps::IsEnabled(aPropID, EnabledState())) {
+ NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID));
+ REPORT_UNEXPECTED_P(PEUnknownProperty, propName);
+ REPORT_UNEXPECTED(PEDeclDropped);
+ OUTPUT_ERROR();
+ ReleaseScanner();
+ return;
+ }
+
+ bool parsedOK = ParseProperty(aPropID);
+ // We should now be at EOF
+ if (parsedOK && GetToken(true)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
+ parsedOK = false;
+ }
+
+ if (!parsedOK) {
+ NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID));
+ REPORT_UNEXPECTED_P(PEValueParsingError, propName);
+ REPORT_UNEXPECTED(PEDeclDropped);
+ OUTPUT_ERROR();
+ mTempData.ClearProperty(aPropID);
+ } else {
+
+ // We know we don't need to force a ValueAppended call for the new
+ // value. So if we are not processing a shorthand, and there's
+ // already a value for this property in the declaration at the
+ // same importance level, then we can just copy our parsed value
+ // directly into the declaration without going through the whole
+ // expand/compress thing.
+ if (!aDeclaration->TryReplaceValue(aPropID, aIsImportant, mTempData,
+ aChanged)) {
+ // Do it the slow way
+ aDeclaration->ExpandTo(&mData);
+ *aChanged = mData.TransferFromBlock(mTempData, aPropID,
+ EnabledState(), aIsImportant,
+ true, false, aDeclaration,
+ GetDocument());
+ aDeclaration->CompressFrom(&mData);
+ }
+ CLEAR_ERROR();
+ }
+
+ mTempData.AssertInitialState();
+
+ ReleaseScanner();
+}
+
+void
+CSSParserImpl::ParseVariable(const nsAString& aVariableName,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ css::Declaration* aDeclaration,
+ bool* aChanged,
+ bool aIsImportant)
+{
+ NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
+ NS_PRECONDITION(aBaseURI, "need base URI");
+ NS_PRECONDITION(aDeclaration, "Need declaration to parse into!");
+ NS_PRECONDITION(nsLayoutUtils::CSSVariablesEnabled(),
+ "expected Variables to be enabled");
+
+ mData.AssertInitialState();
+ mTempData.AssertInitialState();
+ aDeclaration->AssertMutable();
+
+ nsCSSScanner scanner(aPropValue, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
+ InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
+ mSection = eCSSSection_General;
+
+ *aChanged = false;
+
+ CSSVariableDeclarations::Type variableType;
+ nsString variableValue;
+
+ bool parsedOK = ParseVariableDeclaration(&variableType, variableValue);
+
+ // We should now be at EOF
+ if (parsedOK && GetToken(true)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
+ parsedOK = false;
+ }
+
+ if (!parsedOK) {
+ REPORT_UNEXPECTED_P(PEValueParsingError, NS_LITERAL_STRING("--") +
+ aVariableName);
+ REPORT_UNEXPECTED(PEDeclDropped);
+ OUTPUT_ERROR();
+ } else {
+ CLEAR_ERROR();
+ aDeclaration->AddVariable(aVariableName, variableType,
+ variableValue, aIsImportant, true);
+ *aChanged = true;
+ }
+
+ mTempData.AssertInitialState();
+
+ ReleaseScanner();
+}
+
+void
+CSSParserImpl::ParseMediaList(const nsSubstring& aBuffer,
+ nsIURI* aURI, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsMediaList* aMediaList,
+ bool aHTMLMode)
+{
+ // XXX Are there cases where the caller wants to keep what it already
+ // has in case of parser error? If GatherMedia ever changes to return
+ // a value other than true, we probably should avoid modifying aMediaList.
+ aMediaList->Clear();
+
+ // fake base URI since media lists don't have URIs in them
+ nsCSSScanner scanner(aBuffer, aLineNumber);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
+ InitScanner(scanner, reporter, aURI, aURI, nullptr);
+
+ mHTMLMediaMode = aHTMLMode;
+
+ // XXXldb We need to make the scanner not skip CSS comments! (Or
+ // should we?)
+
+ // For aHTMLMode, we used to follow the parsing rules in
+ // http://www.w3.org/TR/1999/REC-html401-19991224/types.html#type-media-descriptors
+ // which wouldn't work for media queries since they remove all but the
+ // first word. However, they're changed in
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-document.html#media2
+ // (as of 2008-05-29) which says that the media attribute just points
+ // to a media query. (The main substative difference is the relative
+ // precedence of commas and paretheses.)
+
+ DebugOnly<bool> parsedOK = GatherMedia(aMediaList, false);
+ NS_ASSERTION(parsedOK, "GatherMedia returned false; we probably want to avoid "
+ "trashing aMediaList");
+
+ CLEAR_ERROR();
+ ReleaseScanner();
+ mHTMLMediaMode = false;
+}
+
+// <source-size-list> = <source-size>#?
+// <source-size> = <media-condition>? <length>
+bool
+CSSParserImpl::ParseSourceSizeList(const nsAString& aBuffer,
+ nsIURI* aURI, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
+ InfallibleTArray<nsCSSValue>& aValues,
+ bool aHTMLMode)
+{
+ aQueries.Clear();
+ aValues.Clear();
+
+ // fake base URI since media value lists don't have URIs in them
+ nsCSSScanner scanner(aBuffer, aLineNumber);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
+ InitScanner(scanner, reporter, aURI, aURI, nullptr);
+
+ // See ParseMediaList comment about HTML mode
+ mHTMLMediaMode = aHTMLMode;
+
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-sizes-attribute
+ bool hitEnd = false;
+ do {
+ bool hitError = false;
+ // Parse single <media-condition> <source-size-value>
+ do {
+ nsAutoPtr<nsMediaQuery> query;
+ nsCSSValue value;
+
+ bool hitStop;
+ if (!ParseMediaQuery(eMediaQuerySingleCondition, getter_Transfers(query),
+ &hitStop)) {
+ NS_ASSERTION(!hitStop, "should return true when hit stop");
+ hitError = true;
+ break;
+ }
+
+ if (!query) {
+ REPORT_UNEXPECTED_EOF(PEParseSourceSizeListEOF);
+ NS_ASSERTION(hitStop,
+ "should return hitStop or an error if returning no query");
+ hitError = true;
+ break;
+ }
+
+ if (hitStop) {
+ // Empty conditions (e.g. just a bare value) should be treated as always
+ // matching (a query with no expressions fails to match, so a negated one
+ // always matches.)
+ query->SetNegated();
+ }
+
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#source-size-value
+ // Percentages are not allowed in a <source-size-value>, to avoid
+ // confusion about what it would be relative to.
+ if (ParseNonNegativeVariant(value, VARIANT_LCALC, nullptr) !=
+ CSSParseResult::Ok) {
+ hitError = true;
+ break;
+ }
+
+ if (GetToken(true)) {
+ if (!mToken.IsSymbol(',')) {
+ REPORT_UNEXPECTED_TOKEN(PEParseSourceSizeListNotComma);
+ hitError = true;
+ break;
+ }
+ } else {
+ hitEnd = true;
+ }
+
+ aQueries.AppendElement(query.forget());
+ aValues.AppendElement(value);
+ } while(0);
+
+ if (hitError) {
+ OUTPUT_ERROR();
+
+ // Per spec, we just skip the current entry if there was a parse error.
+ // Jumps to next entry of <source-size-list> which is a comma-separated list.
+ if (!SkipUntil(',')) {
+ hitEnd = true;
+ }
+ }
+ } while (!hitEnd);
+
+ CLEAR_ERROR();
+ ReleaseScanner();
+ mHTMLMediaMode = false;
+
+ return !aQueries.IsEmpty();
+}
+
+bool
+CSSParserImpl::ParseColorString(const nsSubstring& aBuffer,
+ nsIURI* aURI, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsCSSValue& aValue,
+ bool aSuppressErrors /* false */)
+{
+ nsCSSScanner scanner(aBuffer, aLineNumber);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
+ InitScanner(scanner, reporter, aURI, aURI, nullptr);
+
+ nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);
+
+ // Parse a color, and check that there's nothing else after it.
+ bool colorParsed = ParseColor(aValue) == CSSParseResult::Ok &&
+ !GetToken(true);
+
+ if (aSuppressErrors) {
+ CLEAR_ERROR();
+ } else {
+ OUTPUT_ERROR();
+ }
+
+ ReleaseScanner();
+ return colorParsed;
+}
+
+bool
+CSSParserImpl::ParseMarginString(const nsSubstring& aBuffer,
+ nsIURI* aURI, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsCSSValue& aValue,
+ bool aSuppressErrors /* false */)
+{
+ nsCSSScanner scanner(aBuffer, aLineNumber);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
+ InitScanner(scanner, reporter, aURI, aURI, nullptr);
+
+ nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);
+
+ // Parse a margin, and check that there's nothing else after it.
+ bool marginParsed = ParseGroupedBoxProperty(VARIANT_LP, aValue, 0) && !GetToken(true);
+
+ if (aSuppressErrors) {
+ CLEAR_ERROR();
+ } else {
+ OUTPUT_ERROR();
+ }
+
+ ReleaseScanner();
+ return marginParsed;
+}
+
+bool
+CSSParserImpl::ParseFontFamilyListString(const nsSubstring& aBuffer,
+ nsIURI* aURI, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsCSSValue& aValue)
+{
+ nsCSSScanner scanner(aBuffer, aLineNumber);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
+ InitScanner(scanner, reporter, aURI, aURI, nullptr);
+
+ // Parse a font family list, and check that there's nothing else after it.
+ bool familyParsed = ParseFamily(aValue) && !GetToken(true);
+ OUTPUT_ERROR();
+ ReleaseScanner();
+ return familyParsed;
+}
+
+nsresult
+CSSParserImpl::ParseSelectorString(const nsSubstring& aSelectorString,
+ nsIURI* aURI, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ nsCSSSelectorList **aSelectorList)
+{
+ nsCSSScanner scanner(aSelectorString, aLineNumber);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
+ InitScanner(scanner, reporter, aURI, aURI, nullptr);
+
+ bool success = ParseSelectorList(*aSelectorList, char16_t(0));
+
+ // We deliberately do not call OUTPUT_ERROR here, because all our
+ // callers map a failure return to a JS exception, and if that JS
+ // exception is caught, people don't want to see parser diagnostics;
+ // see e.g. http://bugs.jquery.com/ticket/7535
+ // It would be nice to be able to save the parser diagnostics into
+ // the exception, so that if it _isn't_ caught we can report them
+ // along with the usual uncaught-exception message, but we don't
+ // have any way to do that at present; see bug 631621.
+ CLEAR_ERROR();
+ ReleaseScanner();
+
+ if (success) {
+ NS_ASSERTION(*aSelectorList, "Should have list!");
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!*aSelectorList, "Shouldn't have list!");
+
+ return NS_ERROR_DOM_SYNTAX_ERR;
+}
+
+
+already_AddRefed<nsCSSKeyframeRule>
+CSSParserImpl::ParseKeyframeRule(const nsSubstring& aBuffer,
+ nsIURI* aURI,
+ uint32_t aLineNumber)
+{
+ nsCSSScanner scanner(aBuffer, aLineNumber);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
+ InitScanner(scanner, reporter, aURI, aURI, nullptr);
+
+ RefPtr<nsCSSKeyframeRule> result = ParseKeyframeRule();
+ if (GetToken(true)) {
+ // extra garbage at the end
+ result = nullptr;
+ }
+
+ OUTPUT_ERROR();
+ ReleaseScanner();
+
+ return result.forget();
+}
+
+bool
+CSSParserImpl::ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
+ nsIURI* aURI, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ InfallibleTArray<float>& aSelectorList)
+{
+ MOZ_ASSERT(aSelectorList.IsEmpty(), "given list should start empty");
+
+ nsCSSScanner scanner(aSelectorString, aLineNumber);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
+ InitScanner(scanner, reporter, aURI, aURI, nullptr);
+
+ bool success = ParseKeyframeSelectorList(aSelectorList) &&
+ // must consume entire input string
+ !GetToken(true);
+
+ OUTPUT_ERROR();
+ ReleaseScanner();
+
+ if (success) {
+ NS_ASSERTION(!aSelectorList.IsEmpty(), "should not be empty");
+ } else {
+ aSelectorList.Clear();
+ }
+
+ return success;
+}
+
+bool
+CSSParserImpl::EvaluateSupportsDeclaration(const nsAString& aProperty,
+ const nsAString& aValue,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal)
+{
+ nsCSSPropertyID propID = LookupEnabledProperty(aProperty);
+ if (propID == eCSSProperty_UNKNOWN) {
+ return false;
+ }
+
+ nsCSSScanner scanner(aValue, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
+ InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
+ nsAutoSuppressErrors suppressErrors(this);
+
+ bool parsedOK;
+
+ if (propID == eCSSPropertyExtra_variable) {
+ MOZ_ASSERT(Substring(aProperty, 0,
+ CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--"));
+ const nsDependentSubstring varName =
+ Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH); // remove '--'
+ CSSVariableDeclarations::Type variableType;
+ nsString variableValue;
+ parsedOK = ParseVariableDeclaration(&variableType, variableValue) &&
+ !GetToken(true);
+ } else {
+ parsedOK = ParseProperty(propID) && !GetToken(true);
+
+ mTempData.ClearProperty(propID);
+ mTempData.AssertInitialState();
+ }
+
+ CLEAR_ERROR();
+ ReleaseScanner();
+
+ return parsedOK;
+}
+
+bool
+CSSParserImpl::EvaluateSupportsCondition(const nsAString& aDeclaration,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal)
+{
+ nsCSSScanner scanner(aDeclaration, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
+ InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
+ nsAutoSuppressErrors suppressErrors(this);
+
+ bool conditionMet;
+ bool parsedOK = ParseSupportsCondition(conditionMet) && !GetToken(true);
+
+ CLEAR_ERROR();
+ ReleaseScanner();
+
+ return parsedOK && conditionMet;
+}
+
+bool
+CSSParserImpl::EnumerateVariableReferences(const nsAString& aPropertyValue,
+ VariableEnumFunc aFunc,
+ void* aData)
+{
+ nsCSSScanner scanner(aPropertyValue, 0);
+ css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr);
+ InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
+ nsAutoSuppressErrors suppressErrors(this);
+
+ CSSVariableDeclarations::Type type;
+ bool dropBackslash;
+ nsString impliedCharacters;
+ bool result = ParseValueWithVariables(&type, &dropBackslash,
+ impliedCharacters, aFunc, aData) &&
+ !GetToken(true);
+
+ ReleaseScanner();
+
+ return result;
+}
+
+static bool
+SeparatorRequiredBetweenTokens(nsCSSTokenSerializationType aToken1,
+ nsCSSTokenSerializationType aToken2)
+{
+ // The two lines marked with (*) do not correspond to entries in
+ // the table in the css-syntax spec but which we need to handle,
+ // as we treat them as whole tokens.
+ switch (aToken1) {
+ case eCSSTokenSerialization_Ident:
+ return aToken2 == eCSSTokenSerialization_Ident ||
+ aToken2 == eCSSTokenSerialization_Function ||
+ aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
+ aToken2 == eCSSTokenSerialization_Symbol_Minus ||
+ aToken2 == eCSSTokenSerialization_Number ||
+ aToken2 == eCSSTokenSerialization_Percentage ||
+ aToken2 == eCSSTokenSerialization_Dimension ||
+ aToken2 == eCSSTokenSerialization_URange ||
+ aToken2 == eCSSTokenSerialization_CDC ||
+ aToken2 == eCSSTokenSerialization_Symbol_OpenParen;
+ case eCSSTokenSerialization_AtKeyword_or_Hash:
+ case eCSSTokenSerialization_Dimension:
+ return aToken2 == eCSSTokenSerialization_Ident ||
+ aToken2 == eCSSTokenSerialization_Function ||
+ aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
+ aToken2 == eCSSTokenSerialization_Symbol_Minus ||
+ aToken2 == eCSSTokenSerialization_Number ||
+ aToken2 == eCSSTokenSerialization_Percentage ||
+ aToken2 == eCSSTokenSerialization_Dimension ||
+ aToken2 == eCSSTokenSerialization_URange ||
+ aToken2 == eCSSTokenSerialization_CDC;
+ case eCSSTokenSerialization_Symbol_Hash:
+ case eCSSTokenSerialization_Symbol_Minus:
+ return aToken2 == eCSSTokenSerialization_Ident ||
+ aToken2 == eCSSTokenSerialization_Function ||
+ aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
+ aToken2 == eCSSTokenSerialization_Symbol_Minus ||
+ aToken2 == eCSSTokenSerialization_Number ||
+ aToken2 == eCSSTokenSerialization_Percentage ||
+ aToken2 == eCSSTokenSerialization_Dimension ||
+ aToken2 == eCSSTokenSerialization_URange;
+ case eCSSTokenSerialization_Number:
+ return aToken2 == eCSSTokenSerialization_Ident ||
+ aToken2 == eCSSTokenSerialization_Function ||
+ aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
+ aToken2 == eCSSTokenSerialization_Number ||
+ aToken2 == eCSSTokenSerialization_Percentage ||
+ aToken2 == eCSSTokenSerialization_Dimension ||
+ aToken2 == eCSSTokenSerialization_URange;
+ case eCSSTokenSerialization_Symbol_At:
+ return aToken2 == eCSSTokenSerialization_Ident ||
+ aToken2 == eCSSTokenSerialization_Function ||
+ aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
+ aToken2 == eCSSTokenSerialization_Symbol_Minus ||
+ aToken2 == eCSSTokenSerialization_URange;
+ case eCSSTokenSerialization_URange:
+ return aToken2 == eCSSTokenSerialization_Ident ||
+ aToken2 == eCSSTokenSerialization_Function ||
+ aToken2 == eCSSTokenSerialization_Number ||
+ aToken2 == eCSSTokenSerialization_Percentage ||
+ aToken2 == eCSSTokenSerialization_Dimension ||
+ aToken2 == eCSSTokenSerialization_Symbol_Question;
+ case eCSSTokenSerialization_Symbol_Dot_or_Plus:
+ return aToken2 == eCSSTokenSerialization_Number ||
+ aToken2 == eCSSTokenSerialization_Percentage ||
+ aToken2 == eCSSTokenSerialization_Dimension;
+ case eCSSTokenSerialization_Symbol_Assorted:
+ case eCSSTokenSerialization_Symbol_Asterisk:
+ return aToken2 == eCSSTokenSerialization_Symbol_Equals;
+ case eCSSTokenSerialization_Symbol_Bar:
+ return aToken2 == eCSSTokenSerialization_Symbol_Equals ||
+ aToken2 == eCSSTokenSerialization_Symbol_Bar ||
+ aToken2 == eCSSTokenSerialization_DashMatch; // (*)
+ case eCSSTokenSerialization_Symbol_Slash:
+ return aToken2 == eCSSTokenSerialization_Symbol_Asterisk ||
+ aToken2 == eCSSTokenSerialization_ContainsMatch; // (*)
+ default:
+ MOZ_ASSERT(aToken1 == eCSSTokenSerialization_Nothing ||
+ aToken1 == eCSSTokenSerialization_Whitespace ||
+ aToken1 == eCSSTokenSerialization_Percentage ||
+ aToken1 == eCSSTokenSerialization_URL_or_BadURL ||
+ aToken1 == eCSSTokenSerialization_Function ||
+ aToken1 == eCSSTokenSerialization_CDC ||
+ aToken1 == eCSSTokenSerialization_Symbol_OpenParen ||
+ aToken1 == eCSSTokenSerialization_Symbol_Question ||
+ aToken1 == eCSSTokenSerialization_Symbol_Assorted ||
+ aToken1 == eCSSTokenSerialization_Symbol_Asterisk ||
+ aToken1 == eCSSTokenSerialization_Symbol_Equals ||
+ aToken1 == eCSSTokenSerialization_Symbol_Bar ||
+ aToken1 == eCSSTokenSerialization_Symbol_Slash ||
+ aToken1 == eCSSTokenSerialization_Other,
+ "unexpected nsCSSTokenSerializationType value");
+ return false;
+ }
+}
+
+/**
+ * Appends aValue to aResult, possibly inserting an empty CSS
+ * comment between the two to ensure that tokens from both strings
+ * remain separated.
+ */
+static void
+AppendTokens(nsAString& aResult,
+ nsCSSTokenSerializationType& aResultFirstToken,
+ nsCSSTokenSerializationType& aResultLastToken,
+ nsCSSTokenSerializationType aValueFirstToken,
+ nsCSSTokenSerializationType aValueLastToken,
+ const nsAString& aValue)
+{
+ if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) {
+ aResult.AppendLiteral("/**/");
+ }
+ aResult.Append(aValue);
+ if (aResultFirstToken == eCSSTokenSerialization_Nothing) {
+ aResultFirstToken = aValueFirstToken;
+ }
+ if (aValueLastToken != eCSSTokenSerialization_Nothing) {
+ aResultLastToken = aValueLastToken;
+ }
+}
+
+/**
+ * Stops the given scanner recording, and appends the recorded result
+ * to aResult, possibly inserting an empty CSS comment between the two to
+ * ensure that tokens from both strings remain separated.
+ */
+static void
+StopRecordingAndAppendTokens(nsString& aResult,
+ nsCSSTokenSerializationType& aResultFirstToken,
+ nsCSSTokenSerializationType& aResultLastToken,
+ nsCSSTokenSerializationType aValueFirstToken,
+ nsCSSTokenSerializationType aValueLastToken,
+ nsCSSScanner* aScanner)
+{
+ if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) {
+ aResult.AppendLiteral("/**/");
+ }
+ aScanner->StopRecording(aResult);
+ if (aResultFirstToken == eCSSTokenSerialization_Nothing) {
+ aResultFirstToken = aValueFirstToken;
+ }
+ if (aValueLastToken != eCSSTokenSerialization_Nothing) {
+ aResultLastToken = aValueLastToken;
+ }
+}
+
+bool
+CSSParserImpl::ResolveValueWithVariableReferencesRec(
+ nsString& aResult,
+ nsCSSTokenSerializationType& aResultFirstToken,
+ nsCSSTokenSerializationType& aResultLastToken,
+ const CSSVariableValues* aVariables)
+{
+ // This function assumes we are already recording, and will leave the scanner
+ // recording when it returns.
+ MOZ_ASSERT(mScanner->IsRecording());
+ MOZ_ASSERT(aResult.IsEmpty());
+
+ // Stack of closing characters for currently open constructs.
+ AutoTArray<char16_t, 16> stack;
+
+ // The resolved value for this ResolveValueWithVariableReferencesRec call.
+ nsString value;
+
+ // The length of the scanner's recording before the currently parsed token.
+ // This is used so that when we encounter a "var(" token, we can strip
+ // it off the end of the recording, regardless of how long the token was.
+ // (With escapes, it could be longer than four characters.)
+ uint32_t lengthBeforeVar = 0;
+
+ // Tracking the type of token that appears at the start and end of |value|
+ // and that appears at the start and end of the scanner recording. These are
+ // used to determine whether we need to insert "/**/" when pasting token
+ // streams together.
+ nsCSSTokenSerializationType valueFirstToken = eCSSTokenSerialization_Nothing,
+ valueLastToken = eCSSTokenSerialization_Nothing,
+ recFirstToken = eCSSTokenSerialization_Nothing,
+ recLastToken = eCSSTokenSerialization_Nothing;
+
+#define UPDATE_RECORDING_TOKENS(type) \
+ if (recFirstToken == eCSSTokenSerialization_Nothing) { \
+ recFirstToken = type; \
+ } \
+ recLastToken = type;
+
+ while (GetToken(false)) {
+ switch (mToken.mType) {
+ case eCSSToken_Symbol: {
+ nsCSSTokenSerializationType type = eCSSTokenSerialization_Other;
+ if (mToken.mSymbol == '(') {
+ stack.AppendElement(')');
+ type = eCSSTokenSerialization_Symbol_OpenParen;
+ } else if (mToken.mSymbol == '[') {
+ stack.AppendElement(']');
+ } else if (mToken.mSymbol == '{') {
+ stack.AppendElement('}');
+ } else if (mToken.mSymbol == ';') {
+ if (stack.IsEmpty()) {
+ // A ';' that is at the top level of the value or at the top level
+ // of a variable reference's fallback is invalid.
+ return false;
+ }
+ } else if (mToken.mSymbol == '!') {
+ if (stack.IsEmpty()) {
+ // An '!' that is at the top level of the value or at the top level
+ // of a variable reference's fallback is invalid.
+ return false;
+ }
+ } else if (mToken.mSymbol == ')' &&
+ stack.IsEmpty()) {
+ // We're closing a "var(".
+ nsString finalTokens;
+ mScanner->StopRecording(finalTokens);
+ MOZ_ASSERT(finalTokens[finalTokens.Length() - 1] == ')');
+ finalTokens.Truncate(finalTokens.Length() - 1);
+ aResult.Append(value);
+
+ AppendTokens(aResult, valueFirstToken, valueLastToken,
+ recFirstToken, recLastToken, finalTokens);
+
+ mScanner->StartRecording();
+ UngetToken();
+ aResultFirstToken = valueFirstToken;
+ aResultLastToken = valueLastToken;
+ return true;
+ } else if (mToken.mSymbol == ')' ||
+ mToken.mSymbol == ']' ||
+ mToken.mSymbol == '}') {
+ if (stack.IsEmpty() ||
+ stack.LastElement() != mToken.mSymbol) {
+ // A mismatched closing bracket is invalid.
+ return false;
+ }
+ stack.TruncateLength(stack.Length() - 1);
+ } else if (mToken.mSymbol == '#') {
+ type = eCSSTokenSerialization_Symbol_Hash;
+ } else if (mToken.mSymbol == '@') {
+ type = eCSSTokenSerialization_Symbol_At;
+ } else if (mToken.mSymbol == '.' ||
+ mToken.mSymbol == '+') {
+ type = eCSSTokenSerialization_Symbol_Dot_or_Plus;
+ } else if (mToken.mSymbol == '-') {
+ type = eCSSTokenSerialization_Symbol_Minus;
+ } else if (mToken.mSymbol == '?') {
+ type = eCSSTokenSerialization_Symbol_Question;
+ } else if (mToken.mSymbol == '$' ||
+ mToken.mSymbol == '^' ||
+ mToken.mSymbol == '~') {
+ type = eCSSTokenSerialization_Symbol_Assorted;
+ } else if (mToken.mSymbol == '=') {
+ type = eCSSTokenSerialization_Symbol_Equals;
+ } else if (mToken.mSymbol == '|') {
+ type = eCSSTokenSerialization_Symbol_Bar;
+ } else if (mToken.mSymbol == '/') {
+ type = eCSSTokenSerialization_Symbol_Slash;
+ } else if (mToken.mSymbol == '*') {
+ type = eCSSTokenSerialization_Symbol_Asterisk;
+ }
+ UPDATE_RECORDING_TOKENS(type);
+ break;
+ }
+
+ case eCSSToken_Function:
+ if (mToken.mIdent.LowerCaseEqualsLiteral("var")) {
+ // Save the tokens before the "var(" to our resolved value.
+ nsString recording;
+ mScanner->StopRecording(recording);
+ recording.Truncate(lengthBeforeVar);
+ AppendTokens(value, valueFirstToken, valueLastToken,
+ recFirstToken, recLastToken, recording);
+ recFirstToken = eCSSTokenSerialization_Nothing;
+ recLastToken = eCSSTokenSerialization_Nothing;
+
+ if (!GetToken(true) ||
+ mToken.mType != eCSSToken_Ident ||
+ !nsCSSProps::IsCustomPropertyName(mToken.mIdent)) {
+ // "var(" must be followed by an identifier, and it must be a
+ // custom property name.
+ return false;
+ }
+
+ // Turn the custom property name into a variable name by removing the
+ // '--' prefix.
+ MOZ_ASSERT(Substring(mToken.mIdent, 0,
+ CSS_CUSTOM_NAME_PREFIX_LENGTH).
+ EqualsLiteral("--"));
+ nsDependentString variableName(mToken.mIdent,
+ CSS_CUSTOM_NAME_PREFIX_LENGTH);
+
+ // Get the value of the identified variable. Note that we
+ // check if the variable value is the empty string, as that means
+ // that the variable was invalid at computed value time due to
+ // unresolveable variable references or cycles.
+ nsString variableValue;
+ nsCSSTokenSerializationType varFirstToken, varLastToken;
+ bool valid = aVariables->Get(variableName, variableValue,
+ varFirstToken, varLastToken) &&
+ !variableValue.IsEmpty();
+
+ if (!GetToken(true) ||
+ mToken.IsSymbol(')')) {
+ mScanner->StartRecording();
+ if (!valid) {
+ // Invalid variable with no fallback.
+ return false;
+ }
+ // Valid variable with no fallback.
+ AppendTokens(value, valueFirstToken, valueLastToken,
+ varFirstToken, varLastToken, variableValue);
+ } else if (mToken.IsSymbol(',')) {
+ mScanner->StartRecording();
+ if (!GetToken(false) ||
+ mToken.IsSymbol(')')) {
+ // Comma must be followed by at least one fallback token.
+ return false;
+ }
+ UngetToken();
+ if (valid) {
+ // Valid variable with ignored fallback.
+ mScanner->StopRecording();
+ AppendTokens(value, valueFirstToken, valueLastToken,
+ varFirstToken, varLastToken, variableValue);
+ bool ok = SkipBalancedContentUntil(')');
+ mScanner->StartRecording();
+ if (!ok) {
+ return false;
+ }
+ } else {
+ nsString fallback;
+ if (!ResolveValueWithVariableReferencesRec(fallback,
+ varFirstToken,
+ varLastToken,
+ aVariables)) {
+ // Fallback value had invalid tokens or an invalid variable reference
+ // that itself had no fallback.
+ return false;
+ }
+ AppendTokens(value, valueFirstToken, valueLastToken,
+ varFirstToken, varLastToken, fallback);
+ // Now we're either at the pushed back ')' that finished the
+ // fallback or at EOF.
+ DebugOnly<bool> gotToken = GetToken(false);
+ MOZ_ASSERT(!gotToken || mToken.IsSymbol(')'));
+ }
+ } else {
+ // Expected ',' or ')' after the variable name.
+ mScanner->StartRecording();
+ return false;
+ }
+ } else {
+ stack.AppendElement(')');
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Function);
+ }
+ break;
+
+ case eCSSToken_Bad_String:
+ case eCSSToken_Bad_URL:
+ return false;
+
+ case eCSSToken_Whitespace:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Whitespace);
+ break;
+
+ case eCSSToken_AtKeyword:
+ case eCSSToken_Hash:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_AtKeyword_or_Hash);
+ break;
+
+ case eCSSToken_Number:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Number);
+ break;
+
+ case eCSSToken_Dimension:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Dimension);
+ break;
+
+ case eCSSToken_Ident:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Ident);
+ break;
+
+ case eCSSToken_Percentage:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Percentage);
+ break;
+
+ case eCSSToken_URange:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URange);
+ break;
+
+ case eCSSToken_URL:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URL_or_BadURL);
+ break;
+
+ case eCSSToken_HTMLComment:
+ if (mToken.mIdent[0] == '-') {
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_CDC);
+ } else {
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other);
+ }
+ break;
+
+ case eCSSToken_Dashmatch:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_DashMatch);
+ break;
+
+ case eCSSToken_Containsmatch:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_ContainsMatch);
+ break;
+
+ default:
+ MOZ_FALLTHROUGH_ASSERT("unexpected token type");
+ case eCSSToken_ID:
+ case eCSSToken_String:
+ case eCSSToken_Includes:
+ case eCSSToken_Beginsmatch:
+ case eCSSToken_Endsmatch:
+ UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other);
+ break;
+ }
+
+ lengthBeforeVar = mScanner->RecordingLength();
+ }
+
+#undef UPDATE_RECORDING_TOKENS
+
+ aResult.Append(value);
+ StopRecordingAndAppendTokens(aResult, valueFirstToken, valueLastToken,
+ recFirstToken, recLastToken, mScanner);
+
+ // Append any implicitly closed brackets.
+ if (!stack.IsEmpty()) {
+ do {
+ aResult.Append(stack.LastElement());
+ stack.TruncateLength(stack.Length() - 1);
+ } while (!stack.IsEmpty());
+ valueLastToken = eCSSTokenSerialization_Other;
+ }
+
+ mScanner->StartRecording();
+ aResultFirstToken = valueFirstToken;
+ aResultLastToken = valueLastToken;
+ return true;
+}
+
+bool
+CSSParserImpl::ResolveValueWithVariableReferences(
+ const CSSVariableValues* aVariables,
+ nsString& aResult,
+ nsCSSTokenSerializationType& aFirstToken,
+ nsCSSTokenSerializationType& aLastToken)
+{
+ aResult.Truncate(0);
+
+ // Start recording before we read the first token.
+ mScanner->StartRecording();
+
+ if (!GetToken(false)) {
+ // Value was empty since we reached EOF.
+ mScanner->StopRecording();
+ return false;
+ }
+
+ UngetToken();
+
+ nsString value;
+ nsCSSTokenSerializationType firstToken, lastToken;
+ bool ok = ResolveValueWithVariableReferencesRec(value, firstToken, lastToken, aVariables) &&
+ !GetToken(true);
+
+ mScanner->StopRecording();
+
+ if (ok) {
+ aResult = value;
+ aFirstToken = firstToken;
+ aLastToken = lastToken;
+ }
+ return ok;
+}
+
+bool
+CSSParserImpl::ResolveVariableValue(const nsAString& aPropertyValue,
+ const CSSVariableValues* aVariables,
+ nsString& aResult,
+ nsCSSTokenSerializationType& aFirstToken,
+ nsCSSTokenSerializationType& aLastToken)
+{
+ nsCSSScanner scanner(aPropertyValue, 0);
+
+ // At this point, we know that aPropertyValue is syntactically correct
+ // for a token stream that has variable references. We also won't be
+ // interpreting any of the stream as we parse it, apart from expanding
+ // var() references, so we don't need a base URL etc. or any useful
+ // error reporting.
+ css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr);
+ InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
+
+ bool valid = ResolveValueWithVariableReferences(aVariables, aResult,
+ aFirstToken, aLastToken);
+
+ ReleaseScanner();
+ return valid;
+}
+
+void
+CSSParserImpl::ParsePropertyWithVariableReferences(
+ nsCSSPropertyID aPropertyID,
+ nsCSSPropertyID aShorthandPropertyID,
+ const nsAString& aValue,
+ const CSSVariableValues* aVariables,
+ nsRuleData* aRuleData,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal,
+ CSSStyleSheet* aSheet,
+ uint32_t aLineNumber,
+ uint32_t aLineOffset)
+{
+ mTempData.AssertInitialState();
+
+ bool valid;
+ nsString expandedValue;
+
+ // Resolve any variable references in the property value.
+ {
+ nsCSSScanner scanner(aValue, 0);
+ css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL);
+ InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
+
+ nsCSSTokenSerializationType firstToken, lastToken;
+ valid = ResolveValueWithVariableReferences(aVariables, expandedValue,
+ firstToken, lastToken);
+ if (!valid) {
+ NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropertyID));
+ REPORT_UNEXPECTED(PEInvalidVariableReference);
+ REPORT_UNEXPECTED_P(PEValueParsingError, propName);
+ if (nsCSSProps::IsInherited(aPropertyID)) {
+ REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit);
+ } else {
+ REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial);
+ }
+ OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset);
+ }
+ ReleaseScanner();
+ }
+
+ nsCSSPropertyID propertyToParse =
+ aShorthandPropertyID != eCSSProperty_UNKNOWN ? aShorthandPropertyID :
+ aPropertyID;
+
+ // Parse the property with that resolved value.
+ if (valid) {
+ nsCSSScanner scanner(expandedValue, 0);
+ css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL);
+ InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
+ valid = ParseProperty(propertyToParse);
+ if (valid && GetToken(true)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
+ valid = false;
+ }
+ if (!valid) {
+ NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(
+ propertyToParse));
+ REPORT_UNEXPECTED_P_V(PEValueWithVariablesParsingErrorInValue,
+ propName, expandedValue);
+ if (nsCSSProps::IsInherited(aPropertyID)) {
+ REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit);
+ } else {
+ REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial);
+ }
+ OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset);
+ }
+ ReleaseScanner();
+ }
+
+ // If the property could not be parsed with the resolved value, then we
+ // treat it as if the value were 'initial' or 'inherit', depending on whether
+ // the property is an inherited property.
+ if (!valid) {
+ nsCSSValue defaultValue;
+ if (nsCSSProps::IsInherited(aPropertyID)) {
+ defaultValue.SetInheritValue();
+ } else {
+ defaultValue.SetInitialValue();
+ }
+ mTempData.AddLonghandProperty(aPropertyID, defaultValue);
+ }
+
+ // Copy the property value into the rule data.
+ mTempData.MapRuleInfoInto(aPropertyID, aRuleData);
+
+ mTempData.ClearProperty(propertyToParse);
+ mTempData.AssertInitialState();
+}
+
+bool
+CSSParserImpl::ParseCounterStyleName(const nsAString& aBuffer,
+ nsIURI* aURL,
+ nsAString& aName)
+{
+ nsCSSScanner scanner(aBuffer, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURL);
+ InitScanner(scanner, reporter, aURL, aURL, nullptr);
+
+ bool success = ParseCounterStyleName(aName, true) && !GetToken(true);
+
+ OUTPUT_ERROR();
+ ReleaseScanner();
+
+ return success;
+}
+
+bool
+CSSParserImpl::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
+ const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue)
+{
+ nsCSSScanner scanner(aBuffer, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL);
+ InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal);
+
+ bool success = ParseCounterDescriptorValue(aDescID, aValue) &&
+ !GetToken(true);
+
+ OUTPUT_ERROR();
+ ReleaseScanner();
+
+ return success;
+}
+
+bool
+CSSParserImpl::ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
+ const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue)
+{
+ nsCSSScanner scanner(aBuffer, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL);
+ InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal);
+
+ bool success = ParseFontDescriptorValue(aDescID, aValue) &&
+ !GetToken(true);
+
+ OUTPUT_ERROR();
+ ReleaseScanner();
+
+ return success;
+}
+
+//----------------------------------------------------------------------
+
+bool
+CSSParserImpl::GetToken(bool aSkipWS)
+{
+ if (mHavePushBack) {
+ mHavePushBack = false;
+ if (!aSkipWS || mToken.mType != eCSSToken_Whitespace) {
+ return true;
+ }
+ }
+ return mScanner->Next(mToken, aSkipWS ?
+ eCSSScannerExclude_WhitespaceAndComments :
+ eCSSScannerExclude_Comments);
+}
+
+void
+CSSParserImpl::UngetToken()
+{
+ NS_PRECONDITION(!mHavePushBack, "double pushback");
+ mHavePushBack = true;
+}
+
+bool
+CSSParserImpl::GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum)
+{
+ // Peek at next token so that mScanner updates line and column vals
+ if (!GetToken(aSkipWS)) {
+ return false;
+ }
+ UngetToken();
+ // The scanner uses one-indexing for line numbers but zero-indexing
+ // for column numbers.
+ *linenum = mScanner->GetLineNumber();
+ *colnum = 1 + mScanner->GetColumnNumber();
+ return true;
+}
+
+bool
+CSSParserImpl::ExpectSymbol(char16_t aSymbol,
+ bool aSkipWS)
+{
+ if (!GetToken(aSkipWS)) {
+ // CSS2.1 specifies that all "open constructs" are to be closed at
+ // EOF. It simplifies higher layers if we claim to have found an
+ // ), ], }, or ; if we encounter EOF while looking for one of them.
+ // Do still issue a diagnostic, to aid debugging.
+ if (aSymbol == ')' || aSymbol == ']' ||
+ aSymbol == '}' || aSymbol == ';') {
+ REPORT_UNEXPECTED_EOF_CHAR(aSymbol);
+ return true;
+ }
+ else
+ return false;
+ }
+ if (mToken.IsSymbol(aSymbol)) {
+ return true;
+ }
+ UngetToken();
+ return false;
+}
+
+// Checks to see if we're at the end of a property. If an error occurs during
+// the check, does not signal a parse error.
+bool
+CSSParserImpl::CheckEndProperty()
+{
+ if (!GetToken(true)) {
+ return true; // properties may end with eof
+ }
+ if ((eCSSToken_Symbol == mToken.mType) &&
+ ((';' == mToken.mSymbol) ||
+ ('!' == mToken.mSymbol) ||
+ ('}' == mToken.mSymbol) ||
+ (')' == mToken.mSymbol))) {
+ // XXX need to verify that ! is only followed by "important [;|}]
+ // XXX this requires a multi-token pushback buffer
+ UngetToken();
+ return true;
+ }
+ UngetToken();
+ return false;
+}
+
+// Checks if we're at the end of a property, raising an error if we're not.
+bool
+CSSParserImpl::ExpectEndProperty()
+{
+ if (CheckEndProperty())
+ return true;
+
+ // If we're here, we read something incorrect, so we should report it.
+ REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
+ return false;
+}
+
+// Parses the priority suffix on a property, which at present may be
+// either '!important' or nothing.
+CSSParserImpl::PriorityParsingStatus
+CSSParserImpl::ParsePriority()
+{
+ if (!GetToken(true)) {
+ return ePriority_None; // properties may end with EOF
+ }
+ if (!mToken.IsSymbol('!')) {
+ UngetToken();
+ return ePriority_None; // dunno what it is, but it's not a priority
+ }
+
+ if (!GetToken(true)) {
+ // EOF is not ok after !
+ REPORT_UNEXPECTED_EOF(PEImportantEOF);
+ return ePriority_Error;
+ }
+
+ if (mToken.mType != eCSSToken_Ident ||
+ !mToken.mIdent.LowerCaseEqualsLiteral("important")) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedImportant);
+ UngetToken();
+ return ePriority_Error;
+ }
+
+ return ePriority_Important;
+}
+
+nsSubstring*
+CSSParserImpl::NextIdent()
+{
+ // XXX Error reporting?
+ if (!GetToken(true)) {
+ return nullptr;
+ }
+ if (eCSSToken_Ident != mToken.mType) {
+ UngetToken();
+ return nullptr;
+ }
+ return &mToken.mIdent;
+}
+
+bool
+CSSParserImpl::SkipAtRule(bool aInsideBlock)
+{
+ for (;;) {
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PESkipAtRuleEOF2);
+ return false;
+ }
+ if (eCSSToken_Symbol == mToken.mType) {
+ char16_t symbol = mToken.mSymbol;
+ if (symbol == ';') {
+ break;
+ }
+ if (aInsideBlock && symbol == '}') {
+ // The closing } doesn't belong to us.
+ UngetToken();
+ break;
+ }
+ if (symbol == '{') {
+ SkipUntil('}');
+ break;
+ } else if (symbol == '(') {
+ SkipUntil(')');
+ } else if (symbol == '[') {
+ SkipUntil(']');
+ }
+ } else if (eCSSToken_Function == mToken.mType ||
+ eCSSToken_Bad_URL == mToken.mType) {
+ SkipUntil(')');
+ }
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseAtRule(RuleAppendFunc aAppendFunc,
+ void* aData,
+ bool aInAtRule)
+{
+
+ nsCSSSection newSection;
+ bool (CSSParserImpl::*parseFunc)(RuleAppendFunc, void*);
+
+ if ((mSection <= eCSSSection_Charset) &&
+ (mToken.mIdent.LowerCaseEqualsLiteral("charset"))) {
+ parseFunc = &CSSParserImpl::ParseCharsetRule;
+ newSection = eCSSSection_Import; // only one charset allowed
+
+ } else if ((mSection <= eCSSSection_Import) &&
+ mToken.mIdent.LowerCaseEqualsLiteral("import")) {
+ parseFunc = &CSSParserImpl::ParseImportRule;
+ newSection = eCSSSection_Import;
+
+ } else if ((mSection <= eCSSSection_NameSpace) &&
+ mToken.mIdent.LowerCaseEqualsLiteral("namespace")) {
+ parseFunc = &CSSParserImpl::ParseNameSpaceRule;
+ newSection = eCSSSection_NameSpace;
+
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("media")) {
+ parseFunc = &CSSParserImpl::ParseMediaRule;
+ newSection = eCSSSection_General;
+
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("-moz-document")) {
+ parseFunc = &CSSParserImpl::ParseMozDocumentRule;
+ newSection = eCSSSection_General;
+
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("font-face")) {
+ parseFunc = &CSSParserImpl::ParseFontFaceRule;
+ newSection = eCSSSection_General;
+
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("font-feature-values")) {
+ parseFunc = &CSSParserImpl::ParseFontFeatureValuesRule;
+ newSection = eCSSSection_General;
+
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("page")) {
+ parseFunc = &CSSParserImpl::ParsePageRule;
+ newSection = eCSSSection_General;
+
+ } else if ((nsCSSProps::IsEnabled(eCSSPropertyAlias_MozAnimation,
+ EnabledState()) &&
+ mToken.mIdent.LowerCaseEqualsLiteral("-moz-keyframes")) ||
+ (nsCSSProps::IsEnabled(eCSSPropertyAlias_WebkitAnimation) &&
+ mToken.mIdent.LowerCaseEqualsLiteral("-webkit-keyframes")) ||
+ mToken.mIdent.LowerCaseEqualsLiteral("keyframes")) {
+ parseFunc = &CSSParserImpl::ParseKeyframesRule;
+ newSection = eCSSSection_General;
+
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("supports")) {
+ parseFunc = &CSSParserImpl::ParseSupportsRule;
+ newSection = eCSSSection_General;
+
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("counter-style")) {
+ parseFunc = &CSSParserImpl::ParseCounterStyleRule;
+ newSection = eCSSSection_General;
+
+ } else {
+ if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
+ REPORT_UNEXPECTED_TOKEN(PEUnknownAtRule);
+ OUTPUT_ERROR();
+ }
+ // Skip over unsupported at rule, don't advance section
+ return SkipAtRule(aInAtRule);
+ }
+
+ // Inside of @-rules, only the rules that can occur anywhere
+ // are allowed.
+ bool unnestable = aInAtRule && newSection != eCSSSection_General;
+ if (unnestable) {
+ REPORT_UNEXPECTED_TOKEN(PEGroupRuleNestedAtRule);
+ }
+
+ if (unnestable || !(this->*parseFunc)(aAppendFunc, aData)) {
+ // Skip over invalid at rule, don't advance section
+ OUTPUT_ERROR();
+ return SkipAtRule(aInAtRule);
+ }
+
+ // Nested @-rules don't affect the top-level rule chain requirement
+ if (!aInAtRule) {
+ mSection = newSection;
+ }
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseCharsetRule(RuleAppendFunc aAppendFunc,
+ void* aData)
+{
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum) ||
+ !GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PECharsetRuleEOF);
+ return false;
+ }
+
+ if (eCSSToken_String != mToken.mType) {
+ UngetToken();
+ REPORT_UNEXPECTED_TOKEN(PECharsetRuleNotString);
+ return false;
+ }
+
+ nsAutoString charset = mToken.mIdent;
+
+ if (!ExpectSymbol(';', true)) {
+ return false;
+ }
+
+ // It's intentional that we don't create a rule object for @charset rules.
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseURLOrString(nsString& aURL)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (eCSSToken_String == mToken.mType || eCSSToken_URL == mToken.mType) {
+ aURL = mToken.mIdent;
+ return true;
+ }
+ UngetToken();
+ return false;
+}
+
+bool
+CSSParserImpl::ParseMediaQuery(eMediaQueryType aQueryType,
+ nsMediaQuery **aQuery,
+ bool *aHitStop)
+{
+ *aQuery = nullptr;
+ *aHitStop = false;
+ bool inAtRule = aQueryType == eMediaQueryAtRule;
+ // Attempt to parse a single condition and stop
+ bool singleCondition = aQueryType == eMediaQuerySingleCondition;
+
+ // "If the comma-separated list is the empty list it is assumed to
+ // specify the media query 'all'." (css3-mediaqueries, section
+ // "Media Queries")
+ if (!GetToken(true)) {
+ *aHitStop = true;
+ // expected termination by EOF
+ if (!inAtRule)
+ return true;
+
+ // unexpected termination by EOF
+ REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
+ return true;
+ }
+
+ if (eCSSToken_Symbol == mToken.mType && inAtRule &&
+ (mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}' )) {
+ *aHitStop = true;
+ UngetToken();
+ return true;
+ }
+ UngetToken();
+
+ nsMediaQuery* query = new nsMediaQuery;
+ *aQuery = query;
+
+ if (ExpectSymbol('(', true)) {
+ // we got an expression without a media type
+ UngetToken(); // so ParseMediaQueryExpression can handle it
+ query->SetType(nsGkAtoms::all);
+ query->SetTypeOmitted();
+ // Just parse the first expression here.
+ if (!ParseMediaQueryExpression(query)) {
+ OUTPUT_ERROR();
+ query->SetHadUnknownExpression();
+ }
+ } else if (singleCondition) {
+ // Since we are only trying to consume a single condition, which precludes
+ // media types and not/only, this should be the same as reaching immediate
+ // EOF (no condition to parse)
+ *aHitStop = true;
+ return true;
+ } else {
+ nsCOMPtr<nsIAtom> mediaType;
+ bool gotNotOrOnly = false;
+ for (;;) {
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
+ return false;
+ }
+ if (eCSSToken_Ident != mToken.mType) {
+ REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent);
+ UngetToken();
+ return false;
+ }
+ // case insensitive from CSS - must be lower cased
+ nsContentUtils::ASCIIToLower(mToken.mIdent);
+ mediaType = NS_Atomize(mToken.mIdent);
+ if (!gotNotOrOnly && mediaType == nsGkAtoms::_not) {
+ gotNotOrOnly = true;
+ query->SetNegated();
+ } else if (!gotNotOrOnly && mediaType == nsGkAtoms::only) {
+ gotNotOrOnly = true;
+ query->SetHasOnly();
+ } else if (mediaType == nsGkAtoms::_not ||
+ mediaType == nsGkAtoms::only ||
+ mediaType == nsGkAtoms::_and ||
+ mediaType == nsGkAtoms::_or) {
+ REPORT_UNEXPECTED_TOKEN(PEGatherMediaReservedMediaType);
+ UngetToken();
+ return false;
+ } else {
+ // valid media type
+ break;
+ }
+ }
+ query->SetType(mediaType);
+ }
+
+ for (;;) {
+ if (!GetToken(true)) {
+ *aHitStop = true;
+ // expected termination by EOF
+ if (!inAtRule)
+ break;
+
+ // unexpected termination by EOF
+ REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
+ break;
+ }
+
+ if (eCSSToken_Symbol == mToken.mType && inAtRule &&
+ (mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}')) {
+ *aHitStop = true;
+ UngetToken();
+ break;
+ }
+ if (!singleCondition &&
+ eCSSToken_Symbol == mToken.mType && mToken.mSymbol == ',') {
+ // Done with the expressions for this query
+ break;
+ }
+ if (eCSSToken_Ident != mToken.mType ||
+ !mToken.mIdent.LowerCaseEqualsLiteral("and")) {
+ if (singleCondition) {
+ // We have a condition at this point -- if we're not chained to other
+ // conditions with and/or, we're done.
+ UngetToken();
+ break;
+ } else {
+ REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma);
+ UngetToken();
+ return false;
+ }
+ }
+ if (!ParseMediaQueryExpression(query)) {
+ OUTPUT_ERROR();
+ query->SetHadUnknownExpression();
+ }
+ }
+ return true;
+}
+
+// Returns false only when there is a low-level error in the scanner
+// (out-of-memory).
+bool
+CSSParserImpl::GatherMedia(nsMediaList* aMedia,
+ bool aInAtRule)
+{
+ eMediaQueryType type = aInAtRule ? eMediaQueryAtRule : eMediaQueryNormal;
+ for (;;) {
+ nsAutoPtr<nsMediaQuery> query;
+ bool hitStop;
+ if (!ParseMediaQuery(type, getter_Transfers(query), &hitStop)) {
+ NS_ASSERTION(!hitStop, "should return true when hit stop");
+ OUTPUT_ERROR();
+ if (query) {
+ query->SetHadUnknownExpression();
+ }
+ if (aInAtRule) {
+ const char16_t stopChars[] =
+ { char16_t(','), char16_t('{'), char16_t(';'), char16_t('}'), char16_t(0) };
+ SkipUntilOneOf(stopChars);
+ } else {
+ SkipUntil(',');
+ }
+ // Rely on SkipUntilOneOf leaving mToken around as the last token read.
+ if (mToken.mType == eCSSToken_Symbol && aInAtRule &&
+ (mToken.mSymbol == '{' || mToken.mSymbol == ';' || mToken.mSymbol == '}')) {
+ UngetToken();
+ hitStop = true;
+ }
+ }
+ if (query) {
+ aMedia->AppendQuery(query);
+ }
+ if (hitStop) {
+ break;
+ }
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery)
+{
+ if (!ExpectSymbol('(', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEMQExpectedExpressionStart);
+ return false;
+ }
+ if (! GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEMQExpressionEOF);
+ return false;
+ }
+ if (eCSSToken_Ident != mToken.mType) {
+ REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName);
+ UngetToken();
+ SkipUntil(')');
+ return false;
+ }
+
+ nsMediaExpression *expr = aQuery->NewExpression();
+
+ // case insensitive from CSS - must be lower cased
+ nsContentUtils::ASCIIToLower(mToken.mIdent);
+ nsDependentString featureString(mToken.mIdent, 0);
+ uint8_t satisfiedReqFlags = 0;
+
+ // Strip off "-webkit-" prefix from featureString:
+ if (sWebkitPrefixedAliasesEnabled &&
+ StringBeginsWith(featureString, NS_LITERAL_STRING("-webkit-"))) {
+ satisfiedReqFlags |= nsMediaFeature::eHasWebkitPrefix;
+ featureString.Rebind(featureString, 8);
+ }
+ if (sWebkitDevicePixelRatioEnabled) {
+ satisfiedReqFlags |= nsMediaFeature::eWebkitDevicePixelRatioPrefEnabled;
+ }
+
+ // Strip off "min-"/"max-" prefix from featureString:
+ if (StringBeginsWith(featureString, NS_LITERAL_STRING("min-"))) {
+ expr->mRange = nsMediaExpression::eMin;
+ featureString.Rebind(featureString, 4);
+ } else if (StringBeginsWith(featureString, NS_LITERAL_STRING("max-"))) {
+ expr->mRange = nsMediaExpression::eMax;
+ featureString.Rebind(featureString, 4);
+ } else {
+ expr->mRange = nsMediaExpression::eEqual;
+ }
+
+ nsCOMPtr<nsIAtom> mediaFeatureAtom = NS_Atomize(featureString);
+ const nsMediaFeature *feature = nsMediaFeatures::features;
+ for (; feature->mName; ++feature) {
+ // See if name matches & all requirement flags are satisfied:
+ // (We check requirements by turning off all of the flags that have been
+ // satisfied, and then see if the result is 0.)
+ if (*(feature->mName) == mediaFeatureAtom &&
+ !(feature->mReqFlags & ~satisfiedReqFlags)) {
+ break;
+ }
+ }
+ if (!feature->mName ||
+ (expr->mRange != nsMediaExpression::eEqual &&
+ feature->mRangeType != nsMediaFeature::eMinMaxAllowed)) {
+ REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName);
+ SkipUntil(')');
+ return false;
+ }
+ expr->mFeature = feature;
+
+ if (!GetToken(true) || mToken.IsSymbol(')')) {
+ // Query expressions for any feature can be given without a value.
+ // However, min/max prefixes are not allowed.
+ if (expr->mRange != nsMediaExpression::eEqual) {
+ REPORT_UNEXPECTED(PEMQNoMinMaxWithoutValue);
+ return false;
+ }
+ expr->mValue.Reset();
+ return true;
+ }
+
+ if (!mToken.IsSymbol(':')) {
+ REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureNameEnd);
+ UngetToken();
+ SkipUntil(')');
+ return false;
+ }
+
+ bool rv = false;
+ switch (feature->mValueType) {
+ case nsMediaFeature::eLength:
+ rv = ParseSingleTokenNonNegativeVariant(expr->mValue, VARIANT_LENGTH,
+ nullptr);
+ break;
+ case nsMediaFeature::eInteger:
+ case nsMediaFeature::eBoolInteger:
+ rv = ParseNonNegativeInteger(expr->mValue);
+ // Enforce extra restrictions for eBoolInteger
+ if (rv &&
+ feature->mValueType == nsMediaFeature::eBoolInteger &&
+ expr->mValue.GetIntValue() > 1)
+ rv = false;
+ break;
+ case nsMediaFeature::eFloat:
+ rv = ParseNonNegativeNumber(expr->mValue);
+ break;
+ case nsMediaFeature::eIntRatio:
+ {
+ // Two integers separated by '/', with optional whitespace on
+ // either side of the '/'.
+ RefPtr<nsCSSValue::Array> a = nsCSSValue::Array::Create(2);
+ expr->mValue.SetArrayValue(a, eCSSUnit_Array);
+ // We don't bother with ParseNonNegativeVariant since we have to
+ // check for != 0 as well; no need to worry about the UngetToken
+ // since we're throwing out up to the next ')' anyway.
+ rv = ParseSingleTokenVariant(a->Item(0), VARIANT_INTEGER, nullptr) &&
+ a->Item(0).GetIntValue() > 0 &&
+ ExpectSymbol('/', true) &&
+ ParseSingleTokenVariant(a->Item(1), VARIANT_INTEGER, nullptr) &&
+ a->Item(1).GetIntValue() > 0;
+ }
+ break;
+ case nsMediaFeature::eResolution:
+ rv = GetToken(true);
+ if (!rv)
+ break;
+ rv = mToken.mType == eCSSToken_Dimension && mToken.mNumber > 0.0f;
+ if (!rv) {
+ UngetToken();
+ break;
+ }
+ // No worries about whether unitless zero is allowed, since the
+ // value must be positive (and we checked that above).
+ NS_ASSERTION(!mToken.mIdent.IsEmpty(), "unit lied");
+ if (mToken.mIdent.LowerCaseEqualsLiteral("dpi")) {
+ expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Inch);
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("dppx")) {
+ expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel);
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("dpcm")) {
+ expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Centimeter);
+ } else {
+ rv = false;
+ }
+ break;
+ case nsMediaFeature::eEnumerated:
+ rv = ParseSingleTokenVariant(expr->mValue, VARIANT_KEYWORD,
+ feature->mData.mKeywordTable);
+ break;
+ case nsMediaFeature::eIdent:
+ rv = ParseSingleTokenVariant(expr->mValue, VARIANT_IDENTIFIER, nullptr);
+ break;
+ }
+ if (!rv || !ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED(PEMQExpectedFeatureValue);
+ SkipUntil(')');
+ return false;
+ }
+
+ return true;
+}
+
+// Parse a CSS2 import rule: "@import STRING | URL [medium [, medium]]"
+bool
+CSSParserImpl::ParseImportRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+ RefPtr<nsMediaList> media = new nsMediaList();
+
+ uint32_t linenum, colnum;
+ nsAutoString url;
+ if (!GetNextTokenLocation(true, &linenum, &colnum) ||
+ !ParseURLOrString(url)) {
+ REPORT_UNEXPECTED_TOKEN(PEImportNotURI);
+ return false;
+ }
+
+ if (!ExpectSymbol(';', true)) {
+ if (!GatherMedia(media, true) ||
+ !ExpectSymbol(';', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEImportUnexpected);
+ // don't advance section, simply ignore invalid @import
+ return false;
+ }
+
+ // Safe to assert this, since we ensured that there is something
+ // other than the ';' coming after the @import's url() token.
+ NS_ASSERTION(media->Length() != 0, "media list must be nonempty");
+ }
+
+ ProcessImport(url, media, aAppendFunc, aData, linenum, colnum);
+ return true;
+}
+
+void
+CSSParserImpl::ProcessImport(const nsString& aURLSpec,
+ nsMediaList* aMedia,
+ RuleAppendFunc aAppendFunc,
+ void* aData,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber)
+{
+ RefPtr<css::ImportRule> rule = new css::ImportRule(aMedia, aURLSpec,
+ aLineNumber,
+ aColumnNumber);
+ (*aAppendFunc)(rule, aData);
+
+ // Diagnose bad URIs even if we don't have a child loader.
+ nsCOMPtr<nsIURI> url;
+ // Charset will be deduced from mBaseURI, which is more or less correct.
+ nsresult rv = NS_NewURI(getter_AddRefs(url), aURLSpec, nullptr, mBaseURI);
+
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ // import url is bad
+ REPORT_UNEXPECTED_P(PEImportBadURI, aURLSpec);
+ OUTPUT_ERROR();
+ }
+ return;
+ }
+
+ if (mChildLoader) {
+ mChildLoader->LoadChildSheet(mSheet, url, aMedia, rule, mReusableSheets);
+ }
+}
+
+// Parse the {} part of an @media or @-moz-document rule.
+bool
+CSSParserImpl::ParseGroupRule(css::GroupRule* aRule,
+ RuleAppendFunc aAppendFunc,
+ void* aData)
+{
+ // XXXbz this could use better error reporting throughout the method
+ if (!ExpectSymbol('{', true)) {
+ return false;
+ }
+
+ // push rule on stack, loop over children
+ PushGroup(aRule);
+ nsCSSSection holdSection = mSection;
+ mSection = eCSSSection_General;
+
+ for (;;) {
+ // Get next non-whitespace token
+ if (! GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEGroupRuleEOF2);
+ break;
+ }
+ if (mToken.IsSymbol('}')) { // done!
+ UngetToken();
+ break;
+ }
+ if (eCSSToken_AtKeyword == mToken.mType) {
+ // Parse for nested rules
+ ParseAtRule(aAppendFunc, aData, true);
+ continue;
+ }
+ UngetToken();
+ ParseRuleSet(AppendRuleToSheet, this, true);
+ }
+ PopGroup();
+
+ if (!ExpectSymbol('}', true)) {
+ mSection = holdSection;
+ return false;
+ }
+ (*aAppendFunc)(aRule, aData);
+ return true;
+}
+
+// Parse a CSS2 media rule: "@media medium [, medium] { ... }"
+bool
+CSSParserImpl::ParseMediaRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+ RefPtr<nsMediaList> media = new nsMediaList();
+ uint32_t linenum, colnum;
+ if (GetNextTokenLocation(true, &linenum, &colnum) &&
+ GatherMedia(media, true)) {
+ // XXXbz this could use better error reporting throughout the method
+ RefPtr<css::MediaRule> rule = new css::MediaRule(linenum, colnum);
+ // Append first, so when we do SetMedia() the rule
+ // knows what its stylesheet is.
+ if (ParseGroupRule(rule, aAppendFunc, aData)) {
+ rule->SetMedia(media);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Parse a @-moz-document rule. This is like an @media rule, but instead
+// of a medium it has a nonempty list of items where each item is either
+// url(), url-prefix(), or domain().
+bool
+CSSParserImpl::ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+ css::DocumentRule::URL *urls = nullptr;
+ css::DocumentRule::URL **next = &urls;
+
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum)) {
+ return false;
+ }
+
+ do {
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEMozDocRuleEOF);
+ delete urls;
+ return false;
+ }
+
+ if (!(eCSSToken_URL == mToken.mType ||
+ (eCSSToken_Function == mToken.mType &&
+ (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix") ||
+ mToken.mIdent.LowerCaseEqualsLiteral("domain") ||
+ mToken.mIdent.LowerCaseEqualsLiteral("regexp"))))) {
+ REPORT_UNEXPECTED_TOKEN(PEMozDocRuleBadFunc2);
+ UngetToken();
+ delete urls;
+ return false;
+ }
+ css::DocumentRule::URL *cur = *next = new css::DocumentRule::URL;
+ next = &cur->next;
+ if (mToken.mType == eCSSToken_URL) {
+ cur->func = css::DocumentRule::eURL;
+ CopyUTF16toUTF8(mToken.mIdent, cur->url);
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("regexp")) {
+ // regexp() is different from url-prefix() and domain() (but
+ // probably the way they *should* have been* in that it requires a
+ // string argument, and doesn't try to behave like url().
+ cur->func = css::DocumentRule::eRegExp;
+ GetToken(true);
+ // copy before we know it's valid (but before ExpectSymbol changes
+ // mToken.mIdent)
+ CopyUTF16toUTF8(mToken.mIdent, cur->url);
+ if (eCSSToken_String != mToken.mType || !ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotString);
+ SkipUntil(')');
+ delete urls;
+ return false;
+ }
+ } else {
+ if (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix")) {
+ cur->func = css::DocumentRule::eURLPrefix;
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("domain")) {
+ cur->func = css::DocumentRule::eDomain;
+ }
+
+ NS_ASSERTION(!mHavePushBack, "mustn't have pushback at this point");
+ mScanner->NextURL(mToken);
+ if (mToken.mType != eCSSToken_URL) {
+ REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotURI);
+ SkipUntil(')');
+ delete urls;
+ return false;
+ }
+
+ // We could try to make the URL (as long as it's not domain())
+ // canonical and absolute with NS_NewURI and GetSpec, but I'm
+ // inclined to think we shouldn't.
+ CopyUTF16toUTF8(mToken.mIdent, cur->url);
+ }
+ } while (ExpectSymbol(',', true));
+
+ RefPtr<css::DocumentRule> rule = new css::DocumentRule(linenum, colnum);
+ rule->SetURLs(urls);
+
+ return ParseGroupRule(rule, aAppendFunc, aData);
+}
+
+// Parse a CSS3 namespace rule: "@namespace [prefix] STRING | URL;"
+bool
+CSSParserImpl::ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum) ||
+ !GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEAtNSPrefixEOF);
+ return false;
+ }
+
+ nsAutoString prefix;
+ nsAutoString url;
+
+ if (eCSSToken_Ident == mToken.mType) {
+ prefix = mToken.mIdent;
+ // user-specified identifiers are case-sensitive (bug 416106)
+ } else {
+ UngetToken();
+ }
+
+ if (!ParseURLOrString(url) || !ExpectSymbol(';', true)) {
+ if (mHavePushBack) {
+ REPORT_UNEXPECTED_TOKEN(PEAtNSUnexpected);
+ } else {
+ REPORT_UNEXPECTED_EOF(PEAtNSURIEOF);
+ }
+ return false;
+ }
+
+ ProcessNameSpace(prefix, url, aAppendFunc, aData, linenum, colnum);
+ return true;
+}
+
+void
+CSSParserImpl::ProcessNameSpace(const nsString& aPrefix,
+ const nsString& aURLSpec,
+ RuleAppendFunc aAppendFunc,
+ void* aData,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber)
+{
+ nsCOMPtr<nsIAtom> prefix;
+
+ if (!aPrefix.IsEmpty()) {
+ prefix = NS_Atomize(aPrefix);
+ }
+
+ RefPtr<css::NameSpaceRule> rule = new css::NameSpaceRule(prefix, aURLSpec,
+ aLineNumber,
+ aColumnNumber);
+ (*aAppendFunc)(rule, aData);
+
+ // If this was the first namespace rule encountered, it will trigger
+ // creation of a namespace map.
+ if (!mNameSpaceMap) {
+ mNameSpaceMap = mSheet->GetNameSpaceMap();
+ }
+}
+
+// font-face-rule: '@font-face' '{' font-description '}'
+// font-description: font-descriptor+
+bool
+CSSParserImpl::ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum) ||
+ !ExpectSymbol('{', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEBadFontBlockStart);
+ return false;
+ }
+
+ RefPtr<nsCSSFontFaceRule> rule(new nsCSSFontFaceRule(linenum, colnum));
+
+ for (;;) {
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEFontFaceEOF);
+ break;
+ }
+ if (mToken.IsSymbol('}')) { // done!
+ UngetToken();
+ break;
+ }
+
+ // ignore extra semicolons
+ if (mToken.IsSymbol(';'))
+ continue;
+
+ if (!ParseFontDescriptor(rule)) {
+ REPORT_UNEXPECTED(PEDeclSkipped);
+ OUTPUT_ERROR();
+ if (!SkipDeclaration(true))
+ break;
+ }
+ }
+ if (!ExpectSymbol('}', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEBadFontBlockEnd);
+ return false;
+ }
+ (*aAppendFunc)(rule, aData);
+ return true;
+}
+
+// font-descriptor: font-family-desc
+// | font-style-desc
+// | font-weight-desc
+// | font-stretch-desc
+// | font-src-desc
+// | unicode-range-desc
+//
+// All font-*-desc productions follow the pattern
+// IDENT ':' value ';'
+//
+// On entry to this function, mToken is the IDENT.
+
+bool
+CSSParserImpl::ParseFontDescriptor(nsCSSFontFaceRule* aRule)
+{
+ if (eCSSToken_Ident != mToken.mType) {
+ REPORT_UNEXPECTED_TOKEN(PEFontDescExpected);
+ return false;
+ }
+
+ nsString descName = mToken.mIdent;
+ if (!ExpectSymbol(':', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
+ OUTPUT_ERROR();
+ return false;
+ }
+
+ nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(descName);
+ nsCSSValue value;
+
+ if (descID == eCSSFontDesc_UNKNOWN ||
+ (descID == eCSSFontDesc_Display &&
+ !Preferences::GetBool("layout.css.font-display.enabled"))) {
+ if (NonMozillaVendorIdentifier(descName)) {
+ // silently skip other vendors' extensions
+ SkipDeclaration(true);
+ return true;
+ } else {
+ REPORT_UNEXPECTED_P(PEUnknownFontDesc, descName);
+ return false;
+ }
+ }
+
+ if (!ParseFontDescriptorValue(descID, value)) {
+ REPORT_UNEXPECTED_P(PEValueParsingError, descName);
+ return false;
+ }
+
+ // Expect termination by ;, }, or EOF.
+ if (GetToken(true)) {
+ if (!mToken.IsSymbol(';') &&
+ !mToken.IsSymbol('}')) {
+ UngetToken();
+ REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
+ return false;
+ }
+ UngetToken();
+ }
+
+ aRule->SetDesc(descID, value);
+ return true;
+}
+
+// @font-feature-values <font-family># {
+// @<feature-type> {
+// <feature-ident> : <feature-index>+;
+// <feature-ident> : <feature-index>+;
+// ...
+// }
+// ...
+// }
+
+bool
+CSSParserImpl::ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc,
+ void* aData)
+{
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum)) {
+ return false;
+ }
+
+ RefPtr<nsCSSFontFeatureValuesRule>
+ valuesRule(new nsCSSFontFeatureValuesRule(linenum, colnum));
+
+ // parse family list
+ nsCSSValue fontlistValue;
+
+ if (!ParseFamily(fontlistValue) ||
+ fontlistValue.GetUnit() != eCSSUnit_FontFamilyList)
+ {
+ REPORT_UNEXPECTED_TOKEN(PEFFVNoFamily);
+ return false;
+ }
+
+ // add family to rule
+ const FontFamilyList* fontlist = fontlistValue.GetFontFamilyListValue();
+
+ // family list has generic ==> parse error
+ if (fontlist->HasGeneric()) {
+ REPORT_UNEXPECTED_TOKEN(PEFFVGenericInFamilyList);
+ return false;
+ }
+
+ valuesRule->SetFamilyList(*fontlist);
+
+ // open brace
+ if (!ExpectSymbol('{', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEFFVBlockStart);
+ return false;
+ }
+
+ // list of sets of feature values, each set bound to a specific
+ // feature-type (e.g. swash, annotation)
+ for (;;) {
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
+ break;
+ }
+ if (mToken.IsSymbol('}')) { // done!
+ UngetToken();
+ break;
+ }
+
+ if (!ParseFontFeatureValueSet(valuesRule)) {
+ if (!SkipAtRule(false)) {
+ break;
+ }
+ }
+ }
+ if (!ExpectSymbol('}', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEFFVUnexpectedBlockEnd);
+ SkipUntil('}');
+ return false;
+ }
+
+ (*aAppendFunc)(valuesRule, aData);
+ return true;
+}
+
+#define NUMVALUES_NO_LIMIT 0xFFFF
+
+// parse a single value set containing name-value pairs for a single feature type
+// @<feature-type> { [ <feature-ident> : <feature-index>+ ; ]* }
+// Ex: @swash { flowing: 1; delicate: 2; }
+bool
+CSSParserImpl::ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule
+ *aFeatureValuesRule)
+{
+ // -- @keyword (e.g. swash, styleset)
+ if (eCSSToken_AtKeyword != mToken.mType) {
+ REPORT_UNEXPECTED_TOKEN(PEFontFeatureValuesNoAt);
+ OUTPUT_ERROR();
+ UngetToken();
+ return false;
+ }
+
+ // which font-specific variant of font-variant-alternates
+ int32_t whichVariant;
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+ if (keyword == eCSSKeyword_UNKNOWN ||
+ !nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kFontVariantAlternatesFuncsKTable,
+ whichVariant))
+ {
+ if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
+ REPORT_UNEXPECTED_TOKEN(PEFFVUnknownFontVariantPropValue);
+ OUTPUT_ERROR();
+ }
+ UngetToken();
+ return false;
+ }
+
+ nsAutoString featureType(mToken.mIdent);
+
+ // open brace
+ if (!ExpectSymbol('{', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEFFVValueSetStart);
+ return false;
+ }
+
+ // styleset and character-variant can be multi-valued, otherwise single value
+ int32_t limitNumValues;
+
+ switch (keyword) {
+ case eCSSKeyword_styleset:
+ limitNumValues = NUMVALUES_NO_LIMIT;
+ break;
+ case eCSSKeyword_character_variant:
+ limitNumValues = 2;
+ break;
+ default:
+ limitNumValues = 1;
+ break;
+ }
+
+ // -- ident integer+ [, ident integer+]
+ AutoTArray<gfxFontFeatureValueSet::ValueList, 5> values;
+
+ // list of font-feature-values-declaration's
+ for (;;) {
+ nsAutoString valueId;
+
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
+ break;
+ }
+
+ // ignore extra semicolons
+ if (mToken.IsSymbol(';')) {
+ continue;
+ }
+
+ // close brace ==> done
+ if (mToken.IsSymbol('}')) {
+ break;
+ }
+
+ // ident
+ if (eCSSToken_Ident != mToken.mType) {
+ REPORT_UNEXPECTED_TOKEN(PEFFVExpectedIdent);
+ if (!SkipDeclaration(true)) {
+ break;
+ }
+ continue;
+ }
+
+ valueId.Assign(mToken.mIdent);
+
+ // colon
+ if (!ExpectSymbol(':', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
+ OUTPUT_ERROR();
+ if (!SkipDeclaration(true)) {
+ break;
+ }
+ continue;
+ }
+
+ // value list
+ AutoTArray<uint32_t,4> featureSelectors;
+
+ nsCSSValue intValue;
+ while (ParseNonNegativeInteger(intValue)) {
+ featureSelectors.AppendElement(uint32_t(intValue.GetIntValue()));
+ }
+
+ int32_t numValues = featureSelectors.Length();
+
+ if (numValues == 0) {
+ REPORT_UNEXPECTED_TOKEN(PEFFVExpectedValue);
+ OUTPUT_ERROR();
+ if (!SkipDeclaration(true)) {
+ break;
+ }
+ continue;
+ }
+
+ if (numValues > limitNumValues) {
+ REPORT_UNEXPECTED_P(PEFFVTooManyValues, featureType);
+ OUTPUT_ERROR();
+ if (!SkipDeclaration(true)) {
+ break;
+ }
+ continue;
+ }
+
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
+ gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors);
+ values.AppendElement(v);
+ break;
+ }
+
+ // ';' or '}' to end definition
+ if (!mToken.IsSymbol(';') && !mToken.IsSymbol('}')) {
+ REPORT_UNEXPECTED_TOKEN(PEFFVValueDefinitionTrailing);
+ OUTPUT_ERROR();
+ if (!SkipDeclaration(true)) {
+ break;
+ }
+ continue;
+ }
+
+ gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors);
+ values.AppendElement(v);
+
+ if (mToken.IsSymbol('}')) {
+ break;
+ }
+ }
+
+ aFeatureValuesRule->AddValueList(whichVariant, values);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum) ||
+ !GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEKeyframeNameEOF);
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Ident) {
+ REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName);
+ UngetToken();
+ return false;
+ }
+ nsString name(mToken.mIdent);
+
+ if (!ExpectSymbol('{', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEKeyframeBrace);
+ return false;
+ }
+
+ RefPtr<nsCSSKeyframesRule> rule = new nsCSSKeyframesRule(name,
+ linenum, colnum);
+
+ while (!ExpectSymbol('}', true)) {
+ RefPtr<nsCSSKeyframeRule> kid = ParseKeyframeRule();
+ if (kid) {
+ rule->AppendStyleRule(kid);
+ } else {
+ OUTPUT_ERROR();
+ SkipRuleSet(true);
+ }
+ }
+
+ (*aAppendFunc)(rule, aData);
+ return true;
+}
+
+bool
+CSSParserImpl::ParsePageRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum)) {
+ return false;
+ }
+
+ // TODO: There can be page selectors after @page such as ":first", ":left".
+ uint32_t parseFlags = eParseDeclaration_InBraces |
+ eParseDeclaration_AllowImportant;
+
+ // Forbid viewport units in @page rules. See bug 811391.
+ MOZ_ASSERT(mViewportUnitsEnabled,
+ "Viewport units should be enabled outside of @page rules.");
+ mViewportUnitsEnabled = false;
+ RefPtr<css::Declaration> declaration =
+ ParseDeclarationBlock(parseFlags, eCSSContext_Page);
+ mViewportUnitsEnabled = true;
+
+ if (!declaration) {
+ return false;
+ }
+
+ RefPtr<nsCSSPageRule> rule =
+ new nsCSSPageRule(declaration, linenum, colnum);
+
+ (*aAppendFunc)(rule, aData);
+ return true;
+}
+
+already_AddRefed<nsCSSKeyframeRule>
+CSSParserImpl::ParseKeyframeRule()
+{
+ InfallibleTArray<float> selectorList;
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum) ||
+ !ParseKeyframeSelectorList(selectorList)) {
+ REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored);
+ return nullptr;
+ }
+
+ // Ignore !important in keyframe rules
+ uint32_t parseFlags = eParseDeclaration_InBraces;
+ RefPtr<css::Declaration> declaration(ParseDeclarationBlock(parseFlags));
+ if (!declaration) {
+ return nullptr;
+ }
+
+ // Takes ownership of declaration
+ RefPtr<nsCSSKeyframeRule> rule =
+ new nsCSSKeyframeRule(Move(selectorList), declaration.forget(),
+ linenum, colnum);
+ return rule.forget();
+}
+
+bool
+CSSParserImpl::ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList)
+{
+ for (;;) {
+ if (!GetToken(true)) {
+ // The first time through the loop, this means we got an empty
+ // list. Otherwise, it means we have a trailing comma.
+ return false;
+ }
+ float value;
+ switch (mToken.mType) {
+ case eCSSToken_Percentage:
+ value = mToken.mNumber;
+ break;
+ case eCSSToken_Ident:
+ if (mToken.mIdent.LowerCaseEqualsLiteral("from")) {
+ value = 0.0f;
+ break;
+ }
+ if (mToken.mIdent.LowerCaseEqualsLiteral("to")) {
+ value = 1.0f;
+ break;
+ }
+ MOZ_FALLTHROUGH;
+ default:
+ UngetToken();
+ // The first time through the loop, this means we got an empty
+ // list. Otherwise, it means we have a trailing comma.
+ return false;
+ }
+ aSelectorList.AppendElement(value);
+ if (!ExpectSymbol(',', true)) {
+ return true;
+ }
+ }
+}
+
+// supports_rule
+// : "@supports" supports_condition group_rule_body
+// ;
+bool
+CSSParserImpl::ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData)
+{
+ bool conditionMet = false;
+ nsString condition;
+
+ mScanner->StartRecording();
+
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum)) {
+ return false;
+ }
+
+ bool parsed = ParseSupportsCondition(conditionMet);
+
+ if (!parsed) {
+ mScanner->StopRecording();
+ return false;
+ }
+
+ if (!ExpectSymbol('{', true)) {
+ REPORT_UNEXPECTED_TOKEN(PESupportsGroupRuleStart);
+ mScanner->StopRecording();
+ return false;
+ }
+
+ UngetToken();
+ mScanner->StopRecording(condition);
+
+ // Remove the "{" that would follow the condition.
+ if (condition.Length() != 0) {
+ condition.Truncate(condition.Length() - 1);
+ }
+
+ // Remove spaces from the start and end of the recorded supports condition.
+ condition.Trim(" ", true, true, false);
+
+ // Record whether we are in a failing @supports, so that property parse
+ // errors don't get reported.
+ nsAutoFailingSupportsRule failing(this, conditionMet);
+
+ RefPtr<css::GroupRule> rule = new CSSSupportsRule(conditionMet, condition,
+ linenum, colnum);
+ return ParseGroupRule(rule, aAppendFunc, aProcessData);
+}
+
+// supports_condition
+// : supports_condition_in_parens supports_condition_terms
+// | supports_condition_negation
+// ;
+bool
+CSSParserImpl::ParseSupportsCondition(bool& aConditionMet)
+{
+ nsAutoInSupportsCondition aisc(this);
+
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PESupportsConditionStartEOF2);
+ return false;
+ }
+
+ UngetToken();
+
+ mScanner->ClearSeenBadToken();
+
+ if (mToken.IsSymbol('(') ||
+ mToken.mType == eCSSToken_Function ||
+ mToken.mType == eCSSToken_URL ||
+ mToken.mType == eCSSToken_Bad_URL) {
+ bool result = ParseSupportsConditionInParens(aConditionMet) &&
+ ParseSupportsConditionTerms(aConditionMet) &&
+ !mScanner->SeenBadToken();
+ return result;
+ }
+
+ if (mToken.mType == eCSSToken_Ident &&
+ mToken.mIdent.LowerCaseEqualsLiteral("not")) {
+ bool result = ParseSupportsConditionNegation(aConditionMet) &&
+ !mScanner->SeenBadToken();
+ return result;
+ }
+
+ REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedStart);
+ return false;
+}
+
+// supports_condition_negation
+// : 'not' S+ supports_condition_in_parens
+// ;
+bool
+CSSParserImpl::ParseSupportsConditionNegation(bool& aConditionMet)
+{
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PESupportsConditionNotEOF);
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Ident ||
+ !mToken.mIdent.LowerCaseEqualsLiteral("not")) {
+ REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedNot);
+ return false;
+ }
+
+ if (!RequireWhitespace()) {
+ REPORT_UNEXPECTED(PESupportsWhitespaceRequired);
+ return false;
+ }
+
+ if (ParseSupportsConditionInParens(aConditionMet)) {
+ aConditionMet = !aConditionMet;
+ return true;
+ }
+
+ return false;
+}
+
+// supports_condition_in_parens
+// : '(' S* supports_condition_in_parens_inside_parens ')' S*
+// | supports_condition_pref
+// | general_enclosed
+// ;
+bool
+CSSParserImpl::ParseSupportsConditionInParens(bool& aConditionMet)
+{
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PESupportsConditionInParensStartEOF);
+ return false;
+ }
+
+ if (mToken.mType == eCSSToken_URL) {
+ aConditionMet = false;
+ return true;
+ }
+
+ if (AgentRulesEnabled() &&
+ mToken.mType == eCSSToken_Function &&
+ mToken.mIdent.LowerCaseEqualsLiteral("-moz-bool-pref")) {
+ return ParseSupportsMozBoolPrefName(aConditionMet);
+ }
+
+ if (mToken.mType == eCSSToken_Function ||
+ mToken.mType == eCSSToken_Bad_URL) {
+ if (!SkipUntil(')')) {
+ REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF);
+ return false;
+ }
+ aConditionMet = false;
+ return true;
+ }
+
+ if (!mToken.IsSymbol('(')) {
+ REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedOpenParenOrFunction);
+ UngetToken();
+ return false;
+ }
+
+ if (!ParseSupportsConditionInParensInsideParens(aConditionMet)) {
+ if (!SkipUntil(')')) {
+ REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF);
+ return false;
+ }
+ aConditionMet = false;
+ return true;
+ }
+
+ if (!(ExpectSymbol(')', true))) {
+ SkipUntil(')');
+ aConditionMet = false;
+ return true;
+ }
+
+ return true;
+}
+
+// supports_condition_pref
+// : '-moz-bool-pref(' bool_pref_name ')'
+// ;
+bool
+CSSParserImpl::ParseSupportsMozBoolPrefName(bool& aConditionMet)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_String) {
+ SkipUntil(')');
+ return false;
+ }
+
+ aConditionMet = Preferences::GetBool(
+ NS_ConvertUTF16toUTF8(mToken.mIdent).get());
+
+ if (!ExpectSymbol(')', true)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ return true;
+}
+
+// supports_condition_in_parens_inside_parens
+// : core_declaration
+// | supports_condition_negation
+// | supports_condition_in_parens supports_condition_terms
+// ;
+bool
+CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ if (mToken.mType == eCSSToken_Ident) {
+ if (!mToken.mIdent.LowerCaseEqualsLiteral("not")) {
+ nsAutoString propertyName = mToken.mIdent;
+ if (!ExpectSymbol(':', true)) {
+ return false;
+ }
+
+ nsCSSPropertyID propID = LookupEnabledProperty(propertyName);
+ if (propID == eCSSProperty_UNKNOWN) {
+ if (ExpectSymbol(')', true)) {
+ UngetToken();
+ return false;
+ }
+ aConditionMet = false;
+ SkipUntil(')');
+ UngetToken();
+ } else if (propID == eCSSPropertyExtra_variable) {
+ if (ExpectSymbol(')', false)) {
+ UngetToken();
+ return false;
+ }
+ CSSVariableDeclarations::Type variableType;
+ nsString variableValue;
+ aConditionMet =
+ ParseVariableDeclaration(&variableType, variableValue) &&
+ ParsePriority() != ePriority_Error;
+ if (!aConditionMet) {
+ SkipUntil(')');
+ UngetToken();
+ }
+ } else {
+ if (ExpectSymbol(')', true)) {
+ UngetToken();
+ return false;
+ }
+ aConditionMet = ParseProperty(propID) &&
+ ParsePriority() != ePriority_Error;
+ if (!aConditionMet) {
+ SkipUntil(')');
+ UngetToken();
+ }
+ mTempData.ClearProperty(propID);
+ mTempData.AssertInitialState();
+ }
+ return true;
+ }
+
+ UngetToken();
+ return ParseSupportsConditionNegation(aConditionMet);
+ }
+
+ UngetToken();
+ return ParseSupportsConditionInParens(aConditionMet) &&
+ ParseSupportsConditionTerms(aConditionMet);
+}
+
+// supports_condition_terms
+// : S+ 'and' supports_condition_terms_after_operator('and')
+// | S+ 'or' supports_condition_terms_after_operator('or')
+// |
+// ;
+bool
+CSSParserImpl::ParseSupportsConditionTerms(bool& aConditionMet)
+{
+ if (!RequireWhitespace() || !GetToken(false)) {
+ return true;
+ }
+
+ if (mToken.mType != eCSSToken_Ident) {
+ UngetToken();
+ return true;
+ }
+
+ if (mToken.mIdent.LowerCaseEqualsLiteral("and")) {
+ return ParseSupportsConditionTermsAfterOperator(aConditionMet, eAnd);
+ }
+
+ if (mToken.mIdent.LowerCaseEqualsLiteral("or")) {
+ return ParseSupportsConditionTermsAfterOperator(aConditionMet, eOr);
+ }
+
+ UngetToken();
+ return true;
+}
+
+// supports_condition_terms_after_operator(operator)
+// : S+ supports_condition_in_parens ( <operator> supports_condition_in_parens )*
+// ;
+bool
+CSSParserImpl::ParseSupportsConditionTermsAfterOperator(
+ bool& aConditionMet,
+ CSSParserImpl::SupportsConditionTermOperator aOperator)
+{
+ if (!RequireWhitespace()) {
+ REPORT_UNEXPECTED(PESupportsWhitespaceRequired);
+ return false;
+ }
+
+ const char* token = aOperator == eAnd ? "and" : "or";
+ for (;;) {
+ bool termConditionMet = false;
+ if (!ParseSupportsConditionInParens(termConditionMet)) {
+ return false;
+ }
+ aConditionMet = aOperator == eAnd ? aConditionMet && termConditionMet :
+ aConditionMet || termConditionMet;
+
+ if (!GetToken(true)) {
+ return true;
+ }
+
+ if (mToken.mType != eCSSToken_Ident ||
+ !mToken.mIdent.LowerCaseEqualsASCII(token)) {
+ UngetToken();
+ return true;
+ }
+ }
+}
+
+bool
+CSSParserImpl::ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+ nsAutoString name;
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum) ||
+ !ParseCounterStyleName(name, true)) {
+ REPORT_UNEXPECTED_TOKEN(PECounterStyleNotIdent);
+ return false;
+ }
+
+ if (!ExpectSymbol('{', true)) {
+ REPORT_UNEXPECTED_TOKEN(PECounterStyleBadBlockStart);
+ return false;
+ }
+
+ RefPtr<nsCSSCounterStyleRule> rule = new nsCSSCounterStyleRule(name,
+ linenum,
+ colnum);
+ for (;;) {
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PECounterStyleEOF);
+ break;
+ }
+ if (mToken.IsSymbol('}')) {
+ break;
+ }
+ if (mToken.IsSymbol(';')) {
+ continue;
+ }
+
+ if (!ParseCounterDescriptor(rule)) {
+ REPORT_UNEXPECTED(PEDeclSkipped);
+ OUTPUT_ERROR();
+ if (!SkipDeclaration(true)) {
+ REPORT_UNEXPECTED_EOF(PECounterStyleEOF);
+ break;
+ }
+ }
+ }
+
+ int32_t system = rule->GetSystem();
+ bool isCorrect = false;
+ switch (system) {
+ case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+ case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+ case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+ case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+ case NS_STYLE_COUNTER_SYSTEM_FIXED: {
+ // check whether symbols is set and the length is sufficient
+ const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols);
+ if (symbols.GetUnit() == eCSSUnit_List &&
+ nsCSSCounterStyleRule::CheckDescValue(
+ system, eCSSCounterDesc_Symbols, symbols)) {
+ isCorrect = true;
+ }
+ break;
+ }
+ case NS_STYLE_COUNTER_SYSTEM_ADDITIVE: {
+ // for additive system, additive-symbols must be set
+ const nsCSSValue& symbols =
+ rule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
+ if (symbols.GetUnit() == eCSSUnit_PairList) {
+ isCorrect = true;
+ }
+ break;
+ }
+ case NS_STYLE_COUNTER_SYSTEM_EXTENDS: {
+ // for extends system, symbols & additive-symbols must not be set
+ const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols);
+ const nsCSSValue& additiveSymbols =
+ rule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
+ if (symbols.GetUnit() == eCSSUnit_Null &&
+ additiveSymbols.GetUnit() == eCSSUnit_Null) {
+ isCorrect = true;
+ }
+ break;
+ }
+ default:
+ NS_NOTREACHED("unknown system");
+ }
+
+ if (isCorrect) {
+ (*aAppendFunc)(rule, aData);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseCounterStyleName(nsAString& aName, bool aForDefinition)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Ident) {
+ UngetToken();
+ return false;
+ }
+
+ static const nsCSSKeyword kReservedNames[] = {
+ eCSSKeyword_none,
+ eCSSKeyword_decimal,
+ eCSSKeyword_UNKNOWN
+ };
+
+ nsCSSValue value; // we don't actually care about the value
+ if (!ParseCustomIdent(value, mToken.mIdent,
+ aForDefinition ? kReservedNames : nullptr)) {
+ REPORT_UNEXPECTED_TOKEN(PECounterStyleBadName);
+ UngetToken();
+ return false;
+ }
+
+ aName = mToken.mIdent;
+ if (nsCSSProps::IsPredefinedCounterStyle(aName)) {
+ ToLowerCase(aName);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseCounterStyleNameValue(nsCSSValue& aValue)
+{
+ nsString name;
+ if (ParseCounterStyleName(name, false)) {
+ aValue.SetStringValue(name, eCSSUnit_Ident);
+ return true;
+ }
+ return false;
+}
+
+bool
+CSSParserImpl::ParseCounterDescriptor(nsCSSCounterStyleRule* aRule)
+{
+ if (eCSSToken_Ident != mToken.mType) {
+ REPORT_UNEXPECTED_TOKEN(PECounterDescExpected);
+ return false;
+ }
+
+ nsString descName = mToken.mIdent;
+ if (!ExpectSymbol(':', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
+ OUTPUT_ERROR();
+ return false;
+ }
+
+ nsCSSCounterDesc descID = nsCSSProps::LookupCounterDesc(descName);
+ nsCSSValue value;
+
+ if (descID == eCSSCounterDesc_UNKNOWN) {
+ REPORT_UNEXPECTED_P(PEUnknownCounterDesc, descName);
+ return false;
+ }
+
+ if (!ParseCounterDescriptorValue(descID, value)) {
+ REPORT_UNEXPECTED_P(PEValueParsingError, descName);
+ return false;
+ }
+
+ if (!ExpectEndProperty()) {
+ return false;
+ }
+
+ aRule->SetDesc(descID, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
+ nsCSSValue& aValue)
+{
+ // Should also include VARIANT_IMAGE, but it is not supported currently.
+ // See bug 1024179.
+ static const int32_t VARIANT_COUNTER_SYMBOL =
+ VARIANT_STRING | VARIANT_IDENTIFIER;
+
+ switch (aDescID) {
+ case eCSSCounterDesc_System: {
+ nsCSSValue system;
+ if (!ParseEnum(system, nsCSSProps::kCounterSystemKTable)) {
+ return false;
+ }
+ switch (system.GetIntValue()) {
+ case NS_STYLE_COUNTER_SYSTEM_FIXED: {
+ nsCSSValue start;
+ if (!ParseSingleTokenVariant(start, VARIANT_INTEGER, nullptr)) {
+ start.SetIntValue(1, eCSSUnit_Integer);
+ }
+ aValue.SetPairValue(system, start);
+ return true;
+ }
+ case NS_STYLE_COUNTER_SYSTEM_EXTENDS: {
+ nsCSSValue name;
+ if (!ParseCounterStyleNameValue(name)) {
+ REPORT_UNEXPECTED_TOKEN(PECounterExtendsNotIdent);
+ return false;
+ }
+ aValue.SetPairValue(system, name);
+ return true;
+ }
+ default:
+ aValue = system;
+ return true;
+ }
+ }
+
+ case eCSSCounterDesc_Negative: {
+ nsCSSValue first, second;
+ if (!ParseSingleTokenVariant(first, VARIANT_COUNTER_SYMBOL, nullptr)) {
+ return false;
+ }
+ if (!ParseSingleTokenVariant(second, VARIANT_COUNTER_SYMBOL, nullptr)) {
+ aValue = first;
+ } else {
+ aValue.SetPairValue(first, second);
+ }
+ return true;
+ }
+
+ case eCSSCounterDesc_Prefix:
+ case eCSSCounterDesc_Suffix:
+ return ParseSingleTokenVariant(aValue, VARIANT_COUNTER_SYMBOL, nullptr);
+
+ case eCSSCounterDesc_Range: {
+ if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) {
+ return true;
+ }
+ nsCSSValuePairList* item = aValue.SetPairListValue();
+ for (;;) {
+ nsCSSValuePair pair;
+ if (!ParseCounterRange(pair)) {
+ return false;
+ }
+ item->mXValue = pair.mXValue;
+ item->mYValue = pair.mYValue;
+ if (!ExpectSymbol(',', true)) {
+ return true;
+ }
+ item->mNext = new nsCSSValuePairList;
+ item = item->mNext;
+ }
+ // should always return in the loop
+ }
+
+ case eCSSCounterDesc_Pad: {
+ nsCSSValue width, symbol;
+ bool hasWidth = ParseNonNegativeInteger(width);
+ if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) ||
+ (!hasWidth && !ParseNonNegativeInteger(width))) {
+ return false;
+ }
+ aValue.SetPairValue(width, symbol);
+ return true;
+ }
+
+ case eCSSCounterDesc_Fallback:
+ return ParseCounterStyleNameValue(aValue);
+
+ case eCSSCounterDesc_Symbols: {
+ nsCSSValueList* item = nullptr;
+ for (;;) {
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_COUNTER_SYMBOL, nullptr)) {
+ return !!item;
+ }
+ if (!item) {
+ item = aValue.SetListValue();
+ } else {
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ }
+ item->mValue = value;
+ }
+ // should always return in the loop
+ }
+
+ case eCSSCounterDesc_AdditiveSymbols: {
+ nsCSSValuePairList* item = nullptr;
+ int32_t lastWeight = -1;
+ for (;;) {
+ nsCSSValue weight, symbol;
+ bool hasWeight = ParseNonNegativeInteger(weight);
+ if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) ||
+ (!hasWeight && !ParseNonNegativeInteger(weight))) {
+ return false;
+ }
+ if (lastWeight != -1 && weight.GetIntValue() >= lastWeight) {
+ REPORT_UNEXPECTED(PECounterASWeight);
+ return false;
+ }
+ lastWeight = weight.GetIntValue();
+ if (!item) {
+ item = aValue.SetPairListValue();
+ } else {
+ item->mNext = new nsCSSValuePairList;
+ item = item->mNext;
+ }
+ item->mXValue = weight;
+ item->mYValue = symbol;
+ if (!ExpectSymbol(',', true)) {
+ return true;
+ }
+ }
+ // should always return in the loop
+ }
+
+ case eCSSCounterDesc_SpeakAs:
+ if (ParseSingleTokenVariant(aValue, VARIANT_AUTO | VARIANT_KEYWORD,
+ nsCSSProps::kCounterSpeakAsKTable)) {
+ if (aValue.GetUnit() == eCSSUnit_Enumerated &&
+ aValue.GetIntValue() == NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT) {
+ // Currently spell-out is not supported, so it is explicitly
+ // rejected here rather than parsed as a custom identifier.
+ // See bug 1024178.
+ return false;
+ }
+ return true;
+ }
+ return ParseCounterStyleNameValue(aValue);
+
+ default:
+ NS_NOTREACHED("unknown descriptor");
+ return false;
+ }
+}
+
+bool
+CSSParserImpl::ParseCounterRange(nsCSSValuePair& aPair)
+{
+ static const int32_t VARIANT_BOUND = VARIANT_INTEGER | VARIANT_KEYWORD;
+ nsCSSValue lower, upper;
+ if (!ParseSingleTokenVariant(lower, VARIANT_BOUND,
+ nsCSSProps::kCounterRangeKTable) ||
+ !ParseSingleTokenVariant(upper, VARIANT_BOUND,
+ nsCSSProps::kCounterRangeKTable)) {
+ return false;
+ }
+ if (lower.GetUnit() != eCSSUnit_Enumerated &&
+ upper.GetUnit() != eCSSUnit_Enumerated &&
+ lower.GetIntValue() > upper.GetIntValue()) {
+ return false;
+ }
+ aPair = nsCSSValuePair(lower, upper);
+ return true;
+}
+
+bool
+CSSParserImpl::SkipUntil(char16_t aStopSymbol)
+{
+ nsCSSToken* tk = &mToken;
+ AutoTArray<char16_t, 16> stack;
+ stack.AppendElement(aStopSymbol);
+ for (;;) {
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (eCSSToken_Symbol == tk->mType) {
+ char16_t symbol = tk->mSymbol;
+ uint32_t stackTopIndex = stack.Length() - 1;
+ if (symbol == stack.ElementAt(stackTopIndex)) {
+ stack.RemoveElementAt(stackTopIndex);
+ if (stackTopIndex == 0) {
+ return true;
+ }
+
+ // Just handle out-of-memory by parsing incorrectly. It's
+ // highly unlikely we're dealing with a legitimate style sheet
+ // anyway.
+ } else if ('{' == symbol) {
+ stack.AppendElement('}');
+ } else if ('[' == symbol) {
+ stack.AppendElement(']');
+ } else if ('(' == symbol) {
+ stack.AppendElement(')');
+ }
+ } else if (eCSSToken_Function == tk->mType ||
+ eCSSToken_Bad_URL == tk->mType) {
+ stack.AppendElement(')');
+ }
+ }
+}
+
+bool
+CSSParserImpl::SkipBalancedContentUntil(char16_t aStopSymbol)
+{
+ nsCSSToken* tk = &mToken;
+ AutoTArray<char16_t, 16> stack;
+ stack.AppendElement(aStopSymbol);
+ for (;;) {
+ if (!GetToken(true)) {
+ return true;
+ }
+ if (eCSSToken_Symbol == tk->mType) {
+ char16_t symbol = tk->mSymbol;
+ uint32_t stackTopIndex = stack.Length() - 1;
+ if (symbol == stack.ElementAt(stackTopIndex)) {
+ stack.RemoveElementAt(stackTopIndex);
+ if (stackTopIndex == 0) {
+ return true;
+ }
+
+ // Just handle out-of-memory by parsing incorrectly. It's
+ // highly unlikely we're dealing with a legitimate style sheet
+ // anyway.
+ } else if ('{' == symbol) {
+ stack.AppendElement('}');
+ } else if ('[' == symbol) {
+ stack.AppendElement(']');
+ } else if ('(' == symbol) {
+ stack.AppendElement(')');
+ } else if (')' == symbol ||
+ ']' == symbol ||
+ '}' == symbol) {
+ UngetToken();
+ return false;
+ }
+ } else if (eCSSToken_Function == tk->mType ||
+ eCSSToken_Bad_URL == tk->mType) {
+ stack.AppendElement(')');
+ }
+ }
+}
+
+void
+CSSParserImpl::SkipUntilOneOf(const char16_t* aStopSymbolChars)
+{
+ nsCSSToken* tk = &mToken;
+ nsDependentString stopSymbolChars(aStopSymbolChars);
+ for (;;) {
+ if (!GetToken(true)) {
+ break;
+ }
+ if (eCSSToken_Symbol == tk->mType) {
+ char16_t symbol = tk->mSymbol;
+ if (stopSymbolChars.FindChar(symbol) != -1) {
+ break;
+ } else if ('{' == symbol) {
+ SkipUntil('}');
+ } else if ('[' == symbol) {
+ SkipUntil(']');
+ } else if ('(' == symbol) {
+ SkipUntil(')');
+ }
+ } else if (eCSSToken_Function == tk->mType ||
+ eCSSToken_Bad_URL == tk->mType) {
+ SkipUntil(')');
+ }
+ }
+}
+
+void
+CSSParserImpl::SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars)
+{
+ uint32_t i = aStopSymbolChars.Length();
+ while (i--) {
+ SkipUntil(aStopSymbolChars[i]);
+ }
+}
+
+bool
+CSSParserImpl::SkipDeclaration(bool aCheckForBraces)
+{
+ nsCSSToken* tk = &mToken;
+ for (;;) {
+ if (!GetToken(true)) {
+ if (aCheckForBraces) {
+ REPORT_UNEXPECTED_EOF(PESkipDeclBraceEOF);
+ }
+ return false;
+ }
+ if (eCSSToken_Symbol == tk->mType) {
+ char16_t symbol = tk->mSymbol;
+ if (';' == symbol) {
+ break;
+ }
+ if (aCheckForBraces) {
+ if ('}' == symbol) {
+ UngetToken();
+ break;
+ }
+ }
+ if ('{' == symbol) {
+ SkipUntil('}');
+ } else if ('(' == symbol) {
+ SkipUntil(')');
+ } else if ('[' == symbol) {
+ SkipUntil(']');
+ }
+ } else if (eCSSToken_Function == tk->mType ||
+ eCSSToken_Bad_URL == tk->mType) {
+ SkipUntil(')');
+ }
+ }
+ return true;
+}
+
+void
+CSSParserImpl::SkipRuleSet(bool aInsideBraces)
+{
+ nsCSSToken* tk = &mToken;
+ for (;;) {
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PESkipRSBraceEOF);
+ break;
+ }
+ if (eCSSToken_Symbol == tk->mType) {
+ char16_t symbol = tk->mSymbol;
+ if ('}' == symbol && aInsideBraces) {
+ // leave block closer for higher-level grammar to consume
+ UngetToken();
+ break;
+ } else if ('{' == symbol) {
+ SkipUntil('}');
+ break;
+ } else if ('(' == symbol) {
+ SkipUntil(')');
+ } else if ('[' == symbol) {
+ SkipUntil(']');
+ }
+ } else if (eCSSToken_Function == tk->mType ||
+ eCSSToken_Bad_URL == tk->mType) {
+ SkipUntil(')');
+ }
+ }
+}
+
+void
+CSSParserImpl::PushGroup(css::GroupRule* aRule)
+{
+ mGroupStack.AppendElement(aRule);
+}
+
+void
+CSSParserImpl::PopGroup()
+{
+ uint32_t count = mGroupStack.Length();
+ if (0 < count) {
+ mGroupStack.RemoveElementAt(count - 1);
+ }
+}
+
+void
+CSSParserImpl::AppendRule(css::Rule* aRule)
+{
+ uint32_t count = mGroupStack.Length();
+ if (0 < count) {
+ mGroupStack[count - 1]->AppendStyleRule(aRule);
+ }
+ else {
+ mSheet->AppendStyleRule(aRule);
+ }
+}
+
+bool
+CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData,
+ bool aInsideBraces)
+{
+ // First get the list of selectors for the rule
+ nsCSSSelectorList* slist = nullptr;
+ uint32_t linenum, colnum;
+ if (!GetNextTokenLocation(true, &linenum, &colnum) ||
+ !ParseSelectorList(slist, char16_t('{'))) {
+ REPORT_UNEXPECTED(PEBadSelectorRSIgnored);
+ OUTPUT_ERROR();
+ SkipRuleSet(aInsideBraces);
+ return false;
+ }
+ NS_ASSERTION(nullptr != slist, "null selector list");
+ CLEAR_ERROR();
+
+ // Next parse the declaration block
+ uint32_t parseFlags = eParseDeclaration_InBraces |
+ eParseDeclaration_AllowImportant;
+ RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags);
+ if (nullptr == declaration) {
+ delete slist;
+ return false;
+ }
+
+#if 0
+ slist->Dump();
+ fputs("{\n", stdout);
+ declaration->List();
+ fputs("}\n", stdout);
+#endif
+
+ // Translate the selector list and declaration block into style data
+
+ RefPtr<css::StyleRule> rule = new css::StyleRule(slist, declaration,
+ linenum, colnum);
+ (*aAppendFunc)(rule, aData);
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
+ char16_t aStopChar)
+{
+ nsCSSSelectorList* list = nullptr;
+ if (! ParseSelectorGroup(list)) {
+ // must have at least one selector group
+ aListHead = nullptr;
+ return false;
+ }
+ NS_ASSERTION(nullptr != list, "no selector list");
+ aListHead = list;
+
+ // After that there must either be a "," or a "{" (the latter if
+ // StopChar is nonzero)
+ nsCSSToken* tk = &mToken;
+ for (;;) {
+ if (! GetToken(true)) {
+ if (aStopChar == char16_t(0)) {
+ return true;
+ }
+
+ REPORT_UNEXPECTED_EOF(PESelectorListExtraEOF);
+ break;
+ }
+
+ if (eCSSToken_Symbol == tk->mType) {
+ if (',' == tk->mSymbol) {
+ nsCSSSelectorList* newList = nullptr;
+ // Another selector group must follow
+ if (! ParseSelectorGroup(newList)) {
+ break;
+ }
+ // add new list to the end of the selector list
+ list->mNext = newList;
+ list = newList;
+ continue;
+ } else if (aStopChar == tk->mSymbol && aStopChar != char16_t(0)) {
+ UngetToken();
+ return true;
+ }
+ }
+ REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
+ UngetToken();
+ break;
+ }
+
+ delete aListHead;
+ aListHead = nullptr;
+ return false;
+}
+
+static bool IsUniversalSelector(const nsCSSSelector& aSelector)
+{
+ return bool((aSelector.mNameSpace == kNameSpaceID_Unknown) &&
+ (aSelector.mLowercaseTag == nullptr) &&
+ (aSelector.mIDList == nullptr) &&
+ (aSelector.mClassList == nullptr) &&
+ (aSelector.mAttrList == nullptr) &&
+ (aSelector.mNegations == nullptr) &&
+ (aSelector.mPseudoClassList == nullptr));
+}
+
+bool
+CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList)
+{
+ char16_t combinator = 0;
+ nsAutoPtr<nsCSSSelectorList> list(new nsCSSSelectorList());
+
+ for (;;) {
+ if (!ParseSelector(list, combinator)) {
+ return false;
+ }
+
+ // Look for a combinator.
+ if (!GetToken(false)) {
+ break; // EOF ok here
+ }
+
+ combinator = char16_t(0);
+ if (mToken.mType == eCSSToken_Whitespace) {
+ if (!GetToken(true)) {
+ break; // EOF ok here
+ }
+ combinator = char16_t(' ');
+ }
+
+ if (mToken.mType != eCSSToken_Symbol) {
+ UngetToken(); // not a combinator
+ } else {
+ char16_t symbol = mToken.mSymbol;
+ if (symbol == '+' || symbol == '>' || symbol == '~') {
+ combinator = mToken.mSymbol;
+ } else {
+ UngetToken(); // not a combinator
+ if (symbol == ',' || symbol == '{' || symbol == ')') {
+ break; // end of selector group
+ }
+ }
+ }
+
+ if (!combinator) {
+ REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
+ return false;
+ }
+ }
+
+ aList = list.forget();
+ return true;
+}
+
+#define SEL_MASK_NSPACE 0x01
+#define SEL_MASK_ELEM 0x02
+#define SEL_MASK_ID 0x04
+#define SEL_MASK_CLASS 0x08
+#define SEL_MASK_ATTRIB 0x10
+#define SEL_MASK_PCLASS 0x20
+#define SEL_MASK_PELEM 0x40
+
+//
+// Parses an ID selector #name
+//
+CSSParserImpl::nsSelectorParsingStatus
+CSSParserImpl::ParseIDSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector)
+{
+ NS_ASSERTION(!mToken.mIdent.IsEmpty(),
+ "Empty mIdent in eCSSToken_ID token?");
+ aDataMask |= SEL_MASK_ID;
+ aSelector.AddID(mToken.mIdent);
+ return eSelectorParsingStatus_Continue;
+}
+
+//
+// Parses a class selector .name
+//
+CSSParserImpl::nsSelectorParsingStatus
+CSSParserImpl::ParseClassSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector)
+{
+ if (! GetToken(false)) { // get ident
+ REPORT_UNEXPECTED_EOF(PEClassSelEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ if (eCSSToken_Ident != mToken.mType) { // malformed selector
+ REPORT_UNEXPECTED_TOKEN(PEClassSelNotIdent);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ aDataMask |= SEL_MASK_CLASS;
+
+ aSelector.AddClass(mToken.mIdent);
+
+ return eSelectorParsingStatus_Continue;
+}
+
+//
+// Parse a type element selector or a universal selector
+// namespace|type or namespace|* or *|* or *
+//
+CSSParserImpl::nsSelectorParsingStatus
+CSSParserImpl::ParseTypeOrUniversalSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector,
+ bool aIsNegated)
+{
+ nsAutoString buffer;
+ if (mToken.IsSymbol('*')) { // universal element selector, or universal namespace
+ if (ExpectSymbol('|', false)) { // was namespace
+ aDataMask |= SEL_MASK_NSPACE;
+ aSelector.SetNameSpace(kNameSpaceID_Unknown); // namespace wildcard
+
+ if (! GetToken(false)) {
+ REPORT_UNEXPECTED_EOF(PETypeSelEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ if (eCSSToken_Ident == mToken.mType) { // element name
+ aDataMask |= SEL_MASK_ELEM;
+
+ aSelector.SetTag(mToken.mIdent);
+ }
+ else if (mToken.IsSymbol('*')) { // universal selector
+ aDataMask |= SEL_MASK_ELEM;
+ // don't set tag
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ else { // was universal element selector
+ SetDefaultNamespaceOnSelector(aSelector);
+ aDataMask |= SEL_MASK_ELEM;
+ // don't set any tag in the selector
+ }
+ if (! GetToken(false)) { // premature eof is ok (here!)
+ return eSelectorParsingStatus_Done;
+ }
+ }
+ else if (eCSSToken_Ident == mToken.mType) { // element name or namespace name
+ buffer = mToken.mIdent; // hang on to ident
+
+ if (ExpectSymbol('|', false)) { // was namespace
+ aDataMask |= SEL_MASK_NSPACE;
+ int32_t nameSpaceID = GetNamespaceIdForPrefix(buffer);
+ if (nameSpaceID == kNameSpaceID_Unknown) {
+ return eSelectorParsingStatus_Error;
+ }
+ aSelector.SetNameSpace(nameSpaceID);
+
+ if (! GetToken(false)) {
+ REPORT_UNEXPECTED_EOF(PETypeSelEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ if (eCSSToken_Ident == mToken.mType) { // element name
+ aDataMask |= SEL_MASK_ELEM;
+ aSelector.SetTag(mToken.mIdent);
+ }
+ else if (mToken.IsSymbol('*')) { // universal selector
+ aDataMask |= SEL_MASK_ELEM;
+ // don't set tag
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ else { // was element name
+ SetDefaultNamespaceOnSelector(aSelector);
+ aSelector.SetTag(buffer);
+
+ aDataMask |= SEL_MASK_ELEM;
+ }
+ if (! GetToken(false)) { // premature eof is ok (here!)
+ return eSelectorParsingStatus_Done;
+ }
+ }
+ else if (mToken.IsSymbol('|')) { // No namespace
+ aDataMask |= SEL_MASK_NSPACE;
+ aSelector.SetNameSpace(kNameSpaceID_None); // explicit NO namespace
+
+ // get mandatory tag
+ if (! GetToken(false)) {
+ REPORT_UNEXPECTED_EOF(PETypeSelEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ if (eCSSToken_Ident == mToken.mType) { // element name
+ aDataMask |= SEL_MASK_ELEM;
+ aSelector.SetTag(mToken.mIdent);
+ }
+ else if (mToken.IsSymbol('*')) { // universal selector
+ aDataMask |= SEL_MASK_ELEM;
+ // don't set tag
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ if (! GetToken(false)) { // premature eof is ok (here!)
+ return eSelectorParsingStatus_Done;
+ }
+ }
+ else {
+ SetDefaultNamespaceOnSelector(aSelector);
+ }
+
+ if (aIsNegated) {
+ // restore last token read in case of a negated type selector
+ UngetToken();
+ }
+ return eSelectorParsingStatus_Continue;
+}
+
+//
+// Parse attribute selectors [attr], [attr=value], [attr|=value],
+// [attr~=value], [attr^=value], [attr$=value] and [attr*=value]
+//
+CSSParserImpl::nsSelectorParsingStatus
+CSSParserImpl::ParseAttributeSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector)
+{
+ if (! GetToken(true)) { // premature EOF
+ REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
+ return eSelectorParsingStatus_Error;
+ }
+
+ int32_t nameSpaceID = kNameSpaceID_None;
+ nsAutoString attr;
+ if (mToken.IsSymbol('*')) { // wildcard namespace
+ nameSpaceID = kNameSpaceID_Unknown;
+ if (ExpectSymbol('|', false)) {
+ if (! GetToken(false)) { // premature EOF
+ REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ if (eCSSToken_Ident == mToken.mType) { // attr name
+ attr = mToken.mIdent;
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttSelNoBar);
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ else if (mToken.IsSymbol('|')) { // NO namespace
+ if (! GetToken(false)) { // premature EOF
+ REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ if (eCSSToken_Ident == mToken.mType) { // attr name
+ attr = mToken.mIdent;
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ else if (eCSSToken_Ident == mToken.mType) { // attr name or namespace
+ attr = mToken.mIdent; // hang on to it
+ if (ExpectSymbol('|', false)) { // was a namespace
+ nameSpaceID = GetNamespaceIdForPrefix(attr);
+ if (nameSpaceID == kNameSpaceID_Unknown) {
+ return eSelectorParsingStatus_Error;
+ }
+ if (! GetToken(false)) { // premature EOF
+ REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ if (eCSSToken_Ident == mToken.mType) { // attr name
+ attr = mToken.mIdent;
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ }
+ else { // malformed
+ REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+
+ bool gotEOF = false;
+ if (! GetToken(true)) { // premature EOF
+ // Treat this just like we saw a ']', but do still output the
+ // warning, similar to what ExpectSymbol does.
+ REPORT_UNEXPECTED_EOF(PEAttSelInnerEOF);
+ gotEOF = true;
+ }
+ if (gotEOF ||
+ (eCSSToken_Symbol == mToken.mType) ||
+ (eCSSToken_Includes == mToken.mType) ||
+ (eCSSToken_Dashmatch == mToken.mType) ||
+ (eCSSToken_Beginsmatch == mToken.mType) ||
+ (eCSSToken_Endsmatch == mToken.mType) ||
+ (eCSSToken_Containsmatch == mToken.mType)) {
+ uint8_t func;
+ // Important: Check the EOF/']' case first, since if gotEOF we
+ // don't want to be examining mToken.
+ if (gotEOF || ']' == mToken.mSymbol) {
+ aDataMask |= SEL_MASK_ATTRIB;
+ aSelector.AddAttribute(nameSpaceID, attr);
+ func = NS_ATTR_FUNC_SET;
+ }
+ else if (eCSSToken_Includes == mToken.mType) {
+ func = NS_ATTR_FUNC_INCLUDES;
+ }
+ else if (eCSSToken_Dashmatch == mToken.mType) {
+ func = NS_ATTR_FUNC_DASHMATCH;
+ }
+ else if (eCSSToken_Beginsmatch == mToken.mType) {
+ func = NS_ATTR_FUNC_BEGINSMATCH;
+ }
+ else if (eCSSToken_Endsmatch == mToken.mType) {
+ func = NS_ATTR_FUNC_ENDSMATCH;
+ }
+ else if (eCSSToken_Containsmatch == mToken.mType) {
+ func = NS_ATTR_FUNC_CONTAINSMATCH;
+ }
+ else if ('=' == mToken.mSymbol) {
+ func = NS_ATTR_FUNC_EQUALS;
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected);
+ UngetToken(); // bad function
+ return eSelectorParsingStatus_Error;
+ }
+ if (NS_ATTR_FUNC_SET != func) { // get value
+ if (! GetToken(true)) { // premature EOF
+ REPORT_UNEXPECTED_EOF(PEAttSelValueEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ if ((eCSSToken_Ident == mToken.mType) || (eCSSToken_String == mToken.mType)) {
+ nsAutoString value(mToken.mIdent);
+ // Avoid duplicating the eof handling by just not checking for
+ // the 'i' annotation if we got eof.
+ typedef nsAttrSelector::ValueCaseSensitivity ValueCaseSensitivity;
+ ValueCaseSensitivity valueCaseSensitivity =
+ ValueCaseSensitivity::CaseSensitive;
+ bool eof = !GetToken(true);
+ if (!eof) {
+ if (eCSSToken_Ident == mToken.mType &&
+ mToken.mIdent.LowerCaseEqualsLiteral("i")) {
+ valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitive;
+ eof = !GetToken(true);
+ }
+ }
+ bool gotClosingBracket;
+ if (eof) { // premature EOF
+ // Report a warning, but then treat it as a closing bracket.
+ REPORT_UNEXPECTED_EOF(PEAttSelCloseEOF);
+ gotClosingBracket = true;
+ } else {
+ gotClosingBracket = mToken.IsSymbol(']');
+ }
+ if (gotClosingBracket) {
+ // For cases when this style sheet is applied to an HTML
+ // element in an HTML document, and the attribute selector is
+ // for a non-namespaced attribute, then check to see if it's
+ // one of the known attributes whose VALUE is
+ // case-insensitive.
+ if (valueCaseSensitivity != ValueCaseSensitivity::CaseInsensitive &&
+ nameSpaceID == kNameSpaceID_None) {
+ static const char* caseInsensitiveHTMLAttribute[] = {
+ // list based on http://www.w3.org/TR/html4/
+ "lang",
+ "dir",
+ "http-equiv",
+ "text",
+ "link",
+ "vlink",
+ "alink",
+ "compact",
+ "align",
+ "frame",
+ "rules",
+ "valign",
+ "scope",
+ "axis",
+ "nowrap",
+ "hreflang",
+ "rel",
+ "rev",
+ "charset",
+ "codetype",
+ "declare",
+ "valuetype",
+ "shape",
+ "nohref",
+ "media",
+ "bgcolor",
+ "clear",
+ "color",
+ "face",
+ "noshade",
+ "noresize",
+ "scrolling",
+ "target",
+ "method",
+ "enctype",
+ "accept-charset",
+ "accept",
+ "checked",
+ "multiple",
+ "selected",
+ "disabled",
+ "readonly",
+ "language",
+ "defer",
+ "type",
+ // additional attributes not in HTML4
+ "direction", // marquee
+ nullptr
+ };
+ short i = 0;
+ const char* htmlAttr;
+ while ((htmlAttr = caseInsensitiveHTMLAttribute[i++])) {
+ if (attr.LowerCaseEqualsASCII(htmlAttr)) {
+ valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitiveInHTML;
+ break;
+ }
+ }
+ }
+ aDataMask |= SEL_MASK_ATTRIB;
+ aSelector.AddAttribute(nameSpaceID, attr, func, value,
+ valueCaseSensitivity);
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttSelNoClose);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttSelBadValue);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected);
+ UngetToken(); // bad dog, no biscut!
+ return eSelectorParsingStatus_Error;
+ }
+ return eSelectorParsingStatus_Continue;
+}
+
+//
+// Parse pseudo-classes and pseudo-elements
+//
+CSSParserImpl::nsSelectorParsingStatus
+CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector,
+ bool aIsNegated,
+ nsIAtom** aPseudoElement,
+ nsAtomList** aPseudoElementArgs,
+ CSSPseudoElementType* aPseudoElementType)
+{
+ NS_ASSERTION(aIsNegated || (aPseudoElement && aPseudoElementArgs),
+ "expected location to store pseudo element");
+ NS_ASSERTION(!aIsNegated || (!aPseudoElement && !aPseudoElementArgs),
+ "negated selectors shouldn't have a place to store "
+ "pseudo elements");
+ if (! GetToken(false)) { // premature eof
+ REPORT_UNEXPECTED_EOF(PEPseudoSelEOF);
+ return eSelectorParsingStatus_Error;
+ }
+
+ // First, find out whether we are parsing a CSS3 pseudo-element
+ bool parsingPseudoElement = false;
+ if (mToken.IsSymbol(':')) {
+ parsingPseudoElement = true;
+ if (! GetToken(false)) { // premature eof
+ REPORT_UNEXPECTED_EOF(PEPseudoSelEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ }
+
+ // Do some sanity-checking on the token
+ if (eCSSToken_Ident != mToken.mType && eCSSToken_Function != mToken.mType) {
+ // malformed selector
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelBadName);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+
+ // OK, now we know we have an mIdent. Atomize it. All the atoms, for
+ // pseudo-classes as well as pseudo-elements, start with a single ':'.
+ nsAutoString buffer;
+ buffer.Append(char16_t(':'));
+ buffer.Append(mToken.mIdent);
+ nsContentUtils::ASCIIToLower(buffer);
+ nsCOMPtr<nsIAtom> pseudo = NS_Atomize(buffer);
+
+ // stash away some info about this pseudo so we only have to get it once.
+ bool isTreePseudo = false;
+ CSSEnabledState enabledState = EnabledState();
+ CSSPseudoElementType pseudoElementType =
+ nsCSSPseudoElements::GetPseudoType(pseudo, enabledState);
+ CSSPseudoClassType pseudoClassType =
+ nsCSSPseudoClasses::GetPseudoType(pseudo, enabledState);
+ bool pseudoClassIsUserAction =
+ nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType);
+
+ if (nsCSSAnonBoxes::IsNonElement(pseudo)) {
+ // Non-element anonymous boxes should not match any rule.
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+
+ // We currently allow :-moz-placeholder and ::-moz-placeholder and
+ // ::placeholder. We have to be a bit stricter regarding the
+ // pseudo-element parsing rules.
+ if (pseudoElementType == CSSPseudoElementType::placeholder &&
+ pseudoClassType == CSSPseudoClassType::mozPlaceholder) {
+ if (parsingPseudoElement) {
+ pseudoClassType = CSSPseudoClassType::NotPseudo;
+ } else {
+ pseudoElementType = CSSPseudoElementType::NotPseudo;
+ }
+ }
+
+#ifdef MOZ_XUL
+ isTreePseudo = (pseudoElementType == CSSPseudoElementType::XULTree);
+ // If a tree pseudo-element is using the function syntax, it will
+ // get isTree set here and will pass the check below that only
+ // allows functions if they are in our list of things allowed to be
+ // functions. If it is _not_ using the function syntax, isTree will
+ // be false, and it will still pass that check. So the tree
+ // pseudo-elements are allowed to be either functions or not, as
+ // desired.
+ bool isTree = (eCSSToken_Function == mToken.mType) && isTreePseudo;
+#endif
+ bool isPseudoElement = (pseudoElementType < CSSPseudoElementType::Count);
+ // anonymous boxes are only allowed if they're the tree boxes or we have
+ // enabled agent rules
+ bool isAnonBox = isTreePseudo ||
+ (pseudoElementType == CSSPseudoElementType::AnonBox &&
+ AgentRulesEnabled());
+ bool isPseudoClass =
+ (pseudoClassType != CSSPseudoClassType::NotPseudo);
+
+ NS_ASSERTION(!isPseudoClass ||
+ pseudoElementType == CSSPseudoElementType::NotPseudo,
+ "Why is this atom both a pseudo-class and a pseudo-element?");
+ NS_ASSERTION(isPseudoClass + isPseudoElement + isAnonBox <= 1,
+ "Shouldn't be more than one of these");
+
+ if (!isPseudoClass && !isPseudoElement && !isAnonBox) {
+ // Not a pseudo-class, not a pseudo-element.... forget it
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+
+ // If it's a function token, it better be on our "ok" list, and if the name
+ // is that of a function pseudo it better be a function token
+ if ((eCSSToken_Function == mToken.mType) !=
+ (
+#ifdef MOZ_XUL
+ isTree ||
+#endif
+ CSSPseudoClassType::negation == pseudoClassType ||
+ nsCSSPseudoClasses::HasStringArg(pseudoClassType) ||
+ nsCSSPseudoClasses::HasNthPairArg(pseudoClassType) ||
+ nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType))) {
+ // There are no other function pseudos
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelNonFunc);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+
+ // If it starts with "::", it better be a pseudo-element
+ if (parsingPseudoElement &&
+ !isPseudoElement &&
+ !isAnonBox) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelNotPE);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+
+ if (aSelector.IsPseudoElement()) {
+ CSSPseudoElementType type = aSelector.PseudoType();
+ if (type >= CSSPseudoElementType::Count ||
+ !nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) {
+ // We only allow user action pseudo-classes on certain pseudo-elements.
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ if (!isPseudoClass || !pseudoClassIsUserAction) {
+ // CSS 4 Selectors says that pseudo-elements can only be followed by
+ // a user action pseudo-class.
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+
+ if (!parsingPseudoElement &&
+ CSSPseudoClassType::negation == pseudoClassType) {
+ if (aIsNegated) { // :not() can't be itself negated
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelDoubleNot);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ // CSS 3 Negation pseudo-class takes one simple selector as argument
+ nsSelectorParsingStatus parsingStatus =
+ ParseNegatedSimpleSelector(aDataMask, aSelector);
+ if (eSelectorParsingStatus_Continue != parsingStatus) {
+ return parsingStatus;
+ }
+ }
+ else if (!parsingPseudoElement && isPseudoClass) {
+ aDataMask |= SEL_MASK_PCLASS;
+ if (eCSSToken_Function == mToken.mType) {
+ nsSelectorParsingStatus parsingStatus;
+ if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) {
+ parsingStatus =
+ ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
+ }
+ else if (nsCSSPseudoClasses::HasNthPairArg(pseudoClassType)) {
+ parsingStatus =
+ ParsePseudoClassWithNthPairArg(aSelector, pseudoClassType);
+ }
+ else {
+ MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType),
+ "unexpected pseudo with function token");
+ parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector,
+ pseudoClassType);
+ }
+ if (eSelectorParsingStatus_Continue != parsingStatus) {
+ if (eSelectorParsingStatus_Error == parsingStatus) {
+ SkipUntil(')');
+ }
+ return parsingStatus;
+ }
+ }
+ else {
+ aSelector.AddPseudoClass(pseudoClassType);
+ }
+ }
+ else if (isPseudoElement || isAnonBox) {
+ // Pseudo-element. Make some more sanity checks.
+
+ if (aIsNegated) { // pseudo-elements can't be negated
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelPEInNot);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ // CSS2 pseudo-elements and -moz-tree-* pseudo-elements are allowed
+ // to have a single ':' on them. Others (CSS3+ pseudo-elements and
+ // various -moz-* pseudo-elements) must have |parsingPseudoElement|
+ // set.
+ if (!parsingPseudoElement &&
+ !nsCSSPseudoElements::IsCSS2PseudoElement(pseudo)
+#ifdef MOZ_XUL
+ && !isTreePseudo
+#endif
+ ) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelNewStyleOnly);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+
+ if (0 == (aDataMask & SEL_MASK_PELEM)) {
+ aDataMask |= SEL_MASK_PELEM;
+ NS_ADDREF(*aPseudoElement = pseudo);
+ *aPseudoElementType = pseudoElementType;
+
+#ifdef MOZ_XUL
+ if (isTree) {
+ // We have encountered a pseudoelement of the form
+ // -moz-tree-xxxx(a,b,c). We parse (a,b,c) and add each
+ // item in the list to the pseudoclass list. They will be pulled
+ // from the list later along with the pseudo-element.
+ if (!ParseTreePseudoElement(aPseudoElementArgs)) {
+ return eSelectorParsingStatus_Error;
+ }
+ }
+#endif
+
+ // Pseudo-elements can only be followed by user action pseudo-classes
+ // or be the end of the selector. So the next non-whitespace token must
+ // be ':', '{' or ',' or EOF.
+ if (!GetToken(true)) { // premature eof is ok (here!)
+ return eSelectorParsingStatus_Done;
+ }
+ if (parsingPseudoElement && mToken.IsSymbol(':')) {
+ UngetToken();
+ return eSelectorParsingStatus_Continue;
+ }
+ if ((mToken.IsSymbol('{') || mToken.IsSymbol(','))) {
+ UngetToken();
+ return eSelectorParsingStatus_Done;
+ }
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelEndOrUserActionPC);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ else { // multiple pseudo elements, not legal
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelMultiplePE);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+#ifdef DEBUG
+ else {
+ // We should never end up here. Indeed, if we ended up here, we know (from
+ // the current if/else cascade) that !isPseudoElement and !isAnonBox. But
+ // then due to our earlier check we know that isPseudoClass. Since we
+ // didn't fall into the isPseudoClass case in this cascade, we must have
+ // parsingPseudoElement. But we've already checked the
+ // parsingPseudoElement && !isPseudoClass && !isAnonBox case and bailed if
+ // it's happened.
+ NS_NOTREACHED("How did this happen?");
+ }
+#endif
+ return eSelectorParsingStatus_Continue;
+}
+
+//
+// Parse the argument of a negation pseudo-class :not()
+//
+CSSParserImpl::nsSelectorParsingStatus
+CSSParserImpl::ParseNegatedSimpleSelector(int32_t& aDataMask,
+ nsCSSSelector& aSelector)
+{
+ if (! GetToken(true)) { // premature eof
+ REPORT_UNEXPECTED_EOF(PENegationEOF);
+ return eSelectorParsingStatus_Error;
+ }
+
+ if (mToken.IsSymbol(')')) {
+ REPORT_UNEXPECTED_TOKEN(PENegationBadArg);
+ return eSelectorParsingStatus_Error;
+ }
+
+ // Create a new nsCSSSelector and add it to the end of
+ // aSelector.mNegations.
+ // Given the current parsing rules, every selector in mNegations
+ // contains only one simple selector (css3 definition) within it.
+ // This could easily change in future versions of CSS, and the only
+ // thing we need to change to support that is this parsing code and the
+ // serialization code for nsCSSSelector.
+ nsCSSSelector *newSel = new nsCSSSelector();
+ nsCSSSelector* negations = &aSelector;
+ while (negations->mNegations) {
+ negations = negations->mNegations;
+ }
+ negations->mNegations = newSel;
+
+ nsSelectorParsingStatus parsingStatus;
+ if (eCSSToken_ID == mToken.mType) { // #id
+ parsingStatus = ParseIDSelector(aDataMask, *newSel);
+ }
+ else if (mToken.IsSymbol('.')) { // .class
+ parsingStatus = ParseClassSelector(aDataMask, *newSel);
+ }
+ else if (mToken.IsSymbol(':')) { // :pseudo
+ parsingStatus = ParsePseudoSelector(aDataMask, *newSel, true,
+ nullptr, nullptr, nullptr);
+ }
+ else if (mToken.IsSymbol('[')) { // [attribute
+ parsingStatus = ParseAttributeSelector(aDataMask, *newSel);
+ if (eSelectorParsingStatus_Error == parsingStatus) {
+ // Skip forward to the matching ']'
+ SkipUntil(']');
+ }
+ }
+ else {
+ // then it should be a type element or universal selector
+ parsingStatus = ParseTypeOrUniversalSelector(aDataMask, *newSel, true);
+ }
+ if (eSelectorParsingStatus_Error == parsingStatus) {
+ REPORT_UNEXPECTED_TOKEN(PENegationBadInner);
+ SkipUntil(')');
+ return parsingStatus;
+ }
+ // close the parenthesis
+ if (!ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED_TOKEN(PENegationNoClose);
+ SkipUntil(')');
+ return eSelectorParsingStatus_Error;
+ }
+
+ NS_ASSERTION(newSel->mNameSpace == kNameSpaceID_Unknown ||
+ (!newSel->mIDList && !newSel->mClassList &&
+ !newSel->mPseudoClassList && !newSel->mAttrList),
+ "Need to fix the serialization code to deal with this");
+
+ return eSelectorParsingStatus_Continue;
+}
+
+//
+// Parse the argument of a pseudo-class that has an ident arg
+//
+CSSParserImpl::nsSelectorParsingStatus
+CSSParserImpl::ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
+ CSSPseudoClassType aType)
+{
+ if (! GetToken(true)) { // premature eof
+ REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ // We expect an identifier with a language abbreviation
+ if (eCSSToken_Ident != mToken.mType) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotIdent);
+ UngetToken();
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+
+ // -moz-locale-dir and dir take an identifier argument. While
+ // only 'ltr' and 'rtl' (case-insensitively) will match anything, any
+ // other identifier is still valid.
+ if (aType == CSSPseudoClassType::mozLocaleDir ||
+ aType == CSSPseudoClassType::mozDir ||
+ aType == CSSPseudoClassType::dir) {
+ nsContentUtils::ASCIIToLower(mToken.mIdent); // case insensitive
+ }
+
+ // Add the pseudo with the language parameter
+ aSelector.AddPseudoClass(aType, mToken.mIdent.get());
+
+ // close the parenthesis
+ if (!ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+
+ return eSelectorParsingStatus_Continue;
+}
+
+CSSParserImpl::nsSelectorParsingStatus
+CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
+ CSSPseudoClassType aType)
+{
+ int32_t numbers[2] = { 0, 0 };
+ int32_t sign[2] = { 1, 1 };
+ bool hasSign[2] = { false, false };
+ bool lookForB = true;
+
+ // Follow the whitespace rules as proposed in
+ // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
+
+ if (! GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
+ return eSelectorParsingStatus_Error;
+ }
+
+ if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
+ hasSign[0] = true;
+ if (mToken.IsSymbol('-')) {
+ sign[0] = -1;
+ }
+ if (! GetToken(false)) {
+ REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ }
+
+ // A helper function that checks if the token starts with literal string
+ // |aStr| using a case-insensitive match.
+ auto TokenBeginsWith = [this] (const nsLiteralString& aStr) {
+ return StringBeginsWith(mToken.mIdent, aStr,
+ nsASCIICaseInsensitiveStringComparator());
+ };
+
+ if (eCSSToken_Ident == mToken.mType || eCSSToken_Dimension == mToken.mType) {
+ // The CSS tokenization doesn't handle :nth-child() containing - well:
+ // 2n-1 is a dimension
+ // n-1 is an identifier
+ // The easiest way to deal with that is to push everything from the
+ // minus on back onto the scanner's pushback buffer.
+ uint32_t truncAt = 0;
+ if (TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
+ truncAt = 1;
+ } else if (TokenBeginsWith(NS_LITERAL_STRING("-n-")) && !hasSign[0]) {
+ truncAt = 2;
+ }
+ if (truncAt != 0) {
+ mScanner->Backup(mToken.mIdent.Length() - truncAt);
+ mToken.mIdent.Truncate(truncAt);
+ }
+ }
+
+ if (eCSSToken_Ident == mToken.mType) {
+ if (mToken.mIdent.LowerCaseEqualsLiteral("odd") && !hasSign[0]) {
+ numbers[0] = 2;
+ numbers[1] = 1;
+ lookForB = false;
+ }
+ else if (mToken.mIdent.LowerCaseEqualsLiteral("even") && !hasSign[0]) {
+ numbers[0] = 2;
+ numbers[1] = 0;
+ lookForB = false;
+ }
+ else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) {
+ numbers[0] = sign[0];
+ }
+ else if (mToken.mIdent.LowerCaseEqualsLiteral("-n") && !hasSign[0]) {
+ numbers[0] = -1;
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+ }
+ else if (eCSSToken_Number == mToken.mType) {
+ if (!mToken.mIntegerValid) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+ // for +-an case
+ if (mToken.mHasSign && hasSign[0]) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+ int32_t intValue = mToken.mInteger * sign[0];
+ // for -a/**/n case
+ if (! GetToken(false)) {
+ numbers[1] = intValue;
+ lookForB = false;
+ }
+ else {
+ if (eCSSToken_Ident == mToken.mType && mToken.mIdent.LowerCaseEqualsLiteral("n")) {
+ numbers[0] = intValue;
+ }
+ else if (eCSSToken_Ident == mToken.mType && TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
+ numbers[0] = intValue;
+ mScanner->Backup(mToken.mIdent.Length() - 1);
+ }
+ else {
+ UngetToken();
+ numbers[1] = intValue;
+ lookForB = false;
+ }
+ }
+ }
+ else if (eCSSToken_Dimension == mToken.mType) {
+ if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+ // for +-an case
+ if ( mToken.mHasSign && hasSign[0] ) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+ numbers[0] = mToken.mInteger * sign[0];
+ }
+ // XXX If it's a ')', is that valid? (as 0n+0)
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+ UngetToken();
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+
+ if (! GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ if (lookForB && !mToken.IsSymbol(')')) {
+ // The '+' or '-' sign can optionally be separated by whitespace.
+ // If it is separated by whitespace from what follows it, it appears
+ // as a separate token rather than part of the number token.
+ if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
+ hasSign[1] = true;
+ if (mToken.IsSymbol('-')) {
+ sign[1] = -1;
+ }
+ if (! GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ if (eCSSToken_Number != mToken.mType ||
+ !mToken.mIntegerValid || mToken.mHasSign == hasSign[1]) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+ UngetToken();
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+ numbers[1] = mToken.mInteger * sign[1];
+ if (! GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
+ return eSelectorParsingStatus_Error;
+ }
+ }
+ if (!mToken.IsSymbol(')')) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+ aSelector.AddPseudoClass(aType, numbers);
+ return eSelectorParsingStatus_Continue;
+}
+
+//
+// Parse the argument of a pseudo-class that has a selector list argument.
+// Such selector lists cannot contain combinators, but can contain
+// anything that goes between a pair of combinators.
+//
+CSSParserImpl::nsSelectorParsingStatus
+CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
+ CSSPseudoClassType aType)
+{
+ nsAutoPtr<nsCSSSelectorList> slist;
+ if (! ParseSelectorList(*getter_Transfers(slist), char16_t(')'))) {
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+
+ // Check that none of the selectors in the list have combinators or
+ // pseudo-elements.
+ for (nsCSSSelectorList *l = slist; l; l = l->mNext) {
+ nsCSSSelector *s = l->mSelectors;
+ if (s->mNext || s->IsPseudoElement()) {
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+ }
+
+ // Add the pseudo with the selector list parameter
+ aSelector.AddPseudoClass(aType, slist.forget());
+
+ // close the parenthesis
+ if (!ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
+ return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+ }
+
+ return eSelectorParsingStatus_Continue;
+}
+
+
+/**
+ * This is the format for selectors:
+ * operator? [[namespace |]? element_name]? [ ID | class | attrib | pseudo ]*
+ */
+bool
+CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
+ char16_t aPrevCombinator)
+{
+ if (! GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PESelectorEOF);
+ return false;
+ }
+
+ nsCSSSelector* selector = aList->AddSelector(aPrevCombinator);
+ nsCOMPtr<nsIAtom> pseudoElement;
+ nsAutoPtr<nsAtomList> pseudoElementArgs;
+ CSSPseudoElementType pseudoElementType = CSSPseudoElementType::NotPseudo;
+
+ int32_t dataMask = 0;
+ nsSelectorParsingStatus parsingStatus =
+ ParseTypeOrUniversalSelector(dataMask, *selector, false);
+
+ while (parsingStatus == eSelectorParsingStatus_Continue) {
+ if (mToken.IsSymbol(':')) { // :pseudo
+ parsingStatus = ParsePseudoSelector(dataMask, *selector, false,
+ getter_AddRefs(pseudoElement),
+ getter_Transfers(pseudoElementArgs),
+ &pseudoElementType);
+ if (pseudoElement &&
+ pseudoElementType != CSSPseudoElementType::AnonBox) {
+ // Pseudo-elements other than anonymous boxes are represented with
+ // a special ':' combinator.
+
+ aList->mWeight += selector->CalcWeight();
+
+ selector = aList->AddSelector(':');
+
+ selector->mLowercaseTag.swap(pseudoElement);
+ selector->mClassList = pseudoElementArgs.forget();
+ selector->SetPseudoType(pseudoElementType);
+ }
+ } else if (selector->IsPseudoElement()) {
+ // Once we parsed a pseudo-element, we can only parse
+ // pseudo-classes (and only a limited set, which
+ // ParsePseudoSelector knows how to handle).
+ parsingStatus = eSelectorParsingStatus_Done;
+ UngetToken();
+ break;
+ } else if (eCSSToken_ID == mToken.mType) { // #id
+ parsingStatus = ParseIDSelector(dataMask, *selector);
+ } else if (mToken.IsSymbol('.')) { // .class
+ parsingStatus = ParseClassSelector(dataMask, *selector);
+ }
+ else if (mToken.IsSymbol('[')) { // [attribute
+ parsingStatus = ParseAttributeSelector(dataMask, *selector);
+ if (eSelectorParsingStatus_Error == parsingStatus) {
+ SkipUntil(']');
+ }
+ }
+ else { // not a selector token, we're done
+ parsingStatus = eSelectorParsingStatus_Done;
+ UngetToken();
+ break;
+ }
+
+ if (parsingStatus != eSelectorParsingStatus_Continue) {
+ break;
+ }
+
+ if (! GetToken(false)) { // premature eof is ok (here!)
+ parsingStatus = eSelectorParsingStatus_Done;
+ break;
+ }
+ }
+
+ if (parsingStatus == eSelectorParsingStatus_Error) {
+ return false;
+ }
+
+ if (!dataMask) {
+ if (selector->mNext) {
+ REPORT_UNEXPECTED(PESelectorGroupExtraCombinator);
+ } else {
+ REPORT_UNEXPECTED(PESelectorGroupNoSelector);
+ }
+ return false;
+ }
+
+ if (pseudoElementType == CSSPseudoElementType::AnonBox) {
+ // We got an anonymous box pseudo-element; it must be the only
+ // thing in this selector group.
+ if (selector->mNext || !IsUniversalSelector(*selector)) {
+ REPORT_UNEXPECTED(PEAnonBoxNotAlone);
+ return false;
+ }
+
+ // Rewrite the current selector as this pseudo-element.
+ // It does not contribute to selector weight.
+ selector->mLowercaseTag.swap(pseudoElement);
+ selector->mClassList = pseudoElementArgs.forget();
+ selector->SetPseudoType(pseudoElementType);
+ return true;
+ }
+
+ aList->mWeight += selector->CalcWeight();
+
+ return true;
+}
+
+already_AddRefed<css::Declaration>
+CSSParserImpl::ParseDeclarationBlock(uint32_t aFlags, nsCSSContextType aContext)
+{
+ bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
+
+ MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls,
+ "Someone forgot to clear mWebkitBoxUnprefixState!");
+ AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState);
+ mWebkitBoxUnprefixState = eHaveNotUnprefixed;
+
+ if (checkForBraces) {
+ if (!ExpectSymbol('{', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEBadDeclBlockStart);
+ OUTPUT_ERROR();
+ return nullptr;
+ }
+ }
+ RefPtr<css::Declaration> declaration = new css::Declaration();
+ mData.AssertInitialState();
+ for (;;) {
+ bool changed = false;
+ if (!ParseDeclaration(declaration, aFlags, true, &changed, aContext)) {
+ if (!SkipDeclaration(checkForBraces)) {
+ break;
+ }
+ if (checkForBraces) {
+ if (ExpectSymbol('}', true)) {
+ break;
+ }
+ }
+ // Since the skipped declaration didn't end the block we parse
+ // the next declaration.
+ }
+ }
+ declaration->CompressFrom(&mData);
+ return declaration.forget();
+}
+
+CSSParseResult
+CSSParserImpl::ParseColor(nsCSSValue& aValue)
+{
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEColorEOF);
+ return CSSParseResult::NotFound;
+ }
+
+ nsCSSToken* tk = &mToken;
+ nscolor rgba;
+ switch (tk->mType) {
+ case eCSSToken_ID:
+ case eCSSToken_Hash:
+ // #rgb, #rrggbb, #rgba, #rrggbbaa
+ if (NS_HexToRGBA(tk->mIdent, nsHexColorType::AllowAlpha, &rgba)) {
+ nsCSSUnit unit;
+ switch (tk->mIdent.Length()) {
+ case 3:
+ unit = eCSSUnit_ShortHexColor;
+ break;
+ case 4:
+ unit = eCSSUnit_ShortHexColorAlpha;
+ break;
+ case 6:
+ unit = eCSSUnit_HexColor;
+ break;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("unexpected hex color length");
+ case 8:
+ unit = eCSSUnit_HexColorAlpha;
+ break;
+ }
+ aValue.SetIntegerColorValue(rgba, unit);
+ return CSSParseResult::Ok;
+ }
+ break;
+
+ case eCSSToken_Ident:
+ if (NS_ColorNameToRGB(tk->mIdent, &rgba)) {
+ aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
+ return CSSParseResult::Ok;
+ }
+ else {
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(tk->mIdent);
+ if (eCSSKeyword_UNKNOWN < keyword) { // known keyword
+ int32_t value;
+ if (nsCSSProps::FindKeyword(keyword, nsCSSProps::kColorKTable, value)) {
+ aValue.SetIntValue(value, eCSSUnit_EnumColor);
+ return CSSParseResult::Ok;
+ }
+ }
+ }
+ break;
+ case eCSSToken_Function: {
+ bool isRGB;
+ bool isHSL;
+ if ((isRGB = mToken.mIdent.LowerCaseEqualsLiteral("rgb")) ||
+ mToken.mIdent.LowerCaseEqualsLiteral("rgba")) {
+ // rgb() = rgb( <percentage>{3} [ / <alpha-value> ]? ) |
+ // rgb( <number>{3} [ / <alpha-value> ]? ) |
+ // rgb( <percentage>#{3} , <alpha-value>? ) |
+ // rgb( <number>#{3} , <alpha-value>? )
+ // <alpha-value> = <number> | <percentage>
+ // rgba is an alias of rgb.
+
+ if (GetToken(true)) {
+ UngetToken();
+ }
+ if (mToken.mType == eCSSToken_Number) { // <number>
+ uint8_t r, g, b, a;
+
+ if (ParseRGBColor(r, g, b, a)) {
+ aValue.SetIntegerColorValue(NS_RGBA(r, g, b, a),
+ isRGB ? eCSSUnit_RGBColor : eCSSUnit_RGBAColor);
+ return CSSParseResult::Ok;
+ }
+ } else { // <percentage>
+ float r, g, b, a;
+
+ if (ParseRGBColor(r, g, b, a)) {
+ aValue.SetFloatColorValue(r, g, b, a,
+ isRGB ? eCSSUnit_PercentageRGBColor : eCSSUnit_PercentageRGBAColor);
+ return CSSParseResult::Ok;
+ }
+ }
+ SkipUntil(')');
+ return CSSParseResult::Error;
+ }
+ else if ((isHSL = mToken.mIdent.LowerCaseEqualsLiteral("hsl")) ||
+ mToken.mIdent.LowerCaseEqualsLiteral("hsla")) {
+ // hsl() = hsl( <hue> <percentage> <percentage> [ / <alpha-value> ]? ) ||
+ // hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
+ // <hue> = <number> | <angle>
+ // hsla is an alias of hsl.
+
+ float h, s, l, a;
+
+ if (ParseHSLColor(h, s, l, a)) {
+ aValue.SetFloatColorValue(h, s, l, a,
+ isHSL ? eCSSUnit_HSLColor : eCSSUnit_HSLAColor);
+ return CSSParseResult::Ok;
+ }
+ SkipUntil(')');
+ return CSSParseResult::Error;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ // try 'xxyyzz' without '#' prefix for compatibility with IE and Nav4x (bug 23236 and 45804)
+ if (mHashlessColorQuirk) {
+ // - If the string starts with 'a-f', the nsCSSScanner builds the
+ // token as a eCSSToken_Ident and we can parse the string as a
+ // 'xxyyzz' RGB color.
+ // - If it only contains '0-9' digits, the token is a
+ // eCSSToken_Number and it must be converted back to a 6
+ // characters string to be parsed as a RGB color.
+ // - If it starts with '0-9' and contains any 'a-f', the token is a
+ // eCSSToken_Dimension, the mNumber part must be converted back to
+ // a string and the mIdent part must be appended to that string so
+ // that the resulting string has 6 characters.
+ // Note: This is a hack for Nav compatibility. Do not attempt to
+ // simplify it by hacking into the ncCSSScanner. This would be very
+ // bad.
+ nsAutoString str;
+ char buffer[20];
+ switch (tk->mType) {
+ case eCSSToken_Ident:
+ str.Assign(tk->mIdent);
+ break;
+
+ case eCSSToken_Number:
+ if (tk->mIntegerValid) {
+ SprintfLiteral(buffer, "%06d", tk->mInteger);
+ str.AssignWithConversion(buffer);
+ }
+ break;
+
+ case eCSSToken_Dimension:
+ if (tk->mIdent.Length() <= 6) {
+ SprintfLiteral(buffer, "%06.0f", tk->mNumber);
+ nsAutoString temp;
+ temp.AssignWithConversion(buffer);
+ temp.Right(str, 6 - tk->mIdent.Length());
+ str.Append(tk->mIdent);
+ }
+ break;
+ default:
+ // There is a whole bunch of cases that are
+ // not handled by this switch. Ignore them.
+ break;
+ }
+ // The hashless color quirk does not support 4 & 8 digit colors with alpha.
+ if (NS_HexToRGBA(str, nsHexColorType::NoAlpha, &rgba)) {
+ aValue.SetIntegerColorValue(rgba, eCSSUnit_HexColor);
+ return CSSParseResult::Ok;
+ }
+ }
+
+ // It's not a color
+ REPORT_UNEXPECTED_TOKEN(PEColorNotColor);
+ UngetToken();
+ return CSSParseResult::NotFound;
+}
+
+bool
+CSSParserImpl::ParseColorComponent(uint8_t& aComponent, Maybe<char> aSeparator)
+{
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEColorComponentEOF);
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Number) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedNumber);
+ UngetToken();
+ return false;
+ }
+
+ float value = mToken.mNumber;
+
+ if (aSeparator && !ExpectSymbol(*aSeparator, true)) {
+ REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, *aSeparator);
+ return false;
+ }
+
+ if (value < 0.0f) value = 0.0f;
+ if (value > 255.0f) value = 255.0f;
+
+ aComponent = NSToIntRound(value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseColorComponent(float& aComponent, Maybe<char> aSeparator)
+{
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEColorComponentEOF);
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Percentage) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedPercent);
+ UngetToken();
+ return false;
+ }
+
+ float value = mToken.mNumber;
+
+ if (aSeparator && !ExpectSymbol(*aSeparator, true)) {
+ REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, *aSeparator);
+ return false;
+ }
+
+ if (value < 0.0f) value = 0.0f;
+ if (value > 1.0f) value = 1.0f;
+
+ aComponent = value;
+ return true;
+}
+
+bool
+CSSParserImpl::ParseHue(float& aAngle)
+{
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEColorHueEOF);
+ return false;
+ }
+
+ // <number>
+ if (mToken.mType == eCSSToken_Number) {
+ aAngle = mToken.mNumber;
+ return true;
+ }
+ UngetToken();
+
+ // <angle>
+ nsCSSValue angleValue;
+ // The '0' value is handled by <number> parsing, so use VARIANT_ANGLE flag
+ // instead of VARIANT_ANGLE_OR_ZERO.
+ if (ParseSingleTokenVariant(angleValue, VARIANT_ANGLE, nullptr)) {
+ aAngle = angleValue.GetAngleValueInDegrees();
+ return true;
+ }
+
+ REPORT_UNEXPECTED_TOKEN(PEExpectedNumberOrAngle);
+ return false;
+}
+
+bool
+CSSParserImpl::ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
+ float& aOpacity)
+{
+ // comma-less expression:
+ // hsl() = hsl( <hue> <saturation> <lightness> [ / <alpha-value> ]? )
+ // the expression with comma:
+ // hsl() = hsl( <hue>, <saturation>, <lightness>, <alpha-value>? )
+
+ const char commaSeparator = ',';
+
+ // Parse hue.
+ // <hue> = <number> | <angle>
+ float degreeAngle;
+ if (!ParseHue(degreeAngle)) {
+ return false;
+ }
+ aHue = degreeAngle / 360.0f;
+ // hue values are wraparound
+ aHue = aHue - floor(aHue);
+ // Look for a comma separator after "hue" component to determine if the
+ // expression is comma-less or not.
+ bool hasComma = ExpectSymbol(commaSeparator, true);
+
+ // Parse saturation, lightness and opacity.
+ // The saturation and lightness are <percentage>, so reuse the float version
+ // of ParseColorComponent function for them. No need to check the separator
+ // after 'lightness'. It will be checked in opacity value parsing.
+ const char separatorBeforeAlpha = hasComma ? commaSeparator : '/';
+ if (ParseColorComponent(aSaturation, hasComma ? Some(commaSeparator) : Nothing()) &&
+ ParseColorComponent(aLightness, Nothing()) &&
+ ParseColorOpacityAndCloseParen(aOpacity, separatorBeforeAlpha)) {
+ return true;
+ }
+
+ return false;
+}
+
+
+bool
+CSSParserImpl::ParseColorOpacityAndCloseParen(uint8_t& aOpacity,
+ char aSeparator)
+{
+ float floatOpacity;
+ if (!ParseColorOpacityAndCloseParen(floatOpacity, aSeparator)) {
+ return false;
+ }
+
+ uint8_t value = nsStyleUtil::FloatToColorComponent(floatOpacity);
+ // Need to compare to something slightly larger
+ // than 0.5 due to floating point inaccuracies.
+ NS_ASSERTION(fabs(255.0f * floatOpacity - value) <= 0.51f,
+ "FloatToColorComponent did something weird");
+
+ aOpacity = value;
+ return true;
+}
+
+bool
+CSSParserImpl::ParseColorOpacityAndCloseParen(float& aOpacity,
+ char aSeparator)
+{
+ if (ExpectSymbol(')', true)) {
+ // The optional [separator <alpha-value>] was omitted, so set the opacity
+ // to a fully-opaque value '1.0f' and return success.
+ aOpacity = 1.0f;
+ return true;
+ }
+
+ if (!ExpectSymbol(aSeparator, true)) {
+ REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aSeparator);
+ return false;
+ }
+
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEColorOpacityEOF);
+ return false;
+ }
+
+ // eCSSToken_Number or eCSSToken_Percentage.
+ if (mToken.mType != eCSSToken_Number && mToken.mType != eCSSToken_Percentage) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedNumberOrPercent);
+ UngetToken();
+ return false;
+ }
+
+ if (!ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
+ return false;
+ }
+
+ if (mToken.mNumber < 0.0f) {
+ mToken.mNumber = 0.0f;
+ } else if (mToken.mNumber > 1.0f) {
+ mToken.mNumber = 1.0f;
+ }
+
+ aOpacity = mToken.mNumber;
+ return true;
+}
+
+template<typename ComponentType>
+bool
+CSSParserImpl::ParseRGBColor(ComponentType& aR,
+ ComponentType& aG,
+ ComponentType& aB,
+ ComponentType& aA)
+{
+ // comma-less expression:
+ // rgb() = rgb( component{3} [ / <alpha-value> ]? )
+ // the expression with comma:
+ // rgb() = rgb( component#{3} , <alpha-value>? )
+
+ const char commaSeparator = ',';
+
+ // Parse R.
+ if (!ParseColorComponent(aR, Nothing())) {
+ return false;
+ }
+ // Look for a comma separator after "r" component to determine if the
+ // expression is comma-less or not.
+ bool hasComma = ExpectSymbol(commaSeparator, true);
+
+ // Parse G, B and A.
+ // No need to check the separator after 'B'. It will be checked in 'A' value
+ // parsing.
+ const char separatorBeforeAlpha = hasComma ? commaSeparator : '/';
+ if (ParseColorComponent(aG, hasComma ? Some(commaSeparator) : Nothing()) &&
+ ParseColorComponent(aB, Nothing()) &&
+ ParseColorOpacityAndCloseParen(aA, separatorBeforeAlpha)) {
+ return true;
+ }
+
+ return false;
+}
+
+#ifdef MOZ_XUL
+bool
+CSSParserImpl::ParseTreePseudoElement(nsAtomList **aPseudoElementArgs)
+{
+ // The argument to a tree pseudo-element is a sequence of identifiers
+ // that are either space- or comma-separated. (Was the intent to
+ // allow only comma-separated? That's not what was done.)
+ nsCSSSelector fakeSelector; // so we can reuse AddPseudoClass
+
+ while (!ExpectSymbol(')', true)) {
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (eCSSToken_Ident == mToken.mType) {
+ fakeSelector.AddClass(mToken.mIdent);
+ }
+ else if (!mToken.IsSymbol(',')) {
+ UngetToken();
+ SkipUntil(')');
+ return false;
+ }
+ }
+ *aPseudoElementArgs = fakeSelector.mClassList;
+ fakeSelector.mClassList = nullptr;
+ return true;
+}
+#endif
+
+nsCSSKeyword
+CSSParserImpl::LookupKeywordPrefixAware(nsAString& aKeywordStr,
+ const KTableEntry aKeywordTable[])
+{
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aKeywordStr);
+
+ if (aKeywordTable == nsCSSProps::kDisplayKTable) {
+ // NOTE: This code will be considerably simpler once we can do away with
+ // all Unprefixing Service code, in bug 1259348. But for the time being, we
+ // have to support two different strategies for handling -webkit-box here:
+ // (1) "Native support" (sWebkitPrefixedAliasesEnabled): we assume that
+ // -webkit-box will parse correctly (via an entry in kDisplayKTable),
+ // and we simply make a note that we've parsed it (so that we can we
+ // can give later "-moz-box" styling special handling as noted below).
+ // (2) "Unprefixing Service support" (ShouldUseUnprefixingService): we
+ // convert "-webkit-box" directly to modern "flex" (& do the same for
+ // any later "-moz-box" styling).
+ //
+ // Note that sWebkitPrefixedAliasesEnabled and
+ // ShouldUseUnprefixingService() are mutually exlusive, because the latter
+ // explicitly defers to the former.
+ if ((keyword == eCSSKeyword__webkit_box ||
+ keyword == eCSSKeyword__webkit_inline_box)) {
+ const bool usingUnprefixingService = ShouldUseUnprefixingService();
+ if (sWebkitPrefixedAliasesEnabled || usingUnprefixingService) {
+ // Make a note that we're accepting some "-webkit-{inline-}box" styling,
+ // so we can give special treatment to subsequent "-moz-{inline}-box".
+ // (See special treatment below.)
+ if (mWebkitBoxUnprefixState == eHaveNotUnprefixed) {
+ mWebkitBoxUnprefixState = eHaveUnprefixed;
+ }
+ if (usingUnprefixingService) {
+ // When we're using the unprefixing service, we treat
+ // "display:-webkit-box" as if it were "display:flex"
+ // (and "-webkit-inline-box" as "inline-flex").
+ return (keyword == eCSSKeyword__webkit_box) ?
+ eCSSKeyword_flex : eCSSKeyword_inline_flex;
+ }
+ }
+ }
+
+ // If we've seen "display: -webkit-box" (or "-webkit-inline-box") in an
+ // earlier declaration and we honored it, then we have to watch out for
+ // later "display: -moz-box" (and "-moz-inline-box") declarations; they're
+ // likely just a halfhearted attempt at compatibility, and they actually
+ // end up stomping on our emulation of the earlier -webkit-box
+ // display-value, via the CSS cascade. To prevent this problem, we treat
+ // "display: -moz-box" & "-moz-inline-box" as if they were simply a
+ // repetition of the webkit equivalent that we already parsed.
+ if (mWebkitBoxUnprefixState == eHaveUnprefixed &&
+ (keyword == eCSSKeyword__moz_box ||
+ keyword == eCSSKeyword__moz_inline_box)) {
+ MOZ_ASSERT(sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService(),
+ "mDidUnprefixWebkitBoxInEarlierDecl should only be set if "
+ "we're supporting webkit-prefixed aliases, or if we're using "
+ "the css unprefixing service on this site");
+ if (sWebkitPrefixedAliasesEnabled) {
+ return (keyword == eCSSKeyword__moz_box) ?
+ eCSSKeyword__webkit_box : eCSSKeyword__webkit_inline_box;
+ }
+ // (If we get here, we're using the Unprefixing Service, which means
+ // we're unprefixing all the way to modern flexbox display values.)
+ return (keyword == eCSSKeyword__moz_box) ?
+ eCSSKeyword_flex : eCSSKeyword_inline_flex;
+ }
+ }
+
+ return keyword;
+}
+
+bool
+CSSParserImpl::ShouldUseUnprefixingService() const
+{
+ if (!sUnprefixingServiceEnabled) {
+ // Unprefixing is globally disabled.
+ return false;
+ }
+ if (sWebkitPrefixedAliasesEnabled) {
+ // Native webkit-prefix support is enabled, which trumps the unprefixing
+ // service for handling prefixed CSS. Don't try to use both at once.
+ return false;
+ }
+
+#ifdef NIGHTLY_BUILD
+ if (sUnprefixingServiceGloballyWhitelisted) {
+ // Unprefixing is globally whitelisted,
+ // so no need to check mSheetPrincipal.
+ return true;
+ }
+#endif
+ // Unprefixing enabled; see if our principal is whitelisted for unprefixing.
+ return mSheetPrincipal && mSheetPrincipal->IsOnCSSUnprefixingWhitelist();
+}
+
+bool
+CSSParserImpl::ParsePropertyWithUnprefixingService(
+ const nsAString& aPropertyName,
+ css::Declaration* aDeclaration,
+ uint32_t aFlags,
+ bool aMustCallValueAppended,
+ bool* aChanged,
+ nsCSSContextType aContext)
+{
+ MOZ_ASSERT(ShouldUseUnprefixingService(),
+ "Caller should've checked ShouldUseUnprefixingService()");
+
+ nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc =
+ do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(unprefixingSvc, false);
+
+ // Save the state so we can jump back to this spot if our unprefixing fails
+ // (so we can behave as if we didn't even try to unprefix).
+ nsAutoCSSParserInputStateRestorer parserStateBeforeTryingToUnprefix(this);
+
+ // Caller has already parsed the first half of the declaration --
+ // aPropertyName and the ":". Now, we record the rest of the CSS declaration
+ // (the part after ':') into rightHalfOfDecl. (This is the property value,
+ // plus anything else up to the end of the declaration -- maybe "!important",
+ // maybe trailing junk characters, maybe a semicolon, maybe a trailing "}".)
+ bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
+ nsAutoString rightHalfOfDecl;
+ mScanner->StartRecording();
+ SkipDeclaration(checkForBraces);
+ mScanner->StopRecording(rightHalfOfDecl);
+
+ // Try to unprefix:
+ bool success;
+ nsAutoString unprefixedDecl;
+ nsresult rv =
+ unprefixingSvc->GenerateUnprefixedDeclaration(aPropertyName,
+ rightHalfOfDecl,
+ unprefixedDecl, &success);
+ if (NS_FAILED(rv) || !success) {
+ return false;
+ }
+
+ // Attempt to parse the unprefixed declaration:
+ nsAutoScannerChanger scannerChanger(this, unprefixedDecl);
+ success = ParseDeclaration(aDeclaration,
+ aFlags | eParseDeclaration_FromUnprefixingSvc,
+ aMustCallValueAppended, aChanged, aContext);
+ if (success) {
+ // We succeeded, so we'll leave the parser pointing at the end of
+ // the declaration; don't restore it to the pre-recording position.
+ parserStateBeforeTryingToUnprefix.DoNotRestore();
+ }
+
+ return success;
+}
+
+bool
+CSSParserImpl::ParseWebkitPrefixedGradientWithService(
+ nsAString& aPrefixedFuncName,
+ nsCSSValue& aValue)
+{
+ MOZ_ASSERT(ShouldUseUnprefixingService(),
+ "Should only call if we're allowed to use unprefixing service");
+
+ // Record the body of the "-webkit-*gradient" function into a string.
+ // Note: we're already just after the opening "(".
+ nsAutoString prefixedFuncBody;
+ mScanner->StartRecording();
+ bool gotCloseParen = SkipUntil(')');
+ mScanner->StopRecording(prefixedFuncBody);
+ if (gotCloseParen) {
+ // Strip off trailing close-paren, so that the value we pass to the
+ // unprefixing service is *just* the function-body (no parens).
+ prefixedFuncBody.Truncate(prefixedFuncBody.Length() - 1);
+ }
+
+ // NOTE: Even if we fail, we'll be leaving the parser's cursor just after
+ // the close of the "-webkit-*gradient(...)" expression. This is the same
+ // behavior that the other Parse*Gradient functions have in their failure
+ // cases -- they call "SkipUntil(')') before returning false. So this is
+ // probably what we want.
+ nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc =
+ do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(unprefixingSvc, false);
+
+ bool success;
+ nsAutoString unprefixedFuncName;
+ nsAutoString unprefixedFuncBody;
+ nsresult rv =
+ unprefixingSvc->GenerateUnprefixedGradientValue(aPrefixedFuncName,
+ prefixedFuncBody,
+ unprefixedFuncName,
+ unprefixedFuncBody,
+ &success);
+
+ if (NS_FAILED(rv) || !success) {
+ return false;
+ }
+
+ // JS service thinks it successfully converted the gradient! Now let's try
+ // to parse the resulting string.
+
+ // First, add a close-paren if we originally recorded one (so that what we're
+ // about to put into the CSS parser is a faithful representation of what it
+ // would've seen if it were just parsing the original input stream):
+ if (gotCloseParen) {
+ unprefixedFuncBody.Append(char16_t(')'));
+ }
+
+ nsAutoScannerChanger scannerChanger(this, unprefixedFuncBody);
+ if (unprefixedFuncName.EqualsLiteral("linear-gradient")) {
+ return ParseLinearGradient(aValue, 0);
+ }
+ if (unprefixedFuncName.EqualsLiteral("radial-gradient")) {
+ return ParseRadialGradient(aValue, 0);
+ }
+
+ NS_ERROR("CSSUnprefixingService returned an unrecognized type of "
+ "gradient function");
+
+ return false;
+}
+
+//----------------------------------------------------------------------
+
+bool
+CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration,
+ uint32_t aFlags,
+ bool aMustCallValueAppended,
+ bool* aChanged,
+ nsCSSContextType aContext)
+{
+ NS_PRECONDITION(aContext == eCSSContext_General ||
+ aContext == eCSSContext_Page,
+ "Must be page or general context");
+
+ bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
+
+ mTempData.AssertInitialState();
+
+ // Get property name
+ nsCSSToken* tk = &mToken;
+ nsAutoString propertyName;
+ for (;;) {
+ if (!GetToken(true)) {
+ if (checkForBraces) {
+ REPORT_UNEXPECTED_EOF(PEDeclEndEOF);
+ }
+ return false;
+ }
+ if (eCSSToken_Ident == tk->mType) {
+ propertyName = tk->mIdent;
+ // grab the ident before the ExpectSymbol trashes the token
+ if (!ExpectSymbol(':', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
+ REPORT_UNEXPECTED(PEDeclDropped);
+ OUTPUT_ERROR();
+ return false;
+ }
+ break;
+ }
+ if (tk->IsSymbol(';')) {
+ // dangling semicolons are skipped
+ continue;
+ }
+
+ if (!tk->IsSymbol('}')) {
+ REPORT_UNEXPECTED_TOKEN(PEParseDeclarationDeclExpected);
+ REPORT_UNEXPECTED(PEDeclSkipped);
+ OUTPUT_ERROR();
+
+ if (eCSSToken_AtKeyword == tk->mType) {
+ SkipAtRule(checkForBraces);
+ return true; // Not a declaration, but don't skip until ';'
+ }
+ }
+ // Not a declaration...
+ UngetToken();
+ return false;
+ }
+
+ // Don't report property parse errors if we're inside a failing @supports
+ // rule.
+ nsAutoSuppressErrors suppressErrors(this, mInFailingSupportsRule);
+
+ // Information about a parsed non-custom property.
+ nsCSSPropertyID propID;
+
+ // Information about a parsed custom property.
+ CSSVariableDeclarations::Type variableType;
+ nsString variableValue;
+
+ // Check if the property name is a custom property.
+ bool customProperty = nsLayoutUtils::CSSVariablesEnabled() &&
+ nsCSSProps::IsCustomPropertyName(propertyName) &&
+ aContext == eCSSContext_General;
+
+ if (customProperty) {
+ if (!ParseVariableDeclaration(&variableType, variableValue)) {
+ REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
+ REPORT_UNEXPECTED(PEDeclDropped);
+ OUTPUT_ERROR();
+ return false;
+ }
+ } else {
+ // Map property name to its ID.
+ propID = LookupEnabledProperty(propertyName);
+ if (eCSSProperty_UNKNOWN == propID ||
+ eCSSPropertyExtra_variable == propID ||
+ (aContext == eCSSContext_Page &&
+ !nsCSSProps::PropHasFlags(propID,
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
+ if (NonMozillaVendorIdentifier(propertyName)) {
+ if (!mInSupportsCondition &&
+ aContext == eCSSContext_General &&
+ !(aFlags & eParseDeclaration_FromUnprefixingSvc) && // no recursion
+ ShouldUseUnprefixingService()) {
+ if (ParsePropertyWithUnprefixingService(propertyName,
+ aDeclaration, aFlags,
+ aMustCallValueAppended,
+ aChanged, aContext)) {
+ return true;
+ }
+ }
+ } else {
+ REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName);
+ REPORT_UNEXPECTED(PEDeclDropped);
+ OUTPUT_ERROR();
+ }
+ return false;
+ }
+ // Then parse the property.
+ if (!ParseProperty(propID)) {
+ // XXX Much better to put stuff in the value parsers instead...
+ REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
+ REPORT_UNEXPECTED(PEDeclDropped);
+ OUTPUT_ERROR();
+ mTempData.ClearProperty(propID);
+ mTempData.AssertInitialState();
+ return false;
+ }
+ }
+
+ CLEAR_ERROR();
+
+ // Look for "!important".
+ PriorityParsingStatus status;
+ if ((aFlags & eParseDeclaration_AllowImportant) != 0) {
+ status = ParsePriority();
+ } else {
+ status = ePriority_None;
+ }
+
+ // Look for a semicolon or close brace.
+ if (status != ePriority_Error) {
+ if (!GetToken(true)) {
+ // EOF is always ok
+ } else if (mToken.IsSymbol(';')) {
+ // semicolon is always ok
+ } else if (mToken.IsSymbol('}')) {
+ // brace is ok if checkForBraces, but don't eat it
+ UngetToken();
+ if (!checkForBraces) {
+ status = ePriority_Error;
+ }
+ } else {
+ UngetToken();
+ status = ePriority_Error;
+ }
+ }
+
+ if (status == ePriority_Error) {
+ if (checkForBraces) {
+ REPORT_UNEXPECTED_TOKEN(PEBadDeclOrRuleEnd2);
+ } else {
+ REPORT_UNEXPECTED_TOKEN(PEBadDeclEnd);
+ }
+ REPORT_UNEXPECTED(PEDeclDropped);
+ OUTPUT_ERROR();
+ if (!customProperty) {
+ mTempData.ClearProperty(propID);
+ }
+ mTempData.AssertInitialState();
+ return false;
+ }
+
+ if (customProperty) {
+ MOZ_ASSERT(Substring(propertyName, 0,
+ CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--"));
+ // remove '--'
+ nsDependentString varName(propertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
+ aDeclaration->AddVariable(varName, variableType, variableValue,
+ status == ePriority_Important, false);
+ } else {
+ *aChanged |= mData.TransferFromBlock(mTempData, propID, EnabledState(),
+ status == ePriority_Important,
+ false, aMustCallValueAppended,
+ aDeclaration, GetDocument());
+ }
+
+ return true;
+}
+
+static const nsCSSPropertyID kBorderTopIDs[] = {
+ eCSSProperty_border_top_width,
+ eCSSProperty_border_top_style,
+ eCSSProperty_border_top_color
+};
+static const nsCSSPropertyID kBorderRightIDs[] = {
+ eCSSProperty_border_right_width,
+ eCSSProperty_border_right_style,
+ eCSSProperty_border_right_color
+};
+static const nsCSSPropertyID kBorderBottomIDs[] = {
+ eCSSProperty_border_bottom_width,
+ eCSSProperty_border_bottom_style,
+ eCSSProperty_border_bottom_color
+};
+static const nsCSSPropertyID kBorderLeftIDs[] = {
+ eCSSProperty_border_left_width,
+ eCSSProperty_border_left_style,
+ eCSSProperty_border_left_color
+};
+static const nsCSSPropertyID kBorderInlineStartIDs[] = {
+ eCSSProperty_border_inline_start_width,
+ eCSSProperty_border_inline_start_style,
+ eCSSProperty_border_inline_start_color
+};
+static const nsCSSPropertyID kBorderInlineEndIDs[] = {
+ eCSSProperty_border_inline_end_width,
+ eCSSProperty_border_inline_end_style,
+ eCSSProperty_border_inline_end_color
+};
+static const nsCSSPropertyID kBorderBlockStartIDs[] = {
+ eCSSProperty_border_block_start_width,
+ eCSSProperty_border_block_start_style,
+ eCSSProperty_border_block_start_color
+};
+static const nsCSSPropertyID kBorderBlockEndIDs[] = {
+ eCSSProperty_border_block_end_width,
+ eCSSProperty_border_block_end_style,
+ eCSSProperty_border_block_end_color
+};
+static const nsCSSPropertyID kColumnRuleIDs[] = {
+ eCSSProperty_column_rule_width,
+ eCSSProperty_column_rule_style,
+ eCSSProperty_column_rule_color
+};
+
+bool
+CSSParserImpl::ParseEnum(nsCSSValue& aValue,
+ const KTableEntry aKeywordTable[])
+{
+ nsSubstring* ident = NextIdent();
+ if (nullptr == ident) {
+ return false;
+ }
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident);
+ if (eCSSKeyword_UNKNOWN < keyword) {
+ int32_t value;
+ if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
+ aValue.SetIntValue(value, eCSSUnit_Enumerated);
+ return true;
+ }
+ }
+
+ // Put the unknown identifier back and return
+ UngetToken();
+ return false;
+}
+
+bool
+CSSParserImpl::ParseAlignEnum(nsCSSValue& aValue,
+ const KTableEntry aKeywordTable[])
+{
+ MOZ_ASSERT(nsCSSProps::ValueToKeywordEnum(NS_STYLE_ALIGN_BASELINE,
+ aKeywordTable) !=
+ eCSSKeyword_UNKNOWN,
+ "Please use ParseEnum instead");
+ nsSubstring* ident = NextIdent();
+ if (!ident) {
+ return false;
+ }
+ nsCSSKeyword baselinePrefix = eCSSKeyword_first;
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident);
+ if (keyword == eCSSKeyword_first || keyword == eCSSKeyword_last) {
+ baselinePrefix = keyword;
+ ident = NextIdent();
+ if (!ident) {
+ return false;
+ }
+ keyword = nsCSSKeywords::LookupKeyword(*ident);
+ }
+ if (eCSSKeyword_UNKNOWN < keyword) {
+ int32_t value;
+ if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
+ if (baselinePrefix == eCSSKeyword_last &&
+ keyword == eCSSKeyword_baseline) {
+ value = NS_STYLE_ALIGN_LAST_BASELINE;
+ }
+ aValue.SetIntValue(value, eCSSUnit_Enumerated);
+ return true;
+ }
+ }
+
+ // Put the unknown identifier back and return
+ UngetToken();
+ return false;
+}
+
+struct UnitInfo {
+ char name[6]; // needs to be long enough for the longest unit, with
+ // terminating null.
+ uint32_t length;
+ nsCSSUnit unit;
+ int32_t type;
+};
+
+#define STR_WITH_LEN(_str) \
+ _str, sizeof(_str) - 1
+
+const UnitInfo UnitData[] = {
+ { STR_WITH_LEN("px"), eCSSUnit_Pixel, VARIANT_LENGTH },
+ { STR_WITH_LEN("em"), eCSSUnit_EM, VARIANT_LENGTH },
+ { STR_WITH_LEN("ex"), eCSSUnit_XHeight, VARIANT_LENGTH },
+ { STR_WITH_LEN("pt"), eCSSUnit_Point, VARIANT_LENGTH },
+ { STR_WITH_LEN("in"), eCSSUnit_Inch, VARIANT_LENGTH },
+ { STR_WITH_LEN("cm"), eCSSUnit_Centimeter, VARIANT_LENGTH },
+ { STR_WITH_LEN("ch"), eCSSUnit_Char, VARIANT_LENGTH },
+ { STR_WITH_LEN("rem"), eCSSUnit_RootEM, VARIANT_LENGTH },
+ { STR_WITH_LEN("mm"), eCSSUnit_Millimeter, VARIANT_LENGTH },
+ { STR_WITH_LEN("mozmm"), eCSSUnit_PhysicalMillimeter, VARIANT_LENGTH },
+ { STR_WITH_LEN("vw"), eCSSUnit_ViewportWidth, VARIANT_LENGTH },
+ { STR_WITH_LEN("vh"), eCSSUnit_ViewportHeight, VARIANT_LENGTH },
+ { STR_WITH_LEN("vmin"), eCSSUnit_ViewportMin, VARIANT_LENGTH },
+ { STR_WITH_LEN("vmax"), eCSSUnit_ViewportMax, VARIANT_LENGTH },
+ { STR_WITH_LEN("pc"), eCSSUnit_Pica, VARIANT_LENGTH },
+ { STR_WITH_LEN("q"), eCSSUnit_Quarter, VARIANT_LENGTH },
+ { STR_WITH_LEN("deg"), eCSSUnit_Degree, VARIANT_ANGLE },
+ { STR_WITH_LEN("grad"), eCSSUnit_Grad, VARIANT_ANGLE },
+ { STR_WITH_LEN("rad"), eCSSUnit_Radian, VARIANT_ANGLE },
+ { STR_WITH_LEN("turn"), eCSSUnit_Turn, VARIANT_ANGLE },
+ { STR_WITH_LEN("hz"), eCSSUnit_Hertz, VARIANT_FREQUENCY },
+ { STR_WITH_LEN("khz"), eCSSUnit_Kilohertz, VARIANT_FREQUENCY },
+ { STR_WITH_LEN("s"), eCSSUnit_Seconds, VARIANT_TIME },
+ { STR_WITH_LEN("ms"), eCSSUnit_Milliseconds, VARIANT_TIME }
+};
+
+#undef STR_WITH_LEN
+
+bool
+CSSParserImpl::TranslateDimension(nsCSSValue& aValue,
+ uint32_t aVariantMask,
+ float aNumber,
+ const nsString& aUnit)
+{
+ nsCSSUnit units;
+ int32_t type = 0;
+ if (!aUnit.IsEmpty()) {
+ uint32_t i;
+ for (i = 0; i < ArrayLength(UnitData); ++i) {
+ if (aUnit.LowerCaseEqualsASCII(UnitData[i].name,
+ UnitData[i].length)) {
+ units = UnitData[i].unit;
+ type = UnitData[i].type;
+ break;
+ }
+ }
+
+ if (i == ArrayLength(UnitData)) {
+ // Unknown unit
+ return false;
+ }
+
+ if (!mViewportUnitsEnabled &&
+ (eCSSUnit_ViewportWidth == units ||
+ eCSSUnit_ViewportHeight == units ||
+ eCSSUnit_ViewportMin == units ||
+ eCSSUnit_ViewportMax == units)) {
+ // Viewport units aren't allowed right now, probably because we're
+ // inside an @page declaration. Fail.
+ return false;
+ }
+
+ if ((VARIANT_ABSOLUTE_DIMENSION & aVariantMask) != 0 &&
+ !nsCSSValue::IsPixelLengthUnit(units)) {
+ return false;
+ }
+ } else {
+ // Must be a zero number...
+ NS_ASSERTION(0 == aNumber, "numbers without units must be 0");
+ if ((VARIANT_LENGTH & aVariantMask) != 0) {
+ units = eCSSUnit_Pixel;
+ type = VARIANT_LENGTH;
+ }
+ else if ((VARIANT_ANGLE & aVariantMask) != 0) {
+ NS_ASSERTION(aVariantMask & VARIANT_ZERO_ANGLE,
+ "must have allowed zero angle");
+ units = eCSSUnit_Degree;
+ type = VARIANT_ANGLE;
+ }
+ else {
+ NS_ERROR("Variant mask does not include dimension; why were we called?");
+ return false;
+ }
+ }
+ if ((type & aVariantMask) != 0) {
+ aValue.SetFloatValue(aNumber, units);
+ return true;
+ }
+ return false;
+}
+
+// Note that this does include VARIANT_CALC, which is numeric. This is
+// because calc() parsing, as proposed, drops range restrictions inside
+// the calc() expression and clamps the result of the calculation to the
+// range.
+#define VARIANT_ALL_NONNUMERIC \
+ VARIANT_KEYWORD | \
+ VARIANT_COLOR | \
+ VARIANT_URL | \
+ VARIANT_STRING | \
+ VARIANT_COUNTER | \
+ VARIANT_ATTR | \
+ VARIANT_IDENTIFIER | \
+ VARIANT_IDENTIFIER_NO_INHERIT | \
+ VARIANT_AUTO | \
+ VARIANT_INHERIT | \
+ VARIANT_NONE | \
+ VARIANT_NORMAL | \
+ VARIANT_SYSFONT | \
+ VARIANT_GRADIENT | \
+ VARIANT_TIMING_FUNCTION | \
+ VARIANT_ALL | \
+ VARIANT_CALC | \
+ VARIANT_OPENTYPE_SVG_KEYWORD
+
+CSSParseResult
+CSSParserImpl::ParseVariantWithRestrictions(nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[],
+ uint32_t aRestrictions)
+{
+ switch (aRestrictions) {
+ default:
+ MOZ_FALLTHROUGH_ASSERT("should not be reached");
+ case 0:
+ return ParseVariant(aValue, aVariantMask, aKeywordTable);
+ case CSS_PROPERTY_VALUE_NONNEGATIVE:
+ return ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable);
+ case CSS_PROPERTY_VALUE_AT_LEAST_ONE:
+ return ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable);
+ }
+}
+
+// Note that callers passing VARIANT_CALC in aVariantMask will get
+// full-range parsing inside the calc() expression, and the code that
+// computes the calc will be required to clamp the resulting value to an
+// appropriate range.
+CSSParseResult
+CSSParserImpl::ParseNonNegativeVariant(nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[])
+{
+ // The variant mask must only contain non-numeric variants or the ones
+ // that we specifically handle.
+ MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC |
+ VARIANT_NUMBER |
+ VARIANT_LENGTH |
+ VARIANT_PERCENT |
+ VARIANT_INTEGER)) == 0,
+ "need to update code below to handle additional variants");
+
+ CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
+ if (result == CSSParseResult::Ok) {
+ if (eCSSUnit_Number == aValue.GetUnit() ||
+ aValue.IsLengthUnit()){
+ if (aValue.GetFloatValue() < 0) {
+ UngetToken();
+ return CSSParseResult::NotFound;
+ }
+ }
+ else if (aValue.GetUnit() == eCSSUnit_Percent) {
+ if (aValue.GetPercentValue() < 0) {
+ UngetToken();
+ return CSSParseResult::NotFound;
+ }
+ } else if (aValue.GetUnit() == eCSSUnit_Integer) {
+ if (aValue.GetIntValue() < 0) {
+ UngetToken();
+ return CSSParseResult::NotFound;
+ }
+ }
+ }
+ return result;
+}
+
+// Note that callers passing VARIANT_CALC in aVariantMask will get
+// full-range parsing inside the calc() expression, and the code that
+// computes the calc will be required to clamp the resulting value to an
+// appropriate range.
+CSSParseResult
+CSSParserImpl::ParseOneOrLargerVariant(nsCSSValue& aValue,
+ int32_t aVariantMask,
+ const KTableEntry aKeywordTable[])
+{
+ // The variant mask must only contain non-numeric variants or the ones
+ // that we specifically handle.
+ MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC |
+ VARIANT_NUMBER |
+ VARIANT_INTEGER)) == 0,
+ "need to update code below to handle additional variants");
+
+ CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
+ if (result == CSSParseResult::Ok) {
+ if (aValue.GetUnit() == eCSSUnit_Integer) {
+ if (aValue.GetIntValue() < 1) {
+ UngetToken();
+ return CSSParseResult::NotFound;
+ }
+ } else if (eCSSUnit_Number == aValue.GetUnit()) {
+ if (aValue.GetFloatValue() < 1.0f) {
+ UngetToken();
+ return CSSParseResult::NotFound;
+ }
+ }
+ }
+ return result;
+}
+
+static bool
+IsCSSTokenCalcFunction(const nsCSSToken& aToken)
+{
+ return aToken.mType == eCSSToken_Function &&
+ (aToken.mIdent.LowerCaseEqualsLiteral("calc") ||
+ aToken.mIdent.LowerCaseEqualsLiteral("-moz-calc"));
+}
+
+// Assigns to aValue iff it returns CSSParseResult::Ok.
+CSSParseResult
+CSSParserImpl::ParseVariant(nsCSSValue& aValue,
+ uint32_t aVariantMask,
+ const KTableEntry aKeywordTable[])
+{
+ NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) ||
+ !(aVariantMask & VARIANT_NUMBER),
+ "can't distinguish colors from numbers");
+ NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) ||
+ !(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)),
+ "can't distinguish colors from lengths");
+ NS_ASSERTION(!(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)) ||
+ !(aVariantMask & VARIANT_NUMBER),
+ "can't distinguish lengths from numbers");
+ MOZ_ASSERT(!(aVariantMask & VARIANT_IDENTIFIER) ||
+ !(aVariantMask & VARIANT_IDENTIFIER_NO_INHERIT),
+ "must not set both VARIANT_IDENTIFIER and "
+ "VARIANT_IDENTIFIER_NO_INHERIT");
+
+ uint32_t lineBefore, colBefore;
+ if (!GetNextTokenLocation(true, &lineBefore, &colBefore) ||
+ !GetToken(true)) {
+ // Must be at EOF.
+ return CSSParseResult::NotFound;
+ }
+
+ nsCSSToken* tk = &mToken;
+ if (((aVariantMask & (VARIANT_AHK | VARIANT_NORMAL | VARIANT_NONE | VARIANT_ALL)) != 0) &&
+ (eCSSToken_Ident == tk->mType)) {
+ nsCSSKeyword keyword = LookupKeywordPrefixAware(tk->mIdent,
+ aKeywordTable);
+
+ if (eCSSKeyword_UNKNOWN < keyword) { // known keyword
+ if ((aVariantMask & VARIANT_AUTO) != 0) {
+ if (eCSSKeyword_auto == keyword) {
+ aValue.SetAutoValue();
+ return CSSParseResult::Ok;
+ }
+ }
+ if ((aVariantMask & VARIANT_INHERIT) != 0) {
+ // XXX Should we check IsParsingCompoundProperty, or do all
+ // callers handle it? (Not all callers set it, though, since
+ // they want the quirks that are disabled by setting it.)
+
+ // IMPORTANT: If new keywords are added here,
+ // they probably need to be added in ParseCustomIdent as well.
+ if (eCSSKeyword_inherit == keyword) {
+ aValue.SetInheritValue();
+ return CSSParseResult::Ok;
+ }
+ else if (eCSSKeyword_initial == keyword) {
+ aValue.SetInitialValue();
+ return CSSParseResult::Ok;
+ }
+ else if (eCSSKeyword_unset == keyword &&
+ nsLayoutUtils::UnsetValueEnabled()) {
+ aValue.SetUnsetValue();
+ return CSSParseResult::Ok;
+ }
+ }
+ if ((aVariantMask & VARIANT_NONE) != 0) {
+ if (eCSSKeyword_none == keyword) {
+ aValue.SetNoneValue();
+ return CSSParseResult::Ok;
+ }
+ }
+ if ((aVariantMask & VARIANT_ALL) != 0) {
+ if (eCSSKeyword_all == keyword) {
+ aValue.SetAllValue();
+ return CSSParseResult::Ok;
+ }
+ }
+ if ((aVariantMask & VARIANT_NORMAL) != 0) {
+ if (eCSSKeyword_normal == keyword) {
+ aValue.SetNormalValue();
+ return CSSParseResult::Ok;
+ }
+ }
+ if ((aVariantMask & VARIANT_SYSFONT) != 0) {
+ if (eCSSKeyword__moz_use_system_font == keyword &&
+ !IsParsingCompoundProperty()) {
+ aValue.SetSystemFontValue();
+ return CSSParseResult::Ok;
+ }
+ }
+ if ((aVariantMask & VARIANT_OPENTYPE_SVG_KEYWORD) != 0) {
+ if (sOpentypeSVGEnabled) {
+ aVariantMask |= VARIANT_KEYWORD;
+ }
+ }
+ if ((aVariantMask & VARIANT_KEYWORD) != 0) {
+ int32_t value;
+ if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
+ aValue.SetIntValue(value, eCSSUnit_Enumerated);
+ return CSSParseResult::Ok;
+ }
+ }
+ }
+ }
+ // Check VARIANT_NUMBER and VARIANT_INTEGER before VARIANT_LENGTH or
+ // VARIANT_ZERO_ANGLE.
+ if (((aVariantMask & VARIANT_NUMBER) != 0) &&
+ (eCSSToken_Number == tk->mType)) {
+ aValue.SetFloatValue(tk->mNumber, eCSSUnit_Number);
+ return CSSParseResult::Ok;
+ }
+ if (((aVariantMask & VARIANT_INTEGER) != 0) &&
+ (eCSSToken_Number == tk->mType) && tk->mIntegerValid) {
+ aValue.SetIntValue(tk->mInteger, eCSSUnit_Integer);
+ return CSSParseResult::Ok;
+ }
+ if (((aVariantMask & (VARIANT_LENGTH | VARIANT_ANGLE |
+ VARIANT_FREQUENCY | VARIANT_TIME)) != 0 &&
+ eCSSToken_Dimension == tk->mType) ||
+ ((aVariantMask & (VARIANT_LENGTH | VARIANT_ZERO_ANGLE)) != 0 &&
+ eCSSToken_Number == tk->mType &&
+ tk->mNumber == 0.0f)) {
+ if ((aVariantMask & VARIANT_NONNEGATIVE_DIMENSION) != 0 &&
+ tk->mNumber < 0.0) {
+ UngetToken();
+ AssertNextTokenAt(lineBefore, colBefore);
+ return CSSParseResult::NotFound;
+ }
+ if (TranslateDimension(aValue, aVariantMask, tk->mNumber, tk->mIdent)) {
+ return CSSParseResult::Ok;
+ }
+ // Put the token back; we didn't parse it, so we shouldn't consume it
+ UngetToken();
+ AssertNextTokenAt(lineBefore, colBefore);
+ return CSSParseResult::NotFound;
+ }
+ if (((aVariantMask & VARIANT_PERCENT) != 0) &&
+ (eCSSToken_Percentage == tk->mType)) {
+ aValue.SetPercentValue(tk->mNumber);
+ return CSSParseResult::Ok;
+ }
+ if (mUnitlessLengthQuirk) { // NONSTANDARD: Nav interprets unitless numbers as px
+ if (((aVariantMask & VARIANT_LENGTH) != 0) &&
+ (eCSSToken_Number == tk->mType)) {
+ aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel);
+ return CSSParseResult::Ok;
+ }
+ }
+
+ if (IsSVGMode() && !IsParsingCompoundProperty()) {
+ // STANDARD: SVG Spec states that lengths and coordinates can be unitless
+ // in which case they default to user-units (1 px = 1 user unit)
+ if (((aVariantMask & VARIANT_LENGTH) != 0) &&
+ (eCSSToken_Number == tk->mType)) {
+ aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel);
+ return CSSParseResult::Ok;
+ }
+ }
+
+ if (((aVariantMask & VARIANT_URL) != 0) &&
+ eCSSToken_URL == tk->mType) {
+ SetValueToURL(aValue, tk->mIdent);
+ return CSSParseResult::Ok;
+ }
+ if ((aVariantMask & VARIANT_GRADIENT) != 0 &&
+ eCSSToken_Function == tk->mType) {
+ // a generated gradient
+ nsDependentString tmp(tk->mIdent, 0);
+ uint8_t gradientFlags = 0;
+ if (sMozGradientsEnabled &&
+ StringBeginsWith(tmp, NS_LITERAL_STRING("-moz-"))) {
+ tmp.Rebind(tmp, 5);
+ gradientFlags |= eGradient_MozLegacy;
+ } else if (sWebkitPrefixedAliasesEnabled &&
+ StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) {
+ tmp.Rebind(tmp, 8);
+ gradientFlags |= eGradient_WebkitLegacy;
+ }
+ if (StringBeginsWith(tmp, NS_LITERAL_STRING("repeating-"))) {
+ tmp.Rebind(tmp, 10);
+ gradientFlags |= eGradient_Repeating;
+ }
+
+ if (tmp.LowerCaseEqualsLiteral("linear-gradient")) {
+ if (!ParseLinearGradient(aValue, gradientFlags)) {
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ if (tmp.LowerCaseEqualsLiteral("radial-gradient")) {
+ if (!ParseRadialGradient(aValue, gradientFlags)) {
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ if ((gradientFlags == eGradient_WebkitLegacy) &&
+ tmp.LowerCaseEqualsLiteral("gradient")) {
+ // Note: we check gradientFlags using '==' to select *exactly*
+ // eGradient_WebkitLegacy -- and exclude eGradient_Repeating -- because
+ // we don't want to accept -webkit-repeating-gradient() expressions.
+ // (This is not a recognized syntax.)
+ if (!ParseWebkitGradient(aValue)) {
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+
+ if (ShouldUseUnprefixingService() &&
+ !gradientFlags &&
+ StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) {
+ // Copy 'tmp' into a string on the stack, since as soon as we
+ // start parsing, its backing store (in "tk") will be overwritten
+ nsAutoString prefixedFuncName(tmp);
+ if (!ParseWebkitPrefixedGradientWithService(prefixedFuncName, aValue)) {
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ }
+ if ((aVariantMask & VARIANT_IMAGE_RECT) != 0 &&
+ eCSSToken_Function == tk->mType &&
+ tk->mIdent.LowerCaseEqualsLiteral("-moz-image-rect")) {
+ if (!ParseImageRect(aValue)) {
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ if ((aVariantMask & VARIANT_ELEMENT) != 0 &&
+ eCSSToken_Function == tk->mType &&
+ tk->mIdent.LowerCaseEqualsLiteral("-moz-element")) {
+ if (!ParseElement(aValue)) {
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ if ((aVariantMask & VARIANT_COLOR) != 0) {
+ if (mHashlessColorQuirk || // NONSTANDARD: Nav interprets 'xxyyzz' values even without '#' prefix
+ (eCSSToken_ID == tk->mType) ||
+ (eCSSToken_Hash == tk->mType) ||
+ (eCSSToken_Ident == tk->mType) ||
+ ((eCSSToken_Function == tk->mType) &&
+ (tk->mIdent.LowerCaseEqualsLiteral("rgb") ||
+ tk->mIdent.LowerCaseEqualsLiteral("hsl") ||
+ tk->mIdent.LowerCaseEqualsLiteral("rgba") ||
+ tk->mIdent.LowerCaseEqualsLiteral("hsla"))))
+ {
+ // Put token back so that parse color can get it
+ UngetToken();
+ return ParseColor(aValue);
+ }
+ }
+ if (((aVariantMask & VARIANT_STRING) != 0) &&
+ (eCSSToken_String == tk->mType)) {
+ nsAutoString buffer;
+ buffer.Append(tk->mIdent);
+ aValue.SetStringValue(buffer, eCSSUnit_String);
+ return CSSParseResult::Ok;
+ }
+ if (((aVariantMask &
+ (VARIANT_IDENTIFIER | VARIANT_IDENTIFIER_NO_INHERIT)) != 0) &&
+ (eCSSToken_Ident == tk->mType) &&
+ ((aVariantMask & VARIANT_IDENTIFIER) != 0 ||
+ !(tk->mIdent.LowerCaseEqualsLiteral("inherit") ||
+ tk->mIdent.LowerCaseEqualsLiteral("initial") ||
+ (tk->mIdent.LowerCaseEqualsLiteral("unset") &&
+ nsLayoutUtils::UnsetValueEnabled())))) {
+ aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
+ return CSSParseResult::Ok;
+ }
+ if (((aVariantMask & VARIANT_COUNTER) != 0) &&
+ (eCSSToken_Function == tk->mType) &&
+ (tk->mIdent.LowerCaseEqualsLiteral("counter") ||
+ tk->mIdent.LowerCaseEqualsLiteral("counters"))) {
+ if (!ParseCounter(aValue)) {
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ if (((aVariantMask & VARIANT_ATTR) != 0) &&
+ (eCSSToken_Function == tk->mType) &&
+ tk->mIdent.LowerCaseEqualsLiteral("attr")) {
+ if (!ParseAttr(aValue)) {
+ SkipUntil(')');
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ if (((aVariantMask & VARIANT_TIMING_FUNCTION) != 0) &&
+ (eCSSToken_Function == tk->mType)) {
+ if (tk->mIdent.LowerCaseEqualsLiteral("cubic-bezier")) {
+ if (!ParseTransitionTimingFunctionValues(aValue)) {
+ SkipUntil(')');
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ if (tk->mIdent.LowerCaseEqualsLiteral("steps")) {
+ if (!ParseTransitionStepTimingFunctionValues(aValue)) {
+ SkipUntil(')');
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ }
+ if ((aVariantMask & VARIANT_CALC) &&
+ IsCSSTokenCalcFunction(*tk)) {
+ // calc() currently allows only lengths and percents and number inside it.
+ // And note that in current implementation, number cannot be mixed with
+ // length and percent.
+ if (!ParseCalc(aValue, aVariantMask & VARIANT_LPN)) {
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+
+ UngetToken();
+ AssertNextTokenAt(lineBefore, colBefore);
+ return CSSParseResult::NotFound;
+}
+
+bool
+CSSParserImpl::ParseCustomIdent(nsCSSValue& aValue,
+ const nsAutoString& aIdentValue,
+ const nsCSSKeyword aExcludedKeywords[],
+ const nsCSSProps::KTableEntry aPropertyKTable[])
+{
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aIdentValue);
+ if (keyword == eCSSKeyword_UNKNOWN) {
+ // Fast path for identifiers that are not known CSS keywords:
+ aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
+ return true;
+ }
+ if (keyword == eCSSKeyword_inherit ||
+ keyword == eCSSKeyword_initial ||
+ keyword == eCSSKeyword_unset ||
+ keyword == eCSSKeyword_default ||
+ (aPropertyKTable &&
+ nsCSSProps::FindIndexOfKeyword(keyword, aPropertyKTable) >= 0)) {
+ return false;
+ }
+ if (aExcludedKeywords) {
+ for (uint32_t i = 0;; i++) {
+ nsCSSKeyword excludedKeyword = aExcludedKeywords[i];
+ if (excludedKeyword == eCSSKeyword_UNKNOWN) {
+ break;
+ }
+ if (excludedKeyword == keyword) {
+ return false;
+ }
+ }
+ }
+ aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseCounter(nsCSSValue& aValue)
+{
+ nsCSSUnit unit = (mToken.mIdent.LowerCaseEqualsLiteral("counter") ?
+ eCSSUnit_Counter : eCSSUnit_Counters);
+
+ // A non-iterative for loop to break out when an error occurs.
+ for (;;) {
+ if (!GetToken(true)) {
+ break;
+ }
+ if (eCSSToken_Ident != mToken.mType) {
+ UngetToken();
+ break;
+ }
+
+ RefPtr<nsCSSValue::Array> val =
+ nsCSSValue::Array::Create(unit == eCSSUnit_Counter ? 2 : 3);
+
+ val->Item(0).SetStringValue(mToken.mIdent, eCSSUnit_Ident);
+
+ if (eCSSUnit_Counters == unit) {
+ // must have a comma and then a separator string
+ if (!ExpectSymbol(',', true) || !GetToken(true)) {
+ break;
+ }
+ if (eCSSToken_String != mToken.mType) {
+ UngetToken();
+ break;
+ }
+ val->Item(1).SetStringValue(mToken.mIdent, eCSSUnit_String);
+ }
+
+ // get optional type
+ int32_t typeItem = eCSSUnit_Counters == unit ? 2 : 1;
+ nsCSSValue& type = val->Item(typeItem);
+ if (ExpectSymbol(',', true)) {
+ if (!ParseCounterStyleNameValue(type) && !ParseSymbols(type)) {
+ break;
+ }
+ } else {
+ type.SetStringValue(NS_LITERAL_STRING("decimal"), eCSSUnit_Ident);
+ }
+
+ if (!ExpectSymbol(')', true)) {
+ break;
+ }
+
+ aValue.SetArrayValue(val, unit);
+ return true;
+ }
+
+ SkipUntil(')');
+ return false;
+}
+
+bool
+CSSParserImpl::ParseAttr(nsCSSValue& aValue)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ nsAutoString attr;
+ if (eCSSToken_Ident == mToken.mType) { // attr name or namespace
+ nsAutoString holdIdent(mToken.mIdent);
+ if (ExpectSymbol('|', false)) { // namespace
+ int32_t nameSpaceID = GetNamespaceIdForPrefix(holdIdent);
+ if (nameSpaceID == kNameSpaceID_Unknown) {
+ return false;
+ }
+ attr.AppendInt(nameSpaceID, 10);
+ attr.Append(char16_t('|'));
+ if (! GetToken(false)) {
+ REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
+ return false;
+ }
+ if (eCSSToken_Ident == mToken.mType) {
+ attr.Append(mToken.mIdent);
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
+ UngetToken();
+ return false;
+ }
+ }
+ else { // no namespace
+ attr = holdIdent;
+ }
+ }
+ else if (mToken.IsSymbol('*')) { // namespace wildcard
+ // Wildcard namespace makes no sense here and is not allowed
+ REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
+ UngetToken();
+ return false;
+ }
+ else if (mToken.IsSymbol('|')) { // explicit NO namespace
+ if (! GetToken(false)) {
+ REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
+ return false;
+ }
+ if (eCSSToken_Ident == mToken.mType) {
+ attr.Append(mToken.mIdent);
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
+ UngetToken();
+ return false;
+ }
+ }
+ else {
+ REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected);
+ UngetToken();
+ return false;
+ }
+ if (!ExpectSymbol(')', true)) {
+ return false;
+ }
+ aValue.SetStringValue(attr, eCSSUnit_Attr);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseSymbols(nsCSSValue& aValue)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (mToken.mType != eCSSToken_Function &&
+ !mToken.mIdent.LowerCaseEqualsLiteral("symbols")) {
+ UngetToken();
+ return false;
+ }
+
+ RefPtr<nsCSSValue::Array> params = nsCSSValue::Array::Create(2);
+ nsCSSValue& type = params->Item(0);
+ nsCSSValue& symbols = params->Item(1);
+
+ if (!ParseEnum(type, nsCSSProps::kCounterSymbolsSystemKTable)) {
+ type.SetIntValue(NS_STYLE_COUNTER_SYSTEM_SYMBOLIC, eCSSUnit_Enumerated);
+ }
+
+ bool first = true;
+ nsCSSValueList* item = symbols.SetListValue();
+ for (;;) {
+ // FIXME Should also include VARIANT_IMAGE. See bug 1071436.
+ if (!ParseSingleTokenVariant(item->mValue, VARIANT_STRING, nullptr)) {
+ break;
+ }
+ if (ExpectSymbol(')', true)) {
+ if (first) {
+ switch (type.GetIntValue()) {
+ case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+ case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+ // require at least two symbols
+ return false;
+ }
+ }
+ aValue.SetArrayValue(params, eCSSUnit_Symbols);
+ return true;
+ }
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ first = false;
+ }
+
+ SkipUntil(')');
+ return false;
+}
+
+bool
+CSSParserImpl::SetValueToURL(nsCSSValue& aValue, const nsString& aURL)
+{
+ if (!mSheetPrincipal) {
+ if (!mSheetPrincipalRequired) {
+ /* Pretend to succeed. */
+ return true;
+ }
+
+ NS_NOTREACHED("Codepaths that expect to parse URLs MUST pass in an "
+ "origin principal");
+ return false;
+ }
+
+ RefPtr<nsStringBuffer> buffer(nsCSSValue::BufferFromString(aURL));
+
+ // Note: urlVal retains its own reference to |buffer|.
+ mozilla::css::URLValue *urlVal =
+ new mozilla::css::URLValue(buffer, mBaseURI, mSheetURI, mSheetPrincipal);
+ aValue.SetURLValue(urlVal);
+ return true;
+}
+
+/**
+ * Parse the image-orientation property, which has the grammar:
+ * <angle> flip? | flip | from-image
+ */
+bool
+CSSParserImpl::ParseImageOrientation(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
+ // 'inherit', 'initial' and 'unset' must be alone
+ return true;
+ }
+
+ // Check for an angle with optional 'flip'.
+ nsCSSValue angle;
+ if (ParseSingleTokenVariant(angle, VARIANT_ANGLE, nullptr)) {
+ nsCSSValue flip;
+
+ if (ParseSingleTokenVariant(flip, VARIANT_KEYWORD,
+ nsCSSProps::kImageOrientationFlipKTable)) {
+ RefPtr<nsCSSValue::Array> array = nsCSSValue::Array::Create(2);
+ array->Item(0) = angle;
+ array->Item(1) = flip;
+ aValue.SetArrayValue(array, eCSSUnit_Array);
+ } else {
+ aValue = angle;
+ }
+
+ return true;
+ }
+
+ // The remaining possibilities (bare 'flip' and 'from-image') are both
+ // keywords, so we can handle them at the same time.
+ nsCSSValue keyword;
+ if (ParseSingleTokenVariant(keyword, VARIANT_KEYWORD,
+ nsCSSProps::kImageOrientationKTable)) {
+ aValue = keyword;
+ return true;
+ }
+
+ // All possibilities failed.
+ return false;
+}
+
+/**
+ * Parse the arguments of -moz-image-rect() function.
+ * -moz-image-rect(<uri>, <top>, <right>, <bottom>, <left>)
+ */
+bool
+CSSParserImpl::ParseImageRect(nsCSSValue& aImage)
+{
+ // A non-iterative for loop to break out when an error occurs.
+ for (;;) {
+ nsCSSValue newFunction;
+ static const uint32_t kNumArgs = 5;
+ nsCSSValue::Array* func =
+ newFunction.InitFunction(eCSSKeyword__moz_image_rect, kNumArgs);
+
+ // func->Item(0) is reserved for the function name.
+ nsCSSValue& url = func->Item(1);
+ nsCSSValue& top = func->Item(2);
+ nsCSSValue& right = func->Item(3);
+ nsCSSValue& bottom = func->Item(4);
+ nsCSSValue& left = func->Item(5);
+
+ nsAutoString urlString;
+ if (!ParseURLOrString(urlString) ||
+ !SetValueToURL(url, urlString) ||
+ !ExpectSymbol(',', true)) {
+ break;
+ }
+
+ static const int32_t VARIANT_SIDE = VARIANT_NUMBER | VARIANT_PERCENT;
+ if (!ParseSingleTokenNonNegativeVariant(top, VARIANT_SIDE, nullptr) ||
+ !ExpectSymbol(',', true) ||
+ !ParseSingleTokenNonNegativeVariant(right, VARIANT_SIDE, nullptr) ||
+ !ExpectSymbol(',', true) ||
+ !ParseSingleTokenNonNegativeVariant(bottom, VARIANT_SIDE, nullptr) ||
+ !ExpectSymbol(',', true) ||
+ !ParseSingleTokenNonNegativeVariant(left, VARIANT_SIDE, nullptr) ||
+ !ExpectSymbol(')', true))
+ break;
+
+ aImage = newFunction;
+ return true;
+ }
+
+ SkipUntil(')');
+ return false;
+}
+
+// <element>: -moz-element(# <element_id> )
+bool
+CSSParserImpl::ParseElement(nsCSSValue& aValue)
+{
+ // A non-iterative for loop to break out when an error occurs.
+ for (;;) {
+ if (!GetToken(true))
+ break;
+
+ if (mToken.mType == eCSSToken_ID) {
+ aValue.SetStringValue(mToken.mIdent, eCSSUnit_Element);
+ } else {
+ UngetToken();
+ break;
+ }
+
+ if (!ExpectSymbol(')', true))
+ break;
+
+ return true;
+ }
+
+ // If we detect a syntax error, we must match the opening parenthesis of the
+ // function with the closing parenthesis and skip all the tokens in between.
+ SkipUntil(')');
+ return false;
+}
+
+// flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
+bool
+CSSParserImpl::ParseFlex()
+{
+ // First check for inherit / initial / unset
+ nsCSSValue tmpVal;
+ if (ParseSingleTokenVariant(tmpVal, VARIANT_INHERIT, nullptr)) {
+ AppendValue(eCSSProperty_flex_grow, tmpVal);
+ AppendValue(eCSSProperty_flex_shrink, tmpVal);
+ AppendValue(eCSSProperty_flex_basis, tmpVal);
+ return true;
+ }
+
+ // Next, check for 'none' == '0 0 auto'
+ if (ParseSingleTokenVariant(tmpVal, VARIANT_NONE, nullptr)) {
+ AppendValue(eCSSProperty_flex_grow, nsCSSValue(0.0f, eCSSUnit_Number));
+ AppendValue(eCSSProperty_flex_shrink, nsCSSValue(0.0f, eCSSUnit_Number));
+ AppendValue(eCSSProperty_flex_basis, nsCSSValue(eCSSUnit_Auto));
+ return true;
+ }
+
+ // OK, try parsing our value as individual per-subproperty components:
+ // [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
+
+ // Each subproperty has a default value that it takes when it's omitted in a
+ // "flex" shorthand value. These default values are *only* for the shorthand
+ // syntax -- they're distinct from the subproperties' own initial values. We
+ // start with each subproperty at its default, as if we had "flex: 1 1 0%".
+ nsCSSValue flexGrow(1.0f, eCSSUnit_Number);
+ nsCSSValue flexShrink(1.0f, eCSSUnit_Number);
+ nsCSSValue flexBasis(0.0f, eCSSUnit_Percent);
+
+ // OVERVIEW OF PARSING STRATEGY:
+ // =============================
+ // a) Parse the first component as either flex-basis or flex-grow.
+ // b) If it wasn't flex-grow, parse the _next_ component as flex-grow.
+ // c) Now we've just parsed flex-grow -- so try parsing the next thing as
+ // flex-shrink.
+ // d) Finally: If we didn't get flex-basis at the beginning, try to parse
+ // it now, at the end.
+ //
+ // More details in each section below.
+
+ uint32_t flexBasisVariantMask =
+ (nsCSSProps::ParserVariant(eCSSProperty_flex_basis) & ~(VARIANT_INHERIT));
+
+ // (a) Parse first component. It can be either be a 'flex-basis' value or a
+ // 'flex-grow' value, so we use the flex-basis-specific variant mask, along
+ // with VARIANT_NUMBER to accept 'flex-grow' values.
+ //
+ // NOTE: if we encounter unitless 0 here, we *must* interpret it as a
+ // 'flex-grow' value (a number), *not* as a 'flex-basis' value (a length).
+ // Conveniently, that's the behavior this combined variant-mask gives us --
+ // it'll treat unitless 0 as a number. The flexbox spec requires this:
+ // "a unitless zero that is not already preceded by two flex factors must be
+ // interpreted as a flex factor.
+ if (ParseNonNegativeVariant(tmpVal, flexBasisVariantMask | VARIANT_NUMBER,
+ nsCSSProps::kWidthKTable) != CSSParseResult::Ok) {
+ // First component was not a valid flex-basis or flex-grow value. Fail.
+ return false;
+ }
+
+ // Record what we just parsed as either flex-basis or flex-grow:
+ bool wasFirstComponentFlexBasis = (tmpVal.GetUnit() != eCSSUnit_Number);
+ (wasFirstComponentFlexBasis ? flexBasis : flexGrow) = tmpVal;
+
+ // (b) If we didn't get flex-grow yet, parse _next_ component as flex-grow.
+ bool doneParsing = false;
+ if (wasFirstComponentFlexBasis) {
+ if (ParseNonNegativeNumber(tmpVal)) {
+ flexGrow = tmpVal;
+ } else {
+ // Failed to parse anything after our flex-basis -- that's fine. We can
+ // skip the remaining parsing.
+ doneParsing = true;
+ }
+ }
+
+ if (!doneParsing) {
+ // (c) OK -- the last thing we parsed was flex-grow, so look for a
+ // flex-shrink in the next position.
+ if (ParseNonNegativeNumber(tmpVal)) {
+ flexShrink = tmpVal;
+ }
+
+ // d) Finally: If we didn't get flex-basis at the beginning, try to parse
+ // it now, at the end.
+ //
+ // NOTE: If we encounter unitless 0 in this final position, we'll parse it
+ // as a 'flex-basis' value. That's OK, because we know it must have
+ // been "preceded by 2 flex factors" (justification below), which gets us
+ // out of the spec's requirement of otherwise having to treat unitless 0
+ // as a flex factor.
+ //
+ // JUSTIFICATION: How do we know that a unitless 0 here must have been
+ // preceded by 2 flex factors? Well, suppose we had a unitless 0 that
+ // was preceded by only 1 flex factor. Then, we would have already
+ // accepted this unitless 0 as the 'flex-shrink' value, up above (since
+ // it's a valid flex-shrink value), and we'd have moved on to the next
+ // token (if any). And of course, if we instead had a unitless 0 preceded
+ // by *no* flex factors (if it were the first token), we would've already
+ // parsed it in our very first call to ParseNonNegativeVariant(). So, any
+ // unitless 0 encountered here *must* have been preceded by 2 flex factors.
+ if (!wasFirstComponentFlexBasis) {
+ CSSParseResult result =
+ ParseNonNegativeVariant(tmpVal, flexBasisVariantMask,
+ nsCSSProps::kWidthKTable);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::Ok) {
+ flexBasis = tmpVal;
+ }
+ }
+ }
+
+ AppendValue(eCSSProperty_flex_grow, flexGrow);
+ AppendValue(eCSSProperty_flex_shrink, flexShrink);
+ AppendValue(eCSSProperty_flex_basis, flexBasis);
+
+ return true;
+}
+
+// flex-flow: <flex-direction> || <flex-wrap>
+bool
+CSSParserImpl::ParseFlexFlow()
+{
+ static const nsCSSPropertyID kFlexFlowSubprops[] = {
+ eCSSProperty_flex_direction,
+ eCSSProperty_flex_wrap
+ };
+ const size_t numProps = MOZ_ARRAY_LENGTH(kFlexFlowSubprops);
+ nsCSSValue values[numProps];
+
+ int32_t found = ParseChoice(values, kFlexFlowSubprops, numProps);
+
+ // Bail if we didn't successfully parse anything
+ if (found < 1) {
+ return false;
+ }
+
+ // If either property didn't get an explicit value, use its initial value.
+ if ((found & 1) == 0) {
+ values[0].SetIntValue(NS_STYLE_FLEX_DIRECTION_ROW, eCSSUnit_Enumerated);
+ }
+ if ((found & 2) == 0) {
+ values[1].SetIntValue(NS_STYLE_FLEX_WRAP_NOWRAP, eCSSUnit_Enumerated);
+ }
+
+ // Store these values and declare success!
+ for (size_t i = 0; i < numProps; i++) {
+ AppendValue(kFlexFlowSubprops[i], values[i]);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseGridAutoFlow()
+{
+ nsCSSValue value;
+ if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ AppendValue(eCSSProperty_grid_auto_flow, value);
+ return true;
+ }
+
+ static const int32_t mask[] = {
+ NS_STYLE_GRID_AUTO_FLOW_ROW | NS_STYLE_GRID_AUTO_FLOW_COLUMN,
+ MASK_END_VALUE
+ };
+ if (!ParseBitmaskValues(value, nsCSSProps::kGridAutoFlowKTable, mask)) {
+ return false;
+ }
+ int32_t bitField = value.GetIntValue();
+
+ // If neither row nor column is provided, row is assumed.
+ if (!(bitField & (NS_STYLE_GRID_AUTO_FLOW_ROW |
+ NS_STYLE_GRID_AUTO_FLOW_COLUMN))) {
+ value.SetIntValue(bitField | NS_STYLE_GRID_AUTO_FLOW_ROW,
+ eCSSUnit_Enumerated);
+ }
+
+ AppendValue(eCSSProperty_grid_auto_flow, value);
+ return true;
+}
+
+static const nsCSSKeyword kGridLineKeywords[] = {
+ eCSSKeyword_span,
+ eCSSKeyword_UNKNOWN // End-of-array marker
+};
+
+CSSParseResult
+CSSParserImpl::ParseGridLineNames(nsCSSValue& aValue)
+{
+ if (!ExpectSymbol('[', true)) {
+ return CSSParseResult::NotFound;
+ }
+ if (!GetToken(true) || mToken.IsSymbol(']')) {
+ return CSSParseResult::Ok;
+ }
+ // 'return' so far leaves aValue untouched, to represent an empty list.
+
+ nsCSSValueList* item;
+ if (aValue.GetUnit() == eCSSUnit_List) {
+ // Find the end of an existing list.
+ // The grid-template shorthand uses this, at most once for a given list.
+
+ // NOTE: we could avoid this traversal by somehow keeping around
+ // a pointer to the last item from the previous call.
+ // It's not yet clear if this is worth the additional code complexity.
+ item = aValue.GetListValue();
+ while (item->mNext) {
+ item = item->mNext;
+ }
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ } else {
+ MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null, "Unexpected unit");
+ item = aValue.SetListValue();
+ }
+ for (;;) {
+ if (!(eCSSToken_Ident == mToken.mType &&
+ ParseCustomIdent(item->mValue, mToken.mIdent, kGridLineKeywords))) {
+ UngetToken();
+ SkipUntil(']');
+ return CSSParseResult::Error;
+ }
+ if (!GetToken(true) || mToken.IsSymbol(']')) {
+ return CSSParseResult::Ok;
+ }
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ }
+}
+
+// Assuming the 'repeat(' function token has already been consumed,
+// parse the rest of repeat(<positive-integer> | auto-fill, <line-names>+)
+// Append to the linked list whose end is given by |aTailPtr|,
+// and update |aTailPtr| to point to the new end of the list.
+bool
+CSSParserImpl::ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr)
+{
+ int32_t repetitions;
+ Maybe<int32_t> repeatAutoEnum;
+ if (!ParseGridTrackRepeatIntro(true, &repetitions, &repeatAutoEnum)) {
+ return false;
+ }
+ if (repeatAutoEnum.isSome()) {
+ // Parse exactly one <line-names>.
+ nsCSSValue listValue;
+ nsCSSValueList* list = listValue.SetListValue();
+ if (ParseGridLineNames(list->mValue) != CSSParseResult::Ok) {
+ return false;
+ }
+ if (!ExpectSymbol(')', true)) {
+ return false;
+ }
+ // Instead of hooking up this list into the flat name list as usual,
+ // we create a pair(Int, List) where the first value is the auto-fill
+ // keyword and the second is the name list to repeat.
+ nsCSSValue kwd;
+ kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated);
+ *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
+ (*aTailPtr)->mValue.SetPairValue(kwd, listValue);
+ return true;
+ }
+
+ // Parse at least one <line-names>
+ nsCSSValueList* tail = *aTailPtr;
+ do {
+ tail->mNext = new nsCSSValueList;
+ tail = tail->mNext;
+ if (ParseGridLineNames(tail->mValue) != CSSParseResult::Ok) {
+ return false;
+ }
+ } while (!ExpectSymbol(')', true));
+ nsCSSValueList* firstRepeatedItem = (*aTailPtr)->mNext;
+ nsCSSValueList* lastRepeatedItem = tail;
+
+ // Our repeated items are already in the target list once,
+ // so they need to be repeated |repetitions - 1| more times.
+ MOZ_ASSERT(repetitions > 0, "Expected positive repetitions");
+ while (--repetitions) {
+ nsCSSValueList* repeatedItem = firstRepeatedItem;
+ for (;;) {
+ tail->mNext = new nsCSSValueList;
+ tail = tail->mNext;
+ tail->mValue = repeatedItem->mValue;
+ if (repeatedItem == lastRepeatedItem) {
+ break;
+ }
+ repeatedItem = repeatedItem->mNext;
+ }
+ }
+ *aTailPtr = tail;
+ return true;
+}
+
+// Assuming a 'subgrid' keyword was already consumed, parse <line-name-list>?
+bool
+CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue)
+{
+ nsCSSValueList* item = aValue.SetListValue();
+ // This marker distinguishes the value from a <track-list>.
+ item->mValue.SetIntValue(NS_STYLE_GRID_TEMPLATE_SUBGRID,
+ eCSSUnit_Enumerated);
+ bool haveRepeatAuto = false;
+ for (;;) {
+ // First try to parse <name-repeat>, i.e.
+ // repeat(<positive-integer> | auto-fill, <line-names>+)
+ if (!GetToken(true)) {
+ return true;
+ }
+ if (mToken.mType == eCSSToken_Function &&
+ mToken.mIdent.LowerCaseEqualsLiteral("repeat")) {
+ nsCSSValueList* startOfRepeat = item;
+ if (!ParseGridLineNameListRepeat(&item)) {
+ SkipUntil(')');
+ return false;
+ }
+ if (startOfRepeat->mNext->mValue.GetUnit() == eCSSUnit_Pair) {
+ if (haveRepeatAuto) {
+ REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillInNameList);
+ return false;
+ }
+ haveRepeatAuto = true;
+ }
+ } else {
+ UngetToken();
+
+ // This was not a repeat() function. Try to parse <line-names>.
+ nsCSSValue lineNames;
+ CSSParseResult result = ParseGridLineNames(lineNames);
+ if (result == CSSParseResult::NotFound) {
+ return true;
+ }
+ if (result == CSSParseResult::Error) {
+ return false;
+ }
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ item->mValue = lineNames;
+ }
+ }
+}
+
+CSSParseResult
+CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue)
+{
+ CSSParseResult result = ParseNonNegativeVariant(aValue,
+ VARIANT_AUTO | VARIANT_LPCALC | VARIANT_KEYWORD,
+ nsCSSProps::kGridTrackBreadthKTable);
+
+ if (result == CSSParseResult::Ok ||
+ result == CSSParseResult::Error) {
+ return result;
+ }
+
+ // Attempt to parse <flex> (a dimension with the "fr" unit).
+ if (!GetToken(true)) {
+ return CSSParseResult::NotFound;
+ }
+ if (!(eCSSToken_Dimension == mToken.mType &&
+ mToken.mIdent.LowerCaseEqualsLiteral("fr") &&
+ mToken.mNumber >= 0)) {
+ UngetToken();
+ return CSSParseResult::NotFound;
+ }
+ aValue.SetFloatValue(mToken.mNumber, eCSSUnit_FlexFraction);
+ return CSSParseResult::Ok;
+}
+
+// Parse a <track-size>, or <fixed-size> when aFlags has eFixedTrackSize.
+CSSParseResult
+CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue,
+ GridTrackSizeFlags aFlags)
+{
+ const bool requireFixedSize =
+ !!(aFlags & GridTrackSizeFlags::eFixedTrackSize);
+ // Attempt to parse a single <track-breadth>.
+ CSSParseResult result = ParseGridTrackBreadth(aValue);
+ if (requireFixedSize && result == CSSParseResult::Ok &&
+ !aValue.IsLengthPercentCalcUnit()) {
+ result = CSSParseResult::Error;
+ }
+ if (result == CSSParseResult::Error) {
+ return result;
+ }
+ if (result == CSSParseResult::Ok) {
+ if (aValue.GetUnit() == eCSSUnit_FlexFraction) {
+ // Single value <flex> is represented internally as minmax(auto, <flex>).
+ nsCSSValue minmax;
+ nsCSSValue::Array* func = minmax.InitFunction(eCSSKeyword_minmax, 2);
+ func->Item(1).SetAutoValue();
+ func->Item(2) = aValue;
+ aValue = minmax;
+ }
+ return result;
+ }
+
+ // Attempt to parse a minmax() or fit-content() function.
+ if (!GetToken(true)) {
+ return CSSParseResult::NotFound;
+ }
+ if (eCSSToken_Function != mToken.mType) {
+ UngetToken();
+ return CSSParseResult::NotFound;
+ }
+ if (mToken.mIdent.LowerCaseEqualsLiteral("fit-content")) {
+ nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_fit_content, 1);
+ if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok &&
+ func->Item(1).IsLengthPercentCalcUnit() &&
+ ExpectSymbol(')', true)) {
+ return CSSParseResult::Ok;
+ }
+ SkipUntil(')');
+ return CSSParseResult::Error;
+ }
+ if (!mToken.mIdent.LowerCaseEqualsLiteral("minmax")) {
+ UngetToken();
+ return CSSParseResult::NotFound;
+ }
+ nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_minmax, 2);
+ if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok &&
+ ExpectSymbol(',', true) &&
+ ParseGridTrackBreadth(func->Item(2)) == CSSParseResult::Ok &&
+ ExpectSymbol(')', true)) {
+ if (requireFixedSize &&
+ !func->Item(1).IsLengthPercentCalcUnit() &&
+ !func->Item(2).IsLengthPercentCalcUnit()) {
+ return CSSParseResult::Error;
+ }
+ // Reject <flex> min-sizing.
+ if (func->Item(1).GetUnit() == eCSSUnit_FlexFraction) {
+ return CSSParseResult::Error;
+ }
+ return CSSParseResult::Ok;
+ }
+ SkipUntil(')');
+ return CSSParseResult::Error;
+}
+
+bool
+CSSParserImpl::ParseGridAutoColumnsRows(nsCSSPropertyID aPropID)
+{
+ nsCSSValue value;
+ if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) ||
+ ParseGridTrackSize(value) == CSSParseResult::Ok) {
+ AppendValue(aPropID, value);
+ return true;
+ }
+ return false;
+}
+
+bool
+CSSParserImpl::ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue,
+ const nsCSSValue& aFirstLineNames,
+ GridTrackListFlags aFlags)
+{
+ nsCSSValueList* firstLineNamesItem = aValue.SetListValue();
+ firstLineNamesItem->mValue = aFirstLineNames;
+
+ // This function is trying to parse <track-list>, which is
+ // [ <line-names>? [ <track-size> | <repeat()> ] ]+ <line-names>?
+ // and we're already past the first "<line-names>?".
+ // If aFlags contains eExplicitTrackList then <repeat()> is disallowed.
+ //
+ // Each iteration of the following loop attempts to parse either a
+ // repeat() or a <track-size> expression, and then an (optional)
+ // <line-names> expression.
+ //
+ // The only successful exit point from this loop is the ::NotFound
+ // case after ParseGridTrackSize(); i.e. we'll greedily parse
+ // repeat()/<track-size> until we can't find one.
+ nsCSSValueList* item = firstLineNamesItem;
+ bool haveRepeatAuto = false;
+ for (;;) {
+ // First try to parse repeat()
+ if (!GetToken(true)) {
+ break;
+ }
+ if (!(aFlags & GridTrackListFlags::eExplicitTrackList) &&
+ mToken.mType == eCSSToken_Function &&
+ mToken.mIdent.LowerCaseEqualsLiteral("repeat")) {
+ nsCSSValueList* startOfRepeat = item;
+ if (!ParseGridTrackListRepeat(&item)) {
+ SkipUntil(')');
+ return false;
+ }
+ auto firstRepeat = startOfRepeat->mNext;
+ if (firstRepeat->mValue.GetUnit() == eCSSUnit_Pair) {
+ if (haveRepeatAuto) {
+ REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillFitInTrackList);
+ return false;
+ }
+ haveRepeatAuto = true;
+ // We're parsing an <auto-track-list>, which requires that all tracks
+ // are <fixed-size>, so we need to check the ones we've parsed already.
+ for (nsCSSValueList* list = firstLineNamesItem->mNext;
+ list != firstRepeat; list = list->mNext) {
+ if (list->mValue.GetUnit() == eCSSUnit_Function) {
+ nsCSSValue::Array* func = list->mValue.GetArrayValue();
+ auto funcName = func->Item(0).GetKeywordValue();
+ if (funcName == eCSSKeyword_minmax) {
+ if (!func->Item(1).IsLengthPercentCalcUnit() &&
+ !func->Item(2).IsLengthPercentCalcUnit()) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(funcName == eCSSKeyword_fit_content,
+ "Expected minmax() or fit-content() function");
+ return false; // fit-content() is not a <fixed-size>
+ }
+ } else if (!list->mValue.IsLengthPercentCalcUnit()) {
+ return false;
+ }
+ list = list->mNext; // skip line names
+ }
+ }
+ } else {
+ UngetToken();
+
+ // Not a repeat() function; try to parse <track-size> | <fixed-size>.
+ nsCSSValue trackSize;
+ GridTrackSizeFlags flags = haveRepeatAuto
+ ? GridTrackSizeFlags::eFixedTrackSize
+ : GridTrackSizeFlags::eDefaultTrackSize;
+ CSSParseResult result = ParseGridTrackSize(trackSize, flags);
+ if (result == CSSParseResult::Error) {
+ return false;
+ }
+ if (result == CSSParseResult::NotFound) {
+ // What we've parsed so far is a valid <track-list>
+ // (modulo the "at least one <track-size>" check below.)
+ // Stop here.
+ break;
+ }
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ item->mValue = trackSize;
+
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ }
+ if (ParseGridLineNames(item->mValue) == CSSParseResult::Error) {
+ return false;
+ }
+ }
+
+ // Require at least one <track-size>.
+ if (item == firstLineNamesItem) {
+ return false;
+ }
+
+ MOZ_ASSERT(aValue.GetListValue() &&
+ aValue.GetListValue()->mNext &&
+ aValue.GetListValue()->mNext->mNext,
+ "<track-list> should have a minimum length of 3");
+ return true;
+}
+
+// Takes ownership of |aSecond|
+static void
+ConcatLineNames(nsCSSValue& aFirst, nsCSSValue& aSecond)
+{
+ if (aSecond.GetUnit() == eCSSUnit_Null) {
+ // Nothing to do.
+ return;
+ }
+ if (aFirst.GetUnit() == eCSSUnit_Null) {
+ // Empty or omitted <line-names>. Replace it.
+ aFirst = aSecond;
+ return;
+ }
+
+ // Join the two <line-names> lists.
+ nsCSSValueList* source = aSecond.GetListValue();
+ nsCSSValueList* target = aFirst.GetListValue();
+ // Find the end:
+ while (target->mNext) {
+ target = target->mNext;
+ }
+ // Copy the first name. We can't take ownership of it
+ // as it'll be destroyed when |aSecond| goes out of scope.
+ target->mNext = new nsCSSValueList;
+ target = target->mNext;
+ target->mValue = source->mValue;
+ // Move the rest of the linked list.
+ target->mNext = source->mNext;
+ source->mNext = nullptr;
+}
+
+// Assuming the 'repeat(' function token has already been consumed,
+// parse "repeat( <positive-integer> | auto-fill | auto-fit ,"
+// (or "repeat( <positive-integer> | auto-fill ," when aForSubgrid is true)
+// and stop after the comma. Return true when parsing succeeds,
+// with aRepetitions set to the number of repetitions and aRepeatAutoEnum set
+// to an enum value for auto-fill | auto-fit (it's not set at all when
+// <positive-integer> was parsed).
+bool
+CSSParserImpl::ParseGridTrackRepeatIntro(bool aForSubgrid,
+ int32_t* aRepetitions,
+ Maybe<int32_t>* aRepeatAutoEnum)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (mToken.mType == eCSSToken_Ident) {
+ if (mToken.mIdent.LowerCaseEqualsLiteral("auto-fill")) {
+ aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FILL);
+ } else if (!aForSubgrid &&
+ mToken.mIdent.LowerCaseEqualsLiteral("auto-fit")) {
+ aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FIT);
+ } else {
+ return false;
+ }
+ *aRepetitions = 1;
+ } else if (mToken.mType == eCSSToken_Number) {
+ if (!(mToken.mIntegerValid &&
+ mToken.mInteger > 0)) {
+ return false;
+ }
+ *aRepetitions = std::min(mToken.mInteger, GRID_TEMPLATE_MAX_REPETITIONS);
+ } else {
+ return false;
+ }
+
+ if (!ExpectSymbol(',', true)) {
+ return false;
+ }
+ return true;
+}
+
+// Assuming the 'repeat(' function token has already been consumed,
+// parse the rest of
+// repeat( <positive-integer> | auto-fill | auto-fit ,
+// [ <line-names>? <track-size> ]+ <line-names>? )
+// Append to the linked list whose end is given by |aTailPtr|,
+// and update |aTailPtr| to point to the new end of the list.
+// Note: only one <track-size> is allowed for auto-fill/fit
+bool
+CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr)
+{
+ int32_t repetitions;
+ Maybe<int32_t> repeatAutoEnum;
+ if (!ParseGridTrackRepeatIntro(false, &repetitions, &repeatAutoEnum)) {
+ return false;
+ }
+
+ // Parse [ <line-names>? <track-size> ]+ <line-names>?
+ // but keep the first and last <line-names> separate
+ // because they'll need to be joined.
+ // http://dev.w3.org/csswg/css-grid/#repeat-notation
+ nsCSSValue firstLineNames;
+ nsCSSValue trackSize;
+ nsCSSValue lastLineNames;
+ // Optional
+ if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error) {
+ return false;
+ }
+ // Required
+ GridTrackSizeFlags flags = repeatAutoEnum.isSome()
+ ? GridTrackSizeFlags::eFixedTrackSize
+ : GridTrackSizeFlags::eDefaultTrackSize;
+ if (ParseGridTrackSize(trackSize, flags) != CSSParseResult::Ok) {
+ return false;
+ }
+ // Use nsAutoPtr to free the list in case of early return.
+ nsAutoPtr<nsCSSValueList> firstTrackSizeItemAuto(new nsCSSValueList);
+ firstTrackSizeItemAuto->mValue = trackSize;
+
+ nsCSSValueList* item = firstTrackSizeItemAuto;
+ for (;;) {
+ // Optional
+ if (ParseGridLineNames(lastLineNames) == CSSParseResult::Error) {
+ return false;
+ }
+
+ if (ExpectSymbol(')', true)) {
+ break;
+ }
+
+ // <auto-repeat> only accepts a single track size:
+ // <line-names>? <fixed-size> <line-names>?
+ if (repeatAutoEnum.isSome()) {
+ REPORT_UNEXPECTED(PEMoreThanOneGridRepeatTrackSize);
+ return false;
+ }
+
+ // Required
+ if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) {
+ return false;
+ }
+
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ item->mValue = lastLineNames;
+ // Do not append to this list at the next iteration.
+ lastLineNames.Reset();
+
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ item->mValue = trackSize;
+ }
+ nsCSSValueList* lastTrackSizeItem = item;
+
+ // [ <line-names>? <track-size> ]+ <line-names>? is now parsed into:
+ // * firstLineNames: the first <line-names>
+ // * a linked list of odd length >= 1, from firstTrackSizeItem
+ // (the first <track-size>) to lastTrackSizeItem (the last),
+ // with the <line-names> sublists in between
+ // * lastLineNames: the last <line-names>
+
+ if (repeatAutoEnum.isSome()) {
+ // Instead of hooking up this list into the flat track/name list as usual,
+ // we create a pair(Int, List) where the first value is the auto-fill/fit
+ // keyword and the second is the list to repeat. There are three items
+ // in this list, the first is the list of line names before the track size,
+ // the second item is the track size, and the last item is the list of line
+ // names after the track size. Note that the line names are NOT merged
+ // with any line names before/after the repeat() itself.
+ nsCSSValue listValue;
+ nsCSSValueList* list = listValue.SetListValue();
+ list->mValue = firstLineNames;
+ list = list->mNext = new nsCSSValueList;
+ list->mValue = trackSize;
+ list = list->mNext = new nsCSSValueList;
+ list->mValue = lastLineNames;
+ nsCSSValue kwd;
+ kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated);
+ *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
+ (*aTailPtr)->mValue.SetPairValue(kwd, listValue);
+ // Append an empty list since the caller expects that to represent the names
+ // that follows the repeat() function.
+ *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
+ return true;
+ }
+
+ // Join the last and first <line-names> (in that order.)
+ // For example, repeat(3, (a) 100px (b) 200px (c)) results in
+ // (a) 100px (b) 200px (c a) 100px (b) 200px (c a) 100px (b) 200px (c)
+ // This is (c a).
+ // Make deep copies: the originals will be moved.
+ nsCSSValue joinerLineNames;
+ {
+ nsCSSValueList* target = nullptr;
+ if (lastLineNames.GetUnit() != eCSSUnit_Null) {
+ target = joinerLineNames.SetListValue();
+ nsCSSValueList* source = lastLineNames.GetListValue();
+ for (;;) {
+ target->mValue = source->mValue;
+ source = source->mNext;
+ if (!source) {
+ break;
+ }
+ target->mNext = new nsCSSValueList;
+ target = target->mNext;
+ }
+ }
+
+ if (firstLineNames.GetUnit() != eCSSUnit_Null) {
+ if (target) {
+ target->mNext = new nsCSSValueList;
+ target = target->mNext;
+ } else {
+ target = joinerLineNames.SetListValue();
+ }
+ nsCSSValueList* source = firstLineNames.GetListValue();
+ for (;;) {
+ target->mValue = source->mValue;
+ source = source->mNext;
+ if (!source) {
+ break;
+ }
+ target->mNext = new nsCSSValueList;
+ target = target->mNext;
+ }
+ }
+ }
+
+ // Join our first <line-names> with the one before repeat().
+ // (a) repeat(1, (b) 20px) expands to (a b) 20px
+ nsCSSValueList* previousItemBeforeRepeat = *aTailPtr;
+ ConcatLineNames(previousItemBeforeRepeat->mValue, firstLineNames);
+
+ // Move our linked list
+ // (first to last <track-size>, with the <line-names> sublists in between).
+ // This is the first repetition.
+ NS_ASSERTION(previousItemBeforeRepeat->mNext == nullptr,
+ "Expected the end of a linked list");
+ previousItemBeforeRepeat->mNext = firstTrackSizeItemAuto.forget();
+ nsCSSValueList* firstTrackSizeItem = previousItemBeforeRepeat->mNext;
+ nsCSSValueList* tail = lastTrackSizeItem;
+
+ // Repeat |repetitions - 1| more times:
+ // * the joiner <line-names>
+ // * the linked list
+ // (first to last <track-size>, with the <line-names> sublists in between)
+ MOZ_ASSERT(repetitions > 0, "Expected positive repetitions");
+ while (--repetitions) {
+ tail->mNext = new nsCSSValueList;
+ tail = tail->mNext;
+ tail->mValue = joinerLineNames;
+
+ nsCSSValueList* repeatedItem = firstTrackSizeItem;
+ for (;;) {
+ tail->mNext = new nsCSSValueList;
+ tail = tail->mNext;
+ tail->mValue = repeatedItem->mValue;
+ if (repeatedItem == lastTrackSizeItem) {
+ break;
+ }
+ repeatedItem = repeatedItem->mNext;
+ }
+ }
+
+ // Finally, move our last <line-names>.
+ // Any <line-names> immediately after repeat() will append to it.
+ tail->mNext = new nsCSSValueList;
+ tail = tail->mNext;
+ tail->mValue = lastLineNames;
+
+ *aTailPtr = tail;
+ return true;
+}
+
+bool
+CSSParserImpl::ParseGridTrackList(nsCSSPropertyID aPropID,
+ GridTrackListFlags aFlags)
+{
+ nsCSSValue value;
+ nsCSSValue firstLineNames;
+ if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
+ !ParseGridTrackListWithFirstLineNames(value, firstLineNames, aFlags)) {
+ return false;
+ }
+ AppendValue(aPropID, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID)
+{
+ nsCSSValue value;
+ if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
+ AppendValue(aPropID, value);
+ return true;
+ }
+
+ nsSubstring* ident = NextIdent();
+ if (ident) {
+ if (ident->LowerCaseEqualsLiteral("subgrid")) {
+ if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) {
+ REPORT_UNEXPECTED(PESubgridNotSupported);
+ return false;
+ }
+ if (!ParseOptionalLineNameListAfterSubgrid(value)) {
+ return false;
+ }
+ AppendValue(aPropID, value);
+ return true;
+ }
+ UngetToken();
+ }
+
+ return ParseGridTrackList(aPropID);
+}
+
+bool
+CSSParserImpl::ParseGridTemplateAreasLine(const nsAutoString& aInput,
+ css::GridTemplateAreasValue* aAreas,
+ nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices)
+{
+ aAreas->mTemplates.AppendElement(mToken.mIdent);
+
+ nsCSSGridTemplateAreaScanner scanner(aInput);
+ nsCSSGridTemplateAreaToken token;
+ css::GridNamedArea* currentArea = nullptr;
+ uint32_t row = aAreas->NRows();
+ // Column numbers starts at 1, but we might not have any, eg
+ // grid-template-areas:""; which will result in mNColumns == 0.
+ uint32_t column = 0;
+ while (scanner.Next(token)) {
+ ++column;
+ if (token.isTrash) {
+ return false;
+ }
+ if (currentArea) {
+ if (token.mName == currentArea->mName) {
+ if (currentArea->mRowStart == row) {
+ // Next column in the first row of this named area.
+ currentArea->mColumnEnd++;
+ }
+ continue;
+ }
+ // We're exiting |currentArea|, so currentArea is ending at |column|.
+ // Make sure that this is consistent with currentArea on previous rows:
+ if (currentArea->mColumnEnd != column) {
+ NS_ASSERTION(currentArea->mRowStart != row,
+ "Inconsistent column end for the first row of a named area.");
+ // Not a rectangle
+ return false;
+ }
+ currentArea = nullptr;
+ }
+ if (!token.mName.IsEmpty()) {
+ // Named cell that doesn't have a cell with the same name on its left.
+
+ // Check if this is the continuation of an existing named area:
+ uint32_t index;
+ if (aAreaIndices.Get(token.mName, &index)) {
+ MOZ_ASSERT(index < aAreas->mNamedAreas.Length(),
+ "Invalid aAreaIndices hash table");
+ currentArea = &aAreas->mNamedAreas[index];
+ if (currentArea->mColumnStart != column ||
+ currentArea->mRowEnd != row) {
+ // Existing named area, but not forming a rectangle
+ return false;
+ }
+ // Next row of an existing named area
+ currentArea->mRowEnd++;
+ } else {
+ // New named area
+ aAreaIndices.Put(token.mName, aAreas->mNamedAreas.Length());
+ currentArea = aAreas->mNamedAreas.AppendElement();
+ currentArea->mName = token.mName;
+ // For column or row N (starting at 1),
+ // the start line is N, the end line is N + 1
+ currentArea->mColumnStart = column;
+ currentArea->mColumnEnd = column + 1;
+ currentArea->mRowStart = row;
+ currentArea->mRowEnd = row + 1;
+ }
+ }
+ }
+ if (currentArea && currentArea->mColumnEnd != column + 1) {
+ NS_ASSERTION(currentArea->mRowStart != row,
+ "Inconsistent column end for the first row of a named area.");
+ // Not a rectangle
+ return false;
+ }
+
+ // On the first row, set the number of columns
+ // that grid-template-areas contributes to the explicit grid.
+ // On other rows, check that the number of columns is consistent
+ // between rows.
+ if (row == 1) {
+ aAreas->mNColumns = column;
+ } else if (aAreas->mNColumns != column) {
+ return false;
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseGridTemplateAreas()
+{
+ nsCSSValue value;
+ if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
+ AppendValue(eCSSProperty_grid_template_areas, value);
+ return true;
+ }
+
+ RefPtr<css::GridTemplateAreasValue> areas =
+ new css::GridTemplateAreasValue();
+ nsDataHashtable<nsStringHashKey, uint32_t> areaIndices;
+ for (;;) {
+ if (!GetToken(true)) {
+ break;
+ }
+ if (eCSSToken_String != mToken.mType) {
+ UngetToken();
+ break;
+ }
+ if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) {
+ return false;
+ }
+ }
+
+ if (areas->NRows() == 0) {
+ return false;
+ }
+
+ AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas));
+ return true;
+}
+
+// [ auto-flow && dense? ] <'grid-auto-columns'>? |
+// <'grid-template-columns'>
+bool
+CSSParserImpl::ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand)
+{
+ if (aForGridShorthand) {
+ auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_COLUMN);
+ if (res == CSSParseResult::Error) {
+ return false;
+ }
+ if (res == CSSParseResult::Ok) {
+ nsCSSValue value(eCSSUnit_None);
+ AppendValue(eCSSProperty_grid_template_columns, value);
+ return true;
+ }
+ }
+ return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns);
+}
+
+bool
+CSSParserImpl::ParseGridTemplate(bool aForGridShorthand)
+{
+ // none |
+ // subgrid |
+ // <'grid-template-rows'> / <'grid-template-columns'> |
+ // [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list>]?
+ // or additionally when aForGridShorthand is true:
+ // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
+ nsCSSValue value;
+ if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ AppendValue(eCSSProperty_grid_template_areas, value);
+ AppendValue(eCSSProperty_grid_template_rows, value);
+ AppendValue(eCSSProperty_grid_template_columns, value);
+ return true;
+ }
+
+ // 'none' can appear either by itself,
+ // or as the beginning of <'grid-template-rows'> / <'grid-template-columns'>
+ if (ParseSingleTokenVariant(value, VARIANT_NONE, nullptr)) {
+ AppendValue(eCSSProperty_grid_template_rows, value);
+ AppendValue(eCSSProperty_grid_template_areas, value);
+ if (ExpectSymbol('/', true)) {
+ return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
+ }
+ AppendValue(eCSSProperty_grid_template_columns, value);
+ return true;
+ }
+
+ // 'subgrid' can appear either by itself,
+ // or as the beginning of <'grid-template-rows'> / <'grid-template-columns'>
+ nsSubstring* ident = NextIdent();
+ if (ident) {
+ if (ident->LowerCaseEqualsLiteral("subgrid")) {
+ if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) {
+ REPORT_UNEXPECTED(PESubgridNotSupported);
+ return false;
+ }
+ if (!ParseOptionalLineNameListAfterSubgrid(value)) {
+ return false;
+ }
+ AppendValue(eCSSProperty_grid_template_rows, value);
+ AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(eCSSUnit_None));
+ if (ExpectSymbol('/', true)) {
+ return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
+ }
+ if (value.GetListValue()->mNext) {
+ // Non-empty <line-name-list> after 'subgrid'.
+ // This is only valid as part of <'grid-template-rows'>,
+ // which must be followed by a slash.
+ return false;
+ }
+ // 'subgrid' by itself sets both grid-template-rows/columns.
+ AppendValue(eCSSProperty_grid_template_columns, value);
+ return true;
+ }
+ UngetToken();
+ }
+
+ // [ <line-names>? ] here is ambiguous:
+ // it can be either the start of a <track-list> (in a <'grid-template-rows'>),
+ // or the start of [ <line-names>? <string> <track-size>? <line-names>? ]+
+ nsCSSValue firstLineNames;
+ if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
+ !GetToken(true)) {
+ return false;
+ }
+ if (mToken.mType == eCSSToken_String) {
+ // It's the [ <line-names>? <string> <track-size>? <line-names>? ]+ case.
+ if (!ParseGridTemplateAfterString(firstLineNames)) {
+ return false;
+ }
+ // Parse an optional [ / <explicit-track-list> ] as the columns value.
+ if (ExpectSymbol('/', true)) {
+ return ParseGridTrackList(eCSSProperty_grid_template_columns,
+ GridTrackListFlags::eExplicitTrackList);
+ }
+ value.SetNoneValue(); // absent means 'none'
+ AppendValue(eCSSProperty_grid_template_columns, value);
+ return true;
+ }
+ UngetToken();
+
+ // Finish parsing <'grid-template-rows'> with the |firstLineNames| we have,
+ // and then parse a mandatory [ / <'grid-template-columns'> ].
+ if (!ParseGridTrackListWithFirstLineNames(value, firstLineNames) ||
+ !ExpectSymbol('/', true)) {
+ return false;
+ }
+ AppendValue(eCSSProperty_grid_template_rows, value);
+ value.SetNoneValue();
+ AppendValue(eCSSProperty_grid_template_areas, value);
+ return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
+}
+
+// Helper for parsing the 'grid-template' shorthand:
+// Parse [ <line-names>? <string> <track-size>? <line-names>? ]+
+// with a <line-names>? already consumed, stored in |aFirstLineNames|,
+// and the current token a <string>
+bool
+CSSParserImpl::ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames)
+{
+ MOZ_ASSERT(mToken.mType == eCSSToken_String,
+ "ParseGridTemplateAfterString called with a non-string token");
+
+ nsCSSValue rowsValue;
+ RefPtr<css::GridTemplateAreasValue> areas =
+ new css::GridTemplateAreasValue();
+ nsDataHashtable<nsStringHashKey, uint32_t> areaIndices;
+ nsCSSValueList* rowsItem = rowsValue.SetListValue();
+ rowsItem->mValue = aFirstLineNames;
+
+ for (;;) {
+ if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) {
+ return false;
+ }
+
+ rowsItem->mNext = new nsCSSValueList;
+ rowsItem = rowsItem->mNext;
+ CSSParseResult result = ParseGridTrackSize(rowsItem->mValue);
+ if (result == CSSParseResult::Error) {
+ return false;
+ }
+ if (result == CSSParseResult::NotFound) {
+ rowsItem->mValue.SetAutoValue();
+ }
+
+ rowsItem->mNext = new nsCSSValueList;
+ rowsItem = rowsItem->mNext;
+ result = ParseGridLineNames(rowsItem->mValue);
+ if (result == CSSParseResult::Error) {
+ return false;
+ }
+ if (result == CSSParseResult::Ok) {
+ // Append to the same list as the previous call to ParseGridLineNames.
+ result = ParseGridLineNames(rowsItem->mValue);
+ if (result == CSSParseResult::Error) {
+ return false;
+ }
+ if (result == CSSParseResult::Ok) {
+ // Parsed <line-name> twice.
+ // The property value can not end here, we expect a string next.
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (eCSSToken_String != mToken.mType) {
+ UngetToken();
+ return false;
+ }
+ continue;
+ }
+ }
+
+ // Did not find a <line-names>.
+ // Next, we expect either a string or the end of the property value.
+ if (!GetToken(true)) {
+ break;
+ }
+ if (eCSSToken_String != mToken.mType) {
+ UngetToken();
+ break;
+ }
+ }
+
+ AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas));
+ AppendValue(eCSSProperty_grid_template_rows, rowsValue);
+ return true;
+}
+
+// <'grid-template'> |
+// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? |
+// [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
+bool
+CSSParserImpl::ParseGrid()
+{
+ nsCSSValue value;
+ if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ for (const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_grid);
+ *subprops != eCSSProperty_UNKNOWN; ++subprops) {
+ AppendValue(*subprops, value);
+ }
+ return true;
+ }
+
+ // https://drafts.csswg.org/css-grid/#grid-shorthand
+ // "Also, the gutter properties are reset by this shorthand,
+ // even though they can't be set by it."
+ value.SetFloatValue(0.0f, eCSSUnit_Pixel);
+ AppendValue(eCSSProperty_grid_row_gap, value);
+ AppendValue(eCSSProperty_grid_column_gap, value);
+
+ // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
+ auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_ROW);
+ if (res == CSSParseResult::Error) {
+ return false;
+ }
+ if (res == CSSParseResult::Ok) {
+ value.SetAutoValue();
+ AppendValue(eCSSProperty_grid_auto_columns, value);
+ nsCSSValue none(eCSSUnit_None);
+ AppendValue(eCSSProperty_grid_template_areas, none);
+ AppendValue(eCSSProperty_grid_template_rows, none);
+ if (!ExpectSymbol('/', true)) {
+ return false;
+ }
+ return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns);
+ }
+
+ // Set remaining subproperties that might not be set by ParseGridTemplate to
+ // their initial values and then parse <'grid-template'> |
+ // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? .
+ value.SetIntValue(NS_STYLE_GRID_AUTO_FLOW_ROW, eCSSUnit_Enumerated);
+ AppendValue(eCSSProperty_grid_auto_flow, value);
+ value.SetAutoValue();
+ AppendValue(eCSSProperty_grid_auto_rows, value);
+ AppendValue(eCSSProperty_grid_auto_columns, value);
+ return ParseGridTemplate(true);
+}
+
+// Parse [ auto-flow && dense? ] <'grid-auto-[rows|columns]'>? for the 'grid'
+// shorthand. If aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW then we're
+// parsing row values, otherwise column values.
+CSSParseResult
+CSSParserImpl::ParseGridShorthandAutoProps(int32_t aAutoFlowAxis)
+{
+ MOZ_ASSERT(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ||
+ aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_COLUMN);
+ if (!GetToken(true)) {
+ return CSSParseResult::NotFound;
+ }
+ // [ auto-flow && dense? ]
+ int32_t autoFlowValue = 0;
+ if (mToken.mType == eCSSToken_Ident) {
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+ if (keyword == eCSSKeyword_auto_flow) {
+ autoFlowValue = aAutoFlowAxis;
+ if (GetToken(true)) {
+ if (mToken.mType == eCSSToken_Ident &&
+ nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_dense) {
+ autoFlowValue |= NS_STYLE_GRID_AUTO_FLOW_DENSE;
+ } else {
+ UngetToken();
+ }
+ }
+ } else if (keyword == eCSSKeyword_dense) {
+ if (!GetToken(true)) {
+ return CSSParseResult::Error;
+ }
+ if (mToken.mType != eCSSToken_Ident ||
+ nsCSSKeywords::LookupKeyword(mToken.mIdent) != eCSSKeyword_auto_flow) {
+ UngetToken();
+ return CSSParseResult::Error;
+ }
+ autoFlowValue = aAutoFlowAxis | NS_STYLE_GRID_AUTO_FLOW_DENSE;
+ }
+ }
+ if (autoFlowValue) {
+ nsCSSValue value;
+ value.SetIntValue(autoFlowValue, eCSSUnit_Enumerated);
+ AppendValue(eCSSProperty_grid_auto_flow, value);
+ } else {
+ UngetToken();
+ return CSSParseResult::NotFound;
+ }
+
+ // <'grid-auto-[rows|columns]'>?
+ nsCSSValue autoTrackValue;
+ CSSParseResult result = ParseGridTrackSize(autoTrackValue);
+ if (result == CSSParseResult::Error) {
+ return result;
+ }
+ if (result == CSSParseResult::NotFound) {
+ autoTrackValue.SetAutoValue();
+ }
+ AppendValue(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ?
+ eCSSProperty_grid_auto_rows : eCSSProperty_grid_auto_columns,
+ autoTrackValue);
+ return CSSParseResult::Ok;
+}
+
+// Parse a <grid-line>.
+// If successful, set aValue to eCSSUnit_Auto,
+// or a eCSSUnit_List containing, in that order:
+//
+// * An optional eCSSUnit_Enumerated marking a "span" keyword.
+// * An optional eCSSUnit_Integer
+// * An optional eCSSUnit_Ident
+//
+// At least one of eCSSUnit_Integer or eCSSUnit_Ident is present.
+bool
+CSSParserImpl::ParseGridLine(nsCSSValue& aValue)
+{
+ // <grid-line> =
+ // auto |
+ // <custom-ident> |
+ // [ <integer> && <custom-ident>? ] |
+ // [ span && [ <integer> || <custom-ident> ] ]
+ //
+ // Syntactically, this simplifies to:
+ //
+ // <grid-line> =
+ // auto |
+ // [ span? && [ <integer> || <custom-ident> ] ]
+
+ if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) {
+ return true;
+ }
+
+ bool hasSpan = false;
+ bool hasIdent = false;
+ Maybe<int32_t> integer;
+ nsCSSValue ident;
+
+#ifdef MOZ_VALGRIND
+ // Make the contained value be defined even though we really want a
+ // Nothing here. This works around an otherwise difficult to avoid
+ // Memcheck false positive when this is compiled by gcc-5.3 -O2.
+ // See bug 1301856.
+ integer.emplace(0);
+ integer.reset();
+#endif
+
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (mToken.mType == eCSSToken_Ident &&
+ mToken.mIdent.LowerCaseEqualsLiteral("span")) {
+ hasSpan = true;
+ if (!GetToken(true)) {
+ return false;
+ }
+ }
+
+ do {
+ if (!hasIdent &&
+ mToken.mType == eCSSToken_Ident &&
+ ParseCustomIdent(ident, mToken.mIdent, kGridLineKeywords)) {
+ hasIdent = true;
+ } else if (integer.isNothing() &&
+ mToken.mType == eCSSToken_Number &&
+ mToken.mIntegerValid &&
+ mToken.mInteger != 0) {
+ integer.emplace(mToken.mInteger);
+ } else {
+ UngetToken();
+ break;
+ }
+ } while (!(integer.isSome() && hasIdent) && GetToken(true));
+
+ // Require at least one of <integer> or <custom-ident>
+ if (!(integer.isSome() || hasIdent)) {
+ return false;
+ }
+
+ if (!hasSpan && GetToken(true)) {
+ if (mToken.mType == eCSSToken_Ident &&
+ mToken.mIdent.LowerCaseEqualsLiteral("span")) {
+ hasSpan = true;
+ } else {
+ UngetToken();
+ }
+ }
+
+ nsCSSValueList* item = aValue.SetListValue();
+ if (hasSpan) {
+ // Given "span", a negative <integer> is invalid.
+ if (integer.isSome() && integer.ref() < 0) {
+ return false;
+ }
+ // '1' here is a dummy value.
+ // The mere presence of eCSSUnit_Enumerated indicates a "span" keyword.
+ item->mValue.SetIntValue(1, eCSSUnit_Enumerated);
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ }
+ if (integer.isSome()) {
+ item->mValue.SetIntValue(integer.ref(), eCSSUnit_Integer);
+ if (hasIdent) {
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ }
+ }
+ if (hasIdent) {
+ item->mValue = ident;
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID)
+{
+ nsCSSValue value;
+ if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) ||
+ ParseGridLine(value)) {
+ AppendValue(aPropID, value);
+ return true;
+ }
+ return false;
+}
+
+// If |aFallback| is a List containing a single Ident, set |aValue| to that.
+// Otherwise, set |aValue| to Auto.
+// Used with |aFallback| from ParseGridLine()
+static void
+HandleGridLineFallback(const nsCSSValue& aFallback, nsCSSValue& aValue)
+{
+ if (aFallback.GetUnit() == eCSSUnit_List &&
+ aFallback.GetListValue()->mValue.GetUnit() == eCSSUnit_Ident &&
+ !aFallback.GetListValue()->mNext) {
+ aValue = aFallback;
+ } else {
+ aValue.SetAutoValue();
+ }
+}
+
+bool
+CSSParserImpl::ParseGridColumnRow(nsCSSPropertyID aStartPropID,
+ nsCSSPropertyID aEndPropID)
+{
+ nsCSSValue value;
+ nsCSSValue secondValue;
+ if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ AppendValue(aStartPropID, value);
+ AppendValue(aEndPropID, value);
+ return true;
+ }
+
+ if (!ParseGridLine(value)) {
+ return false;
+ }
+ if (GetToken(true)) {
+ if (mToken.IsSymbol('/')) {
+ if (ParseGridLine(secondValue)) {
+ AppendValue(aStartPropID, value);
+ AppendValue(aEndPropID, secondValue);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ UngetToken();
+ }
+
+ // A single <custom-ident> is repeated to both properties,
+ // anything else sets the grid-{column,row}-end property to 'auto'.
+ HandleGridLineFallback(value, secondValue);
+
+ AppendValue(aStartPropID, value);
+ AppendValue(aEndPropID, secondValue);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseGridArea()
+{
+ nsCSSValue values[4];
+ if (ParseSingleTokenVariant(values[0], VARIANT_INHERIT, nullptr)) {
+ AppendValue(eCSSProperty_grid_row_start, values[0]);
+ AppendValue(eCSSProperty_grid_column_start, values[0]);
+ AppendValue(eCSSProperty_grid_row_end, values[0]);
+ AppendValue(eCSSProperty_grid_column_end, values[0]);
+ return true;
+ }
+
+ int32_t i = 0;
+ for (;;) {
+ if (!ParseGridLine(values[i])) {
+ return false;
+ }
+ if (++i == 4 || !GetToken(true)) {
+ break;
+ }
+ if (!mToken.IsSymbol('/')) {
+ UngetToken();
+ break;
+ }
+ }
+
+ MOZ_ASSERT(i >= 1, "should have parsed at least one grid-line (or returned)");
+ if (i < 2) {
+ HandleGridLineFallback(values[0], values[1]);
+ }
+ if (i < 3) {
+ HandleGridLineFallback(values[0], values[2]);
+ }
+ if (i < 4) {
+ HandleGridLineFallback(values[1], values[3]);
+ }
+
+ AppendValue(eCSSProperty_grid_row_start, values[0]);
+ AppendValue(eCSSProperty_grid_column_start, values[1]);
+ AppendValue(eCSSProperty_grid_row_end, values[2]);
+ AppendValue(eCSSProperty_grid_column_end, values[3]);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseGridGap()
+{
+ nsCSSValue first;
+ if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
+ AppendValue(eCSSProperty_grid_row_gap, first);
+ AppendValue(eCSSProperty_grid_column_gap, first);
+ return true;
+ }
+ if (ParseNonNegativeVariant(first, VARIANT_LPCALC, nullptr) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+ nsCSSValue second;
+ auto result = ParseNonNegativeVariant(second, VARIANT_LPCALC, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ }
+ AppendValue(eCSSProperty_grid_row_gap, first);
+ AppendValue(eCSSProperty_grid_column_gap,
+ result == CSSParseResult::NotFound ? first : second);
+ return true;
+}
+
+// normal | [<number> <integer>?]
+bool
+CSSParserImpl::ParseInitialLetter()
+{
+ nsCSSValue value;
+ // 'inherit', 'initial', 'unset', 'none', and 'normal' must be alone
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NORMAL,
+ nullptr)) {
+ nsCSSValue first, second;
+ if (!ParseOneOrLargerNumber(first)) {
+ return false;
+ }
+
+ if (!ParseOneOrLargerInteger(second)) {
+ AppendValue(eCSSProperty_initial_letter, first);
+ return true;
+ } else {
+ RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2);
+ val->Item(0) = first;
+ val->Item(1) = second;
+ value.SetArrayValue(val, eCSSUnit_Array);
+ }
+ }
+ AppendValue(eCSSProperty_initial_letter, value);
+ return true;
+}
+
+// [ $aTable && <overflow-position>? ] ?
+// $aTable is for <content-position> or <self-position>
+bool
+CSSParserImpl::ParseAlignJustifyPosition(nsCSSValue& aResult,
+ const KTableEntry aTable[])
+{
+ nsCSSValue pos, overflowPos;
+ int32_t value = 0;
+ if (ParseEnum(pos, aTable)) {
+ value = pos.GetIntValue();
+ if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) {
+ value |= overflowPos.GetIntValue();
+ }
+ aResult.SetIntValue(value, eCSSUnit_Enumerated);
+ return true;
+ }
+ if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) {
+ if (ParseEnum(pos, aTable)) {
+ aResult.SetIntValue(pos.GetIntValue() | overflowPos.GetIntValue(),
+ eCSSUnit_Enumerated);
+ return true;
+ }
+ return false; // <overflow-position> must be followed by a value in $table
+ }
+ return true;
+}
+
+// auto | normal | stretch | <baseline-position> |
+// [ <self-position> && <overflow-position>? ] |
+// [ legacy && [ left | right | center ] ]
+bool
+CSSParserImpl::ParseJustifyItems()
+{
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ if (MOZ_UNLIKELY(ParseEnum(value, nsCSSProps::kAlignLegacy))) {
+ nsCSSValue legacy;
+ if (!ParseEnum(legacy, nsCSSProps::kAlignLegacyPosition)) {
+ return false; // leading 'legacy' not followed by 'left' etc is an error
+ }
+ value.SetIntValue(value.GetIntValue() | legacy.GetIntValue(),
+ eCSSUnit_Enumerated);
+ } else {
+ if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) {
+ if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) ||
+ value.GetUnit() == eCSSUnit_Null) {
+ return false;
+ }
+ // check for a trailing 'legacy' after 'left' etc
+ auto val = value.GetIntValue();
+ if (val == NS_STYLE_JUSTIFY_CENTER ||
+ val == NS_STYLE_JUSTIFY_LEFT ||
+ val == NS_STYLE_JUSTIFY_RIGHT) {
+ nsCSSValue legacy;
+ if (ParseEnum(legacy, nsCSSProps::kAlignLegacy)) {
+ value.SetIntValue(val | legacy.GetIntValue(), eCSSUnit_Enumerated);
+ }
+ }
+ }
+ }
+ }
+ AppendValue(eCSSProperty_justify_items, value);
+ return true;
+}
+
+// normal | stretch | <baseline-position> |
+// [ <overflow-position>? && <self-position> ]
+bool
+CSSParserImpl::ParseAlignItems()
+{
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalStretchBaseline)) {
+ if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) ||
+ value.GetUnit() == eCSSUnit_Null) {
+ return false;
+ }
+ }
+ }
+ AppendValue(eCSSProperty_align_items, value);
+ return true;
+}
+
+// auto | normal | stretch | <baseline-position> |
+// [ <overflow-position>? && <self-position> ]
+bool
+CSSParserImpl::ParseAlignJustifySelf(nsCSSPropertyID aPropID)
+{
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) {
+ if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) ||
+ value.GetUnit() == eCSSUnit_Null) {
+ return false;
+ }
+ }
+ }
+ AppendValue(aPropID, value);
+ return true;
+}
+
+// normal | <baseline-position> | [ <content-distribution> ||
+// [ <overflow-position>? && <content-position> ] ]
+// (the part after the || is called <*-position> below)
+bool
+CSSParserImpl::ParseAlignJustifyContent(nsCSSPropertyID aPropID)
+{
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalBaseline)) {
+ nsCSSValue fallbackValue;
+ if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) {
+ if (!ParseAlignJustifyPosition(fallbackValue,
+ nsCSSProps::kAlignContentPosition) ||
+ fallbackValue.GetUnit() == eCSSUnit_Null) {
+ return false;
+ }
+ // optional <content-distribution> after <*-position> ...
+ if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) {
+ // ... is missing so the <*-position> is the value, not the fallback
+ value = fallbackValue;
+ fallbackValue.Reset();
+ }
+ } else {
+ // any optional <*-position> is a fallback value
+ if (!ParseAlignJustifyPosition(fallbackValue,
+ nsCSSProps::kAlignContentPosition)) {
+ return false;
+ }
+ }
+ if (fallbackValue.GetUnit() != eCSSUnit_Null) {
+ auto fallback = fallbackValue.GetIntValue();
+ value.SetIntValue(value.GetIntValue() |
+ (fallback << NS_STYLE_ALIGN_ALL_SHIFT),
+ eCSSUnit_Enumerated);
+ }
+ }
+ }
+ AppendValue(aPropID, value);
+ return true;
+}
+
+// place-content: [ normal | <baseline-position> | <content-distribution> |
+// <content-position> ]{1,2}
+bool
+CSSParserImpl::ParsePlaceContent()
+{
+ nsCSSValue first;
+ if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
+ AppendValue(eCSSProperty_align_content, first);
+ AppendValue(eCSSProperty_justify_content, first);
+ return true;
+ }
+ if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalBaseline) &&
+ !ParseEnum(first, nsCSSProps::kAlignContentDistribution) &&
+ !ParseEnum(first, nsCSSProps::kAlignContentPosition)) {
+ return false;
+ }
+ AppendValue(eCSSProperty_align_content, first);
+ nsCSSValue second;
+ if (!ParseAlignEnum(second, nsCSSProps::kAlignNormalBaseline) &&
+ !ParseEnum(second, nsCSSProps::kAlignContentDistribution) &&
+ !ParseEnum(second, nsCSSProps::kAlignContentPosition)) {
+ AppendValue(eCSSProperty_justify_content, first);
+ } else {
+ AppendValue(eCSSProperty_justify_content, second);
+ }
+ return true;
+}
+
+// place-items: <x> [ auto | <x> ]?
+// <x> = [ normal | stretch | <baseline-position> | <self-position> ]
+bool
+CSSParserImpl::ParsePlaceItems()
+{
+ nsCSSValue first;
+ if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
+ AppendValue(eCSSProperty_align_items, first);
+ AppendValue(eCSSProperty_justify_items, first);
+ return true;
+ }
+ if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalStretchBaseline) &&
+ !ParseEnum(first, nsCSSProps::kAlignSelfPosition)) {
+ return false;
+ }
+ AppendValue(eCSSProperty_align_items, first);
+ nsCSSValue second;
+ // Note: 'auto' is valid for justify-items, but not align-items.
+ if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) &&
+ !ParseEnum(second, nsCSSProps::kAlignSelfPosition)) {
+ AppendValue(eCSSProperty_justify_items, first);
+ } else {
+ AppendValue(eCSSProperty_justify_items, second);
+ }
+ return true;
+}
+
+// place-self: [ auto | normal | stretch | <baseline-position> |
+// <self-position> ]{1,2}
+bool
+CSSParserImpl::ParsePlaceSelf()
+{
+ nsCSSValue first;
+ if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
+ AppendValue(eCSSProperty_align_self, first);
+ AppendValue(eCSSProperty_justify_self, first);
+ return true;
+ }
+ if (!ParseAlignEnum(first, nsCSSProps::kAlignAutoNormalStretchBaseline) &&
+ !ParseEnum(first, nsCSSProps::kAlignSelfPosition)) {
+ return false;
+ }
+ AppendValue(eCSSProperty_align_self, first);
+ nsCSSValue second;
+ if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) &&
+ !ParseEnum(second, nsCSSProps::kAlignSelfPosition)) {
+ AppendValue(eCSSProperty_justify_self, first);
+ } else {
+ AppendValue(eCSSProperty_justify_self, second);
+ }
+ return true;
+}
+
+// <color-stop> : <color> [ <percentage> | <length> ]?
+bool
+CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient)
+{
+ nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
+ CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::NotFound) {
+ stop->mIsInterpolationHint = true;
+ }
+
+ // Stop positions do not have to fall between the starting-point and
+ // ending-point, so we don't use ParseNonNegativeVariant.
+ result = ParseVariant(stop->mLocation, VARIANT_LP | VARIANT_CALC, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::NotFound) {
+ if (stop->mIsInterpolationHint) {
+ return false;
+ }
+ stop->mLocation.SetNoneValue();
+ }
+ return true;
+}
+
+// Helper for ParseLinearGradient -- returns true iff aPosition represents a
+// box-position value which was parsed with only edge keywords.
+// e.g. "left top", or "bottom", but not "left 10px"
+//
+// (NOTE: Even though callers may want to exclude explicit "center", we still
+// need to allow for _CENTER here, because omitted position-values (e.g. the
+// x-component of a value like "top") will have been parsed as being *implicit*
+// center. The correct way to disallow *explicit* center is to pass "false" for
+// ParseBoxPositionValues()'s "aAllowExplicitCenter" parameter, before you
+// call this function.)
+static bool
+IsBoxPositionStrictlyEdgeKeywords(nsCSSValuePair& aPosition)
+{
+ const nsCSSValue& xValue = aPosition.mXValue;
+ const nsCSSValue& yValue = aPosition.mYValue;
+ return (xValue.GetUnit() == eCSSUnit_Enumerated &&
+ (xValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_LEFT |
+ NS_STYLE_IMAGELAYER_POSITION_CENTER |
+ NS_STYLE_IMAGELAYER_POSITION_RIGHT)) &&
+ yValue.GetUnit() == eCSSUnit_Enumerated &&
+ (yValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_TOP |
+ NS_STYLE_IMAGELAYER_POSITION_CENTER |
+ NS_STYLE_IMAGELAYER_POSITION_BOTTOM)));
+}
+
+// <gradient>
+// : linear-gradient( <linear-gradient-line>? <color-stops> ')'
+// | radial-gradient( <radial-gradient-line>? <color-stops> ')'
+//
+// <linear-gradient-line> : [ to [left | right] || [top | bottom] ] ,
+// | <legacy-gradient-line>
+// <radial-gradient-line> : [ <shape> || <size> ] [ at <position> ]? ,
+// | [ at <position> ] ,
+// | <legacy-gradient-line>? <legacy-shape-size>?
+// <shape> : circle | ellipse
+// <size> : closest-side | closest-corner | farthest-side | farthest-corner
+// | <length> | [<length> | <percentage>]{2}
+//
+// <legacy-gradient-line> : [ <position> || <angle>] ,
+//
+// <legacy-shape-size> : [ <shape> || <legacy-size> ] ,
+// <legacy-size> : closest-side | closest-corner | farthest-side
+// | farthest-corner | contain | cover
+//
+// <color-stops> : <color-stop> , <color-stop> [, <color-stop>]*
+bool
+CSSParserImpl::ParseLinearGradient(nsCSSValue& aValue,
+ uint8_t aFlags)
+{
+ RefPtr<nsCSSValueGradient> cssGradient
+ = new nsCSSValueGradient(false, aFlags & eGradient_Repeating);
+
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ // Check for "to" syntax (but not if parsing a -webkit-linear-gradient)
+ if (!(aFlags & eGradient_WebkitLegacy) &&
+ mToken.mType == eCSSToken_Ident &&
+ mToken.mIdent.LowerCaseEqualsLiteral("to")) {
+
+ // "to" syntax doesn't allow explicit "center"
+ if (!ParseBoxPositionValues(cssGradient->mBgPos, false, false)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ // [ to [left | right] || [top | bottom] ] ,
+ if (!IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ if (!ExpectSymbol(',', true)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ return ParseGradientColorStops(cssGradient, aValue);
+ }
+
+ if (!(aFlags & eGradient_AnyLegacy)) {
+ // We're parsing an unprefixed linear-gradient, and we tried & failed to
+ // parse a 'to' token above. Put the token back & try to re-parse our
+ // expression as <angle>? <color-stop-list>
+ UngetToken();
+
+ // <angle> ,
+ if (ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr) &&
+ !ExpectSymbol(',', true)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ return ParseGradientColorStops(cssGradient, aValue);
+ }
+
+ // If we get here, we're parsing a prefixed linear-gradient expression. Put
+ // back the first token (which we may have checked for "to" above) and try to
+ // parse expression as <legacy-gradient-line>? <color-stop-list>
+ bool haveGradientLine = IsLegacyGradientLine(mToken.mType, mToken.mIdent);
+ UngetToken();
+
+ if (haveGradientLine) {
+ // Parse a <legacy-gradient-line>
+ cssGradient->mIsLegacySyntax = true;
+ // In -webkit-linear-gradient expressions (handled below), we need to accept
+ // unitless 0 for angles, to match WebKit/Blink.
+ int32_t angleFlags = (aFlags & eGradient_WebkitLegacy) ?
+ VARIANT_ANGLE | VARIANT_ZERO_ANGLE :
+ VARIANT_ANGLE;
+
+ bool haveAngle =
+ ParseSingleTokenVariant(cssGradient->mAngle, angleFlags, nullptr);
+
+ // If we got an angle, we might now have a comma, ending the gradient-line.
+ bool haveAngleComma = haveAngle && ExpectSymbol(',', true);
+
+ // If we're webkit-prefixed & didn't get an angle,
+ // OR if we're moz-prefixed & didn't get an angle+comma,
+ // then proceed to parse a box-position.
+ if (((aFlags & eGradient_WebkitLegacy) && !haveAngle) ||
+ ((aFlags & eGradient_MozLegacy) && !haveAngleComma)) {
+ // (Note: 3rd arg controls whether the "center" keyword is allowed.
+ // -moz-linear-gradient allows it; -webkit-linear-gradient does not.)
+ if (!ParseBoxPositionValues(cssGradient->mBgPos, false,
+ (aFlags & eGradient_MozLegacy))) {
+ SkipUntil(')');
+ return false;
+ }
+
+ // -webkit-linear-gradient only supports edge keywords here.
+ if ((aFlags & eGradient_WebkitLegacy) &&
+ !IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ if (!ExpectSymbol(',', true) &&
+ // If we didn't already get an angle, and we're not -webkit prefixed,
+ // we can parse an angle+comma now. Otherwise it's an error.
+ (haveAngle ||
+ (aFlags & eGradient_WebkitLegacy) ||
+ !ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE,
+ nullptr) ||
+ // now we better have a comma
+ !ExpectSymbol(',', true))) {
+ SkipUntil(')');
+ return false;
+ }
+ }
+ }
+
+ return ParseGradientColorStops(cssGradient, aValue);
+}
+
+bool
+CSSParserImpl::ParseRadialGradient(nsCSSValue& aValue,
+ uint8_t aFlags)
+{
+ RefPtr<nsCSSValueGradient> cssGradient
+ = new nsCSSValueGradient(true, aFlags & eGradient_Repeating);
+
+ // [ <shape> || <size> ]
+ bool haveShape =
+ ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
+ nsCSSProps::kRadialGradientShapeKTable);
+
+ bool haveSize =
+ ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD,
+ (aFlags & eGradient_AnyLegacy) ?
+ nsCSSProps::kRadialGradientLegacySizeKTable :
+ nsCSSProps::kRadialGradientSizeKTable);
+ if (haveSize) {
+ if (!haveShape) {
+ // <size> <shape>
+ haveShape =
+ ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
+ nsCSSProps::kRadialGradientShapeKTable);
+ }
+ } else if (!(aFlags & eGradient_AnyLegacy)) {
+ // Save RadialShape before parsing RadiusX because RadialShape and
+ // RadiusX share the storage.
+ int32_t shape =
+ cssGradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated ?
+ cssGradient->GetRadialShape().GetIntValue() : -1;
+ // <length> | [<length> | <percentage>]{2}
+ cssGradient->mIsExplicitSize = true;
+ haveSize =
+ ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusX(), VARIANT_LP,
+ nullptr);
+ if (!haveSize) {
+ // It was not an explicit size after all.
+ // Note that ParseNonNegativeVariant may have put something
+ // invalid into our storage, but only in the case where it was
+ // rejected only for being negative. Since this means the token
+ // was a length or a percentage, we know it's not valid syntax
+ // (which must be a comma, the 'at' keyword, or a color), so we
+ // know this value will be dropped. This means it doesn't matter
+ // that we have something invalid in our storage.
+ cssGradient->mIsExplicitSize = false;
+ } else {
+ // vertical extent is optional
+ bool haveYSize =
+ ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusY(),
+ VARIANT_LP, nullptr);
+ if (!haveShape) {
+ nsCSSValue shapeValue;
+ haveShape =
+ ParseSingleTokenVariant(shapeValue, VARIANT_KEYWORD,
+ nsCSSProps::kRadialGradientShapeKTable);
+ if (haveShape) {
+ shape = shapeValue.GetIntValue();
+ }
+ }
+ if (haveYSize
+ ? shape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR
+ : cssGradient->GetRadiusX().GetUnit() == eCSSUnit_Percent ||
+ shape == NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL) {
+ SkipUntil(')');
+ return false;
+ }
+ }
+ }
+
+ if ((haveShape || haveSize) && ExpectSymbol(',', true)) {
+ // [ <shape> || <size> ] ,
+ return ParseGradientColorStops(cssGradient, aValue);
+ }
+
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ if (!(aFlags & eGradient_AnyLegacy)) {
+ if (mToken.mType == eCSSToken_Ident &&
+ mToken.mIdent.LowerCaseEqualsLiteral("at")) {
+ // [ <shape> || <size> ]? at <position> ,
+ if (!ParseBoxPositionValues(cssGradient->mBgPos, false) ||
+ !ExpectSymbol(',', true)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ return ParseGradientColorStops(cssGradient, aValue);
+ }
+
+ // <color-stops> only
+ UngetToken();
+ return ParseGradientColorStops(cssGradient, aValue);
+ }
+ MOZ_ASSERT(!cssGradient->mIsExplicitSize);
+
+ nsCSSTokenType ty = mToken.mType;
+ nsString id = mToken.mIdent;
+ UngetToken();
+
+ // <legacy-gradient-line>
+ bool haveGradientLine = false;
+ // if we already encountered a shape or size,
+ // we can not have a gradient-line in legacy syntax
+ if (!haveShape && !haveSize) {
+ haveGradientLine = IsLegacyGradientLine(ty, id);
+ }
+ if (haveGradientLine) {
+ // Note: -webkit-radial-gradient() doesn't accept angles.
+ bool haveAngle = (aFlags & eGradient_WebkitLegacy)
+ ? false
+ : ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr);
+
+ // If we got an angle, we might now have a comma, ending the gradient-line
+ if (!haveAngle || !ExpectSymbol(',', true)) {
+ if (!ParseBoxPositionValues(cssGradient->mBgPos, false)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ if (!ExpectSymbol(',', true) &&
+ // If we didn't already get an angle, and we're not -webkit prefixed,
+ // can parse an angle+comma now. Otherwise it's an error.
+ (haveAngle ||
+ (aFlags & eGradient_WebkitLegacy) ||
+ !ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE,
+ nullptr) ||
+ // now we better have a comma
+ !ExpectSymbol(',', true))) {
+ SkipUntil(')');
+ return false;
+ }
+ }
+
+ if (cssGradient->mAngle.GetUnit() != eCSSUnit_None) {
+ cssGradient->mIsLegacySyntax = true;
+ }
+ }
+
+ // radial gradients might have a shape and size here for legacy syntax
+ if (!haveShape && !haveSize) {
+ haveShape =
+ ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
+ nsCSSProps::kRadialGradientShapeKTable);
+ haveSize =
+ ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD,
+ nsCSSProps::kRadialGradientLegacySizeKTable);
+
+ // could be in either order
+ if (!haveShape) {
+ haveShape =
+ ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
+ nsCSSProps::kRadialGradientShapeKTable);
+ }
+ }
+
+ if ((haveShape || haveSize) && !ExpectSymbol(',', true)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ return ParseGradientColorStops(cssGradient, aValue);
+}
+
+bool
+CSSParserImpl::IsLegacyGradientLine(const nsCSSTokenType& aType,
+ const nsString& aId)
+{
+ // N.B. ParseBoxPositionValues is not guaranteed to put back
+ // everything it scanned if it fails, so we must only call it
+ // if there is no alternative to consuming a <box-position>.
+ // ParseVariant, as used here, will either succeed and consume
+ // a single token, or fail and consume none, so we can be more
+ // cavalier about calling it.
+
+ bool haveGradientLine = false;
+ switch (aType) {
+ case eCSSToken_Percentage:
+ case eCSSToken_Number:
+ case eCSSToken_Dimension:
+ haveGradientLine = true;
+ break;
+
+ case eCSSToken_Function:
+ if (aId.LowerCaseEqualsLiteral("calc") ||
+ aId.LowerCaseEqualsLiteral("-moz-calc")) {
+ haveGradientLine = true;
+ break;
+ }
+ MOZ_FALLTHROUGH;
+ case eCSSToken_ID:
+ case eCSSToken_Hash:
+ // this is a color
+ break;
+
+ case eCSSToken_Ident: {
+ // This is only a gradient line if it's a box position keyword.
+ nsCSSKeyword kw = nsCSSKeywords::LookupKeyword(aId);
+ int32_t junk;
+ if (kw != eCSSKeyword_UNKNOWN &&
+ nsCSSProps::FindKeyword(kw, nsCSSProps::kImageLayerPositionKTable,
+ junk)) {
+ haveGradientLine = true;
+ }
+ break;
+ }
+
+ default:
+ // error
+ break;
+ }
+
+ return haveGradientLine;
+}
+
+bool
+CSSParserImpl::ParseGradientColorStops(nsCSSValueGradient* aGradient,
+ nsCSSValue& aValue)
+{
+ // At least two color stops are required
+ if (!ParseColorStop(aGradient) ||
+ !ExpectSymbol(',', true) ||
+ !ParseColorStop(aGradient)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ // Additional color stops
+ while (ExpectSymbol(',', true)) {
+ if (!ParseColorStop(aGradient)) {
+ SkipUntil(')');
+ return false;
+ }
+ }
+
+ if (!ExpectSymbol(')', true)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ // Check if interpolation hints are in the correct location
+ bool previousPointWasInterpolationHint = true;
+ for (size_t x = 0; x < aGradient->mStops.Length(); x++) {
+ bool isInterpolationHint = aGradient->mStops[x].mIsInterpolationHint;
+ if (isInterpolationHint && previousPointWasInterpolationHint) {
+ return false;
+ }
+ previousPointWasInterpolationHint = isInterpolationHint;
+ }
+
+ if (previousPointWasInterpolationHint) {
+ return false;
+ }
+
+ aValue.SetGradientValue(aGradient);
+ return true;
+}
+
+// Parses the x or y component of a -webkit-gradient() <point> expression.
+// See ParseWebkitGradientPoint() documentation for more.
+bool
+CSSParserImpl::ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
+ bool aIsHorizontal)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ // Keyword tables to use for keyword-matching
+ // (Keyword order is important; we assume the index can be multiplied by 50%
+ // to convert to a percent-valued component.)
+ static const nsCSSKeyword kHorizKeywords[] = {
+ eCSSKeyword_left, // 0%
+ eCSSKeyword_center, // 50%
+ eCSSKeyword_right // 100%
+ };
+ static const nsCSSKeyword kVertKeywords[] = {
+ eCSSKeyword_top, // 0%
+ eCSSKeyword_center, // 50%
+ eCSSKeyword_bottom // 100%
+ };
+ static const size_t kNumKeywords = MOZ_ARRAY_LENGTH(kHorizKeywords);
+ static_assert(kNumKeywords == MOZ_ARRAY_LENGTH(kVertKeywords),
+ "Horizontal & vertical keyword tables must have same count");
+
+ // Try to parse the component as a number, or a percent, or a
+ // keyword-converted-to-percent.
+ if (mToken.mType == eCSSToken_Number) {
+ aComponent.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel);
+ } else if (mToken.mType == eCSSToken_Percentage) {
+ aComponent.SetPercentValue(mToken.mNumber);
+ } else if (mToken.mType == eCSSToken_Ident) {
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+ if (keyword == eCSSKeyword_UNKNOWN) {
+ return false;
+ }
+ // Choose our keyword table:
+ const nsCSSKeyword* kwTable = aIsHorizontal ? kHorizKeywords : kVertKeywords;
+ // Convert keyword to percent value (0%, 50%, or 100%)
+ bool didAcceptKeyword = false;
+ for (size_t i = 0; i < kNumKeywords; i++) {
+ if (keyword == kwTable[i]) {
+ // 0%, 50%, or 100%:
+ aComponent.SetPercentValue(i * 0.5);
+ didAcceptKeyword = true;
+ break;
+ }
+ }
+ if (!didAcceptKeyword) {
+ return false;
+ }
+ } else {
+ // Unrecognized token type. Put it back. (It might be a closing-paren of an
+ // invalid -webkit-gradient(...) expression, and we need to be sure caller
+ // can see it & stops parsing at that point.)
+ UngetToken();
+ return false;
+ }
+
+ MOZ_ASSERT(aComponent.GetUnit() == eCSSUnit_Pixel ||
+ aComponent.GetUnit() == eCSSUnit_Percent,
+ "If we get here, we should've successfully parsed a number (as a "
+ "pixel length), a percent, or a keyword (converted to percent)");
+ return true;
+}
+
+// This function parses a "<point>" expression for -webkit-gradient(...)
+// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
+// "A point is a pair of space-separated values.
+// The syntax supports numbers, percentages or
+// the keywords top, bottom, left and right
+// for point values."
+//
+// Two additional notes:
+// - WebKit also accepts the "center" keyword (not listed in the text above).
+// - WebKit only accepts horizontal-flavored keywords (left/center/right) in
+// the first ("x") component, and vertical-flavored keywords
+// (top/center/bottom) in the second ("y") component. (This is different
+// from the standard gradient syntax, which accepts both orderings, e.g.
+// "top left" as well as "left top".)
+bool
+CSSParserImpl::ParseWebkitGradientPoint(nsCSSValuePair& aPoint)
+{
+ return ParseWebkitGradientPointComponent(aPoint.mXValue, true) &&
+ ParseWebkitGradientPointComponent(aPoint.mYValue, false);
+}
+
+// Parse the next token as a <number> (for a <radius> in a -webkit-gradient
+// expresison). Returns true on success; returns false & puts back
+// whatever it parsed on failure.
+bool
+CSSParserImpl::ParseWebkitGradientRadius(float& aRadius)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Number) {
+ UngetToken();
+ return false;
+ }
+
+ aRadius = mToken.mNumber;
+ return true;
+}
+
+// Parse one of:
+// color-stop(number|percent, color)
+// from(color)
+// to(color)
+//
+// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
+// A stop is a function, color-stop, that takes two arguments, the stop value
+// (either a percentage or a number between 0 and 1.0), and a color (any
+// valid CSS color). In addition the shorthand functions from and to are
+// supported. These functions only require a color argument and are
+// equivalent to color-stop(0, ...) and color-stop(1.0, …) respectively.
+bool
+CSSParserImpl::ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient)
+{
+ MOZ_ASSERT(aGradient, "null gradient");
+
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ // We're expecting color-stop(...), from(...), or to(...) which are all
+ // functions. Bail if we got anything else.
+ if (mToken.mType != eCSSToken_Function) {
+ UngetToken();
+ return false;
+ }
+
+ nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
+
+ // Parse color-stop location (or infer it, for shorthands "from"/"to"):
+ if (mToken.mIdent.LowerCaseEqualsLiteral("color-stop")) {
+ // Parse stop location, followed by comma.
+ if (!ParseSingleTokenVariant(stop->mLocation,
+ VARIANT_NUMBER | VARIANT_PERCENT,
+ nullptr) ||
+ !ExpectSymbol(',', true)) {
+ SkipUntil(')'); // Skip to end of color-stop(...) expression.
+ return false;
+ }
+
+ // If we got a <number>, convert it to percentage for consistency:
+ if (stop->mLocation.GetUnit() == eCSSUnit_Number) {
+ stop->mLocation.SetPercentValue(stop->mLocation.GetFloatValue());
+ }
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("from")) {
+ // Shorthand for color-stop(0%, ...)
+ stop->mLocation.SetPercentValue(0.0f);
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("to")) {
+ // Shorthand for color-stop(100%, ...)
+ stop->mLocation.SetPercentValue(1.0f);
+ } else {
+ // Unrecognized function name (invalid for a -webkit-gradient color stop).
+ UngetToken();
+ return false;
+ }
+
+ CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr);
+ if (result != CSSParseResult::Ok ||
+ (stop->mColor.GetUnit() == eCSSUnit_EnumColor &&
+ stop->mColor.GetIntValue() == NS_COLOR_CURRENTCOLOR)) {
+ // Parse failure, or parsed "currentColor" which is forbidden in
+ // -webkit-gradient for some reason.
+ SkipUntil(')');
+ return false;
+ }
+
+ // Parse color-stop function close-paren
+ if (!ExpectSymbol(')', true)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ MOZ_ASSERT(stop->mLocation.GetUnit() == eCSSUnit_Percent,
+ "Should produce only percent-valued stop-locations. "
+ "(Caller depends on this when sorting color stops.)");
+
+ return true;
+}
+
+// Comparatison function to use for sorting -webkit-gradient() stops by
+// location. This function assumes stops have percent-valued locations (and
+// CSSParserImpl::ParseWebkitGradientColorStop should enforce this).
+static bool
+IsColorStopPctLocationLessThan(const nsCSSValueGradientStop& aStop1,
+ const nsCSSValueGradientStop& aStop2) {
+ return (aStop1.mLocation.GetPercentValue() <
+ aStop2.mLocation.GetPercentValue());
+}
+
+// This function parses a list of comma-separated color-stops for a
+// -webkit-gradient(...) expression, and then pads & sorts the list as-needed.
+bool
+CSSParserImpl::ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient)
+{
+ MOZ_ASSERT(aGradient, "null gradient");
+
+ // Parse any number of ", <color-stop>" expressions. (0 or more)
+ // Note: This is different from unprefixed gradient syntax, which
+ // requires at least 2 stops.
+ while (ExpectSymbol(',', true)) {
+ if (!ParseWebkitGradientColorStop(aGradient)) {
+ return false;
+ }
+ }
+
+ // Pad up to 2 stops as-needed:
+ // (Modern gradient expressions are required to have at least 2 stops, so we
+ // depend on this internally -- e.g. we have an assertion about this in
+ // nsCSSRendering.cpp. -webkit-gradient syntax allows 0 stops or 1 stop,
+ // though, so we just pad up to 2 stops in this case).
+
+ // If we have no stops, pad with transparent-black:
+ if (aGradient->mStops.IsEmpty()) {
+ nsCSSValueGradientStop* stop1 = aGradient->mStops.AppendElement();
+ stop1->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0),
+ eCSSUnit_RGBAColor);
+ stop1->mLocation.SetPercentValue(0.0f);
+
+ nsCSSValueGradientStop* stop2 = aGradient->mStops.AppendElement();
+ stop2->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0),
+ eCSSUnit_RGBAColor);
+ stop2->mLocation.SetPercentValue(1.0f);
+ } else if (aGradient->mStops.Length() == 1) {
+ // Copy whatever the author provided in the first stop:
+ nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
+ *stop = aGradient->mStops[0];
+ } else {
+ // We have >2 stops. Sort them in order of increasing location.
+ std::stable_sort(aGradient->mStops.begin(),
+ aGradient->mStops.end(),
+ IsColorStopPctLocationLessThan);
+ }
+ return true;
+}
+
+// Compares aStartCoord to aEndCoord, and returns true iff they share the same
+// unit (both pixel, or both percent) and aStartCoord is larger.
+static bool
+IsWebkitGradientCoordLarger(const nsCSSValue& aStartCoord,
+ const nsCSSValue& aEndCoord)
+{
+ if (aStartCoord.GetUnit() == eCSSUnit_Percent &&
+ aEndCoord.GetUnit() == eCSSUnit_Percent) {
+ return aStartCoord.GetPercentValue() > aEndCoord.GetPercentValue();
+ }
+
+ if (aStartCoord.GetUnit() == eCSSUnit_Pixel &&
+ aEndCoord.GetUnit() == eCSSUnit_Pixel) {
+ return aStartCoord.GetFloatValue() > aEndCoord.GetFloatValue();
+ }
+
+ // We can't compare them, since their units differ. Returning false suggests
+ // that aEndCoord is larger, which is probably a decent guess anyway.
+ return false;
+}
+
+// Finalize our internal representation of a -webkit-gradient(linear, ...)
+// expression, given the parsed points. (The parsed color stops
+// should already be hanging off of the passed-in nsCSSValueGradient.)
+//
+// Note: linear gradients progress along a line between two points. The
+// -webkit-gradient(linear, ...) syntax lets the author precisely specify the
+// starting and ending point. However, our internal gradient structures
+// only store one point, and the other point is implicitly its reflection
+// across the painted area's center. (The legacy -moz-linear-gradient syntax
+// also lets us store an angle.)
+//
+// In this function, we try to go from the two-point representation to an
+// equivalent or approximately-equivalent one-point representation.
+void
+CSSParserImpl::FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient,
+ const nsCSSValuePair& aStartPoint,
+ const nsCSSValuePair& aEndPoint)
+{
+ MOZ_ASSERT(!aGradient->mIsRadial, "passed-in gradient must be linear");
+
+ // If the start & end points have the same Y-coordinate, then we can treat
+ // this as a horizontal gradient progressing towards the center of the left
+ // or right side.
+ if (aStartPoint.mYValue == aEndPoint.mYValue) {
+ aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER,
+ eCSSUnit_Enumerated);
+ if (IsWebkitGradientCoordLarger(aStartPoint.mXValue, aEndPoint.mXValue)) {
+ aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_LEFT,
+ eCSSUnit_Enumerated);
+ } else {
+ aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_RIGHT,
+ eCSSUnit_Enumerated);
+ }
+ return;
+ }
+
+ // If the start & end points have the same X-coordinate, then we can treat
+ // this as a horizontal gradient progressing towards the center of the top
+ // or bottom side.
+ if (aStartPoint.mXValue == aEndPoint.mXValue) {
+ aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER,
+ eCSSUnit_Enumerated);
+ if (IsWebkitGradientCoordLarger(aStartPoint.mYValue, aEndPoint.mYValue)) {
+ aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_TOP,
+ eCSSUnit_Enumerated);
+ } else {
+ aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_BOTTOM,
+ eCSSUnit_Enumerated);
+ }
+ return;
+ }
+
+ // OK, the gradient is angled, which means we likely can't represent it
+ // exactly in |aGradient|, without doing analysis on the two points to
+ // extract an angle (which we might not be able to do depending on the units
+ // used). For now, we'll just do something really basic -- just use the
+ // first point as if it were the starting point in a legacy
+ // -moz-linear-gradient() expression. That way, the rendered gradient will
+ // progress from this first point, towards the center of the covered element,
+ // to a reflected end point on the far side. Note that we have to use
+ // mIsLegacySyntax=true for this to work, because standardized (non-legacy)
+ // gradients place some restrictions on the reference point [namely, that it
+ // use percent units & be on the border of the element].
+ aGradient->mIsLegacySyntax = true;
+ aGradient->mBgPos = aStartPoint;
+}
+
+// Finalize our internal representation of a -webkit-gradient(radial, ...)
+// expression, given the parsed points & radii. (The parsed color-stops
+// should already be hanging off of the passed-in nsCSSValueGradient).
+void
+CSSParserImpl::FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient,
+ const nsCSSValuePair& aFirstCenter,
+ const nsCSSValuePair& aSecondCenter,
+ const float aFirstRadius,
+ const float aSecondRadius)
+{
+ MOZ_ASSERT(aGradient->mIsRadial, "passed-in gradient must be radial");
+
+ // NOTE: -webkit-gradient(radial, ...) has *two arbitrary circles*, with the
+ // gradient stretching between the circles' edges. In contrast, the standard
+ // syntax (and hence our data structures) can only represent *one* circle,
+ // with the gradient going from its center to its edge. To bridge this gap
+ // in expressiveness, we'll just see which of our two circles is smaller, and
+ // we'll treat that circle as if it were zero-sized and located at the center
+ // of the larger circle. Then, we'll be able to use the same data structures
+ // that we use for the standard radial-gradient syntax.
+ if (aSecondRadius >= aFirstRadius) {
+ // Second circle is larger.
+ aGradient->mBgPos = aSecondCenter;
+ aGradient->mIsExplicitSize = true;
+ aGradient->GetRadiusX().SetFloatValue(aSecondRadius, eCSSUnit_Pixel);
+ return;
+ }
+
+ // First circle is larger, so we'll have it be the outer circle.
+ aGradient->mBgPos = aFirstCenter;
+ aGradient->mIsExplicitSize = true;
+ aGradient->GetRadiusX().SetFloatValue(aFirstRadius, eCSSUnit_Pixel);
+
+ // For this to work properly (with the earlier color stops attached to the
+ // first circle), we need to also reverse the color-stop list, so that
+ // e.g. the author's "from" color is attached to the outer edge (the first
+ // circle), rather than attached to the center (the collapsed second circle).
+ std::reverse(aGradient->mStops.begin(), aGradient->mStops.end());
+
+ // And now invert the stop locations:
+ for (nsCSSValueGradientStop& colorStop : aGradient->mStops) {
+ float origLocation = colorStop.mLocation.GetPercentValue();
+ colorStop.mLocation.SetPercentValue(1.0f - origLocation);
+ }
+}
+
+bool
+CSSParserImpl::ParseWebkitGradient(nsCSSValue& aValue)
+{
+ // Parse type of gradient
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Ident) {
+ UngetToken(); // Important; the token might be ")", which we're about to
+ // seek to.
+ SkipUntil(')');
+ return false;
+ }
+
+ bool isRadial;
+ if (mToken.mIdent.LowerCaseEqualsLiteral("radial")) {
+ isRadial = true;
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("linear")) {
+ isRadial = false;
+ } else {
+ // Unrecognized gradient type.
+ SkipUntil(')');
+ return false;
+ }
+
+ // Parse a comma + first point:
+ nsCSSValuePair firstPoint;
+ if (!ExpectSymbol(',', true) ||
+ !ParseWebkitGradientPoint(firstPoint)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ // If radial, parse comma + first radius:
+ float firstRadius;
+ if (isRadial) {
+ if (!ExpectSymbol(',', true) ||
+ !ParseWebkitGradientRadius(firstRadius)) {
+ SkipUntil(')');
+ return false;
+ }
+ }
+
+ // Parse a comma + second point:
+ nsCSSValuePair secondPoint;
+ if (!ExpectSymbol(',', true) ||
+ !ParseWebkitGradientPoint(secondPoint)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ // If radial, parse comma + second radius:
+ float secondRadius;
+ if (isRadial) {
+ if (!ExpectSymbol(',', true) ||
+ !ParseWebkitGradientRadius(secondRadius)) {
+ SkipUntil(')');
+ return false;
+ }
+ }
+
+ // Construct a nsCSSValueGradient object, and parse color stops into it:
+ RefPtr<nsCSSValueGradient> cssGradient =
+ new nsCSSValueGradient(isRadial, false /* aIsRepeating */);
+
+ if (!ParseWebkitGradientColorStops(cssGradient) ||
+ !ExpectSymbol(')', true)) {
+ // Failed to parse color-stops, or found trailing junk between them & ')'.
+ SkipUntil(')');
+ return false;
+ }
+
+ // Finish building cssGradient, based on our parsed positioning/sizing info:
+ if (isRadial) {
+ FinalizeRadialWebkitGradient(cssGradient, firstPoint, secondPoint,
+ firstRadius, secondRadius);
+ } else {
+ FinalizeLinearWebkitGradient(cssGradient, firstPoint, secondPoint);
+ }
+
+ aValue.SetGradientValue(cssGradient);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseWebkitTextStroke()
+{
+ static const nsCSSPropertyID kWebkitTextStrokeIDs[] = {
+ eCSSProperty__webkit_text_stroke_width,
+ eCSSProperty__webkit_text_stroke_color
+ };
+
+ const size_t numProps = MOZ_ARRAY_LENGTH(kWebkitTextStrokeIDs);
+ nsCSSValue values[numProps];
+
+ int32_t found = ParseChoice(values, kWebkitTextStrokeIDs, numProps);
+ if (found < 1) {
+ return false;
+ }
+
+ if (!(found & 1)) { // Provide default -webkit-text-stroke-width
+ values[0].SetFloatValue(0, eCSSUnit_Pixel);
+ }
+
+ if (!(found & 2)) { // Provide default -webkit-text-stroke-color
+ values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ }
+
+ for (size_t index = 0; index < numProps; ++index) {
+ AppendValue(kWebkitTextStrokeIDs[index], values[index]);
+ }
+
+ return true;
+}
+
+ int32_t
+CSSParserImpl::ParseChoice(nsCSSValue aValues[],
+ const nsCSSPropertyID aPropIDs[], int32_t aNumIDs)
+{
+ int32_t found = 0;
+ nsAutoParseCompoundProperty compound(this);
+
+ int32_t loop;
+ for (loop = 0; loop < aNumIDs; loop++) {
+ // Try each property parser in order
+ int32_t hadFound = found;
+ int32_t index;
+ for (index = 0; index < aNumIDs; index++) {
+ int32_t bit = 1 << index;
+ if ((found & bit) == 0) {
+ CSSParseResult result =
+ ParseSingleValueProperty(aValues[index], aPropIDs[index]);
+ if (result == CSSParseResult::Error) {
+ return -1;
+ }
+ if (result == CSSParseResult::Ok) {
+ found |= bit;
+ // It's more efficient to break since it will reset |hadFound|
+ // to |found|. Furthermore, ParseListStyle depends on our going
+ // through the properties in order for each value..
+ break;
+ }
+ }
+ }
+ if (found == hadFound) { // found nothing new
+ break;
+ }
+ }
+ if (0 < found) {
+ if (1 == found) { // only first property
+ if (eCSSUnit_Inherit == aValues[0].GetUnit()) { // one inherit, all inherit
+ for (loop = 1; loop < aNumIDs; loop++) {
+ aValues[loop].SetInheritValue();
+ }
+ found = ((1 << aNumIDs) - 1);
+ }
+ else if (eCSSUnit_Initial == aValues[0].GetUnit()) { // one initial, all initial
+ for (loop = 1; loop < aNumIDs; loop++) {
+ aValues[loop].SetInitialValue();
+ }
+ found = ((1 << aNumIDs) - 1);
+ }
+ else if (eCSSUnit_Unset == aValues[0].GetUnit()) { // one unset, all unset
+ for (loop = 1; loop < aNumIDs; loop++) {
+ aValues[loop].SetUnsetValue();
+ }
+ found = ((1 << aNumIDs) - 1);
+ }
+ }
+ else { // more than one value, verify no inherits, initials or unsets
+ for (loop = 0; loop < aNumIDs; loop++) {
+ if (eCSSUnit_Inherit == aValues[loop].GetUnit()) {
+ found = -1;
+ break;
+ }
+ else if (eCSSUnit_Initial == aValues[loop].GetUnit()) {
+ found = -1;
+ break;
+ }
+ else if (eCSSUnit_Unset == aValues[loop].GetUnit()) {
+ found = -1;
+ break;
+ }
+ }
+ }
+ }
+ return found;
+}
+
+void
+CSSParserImpl::AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue)
+{
+ mTempData.AddLonghandProperty(aPropID, aValue);
+}
+
+/**
+ * Parse a "box" property. Box properties have 1 to 4 values. When less
+ * than 4 values are provided a standard mapping is used to replicate
+ * existing values.
+ */
+bool
+CSSParserImpl::ParseBoxProperties(const nsCSSPropertyID aPropIDs[])
+{
+ // Get up to four values for the property
+ int32_t count = 0;
+ nsCSSRect result;
+ NS_FOR_CSS_SIDES (index) {
+ CSSParseResult parseResult =
+ ParseBoxProperty(result.*(nsCSSRect::sides[index]), aPropIDs[index]);
+ if (parseResult == CSSParseResult::NotFound) {
+ break;
+ }
+ if (parseResult == CSSParseResult::Error) {
+ return false;
+ }
+ count++;
+ }
+ if (count == 0) {
+ return false;
+ }
+
+ if (1 < count) { // verify no more than single inherit, initial or unset
+ NS_FOR_CSS_SIDES (index) {
+ nsCSSUnit unit = (result.*(nsCSSRect::sides[index])).GetUnit();
+ if (eCSSUnit_Inherit == unit ||
+ eCSSUnit_Initial == unit ||
+ eCSSUnit_Unset == unit) {
+ return false;
+ }
+ }
+ }
+
+ // Provide missing values by replicating some of the values found
+ switch (count) {
+ case 1: // Make right == top
+ result.mRight = result.mTop;
+ MOZ_FALLTHROUGH;
+ case 2: // Make bottom == top
+ result.mBottom = result.mTop;
+ MOZ_FALLTHROUGH;
+ case 3: // Make left == right
+ result.mLeft = result.mRight;
+ }
+
+ NS_FOR_CSS_SIDES (index) {
+ AppendValue(aPropIDs[index], result.*(nsCSSRect::sides[index]));
+ }
+ return true;
+}
+
+// Similar to ParseBoxProperties, except there is only one property
+// with the result as its value, not four.
+bool
+CSSParserImpl::ParseGroupedBoxProperty(int32_t aVariantMask,
+ /** outparam */ nsCSSValue& aValue,
+ uint32_t aRestrictions)
+{
+ nsCSSRect& result = aValue.SetRectValue();
+
+ int32_t count = 0;
+ NS_FOR_CSS_SIDES (index) {
+ CSSParseResult parseResult =
+ ParseVariantWithRestrictions(result.*(nsCSSRect::sides[index]),
+ aVariantMask, nullptr,
+ aRestrictions);
+ if (parseResult == CSSParseResult::NotFound) {
+ break;
+ }
+ if (parseResult == CSSParseResult::Error) {
+ return false;
+ }
+ count++;
+ }
+
+ if (count == 0) {
+ return false;
+ }
+
+ // Provide missing values by replicating some of the values found
+ switch (count) {
+ case 1: // Make right == top
+ result.mRight = result.mTop;
+ MOZ_FALLTHROUGH;
+ case 2: // Make bottom == top
+ result.mBottom = result.mTop;
+ MOZ_FALLTHROUGH;
+ case 3: // Make left == right
+ result.mLeft = result.mRight;
+ }
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBoxCornerRadius(nsCSSPropertyID aPropID)
+{
+ nsCSSValue dimenX, dimenY;
+ // required first value
+ if (ParseNonNegativeVariant(dimenX, VARIANT_HLP | VARIANT_CALC, nullptr) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+
+ // optional second value (forbidden if first value is inherit/initial/unset)
+ if (dimenX.GetUnit() != eCSSUnit_Inherit &&
+ dimenX.GetUnit() != eCSSUnit_Initial &&
+ dimenX.GetUnit() != eCSSUnit_Unset) {
+ if (ParseNonNegativeVariant(dimenY, VARIANT_LP | VARIANT_CALC, nullptr) ==
+ CSSParseResult::Error) {
+ return false;
+ }
+ }
+
+ if (dimenX == dimenY || dimenY.GetUnit() == eCSSUnit_Null) {
+ AppendValue(aPropID, dimenX);
+ } else {
+ nsCSSValue value;
+ value.SetPairValue(dimenX, dimenY);
+ AppendValue(aPropID, value);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBoxCornerRadiiInternals(nsCSSValue array[])
+{
+ // Rectangles are used as scratch storage.
+ // top => top-left, right => top-right,
+ // bottom => bottom-right, left => bottom-left.
+ nsCSSRect dimenX, dimenY;
+ int32_t countX = 0, countY = 0;
+
+ NS_FOR_CSS_SIDES (side) {
+ CSSParseResult result =
+ ParseNonNegativeVariant(dimenX.*nsCSSRect::sides[side],
+ (side > 0 ? 0 : VARIANT_INHERIT) |
+ VARIANT_LP | VARIANT_CALC,
+ nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::NotFound) {
+ break;
+ }
+ countX++;
+ }
+ if (countX == 0)
+ return false;
+
+ if (ExpectSymbol('/', true)) {
+ NS_FOR_CSS_SIDES (side) {
+ CSSParseResult result =
+ ParseNonNegativeVariant(dimenY.*nsCSSRect::sides[side],
+ VARIANT_LP | VARIANT_CALC, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::NotFound) {
+ break;
+ }
+ countY++;
+ }
+ if (countY == 0)
+ return false;
+ }
+
+ // if 'initial', 'inherit' or 'unset' was used, it must be the only value
+ if (countX > 1 || countY > 0) {
+ nsCSSUnit unit = dimenX.mTop.GetUnit();
+ if (eCSSUnit_Inherit == unit ||
+ eCSSUnit_Initial == unit ||
+ eCSSUnit_Unset == unit)
+ return false;
+ }
+
+ // if we have no Y-values, use the X-values
+ if (countY == 0) {
+ dimenY = dimenX;
+ countY = countX;
+ }
+
+ // Provide missing values by replicating some of the values found
+ switch (countX) {
+ case 1: // Make top-right same as top-left
+ dimenX.mRight = dimenX.mTop;
+ MOZ_FALLTHROUGH;
+ case 2: // Make bottom-right same as top-left
+ dimenX.mBottom = dimenX.mTop;
+ MOZ_FALLTHROUGH;
+ case 3: // Make bottom-left same as top-right
+ dimenX.mLeft = dimenX.mRight;
+ }
+
+ switch (countY) {
+ case 1: // Make top-right same as top-left
+ dimenY.mRight = dimenY.mTop;
+ MOZ_FALLTHROUGH;
+ case 2: // Make bottom-right same as top-left
+ dimenY.mBottom = dimenY.mTop;
+ MOZ_FALLTHROUGH;
+ case 3: // Make bottom-left same as top-right
+ dimenY.mLeft = dimenY.mRight;
+ }
+
+ NS_FOR_CSS_SIDES(side) {
+ nsCSSValue& x = dimenX.*nsCSSRect::sides[side];
+ nsCSSValue& y = dimenY.*nsCSSRect::sides[side];
+
+ if (x == y) {
+ array[side] = x;
+ } else {
+ nsCSSValue pair;
+ pair.SetPairValue(x, y);
+ array[side] = pair;
+ }
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[])
+{
+ nsCSSValue value[4];
+ if (!ParseBoxCornerRadiiInternals(value)) {
+ return false;
+ }
+
+ NS_FOR_CSS_SIDES(side) {
+ AppendValue(aPropIDs[side], value[side]);
+ }
+ return true;
+}
+
+// These must be in CSS order (top,right,bottom,left) for indexing to work
+static const nsCSSPropertyID kBorderStyleIDs[] = {
+ eCSSProperty_border_top_style,
+ eCSSProperty_border_right_style,
+ eCSSProperty_border_bottom_style,
+ eCSSProperty_border_left_style
+};
+static const nsCSSPropertyID kBorderWidthIDs[] = {
+ eCSSProperty_border_top_width,
+ eCSSProperty_border_right_width,
+ eCSSProperty_border_bottom_width,
+ eCSSProperty_border_left_width
+};
+static const nsCSSPropertyID kBorderColorIDs[] = {
+ eCSSProperty_border_top_color,
+ eCSSProperty_border_right_color,
+ eCSSProperty_border_bottom_color,
+ eCSSProperty_border_left_color
+};
+static const nsCSSPropertyID kBorderRadiusIDs[] = {
+ eCSSProperty_border_top_left_radius,
+ eCSSProperty_border_top_right_radius,
+ eCSSProperty_border_bottom_right_radius,
+ eCSSProperty_border_bottom_left_radius
+};
+static const nsCSSPropertyID kOutlineRadiusIDs[] = {
+ eCSSProperty__moz_outline_radius_topLeft,
+ eCSSProperty__moz_outline_radius_topRight,
+ eCSSProperty__moz_outline_radius_bottomRight,
+ eCSSProperty__moz_outline_radius_bottomLeft
+};
+
+void
+CSSParserImpl::SaveInputState(CSSParserInputState& aState)
+{
+ aState.mToken = mToken;
+ aState.mHavePushBack = mHavePushBack;
+ mScanner->SavePosition(aState.mPosition);
+}
+
+void
+CSSParserImpl::RestoreSavedInputState(const CSSParserInputState& aState)
+{
+ mToken = aState.mToken;
+ mHavePushBack = aState.mHavePushBack;
+ mScanner->RestoreSavedPosition(aState.mPosition);
+}
+
+bool
+CSSParserImpl::ParseProperty(nsCSSPropertyID aPropID)
+{
+ // Can't use AutoRestore<bool> because it's a bitfield.
+ MOZ_ASSERT(!mHashlessColorQuirk,
+ "hashless color quirk should not be set");
+ MOZ_ASSERT(!mUnitlessLengthQuirk,
+ "unitless length quirk should not be set");
+ MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable);
+
+ if (mNavQuirkMode) {
+ mHashlessColorQuirk =
+ nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_HASHLESS_COLOR_QUIRK);
+ mUnitlessLengthQuirk =
+ nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_UNITLESS_LENGTH_QUIRK);
+ }
+
+ // Save the current input state so that we can restore it later if we
+ // have to re-parse the property value as a variable-reference-containing
+ // token stream.
+ CSSParserInputState stateBeforeProperty;
+ SaveInputState(stateBeforeProperty);
+ mScanner->ClearSeenVariableReference();
+
+ NS_ASSERTION(aPropID < eCSSProperty_COUNT, "index out of range");
+ bool allowVariables = true;
+ bool result;
+ switch (nsCSSProps::PropertyParseType(aPropID)) {
+ case CSS_PROPERTY_PARSE_INACCESSIBLE: {
+ // The user can't use these
+ REPORT_UNEXPECTED(PEInaccessibleProperty2);
+ allowVariables = false;
+ result = false;
+ break;
+ }
+ case CSS_PROPERTY_PARSE_FUNCTION: {
+ result = ParsePropertyByFunction(aPropID);
+ break;
+ }
+ case CSS_PROPERTY_PARSE_VALUE: {
+ result = false;
+ nsCSSValue value;
+ if (ParseSingleValueProperty(value, aPropID) == CSSParseResult::Ok) {
+ AppendValue(aPropID, value);
+ result = true;
+ }
+ // XXX Report errors?
+ break;
+ }
+ case CSS_PROPERTY_PARSE_VALUE_LIST: {
+ result = ParseValueList(aPropID);
+ break;
+ }
+ default: {
+ result = false;
+ allowVariables = false;
+ MOZ_ASSERT(false,
+ "Property's flags field in nsCSSPropList.h is missing "
+ "one of the CSS_PROPERTY_PARSE_* constants");
+ break;
+ }
+ }
+
+ if (result) {
+ // We need to call ExpectEndProperty() to decide whether to reparse
+ // with variables. This is needed because the property parsing may
+ // have stopped upon finding a variable (e.g., 'margin: 1px var(a)')
+ // in a way that future variable substitutions will be valid, or
+ // because it parsed everything that's possible but we still want to
+ // act as though the property contains variables even though we know
+ // the substitution will never work (e.g., for 'margin: 1px 2px 3px
+ // 4px 5px var(a)').
+ //
+ // It would be nice to find a better solution here
+ // (and for the SkipUntilOneOf below), though, that doesn't depend
+ // on using what we don't accept for doing parsing correctly.
+ if (!ExpectEndProperty()) {
+ result = false;
+ }
+ }
+
+ bool seenVariable = mScanner->SeenVariableReference() ||
+ (stateBeforeProperty.mHavePushBack &&
+ stateBeforeProperty.mToken.mType == eCSSToken_Function &&
+ stateBeforeProperty.mToken.mIdent.LowerCaseEqualsLiteral("var"));
+ bool parseAsTokenStream;
+
+ if (!result && allowVariables) {
+ parseAsTokenStream = true;
+ if (!seenVariable) {
+ // We might have stopped parsing the property before its end and before
+ // finding a variable reference. Keep checking until the end of the
+ // property.
+ CSSParserInputState stateAtError;
+ SaveInputState(stateAtError);
+
+ const char16_t stopChars[] = { ';', '!', '}', ')', 0 };
+ SkipUntilOneOf(stopChars);
+ UngetToken();
+ parseAsTokenStream = mScanner->SeenVariableReference();
+
+ if (!parseAsTokenStream) {
+ // If we parsed to the end of the propery and didn't find any variable
+ // references, then the real position we want to report the error at
+ // is |stateAtError|.
+ RestoreSavedInputState(stateAtError);
+ }
+ }
+ } else {
+ parseAsTokenStream = false;
+ }
+
+ if (parseAsTokenStream) {
+ // Go back to the start of the property value and parse it to make sure
+ // its variable references are syntactically valid and is otherwise
+ // balanced.
+ RestoreSavedInputState(stateBeforeProperty);
+
+ if (!mInSupportsCondition) {
+ mScanner->StartRecording();
+ }
+
+ CSSVariableDeclarations::Type type;
+ bool dropBackslash;
+ nsString impliedCharacters;
+ nsCSSValue value;
+ if (ParseValueWithVariables(&type, &dropBackslash, impliedCharacters,
+ nullptr, nullptr)) {
+ MOZ_ASSERT(type == CSSVariableDeclarations::eTokenStream,
+ "a non-custom property reparsed since it contained variable "
+ "references should not have been 'initial' or 'inherit'");
+
+ nsString propertyValue;
+
+ if (!mInSupportsCondition) {
+ // If we are in an @supports condition, we don't need to store the
+ // actual token stream on the nsCSSValue.
+ mScanner->StopRecording(propertyValue);
+ if (dropBackslash) {
+ MOZ_ASSERT(!propertyValue.IsEmpty() &&
+ propertyValue[propertyValue.Length() - 1] == '\\');
+ propertyValue.Truncate(propertyValue.Length() - 1);
+ }
+ propertyValue.Append(impliedCharacters);
+ }
+
+ if (mHavePushBack) {
+ // If we came to the end of a property value that had a variable
+ // reference and a token was pushed back, then it would have been
+ // ended by '!', ')', ';', ']' or '}'. We should remove it from the
+ // recorded property value.
+ MOZ_ASSERT(mToken.IsSymbol('!') ||
+ mToken.IsSymbol(')') ||
+ mToken.IsSymbol(';') ||
+ mToken.IsSymbol(']') ||
+ mToken.IsSymbol('}'));
+ if (!mInSupportsCondition) {
+ MOZ_ASSERT(!propertyValue.IsEmpty());
+ MOZ_ASSERT(propertyValue[propertyValue.Length() - 1] ==
+ mToken.mSymbol);
+ propertyValue.Truncate(propertyValue.Length() - 1);
+ }
+ }
+
+ if (!mInSupportsCondition) {
+ if (nsCSSProps::IsShorthand(aPropID)) {
+ // If this is a shorthand property, we store the token stream on each
+ // of its corresponding longhand properties.
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID, EnabledState()) {
+ nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
+ tokenStream->mPropertyID = *p;
+ tokenStream->mShorthandPropertyID = aPropID;
+ tokenStream->mTokenStream = propertyValue;
+ tokenStream->mBaseURI = mBaseURI;
+ tokenStream->mSheetURI = mSheetURI;
+ tokenStream->mSheetPrincipal = mSheetPrincipal;
+ // XXX Should store sheet here (see bug 952338).
+ // tokenStream->mSheet = mSheet;
+ tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber();
+ tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset();
+ value.SetTokenStreamValue(tokenStream);
+ AppendValue(*p, value);
+ }
+ } else {
+ nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
+ tokenStream->mPropertyID = aPropID;
+ tokenStream->mTokenStream = propertyValue;
+ tokenStream->mBaseURI = mBaseURI;
+ tokenStream->mSheetURI = mSheetURI;
+ tokenStream->mSheetPrincipal = mSheetPrincipal;
+ // XXX Should store sheet here (see bug 952338).
+ // tokenStream->mSheet = mSheet;
+ tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber();
+ tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset();
+ value.SetTokenStreamValue(tokenStream);
+ AppendValue(aPropID, value);
+ }
+ }
+ result = true;
+ } else {
+ if (!mInSupportsCondition) {
+ mScanner->StopRecording();
+ }
+ }
+ }
+
+ if (mNavQuirkMode) {
+ mHashlessColorQuirk = false;
+ mUnitlessLengthQuirk = false;
+ }
+
+ return result;
+}
+
+bool
+CSSParserImpl::ParsePropertyByFunction(nsCSSPropertyID aPropID)
+{
+ switch (aPropID) { // handle shorthand or multiple properties
+ case eCSSProperty_place_content:
+ return ParsePlaceContent();
+ case eCSSProperty_place_items:
+ return ParsePlaceItems();
+ case eCSSProperty_place_self:
+ return ParsePlaceSelf();
+ case eCSSProperty_background:
+ return ParseImageLayers(nsStyleImageLayers::kBackgroundLayerTable);
+ case eCSSProperty_background_repeat:
+ return ParseImageLayerRepeat(eCSSProperty_background_repeat);
+ case eCSSProperty_background_position:
+ return ParseImageLayerPosition(nsStyleImageLayers::kBackgroundLayerTable);
+ case eCSSProperty_background_position_x:
+ case eCSSProperty_background_position_y:
+ return ParseImageLayerPositionCoord(aPropID,
+ aPropID == eCSSProperty_background_position_x);
+ case eCSSProperty_background_size:
+ return ParseImageLayerSize(eCSSProperty_background_size);
+ case eCSSProperty_border:
+ return ParseBorderSide(kBorderTopIDs, true);
+ case eCSSProperty_border_color:
+ return ParseBorderColor();
+ case eCSSProperty_border_spacing:
+ return ParseBorderSpacing();
+ case eCSSProperty_border_style:
+ return ParseBorderStyle();
+ case eCSSProperty_border_block_end:
+ return ParseBorderSide(kBorderBlockEndIDs, false);
+ case eCSSProperty_border_block_start:
+ return ParseBorderSide(kBorderBlockStartIDs, false);
+ case eCSSProperty_border_bottom:
+ return ParseBorderSide(kBorderBottomIDs, false);
+ case eCSSProperty_border_inline_end:
+ return ParseBorderSide(kBorderInlineEndIDs, false);
+ case eCSSProperty_border_inline_start:
+ return ParseBorderSide(kBorderInlineStartIDs, false);
+ case eCSSProperty_border_left:
+ return ParseBorderSide(kBorderLeftIDs, false);
+ case eCSSProperty_border_right:
+ return ParseBorderSide(kBorderRightIDs, false);
+ case eCSSProperty_border_top:
+ return ParseBorderSide(kBorderTopIDs, false);
+ case eCSSProperty_border_bottom_colors:
+ case eCSSProperty_border_left_colors:
+ case eCSSProperty_border_right_colors:
+ case eCSSProperty_border_top_colors:
+ return ParseBorderColors(aPropID);
+ case eCSSProperty_border_image_slice:
+ return ParseBorderImageSlice(true, nullptr);
+ case eCSSProperty_border_image_width:
+ return ParseBorderImageWidth(true);
+ case eCSSProperty_border_image_outset:
+ return ParseBorderImageOutset(true);
+ case eCSSProperty_border_image_repeat:
+ return ParseBorderImageRepeat(true);
+ case eCSSProperty_border_image:
+ return ParseBorderImage();
+ case eCSSProperty_border_width:
+ return ParseBorderWidth();
+ case eCSSProperty_border_radius:
+ return ParseBoxCornerRadii(kBorderRadiusIDs);
+ case eCSSProperty__moz_outline_radius:
+ return ParseBoxCornerRadii(kOutlineRadiusIDs);
+
+ case eCSSProperty_border_top_left_radius:
+ case eCSSProperty_border_top_right_radius:
+ case eCSSProperty_border_bottom_right_radius:
+ case eCSSProperty_border_bottom_left_radius:
+ case eCSSProperty__moz_outline_radius_topLeft:
+ case eCSSProperty__moz_outline_radius_topRight:
+ case eCSSProperty__moz_outline_radius_bottomRight:
+ case eCSSProperty__moz_outline_radius_bottomLeft:
+ return ParseBoxCornerRadius(aPropID);
+
+ case eCSSProperty_box_shadow:
+ case eCSSProperty_text_shadow:
+ return ParseShadowList(aPropID);
+
+ case eCSSProperty_clip:
+ return ParseRect(eCSSProperty_clip);
+ case eCSSProperty_columns:
+ return ParseColumns();
+ case eCSSProperty_column_rule:
+ return ParseBorderSide(kColumnRuleIDs, false);
+ case eCSSProperty_content:
+ return ParseContent();
+ case eCSSProperty_counter_increment:
+ case eCSSProperty_counter_reset:
+ return ParseCounterData(aPropID);
+ case eCSSProperty_cursor:
+ return ParseCursor();
+ case eCSSProperty_filter:
+ return ParseFilter();
+ case eCSSProperty_flex:
+ return ParseFlex();
+ case eCSSProperty_flex_flow:
+ return ParseFlexFlow();
+ case eCSSProperty_font:
+ return ParseFont();
+ case eCSSProperty_font_variant:
+ return ParseFontVariant();
+ case eCSSProperty_grid_auto_flow:
+ return ParseGridAutoFlow();
+ case eCSSProperty_grid_auto_columns:
+ case eCSSProperty_grid_auto_rows:
+ return ParseGridAutoColumnsRows(aPropID);
+ case eCSSProperty_grid_template_areas:
+ return ParseGridTemplateAreas();
+ case eCSSProperty_grid_template_columns:
+ case eCSSProperty_grid_template_rows:
+ return ParseGridTemplateColumnsRows(aPropID);
+ case eCSSProperty_grid_template:
+ return ParseGridTemplate();
+ case eCSSProperty_grid:
+ return ParseGrid();
+ case eCSSProperty_grid_column_start:
+ case eCSSProperty_grid_column_end:
+ case eCSSProperty_grid_row_start:
+ case eCSSProperty_grid_row_end:
+ return ParseGridColumnRowStartEnd(aPropID);
+ case eCSSProperty_grid_column:
+ return ParseGridColumnRow(eCSSProperty_grid_column_start,
+ eCSSProperty_grid_column_end);
+ case eCSSProperty_grid_row:
+ return ParseGridColumnRow(eCSSProperty_grid_row_start,
+ eCSSProperty_grid_row_end);
+ case eCSSProperty_grid_area:
+ return ParseGridArea();
+ case eCSSProperty_grid_gap:
+ return ParseGridGap();
+ case eCSSProperty_image_region:
+ return ParseRect(eCSSProperty_image_region);
+ case eCSSProperty_align_content:
+ case eCSSProperty_justify_content:
+ return ParseAlignJustifyContent(aPropID);
+ case eCSSProperty_align_items:
+ return ParseAlignItems();
+ case eCSSProperty_align_self:
+ case eCSSProperty_justify_self:
+ return ParseAlignJustifySelf(aPropID);
+ case eCSSProperty_initial_letter:
+ return ParseInitialLetter();
+ case eCSSProperty_justify_items:
+ return ParseJustifyItems();
+ case eCSSProperty_list_style:
+ return ParseListStyle();
+ case eCSSProperty_margin:
+ return ParseMargin();
+ case eCSSProperty_object_position:
+ return ParseObjectPosition();
+ case eCSSProperty_outline:
+ return ParseOutline();
+ case eCSSProperty_overflow:
+ return ParseOverflow();
+ case eCSSProperty_padding:
+ return ParsePadding();
+ case eCSSProperty_quotes:
+ return ParseQuotes();
+ case eCSSProperty_text_decoration:
+ return ParseTextDecoration();
+ case eCSSProperty_text_emphasis:
+ return ParseTextEmphasis();
+ case eCSSProperty_will_change:
+ return ParseWillChange();
+ case eCSSProperty_transform:
+ return ParseTransform(false);
+ case eCSSProperty__moz_transform:
+ return ParseTransform(true);
+ case eCSSProperty_transform_origin:
+ return ParseTransformOrigin(false);
+ case eCSSProperty_perspective_origin:
+ return ParseTransformOrigin(true);
+ case eCSSProperty_transition:
+ return ParseTransition();
+ case eCSSProperty_animation:
+ return ParseAnimation();
+ case eCSSProperty_transition_property:
+ return ParseTransitionProperty();
+ case eCSSProperty_fill:
+ case eCSSProperty_stroke:
+ return ParsePaint(aPropID);
+ case eCSSProperty_stroke_dasharray:
+ return ParseDasharray();
+ case eCSSProperty_marker:
+ return ParseMarker();
+ case eCSSProperty_paint_order:
+ return ParsePaintOrder();
+ case eCSSProperty_scroll_snap_type:
+ return ParseScrollSnapType();
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ case eCSSProperty_mask:
+ return ParseImageLayers(nsStyleImageLayers::kMaskLayerTable);
+ case eCSSProperty_mask_repeat:
+ return ParseImageLayerRepeat(eCSSProperty_mask_repeat);
+ case eCSSProperty_mask_position:
+ return ParseImageLayerPosition(nsStyleImageLayers::kMaskLayerTable);
+ case eCSSProperty_mask_position_x:
+ case eCSSProperty_mask_position_y:
+ return ParseImageLayerPositionCoord(aPropID,
+ aPropID == eCSSProperty_mask_position_x);
+ case eCSSProperty_mask_size:
+ return ParseImageLayerSize(eCSSProperty_mask_size);
+#endif
+ case eCSSProperty__webkit_text_stroke:
+ return ParseWebkitTextStroke();
+ case eCSSProperty_all:
+ return ParseAll();
+ default:
+ MOZ_ASSERT(false, "should not be called");
+ return false;
+ }
+}
+
+// Bits used in determining which background position info we have
+#define BG_CENTER NS_STYLE_IMAGELAYER_POSITION_CENTER
+#define BG_TOP NS_STYLE_IMAGELAYER_POSITION_TOP
+#define BG_BOTTOM NS_STYLE_IMAGELAYER_POSITION_BOTTOM
+#define BG_LEFT NS_STYLE_IMAGELAYER_POSITION_LEFT
+#define BG_RIGHT NS_STYLE_IMAGELAYER_POSITION_RIGHT
+#define BG_CTB (BG_CENTER | BG_TOP | BG_BOTTOM)
+#define BG_TB (BG_TOP | BG_BOTTOM)
+#define BG_CLR (BG_CENTER | BG_LEFT | BG_RIGHT)
+#define BG_LR (BG_LEFT | BG_RIGHT)
+
+CSSParseResult
+CSSParserImpl::ParseBoxProperty(nsCSSValue& aValue,
+ nsCSSPropertyID aPropID)
+{
+ if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) {
+ MOZ_ASSERT(false, "must only be called for longhand properties");
+ return CSSParseResult::NotFound;
+ }
+
+ MOZ_ASSERT(!nsCSSProps::PropHasFlags(aPropID,
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION),
+ "must only be called for non-function-parsed properties");
+
+ uint32_t variant = nsCSSProps::ParserVariant(aPropID);
+ if (variant == 0) {
+ MOZ_ASSERT(false, "must only be called for variant-parsed properties");
+ return CSSParseResult::NotFound;
+ }
+
+ if (variant & ~(VARIANT_AHKLP | VARIANT_COLOR | VARIANT_CALC)) {
+ MOZ_ASSERT(false, "must only be called for properties that take certain "
+ "variants");
+ return CSSParseResult::NotFound;
+ }
+
+ const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID];
+ uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID);
+
+ return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions);
+}
+
+bool
+CSSParserImpl::ParseSingleValuePropertyByFunction(nsCSSValue& aValue,
+ nsCSSPropertyID aPropID)
+{
+ switch (aPropID) {
+ case eCSSProperty_clip_path:
+ return ParseClipPath(aValue);
+ case eCSSProperty_contain:
+ return ParseContain(aValue);
+ case eCSSProperty_font_family:
+ return ParseFamily(aValue);
+ case eCSSProperty_font_synthesis:
+ return ParseFontSynthesis(aValue);
+ case eCSSProperty_font_variant_alternates:
+ return ParseFontVariantAlternates(aValue);
+ case eCSSProperty_font_variant_east_asian:
+ return ParseFontVariantEastAsian(aValue);
+ case eCSSProperty_font_variant_ligatures:
+ return ParseFontVariantLigatures(aValue);
+ case eCSSProperty_font_variant_numeric:
+ return ParseFontVariantNumeric(aValue);
+ case eCSSProperty_font_feature_settings:
+ return ParseFontFeatureSettings(aValue);
+ case eCSSProperty_font_weight:
+ return ParseFontWeight(aValue);
+ case eCSSProperty_image_orientation:
+ return ParseImageOrientation(aValue);
+ case eCSSProperty_list_style_type:
+ return ParseListStyleType(aValue);
+ case eCSSProperty_scroll_snap_points_x:
+ return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_x);
+ case eCSSProperty_scroll_snap_points_y:
+ return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_y);
+ case eCSSProperty_scroll_snap_destination:
+ return ParseScrollSnapDestination(aValue);
+ case eCSSProperty_scroll_snap_coordinate:
+ return ParseScrollSnapCoordinate(aValue);
+ case eCSSProperty_shape_outside:
+ return ParseShapeOutside(aValue);
+ case eCSSProperty_text_align:
+ return ParseTextAlign(aValue);
+ case eCSSProperty_text_align_last:
+ return ParseTextAlignLast(aValue);
+ case eCSSProperty_text_decoration_line:
+ return ParseTextDecorationLine(aValue);
+ case eCSSProperty_text_combine_upright:
+ return ParseTextCombineUpright(aValue);
+ case eCSSProperty_text_emphasis_position:
+ return ParseTextEmphasisPosition(aValue);
+ case eCSSProperty_text_emphasis_style:
+ return ParseTextEmphasisStyle(aValue);
+ case eCSSProperty_text_overflow:
+ return ParseTextOverflow(aValue);
+ case eCSSProperty_touch_action:
+ return ParseTouchAction(aValue);
+ default:
+ MOZ_ASSERT(false, "should not reach here");
+ return false;
+ }
+}
+
+CSSParseResult
+CSSParserImpl::ParseSingleValueProperty(nsCSSValue& aValue,
+ nsCSSPropertyID aPropID)
+{
+ if (aPropID == eCSSPropertyExtra_x_none_value) {
+ return ParseVariant(aValue, VARIANT_NONE | VARIANT_INHERIT, nullptr);
+ }
+
+ if (aPropID == eCSSPropertyExtra_x_auto_value) {
+ return ParseVariant(aValue, VARIANT_AUTO | VARIANT_INHERIT, nullptr);
+ }
+
+ if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) {
+ MOZ_ASSERT(false, "not a single value property");
+ return CSSParseResult::NotFound;
+ }
+
+ if (nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_VALUE_PARSER_FUNCTION)) {
+ uint32_t lineBefore, colBefore;
+ if (!GetNextTokenLocation(true, &lineBefore, &colBefore)) {
+ // We're at EOF before parsing.
+ return CSSParseResult::NotFound;
+ }
+
+ if (ParseSingleValuePropertyByFunction(aValue, aPropID)) {
+ return CSSParseResult::Ok;
+ }
+
+ uint32_t lineAfter, colAfter;
+ if (!GetNextTokenLocation(true, &lineAfter, &colAfter) ||
+ lineAfter != lineBefore ||
+ colAfter != colBefore) {
+ // Any single token value that was invalid will have been pushed back,
+ // so GetNextTokenLocation encountering EOF means we failed while
+ // parsing a multi-token value.
+ return CSSParseResult::Error;
+ }
+
+ return CSSParseResult::NotFound;
+ }
+
+ uint32_t variant = nsCSSProps::ParserVariant(aPropID);
+ if (variant == 0) {
+ MOZ_ASSERT(false, "not a single value property");
+ return CSSParseResult::NotFound;
+ }
+
+ const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID];
+ uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID);
+ return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions);
+}
+
+// font-descriptor: descriptor ':' value ';'
+// caller has advanced mToken to point at the descriptor
+bool
+CSSParserImpl::ParseFontDescriptorValue(nsCSSFontDesc aDescID,
+ nsCSSValue& aValue)
+{
+ switch (aDescID) {
+ // These four are similar to the properties of the same name,
+ // possibly with more restrictions on the values they can take.
+ case eCSSFontDesc_Family: {
+ nsCSSValue value;
+ if (!ParseFamily(value) ||
+ value.GetUnit() != eCSSUnit_FontFamilyList)
+ return false;
+
+ // name can only be a single, non-generic name
+ const FontFamilyList* f = value.GetFontFamilyListValue();
+ const nsTArray<FontFamilyName>& fontlist = f->GetFontlist();
+
+ if (fontlist.Length() != 1 || !fontlist[0].IsNamed()) {
+ return false;
+ }
+
+ aValue.SetStringValue(fontlist[0].mName, eCSSUnit_String);
+ return true;
+ }
+
+ case eCSSFontDesc_Style:
+ // property is VARIANT_HMK|VARIANT_SYSFONT
+ return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD | VARIANT_NORMAL,
+ nsCSSProps::kFontStyleKTable);
+
+ case eCSSFontDesc_Display:
+ return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD,
+ nsCSSProps::kFontDisplayKTable);
+
+ case eCSSFontDesc_Weight:
+ return (ParseFontWeight(aValue) &&
+ aValue.GetUnit() != eCSSUnit_Inherit &&
+ aValue.GetUnit() != eCSSUnit_Initial &&
+ aValue.GetUnit() != eCSSUnit_Unset &&
+ (aValue.GetUnit() != eCSSUnit_Enumerated ||
+ (aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_BOLDER &&
+ aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_LIGHTER)));
+
+ case eCSSFontDesc_Stretch:
+ // property is VARIANT_HK|VARIANT_SYSFONT
+ return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD,
+ nsCSSProps::kFontStretchKTable);
+
+ // These two are unique to @font-face and have their own special grammar.
+ case eCSSFontDesc_Src:
+ return ParseFontSrc(aValue);
+
+ case eCSSFontDesc_UnicodeRange:
+ return ParseFontRanges(aValue);
+
+ case eCSSFontDesc_FontFeatureSettings:
+ return ParseFontFeatureSettings(aValue);
+
+ case eCSSFontDesc_FontLanguageOverride:
+ return ParseSingleTokenVariant(aValue, VARIANT_NORMAL | VARIANT_STRING,
+ nullptr);
+
+ case eCSSFontDesc_UNKNOWN:
+ case eCSSFontDesc_COUNT:
+ NS_NOTREACHED("bad nsCSSFontDesc code");
+ }
+ // explicitly do NOT have a default case to let the compiler
+ // help find missing descriptors
+ return false;
+}
+
+static nsCSSValue
+BoxPositionMaskToCSSValue(int32_t aMask, bool isX)
+{
+ int32_t val = NS_STYLE_IMAGELAYER_POSITION_CENTER;
+ if (isX) {
+ if (aMask & BG_LEFT) {
+ val = NS_STYLE_IMAGELAYER_POSITION_LEFT;
+ }
+ else if (aMask & BG_RIGHT) {
+ val = NS_STYLE_IMAGELAYER_POSITION_RIGHT;
+ }
+ }
+ else {
+ if (aMask & BG_TOP) {
+ val = NS_STYLE_IMAGELAYER_POSITION_TOP;
+ }
+ else if (aMask & BG_BOTTOM) {
+ val = NS_STYLE_IMAGELAYER_POSITION_BOTTOM;
+ }
+ }
+
+ return nsCSSValue(val, eCSSUnit_Enumerated);
+}
+
+bool
+CSSParserImpl::ParseImageLayers(const nsCSSPropertyID aTable[])
+{
+ nsAutoParseCompoundProperty compound(this);
+
+ // background-color can only be set once, so it's not a list.
+ nsCSSValue color;
+
+ // Check first for inherit/initial/unset.
+ if (ParseSingleTokenVariant(color, VARIANT_INHERIT, nullptr)) {
+ // must be alone
+ for (const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(aTable[nsStyleImageLayers::shorthand]);
+ *subprops != eCSSProperty_UNKNOWN; ++subprops) {
+ AppendValue(*subprops, color);
+ }
+ return true;
+ }
+
+ nsCSSValue image, repeat, attachment, clip, origin, positionX, positionY, size,
+ composite, maskMode;
+ ImageLayersShorthandParseState state(color, image.SetListValue(),
+ repeat.SetPairListValue(),
+ attachment.SetListValue(), clip.SetListValue(),
+ origin.SetListValue(),
+ positionX.SetListValue(), positionY.SetListValue(),
+ size.SetPairListValue(), composite.SetListValue(),
+ maskMode.SetListValue());
+
+ for (;;) {
+ if (!ParseImageLayersItem(state, aTable)) {
+ return false;
+ }
+
+ // If we saw a color, this must be the last item.
+ if (color.GetUnit() != eCSSUnit_Null) {
+ MOZ_ASSERT(aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN);
+ break;
+ }
+
+ // If there's a comma, expect another item.
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+
+#define APPENDNEXT(propID_, propMember_, propType_) \
+ if (aTable[propID_] != eCSSProperty_UNKNOWN) { \
+ propMember_->mNext = new propType_; \
+ propMember_ = propMember_->mNext; \
+ }
+ // Chain another entry on all the lists.
+ APPENDNEXT(nsStyleImageLayers::image, state.mImage,
+ nsCSSValueList);
+ APPENDNEXT(nsStyleImageLayers::repeat, state.mRepeat,
+ nsCSSValuePairList);
+ APPENDNEXT(nsStyleImageLayers::clip, state.mClip,
+ nsCSSValueList);
+ APPENDNEXT(nsStyleImageLayers::origin, state.mOrigin,
+ nsCSSValueList);
+ APPENDNEXT(nsStyleImageLayers::positionX, state.mPositionX,
+ nsCSSValueList);
+ APPENDNEXT(nsStyleImageLayers::positionY, state.mPositionY,
+ nsCSSValueList);
+ APPENDNEXT(nsStyleImageLayers::size, state.mSize,
+ nsCSSValuePairList);
+ APPENDNEXT(nsStyleImageLayers::attachment, state.mAttachment,
+ nsCSSValueList);
+ APPENDNEXT(nsStyleImageLayers::maskMode, state.mMode,
+ nsCSSValueList);
+ APPENDNEXT(nsStyleImageLayers::composite, state.mComposite,
+ nsCSSValueList);
+#undef APPENDNEXT
+ }
+
+ // If we get to this point without seeing a color, provide a default.
+ if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
+ if (color.GetUnit() == eCSSUnit_Null) {
+ color.SetIntegerColorValue(NS_RGBA(0,0,0,0), eCSSUnit_RGBAColor);
+ }
+ }
+
+#define APPENDVALUE(propID_, propValue_) \
+ if (propID_ != eCSSProperty_UNKNOWN) { \
+ AppendValue(propID_, propValue_); \
+ }
+
+ APPENDVALUE(aTable[nsStyleImageLayers::image], image);
+ APPENDVALUE(aTable[nsStyleImageLayers::repeat], repeat);
+ APPENDVALUE(aTable[nsStyleImageLayers::clip], clip);
+ APPENDVALUE(aTable[nsStyleImageLayers::origin], origin);
+ APPENDVALUE(aTable[nsStyleImageLayers::positionX], positionX);
+ APPENDVALUE(aTable[nsStyleImageLayers::positionY], positionY);
+ APPENDVALUE(aTable[nsStyleImageLayers::size], size);
+ APPENDVALUE(aTable[nsStyleImageLayers::color], color);
+ APPENDVALUE(aTable[nsStyleImageLayers::attachment], attachment);
+ APPENDVALUE(aTable[nsStyleImageLayers::maskMode], maskMode);
+ APPENDVALUE(aTable[nsStyleImageLayers::composite], composite);
+
+#undef APPENDVALUE
+
+ return true;
+}
+
+// Helper for ParseImageLayersItem. Returns true if the passed-in nsCSSToken is
+// a function which is accepted for background-image.
+bool
+CSSParserImpl::IsFunctionTokenValidForImageLayerImage(
+ const nsCSSToken& aToken) const
+{
+ MOZ_ASSERT(aToken.mType == eCSSToken_Function,
+ "Should only be called for function-typed tokens");
+
+ const nsAString& funcName = aToken.mIdent;
+
+ return funcName.LowerCaseEqualsLiteral("linear-gradient") ||
+ funcName.LowerCaseEqualsLiteral("radial-gradient") ||
+ funcName.LowerCaseEqualsLiteral("repeating-linear-gradient") ||
+ funcName.LowerCaseEqualsLiteral("repeating-radial-gradient") ||
+ funcName.LowerCaseEqualsLiteral("-moz-linear-gradient") ||
+ funcName.LowerCaseEqualsLiteral("-moz-radial-gradient") ||
+ funcName.LowerCaseEqualsLiteral("-moz-repeating-linear-gradient") ||
+ funcName.LowerCaseEqualsLiteral("-moz-repeating-radial-gradient") ||
+ funcName.LowerCaseEqualsLiteral("-moz-image-rect") ||
+ funcName.LowerCaseEqualsLiteral("-moz-element") ||
+ ((sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService()) &&
+ (funcName.LowerCaseEqualsLiteral("-webkit-gradient") ||
+ funcName.LowerCaseEqualsLiteral("-webkit-linear-gradient") ||
+ funcName.LowerCaseEqualsLiteral("-webkit-radial-gradient") ||
+ funcName.LowerCaseEqualsLiteral("-webkit-repeating-linear-gradient") ||
+ funcName.LowerCaseEqualsLiteral("-webkit-repeating-radial-gradient")));
+}
+
+// Parse one item of the background shorthand property.
+bool
+CSSParserImpl::ParseImageLayersItem(
+ CSSParserImpl::ImageLayersShorthandParseState& aState,
+ const nsCSSPropertyID aTable[])
+{
+ // Fill in the values that the shorthand will set if we don't find
+ // other values.
+ aState.mImage->mValue.SetNoneValue();
+ aState.mAttachment->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL,
+ eCSSUnit_Enumerated);
+ aState.mClip->mValue.SetIntValue(NS_STYLE_IMAGELAYER_CLIP_BORDER,
+ eCSSUnit_Enumerated);
+
+ aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_REPEAT,
+ eCSSUnit_Enumerated);
+ aState.mRepeat->mYValue.Reset();
+
+ RefPtr<nsCSSValue::Array> positionXArr = nsCSSValue::Array::Create(2);
+ RefPtr<nsCSSValue::Array> positionYArr = nsCSSValue::Array::Create(2);
+ aState.mPositionX->mValue.SetArrayValue(positionXArr, eCSSUnit_Array);
+ aState.mPositionY->mValue.SetArrayValue(positionYArr, eCSSUnit_Array);
+
+ if (eCSSProperty_mask == aTable[nsStyleImageLayers::shorthand]) {
+ aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_BORDER,
+ eCSSUnit_Enumerated);
+ } else {
+ aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_PADDING,
+ eCSSUnit_Enumerated);
+ }
+ positionXArr->Item(1).SetPercentValue(0.0f);
+ positionYArr->Item(1).SetPercentValue(0.0f);
+
+ aState.mSize->mXValue.SetAutoValue();
+ aState.mSize->mYValue.SetAutoValue();
+ aState.mComposite->mValue.SetIntValue(NS_STYLE_MASK_COMPOSITE_ADD,
+ eCSSUnit_Enumerated);
+ aState.mMode->mValue.SetIntValue(NS_STYLE_MASK_MODE_MATCH_SOURCE,
+ eCSSUnit_Enumerated);
+ bool haveColor = false,
+ haveImage = false,
+ haveRepeat = false,
+ haveAttach = false,
+ havePositionAndSize = false,
+ haveOrigin = false,
+ haveComposite = false,
+ haveMode = false,
+ haveSomething = false;
+
+ while (GetToken(true)) {
+ nsCSSTokenType tt = mToken.mType;
+ UngetToken(); // ...but we'll still cheat and use mToken
+ if (tt == eCSSToken_Symbol) {
+ // ExpectEndProperty only looks for symbols, and nothing else will
+ // show up as one.
+ break;
+ }
+
+ if (tt == eCSSToken_Ident) {
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+ int32_t dummy;
+ if (keyword == eCSSKeyword_inherit ||
+ keyword == eCSSKeyword_initial ||
+ keyword == eCSSKeyword_unset) {
+ return false;
+ } else if (keyword == eCSSKeyword_none) {
+ if (haveImage)
+ return false;
+ haveImage = true;
+ if (ParseSingleValueProperty(aState.mImage->mValue,
+ aTable[nsStyleImageLayers::image]) !=
+ CSSParseResult::Ok) {
+ NS_NOTREACHED("should be able to parse");
+ return false;
+ }
+ } else if (aTable[nsStyleImageLayers::attachment] !=
+ eCSSProperty_UNKNOWN &&
+ nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kImageLayerAttachmentKTable, dummy)) {
+ if (haveAttach)
+ return false;
+ haveAttach = true;
+ if (ParseSingleValueProperty(aState.mAttachment->mValue,
+ aTable[nsStyleImageLayers::attachment]) !=
+ CSSParseResult::Ok) {
+ NS_NOTREACHED("should be able to parse");
+ return false;
+ }
+ } else if (nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kImageLayerRepeatKTable, dummy)) {
+ if (haveRepeat)
+ return false;
+ haveRepeat = true;
+ nsCSSValuePair scratch;
+ if (!ParseImageLayerRepeatValues(scratch)) {
+ NS_NOTREACHED("should be able to parse");
+ return false;
+ }
+ aState.mRepeat->mXValue = scratch.mXValue;
+ aState.mRepeat->mYValue = scratch.mYValue;
+ } else if (nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kImageLayerPositionKTable, dummy)) {
+ if (havePositionAndSize)
+ return false;
+ havePositionAndSize = true;
+
+ if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
+ aState.mPositionY->mValue)) {
+ return false;
+ }
+ if (ExpectSymbol('/', true)) {
+ nsCSSValuePair scratch;
+ if (!ParseImageLayerSizeValues(scratch)) {
+ return false;
+ }
+ aState.mSize->mXValue = scratch.mXValue;
+ aState.mSize->mYValue = scratch.mYValue;
+ }
+ } else if (nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kImageLayerOriginKTable, dummy)) {
+ if (haveOrigin)
+ return false;
+ haveOrigin = true;
+ if (ParseSingleValueProperty(aState.mOrigin->mValue,
+ aTable[nsStyleImageLayers::origin]) !=
+ CSSParseResult::Ok) {
+ NS_NOTREACHED("should be able to parse");
+ return false;
+ }
+
+ // The spec allows a second box value (for background-clip),
+ // immediately following the first one (for background-origin).
+
+#ifdef DEBUG
+ for (size_t i = 0; nsCSSProps::kImageLayerOriginKTable[i].mValue != -1; i++) {
+ // For each keyword & value in kOriginKTable, ensure that
+ // kBackgroundKTable has a matching entry at the same position.
+ MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mKeyword ==
+ nsCSSProps::kBackgroundClipKTable[i].mKeyword);
+ MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mValue ==
+ nsCSSProps::kBackgroundClipKTable[i].mValue);
+ }
+#endif
+ static_assert(NS_STYLE_IMAGELAYER_CLIP_BORDER ==
+ NS_STYLE_IMAGELAYER_ORIGIN_BORDER &&
+ NS_STYLE_IMAGELAYER_CLIP_PADDING ==
+ NS_STYLE_IMAGELAYER_ORIGIN_PADDING &&
+ NS_STYLE_IMAGELAYER_CLIP_CONTENT ==
+ NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
+ "bg-clip and bg-origin style constants must agree");
+
+ CSSParseResult result =
+ ParseSingleValueProperty(aState.mClip->mValue,
+ aTable[nsStyleImageLayers::clip]);
+ MOZ_ASSERT(result != CSSParseResult::Error,
+ "how can failing to parse a single background-clip value "
+ "consume tokens?");
+ if (result == CSSParseResult::NotFound) {
+ // When exactly one <box> value is set, it is used for both
+ // 'background-origin' and 'background-clip'.
+ // See assertions above showing these values are compatible.
+ aState.mClip->mValue = aState.mOrigin->mValue;
+ }
+ } else if (aTable[nsStyleImageLayers::composite] != eCSSProperty_UNKNOWN &&
+ nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kImageLayerCompositeKTable, dummy)) {
+ if (haveComposite)
+ return false;
+ haveComposite = true;
+ if (ParseSingleValueProperty(aState.mComposite->mValue,
+ aTable[nsStyleImageLayers::composite]) !=
+ CSSParseResult::Ok) {
+ NS_NOTREACHED("should be able to parse");
+ return false;
+ }
+ } else if (aTable[nsStyleImageLayers::maskMode] != eCSSProperty_UNKNOWN &&
+ nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kImageLayerModeKTable, dummy)) {
+ if (haveMode)
+ return false;
+ haveMode = true;
+ if (ParseSingleValueProperty(aState.mMode->mValue,
+ aTable[nsStyleImageLayers::maskMode]) !=
+ CSSParseResult::Ok) {
+ NS_NOTREACHED("should be able to parse");
+ return false;
+ }
+ } else if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
+ if (haveColor)
+ return false;
+ haveColor = true;
+ if (ParseSingleValueProperty(aState.mColor,
+ aTable[nsStyleImageLayers::color]) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else if (tt == eCSSToken_URL ||
+ (tt == eCSSToken_Function &&
+ IsFunctionTokenValidForImageLayerImage(mToken))) {
+ if (haveImage)
+ return false;
+ haveImage = true;
+ if (ParseSingleValueProperty(aState.mImage->mValue,
+ aTable[nsStyleImageLayers::image]) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+ } else if (tt == eCSSToken_Dimension ||
+ tt == eCSSToken_Number ||
+ tt == eCSSToken_Percentage ||
+ (tt == eCSSToken_Function &&
+ (mToken.mIdent.LowerCaseEqualsLiteral("calc") ||
+ mToken.mIdent.LowerCaseEqualsLiteral("-moz-calc")))) {
+ if (havePositionAndSize)
+ return false;
+ havePositionAndSize = true;
+ if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
+ aState.mPositionY->mValue)) {
+ return false;
+ }
+ if (ExpectSymbol('/', true)) {
+ nsCSSValuePair scratch;
+ if (!ParseImageLayerSizeValues(scratch)) {
+ return false;
+ }
+ aState.mSize->mXValue = scratch.mXValue;
+ aState.mSize->mYValue = scratch.mYValue;
+ }
+ } else if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
+ if (haveColor)
+ return false;
+ haveColor = true;
+ // Note: This parses 'inherit', 'initial' and 'unset', but
+ // we've already checked for them, so it's ok.
+ if (ParseSingleValueProperty(aState.mColor,
+ aTable[nsStyleImageLayers::color]) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ haveSomething = true;
+ }
+
+ return haveSomething;
+}
+
+// This function is very similar to ParseScrollSnapCoordinate,
+// ParseImageLayerPosition, and ParseImageLayersSize.
+bool
+CSSParserImpl::ParseValueList(nsCSSPropertyID aPropID)
+{
+ // aPropID is a single value prop-id
+ nsCSSValue value;
+ // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ nsCSSValueList* item = value.SetListValue();
+ for (;;) {
+ if (ParseSingleValueProperty(item->mValue, aPropID) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ }
+ }
+ AppendValue(aPropID, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseImageLayerRepeat(nsCSSPropertyID aPropID)
+{
+ nsCSSValue value;
+ // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ nsCSSValuePair valuePair;
+ if (!ParseImageLayerRepeatValues(valuePair)) {
+ return false;
+ }
+ nsCSSValuePairList* item = value.SetPairListValue();
+ for (;;) {
+ item->mXValue = valuePair.mXValue;
+ item->mYValue = valuePair.mYValue;
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+ if (!ParseImageLayerRepeatValues(valuePair)) {
+ return false;
+ }
+ item->mNext = new nsCSSValuePairList;
+ item = item->mNext;
+ }
+ }
+
+ AppendValue(aPropID, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseImageLayerRepeatValues(nsCSSValuePair& aValue)
+{
+ nsCSSValue& xValue = aValue.mXValue;
+ nsCSSValue& yValue = aValue.mYValue;
+
+ if (ParseEnum(xValue, nsCSSProps::kImageLayerRepeatKTable)) {
+ int32_t value = xValue.GetIntValue();
+ // For single values set yValue as eCSSUnit_Null.
+ if (value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X ||
+ value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y ||
+ !ParseEnum(yValue, nsCSSProps::kImageLayerRepeatPartKTable)) {
+ // the caller will fail cases like "repeat-x no-repeat"
+ // by expecting a list separator or an end property.
+ yValue.Reset();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool
+CSSParserImpl::ParseImageLayerPosition(const nsCSSPropertyID aTable[])
+{
+ // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
+ nsCSSValue position;
+ if (ParseSingleTokenVariant(position, VARIANT_INHERIT, nullptr)) {
+ AppendValue(aTable[nsStyleImageLayers::positionX], position);
+ AppendValue(aTable[nsStyleImageLayers::positionY], position);
+ return true;
+ }
+
+ nsCSSValue itemValueX;
+ nsCSSValue itemValueY;
+ if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
+ return false;
+ }
+
+ nsCSSValue valueX;
+ nsCSSValue valueY;
+ nsCSSValueList* itemX = valueX.SetListValue();
+ nsCSSValueList* itemY = valueY.SetListValue();
+ for (;;) {
+ itemX->mValue = itemValueX;
+ itemY->mValue = itemValueY;
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+ if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
+ return false;
+ }
+ itemX->mNext = new nsCSSValueList;
+ itemY->mNext = new nsCSSValueList;
+ itemX = itemX->mNext;
+ itemY = itemY->mNext;
+ }
+ AppendValue(aTable[nsStyleImageLayers::positionX], valueX);
+ AppendValue(aTable[nsStyleImageLayers::positionY], valueY);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal)
+{
+ nsCSSValue value;
+ // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ nsCSSValue itemValue;
+ if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
+ return false;
+ }
+ nsCSSValueList* item = value.SetListValue();
+ for (;;) {
+ item->mValue = itemValue;
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+ if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
+ return false;
+ }
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ }
+ }
+ AppendValue(aPropID, value);
+ return true;
+}
+
+/**
+ * BoxPositionMaskToCSSValue and ParseBoxPositionValues are used
+ * for parsing the CSS 2.1 background-position syntax (which has at
+ * most two values). (Compare to the css3-background syntax which
+ * takes up to four values.) Some current CSS specifications that
+ * use background-position-like syntax still use this old syntax.
+ **
+ * Parses two values that correspond to positions in a box. These can be
+ * values corresponding to percentages of the box, raw offsets, or keywords
+ * like "top," "left center," etc.
+ *
+ * @param aOut The nsCSSValuePair in which to place the result.
+ * @param aAcceptsInherit If true, 'inherit', 'initial' and 'unset' are
+ * legal values
+ * @param aAllowExplicitCenter If true, 'center' is a legal value
+ * @return Whether or not the operation succeeded.
+ */
+bool CSSParserImpl::ParseBoxPositionValues(nsCSSValuePair &aOut,
+ bool aAcceptsInherit,
+ bool aAllowExplicitCenter)
+{
+ // First try a percentage or a length value
+ nsCSSValue &xValue = aOut.mXValue,
+ &yValue = aOut.mYValue;
+ int32_t variantMask =
+ (aAcceptsInherit ? VARIANT_INHERIT : 0) | VARIANT_LP | VARIANT_CALC;
+ CSSParseResult result = ParseVariant(xValue, variantMask, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::Ok) {
+ if (eCSSUnit_Inherit == xValue.GetUnit() ||
+ eCSSUnit_Initial == xValue.GetUnit() ||
+ eCSSUnit_Unset == xValue.GetUnit()) { // both are inherit, initial or unset
+ yValue = xValue;
+ return true;
+ }
+ // We have one percentage/length/calc. Get the optional second
+ // percentage/length/calc/keyword.
+ result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::Ok) {
+ // We have two numbers
+ return true;
+ }
+
+ if (ParseEnum(yValue, nsCSSProps::kImageLayerPositionKTable)) {
+ int32_t yVal = yValue.GetIntValue();
+ if (!(yVal & BG_CTB)) {
+ // The second keyword can only be 'center', 'top', or 'bottom'
+ return false;
+ }
+ yValue = BoxPositionMaskToCSSValue(yVal, false);
+ return true;
+ }
+
+ // If only one percentage or length value is given, it sets the
+ // horizontal position only, and the vertical position will be 50%.
+ yValue.SetPercentValue(0.5f);
+ return true;
+ }
+
+ // Now try keywords. We do this manually to allow for the first
+ // appearance of "center" to apply to the either the x or y
+ // position (it's ambiguous so we have to disambiguate). Each
+ // allowed keyword value is assigned it's own bit. We don't allow
+ // any duplicate keywords other than center. We try to get two
+ // keywords but it's okay if there is only one.
+ int32_t mask = 0;
+ if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) {
+ int32_t bit = xValue.GetIntValue();
+ mask |= bit;
+ if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) {
+ bit = xValue.GetIntValue();
+ if (mask & (bit & ~BG_CENTER)) {
+ // Only the 'center' keyword can be duplicated.
+ return false;
+ }
+ mask |= bit;
+ }
+ else {
+ // Only one keyword. See if we have a length, percentage, or calc.
+ result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::Ok) {
+ if (!(mask & BG_CLR)) {
+ // The first keyword can only be 'center', 'left', or 'right'
+ return false;
+ }
+
+ xValue = BoxPositionMaskToCSSValue(mask, true);
+ return true;
+ }
+ }
+ }
+
+ // Check for bad input. Bad input consists of no matching keywords,
+ // or pairs of x keywords or pairs of y keywords.
+ if ((mask == 0) || (mask == (BG_TOP | BG_BOTTOM)) ||
+ (mask == (BG_LEFT | BG_RIGHT)) ||
+ (!aAllowExplicitCenter && (mask & BG_CENTER))) {
+ return false;
+ }
+
+ // Create style values
+ xValue = BoxPositionMaskToCSSValue(mask, true);
+ yValue = BoxPositionMaskToCSSValue(mask, false);
+ return true;
+}
+
+// Parses a CSS <position> value, for e.g. the 'background-position' property.
+// Spec reference: http://www.w3.org/TR/css3-background/#ltpositiongt
+// Invariants:
+// - Always produces a four-value array on a successful parse.
+// - The values are: X edge, X offset, Y edge, Y offset.
+// - Edges are always keywords or null.
+// - A |center| edge will not have an offset.
+bool
+CSSParserImpl::ParsePositionValue(nsCSSValue& aOut)
+{
+ RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(4);
+ aOut.SetArrayValue(value, eCSSUnit_Array);
+
+ // The following clarifies organisation of the array.
+ nsCSSValue &xEdge = value->Item(0),
+ &xOffset = value->Item(1),
+ &yEdge = value->Item(2),
+ &yOffset = value->Item(3);
+
+ // Parse all the values into the array.
+ uint32_t valueCount = 0;
+ for (int32_t i = 0; i < 4; i++) {
+ CSSParseResult result =
+ ParseVariant(value->Item(i), VARIANT_LPCALC | VARIANT_KEYWORD,
+ nsCSSProps::kImageLayerPositionKTable);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::NotFound) {
+ break;
+ }
+ ++valueCount;
+ }
+
+ switch (valueCount) {
+ case 4:
+ // "If three or four values are given, then each <percentage> or <length>
+ // represents an offset and must be preceded by a keyword, which specifies
+ // from which edge the offset is given."
+ if (eCSSUnit_Enumerated != xEdge.GetUnit() ||
+ BG_CENTER == xEdge.GetIntValue() ||
+ eCSSUnit_Enumerated == xOffset.GetUnit() ||
+ eCSSUnit_Enumerated != yEdge.GetUnit() ||
+ BG_CENTER == yEdge.GetIntValue() ||
+ eCSSUnit_Enumerated == yOffset.GetUnit()) {
+ return false;
+ }
+ break;
+ case 3:
+ // "If three or four values are given, then each <percentage> or<length>
+ // represents an offset and must be preceded by a keyword, which specifies
+ // from which edge the offset is given." ... "If three values are given,
+ // the missing offset is assumed to be zero."
+ if (eCSSUnit_Enumerated != value->Item(1).GetUnit()) {
+ // keyword offset keyword
+ // Second value is non-keyword, thus first value must be a non-center
+ // keyword.
+ if (eCSSUnit_Enumerated != value->Item(0).GetUnit() ||
+ BG_CENTER == value->Item(0).GetIntValue()) {
+ return false;
+ }
+
+ // Remaining value must be a keyword.
+ if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) {
+ return false;
+ }
+
+ yOffset.Reset(); // Everything else is in the correct position.
+ } else if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) {
+ // keyword keyword offset
+ // Third value is non-keyword, thus second value must be non-center
+ // keyword.
+ if (BG_CENTER == value->Item(1).GetIntValue()) {
+ return false;
+ }
+
+ // Remaining value must be a keyword.
+ if (eCSSUnit_Enumerated != value->Item(0).GetUnit()) {
+ return false;
+ }
+
+ // Move the values to the correct position in the array.
+ value->Item(3) = value->Item(2); // yOffset
+ value->Item(2) = value->Item(1); // yEdge
+ value->Item(1).Reset(); // xOffset
+ } else {
+ return false;
+ }
+ break;
+ case 2:
+ // "If two values are given and at least one value is not a keyword, then
+ // the first value represents the horizontal position (or offset) and the
+ // second represents the vertical position (or offset)"
+ if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) {
+ if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) {
+ // keyword keyword
+ value->Item(2) = value->Item(1); // move yEdge to correct position
+ xOffset.Reset();
+ yOffset.Reset();
+ } else {
+ // keyword offset
+ // First value must represent horizontal position.
+ if ((BG_TOP | BG_BOTTOM) & value->Item(0).GetIntValue()) {
+ return false;
+ }
+ value->Item(3) = value->Item(1); // move yOffset to correct position
+ xOffset.Reset();
+ yEdge.Reset();
+ }
+ } else {
+ if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) {
+ // offset keyword
+ // Second value must represent vertical position.
+ if ((BG_LEFT | BG_RIGHT) & value->Item(1).GetIntValue()) {
+ return false;
+ }
+ value->Item(2) = value->Item(1); // move yEdge to correct position
+ value->Item(1) = value->Item(0); // move xOffset to correct position
+ xEdge.Reset();
+ yOffset.Reset();
+ } else {
+ // offset offset
+ value->Item(3) = value->Item(1); // move yOffset to correct position
+ value->Item(1) = value->Item(0); // move xOffset to correct position
+ xEdge.Reset();
+ yEdge.Reset();
+ }
+ }
+ break;
+ case 1:
+ // "If only one value is specified, the second value is assumed to be
+ // center."
+ if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) {
+ xOffset.Reset();
+ } else {
+ value->Item(1) = value->Item(0); // move xOffset to correct position
+ xEdge.Reset();
+ }
+ yEdge.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER, eCSSUnit_Enumerated);
+ yOffset.Reset();
+ break;
+ default:
+ return false;
+ }
+
+ // For compatibility with CSS2.1 code the edges can be unspecified.
+ // Unspecified edges are recorded as nullptr.
+ NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit() ||
+ eCSSUnit_Null == xEdge.GetUnit()) &&
+ (eCSSUnit_Enumerated == yEdge.GetUnit() ||
+ eCSSUnit_Null == yEdge.GetUnit()) &&
+ eCSSUnit_Enumerated != xOffset.GetUnit() &&
+ eCSSUnit_Enumerated != yOffset.GetUnit(),
+ "Unexpected units");
+
+ // Keywords in first and second pairs can not both be vertical or
+ // horizontal keywords. (eg. left right, bottom top). Additionally,
+ // non-center keyword can not be duplicated (eg. left left).
+ int32_t xEdgeEnum =
+ xEdge.GetUnit() == eCSSUnit_Enumerated ? xEdge.GetIntValue() : 0;
+ int32_t yEdgeEnum =
+ yEdge.GetUnit() == eCSSUnit_Enumerated ? yEdge.GetIntValue() : 0;
+ if ((xEdgeEnum | yEdgeEnum) == (BG_LEFT | BG_RIGHT) ||
+ (xEdgeEnum | yEdgeEnum) == (BG_TOP | BG_BOTTOM) ||
+ (xEdgeEnum & yEdgeEnum & ~BG_CENTER)) {
+ return false;
+ }
+
+ // The values could be in an order that is different than expected.
+ // eg. x contains vertical information, y contains horizontal information.
+ // Swap if incorrect order.
+ if (xEdgeEnum & (BG_TOP | BG_BOTTOM) ||
+ yEdgeEnum & (BG_LEFT | BG_RIGHT)) {
+ nsCSSValue swapEdge = xEdge;
+ nsCSSValue swapOffset = xOffset;
+ xEdge = yEdge;
+ xOffset = yOffset;
+ yEdge = swapEdge;
+ yOffset = swapOffset;
+ }
+
+ return true;
+}
+
+static void
+AdjustEdgeOffsetPairForBasicShape(nsCSSValue& aEdge,
+ nsCSSValue& aOffset,
+ uint8_t aDefaultEdge)
+{
+ // 0 length offsets are 0%
+ if (aOffset.IsLengthUnit() && aOffset.GetFloatValue() == 0.0) {
+ aOffset.SetPercentValue(0);
+ }
+
+ // Default edge is top/left in the 4-value case
+ // In case of 1 or 0 values, the default is center,
+ // but ParsePositionValue already handles this case
+ if (eCSSUnit_Null == aEdge.GetUnit()) {
+ aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
+ }
+ // Default offset is 0%
+ if (eCSSUnit_Null == aOffset.GetUnit()) {
+ aOffset.SetPercentValue(0.0);
+ }
+ if (eCSSUnit_Enumerated == aEdge.GetUnit() &&
+ eCSSUnit_Percent == aOffset.GetUnit()) {
+ switch (aEdge.GetIntValue()) {
+ case NS_STYLE_IMAGELAYER_POSITION_CENTER:
+ aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
+ MOZ_ASSERT(aOffset.GetPercentValue() == 0.0,
+ "center cannot be used with an offset");
+ aOffset.SetPercentValue(0.5);
+ break;
+ case NS_STYLE_IMAGELAYER_POSITION_BOTTOM:
+ MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_TOP);
+ aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
+ aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
+ break;
+ case NS_STYLE_IMAGELAYER_POSITION_RIGHT:
+ MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_LEFT);
+ aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
+ aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
+ }
+ }
+}
+
+// https://drafts.csswg.org/css-shapes/#basic-shape-serialization
+// We set values to defaults while parsing for basic shapes
+// Invariants:
+// - Always produces a four-value array on a successful parse.
+// - The values are: X edge, X offset, Y edge, Y offset
+// - Edges are always keywords (not including center)
+// - Offsets are nonnull
+// - Percentage offsets have keywords folded into them,
+// so "bottom 40%" or "right 20%" will not exist.
+bool
+CSSParserImpl::ParsePositionValueForBasicShape(nsCSSValue& aOut)
+{
+ if (!ParsePositionValue(aOut)) {
+ return false;
+ }
+ nsCSSValue::Array* value = aOut.GetArrayValue();
+ nsCSSValue& xEdge = value->Item(0);
+ nsCSSValue& xOffset = value->Item(1);
+ nsCSSValue& yEdge = value->Item(2);
+ nsCSSValue& yOffset = value->Item(3);
+ // A keyword edge + percent offset pair can be contracted
+ // into the percentage with the default value in the edge.
+ // Offset lengths which are 0 can also be rewritten as 0%
+ AdjustEdgeOffsetPairForBasicShape(xEdge, xOffset,
+ NS_STYLE_IMAGELAYER_POSITION_LEFT);
+ AdjustEdgeOffsetPairForBasicShape(yEdge, yOffset,
+ NS_STYLE_IMAGELAYER_POSITION_TOP);
+ return true;
+}
+
+bool
+CSSParserImpl::ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY)
+{
+ nsCSSValue scratch;
+ if (!ParsePositionValue(scratch)) {
+ return false;
+ }
+
+ // Separate the four values into two pairs of two values for X and Y.
+ RefPtr<nsCSSValue::Array> valueX = nsCSSValue::Array::Create(2);
+ RefPtr<nsCSSValue::Array> valueY = nsCSSValue::Array::Create(2);
+ aOutX.SetArrayValue(valueX, eCSSUnit_Array);
+ aOutY.SetArrayValue(valueY, eCSSUnit_Array);
+
+ RefPtr<nsCSSValue::Array> value = scratch.GetArrayValue();
+ valueX->Item(0) = value->Item(0);
+ valueX->Item(1) = value->Item(1);
+ valueY->Item(0) = value->Item(2);
+ valueY->Item(1) = value->Item(3);
+ return true;
+}
+
+// Parses one item in a list of values for the 'background-position-x' or
+// 'background-position-y' property. Does not support the start/end keywords.
+// Spec reference: https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x
+bool
+CSSParserImpl::ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal)
+{
+ RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(2);
+ aOut.SetArrayValue(value, eCSSUnit_Array);
+
+ nsCSSValue &edge = value->Item(0),
+ &offset = value->Item(1);
+
+ nsCSSValue edgeOrOffset;
+ CSSParseResult result =
+ ParseVariant(edgeOrOffset, VARIANT_LPCALC | VARIANT_KEYWORD,
+ nsCSSProps::kImageLayerPositionKTable);
+ if (result != CSSParseResult::Ok) {
+ return false;
+ }
+
+ if (edgeOrOffset.GetUnit() == eCSSUnit_Enumerated) {
+ edge = edgeOrOffset;
+
+ // The edge can be followed by an optional offset.
+ result = ParseVariant(offset, VARIANT_LPCALC, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ }
+ } else {
+ offset = edgeOrOffset;
+ }
+
+ // Keywords for horizontal properties cannot be vertical keywords, and
+ // keywords for vertical properties cannot be horizontal keywords.
+ // Also, if an offset is specified, the edge cannot be center.
+ int32_t edgeEnum =
+ edge.GetUnit() == eCSSUnit_Enumerated ? edge.GetIntValue() : 0;
+ int32_t allowedKeywords =
+ (aIsHorizontal ? (BG_LEFT | BG_RIGHT) : (BG_TOP | BG_BOTTOM)) |
+ (offset.GetUnit() == eCSSUnit_Null ? BG_CENTER : 0);
+ if (edgeEnum & ~allowedKeywords) {
+ return false;
+ }
+
+ NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() ||
+ eCSSUnit_Null == edge.GetUnit()) &&
+ eCSSUnit_Enumerated != offset.GetUnit(),
+ "Unexpected units");
+
+ return true;
+}
+
+// This function is very similar to ParseScrollSnapCoordinate,
+// ParseImageLayers, and ParseImageLayerPosition.
+bool
+CSSParserImpl::ParseImageLayerSize(nsCSSPropertyID aPropID)
+{
+ nsCSSValue value;
+ // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ nsCSSValuePair valuePair;
+ if (!ParseImageLayerSizeValues(valuePair)) {
+ return false;
+ }
+ nsCSSValuePairList* item = value.SetPairListValue();
+ for (;;) {
+ item->mXValue = valuePair.mXValue;
+ item->mYValue = valuePair.mYValue;
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+ if (!ParseImageLayerSizeValues(valuePair)) {
+ return false;
+ }
+ item->mNext = new nsCSSValuePairList;
+ item = item->mNext;
+ }
+ }
+ AppendValue(aPropID, value);
+ return true;
+}
+
+/**
+ * Parses two values that correspond to lengths for the background-size
+ * property. These can be one or two lengths (or the 'auto' keyword) or
+ * percentages corresponding to the element's dimensions or the single keywords
+ * 'contain' or 'cover'. 'initial', 'inherit' and 'unset' must be handled by
+ * the caller if desired.
+ *
+ * @param aOut The nsCSSValuePair in which to place the result.
+ * @return Whether or not the operation succeeded.
+ */
+#define BG_SIZE_VARIANT (VARIANT_LP | VARIANT_AUTO | VARIANT_CALC)
+bool CSSParserImpl::ParseImageLayerSizeValues(nsCSSValuePair &aOut)
+{
+ // First try a percentage or a length value
+ nsCSSValue &xValue = aOut.mXValue,
+ &yValue = aOut.mYValue;
+ CSSParseResult result =
+ ParseNonNegativeVariant(xValue, BG_SIZE_VARIANT, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::Ok) {
+ // We have one percentage/length/calc/auto. Get the optional second
+ // percentage/length/calc/keyword.
+ result = ParseNonNegativeVariant(yValue, BG_SIZE_VARIANT, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::Ok) {
+ // We have a second percentage/length/calc/auto.
+ return true;
+ }
+
+ // If only one percentage or length value is given, it sets the
+ // horizontal size only, and the vertical size will be as if by 'auto'.
+ yValue.SetAutoValue();
+ return true;
+ }
+
+ // Now address 'contain' and 'cover'.
+ if (!ParseEnum(xValue, nsCSSProps::kImageLayerSizeKTable))
+ return false;
+ yValue.Reset();
+ return true;
+}
+
+#undef BG_SIZE_VARIANT
+
+bool
+CSSParserImpl::ParseBorderColor()
+{
+ return ParseBoxProperties(kBorderColorIDs);
+}
+
+void
+CSSParserImpl::SetBorderImageInitialValues()
+{
+ // border-image-source: none
+ nsCSSValue source;
+ source.SetNoneValue();
+ AppendValue(eCSSProperty_border_image_source, source);
+
+ // border-image-slice: 100%
+ nsCSSValue sliceBoxValue;
+ nsCSSRect& sliceBox = sliceBoxValue.SetRectValue();
+ sliceBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Percent));
+ nsCSSValue slice;
+ nsCSSValueList* sliceList = slice.SetListValue();
+ sliceList->mValue = sliceBoxValue;
+ AppendValue(eCSSProperty_border_image_slice, slice);
+
+ // border-image-width: 1
+ nsCSSValue width;
+ nsCSSRect& widthBox = width.SetRectValue();
+ widthBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Number));
+ AppendValue(eCSSProperty_border_image_width, width);
+
+ // border-image-outset: 0
+ nsCSSValue outset;
+ nsCSSRect& outsetBox = outset.SetRectValue();
+ outsetBox.SetAllSidesTo(nsCSSValue(0.0f, eCSSUnit_Number));
+ AppendValue(eCSSProperty_border_image_outset, outset);
+
+ // border-image-repeat: repeat
+ nsCSSValue repeat;
+ nsCSSValuePair repeatPair;
+ repeatPair.SetBothValuesTo(nsCSSValue(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH,
+ eCSSUnit_Enumerated));
+ repeat.SetPairValue(&repeatPair);
+ AppendValue(eCSSProperty_border_image_repeat, repeat);
+}
+
+bool
+CSSParserImpl::ParseBorderImageSlice(bool aAcceptsInherit,
+ bool* aConsumedTokens)
+{
+ // border-image-slice: initial | [<number>|<percentage>]{1,4} && fill?
+ nsCSSValue value;
+
+ if (aConsumedTokens) {
+ *aConsumedTokens = true;
+ }
+
+ if (aAcceptsInherit &&
+ ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ // Keywords "inherit", "initial" and "unset" can not be mixed, so we
+ // are done.
+ AppendValue(eCSSProperty_border_image_slice, value);
+ return true;
+ }
+
+ // Try parsing "fill" value.
+ nsCSSValue imageSliceFillValue;
+ bool hasFill = ParseEnum(imageSliceFillValue,
+ nsCSSProps::kBorderImageSliceKTable);
+
+ // Parse the box dimensions.
+ nsCSSValue imageSliceBoxValue;
+ if (!ParseGroupedBoxProperty(VARIANT_PN, imageSliceBoxValue,
+ CSS_PROPERTY_VALUE_NONNEGATIVE)) {
+ if (!hasFill && aConsumedTokens) {
+ *aConsumedTokens = false;
+ }
+
+ return false;
+ }
+
+ // Try parsing "fill" keyword again if the first time failed because keyword
+ // and slice dimensions can be in any order.
+ if (!hasFill) {
+ hasFill = ParseEnum(imageSliceFillValue,
+ nsCSSProps::kBorderImageSliceKTable);
+ }
+
+ nsCSSValueList* borderImageSlice = value.SetListValue();
+ // Put the box value into the list.
+ borderImageSlice->mValue = imageSliceBoxValue;
+
+ if (hasFill) {
+ // Put the "fill" value into the list.
+ borderImageSlice->mNext = new nsCSSValueList;
+ borderImageSlice->mNext->mValue = imageSliceFillValue;
+ }
+
+ AppendValue(eCSSProperty_border_image_slice, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBorderImageWidth(bool aAcceptsInherit)
+{
+ // border-image-width: initial | [<length>|<number>|<percentage>|auto]{1,4}
+ nsCSSValue value;
+
+ if (aAcceptsInherit &&
+ ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ // Keywords "inherit", "initial" and "unset" can not be mixed, so we
+ // are done.
+ AppendValue(eCSSProperty_border_image_width, value);
+ return true;
+ }
+
+ // Parse the box dimensions.
+ if (!ParseGroupedBoxProperty(VARIANT_ALPN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) {
+ return false;
+ }
+
+ AppendValue(eCSSProperty_border_image_width, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBorderImageOutset(bool aAcceptsInherit)
+{
+ // border-image-outset: initial | [<length>|<number>]{1,4}
+ nsCSSValue value;
+
+ if (aAcceptsInherit &&
+ ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ // Keywords "inherit", "initial" and "unset" can not be mixed, so we
+ // are done.
+ AppendValue(eCSSProperty_border_image_outset, value);
+ return true;
+ }
+
+ // Parse the box dimensions.
+ if (!ParseGroupedBoxProperty(VARIANT_LN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) {
+ return false;
+ }
+
+ AppendValue(eCSSProperty_border_image_outset, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBorderImageRepeat(bool aAcceptsInherit)
+{
+ nsCSSValue value;
+ if (aAcceptsInherit &&
+ ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ // Keywords "inherit", "initial" and "unset" can not be mixed, so we
+ // are done.
+ AppendValue(eCSSProperty_border_image_repeat, value);
+ return true;
+ }
+
+ nsCSSValuePair result;
+ if (!ParseEnum(result.mXValue, nsCSSProps::kBorderImageRepeatKTable)) {
+ return false;
+ }
+
+ // optional second keyword, defaults to first
+ if (!ParseEnum(result.mYValue, nsCSSProps::kBorderImageRepeatKTable)) {
+ result.mYValue = result.mXValue;
+ }
+
+ value.SetPairValue(&result);
+ AppendValue(eCSSProperty_border_image_repeat, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBorderImage()
+{
+ nsAutoParseCompoundProperty compound(this);
+
+ // border-image: inherit | initial |
+ // <border-image-source> ||
+ // <border-image-slice>
+ // [ / <border-image-width> |
+ // / <border-image-width>? / <border-image-outset> ]? ||
+ // <border-image-repeat>
+
+ nsCSSValue value;
+ if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ AppendValue(eCSSProperty_border_image_source, value);
+ AppendValue(eCSSProperty_border_image_slice, value);
+ AppendValue(eCSSProperty_border_image_width, value);
+ AppendValue(eCSSProperty_border_image_outset, value);
+ AppendValue(eCSSProperty_border_image_repeat, value);
+ // Keywords "inherit", "initial" and "unset" can't be mixed, so we are done.
+ return true;
+ }
+
+ // No empty property.
+ if (CheckEndProperty()) {
+ return false;
+ }
+
+ // Shorthand properties are required to set everything they can.
+ SetBorderImageInitialValues();
+
+ bool foundSource = false;
+ bool foundSliceWidthOutset = false;
+ bool foundRepeat = false;
+
+ // This loop is used to handle the parsing of border-image properties which
+ // can appear in any order.
+ nsCSSValue imageSourceValue;
+ while (!CheckEndProperty()) {
+ // <border-image-source>
+ if (!foundSource) {
+ CSSParseResult result =
+ ParseVariant(imageSourceValue, VARIANT_IMAGE, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::Ok) {
+ AppendValue(eCSSProperty_border_image_source, imageSourceValue);
+ foundSource = true;
+ continue;
+ }
+ }
+
+ // <border-image-slice>
+ // ParseBorderImageSlice is weird. It may consume tokens and then return
+ // false, because it parses a property with two required components that
+ // can appear in either order. Since the tokens that were consumed cannot
+ // parse as anything else we care about, this isn't a problem.
+ if (!foundSliceWidthOutset) {
+ bool sliceConsumedTokens = false;
+ if (ParseBorderImageSlice(false, &sliceConsumedTokens)) {
+ foundSliceWidthOutset = true;
+
+ // [ / <border-image-width>?
+ if (ExpectSymbol('/', true)) {
+ bool foundBorderImageWidth = ParseBorderImageWidth(false);
+
+ // [ / <border-image-outset>
+ if (ExpectSymbol('/', true)) {
+ if (!ParseBorderImageOutset(false)) {
+ return false;
+ }
+ } else if (!foundBorderImageWidth) {
+ // If this part has an trailing slash, the whole declaration is
+ // invalid.
+ return false;
+ }
+ }
+
+ continue;
+ } else {
+ // If we consumed some tokens for <border-image-slice> but did not
+ // successfully parse it, we have an error.
+ if (sliceConsumedTokens) {
+ return false;
+ }
+ }
+ }
+
+ // <border-image-repeat>
+ if (!foundRepeat && ParseBorderImageRepeat(false)) {
+ foundRepeat = true;
+ continue;
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBorderSpacing()
+{
+ nsCSSValue xValue, yValue;
+ if (ParseNonNegativeVariant(xValue, VARIANT_HL | VARIANT_CALC, nullptr) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+
+ // If we have one length, get the optional second length.
+ // set the second value equal to the first.
+ if (xValue.IsLengthUnit() || xValue.IsCalcUnit()) {
+ if (ParseNonNegativeVariant(yValue, VARIANT_LENGTH | VARIANT_CALC,
+ nullptr) == CSSParseResult::Error) {
+ return false;
+ }
+ }
+
+ if (yValue == xValue || yValue.GetUnit() == eCSSUnit_Null) {
+ AppendValue(eCSSProperty_border_spacing, xValue);
+ } else {
+ nsCSSValue pair;
+ pair.SetPairValue(xValue, yValue);
+ AppendValue(eCSSProperty_border_spacing, pair);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBorderSide(const nsCSSPropertyID aPropIDs[],
+ bool aSetAllSides)
+{
+ const int32_t numProps = 3;
+ nsCSSValue values[numProps];
+
+ int32_t found = ParseChoice(values, aPropIDs, numProps);
+ if (found < 1) {
+ return false;
+ }
+
+ if ((found & 1) == 0) { // Provide default border-width
+ values[0].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated);
+ }
+ if ((found & 2) == 0) { // Provide default border-style
+ values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated);
+ }
+ if ((found & 4) == 0) { // text color will be used
+ values[2].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ }
+
+ if (aSetAllSides) {
+ // Parsing "border" shorthand; set all four sides to the same thing
+ for (int32_t index = 0; index < 4; index++) {
+ NS_ASSERTION(numProps == 3, "This code needs updating");
+ AppendValue(kBorderWidthIDs[index], values[0]);
+ AppendValue(kBorderStyleIDs[index], values[1]);
+ AppendValue(kBorderColorIDs[index], values[2]);
+ }
+
+ static const nsCSSPropertyID kBorderColorsProps[] = {
+ eCSSProperty_border_top_colors,
+ eCSSProperty_border_right_colors,
+ eCSSProperty_border_bottom_colors,
+ eCSSProperty_border_left_colors
+ };
+
+ // Set the other properties that the border shorthand sets to their
+ // initial values.
+ nsCSSValue extraValue;
+ switch (values[0].GetUnit()) {
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ extraValue = values[0];
+ // Set value of border-image properties to initial/inherit/unset
+ AppendValue(eCSSProperty_border_image_source, extraValue);
+ AppendValue(eCSSProperty_border_image_slice, extraValue);
+ AppendValue(eCSSProperty_border_image_width, extraValue);
+ AppendValue(eCSSProperty_border_image_outset, extraValue);
+ AppendValue(eCSSProperty_border_image_repeat, extraValue);
+ break;
+ default:
+ extraValue.SetNoneValue();
+ SetBorderImageInitialValues();
+ break;
+ }
+ NS_FOR_CSS_SIDES(side) {
+ AppendValue(kBorderColorsProps[side], extraValue);
+ }
+ }
+ else {
+ // Just set our one side
+ for (int32_t index = 0; index < numProps; index++) {
+ AppendValue(aPropIDs[index], values[index]);
+ }
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBorderStyle()
+{
+ return ParseBoxProperties(kBorderStyleIDs);
+}
+
+bool
+CSSParserImpl::ParseBorderWidth()
+{
+ return ParseBoxProperties(kBorderWidthIDs);
+}
+
+bool
+CSSParserImpl::ParseBorderColors(nsCSSPropertyID aProperty)
+{
+ nsCSSValue value;
+ // 'inherit', 'initial', 'unset' and 'none' are only allowed on their own
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
+ nullptr)) {
+ nsCSSValueList *cur = value.SetListValue();
+ for (;;) {
+ if (ParseVariant(cur->mValue, VARIANT_COLOR, nullptr) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+ if (CheckEndProperty()) {
+ break;
+ }
+ cur->mNext = new nsCSSValueList;
+ cur = cur->mNext;
+ }
+ }
+ AppendValue(aProperty, value);
+ return true;
+}
+
+// Parse the top level of a calc() expression.
+bool
+CSSParserImpl::ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask)
+{
+ // Parsing calc expressions requires, in a number of cases, looking
+ // for a token that is *either* a value of the property or a number.
+ // This can be done without lookahead when we assume that the property
+ // values cannot themselves be numbers.
+ MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
+
+ bool oldUnitlessLengthQuirk = mUnitlessLengthQuirk;
+ mUnitlessLengthQuirk = false;
+
+ // One-iteration loop so we can break to the error-handling case.
+ do {
+ // The toplevel of a calc() is always an nsCSSValue::Array of length 1.
+ RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(1);
+
+ if (!ParseCalcAdditiveExpression(arr->Item(0), aVariantMask))
+ break;
+
+ if (!ExpectSymbol(')', true))
+ break;
+
+ aValue.SetArrayValue(arr, eCSSUnit_Calc);
+ mUnitlessLengthQuirk = oldUnitlessLengthQuirk;
+ return true;
+ } while (false);
+
+ SkipUntil(')');
+ mUnitlessLengthQuirk = oldUnitlessLengthQuirk;
+ return false;
+}
+
+// We optimize away the <value-expression> production given that
+// ParseVariant consumes initial whitespace and we call
+// ExpectSymbol(')') with true for aSkipWS.
+// * If aVariantMask is VARIANT_NUMBER, this function parses the
+// <number-additive-expression> production.
+// * If aVariantMask does not contain VARIANT_NUMBER, this function
+// parses the <value-additive-expression> production.
+// * Otherwise (VARIANT_NUMBER and other bits) this function parses
+// whichever one of the productions matches ***and modifies
+// aVariantMask*** to reflect which one it has parsed by either
+// removing VARIANT_NUMBER or removing all other bits.
+// It does so iteratively, but builds the correct recursive
+// data structure.
+bool
+CSSParserImpl::ParseCalcAdditiveExpression(nsCSSValue& aValue,
+ uint32_t& aVariantMask)
+{
+ MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
+ nsCSSValue *storage = &aValue;
+ for (;;) {
+ bool haveWS;
+ if (!ParseCalcMultiplicativeExpression(*storage, aVariantMask, &haveWS))
+ return false;
+
+ if (!haveWS || !GetToken(false))
+ return true;
+ nsCSSUnit unit;
+ if (mToken.IsSymbol('+')) {
+ unit = eCSSUnit_Calc_Plus;
+ } else if (mToken.IsSymbol('-')) {
+ unit = eCSSUnit_Calc_Minus;
+ } else {
+ UngetToken();
+ return true;
+ }
+ if (!RequireWhitespace())
+ return false;
+
+ RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
+ arr->Item(0) = aValue;
+ storage = &arr->Item(1);
+ aValue.SetArrayValue(arr, unit);
+ }
+}
+
+struct ReduceNumberCalcOps : public mozilla::css::BasicFloatCalcOps,
+ public mozilla::css::CSSValueInputCalcOps
+{
+ result_type ComputeLeafValue(const nsCSSValue& aValue)
+ {
+ MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
+ return aValue.GetFloatValue();
+ }
+
+ float ComputeNumber(const nsCSSValue& aValue)
+ {
+ return mozilla::css::ComputeCalc(aValue, *this);
+ }
+};
+
+// * If aVariantMask is VARIANT_NUMBER, this function parses the
+// <number-multiplicative-expression> production.
+// * If aVariantMask does not contain VARIANT_NUMBER, this function
+// parses the <value-multiplicative-expression> production.
+// * Otherwise (VARIANT_NUMBER and other bits) this function parses
+// whichever one of the productions matches ***and modifies
+// aVariantMask*** to reflect which one it has parsed by either
+// removing VARIANT_NUMBER or removing all other bits.
+// It does so iteratively, but builds the correct recursive data
+// structure.
+// This function always consumes *trailing* whitespace when it returns
+// true; whether there was any such whitespace is returned in the
+// aHadFinalWS parameter.
+bool
+CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
+ uint32_t& aVariantMask,
+ bool *aHadFinalWS)
+{
+ MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
+ bool gotValue = false; // already got the part with the unit
+ bool afterDivision = false;
+
+ nsCSSValue *storage = &aValue;
+ for (;;) {
+ uint32_t variantMask;
+ if (afterDivision || gotValue) {
+ variantMask = VARIANT_NUMBER;
+ } else {
+ variantMask = aVariantMask | VARIANT_NUMBER;
+ }
+ if (!ParseCalcTerm(*storage, variantMask))
+ return false;
+ MOZ_ASSERT(variantMask != 0,
+ "ParseCalcTerm did not set variantMask appropriately");
+ MOZ_ASSERT(!(variantMask & VARIANT_NUMBER) ||
+ !(variantMask & ~int32_t(VARIANT_NUMBER)),
+ "ParseCalcTerm did not set variantMask appropriately");
+
+ if (variantMask & VARIANT_NUMBER) {
+ // Simplify the value immediately so we can check for division by
+ // zero.
+ ReduceNumberCalcOps ops;
+ float number = mozilla::css::ComputeCalc(*storage, ops);
+ if (number == 0.0 && afterDivision)
+ return false;
+ storage->SetFloatValue(number, eCSSUnit_Number);
+ } else {
+ gotValue = true;
+
+ if (storage != &aValue) {
+ // Simplify any numbers in the Times_L position (which are
+ // not simplified by the check above).
+ MOZ_ASSERT(storage == &aValue.GetArrayValue()->Item(1),
+ "unexpected relationship to current storage");
+ nsCSSValue &leftValue = aValue.GetArrayValue()->Item(0);
+ ReduceNumberCalcOps ops;
+ float number = mozilla::css::ComputeCalc(leftValue, ops);
+ leftValue.SetFloatValue(number, eCSSUnit_Number);
+ }
+ }
+
+ bool hadWS = RequireWhitespace();
+ if (!GetToken(false)) {
+ *aHadFinalWS = hadWS;
+ break;
+ }
+ nsCSSUnit unit;
+ if (mToken.IsSymbol('*')) {
+ unit = gotValue ? eCSSUnit_Calc_Times_R : eCSSUnit_Calc_Times_L;
+ afterDivision = false;
+ } else if (mToken.IsSymbol('/')) {
+ unit = eCSSUnit_Calc_Divided;
+ afterDivision = true;
+ } else {
+ UngetToken();
+ *aHadFinalWS = hadWS;
+ break;
+ }
+
+ RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
+ arr->Item(0) = aValue;
+ storage = &arr->Item(1);
+ aValue.SetArrayValue(arr, unit);
+ }
+
+ // Adjust aVariantMask (see comments above function) to reflect which
+ // option we took.
+ if (aVariantMask & VARIANT_NUMBER) {
+ if (gotValue) {
+ aVariantMask &= ~int32_t(VARIANT_NUMBER);
+ } else {
+ aVariantMask = VARIANT_NUMBER;
+ }
+ } else {
+ if (!gotValue) {
+ // We had to find a value, but we didn't.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// * If aVariantMask is VARIANT_NUMBER, this function parses the
+// <number-term> production.
+// * If aVariantMask does not contain VARIANT_NUMBER, this function
+// parses the <value-term> production.
+// * Otherwise (VARIANT_NUMBER and other bits) this function parses
+// whichever one of the productions matches ***and modifies
+// aVariantMask*** to reflect which one it has parsed by either
+// removing VARIANT_NUMBER or removing all other bits.
+bool
+CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask)
+{
+ MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
+ if (!GetToken(true))
+ return false;
+ // Either an additive expression in parentheses...
+ if (mToken.IsSymbol('(') ||
+ // Treat nested calc() as plain parenthesis.
+ IsCSSTokenCalcFunction(mToken)) {
+ if (!ParseCalcAdditiveExpression(aValue, aVariantMask) ||
+ !ExpectSymbol(')', true)) {
+ SkipUntil(')');
+ return false;
+ }
+ return true;
+ }
+ // ... or just a value
+ UngetToken();
+ // Always pass VARIANT_NUMBER to ParseVariant so that unitless zero
+ // always gets picked up
+ if (ParseVariant(aValue, aVariantMask | VARIANT_NUMBER, nullptr) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+ // ...and do the VARIANT_NUMBER check ourselves.
+ if (!(aVariantMask & VARIANT_NUMBER) && aValue.GetUnit() == eCSSUnit_Number) {
+ return false;
+ }
+ // If we did the value parsing, we need to adjust aVariantMask to
+ // reflect which option we took (see above).
+ if (aVariantMask & VARIANT_NUMBER) {
+ if (aValue.GetUnit() == eCSSUnit_Number) {
+ aVariantMask = VARIANT_NUMBER;
+ } else {
+ aVariantMask &= ~int32_t(VARIANT_NUMBER);
+ }
+ }
+ return true;
+}
+
+// This function consumes all consecutive whitespace and returns whether
+// there was any.
+bool
+CSSParserImpl::RequireWhitespace()
+{
+ if (!GetToken(false))
+ return false;
+ if (mToken.mType != eCSSToken_Whitespace) {
+ UngetToken();
+ return false;
+ }
+ // Skip any additional whitespace tokens.
+ if (GetToken(true)) {
+ UngetToken();
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseRect(nsCSSPropertyID aPropID)
+{
+ nsCSSValue val;
+ if (ParseSingleTokenVariant(val, VARIANT_INHERIT | VARIANT_AUTO, nullptr)) {
+ AppendValue(aPropID, val);
+ return true;
+ }
+
+ if (! GetToken(true)) {
+ return false;
+ }
+
+ if (mToken.mType == eCSSToken_Function &&
+ mToken.mIdent.LowerCaseEqualsLiteral("rect")) {
+ nsCSSRect& rect = val.SetRectValue();
+ bool useCommas;
+ NS_FOR_CSS_SIDES(side) {
+ if (!ParseSingleTokenVariant(rect.*(nsCSSRect::sides[side]),
+ VARIANT_AL, nullptr)) {
+ return false;
+ }
+ if (side == 0) {
+ useCommas = ExpectSymbol(',', true);
+ } else if (useCommas && side < 3) {
+ // Skip optional commas between elements, but only if the first
+ // separator was a comma.
+ if (!ExpectSymbol(',', true)) {
+ return false;
+ }
+ }
+ }
+ if (!ExpectSymbol(')', true)) {
+ return false;
+ }
+ } else {
+ UngetToken();
+ return false;
+ }
+
+ AppendValue(aPropID, val);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseColumns()
+{
+ // We use a similar "fake value" hack to ParseListStyle, because
+ // "auto" is acceptable for both column-count and column-width.
+ // If the fake "auto" value is found, and one of the real values isn't,
+ // that means the fake auto value is meant for the real value we didn't
+ // find.
+ static const nsCSSPropertyID columnIDs[] = {
+ eCSSPropertyExtra_x_auto_value,
+ eCSSProperty_column_count,
+ eCSSProperty_column_width
+ };
+ const int32_t numProps = MOZ_ARRAY_LENGTH(columnIDs);
+
+ nsCSSValue values[numProps];
+ int32_t found = ParseChoice(values, columnIDs, numProps);
+ if (found < 1) {
+ return false;
+ }
+ if ((found & (1|2|4)) == (1|2|4) &&
+ values[0].GetUnit() == eCSSUnit_Auto) {
+ // We filled all 3 values, which is invalid
+ return false;
+ }
+
+ if ((found & 2) == 0) {
+ // Provide auto column-count
+ values[1].SetAutoValue();
+ }
+ if ((found & 4) == 0) {
+ // Provide auto column-width
+ values[2].SetAutoValue();
+ }
+
+ // Start at index 1 to skip the fake auto value.
+ for (int32_t index = 1; index < numProps; index++) {
+ AppendValue(columnIDs[index], values[index]);
+ }
+ return true;
+}
+
+#define VARIANT_CONTENT (VARIANT_STRING | VARIANT_URL | VARIANT_COUNTER | VARIANT_ATTR | \
+ VARIANT_KEYWORD)
+bool
+CSSParserImpl::ParseContent()
+{
+ // We need to divide the 'content' keywords into two classes for
+ // ParseVariant's sake, so we can't just use nsCSSProps::kContentKTable.
+ static const KTableEntry kContentListKWs[] = {
+ { eCSSKeyword_open_quote, NS_STYLE_CONTENT_OPEN_QUOTE },
+ { eCSSKeyword_close_quote, NS_STYLE_CONTENT_CLOSE_QUOTE },
+ { eCSSKeyword_no_open_quote, NS_STYLE_CONTENT_NO_OPEN_QUOTE },
+ { eCSSKeyword_no_close_quote, NS_STYLE_CONTENT_NO_CLOSE_QUOTE },
+ { eCSSKeyword_UNKNOWN, -1 }
+ };
+
+ static const KTableEntry kContentSolitaryKWs[] = {
+ { eCSSKeyword__moz_alt_content, NS_STYLE_CONTENT_ALT_CONTENT },
+ { eCSSKeyword_UNKNOWN, -1 }
+ };
+
+ // Verify that these two lists add up to the size of
+ // nsCSSProps::kContentKTable.
+ MOZ_ASSERT(nsCSSProps::kContentKTable[
+ ArrayLength(kContentListKWs) +
+ ArrayLength(kContentSolitaryKWs) - 2].mKeyword ==
+ eCSSKeyword_UNKNOWN &&
+ nsCSSProps::kContentKTable[
+ ArrayLength(kContentListKWs) +
+ ArrayLength(kContentSolitaryKWs) - 2].mValue == -1,
+ "content keyword tables out of sync");
+
+ nsCSSValue value;
+ // 'inherit', 'initial', 'unset', 'normal', 'none', and 'alt-content' must
+ // be alone
+ if (!ParseSingleTokenVariant(value, VARIANT_HMK | VARIANT_NONE,
+ kContentSolitaryKWs)) {
+ nsCSSValueList* cur = value.SetListValue();
+ for (;;) {
+ if (ParseVariant(cur->mValue, VARIANT_CONTENT, kContentListKWs) !=
+ CSSParseResult::Ok) {
+ return false;
+ }
+ if (CheckEndProperty()) {
+ break;
+ }
+ cur->mNext = new nsCSSValueList;
+ cur = cur->mNext;
+ }
+ }
+ AppendValue(eCSSProperty_content, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseCounterData(nsCSSPropertyID aPropID)
+{
+ static const nsCSSKeyword kCounterDataKTable[] = {
+ eCSSKeyword_none,
+ eCSSKeyword_UNKNOWN
+ };
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
+ nullptr)) {
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (mToken.mType != eCSSToken_Ident) {
+ UngetToken();
+ return false;
+ }
+
+ nsCSSValuePairList *cur = value.SetPairListValue();
+ for (;;) {
+ if (!ParseCustomIdent(cur->mXValue, mToken.mIdent, kCounterDataKTable)) {
+ return false;
+ }
+ if (!GetToken(true)) {
+ break;
+ }
+ if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) {
+ cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
+ } else {
+ UngetToken();
+ }
+ if (!GetToken(true)) {
+ break;
+ }
+ if (mToken.mType != eCSSToken_Ident) {
+ UngetToken();
+ break;
+ }
+ cur->mNext = new nsCSSValuePairList;
+ cur = cur->mNext;
+ }
+ }
+ AppendValue(aPropID, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseCursor()
+{
+ nsCSSValue value;
+ // 'inherit', 'initial' and 'unset' must be alone
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ nsCSSValueList* cur = value.SetListValue();
+ for (;;) {
+ if (!ParseSingleTokenVariant(cur->mValue, VARIANT_UK,
+ nsCSSProps::kCursorKTable)) {
+ return false;
+ }
+ if (cur->mValue.GetUnit() != eCSSUnit_URL) { // keyword must be last
+ break;
+ }
+
+ // We have a URL, so make a value array with three values.
+ RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(3);
+ val->Item(0) = cur->mValue;
+
+ // Parse optional x and y position of cursor hotspot (css3-ui).
+ if (ParseSingleTokenVariant(val->Item(1), VARIANT_NUMBER, nullptr)) {
+ // If we have one number, we must have two.
+ if (!ParseSingleTokenVariant(val->Item(2), VARIANT_NUMBER, nullptr)) {
+ return false;
+ }
+ }
+ cur->mValue.SetArrayValue(val, eCSSUnit_Array);
+
+ if (!ExpectSymbol(',', true)) { // url must not be last
+ return false;
+ }
+ cur->mNext = new nsCSSValueList;
+ cur = cur->mNext;
+ }
+ }
+ AppendValue(eCSSProperty_cursor, value);
+ return true;
+}
+
+
+bool
+CSSParserImpl::ParseFont()
+{
+ nsCSSValue family;
+ if (ParseSingleTokenVariant(family, VARIANT_HK, nsCSSProps::kFontKTable)) {
+ if (eCSSUnit_Inherit == family.GetUnit() ||
+ eCSSUnit_Initial == family.GetUnit() ||
+ eCSSUnit_Unset == family.GetUnit()) {
+ AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
+ AppendValue(eCSSProperty_font_family, family);
+ AppendValue(eCSSProperty_font_style, family);
+ AppendValue(eCSSProperty_font_weight, family);
+ AppendValue(eCSSProperty_font_size, family);
+ AppendValue(eCSSProperty_line_height, family);
+ AppendValue(eCSSProperty_font_stretch, family);
+ AppendValue(eCSSProperty_font_size_adjust, family);
+ AppendValue(eCSSProperty_font_feature_settings, family);
+ AppendValue(eCSSProperty_font_language_override, family);
+ AppendValue(eCSSProperty_font_kerning, family);
+ AppendValue(eCSSProperty_font_synthesis, family);
+ AppendValue(eCSSProperty_font_variant_alternates, family);
+ AppendValue(eCSSProperty_font_variant_caps, family);
+ AppendValue(eCSSProperty_font_variant_east_asian, family);
+ AppendValue(eCSSProperty_font_variant_ligatures, family);
+ AppendValue(eCSSProperty_font_variant_numeric, family);
+ AppendValue(eCSSProperty_font_variant_position, family);
+ }
+ else {
+ AppendValue(eCSSProperty__x_system_font, family);
+ nsCSSValue systemFont(eCSSUnit_System_Font);
+ AppendValue(eCSSProperty_font_family, systemFont);
+ AppendValue(eCSSProperty_font_style, systemFont);
+ AppendValue(eCSSProperty_font_weight, systemFont);
+ AppendValue(eCSSProperty_font_size, systemFont);
+ AppendValue(eCSSProperty_line_height, systemFont);
+ AppendValue(eCSSProperty_font_stretch, systemFont);
+ AppendValue(eCSSProperty_font_size_adjust, systemFont);
+ AppendValue(eCSSProperty_font_feature_settings, systemFont);
+ AppendValue(eCSSProperty_font_language_override, systemFont);
+ AppendValue(eCSSProperty_font_kerning, systemFont);
+ AppendValue(eCSSProperty_font_synthesis, systemFont);
+ AppendValue(eCSSProperty_font_variant_alternates, systemFont);
+ AppendValue(eCSSProperty_font_variant_caps, systemFont);
+ AppendValue(eCSSProperty_font_variant_east_asian, systemFont);
+ AppendValue(eCSSProperty_font_variant_ligatures, systemFont);
+ AppendValue(eCSSProperty_font_variant_numeric, systemFont);
+ AppendValue(eCSSProperty_font_variant_position, systemFont);
+ }
+ return true;
+ }
+
+ // Get optional font-style, font-variant, font-weight, font-stretch
+ // (in any order)
+
+ // Indexes into fontIDs[] and values[] arrays.
+ const int kFontStyleIndex = 0;
+ const int kFontVariantIndex = 1;
+ const int kFontWeightIndex = 2;
+ const int kFontStretchIndex = 3;
+
+ // The order of the initializers here must match the order of the indexes
+ // defined above!
+ static const nsCSSPropertyID fontIDs[] = {
+ eCSSProperty_font_style,
+ eCSSProperty_font_variant_caps,
+ eCSSProperty_font_weight,
+ eCSSProperty_font_stretch
+ };
+
+ const int32_t numProps = MOZ_ARRAY_LENGTH(fontIDs);
+ nsCSSValue values[numProps];
+ int32_t found = ParseChoice(values, fontIDs, numProps);
+ if (found < 0 ||
+ eCSSUnit_Inherit == values[kFontStyleIndex].GetUnit() ||
+ eCSSUnit_Initial == values[kFontStyleIndex].GetUnit() ||
+ eCSSUnit_Unset == values[kFontStyleIndex].GetUnit()) { // illegal data
+ return false;
+ }
+ if ((found & (1 << kFontStyleIndex)) == 0) {
+ // Provide default font-style
+ values[kFontStyleIndex].SetIntValue(NS_FONT_STYLE_NORMAL,
+ eCSSUnit_Enumerated);
+ }
+ if ((found & (1 << kFontVariantIndex)) == 0) {
+ // Provide default font-variant
+ values[kFontVariantIndex].SetNormalValue();
+ } else {
+ if (values[kFontVariantIndex].GetUnit() == eCSSUnit_Enumerated &&
+ values[kFontVariantIndex].GetIntValue() !=
+ NS_FONT_VARIANT_CAPS_SMALLCAPS) {
+ // only normal or small-caps is allowed in font shorthand
+ // this also assumes other values for font-variant-caps never overlap
+ // possible values for style or weight
+ return false;
+ }
+ }
+ if ((found & (1 << kFontWeightIndex)) == 0) {
+ // Provide default font-weight
+ values[kFontWeightIndex].SetIntValue(NS_FONT_WEIGHT_NORMAL,
+ eCSSUnit_Enumerated);
+ }
+ if ((found & (1 << kFontStretchIndex)) == 0) {
+ // Provide default font-stretch
+ values[kFontStretchIndex].SetIntValue(NS_FONT_STRETCH_NORMAL,
+ eCSSUnit_Enumerated);
+ }
+
+ // Get mandatory font-size
+ nsCSSValue size;
+ if (!ParseSingleTokenNonNegativeVariant(size, VARIANT_KEYWORD | VARIANT_LP,
+ nsCSSProps::kFontSizeKTable)) {
+ return false;
+ }
+
+ // Get optional "/" line-height
+ nsCSSValue lineHeight;
+ if (ExpectSymbol('/', true)) {
+ if (ParseNonNegativeVariant(lineHeight,
+ VARIANT_NUMBER | VARIANT_LP |
+ VARIANT_NORMAL | VARIANT_CALC,
+ nullptr) != CSSParseResult::Ok) {
+ return false;
+ }
+ }
+ else {
+ lineHeight.SetNormalValue();
+ }
+
+ // Get final mandatory font-family
+ nsAutoParseCompoundProperty compound(this);
+ if (ParseFamily(family)) {
+ if (eCSSUnit_Inherit != family.GetUnit() &&
+ eCSSUnit_Initial != family.GetUnit() &&
+ eCSSUnit_Unset != family.GetUnit()) {
+ AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
+ AppendValue(eCSSProperty_font_family, family);
+ AppendValue(eCSSProperty_font_style, values[kFontStyleIndex]);
+ AppendValue(eCSSProperty_font_variant_caps, values[kFontVariantIndex]);
+ AppendValue(eCSSProperty_font_weight, values[kFontWeightIndex]);
+ AppendValue(eCSSProperty_font_size, size);
+ AppendValue(eCSSProperty_line_height, lineHeight);
+ AppendValue(eCSSProperty_font_stretch, values[kFontStretchIndex]);
+ AppendValue(eCSSProperty_font_size_adjust, nsCSSValue(eCSSUnit_None));
+ AppendValue(eCSSProperty_font_feature_settings, nsCSSValue(eCSSUnit_Normal));
+ AppendValue(eCSSProperty_font_language_override, nsCSSValue(eCSSUnit_Normal));
+ AppendValue(eCSSProperty_font_kerning,
+ nsCSSValue(NS_FONT_KERNING_AUTO, eCSSUnit_Enumerated));
+ AppendValue(eCSSProperty_font_synthesis,
+ nsCSSValue(NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE,
+ eCSSUnit_Enumerated));
+ AppendValue(eCSSProperty_font_variant_alternates,
+ nsCSSValue(eCSSUnit_Normal));
+ AppendValue(eCSSProperty_font_variant_east_asian,
+ nsCSSValue(eCSSUnit_Normal));
+ AppendValue(eCSSProperty_font_variant_ligatures,
+ nsCSSValue(eCSSUnit_Normal));
+ AppendValue(eCSSProperty_font_variant_numeric,
+ nsCSSValue(eCSSUnit_Normal));
+ AppendValue(eCSSProperty_font_variant_position,
+ nsCSSValue(eCSSUnit_Normal));
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CSSParserImpl::ParseFontSynthesis(nsCSSValue& aValue)
+{
+ if (!ParseSingleTokenVariant(aValue, VARIANT_HK | VARIANT_NONE,
+ nsCSSProps::kFontSynthesisKTable)) {
+ return false;
+ }
+
+ // first value 'none' ==> done
+ if (eCSSUnit_None == aValue.GetUnit() ||
+ eCSSUnit_Initial == aValue.GetUnit() ||
+ eCSSUnit_Inherit == aValue.GetUnit() ||
+ eCSSUnit_Unset == aValue.GetUnit())
+ {
+ return true;
+ }
+
+ // look for a second value
+ int32_t intValue = aValue.GetIntValue();
+ nsCSSValue nextValue;
+
+ if (ParseEnum(nextValue, nsCSSProps::kFontSynthesisKTable)) {
+ int32_t nextIntValue = nextValue.GetIntValue();
+ if (nextIntValue & intValue) {
+ return false;
+ }
+ aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated);
+ }
+
+ return true;
+}
+
+// font-variant-alternates allows for a combination of multiple
+// simple enumerated values and functional values. Functional values have
+// parameter lists with one or more idents which are later resolved
+// based on values defined in @font-feature-value rules.
+//
+// font-variant-alternates: swash(flowing) historical-forms styleset(alt-g, alt-m);
+//
+// So for this the nsCSSValue is set to a pair value, with one
+// value for a bitmask of both simple and functional property values
+// and another value containing a ValuePairList with lists of idents
+// for each functional property value.
+//
+// pairValue
+// o intValue
+// NS_FONT_VARIANT_ALTERNATES_SWASH |
+// NS_FONT_VARIANT_ALTERNATES_STYLESET
+// o valuePairList, each element with
+// - intValue - indicates which alternate
+// - string or valueList of strings
+//
+// Note: when only 'historical-forms' is specified, there are no
+// functional values to store, in which case the valuePairList is a
+// single element dummy list. In all other cases, the length of the
+// list will match the number of functional values.
+
+#define MAX_ALLOWED_FEATURES 512
+
+static uint16_t
+MaxElementsForAlternateType(nsCSSKeyword keyword)
+{
+ uint16_t maxElems = 1;
+ if (keyword == eCSSKeyword_styleset ||
+ keyword == eCSSKeyword_character_variant) {
+ maxElems = MAX_ALLOWED_FEATURES;
+ }
+ return maxElems;
+}
+
+bool
+CSSParserImpl::ParseSingleAlternate(int32_t& aWhichFeature,
+ nsCSSValue& aValue)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ bool isIdent = (mToken.mType == eCSSToken_Ident);
+ if (mToken.mType != eCSSToken_Function && !isIdent) {
+ UngetToken();
+ return false;
+ }
+
+ // ident ==> simple enumerated prop val (e.g. historical-forms)
+ // function ==> e.g. swash(flowing) styleset(alt-g, alt-m)
+
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+ if (!(eCSSKeyword_UNKNOWN < keyword &&
+ nsCSSProps::FindKeyword(keyword,
+ (isIdent ?
+ nsCSSProps::kFontVariantAlternatesKTable :
+ nsCSSProps::kFontVariantAlternatesFuncsKTable),
+ aWhichFeature)))
+ {
+ // failed, pop token
+ UngetToken();
+ return false;
+ }
+
+ if (isIdent) {
+ aValue.SetIntValue(aWhichFeature, eCSSUnit_Enumerated);
+ return true;
+ }
+
+ return ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER,
+ 1, MaxElementsForAlternateType(keyword), aValue);
+}
+
+bool
+CSSParserImpl::ParseFontVariantAlternates(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
+ nullptr)) {
+ return true;
+ }
+
+ // iterate through parameters
+ nsCSSValue listValue;
+ int32_t feature, featureFlags = 0;
+
+ // if no functional values, this may be a list with a single, unused element
+ listValue.SetListValue();
+
+ nsCSSValueList* list = nullptr;
+ nsCSSValue value;
+ while (ParseSingleAlternate(feature, value)) {
+
+ // check to make sure value not already set
+ if (feature == 0 ||
+ feature & featureFlags) {
+ return false;
+ }
+
+ featureFlags |= feature;
+
+ // if function, need to add to the list of functions
+ if (value.GetUnit() == eCSSUnit_Function) {
+ if (!list) {
+ list = listValue.GetListValue();
+ } else {
+ list->mNext = new nsCSSValueList;
+ list = list->mNext;
+ }
+ list->mValue = value;
+ }
+ }
+
+ if (featureFlags == 0) {
+ // ParseSingleAlternate failed the first time through the loop.
+ return false;
+ }
+
+ nsCSSValue featureValue;
+ featureValue.SetIntValue(featureFlags, eCSSUnit_Enumerated);
+ aValue.SetPairValue(featureValue, listValue);
+
+ return true;
+}
+
+bool
+CSSParserImpl::MergeBitmaskValue(int32_t aNewValue,
+ const int32_t aMasks[],
+ int32_t& aMergedValue)
+{
+ // check to make sure value not already set
+ if (aNewValue & aMergedValue) {
+ return false;
+ }
+
+ const int32_t *m = aMasks;
+ int32_t c = 0;
+
+ while (*m != MASK_END_VALUE) {
+ if (*m & aNewValue) {
+ c = aMergedValue & *m;
+ break;
+ }
+ m++;
+ }
+
+ if (c) {
+ return false;
+ }
+
+ aMergedValue |= aNewValue;
+ return true;
+}
+
+// aMasks - array of masks for mutually-exclusive property values,
+// e.g. proportial-nums, tabular-nums
+
+bool
+CSSParserImpl::ParseBitmaskValues(nsCSSValue& aValue,
+ const KTableEntry aKeywordTable[],
+ const int32_t aMasks[])
+{
+ // Parse at least one keyword
+ if (!ParseEnum(aValue, aKeywordTable)) {
+ return false;
+ }
+
+ // look for more values
+ nsCSSValue nextValue;
+ int32_t mergedValue = aValue.GetIntValue();
+
+ while (ParseEnum(nextValue, aKeywordTable))
+ {
+ if (!MergeBitmaskValue(nextValue.GetIntValue(), aMasks, mergedValue)) {
+ return false;
+ }
+ }
+
+ aValue.SetIntValue(mergedValue, eCSSUnit_Enumerated);
+
+ return true;
+}
+
+static const int32_t maskEastAsian[] = {
+ NS_FONT_VARIANT_EAST_ASIAN_VARIANT_MASK,
+ NS_FONT_VARIANT_EAST_ASIAN_WIDTH_MASK,
+ MASK_END_VALUE
+};
+
+bool
+CSSParserImpl::ParseFontVariantEastAsian(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
+ nullptr)) {
+ return true;
+ }
+
+ NS_ASSERTION(maskEastAsian[ArrayLength(maskEastAsian) - 1] ==
+ MASK_END_VALUE,
+ "incorrectly terminated array");
+
+ return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantEastAsianKTable,
+ maskEastAsian);
+}
+
+bool
+CSSParserImpl::ParseContain(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
+ nullptr)) {
+ return true;
+ }
+ static const int32_t maskContain[] = { MASK_END_VALUE };
+ if (!ParseBitmaskValues(aValue, nsCSSProps::kContainKTable, maskContain)) {
+ return false;
+ }
+ if (aValue.GetIntValue() & NS_STYLE_CONTAIN_STRICT) {
+ if (aValue.GetIntValue() != NS_STYLE_CONTAIN_STRICT) {
+ // Disallow any other keywords in combination with 'strict'.
+ return false;
+ }
+ // Strict implies layout, style, and paint.
+ // However, for serialization purposes, we keep the strict bit around.
+ aValue.SetIntValue(NS_STYLE_CONTAIN_STRICT |
+ NS_STYLE_CONTAIN_ALL_BITS, eCSSUnit_Enumerated);
+ }
+ return true;
+}
+
+static const int32_t maskLigatures[] = {
+ NS_FONT_VARIANT_LIGATURES_COMMON_MASK,
+ NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK,
+ NS_FONT_VARIANT_LIGATURES_HISTORICAL_MASK,
+ NS_FONT_VARIANT_LIGATURES_CONTEXTUAL_MASK,
+ MASK_END_VALUE
+};
+
+bool
+CSSParserImpl::ParseFontVariantLigatures(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue,
+ VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE,
+ nullptr)) {
+ return true;
+ }
+
+ NS_ASSERTION(maskLigatures[ArrayLength(maskLigatures) - 1] ==
+ MASK_END_VALUE,
+ "incorrectly terminated array");
+
+ return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantLigaturesKTable,
+ maskLigatures);
+}
+
+static const int32_t maskNumeric[] = {
+ NS_FONT_VARIANT_NUMERIC_FIGURE_MASK,
+ NS_FONT_VARIANT_NUMERIC_SPACING_MASK,
+ NS_FONT_VARIANT_NUMERIC_FRACTION_MASK,
+ MASK_END_VALUE
+};
+
+bool
+CSSParserImpl::ParseFontVariantNumeric(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
+ nullptr)) {
+ return true;
+ }
+
+ NS_ASSERTION(maskNumeric[ArrayLength(maskNumeric) - 1] ==
+ MASK_END_VALUE,
+ "incorrectly terminated array");
+
+ return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantNumericKTable,
+ maskNumeric);
+}
+
+bool
+CSSParserImpl::ParseFontVariant()
+{
+ // parse single values - normal/inherit/none
+ nsCSSValue value;
+ nsCSSValue normal(eCSSUnit_Normal);
+
+ if (ParseSingleTokenVariant(value,
+ VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE,
+ nullptr)) {
+ AppendValue(eCSSProperty_font_variant_ligatures, value);
+ if (eCSSUnit_None == value.GetUnit()) {
+ // 'none' applies the value 'normal' to all properties other
+ // than 'font-variant-ligatures'
+ value.SetNormalValue();
+ }
+ AppendValue(eCSSProperty_font_variant_alternates, value);
+ AppendValue(eCSSProperty_font_variant_caps, value);
+ AppendValue(eCSSProperty_font_variant_east_asian, value);
+ AppendValue(eCSSProperty_font_variant_numeric, value);
+ AppendValue(eCSSProperty_font_variant_position, value);
+ return true;
+ }
+
+ // set each of the individual subproperties
+ int32_t altFeatures = 0, capsFeatures = 0, eastAsianFeatures = 0,
+ ligFeatures = 0, numericFeatures = 0, posFeatures = 0;
+ nsCSSValue altListValue;
+ nsCSSValueList* altList = nullptr;
+
+ // if no functional values, this may be a list with a single, unused element
+ altListValue.SetListValue();
+
+ bool foundValid = false; // found at least one proper value
+ while (GetToken(true)) {
+ // only an ident or a function at this point
+ bool isFunction = (mToken.mType == eCSSToken_Function);
+ if (mToken.mType != eCSSToken_Ident && !isFunction) {
+ UngetToken();
+ break;
+ }
+
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+ if (keyword == eCSSKeyword_UNKNOWN) {
+ UngetToken();
+ return false;
+ }
+
+ int32_t feature;
+
+ // function? ==> font-variant-alternates
+ if (isFunction) {
+ if (!nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kFontVariantAlternatesFuncsKTable,
+ feature) ||
+ (feature & altFeatures)) {
+ UngetToken();
+ return false;
+ }
+
+ altFeatures |= feature;
+ nsCSSValue funcValue;
+ if (!ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER, 1,
+ MaxElementsForAlternateType(keyword), funcValue) ||
+ funcValue.GetUnit() != eCSSUnit_Function) {
+ UngetToken();
+ return false;
+ }
+
+ if (!altList) {
+ altList = altListValue.GetListValue();
+ } else {
+ altList->mNext = new nsCSSValueList;
+ altList = altList->mNext;
+ }
+ altList->mValue = funcValue;
+ } else if (nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kFontVariantCapsKTable,
+ feature)) {
+ if (capsFeatures != 0) {
+ // multiple values for font-variant-caps
+ UngetToken();
+ return false;
+ }
+ capsFeatures = feature;
+ } else if (nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kFontVariantAlternatesKTable,
+ feature)) {
+ if (feature & altFeatures) {
+ // same value repeated
+ UngetToken();
+ return false;
+ }
+ altFeatures |= feature;
+ } else if (nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kFontVariantEastAsianKTable,
+ feature)) {
+ if (!MergeBitmaskValue(feature, maskEastAsian, eastAsianFeatures)) {
+ // multiple mutually exclusive values
+ UngetToken();
+ return false;
+ }
+ } else if (nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kFontVariantLigaturesKTable,
+ feature)) {
+ if (keyword == eCSSKeyword_none ||
+ !MergeBitmaskValue(feature, maskLigatures, ligFeatures)) {
+ // none or multiple mutually exclusive values
+ UngetToken();
+ return false;
+ }
+ } else if (nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kFontVariantNumericKTable,
+ feature)) {
+ if (!MergeBitmaskValue(feature, maskNumeric, numericFeatures)) {
+ // multiple mutually exclusive values
+ UngetToken();
+ return false;
+ }
+ } else if (nsCSSProps::FindKeyword(keyword,
+ nsCSSProps::kFontVariantPositionKTable,
+ feature)) {
+ if (posFeatures != 0) {
+ // multiple values for font-variant-caps
+ UngetToken();
+ return false;
+ }
+ posFeatures = feature;
+ } else {
+ // bogus keyword, bail...
+ UngetToken();
+ return false;
+ }
+
+ foundValid = true;
+ }
+
+ if (!foundValid) {
+ return false;
+ }
+
+ if (altFeatures) {
+ nsCSSValue featureValue;
+ featureValue.SetIntValue(altFeatures, eCSSUnit_Enumerated);
+ value.SetPairValue(featureValue, altListValue);
+ AppendValue(eCSSProperty_font_variant_alternates, value);
+ } else {
+ AppendValue(eCSSProperty_font_variant_alternates, normal);
+ }
+
+ if (capsFeatures) {
+ value.SetIntValue(capsFeatures, eCSSUnit_Enumerated);
+ AppendValue(eCSSProperty_font_variant_caps, value);
+ } else {
+ AppendValue(eCSSProperty_font_variant_caps, normal);
+ }
+
+ if (eastAsianFeatures) {
+ value.SetIntValue(eastAsianFeatures, eCSSUnit_Enumerated);
+ AppendValue(eCSSProperty_font_variant_east_asian, value);
+ } else {
+ AppendValue(eCSSProperty_font_variant_east_asian, normal);
+ }
+
+ if (ligFeatures) {
+ value.SetIntValue(ligFeatures, eCSSUnit_Enumerated);
+ AppendValue(eCSSProperty_font_variant_ligatures, value);
+ } else {
+ AppendValue(eCSSProperty_font_variant_ligatures, normal);
+ }
+
+ if (numericFeatures) {
+ value.SetIntValue(numericFeatures, eCSSUnit_Enumerated);
+ AppendValue(eCSSProperty_font_variant_numeric, value);
+ } else {
+ AppendValue(eCSSProperty_font_variant_numeric, normal);
+ }
+
+ if (posFeatures) {
+ value.SetIntValue(posFeatures, eCSSUnit_Enumerated);
+ AppendValue(eCSSProperty_font_variant_position, value);
+ } else {
+ AppendValue(eCSSProperty_font_variant_position, normal);
+ }
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseFontWeight(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_HKI | VARIANT_SYSFONT,
+ nsCSSProps::kFontWeightKTable)) {
+ if (eCSSUnit_Integer == aValue.GetUnit()) { // ensure unit value
+ int32_t intValue = aValue.GetIntValue();
+ if ((100 <= intValue) &&
+ (intValue <= 900) &&
+ (0 == (intValue % 100))) {
+ return true;
+ } else {
+ UngetToken();
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool
+CSSParserImpl::ParseOneFamily(nsAString& aFamily,
+ bool& aOneKeyword,
+ bool& aQuoted)
+{
+ if (!GetToken(true))
+ return false;
+
+ nsCSSToken* tk = &mToken;
+
+ aOneKeyword = false;
+ aQuoted = false;
+ if (eCSSToken_Ident == tk->mType) {
+ aOneKeyword = true;
+ aFamily.Append(tk->mIdent);
+ for (;;) {
+ if (!GetToken(false))
+ break;
+
+ if (eCSSToken_Ident == tk->mType) {
+ aOneKeyword = false;
+ // We had at least another keyword before.
+ // "If a sequence of identifiers is given as a font family name,
+ // the computed value is the name converted to a string by joining
+ // all the identifiers in the sequence by single spaces."
+ // -- CSS 2.1, section 15.3
+ // Whitespace tokens do not actually matter,
+ // identifier tokens can be separated by comments.
+ aFamily.Append(char16_t(' '));
+ aFamily.Append(tk->mIdent);
+ } else if (eCSSToken_Whitespace != tk->mType) {
+ UngetToken();
+ break;
+ }
+ }
+ return true;
+
+ } else if (eCSSToken_String == tk->mType) {
+ aQuoted = true;
+ aFamily.Append(tk->mIdent); // XXX What if it had escaped quotes?
+ return true;
+
+ } else {
+ UngetToken();
+ return false;
+ }
+}
+
+
+static bool
+AppendGeneric(nsCSSKeyword aKeyword, FontFamilyList *aFamilyList)
+{
+ switch (aKeyword) {
+ case eCSSKeyword_serif:
+ aFamilyList->Append(FontFamilyName(eFamily_serif));
+ return true;
+ case eCSSKeyword_sans_serif:
+ aFamilyList->Append(FontFamilyName(eFamily_sans_serif));
+ return true;
+ case eCSSKeyword_monospace:
+ aFamilyList->Append(FontFamilyName(eFamily_monospace));
+ return true;
+ case eCSSKeyword_cursive:
+ aFamilyList->Append(FontFamilyName(eFamily_cursive));
+ return true;
+ case eCSSKeyword_fantasy:
+ aFamilyList->Append(FontFamilyName(eFamily_fantasy));
+ return true;
+ case eCSSKeyword__moz_fixed:
+ aFamilyList->Append(FontFamilyName(eFamily_moz_fixed));
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool
+CSSParserImpl::ParseFamily(nsCSSValue& aValue)
+{
+ RefPtr<css::FontFamilyListRefCnt> familyList =
+ new css::FontFamilyListRefCnt();
+ nsAutoString family;
+ bool single, quoted;
+
+ // keywords only have meaning in the first position
+ if (!ParseOneFamily(family, single, quoted))
+ return false;
+
+ // check for keywords, but only when keywords appear by themselves
+ // i.e. not in compounds such as font-family: default blah;
+ bool foundGeneric = false;
+ if (single) {
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family);
+ switch (keyword) {
+ case eCSSKeyword_inherit:
+ aValue.SetInheritValue();
+ return true;
+ case eCSSKeyword_default:
+ // 605231 - don't parse unquoted 'default' reserved keyword
+ return false;
+ case eCSSKeyword_initial:
+ aValue.SetInitialValue();
+ return true;
+ case eCSSKeyword_unset:
+ if (nsLayoutUtils::UnsetValueEnabled()) {
+ aValue.SetUnsetValue();
+ return true;
+ }
+ break;
+ case eCSSKeyword__moz_use_system_font:
+ if (!IsParsingCompoundProperty()) {
+ aValue.SetSystemFontValue();
+ return true;
+ }
+ break;
+ default:
+ foundGeneric = AppendGeneric(keyword, familyList);
+ }
+ }
+
+ if (!foundGeneric) {
+ familyList->Append(
+ FontFamilyName(family, (quoted ? eQuotedName : eUnquotedName)));
+ }
+
+ for (;;) {
+ if (!ExpectSymbol(',', true))
+ break;
+
+ nsAutoString nextFamily;
+ if (!ParseOneFamily(nextFamily, single, quoted))
+ return false;
+
+ // at this point unquoted keywords are not allowed
+ // as font family names but can appear within names
+ foundGeneric = false;
+ if (single) {
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(nextFamily);
+ switch (keyword) {
+ case eCSSKeyword_inherit:
+ case eCSSKeyword_initial:
+ case eCSSKeyword_default:
+ case eCSSKeyword__moz_use_system_font:
+ return false;
+ case eCSSKeyword_unset:
+ if (nsLayoutUtils::UnsetValueEnabled()) {
+ return false;
+ }
+ break;
+ default:
+ foundGeneric = AppendGeneric(keyword, familyList);
+ break;
+ }
+ }
+
+ if (!foundGeneric) {
+ familyList->Append(
+ FontFamilyName(nextFamily, (quoted ? eQuotedName : eUnquotedName)));
+ }
+ }
+
+ if (familyList->IsEmpty()) {
+ return false;
+ }
+
+ aValue.SetFontFamilyListValue(familyList);
+ return true;
+}
+
+// src: ( uri-src | local-src ) (',' ( uri-src | local-src ) )*
+// uri-src: uri [ 'format(' string ( ',' string )* ')' ]
+// local-src: 'local(' ( string | ident ) ')'
+
+bool
+CSSParserImpl::ParseFontSrc(nsCSSValue& aValue)
+{
+ // could we maybe turn nsCSSValue::Array into InfallibleTArray<nsCSSValue>?
+ InfallibleTArray<nsCSSValue> values;
+ nsCSSValue cur;
+ for (;;) {
+ if (!GetToken(true))
+ break;
+
+ if (mToken.mType == eCSSToken_URL) {
+ SetValueToURL(cur, mToken.mIdent);
+ values.AppendElement(cur);
+ if (!ParseFontSrcFormat(values))
+ return false;
+
+ } else if (mToken.mType == eCSSToken_Function &&
+ mToken.mIdent.LowerCaseEqualsLiteral("local")) {
+ // css3-fonts does not specify a formal grammar for local().
+ // The text permits both unquoted identifiers and quoted
+ // strings. We resolve this ambiguity in the spec by
+ // assuming that the appropriate production is a single
+ // <family-name>, possibly surrounded by whitespace.
+
+ nsAutoString family;
+ bool single, quoted;
+ if (!ParseOneFamily(family, single, quoted)) {
+ SkipUntil(')');
+ return false;
+ }
+ if (!ExpectSymbol(')', true)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ // reject generics
+ if (single) {
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family);
+ switch (keyword) {
+ case eCSSKeyword_serif:
+ case eCSSKeyword_sans_serif:
+ case eCSSKeyword_monospace:
+ case eCSSKeyword_cursive:
+ case eCSSKeyword_fantasy:
+ case eCSSKeyword__moz_fixed:
+ return false;
+ default:
+ break;
+ }
+ }
+
+ cur.SetStringValue(family, eCSSUnit_Local_Font);
+ values.AppendElement(cur);
+ } else {
+ // We don't know what to do with this token; unget it and error out
+ UngetToken();
+ return false;
+ }
+
+ if (!ExpectSymbol(',', true))
+ break;
+ }
+
+ if (values.Length() == 0)
+ return false;
+
+ RefPtr<nsCSSValue::Array> srcVals
+ = nsCSSValue::Array::Create(values.Length());
+
+ uint32_t i;
+ for (i = 0; i < values.Length(); i++)
+ srcVals->Item(i) = values[i];
+ aValue.SetArrayValue(srcVals, eCSSUnit_Array);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseFontSrcFormat(InfallibleTArray<nsCSSValue> & values)
+{
+ if (!GetToken(true))
+ return true; // EOF harmless here
+ if (mToken.mType != eCSSToken_Function ||
+ !mToken.mIdent.LowerCaseEqualsLiteral("format")) {
+ UngetToken();
+ return true;
+ }
+
+ do {
+ if (!GetToken(true))
+ return false; // EOF - no need for SkipUntil
+
+ if (mToken.mType != eCSSToken_String) {
+ UngetToken();
+ SkipUntil(')');
+ return false;
+ }
+
+ nsCSSValue cur(mToken.mIdent, eCSSUnit_Font_Format);
+ values.AppendElement(cur);
+ } while (ExpectSymbol(',', true));
+
+ if (!ExpectSymbol(')', true)) {
+ SkipUntil(')');
+ return false;
+ }
+
+ return true;
+}
+
+// font-ranges: urange ( ',' urange )*
+bool
+CSSParserImpl::ParseFontRanges(nsCSSValue& aValue)
+{
+ InfallibleTArray<uint32_t> ranges;
+ for (;;) {
+ if (!GetToken(true))
+ break;
+
+ if (mToken.mType != eCSSToken_URange) {
+ UngetToken();
+ break;
+ }
+
+ // An invalid range token is a parsing error, causing the entire
+ // descriptor to be ignored.
+ if (!mToken.mIntegerValid)
+ return false;
+
+ uint32_t low = mToken.mInteger;
+ uint32_t high = mToken.mInteger2;
+
+ // A range that descends, or a range that is entirely outside the
+ // current range of Unicode (U+0-10FFFF) is ignored, but does not
+ // invalidate the descriptor. A range that straddles the high end
+ // is clipped.
+ if (low <= 0x10FFFF && low <= high) {
+ if (high > 0x10FFFF)
+ high = 0x10FFFF;
+
+ ranges.AppendElement(low);
+ ranges.AppendElement(high);
+ }
+ if (!ExpectSymbol(',', true))
+ break;
+ }
+
+ if (ranges.Length() == 0)
+ return false;
+
+ RefPtr<nsCSSValue::Array> srcVals
+ = nsCSSValue::Array::Create(ranges.Length());
+
+ for (uint32_t i = 0; i < ranges.Length(); i++)
+ srcVals->Item(i).SetIntValue(ranges[i], eCSSUnit_Integer);
+ aValue.SetArrayValue(srcVals, eCSSUnit_Array);
+ return true;
+}
+
+// font-feature-settings: normal | <feature-tag-value> [, <feature-tag-value>]*
+// <feature-tag-value> = <string> [ <integer> | on | off ]?
+
+// minimum - "tagx", "tagy", "tagz"
+// edge error case - "tagx" on 1, "tagx" "tagy", "tagx" -1, "tagx" big
+
+// pair value is always x = string, y = int
+
+// font feature tags must be four ASCII characters
+#define FEATURE_TAG_LENGTH 4
+
+static bool
+ValidFontFeatureTag(const nsString& aTag)
+{
+ if (aTag.Length() != FEATURE_TAG_LENGTH) {
+ return false;
+ }
+ uint32_t i;
+ for (i = 0; i < FEATURE_TAG_LENGTH; i++) {
+ uint32_t ch = aTag[i];
+ if (ch < 0x20 || ch > 0x7e) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseFontFeatureSettings(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
+ nullptr)) {
+ return true;
+ }
+
+ nsCSSValuePairList *cur = aValue.SetPairListValue();
+ for (;;) {
+ // feature tag
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_String ||
+ !ValidFontFeatureTag(mToken.mIdent)) {
+ UngetToken();
+ return false;
+ }
+ cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_String);
+
+ if (!GetToken(true)) {
+ cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
+ break;
+ }
+
+ // optional value or on/off keyword
+ if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid &&
+ mToken.mInteger >= 0) {
+ cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
+ } else if (mToken.mType == eCSSToken_Ident &&
+ mToken.mIdent.LowerCaseEqualsLiteral("on")) {
+ cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
+ } else if (mToken.mType == eCSSToken_Ident &&
+ mToken.mIdent.LowerCaseEqualsLiteral("off")) {
+ cur->mYValue.SetIntValue(0, eCSSUnit_Integer);
+ } else {
+ // something other than value/on/off, set default value
+ cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
+ UngetToken();
+ }
+
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+
+ cur->mNext = new nsCSSValuePairList;
+ cur = cur->mNext;
+ }
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseListStyle()
+{
+ // 'list-style' can accept 'none' for two different subproperties,
+ // 'list-style-type' and 'list-style-image'. In order to accept
+ // 'none' as the value of either but still allow another value for
+ // either, we need to ensure that the first 'none' we find gets
+ // allocated to a dummy property instead. Since parse function for
+ // 'list-style-type' could accept values for 'list-style-position',
+ // we put position in front of type.
+ static const nsCSSPropertyID listStyleIDs[] = {
+ eCSSPropertyExtra_x_none_value,
+ eCSSProperty_list_style_position,
+ eCSSProperty_list_style_type,
+ eCSSProperty_list_style_image
+ };
+
+ nsCSSValue values[MOZ_ARRAY_LENGTH(listStyleIDs)];
+ int32_t found =
+ ParseChoice(values, listStyleIDs, ArrayLength(listStyleIDs));
+ if (found < 1) {
+ return false;
+ }
+
+ if ((found & (1|4|8)) == (1|4|8)) {
+ if (values[0].GetUnit() == eCSSUnit_None) {
+ // We found a 'none' plus another value for both of
+ // 'list-style-type' and 'list-style-image'. This is a parse
+ // error, since the 'none' has to count for at least one of them.
+ return false;
+ } else {
+ NS_ASSERTION(found == (1|2|4|8) && values[0] == values[1] &&
+ values[0] == values[2] && values[0] == values[3],
+ "should be a special value");
+ }
+ }
+
+ if ((found & 2) == 0) {
+ values[1].SetIntValue(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE,
+ eCSSUnit_Enumerated);
+ }
+ if ((found & 4) == 0) {
+ // Provide default values
+ nsString type = (found & 1) ?
+ NS_LITERAL_STRING("none") : NS_LITERAL_STRING("disc");
+ values[2].SetStringValue(type, eCSSUnit_Ident);
+ }
+ if ((found & 8) == 0) {
+ values[3].SetNoneValue();
+ }
+
+ // Start at 1 to avoid appending fake value.
+ for (uint32_t index = 1; index < ArrayLength(listStyleIDs); ++index) {
+ AppendValue(listStyleIDs[index], values[index]);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseListStyleType(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_STRING,
+ nullptr)) {
+ return true;
+ }
+
+ if (ParseCounterStyleNameValue(aValue) || ParseSymbols(aValue)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+CSSParserImpl::ParseMargin()
+{
+ static const nsCSSPropertyID kMarginSideIDs[] = {
+ eCSSProperty_margin_top,
+ eCSSProperty_margin_right,
+ eCSSProperty_margin_bottom,
+ eCSSProperty_margin_left
+ };
+
+ return ParseBoxProperties(kMarginSideIDs);
+}
+
+bool
+CSSParserImpl::ParseObjectPosition()
+{
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) &&
+ !ParsePositionValue(value)) {
+ return false;
+ }
+ AppendValue(eCSSProperty_object_position, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseOutline()
+{
+ const int32_t numProps = 3;
+ static const nsCSSPropertyID kOutlineIDs[] = {
+ eCSSProperty_outline_color,
+ eCSSProperty_outline_style,
+ eCSSProperty_outline_width
+ };
+
+ nsCSSValue values[numProps];
+ int32_t found = ParseChoice(values, kOutlineIDs, numProps);
+ if (found < 1) {
+ return false;
+ }
+
+ // Provide default values
+ if ((found & 1) == 0) { // Provide default outline-color
+ values[0].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ }
+ if ((found & 2) == 0) { // Provide default outline-style
+ values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated);
+ }
+ if ((found & 4) == 0) { // Provide default outline-width
+ values[2].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated);
+ }
+
+ int32_t index;
+ for (index = 0; index < numProps; index++) {
+ AppendValue(kOutlineIDs[index], values[index]);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseOverflow()
+{
+ nsCSSValue overflow;
+ if (!ParseSingleTokenVariant(overflow, VARIANT_HK,
+ nsCSSProps::kOverflowKTable)) {
+ return false;
+ }
+
+ nsCSSValue overflowX(overflow);
+ nsCSSValue overflowY(overflow);
+ if (eCSSUnit_Enumerated == overflow.GetUnit())
+ switch(overflow.GetIntValue()) {
+ case NS_STYLE_OVERFLOW_SCROLLBARS_HORIZONTAL:
+ overflowX.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated);
+ overflowY.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated);
+ break;
+ case NS_STYLE_OVERFLOW_SCROLLBARS_VERTICAL:
+ overflowX.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated);
+ overflowY.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated);
+ break;
+ }
+ AppendValue(eCSSProperty_overflow_x, overflowX);
+ AppendValue(eCSSProperty_overflow_y, overflowY);
+ return true;
+}
+
+bool
+CSSParserImpl::ParsePadding()
+{
+ static const nsCSSPropertyID kPaddingSideIDs[] = {
+ eCSSProperty_padding_top,
+ eCSSProperty_padding_right,
+ eCSSProperty_padding_bottom,
+ eCSSProperty_padding_left
+ };
+
+ return ParseBoxProperties(kPaddingSideIDs);
+}
+
+bool
+CSSParserImpl::ParseQuotes()
+{
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_HOS, nullptr)) {
+ return false;
+ }
+ if (value.GetUnit() == eCSSUnit_String) {
+ nsCSSValue open = value;
+ nsCSSValuePairList* quotes = value.SetPairListValue();
+ for (;;) {
+ quotes->mXValue = open;
+ // get mandatory close
+ if (!ParseSingleTokenVariant(quotes->mYValue, VARIANT_STRING, nullptr)) {
+ return false;
+ }
+ // look for another open
+ if (!ParseSingleTokenVariant(open, VARIANT_STRING, nullptr)) {
+ break;
+ }
+ quotes->mNext = new nsCSSValuePairList;
+ quotes = quotes->mNext;
+ }
+ }
+ AppendValue(eCSSProperty_quotes, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTextDecoration()
+{
+ static const nsCSSPropertyID kTextDecorationIDs[] = {
+ eCSSProperty_text_decoration_line,
+ eCSSProperty_text_decoration_style,
+ eCSSProperty_text_decoration_color
+ };
+ const int32_t numProps = MOZ_ARRAY_LENGTH(kTextDecorationIDs);
+ nsCSSValue values[numProps];
+
+ int32_t found = ParseChoice(values, kTextDecorationIDs, numProps);
+ if (found < 1) {
+ return false;
+ }
+
+ // Provide default values
+ if ((found & 1) == 0) { // Provide default text-decoration-line
+ values[0].SetIntValue(NS_STYLE_TEXT_DECORATION_LINE_NONE,
+ eCSSUnit_Enumerated);
+ }
+ if ((found & 2) == 0) { // Provide default text-decoration-style
+ values[1].SetIntValue(NS_STYLE_TEXT_DECORATION_STYLE_SOLID,
+ eCSSUnit_Enumerated);
+ }
+ if ((found & 4) == 0) { // Provide default text-decoration-color
+ values[2].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ }
+
+ for (int32_t index = 0; index < numProps; index++) {
+ AppendValue(kTextDecorationIDs[index], values[index]);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTextEmphasis()
+{
+ static constexpr nsCSSPropertyID kTextEmphasisIDs[] = {
+ eCSSProperty_text_emphasis_style,
+ eCSSProperty_text_emphasis_color
+ };
+ constexpr int32_t numProps = MOZ_ARRAY_LENGTH(kTextEmphasisIDs);
+ nsCSSValue values[numProps];
+
+ int32_t found = ParseChoice(values, kTextEmphasisIDs, numProps);
+ if (found < 1) {
+ return false;
+ }
+
+ if (!(found & 1)) { // Provide default text-emphasis-style
+ values[0].SetNoneValue();
+ }
+ if (!(found & 2)) { // Provide default text-emphasis-color
+ values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ }
+
+ for (int32_t index = 0; index < numProps; index++) {
+ AppendValue(kTextEmphasisIDs[index], values[index]);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTextEmphasisPosition(nsCSSValue& aValue)
+{
+ static_assert((NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ^
+ NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER ^
+ NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT ^
+ NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT) ==
+ (NS_STYLE_TEXT_EMPHASIS_POSITION_OVER |
+ NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER |
+ NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT |
+ NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT),
+ "text-emphasis-position constants should be bitmasks");
+
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
+ return true;
+ }
+
+ nsCSSValue first, second;
+ const auto& kTable = nsCSSProps::kTextEmphasisPositionKTable;
+ if (!ParseSingleTokenVariant(first, VARIANT_KEYWORD, kTable) ||
+ !ParseSingleTokenVariant(second, VARIANT_KEYWORD, kTable)) {
+ return false;
+ }
+
+ auto firstValue = first.GetIntValue();
+ auto secondValue = second.GetIntValue();
+ if ((firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ||
+ firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER) ==
+ (secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ||
+ secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER)) {
+ return false;
+ }
+
+ aValue.SetIntValue(firstValue | secondValue, eCSSUnit_Enumerated);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTextEmphasisStyle(nsCSSValue& aValue)
+{
+ static_assert((NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK ^
+ NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) ==
+ (NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK |
+ NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK),
+ "text-emphasis-style shape and fill constants "
+ "should not intersect");
+ static_assert(NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED == 0,
+ "Making 'filled' zero ensures that if neither 'filled' nor "
+ "'open' is specified, we compute it to 'filled' per spec");
+
+ if (ParseSingleTokenVariant(aValue, VARIANT_HOS, nullptr)) {
+ return true;
+ }
+
+ nsCSSValue first, second;
+ const auto& fillKTable = nsCSSProps::kTextEmphasisStyleFillKTable;
+ const auto& shapeKTable = nsCSSProps::kTextEmphasisStyleShapeKTable;
+ if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, fillKTable)) {
+ ParseSingleTokenVariant(second, VARIANT_KEYWORD, shapeKTable);
+ } else if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, shapeKTable)) {
+ ParseSingleTokenVariant(second, VARIANT_KEYWORD, fillKTable);
+ } else {
+ return false;
+ }
+
+ auto value = first.GetIntValue();
+ if (second.GetUnit() == eCSSUnit_Enumerated) {
+ value |= second.GetIntValue();
+ }
+ aValue.SetIntValue(value, eCSSUnit_Enumerated);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTextAlign(nsCSSValue& aValue, const KTableEntry aTable[])
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
+ // 'inherit', 'initial' and 'unset' must be alone
+ return true;
+ }
+
+ nsCSSValue left;
+ if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD, aTable)) {
+ return false;
+ }
+
+ if (!nsLayoutUtils::IsTextAlignUnsafeValueEnabled()) {
+ aValue = left;
+ return true;
+ }
+
+ nsCSSValue right;
+ if (ParseSingleTokenVariant(right, VARIANT_KEYWORD, aTable)) {
+ // 'true' must be combined with some other value than 'true'.
+ if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE &&
+ right.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
+ return false;
+ }
+ aValue.SetPairValue(left, right);
+ } else {
+ // Single value 'true' is not allowed.
+ if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
+ return false;
+ }
+ aValue = left;
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTextAlign(nsCSSValue& aValue)
+{
+ return ParseTextAlign(aValue, nsCSSProps::kTextAlignKTable);
+}
+
+bool
+CSSParserImpl::ParseTextAlignLast(nsCSSValue& aValue)
+{
+ return ParseTextAlign(aValue, nsCSSProps::kTextAlignLastKTable);
+}
+
+bool
+CSSParserImpl::ParseTextDecorationLine(nsCSSValue& aValue)
+{
+ static_assert((NS_STYLE_TEXT_DECORATION_LINE_NONE ^
+ NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE ^
+ NS_STYLE_TEXT_DECORATION_LINE_OVERLINE ^
+ NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH ^
+ NS_STYLE_TEXT_DECORATION_LINE_BLINK ^
+ NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS) ==
+ (NS_STYLE_TEXT_DECORATION_LINE_NONE |
+ NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE |
+ NS_STYLE_TEXT_DECORATION_LINE_OVERLINE |
+ NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH |
+ NS_STYLE_TEXT_DECORATION_LINE_BLINK |
+ NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS),
+ "text decoration constants need to be bitmasks");
+ if (ParseSingleTokenVariant(aValue, VARIANT_HK,
+ nsCSSProps::kTextDecorationLineKTable)) {
+ if (eCSSUnit_Enumerated == aValue.GetUnit()) {
+ int32_t intValue = aValue.GetIntValue();
+ if (intValue != NS_STYLE_TEXT_DECORATION_LINE_NONE) {
+ // look for more keywords
+ nsCSSValue keyword;
+ int32_t index;
+ for (index = 0; index < 3; index++) {
+ if (ParseEnum(keyword, nsCSSProps::kTextDecorationLineKTable)) {
+ int32_t newValue = keyword.GetIntValue();
+ if (newValue == NS_STYLE_TEXT_DECORATION_LINE_NONE ||
+ newValue & intValue) {
+ // 'none' keyword in conjuction with others is not allowed, and
+ // duplicate keyword is not allowed.
+ return false;
+ }
+ intValue |= newValue;
+ }
+ else {
+ break;
+ }
+ }
+ aValue.SetIntValue(intValue, eCSSUnit_Enumerated);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool
+CSSParserImpl::ParseTextOverflow(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
+ // 'inherit', 'initial' and 'unset' must be alone
+ return true;
+ }
+
+ nsCSSValue left;
+ if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD | VARIANT_STRING,
+ nsCSSProps::kTextOverflowKTable))
+ return false;
+
+ nsCSSValue right;
+ if (ParseSingleTokenVariant(right, VARIANT_KEYWORD | VARIANT_STRING,
+ nsCSSProps::kTextOverflowKTable))
+ aValue.SetPairValue(left, right);
+ else {
+ aValue = left;
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTouchAction(nsCSSValue& aValue)
+{
+ // Avaliable values of property touch-action:
+ // auto | none | [pan-x || pan-y] | manipulation
+
+ if (!ParseSingleTokenVariant(aValue, VARIANT_HK,
+ nsCSSProps::kTouchActionKTable)) {
+ return false;
+ }
+
+ // Auto and None keywords aren't allowed in conjunction with others.
+ // Also inherit, initial and unset values are available.
+ if (eCSSUnit_Enumerated != aValue.GetUnit()) {
+ return true;
+ }
+
+ int32_t intValue = aValue.GetIntValue();
+ nsCSSValue nextValue;
+ if (ParseEnum(nextValue, nsCSSProps::kTouchActionKTable)) {
+ int32_t nextIntValue = nextValue.GetIntValue();
+
+ // duplicates aren't allowed.
+ if (nextIntValue & intValue) {
+ return false;
+ }
+
+ // Auto and None and Manipulation is not allowed in conjunction with others.
+ if ((intValue | nextIntValue) & (NS_STYLE_TOUCH_ACTION_NONE |
+ NS_STYLE_TOUCH_ACTION_AUTO |
+ NS_STYLE_TOUCH_ACTION_MANIPULATION)) {
+ return false;
+ }
+
+ aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated);
+ }
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTextCombineUpright(nsCSSValue& aValue)
+{
+ if (!ParseSingleTokenVariant(aValue, VARIANT_HK,
+ nsCSSProps::kTextCombineUprightKTable)) {
+ return false;
+ }
+
+ // if 'digits', need to check for an explicit number [2, 3, 4]
+ if (eCSSUnit_Enumerated == aValue.GetUnit() &&
+ aValue.GetIntValue() == NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2) {
+ if (!nsLayoutUtils::TextCombineUprightDigitsEnabled()) {
+ return false;
+ }
+ if (!GetToken(true)) {
+ return true;
+ }
+ if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) {
+ switch (mToken.mInteger) {
+ case 2: // already set, nothing to do
+ break;
+ case 3:
+ aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3,
+ eCSSUnit_Enumerated);
+ break;
+ case 4:
+ aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_4,
+ eCSSUnit_Enumerated);
+ break;
+ default:
+ // invalid digits value
+ return false;
+ }
+ } else {
+ UngetToken();
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////
+// transform Parsing Implementation
+
+/* Reads a function list of arguments and consumes the closing parenthesis.
+ * Do not call this function directly; it's meant to be called from
+ * ParseFunction.
+ */
+bool
+CSSParserImpl::ParseFunctionInternals(const uint32_t aVariantMask[],
+ uint32_t aVariantMaskAll,
+ uint16_t aMinElems,
+ uint16_t aMaxElems,
+ InfallibleTArray<nsCSSValue> &aOutput)
+{
+ NS_ASSERTION((aVariantMask && !aVariantMaskAll) ||
+ (!aVariantMask && aVariantMaskAll),
+ "only one of the two variant mask parameters can be set");
+
+ for (uint16_t index = 0; index < aMaxElems; ++index) {
+ nsCSSValue newValue;
+ uint32_t m = aVariantMaskAll ? aVariantMaskAll : aVariantMask[index];
+ if (ParseVariant(newValue, m, nullptr) != CSSParseResult::Ok) {
+ break;
+ }
+
+ if (nsCSSValue::IsFloatUnit(newValue.GetUnit())) {
+ // Clamp infinity or -infinity values to max float or -max float to avoid
+ // calculations with infinity.
+ newValue.SetFloatValue(
+ mozilla::clamped(newValue.GetFloatValue(),
+ -std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max()),
+ newValue.GetUnit());
+ }
+
+ aOutput.AppendElement(newValue);
+
+ if (ExpectSymbol(',', true)) {
+ // Move on to the next argument if we see a comma.
+ continue;
+ }
+
+ if (ExpectSymbol(')', true)) {
+ // Make sure we've read enough symbols if we see a closing parenthesis.
+ return (index + 1) >= aMinElems;
+ }
+
+ // Only a comma or a closing parenthesis is valid after an argument.
+ break;
+ }
+
+ // If we're here, we've hit an error without seeing a closing parenthesis or
+ // we've read too many elements without seeing a closing parenthesis.
+ SkipUntil(')');
+ return false;
+}
+
+/* Parses a function [ input of the form (a [, b]*) ] and stores it
+ * as an nsCSSValue that holds a function of the form
+ * function-name arg1 arg2 ... argN
+ *
+ * On error, the return value is false.
+ *
+ * @param aFunction The name of the function that we're reading.
+ * @param aAllowedTypes An array of values corresponding to the legal
+ * types for each element in the function. The zeroth element in the
+ * array corresponds to the first function parameter, etc. The length
+ * of this array _must_ be greater than or equal to aMaxElems or the
+ * behavior is undefined. If not null, aAllowTypesAll must be 0.
+ * @param aAllowedTypesAll If set, every element tested for these types
+ * @param aMinElems Minimum number of elements to read. Reading fewer than
+ * this many elements will result in the function failing.
+ * @param aMaxElems Maximum number of elements to read. Reading more than
+ * this many elements will result in the function failing.
+ * @param aValue (out) The value that was parsed.
+ */
+bool
+CSSParserImpl::ParseFunction(nsCSSKeyword aFunction,
+ const uint32_t aAllowedTypes[],
+ uint32_t aAllowedTypesAll,
+ uint16_t aMinElems, uint16_t aMaxElems,
+ nsCSSValue &aValue)
+{
+ NS_ASSERTION((aAllowedTypes && !aAllowedTypesAll) ||
+ (!aAllowedTypes && aAllowedTypesAll),
+ "only one of the two allowed type parameter can be set");
+ typedef InfallibleTArray<nsCSSValue>::size_type arrlen_t;
+
+ /* 2^16 - 2, so that if we have 2^16 - 2 transforms, we have 2^16 - 1
+ * elements stored in the the nsCSSValue::Array.
+ */
+ static const arrlen_t MAX_ALLOWED_ELEMS = 0xFFFE;
+
+ /* Read in a list of values as an array, failing if we can't or if
+ * it's out of bounds.
+ *
+ * We reserve 16 entries in the foundValues array in order to avoid
+ * having to resize the array dynamically when parsing some well-formed
+ * functions. The number 16 is coming from the number of arguments that
+ * matrix3d() accepts.
+ */
+ AutoTArray<nsCSSValue, 16> foundValues;
+ if (!ParseFunctionInternals(aAllowedTypes, aAllowedTypesAll, aMinElems,
+ aMaxElems, foundValues)) {
+ return false;
+ }
+
+ /*
+ * In case the user has given us more than 2^16 - 2 arguments,
+ * we'll truncate them at 2^16 - 2 arguments.
+ */
+ uint16_t numArgs = std::min(foundValues.Length(), MAX_ALLOWED_ELEMS);
+ RefPtr<nsCSSValue::Array> convertedArray =
+ aValue.InitFunction(aFunction, numArgs);
+
+ /* Copy things over. */
+ for (uint16_t index = 0; index < numArgs; ++index)
+ convertedArray->Item(index + 1) = foundValues[static_cast<arrlen_t>(index)];
+
+ /* Return it! */
+ return true;
+}
+
+/**
+ * Given a token, determines the minimum and maximum number of function
+ * parameters to read, along with the mask that should be used to read
+ * those function parameters. If the token isn't a transform function,
+ * returns an error.
+ *
+ * @param aToken The token identifying the function.
+ * @param aIsPrefixed If true, parse matrices using the matrix syntax
+ * for -moz-transform.
+ * @param aDisallowRelativeValues If true, only allow variants that are
+ * numbers or have non-relative dimensions.
+ * @param aMinElems [out] The minimum number of elements to read.
+ * @param aMaxElems [out] The maximum number of elements to read
+ * @param aVariantMask [out] The variant mask to use during parsing
+ * @return Whether the information was loaded successfully.
+ */
+static bool GetFunctionParseInformation(nsCSSKeyword aToken,
+ bool aIsPrefixed,
+ bool aDisallowRelativeValues,
+ uint16_t &aMinElems,
+ uint16_t &aMaxElems,
+ const uint32_t *& aVariantMask)
+{
+/* These types represent the common variant masks that will be used to
+ * parse out the individual functions. The order in the enumeration
+ * must match the order in which the masks are declared.
+ */
+ enum { eLengthPercentCalc,
+ eLengthCalc,
+ eAbsoluteLengthCalc,
+ eTwoLengthPercentCalcs,
+ eTwoAbsoluteLengthCalcs,
+ eTwoLengthPercentCalcsOneLengthCalc,
+ eThreeAbsoluteLengthCalc,
+ eAngle,
+ eTwoAngles,
+ eNumber,
+ eNonNegativeLength,
+ eNonNegativeAbsoluteLength,
+ eTwoNumbers,
+ eThreeNumbers,
+ eThreeNumbersOneAngle,
+ eMatrix,
+ eMatrixPrefixed,
+ eMatrix3d,
+ eMatrix3dPrefixed,
+ eNumVariantMasks };
+ static const int32_t kMaxElemsPerFunction = 16;
+ static const uint32_t kVariantMasks[eNumVariantMasks][kMaxElemsPerFunction] = {
+ {VARIANT_LPCALC},
+ {VARIANT_LCALC},
+ {VARIANT_LB},
+ {VARIANT_LPCALC, VARIANT_LPCALC},
+ {VARIANT_LBCALC, VARIANT_LBCALC},
+ {VARIANT_LPCALC, VARIANT_LPCALC, VARIANT_LCALC},
+ {VARIANT_LBCALC, VARIANT_LBCALC, VARIANT_LBCALC},
+ {VARIANT_ANGLE_OR_ZERO},
+ {VARIANT_ANGLE_OR_ZERO, VARIANT_ANGLE_OR_ZERO},
+ {VARIANT_NUMBER},
+ {VARIANT_LENGTH|VARIANT_NONNEGATIVE_DIMENSION},
+ {VARIANT_LB|VARIANT_NONNEGATIVE_DIMENSION},
+ {VARIANT_NUMBER, VARIANT_NUMBER},
+ {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER},
+ {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_ANGLE_OR_ZERO},
+ {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
+ VARIANT_NUMBER, VARIANT_NUMBER},
+ {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
+ VARIANT_LPNCALC, VARIANT_LPNCALC},
+ {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
+ VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
+ VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
+ VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER},
+ {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
+ VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
+ VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
+ VARIANT_LPNCALC, VARIANT_LPNCALC, VARIANT_LNCALC, VARIANT_NUMBER}};
+ // Map from a mask to a congruent mask that excludes relative variants.
+ static const int32_t kNonRelativeVariantMap[eNumVariantMasks] = {
+ eAbsoluteLengthCalc,
+ eAbsoluteLengthCalc,
+ eAbsoluteLengthCalc,
+ eTwoAbsoluteLengthCalcs,
+ eTwoAbsoluteLengthCalcs,
+ eThreeAbsoluteLengthCalc,
+ eThreeAbsoluteLengthCalc,
+ eAngle,
+ eTwoAngles,
+ eNumber,
+ eNonNegativeAbsoluteLength,
+ eNonNegativeAbsoluteLength,
+ eTwoNumbers,
+ eThreeNumbers,
+ eThreeNumbersOneAngle,
+ eMatrix,
+ eMatrix,
+ eMatrix3d,
+ eMatrix3d };
+
+#ifdef DEBUG
+ static const uint8_t kVariantMaskLengths[eNumVariantMasks] =
+ {1, 1, 1, 2, 2, 3, 3, 1, 2, 1, 1, 1, 2, 3, 4, 6, 6, 16, 16};
+#endif
+
+ int32_t variantIndex = eNumVariantMasks;
+
+ switch (aToken) {
+ case eCSSKeyword_translatex:
+ case eCSSKeyword_translatey:
+ /* Exactly one length or percent. */
+ variantIndex = eLengthPercentCalc;
+ aMinElems = 1U;
+ aMaxElems = 1U;
+ break;
+ case eCSSKeyword_translatez:
+ /* Exactly one length */
+ variantIndex = eLengthCalc;
+ aMinElems = 1U;
+ aMaxElems = 1U;
+ break;
+ case eCSSKeyword_translate3d:
+ /* Exactly two lengthds or percents and a number */
+ variantIndex = eTwoLengthPercentCalcsOneLengthCalc;
+ aMinElems = 3U;
+ aMaxElems = 3U;
+ break;
+ case eCSSKeyword_scalez:
+ case eCSSKeyword_scalex:
+ case eCSSKeyword_scaley:
+ /* Exactly one scale factor. */
+ variantIndex = eNumber;
+ aMinElems = 1U;
+ aMaxElems = 1U;
+ break;
+ case eCSSKeyword_scale3d:
+ /* Exactly three scale factors. */
+ variantIndex = eThreeNumbers;
+ aMinElems = 3U;
+ aMaxElems = 3U;
+ break;
+ case eCSSKeyword_rotatex:
+ case eCSSKeyword_rotatey:
+ case eCSSKeyword_rotate:
+ case eCSSKeyword_rotatez:
+ /* Exactly one angle. */
+ variantIndex = eAngle;
+ aMinElems = 1U;
+ aMaxElems = 1U;
+ break;
+ case eCSSKeyword_rotate3d:
+ variantIndex = eThreeNumbersOneAngle;
+ aMinElems = 4U;
+ aMaxElems = 4U;
+ break;
+ case eCSSKeyword_translate:
+ /* One or two lengths or percents. */
+ variantIndex = eTwoLengthPercentCalcs;
+ aMinElems = 1U;
+ aMaxElems = 2U;
+ break;
+ case eCSSKeyword_skew:
+ /* Exactly one or two angles. */
+ variantIndex = eTwoAngles;
+ aMinElems = 1U;
+ aMaxElems = 2U;
+ break;
+ case eCSSKeyword_scale:
+ /* One or two scale factors. */
+ variantIndex = eTwoNumbers;
+ aMinElems = 1U;
+ aMaxElems = 2U;
+ break;
+ case eCSSKeyword_skewx:
+ /* Exactly one angle. */
+ variantIndex = eAngle;
+ aMinElems = 1U;
+ aMaxElems = 1U;
+ break;
+ case eCSSKeyword_skewy:
+ /* Exactly one angle. */
+ variantIndex = eAngle;
+ aMinElems = 1U;
+ aMaxElems = 1U;
+ break;
+ case eCSSKeyword_matrix:
+ /* Six values, all numbers. */
+ variantIndex = aIsPrefixed ? eMatrixPrefixed : eMatrix;
+ aMinElems = 6U;
+ aMaxElems = 6U;
+ break;
+ case eCSSKeyword_matrix3d:
+ /* 16 matrix values, all numbers */
+ variantIndex = aIsPrefixed ? eMatrix3dPrefixed : eMatrix3d;
+ aMinElems = 16U;
+ aMaxElems = 16U;
+ break;
+ case eCSSKeyword_perspective:
+ /* Exactly one scale number. */
+ variantIndex = eNonNegativeLength;
+ aMinElems = 1U;
+ aMaxElems = 1U;
+ break;
+ default:
+ /* Oh dear, we didn't match. Report an error. */
+ return false;
+ }
+
+ if (aDisallowRelativeValues) {
+ variantIndex = kNonRelativeVariantMap[variantIndex];
+ }
+
+ NS_ASSERTION(aMinElems > 0, "Didn't update minimum elements!");
+ NS_ASSERTION(aMaxElems > 0, "Didn't update maximum elements!");
+ NS_ASSERTION(aMinElems <= aMaxElems, "aMinElems > aMaxElems!");
+ NS_ASSERTION(variantIndex >= 0, "Invalid variant mask!");
+ NS_ASSERTION(variantIndex < eNumVariantMasks, "Invalid variant mask!");
+#ifdef DEBUG
+ NS_ASSERTION(aMaxElems <= kVariantMaskLengths[variantIndex],
+ "Invalid aMaxElems for this variant mask.");
+#endif
+
+ // Convert the index into a mask.
+ aVariantMask = kVariantMasks[variantIndex];
+
+ return true;
+}
+
+bool CSSParserImpl::ParseWillChange()
+{
+ nsCSSValue listValue;
+ nsCSSValueList* currentListValue = listValue.SetListValue();
+ bool first = true;
+ for (;;) {
+ const uint32_t variantMask = VARIANT_IDENTIFIER |
+ VARIANT_INHERIT |
+ VARIANT_NONE |
+ VARIANT_ALL |
+ VARIANT_AUTO;
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, variantMask, nullptr)) {
+ return false;
+ }
+
+ if (value.GetUnit() == eCSSUnit_None ||
+ value.GetUnit() == eCSSUnit_All)
+ {
+ return false;
+ }
+
+ if (value.GetUnit() != eCSSUnit_Ident) {
+ if (first) {
+ AppendValue(eCSSProperty_will_change, value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ nsString str;
+ value.GetStringValue(str);
+ if (str.LowerCaseEqualsLiteral("default") ||
+ str.LowerCaseEqualsLiteral("will-change")) {
+ return false;
+ }
+
+ currentListValue->mValue = value;
+
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+ currentListValue->mNext = new nsCSSValueList;
+ currentListValue = currentListValue->mNext;
+ first = false;
+ }
+
+ AppendValue(eCSSProperty_will_change, listValue);
+ return true;
+}
+
+/* Reads a single transform function from the tokenizer stream, reporting an
+ * error if something goes wrong.
+ */
+bool
+CSSParserImpl::ParseSingleTransform(bool aIsPrefixed,
+ bool aDisallowRelativeValues,
+ nsCSSValue& aValue)
+{
+ if (!GetToken(true))
+ return false;
+
+ if (mToken.mType != eCSSToken_Function) {
+ UngetToken();
+ return false;
+ }
+
+ const uint32_t* variantMask;
+ uint16_t minElems, maxElems;
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+
+ if (!GetFunctionParseInformation(keyword, aIsPrefixed,
+ aDisallowRelativeValues,
+ minElems, maxElems,
+ variantMask))
+ return false;
+
+ return ParseFunction(keyword, variantMask, 0, minElems, maxElems, aValue);
+}
+
+/* Parses a transform property list by continuously reading in properties
+ * and constructing a matrix from it.
+ */
+bool
+CSSParserImpl::ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues)
+{
+ nsCSSValue value;
+ // 'inherit', 'initial', 'unset' and 'none' must be alone
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
+ nullptr)) {
+ nsCSSValueSharedList* list = new nsCSSValueSharedList;
+ value.SetSharedListValue(list);
+ list->mHead = new nsCSSValueList;
+ nsCSSValueList* cur = list->mHead;
+ for (;;) {
+ if (!ParseSingleTransform(aIsPrefixed, aDisallowRelativeValues,
+ cur->mValue)) {
+ return false;
+ }
+ if (CheckEndProperty()) {
+ break;
+ }
+ cur->mNext = new nsCSSValueList;
+ cur = cur->mNext;
+ }
+ }
+ AppendValue(eCSSProperty_transform, value);
+ return true;
+}
+
+/* Reads a polygon function's argument list.
+ */
+bool
+CSSParserImpl::ParsePolygonFunction(nsCSSValue& aValue)
+{
+ uint16_t numArgs = 1;
+
+ nsCSSValue fillRuleValue;
+ if (ParseEnum(fillRuleValue, nsCSSProps::kFillRuleKTable)) {
+ numArgs++;
+
+ // The fill-rule must be comma separated from the polygon points.
+ if (!ExpectSymbol(',', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedComma);
+ SkipUntil(')');
+ return false;
+ }
+ }
+
+ nsCSSValue coordinates;
+ nsCSSValuePairList* item = coordinates.SetPairListValue();
+ for (;;) {
+ nsCSSValue xValue, yValue;
+ if (ParseVariant(xValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok ||
+ ParseVariant(yValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok) {
+ REPORT_UNEXPECTED_TOKEN(PECoordinatePair);
+ SkipUntil(')');
+ return false;
+ }
+ item->mXValue = xValue;
+ item->mYValue = yValue;
+
+ // See whether to continue or whether to look for end of function.
+ if (!ExpectSymbol(',', true)) {
+ // We need to read the closing parenthesis.
+ if (!ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
+ SkipUntil(')');
+ return false;
+ }
+ break;
+ }
+ item->mNext = new nsCSSValuePairList;
+ item = item->mNext;
+ }
+
+ RefPtr<nsCSSValue::Array> functionArray =
+ aValue.InitFunction(eCSSKeyword_polygon, numArgs);
+ functionArray->Item(numArgs) = coordinates;
+ if (numArgs > 1) {
+ functionArray->Item(1) = fillRuleValue;
+ }
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseCircleOrEllipseFunction(nsCSSKeyword aKeyword,
+ nsCSSValue& aValue)
+{
+ nsCSSValue radiusX, radiusY, position;
+ bool hasRadius = false, hasPosition = false;
+
+ int32_t mask = VARIANT_LPCALC | VARIANT_NONNEGATIVE_DIMENSION |
+ VARIANT_KEYWORD;
+ CSSParseResult result =
+ ParseVariant(radiusX, mask, nsCSSProps::kShapeRadiusKTable);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::Ok) {
+ if (aKeyword == eCSSKeyword_ellipse) {
+ if (ParseVariant(radiusY, mask, nsCSSProps::kShapeRadiusKTable) !=
+ CSSParseResult::Ok) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
+ SkipUntil(')');
+ return false;
+ }
+ }
+ hasRadius = true;
+ }
+
+ if (!ExpectSymbol(')', true)) {
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEPositionEOF);
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Ident ||
+ !mToken.mIdent.LowerCaseEqualsLiteral("at") ||
+ !ParsePositionValueForBasicShape(position)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
+ SkipUntil(')');
+ return false;
+ }
+ if (!ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
+ SkipUntil(')');
+ return false;
+ }
+ hasPosition = true;
+ }
+
+ size_t count = aKeyword == eCSSKeyword_circle ? 2 : 3;
+ RefPtr<nsCSSValue::Array> functionArray =
+ aValue.InitFunction(aKeyword, count);
+ if (hasRadius) {
+ functionArray->Item(1) = radiusX;
+ if (aKeyword == eCSSKeyword_ellipse) {
+ functionArray->Item(2) = radiusY;
+ }
+ }
+ if (hasPosition) {
+ functionArray->Item(count) = position;
+ }
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseInsetFunction(nsCSSValue& aValue)
+{
+ RefPtr<nsCSSValue::Array> functionArray =
+ aValue.InitFunction(eCSSKeyword_inset, 5);
+
+ int count = 0;
+ while (count < 4) {
+ CSSParseResult result =
+ ParseVariant(functionArray->Item(count + 1), VARIANT_LPCALC, nullptr);
+ if (result == CSSParseResult::Error) {
+ count = 0;
+ break;
+ } else if (result == CSSParseResult::NotFound) {
+ break;
+ }
+ ++count;
+ }
+
+ if (count == 0) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedShapeArg);
+ SkipUntil(')');
+ return false;
+ }
+
+ if (!ExpectSymbol(')', true)) {
+ if (!GetToken(true)) {
+ NS_NOTREACHED("ExpectSymbol should have returned true");
+ return false;
+ }
+
+ RefPtr<nsCSSValue::Array> radiusArray = nsCSSValue::Array::Create(4);
+ functionArray->Item(5).SetArrayValue(radiusArray, eCSSUnit_Array);
+ if (mToken.mType != eCSSToken_Ident ||
+ !mToken.mIdent.LowerCaseEqualsLiteral("round") ||
+ !ParseBoxCornerRadiiInternals(radiusArray->ItemStorage())) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
+ SkipUntil(')');
+ return false;
+ }
+
+ if (!ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
+ SkipUntil(')');
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Function) {
+ UngetToken();
+ return false;
+ }
+
+ // Specific shape function parsing always consumes tokens.
+ *aConsumedTokens = true;
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+ switch (keyword) {
+ case eCSSKeyword_polygon:
+ return ParsePolygonFunction(aValue);
+ case eCSSKeyword_circle:
+ case eCSSKeyword_ellipse:
+ return ParseCircleOrEllipseFunction(keyword, aValue);
+ case eCSSKeyword_inset:
+ return ParseInsetFunction(aValue);
+ default:
+ return false;
+ }
+}
+
+bool
+CSSParserImpl::ParseReferenceBoxAndBasicShape(
+ nsCSSValue& aValue,
+ const KTableEntry aBoxKeywordTable[])
+{
+ nsCSSValue referenceBox;
+ bool hasBox = ParseEnum(referenceBox, aBoxKeywordTable);
+
+ const bool boxCameFirst = hasBox;
+
+ nsCSSValue basicShape;
+ bool basicShapeConsumedTokens = false;
+ bool hasShape = ParseBasicShape(basicShape, &basicShapeConsumedTokens);
+
+ // Parsing wasn't successful if ParseBasicShape consumed tokens but failed
+ // or if the token was neither a reference box nor a basic shape.
+ if ((!hasShape && basicShapeConsumedTokens) || (!hasBox && !hasShape)) {
+ return false;
+ }
+
+ // Check if the second argument is a reference box if the first wasn't.
+ if (!hasBox) {
+ hasBox = ParseEnum(referenceBox, aBoxKeywordTable);
+ }
+
+ RefPtr<nsCSSValue::Array> fullValue =
+ nsCSSValue::Array::Create((hasBox && hasShape) ? 2 : 1);
+
+ if (hasBox && hasShape) {
+ fullValue->Item(boxCameFirst ? 0 : 1) = referenceBox;
+ fullValue->Item(boxCameFirst ? 1 : 0) = basicShape;
+ } else if (hasBox) {
+ fullValue->Item(0) = referenceBox;
+ } else {
+ MOZ_ASSERT(hasShape, "should've bailed if we got neither box nor shape");
+ fullValue->Item(0) = basicShape;
+ }
+
+ aValue.SetArrayValue(fullValue, eCSSUnit_Array);
+ return true;
+}
+
+// Parse a clip-path url to a <clipPath> element or a basic shape.
+bool
+CSSParserImpl::ParseClipPath(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) {
+ return true;
+ }
+
+ if (!nsLayoutUtils::CSSClipPathShapesEnabled()) {
+ // With CSS Clip Path Shapes disabled, we should only accept
+ // SVG clipPath reference and none.
+ REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL);
+ return false;
+ }
+
+ return ParseReferenceBoxAndBasicShape(
+ aValue, nsCSSProps::kClipPathGeometryBoxKTable);
+}
+
+// none | [ <basic-shape> || <shape-box> ] | <image>
+bool
+CSSParserImpl::ParseShapeOutside(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) {
+ // 'inherit', 'initial', 'unset', 'none', and <image> url must be alone.
+ return true;
+ }
+
+ return ParseReferenceBoxAndBasicShape(
+ aValue, nsCSSProps::kShapeOutsideShapeBoxKTable);
+}
+
+bool CSSParserImpl::ParseTransformOrigin(bool aPerspective)
+{
+ nsCSSValuePair position;
+ if (!ParseBoxPositionValues(position, true))
+ return false;
+
+ nsCSSPropertyID prop = eCSSProperty_transform_origin;
+ if (aPerspective) {
+ prop = eCSSProperty_perspective_origin;
+ }
+
+ // Unlike many other uses of pairs, this position should always be stored
+ // as a pair, even if the values are the same, so it always serializes as
+ // a pair, and to keep the computation code simple.
+ if (position.mXValue.GetUnit() == eCSSUnit_Inherit ||
+ position.mXValue.GetUnit() == eCSSUnit_Initial ||
+ position.mXValue.GetUnit() == eCSSUnit_Unset) {
+ MOZ_ASSERT(position.mXValue == position.mYValue,
+ "inherit/initial/unset only half?");
+ AppendValue(prop, position.mXValue);
+ } else {
+ nsCSSValue value;
+ if (aPerspective) {
+ value.SetPairValue(position.mXValue, position.mYValue);
+ } else {
+ nsCSSValue depth;
+ CSSParseResult result =
+ ParseVariant(depth, VARIANT_LENGTH | VARIANT_CALC, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::NotFound) {
+ depth.SetFloatValue(0.0f, eCSSUnit_Pixel);
+ }
+ value.SetTripletValue(position.mXValue, position.mYValue, depth);
+ }
+
+ AppendValue(prop, value);
+ }
+ return true;
+}
+
+/**
+ * Reads a drop-shadow value. At the moment the Filter Effects specification
+ * just expects one shadow item. Should this ever change to a list of shadow
+ * items, use ParseShadowList instead.
+ */
+bool
+CSSParserImpl::ParseDropShadow(nsCSSValue* aValue)
+{
+ // Use nsCSSValueList to reuse the shadow resolving code in
+ // nsRuleNode and nsComputedDOMStyle.
+ nsCSSValue shadow;
+ nsCSSValueList* cur = shadow.SetListValue();
+ if (!ParseShadowItem(cur->mValue, false))
+ return false;
+
+ if (!ExpectSymbol(')', true))
+ return false;
+
+ nsCSSValue::Array* dropShadow = aValue->InitFunction(eCSSKeyword_drop_shadow, 1);
+
+ // Copy things over.
+ dropShadow->Item(1) = shadow;
+
+ return true;
+}
+
+/**
+ * Reads a single url or filter function from the tokenizer stream, reporting an
+ * error if something goes wrong.
+ */
+bool
+CSSParserImpl::ParseSingleFilter(nsCSSValue* aValue)
+{
+ if (ParseSingleTokenVariant(*aValue, VARIANT_URL, nullptr)) {
+ return true;
+ }
+
+ if (!nsLayoutUtils::CSSFiltersEnabled()) {
+ // With CSS Filters disabled, we should only accept an SVG reference filter.
+ REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL);
+ return false;
+ }
+
+ if (!GetToken(true)) {
+ REPORT_UNEXPECTED_EOF(PEFilterEOF);
+ return false;
+ }
+
+ if (mToken.mType != eCSSToken_Function) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
+ UngetToken();
+ return false;
+ }
+
+ nsCSSKeyword functionName = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+ // Parse drop-shadow independently of the other filter functions
+ // because of its more complex characteristics.
+ if (functionName == eCSSKeyword_drop_shadow) {
+ if (ParseDropShadow(aValue)) {
+ return true;
+ } else {
+ // Unrecognized filter function.
+ REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
+ SkipUntil(')');
+ return false;
+ }
+ }
+
+ // Set up the parsing rules based on the filter function.
+ uint32_t variantMask = VARIANT_PN;
+ bool rejectNegativeArgument = true;
+ bool clampArgumentToOne = false;
+ switch (functionName) {
+ case eCSSKeyword_blur:
+ variantMask = VARIANT_LCALC | VARIANT_NONNEGATIVE_DIMENSION;
+ // VARIANT_NONNEGATIVE_DIMENSION will already reject negative lengths.
+ rejectNegativeArgument = false;
+ break;
+ case eCSSKeyword_brightness:
+ case eCSSKeyword_contrast:
+ case eCSSKeyword_saturate:
+ break;
+ case eCSSKeyword_grayscale:
+ case eCSSKeyword_invert:
+ case eCSSKeyword_sepia:
+ case eCSSKeyword_opacity:
+ clampArgumentToOne = true;
+ break;
+ case eCSSKeyword_hue_rotate:
+ variantMask = VARIANT_ANGLE;
+ rejectNegativeArgument = false;
+ break;
+ default:
+ // Unrecognized filter function.
+ REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
+ SkipUntil(')');
+ return false;
+ }
+
+ // Parse the function.
+ uint16_t minElems = 1U;
+ uint16_t maxElems = 1U;
+ uint32_t allVariants = 0;
+ if (!ParseFunction(functionName, &variantMask, allVariants,
+ minElems, maxElems, *aValue)) {
+ REPORT_UNEXPECTED(PEFilterFunctionArgumentsParsingError);
+ return false;
+ }
+
+ // Get the first and only argument to the filter function.
+ MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Function,
+ "expected a filter function");
+ MOZ_ASSERT(aValue->UnitHasArrayValue(),
+ "filter function should be an array");
+ MOZ_ASSERT(aValue->GetArrayValue()->Count() == 2,
+ "filter function should have exactly one argument");
+ nsCSSValue& arg = aValue->GetArrayValue()->Item(1);
+
+ if (rejectNegativeArgument &&
+ ((arg.GetUnit() == eCSSUnit_Percent && arg.GetPercentValue() < 0.0f) ||
+ (arg.GetUnit() == eCSSUnit_Number && arg.GetFloatValue() < 0.0f))) {
+ REPORT_UNEXPECTED(PEExpectedNonnegativeNP);
+ return false;
+ }
+
+ if (clampArgumentToOne) {
+ if (arg.GetUnit() == eCSSUnit_Number &&
+ arg.GetFloatValue() > 1.0f) {
+ arg.SetFloatValue(1.0f, arg.GetUnit());
+ } else if (arg.GetUnit() == eCSSUnit_Percent &&
+ arg.GetPercentValue() > 1.0f) {
+ arg.SetPercentValue(1.0f);
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Parses a filter property value by continuously reading in urls and/or filter
+ * functions and constructing a list.
+ *
+ * When CSS Filters are enabled, the filter property accepts one or more SVG
+ * reference filters and/or CSS filter functions.
+ * e.g. filter: url(#my-filter-1) blur(3px) url(#my-filter-2) grayscale(50%);
+ *
+ * When CSS Filters are disabled, the filter property only accepts one SVG
+ * reference filter.
+ * e.g. filter: url(#my-filter);
+ */
+bool
+CSSParserImpl::ParseFilter()
+{
+ nsCSSValue value;
+ // 'inherit', 'initial', 'unset' and 'none' must be alone
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
+ nullptr)) {
+ nsCSSValueList* cur = value.SetListValue();
+ while (cur) {
+ if (!ParseSingleFilter(&cur->mValue)) {
+ return false;
+ }
+ if (CheckEndProperty()) {
+ break;
+ }
+ if (!nsLayoutUtils::CSSFiltersEnabled()) {
+ // With CSS Filters disabled, we should only accept one SVG reference
+ // filter.
+ REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
+ return false;
+ }
+ cur->mNext = new nsCSSValueList;
+ cur = cur->mNext;
+ }
+ }
+ AppendValue(eCSSProperty_filter, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTransitionProperty()
+{
+ nsCSSValue value;
+ // 'inherit', 'initial', 'unset' and 'none' must be alone
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
+ nullptr)) {
+ // Accept a list of arbitrary identifiers. They should be
+ // CSS properties, but we want to accept any so that we
+ // accept properties that we don't know about yet, e.g.
+ // transition-property: invalid-property, left, opacity;
+ nsCSSValueList* cur = value.SetListValue();
+ for (;;) {
+ if (!ParseSingleTokenVariant(cur->mValue,
+ VARIANT_IDENTIFIER | VARIANT_ALL,
+ nullptr)) {
+ return false;
+ }
+ if (cur->mValue.GetUnit() == eCSSUnit_Ident) {
+ nsDependentString str(cur->mValue.GetStringBufferValue());
+ // Exclude 'none', 'inherit', 'initial' and 'unset' according to the
+ // same rules as for 'counter-reset' in CSS 2.1.
+ if (str.LowerCaseEqualsLiteral("none") ||
+ str.LowerCaseEqualsLiteral("inherit") ||
+ str.LowerCaseEqualsLiteral("initial") ||
+ (str.LowerCaseEqualsLiteral("unset") &&
+ nsLayoutUtils::UnsetValueEnabled())) {
+ return false;
+ }
+ }
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+ cur->mNext = new nsCSSValueList;
+ cur = cur->mNext;
+ }
+ }
+ AppendValue(eCSSProperty_transition_property, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTransitionTimingFunctionValues(nsCSSValue& aValue)
+{
+ NS_ASSERTION(!mHavePushBack &&
+ mToken.mType == eCSSToken_Function &&
+ mToken.mIdent.LowerCaseEqualsLiteral("cubic-bezier"),
+ "unexpected initial state");
+
+ RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(4);
+
+ float x1, x2, y1, y2;
+ if (!ParseTransitionTimingFunctionValueComponent(x1, ',', true) ||
+ !ParseTransitionTimingFunctionValueComponent(y1, ',', false) ||
+ !ParseTransitionTimingFunctionValueComponent(x2, ',', true) ||
+ !ParseTransitionTimingFunctionValueComponent(y2, ')', false)) {
+ return false;
+ }
+
+ val->Item(0).SetFloatValue(x1, eCSSUnit_Number);
+ val->Item(1).SetFloatValue(y1, eCSSUnit_Number);
+ val->Item(2).SetFloatValue(x2, eCSSUnit_Number);
+ val->Item(3).SetFloatValue(y2, eCSSUnit_Number);
+
+ aValue.SetArrayValue(val, eCSSUnit_Cubic_Bezier);
+
+ return true;
+}
+
+bool
+CSSParserImpl::ParseTransitionTimingFunctionValueComponent(float& aComponent,
+ char aStop,
+ bool aIsXPoint)
+{
+ if (!GetToken(true)) {
+ return false;
+ }
+ nsCSSToken* tk = &mToken;
+ if (tk->mType == eCSSToken_Number) {
+ float num = tk->mNumber;
+
+ // Clamp infinity or -infinity values to max float or -max float to avoid
+ // calculations with infinity.
+ num = mozilla::clamped(num, -std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max());
+
+ // X control point should be inside [0, 1] range.
+ if (aIsXPoint && (num < 0.0 || num > 1.0)) {
+ return false;
+ }
+ aComponent = num;
+ if (ExpectSymbol(aStop, true)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CSSParserImpl::ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue)
+{
+ NS_ASSERTION(!mHavePushBack &&
+ mToken.mType == eCSSToken_Function &&
+ mToken.mIdent.LowerCaseEqualsLiteral("steps"),
+ "unexpected initial state");
+
+ RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2);
+
+ if (!ParseSingleTokenOneOrLargerVariant(val->Item(0), VARIANT_INTEGER,
+ nullptr)) {
+ return false;
+ }
+
+ int32_t type = -1; // indicates an implicit end value
+ if (ExpectSymbol(',', true)) {
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (mToken.mType == eCSSToken_Ident) {
+ if (mToken.mIdent.LowerCaseEqualsLiteral("start")) {
+ type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START;
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("end")) {
+ type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END;
+ }
+ }
+ if (type == -1) {
+ UngetToken();
+ return false;
+ }
+ }
+ val->Item(1).SetIntValue(type, eCSSUnit_Enumerated);
+
+ if (!ExpectSymbol(')', true)) {
+ return false;
+ }
+
+ aValue.SetArrayValue(val, eCSSUnit_Steps);
+ return true;
+}
+
+static nsCSSValueList*
+AppendValueToList(nsCSSValue& aContainer,
+ nsCSSValueList* aTail,
+ const nsCSSValue& aValue)
+{
+ nsCSSValueList* entry;
+ if (aContainer.GetUnit() == eCSSUnit_Null) {
+ MOZ_ASSERT(!aTail, "should not have an entry");
+ entry = aContainer.SetListValue();
+ } else {
+ MOZ_ASSERT(!aTail->mNext, "should not have a next entry");
+ MOZ_ASSERT(aContainer.GetUnit() == eCSSUnit_List, "not a list");
+ entry = new nsCSSValueList;
+ aTail->mNext = entry;
+ }
+ entry->mValue = aValue;
+ return entry;
+}
+
+CSSParserImpl::ParseAnimationOrTransitionShorthandResult
+CSSParserImpl::ParseAnimationOrTransitionShorthand(
+ const nsCSSPropertyID* aProperties,
+ const nsCSSValue* aInitialValues,
+ nsCSSValue* aValues,
+ size_t aNumProperties)
+{
+ nsCSSValue tempValue;
+ // first see if 'inherit', 'initial' or 'unset' is specified. If one is,
+ // it can be the only thing specified, so don't attempt to parse any
+ // additional properties
+ if (ParseSingleTokenVariant(tempValue, VARIANT_INHERIT, nullptr)) {
+ for (uint32_t i = 0; i < aNumProperties; ++i) {
+ AppendValue(aProperties[i], tempValue);
+ }
+ return eParseAnimationOrTransitionShorthand_Inherit;
+ }
+
+ static const size_t maxNumProperties = 8;
+ MOZ_ASSERT(aNumProperties <= maxNumProperties,
+ "can't handle this many properties");
+ nsCSSValueList *cur[maxNumProperties];
+ bool parsedProperty[maxNumProperties];
+
+ for (size_t i = 0; i < aNumProperties; ++i) {
+ cur[i] = nullptr;
+ }
+ bool atEOP = false; // at end of property?
+ for (;;) { // loop over comma-separated transitions or animations
+ // whether a particular subproperty was specified for this
+ // transition or animation
+ bool haveAnyProperty = false;
+ for (size_t i = 0; i < aNumProperties; ++i) {
+ parsedProperty[i] = false;
+ }
+ for (;;) { // loop over values within a transition or animation
+ bool foundProperty = false;
+ // check to see if we're at the end of one full transition or
+ // animation definition (either because we hit a comma or because
+ // we hit the end of the property definition)
+ if (ExpectSymbol(',', true))
+ break;
+ if (CheckEndProperty()) {
+ atEOP = true;
+ break;
+ }
+
+ // else, try to parse the next transition or animation sub-property
+ for (uint32_t i = 0; !foundProperty && i < aNumProperties; ++i) {
+ if (!parsedProperty[i]) {
+ // if we haven't found this property yet, try to parse it
+ CSSParseResult result =
+ ParseSingleValueProperty(tempValue, aProperties[i]);
+ if (result == CSSParseResult::Error) {
+ return eParseAnimationOrTransitionShorthand_Error;
+ }
+ if (result == CSSParseResult::Ok) {
+ parsedProperty[i] = true;
+ cur[i] = AppendValueToList(aValues[i], cur[i], tempValue);
+ foundProperty = true;
+ haveAnyProperty = true;
+ break; // out of inner loop; continue looking for next sub-property
+ }
+ }
+ }
+ if (!foundProperty) {
+ // We're not at a ',' or at the end of the property, but we couldn't
+ // parse any of the sub-properties, so the declaration is invalid.
+ return eParseAnimationOrTransitionShorthand_Error;
+ }
+ }
+
+ if (!haveAnyProperty) {
+ // Got an empty item.
+ return eParseAnimationOrTransitionShorthand_Error;
+ }
+
+ // We hit the end of the property or the end of one transition
+ // or animation definition, add its components to the list.
+ for (uint32_t i = 0; i < aNumProperties; ++i) {
+ // If all of the subproperties were not explicitly specified, fill
+ // in the missing ones with initial values.
+ if (!parsedProperty[i]) {
+ cur[i] = AppendValueToList(aValues[i], cur[i], aInitialValues[i]);
+ }
+ }
+
+ if (atEOP)
+ break;
+ // else we just hit a ',' so continue parsing the next compound transition
+ }
+
+ return eParseAnimationOrTransitionShorthand_Values;
+}
+
+bool
+CSSParserImpl::ParseTransition()
+{
+ static const nsCSSPropertyID kTransitionProperties[] = {
+ eCSSProperty_transition_duration,
+ eCSSProperty_transition_timing_function,
+ // Must check 'transition-delay' after 'transition-duration', since
+ // that's our assumption about what the spec means for the shorthand
+ // syntax (the first time given is the duration, and the second
+ // given is the delay).
+ eCSSProperty_transition_delay,
+ // Must check 'transition-property' after
+ // 'transition-timing-function' since 'transition-property' accepts
+ // any keyword.
+ eCSSProperty_transition_property
+ };
+ static const uint32_t numProps = MOZ_ARRAY_LENGTH(kTransitionProperties);
+ // this is a shorthand property that accepts -property, -delay,
+ // -duration, and -timing-function with some components missing.
+ // there can be multiple transitions, separated with commas
+
+ nsCSSValue initialValues[numProps];
+ initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds);
+ initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE,
+ eCSSUnit_Enumerated);
+ initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds);
+ initialValues[3].SetAllValue();
+
+ nsCSSValue values[numProps];
+
+ ParseAnimationOrTransitionShorthandResult spres =
+ ParseAnimationOrTransitionShorthand(kTransitionProperties,
+ initialValues, values, numProps);
+ if (spres != eParseAnimationOrTransitionShorthand_Values) {
+ return spres != eParseAnimationOrTransitionShorthand_Error;
+ }
+
+ // Make two checks on the list for 'transition-property':
+ // + If there is more than one item, then none of the items can be
+ // 'none'.
+ // + None of the items can be 'inherit', 'initial' or 'unset'.
+ {
+ MOZ_ASSERT(kTransitionProperties[3] == eCSSProperty_transition_property,
+ "array index mismatch");
+ nsCSSValueList *l = values[3].GetListValue();
+ bool multipleItems = !!l->mNext;
+ do {
+ const nsCSSValue& val = l->mValue;
+ if (val.GetUnit() == eCSSUnit_None) {
+ if (multipleItems) {
+ // This is a syntax error.
+ return false;
+ }
+
+ // Unbox a solitary 'none'.
+ values[3].SetNoneValue();
+ break;
+ }
+ if (val.GetUnit() == eCSSUnit_Ident) {
+ nsDependentString str(val.GetStringBufferValue());
+ if (str.EqualsLiteral("inherit") ||
+ str.EqualsLiteral("initial") ||
+ (str.EqualsLiteral("unset") &&
+ nsLayoutUtils::UnsetValueEnabled())) {
+ return false;
+ }
+ }
+ } while ((l = l->mNext));
+ }
+
+ // Save all parsed transition sub-properties in mTempData
+ for (uint32_t i = 0; i < numProps; ++i) {
+ AppendValue(kTransitionProperties[i], values[i]);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseAnimation()
+{
+ static const nsCSSPropertyID kAnimationProperties[] = {
+ eCSSProperty_animation_duration,
+ eCSSProperty_animation_timing_function,
+ // Must check 'animation-delay' after 'animation-duration', since
+ // that's our assumption about what the spec means for the shorthand
+ // syntax (the first time given is the duration, and the second
+ // given is the delay).
+ eCSSProperty_animation_delay,
+ eCSSProperty_animation_direction,
+ eCSSProperty_animation_fill_mode,
+ eCSSProperty_animation_iteration_count,
+ eCSSProperty_animation_play_state,
+ // Must check 'animation-name' after 'animation-timing-function',
+ // 'animation-direction', 'animation-fill-mode',
+ // 'animation-iteration-count', and 'animation-play-state' since
+ // 'animation-name' accepts any keyword.
+ eCSSProperty_animation_name
+ };
+ static const uint32_t numProps = MOZ_ARRAY_LENGTH(kAnimationProperties);
+ // this is a shorthand property that accepts -property, -delay,
+ // -duration, and -timing-function with some components missing.
+ // there can be multiple animations, separated with commas
+
+ nsCSSValue initialValues[numProps];
+ initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds);
+ initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE,
+ eCSSUnit_Enumerated);
+ initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds);
+ initialValues[3].SetIntValue(static_cast<uint32_t>(mozilla::dom::PlaybackDirection::Normal),
+ eCSSUnit_Enumerated);
+ initialValues[4].SetIntValue(static_cast<uint32_t>(mozilla::dom::FillMode::None),
+ eCSSUnit_Enumerated);
+ initialValues[5].SetFloatValue(1.0f, eCSSUnit_Number);
+ initialValues[6].SetIntValue(NS_STYLE_ANIMATION_PLAY_STATE_RUNNING, eCSSUnit_Enumerated);
+ initialValues[7].SetNoneValue();
+
+ nsCSSValue values[numProps];
+
+ ParseAnimationOrTransitionShorthandResult spres =
+ ParseAnimationOrTransitionShorthand(kAnimationProperties,
+ initialValues, values, numProps);
+ if (spres != eParseAnimationOrTransitionShorthand_Values) {
+ return spres != eParseAnimationOrTransitionShorthand_Error;
+ }
+
+ // Save all parsed animation sub-properties in mTempData
+ for (uint32_t i = 0; i < numProps; ++i) {
+ AppendValue(kAnimationProperties[i], values[i]);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow)
+{
+ // A shadow list item is an array, with entries in this sequence:
+ enum {
+ IndexX,
+ IndexY,
+ IndexRadius,
+ IndexSpread, // only for box-shadow
+ IndexColor,
+ IndexInset // only for box-shadow
+ };
+
+ RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(6);
+
+ if (aIsBoxShadow) {
+ // Optional inset keyword (ignore errors)
+ ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD,
+ nsCSSProps::kBoxShadowTypeKTable);
+ }
+
+ nsCSSValue xOrColor;
+ bool haveColor = false;
+ if (ParseVariant(xOrColor, VARIANT_COLOR | VARIANT_LENGTH | VARIANT_CALC,
+ nullptr) != CSSParseResult::Ok) {
+ return false;
+ }
+ if (xOrColor.IsLengthUnit() || xOrColor.IsCalcUnit()) {
+ val->Item(IndexX) = xOrColor;
+ } else {
+ // Must be a color (as string or color value)
+ NS_ASSERTION(xOrColor.GetUnit() == eCSSUnit_Ident ||
+ xOrColor.GetUnit() == eCSSUnit_EnumColor ||
+ xOrColor.IsNumericColorUnit(),
+ "Must be a color value");
+ val->Item(IndexColor) = xOrColor;
+ haveColor = true;
+
+ // X coordinate mandatory after color
+ if (ParseVariant(val->Item(IndexX), VARIANT_LENGTH | VARIANT_CALC,
+ nullptr) != CSSParseResult::Ok) {
+ return false;
+ }
+ }
+
+ // Y coordinate; mandatory
+ if (ParseVariant(val->Item(IndexY), VARIANT_LENGTH | VARIANT_CALC,
+ nullptr) != CSSParseResult::Ok) {
+ return false;
+ }
+
+ // Optional radius. Ignore errors except if they pass a negative
+ // value which we must reject. If we use ParseNonNegativeVariant
+ // we can't tell the difference between an unspecified radius
+ // and a negative radius.
+ CSSParseResult result =
+ ParseVariant(val->Item(IndexRadius), VARIANT_LENGTH | VARIANT_CALC,
+ nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::Ok) {
+ if (val->Item(IndexRadius).IsLengthUnit() &&
+ val->Item(IndexRadius).GetFloatValue() < 0) {
+ return false;
+ }
+ }
+
+ if (aIsBoxShadow) {
+ // Optional spread
+ if (ParseVariant(val->Item(IndexSpread), VARIANT_LENGTH | VARIANT_CALC,
+ nullptr) == CSSParseResult::Error) {
+ return false;
+ }
+ }
+
+ if (!haveColor) {
+ // Optional color
+ if (ParseVariant(val->Item(IndexColor), VARIANT_COLOR, nullptr) ==
+ CSSParseResult::Error) {
+ return false;
+ }
+ }
+
+ if (aIsBoxShadow && val->Item(IndexInset).GetUnit() == eCSSUnit_Null) {
+ // Optional inset keyword
+ ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD,
+ nsCSSProps::kBoxShadowTypeKTable);
+ }
+
+ aValue.SetArrayValue(val, eCSSUnit_Array);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseShadowList(nsCSSPropertyID aProperty)
+{
+ nsAutoParseCompoundProperty compound(this);
+ bool isBoxShadow = aProperty == eCSSProperty_box_shadow;
+
+ nsCSSValue value;
+ // 'inherit', 'initial', 'unset' and 'none' must be alone
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
+ nullptr)) {
+ nsCSSValueList* cur = value.SetListValue();
+ for (;;) {
+ if (!ParseShadowItem(cur->mValue, isBoxShadow)) {
+ return false;
+ }
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+ cur->mNext = new nsCSSValueList;
+ cur = cur->mNext;
+ }
+ }
+ AppendValue(aProperty, value);
+ return true;
+}
+
+int32_t
+CSSParserImpl::GetNamespaceIdForPrefix(const nsString& aPrefix)
+{
+ NS_PRECONDITION(!aPrefix.IsEmpty(), "Must have a prefix here");
+
+ int32_t nameSpaceID = kNameSpaceID_Unknown;
+ if (mNameSpaceMap) {
+ // user-specified identifiers are case-sensitive (bug 416106)
+ nsCOMPtr<nsIAtom> prefix = NS_Atomize(aPrefix);
+ nameSpaceID = mNameSpaceMap->FindNameSpaceID(prefix);
+ }
+ // else no declared namespaces
+
+ if (nameSpaceID == kNameSpaceID_Unknown) { // unknown prefix, dump it
+ REPORT_UNEXPECTED_P(PEUnknownNamespacePrefix, aPrefix);
+ }
+
+ return nameSpaceID;
+}
+
+void
+CSSParserImpl::SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector)
+{
+ if (mNameSpaceMap) {
+ aSelector.SetNameSpace(mNameSpaceMap->FindNameSpaceID(nullptr));
+ } else {
+ aSelector.SetNameSpace(kNameSpaceID_Unknown); // wildcard
+ }
+}
+
+bool
+CSSParserImpl::ParsePaint(nsCSSPropertyID aPropID)
+{
+ nsCSSValue x, y;
+
+ if (ParseVariant(x, VARIANT_HC | VARIANT_NONE | VARIANT_URL |
+ VARIANT_OPENTYPE_SVG_KEYWORD,
+ nsCSSProps::kContextPatternKTable) != CSSParseResult::Ok) {
+ return false;
+ }
+
+ bool canHaveFallback = x.GetUnit() == eCSSUnit_URL ||
+ x.GetUnit() == eCSSUnit_Enumerated;
+ if (canHaveFallback) {
+ CSSParseResult result =
+ ParseVariant(y, VARIANT_COLOR | VARIANT_NONE, nullptr);
+ if (result == CSSParseResult::Error) {
+ return false;
+ } else if (result == CSSParseResult::NotFound) {
+ y.SetNoneValue();
+ }
+ }
+
+ if (!canHaveFallback) {
+ AppendValue(aPropID, x);
+ } else {
+ nsCSSValue val;
+ val.SetPairValue(x, y);
+ AppendValue(aPropID, val);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseDasharray()
+{
+ nsCSSValue value;
+
+ // 'inherit', 'initial', 'unset' and 'none' are only allowed on their own
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE |
+ VARIANT_OPENTYPE_SVG_KEYWORD,
+ nsCSSProps::kStrokeContextValueKTable)) {
+ nsCSSValueList *cur = value.SetListValue();
+ for (;;) {
+ if (!ParseSingleTokenNonNegativeVariant(cur->mValue, VARIANT_LPN,
+ nullptr)) {
+ return false;
+ }
+ if (CheckEndProperty()) {
+ break;
+ }
+ // skip optional commas between elements
+ (void)ExpectSymbol(',', true);
+
+ cur->mNext = new nsCSSValueList;
+ cur = cur->mNext;
+ }
+ }
+ AppendValue(eCSSProperty_stroke_dasharray, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseMarker()
+{
+ nsCSSValue marker;
+ if (ParseSingleValueProperty(marker, eCSSProperty_marker_end) ==
+ CSSParseResult::Ok) {
+ AppendValue(eCSSProperty_marker_end, marker);
+ AppendValue(eCSSProperty_marker_mid, marker);
+ AppendValue(eCSSProperty_marker_start, marker);
+ return true;
+ }
+ return false;
+}
+
+bool
+CSSParserImpl::ParsePaintOrder()
+{
+ static_assert
+ ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) > NS_STYLE_PAINT_ORDER_LAST_VALUE,
+ "bitfield width insufficient for paint-order constants");
+
+ static const KTableEntry kPaintOrderKTable[] = {
+ { eCSSKeyword_normal, NS_STYLE_PAINT_ORDER_NORMAL },
+ { eCSSKeyword_fill, NS_STYLE_PAINT_ORDER_FILL },
+ { eCSSKeyword_stroke, NS_STYLE_PAINT_ORDER_STROKE },
+ { eCSSKeyword_markers, NS_STYLE_PAINT_ORDER_MARKERS },
+ { eCSSKeyword_UNKNOWN, -1 }
+ };
+
+ static_assert(MOZ_ARRAY_LENGTH(kPaintOrderKTable) ==
+ NS_STYLE_PAINT_ORDER_LAST_VALUE + 2,
+ "missing paint-order values in kPaintOrderKTable");
+
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_HK, kPaintOrderKTable)) {
+ return false;
+ }
+
+ uint32_t seen = 0;
+ uint32_t order = 0;
+ uint32_t position = 0;
+
+ // Ensure that even cast to a signed int32_t when stored in CSSValue,
+ // we have enough space for the entire paint-order value.
+ static_assert
+ (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE < 32,
+ "seen and order not big enough");
+
+ if (value.GetUnit() == eCSSUnit_Enumerated) {
+ uint32_t component = static_cast<uint32_t>(value.GetIntValue());
+ if (component != NS_STYLE_PAINT_ORDER_NORMAL) {
+ bool parsedOK = true;
+ for (;;) {
+ if (seen & (1 << component)) {
+ // Already seen this component.
+ UngetToken();
+ parsedOK = false;
+ break;
+ }
+ seen |= (1 << component);
+ order |= (component << position);
+ position += NS_STYLE_PAINT_ORDER_BITWIDTH;
+ if (!ParseEnum(value, kPaintOrderKTable)) {
+ break;
+ }
+ component = value.GetIntValue();
+ if (component == NS_STYLE_PAINT_ORDER_NORMAL) {
+ // Can't have "normal" in the middle of the list of paint components.
+ UngetToken();
+ parsedOK = false;
+ break;
+ }
+ }
+
+ // Fill in the remaining paint-order components in the order of their
+ // constant values.
+ if (parsedOK) {
+ for (component = 1;
+ component <= NS_STYLE_PAINT_ORDER_LAST_VALUE;
+ component++) {
+ if (!(seen & (1 << component))) {
+ order |= (component << position);
+ position += NS_STYLE_PAINT_ORDER_BITWIDTH;
+ }
+ }
+ }
+ }
+
+ static_assert(NS_STYLE_PAINT_ORDER_NORMAL == 0,
+ "unexpected value for NS_STYLE_PAINT_ORDER_NORMAL");
+ value.SetIntValue(static_cast<int32_t>(order), eCSSUnit_Enumerated);
+ }
+
+ AppendValue(eCSSProperty_paint_order, value);
+ return true;
+}
+
+bool
+CSSParserImpl::BackslashDropped()
+{
+ return mScanner->GetEOFCharacters() &
+ nsCSSScanner::eEOFCharacters_DropBackslash;
+}
+
+void
+CSSParserImpl::AppendImpliedEOFCharacters(nsAString& aResult)
+{
+ nsCSSScanner::AppendImpliedEOFCharacters(mScanner->GetEOFCharacters(),
+ aResult);
+}
+
+bool
+CSSParserImpl::ParseAll()
+{
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
+ return false;
+ }
+
+ // It's unlikely we'll want to use 'all' from within a UA style sheet, so
+ // instead of computing the correct EnabledState value we just expand out
+ // to all content-visible properties.
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, eCSSProperty_all,
+ CSSEnabledState::eForAllContent) {
+ AppendValue(*p, value);
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
+ nsString& aValue)
+{
+ CSSVariableDeclarations::Type type;
+ nsString variableValue;
+ bool dropBackslash;
+ nsString impliedCharacters;
+
+ // Record the token stream while parsing a variable value.
+ if (!mInSupportsCondition) {
+ mScanner->StartRecording();
+ }
+ if (!ParseValueWithVariables(&type, &dropBackslash, impliedCharacters,
+ nullptr, nullptr)) {
+ if (!mInSupportsCondition) {
+ mScanner->StopRecording();
+ }
+ return false;
+ }
+
+ if (!mInSupportsCondition) {
+ if (type == CSSVariableDeclarations::eTokenStream) {
+ // This was indeed a token stream value, so store it in variableValue.
+ mScanner->StopRecording(variableValue);
+ if (dropBackslash) {
+ MOZ_ASSERT(!variableValue.IsEmpty() &&
+ variableValue[variableValue.Length() - 1] == '\\');
+ variableValue.Truncate(variableValue.Length() - 1);
+ }
+ variableValue.Append(impliedCharacters);
+ } else {
+ // This was either 'inherit' or 'initial'; we don't need the recorded
+ // input.
+ mScanner->StopRecording();
+ }
+ }
+
+ if (mHavePushBack && type == CSSVariableDeclarations::eTokenStream) {
+ // If we came to the end of a valid variable declaration and a token was
+ // pushed back, then it would have been ended by '!', ')', ';', ']' or '}'.
+ // We need to remove it from the recorded variable value.
+ MOZ_ASSERT(mToken.IsSymbol('!') ||
+ mToken.IsSymbol(')') ||
+ mToken.IsSymbol(';') ||
+ mToken.IsSymbol(']') ||
+ mToken.IsSymbol('}'));
+ if (!mInSupportsCondition) {
+ MOZ_ASSERT(!variableValue.IsEmpty());
+ MOZ_ASSERT(variableValue[variableValue.Length() - 1] == mToken.mSymbol);
+ variableValue.Truncate(variableValue.Length() - 1);
+ }
+ }
+
+ *aType = type;
+ aValue = variableValue;
+ return true;
+}
+
+bool
+CSSParserImpl::ParseScrollSnapType()
+{
+ nsCSSValue value;
+ if (!ParseSingleTokenVariant(value, VARIANT_HK,
+ nsCSSProps::kScrollSnapTypeKTable)) {
+ return false;
+ }
+ AppendValue(eCSSProperty_scroll_snap_type_x, value);
+ AppendValue(eCSSProperty_scroll_snap_type_y, value);
+ return true;
+}
+
+bool
+CSSParserImpl::ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
+ nullptr)) {
+ return true;
+ }
+ if (!GetToken(true)) {
+ return false;
+ }
+ if (mToken.mType == eCSSToken_Function &&
+ nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_repeat) {
+ nsCSSValue lengthValue;
+ if (ParseNonNegativeVariant(lengthValue,
+ VARIANT_LENGTH | VARIANT_PERCENT | VARIANT_CALC,
+ nullptr) != CSSParseResult::Ok) {
+ REPORT_UNEXPECTED(PEExpectedNonnegativeNP);
+ SkipUntil(')');
+ return false;
+ }
+ if (!ExpectSymbol(')', true)) {
+ REPORT_UNEXPECTED(PEExpectedCloseParen);
+ SkipUntil(')');
+ return false;
+ }
+ RefPtr<nsCSSValue::Array> functionArray =
+ aValue.InitFunction(eCSSKeyword_repeat, 1);
+ functionArray->Item(1) = lengthValue;
+ return true;
+ }
+ UngetToken();
+ return false;
+}
+
+
+bool
+CSSParserImpl::ParseScrollSnapDestination(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
+ return true;
+ }
+ nsCSSValue itemValue;
+ if (!ParsePositionValue(aValue)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
+ return false;
+ }
+ return true;
+}
+
+// This function is very similar to ParseImageLayerPosition, and ParseImageLayerSize.
+bool
+CSSParserImpl::ParseScrollSnapCoordinate(nsCSSValue& aValue)
+{
+ if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
+ nullptr)) {
+ return true;
+ }
+ nsCSSValue itemValue;
+ if (!ParsePositionValue(itemValue)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
+ return false;
+ }
+ nsCSSValueList* item = aValue.SetListValue();
+ for (;;) {
+ item->mValue = itemValue;
+ if (!ExpectSymbol(',', true)) {
+ break;
+ }
+ if (!ParsePositionValue(itemValue)) {
+ REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
+ return false;
+ }
+ item->mNext = new nsCSSValueList;
+ item = item->mNext;
+ }
+ return true;
+}
+
+bool
+CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
+ bool* aDropBackslash,
+ nsString& aImpliedCharacters,
+ void (*aFunc)(const nsAString&, void*),
+ void* aData)
+{
+ // A property value is invalid if it contains variable references and also:
+ //
+ // * has unbalanced parens, brackets or braces
+ // * has any BAD_STRING or BAD_URL tokens
+ // * has any ';' or '!' tokens at the top level of a variable reference's
+ // fallback
+ //
+ // If the property is a custom property (i.e. a variable declaration), then
+ // it is also invalid if it consists of no tokens, such as:
+ //
+ // --invalid:;
+ //
+ // Note that is valid for a custom property to have a value that consists
+ // solely of white space, such as:
+ //
+ // --valid: ;
+
+ // Stack of closing characters for currently open constructs.
+ StopSymbolCharStack stack;
+
+ // Indexes into ')' characters in |stack| that correspond to "var(". This
+ // is used to stop parsing when we encounter a '!' or ';' at the top level
+ // of a variable reference's fallback.
+ AutoTArray<uint32_t, 16> references;
+
+ if (!GetToken(false)) {
+ // Variable value was empty since we reached EOF.
+ REPORT_UNEXPECTED_EOF(PEVariableEOF);
+ return false;
+ }
+
+ if (mToken.mType == eCSSToken_Symbol &&
+ (mToken.mSymbol == '!' ||
+ mToken.mSymbol == ')' ||
+ mToken.mSymbol == ';' ||
+ mToken.mSymbol == ']' ||
+ mToken.mSymbol == '}')) {
+ // Variable value was empty since we reached the end of the construct.
+ UngetToken();
+ REPORT_UNEXPECTED_TOKEN(PEVariableEmpty);
+ return false;
+ }
+
+ if (mToken.mType == eCSSToken_Whitespace) {
+ if (!GetToken(true)) {
+ // Variable value was white space only. This is valid.
+ MOZ_ASSERT(!BackslashDropped());
+ *aType = CSSVariableDeclarations::eTokenStream;
+ *aDropBackslash = false;
+ AppendImpliedEOFCharacters(aImpliedCharacters);
+ return true;
+ }
+ }
+
+ // Look for 'initial', 'inherit' or 'unset' as the first non-white space
+ // token.
+ CSSVariableDeclarations::Type type = CSSVariableDeclarations::eTokenStream;
+ if (mToken.mType == eCSSToken_Ident) {
+ if (mToken.mIdent.LowerCaseEqualsLiteral("initial")) {
+ type = CSSVariableDeclarations::eInitial;
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("inherit")) {
+ type = CSSVariableDeclarations::eInherit;
+ } else if (mToken.mIdent.LowerCaseEqualsLiteral("unset")) {
+ type = CSSVariableDeclarations::eUnset;
+ }
+ }
+
+ if (type != CSSVariableDeclarations::eTokenStream) {
+ if (!GetToken(true)) {
+ // Variable value was 'initial' or 'inherit' followed by EOF.
+ MOZ_ASSERT(!BackslashDropped());
+ *aType = type;
+ *aDropBackslash = false;
+ AppendImpliedEOFCharacters(aImpliedCharacters);
+ return true;
+ }
+ UngetToken();
+ if (mToken.mType == eCSSToken_Symbol &&
+ (mToken.mSymbol == '!' ||
+ mToken.mSymbol == ')' ||
+ mToken.mSymbol == ';' ||
+ mToken.mSymbol == ']' ||
+ mToken.mSymbol == '}')) {
+ // Variable value was 'initial' or 'inherit' followed by the end
+ // of the declaration.
+ MOZ_ASSERT(!BackslashDropped());
+ *aType = type;
+ *aDropBackslash = false;
+ return true;
+ }
+ }
+
+ do {
+ switch (mToken.mType) {
+ case eCSSToken_Symbol:
+ if (mToken.mSymbol == '(') {
+ stack.AppendElement(')');
+ } else if (mToken.mSymbol == '[') {
+ stack.AppendElement(']');
+ } else if (mToken.mSymbol == '{') {
+ stack.AppendElement('}');
+ } else if (mToken.mSymbol == ';' ||
+ mToken.mSymbol == '!') {
+ if (stack.IsEmpty()) {
+ UngetToken();
+ MOZ_ASSERT(!BackslashDropped());
+ *aType = CSSVariableDeclarations::eTokenStream;
+ *aDropBackslash = false;
+ return true;
+ } else if (!references.IsEmpty() &&
+ references.LastElement() == stack.Length() - 1) {
+ REPORT_UNEXPECTED_TOKEN(PEInvalidVariableTokenFallback);
+ SkipUntilAllOf(stack);
+ return false;
+ }
+ } else if (mToken.mSymbol == ')' ||
+ mToken.mSymbol == ']' ||
+ mToken.mSymbol == '}') {
+ for (;;) {
+ if (stack.IsEmpty()) {
+ UngetToken();
+ MOZ_ASSERT(!BackslashDropped());
+ *aType = CSSVariableDeclarations::eTokenStream;
+ *aDropBackslash = false;
+ return true;
+ }
+ char16_t c = stack.LastElement();
+ stack.TruncateLength(stack.Length() - 1);
+ if (!references.IsEmpty() &&
+ references.LastElement() == stack.Length()) {
+ references.TruncateLength(references.Length() - 1);
+ }
+ if (mToken.mSymbol == c) {
+ break;
+ }
+ }
+ }
+ break;
+
+ case eCSSToken_Function:
+ if (mToken.mIdent.LowerCaseEqualsLiteral("var")) {
+ if (!GetToken(true)) {
+ // EOF directly after "var(".
+ REPORT_UNEXPECTED_EOF(PEExpectedVariableNameEOF);
+ return false;
+ }
+ if (mToken.mType != eCSSToken_Ident ||
+ !nsCSSProps::IsCustomPropertyName(mToken.mIdent)) {
+ // There must be an identifier directly after the "var(" and
+ // it must be a custom property name.
+ UngetToken();
+ REPORT_UNEXPECTED_TOKEN(PEExpectedVariableName);
+ SkipUntil(')');
+ SkipUntilAllOf(stack);
+ return false;
+ }
+ if (aFunc) {
+ MOZ_ASSERT(Substring(mToken.mIdent, 0,
+ CSS_CUSTOM_NAME_PREFIX_LENGTH).
+ EqualsLiteral("--"));
+ // remove '--'
+ const nsDependentSubstring varName =
+ Substring(mToken.mIdent, CSS_CUSTOM_NAME_PREFIX_LENGTH);
+ aFunc(varName, aData);
+ }
+ if (!GetToken(true)) {
+ // EOF right after "var(<ident>".
+ stack.AppendElement(')');
+ } else if (mToken.IsSymbol(',')) {
+ // Variable reference with fallback.
+ if (!GetToken(false) || mToken.IsSymbol(')')) {
+ // Comma must be followed by at least one fallback token.
+ REPORT_UNEXPECTED(PEExpectedVariableFallback);
+ SkipUntilAllOf(stack);
+ return false;
+ }
+ UngetToken();
+ references.AppendElement(stack.Length());
+ stack.AppendElement(')');
+ } else if (mToken.IsSymbol(')')) {
+ // Correctly closed variable reference.
+ } else {
+ // Malformed variable reference.
+ REPORT_UNEXPECTED_TOKEN(PEExpectedVariableCommaOrCloseParen);
+ SkipUntil(')');
+ SkipUntilAllOf(stack);
+ return false;
+ }
+ } else {
+ stack.AppendElement(')');
+ }
+ break;
+
+ case eCSSToken_Bad_String:
+ SkipUntilAllOf(stack);
+ return false;
+
+ case eCSSToken_Bad_URL:
+ SkipUntil(')');
+ SkipUntilAllOf(stack);
+ return false;
+
+ default:
+ break;
+ }
+ } while (GetToken(true));
+
+ // Append any implied closing characters.
+ *aDropBackslash = BackslashDropped();
+ AppendImpliedEOFCharacters(aImpliedCharacters);
+ uint32_t i = stack.Length();
+ while (i--) {
+ aImpliedCharacters.Append(stack[i]);
+ }
+
+ *aType = type;
+ return true;
+}
+
+bool
+CSSParserImpl::IsValueValidForProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue)
+{
+ mData.AssertInitialState();
+ mTempData.AssertInitialState();
+
+ nsCSSScanner scanner(aPropValue, 0);
+ css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
+ InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
+
+ // We normally would need to pass in a sheet principal to InitScanner,
+ // because we might parse a URL value. However, we will never use the
+ // parsed nsCSSValue (and so whether we have a sheet principal or not
+ // doesn't really matter), so to avoid failing the assertion in
+ // SetValueToURL, we set mSheetPrincipalRequired to false to declare
+ // that it's safe to skip the assertion.
+ AutoRestore<bool> autoRestore(mSheetPrincipalRequired);
+ mSheetPrincipalRequired = false;
+
+ nsAutoSuppressErrors suppressErrors(this);
+
+ mSection = eCSSSection_General;
+ scanner.SetSVGMode(false);
+
+ // Check for unknown properties
+ if (eCSSProperty_UNKNOWN == aPropID) {
+ ReleaseScanner();
+ return false;
+ }
+
+ // Check that the property and value parse successfully
+ bool parsedOK = ParseProperty(aPropID);
+
+ // Check for priority
+ parsedOK = parsedOK && ParsePriority() != ePriority_Error;
+
+ // We should now be at EOF
+ parsedOK = parsedOK && !GetToken(true);
+
+ mTempData.ClearProperty(aPropID);
+ mTempData.AssertInitialState();
+ mData.AssertInitialState();
+
+ CLEAR_ERROR();
+ ReleaseScanner();
+
+ return parsedOK;
+}
+
+} // namespace
+
+// Recycling of parser implementation objects
+
+static CSSParserImpl* gFreeList = nullptr;
+
+/* static */ void
+nsCSSParser::Startup()
+{
+ Preferences::AddBoolVarCache(&sOpentypeSVGEnabled,
+ "gfx.font_rendering.opentype_svg.enabled");
+ Preferences::AddBoolVarCache(&sWebkitPrefixedAliasesEnabled,
+ "layout.css.prefixes.webkit");
+ Preferences::AddBoolVarCache(&sWebkitDevicePixelRatioEnabled,
+ "layout.css.prefixes.device-pixel-ratio-webkit");
+ Preferences::AddBoolVarCache(&sUnprefixingServiceEnabled,
+ "layout.css.unprefixing-service.enabled");
+#ifdef NIGHTLY_BUILD
+ Preferences::AddBoolVarCache(&sUnprefixingServiceGloballyWhitelisted,
+ "layout.css.unprefixing-service.globally-whitelisted");
+#endif
+ Preferences::AddBoolVarCache(&sMozGradientsEnabled,
+ "layout.css.prefixes.gradients");
+ Preferences::AddBoolVarCache(&sControlCharVisibility,
+ "layout.css.control-characters.visible");
+}
+
+nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader,
+ CSSStyleSheet* aSheet)
+{
+ CSSParserImpl *impl = gFreeList;
+ if (impl) {
+ gFreeList = impl->mNextFree;
+ impl->mNextFree = nullptr;
+ } else {
+ impl = new CSSParserImpl();
+ }
+
+ if (aLoader) {
+ impl->SetChildLoader(aLoader);
+ impl->SetQuirkMode(aLoader->GetCompatibilityMode() ==
+ eCompatibility_NavQuirks);
+ }
+ if (aSheet) {
+ impl->SetStyleSheet(aSheet);
+ }
+
+ mImpl = static_cast<void*>(impl);
+}
+
+nsCSSParser::~nsCSSParser()
+{
+ CSSParserImpl *impl = static_cast<CSSParserImpl*>(mImpl);
+ impl->Reset();
+ impl->mNextFree = gFreeList;
+ gFreeList = impl;
+}
+
+/* static */ void
+nsCSSParser::Shutdown()
+{
+ CSSParserImpl *tofree = gFreeList;
+ CSSParserImpl *next;
+ while (tofree)
+ {
+ next = tofree->mNextFree;
+ delete tofree;
+ tofree = next;
+ }
+}
+
+// Wrapper methods
+
+nsresult
+nsCSSParser::ParseSheet(const nsAString& aInput,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ uint32_t aLineNumber,
+ css::LoaderReusableStyleSheets* aReusableSheets)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseSheet(aInput, aSheetURI, aBaseURI, aSheetPrincipal, aLineNumber,
+ aReusableSheets);
+}
+
+already_AddRefed<css::Declaration>
+nsCSSParser::ParseStyleAttribute(const nsAString& aAttributeValue,
+ nsIURI* aDocURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aNodePrincipal)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseStyleAttribute(aAttributeValue, aDocURI, aBaseURI, aNodePrincipal);
+}
+
+nsresult
+nsCSSParser::ParseDeclarations(const nsAString& aBuffer,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ css::Declaration* aDeclaration,
+ bool* aChanged)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseDeclarations(aBuffer, aSheetURI, aBaseURI, aSheetPrincipal,
+ aDeclaration, aChanged);
+}
+
+nsresult
+nsCSSParser::ParseRule(const nsAString& aRule,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ css::Rule** aResult)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseRule(aRule, aSheetURI, aBaseURI, aSheetPrincipal, aResult);
+}
+
+void
+nsCSSParser::ParseProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ css::Declaration* aDeclaration,
+ bool* aChanged,
+ bool aIsImportant,
+ bool aIsSVGMode)
+{
+ static_cast<CSSParserImpl*>(mImpl)->
+ ParseProperty(aPropID, aPropValue, aSheetURI, aBaseURI,
+ aSheetPrincipal, aDeclaration, aChanged,
+ aIsImportant, aIsSVGMode);
+}
+
+void
+nsCSSParser::ParseLonghandProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aResult)
+{
+ static_cast<CSSParserImpl*>(mImpl)->
+ ParseLonghandProperty(aPropID, aPropValue, aSheetURI, aBaseURI,
+ aSheetPrincipal, aResult);
+}
+
+bool
+nsCSSParser::ParseTransformProperty(const nsAString& aPropValue,
+ bool aDisallowRelativeValues,
+ nsCSSValue& aResult)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseTransformProperty(aPropValue, aDisallowRelativeValues, aResult);
+}
+
+void
+nsCSSParser::ParseVariable(const nsAString& aVariableName,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ css::Declaration* aDeclaration,
+ bool* aChanged,
+ bool aIsImportant)
+{
+ static_cast<CSSParserImpl*>(mImpl)->
+ ParseVariable(aVariableName, aPropValue, aSheetURI, aBaseURI,
+ aSheetPrincipal, aDeclaration, aChanged, aIsImportant);
+}
+
+void
+nsCSSParser::ParseMediaList(const nsSubstring& aBuffer,
+ nsIURI* aURI,
+ uint32_t aLineNumber,
+ nsMediaList* aMediaList,
+ bool aHTMLMode)
+{
+ static_cast<CSSParserImpl*>(mImpl)->
+ ParseMediaList(aBuffer, aURI, aLineNumber, aMediaList, aHTMLMode);
+}
+
+bool
+nsCSSParser::ParseSourceSizeList(const nsAString& aBuffer,
+ nsIURI* aURI,
+ uint32_t aLineNumber,
+ InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
+ InfallibleTArray<nsCSSValue>& aValues,
+ bool aHTMLMode)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseSourceSizeList(aBuffer, aURI, aLineNumber, aQueries, aValues,
+ aHTMLMode);
+}
+
+bool
+nsCSSParser::ParseFontFamilyListString(const nsSubstring& aBuffer,
+ nsIURI* aURI,
+ uint32_t aLineNumber,
+ nsCSSValue& aValue)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseFontFamilyListString(aBuffer, aURI, aLineNumber, aValue);
+}
+
+bool
+nsCSSParser::ParseColorString(const nsSubstring& aBuffer,
+ nsIURI* aURI,
+ uint32_t aLineNumber,
+ nsCSSValue& aValue,
+ bool aSuppressErrors /* false */)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseColorString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
+}
+
+bool
+nsCSSParser::ParseMarginString(const nsSubstring& aBuffer,
+ nsIURI* aURI,
+ uint32_t aLineNumber,
+ nsCSSValue& aValue,
+ bool aSuppressErrors /* false */)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseMarginString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
+}
+
+nsresult
+nsCSSParser::ParseSelectorString(const nsSubstring& aSelectorString,
+ nsIURI* aURI,
+ uint32_t aLineNumber,
+ nsCSSSelectorList** aSelectorList)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseSelectorString(aSelectorString, aURI, aLineNumber, aSelectorList);
+}
+
+already_AddRefed<nsCSSKeyframeRule>
+nsCSSParser::ParseKeyframeRule(const nsSubstring& aBuffer,
+ nsIURI* aURI,
+ uint32_t aLineNumber)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseKeyframeRule(aBuffer, aURI, aLineNumber);
+}
+
+bool
+nsCSSParser::ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
+ nsIURI* aURI,
+ uint32_t aLineNumber,
+ InfallibleTArray<float>& aSelectorList)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseKeyframeSelectorString(aSelectorString, aURI, aLineNumber,
+ aSelectorList);
+}
+
+bool
+nsCSSParser::EvaluateSupportsDeclaration(const nsAString& aProperty,
+ const nsAString& aValue,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ EvaluateSupportsDeclaration(aProperty, aValue, aDocURL, aBaseURL,
+ aDocPrincipal);
+}
+
+bool
+nsCSSParser::EvaluateSupportsCondition(const nsAString& aCondition,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ EvaluateSupportsCondition(aCondition, aDocURL, aBaseURL, aDocPrincipal);
+}
+
+bool
+nsCSSParser::EnumerateVariableReferences(const nsAString& aPropertyValue,
+ VariableEnumFunc aFunc,
+ void* aData)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ EnumerateVariableReferences(aPropertyValue, aFunc, aData);
+}
+
+bool
+nsCSSParser::ResolveVariableValue(const nsAString& aPropertyValue,
+ const CSSVariableValues* aVariables,
+ nsString& aResult,
+ nsCSSTokenSerializationType& aFirstToken,
+ nsCSSTokenSerializationType& aLastToken)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ResolveVariableValue(aPropertyValue, aVariables,
+ aResult, aFirstToken, aLastToken);
+}
+
+void
+nsCSSParser::ParsePropertyWithVariableReferences(
+ nsCSSPropertyID aPropertyID,
+ nsCSSPropertyID aShorthandPropertyID,
+ const nsAString& aValue,
+ const CSSVariableValues* aVariables,
+ nsRuleData* aRuleData,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal,
+ CSSStyleSheet* aSheet,
+ uint32_t aLineNumber,
+ uint32_t aLineOffset)
+{
+ static_cast<CSSParserImpl*>(mImpl)->
+ ParsePropertyWithVariableReferences(aPropertyID, aShorthandPropertyID,
+ aValue, aVariables, aRuleData, aDocURL,
+ aBaseURL, aDocPrincipal, aSheet,
+ aLineNumber, aLineOffset);
+}
+
+bool
+nsCSSParser::ParseCounterStyleName(const nsAString& aBuffer,
+ nsIURI* aURL,
+ nsAString& aName)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseCounterStyleName(aBuffer, aURL, aName);
+}
+
+bool
+nsCSSParser::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
+ const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseCounterDescriptor(aDescID, aBuffer,
+ aSheetURL, aBaseURL, aSheetPrincipal, aValue);
+}
+
+bool
+nsCSSParser::ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
+ const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ ParseFontFaceDescriptor(aDescID, aBuffer,
+ aSheetURL, aBaseURL, aSheetPrincipal, aValue);
+}
+
+bool
+nsCSSParser::IsValueValidForProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue)
+{
+ return static_cast<CSSParserImpl*>(mImpl)->
+ IsValueValidForProperty(aPropID, aPropValue);
+}
+
+/* static */
+uint8_t
+nsCSSParser::ControlCharVisibilityDefault()
+{
+ return sControlCharVisibility
+ ? NS_STYLE_CONTROL_CHARACTER_VISIBILITY_VISIBLE
+ : NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN;
+}
diff --git a/layout/style/nsCSSParser.h b/layout/style/nsCSSParser.h
new file mode 100644
index 000000000..37cd325f2
--- /dev/null
+++ b/layout/style/nsCSSParser.h
@@ -0,0 +1,346 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* parsing of CSS stylesheets, based on a token stream from the CSS scanner */
+
+#ifndef nsCSSParser_h___
+#define nsCSSParser_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/css/Loader.h"
+
+#include "nsCSSPropertyID.h"
+#include "nsCSSScanner.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsIPrincipal;
+class nsIURI;
+struct nsCSSSelectorList;
+class nsMediaList;
+class nsMediaQuery;
+class nsCSSKeyframeRule;
+class nsCSSValue;
+struct nsRuleData;
+
+namespace mozilla {
+class CSSStyleSheet;
+class CSSVariableValues;
+namespace css {
+class Rule;
+class Declaration;
+class StyleRule;
+} // namespace css
+} // namespace mozilla
+
+// Interface to the css parser.
+
+class MOZ_STACK_CLASS nsCSSParser {
+public:
+ explicit nsCSSParser(mozilla::css::Loader* aLoader = nullptr,
+ mozilla::CSSStyleSheet* aSheet = nullptr);
+ ~nsCSSParser();
+
+ static void Startup();
+ static void Shutdown();
+
+private:
+ nsCSSParser(nsCSSParser const&) = delete;
+ nsCSSParser& operator=(nsCSSParser const&) = delete;
+
+public:
+ /**
+ * Parse aInput into the stylesheet that was previously passed to the
+ * constructor. Calling this method on an nsCSSParser that had nullptr
+ * passed in as the style sheet is an error.
+ *
+ * @param aInput the data to parse
+ * @param aSheetURL the URI to use as the sheet URI (for error reporting).
+ * This must match the URI of the sheet passed to
+ * the constructor.
+ * @param aBaseURI the URI to use for relative URI resolution
+ * @param aSheetPrincipal the principal of the stylesheet. This must match
+ * the principal of the sheet passed to the
+ * constructor.
+ * @param aLineNumber the line number of the first line of the sheet.
+ * @param aReusableSheets style sheets that can be reused by an @import.
+ * This can be nullptr.
+ */
+ nsresult ParseSheet(const nsAString& aInput,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aSheetPrincipal,
+ uint32_t aLineNumber,
+ mozilla::css::LoaderReusableStyleSheets* aReusableSheets =
+ nullptr);
+
+ // Parse HTML style attribute or its equivalent in other markup
+ // languages. aBaseURL is the base url to use for relative links in
+ // the declaration.
+ already_AddRefed<mozilla::css::Declaration>
+ ParseStyleAttribute(const nsAString& aAttributeValue,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aNodePrincipal);
+
+ // Parse the body of a declaration block. Very similar to
+ // ParseStyleAttribute, but used under different circumstances.
+ // The contents of aDeclaration will be erased and replaced with the
+ // results of parsing; aChanged will be set true if the aDeclaration
+ // argument was modified.
+ nsresult ParseDeclarations(const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ mozilla::css::Declaration* aDeclaration,
+ bool* aChanged);
+
+ nsresult ParseRule(const nsAString& aRule,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ mozilla::css::Rule** aResult);
+
+ // Parse the value of a single CSS property, and add or replace that
+ // property in aDeclaration.
+ //
+ // SVG "mapped attributes" (which correspond directly to CSS
+ // properties) are parsed slightly differently from regular CSS; in
+ // particular, units may be omitted from <length>. The 'aIsSVGMode'
+ // argument controls this quirk. Note that this *only* applies to
+ // mapped attributes, not inline styles or full style sheets in SVG.
+ void ParseProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ mozilla::css::Declaration* aDeclaration,
+ bool* aChanged,
+ bool aIsImportant,
+ bool aIsSVGMode = false);
+
+ // Same as ParseProperty but returns an nsCSSValue in aResult
+ // rather than storing the property in a Declaration. aPropID
+ // must be a longhand property.
+ void ParseLonghandProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aResult);
+
+ // Parse the value of a CSS transform property. Returns
+ // whether the value was successfully parsed. If
+ // aDisallowRelativeValues is true then this method will
+ // only successfully parse if all values are numbers or
+ // have non-relative dimensions.
+ bool ParseTransformProperty(const nsAString& aPropValue,
+ bool aDisallowRelativeValues,
+ nsCSSValue& aResult);
+
+ // The same as ParseProperty but for a variable.
+ void ParseVariable(const nsAString& aVariableName,
+ const nsAString& aPropValue,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ mozilla::css::Declaration* aDeclaration,
+ bool* aChanged,
+ bool aIsImportant);
+ /**
+ * Parse aBuffer into a media list |aMediaList|, which must be
+ * non-null, replacing its current contents. If aHTMLMode is true,
+ * parse according to HTML rules, with commas as the most important
+ * delimiter. Otherwise, parse according to CSS rules, with
+ * parentheses and strings more important than commas. |aURL| and
+ * |aLineNumber| are used for error reporting.
+ */
+ void ParseMediaList(const nsSubstring& aBuffer,
+ nsIURI* aURL,
+ uint32_t aLineNumber,
+ nsMediaList* aMediaList,
+ bool aHTMLMode);
+
+ /*
+ * Parse aBuffer into a list of media queries and their associated values,
+ * according to grammar:
+ * <source-size-list> = <source-size>#?
+ * <source-size> = <media-condition>? <length>
+ *
+ * Note that this grammar is top-level: The function expects to consume the
+ * entire input buffer.
+ *
+ * Output arrays overwritten (not appended) and are cleared in case of parse
+ * failure.
+ */
+ bool ParseSourceSizeList(const nsAString& aBuffer,
+ nsIURI* aURI, // for error reporting
+ uint32_t aLineNumber, // for error reporting
+ InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
+ InfallibleTArray<nsCSSValue>& aValues,
+ bool aHTMLMode);
+
+ /**
+ * Parse aBuffer into a nsCSSValue |aValue|. Will return false
+ * if aBuffer is not a valid font family list.
+ */
+ bool ParseFontFamilyListString(const nsSubstring& aBuffer,
+ nsIURI* aURL,
+ uint32_t aLineNumber,
+ nsCSSValue& aValue);
+
+ /**
+ * Parse aBuffer into a nsCSSValue |aValue|. Will return false
+ * if aBuffer is not a valid CSS color specification.
+ * One can use nsRuleNode::ComputeColor to compute an nscolor from
+ * the returned nsCSSValue.
+ */
+ bool ParseColorString(const nsSubstring& aBuffer,
+ nsIURI* aURL,
+ uint32_t aLineNumber,
+ nsCSSValue& aValue,
+ bool aSuppressErrors = false);
+
+ /**
+ * Parse aBuffer into a nsCSSValue |aValue|. Will return false
+ * if aBuffer is not a valid CSS margin specification.
+ * One can use nsRuleNode::GetRectValue to compute an nsCSSRect from
+ * the returned nsCSSValue.
+ */
+ bool ParseMarginString(const nsSubstring& aBuffer,
+ nsIURI* aURL,
+ uint32_t aLineNumber,
+ nsCSSValue& aValue,
+ bool aSuppressErrors = false);
+
+ /**
+ * Parse aBuffer into a selector list. On success, caller must
+ * delete *aSelectorList when done with it.
+ */
+ nsresult ParseSelectorString(const nsSubstring& aSelectorString,
+ nsIURI* aURL,
+ uint32_t aLineNumber,
+ nsCSSSelectorList** aSelectorList);
+
+ /*
+ * Parse a keyframe rule (which goes inside an @keyframes rule).
+ * Return it if the parse was successful.
+ */
+ already_AddRefed<nsCSSKeyframeRule>
+ ParseKeyframeRule(const nsSubstring& aBuffer,
+ nsIURI* aURL,
+ uint32_t aLineNumber);
+
+ /*
+ * Parse a selector list for a keyframe rule. Return whether
+ * the parse succeeded.
+ */
+ bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
+ nsIURI* aURL,
+ uint32_t aLineNumber,
+ InfallibleTArray<float>& aSelectorList);
+
+ /**
+ * Parse a property and value and return whether the property/value pair
+ * is supported.
+ */
+ bool EvaluateSupportsDeclaration(const nsAString& aProperty,
+ const nsAString& aValue,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal);
+
+ /**
+ * Parse an @supports condition and returns the result of evaluating the
+ * condition.
+ */
+ bool EvaluateSupportsCondition(const nsAString& aCondition,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal);
+
+ typedef void (*VariableEnumFunc)(const nsAString&, void*);
+
+ /**
+ * Parses aPropertyValue as a property value and calls aFunc for each
+ * variable reference that is found. Returns false if there was
+ * a syntax error in the use of variable references.
+ */
+ bool EnumerateVariableReferences(const nsAString& aPropertyValue,
+ VariableEnumFunc aFunc,
+ void* aData);
+
+ /**
+ * Parses aPropertyValue as a property value and resolves variable references
+ * using the values in aVariables.
+ */
+ bool ResolveVariableValue(const nsAString& aPropertyValue,
+ const mozilla::CSSVariableValues* aVariables,
+ nsString& aResult,
+ nsCSSTokenSerializationType& aFirstToken,
+ nsCSSTokenSerializationType& aLastToken);
+
+ /**
+ * Parses a string as a CSS token stream value for particular property,
+ * resolving any variable references. The parsed property value is stored
+ * in the specified nsRuleData object. If aShorthandPropertyID has a value
+ * other than eCSSProperty_UNKNOWN, this is the property that will be parsed;
+ * otherwise, aPropertyID will be parsed. Either way, only aPropertyID,
+ * a longhand property, will be copied over to the rule data.
+ *
+ * If the property cannot be parsed, it will be treated as if 'initial' or
+ * 'inherit' were specified, for non-inherited and inherited properties
+ * respectively.
+ */
+ void ParsePropertyWithVariableReferences(
+ nsCSSPropertyID aPropertyID,
+ nsCSSPropertyID aShorthandPropertyID,
+ const nsAString& aValue,
+ const mozilla::CSSVariableValues* aVariables,
+ nsRuleData* aRuleData,
+ nsIURI* aDocURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aDocPrincipal,
+ mozilla::CSSStyleSheet* aSheet,
+ uint32_t aLineNumber,
+ uint32_t aLineOffset);
+
+ bool ParseCounterStyleName(const nsAString& aBuffer,
+ nsIURI* aURL,
+ nsAString& aName);
+
+ bool ParseCounterDescriptor(nsCSSCounterDesc aDescID,
+ const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue);
+
+ bool ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
+ const nsAString& aBuffer,
+ nsIURI* aSheetURL,
+ nsIURI* aBaseURL,
+ nsIPrincipal* aSheetPrincipal,
+ nsCSSValue& aValue);
+
+ // Check whether a given value can be applied to a property.
+ bool IsValueValidForProperty(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue);
+
+ // Return the default value to be used for -moz-control-character-visibility,
+ // from preferences (cached by our Startup(), so that both nsStyleText and
+ // nsRuleNode can have fast access to it).
+ static uint8_t ControlCharVisibilityDefault();
+
+protected:
+ // This is a CSSParserImpl*, but if we expose that type name in this
+ // header, we can't put the type definition (in nsCSSParser.cpp) in
+ // the anonymous namespace.
+ void* mImpl;
+};
+
+#endif /* nsCSSParser_h___ */
diff --git a/layout/style/nsCSSPropAliasList.h b/layout/style/nsCSSPropAliasList.h
new file mode 100644
index 000000000..2699549ff
--- /dev/null
+++ b/layout/style/nsCSSPropAliasList.h
@@ -0,0 +1,498 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * a list of all CSS property aliases with data about them, for preprocessing
+ */
+
+/******
+
+ This file contains the list of all CSS properties that are just
+ aliases for other properties (e.g., for when we temporarily continue
+ to support a prefixed property after adding support for its unprefixed
+ form). It is designed to be used as inline input through the magic of
+ C preprocessing. All entries must be enclosed in the appropriate
+ CSS_PROP_ALIAS macro which will have cruel and unusual things done to
+ it.
+
+ The arguments to CSS_PROP_ALIAS are:
+
+ -. 'aliasname' entries represent a CSS property name and *must* use
+ only lowercase characters.
+
+ -. 'id' should be the same as the 'id' field in nsCSSPropList.h for
+ the property that 'aliasname' is being aliased to.
+
+ -. 'method' is the CSS2Properties property name. Unlike
+ nsCSSPropList.h, prefixes should just be included in this file (rather
+ than needing the CSS_PROP_DOMPROP_PREFIXED(prop) macro).
+
+ -. 'pref' is the name of a pref that controls whether the property
+ is enabled. The property is enabled if 'pref' is an empty string,
+ or if the boolean property whose name is 'pref' is set to true.
+
+ ******/
+
+CSS_PROP_ALIAS(word-wrap,
+ overflow_wrap,
+ WordWrap,
+ "")
+CSS_PROP_ALIAS(-moz-transform-origin,
+ transform_origin,
+ MozTransformOrigin,
+ "layout.css.prefixes.transforms")
+CSS_PROP_ALIAS(-moz-perspective-origin,
+ perspective_origin,
+ MozPerspectiveOrigin,
+ "layout.css.prefixes.transforms")
+CSS_PROP_ALIAS(-moz-perspective,
+ perspective,
+ MozPerspective,
+ "layout.css.prefixes.transforms")
+CSS_PROP_ALIAS(-moz-transform-style,
+ transform_style,
+ MozTransformStyle,
+ "layout.css.prefixes.transforms")
+CSS_PROP_ALIAS(-moz-backface-visibility,
+ backface_visibility,
+ MozBackfaceVisibility,
+ "layout.css.prefixes.transforms")
+CSS_PROP_ALIAS(-moz-border-image,
+ border_image,
+ MozBorderImage,
+ "layout.css.prefixes.border-image")
+CSS_PROP_ALIAS(-moz-transition,
+ transition,
+ MozTransition,
+ "layout.css.prefixes.transitions")
+CSS_PROP_ALIAS(-moz-transition-delay,
+ transition_delay,
+ MozTransitionDelay,
+ "layout.css.prefixes.transitions")
+CSS_PROP_ALIAS(-moz-transition-duration,
+ transition_duration,
+ MozTransitionDuration,
+ "layout.css.prefixes.transitions")
+CSS_PROP_ALIAS(-moz-transition-property,
+ transition_property,
+ MozTransitionProperty,
+ "layout.css.prefixes.transitions")
+CSS_PROP_ALIAS(-moz-transition-timing-function,
+ transition_timing_function,
+ MozTransitionTimingFunction,
+ "layout.css.prefixes.transitions")
+CSS_PROP_ALIAS(-moz-animation,
+ animation,
+ MozAnimation,
+ "layout.css.prefixes.animations")
+CSS_PROP_ALIAS(-moz-animation-delay,
+ animation_delay,
+ MozAnimationDelay,
+ "layout.css.prefixes.animations")
+CSS_PROP_ALIAS(-moz-animation-direction,
+ animation_direction,
+ MozAnimationDirection,
+ "layout.css.prefixes.animations")
+CSS_PROP_ALIAS(-moz-animation-duration,
+ animation_duration,
+ MozAnimationDuration,
+ "layout.css.prefixes.animations")
+CSS_PROP_ALIAS(-moz-animation-fill-mode,
+ animation_fill_mode,
+ MozAnimationFillMode,
+ "layout.css.prefixes.animations")
+CSS_PROP_ALIAS(-moz-animation-iteration-count,
+ animation_iteration_count,
+ MozAnimationIterationCount,
+ "layout.css.prefixes.animations")
+CSS_PROP_ALIAS(-moz-animation-name,
+ animation_name,
+ MozAnimationName,
+ "layout.css.prefixes.animations")
+CSS_PROP_ALIAS(-moz-animation-play-state,
+ animation_play_state,
+ MozAnimationPlayState,
+ "layout.css.prefixes.animations")
+CSS_PROP_ALIAS(-moz-animation-timing-function,
+ animation_timing_function,
+ MozAnimationTimingFunction,
+ "layout.css.prefixes.animations")
+CSS_PROP_ALIAS(-moz-box-sizing,
+ box_sizing,
+ MozBoxSizing,
+ "layout.css.prefixes.box-sizing")
+CSS_PROP_ALIAS(-moz-font-feature-settings,
+ font_feature_settings,
+ MozFontFeatureSettings,
+ "layout.css.prefixes.font-features")
+CSS_PROP_ALIAS(-moz-font-language-override,
+ font_language_override,
+ MozFontLanguageOverride,
+ "layout.css.prefixes.font-features")
+CSS_PROP_ALIAS(-moz-padding-end,
+ padding_inline_end,
+ MozPaddingEnd,
+ "")
+CSS_PROP_ALIAS(-moz-padding-start,
+ padding_inline_start,
+ MozPaddingStart,
+ "")
+CSS_PROP_ALIAS(-moz-margin-end,
+ margin_inline_end,
+ MozMarginEnd,
+ "")
+CSS_PROP_ALIAS(-moz-margin-start,
+ margin_inline_start,
+ MozMarginStart,
+ "")
+CSS_PROP_ALIAS(-moz-border-end,
+ border_inline_end,
+ MozBorderEnd,
+ "")
+CSS_PROP_ALIAS(-moz-border-end-color,
+ border_inline_end_color,
+ MozBorderEndColor,
+ "")
+CSS_PROP_ALIAS(-moz-border-end-style,
+ border_inline_end_style,
+ MozBorderEndStyle,
+ "")
+CSS_PROP_ALIAS(-moz-border-end-width,
+ border_inline_end_width,
+ MozBorderEndWidth,
+ "")
+CSS_PROP_ALIAS(-moz-border-start,
+ border_inline_start,
+ MozBorderStart,
+ "")
+CSS_PROP_ALIAS(-moz-border-start-color,
+ border_inline_start_color,
+ MozBorderStartColor,
+ "")
+CSS_PROP_ALIAS(-moz-border-start-style,
+ border_inline_start_style,
+ MozBorderStartStyle,
+ "")
+CSS_PROP_ALIAS(-moz-border-start-width,
+ border_inline_start_width,
+ MozBorderStartWidth,
+ "")
+CSS_PROP_ALIAS(-moz-hyphens,
+ hyphens,
+ MozHyphens,
+ "")
+CSS_PROP_ALIAS(-moz-text-align-last,
+ text_align_last,
+ MozTextAlignLast,
+ "")
+CSS_PROP_ALIAS(-moz-column-count,
+ column_count,
+ MozColumnCount,
+ "")
+CSS_PROP_ALIAS(-moz-column-fill,
+ column_fill,
+ MozColumnFill,
+ "")
+CSS_PROP_ALIAS(-moz-column-gap,
+ column_gap,
+ MozColumnGap,
+ "")
+CSS_PROP_ALIAS(-moz-column-rule,
+ column_rule,
+ MozColumnRule,
+ "")
+CSS_PROP_ALIAS(-moz-column-rule-color,
+ column_rule_color,
+ MozColumnRuleColor,
+ "")
+CSS_PROP_ALIAS(-moz-column-rule-style,
+ column_rule_style,
+ MozColumnRuleStyle,
+ "")
+CSS_PROP_ALIAS(-moz-column-rule-width,
+ column_rule_width,
+ MozColumnRuleWidth,
+ "")
+CSS_PROP_ALIAS(-moz-column-width,
+ column_width,
+ MozColumnWidth,
+ "")
+CSS_PROP_ALIAS(-moz-columns,
+ columns,
+ MozColumns,
+ "")
+
+#define WEBKIT_PREFIX_PREF "layout.css.prefixes.webkit"
+
+// -webkit- prefixes
+CSS_PROP_ALIAS(-webkit-animation,
+ animation,
+ WebkitAnimation,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-animation-delay,
+ animation_delay,
+ WebkitAnimationDelay,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-animation-direction,
+ animation_direction,
+ WebkitAnimationDirection,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-animation-duration,
+ animation_duration,
+ WebkitAnimationDuration,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-animation-fill-mode,
+ animation_fill_mode,
+ WebkitAnimationFillMode,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-animation-iteration-count,
+ animation_iteration_count,
+ WebkitAnimationIterationCount,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-animation-name,
+ animation_name,
+ WebkitAnimationName,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-animation-play-state,
+ animation_play_state,
+ WebkitAnimationPlayState,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-animation-timing-function,
+ animation_timing_function,
+ WebkitAnimationTimingFunction,
+ WEBKIT_PREFIX_PREF)
+
+CSS_PROP_ALIAS(-webkit-filter,
+ filter,
+ WebkitFilter,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-text-size-adjust,
+ text_size_adjust,
+ WebkitTextSizeAdjust,
+ WEBKIT_PREFIX_PREF)
+
+CSS_PROP_ALIAS(-webkit-transform,
+ transform,
+ WebkitTransform,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-transform-origin,
+ transform_origin,
+ WebkitTransformOrigin,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-transform-style,
+ transform_style,
+ WebkitTransformStyle,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-backface-visibility,
+ backface_visibility,
+ WebkitBackfaceVisibility,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-perspective,
+ perspective,
+ WebkitPerspective,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-perspective-origin,
+ perspective_origin,
+ WebkitPerspectiveOrigin,
+ WEBKIT_PREFIX_PREF)
+
+CSS_PROP_ALIAS(-webkit-transition,
+ transition,
+ WebkitTransition,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-transition-delay,
+ transition_delay,
+ WebkitTransitionDelay,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-transition-duration,
+ transition_duration,
+ WebkitTransitionDuration,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-transition-property,
+ transition_property,
+ WebkitTransitionProperty,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-transition-timing-function,
+ transition_timing_function,
+ WebkitTransitionTimingFunction,
+ WEBKIT_PREFIX_PREF)
+
+CSS_PROP_ALIAS(-webkit-border-radius,
+ border_radius,
+ WebkitBorderRadius,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-border-top-left-radius,
+ border_top_left_radius,
+ WebkitBorderTopLeftRadius, // really no dom property
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-border-top-right-radius,
+ border_top_right_radius,
+ WebkitBorderTopRightRadius, // really no dom property
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-border-bottom-left-radius,
+ border_bottom_left_radius,
+ WebkitBorderBottomLeftRadius, // really no dom property
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-border-bottom-right-radius,
+ border_bottom_right_radius,
+ WebkitBorderBottomRightRadius, // really no dom property
+ WEBKIT_PREFIX_PREF)
+
+CSS_PROP_ALIAS(-webkit-background-clip,
+ background_clip,
+ WebkitBackgroundClip,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-background-origin,
+ background_origin,
+ WebkitBackgroundOrigin,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-background-size,
+ background_size,
+ WebkitBackgroundSize,
+ WEBKIT_PREFIX_PREF)
+
+CSS_PROP_ALIAS(-webkit-border-image,
+ border_image,
+ WebkitBorderImage,
+ WEBKIT_PREFIX_PREF)
+
+CSS_PROP_ALIAS(-webkit-box-shadow,
+ box_shadow,
+ WebkitBoxShadow,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-box-sizing,
+ box_sizing,
+ WebkitBoxSizing,
+ WEBKIT_PREFIX_PREF)
+
+// Alias -webkit-box properties to their -moz-box equivalents.
+// (NOTE: Even though they're aliases, in practice these -webkit properties
+// will behave a bit differently from their -moz versions, if they're
+// accompanied by "display:-webkit-box", because we generate a different frame
+// for those two display values.)
+CSS_PROP_ALIAS(-webkit-box-flex,
+ box_flex,
+ WebkitBoxFlex,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-box-ordinal-group,
+ box_ordinal_group,
+ WebkitBoxOrdinalGroup,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-box-orient,
+ box_orient,
+ WebkitBoxOrient,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-box-direction,
+ box_direction,
+ WebkitBoxDirection,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-box-align,
+ box_align,
+ WebkitBoxAlign,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-box-pack,
+ box_pack,
+ WebkitBoxPack,
+ WEBKIT_PREFIX_PREF)
+
+// Alias -webkit-flex related properties to their unprefixed equivalents:
+// (Matching ordering at https://drafts.csswg.org/css-flexbox-1/#property-index )
+CSS_PROP_ALIAS(-webkit-flex-direction,
+ flex_direction,
+ WebkitFlexDirection,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-flex-wrap,
+ flex_wrap,
+ WebkitFlexWrap,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-flex-flow,
+ flex_flow,
+ WebkitFlexFlow,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-order,
+ order,
+ WebkitOrder,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-flex,
+ flex,
+ WebkitFlex,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-flex-grow,
+ flex_grow,
+ WebkitFlexGrow,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-flex-shrink,
+ flex_shrink,
+ WebkitFlexShrink,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-flex-basis,
+ flex_basis,
+ WebkitFlexBasis,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-justify-content,
+ justify_content,
+ WebkitJustifyContent,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-align-items,
+ align_items,
+ WebkitAlignItems,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-align-self,
+ align_self,
+ WebkitAlignSelf,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-align-content,
+ align_content,
+ WebkitAlignContent,
+ WEBKIT_PREFIX_PREF)
+
+CSS_PROP_ALIAS(-webkit-user-select,
+ user_select,
+ WebkitUserSelect,
+ WEBKIT_PREFIX_PREF)
+
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+CSS_PROP_ALIAS(-webkit-mask,
+ mask,
+ WebkitMask,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-clip,
+ mask_clip,
+ WebkitMaskClip,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-composite,
+ mask_composite,
+ WebkitMaskComposite,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-image,
+ mask_image,
+ WebkitMaskImage,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-origin,
+ mask_origin,
+ WebkitMaskOrigin,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-position,
+ mask_position,
+ WebkitMaskPosition,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-position-x,
+ mask_position_x,
+ WebkitMaskPositionX,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-position-y,
+ mask_position_y,
+ WebkitMaskPositionY,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-repeat,
+ mask_repeat,
+ WebkitMaskRepeat,
+ WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-size,
+ mask_size,
+ WebkitMaskSize,
+ WEBKIT_PREFIX_PREF)
+#endif
+#undef WEBKIT_PREFIX_PREF
diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h
new file mode 100644
index 000000000..6931d8c2b
--- /dev/null
+++ b/layout/style/nsCSSPropList.h
@@ -0,0 +1,4635 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * a list of all CSS properties with considerable data about them, for
+ * preprocessing
+ */
+
+/******
+
+ This file contains the list of all parsed CSS properties. It is
+ designed to be used as inline input through the magic of C
+ preprocessing. All entries must be enclosed in the appropriate
+ CSS_PROP_* macro which will have cruel and unusual things done to it.
+ It is recommended (but not strictly necessary) to keep all entries in
+ alphabetical order.
+
+ The arguments to CSS_PROP, CSS_PROP_LOGICAL and CSS_PROP_* are:
+
+ -. 'name' entries represent a CSS property name and *must* use only
+ lowercase characters.
+
+ -. 'id' should be the same as 'name' except that all hyphens ('-')
+ in 'name' are converted to underscores ('_') in 'id'. For properties
+ on a standards track, any '-moz-' prefix is removed in 'id'. This
+ lets us do nice things with the macros without having to copy/convert
+ strings at runtime. These are the names used for the enum values of
+ the nsCSSPropertyID enumeration defined in nsCSSProps.h.
+
+ -. 'method' is designed to be as input for CSS2Properties and similar
+ callers. It must always be the same as 'name' except it must use
+ InterCaps and all hyphens ('-') must be removed. Callers using this
+ parameter must also define the CSS_PROP_PUBLIC_OR_PRIVATE(publicname_,
+ privatename_) macro to yield either publicname_ or privatename_.
+ The names differ in that publicname_ has Moz prefixes where they are
+ used, and also in CssFloat vs. Float. The caller's choice depends on
+ whether the use is for internal use such as eCSSProperty_* or
+ nsRuleData::ValueFor* or external use such as exposing DOM properties.
+
+ -. 'flags', a bitfield containing CSS_PROPERTY_* flags.
+
+ -. 'pref' is the name of a pref that controls whether the property
+ is enabled. The property is enabled if 'pref' is an empty string,
+ or if the boolean property whose name is 'pref' is set to true.
+
+ -. 'parsevariant', to be passed to ParseVariant in the parser.
+
+ -. 'kwtable', which is either nullptr or the name of the appropriate
+ keyword table member of class nsCSSProps, for use in
+ nsCSSProps::LookupPropertyValue.
+
+ -. 'group_' [used only for CSS_PROP_LOGICAL] is the name of
+ the logical property group that contains the physical properties
+ that can be set by this logical property. The name must be one
+ from nsCSSPropLogicalGroupList.h. For example, this would be
+ 'BorderColor' for 'border-block-start-color'.
+
+ -. 'stylestruct_' [used only for CSS_PROP and CSS_PROP_LOGICAL, not
+ CSS_PROP_*] gives the name of the style struct. Can be used to make
+ nsStyle##stylestruct_ and eStyleStruct_##stylestruct_
+
+ -. 'stylestructoffset_' gives the result of offsetof(nsStyle*,
+ member). Ignored (and generally CSS_PROP_NO_OFFSET, or -1) for
+ properties whose animtype_ is eStyleAnimType_None.
+
+ -. 'animtype_' gives the animation type (see nsStyleAnimType) of this
+ property.
+
+ CSS_PROP_SHORTHAND only takes 1-5.
+
+ CSS_PROP_LOGICAL should be used instead of CSS_PROP_struct when
+ defining logical properties (which also must be defined with the
+ CSS_PROPERTY_LOGICAL flag). Logical shorthand properties should still
+ be defined with CSS_PROP_SHORTHAND.
+
+ ******/
+
+
+/*************************************************************************/
+
+
+// All includers must explicitly define CSS_PROP_SHORTHAND if they
+// want it.
+#ifndef CSS_PROP_SHORTHAND
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) /* nothing */
+#define DEFINED_CSS_PROP_SHORTHAND
+#endif
+
+#define CSS_PROP_DOMPROP_PREFIXED(name_) \
+ CSS_PROP_PUBLIC_OR_PRIVATE(Moz ## name_, name_)
+
+#define CSS_PROP_NO_OFFSET (-1)
+
+// Callers may define CSS_PROP_LIST_EXCLUDE_INTERNAL if they want to
+// exclude internal properties that are not represented in the DOM (only
+// the DOM style code defines this). All properties defined in an
+// #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL section must have the
+// CSS_PROPERTY_INTERNAL flag set.
+
+// When capturing all properties by defining CSS_PROP, callers must also
+// define one of the following three macros:
+//
+// CSS_PROP_LIST_EXCLUDE_LOGICAL
+// Does not include logical properties (defined with CSS_PROP_LOGICAL,
+// such as margin-inline-start) when capturing properties to CSS_PROP.
+//
+// CSS_PROP_LIST_INCLUDE_LOGICAL
+// Does include logical properties when capturing properties to
+// CSS_PROP.
+//
+// CSS_PROP_LOGICAL
+// Captures logical properties separately to CSS_PROP_LOGICAL.
+//
+// (CSS_PROP_LIST_EXCLUDE_LOGICAL is used for example to ensure
+// gPropertyCountInStruct and gPropertyIndexInStruct do not allocate any
+// storage to logical properties, since the result of the cascade, stored
+// in an nsRuleData, does not need to store both logical and physical
+// property values.)
+
+// Callers may also define CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+// to exclude properties that are not considered to be components of the 'all'
+// shorthand property. Currently this excludes 'direction' and 'unicode-bidi',
+// as required by the CSS Cascading and Inheritance specification, and any
+// internal properties that cannot be changed by using CSS syntax. For example,
+// the internal '-moz-system-font' property is not excluded, as it is set by the
+// 'font' shorthand, while '-x-lang' is excluded as there is no way to set this
+// internal property from a style sheet.
+
+// A caller who wants all the properties can define the |CSS_PROP|
+// macro.
+#ifdef CSS_PROP
+
+#define USED_CSS_PROP
+#define CSS_PROP_FONT(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Font, stylestructoffset_, animtype_)
+#define CSS_PROP_COLOR(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Color, stylestructoffset_, animtype_)
+#define CSS_PROP_BACKGROUND(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Background, stylestructoffset_, animtype_)
+#define CSS_PROP_LIST(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, List, stylestructoffset_, animtype_)
+#define CSS_PROP_POSITION(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Position, stylestructoffset_, animtype_)
+#define CSS_PROP_TEXT(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Text, stylestructoffset_, animtype_)
+#define CSS_PROP_TEXTRESET(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, TextReset, stylestructoffset_, animtype_)
+#define CSS_PROP_DISPLAY(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Display, stylestructoffset_, animtype_)
+#define CSS_PROP_VISIBILITY(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Visibility, stylestructoffset_, animtype_)
+#define CSS_PROP_CONTENT(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Content, stylestructoffset_, animtype_)
+#define CSS_PROP_USERINTERFACE(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, UserInterface, stylestructoffset_, animtype_)
+#define CSS_PROP_UIRESET(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, UIReset, stylestructoffset_, animtype_)
+#define CSS_PROP_TABLE(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Table, stylestructoffset_, animtype_)
+#define CSS_PROP_TABLEBORDER(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, TableBorder, stylestructoffset_, animtype_)
+#define CSS_PROP_MARGIN(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Margin, stylestructoffset_, animtype_)
+#define CSS_PROP_PADDING(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Padding, stylestructoffset_, animtype_)
+#define CSS_PROP_BORDER(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Border, stylestructoffset_, animtype_)
+#define CSS_PROP_OUTLINE(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Outline, stylestructoffset_, animtype_)
+#define CSS_PROP_XUL(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, XUL, stylestructoffset_, animtype_)
+#define CSS_PROP_COLUMN(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Column, stylestructoffset_, animtype_)
+#define CSS_PROP_SVG(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, SVG, stylestructoffset_, animtype_)
+#define CSS_PROP_SVGRESET(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, SVGReset, stylestructoffset_, animtype_)
+#define CSS_PROP_VARIABLES(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Variables, stylestructoffset_, animtype_)
+#define CSS_PROP_EFFECTS(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, Effects, stylestructoffset_, animtype_)
+
+// And similarly for logical properties. An includer can define
+// CSS_PROP_LOGICAL to capture all logical properties, but otherwise they
+// are included in CSS_PROP (as long as CSS_PROP_LIST_INCLUDE_LOGICAL is
+// defined).
+#if defined(CSS_PROP_LOGICAL) && defined(CSS_PROP_LIST_EXCLUDE_LOGICAL) || defined(CSS_PROP_LOGICAL) && defined(CSS_PROP_LIST_INCLUDE_LOGICAL) || defined(CSS_PROP_LIST_EXCLUDE_LOGICAL) && defined(CSS_PROP_LIST_INCLUDE_LOGICAL)
+#error Do not define more than one of CSS_PROP_LOGICAL, CSS_PROP_LIST_EXCLUDE_LOGICAL and CSS_PROP_LIST_INCLUDE_LOGICAL when capturing properties using CSS_PROP.
+#endif
+
+#ifndef CSS_PROP_LOGICAL
+#ifdef CSS_PROP_LIST_INCLUDE_LOGICAL
+#define CSS_PROP_LOGICAL(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, group_, struct_, stylestructoffset_, animtype_) CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, struct_, stylestructoffset_, animtype_)
+#else
+#ifndef CSS_PROP_LIST_EXCLUDE_LOGICAL
+#error Must define exactly one of CSS_PROP_LOGICAL, CSS_PROP_LIST_EXCLUDE_LOGICAL and CSS_PROP_LIST_INCLUDE_LOGICAL when capturing properties using CSS_PROP.
+#endif
+#define CSS_PROP_LOGICAL(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, group_, struct_, stylestructoffset_, animtype_) /* nothing */
+#endif
+#define DEFINED_CSS_PROP_LOGICAL
+#endif
+
+#else /* !defined(CSS_PROP) */
+
+// An includer who does not define CSS_PROP can define any or all of the
+// per-struct macros that are equivalent to it, and the rest will be
+// ignored.
+
+#if defined(CSS_PROP_LIST_EXCLUDE_LOGICAL) || defined(CSS_PROP_LIST_INCLUDE_LOGICAL)
+#error Do not define CSS_PROP_LIST_EXCLUDE_LOGICAL or CSS_PROP_LIST_INCLUDE_LOGICAL when not capturing properties using CSS_PROP.
+#endif
+
+#ifndef CSS_PROP_FONT
+#define CSS_PROP_FONT(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_FONT
+#endif
+#ifndef CSS_PROP_COLOR
+#define CSS_PROP_COLOR(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_COLOR
+#endif
+#ifndef CSS_PROP_BACKGROUND
+#define CSS_PROP_BACKGROUND(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_BACKGROUND
+#endif
+#ifndef CSS_PROP_LIST
+#define CSS_PROP_LIST(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_LIST
+#endif
+#ifndef CSS_PROP_POSITION
+#define CSS_PROP_POSITION(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_POSITION
+#endif
+#ifndef CSS_PROP_TEXT
+#define CSS_PROP_TEXT(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_TEXT
+#endif
+#ifndef CSS_PROP_TEXTRESET
+#define CSS_PROP_TEXTRESET(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_TEXTRESET
+#endif
+#ifndef CSS_PROP_DISPLAY
+#define CSS_PROP_DISPLAY(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_DISPLAY
+#endif
+#ifndef CSS_PROP_VISIBILITY
+#define CSS_PROP_VISIBILITY(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_VISIBILITY
+#endif
+#ifndef CSS_PROP_CONTENT
+#define CSS_PROP_CONTENT(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_CONTENT
+#endif
+#ifndef CSS_PROP_USERINTERFACE
+#define CSS_PROP_USERINTERFACE(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_USERINTERFACE
+#endif
+#ifndef CSS_PROP_UIRESET
+#define CSS_PROP_UIRESET(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_UIRESET
+#endif
+#ifndef CSS_PROP_TABLE
+#define CSS_PROP_TABLE(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_TABLE
+#endif
+#ifndef CSS_PROP_TABLEBORDER
+#define CSS_PROP_TABLEBORDER(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_TABLEBORDER
+#endif
+#ifndef CSS_PROP_MARGIN
+#define CSS_PROP_MARGIN(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_MARGIN
+#endif
+#ifndef CSS_PROP_PADDING
+#define CSS_PROP_PADDING(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_PADDING
+#endif
+#ifndef CSS_PROP_BORDER
+#define CSS_PROP_BORDER(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_BORDER
+#endif
+#ifndef CSS_PROP_OUTLINE
+#define CSS_PROP_OUTLINE(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_OUTLINE
+#endif
+#ifndef CSS_PROP_XUL
+#define CSS_PROP_XUL(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_XUL
+#endif
+#ifndef CSS_PROP_COLUMN
+#define CSS_PROP_COLUMN(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_COLUMN
+#endif
+#ifndef CSS_PROP_SVG
+#define CSS_PROP_SVG(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_SVG
+#endif
+#ifndef CSS_PROP_SVGRESET
+#define CSS_PROP_SVGRESET(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_SVGRESET
+#endif
+#ifndef CSS_PROP_VARIABLES
+#define CSS_PROP_VARIABLES(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_VARIABLES
+#endif
+#ifndef CSS_PROP_EFFECTS
+#define CSS_PROP_EFFECTS(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_EFFECTS
+#endif
+
+#ifndef CSS_PROP_LOGICAL
+#define CSS_PROP_LOGICAL(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, group_, struct_, stylestructoffset_, animtype_) /* nothing */
+#define DEFINED_CSS_PROP_LOGICAL
+#endif
+
+#endif /* !defined(CSS_PROP) */
+
+/*************************************************************************/
+
+// For notes XXX bug 3935 below, the names being parsed do not correspond
+// to the constants used internally. It would be nice to bring the
+// constants into line sometime.
+
+// The parser will refuse to parse properties marked with -x-.
+
+// Those marked XXX bug 48973 are CSS2 properties that we support
+// differently from the spec for UI requirements. If we ever
+// support them correctly the old constants need to be renamed and
+// new ones should be entered.
+
+// CSS2.1 section 5.12.1 says that the properties that apply to
+// :first-line are: font properties, color properties, background
+// properties, 'word-spacing', 'letter-spacing', 'text-decoration',
+// 'vertical-align', 'text-transform', and 'line-height'.
+//
+// We also allow 'text-shadow', which was listed in CSS2 (where the
+// property existed).
+
+// CSS2.1 section 5.12.2 says that the properties that apply to
+// :first-letter are: font properties, 'text-decoration',
+// 'text-transform', 'letter-spacing', 'word-spacing' (when
+// appropriate), 'line-height', 'float', 'vertical-align' (only if
+// 'float' is 'none'), margin properties, padding properties, border
+// properties, 'color', and background properties. We also allow
+// 'text-shadow' (see above) and 'box-shadow' (which is like the
+// border properties).
+
+// Please keep these sorted by property name, ignoring any "-moz-",
+// "-webkit-" or "-x-" prefix.
+
+CSS_PROP_POSITION(
+ align-content,
+ align_content,
+ AlignContent,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ VARIANT_HK,
+ kAutoCompletionAlignJustifyContent,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ align-items,
+ align_items,
+ AlignItems,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ VARIANT_HK,
+ kAutoCompletionAlignItems,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ align-self,
+ align_self,
+ AlignSelf,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ VARIANT_HK,
+ kAutoCompletionAlignJustifySelf,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ all,
+ all,
+ All,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.all-shorthand.enabled")
+CSS_PROP_SHORTHAND(
+ animation,
+ animation,
+ Animation,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_DISPLAY(
+ animation-delay,
+ animation_delay,
+ AnimationDelay,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_TIME, // used by list parsing
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ animation-direction,
+ animation_direction,
+ AnimationDirection,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kAnimationDirectionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ animation-duration,
+ animation_duration,
+ AnimationDuration,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_TIME | VARIANT_NONNEGATIVE_DIMENSION, // used by list parsing
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ animation-fill-mode,
+ animation_fill_mode,
+ AnimationFillMode,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kAnimationFillModeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ animation-iteration-count,
+ animation_iteration_count,
+ AnimationIterationCount,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ // nonnegative per
+ // http://lists.w3.org/Archives/Public/www-style/2011Mar/0355.html
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD | VARIANT_NUMBER, // used by list parsing
+ kAnimationIterationCountKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ animation-name,
+ animation_name,
+ AnimationName,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ // FIXME: The spec should say something about 'inherit' and 'initial'
+ // not being allowed.
+ VARIANT_NONE | VARIANT_IDENTIFIER_NO_INHERIT, // used by list parsing
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ animation-play-state,
+ animation_play_state,
+ AnimationPlayState,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kAnimationPlayStateKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ animation-timing-function,
+ animation_timing_function,
+ AnimationTimingFunction,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD | VARIANT_TIMING_FUNCTION, // used by list parsing
+ kTransitionTimingFunctionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ -moz-appearance,
+ appearance,
+ CSS_PROP_DOMPROP_PREFIXED(Appearance),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kAppearanceKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ backface-visibility,
+ backface_visibility,
+ BackfaceVisibility,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kBackfaceVisibilityKTable,
+ offsetof(nsStyleDisplay, mBackfaceVisibility),
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ background,
+ background,
+ Background,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_BACKGROUND(
+ background-attachment,
+ background_attachment,
+ BackgroundAttachment,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kImageLayerAttachmentKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BACKGROUND(
+ background-blend-mode,
+ background_blend_mode,
+ BackgroundBlendMode,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "layout.css.background-blend-mode.enabled",
+ VARIANT_KEYWORD, // used by list parsing
+ kBlendModeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BACKGROUND(
+ background-clip,
+ background_clip,
+ BackgroundClip,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kBackgroundClipKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BACKGROUND(
+ background-color,
+ background_color,
+ BackgroundColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED |
+ CSS_PROPERTY_HASHLESS_COLOR_QUIRK,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleBackground, mBackgroundColor),
+ eStyleAnimType_Color)
+CSS_PROP_BACKGROUND(
+ background-image,
+ background_image,
+ BackgroundImage,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED |
+ CSS_PROPERTY_START_IMAGE_LOADS,
+ "",
+ VARIANT_IMAGE, // used by list parsing
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BACKGROUND(
+ background-origin,
+ background_origin,
+ BackgroundOrigin,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kImageLayerOriginKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ background-position,
+ background_position,
+ BackgroundPosition,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "")
+CSS_PROP_BACKGROUND(
+ background-position-x,
+ background_position_x,
+ BackgroundPositionX,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ kImageLayerPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_BACKGROUND(
+ background-position-y,
+ background_position_y,
+ BackgroundPositionY,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ kImageLayerPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_BACKGROUND(
+ background-repeat,
+ background_repeat,
+ BackgroundRepeat,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kImageLayerRepeatKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BACKGROUND(
+ background-size,
+ background_size,
+ BackgroundSize,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ kImageLayerSizeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_DISPLAY(
+ -moz-binding,
+ binding,
+ CSS_PROP_DOMPROP_PREFIXED(Binding),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HUO,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None) // XXX bug 3935
+CSS_PROP_LOGICAL(
+ block-size,
+ block_size,
+ BlockSize,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_AXIS |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ Size,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_SHORTHAND(
+ border,
+ border,
+ Border,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_SHORTHAND(
+ border-block-end,
+ border_block_end,
+ BorderBlockEnd,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_LOGICAL(
+ border-block-end-color,
+ border_block_end_color,
+ BorderBlockEndColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_HC,
+ nullptr,
+ BorderColor,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ border-block-end-style,
+ border_block_end_style,
+ BorderBlockEndStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_HK,
+ kBorderStyleKTable,
+ BorderStyle,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ border-block-end-width,
+ border_block_end_width,
+ BorderBlockEndWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ BorderWidth,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_SHORTHAND(
+ border-block-start,
+ border_block_start,
+ BorderBlockStart,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_LOGICAL(
+ border-block-start-color,
+ border_block_start_color,
+ BorderBlockStartColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
+ "",
+ VARIANT_HC,
+ nullptr,
+ BorderColor,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ border-block-start-style,
+ border_block_start_style,
+ BorderBlockStartStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
+ "",
+ VARIANT_HK,
+ kBorderStyleKTable,
+ BorderStyle,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ border-block-start-width,
+ border_block_start_width,
+ BorderBlockStartWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ BorderWidth,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_SHORTHAND(
+ border-bottom,
+ border_bottom,
+ BorderBottom,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_BORDER(
+ border-bottom-color,
+ border_bottom_color,
+ BorderBottomColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED |
+ CSS_PROPERTY_HASHLESS_COLOR_QUIRK,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleBorder, mBorderBottomColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_BORDER(
+ -moz-border-bottom-colors,
+ border_bottom_colors,
+ CSS_PROP_DOMPROP_PREFIXED(BorderBottomColors),
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-bottom-left-radius,
+ border_bottom_left_radius,
+ BorderBottomLeftRadius,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleBorder, mBorderRadius),
+ eStyleAnimType_Corner_BottomLeft)
+CSS_PROP_BORDER(
+ border-bottom-right-radius,
+ border_bottom_right_radius,
+ BorderBottomRightRadius,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleBorder, mBorderRadius),
+ eStyleAnimType_Corner_BottomRight)
+CSS_PROP_BORDER(
+ border-bottom-style,
+ border_bottom_style,
+ BorderBottomStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ VARIANT_HK,
+ kBorderStyleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // on/off will need reflow
+CSS_PROP_BORDER(
+ border-bottom-width,
+ border_bottom_width,
+ BorderBottomWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_TABLEBORDER(
+ border-collapse,
+ border_collapse,
+ BorderCollapse,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kBorderCollapseKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ border-color,
+ border_color,
+ BorderColor,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_HASHLESS_COLOR_QUIRK,
+ "")
+CSS_PROP_SHORTHAND(
+ border-image,
+ border_image,
+ BorderImage,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_BORDER(
+ border-image-outset,
+ border_image_outset,
+ BorderImageOutset,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-image-repeat,
+ border_image_repeat,
+ BorderImageRepeat,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ 0,
+ kBorderImageRepeatKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-image-slice,
+ border_image_slice,
+ BorderImageSlice,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ 0,
+ kBorderImageSliceKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-image-source,
+ border_image_source,
+ BorderImageSource,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_START_IMAGE_LOADS,
+ "",
+ VARIANT_IMAGE | VARIANT_INHERIT,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-image-width,
+ border_image_width,
+ BorderImageWidth,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ border-inline-end,
+ border_inline_end,
+ BorderInlineEnd,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_LOGICAL(
+ border-inline-end-color,
+ border_inline_end_color,
+ BorderInlineEndColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_HC,
+ nullptr,
+ BorderColor,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ border-inline-end-style,
+ border_inline_end_style,
+ BorderInlineEndStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_HK,
+ kBorderStyleKTable,
+ BorderStyle,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ border-inline-end-width,
+ border_inline_end_width,
+ BorderInlineEndWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ BorderWidth,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_SHORTHAND(
+ border-inline-start,
+ border_inline_start,
+ BorderInlineStart,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_LOGICAL(
+ border-inline-start-color,
+ border_inline_start_color,
+ BorderInlineStartColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL,
+ "",
+ VARIANT_HC,
+ nullptr,
+ BorderColor,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ border-inline-start-style,
+ border_inline_start_style,
+ BorderInlineStartStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL,
+ "",
+ VARIANT_HK,
+ kBorderStyleKTable,
+ BorderStyle,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ border-inline-start-width,
+ border_inline_start_width,
+ BorderInlineStartWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_LOGICAL,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ BorderWidth,
+ Border,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_SHORTHAND(
+ border-left,
+ border_left,
+ BorderLeft,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_BORDER(
+ border-left-color,
+ border_left_color,
+ BorderLeftColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_HASHLESS_COLOR_QUIRK |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleBorder, mBorderLeftColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_BORDER(
+ -moz-border-left-colors,
+ border_left_colors,
+ CSS_PROP_DOMPROP_PREFIXED(BorderLeftColors),
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-left-style,
+ border_left_style,
+ BorderLeftStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ VARIANT_HK,
+ kBorderStyleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-left-width,
+ border_left_width,
+ BorderLeftWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_SHORTHAND(
+ border-radius,
+ border_radius,
+ BorderRadius,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_SHORTHAND(
+ border-right,
+ border_right,
+ BorderRight,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_BORDER(
+ border-right-color,
+ border_right_color,
+ BorderRightColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_HASHLESS_COLOR_QUIRK |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleBorder, mBorderRightColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_BORDER(
+ -moz-border-right-colors,
+ border_right_colors,
+ CSS_PROP_DOMPROP_PREFIXED(BorderRightColors),
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-right-style,
+ border_right_style,
+ BorderRightStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ VARIANT_HK,
+ kBorderStyleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-right-width,
+ border_right_width,
+ BorderRightWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_TABLEBORDER(
+ border-spacing,
+ border_spacing,
+ BorderSpacing,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_SHORTHAND(
+ border-style,
+ border_style,
+ BorderStyle,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "") // on/off will need reflow
+CSS_PROP_SHORTHAND(
+ border-top,
+ border_top,
+ BorderTop,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_BORDER(
+ border-top-color,
+ border_top_color,
+ BorderTopColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED |
+ CSS_PROPERTY_HASHLESS_COLOR_QUIRK,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleBorder, mBorderTopColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_BORDER(
+ -moz-border-top-colors,
+ border_top_colors,
+ CSS_PROP_DOMPROP_PREFIXED(BorderTopColors),
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ border-top-left-radius,
+ border_top_left_radius,
+ BorderTopLeftRadius,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleBorder, mBorderRadius),
+ eStyleAnimType_Corner_TopLeft)
+CSS_PROP_BORDER(
+ border-top-right-radius,
+ border_top_right_radius,
+ BorderTopRightRadius,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleBorder, mBorderRadius),
+ eStyleAnimType_Corner_TopRight)
+CSS_PROP_BORDER(
+ border-top-style,
+ border_top_style,
+ BorderTopStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ VARIANT_HK,
+ kBorderStyleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // on/off will need reflow
+CSS_PROP_BORDER(
+ border-top-width,
+ border_top_width,
+ BorderTopWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_SHORTHAND(
+ border-width,
+ border_width,
+ BorderWidth,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "")
+CSS_PROP_POSITION(
+ bottom,
+ bottom,
+ Bottom,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePosition, mOffset),
+ eStyleAnimType_Sides_Bottom)
+CSS_PROP_XUL(
+ -moz-box-align,
+ box_align,
+ CSS_PROP_DOMPROP_PREFIXED(BoxAlign),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kBoxAlignKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 3935
+CSS_PROP_BORDER(
+ box-decoration-break,
+ box_decoration_break,
+ BoxDecorationBreak,
+ CSS_PROPERTY_PARSE_VALUE,
+ "layout.css.box-decoration-break.enabled",
+ VARIANT_HK,
+ kBoxDecorationBreakKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_XUL(
+ -moz-box-direction,
+ box_direction,
+ CSS_PROP_DOMPROP_PREFIXED(BoxDirection),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kBoxDirectionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 3935
+CSS_PROP_XUL(
+ -moz-box-flex,
+ box_flex,
+ CSS_PROP_DOMPROP_PREFIXED(BoxFlex),
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ VARIANT_HN,
+ nullptr,
+ offsetof(nsStyleXUL, mBoxFlex),
+ eStyleAnimType_float) // XXX bug 3935
+CSS_PROP_XUL(
+ -moz-box-ordinal-group,
+ box_ordinal_group,
+ CSS_PROP_DOMPROP_PREFIXED(BoxOrdinalGroup),
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ VARIANT_HI,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_XUL(
+ -moz-box-orient,
+ box_orient,
+ CSS_PROP_DOMPROP_PREFIXED(BoxOrient),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kBoxOrientKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 3935
+CSS_PROP_XUL(
+ -moz-box-pack,
+ box_pack,
+ CSS_PROP_DOMPROP_PREFIXED(BoxPack),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kBoxPackKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 3935
+CSS_PROP_EFFECTS(
+ box-shadow,
+ box_shadow,
+ BoxShadow,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ // NOTE: some components must be nonnegative
+ "",
+ 0,
+ kBoxShadowTypeKTable,
+ offsetof(nsStyleEffects, mBoxShadow),
+ eStyleAnimType_Shadow)
+CSS_PROP_POSITION(
+ box-sizing,
+ box_sizing,
+ BoxSizing,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kBoxSizingKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TABLEBORDER(
+ caption-side,
+ caption_side,
+ CaptionSide,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kCaptionSideKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ clear,
+ clear,
+ Clear,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kClearKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_EFFECTS(
+ clip,
+ clip,
+ Clip,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleEffects, mClip),
+ eStyleAnimType_Custom)
+CSS_PROP_SVGRESET(
+ clip-path,
+ clip_path,
+ ClipPath,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_SVG(
+ clip-rule,
+ clip_rule,
+ ClipRule,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kFillRuleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_COLOR(
+ color,
+ color,
+ Color,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED |
+ CSS_PROPERTY_HASHLESS_COLOR_QUIRK,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleColor, mColor),
+ eStyleAnimType_Color)
+CSS_PROP_VISIBILITY(
+ color-adjust,
+ color_adjust,
+ ColorAdjust,
+ CSS_PROPERTY_PARSE_VALUE,
+ "layout.css.color-adjust.enabled",
+ VARIANT_HK,
+ kColorAdjustKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVG(
+ color-interpolation,
+ color_interpolation,
+ ColorInterpolation,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kColorInterpolationKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVG(
+ color-interpolation-filters,
+ color_interpolation_filters,
+ ColorInterpolationFilters,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kColorInterpolationKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_COLUMN(
+ column-count,
+ column_count,
+ ColumnCount,
+ CSS_PROPERTY_PARSE_VALUE |
+ // Need to reject 0 in addition to negatives. If we accept 0, we
+ // need to change NS_STYLE_COLUMN_COUNT_AUTO to something else.
+ CSS_PROPERTY_VALUE_AT_LEAST_ONE,
+ "",
+ VARIANT_AHI,
+ nullptr,
+ offsetof(nsStyleColumn, mColumnCount),
+ eStyleAnimType_Custom)
+CSS_PROP_COLUMN(
+ column-fill,
+ column_fill,
+ ColumnFill,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kColumnFillKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_COLUMN(
+ column-gap,
+ column_gap,
+ ColumnGap,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ VARIANT_HL | VARIANT_NORMAL | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleColumn, mColumnGap),
+ eStyleAnimType_Coord)
+CSS_PROP_SHORTHAND(
+ column-rule,
+ column_rule,
+ ColumnRule,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_COLUMN(
+ column-rule-color,
+ column_rule_color,
+ ColumnRuleColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleColumn, mColumnRuleColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_COLUMN(
+ column-rule-style,
+ column_rule_style,
+ ColumnRuleStyle,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kBorderStyleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_COLUMN(
+ column-rule-width,
+ column_rule_width,
+ ColumnRuleWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_COLUMN(
+ column-width,
+ column_width,
+ ColumnWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ VARIANT_AHL | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleColumn, mColumnWidth),
+ eStyleAnimType_Coord)
+CSS_PROP_SHORTHAND(
+ columns,
+ columns,
+ Columns,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_DISPLAY(
+ contain,
+ contain,
+ Contain,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_FIXPOS_CB,
+ "layout.css.contain.enabled",
+ // Does not affect parsing, but is needed for tab completion in devtools:
+ VARIANT_HK | VARIANT_NONE,
+ kContainKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_CONTENT(
+ content,
+ content,
+ Content,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_START_IMAGE_LOADS,
+ "",
+ 0,
+ kContentKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_TEXT(
+ -moz-control-character-visibility,
+ _moz_control_character_visibility,
+ CSS_PROP_DOMPROP_PREFIXED(ControlCharacterVisibility),
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kControlCharacterVisibilityKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_CONTENT(
+ counter-increment,
+ counter_increment,
+ CounterIncrement,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 137285
+CSS_PROP_CONTENT(
+ counter-reset,
+ counter_reset,
+ CounterReset,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 137285
+CSS_PROP_USERINTERFACE(
+ cursor,
+ cursor,
+ Cursor,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_START_IMAGE_LOADS |
+ CSS_PROPERTY_IMAGE_IS_IN_ARRAY_0,
+ "",
+ 0,
+ kCursorKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+CSS_PROP_VISIBILITY(
+ direction,
+ direction,
+ Direction,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kDirectionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#endif // !defined(CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND)
+CSS_PROP_DISPLAY(
+ display,
+ display,
+ Display,
+ CSS_PROPERTY_PARSE_VALUE |
+ // This is allowed because we need to make the placeholder
+ // pseudo-element an inline-block in the UA stylesheet. It is a block
+ // by default.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK,
+ kDisplayKTable,
+ offsetof(nsStyleDisplay, mDisplay),
+ eStyleAnimType_None)
+CSS_PROP_SVGRESET(
+ dominant-baseline,
+ dominant_baseline,
+ DominantBaseline,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kDominantBaselineKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TABLEBORDER(
+ empty-cells,
+ empty_cells,
+ EmptyCells,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kEmptyCellsKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVG(
+ fill,
+ fill,
+ Fill,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ 0,
+ kContextPatternKTable,
+ offsetof(nsStyleSVG, mFill),
+ eStyleAnimType_PaintServer)
+CSS_PROP_SVG(
+ fill-opacity,
+ fill_opacity,
+ FillOpacity,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HN | VARIANT_OPENTYPE_SVG_KEYWORD,
+ kContextOpacityKTable,
+ offsetof(nsStyleSVG, mFillOpacity),
+ eStyleAnimType_float)
+CSS_PROP_SVG(
+ fill-rule,
+ fill_rule,
+ FillRule,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kFillRuleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_EFFECTS(
+ filter,
+ filter,
+ Filter,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+ CSS_PROPERTY_FIXPOS_CB,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_SHORTHAND(
+ flex,
+ flex,
+ Flex,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_POSITION(
+ flex-basis,
+ flex_basis,
+ FlexBasis,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ // NOTE: The parsing implementation for the 'flex' shorthand property has
+ // its own code to parse each subproperty. It does not depend on the
+ // longhand parsing defined here.
+ VARIANT_AHKLP | VARIANT_CALC,
+ kWidthKTable,
+ offsetof(nsStylePosition, mFlexBasis),
+ eStyleAnimType_Coord)
+CSS_PROP_POSITION(
+ flex-direction,
+ flex_direction,
+ FlexDirection,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kFlexDirectionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ flex-flow,
+ flex_flow,
+ FlexFlow,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_POSITION(
+ flex-grow,
+ flex_grow,
+ FlexGrow,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ // NOTE: The parsing implementation for the 'flex' shorthand property has
+ // its own code to parse each subproperty. It does not depend on the
+ // longhand parsing defined here.
+ VARIANT_HN,
+ nullptr,
+ offsetof(nsStylePosition, mFlexGrow),
+ eStyleAnimType_float)
+CSS_PROP_POSITION(
+ flex-shrink,
+ flex_shrink,
+ FlexShrink,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ // NOTE: The parsing implementation for the 'flex' shorthand property has
+ // its own code to parse each subproperty. It does not depend on the
+ // longhand parsing defined here.
+ VARIANT_HN,
+ nullptr,
+ offsetof(nsStylePosition, mFlexShrink),
+ eStyleAnimType_float)
+CSS_PROP_POSITION(
+ flex-wrap,
+ flex_wrap,
+ FlexWrap,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kFlexWrapKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ float,
+ float_,
+ CSS_PROP_PUBLIC_OR_PRIVATE(CssFloat, Float),
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "",
+ VARIANT_HK,
+ kFloatKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_BORDER(
+ -moz-float-edge,
+ float_edge,
+ CSS_PROP_DOMPROP_PREFIXED(FloatEdge),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kFloatEdgeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 3935
+CSS_PROP_SVGRESET(
+ flood-color,
+ flood_color,
+ FloodColor,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleSVGReset, mFloodColor),
+ eStyleAnimType_Color)
+CSS_PROP_SVGRESET(
+ flood-opacity,
+ flood_opacity,
+ FloodOpacity,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HN,
+ nullptr,
+ offsetof(nsStyleSVGReset, mFloodOpacity),
+ eStyleAnimType_float)
+CSS_PROP_SHORTHAND(
+ font,
+ font,
+ Font,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_FONT(
+ font-family,
+ font_family,
+ FontFamily,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-feature-settings,
+ font_feature_settings,
+ FontFeatureSettings,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-kerning,
+ font_kerning,
+ FontKerning,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK,
+ kFontKerningKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-language-override,
+ font_language_override,
+ FontLanguageOverride,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_NORMAL | VARIANT_INHERIT | VARIANT_STRING,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-size,
+ font_size,
+ FontSize,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "",
+ VARIANT_HKLP | VARIANT_SYSFONT | VARIANT_CALC,
+ kFontSizeKTable,
+ // Note that mSize is the correct place for *reading* the computed value,
+ // but setting it requires setting mFont.size as well.
+ offsetof(nsStyleFont, mSize),
+ eStyleAnimType_nscoord)
+CSS_PROP_FONT(
+ font-size-adjust,
+ font_size_adjust,
+ FontSizeAdjust,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HON | VARIANT_SYSFONT,
+ nullptr,
+ offsetof(nsStyleFont, mFont.sizeAdjust),
+ eStyleAnimType_float)
+CSS_PROP_FONT(
+ font-stretch,
+ font_stretch,
+ FontStretch,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK | VARIANT_SYSFONT,
+ kFontStretchKTable,
+ offsetof(nsStyleFont, mFont.stretch),
+ eStyleAnimType_Custom)
+CSS_PROP_FONT(
+ font-style,
+ font_style,
+ FontStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK | VARIANT_SYSFONT,
+ kFontStyleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-synthesis,
+ font_synthesis,
+ FontSynthesis,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ kFontSynthesisKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ font-variant,
+ font_variant,
+ FontVariant,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_FONT(
+ font-variant-alternates,
+ font_variant_alternates,
+ FontVariantAlternates,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ kFontVariantAlternatesKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-variant-caps,
+ font_variant_caps,
+ FontVariantCaps,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HMK,
+ kFontVariantCapsKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-variant-east-asian,
+ font_variant_east_asian,
+ FontVariantEastAsian,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ kFontVariantEastAsianKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-variant-ligatures,
+ font_variant_ligatures,
+ FontVariantLigatures,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ kFontVariantLigaturesKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-variant-numeric,
+ font_variant_numeric,
+ FontVariantNumeric,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ kFontVariantNumericKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-variant-position,
+ font_variant_position,
+ FontVariantPosition,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HMK,
+ kFontVariantPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ font-weight,
+ font_weight,
+ FontWeight,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ // NOTE: This property has range restrictions on interpolation!
+ "",
+ 0,
+ kFontWeightKTable,
+ offsetof(nsStyleFont, mFont.weight),
+ eStyleAnimType_Custom)
+CSS_PROP_UIRESET(
+ -moz-force-broken-image-icon,
+ force_broken_image_icon,
+ CSS_PROP_DOMPROP_PREFIXED(ForceBrokenImageIcon),
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ VARIANT_HI,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // bug 58646
+CSS_PROP_SHORTHAND(
+ grid,
+ grid,
+ Grid,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled")
+CSS_PROP_SHORTHAND(
+ grid-area,
+ grid_area,
+ GridArea,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled")
+CSS_PROP_POSITION(
+ grid-auto-columns,
+ grid_auto_columns,
+ GridAutoColumns,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.grid.enabled",
+ 0,
+ kGridTrackBreadthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ grid-auto-flow,
+ grid_auto_flow,
+ GridAutoFlow,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.grid.enabled",
+ 0,
+ kGridAutoFlowKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ grid-auto-rows,
+ grid_auto_rows,
+ GridAutoRows,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.grid.enabled",
+ 0,
+ kGridTrackBreadthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ grid-column,
+ grid_column,
+ GridColumn,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled")
+CSS_PROP_POSITION(
+ grid-column-end,
+ grid_column_end,
+ GridColumnEnd,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ grid-column-gap,
+ grid_column_gap,
+ GridColumnGap,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.grid.enabled",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePosition, mGridColumnGap),
+ eStyleAnimType_Coord)
+CSS_PROP_POSITION(
+ grid-column-start,
+ grid_column_start,
+ GridColumnStart,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ grid-gap,
+ grid_gap,
+ GridGap,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled")
+CSS_PROP_SHORTHAND(
+ grid-row,
+ grid_row,
+ GridRow,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled")
+CSS_PROP_POSITION(
+ grid-row-end,
+ grid_row_end,
+ GridRowEnd,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ grid-row-gap,
+ grid_row_gap,
+ GridRowGap,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.grid.enabled",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePosition, mGridRowGap),
+ eStyleAnimType_Coord)
+CSS_PROP_POSITION(
+ grid-row-start,
+ grid_row_start,
+ GridRowStart,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ grid-template,
+ grid_template,
+ GridTemplate,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.grid.enabled")
+CSS_PROP_POSITION(
+ grid-template-areas,
+ grid_template_areas,
+ GridTemplateAreas,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.grid.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ grid-template-columns,
+ grid_template_columns,
+ GridTemplateColumns,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.grid.enabled",
+ 0,
+ kGridTrackBreadthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ grid-template-rows,
+ grid_template_rows,
+ GridTemplateRows,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.grid.enabled",
+ 0,
+ kGridTrackBreadthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ height,
+ height,
+ Height,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHKLP | VARIANT_CALC,
+ kWidthKTable,
+ offsetof(nsStylePosition, mHeight),
+ eStyleAnimType_Coord)
+CSS_PROP_TEXT(
+ hyphens,
+ hyphens,
+ Hyphens,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kHyphensKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXTRESET(
+ initial-letter,
+ initial_letter,
+ InitialLetter,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "layout.css.initial-letter.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_VISIBILITY(
+ image-orientation,
+ image_orientation,
+ ImageOrientation,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION,
+ "layout.css.image-orientation.enabled",
+ 0,
+ kImageOrientationKTable,
+ offsetof(nsStyleVisibility, mImageOrientation),
+ eStyleAnimType_Discrete)
+CSS_PROP_LIST(
+ -moz-image-region,
+ image_region,
+ CSS_PROP_DOMPROP_PREFIXED(ImageRegion),
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleList, mImageRegion),
+ eStyleAnimType_Custom)
+CSS_PROP_VISIBILITY(
+ image-rendering,
+ image_rendering,
+ ImageRendering,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kImageRenderingKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_UIRESET(
+ ime-mode,
+ ime_mode,
+ ImeMode,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kIMEModeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_LOGICAL(
+ inline-size,
+ inline_size,
+ InlineSize,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_AXIS,
+ "",
+ VARIANT_AHKLP | VARIANT_CALC,
+ kWidthKTable,
+ Size,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ isolation,
+ isolation,
+ Isolation,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT,
+ "layout.css.isolation.enabled",
+ VARIANT_HK,
+ kIsolationKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ justify-content,
+ justify_content,
+ JustifyContent,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ VARIANT_HK,
+ kAutoCompletionAlignJustifyContent,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ justify-items,
+ justify_items,
+ JustifyItems,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ VARIANT_HK,
+ // for auto-completion we use same values as justify-self:
+ kAutoCompletionAlignJustifySelf,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ justify-self,
+ justify_self,
+ JustifySelf,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ VARIANT_HK,
+ kAutoCompletionAlignJustifySelf,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_FONT(
+ -x-lang,
+ _x_lang,
+ Lang,
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_INACCESSIBLE,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+#endif // CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+CSS_PROP_POSITION(
+ left,
+ left,
+ Left,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePosition, mOffset),
+ eStyleAnimType_Sides_Left)
+CSS_PROP_TEXT(
+ letter-spacing,
+ letter_spacing,
+ LetterSpacing,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "",
+ VARIANT_HL | VARIANT_NORMAL | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleText, mLetterSpacing),
+ eStyleAnimType_Coord)
+CSS_PROP_SVGRESET(
+ lighting-color,
+ lighting_color,
+ LightingColor,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleSVGReset, mLightingColor),
+ eStyleAnimType_Color)
+CSS_PROP_TEXT(
+ line-height,
+ line_height,
+ LineHeight,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_HLPN | VARIANT_KEYWORD | VARIANT_NORMAL | VARIANT_SYSFONT | VARIANT_CALC,
+ kLineHeightKTable,
+ offsetof(nsStyleText, mLineHeight),
+ eStyleAnimType_Coord)
+CSS_PROP_SHORTHAND(
+ list-style,
+ list_style,
+ ListStyle,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_LIST(
+ list-style-image,
+ list_style_image,
+ ListStyleImage,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_START_IMAGE_LOADS,
+ "",
+ VARIANT_HUO,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_LIST(
+ list-style-position,
+ list_style_position,
+ ListStylePosition,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kListStylePositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_LIST(
+ list-style-type,
+ list_style_type,
+ ListStyleType,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ margin,
+ margin,
+ Margin,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
+ "")
+CSS_PROP_LOGICAL(
+ margin-block-end,
+ margin_block_end,
+ MarginBlockEnd,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ Margin,
+ Margin,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ margin-block-start,
+ margin_block_start,
+ MarginBlockStart,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ Margin,
+ Margin,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_MARGIN(
+ margin-bottom,
+ margin_bottom,
+ MarginBottom,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleMargin, mMargin),
+ eStyleAnimType_Sides_Bottom)
+CSS_PROP_LOGICAL(
+ margin-inline-end,
+ margin_inline_end,
+ MarginInlineEnd,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ Margin,
+ Margin,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ margin-inline-start,
+ margin_inline_start,
+ MarginInlineStart,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE |
+ CSS_PROPERTY_LOGICAL,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ Margin,
+ Margin,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_MARGIN(
+ margin-left,
+ margin_left,
+ MarginLeft,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleMargin, mMargin),
+ eStyleAnimType_Sides_Left)
+CSS_PROP_MARGIN(
+ margin-right,
+ margin_right,
+ MarginRight,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleMargin, mMargin),
+ eStyleAnimType_Sides_Right)
+CSS_PROP_MARGIN(
+ margin-top,
+ margin_top,
+ MarginTop,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_APPLIES_TO_PAGE_RULE |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleMargin, mMargin),
+ eStyleAnimType_Sides_Top)
+CSS_PROP_SHORTHAND(
+ marker,
+ marker,
+ Marker,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_SVG(
+ marker-end,
+ marker_end,
+ MarkerEnd,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HUO,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVG(
+ marker-mid,
+ marker_mid,
+ MarkerMid,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HUO,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVG(
+ marker-start,
+ marker_start,
+ MarkerStart,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HUO,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#ifndef MOZ_ENABLE_MASK_AS_SHORTHAND
+CSS_PROP_SVGRESET(
+ mask,
+ mask,
+ Mask,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT,
+ "",
+ VARIANT_HUO,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#else
+CSS_PROP_SHORTHAND(
+ mask,
+ mask,
+ Mask,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_SVGRESET(
+ mask-clip,
+ mask_clip,
+ MaskClip,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kImageLayerOriginKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVGRESET(
+ mask-composite,
+ mask_composite,
+ MaskComposite,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kImageLayerCompositeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVGRESET(
+ mask-image,
+ mask_image,
+ MaskImage,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_START_IMAGE_LOADS,
+ "",
+ VARIANT_IMAGE, // used by list parsing
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVGRESET(
+ mask-mode,
+ mask_mode,
+ MaskMode,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kImageLayerModeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVGRESET(
+ mask-origin,
+ mask_origin,
+ MaskOrigin,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kImageLayerOriginKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ mask-position,
+ mask_position,
+ MaskPosition,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "")
+CSS_PROP_SVGRESET(
+ mask-position-x,
+ mask_position_x,
+ MaskPositionX,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ kImageLayerPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_SVGRESET(
+ mask-position-y,
+ mask_position_y,
+ MaskPositionY,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ kImageLayerPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_SVGRESET(
+ mask-repeat,
+ mask_repeat,
+ MaskRepeat,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD, // used by list parsing
+ kImageLayerRepeatKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVGRESET(
+ mask-size,
+ mask_size,
+ MaskSize,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ kImageLayerSizeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+#endif // MOZ_ENABLE_MASK_AS_SHORTHAND
+CSS_PROP_SVGRESET(
+ mask-type,
+ mask_type,
+ MaskType,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kMaskTypeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_FONT(
+ -moz-math-display,
+ math_display,
+ MathDisplay,
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS |
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kMathDisplayKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_FONT(
+ -moz-math-variant,
+ math_variant,
+ MathVariant,
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_INACCESSIBLE,
+ "",
+ VARIANT_HK,
+ kMathVariantKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+#endif // CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+CSS_PROP_LOGICAL(
+ max-block-size,
+ max_block_size,
+ MaxBlockSize,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_AXIS |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
+ "",
+ VARIANT_HLPO | VARIANT_CALC,
+ nullptr,
+ MaxSize,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_POSITION(
+ max-height,
+ max_height,
+ MaxHeight,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "",
+ VARIANT_HKLPO | VARIANT_CALC,
+ kWidthKTable,
+ offsetof(nsStylePosition, mMaxHeight),
+ eStyleAnimType_Coord)
+CSS_PROP_LOGICAL(
+ max-inline-size,
+ max_inline_size,
+ MaxInlineSize,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_AXIS,
+ "",
+ VARIANT_HKLPO | VARIANT_CALC,
+ kWidthKTable,
+ MaxSize,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_POSITION(
+ max-width,
+ max_width,
+ MaxWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "",
+ VARIANT_HKLPO | VARIANT_CALC,
+ kWidthKTable,
+ offsetof(nsStylePosition, mMaxWidth),
+ eStyleAnimType_Coord)
+CSS_PROP_LOGICAL(
+ min-block-size,
+ min_block_size,
+ MinBlockSize,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_AXIS |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ MinSize,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_FONT(
+ -moz-min-font-size-ratio,
+ _moz_min_font_size_ratio,
+ CSS_PROP_DOMPROP_PREFIXED(MinFontSizeRatio),
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "",
+ VARIANT_INHERIT | VARIANT_PERCENT,
+ nullptr,
+ offsetof(nsStyleFont, mMinFontSizeRatio),
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_POSITION(
+ min-height,
+ min_height,
+ MinHeight,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "",
+ VARIANT_AHKLP | VARIANT_CALC,
+ kWidthKTable,
+ offsetof(nsStylePosition, mMinHeight),
+ eStyleAnimType_Coord)
+CSS_PROP_LOGICAL(
+ min-inline-size,
+ min_inline_size,
+ MinInlineSize,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_AXIS,
+ "",
+ VARIANT_AHKLP | VARIANT_CALC,
+ kWidthKTable,
+ MinSize,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_POSITION(
+ min-width,
+ min_width,
+ MinWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "",
+ VARIANT_AHKLP | VARIANT_CALC,
+ kWidthKTable,
+ offsetof(nsStylePosition, mMinWidth),
+ eStyleAnimType_Coord)
+CSS_PROP_EFFECTS(
+ mix-blend-mode,
+ mix_blend_mode,
+ MixBlendMode,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT,
+ "layout.css.mix-blend-mode.enabled",
+ VARIANT_HK,
+ kBlendModeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ object-fit,
+ object_fit,
+ ObjectFit,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.object-fit-and-position.enabled",
+ VARIANT_HK,
+ kObjectFitKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ object-position,
+ object_position,
+ ObjectPosition,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "layout.css.object-fit-and-position.enabled",
+ 0,
+ kImageLayerPositionKTable,
+ offsetof(nsStylePosition, mObjectPosition),
+ eStyleAnimType_Custom)
+CSS_PROP_LOGICAL(
+ offset-block-end,
+ offset_block_end,
+ OffsetBlockEnd,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ Offset,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ offset-block-start,
+ offset_block_start,
+ OffsetBlockStart,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ Offset,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ offset-inline-end,
+ offset_inline_end,
+ OffsetInlineEnd,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ Offset,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ offset-inline-start,
+ offset_inline_start,
+ OffsetInlineStart,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ Offset,
+ Position,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_EFFECTS(
+ opacity,
+ opacity,
+ Opacity,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT,
+ "",
+ VARIANT_HN,
+ nullptr,
+ offsetof(nsStyleEffects, mOpacity),
+ eStyleAnimType_float)
+CSS_PROP_POSITION(
+ order,
+ order,
+ Order,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HI,
+ nullptr,
+ offsetof(nsStylePosition, mOrder),
+ eStyleAnimType_Custom) // <integer>
+CSS_PROP_DISPLAY(
+ -moz-orient,
+ orient,
+ CSS_PROP_DOMPROP_PREFIXED(Orient),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kOrientKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_FONT(
+ -moz-osx-font-smoothing,
+ osx_font_smoothing,
+ CSS_PROP_DOMPROP_PREFIXED(OsxFontSmoothing),
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "layout.css.osx-font-smoothing.enabled",
+ VARIANT_HK,
+ kFontSmoothingKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ outline,
+ outline,
+ Outline,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_OUTLINE(
+ outline-color,
+ outline_color,
+ OutlineColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleOutline, mOutlineColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_OUTLINE(
+ outline-offset,
+ outline_offset,
+ OutlineOffset,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HL | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleOutline, mOutlineOffset),
+ eStyleAnimType_nscoord)
+CSS_PROP_SHORTHAND(
+ -moz-outline-radius,
+ _moz_outline_radius,
+ CSS_PROP_DOMPROP_PREFIXED(OutlineRadius),
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_OUTLINE(
+ -moz-outline-radius-bottomleft,
+ _moz_outline_radius_bottomLeft,
+ CSS_PROP_DOMPROP_PREFIXED(OutlineRadiusBottomleft),
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleOutline, mOutlineRadius),
+ eStyleAnimType_Corner_BottomLeft)
+CSS_PROP_OUTLINE(
+ -moz-outline-radius-bottomright,
+ _moz_outline_radius_bottomRight,
+ CSS_PROP_DOMPROP_PREFIXED(OutlineRadiusBottomright),
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleOutline, mOutlineRadius),
+ eStyleAnimType_Corner_BottomRight)
+CSS_PROP_OUTLINE(
+ -moz-outline-radius-topleft,
+ _moz_outline_radius_topLeft,
+ CSS_PROP_DOMPROP_PREFIXED(OutlineRadiusTopleft),
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleOutline, mOutlineRadius),
+ eStyleAnimType_Corner_TopLeft)
+CSS_PROP_OUTLINE(
+ -moz-outline-radius-topright,
+ _moz_outline_radius_topRight,
+ CSS_PROP_DOMPROP_PREFIXED(OutlineRadiusTopright),
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleOutline, mOutlineRadius),
+ eStyleAnimType_Corner_TopRight)
+CSS_PROP_OUTLINE(
+ outline-style,
+ outline_style,
+ OutlineStyle,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kOutlineStyleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_OUTLINE(
+ outline-width,
+ outline_width,
+ OutlineWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ offsetof(nsStyleOutline, mOutlineWidth),
+ eStyleAnimType_Coord)
+CSS_PROP_SHORTHAND(
+ overflow,
+ overflow,
+ Overflow,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_DISPLAY(
+ overflow-clip-box,
+ overflow_clip_box,
+ OverflowClipBox,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "layout.css.overflow-clip-box.enabled",
+ VARIANT_HK,
+ kOverflowClipBoxKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ overflow-x,
+ overflow_x,
+ OverflowX,
+ CSS_PROPERTY_PARSE_VALUE |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK,
+ kOverflowSubKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ overflow-y,
+ overflow_y,
+ OverflowY,
+ CSS_PROPERTY_PARSE_VALUE |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK,
+ kOverflowSubKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ padding,
+ padding,
+ Padding,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "")
+CSS_PROP_LOGICAL(
+ padding-block-end,
+ padding_block_end,
+ PaddingBlockEnd,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ Padding,
+ Padding,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ padding-block-start,
+ padding_block_start,
+ PaddingBlockStart,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
+ "",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ Padding,
+ Padding,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_PADDING(
+ padding-bottom,
+ padding_bottom,
+ PaddingBottom,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePadding, mPadding),
+ eStyleAnimType_Sides_Bottom)
+CSS_PROP_LOGICAL(
+ padding-inline-end,
+ padding_inline_end,
+ PaddingInlineEnd,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL |
+ CSS_PROPERTY_LOGICAL_END_EDGE,
+ "",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ Padding,
+ Padding,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_LOGICAL(
+ padding-inline-start,
+ padding_inline_start,
+ PaddingInlineStart,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_LOGICAL,
+ "",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ Padding,
+ Padding,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_PADDING(
+ padding-left,
+ padding_left,
+ PaddingLeft,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePadding, mPadding),
+ eStyleAnimType_Sides_Left)
+CSS_PROP_PADDING(
+ padding-right,
+ padding_right,
+ PaddingRight,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePadding, mPadding),
+ eStyleAnimType_Sides_Right)
+CSS_PROP_PADDING(
+ padding-top,
+ padding_top,
+ PaddingTop,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePadding, mPadding),
+ eStyleAnimType_Sides_Top)
+CSS_PROP_DISPLAY(
+ page-break-after,
+ page_break_after,
+ PageBreakAfter,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kPageBreakKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // temp fix for bug 24000
+CSS_PROP_DISPLAY(
+ page-break-before,
+ page_break_before,
+ PageBreakBefore,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kPageBreakKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // temp fix for bug 24000
+CSS_PROP_DISPLAY(
+ page-break-inside,
+ page_break_inside,
+ PageBreakInside,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kPageBreakInsideKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVG(
+ paint-order,
+ paint_order,
+ PaintOrder,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "svg.paint-order.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ perspective,
+ perspective,
+ Perspective,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+ CSS_PROPERTY_FIXPOS_CB,
+ "",
+ VARIANT_NONE | VARIANT_INHERIT | VARIANT_LENGTH |
+ VARIANT_NONNEGATIVE_DIMENSION,
+ nullptr,
+ offsetof(nsStyleDisplay, mChildPerspective),
+ eStyleAnimType_Coord)
+CSS_PROP_DISPLAY(
+ perspective-origin,
+ perspective_origin,
+ PerspectiveOrigin,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ 0,
+ kImageLayerPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_SHORTHAND(
+ place-content,
+ place_content,
+ PlaceContent,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_SHORTHAND(
+ place-items,
+ place_items,
+ PlaceItems,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_SHORTHAND(
+ place-self,
+ place_self,
+ PlaceSelf,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_USERINTERFACE(
+ pointer-events,
+ pointer_events,
+ PointerEvents,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK,
+ kPointerEventsKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ position,
+ position,
+ Position,
+ CSS_PROPERTY_PARSE_VALUE |
+ // For position: sticky/fixed
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+ CSS_PROPERTY_ABSPOS_CB,
+ "",
+ VARIANT_HK,
+ kPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_LIST(
+ quotes,
+ quotes,
+ Quotes,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ resize,
+ resize,
+ Resize,
+ CSS_PROPERTY_PARSE_VALUE |
+ // This is allowed because the UA stylesheet sets 'resize: both;' on
+ // textarea and we need to disable this for the placeholder
+ // pseudo-element.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK,
+ kResizeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ right,
+ right,
+ Right,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePosition, mOffset),
+ eStyleAnimType_Sides_Right)
+CSS_PROP_TEXT(
+ ruby-align,
+ ruby_align,
+ RubyAlign,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kRubyAlignKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ ruby-position,
+ ruby_position,
+ RubyPosition,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kRubyPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_FONT(
+ -moz-script-level,
+ script_level,
+ ScriptLevel,
+ // We only allow 'script-level' when unsafe rules are enabled, because
+ // otherwise it could interfere with rulenode optimizations if used in
+ // a non-MathML-enabled document.
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS |
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ // script-level can take Auto, Integer and Number values, but only Auto
+ // ("increment if parent is not in displaystyle") and Integer
+ // ("relative") values can be specified in a style sheet.
+ VARIANT_AHI,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_FONT(
+ -moz-script-min-size,
+ script_min_size,
+ ScriptMinSize,
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_INACCESSIBLE,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_FONT(
+ -moz-script-size-multiplier,
+ script_size_multiplier,
+ ScriptSizeMultiplier,
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_INACCESSIBLE,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+#endif // CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+CSS_PROP_DISPLAY(
+ scroll-behavior,
+ scroll_behavior,
+ ScrollBehavior,
+ CSS_PROPERTY_PARSE_VALUE,
+ "layout.css.scroll-behavior.property-enabled",
+ VARIANT_HK,
+ kScrollBehaviorKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ scroll-snap-coordinate,
+ scroll_snap_coordinate,
+ ScrollSnapCoordinate,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_STORES_CALC,
+ "layout.css.scroll-snap.enabled",
+ 0,
+ kImageLayerPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ scroll-snap-destination,
+ scroll_snap_destination,
+ ScrollSnapDestination,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_STORES_CALC,
+ "layout.css.scroll-snap.enabled",
+ 0,
+ kImageLayerPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ scroll-snap-points-x,
+ scroll_snap_points_x,
+ ScrollSnapPointsX,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_STORES_CALC,
+ "layout.css.scroll-snap.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ scroll-snap-points-y,
+ scroll_snap_points_y,
+ ScrollSnapPointsY,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_STORES_CALC,
+ "layout.css.scroll-snap.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ scroll-snap-type,
+ scroll_snap_type,
+ ScrollSnapType,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.scroll-snap.enabled")
+CSS_PROP_DISPLAY(
+ scroll-snap-type-x,
+ scroll_snap_type_x,
+ ScrollSnapTypeX,
+ CSS_PROPERTY_PARSE_VALUE,
+ "layout.css.scroll-snap.enabled",
+ VARIANT_HK,
+ kScrollSnapTypeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ scroll-snap-type-y,
+ scroll_snap_type_y,
+ ScrollSnapTypeY,
+ CSS_PROPERTY_PARSE_VALUE,
+ "layout.css.scroll-snap.enabled",
+ VARIANT_HK,
+ kScrollSnapTypeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ shape-outside,
+ shape_outside,
+ ShapeOutside,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+ "layout.css.shape-outside.enabled",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // FIXME: Bug 1289049 for adding animation support
+CSS_PROP_SVG(
+ shape-rendering,
+ shape_rendering,
+ ShapeRendering,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kShapeRenderingKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_TABLE(
+ -x-span,
+ _x_span,
+ Span,
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_INACCESSIBLE,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+#endif // CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+CSS_PROP_XUL(
+ -moz-stack-sizing,
+ stack_sizing,
+ CSS_PROP_DOMPROP_PREFIXED(StackSizing),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kStackSizingKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVGRESET(
+ stop-color,
+ stop_color,
+ StopColor,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleSVGReset, mStopColor),
+ eStyleAnimType_Color)
+CSS_PROP_SVGRESET(
+ stop-opacity,
+ stop_opacity,
+ StopOpacity,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HN,
+ nullptr,
+ offsetof(nsStyleSVGReset, mStopOpacity),
+ eStyleAnimType_float)
+CSS_PROP_SVG(
+ stroke,
+ stroke,
+ Stroke,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "",
+ 0,
+ kContextPatternKTable,
+ offsetof(nsStyleSVG, mStroke),
+ eStyleAnimType_PaintServer)
+CSS_PROP_SVG(
+ stroke-dasharray,
+ stroke_dasharray,
+ StrokeDasharray,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_NUMBERS_ARE_PIXELS,
+ // NOTE: Internal values have range restrictions.
+ "",
+ 0,
+ kStrokeContextValueKTable,
+ CSS_PROP_NO_OFFSET, /* property stored in 2 separate members */
+ eStyleAnimType_Custom)
+CSS_PROP_SVG(
+ stroke-dashoffset,
+ stroke_dashoffset,
+ StrokeDashoffset,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_NUMBERS_ARE_PIXELS,
+ "",
+ VARIANT_HLPN | VARIANT_OPENTYPE_SVG_KEYWORD,
+ kStrokeContextValueKTable,
+ offsetof(nsStyleSVG, mStrokeDashoffset),
+ eStyleAnimType_Coord)
+CSS_PROP_SVG(
+ stroke-linecap,
+ stroke_linecap,
+ StrokeLinecap,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kStrokeLinecapKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVG(
+ stroke-linejoin,
+ stroke_linejoin,
+ StrokeLinejoin,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kStrokeLinejoinKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SVG(
+ stroke-miterlimit,
+ stroke_miterlimit,
+ StrokeMiterlimit,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_AT_LEAST_ONE,
+ "",
+ VARIANT_HN,
+ nullptr,
+ offsetof(nsStyleSVG, mStrokeMiterlimit),
+ eStyleAnimType_float)
+CSS_PROP_SVG(
+ stroke-opacity,
+ stroke_opacity,
+ StrokeOpacity,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HN | VARIANT_OPENTYPE_SVG_KEYWORD,
+ kContextOpacityKTable,
+ offsetof(nsStyleSVG, mStrokeOpacity),
+ eStyleAnimType_float)
+CSS_PROP_SVG(
+ stroke-width,
+ stroke_width,
+ StrokeWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_NUMBERS_ARE_PIXELS,
+ "",
+ VARIANT_HLPN | VARIANT_OPENTYPE_SVG_KEYWORD,
+ kStrokeContextValueKTable,
+ offsetof(nsStyleSVG, mStrokeWidth),
+ eStyleAnimType_Coord)
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_FONT(
+ -x-system-font,
+ _x_system_font,
+ CSS_PROP_DOMPROP_PREFIXED(SystemFont),
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_INACCESSIBLE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ kFontKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_TEXT(
+ -moz-tab-size,
+ _moz_tab_size,
+ CSS_PROP_DOMPROP_PREFIXED(TabSize),
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE,
+ "",
+ VARIANT_HI,
+ nullptr,
+ offsetof(nsStyleText, mTabSize),
+ eStyleAnimType_Discrete)
+CSS_PROP_TABLE(
+ table-layout,
+ table_layout,
+ TableLayout,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kTableLayoutKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ text-align,
+ text_align,
+ TextAlign,
+ CSS_PROPERTY_PARSE_VALUE | CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ // When we support aligning on a string, we can parse text-align
+ // as a string....
+ VARIANT_HK /* | VARIANT_STRING */,
+ kTextAlignKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ text-align-last,
+ text_align_last,
+ TextAlignLast,
+ CSS_PROPERTY_PARSE_VALUE | CSS_PROPERTY_VALUE_PARSER_FUNCTION,
+ "",
+ VARIANT_HK,
+ kTextAlignLastKTable,
+ offsetof(nsStyleText, mTextAlignLast),
+ eStyleAnimType_Discrete)
+CSS_PROP_SVG(
+ text-anchor,
+ text_anchor,
+ TextAnchor,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kTextAnchorKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ text-combine-upright,
+ text_combine_upright,
+ TextCombineUpright,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION,
+ "layout.css.text-combine-upright.enabled",
+ 0,
+ kTextCombineUprightKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ text-decoration,
+ text_decoration,
+ TextDecoration,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_TEXTRESET(
+ text-decoration-color,
+ text_decoration_color,
+ TextDecorationColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleTextReset, mTextDecorationColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_TEXTRESET(
+ text-decoration-line,
+ text_decoration_line,
+ TextDecorationLine,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ kTextDecorationLineKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXTRESET(
+ text-decoration-style,
+ text_decoration_style,
+ TextDecorationStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK,
+ kTextDecorationStyleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ text-emphasis,
+ text_emphasis,
+ TextEmphasis,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_TEXT(
+ text-emphasis-color,
+ text_emphasis_color,
+ TextEmphasisColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleText, mTextEmphasisColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_TEXT(
+ text-emphasis-position,
+ text_emphasis_position,
+ TextEmphasisPosition,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION,
+ "",
+ 0,
+ kTextEmphasisPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ text-emphasis-style,
+ text_emphasis_style,
+ TextEmphasisStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ -webkit-text-fill-color,
+ _webkit_text_fill_color,
+ WebkitTextFillColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "layout.css.prefixes.webkit",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleText, mWebkitTextFillColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_TEXT(
+ text-indent,
+ text_indent,
+ TextIndent,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "",
+ VARIANT_HLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleText, mTextIndent),
+ eStyleAnimType_Coord)
+CSS_PROP_VISIBILITY(
+ text-orientation,
+ text_orientation,
+ TextOrientation,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kTextOrientationKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXTRESET(
+ text-overflow,
+ text_overflow,
+ TextOverflow,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ 0,
+ kTextOverflowKTable,
+ offsetof(nsStyleTextReset, mTextOverflow),
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ text-rendering,
+ text_rendering,
+ TextRendering,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kTextRenderingKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ text-shadow,
+ text_shadow,
+ TextShadow,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ // NOTE: some components must be nonnegative
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleText, mTextShadow),
+ eStyleAnimType_Shadow)
+CSS_PROP_TEXT(
+ -moz-text-size-adjust,
+ text_size_adjust,
+ CSS_PROP_DOMPROP_PREFIXED(TextSizeAdjust),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_AUTO | VARIANT_NONE | VARIANT_INHERIT,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ -webkit-text-stroke,
+ _webkit_text_stroke,
+ WebkitTextStroke,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "layout.css.prefixes.webkit")
+CSS_PROP_TEXT(
+ -webkit-text-stroke-color,
+ _webkit_text_stroke_color,
+ WebkitTextStrokeColor,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
+ "layout.css.prefixes.webkit",
+ VARIANT_HC,
+ nullptr,
+ offsetof(nsStyleText, mWebkitTextStrokeColor),
+ eStyleAnimType_ComplexColor)
+CSS_PROP_TEXT(
+ -webkit-text-stroke-width,
+ _webkit_text_stroke_width,
+ WebkitTextStrokeWidth,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "layout.css.prefixes.webkit",
+ VARIANT_HKL | VARIANT_CALC,
+ kBorderWidthKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ text-transform,
+ text_transform,
+ TextTransform,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK,
+ kTextTransformKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_FONT(
+ -x-text-zoom,
+ _x_text_zoom,
+ TextZoom,
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_INACCESSIBLE,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+#endif // CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+CSS_PROP_POSITION(
+ top,
+ top,
+ Top,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHLP | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStylePosition, mOffset),
+ eStyleAnimType_Sides_Top)
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_DISPLAY(
+ -moz-top-layer,
+ _moz_top_layer,
+ CSS_PROP_DOMPROP_PREFIXED(TopLayer),
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ "",
+ VARIANT_HK,
+ kTopLayerKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_DISPLAY(
+ touch-action,
+ touch_action,
+ TouchAction,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION,
+ "layout.css.touch_action.enabled",
+ VARIANT_HK,
+ kTouchActionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ transform,
+ transform,
+ Transform,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+ CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR |
+ CSS_PROPERTY_FIXPOS_CB,
+ "",
+ 0,
+ nullptr,
+ offsetof(nsStyleDisplay, mSpecifiedTransform),
+ eStyleAnimType_Custom)
+// This shorthand is essentially an alias, but it requires different
+// parsing rules, and it therefore implemented as a shorthand.
+CSS_PROP_SHORTHAND(
+ -moz-transform,
+ _moz_transform,
+ MozTransform,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_IS_ALIAS,
+ "layout.css.prefixes.transforms")
+CSS_PROP_DISPLAY(
+ transform-box,
+ transform_box,
+ TransformBox,
+ CSS_PROPERTY_PARSE_VALUE,
+ "svg.transform-box.enabled",
+ VARIANT_HK,
+ kTransformBoxKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_DISPLAY(
+ transform-origin,
+ transform_origin,
+ TransformOrigin,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ 0,
+ kImageLayerPositionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Custom)
+CSS_PROP_DISPLAY(
+ transform-style,
+ transform_style,
+ TransformStyle,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT |
+ CSS_PROPERTY_FIXPOS_CB,
+ "",
+ VARIANT_HK,
+ kTransformStyleKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_SHORTHAND(
+ transition,
+ transition,
+ Transition,
+ CSS_PROPERTY_PARSE_FUNCTION,
+ "")
+CSS_PROP_DISPLAY(
+ transition-delay,
+ transition_delay,
+ TransitionDelay,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_TIME, // used by list parsing
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ transition-duration,
+ transition_duration,
+ TransitionDuration,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_TIME | VARIANT_NONNEGATIVE_DIMENSION, // used by list parsing
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ transition-property,
+ transition_property,
+ TransitionProperty,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_IDENTIFIER | VARIANT_NONE | VARIANT_ALL, // used only in shorthand
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+CSS_PROP_DISPLAY(
+ transition-timing-function,
+ transition_timing_function,
+ TransitionTimingFunction,
+ CSS_PROPERTY_PARSE_VALUE_LIST |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ VARIANT_KEYWORD | VARIANT_TIMING_FUNCTION, // used by list parsing
+ kTransitionTimingFunctionKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+CSS_PROP_TEXTRESET(
+ unicode-bidi,
+ unicode_bidi,
+ UnicodeBidi,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kUnicodeBidiKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#endif // CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+CSS_PROP_USERINTERFACE(
+ -moz-user-focus,
+ user_focus,
+ CSS_PROP_DOMPROP_PREFIXED(UserFocus),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kUserFocusKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 3935
+CSS_PROP_USERINTERFACE(
+ -moz-user-input,
+ user_input,
+ CSS_PROP_DOMPROP_PREFIXED(UserInput),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kUserInputKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX ??? // XXX bug 3935
+CSS_PROP_USERINTERFACE(
+ -moz-user-modify,
+ user_modify,
+ CSS_PROP_DOMPROP_PREFIXED(UserModify),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kUserModifyKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 3935
+CSS_PROP_UIRESET(
+ -moz-user-select,
+ user_select,
+ CSS_PROP_DOMPROP_PREFIXED(UserSelect),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kUserSelectKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // XXX bug 3935
+CSS_PROP_SVGRESET(
+ vector-effect,
+ vector_effect,
+ VectorEffect,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kVectorEffectKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+// NOTE: vertical-align is only supposed to apply to :first-letter when
+// 'float' is 'none', but we don't worry about that since it has no
+// effect otherwise
+CSS_PROP_DISPLAY(
+ vertical-align,
+ vertical_align,
+ VerticalAlign,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+ "",
+ VARIANT_HKLP | VARIANT_CALC,
+ kVerticalAlignKTable,
+ offsetof(nsStyleDisplay, mVerticalAlign),
+ eStyleAnimType_Coord)
+CSS_PROP_VISIBILITY(
+ visibility,
+ visibility,
+ Visibility,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kVisibilityKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete) // reflow for collapse
+CSS_PROP_TEXT(
+ white-space,
+ white_space,
+ WhiteSpace,
+ CSS_PROPERTY_PARSE_VALUE |
+ // This is required by the UA stylesheet and can't be overridden.
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "",
+ VARIANT_HK,
+ kWhitespaceKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ width,
+ width,
+ Width,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_VALUE_NONNEGATIVE |
+ CSS_PROPERTY_STORES_CALC |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+ "",
+ VARIANT_AHKLP | VARIANT_CALC,
+ kWidthKTable,
+ offsetof(nsStylePosition, mWidth),
+ eStyleAnimType_Coord)
+CSS_PROP_DISPLAY(
+ will-change,
+ will_change,
+ WillChange,
+ CSS_PROPERTY_PARSE_FUNCTION |
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+ "",
+ 0,
+ nullptr,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_UIRESET(
+ -moz-window-dragging,
+ _moz_window_dragging,
+ CSS_PROP_DOMPROP_PREFIXED(WindowDragging),
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kWindowDraggingKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_UIRESET(
+ -moz-window-shadow,
+ _moz_window_shadow,
+ CSS_PROP_DOMPROP_PREFIXED(WindowShadow),
+ CSS_PROPERTY_INTERNAL |
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS_AND_CHROME,
+ "",
+ VARIANT_HK,
+ kWindowShadowKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_None)
+#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
+CSS_PROP_TEXT(
+ word-break,
+ word_break,
+ WordBreak,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kWordBreakKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_TEXT(
+ word-spacing,
+ word_spacing,
+ WordSpacing,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+ CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+ CSS_PROPERTY_STORES_CALC,
+ "",
+ VARIANT_HLP | VARIANT_NORMAL | VARIANT_CALC,
+ nullptr,
+ offsetof(nsStyleText, mWordSpacing),
+ eStyleAnimType_Coord)
+CSS_PROP_TEXT(
+ overflow-wrap,
+ overflow_wrap,
+ OverflowWrap,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kOverflowWrapKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_VISIBILITY(
+ writing-mode,
+ writing_mode,
+ WritingMode,
+ CSS_PROPERTY_PARSE_VALUE,
+ "",
+ VARIANT_HK,
+ kWritingModeKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
+CSS_PROP_POSITION(
+ z-index,
+ z_index,
+ ZIndex,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_CREATES_STACKING_CONTEXT,
+ "",
+ VARIANT_AHI,
+ nullptr,
+ offsetof(nsStylePosition, mZIndex),
+ eStyleAnimType_Coord)
+
+#ifdef USED_CSS_PROP
+
+#undef USED_CSS_PROP
+#undef CSS_PROP_FONT
+#undef CSS_PROP_COLOR
+#undef CSS_PROP_BACKGROUND
+#undef CSS_PROP_LIST
+#undef CSS_PROP_POSITION
+#undef CSS_PROP_TEXT
+#undef CSS_PROP_TEXTRESET
+#undef CSS_PROP_DISPLAY
+#undef CSS_PROP_VISIBILITY
+#undef CSS_PROP_CONTENT
+#undef CSS_PROP_USERINTERFACE
+#undef CSS_PROP_UIRESET
+#undef CSS_PROP_TABLE
+#undef CSS_PROP_TABLEBORDER
+#undef CSS_PROP_MARGIN
+#undef CSS_PROP_PADDING
+#undef CSS_PROP_BORDER
+#undef CSS_PROP_OUTLINE
+#undef CSS_PROP_XUL
+#undef CSS_PROP_COLUMN
+#undef CSS_PROP_SVG
+#undef CSS_PROP_SVGRESET
+#undef CSS_PROP_VARIABLES
+#undef CSS_PROP_EFFECTS
+
+#else /* !defined(USED_CSS_PROP) */
+
+#ifdef DEFINED_CSS_PROP_FONT
+#undef CSS_PROP_FONT
+#undef DEFINED_CSS_PROP_FONT
+#endif
+#ifdef DEFINED_CSS_PROP_COLOR
+#undef CSS_PROP_COLOR
+#undef DEFINED_CSS_PROP_COLOR
+#endif
+#ifdef DEFINED_CSS_PROP_BACKGROUND
+#undef CSS_PROP_BACKGROUND
+#undef DEFINED_CSS_PROP_BACKGROUND
+#endif
+#ifdef DEFINED_CSS_PROP_LIST
+#undef CSS_PROP_LIST
+#undef DEFINED_CSS_PROP_LIST
+#endif
+#ifdef DEFINED_CSS_PROP_POSITION
+#undef CSS_PROP_POSITION
+#undef DEFINED_CSS_PROP_POSITION
+#endif
+#ifdef DEFINED_CSS_PROP_TEXT
+#undef CSS_PROP_TEXT
+#undef DEFINED_CSS_PROP_TETEXTRESETT
+#endif
+#ifdef DEFINED_CSS_PROP_TEXTRESET
+#undef CSS_PROP_TEXTRESET
+#undef DEFINED_CSS_PROP_TEDISPLAYTRESET
+#endif
+#ifdef DEFINED_CSS_PROP_DISPLAY
+#undef CSS_PROP_DISPLAY
+#undef DEFINED_CSS_PROP_DISPLAY
+#endif
+#ifdef DEFINED_CSS_PROP_VISIBILITY
+#undef CSS_PROP_VISIBILITY
+#undef DEFINED_CSS_PROP_VISIBILITY
+#endif
+#ifdef DEFINED_CSS_PROP_CONTENT
+#undef CSS_PROP_CONTENT
+#undef DEFINED_CSS_PROP_CONTENT
+#endif
+#ifdef DEFINED_CSS_PROP_USERINTERFACE
+#undef CSS_PROP_USERINTERFACE
+#undef DEFINED_CSS_PROP_USERINTERFACE
+#endif
+#ifdef DEFINED_CSS_PROP_UIRESET
+#undef CSS_PROP_UIRESET
+#undef DEFINED_CSS_PROP_UIRESET
+#endif
+#ifdef DEFINED_CSS_PROP_TABLE
+#undef CSS_PROP_TABLE
+#undef DEFINED_CSS_PROP_TABLE
+#endif
+#ifdef DEFINED_CSS_PROP_TABLEBORDER
+#undef CSS_PROP_TABLEBORDER
+#undef DEFINED_CSS_PROP_TABLEBORDER
+#endif
+#ifdef DEFINED_CSS_PROP_MARGIN
+#undef CSS_PROP_MARGIN
+#undef DEFINED_CSS_PROP_MARGIN
+#endif
+#ifdef DEFINED_CSS_PROP_PADDING
+#undef CSS_PROP_PADDING
+#undef DEFINED_CSS_PROP_PADDING
+#endif
+#ifdef DEFINED_CSS_PROP_BORDER
+#undef CSS_PROP_BORDER
+#undef DEFINED_CSS_PROP_BORDER
+#endif
+#ifdef DEFINED_CSS_PROP_OUTLINE
+#undef CSS_PROP_OUTLINE
+#undef DEFINED_CSS_PROP_OUTLINE
+#endif
+#ifdef DEFINED_CSS_PROP_XUL
+#undef CSS_PROP_XUL
+#undef DEFINED_CSS_PROP_XUL
+#endif
+#ifdef DEFINED_CSS_PROP_COLUMN
+#undef CSS_PROP_COLUMN
+#undef DEFINED_CSS_PROP_COLUMN
+#endif
+#ifdef DEFINED_CSS_PROP_SVG
+#undef CSS_PROP_SVG
+#undef DEFINED_CSS_PROP_SVG
+#endif
+#ifdef DEFINED_CSS_PROP_SVGRESET
+#undef CSS_PROP_SVGRESET
+#undef DEFINED_CSS_PROP_SVGRESET
+#endif
+#ifdef DEFINED_CSS_PROP_VARIABLES
+#undef CSS_PROP_VARIABLES
+#undef DEFINED_CSS_PROP_VARIABLES
+#endif
+#ifdef DEFINED_CSS_PROP_EFFECTS
+#undef CSS_PROP_EFFECTS
+#undef DEFINED_CSS_PROP_EFFECTS
+#endif
+
+#endif /* !defined(USED_CSS_PROP) */
+
+#ifdef DEFINED_CSS_PROP_SHORTHAND
+#undef CSS_PROP_SHORTHAND
+#undef DEFINED_CSS_PROP_SHORTHAND
+#endif
+#ifdef DEFINED_CSS_PROP_LOGICAL
+#undef CSS_PROP_LOGICAL
+#undef DEFINED_CSS_PROP_LOGICAL
+#endif
+
+#undef CSS_PROP_DOMPROP_PREFIXED
diff --git a/layout/style/nsCSSPropLogicalGroupList.h b/layout/style/nsCSSPropLogicalGroupList.h
new file mode 100644
index 000000000..3d8a52bc9
--- /dev/null
+++ b/layout/style/nsCSSPropLogicalGroupList.h
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 groups of logical properties, for preprocessing
+ */
+
+// A logical property group is one that defines the corresponding physical
+// longhand properties that could be set by a given set of logical longhand
+// properties. For example, the logical property group for margin-block-start
+// (and the other three logical margin properties) is one that contains
+// margin-top, margin-right, margin-bottom and margin-left.
+//
+// Logical property groups are defined below using one of the following
+// macros, where the name_ argument must be capitalized LikeThis and
+// must not collide with the name of a property's DOM method (its
+// method_ in nsCSSPropList.h):
+//
+// CSS_PROP_LOGICAL_GROUP_SHORTHAND(name_)
+// Defines a logical property group whose corresponding physical
+// properties are those in a given shorthand. For example, the
+// logical property group for margin-{block,inline}-{start,end}
+// is defined by the margin shorthand. The name_ argument must
+// be the method_ name of the shorthand (so Margin rather than
+// margin).
+//
+// CSS_PROP_LOGICAL_GROUP_BOX(name_)
+// Defines a logical property group whose corresponding physical
+// properties are a set of four box properties which are not
+// already represented by an existing shorthand property. For
+// example, the logical property group for
+// offset-{block,inline}-{start,end} contains the top, right,
+// bottom and left physical properties, but there is no shorthand
+// that sets those four properties. A table must be defined in
+// nsCSSProps.cpp named g<name_>LogicalGroupTable containing the
+// four physical properties in top/right/bottom/left order.
+//
+// CSS_PROP_LOGICAL_GROUP_AXIS(name_)
+// Defines a logical property group whose corresponding physical
+// properties are a set of two axis-related properties. For
+// example, the logical property group for {block,inline}-size
+// contains the width and height properties. A table must be
+// defined in nCSSProps.cpp named g<name_>LogicalGroupTable
+// containing the two physical properties in vertical/horizontal
+// order, followed by an nsCSSProperty_UNKNOWN entry.
+
+CSS_PROP_LOGICAL_GROUP_SHORTHAND(BorderColor)
+CSS_PROP_LOGICAL_GROUP_SHORTHAND(BorderStyle)
+CSS_PROP_LOGICAL_GROUP_SHORTHAND(BorderWidth)
+CSS_PROP_LOGICAL_GROUP_SHORTHAND(Margin)
+CSS_PROP_LOGICAL_GROUP_AXIS(MaxSize)
+CSS_PROP_LOGICAL_GROUP_BOX(Offset)
+CSS_PROP_LOGICAL_GROUP_SHORTHAND(Padding)
+CSS_PROP_LOGICAL_GROUP_AXIS(MinSize)
+CSS_PROP_LOGICAL_GROUP_AXIS(Size)
diff --git a/layout/style/nsCSSPropertyID.h b/layout/style/nsCSSPropertyID.h
new file mode 100644
index 000000000..19f254006
--- /dev/null
+++ b/layout/style/nsCSSPropertyID.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* enum 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 nsCSSPropList.h
+
+ */
+enum nsCSSPropertyID {
+ eCSSProperty_UNKNOWN = -1,
+
+ #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ eCSSProperty_##id_,
+ #define CSS_PROP_LIST_INCLUDE_LOGICAL
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_LIST_INCLUDE_LOGICAL
+ #undef CSS_PROP
+
+ eCSSProperty_COUNT_no_shorthands,
+ // Make the count continue where it left off:
+ eCSSProperty_COUNT_DUMMY = eCSSProperty_COUNT_no_shorthands - 1,
+
+ #define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+ eCSSProperty_##id_,
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_SHORTHAND
+
+ eCSSProperty_COUNT,
+ // Make the count continue where it left off:
+ eCSSProperty_COUNT_DUMMY2 = eCSSProperty_COUNT - 1,
+
+ #define CSS_PROP_ALIAS(aliasname_, id_, method_, pref_) \
+ eCSSPropertyAlias_##method_,
+ #include "nsCSSPropAliasList.h"
+ #undef CSS_PROP_ALIAS
+
+ eCSSProperty_COUNT_with_aliases,
+ // Make the count continue where it left off:
+ eCSSProperty_COUNT_DUMMY3 = eCSSProperty_COUNT_with_aliases - 1,
+
+ // Some of the values below could probably overlap with each other
+ // if we had a need for them to do so.
+
+ // Extra values for use in the values of the 'transition-property'
+ // property.
+ eCSSPropertyExtra_no_properties,
+ eCSSPropertyExtra_all_properties,
+
+ // Extra dummy values for nsCSSParser internal use.
+ eCSSPropertyExtra_x_none_value,
+ eCSSPropertyExtra_x_auto_value,
+
+ // Extra value to represent custom properties (--*).
+ eCSSPropertyExtra_variable,
+
+ // Extra value for use in the DOM API's
+ eCSSProperty_DOM
+};
+
+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
+};
+
+enum nsCSSPropertyLogicalGroup {
+ eCSSPropertyLogicalGroup_UNKNOWN = -1,
+#define CSS_PROP_LOGICAL_GROUP_AXIS(name_) \
+ eCSSPropertyLogicalGroup_##name_,
+#define CSS_PROP_LOGICAL_GROUP_BOX(name_) \
+ eCSSPropertyLogicalGroup_##name_,
+#define CSS_PROP_LOGICAL_GROUP_SHORTHAND(name_) \
+ eCSSPropertyLogicalGroup_##name_,
+#include "nsCSSPropLogicalGroupList.h"
+#undef CSS_PROP_LOGICAL_GROUP_SHORTHAND
+#undef CSS_PROP_LOGICAL_GROUP_BOX
+#undef CSS_PROP_LOGICAL_GROUP_AXIS
+ eCSSPropertyLogicalGroup_COUNT
+};
+
+#endif /* nsCSSPropertyID_h___ */
diff --git a/layout/style/nsCSSPropertyIDSet.h b/layout/style/nsCSSPropertyIDSet.h
new file mode 100644
index 000000000..4de0471d1
--- /dev/null
+++ b/layout/style/nsCSSPropertyIDSet.h
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/ArrayUtils.h"
+#include "mozilla/PodOperations.h"
+
+#include "nsCSSPropertyID.h"
+#include <limits.h> // for CHAR_BIT
+
+/**
+ * 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:
+ nsCSSPropertyIDSet() { Empty(); }
+ // auto-generated copy-constructor OK
+
+ void AssertInSetRange(nsCSSPropertyID aProperty) const {
+ NS_ASSERTION(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(nsCSSPropertyID aProperty) const {
+ AssertInSetRange(aProperty);
+ size_t p = aProperty;
+ return (mProperties[p / kBitsInChunk] &
+ (property_set_type(1) << (p % kBitsInChunk))) != 0;
+ }
+
+ 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::PodEqual(mProperties, aOther.mProperties);
+ }
+
+ // Return a new nsCSSPropertyIDSet which is the inverse of this set.
+ nsCSSPropertyIDSet Invert() const {
+ nsCSSPropertyIDSet result;
+ for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
+ result.mProperties[i] = ~mProperties[i];
+ }
+ return result;
+ }
+
+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);
+ }
+
+private:
+ property_set_type mProperties[kChunkCount];
+};
+
+#endif /* !defined(nsCSSPropertyIDSet_h__) */
diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp
new file mode 100644
index 000000000..ec28d06f8
--- /dev/null
+++ b/layout/style/nsCSSProps.cpp
@@ -0,0 +1,3428 @@
+/* -*- 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/. */
+
+/*
+ * methods for dealing with CSS properties and tables of the keyword
+ * values they accept
+ */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCSSProps.h"
+#include "nsCSSKeywords.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleConsts.h"
+#include "nsIWidget.h"
+#include "nsThemeConstants.h" // For system widget appearance types
+
+#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for PlaybackDirection
+#include "mozilla/LookAndFeel.h" // for system colors
+
+#include "nsString.h"
+#include "nsStaticAtom.h"
+#include "nsStaticNameTable.h"
+
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+typedef nsCSSProps::KTableEntry KTableEntry;
+
+// By wrapping internal-only properties in this macro, we are not
+// exposing them in the CSSOM. Since currently it is not necessary to
+// allow accessing them in that way, it is easier and cheaper to just
+// do this rather than exposing them conditionally.
+#define CSS_PROP(name_, id_, method_, flags_, pref_, ...) \
+ static_assert(!((flags_) & CSS_PROPERTY_ENABLED_MASK) || pref_[0], \
+ "Internal-only property '" #name_ "' should be wrapped in " \
+ "#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL");
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#define CSS_PROP_LIST_EXCLUDE_INTERNAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_EXCLUDE_INTERNAL
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+
+#define CSS_PROP(name_, id_, method_, flags_, pref_, ...) \
+ static_assert(!((flags_) & CSS_PROPERTY_ENABLED_IN_CHROME) || \
+ ((flags_) & CSS_PROPERTY_ENABLED_IN_UA_SHEETS), \
+ "Property '" #name_ "' is enabled in chrome, so it should " \
+ "also be enabled in UA sheets");
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+
+// required to make the symbol external, so that TestCSSPropertyLookup.cpp can link with it
+extern const char* const kCSSRawProperties[];
+
+// define an array of all CSS properties
+const char* const kCSSRawProperties[eCSSProperty_COUNT_with_aliases] = {
+#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
+ stylestruct_, stylestructoffset_, animtype_) \
+ #name_,
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) #name_,
+#include "nsCSSPropList.h"
+#undef CSS_PROP_SHORTHAND
+#define CSS_PROP_ALIAS(aliasname_, id_, method_, pref_) #aliasname_,
+#include "nsCSSPropAliasList.h"
+#undef CSS_PROP_ALIAS
+};
+
+using namespace mozilla;
+
+static int32_t gPropertyTableRefCount;
+static nsStaticCaseInsensitiveNameTable* gPropertyTable;
+static nsStaticCaseInsensitiveNameTable* gFontDescTable;
+static nsStaticCaseInsensitiveNameTable* gCounterDescTable;
+static nsStaticCaseInsensitiveNameTable* gPredefinedCounterStyleTable;
+static nsDataHashtable<nsCStringHashKey,nsCSSPropertyID>* gPropertyIDLNameTable;
+
+/* static */ nsCSSPropertyID *
+ nsCSSProps::gShorthandsContainingTable[eCSSProperty_COUNT_no_shorthands];
+/* static */ nsCSSPropertyID* nsCSSProps::gShorthandsContainingPool = nullptr;
+
+static const char* const kCSSRawFontDescs[] = {
+#define CSS_FONT_DESC(name_, method_) #name_,
+#include "nsCSSFontDescList.h"
+#undef CSS_FONT_DESC
+};
+
+static const char* const kCSSRawCounterDescs[] = {
+#define CSS_COUNTER_DESC(name_, method_) #name_,
+#include "nsCSSCounterDescList.h"
+#undef CSS_COUNTER_DESC
+};
+
+static const char* const kCSSRawPredefinedCounterStyles[] = {
+ "none",
+ // 6 Simple Predefined Counter Styles
+ // 6.1 Numeric
+ "decimal", "decimal-leading-zero", "arabic-indic", "armenian",
+ "upper-armenian", "lower-armenian", "bengali", "cambodian", "khmer",
+ "cjk-decimal", "devanagari", "georgian", "gujarati", "gurmukhi", "hebrew",
+ "kannada", "lao", "malayalam", "mongolian", "myanmar", "oriya", "persian",
+ "lower-roman", "upper-roman", "tamil", "telugu", "thai", "tibetan",
+ // 6.2 Alphabetic
+ "lower-alpha", "lower-latin", "upper-alpha", "upper-latin",
+ "cjk-earthly-branch", "cjk-heavenly-stem", "lower-greek",
+ "hiragana", "hiragana-iroha", "katakana", "katakana-iroha",
+ // 6.3 Symbolic
+ "disc", "circle", "square", "disclosure-open", "disclosure-closed",
+ // 7 Complex Predefined Counter Styles
+ // 7.1 Longhand East Asian Counter Styles
+ // 7.1.1 Japanese
+ "japanese-informal", "japanese-formal",
+ // 7.1.2 Korean
+ "korean-hangul-formal", "korean-hanja-informal", "korean-hanja-formal",
+ // 7.1.3 Chinese
+ "simp-chinese-informal", "simp-chinese-formal",
+ "trad-chinese-informal", "trad-chinese-formal", "cjk-ideographic",
+ // 7.2 Ethiopic Numeric Counter Style
+ "ethiopic-numeric"
+};
+
+struct PropertyAndCount {
+ nsCSSPropertyID property;
+ uint32_t count;
+};
+
+static int
+SortPropertyAndCount(const void* s1, const void* s2, void *closure)
+{
+ const PropertyAndCount *pc1 = static_cast<const PropertyAndCount*>(s1);
+ const PropertyAndCount *pc2 = static_cast<const PropertyAndCount*>(s2);
+ // Primary sort by count (lowest to highest)
+ if (pc1->count != pc2->count)
+ return pc1->count - pc2->count;
+ // Secondary sort by property index (highest to lowest)
+ return pc2->property - pc1->property;
+}
+
+// We need eCSSAliasCount so we can make gAliases nonzero size when there
+// are no aliases.
+enum {
+ eCSSAliasCount = eCSSProperty_COUNT_with_aliases - eCSSProperty_COUNT
+};
+
+// The names are in kCSSRawProperties.
+static nsCSSPropertyID gAliases[eCSSAliasCount != 0 ? eCSSAliasCount : 1] = {
+#define CSS_PROP_ALIAS(aliasname_, propid_, aliasmethod_, pref_) \
+ eCSSProperty_##propid_ ,
+#include "nsCSSPropAliasList.h"
+#undef CSS_PROP_ALIAS
+};
+
+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::AddRefTable(void)
+{
+ if (0 == gPropertyTableRefCount++) {
+ MOZ_ASSERT(!gPropertyTable, "pre existing array!");
+ MOZ_ASSERT(!gFontDescTable, "pre existing array!");
+ MOZ_ASSERT(!gCounterDescTable, "pre existing array!");
+ MOZ_ASSERT(!gPredefinedCounterStyleTable, "pre existing array!");
+ MOZ_ASSERT(!gPropertyIDLNameTable, "pre existing array!");
+
+ gPropertyTable = CreateStaticTable(
+ kCSSRawProperties, eCSSProperty_COUNT_with_aliases);
+ gFontDescTable = CreateStaticTable(kCSSRawFontDescs, eCSSFontDesc_COUNT);
+ gCounterDescTable = CreateStaticTable(
+ kCSSRawCounterDescs, eCSSCounterDesc_COUNT);
+ gPredefinedCounterStyleTable = CreateStaticTable(
+ kCSSRawPredefinedCounterStyles,
+ ArrayLength(kCSSRawPredefinedCounterStyles));
+
+ gPropertyIDLNameTable = new nsDataHashtable<nsCStringHashKey,nsCSSPropertyID>;
+ for (nsCSSPropertyID p = nsCSSPropertyID(0);
+ size_t(p) < ArrayLength(kIDLNameTable);
+ p = nsCSSPropertyID(p + 1)) {
+ if (kIDLNameTable[p]) {
+ gPropertyIDLNameTable->Put(nsDependentCString(kIDLNameTable[p]), p);
+ }
+ }
+
+ BuildShorthandsContainingTable();
+
+ static bool prefObserversInited = false;
+ if (!prefObserversInited) {
+ prefObserversInited = true;
+
+ #define OBSERVE_PROP(pref_, id_) \
+ if (pref_[0]) { \
+ Preferences::AddBoolVarCache(&gPropertyEnabled[id_], \
+ pref_); \
+ }
+
+ #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ OBSERVE_PROP(pref_, eCSSProperty_##id_)
+ #define CSS_PROP_LIST_INCLUDE_LOGICAL
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_LIST_INCLUDE_LOGICAL
+ #undef CSS_PROP
+
+ #define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+ OBSERVE_PROP(pref_, eCSSProperty_##id_)
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_SHORTHAND
+
+ #define CSS_PROP_ALIAS(aliasname_, propid_, aliasmethod_, pref_) \
+ OBSERVE_PROP(pref_, eCSSPropertyAlias_##aliasmethod_)
+ #include "nsCSSPropAliasList.h"
+ #undef CSS_PROP_ALIAS
+
+ #undef OBSERVE_PROP
+ }
+
+#ifdef DEBUG
+ {
+ // Assert that if CSS_PROPERTY_ENABLED_IN_UA_SHEETS or
+ // CSS_PROPERTY_ENABLED_IN_CHROME is used on a shorthand property
+ // that all of its component longhands also have the flag.
+ static uint32_t flagsToCheck[] = {
+ CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
+ CSS_PROPERTY_ENABLED_IN_CHROME
+ };
+ for (nsCSSPropertyID shorthand = eCSSProperty_COUNT_no_shorthands;
+ shorthand < eCSSProperty_COUNT;
+ shorthand = nsCSSPropertyID(shorthand + 1)) {
+ for (size_t i = 0; i < ArrayLength(flagsToCheck); i++) {
+ uint32_t flag = flagsToCheck[i];
+ if (!nsCSSProps::PropHasFlags(shorthand, flag)) {
+ continue;
+ }
+ for (const nsCSSPropertyID* p =
+ nsCSSProps::SubpropertyEntryFor(shorthand);
+ *p != eCSSProperty_UNKNOWN;
+ ++p) {
+ MOZ_ASSERT(nsCSSProps::PropHasFlags(*p, flag),
+ "all subproperties of a property with a "
+ "CSS_PROPERTY_ENABLED_* flag must also have "
+ "the flag");
+ }
+ }
+ }
+
+ // Assert that CSS_PROPERTY_INTERNAL is used on properties in
+ // #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL sections of nsCSSPropList.h
+ // and on no others.
+ static nsCSSPropertyID nonInternalProperties[] = {
+ #define CSS_PROP(name_, id_, ...) eCSSProperty_##id_,
+ #define CSS_PROP_SHORTHAND(name_, id_, ...) eCSSProperty_##id_,
+ #define CSS_PROP_LIST_INCLUDE_LOGICAL
+ #define CSS_PROP_LIST_EXCLUDE_INTERNAL
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_LIST_EXCLUDE_INTERNAL
+ #undef CSS_PROP_LIST_INCLUDE_LOGICAL
+ #undef CSS_PROP_SHORTHAND
+ #undef CSS_PROP
+ };
+ MOZ_ASSERT(ArrayLength(nonInternalProperties) <= eCSSProperty_COUNT);
+
+ bool found[eCSSProperty_COUNT];
+ PodArrayZero(found);
+ for (nsCSSPropertyID p : nonInternalProperties) {
+ MOZ_ASSERT(!nsCSSProps::PropHasFlags(p, CSS_PROPERTY_INTERNAL),
+ "properties defined outside of #ifndef "
+ "CSS_PROP_LIST_EXCLUDE_INTERNAL sections must not have "
+ "the CSS_PROPERTY_INTERNAL flag");
+ found[p] = true;
+ }
+
+ for (size_t i = 0; i < ArrayLength(found); ++i) {
+ if (!found[i]) {
+ auto p = static_cast<nsCSSPropertyID>(i);
+ MOZ_ASSERT(nsCSSProps::PropHasFlags(p, CSS_PROPERTY_INTERNAL),
+ "properties defined in #ifndef "
+ "CSS_PROP_LIST_EXCLUDE_INTERNAL sections must have "
+ "the CSS_PROPERTY_INTERNAL flag");
+ }
+ }
+ }
+#endif
+ }
+}
+
+#undef DEBUG_SHORTHANDS_CONTAINING
+
+bool
+nsCSSProps::BuildShorthandsContainingTable()
+{
+ uint32_t occurrenceCounts[eCSSProperty_COUNT_no_shorthands];
+ memset(occurrenceCounts, 0, sizeof(occurrenceCounts));
+ PropertyAndCount subpropCounts[eCSSProperty_COUNT -
+ eCSSProperty_COUNT_no_shorthands];
+ for (nsCSSPropertyID shorthand = eCSSProperty_COUNT_no_shorthands;
+ shorthand < eCSSProperty_COUNT;
+ shorthand = nsCSSPropertyID(shorthand + 1)) {
+#ifdef DEBUG_SHORTHANDS_CONTAINING
+ printf("Considering shorthand property '%s'.\n",
+ nsCSSProps::GetStringValue(shorthand).get());
+#endif
+ PropertyAndCount &subpropCountsEntry =
+ subpropCounts[shorthand - eCSSProperty_COUNT_no_shorthands];
+ subpropCountsEntry.property = shorthand;
+ subpropCountsEntry.count = 0;
+ if (nsCSSProps::PropHasFlags(shorthand, CSS_PROPERTY_IS_ALIAS)) {
+ // Don't put shorthands that are acting as aliases in the
+ // shorthands-containing lists.
+ continue;
+ }
+ for (const nsCSSPropertyID* subprops = SubpropertyEntryFor(shorthand);
+ *subprops != eCSSProperty_UNKNOWN;
+ ++subprops) {
+ MOZ_ASSERT(0 <= *subprops && *subprops < eCSSProperty_COUNT_no_shorthands,
+ "subproperty must be a longhand");
+ ++occurrenceCounts[*subprops];
+ ++subpropCountsEntry.count;
+ }
+ }
+
+ uint32_t poolEntries = 0;
+ for (nsCSSPropertyID longhand = nsCSSPropertyID(0);
+ longhand < eCSSProperty_COUNT_no_shorthands;
+ longhand = nsCSSPropertyID(longhand + 1)) {
+ uint32_t count = occurrenceCounts[longhand];
+ if (count > 0)
+ // leave room for terminator
+ poolEntries += count + 1;
+ }
+
+ gShorthandsContainingPool = new nsCSSPropertyID[poolEntries];
+ if (!gShorthandsContainingPool)
+ return false;
+
+ // Initialize all entries to point to their null-terminator.
+ {
+ nsCSSPropertyID *poolCursor = gShorthandsContainingPool - 1;
+ nsCSSPropertyID *lastTerminator =
+ gShorthandsContainingPool + poolEntries - 1;
+ for (nsCSSPropertyID longhand = nsCSSPropertyID(0);
+ longhand < eCSSProperty_COUNT_no_shorthands;
+ longhand = nsCSSPropertyID(longhand + 1)) {
+ uint32_t count = occurrenceCounts[longhand];
+ if (count > 0) {
+ poolCursor += count + 1;
+ gShorthandsContainingTable[longhand] = poolCursor;
+ *poolCursor = eCSSProperty_UNKNOWN;
+ } else {
+ gShorthandsContainingTable[longhand] = lastTerminator;
+ }
+ }
+ MOZ_ASSERT(poolCursor == lastTerminator, "miscalculation");
+ }
+
+ // Sort with lowest count at the start and highest at the end, and
+ // within counts sort in reverse property index order.
+ NS_QuickSort(&subpropCounts, ArrayLength(subpropCounts),
+ sizeof(subpropCounts[0]), SortPropertyAndCount, nullptr);
+
+ // Fill in all the entries in gShorthandsContainingTable
+ for (const PropertyAndCount *shorthandAndCount = subpropCounts,
+ *shorthandAndCountEnd = ArrayEnd(subpropCounts);
+ shorthandAndCount < shorthandAndCountEnd;
+ ++shorthandAndCount) {
+#ifdef DEBUG_SHORTHANDS_CONTAINING
+ printf("Entering %u subprops for '%s'.\n",
+ shorthandAndCount->count,
+ nsCSSProps::GetStringValue(shorthandAndCount->property).get());
+#endif
+ if (nsCSSProps::PropHasFlags(shorthandAndCount->property,
+ CSS_PROPERTY_IS_ALIAS)) {
+ // Don't put shorthands that are acting as aliases in the
+ // shorthands-containing lists.
+ continue;
+ }
+ for (const nsCSSPropertyID* subprops =
+ SubpropertyEntryFor(shorthandAndCount->property);
+ *subprops != eCSSProperty_UNKNOWN;
+ ++subprops) {
+ *(--gShorthandsContainingTable[*subprops]) = shorthandAndCount->property;
+ }
+ }
+
+#ifdef DEBUG_SHORTHANDS_CONTAINING
+ for (nsCSSPropertyID longhand = nsCSSPropertyID(0);
+ longhand < eCSSProperty_COUNT_no_shorthands;
+ longhand = nsCSSPropertyID(longhand + 1)) {
+ printf("Property %s is in %d shorthands.\n",
+ nsCSSProps::GetStringValue(longhand).get(),
+ occurrenceCounts[longhand]);
+ for (const nsCSSPropertyID *shorthands = ShorthandsContaining(longhand);
+ *shorthands != eCSSProperty_UNKNOWN;
+ ++shorthands) {
+ printf(" %s\n", nsCSSProps::GetStringValue(*shorthands).get());
+ }
+ }
+#endif
+
+#ifdef DEBUG
+ // Verify that all values that should be are present.
+ for (nsCSSPropertyID shorthand = eCSSProperty_COUNT_no_shorthands;
+ shorthand < eCSSProperty_COUNT;
+ shorthand = nsCSSPropertyID(shorthand + 1)) {
+ if (nsCSSProps::PropHasFlags(shorthand, CSS_PROPERTY_IS_ALIAS)) {
+ // Don't put shorthands that are acting as aliases in the
+ // shorthands-containing lists.
+ continue;
+ }
+ for (const nsCSSPropertyID* subprops = SubpropertyEntryFor(shorthand);
+ *subprops != eCSSProperty_UNKNOWN;
+ ++subprops) {
+ uint32_t count = 0;
+ for (const nsCSSPropertyID *shcont = ShorthandsContaining(*subprops);
+ *shcont != eCSSProperty_UNKNOWN;
+ ++shcont) {
+ if (*shcont == shorthand)
+ ++count;
+ }
+ MOZ_ASSERT(count == 1,
+ "subproperty of shorthand should have shorthand"
+ " in its ShorthandsContaining() table");
+ }
+ }
+
+ // Verify that there are no extra values
+ for (nsCSSPropertyID longhand = nsCSSPropertyID(0);
+ longhand < eCSSProperty_COUNT_no_shorthands;
+ longhand = nsCSSPropertyID(longhand + 1)) {
+ for (const nsCSSPropertyID *shorthands = ShorthandsContaining(longhand);
+ *shorthands != eCSSProperty_UNKNOWN;
+ ++shorthands) {
+ uint32_t count = 0;
+ for (const nsCSSPropertyID* subprops = SubpropertyEntryFor(*shorthands);
+ *subprops != eCSSProperty_UNKNOWN;
+ ++subprops) {
+ if (*subprops == longhand)
+ ++count;
+ }
+ MOZ_ASSERT(count == 1,
+ "longhand should be in subproperty table of "
+ "property in its ShorthandsContaining() table");
+ }
+ }
+#endif
+
+ return true;
+}
+
+void
+nsCSSProps::ReleaseTable(void)
+{
+ if (0 == --gPropertyTableRefCount) {
+ delete gPropertyTable;
+ gPropertyTable = nullptr;
+
+ delete gFontDescTable;
+ gFontDescTable = nullptr;
+
+ delete gCounterDescTable;
+ gCounterDescTable = nullptr;
+
+ delete gPredefinedCounterStyleTable;
+ gPredefinedCounterStyleTable = nullptr;
+
+ delete gPropertyIDLNameTable;
+ gPropertyIDLNameTable = nullptr;
+
+ delete [] gShorthandsContainingPool;
+ gShorthandsContainingPool = nullptr;
+ }
+}
+
+/* static */ bool
+nsCSSProps::IsInherited(nsCSSPropertyID aProperty)
+{
+ MOZ_ASSERT(!IsShorthand(aProperty));
+
+ nsStyleStructID sid = kSIDTable[aProperty];
+ return nsCachedStyleData::IsInherited(sid);
+}
+
+/* static */ bool
+nsCSSProps::IsCustomPropertyName(const nsACString& aProperty)
+{
+ // Custom properties don't need to have a character after the "--" prefix.
+ return aProperty.Length() >= CSS_CUSTOM_NAME_PREFIX_LENGTH &&
+ StringBeginsWith(aProperty, NS_LITERAL_CSTRING("--"));
+}
+
+/* static */ bool
+nsCSSProps::IsCustomPropertyName(const nsAString& aProperty)
+{
+ return aProperty.Length() >= CSS_CUSTOM_NAME_PREFIX_LENGTH &&
+ StringBeginsWith(aProperty, NS_LITERAL_STRING("--"));
+}
+
+nsCSSPropertyID
+nsCSSProps::LookupProperty(const nsACString& aProperty,
+ EnabledState aEnabled)
+{
+ MOZ_ASSERT(gPropertyTable, "no lookup table, needs addref");
+
+ if (nsLayoutUtils::CSSVariablesEnabled() &&
+ IsCustomPropertyName(aProperty)) {
+ return eCSSPropertyExtra_variable;
+ }
+
+ nsCSSPropertyID res = nsCSSPropertyID(gPropertyTable->Lookup(aProperty));
+ if (MOZ_LIKELY(res < eCSSProperty_COUNT)) {
+ if (res != eCSSProperty_UNKNOWN && !IsEnabled(res, aEnabled)) {
+ res = eCSSProperty_UNKNOWN;
+ }
+ return res;
+ }
+ MOZ_ASSERT(eCSSAliasCount != 0,
+ "'res' must be an alias at this point so we better have some!");
+ // We intentionally don't support CSSEnabledState::eInUASheets or
+ // CSSEnabledState::eInChrome for aliases yet because it's unlikely
+ // there will be a need for it.
+ if (IsEnabled(res) || aEnabled == CSSEnabledState::eIgnoreEnabledState) {
+ res = gAliases[res - eCSSProperty_COUNT];
+ MOZ_ASSERT(0 <= res && res < eCSSProperty_COUNT,
+ "aliases must not point to other aliases");
+ if (IsEnabled(res) || aEnabled == CSSEnabledState::eIgnoreEnabledState) {
+ return res;
+ }
+ }
+ return eCSSProperty_UNKNOWN;
+}
+
+nsCSSPropertyID
+nsCSSProps::LookupProperty(const nsAString& aProperty, EnabledState aEnabled)
+{
+ if (nsLayoutUtils::CSSVariablesEnabled() &&
+ IsCustomPropertyName(aProperty)) {
+ return eCSSPropertyExtra_variable;
+ }
+
+ // This is faster than converting and calling
+ // LookupProperty(nsACString&). The table will do its own
+ // converting and avoid a PromiseFlatCString() call.
+ MOZ_ASSERT(gPropertyTable, "no lookup table, needs addref");
+ nsCSSPropertyID res = nsCSSPropertyID(gPropertyTable->Lookup(aProperty));
+ if (MOZ_LIKELY(res < eCSSProperty_COUNT)) {
+ if (res != eCSSProperty_UNKNOWN && !IsEnabled(res, aEnabled)) {
+ res = eCSSProperty_UNKNOWN;
+ }
+ return res;
+ }
+ MOZ_ASSERT(eCSSAliasCount != 0,
+ "'res' must be an alias at this point so we better have some!");
+ // We intentionally don't support CSSEnabledState::eInUASheets or
+ // CSSEnabledState::eInChrome for aliases yet because it's unlikely
+ // there will be a need for it.
+ if (IsEnabled(res) || aEnabled == CSSEnabledState::eIgnoreEnabledState) {
+ res = gAliases[res - eCSSProperty_COUNT];
+ MOZ_ASSERT(0 <= res && res < eCSSProperty_COUNT,
+ "aliases must not point to other aliases");
+ if (IsEnabled(res) || aEnabled == CSSEnabledState::eIgnoreEnabledState) {
+ return res;
+ }
+ }
+ return eCSSProperty_UNKNOWN;
+}
+
+nsCSSPropertyID
+nsCSSProps::LookupPropertyByIDLName(const nsACString& aPropertyIDLName,
+ EnabledState aEnabled)
+{
+ nsCSSPropertyID res;
+ if (!gPropertyIDLNameTable->Get(aPropertyIDLName, &res)) {
+ return eCSSProperty_UNKNOWN;
+ }
+ MOZ_ASSERT(res < eCSSProperty_COUNT);
+ if (!IsEnabled(res, aEnabled)) {
+ return eCSSProperty_UNKNOWN;
+ }
+ return res;
+}
+
+nsCSSPropertyID
+nsCSSProps::LookupPropertyByIDLName(const nsAString& aPropertyIDLName,
+ EnabledState aEnabled)
+{
+ MOZ_ASSERT(gPropertyIDLNameTable, "no lookup table, needs addref");
+ return LookupPropertyByIDLName(NS_ConvertUTF16toUTF8(aPropertyIDLName),
+ aEnabled);
+}
+
+nsCSSFontDesc
+nsCSSProps::LookupFontDesc(const nsACString& aFontDesc)
+{
+ MOZ_ASSERT(gFontDescTable, "no lookup table, needs addref");
+ nsCSSFontDesc which = nsCSSFontDesc(gFontDescTable->Lookup(aFontDesc));
+
+ if (which == eCSSFontDesc_Display &&
+ !Preferences::GetBool("layout.css.font-display.enabled")) {
+ which = eCSSFontDesc_UNKNOWN;
+ } else if (which == eCSSFontDesc_UNKNOWN) {
+ // check for unprefixed font-feature-settings/font-language-override
+ nsAutoCString prefixedProp;
+ prefixedProp.AppendLiteral("-moz-");
+ prefixedProp.Append(aFontDesc);
+ which = nsCSSFontDesc(gFontDescTable->Lookup(prefixedProp));
+ }
+ return which;
+}
+
+nsCSSFontDesc
+nsCSSProps::LookupFontDesc(const nsAString& aFontDesc)
+{
+ MOZ_ASSERT(gFontDescTable, "no lookup table, needs addref");
+ nsCSSFontDesc which = nsCSSFontDesc(gFontDescTable->Lookup(aFontDesc));
+
+ if (which == eCSSFontDesc_Display &&
+ !Preferences::GetBool("layout.css.font-display.enabled")) {
+ which = eCSSFontDesc_UNKNOWN;
+ } else if (which == eCSSFontDesc_UNKNOWN) {
+ // check for unprefixed font-feature-settings/font-language-override
+ nsAutoString prefixedProp;
+ prefixedProp.AppendLiteral("-moz-");
+ prefixedProp.Append(aFontDesc);
+ which = nsCSSFontDesc(gFontDescTable->Lookup(prefixedProp));
+ }
+ return which;
+}
+
+nsCSSCounterDesc
+nsCSSProps::LookupCounterDesc(const nsAString& aProperty)
+{
+ MOZ_ASSERT(gCounterDescTable, "no lookup table, needs addref");
+ return nsCSSCounterDesc(gCounterDescTable->Lookup(aProperty));
+}
+
+nsCSSCounterDesc
+nsCSSProps::LookupCounterDesc(const nsACString& aProperty)
+{
+ MOZ_ASSERT(gCounterDescTable, "no lookup table, needs addref");
+ return nsCSSCounterDesc(gCounterDescTable->Lookup(aProperty));
+}
+
+bool
+nsCSSProps::IsPredefinedCounterStyle(const nsAString& aStyle)
+{
+ MOZ_ASSERT(gPredefinedCounterStyleTable,
+ "no lookup table, needs addref");
+ return gPredefinedCounterStyleTable->Lookup(aStyle) !=
+ nsStaticCaseInsensitiveNameTable::NOT_FOUND;
+}
+
+bool
+nsCSSProps::IsPredefinedCounterStyle(const nsACString& aStyle)
+{
+ MOZ_ASSERT(gPredefinedCounterStyleTable,
+ "no lookup table, needs addref");
+ return gPredefinedCounterStyleTable->Lookup(aStyle) !=
+ nsStaticCaseInsensitiveNameTable::NOT_FOUND;
+}
+
+const nsAFlatCString&
+nsCSSProps::GetStringValue(nsCSSPropertyID aProperty)
+{
+ MOZ_ASSERT(gPropertyTable, "no lookup table, needs addref");
+ if (gPropertyTable) {
+ return gPropertyTable->GetStringValue(int32_t(aProperty));
+ } else {
+ static nsDependentCString sNullStr("");
+ return sNullStr;
+ }
+}
+
+const nsAFlatCString&
+nsCSSProps::GetStringValue(nsCSSFontDesc aFontDescID)
+{
+ MOZ_ASSERT(gFontDescTable, "no lookup table, needs addref");
+ if (gFontDescTable) {
+ return gFontDescTable->GetStringValue(int32_t(aFontDescID));
+ } else {
+ static nsDependentCString sNullStr("");
+ return sNullStr;
+ }
+}
+
+const nsAFlatCString&
+nsCSSProps::GetStringValue(nsCSSCounterDesc aCounterDesc)
+{
+ MOZ_ASSERT(gCounterDescTable, "no lookup table, needs addref");
+ if (gCounterDescTable) {
+ return gCounterDescTable->GetStringValue(int32_t(aCounterDesc));
+ } else {
+ static nsDependentCString sNullStr("");
+ return sNullStr;
+ }
+}
+
+#define CSS_PROP(name_, id_, ...) nsICSSProperty* nsCSSProps::id_;
+#define CSS_PROP_SHORTHAND(name_, id_, ...) CSS_PROP(name_, id_, ...)
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP
+
+#define CSS_PROP(name_, id_, ...) NS_STATIC_ATOM_BUFFER(id_##_buffer, #name_)
+#define CSS_PROP_SHORTHAND(name_, id_, ...) CSS_PROP(name_, id_, ...)
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP
+
+static const nsStaticAtom CSSProps_info[] = {
+#define CSS_PROP(name_, id_, ...) \
+ NS_STATIC_ATOM(id_##_buffer, (nsIAtom**)&nsCSSProps::id_),
+#define CSS_PROP_SHORTHAND(name_, id_, ...) CSS_PROP(name_, id_, __VA_ARGS__)
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP
+};
+
+nsICSSProperty* nsCSSProps::gPropertyAtomTable[eCSSProperty_COUNT];
+
+/* static */ void
+nsCSSProps::AddRefAtoms()
+{
+ NS_RegisterStaticAtoms(CSSProps_info);
+#define CSS_PROP(name_, id_, ...) \
+ gPropertyAtomTable[eCSSProperty_##id_] = nsCSSProps::id_;
+#define CSS_PROP_SHORTHAND(name_, id_, ...) CSS_PROP(name_, id_, ...)
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP
+}
+
+/***************************************************************************/
+
+const KTableEntry nsCSSProps::kAnimationDirectionKTable[] = {
+ { eCSSKeyword_normal, static_cast<uint32_t>(dom::PlaybackDirection::Normal) },
+ { eCSSKeyword_reverse, static_cast<uint32_t>(dom::PlaybackDirection::Reverse) },
+ { eCSSKeyword_alternate, static_cast<uint32_t>(dom::PlaybackDirection::Alternate) },
+ { eCSSKeyword_alternate_reverse, static_cast<uint32_t>(dom::PlaybackDirection::Alternate_reverse) },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAnimationFillModeKTable[] = {
+ { eCSSKeyword_none, static_cast<uint32_t>(dom::FillMode::None) },
+ { eCSSKeyword_forwards, static_cast<uint32_t>(dom::FillMode::Forwards) },
+ { eCSSKeyword_backwards, static_cast<uint32_t>(dom::FillMode::Backwards) },
+ { eCSSKeyword_both, static_cast<uint32_t>(dom::FillMode::Both) },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAnimationIterationCountKTable[] = {
+ { eCSSKeyword_infinite, NS_STYLE_ANIMATION_ITERATION_COUNT_INFINITE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAnimationPlayStateKTable[] = {
+ { eCSSKeyword_running, NS_STYLE_ANIMATION_PLAY_STATE_RUNNING },
+ { eCSSKeyword_paused, NS_STYLE_ANIMATION_PLAY_STATE_PAUSED },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAppearanceKTable[] = {
+ { eCSSKeyword_none, NS_THEME_NONE },
+ { eCSSKeyword_button, NS_THEME_BUTTON },
+ { eCSSKeyword_radio, NS_THEME_RADIO },
+ { eCSSKeyword_checkbox, NS_THEME_CHECKBOX },
+ { eCSSKeyword_button_bevel, NS_THEME_BUTTON_BEVEL },
+ { eCSSKeyword_toolbox, NS_THEME_TOOLBOX },
+ { eCSSKeyword_toolbar, NS_THEME_TOOLBAR },
+ { eCSSKeyword_toolbarbutton, NS_THEME_TOOLBARBUTTON },
+ { eCSSKeyword_toolbargripper, NS_THEME_TOOLBARGRIPPER },
+ { eCSSKeyword_dualbutton, NS_THEME_DUALBUTTON },
+ { eCSSKeyword_toolbarbutton_dropdown, NS_THEME_TOOLBARBUTTON_DROPDOWN },
+ { eCSSKeyword_button_arrow_up, NS_THEME_BUTTON_ARROW_UP },
+ { eCSSKeyword_button_arrow_down, NS_THEME_BUTTON_ARROW_DOWN },
+ { eCSSKeyword_button_arrow_next, NS_THEME_BUTTON_ARROW_NEXT },
+ { eCSSKeyword_button_arrow_previous, NS_THEME_BUTTON_ARROW_PREVIOUS },
+ { eCSSKeyword_meterbar, NS_THEME_METERBAR },
+ { eCSSKeyword_meterchunk, NS_THEME_METERCHUNK },
+ { eCSSKeyword_number_input, NS_THEME_NUMBER_INPUT },
+ { eCSSKeyword_separator, NS_THEME_SEPARATOR },
+ { eCSSKeyword_splitter, NS_THEME_SPLITTER },
+ { eCSSKeyword_statusbar, NS_THEME_STATUSBAR },
+ { eCSSKeyword_statusbarpanel, NS_THEME_STATUSBARPANEL },
+ { eCSSKeyword_resizerpanel, NS_THEME_RESIZERPANEL },
+ { eCSSKeyword_resizer, NS_THEME_RESIZER },
+ { eCSSKeyword_listbox, NS_THEME_LISTBOX },
+ { eCSSKeyword_listitem, NS_THEME_LISTITEM },
+ { eCSSKeyword_treeview, NS_THEME_TREEVIEW },
+ { eCSSKeyword_treeitem, NS_THEME_TREEITEM },
+ { eCSSKeyword_treetwisty, NS_THEME_TREETWISTY },
+ { eCSSKeyword_treetwistyopen, NS_THEME_TREETWISTYOPEN },
+ { eCSSKeyword_treeline, NS_THEME_TREELINE },
+ { eCSSKeyword_treeheader, NS_THEME_TREEHEADER },
+ { eCSSKeyword_treeheadercell, NS_THEME_TREEHEADERCELL },
+ { eCSSKeyword_treeheadersortarrow, NS_THEME_TREEHEADERSORTARROW },
+ { eCSSKeyword_progressbar, NS_THEME_PROGRESSBAR },
+ { eCSSKeyword_progresschunk, NS_THEME_PROGRESSCHUNK },
+ { eCSSKeyword_progressbar_vertical, NS_THEME_PROGRESSBAR_VERTICAL },
+ { eCSSKeyword_progresschunk_vertical, NS_THEME_PROGRESSCHUNK_VERTICAL },
+ { eCSSKeyword_tab, NS_THEME_TAB },
+ { eCSSKeyword_tabpanels, NS_THEME_TABPANELS },
+ { eCSSKeyword_tabpanel, NS_THEME_TABPANEL },
+ { eCSSKeyword_tab_scroll_arrow_back, NS_THEME_TAB_SCROLL_ARROW_BACK },
+ { eCSSKeyword_tab_scroll_arrow_forward, NS_THEME_TAB_SCROLL_ARROW_FORWARD },
+ { eCSSKeyword_tooltip, NS_THEME_TOOLTIP },
+ { eCSSKeyword_spinner, NS_THEME_SPINNER },
+ { eCSSKeyword_spinner_upbutton, NS_THEME_SPINNER_UPBUTTON },
+ { eCSSKeyword_spinner_downbutton, NS_THEME_SPINNER_DOWNBUTTON },
+ { eCSSKeyword_spinner_textfield, NS_THEME_SPINNER_TEXTFIELD },
+ { eCSSKeyword_scrollbar, NS_THEME_SCROLLBAR },
+ { eCSSKeyword_scrollbar_small, NS_THEME_SCROLLBAR_SMALL },
+ { eCSSKeyword_scrollbar_horizontal, NS_THEME_SCROLLBAR_HORIZONTAL },
+ { eCSSKeyword_scrollbar_vertical, NS_THEME_SCROLLBAR_VERTICAL },
+ { eCSSKeyword_scrollbarbutton_up, NS_THEME_SCROLLBARBUTTON_UP },
+ { eCSSKeyword_scrollbarbutton_down, NS_THEME_SCROLLBARBUTTON_DOWN },
+ { eCSSKeyword_scrollbarbutton_left, NS_THEME_SCROLLBARBUTTON_LEFT },
+ { eCSSKeyword_scrollbarbutton_right, NS_THEME_SCROLLBARBUTTON_RIGHT },
+ { eCSSKeyword_scrollbartrack_horizontal, NS_THEME_SCROLLBARTRACK_HORIZONTAL },
+ { eCSSKeyword_scrollbartrack_vertical, NS_THEME_SCROLLBARTRACK_VERTICAL },
+ { eCSSKeyword_scrollbarthumb_horizontal, NS_THEME_SCROLLBARTHUMB_HORIZONTAL },
+ { eCSSKeyword_scrollbarthumb_vertical, NS_THEME_SCROLLBARTHUMB_VERTICAL },
+ { eCSSKeyword_textfield, NS_THEME_TEXTFIELD },
+ { eCSSKeyword_textfield_multiline, NS_THEME_TEXTFIELD_MULTILINE },
+ { eCSSKeyword_caret, NS_THEME_CARET },
+ { eCSSKeyword_searchfield, NS_THEME_SEARCHFIELD },
+ { eCSSKeyword_menulist, NS_THEME_MENULIST },
+ { eCSSKeyword_menulist_button, NS_THEME_MENULIST_BUTTON },
+ { eCSSKeyword_menulist_text, NS_THEME_MENULIST_TEXT },
+ { eCSSKeyword_menulist_textfield, NS_THEME_MENULIST_TEXTFIELD },
+ { eCSSKeyword_range, NS_THEME_RANGE },
+ { eCSSKeyword_range_thumb, NS_THEME_RANGE_THUMB },
+ { eCSSKeyword_scale_horizontal, NS_THEME_SCALE_HORIZONTAL },
+ { eCSSKeyword_scale_vertical, NS_THEME_SCALE_VERTICAL },
+ { eCSSKeyword_scalethumb_horizontal, NS_THEME_SCALETHUMB_HORIZONTAL },
+ { eCSSKeyword_scalethumb_vertical, NS_THEME_SCALETHUMB_VERTICAL },
+ { eCSSKeyword_scalethumbstart, NS_THEME_SCALETHUMBSTART },
+ { eCSSKeyword_scalethumbend, NS_THEME_SCALETHUMBEND },
+ { eCSSKeyword_scalethumbtick, NS_THEME_SCALETHUMBTICK },
+ { eCSSKeyword_groupbox, NS_THEME_GROUPBOX },
+ { eCSSKeyword_checkbox_container, NS_THEME_CHECKBOX_CONTAINER },
+ { eCSSKeyword_radio_container, NS_THEME_RADIO_CONTAINER },
+ { eCSSKeyword_checkbox_label, NS_THEME_CHECKBOX_LABEL },
+ { eCSSKeyword_radio_label, NS_THEME_RADIO_LABEL },
+ { eCSSKeyword_button_focus, NS_THEME_BUTTON_FOCUS },
+ { eCSSKeyword_window, NS_THEME_WINDOW },
+ { eCSSKeyword_dialog, NS_THEME_DIALOG },
+ { eCSSKeyword_menubar, NS_THEME_MENUBAR },
+ { eCSSKeyword_menupopup, NS_THEME_MENUPOPUP },
+ { eCSSKeyword_menuitem, NS_THEME_MENUITEM },
+ { eCSSKeyword_checkmenuitem, NS_THEME_CHECKMENUITEM },
+ { eCSSKeyword_radiomenuitem, NS_THEME_RADIOMENUITEM },
+ { eCSSKeyword_menucheckbox, NS_THEME_MENUCHECKBOX },
+ { eCSSKeyword_menuradio, NS_THEME_MENURADIO },
+ { eCSSKeyword_menuseparator, NS_THEME_MENUSEPARATOR },
+ { eCSSKeyword_menuarrow, NS_THEME_MENUARROW },
+ { eCSSKeyword_menuimage, NS_THEME_MENUIMAGE },
+ { eCSSKeyword_menuitemtext, NS_THEME_MENUITEMTEXT },
+ { eCSSKeyword__moz_win_media_toolbox, NS_THEME_WIN_MEDIA_TOOLBOX },
+ { eCSSKeyword__moz_win_communications_toolbox, NS_THEME_WIN_COMMUNICATIONS_TOOLBOX },
+ { eCSSKeyword__moz_win_browsertabbar_toolbox, NS_THEME_WIN_BROWSERTABBAR_TOOLBOX },
+ { eCSSKeyword__moz_win_glass, NS_THEME_WIN_GLASS },
+ { eCSSKeyword__moz_win_borderless_glass, NS_THEME_WIN_BORDERLESS_GLASS },
+ { eCSSKeyword__moz_mac_fullscreen_button, NS_THEME_MAC_FULLSCREEN_BUTTON },
+ { eCSSKeyword__moz_mac_help_button, NS_THEME_MAC_HELP_BUTTON },
+ { eCSSKeyword__moz_window_titlebar, NS_THEME_WINDOW_TITLEBAR },
+ { eCSSKeyword__moz_window_titlebar_maximized, NS_THEME_WINDOW_TITLEBAR_MAXIMIZED },
+ { eCSSKeyword__moz_window_frame_left, NS_THEME_WINDOW_FRAME_LEFT },
+ { eCSSKeyword__moz_window_frame_right, NS_THEME_WINDOW_FRAME_RIGHT },
+ { eCSSKeyword__moz_window_frame_bottom, NS_THEME_WINDOW_FRAME_BOTTOM },
+ { eCSSKeyword__moz_window_button_close, NS_THEME_WINDOW_BUTTON_CLOSE },
+ { eCSSKeyword__moz_window_button_minimize, NS_THEME_WINDOW_BUTTON_MINIMIZE },
+ { eCSSKeyword__moz_window_button_maximize, NS_THEME_WINDOW_BUTTON_MAXIMIZE },
+ { eCSSKeyword__moz_window_button_restore, NS_THEME_WINDOW_BUTTON_RESTORE },
+ { eCSSKeyword__moz_window_button_box, NS_THEME_WINDOW_BUTTON_BOX },
+ { eCSSKeyword__moz_window_button_box_maximized, NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED },
+ { eCSSKeyword__moz_win_exclude_glass, NS_THEME_WIN_EXCLUDE_GLASS },
+ { eCSSKeyword__moz_mac_vibrancy_light, NS_THEME_MAC_VIBRANCY_LIGHT },
+ { eCSSKeyword__moz_mac_vibrancy_dark, NS_THEME_MAC_VIBRANCY_DARK },
+ { eCSSKeyword__moz_mac_disclosure_button_open, NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN },
+ { eCSSKeyword__moz_mac_disclosure_button_closed, NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED },
+ { eCSSKeyword__moz_gtk_info_bar, NS_THEME_GTK_INFO_BAR },
+ { eCSSKeyword__moz_mac_source_list, NS_THEME_MAC_SOURCE_LIST },
+ { eCSSKeyword__moz_mac_source_list_selection, NS_THEME_MAC_SOURCE_LIST_SELECTION },
+ { eCSSKeyword__moz_mac_active_source_list_selection, NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBackfaceVisibilityKTable[] = {
+ { eCSSKeyword_visible, NS_STYLE_BACKFACE_VISIBILITY_VISIBLE },
+ { eCSSKeyword_hidden, NS_STYLE_BACKFACE_VISIBILITY_HIDDEN },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTransformStyleKTable[] = {
+ { eCSSKeyword_flat, NS_STYLE_TRANSFORM_STYLE_FLAT },
+ { eCSSKeyword_preserve_3d, NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kImageLayerAttachmentKTable[] = {
+ { eCSSKeyword_fixed, NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED },
+ { eCSSKeyword_scroll, NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL },
+ { eCSSKeyword_local, NS_STYLE_IMAGELAYER_ATTACHMENT_LOCAL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+static_assert(NS_STYLE_IMAGELAYER_CLIP_BORDER == NS_STYLE_IMAGELAYER_ORIGIN_BORDER &&
+ NS_STYLE_IMAGELAYER_CLIP_PADDING == NS_STYLE_IMAGELAYER_ORIGIN_PADDING &&
+ NS_STYLE_IMAGELAYER_CLIP_CONTENT == NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
+ "Except background-clip:text, all {background,mask}-clip and "
+ "{background,mask}-origin style constants must agree");
+
+const KTableEntry nsCSSProps::kImageLayerOriginKTable[] = {
+ { eCSSKeyword_border_box, NS_STYLE_IMAGELAYER_ORIGIN_BORDER },
+ { eCSSKeyword_padding_box, NS_STYLE_IMAGELAYER_ORIGIN_PADDING },
+ { eCSSKeyword_content_box, NS_STYLE_IMAGELAYER_ORIGIN_CONTENT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+KTableEntry nsCSSProps::kBackgroundClipKTable[] = {
+ { eCSSKeyword_border_box, NS_STYLE_IMAGELAYER_CLIP_BORDER },
+ { eCSSKeyword_padding_box, NS_STYLE_IMAGELAYER_CLIP_PADDING },
+ { eCSSKeyword_content_box, NS_STYLE_IMAGELAYER_CLIP_CONTENT },
+ // The next entry is controlled by the layout.css.background-clip-text.enabled
+ // pref.
+ { eCSSKeyword_text, NS_STYLE_IMAGELAYER_CLIP_TEXT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+static_assert(MOZ_ARRAY_LENGTH(nsCSSProps::kImageLayerOriginKTable) ==
+ MOZ_ARRAY_LENGTH(nsCSSProps::kBackgroundClipKTable) - 1,
+ "background-clip has one extra value, which is text, compared"
+ "to {background,mask}-origin");
+
+// Note: Don't change this table unless you update
+// ParseImageLayerPosition!
+
+const KTableEntry nsCSSProps::kImageLayerPositionKTable[] = {
+ { eCSSKeyword_center, NS_STYLE_IMAGELAYER_POSITION_CENTER },
+ { eCSSKeyword_top, NS_STYLE_IMAGELAYER_POSITION_TOP },
+ { eCSSKeyword_bottom, NS_STYLE_IMAGELAYER_POSITION_BOTTOM },
+ { eCSSKeyword_left, NS_STYLE_IMAGELAYER_POSITION_LEFT },
+ { eCSSKeyword_right, NS_STYLE_IMAGELAYER_POSITION_RIGHT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kImageLayerRepeatKTable[] = {
+ { eCSSKeyword_no_repeat, NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT },
+ { eCSSKeyword_repeat, NS_STYLE_IMAGELAYER_REPEAT_REPEAT },
+ { eCSSKeyword_repeat_x, NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X },
+ { eCSSKeyword_repeat_y, NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y },
+ { eCSSKeyword_round, NS_STYLE_IMAGELAYER_REPEAT_ROUND},
+ { eCSSKeyword_space, NS_STYLE_IMAGELAYER_REPEAT_SPACE},
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kImageLayerRepeatPartKTable[] = {
+ { eCSSKeyword_no_repeat, NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT },
+ { eCSSKeyword_repeat, NS_STYLE_IMAGELAYER_REPEAT_REPEAT },
+ { eCSSKeyword_round, NS_STYLE_IMAGELAYER_REPEAT_ROUND},
+ { eCSSKeyword_space, NS_STYLE_IMAGELAYER_REPEAT_SPACE},
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kImageLayerSizeKTable[] = {
+ { eCSSKeyword_contain, NS_STYLE_IMAGELAYER_SIZE_CONTAIN },
+ { eCSSKeyword_cover, NS_STYLE_IMAGELAYER_SIZE_COVER },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kImageLayerModeKTable[] = {
+ { eCSSKeyword_alpha, NS_STYLE_MASK_MODE_ALPHA },
+ { eCSSKeyword_luminance, NS_STYLE_MASK_MODE_LUMINANCE },
+ { eCSSKeyword_match_source, NS_STYLE_MASK_MODE_MATCH_SOURCE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kImageLayerCompositeKTable[] = {
+ { eCSSKeyword_add, NS_STYLE_MASK_COMPOSITE_ADD },
+ { eCSSKeyword_subtract, NS_STYLE_MASK_COMPOSITE_SUBTRACT },
+ { eCSSKeyword_intersect, NS_STYLE_MASK_COMPOSITE_INTERSECT },
+ { eCSSKeyword_exclude, NS_STYLE_MASK_COMPOSITE_EXCLUDE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBlendModeKTable[] = {
+ { eCSSKeyword_normal, NS_STYLE_BLEND_NORMAL },
+ { eCSSKeyword_multiply, NS_STYLE_BLEND_MULTIPLY },
+ { eCSSKeyword_screen, NS_STYLE_BLEND_SCREEN },
+ { eCSSKeyword_overlay, NS_STYLE_BLEND_OVERLAY },
+ { eCSSKeyword_darken, NS_STYLE_BLEND_DARKEN },
+ { eCSSKeyword_lighten, NS_STYLE_BLEND_LIGHTEN },
+ { eCSSKeyword_color_dodge, NS_STYLE_BLEND_COLOR_DODGE },
+ { eCSSKeyword_color_burn, NS_STYLE_BLEND_COLOR_BURN },
+ { eCSSKeyword_hard_light, NS_STYLE_BLEND_HARD_LIGHT },
+ { eCSSKeyword_soft_light, NS_STYLE_BLEND_SOFT_LIGHT },
+ { eCSSKeyword_difference, NS_STYLE_BLEND_DIFFERENCE },
+ { eCSSKeyword_exclusion, NS_STYLE_BLEND_EXCLUSION },
+ { eCSSKeyword_hue, NS_STYLE_BLEND_HUE },
+ { eCSSKeyword_saturation, NS_STYLE_BLEND_SATURATION },
+ { eCSSKeyword_color, NS_STYLE_BLEND_COLOR },
+ { eCSSKeyword_luminosity, NS_STYLE_BLEND_LUMINOSITY },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBorderCollapseKTable[] = {
+ { eCSSKeyword_collapse, NS_STYLE_BORDER_COLLAPSE },
+ { eCSSKeyword_separate, NS_STYLE_BORDER_SEPARATE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBorderImageRepeatKTable[] = {
+ { eCSSKeyword_stretch, NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH },
+ { eCSSKeyword_repeat, NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT },
+ { eCSSKeyword_round, NS_STYLE_BORDER_IMAGE_REPEAT_ROUND },
+ { eCSSKeyword_space, NS_STYLE_BORDER_IMAGE_REPEAT_SPACE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBorderImageSliceKTable[] = {
+ { eCSSKeyword_fill, NS_STYLE_BORDER_IMAGE_SLICE_FILL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBorderStyleKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_BORDER_STYLE_NONE },
+ { eCSSKeyword_hidden, NS_STYLE_BORDER_STYLE_HIDDEN },
+ { eCSSKeyword_dotted, NS_STYLE_BORDER_STYLE_DOTTED },
+ { eCSSKeyword_dashed, NS_STYLE_BORDER_STYLE_DASHED },
+ { eCSSKeyword_solid, NS_STYLE_BORDER_STYLE_SOLID },
+ { eCSSKeyword_double, NS_STYLE_BORDER_STYLE_DOUBLE },
+ { eCSSKeyword_groove, NS_STYLE_BORDER_STYLE_GROOVE },
+ { eCSSKeyword_ridge, NS_STYLE_BORDER_STYLE_RIDGE },
+ { eCSSKeyword_inset, NS_STYLE_BORDER_STYLE_INSET },
+ { eCSSKeyword_outset, NS_STYLE_BORDER_STYLE_OUTSET },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBorderWidthKTable[] = {
+ { eCSSKeyword_thin, NS_STYLE_BORDER_WIDTH_THIN },
+ { eCSSKeyword_medium, NS_STYLE_BORDER_WIDTH_MEDIUM },
+ { eCSSKeyword_thick, NS_STYLE_BORDER_WIDTH_THICK },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBoxDecorationBreakKTable[] = {
+ { eCSSKeyword_slice, StyleBoxDecorationBreak::Slice },
+ { eCSSKeyword_clone, StyleBoxDecorationBreak::Clone },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBoxShadowTypeKTable[] = {
+ { eCSSKeyword_inset, uint8_t(StyleBoxShadowType::Inset) },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBoxSizingKTable[] = {
+ { eCSSKeyword_content_box, StyleBoxSizing::Content },
+ { eCSSKeyword_border_box, StyleBoxSizing::Border },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kCaptionSideKTable[] = {
+ { eCSSKeyword_top, NS_STYLE_CAPTION_SIDE_TOP },
+ { eCSSKeyword_right, NS_STYLE_CAPTION_SIDE_RIGHT },
+ { eCSSKeyword_bottom, NS_STYLE_CAPTION_SIDE_BOTTOM },
+ { eCSSKeyword_left, NS_STYLE_CAPTION_SIDE_LEFT },
+ { eCSSKeyword_top_outside, NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE },
+ { eCSSKeyword_bottom_outside, NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+KTableEntry nsCSSProps::kClearKTable[] = {
+ { eCSSKeyword_none, StyleClear::None },
+ { eCSSKeyword_left, StyleClear::Left },
+ { eCSSKeyword_right, StyleClear::Right },
+ { eCSSKeyword_inline_start, StyleClear::InlineStart },
+ { eCSSKeyword_inline_end, StyleClear::InlineEnd },
+ { eCSSKeyword_both, StyleClear::Both },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+// See also kContextPatternKTable for SVG paint-specific values
+const KTableEntry nsCSSProps::kColorKTable[] = {
+ { eCSSKeyword_activeborder, LookAndFeel::eColorID_activeborder },
+ { eCSSKeyword_activecaption, LookAndFeel::eColorID_activecaption },
+ { eCSSKeyword_appworkspace, LookAndFeel::eColorID_appworkspace },
+ { eCSSKeyword_background, LookAndFeel::eColorID_background },
+ { eCSSKeyword_buttonface, LookAndFeel::eColorID_buttonface },
+ { eCSSKeyword_buttonhighlight, LookAndFeel::eColorID_buttonhighlight },
+ { eCSSKeyword_buttonshadow, LookAndFeel::eColorID_buttonshadow },
+ { eCSSKeyword_buttontext, LookAndFeel::eColorID_buttontext },
+ { eCSSKeyword_captiontext, LookAndFeel::eColorID_captiontext },
+ { eCSSKeyword_graytext, LookAndFeel::eColorID_graytext },
+ { eCSSKeyword_highlight, LookAndFeel::eColorID_highlight },
+ { eCSSKeyword_highlighttext, LookAndFeel::eColorID_highlighttext },
+ { eCSSKeyword_inactiveborder, LookAndFeel::eColorID_inactiveborder },
+ { eCSSKeyword_inactivecaption, LookAndFeel::eColorID_inactivecaption },
+ { eCSSKeyword_inactivecaptiontext, LookAndFeel::eColorID_inactivecaptiontext },
+ { eCSSKeyword_infobackground, LookAndFeel::eColorID_infobackground },
+ { eCSSKeyword_infotext, LookAndFeel::eColorID_infotext },
+ { eCSSKeyword_menu, LookAndFeel::eColorID_menu },
+ { eCSSKeyword_menutext, LookAndFeel::eColorID_menutext },
+ { eCSSKeyword_scrollbar, LookAndFeel::eColorID_scrollbar },
+ { eCSSKeyword_threeddarkshadow, LookAndFeel::eColorID_threeddarkshadow },
+ { eCSSKeyword_threedface, LookAndFeel::eColorID_threedface },
+ { eCSSKeyword_threedhighlight, LookAndFeel::eColorID_threedhighlight },
+ { eCSSKeyword_threedlightshadow, LookAndFeel::eColorID_threedlightshadow },
+ { eCSSKeyword_threedshadow, LookAndFeel::eColorID_threedshadow },
+ { eCSSKeyword_window, LookAndFeel::eColorID_window },
+ { eCSSKeyword_windowframe, LookAndFeel::eColorID_windowframe },
+ { eCSSKeyword_windowtext, LookAndFeel::eColorID_windowtext },
+ { eCSSKeyword__moz_activehyperlinktext, NS_COLOR_MOZ_ACTIVEHYPERLINKTEXT },
+ { eCSSKeyword__moz_buttondefault, LookAndFeel::eColorID__moz_buttondefault },
+ { eCSSKeyword__moz_buttonhoverface, LookAndFeel::eColorID__moz_buttonhoverface },
+ { eCSSKeyword__moz_buttonhovertext, LookAndFeel::eColorID__moz_buttonhovertext },
+ { eCSSKeyword__moz_cellhighlight, LookAndFeel::eColorID__moz_cellhighlight },
+ { eCSSKeyword__moz_cellhighlighttext, LookAndFeel::eColorID__moz_cellhighlighttext },
+ { eCSSKeyword__moz_eventreerow, LookAndFeel::eColorID__moz_eventreerow },
+ { eCSSKeyword__moz_field, LookAndFeel::eColorID__moz_field },
+ { eCSSKeyword__moz_fieldtext, LookAndFeel::eColorID__moz_fieldtext },
+ { eCSSKeyword__moz_default_background_color, NS_COLOR_MOZ_DEFAULT_BACKGROUND_COLOR },
+ { eCSSKeyword__moz_default_color, NS_COLOR_MOZ_DEFAULT_COLOR },
+ { eCSSKeyword__moz_dialog, LookAndFeel::eColorID__moz_dialog },
+ { eCSSKeyword__moz_dialogtext, LookAndFeel::eColorID__moz_dialogtext },
+ { eCSSKeyword__moz_dragtargetzone, LookAndFeel::eColorID__moz_dragtargetzone },
+ { eCSSKeyword__moz_gtk_info_bar_text, LookAndFeel::eColorID__moz_gtk_info_bar_text },
+ { eCSSKeyword__moz_hyperlinktext, NS_COLOR_MOZ_HYPERLINKTEXT },
+ { eCSSKeyword__moz_html_cellhighlight, LookAndFeel::eColorID__moz_html_cellhighlight },
+ { eCSSKeyword__moz_html_cellhighlighttext, LookAndFeel::eColorID__moz_html_cellhighlighttext },
+ { eCSSKeyword__moz_mac_buttonactivetext, LookAndFeel::eColorID__moz_mac_buttonactivetext },
+ { eCSSKeyword__moz_mac_chrome_active, LookAndFeel::eColorID__moz_mac_chrome_active },
+ { eCSSKeyword__moz_mac_chrome_inactive, LookAndFeel::eColorID__moz_mac_chrome_inactive },
+ { eCSSKeyword__moz_mac_defaultbuttontext, LookAndFeel::eColorID__moz_mac_defaultbuttontext },
+ { eCSSKeyword__moz_mac_focusring, LookAndFeel::eColorID__moz_mac_focusring },
+ { eCSSKeyword__moz_mac_menuselect, LookAndFeel::eColorID__moz_mac_menuselect },
+ { eCSSKeyword__moz_mac_menushadow, LookAndFeel::eColorID__moz_mac_menushadow },
+ { eCSSKeyword__moz_mac_menutextdisable, LookAndFeel::eColorID__moz_mac_menutextdisable },
+ { eCSSKeyword__moz_mac_menutextselect, LookAndFeel::eColorID__moz_mac_menutextselect },
+ { eCSSKeyword__moz_mac_disabledtoolbartext, LookAndFeel::eColorID__moz_mac_disabledtoolbartext },
+ { eCSSKeyword__moz_mac_secondaryhighlight, LookAndFeel::eColorID__moz_mac_secondaryhighlight },
+ { eCSSKeyword__moz_menuhover, LookAndFeel::eColorID__moz_menuhover },
+ { eCSSKeyword__moz_menuhovertext, LookAndFeel::eColorID__moz_menuhovertext },
+ { eCSSKeyword__moz_menubartext, LookAndFeel::eColorID__moz_menubartext },
+ { eCSSKeyword__moz_menubarhovertext, LookAndFeel::eColorID__moz_menubarhovertext },
+ { eCSSKeyword__moz_oddtreerow, LookAndFeel::eColorID__moz_oddtreerow },
+ { eCSSKeyword__moz_visitedhyperlinktext, NS_COLOR_MOZ_VISITEDHYPERLINKTEXT },
+ { eCSSKeyword_currentcolor, NS_COLOR_CURRENTCOLOR },
+ { eCSSKeyword__moz_win_mediatext, LookAndFeel::eColorID__moz_win_mediatext },
+ { eCSSKeyword__moz_win_communicationstext, LookAndFeel::eColorID__moz_win_communicationstext },
+ { eCSSKeyword__moz_nativehyperlinktext, LookAndFeel::eColorID__moz_nativehyperlinktext },
+ { eCSSKeyword__moz_comboboxtext, LookAndFeel::eColorID__moz_comboboxtext },
+ { eCSSKeyword__moz_combobox, LookAndFeel::eColorID__moz_combobox },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kContentKTable[] = {
+ { eCSSKeyword_open_quote, NS_STYLE_CONTENT_OPEN_QUOTE },
+ { eCSSKeyword_close_quote, NS_STYLE_CONTENT_CLOSE_QUOTE },
+ { eCSSKeyword_no_open_quote, NS_STYLE_CONTENT_NO_OPEN_QUOTE },
+ { eCSSKeyword_no_close_quote, NS_STYLE_CONTENT_NO_CLOSE_QUOTE },
+ { eCSSKeyword__moz_alt_content, NS_STYLE_CONTENT_ALT_CONTENT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kControlCharacterVisibilityKTable[] = {
+ { eCSSKeyword_hidden, NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN },
+ { eCSSKeyword_visible, NS_STYLE_CONTROL_CHARACTER_VISIBILITY_VISIBLE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kCounterRangeKTable[] = {
+ { eCSSKeyword_infinite, NS_STYLE_COUNTER_RANGE_INFINITE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kCounterSpeakAsKTable[] = {
+ { eCSSKeyword_bullets, NS_STYLE_COUNTER_SPEAKAS_BULLETS },
+ { eCSSKeyword_numbers, NS_STYLE_COUNTER_SPEAKAS_NUMBERS },
+ { eCSSKeyword_words, NS_STYLE_COUNTER_SPEAKAS_WORDS },
+ { eCSSKeyword_spell_out, NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kCounterSymbolsSystemKTable[] = {
+ { eCSSKeyword_cyclic, NS_STYLE_COUNTER_SYSTEM_CYCLIC },
+ { eCSSKeyword_numeric, NS_STYLE_COUNTER_SYSTEM_NUMERIC },
+ { eCSSKeyword_alphabetic, NS_STYLE_COUNTER_SYSTEM_ALPHABETIC },
+ { eCSSKeyword_symbolic, NS_STYLE_COUNTER_SYSTEM_SYMBOLIC },
+ { eCSSKeyword_fixed, NS_STYLE_COUNTER_SYSTEM_FIXED },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kCounterSystemKTable[] = {
+ { eCSSKeyword_cyclic, NS_STYLE_COUNTER_SYSTEM_CYCLIC },
+ { eCSSKeyword_numeric, NS_STYLE_COUNTER_SYSTEM_NUMERIC },
+ { eCSSKeyword_alphabetic, NS_STYLE_COUNTER_SYSTEM_ALPHABETIC },
+ { eCSSKeyword_symbolic, NS_STYLE_COUNTER_SYSTEM_SYMBOLIC },
+ { eCSSKeyword_additive, NS_STYLE_COUNTER_SYSTEM_ADDITIVE },
+ { eCSSKeyword_fixed, NS_STYLE_COUNTER_SYSTEM_FIXED },
+ { eCSSKeyword_extends, NS_STYLE_COUNTER_SYSTEM_EXTENDS },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kCursorKTable[] = {
+ // CSS 2.0
+ { eCSSKeyword_auto, NS_STYLE_CURSOR_AUTO },
+ { eCSSKeyword_crosshair, NS_STYLE_CURSOR_CROSSHAIR },
+ { eCSSKeyword_default, NS_STYLE_CURSOR_DEFAULT },
+ { eCSSKeyword_pointer, NS_STYLE_CURSOR_POINTER },
+ { eCSSKeyword_move, NS_STYLE_CURSOR_MOVE },
+ { eCSSKeyword_e_resize, NS_STYLE_CURSOR_E_RESIZE },
+ { eCSSKeyword_ne_resize, NS_STYLE_CURSOR_NE_RESIZE },
+ { eCSSKeyword_nw_resize, NS_STYLE_CURSOR_NW_RESIZE },
+ { eCSSKeyword_n_resize, NS_STYLE_CURSOR_N_RESIZE },
+ { eCSSKeyword_se_resize, NS_STYLE_CURSOR_SE_RESIZE },
+ { eCSSKeyword_sw_resize, NS_STYLE_CURSOR_SW_RESIZE },
+ { eCSSKeyword_s_resize, NS_STYLE_CURSOR_S_RESIZE },
+ { eCSSKeyword_w_resize, NS_STYLE_CURSOR_W_RESIZE },
+ { eCSSKeyword_text, NS_STYLE_CURSOR_TEXT },
+ { eCSSKeyword_wait, NS_STYLE_CURSOR_WAIT },
+ { eCSSKeyword_help, NS_STYLE_CURSOR_HELP },
+ // CSS 2.1
+ { eCSSKeyword_progress, NS_STYLE_CURSOR_SPINNING },
+ // CSS3 basic user interface module
+ { eCSSKeyword_copy, NS_STYLE_CURSOR_COPY },
+ { eCSSKeyword_alias, NS_STYLE_CURSOR_ALIAS },
+ { eCSSKeyword_context_menu, NS_STYLE_CURSOR_CONTEXT_MENU },
+ { eCSSKeyword_cell, NS_STYLE_CURSOR_CELL },
+ { eCSSKeyword_not_allowed, NS_STYLE_CURSOR_NOT_ALLOWED },
+ { eCSSKeyword_col_resize, NS_STYLE_CURSOR_COL_RESIZE },
+ { eCSSKeyword_row_resize, NS_STYLE_CURSOR_ROW_RESIZE },
+ { eCSSKeyword_no_drop, NS_STYLE_CURSOR_NO_DROP },
+ { eCSSKeyword_vertical_text, NS_STYLE_CURSOR_VERTICAL_TEXT },
+ { eCSSKeyword_all_scroll, NS_STYLE_CURSOR_ALL_SCROLL },
+ { eCSSKeyword_nesw_resize, NS_STYLE_CURSOR_NESW_RESIZE },
+ { eCSSKeyword_nwse_resize, NS_STYLE_CURSOR_NWSE_RESIZE },
+ { eCSSKeyword_ns_resize, NS_STYLE_CURSOR_NS_RESIZE },
+ { eCSSKeyword_ew_resize, NS_STYLE_CURSOR_EW_RESIZE },
+ { eCSSKeyword_none, NS_STYLE_CURSOR_NONE },
+ { eCSSKeyword_grab, NS_STYLE_CURSOR_GRAB },
+ { eCSSKeyword_grabbing, NS_STYLE_CURSOR_GRABBING },
+ { eCSSKeyword_zoom_in, NS_STYLE_CURSOR_ZOOM_IN },
+ { eCSSKeyword_zoom_out, NS_STYLE_CURSOR_ZOOM_OUT },
+ // -moz- prefixed vendor specific
+ { eCSSKeyword__moz_grab, NS_STYLE_CURSOR_GRAB },
+ { eCSSKeyword__moz_grabbing, NS_STYLE_CURSOR_GRABBING },
+ { eCSSKeyword__moz_zoom_in, NS_STYLE_CURSOR_ZOOM_IN },
+ { eCSSKeyword__moz_zoom_out, NS_STYLE_CURSOR_ZOOM_OUT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kDirectionKTable[] = {
+ { eCSSKeyword_ltr, NS_STYLE_DIRECTION_LTR },
+ { eCSSKeyword_rtl, NS_STYLE_DIRECTION_RTL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+KTableEntry nsCSSProps::kDisplayKTable[] = {
+ { eCSSKeyword_none, StyleDisplay::None },
+ { eCSSKeyword_inline, StyleDisplay::Inline },
+ { eCSSKeyword_block, StyleDisplay::Block },
+ { eCSSKeyword_inline_block, StyleDisplay::InlineBlock },
+ { eCSSKeyword_list_item, StyleDisplay::ListItem },
+ { eCSSKeyword_table, StyleDisplay::Table },
+ { eCSSKeyword_inline_table, StyleDisplay::InlineTable },
+ { eCSSKeyword_table_row_group, StyleDisplay::TableRowGroup },
+ { eCSSKeyword_table_header_group, StyleDisplay::TableHeaderGroup },
+ { eCSSKeyword_table_footer_group, StyleDisplay::TableFooterGroup },
+ { eCSSKeyword_table_row, StyleDisplay::TableRow },
+ { eCSSKeyword_table_column_group, StyleDisplay::TableColumnGroup },
+ { eCSSKeyword_table_column, StyleDisplay::TableColumn },
+ { eCSSKeyword_table_cell, StyleDisplay::TableCell },
+ { eCSSKeyword_table_caption, StyleDisplay::TableCaption },
+ // Make sure this is kept in sync with the code in
+ // nsCSSFrameConstructor::ConstructXULFrame
+ { eCSSKeyword__moz_box, StyleDisplay::Box },
+ { eCSSKeyword__moz_inline_box, StyleDisplay::InlineBox },
+#ifdef MOZ_XUL
+ { eCSSKeyword__moz_grid, StyleDisplay::XulGrid },
+ { eCSSKeyword__moz_inline_grid, StyleDisplay::InlineXulGrid },
+ { eCSSKeyword__moz_grid_group, StyleDisplay::XulGridGroup },
+ { eCSSKeyword__moz_grid_line, StyleDisplay::XulGridLine },
+ { eCSSKeyword__moz_stack, StyleDisplay::Stack },
+ { eCSSKeyword__moz_inline_stack, StyleDisplay::InlineStack },
+ { eCSSKeyword__moz_deck, StyleDisplay::Deck },
+ { eCSSKeyword__moz_popup, StyleDisplay::Popup },
+ { eCSSKeyword__moz_groupbox, StyleDisplay::Groupbox },
+#endif
+ { eCSSKeyword_flex, StyleDisplay::Flex },
+ { eCSSKeyword_inline_flex, StyleDisplay::InlineFlex },
+ { eCSSKeyword_ruby, StyleDisplay::Ruby },
+ { eCSSKeyword_ruby_base, StyleDisplay::RubyBase },
+ { eCSSKeyword_ruby_base_container, StyleDisplay::RubyBaseContainer },
+ { eCSSKeyword_ruby_text, StyleDisplay::RubyText },
+ { eCSSKeyword_ruby_text_container, StyleDisplay::RubyTextContainer },
+ // The next two entries are controlled by the layout.css.grid.enabled pref.
+ { eCSSKeyword_grid, StyleDisplay::Grid },
+ { eCSSKeyword_inline_grid, StyleDisplay::InlineGrid },
+ // The next 4 entries are controlled by the layout.css.prefixes.webkit pref.
+ { eCSSKeyword__webkit_box, StyleDisplay::WebkitBox },
+ { eCSSKeyword__webkit_inline_box, StyleDisplay::WebkitInlineBox },
+ { eCSSKeyword__webkit_flex, StyleDisplay::Flex },
+ { eCSSKeyword__webkit_inline_flex, StyleDisplay::InlineFlex },
+ // The next entry is controlled by the layout.css.display-contents.enabled
+ // pref.
+ { eCSSKeyword_contents, StyleDisplay::Contents },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kEmptyCellsKTable[] = {
+ { eCSSKeyword_show, NS_STYLE_TABLE_EMPTY_CELLS_SHOW },
+ { eCSSKeyword_hide, NS_STYLE_TABLE_EMPTY_CELLS_HIDE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignAllKeywords[] = {
+ { eCSSKeyword_auto, NS_STYLE_ALIGN_AUTO },
+ { eCSSKeyword_normal, NS_STYLE_ALIGN_NORMAL },
+ { eCSSKeyword_start, NS_STYLE_ALIGN_START },
+ { eCSSKeyword_end, NS_STYLE_ALIGN_END },
+ { eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START },
+ { eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END },
+ { eCSSKeyword_center, NS_STYLE_ALIGN_CENTER },
+ { eCSSKeyword_left, NS_STYLE_ALIGN_LEFT },
+ { eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT },
+ { eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE },
+ // Also "first/last baseline"; see nsCSSValue::AppendAlignJustifyValueToString
+ { eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH },
+ { eCSSKeyword_self_start, NS_STYLE_ALIGN_SELF_START },
+ { eCSSKeyword_self_end, NS_STYLE_ALIGN_SELF_END },
+ { eCSSKeyword_space_between, NS_STYLE_ALIGN_SPACE_BETWEEN },
+ { eCSSKeyword_space_around, NS_STYLE_ALIGN_SPACE_AROUND },
+ { eCSSKeyword_space_evenly, NS_STYLE_ALIGN_SPACE_EVENLY },
+ { eCSSKeyword_legacy, NS_STYLE_ALIGN_LEGACY },
+ { eCSSKeyword_safe, NS_STYLE_ALIGN_SAFE },
+ { eCSSKeyword_unsafe, NS_STYLE_ALIGN_UNSAFE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignOverflowPosition[] = {
+ { eCSSKeyword_unsafe, NS_STYLE_ALIGN_UNSAFE },
+ { eCSSKeyword_safe, NS_STYLE_ALIGN_SAFE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignSelfPosition[] = {
+ { eCSSKeyword_start, NS_STYLE_ALIGN_START },
+ { eCSSKeyword_end, NS_STYLE_ALIGN_END },
+ { eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START },
+ { eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END },
+ { eCSSKeyword_center, NS_STYLE_ALIGN_CENTER },
+ { eCSSKeyword_left, NS_STYLE_ALIGN_LEFT },
+ { eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT },
+ { eCSSKeyword_self_start, NS_STYLE_ALIGN_SELF_START },
+ { eCSSKeyword_self_end, NS_STYLE_ALIGN_SELF_END },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignLegacy[] = {
+ { eCSSKeyword_legacy, NS_STYLE_ALIGN_LEGACY },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignLegacyPosition[] = {
+ { eCSSKeyword_center, NS_STYLE_ALIGN_CENTER },
+ { eCSSKeyword_left, NS_STYLE_ALIGN_LEFT },
+ { eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignAutoNormalStretchBaseline[] = {
+ { eCSSKeyword_auto, NS_STYLE_ALIGN_AUTO },
+ { eCSSKeyword_normal, NS_STYLE_ALIGN_NORMAL },
+ { eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH },
+ { eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE },
+ // Also "first baseline" & "last baseline"; see CSSParserImpl::ParseAlignEnum
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignNormalStretchBaseline[] = {
+ { eCSSKeyword_normal, NS_STYLE_ALIGN_NORMAL },
+ { eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH },
+ { eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE },
+ // Also "first baseline" & "last baseline"; see CSSParserImpl::ParseAlignEnum
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignNormalBaseline[] = {
+ { eCSSKeyword_normal, NS_STYLE_ALIGN_NORMAL },
+ { eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE },
+ // Also "first baseline" & "last baseline"; see CSSParserImpl::ParseAlignEnum
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignContentDistribution[] = {
+ { eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH },
+ { eCSSKeyword_space_between, NS_STYLE_ALIGN_SPACE_BETWEEN },
+ { eCSSKeyword_space_around, NS_STYLE_ALIGN_SPACE_AROUND },
+ { eCSSKeyword_space_evenly, NS_STYLE_ALIGN_SPACE_EVENLY },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAlignContentPosition[] = {
+ { eCSSKeyword_start, NS_STYLE_ALIGN_START },
+ { eCSSKeyword_end, NS_STYLE_ALIGN_END },
+ { eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START },
+ { eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END },
+ { eCSSKeyword_center, NS_STYLE_ALIGN_CENTER },
+ { eCSSKeyword_left, NS_STYLE_ALIGN_LEFT },
+ { eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+// <NOTE> these are only used for auto-completion, not parsing:
+const KTableEntry nsCSSProps::kAutoCompletionAlignJustifySelf[] = {
+ { eCSSKeyword_auto, NS_STYLE_ALIGN_AUTO },
+ { eCSSKeyword_normal, NS_STYLE_ALIGN_NORMAL },
+ { eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH },
+ { eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE },
+ { eCSSKeyword_last_baseline, NS_STYLE_ALIGN_LAST_BASELINE },
+ { eCSSKeyword_start, NS_STYLE_ALIGN_START },
+ { eCSSKeyword_end, NS_STYLE_ALIGN_END },
+ { eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START },
+ { eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END },
+ { eCSSKeyword_center, NS_STYLE_ALIGN_CENTER },
+ { eCSSKeyword_left, NS_STYLE_ALIGN_LEFT },
+ { eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT },
+ { eCSSKeyword_self_start, NS_STYLE_ALIGN_SELF_START },
+ { eCSSKeyword_self_end, NS_STYLE_ALIGN_SELF_END },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAutoCompletionAlignItems[] = {
+ // Intentionally no 'auto' here.
+ { eCSSKeyword_normal, NS_STYLE_ALIGN_NORMAL },
+ { eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH },
+ { eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE },
+ { eCSSKeyword_last_baseline, NS_STYLE_ALIGN_LAST_BASELINE },
+ { eCSSKeyword_start, NS_STYLE_ALIGN_START },
+ { eCSSKeyword_end, NS_STYLE_ALIGN_END },
+ { eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START },
+ { eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END },
+ { eCSSKeyword_center, NS_STYLE_ALIGN_CENTER },
+ { eCSSKeyword_left, NS_STYLE_ALIGN_LEFT },
+ { eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT },
+ { eCSSKeyword_self_start, NS_STYLE_ALIGN_SELF_START },
+ { eCSSKeyword_self_end, NS_STYLE_ALIGN_SELF_END },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kAutoCompletionAlignJustifyContent[] = {
+ // Intentionally no 'auto' here.
+ { eCSSKeyword_normal, NS_STYLE_ALIGN_NORMAL },
+ { eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE },
+ { eCSSKeyword_last_baseline, NS_STYLE_ALIGN_LAST_BASELINE },
+ { eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH },
+ { eCSSKeyword_space_between, NS_STYLE_ALIGN_SPACE_BETWEEN },
+ { eCSSKeyword_space_around, NS_STYLE_ALIGN_SPACE_AROUND },
+ { eCSSKeyword_space_evenly, NS_STYLE_ALIGN_SPACE_EVENLY },
+ { eCSSKeyword_start, NS_STYLE_ALIGN_START },
+ { eCSSKeyword_end, NS_STYLE_ALIGN_END },
+ { eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START },
+ { eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END },
+ { eCSSKeyword_center, NS_STYLE_ALIGN_CENTER },
+ { eCSSKeyword_left, NS_STYLE_ALIGN_LEFT },
+ { eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+// </NOTE>
+
+const KTableEntry nsCSSProps::kFlexDirectionKTable[] = {
+ { eCSSKeyword_row, NS_STYLE_FLEX_DIRECTION_ROW },
+ { eCSSKeyword_row_reverse, NS_STYLE_FLEX_DIRECTION_ROW_REVERSE },
+ { eCSSKeyword_column, NS_STYLE_FLEX_DIRECTION_COLUMN },
+ { eCSSKeyword_column_reverse, NS_STYLE_FLEX_DIRECTION_COLUMN_REVERSE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFlexWrapKTable[] = {
+ { eCSSKeyword_nowrap, NS_STYLE_FLEX_WRAP_NOWRAP },
+ { eCSSKeyword_wrap, NS_STYLE_FLEX_WRAP_WRAP },
+ { eCSSKeyword_wrap_reverse, NS_STYLE_FLEX_WRAP_WRAP_REVERSE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kHyphensKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_HYPHENS_NONE },
+ { eCSSKeyword_manual, NS_STYLE_HYPHENS_MANUAL },
+ { eCSSKeyword_auto, NS_STYLE_HYPHENS_AUTO },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+KTableEntry nsCSSProps::kFloatKTable[] = {
+ { eCSSKeyword_none, StyleFloat::None },
+ { eCSSKeyword_left, StyleFloat::Left },
+ { eCSSKeyword_right, StyleFloat::Right },
+ { eCSSKeyword_inline_start, StyleFloat::InlineStart },
+ { eCSSKeyword_inline_end, StyleFloat::InlineEnd },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFloatEdgeKTable[] = {
+ { eCSSKeyword_content_box, uint8_t(StyleFloatEdge::ContentBox) },
+ { eCSSKeyword_margin_box, uint8_t(StyleFloatEdge::MarginBox) },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontDisplayKTable[] = {
+ { eCSSKeyword_auto, NS_FONT_DISPLAY_AUTO },
+ { eCSSKeyword_block, NS_FONT_DISPLAY_BLOCK },
+ { eCSSKeyword_swap, NS_FONT_DISPLAY_SWAP },
+ { eCSSKeyword_fallback, NS_FONT_DISPLAY_FALLBACK },
+ { eCSSKeyword_optional, NS_FONT_DISPLAY_OPTIONAL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontKTable[] = {
+ // CSS2.
+ { eCSSKeyword_caption, NS_STYLE_FONT_CAPTION },
+ { eCSSKeyword_icon, NS_STYLE_FONT_ICON },
+ { eCSSKeyword_menu, NS_STYLE_FONT_MENU },
+ { eCSSKeyword_message_box, NS_STYLE_FONT_MESSAGE_BOX },
+ { eCSSKeyword_small_caption, NS_STYLE_FONT_SMALL_CAPTION },
+ { eCSSKeyword_status_bar, NS_STYLE_FONT_STATUS_BAR },
+
+ // Proposed for CSS3.
+ { eCSSKeyword__moz_window, NS_STYLE_FONT_WINDOW },
+ { eCSSKeyword__moz_document, NS_STYLE_FONT_DOCUMENT },
+ { eCSSKeyword__moz_workspace, NS_STYLE_FONT_WORKSPACE },
+ { eCSSKeyword__moz_desktop, NS_STYLE_FONT_DESKTOP },
+ { eCSSKeyword__moz_info, NS_STYLE_FONT_INFO },
+ { eCSSKeyword__moz_dialog, NS_STYLE_FONT_DIALOG },
+ { eCSSKeyword__moz_button, NS_STYLE_FONT_BUTTON },
+ { eCSSKeyword__moz_pull_down_menu, NS_STYLE_FONT_PULL_DOWN_MENU },
+ { eCSSKeyword__moz_list, NS_STYLE_FONT_LIST },
+ { eCSSKeyword__moz_field, NS_STYLE_FONT_FIELD },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontKerningKTable[] = {
+ { eCSSKeyword_auto, NS_FONT_KERNING_AUTO },
+ { eCSSKeyword_none, NS_FONT_KERNING_NONE },
+ { eCSSKeyword_normal, NS_FONT_KERNING_NORMAL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontSizeKTable[] = {
+ { eCSSKeyword_xx_small, NS_STYLE_FONT_SIZE_XXSMALL },
+ { eCSSKeyword_x_small, NS_STYLE_FONT_SIZE_XSMALL },
+ { eCSSKeyword_small, NS_STYLE_FONT_SIZE_SMALL },
+ { eCSSKeyword_medium, NS_STYLE_FONT_SIZE_MEDIUM },
+ { eCSSKeyword_large, NS_STYLE_FONT_SIZE_LARGE },
+ { eCSSKeyword_x_large, NS_STYLE_FONT_SIZE_XLARGE },
+ { eCSSKeyword_xx_large, NS_STYLE_FONT_SIZE_XXLARGE },
+ { eCSSKeyword_larger, NS_STYLE_FONT_SIZE_LARGER },
+ { eCSSKeyword_smaller, NS_STYLE_FONT_SIZE_SMALLER },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontSmoothingKTable[] = {
+ { eCSSKeyword_auto, NS_FONT_SMOOTHING_AUTO },
+ { eCSSKeyword_grayscale, NS_FONT_SMOOTHING_GRAYSCALE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontStretchKTable[] = {
+ { eCSSKeyword_ultra_condensed, NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED },
+ { eCSSKeyword_extra_condensed, NS_STYLE_FONT_STRETCH_EXTRA_CONDENSED },
+ { eCSSKeyword_condensed, NS_STYLE_FONT_STRETCH_CONDENSED },
+ { eCSSKeyword_semi_condensed, NS_STYLE_FONT_STRETCH_SEMI_CONDENSED },
+ { eCSSKeyword_normal, NS_STYLE_FONT_STRETCH_NORMAL },
+ { eCSSKeyword_semi_expanded, NS_STYLE_FONT_STRETCH_SEMI_EXPANDED },
+ { eCSSKeyword_expanded, NS_STYLE_FONT_STRETCH_EXPANDED },
+ { eCSSKeyword_extra_expanded, NS_STYLE_FONT_STRETCH_EXTRA_EXPANDED },
+ { eCSSKeyword_ultra_expanded, NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontStyleKTable[] = {
+ { eCSSKeyword_normal, NS_STYLE_FONT_STYLE_NORMAL },
+ { eCSSKeyword_italic, NS_STYLE_FONT_STYLE_ITALIC },
+ { eCSSKeyword_oblique, NS_STYLE_FONT_STYLE_OBLIQUE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontSynthesisKTable[] = {
+ { eCSSKeyword_weight, NS_FONT_SYNTHESIS_WEIGHT },
+ { eCSSKeyword_style, NS_FONT_SYNTHESIS_STYLE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontVariantAlternatesKTable[] = {
+ { eCSSKeyword_historical_forms, NS_FONT_VARIANT_ALTERNATES_HISTORICAL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontVariantAlternatesFuncsKTable[] = {
+ { eCSSKeyword_stylistic, NS_FONT_VARIANT_ALTERNATES_STYLISTIC },
+ { eCSSKeyword_styleset, NS_FONT_VARIANT_ALTERNATES_STYLESET },
+ { eCSSKeyword_character_variant, NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT },
+ { eCSSKeyword_swash, NS_FONT_VARIANT_ALTERNATES_SWASH },
+ { eCSSKeyword_ornaments, NS_FONT_VARIANT_ALTERNATES_ORNAMENTS },
+ { eCSSKeyword_annotation, NS_FONT_VARIANT_ALTERNATES_ANNOTATION },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontVariantCapsKTable[] = {
+ { eCSSKeyword_small_caps, NS_FONT_VARIANT_CAPS_SMALLCAPS },
+ { eCSSKeyword_all_small_caps, NS_FONT_VARIANT_CAPS_ALLSMALL },
+ { eCSSKeyword_petite_caps, NS_FONT_VARIANT_CAPS_PETITECAPS },
+ { eCSSKeyword_all_petite_caps, NS_FONT_VARIANT_CAPS_ALLPETITE },
+ { eCSSKeyword_titling_caps, NS_FONT_VARIANT_CAPS_TITLING },
+ { eCSSKeyword_unicase, NS_FONT_VARIANT_CAPS_UNICASE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontVariantEastAsianKTable[] = {
+ { eCSSKeyword_jis78, NS_FONT_VARIANT_EAST_ASIAN_JIS78 },
+ { eCSSKeyword_jis83, NS_FONT_VARIANT_EAST_ASIAN_JIS83 },
+ { eCSSKeyword_jis90, NS_FONT_VARIANT_EAST_ASIAN_JIS90 },
+ { eCSSKeyword_jis04, NS_FONT_VARIANT_EAST_ASIAN_JIS04 },
+ { eCSSKeyword_simplified, NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED },
+ { eCSSKeyword_traditional, NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL },
+ { eCSSKeyword_full_width, NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH },
+ { eCSSKeyword_proportional_width, NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH },
+ { eCSSKeyword_ruby, NS_FONT_VARIANT_EAST_ASIAN_RUBY },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontVariantLigaturesKTable[] = {
+ { eCSSKeyword_common_ligatures, NS_FONT_VARIANT_LIGATURES_COMMON },
+ { eCSSKeyword_no_common_ligatures, NS_FONT_VARIANT_LIGATURES_NO_COMMON },
+ { eCSSKeyword_discretionary_ligatures, NS_FONT_VARIANT_LIGATURES_DISCRETIONARY },
+ { eCSSKeyword_no_discretionary_ligatures, NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY },
+ { eCSSKeyword_historical_ligatures, NS_FONT_VARIANT_LIGATURES_HISTORICAL },
+ { eCSSKeyword_no_historical_ligatures, NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL },
+ { eCSSKeyword_contextual, NS_FONT_VARIANT_LIGATURES_CONTEXTUAL },
+ { eCSSKeyword_no_contextual, NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontVariantNumericKTable[] = {
+ { eCSSKeyword_lining_nums, NS_FONT_VARIANT_NUMERIC_LINING },
+ { eCSSKeyword_oldstyle_nums, NS_FONT_VARIANT_NUMERIC_OLDSTYLE },
+ { eCSSKeyword_proportional_nums, NS_FONT_VARIANT_NUMERIC_PROPORTIONAL },
+ { eCSSKeyword_tabular_nums, NS_FONT_VARIANT_NUMERIC_TABULAR },
+ { eCSSKeyword_diagonal_fractions, NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS },
+ { eCSSKeyword_stacked_fractions, NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS },
+ { eCSSKeyword_slashed_zero, NS_FONT_VARIANT_NUMERIC_SLASHZERO },
+ { eCSSKeyword_ordinal, NS_FONT_VARIANT_NUMERIC_ORDINAL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontVariantPositionKTable[] = {
+ { eCSSKeyword_super, NS_FONT_VARIANT_POSITION_SUPER },
+ { eCSSKeyword_sub, NS_FONT_VARIANT_POSITION_SUB },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFontWeightKTable[] = {
+ { eCSSKeyword_normal, NS_STYLE_FONT_WEIGHT_NORMAL },
+ { eCSSKeyword_bold, NS_STYLE_FONT_WEIGHT_BOLD },
+ { eCSSKeyword_bolder, NS_STYLE_FONT_WEIGHT_BOLDER },
+ { eCSSKeyword_lighter, NS_STYLE_FONT_WEIGHT_LIGHTER },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kGridAutoFlowKTable[] = {
+ { eCSSKeyword_row, NS_STYLE_GRID_AUTO_FLOW_ROW },
+ { eCSSKeyword_column, NS_STYLE_GRID_AUTO_FLOW_COLUMN },
+ { eCSSKeyword_dense, NS_STYLE_GRID_AUTO_FLOW_DENSE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kGridTrackBreadthKTable[] = {
+ { eCSSKeyword_min_content, NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT },
+ { eCSSKeyword_max_content, NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kImageOrientationKTable[] = {
+ { eCSSKeyword_flip, NS_STYLE_IMAGE_ORIENTATION_FLIP },
+ { eCSSKeyword_from_image, NS_STYLE_IMAGE_ORIENTATION_FROM_IMAGE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kImageOrientationFlipKTable[] = {
+ { eCSSKeyword_flip, NS_STYLE_IMAGE_ORIENTATION_FLIP },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kIsolationKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_ISOLATION_AUTO },
+ { eCSSKeyword_isolate, NS_STYLE_ISOLATION_ISOLATE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kIMEModeKTable[] = {
+ { eCSSKeyword_normal, NS_STYLE_IME_MODE_NORMAL },
+ { eCSSKeyword_auto, NS_STYLE_IME_MODE_AUTO },
+ { eCSSKeyword_active, NS_STYLE_IME_MODE_ACTIVE },
+ { eCSSKeyword_disabled, NS_STYLE_IME_MODE_DISABLED },
+ { eCSSKeyword_inactive, NS_STYLE_IME_MODE_INACTIVE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kLineHeightKTable[] = {
+ // -moz- prefixed, intended for internal use for single-line controls
+ { eCSSKeyword__moz_block_height, NS_STYLE_LINE_HEIGHT_BLOCK_HEIGHT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kListStylePositionKTable[] = {
+ { eCSSKeyword_inside, NS_STYLE_LIST_STYLE_POSITION_INSIDE },
+ { eCSSKeyword_outside, NS_STYLE_LIST_STYLE_POSITION_OUTSIDE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kListStyleKTable[] = {
+ // none and decimal are not redefinable, so they should not be moved.
+ { eCSSKeyword_none, NS_STYLE_LIST_STYLE_NONE },
+ { eCSSKeyword_decimal, NS_STYLE_LIST_STYLE_DECIMAL },
+ // the following graphic styles are processed in a different way.
+ { eCSSKeyword_disc, NS_STYLE_LIST_STYLE_DISC },
+ { eCSSKeyword_circle, NS_STYLE_LIST_STYLE_CIRCLE },
+ { eCSSKeyword_square, NS_STYLE_LIST_STYLE_SQUARE },
+ { eCSSKeyword_disclosure_closed, NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED },
+ { eCSSKeyword_disclosure_open, NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN },
+ // the following counter styles require specific algorithms to generate.
+ { eCSSKeyword_hebrew, NS_STYLE_LIST_STYLE_HEBREW },
+ { eCSSKeyword_japanese_informal, NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL },
+ { eCSSKeyword_japanese_formal, NS_STYLE_LIST_STYLE_JAPANESE_FORMAL },
+ { eCSSKeyword_korean_hangul_formal, NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL },
+ { eCSSKeyword_korean_hanja_informal, NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL },
+ { eCSSKeyword_korean_hanja_formal, NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL },
+ { eCSSKeyword_simp_chinese_informal, NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL },
+ { eCSSKeyword_simp_chinese_formal, NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL },
+ { eCSSKeyword_trad_chinese_informal, NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL },
+ { eCSSKeyword_trad_chinese_formal, NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL },
+ { eCSSKeyword_ethiopic_numeric, NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kMathVariantKTable[] = {
+ { eCSSKeyword_none, NS_MATHML_MATHVARIANT_NONE },
+ { eCSSKeyword_normal, NS_MATHML_MATHVARIANT_NORMAL },
+ { eCSSKeyword_bold, NS_MATHML_MATHVARIANT_BOLD },
+ { eCSSKeyword_italic, NS_MATHML_MATHVARIANT_ITALIC },
+ { eCSSKeyword_bold_italic, NS_MATHML_MATHVARIANT_BOLD_ITALIC },
+ { eCSSKeyword_script, NS_MATHML_MATHVARIANT_SCRIPT },
+ { eCSSKeyword_bold_script, NS_MATHML_MATHVARIANT_BOLD_SCRIPT },
+ { eCSSKeyword_fraktur, NS_MATHML_MATHVARIANT_FRAKTUR },
+ { eCSSKeyword_double_struck, NS_MATHML_MATHVARIANT_DOUBLE_STRUCK },
+ { eCSSKeyword_bold_fraktur, NS_MATHML_MATHVARIANT_BOLD_FRAKTUR },
+ { eCSSKeyword_sans_serif, NS_MATHML_MATHVARIANT_SANS_SERIF },
+ { eCSSKeyword_bold_sans_serif, NS_MATHML_MATHVARIANT_BOLD_SANS_SERIF },
+ { eCSSKeyword_sans_serif_italic, NS_MATHML_MATHVARIANT_SANS_SERIF_ITALIC },
+ { eCSSKeyword_sans_serif_bold_italic, NS_MATHML_MATHVARIANT_SANS_SERIF_BOLD_ITALIC },
+ { eCSSKeyword_monospace, NS_MATHML_MATHVARIANT_MONOSPACE },
+ { eCSSKeyword_initial, NS_MATHML_MATHVARIANT_INITIAL },
+ { eCSSKeyword_tailed, NS_MATHML_MATHVARIANT_TAILED },
+ { eCSSKeyword_looped, NS_MATHML_MATHVARIANT_LOOPED },
+ { eCSSKeyword_stretched, NS_MATHML_MATHVARIANT_STRETCHED },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kMathDisplayKTable[] = {
+ { eCSSKeyword_inline, NS_MATHML_DISPLAYSTYLE_INLINE },
+ { eCSSKeyword_block, NS_MATHML_DISPLAYSTYLE_BLOCK },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kContainKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_CONTAIN_NONE },
+ { eCSSKeyword_strict, NS_STYLE_CONTAIN_STRICT },
+ { eCSSKeyword_layout, NS_STYLE_CONTAIN_LAYOUT },
+ { eCSSKeyword_style, NS_STYLE_CONTAIN_STYLE },
+ { eCSSKeyword_paint, NS_STYLE_CONTAIN_PAINT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kContextOpacityKTable[] = {
+ { eCSSKeyword_context_fill_opacity, NS_STYLE_CONTEXT_FILL_OPACITY },
+ { eCSSKeyword_context_stroke_opacity, NS_STYLE_CONTEXT_STROKE_OPACITY },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kContextPatternKTable[] = {
+ { eCSSKeyword_context_fill, NS_COLOR_CONTEXT_FILL },
+ { eCSSKeyword_context_stroke, NS_COLOR_CONTEXT_STROKE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kObjectFitKTable[] = {
+ { eCSSKeyword_fill, NS_STYLE_OBJECT_FIT_FILL },
+ { eCSSKeyword_contain, NS_STYLE_OBJECT_FIT_CONTAIN },
+ { eCSSKeyword_cover, NS_STYLE_OBJECT_FIT_COVER },
+ { eCSSKeyword_none, NS_STYLE_OBJECT_FIT_NONE },
+ { eCSSKeyword_scale_down, NS_STYLE_OBJECT_FIT_SCALE_DOWN },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kOrientKTable[] = {
+ { eCSSKeyword_inline, StyleOrient::Inline },
+ { eCSSKeyword_block, StyleOrient::Block },
+ { eCSSKeyword_horizontal, StyleOrient::Horizontal },
+ { eCSSKeyword_vertical, StyleOrient::Vertical },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+// Same as kBorderStyleKTable except 'hidden'.
+const KTableEntry nsCSSProps::kOutlineStyleKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_BORDER_STYLE_NONE },
+ { eCSSKeyword_auto, NS_STYLE_BORDER_STYLE_AUTO },
+ { eCSSKeyword_dotted, NS_STYLE_BORDER_STYLE_DOTTED },
+ { eCSSKeyword_dashed, NS_STYLE_BORDER_STYLE_DASHED },
+ { eCSSKeyword_solid, NS_STYLE_BORDER_STYLE_SOLID },
+ { eCSSKeyword_double, NS_STYLE_BORDER_STYLE_DOUBLE },
+ { eCSSKeyword_groove, NS_STYLE_BORDER_STYLE_GROOVE },
+ { eCSSKeyword_ridge, NS_STYLE_BORDER_STYLE_RIDGE },
+ { eCSSKeyword_inset, NS_STYLE_BORDER_STYLE_INSET },
+ { eCSSKeyword_outset, NS_STYLE_BORDER_STYLE_OUTSET },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kOverflowKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_OVERFLOW_AUTO },
+ { eCSSKeyword_visible, NS_STYLE_OVERFLOW_VISIBLE },
+ { eCSSKeyword_hidden, NS_STYLE_OVERFLOW_HIDDEN },
+ { eCSSKeyword_scroll, NS_STYLE_OVERFLOW_SCROLL },
+ // Deprecated:
+ { eCSSKeyword__moz_scrollbars_none, NS_STYLE_OVERFLOW_HIDDEN },
+ { eCSSKeyword__moz_scrollbars_horizontal, NS_STYLE_OVERFLOW_SCROLLBARS_HORIZONTAL },
+ { eCSSKeyword__moz_scrollbars_vertical, NS_STYLE_OVERFLOW_SCROLLBARS_VERTICAL },
+ { eCSSKeyword__moz_hidden_unscrollable, NS_STYLE_OVERFLOW_CLIP },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kOverflowClipBoxKTable[] = {
+ { eCSSKeyword_padding_box, NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX },
+ { eCSSKeyword_content_box, NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kOverflowSubKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_OVERFLOW_AUTO },
+ { eCSSKeyword_visible, NS_STYLE_OVERFLOW_VISIBLE },
+ { eCSSKeyword_hidden, NS_STYLE_OVERFLOW_HIDDEN },
+ { eCSSKeyword_scroll, NS_STYLE_OVERFLOW_SCROLL },
+ // Deprecated:
+ { eCSSKeyword__moz_hidden_unscrollable, NS_STYLE_OVERFLOW_CLIP },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kPageBreakKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_PAGE_BREAK_AUTO },
+ { eCSSKeyword_always, NS_STYLE_PAGE_BREAK_ALWAYS },
+ { eCSSKeyword_avoid, NS_STYLE_PAGE_BREAK_AVOID },
+ { eCSSKeyword_left, NS_STYLE_PAGE_BREAK_LEFT },
+ { eCSSKeyword_right, NS_STYLE_PAGE_BREAK_RIGHT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kPageBreakInsideKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_PAGE_BREAK_AUTO },
+ { eCSSKeyword_avoid, NS_STYLE_PAGE_BREAK_AVOID },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kPageMarksKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_PAGE_MARKS_NONE },
+ { eCSSKeyword_crop, NS_STYLE_PAGE_MARKS_CROP },
+ { eCSSKeyword_cross, NS_STYLE_PAGE_MARKS_REGISTER },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kPageSizeKTable[] = {
+ { eCSSKeyword_landscape, NS_STYLE_PAGE_SIZE_LANDSCAPE },
+ { eCSSKeyword_portrait, NS_STYLE_PAGE_SIZE_PORTRAIT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kPointerEventsKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_POINTER_EVENTS_NONE },
+ { eCSSKeyword_visiblepainted, NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED },
+ { eCSSKeyword_visiblefill, NS_STYLE_POINTER_EVENTS_VISIBLEFILL },
+ { eCSSKeyword_visiblestroke, NS_STYLE_POINTER_EVENTS_VISIBLESTROKE },
+ { eCSSKeyword_visible, NS_STYLE_POINTER_EVENTS_VISIBLE },
+ { eCSSKeyword_painted, NS_STYLE_POINTER_EVENTS_PAINTED },
+ { eCSSKeyword_fill, NS_STYLE_POINTER_EVENTS_FILL },
+ { eCSSKeyword_stroke, NS_STYLE_POINTER_EVENTS_STROKE },
+ { eCSSKeyword_all, NS_STYLE_POINTER_EVENTS_ALL },
+ { eCSSKeyword_auto, NS_STYLE_POINTER_EVENTS_AUTO },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kPositionKTable[] = {
+ { eCSSKeyword_static, NS_STYLE_POSITION_STATIC },
+ { eCSSKeyword_relative, NS_STYLE_POSITION_RELATIVE },
+ { eCSSKeyword_absolute, NS_STYLE_POSITION_ABSOLUTE },
+ { eCSSKeyword_fixed, NS_STYLE_POSITION_FIXED },
+ { eCSSKeyword_sticky, NS_STYLE_POSITION_STICKY },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kRadialGradientShapeKTable[] = {
+ { eCSSKeyword_circle, NS_STYLE_GRADIENT_SHAPE_CIRCULAR },
+ { eCSSKeyword_ellipse, NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kRadialGradientSizeKTable[] = {
+ { eCSSKeyword_closest_side, NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE },
+ { eCSSKeyword_closest_corner, NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER },
+ { eCSSKeyword_farthest_side, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE },
+ { eCSSKeyword_farthest_corner, NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kRadialGradientLegacySizeKTable[] = {
+ { eCSSKeyword_closest_side, NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE },
+ { eCSSKeyword_closest_corner, NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER },
+ { eCSSKeyword_farthest_side, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE },
+ { eCSSKeyword_farthest_corner, NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER },
+ // synonyms
+ { eCSSKeyword_contain, NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE },
+ { eCSSKeyword_cover, NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kResizeKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_RESIZE_NONE },
+ { eCSSKeyword_both, NS_STYLE_RESIZE_BOTH },
+ { eCSSKeyword_horizontal, NS_STYLE_RESIZE_HORIZONTAL },
+ { eCSSKeyword_vertical, NS_STYLE_RESIZE_VERTICAL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kRubyAlignKTable[] = {
+ { eCSSKeyword_start, NS_STYLE_RUBY_ALIGN_START },
+ { eCSSKeyword_center, NS_STYLE_RUBY_ALIGN_CENTER },
+ { eCSSKeyword_space_between, NS_STYLE_RUBY_ALIGN_SPACE_BETWEEN },
+ { eCSSKeyword_space_around, NS_STYLE_RUBY_ALIGN_SPACE_AROUND },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kRubyPositionKTable[] = {
+ { eCSSKeyword_over, NS_STYLE_RUBY_POSITION_OVER },
+ { eCSSKeyword_under, NS_STYLE_RUBY_POSITION_UNDER },
+ // bug 1055672 for 'inter-character' support
+ // { eCSSKeyword_inter_character, NS_STYLE_RUBY_POSITION_INTER_CHARACTER },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kScrollBehaviorKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_SCROLL_BEHAVIOR_AUTO },
+ { eCSSKeyword_smooth, NS_STYLE_SCROLL_BEHAVIOR_SMOOTH },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kScrollSnapTypeKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_SCROLL_SNAP_TYPE_NONE },
+ { eCSSKeyword_mandatory, NS_STYLE_SCROLL_SNAP_TYPE_MANDATORY },
+ { eCSSKeyword_proximity, NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kStackSizingKTable[] = {
+ { eCSSKeyword_ignore, NS_STYLE_STACK_SIZING_IGNORE },
+ { eCSSKeyword_stretch_to_fit, NS_STYLE_STACK_SIZING_STRETCH_TO_FIT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTableLayoutKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_TABLE_LAYOUT_AUTO },
+ { eCSSKeyword_fixed, NS_STYLE_TABLE_LAYOUT_FIXED },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+KTableEntry nsCSSProps::kTextAlignKTable[] = {
+ { eCSSKeyword_left, NS_STYLE_TEXT_ALIGN_LEFT },
+ { eCSSKeyword_right, NS_STYLE_TEXT_ALIGN_RIGHT },
+ { eCSSKeyword_center, NS_STYLE_TEXT_ALIGN_CENTER },
+ { eCSSKeyword_justify, NS_STYLE_TEXT_ALIGN_JUSTIFY },
+ { eCSSKeyword__moz_center, NS_STYLE_TEXT_ALIGN_MOZ_CENTER },
+ { eCSSKeyword__moz_right, NS_STYLE_TEXT_ALIGN_MOZ_RIGHT },
+ { eCSSKeyword__moz_left, NS_STYLE_TEXT_ALIGN_MOZ_LEFT },
+ { eCSSKeyword_start, NS_STYLE_TEXT_ALIGN_START },
+ { eCSSKeyword_end, NS_STYLE_TEXT_ALIGN_END },
+ { eCSSKeyword_unsafe, NS_STYLE_TEXT_ALIGN_UNSAFE },
+ { eCSSKeyword_match_parent, NS_STYLE_TEXT_ALIGN_MATCH_PARENT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+KTableEntry nsCSSProps::kTextAlignLastKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_TEXT_ALIGN_AUTO },
+ { eCSSKeyword_left, NS_STYLE_TEXT_ALIGN_LEFT },
+ { eCSSKeyword_right, NS_STYLE_TEXT_ALIGN_RIGHT },
+ { eCSSKeyword_center, NS_STYLE_TEXT_ALIGN_CENTER },
+ { eCSSKeyword_justify, NS_STYLE_TEXT_ALIGN_JUSTIFY },
+ { eCSSKeyword_start, NS_STYLE_TEXT_ALIGN_START },
+ { eCSSKeyword_end, NS_STYLE_TEXT_ALIGN_END },
+ { eCSSKeyword_unsafe, NS_STYLE_TEXT_ALIGN_UNSAFE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextCombineUprightKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE },
+ { eCSSKeyword_all, NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL },
+ { eCSSKeyword_digits, NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2 }, // w/o number ==> 2
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextDecorationLineKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_TEXT_DECORATION_LINE_NONE },
+ { eCSSKeyword_underline, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE },
+ { eCSSKeyword_overline, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE },
+ { eCSSKeyword_line_through, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH },
+ { eCSSKeyword_blink, NS_STYLE_TEXT_DECORATION_LINE_BLINK },
+ { eCSSKeyword__moz_anchor_decoration, NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextDecorationStyleKTable[] = {
+ { eCSSKeyword__moz_none, NS_STYLE_TEXT_DECORATION_STYLE_NONE },
+ { eCSSKeyword_solid, NS_STYLE_TEXT_DECORATION_STYLE_SOLID },
+ { eCSSKeyword_double, NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE },
+ { eCSSKeyword_dotted, NS_STYLE_TEXT_DECORATION_STYLE_DOTTED },
+ { eCSSKeyword_dashed, NS_STYLE_TEXT_DECORATION_STYLE_DASHED },
+ { eCSSKeyword_wavy, NS_STYLE_TEXT_DECORATION_STYLE_WAVY },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextOrientationKTable[] = {
+ { eCSSKeyword_mixed, NS_STYLE_TEXT_ORIENTATION_MIXED },
+ { eCSSKeyword_upright, NS_STYLE_TEXT_ORIENTATION_UPRIGHT },
+ { eCSSKeyword_sideways, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS },
+ { eCSSKeyword_sideways_right, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextEmphasisPositionKTable[] = {
+ { eCSSKeyword_over, NS_STYLE_TEXT_EMPHASIS_POSITION_OVER },
+ { eCSSKeyword_under, NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER },
+ { eCSSKeyword_left, NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT },
+ { eCSSKeyword_right, NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextEmphasisStyleFillKTable[] = {
+ { eCSSKeyword_filled, NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED },
+ { eCSSKeyword_open, NS_STYLE_TEXT_EMPHASIS_STYLE_OPEN },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextEmphasisStyleShapeKTable[] = {
+ { eCSSKeyword_dot, NS_STYLE_TEXT_EMPHASIS_STYLE_DOT },
+ { eCSSKeyword_circle, NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE },
+ { eCSSKeyword_double_circle, NS_STYLE_TEXT_EMPHASIS_STYLE_DOUBLE_CIRCLE },
+ { eCSSKeyword_triangle, NS_STYLE_TEXT_EMPHASIS_STYLE_TRIANGLE },
+ { eCSSKeyword_sesame, NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME} ,
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextOverflowKTable[] = {
+ { eCSSKeyword_clip, NS_STYLE_TEXT_OVERFLOW_CLIP },
+ { eCSSKeyword_ellipsis, NS_STYLE_TEXT_OVERFLOW_ELLIPSIS },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextTransformKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_TEXT_TRANSFORM_NONE },
+ { eCSSKeyword_capitalize, NS_STYLE_TEXT_TRANSFORM_CAPITALIZE },
+ { eCSSKeyword_lowercase, NS_STYLE_TEXT_TRANSFORM_LOWERCASE },
+ { eCSSKeyword_uppercase, NS_STYLE_TEXT_TRANSFORM_UPPERCASE },
+ { eCSSKeyword_full_width, NS_STYLE_TEXT_TRANSFORM_FULL_WIDTH },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTouchActionKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_TOUCH_ACTION_NONE },
+ { eCSSKeyword_auto, NS_STYLE_TOUCH_ACTION_AUTO },
+ { eCSSKeyword_pan_x, NS_STYLE_TOUCH_ACTION_PAN_X },
+ { eCSSKeyword_pan_y, NS_STYLE_TOUCH_ACTION_PAN_Y },
+ { eCSSKeyword_manipulation, NS_STYLE_TOUCH_ACTION_MANIPULATION },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTopLayerKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_TOP_LAYER_NONE },
+ { eCSSKeyword_top, NS_STYLE_TOP_LAYER_TOP },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTransformBoxKTable[] = {
+ { eCSSKeyword_border_box, NS_STYLE_TRANSFORM_BOX_BORDER_BOX },
+ { eCSSKeyword_fill_box, NS_STYLE_TRANSFORM_BOX_FILL_BOX },
+ { eCSSKeyword_view_box, NS_STYLE_TRANSFORM_BOX_VIEW_BOX },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTransitionTimingFunctionKTable[] = {
+ { eCSSKeyword_ease, NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE },
+ { eCSSKeyword_linear, NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR },
+ { eCSSKeyword_ease_in, NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN },
+ { eCSSKeyword_ease_out, NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_OUT },
+ { eCSSKeyword_ease_in_out, NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN_OUT },
+ { eCSSKeyword_step_start, NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START },
+ { eCSSKeyword_step_end, NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kUnicodeBidiKTable[] = {
+ { eCSSKeyword_normal, NS_STYLE_UNICODE_BIDI_NORMAL },
+ { eCSSKeyword_embed, NS_STYLE_UNICODE_BIDI_EMBED },
+ { eCSSKeyword_bidi_override, NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE },
+ { eCSSKeyword_isolate, NS_STYLE_UNICODE_BIDI_ISOLATE },
+ { eCSSKeyword_isolate_override, NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE },
+ { eCSSKeyword_plaintext, NS_STYLE_UNICODE_BIDI_PLAINTEXT },
+ { eCSSKeyword__moz_isolate, NS_STYLE_UNICODE_BIDI_ISOLATE },
+ { eCSSKeyword__moz_isolate_override, NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE },
+ { eCSSKeyword__moz_plaintext, NS_STYLE_UNICODE_BIDI_PLAINTEXT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kUserFocusKTable[] = {
+ { eCSSKeyword_none, uint8_t(StyleUserFocus::None) },
+ { eCSSKeyword_normal, uint8_t(StyleUserFocus::Normal) },
+ { eCSSKeyword_ignore, uint8_t(StyleUserFocus::Ignore) },
+ { eCSSKeyword_select_all, uint8_t(StyleUserFocus::SelectAll) },
+ { eCSSKeyword_select_before, uint8_t(StyleUserFocus::SelectBefore) },
+ { eCSSKeyword_select_after, uint8_t(StyleUserFocus::SelectAfter) },
+ { eCSSKeyword_select_same, uint8_t(StyleUserFocus::SelectSame) },
+ { eCSSKeyword_select_menu, uint8_t(StyleUserFocus::SelectMenu) },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kUserInputKTable[] = {
+ { eCSSKeyword_none, StyleUserInput::None },
+ { eCSSKeyword_enabled, StyleUserInput::Enabled },
+ { eCSSKeyword_disabled, StyleUserInput::Disabled },
+ { eCSSKeyword_auto, StyleUserInput::Auto },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kUserModifyKTable[] = {
+ { eCSSKeyword_read_only, StyleUserModify::ReadOnly },
+ { eCSSKeyword_read_write, StyleUserModify::ReadWrite },
+ { eCSSKeyword_write_only, StyleUserModify::WriteOnly },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kUserSelectKTable[] = {
+ { eCSSKeyword_none, StyleUserSelect::None },
+ { eCSSKeyword_auto, StyleUserSelect::Auto },
+ { eCSSKeyword_text, StyleUserSelect::Text },
+ { eCSSKeyword_element, StyleUserSelect::Element },
+ { eCSSKeyword_elements, StyleUserSelect::Elements },
+ { eCSSKeyword_all, StyleUserSelect::All },
+ { eCSSKeyword_toggle, StyleUserSelect::Toggle },
+ { eCSSKeyword_tri_state, StyleUserSelect::TriState },
+ { eCSSKeyword__moz_all, StyleUserSelect::MozAll },
+ { eCSSKeyword__moz_none, StyleUserSelect::None },
+ { eCSSKeyword__moz_text, StyleUserSelect::MozText },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kVerticalAlignKTable[] = {
+ { eCSSKeyword_baseline, NS_STYLE_VERTICAL_ALIGN_BASELINE },
+ { eCSSKeyword_sub, NS_STYLE_VERTICAL_ALIGN_SUB },
+ { eCSSKeyword_super, NS_STYLE_VERTICAL_ALIGN_SUPER },
+ { eCSSKeyword_top, NS_STYLE_VERTICAL_ALIGN_TOP },
+ { eCSSKeyword_text_top, NS_STYLE_VERTICAL_ALIGN_TEXT_TOP },
+ { eCSSKeyword_middle, NS_STYLE_VERTICAL_ALIGN_MIDDLE },
+ { eCSSKeyword__moz_middle_with_baseline, NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE },
+ { eCSSKeyword_bottom, NS_STYLE_VERTICAL_ALIGN_BOTTOM },
+ { eCSSKeyword_text_bottom, NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kVisibilityKTable[] = {
+ { eCSSKeyword_visible, NS_STYLE_VISIBILITY_VISIBLE },
+ { eCSSKeyword_hidden, NS_STYLE_VISIBILITY_HIDDEN },
+ { eCSSKeyword_collapse, NS_STYLE_VISIBILITY_COLLAPSE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kWhitespaceKTable[] = {
+ { eCSSKeyword_normal, NS_STYLE_WHITESPACE_NORMAL },
+ { eCSSKeyword_pre, NS_STYLE_WHITESPACE_PRE },
+ { eCSSKeyword_nowrap, NS_STYLE_WHITESPACE_NOWRAP },
+ { eCSSKeyword_pre_wrap, NS_STYLE_WHITESPACE_PRE_WRAP },
+ { eCSSKeyword_pre_line, NS_STYLE_WHITESPACE_PRE_LINE },
+ { eCSSKeyword__moz_pre_space, NS_STYLE_WHITESPACE_PRE_SPACE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kWidthKTable[] = {
+ { eCSSKeyword__moz_max_content, NS_STYLE_WIDTH_MAX_CONTENT },
+ { eCSSKeyword__moz_min_content, NS_STYLE_WIDTH_MIN_CONTENT },
+ { eCSSKeyword__moz_fit_content, NS_STYLE_WIDTH_FIT_CONTENT },
+ { eCSSKeyword__moz_available, NS_STYLE_WIDTH_AVAILABLE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kWindowDraggingKTable[] = {
+ { eCSSKeyword_default, StyleWindowDragging::Default },
+ { eCSSKeyword_drag, StyleWindowDragging::Drag },
+ { eCSSKeyword_no_drag, StyleWindowDragging::NoDrag },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kWindowShadowKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_WINDOW_SHADOW_NONE },
+ { eCSSKeyword_default, NS_STYLE_WINDOW_SHADOW_DEFAULT },
+ { eCSSKeyword_menu, NS_STYLE_WINDOW_SHADOW_MENU },
+ { eCSSKeyword_tooltip, NS_STYLE_WINDOW_SHADOW_TOOLTIP },
+ { eCSSKeyword_sheet, NS_STYLE_WINDOW_SHADOW_SHEET },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kWordBreakKTable[] = {
+ { eCSSKeyword_normal, NS_STYLE_WORDBREAK_NORMAL },
+ { eCSSKeyword_break_all, NS_STYLE_WORDBREAK_BREAK_ALL },
+ { eCSSKeyword_keep_all, NS_STYLE_WORDBREAK_KEEP_ALL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kOverflowWrapKTable[] = {
+ { eCSSKeyword_normal, NS_STYLE_OVERFLOWWRAP_NORMAL },
+ { eCSSKeyword_break_word, NS_STYLE_OVERFLOWWRAP_BREAK_WORD },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kWritingModeKTable[] = {
+ { eCSSKeyword_horizontal_tb, NS_STYLE_WRITING_MODE_HORIZONTAL_TB },
+ { eCSSKeyword_vertical_lr, NS_STYLE_WRITING_MODE_VERTICAL_LR },
+ { eCSSKeyword_vertical_rl, NS_STYLE_WRITING_MODE_VERTICAL_RL },
+ { eCSSKeyword_sideways_lr, NS_STYLE_WRITING_MODE_SIDEWAYS_LR },
+ { eCSSKeyword_sideways_rl, NS_STYLE_WRITING_MODE_SIDEWAYS_RL },
+ { eCSSKeyword_lr, NS_STYLE_WRITING_MODE_HORIZONTAL_TB },
+ { eCSSKeyword_lr_tb, NS_STYLE_WRITING_MODE_HORIZONTAL_TB },
+ { eCSSKeyword_rl, NS_STYLE_WRITING_MODE_HORIZONTAL_TB },
+ { eCSSKeyword_rl_tb, NS_STYLE_WRITING_MODE_HORIZONTAL_TB },
+ { eCSSKeyword_tb, NS_STYLE_WRITING_MODE_VERTICAL_RL },
+ { eCSSKeyword_tb_rl, NS_STYLE_WRITING_MODE_VERTICAL_RL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+// Specific keyword tables for XUL.properties
+const KTableEntry nsCSSProps::kBoxAlignKTable[] = {
+ { eCSSKeyword_stretch, StyleBoxAlign::Stretch },
+ { eCSSKeyword_start, StyleBoxAlign::Start },
+ { eCSSKeyword_center, StyleBoxAlign::Center },
+ { eCSSKeyword_baseline, StyleBoxAlign::Baseline },
+ { eCSSKeyword_end, StyleBoxAlign::End },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBoxDirectionKTable[] = {
+ { eCSSKeyword_normal, StyleBoxDirection::Normal },
+ { eCSSKeyword_reverse, StyleBoxDirection::Reverse },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBoxOrientKTable[] = {
+ { eCSSKeyword_horizontal, StyleBoxOrient::Horizontal },
+ { eCSSKeyword_vertical, StyleBoxOrient::Vertical },
+ { eCSSKeyword_inline_axis, StyleBoxOrient::Horizontal },
+ { eCSSKeyword_block_axis, StyleBoxOrient::Vertical },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kBoxPackKTable[] = {
+ { eCSSKeyword_start, StyleBoxPack::Start },
+ { eCSSKeyword_center, StyleBoxPack::Center },
+ { eCSSKeyword_end, StyleBoxPack::End },
+ { eCSSKeyword_justify, StyleBoxPack::Justify },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+// keyword tables for SVG properties
+
+const KTableEntry nsCSSProps::kDominantBaselineKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_DOMINANT_BASELINE_AUTO },
+ { eCSSKeyword_use_script, NS_STYLE_DOMINANT_BASELINE_USE_SCRIPT },
+ { eCSSKeyword_no_change, NS_STYLE_DOMINANT_BASELINE_NO_CHANGE },
+ { eCSSKeyword_reset_size, NS_STYLE_DOMINANT_BASELINE_RESET_SIZE },
+ { eCSSKeyword_alphabetic, NS_STYLE_DOMINANT_BASELINE_ALPHABETIC },
+ { eCSSKeyword_hanging, NS_STYLE_DOMINANT_BASELINE_HANGING },
+ { eCSSKeyword_ideographic, NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC },
+ { eCSSKeyword_mathematical, NS_STYLE_DOMINANT_BASELINE_MATHEMATICAL },
+ { eCSSKeyword_central, NS_STYLE_DOMINANT_BASELINE_CENTRAL },
+ { eCSSKeyword_middle, NS_STYLE_DOMINANT_BASELINE_MIDDLE },
+ { eCSSKeyword_text_after_edge, NS_STYLE_DOMINANT_BASELINE_TEXT_AFTER_EDGE },
+ { eCSSKeyword_text_before_edge, NS_STYLE_DOMINANT_BASELINE_TEXT_BEFORE_EDGE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFillRuleKTable[] = {
+ { eCSSKeyword_nonzero, StyleFillRule::Nonzero },
+ { eCSSKeyword_evenodd, StyleFillRule::Evenodd },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kClipPathGeometryBoxKTable[] = {
+ { eCSSKeyword_content_box, StyleClipPathGeometryBox::Content },
+ { eCSSKeyword_padding_box, StyleClipPathGeometryBox::Padding },
+ { eCSSKeyword_border_box, StyleClipPathGeometryBox::Border },
+ { eCSSKeyword_margin_box, StyleClipPathGeometryBox::Margin },
+ { eCSSKeyword_fill_box, StyleClipPathGeometryBox::Fill },
+ { eCSSKeyword_stroke_box, StyleClipPathGeometryBox::Stroke },
+ { eCSSKeyword_view_box, StyleClipPathGeometryBox::View },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kShapeRadiusKTable[] = {
+ { eCSSKeyword_closest_side, NS_RADIUS_CLOSEST_SIDE },
+ { eCSSKeyword_farthest_side, NS_RADIUS_FARTHEST_SIDE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kFilterFunctionKTable[] = {
+ { eCSSKeyword_blur, NS_STYLE_FILTER_BLUR },
+ { eCSSKeyword_brightness, NS_STYLE_FILTER_BRIGHTNESS },
+ { eCSSKeyword_contrast, NS_STYLE_FILTER_CONTRAST },
+ { eCSSKeyword_grayscale, NS_STYLE_FILTER_GRAYSCALE },
+ { eCSSKeyword_invert, NS_STYLE_FILTER_INVERT },
+ { eCSSKeyword_opacity, NS_STYLE_FILTER_OPACITY },
+ { eCSSKeyword_saturate, NS_STYLE_FILTER_SATURATE },
+ { eCSSKeyword_sepia, NS_STYLE_FILTER_SEPIA },
+ { eCSSKeyword_hue_rotate, NS_STYLE_FILTER_HUE_ROTATE },
+ { eCSSKeyword_drop_shadow, NS_STYLE_FILTER_DROP_SHADOW },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kImageRenderingKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_IMAGE_RENDERING_AUTO },
+ { eCSSKeyword_optimizespeed, NS_STYLE_IMAGE_RENDERING_OPTIMIZESPEED },
+ { eCSSKeyword_optimizequality, NS_STYLE_IMAGE_RENDERING_OPTIMIZEQUALITY },
+ { eCSSKeyword__moz_crisp_edges, NS_STYLE_IMAGE_RENDERING_CRISPEDGES },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kMaskTypeKTable[] = {
+ { eCSSKeyword_luminance, NS_STYLE_MASK_TYPE_LUMINANCE },
+ { eCSSKeyword_alpha, NS_STYLE_MASK_TYPE_ALPHA },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kShapeOutsideShapeBoxKTable[] = {
+ { eCSSKeyword_content_box, StyleShapeOutsideShapeBox::Content },
+ { eCSSKeyword_padding_box, StyleShapeOutsideShapeBox::Padding },
+ { eCSSKeyword_border_box, StyleShapeOutsideShapeBox::Border },
+ { eCSSKeyword_margin_box, StyleShapeOutsideShapeBox::Margin },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kShapeRenderingKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_SHAPE_RENDERING_AUTO },
+ { eCSSKeyword_optimizespeed, NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED },
+ { eCSSKeyword_crispedges, NS_STYLE_SHAPE_RENDERING_CRISPEDGES },
+ { eCSSKeyword_geometricprecision, NS_STYLE_SHAPE_RENDERING_GEOMETRICPRECISION },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kStrokeLinecapKTable[] = {
+ { eCSSKeyword_butt, NS_STYLE_STROKE_LINECAP_BUTT },
+ { eCSSKeyword_round, NS_STYLE_STROKE_LINECAP_ROUND },
+ { eCSSKeyword_square, NS_STYLE_STROKE_LINECAP_SQUARE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kStrokeLinejoinKTable[] = {
+ { eCSSKeyword_miter, NS_STYLE_STROKE_LINEJOIN_MITER },
+ { eCSSKeyword_round, NS_STYLE_STROKE_LINEJOIN_ROUND },
+ { eCSSKeyword_bevel, NS_STYLE_STROKE_LINEJOIN_BEVEL },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+// Lookup table to store the sole objectValue keyword to let SVG glyphs inherit
+// certain stroke-* properties from the outer text object
+const KTableEntry nsCSSProps::kStrokeContextValueKTable[] = {
+ { eCSSKeyword_context_value, NS_STYLE_STROKE_PROP_CONTEXT_VALUE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextAnchorKTable[] = {
+ { eCSSKeyword_start, NS_STYLE_TEXT_ANCHOR_START },
+ { eCSSKeyword_middle, NS_STYLE_TEXT_ANCHOR_MIDDLE },
+ { eCSSKeyword_end, NS_STYLE_TEXT_ANCHOR_END },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kTextRenderingKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_TEXT_RENDERING_AUTO },
+ { eCSSKeyword_optimizespeed, NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED },
+ { eCSSKeyword_optimizelegibility, NS_STYLE_TEXT_RENDERING_OPTIMIZELEGIBILITY },
+ { eCSSKeyword_geometricprecision, NS_STYLE_TEXT_RENDERING_GEOMETRICPRECISION },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kVectorEffectKTable[] = {
+ { eCSSKeyword_none, NS_STYLE_VECTOR_EFFECT_NONE },
+ { eCSSKeyword_non_scaling_stroke, NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kColorAdjustKTable[] = {
+ { eCSSKeyword_economy, NS_STYLE_COLOR_ADJUST_ECONOMY },
+ { eCSSKeyword_exact, NS_STYLE_COLOR_ADJUST_EXACT },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kColorInterpolationKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_COLOR_INTERPOLATION_AUTO },
+ { eCSSKeyword_srgb, NS_STYLE_COLOR_INTERPOLATION_SRGB },
+ { eCSSKeyword_linearrgb, NS_STYLE_COLOR_INTERPOLATION_LINEARRGB },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+const KTableEntry nsCSSProps::kColumnFillKTable[] = {
+ { eCSSKeyword_auto, NS_STYLE_COLUMN_FILL_AUTO },
+ { eCSSKeyword_balance, NS_STYLE_COLUMN_FILL_BALANCE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+static inline bool
+IsKeyValSentinel(const KTableEntry& aTableEntry)
+{
+ return aTableEntry.mKeyword == eCSSKeyword_UNKNOWN &&
+ aTableEntry.mValue == -1;
+}
+
+int32_t
+nsCSSProps::FindIndexOfKeyword(nsCSSKeyword aKeyword,
+ const KTableEntry aTable[])
+{
+ if (eCSSKeyword_UNKNOWN == aKeyword) {
+ // NOTE: we can have keyword tables where eCSSKeyword_UNKNOWN is used
+ // not only for the sentinel, but also in the middle of the table to
+ // knock out values that have been disabled by prefs, e.g. kDisplayKTable.
+ // So we deal with eCSSKeyword_UNKNOWN up front to avoid returning a valid
+ // index in the loop below.
+ return -1;
+ }
+ for (int32_t i = 0; ; ++i) {
+ const KTableEntry& entry = aTable[i];
+ if (::IsKeyValSentinel(entry)) {
+ break;
+ }
+ if (aKeyword == entry.mKeyword) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+bool
+nsCSSProps::FindKeyword(nsCSSKeyword aKeyword, const KTableEntry aTable[],
+ int32_t& aResult)
+{
+ int32_t index = FindIndexOfKeyword(aKeyword, aTable);
+ if (index >= 0) {
+ aResult = aTable[index].mValue;
+ return true;
+ }
+ return false;
+}
+
+nsCSSKeyword
+nsCSSProps::ValueToKeywordEnum(int32_t aValue, const KTableEntry aTable[])
+{
+#ifdef DEBUG
+ typedef decltype(aTable[0].mValue) table_value_type;
+ NS_ASSERTION(table_value_type(aValue) == aValue, "Value out of range");
+#endif
+ for (int32_t i = 0; ; ++i) {
+ const KTableEntry& entry = aTable[i];
+ if (::IsKeyValSentinel(entry)) {
+ break;
+ }
+ if (aValue == entry.mValue) {
+ return entry.mKeyword;
+ }
+ }
+ return eCSSKeyword_UNKNOWN;
+}
+
+const nsAFlatCString&
+nsCSSProps::ValueToKeyword(int32_t aValue, const KTableEntry aTable[])
+{
+ nsCSSKeyword keyword = ValueToKeywordEnum(aValue, aTable);
+ if (keyword == eCSSKeyword_UNKNOWN) {
+ static nsDependentCString sNullStr("");
+ return sNullStr;
+ } else {
+ return nsCSSKeywords::GetStringValue(keyword);
+ }
+}
+
+/* static */ const KTableEntry* const
+nsCSSProps::kKeywordTableTable[eCSSProperty_COUNT_no_shorthands] = {
+ #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ kwtable_,
+ #define CSS_PROP_LIST_INCLUDE_LOGICAL
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_LIST_INCLUDE_LOGICAL
+ #undef CSS_PROP
+};
+
+const nsAFlatCString&
+nsCSSProps::LookupPropertyValue(nsCSSPropertyID aProp, int32_t aValue)
+{
+ MOZ_ASSERT(aProp >= 0 && aProp < eCSSProperty_COUNT,
+ "property out of range");
+#ifdef DEBUG
+ typedef decltype(KTableEntry::mValue) table_value_type;
+ NS_ASSERTION(table_value_type(aValue) == aValue, "Value out of range");
+#endif
+
+ const KTableEntry* kwtable = nullptr;
+ if (aProp < eCSSProperty_COUNT_no_shorthands)
+ kwtable = kKeywordTableTable[aProp];
+
+ if (kwtable)
+ return ValueToKeyword(aValue, kwtable);
+
+ static nsDependentCString sNullStr("");
+ return sNullStr;
+}
+
+bool nsCSSProps::GetColorName(int32_t aPropValue, nsCString &aStr)
+{
+ bool rv = false;
+
+ // first get the keyword corresponding to the property Value from the color table
+ nsCSSKeyword keyword = ValueToKeywordEnum(aPropValue, kColorKTable);
+
+ // next get the name as a string from the keywords table
+ if (keyword != eCSSKeyword_UNKNOWN) {
+ nsCSSKeywords::AddRefTable();
+ aStr = nsCSSKeywords::GetStringValue(keyword);
+ nsCSSKeywords::ReleaseTable();
+ rv = true;
+ }
+ return rv;
+}
+
+const nsStyleStructID nsCSSProps::kSIDTable[eCSSProperty_COUNT_no_shorthands] = {
+ #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ eStyleStruct_##stylestruct_,
+ #define CSS_PROP_LIST_INCLUDE_LOGICAL
+
+ #include "nsCSSPropList.h"
+
+ #undef CSS_PROP_LIST_INCLUDE_LOGICAL
+ #undef CSS_PROP
+};
+
+const nsStyleAnimType
+nsCSSProps::kAnimTypeTable[eCSSProperty_COUNT_no_shorthands] = {
+#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
+ stylestruct_, stylestructoffset_, animtype_) \
+ animtype_,
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+};
+
+const ptrdiff_t
+nsCSSProps::kStyleStructOffsetTable[eCSSProperty_COUNT_no_shorthands] = {
+#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
+ stylestruct_, stylestructoffset_, animtype_) \
+ stylestructoffset_,
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+};
+
+const uint32_t nsCSSProps::kFlagsTable[eCSSProperty_COUNT] = {
+#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
+ stylestruct_, stylestructoffset_, animtype_) \
+ flags_,
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) flags_,
+#include "nsCSSPropList.h"
+#undef CSS_PROP_SHORTHAND
+};
+
+static const nsCSSPropertyID gAllSubpropTable[] = {
+#define CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
+ stylestruct_, stylestructoffset_, animtype_) \
+ eCSSProperty_##id_,
+#include "nsCSSPropList.h"
+#undef CSS_PROP
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gAnimationSubpropTable[] = {
+ eCSSProperty_animation_duration,
+ eCSSProperty_animation_timing_function,
+ eCSSProperty_animation_delay,
+ eCSSProperty_animation_direction,
+ eCSSProperty_animation_fill_mode,
+ eCSSProperty_animation_iteration_count,
+ eCSSProperty_animation_play_state,
+ // List animation-name last so we serialize it last, in case it has
+ // a value that conflicts with one of the other properties. (See
+ // how Declaration::GetValue serializes 'animation'.
+ eCSSProperty_animation_name,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderRadiusSubpropTable[] = {
+ // Code relies on these being in topleft-topright-bottomright-bottomleft
+ // order.
+ eCSSProperty_border_top_left_radius,
+ eCSSProperty_border_top_right_radius,
+ eCSSProperty_border_bottom_right_radius,
+ eCSSProperty_border_bottom_left_radius,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gOutlineRadiusSubpropTable[] = {
+ // Code relies on these being in topleft-topright-bottomright-bottomleft
+ // order.
+ eCSSProperty__moz_outline_radius_topLeft,
+ eCSSProperty__moz_outline_radius_topRight,
+ eCSSProperty__moz_outline_radius_bottomRight,
+ eCSSProperty__moz_outline_radius_bottomLeft,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBackgroundSubpropTable[] = {
+ eCSSProperty_background_color,
+ eCSSProperty_background_image,
+ eCSSProperty_background_repeat,
+ eCSSProperty_background_attachment,
+ eCSSProperty_background_clip,
+ eCSSProperty_background_origin,
+ eCSSProperty_background_position_x,
+ eCSSProperty_background_position_y,
+ eCSSProperty_background_size,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBackgroundPositionSubpropTable[] = {
+ eCSSProperty_background_position_x,
+ eCSSProperty_background_position_y,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderSubpropTable[] = {
+ eCSSProperty_border_top_width,
+ eCSSProperty_border_right_width,
+ eCSSProperty_border_bottom_width,
+ eCSSProperty_border_left_width,
+ eCSSProperty_border_top_style,
+ eCSSProperty_border_right_style,
+ eCSSProperty_border_bottom_style,
+ eCSSProperty_border_left_style,
+ eCSSProperty_border_top_color,
+ eCSSProperty_border_right_color,
+ eCSSProperty_border_bottom_color,
+ eCSSProperty_border_left_color,
+ eCSSProperty_border_top_colors,
+ eCSSProperty_border_right_colors,
+ eCSSProperty_border_bottom_colors,
+ eCSSProperty_border_left_colors,
+ eCSSProperty_border_image_source,
+ eCSSProperty_border_image_slice,
+ eCSSProperty_border_image_width,
+ eCSSProperty_border_image_outset,
+ eCSSProperty_border_image_repeat,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderBlockEndSubpropTable[] = {
+ // Declaration.cpp outputs the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_border_block_end_width,
+ eCSSProperty_border_block_end_style,
+ eCSSProperty_border_block_end_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderBlockStartSubpropTable[] = {
+ // Declaration.cpp outputs the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_border_block_start_width,
+ eCSSProperty_border_block_start_style,
+ eCSSProperty_border_block_start_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderBottomSubpropTable[] = {
+ // Declaration.cpp outputs the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_border_bottom_width,
+ eCSSProperty_border_bottom_style,
+ eCSSProperty_border_bottom_color,
+ eCSSProperty_UNKNOWN
+};
+
+static_assert(NS_SIDE_TOP == 0 && NS_SIDE_RIGHT == 1 &&
+ NS_SIDE_BOTTOM == 2 && NS_SIDE_LEFT == 3,
+ "box side constants not top/right/bottom/left == 0/1/2/3");
+static const nsCSSPropertyID gBorderColorSubpropTable[] = {
+ // Code relies on these being in top-right-bottom-left order.
+ // Code relies on these matching the NS_SIDE_* constants.
+ eCSSProperty_border_top_color,
+ eCSSProperty_border_right_color,
+ eCSSProperty_border_bottom_color,
+ eCSSProperty_border_left_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderInlineEndSubpropTable[] = {
+ // Declaration.cpp output the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_border_inline_end_width,
+ eCSSProperty_border_inline_end_style,
+ eCSSProperty_border_inline_end_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderLeftSubpropTable[] = {
+ // Declaration.cpp outputs the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_border_left_width,
+ eCSSProperty_border_left_style,
+ eCSSProperty_border_left_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderRightSubpropTable[] = {
+ // Declaration.cpp outputs the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_border_right_width,
+ eCSSProperty_border_right_style,
+ eCSSProperty_border_right_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderInlineStartSubpropTable[] = {
+ // Declaration.cpp outputs the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_border_inline_start_width,
+ eCSSProperty_border_inline_start_style,
+ eCSSProperty_border_inline_start_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderStyleSubpropTable[] = {
+ // Code relies on these being in top-right-bottom-left order.
+ eCSSProperty_border_top_style,
+ eCSSProperty_border_right_style,
+ eCSSProperty_border_bottom_style,
+ eCSSProperty_border_left_style,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderTopSubpropTable[] = {
+ // Declaration.cpp outputs the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_border_top_width,
+ eCSSProperty_border_top_style,
+ eCSSProperty_border_top_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderWidthSubpropTable[] = {
+ // Code relies on these being in top-right-bottom-left order.
+ eCSSProperty_border_top_width,
+ eCSSProperty_border_right_width,
+ eCSSProperty_border_bottom_width,
+ eCSSProperty_border_left_width,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gFontSubpropTable[] = {
+ eCSSProperty_font_family,
+ eCSSProperty_font_style,
+ eCSSProperty_font_weight,
+ eCSSProperty_font_size,
+ eCSSProperty_line_height,
+ eCSSProperty_font_size_adjust,
+ eCSSProperty_font_stretch,
+ eCSSProperty__x_system_font,
+ eCSSProperty_font_feature_settings,
+ eCSSProperty_font_language_override,
+ eCSSProperty_font_kerning,
+ eCSSProperty_font_synthesis,
+ eCSSProperty_font_variant_alternates,
+ eCSSProperty_font_variant_caps,
+ eCSSProperty_font_variant_east_asian,
+ eCSSProperty_font_variant_ligatures,
+ eCSSProperty_font_variant_numeric,
+ eCSSProperty_font_variant_position,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gFontVariantSubpropTable[] = {
+ eCSSProperty_font_variant_alternates,
+ eCSSProperty_font_variant_caps,
+ eCSSProperty_font_variant_east_asian,
+ eCSSProperty_font_variant_ligatures,
+ eCSSProperty_font_variant_numeric,
+ eCSSProperty_font_variant_position,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gListStyleSubpropTable[] = {
+ eCSSProperty_list_style_type,
+ eCSSProperty_list_style_image,
+ eCSSProperty_list_style_position,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gMarginSubpropTable[] = {
+ // Code relies on these being in top-right-bottom-left order.
+ eCSSProperty_margin_top,
+ eCSSProperty_margin_right,
+ eCSSProperty_margin_bottom,
+ eCSSProperty_margin_left,
+ eCSSProperty_UNKNOWN
+};
+
+
+static const nsCSSPropertyID gOutlineSubpropTable[] = {
+ // nsCSSDeclaration.cpp outputs the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_outline_width,
+ eCSSProperty_outline_style,
+ eCSSProperty_outline_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gColumnsSubpropTable[] = {
+ eCSSProperty_column_count,
+ eCSSProperty_column_width,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gColumnRuleSubpropTable[] = {
+ // nsCSSDeclaration.cpp outputs the subproperties in this order.
+ // It also depends on the color being third.
+ eCSSProperty_column_rule_width,
+ eCSSProperty_column_rule_style,
+ eCSSProperty_column_rule_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gFlexSubpropTable[] = {
+ eCSSProperty_flex_grow,
+ eCSSProperty_flex_shrink,
+ eCSSProperty_flex_basis,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gFlexFlowSubpropTable[] = {
+ eCSSProperty_flex_direction,
+ eCSSProperty_flex_wrap,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gGridTemplateSubpropTable[] = {
+ eCSSProperty_grid_template_areas,
+ eCSSProperty_grid_template_rows,
+ eCSSProperty_grid_template_columns,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gGridSubpropTable[] = {
+ eCSSProperty_grid_template_areas,
+ eCSSProperty_grid_template_rows,
+ eCSSProperty_grid_template_columns,
+ eCSSProperty_grid_auto_flow,
+ eCSSProperty_grid_auto_rows,
+ eCSSProperty_grid_auto_columns,
+ eCSSProperty_grid_row_gap, // can only be reset, not get/set
+ eCSSProperty_grid_column_gap, // can only be reset, not get/set
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gGridColumnSubpropTable[] = {
+ eCSSProperty_grid_column_start,
+ eCSSProperty_grid_column_end,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gGridRowSubpropTable[] = {
+ eCSSProperty_grid_row_start,
+ eCSSProperty_grid_row_end,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gGridAreaSubpropTable[] = {
+ eCSSProperty_grid_row_start,
+ eCSSProperty_grid_column_start,
+ eCSSProperty_grid_row_end,
+ eCSSProperty_grid_column_end,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gGridGapSubpropTable[] = {
+ eCSSProperty_grid_row_gap,
+ eCSSProperty_grid_column_gap,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gOverflowSubpropTable[] = {
+ eCSSProperty_overflow_x,
+ eCSSProperty_overflow_y,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gPaddingSubpropTable[] = {
+ // Code relies on these being in top-right-bottom-left order.
+ eCSSProperty_padding_top,
+ eCSSProperty_padding_right,
+ eCSSProperty_padding_bottom,
+ eCSSProperty_padding_left,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gTextDecorationSubpropTable[] = {
+ eCSSProperty_text_decoration_color,
+ eCSSProperty_text_decoration_line,
+ eCSSProperty_text_decoration_style,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gTextEmphasisSubpropTable[] = {
+ eCSSProperty_text_emphasis_style,
+ eCSSProperty_text_emphasis_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gWebkitTextStrokeSubpropTable[] = {
+ eCSSProperty__webkit_text_stroke_width,
+ eCSSProperty__webkit_text_stroke_color,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gTransitionSubpropTable[] = {
+ eCSSProperty_transition_property,
+ eCSSProperty_transition_duration,
+ eCSSProperty_transition_timing_function,
+ eCSSProperty_transition_delay,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gBorderImageSubpropTable[] = {
+ eCSSProperty_border_image_source,
+ eCSSProperty_border_image_slice,
+ eCSSProperty_border_image_width,
+ eCSSProperty_border_image_outset,
+ eCSSProperty_border_image_repeat,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gMarkerSubpropTable[] = {
+ eCSSProperty_marker_start,
+ eCSSProperty_marker_mid,
+ eCSSProperty_marker_end,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gPlaceContentSubpropTable[] = {
+ eCSSProperty_align_content,
+ eCSSProperty_justify_content,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gPlaceItemsSubpropTable[] = {
+ eCSSProperty_align_items,
+ eCSSProperty_justify_items,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gPlaceSelfSubpropTable[] = {
+ eCSSProperty_align_self,
+ eCSSProperty_justify_self,
+ eCSSProperty_UNKNOWN
+};
+
+// Subproperty tables for shorthands that are just aliases with
+// different parsing rules.
+static const nsCSSPropertyID gMozTransformSubpropTable[] = {
+ eCSSProperty_transform,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gScrollSnapTypeSubpropTable[] = {
+ eCSSProperty_scroll_snap_type_x,
+ eCSSProperty_scroll_snap_type_y,
+ eCSSProperty_UNKNOWN
+};
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+static const nsCSSPropertyID gMaskSubpropTable[] = {
+ eCSSProperty_mask_image,
+ eCSSProperty_mask_repeat,
+ eCSSProperty_mask_position_x,
+ eCSSProperty_mask_position_y,
+ eCSSProperty_mask_clip,
+ eCSSProperty_mask_origin,
+ eCSSProperty_mask_size,
+ eCSSProperty_mask_composite,
+ eCSSProperty_mask_mode,
+ eCSSProperty_UNKNOWN
+};
+static const nsCSSPropertyID gMaskPositionSubpropTable[] = {
+ eCSSProperty_mask_position_x,
+ eCSSProperty_mask_position_y,
+ eCSSProperty_UNKNOWN
+};
+#endif
+// FIXME: mask-border tables should be added when we implement
+// mask-border properties.
+
+const nsCSSPropertyID *const
+nsCSSProps::kSubpropertyTable[eCSSProperty_COUNT - eCSSProperty_COUNT_no_shorthands] = {
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
+// Need an extra level of macro nesting to force expansion of method_
+// params before they get pasted.
+#define NSCSSPROPS_INNER_MACRO(method_) g##method_##SubpropTable,
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+ NSCSSPROPS_INNER_MACRO(method_)
+#include "nsCSSPropList.h"
+#undef CSS_PROP_SHORTHAND
+#undef NSCSSPROPS_INNER_MACRO
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+};
+
+
+static const nsCSSPropertyID gOffsetLogicalGroupTable[] = {
+ eCSSProperty_top,
+ eCSSProperty_right,
+ eCSSProperty_bottom,
+ eCSSProperty_left,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gMaxSizeLogicalGroupTable[] = {
+ eCSSProperty_max_height,
+ eCSSProperty_max_width,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gMinSizeLogicalGroupTable[] = {
+ eCSSProperty_min_height,
+ eCSSProperty_min_width,
+ eCSSProperty_UNKNOWN
+};
+
+static const nsCSSPropertyID gSizeLogicalGroupTable[] = {
+ eCSSProperty_height,
+ eCSSProperty_width,
+ eCSSProperty_UNKNOWN
+};
+
+const nsCSSPropertyID* const
+nsCSSProps::kLogicalGroupTable[eCSSPropertyLogicalGroup_COUNT] = {
+#define CSS_PROP_LOGICAL_GROUP_SHORTHAND(id_) g##id_##SubpropTable,
+#define CSS_PROP_LOGICAL_GROUP_AXIS(name_) g##name_##LogicalGroupTable,
+#define CSS_PROP_LOGICAL_GROUP_BOX(name_) g##name_##LogicalGroupTable,
+#include "nsCSSPropLogicalGroupList.h"
+#undef CSS_PROP_LOGICAL_GROUP_BOX
+#undef CSS_PROP_LOGICAL_GROUP_AXIS
+#undef CSS_PROP_LOGICAL_GROUP_SHORTHAND
+};
+
+// Mapping of logical longhand properties to their logical group (which
+// represents the physical longhands the logical properties an correspond
+// to). The format is pairs of values, where the first is the logical
+// longhand property (an nsCSSPropertyID) and the second is the logical group
+// (an nsCSSPropertyLogicalGroup), stored in a flat array (like KTableEntry
+// arrays).
+static const int gLogicalGroupMappingTable[] = {
+#define CSS_PROP_LOGICAL(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, group_, stylestruct_, \
+ stylestructoffset_, animtype_) \
+ eCSSProperty_##id_, eCSSPropertyLogicalGroup_##group_,
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LOGICAL
+};
+
+/* static */ const nsCSSPropertyID*
+nsCSSProps::LogicalGroup(nsCSSPropertyID aProperty)
+{
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
+ "out of range");
+ MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL),
+ "aProperty must be a logical longhand property");
+
+ for (size_t i = 0; i < ArrayLength(gLogicalGroupMappingTable); i += 2) {
+ if (gLogicalGroupMappingTable[i] == aProperty) {
+ return kLogicalGroupTable[gLogicalGroupMappingTable[i + 1]];
+ }
+ }
+
+ MOZ_ASSERT(false, "missing gLogicalGroupMappingTable entry");
+ return nullptr;
+}
+
+
+#define ENUM_DATA_FOR_PROPERTY(name_, id_, method_, flags_, pref_, \
+ parsevariant_, kwtable_, stylestructoffset_, \
+ animtype_) \
+ ePropertyIndex_for_##id_,
+
+// The order of these enums must match the g*Flags arrays in nsRuleNode.cpp.
+
+enum FontCheckCounter {
+ #define CSS_PROP_FONT ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_FONT
+ ePropertyCount_for_Font
+};
+
+enum DisplayCheckCounter {
+ #define CSS_PROP_DISPLAY ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_DISPLAY
+ ePropertyCount_for_Display
+};
+
+enum VisibilityCheckCounter {
+ #define CSS_PROP_VISIBILITY ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_VISIBILITY
+ ePropertyCount_for_Visibility
+};
+
+enum MarginCheckCounter {
+ #define CSS_PROP_MARGIN ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_MARGIN
+ ePropertyCount_for_Margin
+};
+
+enum BorderCheckCounter {
+ #define CSS_PROP_BORDER ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_BORDER
+ ePropertyCount_for_Border
+};
+
+enum PaddingCheckCounter {
+ #define CSS_PROP_PADDING ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_PADDING
+ ePropertyCount_for_Padding
+};
+
+enum OutlineCheckCounter {
+ #define CSS_PROP_OUTLINE ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_OUTLINE
+ ePropertyCount_for_Outline
+};
+
+enum ListCheckCounter {
+ #define CSS_PROP_LIST ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_LIST
+ ePropertyCount_for_List
+};
+
+enum ColorCheckCounter {
+ #define CSS_PROP_COLOR ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_COLOR
+ ePropertyCount_for_Color
+};
+
+enum BackgroundCheckCounter {
+ #define CSS_PROP_BACKGROUND ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_BACKGROUND
+ ePropertyCount_for_Background
+};
+
+enum PositionCheckCounter {
+ #define CSS_PROP_POSITION ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_POSITION
+ ePropertyCount_for_Position
+};
+
+enum TableCheckCounter {
+ #define CSS_PROP_TABLE ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_TABLE
+ ePropertyCount_for_Table
+};
+
+enum TableBorderCheckCounter {
+ #define CSS_PROP_TABLEBORDER ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_TABLEBORDER
+ ePropertyCount_for_TableBorder
+};
+
+enum ContentCheckCounter {
+ #define CSS_PROP_CONTENT ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_CONTENT
+ ePropertyCount_for_Content
+};
+
+enum TextCheckCounter {
+ #define CSS_PROP_TEXT ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_TEXT
+ ePropertyCount_for_Text
+};
+
+enum TextResetCheckCounter {
+ #define CSS_PROP_TEXTRESET ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_TEXTRESET
+ ePropertyCount_for_TextReset
+};
+
+enum UserInterfaceCheckCounter {
+ #define CSS_PROP_USERINTERFACE ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_USERINTERFACE
+ ePropertyCount_for_UserInterface
+};
+
+enum UIResetCheckCounter {
+ #define CSS_PROP_UIRESET ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_UIRESET
+ ePropertyCount_for_UIReset
+};
+
+enum XULCheckCounter {
+ #define CSS_PROP_XUL ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_XUL
+ ePropertyCount_for_XUL
+};
+
+enum SVGCheckCounter {
+ #define CSS_PROP_SVG ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_SVG
+ ePropertyCount_for_SVG
+};
+
+enum SVGResetCheckCounter {
+ #define CSS_PROP_SVGRESET ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_SVGRESET
+ ePropertyCount_for_SVGReset
+};
+
+enum ColumnCheckCounter {
+ #define CSS_PROP_COLUMN ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_COLUMN
+ ePropertyCount_for_Column
+};
+
+enum VariablesCheckCounter {
+ #define CSS_PROP_VARIABLES ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_VARIABLES
+ ePropertyCount_for_Variables
+};
+
+enum EffectsCheckCounter {
+ #define CSS_PROP_EFFECTS ENUM_DATA_FOR_PROPERTY
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_EFFECTS
+ ePropertyCount_for_Effects
+};
+
+#undef ENUM_DATA_FOR_PROPERTY
+
+/* static */ const size_t
+nsCSSProps::gPropertyCountInStruct[nsStyleStructID_Length] = {
+ #define STYLE_STRUCT(name, checkdata_cb) \
+ ePropertyCount_for_##name,
+ #include "nsStyleStructList.h"
+ #undef STYLE_STRUCT
+};
+
+/* static */ const size_t
+nsCSSProps::gPropertyIndexInStruct[eCSSProperty_COUNT_no_shorthands] = {
+
+ #define CSS_PROP_LOGICAL(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, group_, stylestruct_, \
+ stylestructoffset_, animtype_) \
+ size_t(-1),
+ #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ ePropertyIndex_for_##id_,
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP
+ #undef CSS_PROP_LOGICAL
+
+};
+
+/* 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_) & CSS_PROPERTY_ENABLED_MASK))
+
+ #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ IS_ENABLED_BY_DEFAULT(flags_),
+ #define CSS_PROP_LIST_INCLUDE_LOGICAL
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_LIST_INCLUDE_LOGICAL
+ #undef CSS_PROP
+
+ #define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+ IS_ENABLED_BY_DEFAULT(flags_),
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_SHORTHAND
+
+ #define CSS_PROP_ALIAS(aliasname_, propid_, aliasmethod_, pref_) \
+ true,
+ #include "nsCSSPropAliasList.h"
+ #undef CSS_PROP_ALIAS
+
+ #undef IS_ENABLED_BY_DEFAULT
+};
+
+#include "../../dom/base/PropertyUseCounterMap.inc"
+
+/* static */ const UseCounter
+nsCSSProps::gPropertyUseCounter[eCSSProperty_COUNT_no_shorthands] = {
+ #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
+ #define CSS_PROP_LIST_INCLUDE_LOGICAL
+ #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ static_cast<UseCounter>(USE_COUNTER_FOR_CSS_PROPERTY_##method_),
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP
+ #undef CSS_PROP_LIST_INCLUDE_LOGICAL
+ #undef CSS_PROP_PUBLIC_OR_PRIVATE
+};
+
+// Check that all logical property flags are used appropriately.
+#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ static_assert(!((flags_) & CSS_PROPERTY_LOGICAL), \
+ "only properties defined with CSS_PROP_LOGICAL can use " \
+ "the CSS_PROPERTY_LOGICAL flag"); \
+ static_assert(!((flags_) & CSS_PROPERTY_LOGICAL_AXIS), \
+ "only properties defined with CSS_PROP_LOGICAL can use " \
+ "the CSS_PROPERTY_LOGICAL_AXIS flag"); \
+ static_assert(!((flags_) & CSS_PROPERTY_LOGICAL_BLOCK_AXIS), \
+ "only properties defined with CSS_PROP_LOGICAL can use " \
+ "the CSS_PROPERTY_LOGICAL_BLOCK_AXIS flag"); \
+ static_assert(!((flags_) & CSS_PROPERTY_LOGICAL_END_EDGE), \
+ "only properties defined with CSS_PROP_LOGICAL can use " \
+ "the CSS_PROPERTY_LOGICAL_END_EDGE flag");
+#define CSS_PROP_LOGICAL(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, group_, stylestruct_, \
+ stylestructoffset_, animtype_) \
+ static_assert((flags_) & CSS_PROPERTY_LOGICAL, \
+ "properties defined with CSS_PROP_LOGICAL must also use " \
+ "the CSS_PROPERTY_LOGICAL flag"); \
+ static_assert(!((flags_) & CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED), \
+ "CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED has no effect " \
+ "on logical properties"); \
+ static_assert(!(((flags_) & CSS_PROPERTY_LOGICAL_AXIS) && \
+ ((flags_) & CSS_PROPERTY_LOGICAL_END_EDGE)), \
+ "CSS_PROPERTY_LOGICAL_END_EDGE makes no sense when used " \
+ "with CSS_PROPERTY_LOGICAL_AXIS");
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LOGICAL
+#undef CSS_PROP
+
+#include "nsCSSPropsGenerated.inc"
diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h
new file mode 100644
index 000000000..ab78e6174
--- /dev/null
+++ b/layout/style/nsCSSProps.h
@@ -0,0 +1,895 @@
+/* -*- 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/. */
+
+/*
+ * methods for dealing with CSS properties and tables of the keyword
+ * values they accept
+ */
+
+#ifndef nsCSSProps_h___
+#define nsCSSProps_h___
+
+#include <limits>
+#include <type_traits>
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "nsCSSPropertyID.h"
+#include "nsStyleStructFwd.h"
+#include "nsCSSKeywords.h"
+#include "mozilla/CSSEnabledState.h"
+#include "mozilla/UseCounter.h"
+#include "mozilla/EnumTypeTraits.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
+
+// Flags for ParseVariant method
+#define VARIANT_KEYWORD 0x000001 // K
+#define VARIANT_LENGTH 0x000002 // L
+#define VARIANT_PERCENT 0x000004 // P
+#define VARIANT_COLOR 0x000008 // C eCSSUnit_*Color, eCSSUnit_Ident (e.g. "red")
+#define VARIANT_URL 0x000010 // U
+#define VARIANT_NUMBER 0x000020 // N
+#define VARIANT_INTEGER 0x000040 // I
+#define VARIANT_ANGLE 0x000080 // G
+#define VARIANT_FREQUENCY 0x000100 // F
+#define VARIANT_TIME 0x000200 // T
+#define VARIANT_STRING 0x000400 // S
+#define VARIANT_COUNTER 0x000800 //
+#define VARIANT_ATTR 0x001000 //
+#define VARIANT_IDENTIFIER 0x002000 // D
+#define VARIANT_IDENTIFIER_NO_INHERIT 0x004000 // like above, but excluding
+// 'inherit' and 'initial'
+#define VARIANT_AUTO 0x010000 // A
+#define VARIANT_INHERIT 0x020000 // H eCSSUnit_Initial, eCSSUnit_Inherit, eCSSUnit_Unset
+#define VARIANT_NONE 0x040000 // O
+#define VARIANT_NORMAL 0x080000 // M
+#define VARIANT_SYSFONT 0x100000 // eCSSUnit_System_Font
+#define VARIANT_GRADIENT 0x200000 // eCSSUnit_Gradient
+#define VARIANT_TIMING_FUNCTION 0x400000 // cubic-bezier() and steps()
+#define VARIANT_ALL 0x800000 //
+#define VARIANT_IMAGE_RECT 0x01000000 // eCSSUnit_Function
+// This is an extra bit that says that a VARIANT_ANGLE allows unitless zero:
+#define VARIANT_ZERO_ANGLE 0x02000000 // unitless zero for angles
+#define VARIANT_CALC 0x04000000 // eCSSUnit_Calc
+#define VARIANT_ELEMENT 0x08000000 // eCSSUnit_Element
+#define VARIANT_NONNEGATIVE_DIMENSION 0x10000000 // Only lengths greater than or equal to 0.0
+// Keyword used iff gfx.font_rendering.opentype_svg.enabled is true:
+#define VARIANT_OPENTYPE_SVG_KEYWORD 0x20000000
+#define VARIANT_ABSOLUTE_DIMENSION 0x40000000 // B Only lengths with absolute length unit
+
+// Variants that can consume more than one token
+#define VARIANT_MULTIPLE_TOKENS \
+ (VARIANT_COLOR | /* rgb(...), hsl(...), etc. */ \
+ VARIANT_COUNTER | /* counter(...), counters(...) */ \
+ VARIANT_ATTR | /* attr(...) */ \
+ VARIANT_GRADIENT | /* linear-gradient(...), etc. */ \
+ VARIANT_TIMING_FUNCTION | /* cubic-bezier(...), steps(...) */ \
+ VARIANT_IMAGE_RECT | /* -moz-image-rect(...) */ \
+ VARIANT_CALC | /* calc(...) */ \
+ VARIANT_ELEMENT) /* -moz-element(...) */
+
+// Common combinations of variants
+#define VARIANT_AL (VARIANT_AUTO | VARIANT_LENGTH)
+#define VARIANT_LP (VARIANT_LENGTH | VARIANT_PERCENT)
+#define VARIANT_LN (VARIANT_LENGTH | VARIANT_NUMBER)
+#define VARIANT_AH (VARIANT_AUTO | VARIANT_INHERIT)
+#define VARIANT_AHLP (VARIANT_AH | VARIANT_LP)
+#define VARIANT_AHI (VARIANT_AH | VARIANT_INTEGER)
+#define VARIANT_AHK (VARIANT_AH | VARIANT_KEYWORD)
+#define VARIANT_AHKLP (VARIANT_AHLP | VARIANT_KEYWORD)
+#define VARIANT_AHL (VARIANT_AH | VARIANT_LENGTH)
+#define VARIANT_AHKL (VARIANT_AHK | VARIANT_LENGTH)
+#define VARIANT_HK (VARIANT_INHERIT | VARIANT_KEYWORD)
+#define VARIANT_HKF (VARIANT_HK | VARIANT_FREQUENCY)
+#define VARIANT_HKI (VARIANT_HK | VARIANT_INTEGER)
+#define VARIANT_HKL (VARIANT_HK | VARIANT_LENGTH)
+#define VARIANT_HKLP (VARIANT_HK | VARIANT_LP)
+#define VARIANT_HKLPO (VARIANT_HKLP | VARIANT_NONE)
+#define VARIANT_HL (VARIANT_INHERIT | VARIANT_LENGTH)
+#define VARIANT_HI (VARIANT_INHERIT | VARIANT_INTEGER)
+#define VARIANT_HLP (VARIANT_HL | VARIANT_PERCENT)
+#define VARIANT_HLPN (VARIANT_HLP | VARIANT_NUMBER)
+#define VARIANT_HLPO (VARIANT_HLP | VARIANT_NONE)
+#define VARIANT_HTP (VARIANT_INHERIT | VARIANT_TIME | VARIANT_PERCENT)
+#define VARIANT_HMK (VARIANT_HK | VARIANT_NORMAL)
+#define VARIANT_HC (VARIANT_INHERIT | VARIANT_COLOR)
+#define VARIANT_HCK (VARIANT_HK | VARIANT_COLOR)
+#define VARIANT_HUK (VARIANT_HK | VARIANT_URL)
+#define VARIANT_HUO (VARIANT_INHERIT | VARIANT_URL | VARIANT_NONE)
+#define VARIANT_AHUO (VARIANT_AUTO | VARIANT_HUO)
+#define VARIANT_HPN (VARIANT_INHERIT | VARIANT_PERCENT | VARIANT_NUMBER)
+#define VARIANT_PN (VARIANT_PERCENT | VARIANT_NUMBER)
+#define VARIANT_ALPN (VARIANT_AL | VARIANT_PN)
+#define VARIANT_HN (VARIANT_INHERIT | VARIANT_NUMBER)
+#define VARIANT_HON (VARIANT_HN | VARIANT_NONE)
+#define VARIANT_HOS (VARIANT_INHERIT | VARIANT_NONE | VARIANT_STRING)
+#define VARIANT_LPN (VARIANT_LP | VARIANT_NUMBER)
+#define VARIANT_UK (VARIANT_URL | VARIANT_KEYWORD)
+#define VARIANT_UO (VARIANT_URL | VARIANT_NONE)
+#define VARIANT_ANGLE_OR_ZERO (VARIANT_ANGLE | VARIANT_ZERO_ANGLE)
+#define VARIANT_LB (VARIANT_LENGTH | VARIANT_ABSOLUTE_DIMENSION)
+#define VARIANT_LBCALC (VARIANT_LB | VARIANT_CALC)
+#define VARIANT_LCALC (VARIANT_LENGTH | VARIANT_CALC)
+#define VARIANT_LPCALC (VARIANT_LCALC | VARIANT_PERCENT)
+#define VARIANT_LNCALC (VARIANT_LCALC | VARIANT_NUMBER)
+#define VARIANT_LPNCALC (VARIANT_LNCALC | VARIANT_PERCENT)
+#define VARIANT_IMAGE (VARIANT_URL | VARIANT_NONE | VARIANT_GRADIENT | \
+ VARIANT_IMAGE_RECT | VARIANT_ELEMENT)
+
+// Flags for the kFlagsTable bitfield (flags_ in nsCSSPropList.h)
+
+// This property is a logical property (such as padding-inline-start).
+#define CSS_PROPERTY_LOGICAL (1<<0)
+
+#define CSS_PROPERTY_VALUE_LIST_USES_COMMAS (1<<1) /* otherwise spaces */
+
+#define CSS_PROPERTY_APPLIES_TO_FIRST_LETTER (1<<2)
+#define CSS_PROPERTY_APPLIES_TO_FIRST_LINE (1<<3)
+#define CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE \
+ (CSS_PROPERTY_APPLIES_TO_FIRST_LETTER | CSS_PROPERTY_APPLIES_TO_FIRST_LINE)
+
+// Note that 'background-color' is ignored differently from the other
+// properties that have this set, but that's just special-cased.
+#define CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED (1<<4)
+
+// A property that needs to have image loads started when a URL value
+// for the property is used for an element. This is supported only
+// for a few possible value formats: image directly in the value; list
+// of images; and with CSS_PROPERTY_IMAGE_IS_IN_ARRAY_0, image in slot
+// 0 of an array, or list of such arrays.
+#define CSS_PROPERTY_START_IMAGE_LOADS (1<<5)
+
+// Should be set only for properties with START_IMAGE_LOADS. Indicates
+// that the property has an array value with a URL/image value at index
+// 0 in the array, rather than the URL/image being in the value or value
+// list.
+#define CSS_PROPERTY_IMAGE_IS_IN_ARRAY_0 (1<<6)
+
+// This is a logical property that represents some value associated with
+// a logical axis rather than a logical box side, and thus has two
+// corresponding physical properties it could set rather than four. For
+// example, the block-size logical property has this flag set, as it
+// represents the size in either the block or inline axis dimensions, and
+// has two corresponding physical properties, width and height. Must not
+// be used in conjunction with CSS_PROPERTY_LOGICAL_END_EDGE.
+#define CSS_PROPERTY_LOGICAL_AXIS (1<<7)
+
+// This property allows calc() between lengths and percentages and
+// stores such calc() expressions in its style structs (typically in an
+// nsStyleCoord, although this is not the case for 'background-position'
+// and 'background-size').
+#define CSS_PROPERTY_STORES_CALC (1<<8)
+
+// Define what mechanism the CSS parser uses for parsing the property.
+// See CSSParserImpl::ParseProperty(nsCSSPropertyID). Don't use 0 so that
+// we can verify that every property sets one of the values.
+//
+// CSS_PROPERTY_PARSE_FUNCTION must be used for shorthand properties,
+// since it's the only mechanism that allows appending values for
+// separate properties. Longhand properties that require custom parsing
+// functions should prefer using CSS_PROPERTY_PARSE_VALUE (or
+// CSS_PROPERTY_PARSE_VALUE_LIST) and
+// CSS_PROPERTY_VALUE_PARSER_FUNCTION, though a number of existing
+// longhand properties use CSS_PROPERTY_PARSE_FUNCTION instead.
+#define CSS_PROPERTY_PARSE_PROPERTY_MASK (7<<9)
+#define CSS_PROPERTY_PARSE_INACCESSIBLE (1<<9)
+#define CSS_PROPERTY_PARSE_FUNCTION (2<<9)
+#define CSS_PROPERTY_PARSE_VALUE (3<<9)
+#define CSS_PROPERTY_PARSE_VALUE_LIST (4<<9)
+
+// See CSSParserImpl::ParseSingleValueProperty and comment above
+// CSS_PROPERTY_PARSE_FUNCTION (which is different).
+#define CSS_PROPERTY_VALUE_PARSER_FUNCTION (1<<12)
+static_assert((CSS_PROPERTY_PARSE_PROPERTY_MASK &
+ CSS_PROPERTY_VALUE_PARSER_FUNCTION) == 0,
+ "didn't leave enough room for the parse property constants");
+
+#define CSS_PROPERTY_VALUE_RESTRICTION_MASK (3<<13)
+// The parser (in particular, CSSParserImpl::ParseSingleValueProperty)
+// should enforce that the value of this property must be 0 or larger.
+#define CSS_PROPERTY_VALUE_NONNEGATIVE (1<<13)
+// The parser (in particular, CSSParserImpl::ParseSingleValueProperty)
+// should enforce that the value of this property must be 1 or larger.
+#define CSS_PROPERTY_VALUE_AT_LEAST_ONE (2<<13)
+
+// Does this property support the hashless hex color quirk in quirks mode?
+#define CSS_PROPERTY_HASHLESS_COLOR_QUIRK (1<<15)
+
+// Does this property support the unitless length quirk in quirks mode?
+#define CSS_PROPERTY_UNITLESS_LENGTH_QUIRK (1<<16)
+
+// Is this property (which must be a shorthand) really an alias?
+#define CSS_PROPERTY_IS_ALIAS (1<<17)
+
+// Does the property apply to ::placeholder?
+#define CSS_PROPERTY_APPLIES_TO_PLACEHOLDER (1<<18)
+
+// This property is allowed in an @page rule.
+#define CSS_PROPERTY_APPLIES_TO_PAGE_RULE (1<<19)
+
+// This property's getComputedStyle implementation requires layout to be
+// flushed.
+#define CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH (1<<20)
+
+// This property requires a stacking context.
+#define CSS_PROPERTY_CREATES_STACKING_CONTEXT (1<<21)
+
+// 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.
+#define CSS_PROPERTY_ENABLED_MASK (3<<22)
+#define CSS_PROPERTY_ENABLED_IN_UA_SHEETS (1<<22)
+#define CSS_PROPERTY_ENABLED_IN_CHROME (1<<23)
+#define CSS_PROPERTY_ENABLED_IN_UA_SHEETS_AND_CHROME \
+ (CSS_PROPERTY_ENABLED_IN_UA_SHEETS | CSS_PROPERTY_ENABLED_IN_CHROME)
+
+// This property's unitless values are pixels.
+#define CSS_PROPERTY_NUMBERS_ARE_PIXELS (1<<24)
+
+// This property is a logical property for one of the two block axis
+// sides (such as margin-block-start or margin-block-end). Must only be
+// set if CSS_PROPERTY_LOGICAL is set. When not set, the logical
+// property is for one of the two inline axis sides (such as
+// margin-inline-start or margin-inline-end).
+#define CSS_PROPERTY_LOGICAL_BLOCK_AXIS (1<<25)
+
+// This property is a logical property for the "end" edge of the
+// axis determined by the presence or absence of
+// CSS_PROPERTY_LOGICAL_BLOCK_AXIS (such as margin-block-end or
+// margin-inline-end). Must only be set if CSS_PROPERTY_LOGICAL is set.
+// When not set, the logical property is for the "start" edge (such as
+// margin-block-start or margin-inline-start).
+#define CSS_PROPERTY_LOGICAL_END_EDGE (1<<26)
+
+// This property can be animated on the compositor.
+#define CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR (1<<27)
+
+// This property is an internal property that is not represented
+// in the DOM. Properties with this flag must be defined in an #ifndef
+// CSS_PROP_LIST_EXCLUDE_INTERNAL section of nsCSSPropList.h.
+#define CSS_PROPERTY_INTERNAL (1<<28)
+
+// This property has values that can establish a containing block for
+// fixed positioned and absolutely positioned elements.
+// This should be set for any properties that can cause an element to be
+// such a containing block, as implemented in
+// nsStyleDisplay::IsFixedPosContainingBlock.
+#define CSS_PROPERTY_FIXPOS_CB (1<<29)
+
+// This property has values that can establish a containing block for
+// absolutely positioned elements.
+// This should be set for any properties that can cause an element to be
+// such a containing block, as implemented in
+// nsStyleDisplay::IsAbsPosContainingBlock.
+// It does not need to be set for properties that also have
+// CSS_PROPERTY_FIXPOS_CB set.
+#define CSS_PROPERTY_ABSPOS_CB (1<<30)
+
+/**
+ * Types of animatable values.
+ */
+enum nsStyleAnimType {
+ // requires a custom implementation in
+ // StyleAnimationValue::ExtractComputedValue
+ eStyleAnimType_Custom,
+
+ // nsStyleCoord with animatable values
+ eStyleAnimType_Coord,
+
+ // same as Coord, except for one side of an nsStyleSides
+ // listed in the same order as the NS_STYLE_* constants
+ eStyleAnimType_Sides_Top,
+ eStyleAnimType_Sides_Right,
+ eStyleAnimType_Sides_Bottom,
+ eStyleAnimType_Sides_Left,
+
+ // similar, but for the *pair* of coord members of an nsStyleCorners
+ // for the relevant corner
+ eStyleAnimType_Corner_TopLeft,
+ eStyleAnimType_Corner_TopRight,
+ eStyleAnimType_Corner_BottomRight,
+ eStyleAnimType_Corner_BottomLeft,
+
+ // nscoord values
+ eStyleAnimType_nscoord,
+
+ // float values
+ eStyleAnimType_float,
+
+ // nscolor values
+ eStyleAnimType_Color,
+
+ // StyleComplexColor values
+ eStyleAnimType_ComplexColor,
+
+ // nsStyleSVGPaint values
+ eStyleAnimType_PaintServer,
+
+ // RefPtr<nsCSSShadowArray> values
+ eStyleAnimType_Shadow,
+
+ // discrete values
+ eStyleAnimType_Discrete,
+
+ // property not animatable
+ eStyleAnimType_None
+};
+
+// Empty class derived from nsIAtom so that function signatures can
+// require an atom from the atom list.
+class nsICSSProperty : public nsIAtom {};
+
+class nsCSSProps {
+public:
+ typedef mozilla::CSSEnabledState EnabledState;
+
+ struct KTableEntry
+ {
+ // KTableEntry objects can be initialized either with an int16_t value
+ // or a value of an enumeration type that can fit within an int16_t.
+
+ constexpr KTableEntry(nsCSSKeyword aKeyword, int16_t aValue)
+ : mKeyword(aKeyword)
+ , mValue(aValue)
+ {
+ }
+
+ template<typename T,
+ typename = typename std::enable_if<std::is_enum<T>::value>::type>
+ constexpr KTableEntry(nsCSSKeyword aKeyword, T aValue)
+ : mKeyword(aKeyword)
+ , mValue(static_cast<int16_t>(aValue))
+ {
+ static_assert(mozilla::EnumTypeFitsWithin<T, int16_t>::value,
+ "aValue must be an enum that fits within mValue");
+ }
+
+ nsCSSKeyword mKeyword;
+ int16_t mValue;
+ };
+
+ static void AddRefTable(void);
+ static void ReleaseTable(void);
+
+ // 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.
+ static nsCSSPropertyID LookupProperty(const nsAString& aProperty,
+ EnabledState aEnabled);
+ static nsCSSPropertyID LookupProperty(const nsACString& aProperty,
+ EnabledState aEnabled);
+ // As above, but looked up using a property's IDL name.
+ // eCSSPropertyExtra_variable won't be returned from these methods.
+ static nsCSSPropertyID LookupPropertyByIDLName(
+ const nsAString& aPropertyIDLName,
+ EnabledState aEnabled);
+ 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 nsAString& aProperty);
+ static bool IsCustomPropertyName(const nsACString& aProperty);
+
+ static inline bool IsShorthand(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
+ "out of range");
+ return (aProperty >= eCSSProperty_COUNT_no_shorthands);
+ }
+
+ // Must be given a longhand property.
+ static bool IsInherited(nsCSSPropertyID aProperty);
+
+ // Same but for @font-face descriptors
+ static nsCSSFontDesc LookupFontDesc(const nsAString& aProperty);
+ static nsCSSFontDesc LookupFontDesc(const nsACString& aProperty);
+
+ // For @counter-style descriptors
+ static nsCSSCounterDesc LookupCounterDesc(const nsAString& aProperty);
+ static nsCSSCounterDesc LookupCounterDesc(const nsACString& aProperty);
+
+ // For predefined counter styles which need to be lower-cased during parse
+ static bool IsPredefinedCounterStyle(const nsAString& aStyle);
+ static bool IsPredefinedCounterStyle(const nsACString& aStyle);
+
+ // Given a property enum, get the string value
+ static const nsAFlatCString& GetStringValue(nsCSSPropertyID aProperty);
+ static const nsAFlatCString& GetStringValue(nsCSSFontDesc aFontDesc);
+ static const nsAFlatCString& GetStringValue(nsCSSCounterDesc aCounterDesc);
+
+ // Given a CSS Property and a Property Enum Value
+ // Return back a const nsString& representation of the
+ // value. Return back nullstr if no value is found
+ static const nsAFlatCString& LookupPropertyValue(nsCSSPropertyID aProperty, int32_t aValue);
+
+ // Get a color name for a predefined color value like buttonhighlight or activeborder
+ // Sets the aStr param to the name of the propertyID
+ static bool GetColorName(int32_t aPropID, nsCString &aStr);
+
+ // Returns the index of |aKeyword| in |aTable|, if it exists there;
+ // otherwise, returns -1.
+ // NOTE: Generally, clients should call FindKeyword() instead of this method.
+ static int32_t FindIndexOfKeyword(nsCSSKeyword aKeyword,
+ const KTableEntry aTable[]);
+
+ // Find |aKeyword| in |aTable|, if found set |aValue| to its corresponding value.
+ // If not found, return false and do not set |aValue|.
+ static bool FindKeyword(nsCSSKeyword aKeyword, const KTableEntry aTable[],
+ int32_t& aValue);
+ // Return the first keyword in |aTable| that has the corresponding value |aValue|.
+ // Return |eCSSKeyword_UNKNOWN| if not found.
+ static nsCSSKeyword ValueToKeywordEnum(int32_t aValue,
+ const KTableEntry aTable[]);
+ template<typename T,
+ typename = typename std::enable_if<std::is_enum<T>::value>::type>
+ static nsCSSKeyword ValueToKeywordEnum(T aValue,
+ const KTableEntry aTable[])
+ {
+ static_assert(mozilla::EnumTypeFitsWithin<T, int16_t>::value,
+ "aValue must be an enum that fits within KTableEntry::mValue");
+ return ValueToKeywordEnum(static_cast<int16_t>(aValue), aTable);
+ }
+ // Ditto but as a string, return "" when not found.
+ static const nsAFlatCString& ValueToKeyword(int32_t aValue,
+ const KTableEntry aTable[]);
+ template<typename T,
+ typename = typename std::enable_if<std::is_enum<T>::value>::type>
+ static const nsAFlatCString& ValueToKeyword(T aValue,
+ const KTableEntry aTable[])
+ {
+ static_assert(mozilla::EnumTypeFitsWithin<T, int16_t>::value,
+ "aValue must be an enum that fits within KTableEntry::mValue");
+ return ValueToKeyword(static_cast<int16_t>(aValue), aTable);
+ }
+
+ static const nsStyleStructID kSIDTable[eCSSProperty_COUNT_no_shorthands];
+ static const KTableEntry* const kKeywordTableTable[eCSSProperty_COUNT_no_shorthands];
+ static const nsStyleAnimType kAnimTypeTable[eCSSProperty_COUNT_no_shorthands];
+ static const ptrdiff_t
+ kStyleStructOffsetTable[eCSSProperty_COUNT_no_shorthands];
+
+private:
+ static const uint32_t kFlagsTable[eCSSProperty_COUNT];
+
+public:
+ static inline bool PropHasFlags(nsCSSPropertyID aProperty, uint32_t aFlags)
+ {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
+ "out of range");
+ MOZ_ASSERT(!(aFlags & CSS_PROPERTY_PARSE_PROPERTY_MASK),
+ "The CSS_PROPERTY_PARSE_* values are not bitflags; don't pass "
+ "them to PropHasFlags. You probably want PropertyParseType "
+ "instead.");
+ return (nsCSSProps::kFlagsTable[aProperty] & aFlags) == aFlags;
+ }
+
+ static inline uint32_t PropertyParseType(nsCSSPropertyID aProperty)
+ {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
+ "out of range");
+ return nsCSSProps::kFlagsTable[aProperty] &
+ CSS_PROPERTY_PARSE_PROPERTY_MASK;
+ }
+
+ static inline uint32_t ValueRestrictions(nsCSSPropertyID aProperty)
+ {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
+ "out of range");
+ return nsCSSProps::kFlagsTable[aProperty] &
+ CSS_PROPERTY_VALUE_RESTRICTION_MASK;
+ }
+
+private:
+ // Lives in nsCSSParser.cpp for the macros it depends on.
+ static const uint32_t kParserVariantTable[eCSSProperty_COUNT_no_shorthands];
+
+public:
+ static inline uint32_t ParserVariant(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
+ "out of range");
+ return nsCSSProps::kParserVariantTable[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:
+ static inline
+ const nsCSSPropertyID * SubpropertyEntryFor(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(eCSSProperty_COUNT_no_shorthands <= aProperty &&
+ aProperty < eCSSProperty_COUNT,
+ "out of range");
+ return nsCSSProps::kSubpropertyTable[aProperty -
+ eCSSProperty_COUNT_no_shorthands];
+ }
+
+ // Returns an eCSSProperty_UNKNOWN-terminated array of the shorthand
+ // properties containing |aProperty|, sorted from those that contain
+ // the most properties to those that contain the least.
+ static const nsCSSPropertyID * ShorthandsContaining(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(gShorthandsContainingPool, "uninitialized");
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
+ "out of range");
+ return gShorthandsContainingTable[aProperty];
+ }
+private:
+ // gShorthandsContainingTable is an array of the return values for
+ // ShorthandsContaining (arrays of nsCSSPropertyID terminated by
+ // eCSSProperty_UNKNOWN) pointing into memory in
+ // gShorthandsContainingPool (which contains all of those arrays in a
+ // single allocation, and is the one pointer that should be |free|d).
+ static nsCSSPropertyID *gShorthandsContainingTable[eCSSProperty_COUNT_no_shorthands];
+ static nsCSSPropertyID* gShorthandsContainingPool;
+ static bool BuildShorthandsContainingTable();
+
+private:
+ static const size_t gPropertyCountInStruct[nsStyleStructID_Length];
+ static const size_t gPropertyIndexInStruct[eCSSProperty_COUNT_no_shorthands];
+public:
+ /**
+ * Return the number of properties that must be cascaded when
+ * nsRuleNode builds the nsStyle* for aSID.
+ */
+ static size_t PropertyCountInStruct(nsStyleStructID aSID) {
+ MOZ_ASSERT(0 <= aSID && aSID < nsStyleStructID_Length,
+ "out of range");
+ return gPropertyCountInStruct[aSID];
+ }
+ /**
+ * Return an index for aProperty that is unique within its SID and in
+ * the range 0 <= index < PropertyCountInStruct(aSID).
+ */
+ static size_t PropertyIndexInStruct(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
+ "out of range");
+ return gPropertyIndexInStruct[aProperty];
+ }
+
+private:
+ // A table for logical property groups. Indexes are
+ // nsCSSPropertyLogicalGroup values.
+ static const nsCSSPropertyID* const
+ kLogicalGroupTable[eCSSPropertyLogicalGroup_COUNT];
+
+public:
+ /**
+ * Returns an array of longhand physical properties which can be set by
+ * the argument, which must be a logical longhand property. The returned
+ * array is terminated by an eCSSProperty_UNKNOWN value. For example,
+ * given eCSSProperty_margin_block_start, returns an array of the four
+ * properties eCSSProperty_margin_top, eCSSProperty_margin_right,
+ * eCSSProperty_margin_bottom and eCSSProperty_margin_left, followed
+ * by the sentinel.
+ *
+ * When called with a property that has the CSS_PROPERTY_LOGICAL_AXIS
+ * flag, the returned array will have two values preceding the sentinel;
+ * otherwise it will have four.
+ *
+ * (Note that the running time of this function is proportional to the
+ * number of logical longhand properties that exist. If we start
+ * getting too many of these properties, we should make kLogicalGroupTable
+ * be a simple array of eCSSProperty_COUNT length.)
+ */
+ static const nsCSSPropertyID* LogicalGroup(nsCSSPropertyID aProperty);
+
+private:
+ static bool gPropertyEnabled[eCSSProperty_COUNT_with_aliases];
+
+private:
+ // Defined in the generated nsCSSPropsGenerated.inc.
+ static const char* const kIDLNameTable[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];
+ }
+
+private:
+ static const int32_t kIDLNameSortPositionTable[eCSSProperty_COUNT];
+
+public:
+ /**
+ * 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) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_with_aliases,
+ "out of range");
+ return gPropertyEnabled[aProperty];
+ }
+
+ // A table for the use counter associated with each CSS property. If a
+ // property does not have a use counter defined in UseCounters.conf, then
+ // its associated entry is |eUseCounter_UNKNOWN|.
+ static const mozilla::UseCounter gPropertyUseCounter[eCSSProperty_COUNT_no_shorthands];
+
+public:
+
+ static mozilla::UseCounter UseCounterFor(nsCSSPropertyID aProperty) {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
+ "out of range");
+ return gPropertyUseCounter[aProperty];
+ }
+
+ static bool IsEnabled(nsCSSPropertyID aProperty, EnabledState aEnabled)
+ {
+ if (IsEnabled(aProperty)) {
+ return true;
+ }
+ if (aEnabled == EnabledState::eIgnoreEnabledState) {
+ return true;
+ }
+ if ((aEnabled & EnabledState::eInUASheets) &&
+ PropHasFlags(aProperty, CSS_PROPERTY_ENABLED_IN_UA_SHEETS))
+ {
+ return true;
+ }
+ if ((aEnabled & EnabledState::eInChrome) &&
+ PropHasFlags(aProperty, CSS_PROPERTY_ENABLED_IN_CHROME))
+ {
+ return true;
+ }
+ return false;
+ }
+
+public:
+ static void AddRefAtoms();
+ static nsICSSProperty* AtomForProperty(nsCSSPropertyID aProperty)
+ {
+ MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT);
+ return gPropertyAtomTable[aProperty];
+ }
+
+#define CSS_PROP(name_, id_, ...) static nsICSSProperty* id_;
+#define CSS_PROP_SHORTHAND(name_, id_, ...) CSS_PROP(name_, id_, ...)
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP
+
+private:
+ static nsICSSProperty* gPropertyAtomTable[eCSSProperty_COUNT];
+
+public:
+
+// 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_))
+
+ // Keyword/Enum value tables
+ static const KTableEntry kAnimationDirectionKTable[];
+ static const KTableEntry kAnimationFillModeKTable[];
+ static const KTableEntry kAnimationIterationCountKTable[];
+ static const KTableEntry kAnimationPlayStateKTable[];
+ static const KTableEntry kAnimationTimingFunctionKTable[];
+ static const KTableEntry kAppearanceKTable[];
+ static const KTableEntry kAzimuthKTable[];
+ static const KTableEntry kBackfaceVisibilityKTable[];
+ static const KTableEntry kTransformStyleKTable[];
+ static const KTableEntry kImageLayerAttachmentKTable[];
+ static const KTableEntry kImageLayerOriginKTable[];
+ static const KTableEntry kImageLayerPositionKTable[];
+ static const KTableEntry kImageLayerRepeatKTable[];
+ static const KTableEntry kImageLayerRepeatPartKTable[];
+ static const KTableEntry kImageLayerSizeKTable[];
+ static const KTableEntry kImageLayerCompositeKTable[];
+ static const KTableEntry kImageLayerModeKTable[];
+ // Not const because we modify its entries when the pref
+ // "layout.css.background-clip.text" changes:
+ static KTableEntry kBackgroundClipKTable[];
+ static const KTableEntry kBlendModeKTable[];
+ static const KTableEntry kBorderCollapseKTable[];
+ static const KTableEntry kBorderImageRepeatKTable[];
+ static const KTableEntry kBorderImageSliceKTable[];
+ static const KTableEntry kBorderStyleKTable[];
+ static const KTableEntry kBorderWidthKTable[];
+ static const KTableEntry kBoxAlignKTable[];
+ static const KTableEntry kBoxDecorationBreakKTable[];
+ static const KTableEntry kBoxDirectionKTable[];
+ static const KTableEntry kBoxOrientKTable[];
+ static const KTableEntry kBoxPackKTable[];
+ static const KTableEntry kClipPathGeometryBoxKTable[];
+ static const KTableEntry kCounterRangeKTable[];
+ static const KTableEntry kCounterSpeakAsKTable[];
+ static const KTableEntry kCounterSymbolsSystemKTable[];
+ static const KTableEntry kCounterSystemKTable[];
+ static const KTableEntry kDominantBaselineKTable[];
+ static const KTableEntry kShapeRadiusKTable[];
+ static const KTableEntry kFillRuleKTable[];
+ static const KTableEntry kFilterFunctionKTable[];
+ static const KTableEntry kImageRenderingKTable[];
+ static const KTableEntry kShapeOutsideShapeBoxKTable[];
+ static const KTableEntry kShapeRenderingKTable[];
+ static const KTableEntry kStrokeLinecapKTable[];
+ static const KTableEntry kStrokeLinejoinKTable[];
+ static const KTableEntry kStrokeContextValueKTable[];
+ static const KTableEntry kVectorEffectKTable[];
+ static const KTableEntry kTextAnchorKTable[];
+ static const KTableEntry kTextRenderingKTable[];
+ static const KTableEntry kColorAdjustKTable[];
+ static const KTableEntry kColorInterpolationKTable[];
+ static const KTableEntry kColumnFillKTable[];
+ static const KTableEntry kBoxPropSourceKTable[];
+ static const KTableEntry kBoxShadowTypeKTable[];
+ static const KTableEntry kBoxSizingKTable[];
+ static const KTableEntry kCaptionSideKTable[];
+ // Not const because we modify its entries when the pref
+ // "layout.css.float-logical-values.enabled" changes:
+ static KTableEntry kClearKTable[];
+ static const KTableEntry kColorKTable[];
+ static const KTableEntry kContentKTable[];
+ static const KTableEntry kControlCharacterVisibilityKTable[];
+ static const KTableEntry kCursorKTable[];
+ static const KTableEntry kDirectionKTable[];
+ // Not const because we modify its entries when various
+ // "layout.css.*.enabled" prefs changes:
+ static KTableEntry kDisplayKTable[];
+ static const KTableEntry kElevationKTable[];
+ static const KTableEntry kEmptyCellsKTable[];
+ // -- tables for parsing the {align,justify}-{content,items,self} properties --
+ static const KTableEntry kAlignAllKeywords[];
+ static const KTableEntry kAlignOverflowPosition[]; // <overflow-position>
+ static const KTableEntry kAlignSelfPosition[]; // <self-position>
+ static const KTableEntry kAlignLegacy[]; // 'legacy'
+ static const KTableEntry kAlignLegacyPosition[]; // 'left/right/center'
+ static const KTableEntry kAlignAutoNormalStretchBaseline[]; // 'auto/normal/stretch/baseline'
+ static const KTableEntry kAlignNormalStretchBaseline[]; // 'normal/stretch/baseline'
+ static const KTableEntry kAlignNormalBaseline[]; // 'normal/baseline'
+ static const KTableEntry kAlignContentDistribution[]; // <content-distribution>
+ static const KTableEntry kAlignContentPosition[]; // <content-position>
+ // -- tables for auto-completion of the {align,justify}-{content,items,self} properties --
+ static const KTableEntry kAutoCompletionAlignJustifySelf[];
+ static const KTableEntry kAutoCompletionAlignItems[];
+ static const KTableEntry kAutoCompletionAlignJustifyContent[];
+ // ------------------------------------------------------------------
+ static const KTableEntry kFlexDirectionKTable[];
+ static const KTableEntry kFlexWrapKTable[];
+ // Not const because we modify its entries when the pref
+ // "layout.css.float-logical-values.enabled" changes:
+ static KTableEntry kFloatKTable[];
+ static const KTableEntry kFloatEdgeKTable[];
+ static const KTableEntry kFontDisplayKTable[];
+ static const KTableEntry kFontKTable[];
+ static const KTableEntry kFontKerningKTable[];
+ static const KTableEntry kFontSizeKTable[];
+ static const KTableEntry kFontSmoothingKTable[];
+ static const KTableEntry kFontStretchKTable[];
+ static const KTableEntry kFontStyleKTable[];
+ static const KTableEntry kFontSynthesisKTable[];
+ static const KTableEntry kFontVariantKTable[];
+ static const KTableEntry kFontVariantAlternatesKTable[];
+ static const KTableEntry kFontVariantAlternatesFuncsKTable[];
+ static const KTableEntry kFontVariantCapsKTable[];
+ static const KTableEntry kFontVariantEastAsianKTable[];
+ static const KTableEntry kFontVariantLigaturesKTable[];
+ static const KTableEntry kFontVariantNumericKTable[];
+ static const KTableEntry kFontVariantPositionKTable[];
+ static const KTableEntry kFontWeightKTable[];
+ static const KTableEntry kGridAutoFlowKTable[];
+ static const KTableEntry kGridTrackBreadthKTable[];
+ static const KTableEntry kHyphensKTable[];
+ static const KTableEntry kImageOrientationKTable[];
+ static const KTableEntry kImageOrientationFlipKTable[];
+ static const KTableEntry kIsolationKTable[];
+ static const KTableEntry kIMEModeKTable[];
+ static const KTableEntry kLineHeightKTable[];
+ static const KTableEntry kListStylePositionKTable[];
+ static const KTableEntry kListStyleKTable[];
+ static const KTableEntry kMaskTypeKTable[];
+ static const KTableEntry kMathVariantKTable[];
+ static const KTableEntry kMathDisplayKTable[];
+ static const KTableEntry kContainKTable[];
+ static const KTableEntry kContextOpacityKTable[];
+ static const KTableEntry kContextPatternKTable[];
+ static const KTableEntry kObjectFitKTable[];
+ static const KTableEntry kOrientKTable[];
+ static const KTableEntry kOutlineStyleKTable[];
+ static const KTableEntry kOverflowKTable[];
+ static const KTableEntry kOverflowSubKTable[];
+ static const KTableEntry kOverflowClipBoxKTable[];
+ static const KTableEntry kOverflowWrapKTable[];
+ static const KTableEntry kPageBreakKTable[];
+ static const KTableEntry kPageBreakInsideKTable[];
+ static const KTableEntry kPageMarksKTable[];
+ static const KTableEntry kPageSizeKTable[];
+ static const KTableEntry kPitchKTable[];
+ static const KTableEntry kPointerEventsKTable[];
+ static const KTableEntry kPositionKTable[];
+ static const KTableEntry kRadialGradientShapeKTable[];
+ static const KTableEntry kRadialGradientSizeKTable[];
+ static const KTableEntry kRadialGradientLegacySizeKTable[];
+ static const KTableEntry kResizeKTable[];
+ static const KTableEntry kRubyAlignKTable[];
+ static const KTableEntry kRubyPositionKTable[];
+ static const KTableEntry kScrollBehaviorKTable[];
+ static const KTableEntry kScrollSnapTypeKTable[];
+ static const KTableEntry kSpeakKTable[];
+ static const KTableEntry kSpeakHeaderKTable[];
+ static const KTableEntry kSpeakNumeralKTable[];
+ static const KTableEntry kSpeakPunctuationKTable[];
+ static const KTableEntry kSpeechRateKTable[];
+ static const KTableEntry kStackSizingKTable[];
+ static const KTableEntry kTableLayoutKTable[];
+ // Not const because we modify its entries when the pref
+ // "layout.css.text-align-unsafe-value.enabled" changes:
+ static KTableEntry kTextAlignKTable[];
+ static KTableEntry kTextAlignLastKTable[];
+ static const KTableEntry kTextCombineUprightKTable[];
+ static const KTableEntry kTextDecorationLineKTable[];
+ static const KTableEntry kTextDecorationStyleKTable[];
+ static const KTableEntry kTextEmphasisPositionKTable[];
+ static const KTableEntry kTextEmphasisStyleFillKTable[];
+ static const KTableEntry kTextEmphasisStyleShapeKTable[];
+ static const KTableEntry kTextOrientationKTable[];
+ static const KTableEntry kTextOverflowKTable[];
+ static const KTableEntry kTextTransformKTable[];
+ static const KTableEntry kTouchActionKTable[];
+ static const KTableEntry kTopLayerKTable[];
+ static const KTableEntry kTransformBoxKTable[];
+ static const KTableEntry kTransitionTimingFunctionKTable[];
+ static const KTableEntry kUnicodeBidiKTable[];
+ static const KTableEntry kUserFocusKTable[];
+ static const KTableEntry kUserInputKTable[];
+ static const KTableEntry kUserModifyKTable[];
+ static const KTableEntry kUserSelectKTable[];
+ static const KTableEntry kVerticalAlignKTable[];
+ static const KTableEntry kVisibilityKTable[];
+ static const KTableEntry kVolumeKTable[];
+ static const KTableEntry kWhitespaceKTable[];
+ static const KTableEntry kWidthKTable[]; // also min-width, max-width
+ static const KTableEntry kWindowDraggingKTable[];
+ static const KTableEntry kWindowShadowKTable[];
+ static const KTableEntry kWordBreakKTable[];
+ static const KTableEntry kWritingModeKTable[];
+};
+
+#endif /* nsCSSProps_h___ */
diff --git a/layout/style/nsCSSPropsGenerated.inc.in b/layout/style/nsCSSPropsGenerated.inc.in
new file mode 100644
index 000000000..572342c93
--- /dev/null
+++ b/layout/style/nsCSSPropsGenerated.inc.in
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* processed file that defines CSS property tables that can't be generated
+ with the pre-processor, designed to be #included in nsCSSProps.cpp */
+
+const char* const nsCSSProps::kIDLNameTable[eCSSProperty_COUNT] = {
+${idl_names}
+};
+
+const int32_t nsCSSProps::kIDLNameSortPositionTable[eCSSProperty_COUNT] = {
+${idl_name_positions}
+};
+
+${assertions}
diff --git a/layout/style/nsCSSPseudoClassList.h b/layout/style/nsCSSPseudoClassList.h
new file mode 100644
index 000000000..701578338
--- /dev/null
+++ b/layout/style/nsCSSPseudoClassList.h
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* atom list for CSS pseudo-classes */
+
+/*
+ * This file contains the list of nsIAtoms and their values for CSS
+ * pseudo-classes. It is designed to be used as inline input to
+ * nsCSSPseudoClasses.cpp *only* through the magic of C preprocessing.
+ * All entries must be enclosed in the macros CSS_PSEUDO_CLASS,
+ * CSS_STATE_DEPENDENT_PSEUDO_CLASS, or CSS_STATE_PSEUDO_CLASS which
+ * will have cruel and unusual things done to them. The entries should
+ * be kept in some sort of logical order. The common arguments to these
+ * macros are:
+ * name_ : The C++ identifier used for the atom (which will be a member
+ * of nsCSSPseudoClasses)
+ * value_ : The pseudo-class as a string, including the initial colon,
+ * used as the string value of the atom.
+ * flags_ : A bitfield containing flags defined in nsCSSPseudoClasses.h
+ * pref_ : The name of the preference controlling whether the
+ * pseudo-class is recognized by the parser, or the empty
+ * string if it's unconditional.
+ * CSS_STATE_PSEUDO_CLASS has an additional argument:
+ * bit_ : The event state bit or bits that corresponds to the
+ * pseudo-class, i.e., causes it to match (only one bit
+ * required to match).
+ * CSS_STATE_DEPENDENT_PSEUDO_CLASS has an additional argument:
+ * bit_ : The event state bits that affect whether the pseudo-class
+ * matches. Matching depends on a customized per-class
+ * algorithm which should be defined in SelectorMatches() in
+ * nsCSSRuleProcessor.cpp.
+ *
+ * If CSS_STATE_PSEUDO_CLASS is not defined, it'll be automatically
+ * defined to CSS_STATE_DEPENDENT_PSEUDO_CLASS;
+ * if CSS_STATE_DEPENDENT_PSEUDO_CLASS is not defined, it'll be
+ * automatically defined to CSS_PSEUDO_CLASS.
+ */
+
+// OUTPUT_CLASS=nsCSSPseudoClasses
+// MACRO_NAME=CSS_PSEUDO_CLASS
+
+#ifdef DEFINED_CSS_STATE_DEPENDENT_PSEUDO_CLASS
+#error "CSS_STATE_DEPENDENT_PSEUDO_CLASS shouldn't be defined"
+#endif
+
+#ifndef CSS_STATE_DEPENDENT_PSEUDO_CLASS
+#define CSS_STATE_DEPENDENT_PSEUDO_CLASS(_name, _value, _flags, _pref, _bit) \
+ CSS_PSEUDO_CLASS(_name, _value, _flags, _pref)
+#define DEFINED_CSS_STATE_DEPENDENT_PSEUDO_CLASS
+#endif
+
+#ifdef DEFINED_CSS_STATE_PSEUDO_CLASS
+#error "CSS_STATE_PSEUDO_CLASS shouldn't be defined"
+#endif
+
+#ifndef CSS_STATE_PSEUDO_CLASS
+#define CSS_STATE_PSEUDO_CLASS(_name, _value, _flags, _pref, _bit) \
+ CSS_STATE_DEPENDENT_PSEUDO_CLASS(_name, _value, _flags, _pref, _bit)
+#define DEFINED_CSS_STATE_PSEUDO_CLASS
+#endif
+
+// The CSS_PSEUDO_CLASS entries should all come before the
+// CSS_STATE_PSEUDO_CLASS entries. The CSS_PSEUDO_CLASS entry order
+// must be the same as the order of cases in SelectorMatches. :not
+// must be the last CSS_PSEUDO_CLASS.
+
+CSS_PSEUDO_CLASS(empty, ":empty", 0, "")
+CSS_PSEUDO_CLASS(mozOnlyWhitespace, ":-moz-only-whitespace", 0, "")
+CSS_PSEUDO_CLASS(mozEmptyExceptChildrenWithLocalname, ":-moz-empty-except-children-with-localname", 0, "")
+CSS_PSEUDO_CLASS(lang, ":lang", 0, "")
+CSS_PSEUDO_CLASS(mozBoundElement, ":-moz-bound-element", 0, "")
+CSS_PSEUDO_CLASS(root, ":root", 0, "")
+CSS_PSEUDO_CLASS(any, ":-moz-any", 0, "")
+
+CSS_PSEUDO_CLASS(firstChild, ":first-child", 0, "")
+CSS_PSEUDO_CLASS(firstNode, ":-moz-first-node", 0, "")
+CSS_PSEUDO_CLASS(lastChild, ":last-child", 0, "")
+CSS_PSEUDO_CLASS(lastNode, ":-moz-last-node", 0, "")
+CSS_PSEUDO_CLASS(onlyChild, ":only-child", 0, "")
+CSS_PSEUDO_CLASS(firstOfType, ":first-of-type", 0, "")
+CSS_PSEUDO_CLASS(lastOfType, ":last-of-type", 0, "")
+CSS_PSEUDO_CLASS(onlyOfType, ":only-of-type", 0, "")
+CSS_PSEUDO_CLASS(nthChild, ":nth-child", 0, "")
+CSS_PSEUDO_CLASS(nthLastChild, ":nth-last-child", 0, "")
+CSS_PSEUDO_CLASS(nthOfType, ":nth-of-type", 0, "")
+CSS_PSEUDO_CLASS(nthLastOfType, ":nth-last-of-type", 0, "")
+
+// Match nodes that are HTML but not XHTML
+CSS_PSEUDO_CLASS(mozIsHTML, ":-moz-is-html", 0, "")
+
+// Match all custom elements whose created callback has not yet been invoked
+ CSS_STATE_PSEUDO_CLASS(unresolved, ":unresolved", 0, "", NS_EVENT_STATE_UNRESOLVED)
+
+// Matches nodes that are in a native-anonymous subtree (i.e., nodes in
+// a subtree of C++ anonymous content constructed by Gecko for its own
+// purposes).
+CSS_PSEUDO_CLASS(mozNativeAnonymous, ":-moz-native-anonymous",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS, "")
+
+// Matches anything when the specified look-and-feel metric is set
+CSS_PSEUDO_CLASS(mozSystemMetric, ":-moz-system-metric", 0, "")
+
+// -moz-locale-dir(ltr) and -moz-locale-dir(rtl) may be used
+// to match based on the locale's chrome direction
+CSS_PSEUDO_CLASS(mozLocaleDir, ":-moz-locale-dir", 0, "")
+
+// -moz-lwtheme may be used to match a document that has a lightweight theme
+CSS_PSEUDO_CLASS(mozLWTheme, ":-moz-lwtheme", 0, "")
+
+// -moz-lwtheme-brighttext matches a document that has a dark lightweight theme
+CSS_PSEUDO_CLASS(mozLWThemeBrightText, ":-moz-lwtheme-brighttext", 0, "")
+
+// -moz-lwtheme-darktext matches a document that has a bright lightweight theme
+CSS_PSEUDO_CLASS(mozLWThemeDarkText, ":-moz-lwtheme-darktext", 0, "")
+
+// Matches anything when the containing window is inactive
+CSS_PSEUDO_CLASS(mozWindowInactive, ":-moz-window-inactive", 0, "")
+
+// Matches any table elements that have a nonzero border attribute,
+// according to HTML integer attribute parsing rules.
+CSS_PSEUDO_CLASS(mozTableBorderNonzero, ":-moz-table-border-nonzero", 0, "")
+
+// Matches HTML frame/iframe elements which are mozbrowser.
+CSS_PSEUDO_CLASS(mozBrowserFrame, ":-moz-browser-frame",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "")
+
+// Matches whatever the contextual reference elements are for the
+// matching operation.
+CSS_PSEUDO_CLASS(scope, ":scope", 0, "layout.css.scope-pseudo.enabled")
+
+// :not needs to come at the end of the non-bit pseudo-class list, since
+// it doesn't actually get directly matched on in SelectorMatches.
+CSS_PSEUDO_CLASS(negation, ":not", 0, "")
+
+// :dir(ltr) and :dir(rtl) match elements whose resolved
+// directionality in the markup language is ltr or rtl respectively.
+CSS_STATE_DEPENDENT_PSEUDO_CLASS(dir, ":dir", 0, "",
+ NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL)
+// prefix version is deprecated and will be removed per bug 1270406.
+CSS_STATE_DEPENDENT_PSEUDO_CLASS(mozDir, ":-moz-dir", 0, "",
+ NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL)
+
+CSS_STATE_PSEUDO_CLASS(link, ":link", 0, "", NS_EVENT_STATE_UNVISITED)
+// what matches :link or :visited
+CSS_STATE_PSEUDO_CLASS(mozAnyLink, ":-moz-any-link", 0, "",
+ NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)
+CSS_STATE_PSEUDO_CLASS(anyLink, ":any-link", 0, "",
+ NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)
+CSS_STATE_PSEUDO_CLASS(visited, ":visited", 0, "", NS_EVENT_STATE_VISITED)
+
+CSS_STATE_PSEUDO_CLASS(active, ":active", 0, "", NS_EVENT_STATE_ACTIVE)
+CSS_STATE_PSEUDO_CLASS(checked, ":checked", 0, "", NS_EVENT_STATE_CHECKED)
+CSS_STATE_PSEUDO_CLASS(disabled, ":disabled", 0, "", NS_EVENT_STATE_DISABLED)
+CSS_STATE_PSEUDO_CLASS(enabled, ":enabled", 0, "", NS_EVENT_STATE_ENABLED)
+CSS_STATE_PSEUDO_CLASS(focus, ":focus", 0, "", NS_EVENT_STATE_FOCUS)
+CSS_STATE_PSEUDO_CLASS(focusWithin, ":focus-within", 0, "", NS_EVENT_STATE_FOCUS_WITHIN)
+CSS_STATE_PSEUDO_CLASS(hover, ":hover", 0, "", NS_EVENT_STATE_HOVER)
+CSS_STATE_PSEUDO_CLASS(mozDragOver, ":-moz-drag-over", 0, "", NS_EVENT_STATE_DRAGOVER)
+CSS_STATE_PSEUDO_CLASS(target, ":target", 0, "", NS_EVENT_STATE_URLTARGET)
+CSS_STATE_PSEUDO_CLASS(indeterminate, ":indeterminate", 0, "",
+ NS_EVENT_STATE_INDETERMINATE)
+
+CSS_STATE_PSEUDO_CLASS(mozDevtoolsHighlighted, ":-moz-devtools-highlighted", 0, "",
+ NS_EVENT_STATE_DEVTOOLS_HIGHLIGHTED)
+CSS_STATE_PSEUDO_CLASS(mozStyleeditorTransitioning, ":-moz-styleeditor-transitioning", 0, "",
+ NS_EVENT_STATE_STYLEEDITOR_TRANSITIONING)
+
+// Matches the element which is being displayed full-screen, and
+// any containing frames.
+CSS_STATE_PSEUDO_CLASS(fullscreen, ":fullscreen",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME,
+ "full-screen-api.unprefix.enabled",
+ NS_EVENT_STATE_FULL_SCREEN)
+CSS_STATE_PSEUDO_CLASS(mozFullScreen, ":-moz-full-screen", 0, "", NS_EVENT_STATE_FULL_SCREEN)
+
+// Matches if the element is focused and should show a focus ring
+CSS_STATE_PSEUDO_CLASS(mozFocusRing, ":-moz-focusring", 0, "", NS_EVENT_STATE_FOCUSRING)
+
+// Image, object, etc state pseudo-classes
+CSS_STATE_PSEUDO_CLASS(mozBroken, ":-moz-broken", 0, "", NS_EVENT_STATE_BROKEN)
+CSS_STATE_PSEUDO_CLASS(mozLoading, ":-moz-loading", 0, "", NS_EVENT_STATE_LOADING)
+
+CSS_STATE_PSEUDO_CLASS(mozUserDisabled, ":-moz-user-disabled",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
+ NS_EVENT_STATE_USERDISABLED)
+CSS_STATE_PSEUDO_CLASS(mozSuppressed, ":-moz-suppressed",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
+ NS_EVENT_STATE_SUPPRESSED)
+CSS_STATE_PSEUDO_CLASS(mozHandlerClickToPlay, ":-moz-handler-clicktoplay",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
+ NS_EVENT_STATE_TYPE_CLICK_TO_PLAY)
+CSS_STATE_PSEUDO_CLASS(mozHandlerVulnerableUpdatable, ":-moz-handler-vulnerable-updatable",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
+ NS_EVENT_STATE_VULNERABLE_UPDATABLE)
+CSS_STATE_PSEUDO_CLASS(mozHandlerVulnerableNoUpdate, ":-moz-handler-vulnerable-no-update",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
+ NS_EVENT_STATE_VULNERABLE_NO_UPDATE)
+CSS_STATE_PSEUDO_CLASS(mozHandlerDisabled, ":-moz-handler-disabled",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
+ NS_EVENT_STATE_HANDLER_DISABLED)
+CSS_STATE_PSEUDO_CLASS(mozHandlerBlocked, ":-moz-handler-blocked",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
+ NS_EVENT_STATE_HANDLER_BLOCKED)
+CSS_STATE_PSEUDO_CLASS(mozHandlerCrashed, ":-moz-handler-crashed",
+ CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
+ NS_EVENT_STATE_HANDLER_CRASHED)
+
+CSS_STATE_PSEUDO_CLASS(mozMathIncrementScriptLevel,
+ ":-moz-math-increment-script-level", 0, "",
+ NS_EVENT_STATE_INCREMENT_SCRIPT_LEVEL)
+
+// CSS 3 UI
+// http://www.w3.org/TR/2004/CR-css3-ui-20040511/#pseudo-classes
+CSS_STATE_PSEUDO_CLASS(required, ":required", 0, "", NS_EVENT_STATE_REQUIRED)
+CSS_STATE_PSEUDO_CLASS(optional, ":optional", 0, "", NS_EVENT_STATE_OPTIONAL)
+CSS_STATE_PSEUDO_CLASS(valid, ":valid", 0, "", NS_EVENT_STATE_VALID)
+CSS_STATE_PSEUDO_CLASS(invalid, ":invalid", 0, "", NS_EVENT_STATE_INVALID)
+CSS_STATE_PSEUDO_CLASS(inRange, ":in-range", 0, "", NS_EVENT_STATE_INRANGE)
+CSS_STATE_PSEUDO_CLASS(outOfRange, ":out-of-range", 0, "", NS_EVENT_STATE_OUTOFRANGE)
+CSS_STATE_PSEUDO_CLASS(defaultPseudo, ":default", 0, "", NS_EVENT_STATE_DEFAULT)
+CSS_STATE_PSEUDO_CLASS(placeholderShown, ":placeholder-shown", 0, "",
+ NS_EVENT_STATE_PLACEHOLDERSHOWN)
+CSS_STATE_PSEUDO_CLASS(mozReadOnly, ":-moz-read-only", 0, "",
+ NS_EVENT_STATE_MOZ_READONLY)
+CSS_STATE_PSEUDO_CLASS(mozReadWrite, ":-moz-read-write", 0, "",
+ NS_EVENT_STATE_MOZ_READWRITE)
+CSS_STATE_PSEUDO_CLASS(mozSubmitInvalid, ":-moz-submit-invalid", 0, "",
+ NS_EVENT_STATE_MOZ_SUBMITINVALID)
+CSS_STATE_PSEUDO_CLASS(mozUIInvalid, ":-moz-ui-invalid", 0, "",
+ NS_EVENT_STATE_MOZ_UI_INVALID)
+CSS_STATE_PSEUDO_CLASS(mozUIValid, ":-moz-ui-valid", 0, "",
+ NS_EVENT_STATE_MOZ_UI_VALID)
+CSS_STATE_PSEUDO_CLASS(mozMeterOptimum, ":-moz-meter-optimum", 0, "",
+ NS_EVENT_STATE_OPTIMUM)
+CSS_STATE_PSEUDO_CLASS(mozMeterSubOptimum, ":-moz-meter-sub-optimum", 0, "",
+ NS_EVENT_STATE_SUB_OPTIMUM)
+CSS_STATE_PSEUDO_CLASS(mozMeterSubSubOptimum, ":-moz-meter-sub-sub-optimum", 0, "",
+ NS_EVENT_STATE_SUB_SUB_OPTIMUM)
+
+// Those values should be parsed but do nothing.
+CSS_STATE_PSEUDO_CLASS(mozPlaceholder, ":-moz-placeholder", 0, "", NS_EVENT_STATE_IGNORE)
+
+#ifdef DEFINED_CSS_STATE_PSEUDO_CLASS
+#undef DEFINED_CSS_STATE_PSEUDO_CLASS
+#undef CSS_STATE_PSEUDO_CLASS
+#endif
+
+#ifdef DEFINED_CSS_STATE_DEPENDENT_PSEUDO_CLASS
+#undef DEFINED_CSS_STATE_DEPENDENT_PSEUDO_CLASS
+#undef CSS_STATE_DEPENDENT_PSEUDO_CLASS
+#endif
diff --git a/layout/style/nsCSSPseudoClasses.cpp b/layout/style/nsCSSPseudoClasses.cpp
new file mode 100644
index 000000000..9b26459dd
--- /dev/null
+++ b/layout/style/nsCSSPseudoClasses.cpp
@@ -0,0 +1,131 @@
+/* -*- 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/. */
+
+/* atom list for CSS pseudo-classes */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCSSPseudoClasses.h"
+#include "nsStaticAtom.h"
+#include "mozilla/Preferences.h"
+#include "nsString.h"
+
+using namespace mozilla;
+
+// define storage for all atoms
+#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) \
+ static nsIAtom* sPseudoClass_##_name;
+#include "nsCSSPseudoClassList.h"
+#undef CSS_PSEUDO_CLASS
+
+#define CSS_PSEUDO_CLASS(name_, value_, flags_, pref_) \
+ NS_STATIC_ATOM_BUFFER(name_##_pseudo_class_buffer, value_)
+#include "nsCSSPseudoClassList.h"
+#undef CSS_PSEUDO_CLASS
+
+#define CSS_PSEUDO_CLASS(name_, value_, flags_, pref_) \
+ static_assert(!((flags_) & CSS_PSEUDO_CLASS_ENABLED_IN_CHROME) || \
+ ((flags_) & CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), \
+ "Pseudo-class '" #name_ "' is enabled in chrome, so it " \
+ "should also be enabled in UA sheets");
+#include "nsCSSPseudoClassList.h"
+#undef CSS_PSEUDO_CLASS
+
+// Array of nsStaticAtom for each of the pseudo-classes.
+static const nsStaticAtom CSSPseudoClasses_info[] = {
+#define CSS_PSEUDO_CLASS(name_, value_, flags_, pref_) \
+ NS_STATIC_ATOM(name_##_pseudo_class_buffer, &sPseudoClass_##name_),
+#include "nsCSSPseudoClassList.h"
+#undef CSS_PSEUDO_CLASS
+};
+
+// Flags data for each of the pseudo-classes, which must be separate
+// from the previous array since there's no place for it in
+// nsStaticAtom.
+/* static */ const uint32_t
+nsCSSPseudoClasses::kPseudoClassFlags[] = {
+#define CSS_PSEUDO_CLASS(name_, value_, flags_, pref_) \
+ flags_,
+#include "nsCSSPseudoClassList.h"
+#undef CSS_PSEUDO_CLASS
+};
+
+/* static */ bool
+nsCSSPseudoClasses::sPseudoClassEnabled[] = {
+// If the pseudo class has any "ENABLED_IN" flag set, it is disabled by
+// default. Note that, if a pseudo class has pref, whatever its default
+// value is, it'll later be changed in nsCSSPseudoClasses::AddRefAtoms()
+// If the pseudo class has "ENABLED_IN" flags but doesn't have a pref,
+// it is an internal pseudo class which is disabled elsewhere.
+#define IS_ENABLED_BY_DEFAULT(flags_) \
+ (!((flags_) & CSS_PSEUDO_CLASS_ENABLED_MASK))
+#define CSS_PSEUDO_CLASS(name_, value_, flags_, pref_) \
+ IS_ENABLED_BY_DEFAULT(flags_),
+#include "nsCSSPseudoClassList.h"
+#undef CSS_PSEUDO_CLASS
+#undef IS_ENABLED_BY_DEFAULT
+};
+
+void nsCSSPseudoClasses::AddRefAtoms()
+{
+ NS_RegisterStaticAtoms(CSSPseudoClasses_info);
+
+#define CSS_PSEUDO_CLASS(name_, value_, flags_, pref_) \
+ if (pref_[0]) { \
+ auto idx = static_cast<CSSPseudoElementTypeBase>(Type::name_); \
+ Preferences::AddBoolVarCache(&sPseudoClassEnabled[idx], pref_); \
+ }
+#include "nsCSSPseudoClassList.h"
+#undef CSS_PSEUDO_CLASS
+}
+
+bool
+nsCSSPseudoClasses::HasStringArg(Type aType)
+{
+ return aType == Type::lang ||
+ aType == Type::mozEmptyExceptChildrenWithLocalname ||
+ aType == Type::mozSystemMetric ||
+ aType == Type::mozLocaleDir ||
+ aType == Type::mozDir ||
+ aType == Type::dir;
+}
+
+bool
+nsCSSPseudoClasses::HasNthPairArg(Type aType)
+{
+ return aType == Type::nthChild ||
+ aType == Type::nthLastChild ||
+ aType == Type::nthOfType ||
+ aType == Type::nthLastOfType;
+}
+
+void
+nsCSSPseudoClasses::PseudoTypeToString(Type aType, nsAString& aString)
+{
+ MOZ_ASSERT(aType < Type::Count, "Unexpected type");
+ auto idx = static_cast<CSSPseudoClassTypeBase>(aType);
+ (*CSSPseudoClasses_info[idx].mAtom)->ToString(aString);
+}
+
+/* static */ CSSPseudoClassType
+nsCSSPseudoClasses::GetPseudoType(nsIAtom* aAtom, EnabledState aEnabledState)
+{
+ for (uint32_t i = 0; i < ArrayLength(CSSPseudoClasses_info); ++i) {
+ if (*CSSPseudoClasses_info[i].mAtom == aAtom) {
+ Type type = Type(i);
+ return IsEnabled(type, aEnabledState) ? type : Type::NotPseudo;
+ }
+ }
+ return Type::NotPseudo;
+}
+
+/* static */ bool
+nsCSSPseudoClasses::IsUserActionPseudoClass(Type aType)
+{
+ // See http://dev.w3.org/csswg/selectors4/#useraction-pseudos
+ return aType == Type::hover ||
+ aType == Type::active ||
+ aType == Type::focus;
+}
diff --git a/layout/style/nsCSSPseudoClasses.h b/layout/style/nsCSSPseudoClasses.h
new file mode 100644
index 000000000..ca1cb2f39
--- /dev/null
+++ b/layout/style/nsCSSPseudoClasses.h
@@ -0,0 +1,90 @@
+/* -*- 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/. */
+
+/* atom list for CSS pseudo-classes */
+
+#ifndef nsCSSPseudoClasses_h___
+#define nsCSSPseudoClasses_h___
+
+#include "nsStringFwd.h"
+
+// The following two flags along with the pref defines where this pseudo
+// class can be used:
+// * If none of the two flags is presented, the pref completely controls
+// the availability of this pseudo class. And in that case, if it has
+// no pref, this property is usable everywhere.
+// * If any of the flags is set, this pseudo class is always enabled in
+// the specific contexts regardless of the value of the pref. If there
+// is no pref for this pseudo class at all in this case, it is an
+// internal-only pseudo class, which cannot be used anywhere else.
+#define CSS_PSEUDO_CLASS_ENABLED_MASK (3<<0)
+#define CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS (1<<0)
+#define CSS_PSEUDO_CLASS_ENABLED_IN_CHROME (1<<1)
+#define CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME \
+ (CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS | CSS_PSEUDO_CLASS_ENABLED_IN_CHROME)
+
+class nsIAtom;
+
+namespace mozilla {
+
+// The total count of CSSPseudoClassType is less than 256,
+// so use uint8_t as its underlying type.
+typedef uint8_t CSSPseudoClassTypeBase;
+enum class CSSPseudoClassType : CSSPseudoClassTypeBase
+{
+#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) \
+ _name,
+#include "nsCSSPseudoClassList.h"
+#undef CSS_PSEUDO_CLASS
+ Count,
+ NotPseudo, // This value MUST be second last! SelectorMatches depends on it.
+ MAX
+};
+
+} // namespace mozilla
+
+class nsCSSPseudoClasses
+{
+ typedef mozilla::CSSPseudoClassType Type;
+ typedef mozilla::CSSEnabledState EnabledState;
+
+public:
+ static void AddRefAtoms();
+
+ static Type GetPseudoType(nsIAtom* aAtom, EnabledState aEnabledState);
+ static bool HasStringArg(Type aType);
+ static bool HasNthPairArg(Type aType);
+ static bool HasSelectorListArg(Type aType) {
+ return aType == Type::any;
+ }
+ static bool IsUserActionPseudoClass(Type aType);
+
+ // Should only be used on types other than Count and NotPseudoClass
+ static void PseudoTypeToString(Type aType, nsAString& aString);
+
+ static bool IsEnabled(Type aType, EnabledState aEnabledState)
+ {
+ auto index = static_cast<size_t>(aType);
+ MOZ_ASSERT(index < static_cast<size_t>(Type::Count));
+ if (sPseudoClassEnabled[index] ||
+ aEnabledState == EnabledState::eIgnoreEnabledState) {
+ return true;
+ }
+ auto flags = kPseudoClassFlags[index];
+ if (((aEnabledState & EnabledState::eInChrome) &&
+ (flags & CSS_PSEUDO_CLASS_ENABLED_IN_CHROME)) ||
+ ((aEnabledState & EnabledState::eInUASheets) &&
+ (flags & CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS))) {
+ return true;
+ }
+ return false;
+ }
+
+private:
+ static const uint32_t kPseudoClassFlags[size_t(Type::Count)];
+ static bool sPseudoClassEnabled[size_t(Type::Count)];
+};
+
+#endif /* nsCSSPseudoClasses_h___ */
diff --git a/layout/style/nsCSSPseudoElementList.h b/layout/style/nsCSSPseudoElementList.h
new file mode 100644
index 000000000..552c76734
--- /dev/null
+++ b/layout/style/nsCSSPseudoElementList.h
@@ -0,0 +1,87 @@
+/* -*- 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/. */
+
+/* atom list for CSS pseudo-elements */
+
+/*
+ * This file contains the list of nsIAtoms and their values for CSS
+ * pseudo-elements. 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
+ */
+
+// OUTPUT_CLASS=nsCSSPseudoElements
+// MACRO_NAME=CSS_PSEUDO_ELEMENT
+
+CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2)
+CSS_PSEUDO_ELEMENT(before, ":before", CSS_PSEUDO_ELEMENT_IS_CSS2)
+
+CSS_PSEUDO_ELEMENT(backdrop, ":backdrop", 0)
+
+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(mozSelection, ":-moz-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)
+CSS_PSEUDO_ELEMENT(mozFocusOuter, ":-moz-focus-outer", 0)
+
+// XXXbz should we really allow random content to style these? Maybe
+// use our flags to prevent that?
+CSS_PSEUDO_ELEMENT(mozListBullet, ":-moz-list-bullet", 0)
+CSS_PSEUDO_ELEMENT(mozListNumber, ":-moz-list-number", 0)
+
+CSS_PSEUDO_ELEMENT(mozMathAnonymous, ":-moz-math-anonymous", 0)
+
+// HTML5 Forms pseudo elements
+CSS_PSEUDO_ELEMENT(mozNumberWrapper, ":-moz-number-wrapper",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY)
+CSS_PSEUDO_ELEMENT(mozNumberText, ":-moz-number-text",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY)
+CSS_PSEUDO_ELEMENT(mozNumberSpinBox, ":-moz-number-spin-box",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY)
+CSS_PSEUDO_ELEMENT(mozNumberSpinUp, ":-moz-number-spin-up",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY)
+CSS_PSEUDO_ELEMENT(mozNumberSpinDown, ":-moz-number-spin-down",
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
+ CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY)
+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(mozPlaceholder, ":-moz-placeholder",
+ 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)
diff --git a/layout/style/nsCSSPseudoElements.cpp b/layout/style/nsCSSPseudoElements.cpp
new file mode 100644
index 000000000..ef09f2d42
--- /dev/null
+++ b/layout/style/nsCSSPseudoElements.cpp
@@ -0,0 +1,120 @@
+/* -*- 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/. */
+
+/* atom list for CSS pseudo-elements */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCSSPseudoElements.h"
+#include "nsAtomListUtils.h"
+#include "nsStaticAtom.h"
+#include "nsCSSAnonBoxes.h"
+
+using namespace mozilla;
+
+// define storage for all atoms
+#define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
+ nsICSSPseudoElement* nsCSSPseudoElements::name_;
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+
+#define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
+ NS_STATIC_ATOM_BUFFER(name_##_pseudo_element_buffer, value_)
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+
+// Array of nsStaticAtom for each of the pseudo-elements.
+static const nsStaticAtom CSSPseudoElements_info[] = {
+#define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
+ NS_STATIC_ATOM(name_##_pseudo_element_buffer, (nsIAtom**)&nsCSSPseudoElements::name_),
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+};
+
+// Flags data for each of the pseudo-elements, which must be separate
+// from the previous array since there's no place for it in
+// nsStaticAtom.
+/* static */ const uint32_t
+nsCSSPseudoElements::kPseudoElementFlags[] = {
+#define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
+ flags_,
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+};
+
+void nsCSSPseudoElements::AddRefAtoms()
+{
+ NS_RegisterStaticAtoms(CSSPseudoElements_info);
+}
+
+bool nsCSSPseudoElements::IsPseudoElement(nsIAtom *aAtom)
+{
+ return nsAtomListUtils::IsMember(aAtom, CSSPseudoElements_info,
+ ArrayLength(CSSPseudoElements_info));
+}
+
+/* static */ bool
+nsCSSPseudoElements::IsCSS2PseudoElement(nsIAtom *aAtom)
+{
+ // We don't implement this using PseudoElementHasFlags because callers
+ // want to pass things that could be anon boxes.
+ NS_ASSERTION(nsCSSPseudoElements::IsPseudoElement(aAtom) ||
+ nsCSSAnonBoxes::IsAnonBox(aAtom),
+ "must be pseudo element or anon box");
+ bool result = aAtom == nsCSSPseudoElements::after ||
+ aAtom == nsCSSPseudoElements::before ||
+ aAtom == nsCSSPseudoElements::firstLetter ||
+ aAtom == nsCSSPseudoElements::firstLine;
+ NS_ASSERTION(nsCSSAnonBoxes::IsAnonBox(aAtom) ||
+ result == PseudoElementHasFlags(
+ GetPseudoType(aAtom, EnabledState::eIgnoreEnabledState),
+ CSS_PSEUDO_ELEMENT_IS_CSS2),
+ "result doesn't match flags");
+ return result;
+}
+
+/* static */ CSSPseudoElementType
+nsCSSPseudoElements::GetPseudoType(nsIAtom *aAtom, EnabledState aEnabledState)
+{
+ for (CSSPseudoElementTypeBase i = 0;
+ i < ArrayLength(CSSPseudoElements_info);
+ ++i) {
+ if (*CSSPseudoElements_info[i].mAtom == aAtom) {
+ auto type = static_cast<Type>(i);
+ // ::moz-placeholder is an alias for ::placeholder
+ if (type == CSSPseudoElementType::mozPlaceholder) {
+ type = CSSPseudoElementType::placeholder;
+ }
+ return IsEnabled(type, aEnabledState) ? type : Type::NotPseudo;
+ }
+ }
+
+ if (nsCSSAnonBoxes::IsAnonBox(aAtom)) {
+#ifdef MOZ_XUL
+ if (nsCSSAnonBoxes::IsTreePseudoElement(aAtom)) {
+ return Type::XULTree;
+ }
+#endif
+
+ return Type::AnonBox;
+ }
+
+ return Type::NotPseudo;
+}
+
+/* static */ nsIAtom*
+nsCSSPseudoElements::GetPseudoAtom(Type aType)
+{
+ NS_ASSERTION(aType < Type::Count, "Unexpected type");
+ return *CSSPseudoElements_info[
+ static_cast<CSSPseudoElementTypeBase>(aType)].mAtom;
+}
+
+/* static */ bool
+nsCSSPseudoElements::PseudoElementSupportsUserActionState(const Type aType)
+{
+ return PseudoElementHasFlags(aType,
+ CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE);
+}
diff --git a/layout/style/nsCSSPseudoElements.h b/layout/style/nsCSSPseudoElements.h
new file mode 100644
index 000000000..657ef663e
--- /dev/null
+++ b/layout/style/nsCSSPseudoElements.h
@@ -0,0 +1,128 @@
+/* -*- 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/. */
+
+/* atom list for CSS pseudo-elements */
+
+#ifndef nsCSSPseudoElements_h___
+#define nsCSSPseudoElements_h___
+
+#include "nsIAtom.h"
+#include "mozilla/CSSEnabledState.h"
+#include "mozilla/Compiler.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)?
+#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)
+// Is content prevented from parsing selectors containing this pseudo-element?
+#define CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY (1<<4)
+
+namespace mozilla {
+
+// The total count of CSSPseudoElement is less than 256,
+// so use uint8_t as its underlying type.
+typedef uint8_t CSSPseudoElementTypeBase;
+enum class CSSPseudoElementType : CSSPseudoElementTypeBase {
+ // If the actual pseudo-elements stop being first here, change
+ // GetPseudoType.
+#define CSS_PSEUDO_ELEMENT(_name, _value_, _flags) \
+ _name,
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+ Count,
+ AnonBox = Count,
+#ifdef MOZ_XUL
+ XULTree,
+#endif
+ NotPseudo,
+ MAX
+};
+
+} // namespace mozilla
+
+// Empty class derived from nsIAtom so that function signatures can
+// require an atom from this atom list.
+class nsICSSPseudoElement : public nsIAtom {};
+
+class nsCSSPseudoElements
+{
+ typedef mozilla::CSSPseudoElementType Type;
+ typedef mozilla::CSSEnabledState EnabledState;
+
+public:
+ static void AddRefAtoms();
+
+ static bool IsPseudoElement(nsIAtom *aAtom);
+
+ static bool IsCSS2PseudoElement(nsIAtom *aAtom);
+
+#define CSS_PSEUDO_ELEMENT(_name, _value, _flags) \
+ static nsICSSPseudoElement* _name;
+#include "nsCSSPseudoElementList.h"
+#undef CSS_PSEUDO_ELEMENT
+
+ static Type GetPseudoType(nsIAtom* aAtom, EnabledState aEnabledState);
+
+ // Get the atom for a given Type. aType must be < CSSPseudoElementType::Count
+ static nsIAtom* GetPseudoAtom(Type aType);
+
+ static bool PseudoElementContainsElements(const Type aType) {
+ return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS);
+ }
+
+ static bool PseudoElementSupportsStyleAttribute(const Type aType) {
+ MOZ_ASSERT(aType < Type::Count);
+ return PseudoElementHasFlags(aType,
+ CSS_PSEUDO_ELEMENT_SUPPORTS_STYLE_ATTRIBUTE);
+ }
+
+ static bool PseudoElementSupportsUserActionState(const Type aType);
+
+ static bool IsEnabled(Type aType, EnabledState aEnabledState)
+ {
+ return !PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY) ||
+ (aEnabledState & EnabledState::eInUASheets);
+ }
+
+private:
+ // Does the given pseudo-element have all of the flags given?
+
+ // Work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64037 ,
+ // which is a general gcc bug that we seem to have hit only on Android/x86.
+#if defined(ANDROID) && defined(__i386__) && defined(__GNUC__) && \
+ !defined(__clang__)
+#if (MOZ_GCC_VERSION_AT_LEAST(4,8,0) && MOZ_GCC_VERSION_AT_MOST(4,8,4)) || \
+ (MOZ_GCC_VERSION_AT_LEAST(4,9,0) && MOZ_GCC_VERSION_AT_MOST(4,9,2))
+ __attribute__((noinline))
+#endif
+#endif
+ static bool PseudoElementHasFlags(const Type aType, uint32_t aFlags)
+ {
+ MOZ_ASSERT(aType < Type::Count);
+ return (kPseudoElementFlags[size_t(aType)] & aFlags) == aFlags;
+ }
+
+ static const uint32_t kPseudoElementFlags[size_t(Type::Count)];
+};
+
+#endif /* nsCSSPseudoElements_h___ */
diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp
new file mode 100644
index 000000000..07a4ef57b
--- /dev/null
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -0,0 +1,4061 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:tabstop=2:expandtab:shiftwidth=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/. */
+
+/*
+ * style rule processor for CSS style sheets, responsible for selector
+ * matching and cascading
+ */
+
+#define PL_ARENA_CONST_ALIGN_MASK 7
+// We want page-sized arenas so there's no fragmentation involved.
+// Including plarena.h must come first to avoid it being included by some
+// header file thereby making PL_ARENA_CONST_ALIGN_MASK ineffective.
+#define NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE (4096)
+#include "plarena.h"
+
+#include "nsAutoPtr.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsRuleProcessorData.h"
+#include <algorithm>
+#include "nsIAtom.h"
+#include "PLDHashTable.h"
+#include "nsICSSPseudoComparator.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/StyleRule.h"
+#include "mozilla/css/GroupRule.h"
+#include "nsIDocument.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsUnicharUtils.h"
+#include "nsError.h"
+#include "nsRuleWalker.h"
+#include "nsCSSPseudoClasses.h"
+#include "nsCSSPseudoElements.h"
+#include "nsIContent.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsStyleUtil.h"
+#include "nsQuickSort.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsAttrName.h"
+#include "nsTArray.h"
+#include "nsContentUtils.h"
+#include "nsIMediaList.h"
+#include "nsCSSRules.h"
+#include "nsStyleSet.h"
+#include "mozilla/dom/Element.h"
+#include "nsNthIndexCache.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Likely.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/TypedEnumBits.h"
+#include "RuleProcessorCache.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsIMozBrowserFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define VISITED_PSEUDO_PREF "layout.css.visited_links_enabled"
+
+static bool gSupportVisitedPseudo = true;
+
+static nsTArray< nsCOMPtr<nsIAtom> >* sSystemMetrics = 0;
+
+#ifdef XP_WIN
+uint8_t nsCSSRuleProcessor::sWinThemeId = LookAndFeel::eWindowsTheme_Generic;
+#endif
+
+/**
+ * A struct representing a given CSS rule and a particular selector
+ * from that rule's selector list.
+ */
+struct RuleSelectorPair {
+ RuleSelectorPair(css::StyleRule* aRule, nsCSSSelector* aSelector)
+ : mRule(aRule), mSelector(aSelector) {}
+ // If this class ever grows a destructor, deal with
+ // PerWeightDataListItem appropriately.
+
+ css::StyleRule* mRule;
+ nsCSSSelector* mSelector; // which of |mRule|'s selectors
+};
+
+#define NS_IS_ANCESTOR_OPERATOR(ch) \
+ ((ch) == char16_t(' ') || (ch) == char16_t('>'))
+
+/**
+ * A struct representing a particular rule in an ordered list of rules
+ * (the ordering depending on the weight of mSelector and the order of
+ * our rules to start with).
+ */
+struct RuleValue : RuleSelectorPair {
+ enum {
+ eMaxAncestorHashes = 4
+ };
+
+ RuleValue(const RuleSelectorPair& aRuleSelectorPair, int32_t aIndex,
+ bool aQuirksMode) :
+ RuleSelectorPair(aRuleSelectorPair),
+ mIndex(aIndex)
+ {
+ CollectAncestorHashes(aQuirksMode);
+ }
+
+ int32_t mIndex; // High index means high weight/order.
+ uint32_t mAncestorSelectorHashes[eMaxAncestorHashes];
+
+private:
+ void CollectAncestorHashes(bool aQuirksMode) {
+ // Collect up our mAncestorSelectorHashes. It's not clear whether it's
+ // better to stop once we've found eMaxAncestorHashes of them or to keep
+ // going and preferentially collect information from selectors higher up the
+ // chain... Let's do the former for now.
+ size_t hashIndex = 0;
+ for (nsCSSSelector* sel = mSelector->mNext; sel; sel = sel->mNext) {
+ if (!NS_IS_ANCESTOR_OPERATOR(sel->mOperator)) {
+ // |sel| is going to select something that's not actually one of our
+ // ancestors, so don't add it to mAncestorSelectorHashes. But keep
+ // going, because it'll select a sibling of one of our ancestors, so its
+ // ancestors would be our ancestors too.
+ continue;
+ }
+
+ // Now sel is supposed to select one of our ancestors. Grab
+ // whatever info we can from it into mAncestorSelectorHashes.
+ // But in qurks mode, don't grab IDs and classes because those
+ // need to be matched case-insensitively.
+ if (!aQuirksMode) {
+ nsAtomList* ids = sel->mIDList;
+ while (ids) {
+ mAncestorSelectorHashes[hashIndex++] = ids->mAtom->hash();
+ if (hashIndex == eMaxAncestorHashes) {
+ return;
+ }
+ ids = ids->mNext;
+ }
+
+ nsAtomList* classes = sel->mClassList;
+ while (classes) {
+ mAncestorSelectorHashes[hashIndex++] = classes->mAtom->hash();
+ if (hashIndex == eMaxAncestorHashes) {
+ return;
+ }
+ classes = classes->mNext;
+ }
+ }
+
+ // Only put in the tag name if it's all-lowercase. Otherwise we run into
+ // trouble because we may test the wrong one of mLowercaseTag and
+ // mCasedTag against the filter.
+ if (sel->mLowercaseTag && sel->mCasedTag == sel->mLowercaseTag) {
+ mAncestorSelectorHashes[hashIndex++] = sel->mLowercaseTag->hash();
+ if (hashIndex == eMaxAncestorHashes) {
+ return;
+ }
+ }
+ }
+
+ while (hashIndex != eMaxAncestorHashes) {
+ mAncestorSelectorHashes[hashIndex++] = 0;
+ }
+ }
+};
+
+// ------------------------------
+// Rule hash table
+//
+
+// Uses any of the sets of ops below.
+struct RuleHashTableEntry : public PLDHashEntryHdr {
+ // If you add members that have heap allocated memory be sure to change the
+ // logic in SizeOfRuleHashTable().
+ // Auto length 1, because we always have at least one entry in mRules.
+ AutoTArray<RuleValue, 1> mRules;
+};
+
+struct RuleHashTagTableEntry : public RuleHashTableEntry {
+ // If you add members that have heap allocated memory be sure to change the
+ // logic in RuleHash::SizeOf{In,Ex}cludingThis.
+ nsCOMPtr<nsIAtom> mTag;
+};
+
+static PLDHashNumber
+RuleHash_CIHashKey(const void *key)
+{
+ nsIAtom *atom = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key));
+
+ nsAutoString str;
+ atom->ToString(str);
+ nsContentUtils::ASCIIToLower(str);
+ return HashString(str);
+}
+
+static inline nsCSSSelector*
+SubjectSelectorForRuleHash(const PLDHashEntryHdr *hdr)
+{
+ auto entry = static_cast<const RuleHashTableEntry*>(hdr);
+ nsCSSSelector* selector = entry->mRules[0].mSelector;
+ if (selector->IsPseudoElement()) {
+ selector = selector->mNext;
+ }
+ return selector;
+}
+
+static inline bool
+CIMatchAtoms(const void* key, nsIAtom *entry_atom)
+{
+ auto match_atom = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key));
+
+ // Check for case-sensitive match first.
+ if (match_atom == entry_atom) {
+ return true;
+ }
+
+ // Use EqualsIgnoreASCIICase instead of full on unicode case conversion
+ // in order to save on performance. This is only used in quirks mode
+ // anyway.
+ return
+ nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(entry_atom),
+ nsDependentAtomString(match_atom));
+}
+
+static inline bool
+CSMatchAtoms(const void* key, nsIAtom *entry_atom)
+{
+ auto match_atom = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key));
+ return match_atom == entry_atom;
+}
+
+static bool
+RuleHash_ClassCIMatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ return CIMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mClassList->mAtom);
+}
+
+static bool
+RuleHash_IdCIMatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ return CIMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mIDList->mAtom);
+}
+
+static bool
+RuleHash_ClassCSMatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ return CSMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mClassList->mAtom);
+}
+
+static bool
+RuleHash_IdCSMatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ return CSMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mIDList->mAtom);
+}
+
+static void
+RuleHash_InitEntry(PLDHashEntryHdr *hdr, const void *key)
+{
+ RuleHashTableEntry* entry = static_cast<RuleHashTableEntry*>(hdr);
+ new (KnownNotNull, entry) RuleHashTableEntry();
+}
+
+static void
+RuleHash_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
+{
+ RuleHashTableEntry* entry = static_cast<RuleHashTableEntry*>(hdr);
+ entry->~RuleHashTableEntry();
+}
+
+static void
+RuleHash_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from,
+ PLDHashEntryHdr *to)
+{
+ NS_PRECONDITION(from != to, "This is not going to work!");
+ RuleHashTableEntry *oldEntry =
+ const_cast<RuleHashTableEntry*>(
+ static_cast<const RuleHashTableEntry*>(from));
+ auto* newEntry = new (KnownNotNull, to) RuleHashTableEntry();
+ newEntry->mRules.SwapElements(oldEntry->mRules);
+ oldEntry->~RuleHashTableEntry();
+}
+
+static bool
+RuleHash_TagTable_MatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ nsIAtom *match_atom = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key));
+ nsIAtom *entry_atom = static_cast<const RuleHashTagTableEntry*>(hdr)->mTag;
+
+ return match_atom == entry_atom;
+}
+
+static void
+RuleHash_TagTable_InitEntry(PLDHashEntryHdr *hdr, const void *key)
+{
+ RuleHashTagTableEntry* entry = static_cast<RuleHashTagTableEntry*>(hdr);
+ new (KnownNotNull, entry) RuleHashTagTableEntry();
+ entry->mTag = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key));
+}
+
+static void
+RuleHash_TagTable_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
+{
+ RuleHashTagTableEntry* entry = static_cast<RuleHashTagTableEntry*>(hdr);
+ entry->~RuleHashTagTableEntry();
+}
+
+static void
+RuleHash_TagTable_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from,
+ PLDHashEntryHdr *to)
+{
+ NS_PRECONDITION(from != to, "This is not going to work!");
+ RuleHashTagTableEntry *oldEntry =
+ const_cast<RuleHashTagTableEntry*>(
+ static_cast<const RuleHashTagTableEntry*>(from));
+ auto* newEntry = new (KnownNotNull, to) RuleHashTagTableEntry();
+ newEntry->mTag.swap(oldEntry->mTag);
+ newEntry->mRules.SwapElements(oldEntry->mRules);
+ oldEntry->~RuleHashTagTableEntry();
+}
+
+static PLDHashNumber
+RuleHash_NameSpaceTable_HashKey(const void *key)
+{
+ return NS_PTR_TO_INT32(key);
+}
+
+static bool
+RuleHash_NameSpaceTable_MatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ const RuleHashTableEntry *entry =
+ static_cast<const RuleHashTableEntry*>(hdr);
+
+ nsCSSSelector* selector = entry->mRules[0].mSelector;
+ if (selector->IsPseudoElement()) {
+ selector = selector->mNext;
+ }
+ return NS_PTR_TO_INT32(key) == selector->mNameSpace;
+}
+
+static const PLDHashTableOps RuleHash_TagTable_Ops = {
+ PLDHashTable::HashVoidPtrKeyStub,
+ RuleHash_TagTable_MatchEntry,
+ RuleHash_TagTable_MoveEntry,
+ RuleHash_TagTable_ClearEntry,
+ RuleHash_TagTable_InitEntry
+};
+
+// Case-sensitive ops.
+static const PLDHashTableOps RuleHash_ClassTable_CSOps = {
+ PLDHashTable::HashVoidPtrKeyStub,
+ RuleHash_ClassCSMatchEntry,
+ RuleHash_MoveEntry,
+ RuleHash_ClearEntry,
+ RuleHash_InitEntry
+};
+
+// Case-insensitive ops.
+static const PLDHashTableOps RuleHash_ClassTable_CIOps = {
+ RuleHash_CIHashKey,
+ RuleHash_ClassCIMatchEntry,
+ RuleHash_MoveEntry,
+ RuleHash_ClearEntry,
+ RuleHash_InitEntry
+};
+
+// Case-sensitive ops.
+static const PLDHashTableOps RuleHash_IdTable_CSOps = {
+ PLDHashTable::HashVoidPtrKeyStub,
+ RuleHash_IdCSMatchEntry,
+ RuleHash_MoveEntry,
+ RuleHash_ClearEntry,
+ RuleHash_InitEntry
+};
+
+// Case-insensitive ops.
+static const PLDHashTableOps RuleHash_IdTable_CIOps = {
+ RuleHash_CIHashKey,
+ RuleHash_IdCIMatchEntry,
+ RuleHash_MoveEntry,
+ RuleHash_ClearEntry,
+ RuleHash_InitEntry
+};
+
+static const PLDHashTableOps RuleHash_NameSpaceTable_Ops = {
+ RuleHash_NameSpaceTable_HashKey,
+ RuleHash_NameSpaceTable_MatchEntry,
+ RuleHash_MoveEntry,
+ RuleHash_ClearEntry,
+ RuleHash_InitEntry
+};
+
+#undef RULE_HASH_STATS
+#undef PRINT_UNIVERSAL_RULES
+
+#ifdef RULE_HASH_STATS
+#define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO ++(var_); PR_END_MACRO
+#else
+#define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO PR_END_MACRO
+#endif
+
+struct NodeMatchContext;
+
+class RuleHash {
+public:
+ explicit RuleHash(bool aQuirksMode);
+ ~RuleHash();
+ void AppendRule(const RuleSelectorPair &aRuleInfo);
+ void EnumerateAllRules(Element* aElement, ElementDependentRuleProcessorData* aData,
+ NodeMatchContext& aNodeMatchContext);
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+protected:
+ typedef nsTArray<RuleValue> RuleValueList;
+ void AppendRuleToTable(PLDHashTable* aTable, const void* aKey,
+ const RuleSelectorPair& aRuleInfo);
+ void AppendUniversalRule(const RuleSelectorPair& aRuleInfo);
+
+ int32_t mRuleCount;
+
+ PLDHashTable mIdTable;
+ PLDHashTable mClassTable;
+ PLDHashTable mTagTable;
+ PLDHashTable mNameSpaceTable;
+ RuleValueList mUniversalRules;
+
+ struct EnumData {
+ const RuleValue* mCurValue;
+ const RuleValue* mEnd;
+ };
+ EnumData* mEnumList;
+ int32_t mEnumListSize;
+
+ bool mQuirksMode;
+
+ inline EnumData ToEnumData(const RuleValueList& arr) {
+ EnumData data = { arr.Elements(), arr.Elements() + arr.Length() };
+ return data;
+ }
+
+#ifdef RULE_HASH_STATS
+ uint32_t mUniversalSelectors;
+ uint32_t mNameSpaceSelectors;
+ uint32_t mTagSelectors;
+ uint32_t mClassSelectors;
+ uint32_t mIdSelectors;
+
+ uint32_t mElementsMatched;
+
+ uint32_t mElementUniversalCalls;
+ uint32_t mElementNameSpaceCalls;
+ uint32_t mElementTagCalls;
+ uint32_t mElementClassCalls;
+ uint32_t mElementIdCalls;
+#endif // RULE_HASH_STATS
+};
+
+RuleHash::RuleHash(bool aQuirksMode)
+ : mRuleCount(0),
+ mIdTable(aQuirksMode ? &RuleHash_IdTable_CIOps
+ : &RuleHash_IdTable_CSOps,
+ sizeof(RuleHashTableEntry)),
+ mClassTable(aQuirksMode ? &RuleHash_ClassTable_CIOps
+ : &RuleHash_ClassTable_CSOps,
+ sizeof(RuleHashTableEntry)),
+ mTagTable(&RuleHash_TagTable_Ops, sizeof(RuleHashTagTableEntry)),
+ mNameSpaceTable(&RuleHash_NameSpaceTable_Ops, sizeof(RuleHashTableEntry)),
+ mUniversalRules(0),
+ mEnumList(nullptr), mEnumListSize(0),
+ mQuirksMode(aQuirksMode)
+#ifdef RULE_HASH_STATS
+ ,
+ mUniversalSelectors(0),
+ mNameSpaceSelectors(0),
+ mTagSelectors(0),
+ mClassSelectors(0),
+ mIdSelectors(0),
+ mElementsMatched(0),
+ mElementUniversalCalls(0),
+ mElementNameSpaceCalls(0),
+ mElementTagCalls(0),
+ mElementClassCalls(0),
+ mElementIdCalls(0)
+#endif
+{
+ MOZ_COUNT_CTOR(RuleHash);
+}
+
+RuleHash::~RuleHash()
+{
+ MOZ_COUNT_DTOR(RuleHash);
+#ifdef RULE_HASH_STATS
+ printf(
+"RuleHash(%p):\n"
+" Selectors: Universal (%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n"
+" Content Nodes: Elements(%u)\n"
+" Element Calls: Universal(%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n"
+ static_cast<void*>(this),
+ mUniversalSelectors, mNameSpaceSelectors, mTagSelectors,
+ mClassSelectors, mIdSelectors,
+ mElementsMatched,
+ mElementUniversalCalls, mElementNameSpaceCalls, mElementTagCalls,
+ mElementClassCalls, mElementIdCalls);
+#ifdef PRINT_UNIVERSAL_RULES
+ {
+ if (mUniversalRules.Length() > 0) {
+ printf(" Universal rules:\n");
+ for (uint32_t i = 0; i < mUniversalRules.Length(); ++i) {
+ RuleValue* value = &(mUniversalRules[i]);
+ nsAutoString selectorText;
+ uint32_t lineNumber = value->mRule->GetLineNumber();
+ RefPtr<CSSStyleSheet> cssSheet = value->mRule->GetStyleSheet();
+ value->mSelector->ToString(selectorText, cssSheet);
+
+ printf(" line %d, %s\n",
+ lineNumber, NS_ConvertUTF16toUTF8(selectorText).get());
+ }
+ }
+ }
+#endif // PRINT_UNIVERSAL_RULES
+#endif // RULE_HASH_STATS
+ // Rule Values are arena allocated no need to delete them. Their destructor
+ // isn't doing any cleanup. So we dont even bother to enumerate through
+ // the hash tables and call their destructors.
+ if (nullptr != mEnumList) {
+ delete [] mEnumList;
+ }
+}
+
+void RuleHash::AppendRuleToTable(PLDHashTable* aTable, const void* aKey,
+ const RuleSelectorPair& aRuleInfo)
+{
+ // Get a new or existing entry.
+ auto entry = static_cast<RuleHashTableEntry*>(aTable->Add(aKey, fallible));
+ if (!entry)
+ return;
+ entry->mRules.AppendElement(RuleValue(aRuleInfo, mRuleCount++, mQuirksMode));
+}
+
+static void
+AppendRuleToTagTable(PLDHashTable* aTable, nsIAtom* aKey,
+ const RuleValue& aRuleInfo)
+{
+ // Get a new or exisiting entry
+ auto entry = static_cast<RuleHashTagTableEntry*>(aTable->Add(aKey, fallible));
+ if (!entry)
+ return;
+
+ entry->mRules.AppendElement(aRuleInfo);
+}
+
+void RuleHash::AppendUniversalRule(const RuleSelectorPair& aRuleInfo)
+{
+ mUniversalRules.AppendElement(RuleValue(aRuleInfo, mRuleCount++, mQuirksMode));
+}
+
+void RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo)
+{
+ nsCSSSelector *selector = aRuleInfo.mSelector;
+ if (selector->IsPseudoElement()) {
+ selector = selector->mNext;
+ }
+ if (nullptr != selector->mIDList) {
+ AppendRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo);
+ RULE_HASH_STAT_INCREMENT(mIdSelectors);
+ }
+ else if (nullptr != selector->mClassList) {
+ AppendRuleToTable(&mClassTable, selector->mClassList->mAtom, aRuleInfo);
+ RULE_HASH_STAT_INCREMENT(mClassSelectors);
+ }
+ else if (selector->mLowercaseTag) {
+ RuleValue ruleValue(aRuleInfo, mRuleCount++, mQuirksMode);
+ AppendRuleToTagTable(&mTagTable, selector->mLowercaseTag, ruleValue);
+ RULE_HASH_STAT_INCREMENT(mTagSelectors);
+ if (selector->mCasedTag &&
+ selector->mCasedTag != selector->mLowercaseTag) {
+ AppendRuleToTagTable(&mTagTable, selector->mCasedTag, ruleValue);
+ RULE_HASH_STAT_INCREMENT(mTagSelectors);
+ }
+ }
+ else if (kNameSpaceID_Unknown != selector->mNameSpace) {
+ AppendRuleToTable(&mNameSpaceTable,
+ NS_INT32_TO_PTR(selector->mNameSpace), aRuleInfo);
+ RULE_HASH_STAT_INCREMENT(mNameSpaceSelectors);
+ }
+ else { // universal tag selector
+ AppendUniversalRule(aRuleInfo);
+ RULE_HASH_STAT_INCREMENT(mUniversalSelectors);
+ }
+}
+
+// this should cover practically all cases so we don't need to reallocate
+#define MIN_ENUM_LIST_SIZE 8
+
+#ifdef RULE_HASH_STATS
+#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \
+ (var_) += (list_).Length()
+#else
+#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \
+ PR_BEGIN_MACRO PR_END_MACRO
+#endif
+
+static inline
+void ContentEnumFunc(const RuleValue &value, nsCSSSelector* selector,
+ ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext,
+ AncestorFilter *ancestorFilter);
+
+void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcessorData* aData,
+ NodeMatchContext& aNodeContext)
+{
+ int32_t nameSpace = aElement->GetNameSpaceID();
+ nsIAtom* tag = aElement->NodeInfo()->NameAtom();
+ nsIAtom* id = aElement->GetID();
+ const nsAttrValue* classList = aElement->GetClasses();
+
+ MOZ_ASSERT(tag, "How could we not have a tag?");
+
+ int32_t classCount = classList ? classList->GetAtomCount() : 0;
+
+ // assume 1 universal, tag, id, and namespace, rather than wasting
+ // time counting
+ int32_t testCount = classCount + 4;
+
+ if (mEnumListSize < testCount) {
+ delete [] mEnumList;
+ mEnumListSize = std::max(testCount, MIN_ENUM_LIST_SIZE);
+ mEnumList = new EnumData[mEnumListSize];
+ }
+
+ int32_t valueCount = 0;
+ RULE_HASH_STAT_INCREMENT(mElementsMatched);
+
+ if (mUniversalRules.Length() != 0) { // universal rules
+ mEnumList[valueCount++] = ToEnumData(mUniversalRules);
+ RULE_HASH_STAT_INCREMENT_LIST_COUNT(mUniversalRules, mElementUniversalCalls);
+ }
+ // universal rules within the namespace
+ if (kNameSpaceID_Unknown != nameSpace && mNameSpaceTable.EntryCount() > 0) {
+ auto entry = static_cast<RuleHashTableEntry*>
+ (mNameSpaceTable.Search(NS_INT32_TO_PTR(nameSpace)));
+ if (entry) {
+ mEnumList[valueCount++] = ToEnumData(entry->mRules);
+ RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementNameSpaceCalls);
+ }
+ }
+ if (mTagTable.EntryCount() > 0) {
+ auto entry = static_cast<RuleHashTableEntry*>(mTagTable.Search(tag));
+ if (entry) {
+ mEnumList[valueCount++] = ToEnumData(entry->mRules);
+ RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementTagCalls);
+ }
+ }
+ if (id && mIdTable.EntryCount() > 0) {
+ auto entry = static_cast<RuleHashTableEntry*>(mIdTable.Search(id));
+ if (entry) {
+ mEnumList[valueCount++] = ToEnumData(entry->mRules);
+ RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementIdCalls);
+ }
+ }
+ if (mClassTable.EntryCount() > 0) {
+ for (int32_t index = 0; index < classCount; ++index) {
+ auto entry = static_cast<RuleHashTableEntry*>
+ (mClassTable.Search(classList->AtomAt(index)));
+ if (entry) {
+ mEnumList[valueCount++] = ToEnumData(entry->mRules);
+ RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementClassCalls);
+ }
+ }
+ }
+ NS_ASSERTION(valueCount <= testCount, "values exceeded list size");
+
+ if (valueCount > 0) {
+ AncestorFilter *filter =
+ aData->mTreeMatchContext.mAncestorFilter.HasFilter() ?
+ &aData->mTreeMatchContext.mAncestorFilter : nullptr;
+#ifdef DEBUG
+ if (filter) {
+ filter->AssertHasAllAncestors(aElement);
+ }
+#endif
+ // Merge the lists while there are still multiple lists to merge.
+ while (valueCount > 1) {
+ int32_t valueIndex = 0;
+ int32_t lowestRuleIndex = mEnumList[valueIndex].mCurValue->mIndex;
+ for (int32_t index = 1; index < valueCount; ++index) {
+ int32_t ruleIndex = mEnumList[index].mCurValue->mIndex;
+ if (ruleIndex < lowestRuleIndex) {
+ valueIndex = index;
+ lowestRuleIndex = ruleIndex;
+ }
+ }
+ const RuleValue *cur = mEnumList[valueIndex].mCurValue;
+ ContentEnumFunc(*cur, cur->mSelector, aData, aNodeContext, filter);
+ cur++;
+ if (cur == mEnumList[valueIndex].mEnd) {
+ mEnumList[valueIndex] = mEnumList[--valueCount];
+ } else {
+ mEnumList[valueIndex].mCurValue = cur;
+ }
+ }
+
+ // Fast loop over single value.
+ for (const RuleValue *value = mEnumList[0].mCurValue,
+ *end = mEnumList[0].mEnd;
+ value != end; ++value) {
+ ContentEnumFunc(*value, value->mSelector, aData, aNodeContext, filter);
+ }
+ }
+}
+
+static size_t
+SizeOfRuleHashTable(const PLDHashTable& aTable, MallocSizeOf aMallocSizeOf)
+{
+ size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<RuleHashTableEntry*>(iter.Get());
+ n += entry->mRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+size_t
+RuleHash::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+
+ n += SizeOfRuleHashTable(mIdTable, aMallocSizeOf);
+
+ n += SizeOfRuleHashTable(mClassTable, aMallocSizeOf);
+
+ n += SizeOfRuleHashTable(mTagTable, aMallocSizeOf);
+
+ n += SizeOfRuleHashTable(mNameSpaceTable, aMallocSizeOf);
+
+ n += mUniversalRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ return n;
+}
+
+size_t
+RuleHash::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+//--------------------------------
+
+/**
+ * A struct that stores an nsCSSSelector pointer along side a pointer to
+ * the rightmost nsCSSSelector in the selector. For example, for
+ *
+ * .main p > span
+ *
+ * if mSelector points to the |p| nsCSSSelector, mRightmostSelector would
+ * point to the |span| nsCSSSelector.
+ *
+ * Both mSelector and mRightmostSelector are always top-level selectors,
+ * i.e. they aren't selectors within a :not() or :-moz-any().
+ */
+struct SelectorPair
+{
+ SelectorPair(nsCSSSelector* aSelector, nsCSSSelector* aRightmostSelector)
+ : mSelector(aSelector), mRightmostSelector(aRightmostSelector)
+ {
+ MOZ_ASSERT(aSelector);
+ MOZ_ASSERT(mRightmostSelector);
+ }
+ SelectorPair(const SelectorPair& aOther) = default;
+ nsCSSSelector* const mSelector;
+ nsCSSSelector* const mRightmostSelector;
+};
+
+// A hash table mapping atoms to lists of selectors
+struct AtomSelectorEntry : public PLDHashEntryHdr {
+ nsIAtom *mAtom;
+ // Auto length 2, because a decent fraction of these arrays ends up
+ // with 2 elements, and each entry is cheap.
+ AutoTArray<SelectorPair, 2> mSelectors;
+};
+
+static void
+AtomSelector_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
+{
+ (static_cast<AtomSelectorEntry*>(hdr))->~AtomSelectorEntry();
+}
+
+static void
+AtomSelector_InitEntry(PLDHashEntryHdr *hdr, const void *key)
+{
+ AtomSelectorEntry *entry = static_cast<AtomSelectorEntry*>(hdr);
+ new (KnownNotNull, entry) AtomSelectorEntry();
+ entry->mAtom = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key));
+}
+
+static void
+AtomSelector_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from,
+ PLDHashEntryHdr *to)
+{
+ NS_PRECONDITION(from != to, "This is not going to work!");
+ AtomSelectorEntry *oldEntry =
+ const_cast<AtomSelectorEntry*>(static_cast<const AtomSelectorEntry*>(from));
+ auto* newEntry = new (KnownNotNull, to) AtomSelectorEntry();
+ newEntry->mAtom = oldEntry->mAtom;
+ newEntry->mSelectors.SwapElements(oldEntry->mSelectors);
+ oldEntry->~AtomSelectorEntry();
+}
+
+static bool
+AtomSelector_CIMatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ const AtomSelectorEntry *entry = static_cast<const AtomSelectorEntry*>(hdr);
+ return CIMatchAtoms(key, entry->mAtom);
+}
+
+// Case-sensitive ops.
+static const PLDHashTableOps AtomSelector_CSOps = {
+ PLDHashTable::HashVoidPtrKeyStub,
+ PLDHashTable::MatchEntryStub,
+ AtomSelector_MoveEntry,
+ AtomSelector_ClearEntry,
+ AtomSelector_InitEntry
+};
+
+// Case-insensitive ops.
+static const PLDHashTableOps AtomSelector_CIOps = {
+ RuleHash_CIHashKey,
+ AtomSelector_CIMatchEntry,
+ AtomSelector_MoveEntry,
+ AtomSelector_ClearEntry,
+ AtomSelector_InitEntry
+};
+
+//--------------------------------
+
+struct RuleCascadeData {
+ RuleCascadeData(nsIAtom *aMedium, bool aQuirksMode)
+ : mRuleHash(aQuirksMode),
+ mStateSelectors(),
+ mSelectorDocumentStates(0),
+ mClassSelectors(aQuirksMode ? &AtomSelector_CIOps
+ : &AtomSelector_CSOps,
+ sizeof(AtomSelectorEntry)),
+ mIdSelectors(aQuirksMode ? &AtomSelector_CIOps
+ : &AtomSelector_CSOps,
+ sizeof(AtomSelectorEntry)),
+ // mAttributeSelectors is matching on the attribute _name_, not the
+ // value, and we case-fold names at parse-time, so this is a
+ // case-sensitive match.
+ mAttributeSelectors(&AtomSelector_CSOps, sizeof(AtomSelectorEntry)),
+ mAnonBoxRules(&RuleHash_TagTable_Ops, sizeof(RuleHashTagTableEntry)),
+#ifdef MOZ_XUL
+ mXULTreeRules(&RuleHash_TagTable_Ops, sizeof(RuleHashTagTableEntry)),
+#endif
+ mKeyframesRuleTable(),
+ mCounterStyleRuleTable(),
+ mCacheKey(aMedium),
+ mNext(nullptr),
+ mQuirksMode(aQuirksMode)
+ {
+ memset(mPseudoElementRuleHashes, 0, sizeof(mPseudoElementRuleHashes));
+ }
+
+ ~RuleCascadeData()
+ {
+ for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) {
+ delete mPseudoElementRuleHashes[i];
+ }
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ RuleHash mRuleHash;
+ RuleHash* mPseudoElementRuleHashes[
+ static_cast<CSSPseudoElementTypeBase>(CSSPseudoElementType::Count)];
+ nsTArray<nsCSSRuleProcessor::StateSelector> mStateSelectors;
+ EventStates mSelectorDocumentStates;
+ PLDHashTable mClassSelectors;
+ PLDHashTable mIdSelectors;
+ nsTArray<nsCSSSelector*> mPossiblyNegatedClassSelectors;
+ nsTArray<nsCSSSelector*> mPossiblyNegatedIDSelectors;
+ PLDHashTable mAttributeSelectors;
+ PLDHashTable mAnonBoxRules;
+#ifdef MOZ_XUL
+ PLDHashTable mXULTreeRules;
+#endif
+
+ nsTArray<nsFontFaceRuleContainer> mFontFaceRules;
+ nsTArray<nsCSSKeyframesRule*> mKeyframesRules;
+ nsTArray<nsCSSFontFeatureValuesRule*> mFontFeatureValuesRules;
+ nsTArray<nsCSSPageRule*> mPageRules;
+ nsTArray<nsCSSCounterStyleRule*> mCounterStyleRules;
+
+ nsDataHashtable<nsStringHashKey, nsCSSKeyframesRule*> mKeyframesRuleTable;
+ nsDataHashtable<nsStringHashKey, nsCSSCounterStyleRule*> mCounterStyleRuleTable;
+
+ // Looks up or creates the appropriate list in |mAttributeSelectors|.
+ // Returns null only on allocation failure.
+ nsTArray<SelectorPair>* AttributeListFor(nsIAtom* aAttribute);
+
+ nsMediaQueryResultCacheKey mCacheKey;
+ RuleCascadeData* mNext; // for a different medium
+
+ const bool mQuirksMode;
+};
+
+static size_t
+SizeOfSelectorsHashTable(const PLDHashTable& aTable, MallocSizeOf aMallocSizeOf)
+{
+ size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<AtomSelectorEntry*>(iter.Get());
+ n += entry->mSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+size_t
+RuleCascadeData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ n += mRuleHash.SizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) {
+ if (mPseudoElementRuleHashes[i])
+ n += mPseudoElementRuleHashes[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ n += mStateSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ n += SizeOfSelectorsHashTable(mIdSelectors, aMallocSizeOf);
+ n += SizeOfSelectorsHashTable(mClassSelectors, aMallocSizeOf);
+
+ n += mPossiblyNegatedClassSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mPossiblyNegatedIDSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ n += SizeOfSelectorsHashTable(mAttributeSelectors, aMallocSizeOf);
+ n += SizeOfRuleHashTable(mAnonBoxRules, aMallocSizeOf);
+#ifdef MOZ_XUL
+ n += SizeOfRuleHashTable(mXULTreeRules, aMallocSizeOf);
+#endif
+
+ n += mFontFaceRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mKeyframesRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mFontFeatureValuesRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mPageRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mCounterStyleRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ n += mKeyframesRuleTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mKeyframesRuleTable.ConstIter(); !iter.Done(); iter.Next()) {
+ // We don't own the nsCSSKeyframesRule objects so we don't count them. We
+ // do care about the size of the keys' nsAString members' buffers though.
+ //
+ // Note that we depend on nsStringHashKey::GetKey() returning a reference,
+ // since otherwise aKey would be a copy of the string key and we would not
+ // be measuring the right object here.
+ n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+nsTArray<SelectorPair>*
+RuleCascadeData::AttributeListFor(nsIAtom* aAttribute)
+{
+ auto entry = static_cast<AtomSelectorEntry*>
+ (mAttributeSelectors.Add(aAttribute, fallible));
+ if (!entry)
+ return nullptr;
+ return &entry->mSelectors;
+}
+
+// -------------------------------
+// CSS Style rule processor implementation
+//
+
+nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets,
+ SheetType aSheetType,
+ Element* aScopeElement,
+ nsCSSRuleProcessor*
+ aPreviousCSSRuleProcessor,
+ bool aIsShared)
+ : nsCSSRuleProcessor(sheet_array_type(aSheets), aSheetType, aScopeElement,
+ aPreviousCSSRuleProcessor, aIsShared)
+{
+}
+
+nsCSSRuleProcessor::nsCSSRuleProcessor(sheet_array_type&& aSheets,
+ SheetType aSheetType,
+ Element* aScopeElement,
+ nsCSSRuleProcessor*
+ aPreviousCSSRuleProcessor,
+ bool aIsShared)
+ : mSheets(aSheets)
+ , mRuleCascades(nullptr)
+ , mPreviousCacheKey(aPreviousCSSRuleProcessor
+ ? aPreviousCSSRuleProcessor->CloneMQCacheKey()
+ : UniquePtr<nsMediaQueryResultCacheKey>())
+ , mLastPresContext(nullptr)
+ , mScopeElement(aScopeElement)
+ , mStyleSetRefCnt(0)
+ , mSheetType(aSheetType)
+ , mIsShared(aIsShared)
+ , mMustGatherDocumentRules(aIsShared)
+ , mInRuleProcessorCache(false)
+#ifdef DEBUG
+ , mDocumentRulesAndCacheKeyValid(false)
+#endif
+{
+ NS_ASSERTION(!!mScopeElement == (aSheetType == SheetType::ScopedDoc),
+ "aScopeElement must be specified iff aSheetType is "
+ "eScopedDocSheet");
+ for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) {
+ mSheets[i]->AddRuleProcessor(this);
+ }
+}
+
+nsCSSRuleProcessor::~nsCSSRuleProcessor()
+{
+ if (mInRuleProcessorCache) {
+ RuleProcessorCache::RemoveRuleProcessor(this);
+ }
+ MOZ_ASSERT(!mExpirationState.IsTracked());
+ MOZ_ASSERT(mStyleSetRefCnt == 0);
+ ClearSheets();
+ ClearRuleCascades();
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSRuleProcessor)
+ NS_INTERFACE_MAP_ENTRY(nsIStyleRuleProcessor)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSRuleProcessor)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSRuleProcessor)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsCSSRuleProcessor)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCSSRuleProcessor)
+ tmp->ClearSheets();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScopeElement)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCSSRuleProcessor)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSheets)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScopeElement)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void
+nsCSSRuleProcessor::ClearSheets()
+{
+ for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) {
+ mSheets[i]->DropRuleProcessor(this);
+ }
+ mSheets.Clear();
+}
+
+/* static */ void
+nsCSSRuleProcessor::Startup()
+{
+ Preferences::AddBoolVarCache(&gSupportVisitedPseudo, VISITED_PSEUDO_PREF,
+ true);
+}
+
+static bool
+InitSystemMetrics()
+{
+ NS_ASSERTION(!sSystemMetrics, "already initialized");
+
+ sSystemMetrics = new nsTArray< nsCOMPtr<nsIAtom> >;
+ NS_ENSURE_TRUE(sSystemMetrics, false);
+
+ /***************************************************************************
+ * ANY METRICS ADDED HERE SHOULD ALSO BE ADDED AS MEDIA QUERIES IN *
+ * nsMediaFeatures.cpp *
+ ***************************************************************************/
+
+ int32_t metricResult =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollArrowStyle);
+ if (metricResult & LookAndFeel::eScrollArrow_StartBackward) {
+ sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_backward);
+ }
+ if (metricResult & LookAndFeel::eScrollArrow_StartForward) {
+ sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_forward);
+ }
+ if (metricResult & LookAndFeel::eScrollArrow_EndBackward) {
+ sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_backward);
+ }
+ if (metricResult & LookAndFeel::eScrollArrow_EndForward) {
+ sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_forward);
+ }
+
+ metricResult =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollSliderStyle);
+ if (metricResult != LookAndFeel::eScrollThumbStyle_Normal) {
+ sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_thumb_proportional);
+ }
+
+ metricResult =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars);
+ if (metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::overlay_scrollbars);
+ }
+
+ metricResult =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_MenuBarDrag);
+ if (metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::menubar_drag);
+ }
+
+ nsresult rv =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsDefaultTheme, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_default_theme);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacGraphiteTheme, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::mac_graphite_theme);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacYosemiteTheme, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::mac_yosemite_theme);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_DWMCompositor, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_compositor);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsGlass, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_glass);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_ColorPickerAvailable, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::color_picker_available);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsClassic, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_classic);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_TouchEnabled, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::touch_enabled);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_SwipeAnimationEnabled,
+ &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::swipe_animation_enabled);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_PhysicalHomeButton,
+ &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::physical_home_button);
+ }
+
+#ifdef XP_WIN
+ if (NS_SUCCEEDED(
+ LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsThemeIdentifier,
+ &metricResult))) {
+ nsCSSRuleProcessor::SetWindowsThemeIdentifier(static_cast<uint8_t>(metricResult));
+ switch(metricResult) {
+ case LookAndFeel::eWindowsTheme_Aero:
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero);
+ break;
+ case LookAndFeel::eWindowsTheme_AeroLite:
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero_lite);
+ break;
+ case LookAndFeel::eWindowsTheme_LunaBlue:
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_blue);
+ break;
+ case LookAndFeel::eWindowsTheme_LunaOlive:
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_olive);
+ break;
+ case LookAndFeel::eWindowsTheme_LunaSilver:
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_silver);
+ break;
+ case LookAndFeel::eWindowsTheme_Royale:
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_royale);
+ break;
+ case LookAndFeel::eWindowsTheme_Zune:
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_zune);
+ break;
+ case LookAndFeel::eWindowsTheme_Generic:
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_generic);
+ break;
+ }
+ }
+#endif
+
+ return true;
+}
+
+/* static */ void
+nsCSSRuleProcessor::FreeSystemMetrics()
+{
+ delete sSystemMetrics;
+ sSystemMetrics = nullptr;
+}
+
+/* static */ void
+nsCSSRuleProcessor::Shutdown()
+{
+ FreeSystemMetrics();
+}
+
+/* static */ bool
+nsCSSRuleProcessor::HasSystemMetric(nsIAtom* aMetric)
+{
+ if (!sSystemMetrics && !InitSystemMetrics()) {
+ return false;
+ }
+ return sSystemMetrics->IndexOf(aMetric) != sSystemMetrics->NoIndex;
+}
+
+#ifdef XP_WIN
+/* static */ uint8_t
+nsCSSRuleProcessor::GetWindowsThemeIdentifier()
+{
+ if (!sSystemMetrics)
+ InitSystemMetrics();
+ return sWinThemeId;
+}
+#endif
+
+/* static */
+EventStates
+nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& aTreeMatchContext)
+{
+ EventStates state = aElement->StyleState();
+
+ // If we are not supposed to mark visited links as such, be sure to
+ // flip the bits appropriately. We want to do this here, rather
+ // than in GetContentStateForVisitedHandling, so that we don't
+ // expose that :visited support is disabled to the Web page.
+ if (state.HasState(NS_EVENT_STATE_VISITED) &&
+ (!gSupportVisitedPseudo ||
+ aElement->OwnerDoc()->IsBeingUsedAsImage() ||
+ aTreeMatchContext.mUsingPrivateBrowsing)) {
+ state &= ~NS_EVENT_STATE_VISITED;
+ state |= NS_EVENT_STATE_UNVISITED;
+ }
+ return state;
+}
+
+/* static */
+bool
+nsCSSRuleProcessor::IsLink(const Element* aElement)
+{
+ EventStates state = aElement->StyleState();
+ return state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED);
+}
+
+/* static */
+EventStates
+nsCSSRuleProcessor::GetContentStateForVisitedHandling(
+ Element* aElement,
+ const TreeMatchContext& aTreeMatchContext,
+ nsRuleWalker::VisitedHandlingType aVisitedHandling,
+ bool aIsRelevantLink)
+{
+ EventStates contentState = GetContentState(aElement, aTreeMatchContext);
+ if (contentState.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)) {
+ MOZ_ASSERT(IsLink(aElement), "IsLink() should match state");
+ contentState &= ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED);
+ if (aIsRelevantLink) {
+ switch (aVisitedHandling) {
+ case nsRuleWalker::eRelevantLinkUnvisited:
+ contentState |= NS_EVENT_STATE_UNVISITED;
+ break;
+ case nsRuleWalker::eRelevantLinkVisited:
+ contentState |= NS_EVENT_STATE_VISITED;
+ break;
+ case nsRuleWalker::eLinksVisitedOrUnvisited:
+ contentState |= NS_EVENT_STATE_UNVISITED | NS_EVENT_STATE_VISITED;
+ break;
+ }
+ } else {
+ contentState |= NS_EVENT_STATE_UNVISITED;
+ }
+ }
+ return contentState;
+}
+
+/**
+ * A |NodeMatchContext| has data about matching a selector (without
+ * combinators) against a single node. It contains only input to the
+ * matching.
+ *
+ * Unlike |RuleProcessorData|, which is similar, a |NodeMatchContext|
+ * can vary depending on the selector matching process. In other words,
+ * there might be multiple NodeMatchContexts corresponding to a single
+ * node, but only one possible RuleProcessorData.
+ */
+struct NodeMatchContext {
+ // In order to implement nsCSSRuleProcessor::HasStateDependentStyle,
+ // we need to be able to see if a node might match an
+ // event-state-dependent selector for any value of that event state.
+ // So mStateMask contains the states that should NOT be tested.
+ //
+ // NOTE: For |mStateMask| to work correctly, it's important that any
+ // change that changes multiple state bits include all those state
+ // bits in the notification. Otherwise, if multiple states change but
+ // we do separate notifications then we might determine the style is
+ // not state-dependent when it really is (e.g., determining that a
+ // :hover:active rule no longer matches when both states are unset).
+ const EventStates mStateMask;
+
+ // Is this link the unique link whose visitedness can affect the style
+ // of the node being matched? (That link is the nearest link to the
+ // node being matched that is itself or an ancestor.)
+ //
+ // Always false when TreeMatchContext::mForStyling is false. (We
+ // could figure it out for SelectorListMatches, but we're starting
+ // from the middle of the selector list when doing
+ // Has{Attribute,State}DependentStyle, so we can't tell. So when
+ // mForStyling is false, we have to assume we don't know.)
+ const bool mIsRelevantLink;
+
+ NodeMatchContext(EventStates aStateMask, bool aIsRelevantLink)
+ : mStateMask(aStateMask)
+ , mIsRelevantLink(aIsRelevantLink)
+ {
+ }
+};
+
+/**
+ * Additional information about a selector (without combinators) that is
+ * being matched.
+ */
+enum class SelectorMatchesFlags : uint8_t {
+ NONE = 0,
+
+ // The selector's flags are unknown. This happens when you don't know
+ // if you're starting from the top of a selector. Only used in cases
+ // where it's acceptable for matching to return a false positive.
+ // (It's not OK to return a false negative.)
+ UNKNOWN = 1 << 0,
+
+ // The selector is part of a compound selector which has been split in
+ // half, where the other half is a pseudo-element. The current
+ // selector is not a pseudo-element itself.
+ HAS_PSEUDO_ELEMENT = 1 << 1,
+
+ // The selector is part of an argument to a functional pseudo-class or
+ // pseudo-element.
+ IS_PSEUDO_CLASS_ARGUMENT = 1 << 2
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SelectorMatchesFlags)
+
+// Return whether the selector matches conditions for the :active and
+// :hover quirk.
+static inline bool ActiveHoverQuirkMatches(nsCSSSelector* aSelector,
+ SelectorMatchesFlags aSelectorFlags)
+{
+ if (aSelector->HasTagSelector() || aSelector->mAttrList ||
+ aSelector->mIDList || aSelector->mClassList ||
+ aSelector->IsPseudoElement() ||
+ // Having this quirk means that some selectors will no longer match,
+ // so it's better to return false when we aren't sure (i.e., the
+ // flags are unknown).
+ aSelectorFlags & (SelectorMatchesFlags::UNKNOWN |
+ SelectorMatchesFlags::HAS_PSEUDO_ELEMENT |
+ SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) {
+ return false;
+ }
+
+ // No pseudo-class other than :active and :hover.
+ for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ if (pseudoClass->mType != CSSPseudoClassType::hover &&
+ pseudoClass->mType != CSSPseudoClassType::active) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+static inline bool
+IsSignificantChild(nsIContent* aChild, bool aTextIsSignificant,
+ bool aWhitespaceIsSignificant)
+{
+ return nsStyleUtil::IsSignificantChild(aChild, aTextIsSignificant,
+ aWhitespaceIsSignificant);
+}
+
+// This function is to be called once we have fetched a value for an attribute
+// whose namespace and name match those of aAttrSelector. This function
+// performs comparisons on the value only, based on aAttrSelector->mFunction.
+static bool AttrMatchesValue(const nsAttrSelector* aAttrSelector,
+ const nsString& aValue, bool isHTML)
+{
+ NS_PRECONDITION(aAttrSelector, "Must have an attribute selector");
+
+ // http://lists.w3.org/Archives/Public/www-style/2008Apr/0038.html
+ // *= (CONTAINSMATCH) ~= (INCLUDES) ^= (BEGINSMATCH) $= (ENDSMATCH)
+ // all accept the empty string, but match nothing.
+ if (aAttrSelector->mValue.IsEmpty() &&
+ (aAttrSelector->mFunction == NS_ATTR_FUNC_INCLUDES ||
+ aAttrSelector->mFunction == NS_ATTR_FUNC_ENDSMATCH ||
+ aAttrSelector->mFunction == NS_ATTR_FUNC_BEGINSMATCH ||
+ aAttrSelector->mFunction == NS_ATTR_FUNC_CONTAINSMATCH))
+ return false;
+
+ const nsDefaultStringComparator defaultComparator;
+ const nsASCIICaseInsensitiveStringComparator ciComparator;
+ const nsStringComparator& comparator =
+ aAttrSelector->IsValueCaseSensitive(isHTML)
+ ? static_cast<const nsStringComparator&>(defaultComparator)
+ : static_cast<const nsStringComparator&>(ciComparator);
+
+ switch (aAttrSelector->mFunction) {
+ case NS_ATTR_FUNC_EQUALS:
+ return aValue.Equals(aAttrSelector->mValue, comparator);
+ case NS_ATTR_FUNC_INCLUDES:
+ return nsStyleUtil::ValueIncludes(aValue, aAttrSelector->mValue, comparator);
+ case NS_ATTR_FUNC_DASHMATCH:
+ return nsStyleUtil::DashMatchCompare(aValue, aAttrSelector->mValue, comparator);
+ case NS_ATTR_FUNC_ENDSMATCH:
+ return StringEndsWith(aValue, aAttrSelector->mValue, comparator);
+ case NS_ATTR_FUNC_BEGINSMATCH:
+ return StringBeginsWith(aValue, aAttrSelector->mValue, comparator);
+ case NS_ATTR_FUNC_CONTAINSMATCH:
+ return FindInReadable(aAttrSelector->mValue, aValue, comparator);
+ default:
+ NS_NOTREACHED("Shouldn't be ending up here");
+ return false;
+ }
+}
+
+static inline bool
+edgeChildMatches(Element* aElement, TreeMatchContext& aTreeMatchContext,
+ bool checkFirst, bool checkLast)
+{
+ nsIContent* parent = aElement->GetParent();
+ if (parent && aTreeMatchContext.mForStyling)
+ parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR);
+
+ return (!checkFirst ||
+ aTreeMatchContext.mNthIndexCache.
+ GetNthIndex(aElement, false, false, true) == 1) &&
+ (!checkLast ||
+ aTreeMatchContext.mNthIndexCache.
+ GetNthIndex(aElement, false, true, true) == 1);
+}
+
+static inline bool
+nthChildGenericMatches(Element* aElement,
+ TreeMatchContext& aTreeMatchContext,
+ nsPseudoClassList* pseudoClass,
+ bool isOfType, bool isFromEnd)
+{
+ nsIContent* parent = aElement->GetParent();
+ if (parent && aTreeMatchContext.mForStyling) {
+ if (isFromEnd)
+ parent->SetFlags(NODE_HAS_SLOW_SELECTOR);
+ else
+ parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
+ }
+
+ const int32_t index = aTreeMatchContext.mNthIndexCache.
+ GetNthIndex(aElement, isOfType, isFromEnd, false);
+ if (index <= 0) {
+ // Node is anonymous content (not really a child of its parent).
+ return false;
+ }
+
+ const int32_t a = pseudoClass->u.mNumbers[0];
+ const int32_t b = pseudoClass->u.mNumbers[1];
+ // result should be true if there exists n >= 0 such that
+ // a * n + b == index.
+ if (a == 0) {
+ return b == index;
+ }
+
+ // Integer division in C does truncation (towards 0). So
+ // check that the result is nonnegative, and that there was no
+ // truncation.
+ const CheckedInt<int32_t> indexMinusB = CheckedInt<int32_t>(index) - b;
+ const CheckedInt<int32_t> n = indexMinusB / a;
+ return n.isValid() &&
+ n.value() >= 0 &&
+ a * n == indexMinusB;
+}
+
+static inline bool
+edgeOfTypeMatches(Element* aElement, TreeMatchContext& aTreeMatchContext,
+ bool checkFirst, bool checkLast)
+{
+ nsIContent *parent = aElement->GetParent();
+ if (parent && aTreeMatchContext.mForStyling) {
+ if (checkLast)
+ parent->SetFlags(NODE_HAS_SLOW_SELECTOR);
+ else
+ parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
+ }
+
+ return (!checkFirst ||
+ aTreeMatchContext.mNthIndexCache.
+ GetNthIndex(aElement, true, false, true) == 1) &&
+ (!checkLast ||
+ aTreeMatchContext.mNthIndexCache.
+ GetNthIndex(aElement, true, true, true) == 1);
+}
+
+static inline bool
+checkGenericEmptyMatches(Element* aElement,
+ TreeMatchContext& aTreeMatchContext,
+ bool isWhitespaceSignificant)
+{
+ nsIContent *child = nullptr;
+ int32_t index = -1;
+
+ if (aTreeMatchContext.mForStyling)
+ aElement->SetFlags(NODE_HAS_EMPTY_SELECTOR);
+
+ do {
+ child = aElement->GetChildAt(++index);
+ // stop at first non-comment (and non-whitespace for
+ // :-moz-only-whitespace) node
+ } while (child && !IsSignificantChild(child, true, isWhitespaceSignificant));
+ return (child == nullptr);
+}
+
+// Arrays of the states that are relevant for various pseudoclasses.
+static const EventStates sPseudoClassStateDependences[] = {
+#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) \
+ EventStates(),
+#define CSS_STATE_DEPENDENT_PSEUDO_CLASS(_name, _value, _flags, _pref, _states) \
+ _states,
+#include "nsCSSPseudoClassList.h"
+#undef CSS_STATE_DEPENDENT_PSEUDO_CLASS
+#undef CSS_PSEUDO_CLASS
+ // Add more entries for our fake values to make sure we can't
+ // index out of bounds into this array no matter what.
+ EventStates(),
+ EventStates()
+};
+
+static const EventStates sPseudoClassStates[] = {
+#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) \
+ EventStates(),
+#define CSS_STATE_PSEUDO_CLASS(_name, _value, _flags, _pref, _states) \
+ _states,
+#include "nsCSSPseudoClassList.h"
+#undef CSS_STATE_PSEUDO_CLASS
+#undef CSS_PSEUDO_CLASS
+ // Add more entries for our fake values to make sure we can't
+ // index out of bounds into this array no matter what.
+ EventStates(),
+ EventStates()
+};
+static_assert(MOZ_ARRAY_LENGTH(sPseudoClassStates) ==
+ static_cast<size_t>(CSSPseudoClassType::MAX),
+ "CSSPseudoClassType::MAX is no longer equal to the length of "
+ "sPseudoClassStates");
+
+static bool
+StateSelectorMatches(Element* aElement,
+ nsCSSSelector* aSelector,
+ NodeMatchContext& aNodeMatchContext,
+ TreeMatchContext& aTreeMatchContext,
+ SelectorMatchesFlags aSelectorFlags,
+ bool* const aDependence,
+ EventStates aStatesToCheck)
+{
+ NS_PRECONDITION(!aStatesToCheck.IsEmpty(),
+ "should only need to call StateSelectorMatches if "
+ "aStatesToCheck is not empty");
+
+ // Bit-based pseudo-classes
+ if (aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE |
+ NS_EVENT_STATE_HOVER) &&
+ aTreeMatchContext.mCompatMode == eCompatibility_NavQuirks &&
+ ActiveHoverQuirkMatches(aSelector, aSelectorFlags) &&
+ aElement->IsHTMLElement() && !nsCSSRuleProcessor::IsLink(aElement)) {
+ // In quirks mode, only make links sensitive to selectors ":active"
+ // and ":hover".
+ return false;
+ }
+
+ if (aTreeMatchContext.mForStyling &&
+ aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER)) {
+ // Mark the element as having :hover-dependent style
+ aElement->SetHasRelevantHoverRules();
+ }
+
+ if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(aStatesToCheck)) {
+ if (aDependence) {
+ *aDependence = true;
+ }
+ } else {
+ EventStates contentState =
+ nsCSSRuleProcessor::GetContentStateForVisitedHandling(
+ aElement,
+ aTreeMatchContext,
+ aTreeMatchContext.VisitedHandling(),
+ aNodeMatchContext.mIsRelevantLink);
+ if (!contentState.HasAtLeastOneOfStates(aStatesToCheck)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+StateSelectorMatches(Element* aElement,
+ nsCSSSelector* aSelector,
+ NodeMatchContext& aNodeMatchContext,
+ TreeMatchContext& aTreeMatchContext,
+ SelectorMatchesFlags aSelectorFlags)
+{
+ for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ auto idx = static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType);
+ EventStates statesToCheck = sPseudoClassStates[idx];
+ if (!statesToCheck.IsEmpty() &&
+ !StateSelectorMatches(aElement, aSelector, aNodeMatchContext,
+ aTreeMatchContext, aSelectorFlags, nullptr,
+ statesToCheck)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// |aDependence| has two functions:
+// * when non-null, it indicates that we're processing a negation,
+// which is done only when SelectorMatches calls itself recursively
+// * what it points to should be set to true whenever a test is skipped
+// because of aNodeMatchContent.mStateMask
+static bool SelectorMatches(Element* aElement,
+ nsCSSSelector* aSelector,
+ NodeMatchContext& aNodeMatchContext,
+ TreeMatchContext& aTreeMatchContext,
+ SelectorMatchesFlags aSelectorFlags,
+ bool* const aDependence = nullptr)
+{
+ NS_PRECONDITION(!aSelector->IsPseudoElement(),
+ "Pseudo-element snuck into SelectorMatches?");
+ MOZ_ASSERT(aTreeMatchContext.mForStyling ||
+ !aNodeMatchContext.mIsRelevantLink,
+ "mIsRelevantLink should be set to false when mForStyling "
+ "is false since we don't know how to set it correctly in "
+ "Has(Attribute|State)DependentStyle");
+
+ // namespace/tag match
+ // optimization : bail out early if we can
+ if ((kNameSpaceID_Unknown != aSelector->mNameSpace &&
+ aElement->GetNameSpaceID() != aSelector->mNameSpace))
+ return false;
+
+ if (aSelector->mLowercaseTag) {
+ nsIAtom* selectorTag =
+ (aTreeMatchContext.mIsHTMLDocument && aElement->IsHTMLElement()) ?
+ aSelector->mLowercaseTag : aSelector->mCasedTag;
+ if (selectorTag != aElement->NodeInfo()->NameAtom()) {
+ return false;
+ }
+ }
+
+ nsAtomList* IDList = aSelector->mIDList;
+ if (IDList) {
+ nsIAtom* id = aElement->GetID();
+ if (id) {
+ // case sensitivity: bug 93371
+ const bool isCaseSensitive =
+ aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks;
+
+ if (isCaseSensitive) {
+ do {
+ if (IDList->mAtom != id) {
+ return false;
+ }
+ IDList = IDList->mNext;
+ } while (IDList);
+ } else {
+ // Use EqualsIgnoreASCIICase instead of full on unicode case conversion
+ // in order to save on performance. This is only used in quirks mode
+ // anyway.
+ nsDependentAtomString id1Str(id);
+ do {
+ if (!nsContentUtils::EqualsIgnoreASCIICase(id1Str,
+ nsDependentAtomString(IDList->mAtom))) {
+ return false;
+ }
+ IDList = IDList->mNext;
+ } while (IDList);
+ }
+ } else {
+ // Element has no id but we have an id selector
+ return false;
+ }
+ }
+
+ nsAtomList* classList = aSelector->mClassList;
+ if (classList) {
+ // test for class match
+ const nsAttrValue *elementClasses = aElement->GetClasses();
+ if (!elementClasses) {
+ // Element has no classes but we have a class selector
+ return false;
+ }
+
+ // case sensitivity: bug 93371
+ const bool isCaseSensitive =
+ aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks;
+
+ while (classList) {
+ if (!elementClasses->Contains(classList->mAtom,
+ isCaseSensitive ?
+ eCaseMatters : eIgnoreCase)) {
+ return false;
+ }
+ classList = classList->mNext;
+ }
+ }
+
+ const bool isNegated = (aDependence != nullptr);
+ // The selectors for which we set node bits are, unfortunately, early
+ // in this function (because they're pseudo-classes, which are
+ // generally quick to test, and thus earlier). If they were later,
+ // we'd probably avoid setting those bits in more cases where setting
+ // them is unnecessary.
+ NS_ASSERTION(aNodeMatchContext.mStateMask.IsEmpty() ||
+ !aTreeMatchContext.mForStyling,
+ "mForStyling must be false if we're just testing for "
+ "state-dependence");
+
+ // test for pseudo class match
+ for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ auto idx = static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType);
+ EventStates statesToCheck = sPseudoClassStates[idx];
+ if (statesToCheck.IsEmpty()) {
+ // keep the cases here in the same order as the list in
+ // nsCSSPseudoClassList.h
+ switch (pseudoClass->mType) {
+ case CSSPseudoClassType::empty:
+ if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, true)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::mozOnlyWhitespace:
+ if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, false)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::mozEmptyExceptChildrenWithLocalname:
+ {
+ NS_ASSERTION(pseudoClass->u.mString, "Must have string!");
+ nsIContent *child = nullptr;
+ int32_t index = -1;
+
+ if (aTreeMatchContext.mForStyling)
+ // FIXME: This isn't sufficient to handle:
+ // :-moz-empty-except-children-with-localname() + E
+ // :-moz-empty-except-children-with-localname() ~ E
+ // because we don't know to restyle the grandparent of the
+ // inserted/removed element (as in bug 534804 for :empty).
+ aElement->SetFlags(NODE_HAS_SLOW_SELECTOR);
+ do {
+ child = aElement->GetChildAt(++index);
+ } while (child &&
+ (!IsSignificantChild(child, true, false) ||
+ (child->GetNameSpaceID() == aElement->GetNameSpaceID() &&
+ child->NodeInfo()->NameAtom()->Equals(nsDependentString(pseudoClass->u.mString)))));
+ if (child != nullptr) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::lang:
+ {
+ NS_ASSERTION(nullptr != pseudoClass->u.mString, "null lang parameter");
+ if (!pseudoClass->u.mString || !*pseudoClass->u.mString) {
+ 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.
+ nsAutoString language;
+ if (aElement->GetLang(language)) {
+ if (!nsStyleUtil::DashMatchCompare(language,
+ nsDependentString(pseudoClass->u.mString),
+ nsASCIICaseInsensitiveStringComparator())) {
+ return false;
+ }
+ // This pseudo-class matched; move on to the next thing
+ break;
+ }
+
+ nsIDocument* doc = aTreeMatchContext.mDocument;
+ if (doc) {
+ // 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.
+ doc->GetContentLanguage(language);
+
+ nsDependentString langString(pseudoClass->u.mString);
+ language.StripWhitespace();
+ int32_t begin = 0;
+ int32_t len = language.Length();
+ while (begin < len) {
+ int32_t end = language.FindChar(char16_t(','), begin);
+ if (end == kNotFound) {
+ end = len;
+ }
+ if (nsStyleUtil::DashMatchCompare(Substring(language, begin,
+ end-begin),
+ langString,
+ nsASCIICaseInsensitiveStringComparator())) {
+ break;
+ }
+ begin = end + 1;
+ }
+ if (begin < len) {
+ // This pseudo-class matched
+ break;
+ }
+ }
+ }
+ return false;
+
+ case CSSPseudoClassType::mozBoundElement:
+ if (aTreeMatchContext.mScopedRoot != aElement) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::root:
+ if (aElement != aElement->OwnerDoc()->GetRootElement()) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::any:
+ {
+ nsCSSSelectorList *l;
+ for (l = pseudoClass->u.mSelectors; l; l = l->mNext) {
+ nsCSSSelector *s = l->mSelectors;
+ MOZ_ASSERT(!s->mNext && !s->IsPseudoElement(),
+ "parser failed");
+ if (SelectorMatches(
+ aElement, s, aNodeMatchContext, aTreeMatchContext,
+ SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) {
+ break;
+ }
+ }
+ if (!l) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::firstChild:
+ if (!edgeChildMatches(aElement, aTreeMatchContext, true, false)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::firstNode:
+ {
+ nsIContent *firstNode = nullptr;
+ nsIContent *parent = aElement->GetParent();
+ if (parent) {
+ if (aTreeMatchContext.mForStyling)
+ parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR);
+
+ int32_t index = -1;
+ do {
+ firstNode = parent->GetChildAt(++index);
+ // stop at first non-comment and non-whitespace node
+ } while (firstNode &&
+ !IsSignificantChild(firstNode, true, false));
+ }
+ if (aElement != firstNode) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::lastChild:
+ if (!edgeChildMatches(aElement, aTreeMatchContext, false, true)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::lastNode:
+ {
+ nsIContent *lastNode = nullptr;
+ nsIContent *parent = aElement->GetParent();
+ if (parent) {
+ if (aTreeMatchContext.mForStyling)
+ parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR);
+
+ uint32_t index = parent->GetChildCount();
+ do {
+ lastNode = parent->GetChildAt(--index);
+ // stop at first non-comment and non-whitespace node
+ } while (lastNode &&
+ !IsSignificantChild(lastNode, true, false));
+ }
+ if (aElement != lastNode) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::onlyChild:
+ if (!edgeChildMatches(aElement, aTreeMatchContext, true, true)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::firstOfType:
+ if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, false)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::lastOfType:
+ if (!edgeOfTypeMatches(aElement, aTreeMatchContext, false, true)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::onlyOfType:
+ if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, true)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::nthChild:
+ if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass,
+ false, false)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::nthLastChild:
+ if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass,
+ false, true)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::nthOfType:
+ if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass,
+ true, false)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::nthLastOfType:
+ if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass,
+ true, true)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::mozIsHTML:
+ if (!aTreeMatchContext.mIsHTMLDocument || !aElement->IsHTMLElement()) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::mozNativeAnonymous:
+ if (!aElement->IsInNativeAnonymousSubtree()) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::mozSystemMetric:
+ {
+ nsCOMPtr<nsIAtom> metric = NS_Atomize(pseudoClass->u.mString);
+ if (!nsCSSRuleProcessor::HasSystemMetric(metric)) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::mozLocaleDir:
+ {
+ bool docIsRTL =
+ aTreeMatchContext.mDocument->GetDocumentState().
+ HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
+
+ nsDependentString dirString(pseudoClass->u.mString);
+
+ if (dirString.EqualsLiteral("rtl")) {
+ if (!docIsRTL) {
+ return false;
+ }
+ } else if (dirString.EqualsLiteral("ltr")) {
+ if (docIsRTL) {
+ return false;
+ }
+ } else {
+ // Selectors specifying other directions never match.
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::mozLWTheme:
+ {
+ if (aTreeMatchContext.mDocument->GetDocumentLWTheme() <=
+ nsIDocument::Doc_Theme_None) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::mozLWThemeBrightText:
+ {
+ if (aTreeMatchContext.mDocument->GetDocumentLWTheme() !=
+ nsIDocument::Doc_Theme_Bright) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::mozLWThemeDarkText:
+ {
+ if (aTreeMatchContext.mDocument->GetDocumentLWTheme() !=
+ nsIDocument::Doc_Theme_Dark) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::mozWindowInactive:
+ if (!aTreeMatchContext.mDocument->GetDocumentState().
+ HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
+ return false;
+ }
+ break;
+
+ case CSSPseudoClassType::mozTableBorderNonzero:
+ {
+ if (!aElement->IsHTMLElement(nsGkAtoms::table)) {
+ return false;
+ }
+ const nsAttrValue *val = aElement->GetParsedAttr(nsGkAtoms::border);
+ if (!val ||
+ (val->Type() == nsAttrValue::eInteger &&
+ val->GetIntegerValue() == 0)) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::mozBrowserFrame:
+ {
+ nsCOMPtr<nsIMozBrowserFrame>
+ browserFrame = do_QueryInterface(aElement);
+ if (!browserFrame ||
+ !browserFrame->GetReallyIsBrowserOrApp()) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::mozDir:
+ case CSSPseudoClassType::dir:
+ {
+ if (aDependence) {
+ EventStates states = sPseudoClassStateDependences[
+ static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType)];
+ if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(states)) {
+ *aDependence = true;
+ return false;
+ }
+ }
+
+ // If we only had to consider HTML, directionality would be
+ // exclusively LTR or RTL.
+ //
+ // However, in markup languages where there is no direction attribute
+ // we have to consider the possibility that neither dir(rtl) nor
+ // dir(ltr) matches.
+ EventStates state = aElement->StyleState();
+ nsDependentString dirString(pseudoClass->u.mString);
+
+ if (dirString.EqualsLiteral("rtl")) {
+ if (!state.HasState(NS_EVENT_STATE_RTL)) {
+ return false;
+ }
+ } else if (dirString.EqualsLiteral("ltr")) {
+ if (!state.HasState(NS_EVENT_STATE_LTR)) {
+ return false;
+ }
+ } else {
+ // Selectors specifying other directions never match.
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::scope:
+ if (aTreeMatchContext.mForScopedStyle) {
+ if (aTreeMatchContext.mCurrentStyleScope) {
+ // If mCurrentStyleScope is null, aElement must be the style
+ // scope root. This is because the PopStyleScopeForSelectorMatching
+ // call in SelectorMatchesTree sets mCurrentStyleScope to null
+ // as soon as we visit the style scope element, and we won't
+ // progress further up the tree after this call to
+ // SelectorMatches. Thus if mCurrentStyleScope is still set,
+ // we know the selector does not match.
+ return false;
+ }
+ } else if (aTreeMatchContext.HasSpecifiedScope()) {
+ if (!aTreeMatchContext.IsScopeElement(aElement)) {
+ return false;
+ }
+ } else {
+ if (aElement != aElement->OwnerDoc()->GetRootElement()) {
+ return false;
+ }
+ }
+ break;
+
+ default:
+ MOZ_ASSERT(false, "How did that happen?");
+ }
+ } else {
+ if (!StateSelectorMatches(aElement, aSelector, aNodeMatchContext,
+ aTreeMatchContext, aSelectorFlags, aDependence,
+ statesToCheck)) {
+ return false;
+ }
+ }
+ }
+
+ bool result = true;
+ if (aSelector->mAttrList) {
+ // test for attribute match
+ if (!aElement->HasAttrs()) {
+ // if no attributes on the content, no match
+ return false;
+ } else {
+ result = true;
+ nsAttrSelector* attr = aSelector->mAttrList;
+ nsIAtom* matchAttribute;
+
+ do {
+ bool isHTML =
+ (aTreeMatchContext.mIsHTMLDocument && aElement->IsHTMLElement());
+ matchAttribute = isHTML ? attr->mLowercaseAttr : attr->mCasedAttr;
+ if (attr->mNameSpace == kNameSpaceID_Unknown) {
+ // Attr selector with a wildcard namespace. We have to examine all
+ // the attributes on our content node.... This sort of selector is
+ // essentially a boolean OR, over all namespaces, of equivalent attr
+ // selectors with those namespaces. So to evaluate whether it
+ // matches, evaluate for each namespace (the only namespaces that
+ // have a chance at matching, of course, are ones that the element
+ // actually has attributes in), short-circuiting if we ever match.
+ result = false;
+ const nsAttrName* attrName;
+ for (uint32_t i = 0; (attrName = aElement->GetAttrNameAt(i)); ++i) {
+ if (attrName->LocalName() != matchAttribute) {
+ continue;
+ }
+ if (attr->mFunction == NS_ATTR_FUNC_SET) {
+ result = true;
+ } else {
+ nsAutoString value;
+#ifdef DEBUG
+ bool hasAttr =
+#endif
+ aElement->GetAttr(attrName->NamespaceID(),
+ attrName->LocalName(), value);
+ NS_ASSERTION(hasAttr, "GetAttrNameAt lied");
+ result = AttrMatchesValue(attr, value, isHTML);
+ }
+
+ // At this point |result| has been set by us
+ // explicitly in this loop. If it's false, we may still match
+ // -- the content may have another attribute with the same name but
+ // in a different namespace. But if it's true, we are done (we
+ // can short-circuit the boolean OR described above).
+ if (result) {
+ break;
+ }
+ }
+ }
+ else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) {
+ result =
+ aElement->
+ AttrValueIs(attr->mNameSpace, matchAttribute, attr->mValue,
+ attr->IsValueCaseSensitive(isHTML) ? eCaseMatters
+ : eIgnoreCase);
+ }
+ else if (!aElement->HasAttr(attr->mNameSpace, matchAttribute)) {
+ result = false;
+ }
+ else if (attr->mFunction != NS_ATTR_FUNC_SET) {
+ nsAutoString value;
+#ifdef DEBUG
+ bool hasAttr =
+#endif
+ aElement->GetAttr(attr->mNameSpace, matchAttribute, value);
+ NS_ASSERTION(hasAttr, "HasAttr lied");
+ result = AttrMatchesValue(attr, value, isHTML);
+ }
+
+ attr = attr->mNext;
+ } while (attr && result);
+ }
+ }
+
+ // apply SelectorMatches to the negated selectors in the chain
+ if (!isNegated) {
+ for (nsCSSSelector *negation = aSelector->mNegations;
+ result && negation; negation = negation->mNegations) {
+ bool dependence = false;
+ result = !SelectorMatches(aElement, negation, aNodeMatchContext,
+ aTreeMatchContext,
+ SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT,
+ &dependence);
+ // If the selector does match due to the dependence on
+ // aNodeMatchContext.mStateMask, then we want to keep result true
+ // so that the final result of SelectorMatches is true. Doing so
+ // tells StateEnumFunc that there is a dependence on the state.
+ result = result || dependence;
+ }
+ }
+ return result;
+}
+
+#undef STATE_CHECK
+
+#ifdef DEBUG
+static bool
+HasPseudoClassSelectorArgsWithCombinators(nsCSSSelector* aSelector)
+{
+ for (nsPseudoClassList* p = aSelector->mPseudoClassList; p; p = p->mNext) {
+ if (nsCSSPseudoClasses::HasSelectorListArg(p->mType)) {
+ for (nsCSSSelectorList* l = p->u.mSelectors; l; l = l->mNext) {
+ if (l->mSelectors->mNext) {
+ return true;
+ }
+ }
+ }
+ }
+ for (nsCSSSelector* n = aSelector->mNegations; n; n = n->mNegations) {
+ if (n->mNext) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+/* static */ bool
+nsCSSRuleProcessor::RestrictedSelectorMatches(
+ Element* aElement,
+ nsCSSSelector* aSelector,
+ TreeMatchContext& aTreeMatchContext)
+{
+ MOZ_ASSERT(aSelector->IsRestrictedSelector(),
+ "aSelector must not have a pseudo-element");
+
+ NS_WARNING_ASSERTION(
+ !HasPseudoClassSelectorArgsWithCombinators(aSelector),
+ "processing eRestyle_SomeDescendants can be slow if pseudo-classes with "
+ "selector arguments can now have combinators in them");
+
+ // We match aSelector as if :visited and :link both match visited and
+ // unvisited links.
+
+ NodeMatchContext nodeContext(EventStates(),
+ nsCSSRuleProcessor::IsLink(aElement));
+ if (nodeContext.mIsRelevantLink) {
+ aTreeMatchContext.SetHaveRelevantLink();
+ }
+ aTreeMatchContext.ResetForUnvisitedMatching();
+ bool matches = SelectorMatches(aElement, aSelector, nodeContext,
+ aTreeMatchContext, SelectorMatchesFlags::NONE);
+ if (nodeContext.mIsRelevantLink) {
+ aTreeMatchContext.ResetForVisitedMatching();
+ if (SelectorMatches(aElement, aSelector, nodeContext, aTreeMatchContext,
+ SelectorMatchesFlags::NONE)) {
+ matches = true;
+ }
+ }
+ return matches;
+}
+
+// Right now, there are four operators:
+// ' ', the descendant combinator, is greedy
+// '~', the indirect adjacent sibling combinator, is greedy
+// '+' and '>', the direct adjacent sibling and child combinators, are not
+#define NS_IS_GREEDY_OPERATOR(ch) \
+ ((ch) == char16_t(' ') || (ch) == char16_t('~'))
+
+/**
+ * Flags for SelectorMatchesTree.
+ */
+enum SelectorMatchesTreeFlags {
+ // Whether we still have not found the closest ancestor link element and
+ // thus have to check the current element for it.
+ eLookForRelevantLink = 0x1,
+
+ // Whether SelectorMatchesTree should check for, and return true upon
+ // finding, an ancestor element that has an eRestyle_SomeDescendants
+ // restyle hint pending.
+ eMatchOnConditionalRestyleAncestor = 0x2,
+};
+
+static bool
+SelectorMatchesTree(Element* aPrevElement,
+ nsCSSSelector* aSelector,
+ TreeMatchContext& aTreeMatchContext,
+ SelectorMatchesTreeFlags aFlags)
+{
+ MOZ_ASSERT(!aSelector || !aSelector->IsPseudoElement());
+ nsCSSSelector* selector = aSelector;
+ Element* prevElement = aPrevElement;
+ while (selector) { // check compound selectors
+ NS_ASSERTION(!selector->mNext ||
+ selector->mNext->mOperator != char16_t(0),
+ "compound selector without combinator");
+
+ // If after the previous selector match we are now outside the
+ // current style scope, we don't need to match any further.
+ if (aTreeMatchContext.mForScopedStyle &&
+ !aTreeMatchContext.IsWithinStyleScopeForSelectorMatching()) {
+ return false;
+ }
+
+ // for adjacent sibling combinators, the content to test against the
+ // selector is the previous sibling *element*
+ Element* element = nullptr;
+ if (char16_t('+') == selector->mOperator ||
+ char16_t('~') == selector->mOperator) {
+ // The relevant link must be an ancestor of the node being matched.
+ aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink);
+ nsIContent* parent = prevElement->GetParent();
+ if (parent) {
+ if (aTreeMatchContext.mForStyling)
+ parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
+
+ element = prevElement->GetPreviousElementSibling();
+ }
+ }
+ // for descendant combinators and child combinators, the element
+ // to test against is the parent
+ else {
+ nsIContent *content = prevElement->GetParent();
+ // GetParent could return a document fragment; we only want
+ // element parents.
+ if (content && content->IsElement()) {
+ element = content->AsElement();
+ if (aTreeMatchContext.mForScopedStyle) {
+ // We are moving up to the parent element; tell the
+ // TreeMatchContext, so that in case this element is the
+ // style scope element, selector matching stops before we
+ // traverse further up the tree.
+ aTreeMatchContext.PopStyleScopeForSelectorMatching(element);
+ }
+
+ // Compatibility hack: First try matching this selector as though the
+ // <xbl:children> element wasn't in the tree to allow old selectors
+ // were written before <xbl:children> participated in CSS selector
+ // matching to work.
+ if (selector->mOperator == '>' && element->IsActiveChildrenElement()) {
+ Element* styleScope = aTreeMatchContext.mCurrentStyleScope;
+ if (SelectorMatchesTree(element, selector, aTreeMatchContext,
+ aFlags)) {
+ // It matched, don't try matching on the <xbl:children> element at
+ // all.
+ return true;
+ }
+ // We want to reset mCurrentStyleScope on aTreeMatchContext
+ // back to its state before the SelectorMatchesTree call, in
+ // case that call happens to traverse past the style scope element
+ // and sets it to null.
+ aTreeMatchContext.mCurrentStyleScope = styleScope;
+ }
+ }
+ }
+ if (!element) {
+ return false;
+ }
+ if ((aFlags & eMatchOnConditionalRestyleAncestor) &&
+ element->HasFlag(ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR)) {
+ // If we're looking at an element that we already generated an
+ // eRestyle_SomeDescendants restyle hint for, then we should pretend
+ // that we matched here, because we don't know what the values of
+ // attributes on |element| were at the time we generated the
+ // eRestyle_SomeDescendants. This causes AttributeEnumFunc and
+ // HasStateDependentStyle below to generate a restyle hint for the
+ // change we're currently looking at, as we don't know whether the LHS
+ // of the selector we looked up matches or not. (We only pass in aFlags
+ // to cause us to look for eRestyle_SomeDescendants here under
+ // AttributeEnumFunc and HasStateDependentStyle.)
+ return true;
+ }
+ const bool isRelevantLink = (aFlags & eLookForRelevantLink) &&
+ nsCSSRuleProcessor::IsLink(element);
+ NodeMatchContext nodeContext(EventStates(), isRelevantLink);
+ if (isRelevantLink) {
+ // If we find an ancestor of the matched node that is a link
+ // during the matching process, then it's the relevant link (see
+ // constructor call above).
+ // Since we are still matching against selectors that contain
+ // :visited (they'll just fail), we will always find such a node
+ // during the selector matching process if there is a relevant
+ // link that can influence selector matching.
+ aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink);
+ aTreeMatchContext.SetHaveRelevantLink();
+ }
+ if (SelectorMatches(element, selector, nodeContext, aTreeMatchContext,
+ SelectorMatchesFlags::NONE)) {
+ // to avoid greedy matching, we need to recur if this is a
+ // descendant or general sibling combinator and the next
+ // combinator is different, but we can make an exception for
+ // sibling, then parent, since a sibling's parent is always the
+ // same.
+ if (NS_IS_GREEDY_OPERATOR(selector->mOperator) &&
+ selector->mNext &&
+ selector->mNext->mOperator != selector->mOperator &&
+ !(selector->mOperator == '~' &&
+ NS_IS_ANCESTOR_OPERATOR(selector->mNext->mOperator))) {
+
+ // pretend the selector didn't match, and step through content
+ // while testing the same selector
+
+ // This approach is slightly strange in that when it recurs
+ // it tests from the top of the content tree, down. This
+ // doesn't matter much for performance since most selectors
+ // don't match. (If most did, it might be faster...)
+ Element* styleScope = aTreeMatchContext.mCurrentStyleScope;
+ if (SelectorMatchesTree(element, selector, aTreeMatchContext, aFlags)) {
+ return true;
+ }
+ // We want to reset mCurrentStyleScope on aTreeMatchContext
+ // back to its state before the SelectorMatchesTree call, in
+ // case that call happens to traverse past the style scope element
+ // and sets it to null.
+ aTreeMatchContext.mCurrentStyleScope = styleScope;
+ }
+ selector = selector->mNext;
+ }
+ else {
+ // for adjacent sibling and child combinators, if we didn't find
+ // a match, we're done
+ if (!NS_IS_GREEDY_OPERATOR(selector->mOperator)) {
+ return false; // parent was required to match
+ }
+ }
+ prevElement = element;
+ }
+ return true; // all the selectors matched.
+}
+
+static inline
+void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector,
+ ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext,
+ AncestorFilter *ancestorFilter)
+{
+ if (nodeContext.mIsRelevantLink) {
+ data->mTreeMatchContext.SetHaveRelevantLink();
+ }
+ if (ancestorFilter &&
+ !ancestorFilter->MightHaveMatchingAncestor<RuleValue::eMaxAncestorHashes>(
+ value.mAncestorSelectorHashes)) {
+ // We won't match; nothing else to do here
+ return;
+ }
+ if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement,
+ data->mScope)) {
+ // The selector is for a rule in a scoped style sheet, and the subject
+ // of the selector matching is not in its scope.
+ return;
+ }
+ nsCSSSelector* selector = aSelector;
+ if (selector->IsPseudoElement()) {
+ PseudoElementRuleProcessorData* pdata =
+ static_cast<PseudoElementRuleProcessorData*>(data);
+ if (!pdata->mPseudoElement && selector->mPseudoClassList) {
+ // We can get here when calling getComputedStyle(aElt, aPseudo) if:
+ //
+ // * aPseudo is a pseudo-element that supports a user action
+ // pseudo-class, like "::placeholder";
+ // * there is a style rule that uses a pseudo-class on this
+ // pseudo-element in the document, like ::placeholder:hover; and
+ // * aElt does not have such a pseudo-element.
+ //
+ // We know that the selector can't match, since there is no element for
+ // the user action pseudo-class to match against.
+ return;
+ }
+ if (!StateSelectorMatches(pdata->mPseudoElement, aSelector, nodeContext,
+ data->mTreeMatchContext,
+ SelectorMatchesFlags::NONE)) {
+ return;
+ }
+ selector = selector->mNext;
+ }
+
+ SelectorMatchesFlags selectorFlags = SelectorMatchesFlags::NONE;
+ if (aSelector->IsPseudoElement()) {
+ selectorFlags |= SelectorMatchesFlags::HAS_PSEUDO_ELEMENT;
+ }
+ if (SelectorMatches(data->mElement, selector, nodeContext,
+ data->mTreeMatchContext, selectorFlags)) {
+ nsCSSSelector *next = selector->mNext;
+ if (!next ||
+ SelectorMatchesTree(data->mElement, next,
+ data->mTreeMatchContext,
+ nodeContext.mIsRelevantLink ?
+ SelectorMatchesTreeFlags(0) :
+ eLookForRelevantLink)) {
+ css::Declaration* declaration = value.mRule->GetDeclaration();
+ declaration->SetImmutable();
+ data->mRuleWalker->Forward(declaration);
+ // nsStyleSet will deal with the !important rule
+ }
+ }
+}
+
+/* virtual */ void
+nsCSSRuleProcessor::RulesMatching(ElementRuleProcessorData *aData)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ if (cascade) {
+ NodeMatchContext nodeContext(EventStates(),
+ nsCSSRuleProcessor::IsLink(aData->mElement));
+ cascade->mRuleHash.EnumerateAllRules(aData->mElement, aData, nodeContext);
+ }
+}
+
+/* virtual */ void
+nsCSSRuleProcessor::RulesMatching(PseudoElementRuleProcessorData* aData)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ if (cascade) {
+ RuleHash* ruleHash = cascade->mPseudoElementRuleHashes[
+ static_cast<CSSPseudoElementTypeBase>(aData->mPseudoType)];
+ if (ruleHash) {
+ NodeMatchContext nodeContext(EventStates(),
+ nsCSSRuleProcessor::IsLink(aData->mElement));
+ ruleHash->EnumerateAllRules(aData->mElement, aData, nodeContext);
+ }
+ }
+}
+
+/* virtual */ void
+nsCSSRuleProcessor::RulesMatching(AnonBoxRuleProcessorData* aData)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ if (cascade && cascade->mAnonBoxRules.EntryCount()) {
+ auto entry = static_cast<RuleHashTagTableEntry*>
+ (cascade->mAnonBoxRules.Search(aData->mPseudoTag));
+ if (entry) {
+ nsTArray<RuleValue>& rules = entry->mRules;
+ for (RuleValue *value = rules.Elements(), *end = value + rules.Length();
+ value != end; ++value) {
+ css::Declaration* declaration = value->mRule->GetDeclaration();
+ declaration->SetImmutable();
+ aData->mRuleWalker->Forward(declaration);
+ }
+ }
+ }
+}
+
+#ifdef MOZ_XUL
+/* virtual */ void
+nsCSSRuleProcessor::RulesMatching(XULTreeRuleProcessorData* aData)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ if (cascade && cascade->mXULTreeRules.EntryCount()) {
+ auto entry = static_cast<RuleHashTagTableEntry*>
+ (cascade->mXULTreeRules.Search(aData->mPseudoTag));
+ if (entry) {
+ NodeMatchContext nodeContext(EventStates(),
+ nsCSSRuleProcessor::IsLink(aData->mElement));
+ nsTArray<RuleValue>& rules = entry->mRules;
+ for (RuleValue *value = rules.Elements(), *end = value + rules.Length();
+ value != end; ++value) {
+ if (aData->mComparator->PseudoMatches(value->mSelector)) {
+ ContentEnumFunc(*value, value->mSelector->mNext, aData, nodeContext,
+ nullptr);
+ }
+ }
+ }
+ }
+}
+#endif
+
+static inline nsRestyleHint RestyleHintForOp(char16_t oper)
+{
+ if (oper == char16_t('+') || oper == char16_t('~')) {
+ return eRestyle_LaterSiblings;
+ }
+
+ if (oper != char16_t(0)) {
+ return eRestyle_Subtree;
+ }
+
+ return eRestyle_Self;
+}
+
+nsRestyleHint
+nsCSSRuleProcessor::HasStateDependentStyle(ElementDependentRuleProcessorData* aData,
+ Element* aStatefulElement,
+ CSSPseudoElementType aPseudoType,
+ EventStates aStateMask)
+{
+ MOZ_ASSERT(!aData->mTreeMatchContext.mForScopedStyle,
+ "mCurrentStyleScope will need to be saved and restored after the "
+ "SelectorMatchesTree call");
+
+ bool isPseudoElement =
+ aPseudoType != CSSPseudoElementType::NotPseudo;
+
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ // Look up the content node in the state rule list, which points to
+ // any (CSS2 definition) simple selector (whether or not it is the
+ // subject) that has a state pseudo-class on it. This means that this
+ // code will be matching selectors that aren't real selectors in any
+ // stylesheet (e.g., if there is a selector "body > p:hover > a", then
+ // "body > p:hover" will be in |cascade->mStateSelectors|). Note that
+ // |ComputeSelectorStateDependence| below determines which selectors are in
+ // |cascade->mStateSelectors|.
+ nsRestyleHint hint = nsRestyleHint(0);
+ if (cascade) {
+ StateSelector *iter = cascade->mStateSelectors.Elements(),
+ *end = iter + cascade->mStateSelectors.Length();
+ NodeMatchContext nodeContext(aStateMask, false);
+ for(; iter != end; ++iter) {
+ nsCSSSelector* selector = iter->mSelector;
+ EventStates states = iter->mStates;
+
+ if (selector->IsPseudoElement() != isPseudoElement) {
+ continue;
+ }
+
+ nsCSSSelector* selectorForPseudo;
+ if (isPseudoElement) {
+ if (selector->PseudoType() != aPseudoType) {
+ continue;
+ }
+ selectorForPseudo = selector;
+ selector = selector->mNext;
+ }
+
+ nsRestyleHint possibleChange = RestyleHintForOp(selector->mOperator);
+ SelectorMatchesFlags selectorFlags = SelectorMatchesFlags::UNKNOWN;
+
+ // If hint already includes all the bits of possibleChange,
+ // don't bother calling SelectorMatches, since even if it returns false
+ // hint won't change.
+ // Also don't bother calling SelectorMatches if none of the
+ // states passed in are relevant here.
+ if ((possibleChange & ~hint) &&
+ states.HasAtLeastOneOfStates(aStateMask) &&
+ // We can optimize away testing selectors that only involve :hover, a
+ // namespace, and a tag name against nodes that don't have the
+ // NodeHasRelevantHoverRules flag: such a selector didn't match
+ // the tag name or namespace the first time around (since the :hover
+ // didn't set the NodeHasRelevantHoverRules flag), so it won't
+ // match it now. Check for our selector only having :hover states, or
+ // the element having the hover rules flag, or the selector having
+ // some sort of non-namespace, non-tagname data in it.
+ (states != NS_EVENT_STATE_HOVER ||
+ aStatefulElement->HasRelevantHoverRules() ||
+ selector->mIDList || selector->mClassList ||
+ // We generally expect an mPseudoClassList, since we have a :hover.
+ // The question is whether we have anything else in there.
+ (selector->mPseudoClassList &&
+ (selector->mPseudoClassList->mNext ||
+ selector->mPseudoClassList->mType !=
+ CSSPseudoClassType::hover)) ||
+ selector->mAttrList || selector->mNegations) &&
+ (!isPseudoElement ||
+ StateSelectorMatches(aStatefulElement, selectorForPseudo,
+ nodeContext, aData->mTreeMatchContext,
+ selectorFlags, nullptr, aStateMask)) &&
+ SelectorMatches(aData->mElement, selector, nodeContext,
+ aData->mTreeMatchContext, selectorFlags) &&
+ SelectorMatchesTree(aData->mElement, selector->mNext,
+ aData->mTreeMatchContext,
+ eMatchOnConditionalRestyleAncestor))
+ {
+ hint = nsRestyleHint(hint | possibleChange);
+ }
+ }
+ }
+ return hint;
+}
+
+nsRestyleHint
+nsCSSRuleProcessor::HasStateDependentStyle(StateRuleProcessorData* aData)
+{
+ return HasStateDependentStyle(aData,
+ aData->mElement,
+ CSSPseudoElementType::NotPseudo,
+ aData->mStateMask);
+}
+
+nsRestyleHint
+nsCSSRuleProcessor::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData)
+{
+ return HasStateDependentStyle(aData,
+ aData->mPseudoElement,
+ aData->mPseudoType,
+ aData->mStateMask);
+}
+
+bool
+nsCSSRuleProcessor::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ return cascade && cascade->mSelectorDocumentStates.HasAtLeastOneOfStates(aData->mStateMask);
+}
+
+struct AttributeEnumData {
+ AttributeEnumData(AttributeRuleProcessorData *aData,
+ RestyleHintData& aRestyleHintData)
+ : data(aData), change(nsRestyleHint(0)), hintData(aRestyleHintData) {}
+
+ AttributeRuleProcessorData *data;
+ nsRestyleHint change;
+ RestyleHintData& hintData;
+};
+
+
+static inline nsRestyleHint
+RestyleHintForSelectorWithAttributeChange(nsRestyleHint aCurrentHint,
+ nsCSSSelector* aSelector,
+ nsCSSSelector* aRightmostSelector)
+{
+ MOZ_ASSERT(aSelector);
+
+ char16_t oper = aSelector->mOperator;
+
+ if (oper == char16_t('+') || oper == char16_t('~')) {
+ return eRestyle_LaterSiblings;
+ }
+
+ if (oper == char16_t(':')) {
+ return eRestyle_Subtree;
+ }
+
+ if (oper != char16_t(0)) {
+ // Check whether the selector is in a form that supports
+ // eRestyle_SomeDescendants. If it isn't, return eRestyle_Subtree.
+
+ if (aCurrentHint & eRestyle_Subtree) {
+ // No point checking, since we'll end up restyling the whole
+ // subtree anyway.
+ return eRestyle_Subtree;
+ }
+
+ if (!aRightmostSelector) {
+ // aSelector wasn't a top-level selector, which means we were inside
+ // a :not() or :-moz-any(). We don't support that.
+ return eRestyle_Subtree;
+ }
+
+ MOZ_ASSERT(aSelector != aRightmostSelector,
+ "if aSelector == aRightmostSelector then we should have "
+ "no operator");
+
+ // Check that aRightmostSelector can be passed to RestrictedSelectorMatches.
+ if (!aRightmostSelector->IsRestrictedSelector()) {
+ return eRestyle_Subtree;
+ }
+
+ // We also don't support pseudo-elements on any of the selectors
+ // between aRightmostSelector and aSelector.
+ // XXX Can we lift this restriction, so that we don't have to loop
+ // over all the selectors?
+ for (nsCSSSelector* sel = aRightmostSelector->mNext;
+ sel != aSelector;
+ sel = sel->mNext) {
+ MOZ_ASSERT(sel, "aSelector must be reachable from aRightmostSelector");
+ if (sel->PseudoType() != CSSPseudoElementType::NotPseudo) {
+ return eRestyle_Subtree;
+ }
+ }
+
+ return eRestyle_SomeDescendants;
+ }
+
+ return eRestyle_Self;
+}
+
+static void
+AttributeEnumFunc(nsCSSSelector* aSelector,
+ nsCSSSelector* aRightmostSelector,
+ AttributeEnumData* aData)
+{
+ AttributeRuleProcessorData *data = aData->data;
+
+ if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement,
+ data->mScope)) {
+ // The selector is for a rule in a scoped style sheet, and the subject
+ // of the selector matching is not in its scope.
+ return;
+ }
+
+ nsRestyleHint possibleChange =
+ RestyleHintForSelectorWithAttributeChange(aData->change,
+ aSelector, aRightmostSelector);
+
+ // If, ignoring eRestyle_SomeDescendants, enumData->change already includes
+ // all the bits of possibleChange, don't bother calling SelectorMatches, since
+ // even if it returns false enumData->change won't change. If possibleChange
+ // has eRestyle_SomeDescendants, we need to call SelectorMatches(Tree)
+ // regardless as it might give us new selectors to append to
+ // mSelectorsForDescendants.
+ NodeMatchContext nodeContext(EventStates(), false);
+ if (((possibleChange & (~(aData->change) | eRestyle_SomeDescendants))) &&
+ SelectorMatches(data->mElement, aSelector, nodeContext,
+ data->mTreeMatchContext, SelectorMatchesFlags::UNKNOWN) &&
+ SelectorMatchesTree(data->mElement, aSelector->mNext,
+ data->mTreeMatchContext,
+ eMatchOnConditionalRestyleAncestor)) {
+ aData->change = nsRestyleHint(aData->change | possibleChange);
+ if (possibleChange & eRestyle_SomeDescendants) {
+ aData->hintData.mSelectorsForDescendants.AppendElement(aRightmostSelector);
+ }
+ }
+}
+
+static MOZ_ALWAYS_INLINE void
+EnumerateSelectors(nsTArray<SelectorPair>& aSelectors, AttributeEnumData* aData)
+{
+ SelectorPair *iter = aSelectors.Elements(),
+ *end = iter + aSelectors.Length();
+ for (; iter != end; ++iter) {
+ AttributeEnumFunc(iter->mSelector, iter->mRightmostSelector, aData);
+ }
+}
+
+static MOZ_ALWAYS_INLINE void
+EnumerateSelectors(nsTArray<nsCSSSelector*>& aSelectors, AttributeEnumData* aData)
+{
+ nsCSSSelector **iter = aSelectors.Elements(),
+ **end = iter + aSelectors.Length();
+ for (; iter != end; ++iter) {
+ AttributeEnumFunc(*iter, nullptr, aData);
+ }
+}
+
+nsRestyleHint
+nsCSSRuleProcessor::HasAttributeDependentStyle(
+ AttributeRuleProcessorData* aData,
+ RestyleHintData& aRestyleHintDataResult)
+{
+ // We could try making use of aData->mModType, but :not rules make it a bit
+ // of a pain to do so... So just ignore it for now.
+
+ AttributeEnumData data(aData, aRestyleHintDataResult);
+
+ // Don't do our special handling of certain attributes if the attr
+ // hasn't changed yet.
+ if (aData->mAttrHasChanged) {
+ // check for the lwtheme and lwthemetextcolor attribute on root XUL elements
+ if ((aData->mAttribute == nsGkAtoms::lwtheme ||
+ aData->mAttribute == nsGkAtoms::lwthemetextcolor) &&
+ aData->mElement->GetNameSpaceID() == kNameSpaceID_XUL &&
+ aData->mElement == aData->mElement->OwnerDoc()->GetRootElement())
+ {
+ data.change = nsRestyleHint(data.change | eRestyle_Subtree);
+ }
+
+ // We don't know the namespace of the attribute, and xml:lang applies to
+ // all elements. If the lang attribute changes, we need to restyle our
+ // whole subtree, since the :lang selector on our descendants can examine
+ // our lang attribute.
+ if (aData->mAttribute == nsGkAtoms::lang) {
+ data.change = nsRestyleHint(data.change | eRestyle_Subtree);
+ }
+ }
+
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ // Since we get both before and after notifications for attributes, we
+ // don't have to ignore aData->mAttribute while matching. Just check
+ // whether we have selectors relevant to aData->mAttribute that we
+ // match. If this is the before change notification, that will catch
+ // rules we might stop matching; if the after change notification, the
+ // ones we might have started matching.
+ if (cascade) {
+ if (aData->mAttribute == nsGkAtoms::id) {
+ nsIAtom* id = aData->mElement->GetID();
+ if (id) {
+ auto entry =
+ static_cast<AtomSelectorEntry*>(cascade->mIdSelectors.Search(id));
+ if (entry) {
+ EnumerateSelectors(entry->mSelectors, &data);
+ }
+ }
+
+ EnumerateSelectors(cascade->mPossiblyNegatedIDSelectors, &data);
+ }
+
+ if (aData->mAttribute == nsGkAtoms::_class &&
+ aData->mNameSpaceID == kNameSpaceID_None) {
+ const nsAttrValue* otherClasses = aData->mOtherValue;
+ NS_ASSERTION(otherClasses ||
+ aData->mModType == nsIDOMMutationEvent::REMOVAL,
+ "All class values should be StoresOwnData and parsed"
+ "via Element::BeforeSetAttr, so available here");
+ // For WillChange, enumerate classes that will be removed to see which
+ // rules apply before the change.
+ // For Changed, enumerate classes that have been added to see which rules
+ // apply after the change.
+ // In both cases we're interested in the classes that are currently on
+ // the element but not in mOtherValue.
+ const nsAttrValue* elementClasses = aData->mElement->GetClasses();
+ if (elementClasses) {
+ int32_t atomCount = elementClasses->GetAtomCount();
+ if (atomCount > 0) {
+ nsTHashtable<nsPtrHashKey<nsIAtom>> otherClassesTable;
+ if (otherClasses) {
+ int32_t otherClassesCount = otherClasses->GetAtomCount();
+ for (int32_t i = 0; i < otherClassesCount; ++i) {
+ otherClassesTable.PutEntry(otherClasses->AtomAt(i));
+ }
+ }
+ for (int32_t i = 0; i < atomCount; ++i) {
+ nsIAtom* curClass = elementClasses->AtomAt(i);
+ if (!otherClassesTable.Contains(curClass)) {
+ auto entry =
+ static_cast<AtomSelectorEntry*>
+ (cascade->mClassSelectors.Search(curClass));
+ if (entry) {
+ EnumerateSelectors(entry->mSelectors, &data);
+ }
+ }
+ }
+ }
+ }
+
+ EnumerateSelectors(cascade->mPossiblyNegatedClassSelectors, &data);
+ }
+
+ auto entry =
+ static_cast<AtomSelectorEntry*>
+ (cascade->mAttributeSelectors.Search(aData->mAttribute));
+ if (entry) {
+ EnumerateSelectors(entry->mSelectors, &data);
+ }
+ }
+
+ return data.change;
+}
+
+/* virtual */ bool
+nsCSSRuleProcessor::MediumFeaturesChanged(nsPresContext* aPresContext)
+{
+ // We don't want to do anything if there aren't any sets of rules
+ // cached yet, since we should not build the rule cascade too early
+ // (e.g., before we know whether the quirk style sheet should be
+ // enabled). And if there's nothing cached, it doesn't matter if
+ // anything changed. But in the cases where it does matter, we've
+ // cached a previous cache key to test against, instead of our current
+ // rule cascades. See bug 448281 and bug 1089417.
+ MOZ_ASSERT(!(mRuleCascades && mPreviousCacheKey));
+ RuleCascadeData *old = mRuleCascades;
+ if (old) {
+ RefreshRuleCascade(aPresContext);
+ return (old != mRuleCascades);
+ }
+
+ if (mPreviousCacheKey) {
+ // RefreshRuleCascade will get rid of mPreviousCacheKey anyway to
+ // maintain the invariant that we can't have both an mRuleCascades
+ // and an mPreviousCacheKey. But we need to hold it a little
+ // longer.
+ UniquePtr<nsMediaQueryResultCacheKey> previousCacheKey(
+ Move(mPreviousCacheKey));
+ RefreshRuleCascade(aPresContext);
+
+ // This test is a bit pessimistic since the cache key's operator==
+ // just does list comparison rather than set comparison, but it
+ // should catch all the cases we care about (i.e., where the cascade
+ // order hasn't changed). Other cases will do a restyle anyway, so
+ // we shouldn't need to worry about posting a second.
+ return !mRuleCascades || // all sheets gone, but we had sheets before
+ mRuleCascades->mCacheKey != *previousCacheKey;
+ }
+
+ return false;
+}
+
+UniquePtr<nsMediaQueryResultCacheKey>
+nsCSSRuleProcessor::CloneMQCacheKey()
+{
+ MOZ_ASSERT(!(mRuleCascades && mPreviousCacheKey));
+
+ RuleCascadeData* c = mRuleCascades;
+ if (!c) {
+ // We might have an mPreviousCacheKey. It already comes from a call
+ // to CloneMQCacheKey, so don't bother checking
+ // HasFeatureConditions().
+ if (mPreviousCacheKey) {
+ NS_ASSERTION(mPreviousCacheKey->HasFeatureConditions(),
+ "we shouldn't have a previous cache key unless it has "
+ "feature conditions");
+ return MakeUnique<nsMediaQueryResultCacheKey>(*mPreviousCacheKey);
+ }
+
+ return UniquePtr<nsMediaQueryResultCacheKey>();
+ }
+
+ if (!c->mCacheKey.HasFeatureConditions()) {
+ return UniquePtr<nsMediaQueryResultCacheKey>();
+ }
+
+ return MakeUnique<nsMediaQueryResultCacheKey>(c->mCacheKey);
+}
+
+/* virtual */ size_t
+nsCSSRuleProcessor::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ n += mSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (RuleCascadeData* cascade = mRuleCascades; cascade;
+ cascade = cascade->mNext) {
+ n += cascade->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+/* virtual */ size_t
+nsCSSRuleProcessor::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+// Append all the currently-active font face rules to aArray. Return
+// true for success and false for failure.
+bool
+nsCSSRuleProcessor::AppendFontFaceRules(
+ nsPresContext *aPresContext,
+ nsTArray<nsFontFaceRuleContainer>& aArray)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ if (!aArray.AppendElements(cascade->mFontFaceRules))
+ return false;
+ }
+
+ return true;
+}
+
+nsCSSKeyframesRule*
+nsCSSRuleProcessor::KeyframesRuleForName(nsPresContext* aPresContext,
+ const nsString& aName)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ return cascade->mKeyframesRuleTable.Get(aName);
+ }
+
+ return nullptr;
+}
+
+nsCSSCounterStyleRule*
+nsCSSRuleProcessor::CounterStyleRuleForName(nsPresContext* aPresContext,
+ const nsAString& aName)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ return cascade->mCounterStyleRuleTable.Get(aName);
+ }
+
+ return nullptr;
+}
+
+// Append all the currently-active page rules to aArray. Return
+// true for success and false for failure.
+bool
+nsCSSRuleProcessor::AppendPageRules(
+ nsPresContext* aPresContext,
+ nsTArray<nsCSSPageRule*>& aArray)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ if (!aArray.AppendElements(cascade->mPageRules)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+nsCSSRuleProcessor::AppendFontFeatureValuesRules(
+ nsPresContext *aPresContext,
+ nsTArray<nsCSSFontFeatureValuesRule*>& aArray)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ if (!aArray.AppendElements(cascade->mFontFeatureValuesRules))
+ return false;
+ }
+
+ return true;
+}
+
+nsresult
+nsCSSRuleProcessor::ClearRuleCascades()
+{
+ if (!mPreviousCacheKey) {
+ mPreviousCacheKey = CloneMQCacheKey();
+ }
+
+ // No need to remove the rule processor from the RuleProcessorCache here,
+ // since CSSStyleSheet::ClearRuleCascades will have called
+ // RuleProcessorCache::RemoveSheet() passing itself, which will catch
+ // this rule processor (and any others for different @-moz-document
+ // cache key results).
+ MOZ_ASSERT(!RuleProcessorCache::HasRuleProcessor(this));
+
+#ifdef DEBUG
+ // For shared rule processors, if we've already gathered document
+ // rules, then they will now be out of date. We don't actually need
+ // them to be up-to-date (see the comment in RefreshRuleCascade), so
+ // record their invalidity so we can assert if we try to use them.
+ if (!mMustGatherDocumentRules) {
+ mDocumentRulesAndCacheKeyValid = false;
+ }
+#endif
+
+ // We rely on our caller (perhaps indirectly) to do something that
+ // will rebuild style data and the user font set (either
+ // nsIPresShell::RestyleForCSSRuleChanges or
+ // nsPresContext::RebuildAllStyleData).
+ RuleCascadeData *data = mRuleCascades;
+ mRuleCascades = nullptr;
+ while (data) {
+ RuleCascadeData *next = data->mNext;
+ delete data;
+ data = next;
+ }
+ return NS_OK;
+}
+
+
+// This function should return the set of states that this selector
+// depends on; this is used to implement HasStateDependentStyle. It
+// does NOT recur down into things like :not and :-moz-any.
+inline
+EventStates ComputeSelectorStateDependence(nsCSSSelector& aSelector)
+{
+ EventStates states;
+ for (nsPseudoClassList* pseudoClass = aSelector.mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ // Tree pseudo-elements overload mPseudoClassList for things that
+ // aren't pseudo-classes.
+ if (pseudoClass->mType >= CSSPseudoClassType::Count) {
+ continue;
+ }
+
+ auto idx = static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType);
+ states |= sPseudoClassStateDependences[idx];
+ }
+ return states;
+}
+
+static bool
+AddSelector(RuleCascadeData* aCascade,
+ // The part between combinators at the top level of the selector
+ nsCSSSelector* aSelectorInTopLevel,
+ // The part we should look through (might be in :not or :-moz-any())
+ nsCSSSelector* aSelectorPart,
+ // The right-most selector at the top level
+ nsCSSSelector* aRightmostSelector)
+{
+ // It's worth noting that this loop over negations isn't quite
+ // optimal for two reasons. One, we could add something to one of
+ // these lists twice, which means we'll check it twice, but I don't
+ // think that's worth worrying about. (We do the same for multiple
+ // attribute selectors on the same attribute.) Two, we don't really
+ // need to check negations past the first in the current
+ // implementation (and they're rare as well), but that might change
+ // in the future if :not() is extended.
+ for (nsCSSSelector* negation = aSelectorPart; negation;
+ negation = negation->mNegations) {
+ // Track both document states and attribute dependence in pseudo-classes.
+ for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ switch (pseudoClass->mType) {
+ case CSSPseudoClassType::mozLocaleDir: {
+ aCascade->mSelectorDocumentStates |= NS_DOCUMENT_STATE_RTL_LOCALE;
+ break;
+ }
+ case CSSPseudoClassType::mozWindowInactive: {
+ aCascade->mSelectorDocumentStates |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
+ break;
+ }
+ case CSSPseudoClassType::mozTableBorderNonzero: {
+ nsTArray<SelectorPair> *array =
+ aCascade->AttributeListFor(nsGkAtoms::border);
+ if (!array) {
+ return false;
+ }
+ array->AppendElement(SelectorPair(aSelectorInTopLevel,
+ aRightmostSelector));
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+
+ // Build mStateSelectors.
+ EventStates dependentStates = ComputeSelectorStateDependence(*negation);
+ if (!dependentStates.IsEmpty()) {
+ aCascade->mStateSelectors.AppendElement(
+ nsCSSRuleProcessor::StateSelector(dependentStates,
+ aSelectorInTopLevel));
+ }
+
+ // Build mIDSelectors
+ if (negation == aSelectorInTopLevel) {
+ for (nsAtomList* curID = negation->mIDList; curID;
+ curID = curID->mNext) {
+ auto entry = static_cast<AtomSelectorEntry*>
+ (aCascade->mIdSelectors.Add(curID->mAtom, fallible));
+ if (entry) {
+ entry->mSelectors.AppendElement(SelectorPair(aSelectorInTopLevel,
+ aRightmostSelector));
+ }
+ }
+ } else if (negation->mIDList) {
+ aCascade->mPossiblyNegatedIDSelectors.AppendElement(aSelectorInTopLevel);
+ }
+
+ // Build mClassSelectors
+ if (negation == aSelectorInTopLevel) {
+ for (nsAtomList* curClass = negation->mClassList; curClass;
+ curClass = curClass->mNext) {
+ auto entry = static_cast<AtomSelectorEntry*>
+ (aCascade->mClassSelectors.Add(curClass->mAtom, fallible));
+ if (entry) {
+ entry->mSelectors.AppendElement(SelectorPair(aSelectorInTopLevel,
+ aRightmostSelector));
+ }
+ }
+ } else if (negation->mClassList) {
+ aCascade->mPossiblyNegatedClassSelectors.AppendElement(aSelectorInTopLevel);
+ }
+
+ // Build mAttributeSelectors.
+ for (nsAttrSelector *attr = negation->mAttrList; attr;
+ attr = attr->mNext) {
+ nsTArray<SelectorPair> *array =
+ aCascade->AttributeListFor(attr->mCasedAttr);
+ if (!array) {
+ return false;
+ }
+ array->AppendElement(SelectorPair(aSelectorInTopLevel,
+ aRightmostSelector));
+ if (attr->mLowercaseAttr != attr->mCasedAttr) {
+ array = aCascade->AttributeListFor(attr->mLowercaseAttr);
+ if (!array) {
+ return false;
+ }
+ array->AppendElement(SelectorPair(aSelectorInTopLevel,
+ aRightmostSelector));
+ }
+ }
+
+ // Recur through any :-moz-any selectors
+ for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ if (pseudoClass->mType == CSSPseudoClassType::any) {
+ for (nsCSSSelectorList *l = pseudoClass->u.mSelectors; l; l = l->mNext) {
+ nsCSSSelector *s = l->mSelectors;
+ if (!AddSelector(aCascade, aSelectorInTopLevel, s,
+ aRightmostSelector)) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool
+AddRule(RuleSelectorPair* aRuleInfo, RuleCascadeData* aCascade)
+{
+ RuleCascadeData * const cascade = aCascade;
+
+ // Build the rule hash.
+ CSSPseudoElementType pseudoType = aRuleInfo->mSelector->PseudoType();
+ if (MOZ_LIKELY(pseudoType == CSSPseudoElementType::NotPseudo)) {
+ cascade->mRuleHash.AppendRule(*aRuleInfo);
+ } else if (pseudoType < CSSPseudoElementType::Count) {
+ RuleHash*& ruleHash = cascade->mPseudoElementRuleHashes[
+ static_cast<CSSPseudoElementTypeBase>(pseudoType)];
+ if (!ruleHash) {
+ ruleHash = new RuleHash(cascade->mQuirksMode);
+ if (!ruleHash) {
+ // Out of memory; give up
+ return false;
+ }
+ }
+ NS_ASSERTION(aRuleInfo->mSelector->mNext,
+ "Must have mNext; parser screwed up");
+ NS_ASSERTION(aRuleInfo->mSelector->mNext->mOperator == ':',
+ "Unexpected mNext combinator");
+ ruleHash->AppendRule(*aRuleInfo);
+ } else if (pseudoType == CSSPseudoElementType::AnonBox) {
+ NS_ASSERTION(!aRuleInfo->mSelector->mCasedTag &&
+ !aRuleInfo->mSelector->mIDList &&
+ !aRuleInfo->mSelector->mClassList &&
+ !aRuleInfo->mSelector->mPseudoClassList &&
+ !aRuleInfo->mSelector->mAttrList &&
+ !aRuleInfo->mSelector->mNegations &&
+ !aRuleInfo->mSelector->mNext &&
+ aRuleInfo->mSelector->mNameSpace == kNameSpaceID_Unknown,
+ "Parser messed up with anon box selector");
+
+ // Index doesn't matter here, since we'll just be walking these
+ // rules in order; just pass 0.
+ AppendRuleToTagTable(&cascade->mAnonBoxRules,
+ aRuleInfo->mSelector->mLowercaseTag,
+ RuleValue(*aRuleInfo, 0, aCascade->mQuirksMode));
+ } else {
+#ifdef MOZ_XUL
+ NS_ASSERTION(pseudoType == CSSPseudoElementType::XULTree,
+ "Unexpected pseudo type");
+ // Index doesn't matter here, since we'll just be walking these
+ // rules in order; just pass 0.
+ AppendRuleToTagTable(&cascade->mXULTreeRules,
+ aRuleInfo->mSelector->mLowercaseTag,
+ RuleValue(*aRuleInfo, 0, aCascade->mQuirksMode));
+#else
+ NS_NOTREACHED("Unexpected pseudo type");
+#endif
+ }
+
+ for (nsCSSSelector* selector = aRuleInfo->mSelector;
+ selector; selector = selector->mNext) {
+ if (selector->IsPseudoElement()) {
+ CSSPseudoElementType pseudo = selector->PseudoType();
+ if (pseudo >= CSSPseudoElementType::Count ||
+ !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudo)) {
+ NS_ASSERTION(!selector->mNegations, "Shouldn't have negations");
+ // We do store selectors ending with pseudo-elements that allow :hover
+ // and :active after them in the hashtables corresponding to that
+ // selector's mNext (i.e. the thing that matches against the element),
+ // but we want to make sure that selectors for any other kinds of
+ // pseudo-elements don't end up in the hashtables. In particular, tree
+ // pseudos store strange things in mPseudoClassList that we don't want
+ // to try to match elements against.
+ continue;
+ }
+ }
+ if (!AddSelector(cascade, selector, selector, aRuleInfo->mSelector)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+struct PerWeightDataListItem : public RuleSelectorPair {
+ PerWeightDataListItem(css::StyleRule* aRule, nsCSSSelector* aSelector)
+ : RuleSelectorPair(aRule, aSelector)
+ , mNext(nullptr)
+ {}
+ // No destructor; these are arena-allocated
+
+
+ // Placement new to arena allocate the PerWeightDataListItem
+ void *operator new(size_t aSize, PLArenaPool &aArena) CPP_THROW_NEW {
+ void *mem;
+ PL_ARENA_ALLOCATE(mem, &aArena, aSize);
+ return mem;
+ }
+
+ PerWeightDataListItem *mNext;
+};
+
+struct PerWeightData {
+ PerWeightData()
+ : mRuleSelectorPairs(nullptr)
+ , mTail(&mRuleSelectorPairs)
+ {}
+
+ int32_t mWeight;
+ PerWeightDataListItem *mRuleSelectorPairs;
+ PerWeightDataListItem **mTail;
+};
+
+struct RuleByWeightEntry : public PLDHashEntryHdr {
+ PerWeightData data; // mWeight is key, mRuleSelectorPairs are value
+};
+
+static PLDHashNumber
+HashIntKey(const void *key)
+{
+ return PLDHashNumber(NS_PTR_TO_INT32(key));
+}
+
+static bool
+MatchWeightEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ const RuleByWeightEntry *entry = (const RuleByWeightEntry *)hdr;
+ return entry->data.mWeight == NS_PTR_TO_INT32(key);
+}
+
+static void
+InitWeightEntry(PLDHashEntryHdr *hdr, const void *key)
+{
+ RuleByWeightEntry* entry = static_cast<RuleByWeightEntry*>(hdr);
+ new (KnownNotNull, entry) RuleByWeightEntry();
+}
+
+static const PLDHashTableOps gRulesByWeightOps = {
+ HashIntKey,
+ MatchWeightEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ InitWeightEntry
+};
+
+struct CascadeEnumData {
+ CascadeEnumData(nsPresContext* aPresContext,
+ nsTArray<nsFontFaceRuleContainer>& aFontFaceRules,
+ nsTArray<nsCSSKeyframesRule*>& aKeyframesRules,
+ nsTArray<nsCSSFontFeatureValuesRule*>& aFontFeatureValuesRules,
+ nsTArray<nsCSSPageRule*>& aPageRules,
+ nsTArray<nsCSSCounterStyleRule*>& aCounterStyleRules,
+ nsTArray<css::DocumentRule*>& aDocumentRules,
+ nsMediaQueryResultCacheKey& aKey,
+ nsDocumentRuleResultCacheKey& aDocumentKey,
+ SheetType aSheetType,
+ bool aMustGatherDocumentRules)
+ : mPresContext(aPresContext),
+ mFontFaceRules(aFontFaceRules),
+ mKeyframesRules(aKeyframesRules),
+ mFontFeatureValuesRules(aFontFeatureValuesRules),
+ mPageRules(aPageRules),
+ mCounterStyleRules(aCounterStyleRules),
+ mDocumentRules(aDocumentRules),
+ mCacheKey(aKey),
+ mDocumentCacheKey(aDocumentKey),
+ mRulesByWeight(&gRulesByWeightOps, sizeof(RuleByWeightEntry), 32),
+ mSheetType(aSheetType),
+ mMustGatherDocumentRules(aMustGatherDocumentRules)
+ {
+ // Initialize our arena
+ PL_INIT_ARENA_POOL(&mArena, "CascadeEnumDataArena",
+ NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE);
+ }
+
+ ~CascadeEnumData()
+ {
+ PL_FinishArenaPool(&mArena);
+ }
+
+ nsPresContext* mPresContext;
+ nsTArray<nsFontFaceRuleContainer>& mFontFaceRules;
+ nsTArray<nsCSSKeyframesRule*>& mKeyframesRules;
+ nsTArray<nsCSSFontFeatureValuesRule*>& mFontFeatureValuesRules;
+ nsTArray<nsCSSPageRule*>& mPageRules;
+ nsTArray<nsCSSCounterStyleRule*>& mCounterStyleRules;
+ nsTArray<css::DocumentRule*>& mDocumentRules;
+ nsMediaQueryResultCacheKey& mCacheKey;
+ nsDocumentRuleResultCacheKey& mDocumentCacheKey;
+ PLArenaPool mArena;
+ // Hooray, a manual PLDHashTable since nsClassHashtable doesn't
+ // provide a getter that gives me a *reference* to the value.
+ PLDHashTable mRulesByWeight; // of PerWeightDataListItem linked lists
+ SheetType mSheetType;
+ bool mMustGatherDocumentRules;
+};
+
+/**
+ * Recursively traverses rules in order to:
+ * (1) add any @-moz-document rules into data->mDocumentRules.
+ * (2) record any @-moz-document rules whose conditions evaluate to true
+ * on data->mDocumentCacheKey.
+ *
+ * See also CascadeRuleEnumFunc below, which calls us via
+ * EnumerateRulesForwards. If modifying this function you may need to
+ * update CascadeRuleEnumFunc too.
+ */
+static bool
+GatherDocRuleEnumFunc(css::Rule* aRule, void* aData)
+{
+ CascadeEnumData* data = (CascadeEnumData*)aData;
+ int32_t type = aRule->GetType();
+
+ MOZ_ASSERT(data->mMustGatherDocumentRules,
+ "should only call GatherDocRuleEnumFunc if "
+ "mMustGatherDocumentRules is true");
+
+ if (css::Rule::MEDIA_RULE == type ||
+ css::Rule::SUPPORTS_RULE == type) {
+ css::GroupRule* groupRule = static_cast<css::GroupRule*>(aRule);
+ if (!groupRule->EnumerateRulesForwards(GatherDocRuleEnumFunc, aData)) {
+ return false;
+ }
+ }
+ else if (css::Rule::DOCUMENT_RULE == type) {
+ css::DocumentRule* docRule = static_cast<css::DocumentRule*>(aRule);
+ if (!data->mDocumentRules.AppendElement(docRule)) {
+ return false;
+ }
+ if (docRule->UseForPresentation(data->mPresContext)) {
+ if (!data->mDocumentCacheKey.AddMatchingRule(docRule)) {
+ return false;
+ }
+ }
+ if (!docRule->EnumerateRulesForwards(GatherDocRuleEnumFunc, aData)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * This enumerates style rules in a sheet (and recursively into any
+ * grouping rules) in order to:
+ * (1) add any style rules, in order, into data->mRulesByWeight (for
+ * the primary CSS cascade), where they are separated by weight
+ * but kept in order per-weight, and
+ * (2) add any @font-face rules, in order, into data->mFontFaceRules.
+ * (3) add any @keyframes rules, in order, into data->mKeyframesRules.
+ * (4) add any @font-feature-value rules, in order,
+ * into data->mFontFeatureValuesRules.
+ * (5) add any @page rules, in order, into data->mPageRules.
+ * (6) add any @counter-style rules, in order, into data->mCounterStyleRules.
+ * (7) add any @-moz-document rules into data->mDocumentRules.
+ * (8) record any @-moz-document rules whose conditions evaluate to true
+ * on data->mDocumentCacheKey.
+ *
+ * See also GatherDocRuleEnumFunc above, which we call to traverse into
+ * @-moz-document rules even if their (or an ancestor's) condition
+ * fails. This means we might look at the result of some @-moz-document
+ * rules that don't actually affect whether a RuleProcessorCache lookup
+ * is a hit or a miss. The presence of @-moz-document rules inside
+ * @media etc. rules should be rare, and looking at all of them in the
+ * sheets lets us avoid the complication of having different document
+ * cache key results for different media.
+ *
+ * If modifying this function you may need to update
+ * GatherDocRuleEnumFunc too.
+ */
+static bool
+CascadeRuleEnumFunc(css::Rule* aRule, void* aData)
+{
+ CascadeEnumData* data = (CascadeEnumData*)aData;
+ int32_t type = aRule->GetType();
+
+ if (css::Rule::STYLE_RULE == type) {
+ css::StyleRule* styleRule = static_cast<css::StyleRule*>(aRule);
+
+ for (nsCSSSelectorList *sel = styleRule->Selector();
+ sel; sel = sel->mNext) {
+ int32_t weight = sel->mWeight;
+ auto entry = static_cast<RuleByWeightEntry*>
+ (data->mRulesByWeight.Add(NS_INT32_TO_PTR(weight), fallible));
+ if (!entry)
+ return false;
+ entry->data.mWeight = weight;
+ // entry->data.mRuleSelectorPairs should be linked in forward order;
+ // entry->data.mTail is the slot to write to.
+ auto* newItem =
+ new (data->mArena) PerWeightDataListItem(styleRule, sel->mSelectors);
+ if (newItem) {
+ *(entry->data.mTail) = newItem;
+ entry->data.mTail = &newItem->mNext;
+ }
+ }
+ }
+ else if (css::Rule::MEDIA_RULE == type ||
+ css::Rule::SUPPORTS_RULE == type) {
+ css::GroupRule* groupRule = static_cast<css::GroupRule*>(aRule);
+ const bool use =
+ groupRule->UseForPresentation(data->mPresContext, data->mCacheKey);
+ if (use || data->mMustGatherDocumentRules) {
+ if (!groupRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc :
+ GatherDocRuleEnumFunc,
+ aData)) {
+ return false;
+ }
+ }
+ }
+ else if (css::Rule::DOCUMENT_RULE == type) {
+ css::DocumentRule* docRule = static_cast<css::DocumentRule*>(aRule);
+ if (data->mMustGatherDocumentRules) {
+ if (!data->mDocumentRules.AppendElement(docRule)) {
+ return false;
+ }
+ }
+ const bool use = docRule->UseForPresentation(data->mPresContext);
+ if (use && data->mMustGatherDocumentRules) {
+ if (!data->mDocumentCacheKey.AddMatchingRule(docRule)) {
+ return false;
+ }
+ }
+ if (use || data->mMustGatherDocumentRules) {
+ if (!docRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc
+ : GatherDocRuleEnumFunc,
+ aData)) {
+ return false;
+ }
+ }
+ }
+ else if (css::Rule::FONT_FACE_RULE == type) {
+ nsCSSFontFaceRule *fontFaceRule = static_cast<nsCSSFontFaceRule*>(aRule);
+ nsFontFaceRuleContainer *ptr = data->mFontFaceRules.AppendElement();
+ if (!ptr)
+ return false;
+ ptr->mRule = fontFaceRule;
+ ptr->mSheetType = data->mSheetType;
+ }
+ else if (css::Rule::KEYFRAMES_RULE == type) {
+ nsCSSKeyframesRule *keyframesRule =
+ static_cast<nsCSSKeyframesRule*>(aRule);
+ if (!data->mKeyframesRules.AppendElement(keyframesRule)) {
+ return false;
+ }
+ }
+ else if (css::Rule::FONT_FEATURE_VALUES_RULE == type) {
+ nsCSSFontFeatureValuesRule *fontFeatureValuesRule =
+ static_cast<nsCSSFontFeatureValuesRule*>(aRule);
+ if (!data->mFontFeatureValuesRules.AppendElement(fontFeatureValuesRule)) {
+ return false;
+ }
+ }
+ else if (css::Rule::PAGE_RULE == type) {
+ nsCSSPageRule* pageRule = static_cast<nsCSSPageRule*>(aRule);
+ if (!data->mPageRules.AppendElement(pageRule)) {
+ return false;
+ }
+ }
+ else if (css::Rule::COUNTER_STYLE_RULE == type) {
+ nsCSSCounterStyleRule* counterStyleRule =
+ static_cast<nsCSSCounterStyleRule*>(aRule);
+ if (!data->mCounterStyleRules.AppendElement(counterStyleRule)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* static */ bool
+nsCSSRuleProcessor::CascadeSheet(CSSStyleSheet* aSheet, CascadeEnumData* aData)
+{
+ if (aSheet->IsApplicable() &&
+ aSheet->UseForPresentation(aData->mPresContext, aData->mCacheKey) &&
+ aSheet->mInner) {
+ CSSStyleSheet* child = aSheet->mInner->mFirstChild;
+ while (child) {
+ CascadeSheet(child, aData);
+ child = child->mNext;
+ }
+
+ if (!aSheet->mInner->mOrderedRules.EnumerateForwards(CascadeRuleEnumFunc,
+ aData))
+ return false;
+ }
+ return true;
+}
+
+static int CompareWeightData(const void* aArg1, const void* aArg2,
+ void* closure)
+{
+ const PerWeightData* arg1 = static_cast<const PerWeightData*>(aArg1);
+ const PerWeightData* arg2 = static_cast<const PerWeightData*>(aArg2);
+ return arg1->mWeight - arg2->mWeight; // put lower weight first
+}
+
+RuleCascadeData*
+nsCSSRuleProcessor::GetRuleCascade(nsPresContext* aPresContext)
+{
+ // FIXME: Make this infallible!
+
+ // If anything changes about the presentation context, we will be
+ // notified. Otherwise, our cache is valid if mLastPresContext
+ // matches aPresContext. (The only rule processors used for multiple
+ // pres contexts are for XBL. These rule processors are probably less
+ // likely to have @media rules, and thus the cache is pretty likely to
+ // hit instantly even when we're switching between pres contexts.)
+
+ if (!mRuleCascades || aPresContext != mLastPresContext) {
+ RefreshRuleCascade(aPresContext);
+ }
+ mLastPresContext = aPresContext;
+
+ return mRuleCascades;
+}
+
+void
+nsCSSRuleProcessor::RefreshRuleCascade(nsPresContext* aPresContext)
+{
+ // Having RuleCascadeData objects be per-medium (over all variation
+ // caused by media queries, handled through mCacheKey) works for now
+ // since nsCSSRuleProcessor objects are per-document. (For a given
+ // set of stylesheets they can vary based on medium (@media) or
+ // document (@-moz-document).)
+
+ for (RuleCascadeData **cascadep = &mRuleCascades, *cascade;
+ (cascade = *cascadep); cascadep = &cascade->mNext) {
+ if (cascade->mCacheKey.Matches(aPresContext)) {
+ // Ensure that the current one is always mRuleCascades.
+ *cascadep = cascade->mNext;
+ cascade->mNext = mRuleCascades;
+ mRuleCascades = cascade;
+
+ return;
+ }
+ }
+
+ // We're going to make a new rule cascade; this means that we should
+ // now stop using the previous cache key that we're holding on to from
+ // the last time we had rule cascades.
+ mPreviousCacheKey = nullptr;
+
+ if (mSheets.Length() != 0) {
+ nsAutoPtr<RuleCascadeData> newCascade(
+ new RuleCascadeData(aPresContext->Medium(),
+ eCompatibility_NavQuirks == aPresContext->CompatibilityMode()));
+ if (newCascade) {
+ CascadeEnumData data(aPresContext, newCascade->mFontFaceRules,
+ newCascade->mKeyframesRules,
+ newCascade->mFontFeatureValuesRules,
+ newCascade->mPageRules,
+ newCascade->mCounterStyleRules,
+ mDocumentRules,
+ newCascade->mCacheKey,
+ mDocumentCacheKey,
+ mSheetType,
+ mMustGatherDocumentRules);
+
+ for (uint32_t i = 0; i < mSheets.Length(); ++i) {
+ if (!CascadeSheet(mSheets.ElementAt(i), &data))
+ return; /* out of memory */
+ }
+
+ // Sort the hash table of per-weight linked lists by weight.
+ uint32_t weightCount = data.mRulesByWeight.EntryCount();
+ auto weightArray = MakeUnique<PerWeightData[]>(weightCount);
+ int32_t j = 0;
+ for (auto iter = data.mRulesByWeight.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<const RuleByWeightEntry*>(iter.Get());
+ weightArray[j++] = entry->data;
+ }
+ NS_QuickSort(weightArray.get(), weightCount, sizeof(PerWeightData),
+ CompareWeightData, nullptr);
+
+ // Put things into the rule hash.
+ // The primary sort is by weight...
+ for (uint32_t i = 0; i < weightCount; ++i) {
+ // and the secondary sort is by order. mRuleSelectorPairs is already in
+ // the right order..
+ for (PerWeightDataListItem *cur = weightArray[i].mRuleSelectorPairs;
+ cur;
+ cur = cur->mNext) {
+ if (!AddRule(cur, newCascade))
+ return; /* out of memory */
+ }
+ }
+
+ // Build mKeyframesRuleTable.
+ for (nsTArray<nsCSSKeyframesRule*>::size_type i = 0,
+ iEnd = newCascade->mKeyframesRules.Length(); i < iEnd; ++i) {
+ nsCSSKeyframesRule* rule = newCascade->mKeyframesRules[i];
+ newCascade->mKeyframesRuleTable.Put(rule->GetName(), rule);
+ }
+
+ // Build mCounterStyleRuleTable
+ for (nsTArray<nsCSSCounterStyleRule*>::size_type i = 0,
+ iEnd = newCascade->mCounterStyleRules.Length(); i < iEnd; ++i) {
+ nsCSSCounterStyleRule* rule = newCascade->mCounterStyleRules[i];
+ newCascade->mCounterStyleRuleTable.Put(rule->GetName(), rule);
+ }
+
+ // mMustGatherDocumentRules controls whether we build mDocumentRules
+ // and mDocumentCacheKey so that they can be used as keys by the
+ // RuleProcessorCache, as obtained by TakeDocumentRulesAndCacheKey
+ // later. We set it to false just below so that we only do this
+ // the first time we build a RuleProcessorCache for a shared rule
+ // processor.
+ //
+ // An up-to-date mDocumentCacheKey is only needed if we
+ // are still in the RuleProcessorCache (as we store a copy of the
+ // cache key in the RuleProcessorCache), and an up-to-date
+ // mDocumentRules is only needed at the time TakeDocumentRulesAndCacheKey
+ // is called, which is immediately after the rule processor is created
+ // (by nsStyleSet).
+ //
+ // Note that when nsCSSRuleProcessor::ClearRuleCascades is called,
+ // by CSSStyleSheet::ClearRuleCascades, we will have called
+ // RuleProcessorCache::RemoveSheet, which will remove the rule
+ // processor from the cache. (This is because the list of document
+ // rules now may not match the one used as they key in the
+ // RuleProcessorCache.)
+ //
+ // Thus, as we'll no longer be in the RuleProcessorCache, and we won't
+ // have TakeDocumentRulesAndCacheKey called on us, we don't need to ensure
+ // mDocumentCacheKey and mDocumentRules are up-to-date after the
+ // first time GetRuleCascade is called.
+ if (mMustGatherDocumentRules) {
+ mDocumentRules.Sort();
+ mDocumentCacheKey.Finalize();
+ mMustGatherDocumentRules = false;
+#ifdef DEBUG
+ mDocumentRulesAndCacheKeyValid = true;
+#endif
+ }
+
+ // Ensure that the current one is always mRuleCascades.
+ newCascade->mNext = mRuleCascades;
+ mRuleCascades = newCascade.forget();
+ }
+ }
+ return;
+}
+
+/* static */ bool
+nsCSSRuleProcessor::SelectorListMatches(Element* aElement,
+ TreeMatchContext& aTreeMatchContext,
+ nsCSSSelectorList* aSelectorList)
+{
+ MOZ_ASSERT(!aTreeMatchContext.mForScopedStyle,
+ "mCurrentStyleScope will need to be saved and restored after the "
+ "SelectorMatchesTree call");
+
+ while (aSelectorList) {
+ nsCSSSelector* sel = aSelectorList->mSelectors;
+ NS_ASSERTION(sel, "Should have *some* selectors");
+ NS_ASSERTION(!sel->IsPseudoElement(), "Shouldn't have been called");
+ NodeMatchContext nodeContext(EventStates(), false);
+ if (SelectorMatches(aElement, sel, nodeContext, aTreeMatchContext,
+ SelectorMatchesFlags::NONE)) {
+ nsCSSSelector* next = sel->mNext;
+ if (!next ||
+ SelectorMatchesTree(aElement, next, aTreeMatchContext,
+ SelectorMatchesTreeFlags(0))) {
+ return true;
+ }
+ }
+
+ aSelectorList = aSelectorList->mNext;
+ }
+
+ return false;
+}
+
+void
+nsCSSRuleProcessor::TakeDocumentRulesAndCacheKey(
+ nsPresContext* aPresContext,
+ nsTArray<css::DocumentRule*>& aDocumentRules,
+ nsDocumentRuleResultCacheKey& aCacheKey)
+{
+ MOZ_ASSERT(mIsShared);
+
+ GetRuleCascade(aPresContext);
+ MOZ_ASSERT(mDocumentRulesAndCacheKeyValid);
+
+ aDocumentRules.Clear();
+ aDocumentRules.SwapElements(mDocumentRules);
+ aCacheKey.Swap(mDocumentCacheKey);
+
+#ifdef DEBUG
+ mDocumentRulesAndCacheKeyValid = false;
+#endif
+}
+
+void
+nsCSSRuleProcessor::AddStyleSetRef()
+{
+ MOZ_ASSERT(mIsShared);
+ if (++mStyleSetRefCnt == 1) {
+ RuleProcessorCache::StopTracking(this);
+ }
+}
+
+void
+nsCSSRuleProcessor::ReleaseStyleSetRef()
+{
+ MOZ_ASSERT(mIsShared);
+ MOZ_ASSERT(mStyleSetRefCnt > 0);
+ if (--mStyleSetRefCnt == 0 && mInRuleProcessorCache) {
+ RuleProcessorCache::StartTracking(this);
+ }
+}
+
+// TreeMatchContext and AncestorFilter out of line methods
+void
+TreeMatchContext::InitAncestors(Element *aElement)
+{
+ MOZ_ASSERT(!mAncestorFilter.mFilter);
+ MOZ_ASSERT(mAncestorFilter.mHashes.IsEmpty());
+ MOZ_ASSERT(mStyleScopes.IsEmpty());
+
+ mAncestorFilter.mFilter = new AncestorFilter::Filter();
+
+ if (MOZ_LIKELY(aElement)) {
+ MOZ_ASSERT(aElement->GetUncomposedDoc() ||
+ aElement->HasFlag(NODE_IS_IN_SHADOW_TREE),
+ "aElement must be in the document or in shadow tree "
+ "for the assumption that GetParentNode() is non-null "
+ "on all element ancestors of aElement to be true");
+ // Collect up the ancestors
+ AutoTArray<Element*, 50> ancestors;
+ Element* cur = aElement;
+ do {
+ ancestors.AppendElement(cur);
+ cur = cur->GetParentElementCrossingShadowRoot();
+ } while (cur);
+
+ // Now push them in reverse order.
+ for (uint32_t i = ancestors.Length(); i-- != 0; ) {
+ mAncestorFilter.PushAncestor(ancestors[i]);
+ PushStyleScope(ancestors[i]);
+ }
+ }
+}
+
+void
+TreeMatchContext::InitStyleScopes(Element* aElement)
+{
+ MOZ_ASSERT(mStyleScopes.IsEmpty());
+
+ if (MOZ_LIKELY(aElement)) {
+ // Collect up the ancestors
+ AutoTArray<Element*, 50> ancestors;
+ Element* cur = aElement;
+ do {
+ ancestors.AppendElement(cur);
+ cur = cur->GetParentElementCrossingShadowRoot();
+ } while (cur);
+
+ // Now push them in reverse order.
+ for (uint32_t i = ancestors.Length(); i-- != 0; ) {
+ PushStyleScope(ancestors[i]);
+ }
+ }
+}
+
+void
+AncestorFilter::PushAncestor(Element *aElement)
+{
+ MOZ_ASSERT(mFilter);
+
+ uint32_t oldLength = mHashes.Length();
+
+ mPopTargets.AppendElement(oldLength);
+#ifdef DEBUG
+ mElements.AppendElement(aElement);
+#endif
+ mHashes.AppendElement(aElement->NodeInfo()->NameAtom()->hash());
+ nsIAtom *id = aElement->GetID();
+ if (id) {
+ mHashes.AppendElement(id->hash());
+ }
+ const nsAttrValue *classes = aElement->GetClasses();
+ if (classes) {
+ uint32_t classCount = classes->GetAtomCount();
+ for (uint32_t i = 0; i < classCount; ++i) {
+ mHashes.AppendElement(classes->AtomAt(i)->hash());
+ }
+ }
+
+ uint32_t newLength = mHashes.Length();
+ for (uint32_t i = oldLength; i < newLength; ++i) {
+ mFilter->add(mHashes[i]);
+ }
+}
+
+void
+AncestorFilter::PopAncestor()
+{
+ MOZ_ASSERT(!mPopTargets.IsEmpty());
+ MOZ_ASSERT(mPopTargets.Length() == mElements.Length());
+
+ uint32_t popTargetLength = mPopTargets.Length();
+ uint32_t newLength = mPopTargets[popTargetLength-1];
+
+ mPopTargets.TruncateLength(popTargetLength-1);
+#ifdef DEBUG
+ mElements.TruncateLength(popTargetLength-1);
+#endif
+
+ uint32_t oldLength = mHashes.Length();
+ for (uint32_t i = newLength; i < oldLength; ++i) {
+ mFilter->remove(mHashes[i]);
+ }
+ mHashes.TruncateLength(newLength);
+}
+
+#ifdef DEBUG
+void
+AncestorFilter::AssertHasAllAncestors(Element *aElement) const
+{
+ Element* cur = aElement->GetParentElementCrossingShadowRoot();
+ while (cur) {
+ MOZ_ASSERT(mElements.Contains(cur));
+ cur = cur->GetParentElementCrossingShadowRoot();
+ }
+}
+
+void
+TreeMatchContext::AssertHasAllStyleScopes(Element* aElement) const
+{
+ if (aElement->IsInNativeAnonymousSubtree()) {
+ // Document style sheets are never applied to native anonymous content,
+ // so it's not possible for them to be in a <style scoped> scope.
+ return;
+ }
+ Element* cur = aElement->GetParentElementCrossingShadowRoot();
+ while (cur) {
+ if (cur->IsScopedStyleRoot()) {
+ MOZ_ASSERT(mStyleScopes.Contains(cur));
+ }
+ cur = cur->GetParentElementCrossingShadowRoot();
+ }
+}
+#endif
diff --git a/layout/style/nsCSSRuleProcessor.h b/layout/style/nsCSSRuleProcessor.h
new file mode 100644
index 000000000..e207a71af
--- /dev/null
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:tabstop=2:expandtab:shiftwidth=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/. */
+
+/*
+ * style rule processor for CSS style sheets, responsible for selector
+ * matching and cascading
+ */
+
+#ifndef nsCSSRuleProcessor_h_
+#define nsCSSRuleProcessor_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefCountType.h"
+#include "mozilla/SheetType.h"
+#include "mozilla/UniquePtr.h"
+#include "nsExpirationTracker.h"
+#include "nsIMediaList.h"
+#include "nsIStyleRuleProcessor.h"
+#include "nsRuleWalker.h"
+#include "nsTArray.h"
+
+struct CascadeEnumData;
+struct ElementDependentRuleProcessorData;
+struct nsCSSSelector;
+struct nsCSSSelectorList;
+struct nsFontFaceRuleContainer;
+struct RuleCascadeData;
+struct TreeMatchContext;
+class nsCSSKeyframesRule;
+class nsCSSPageRule;
+class nsCSSFontFeatureValuesRule;
+class nsCSSCounterStyleRule;
+
+namespace mozilla {
+class CSSStyleSheet;
+enum class CSSPseudoElementType : uint8_t;
+namespace css {
+class DocumentRule;
+} // namespace css
+} // namespace mozilla
+
+/**
+ * The CSS style rule processor provides a mechanism for sibling style
+ * sheets to combine their rule processing in order to allow proper
+ * cascading to happen.
+ *
+ * CSS style rule processors keep a live reference on all style sheets
+ * bound to them. The CSS style sheets keep a weak reference to all the
+ * processors that they are bound to (many to many). The CSS style sheet
+ * is told when the rule processor is going away (via DropRuleProcessor).
+ */
+
+class nsCSSRuleProcessor: public nsIStyleRuleProcessor {
+public:
+ typedef nsTArray<RefPtr<mozilla::CSSStyleSheet>> sheet_array_type;
+
+ // aScopeElement must be non-null iff aSheetType is
+ // SheetType::ScopedDoc.
+ // aPreviousCSSRuleProcessor is the rule processor (if any) that this
+ // one is replacing.
+ nsCSSRuleProcessor(const sheet_array_type& aSheets,
+ mozilla::SheetType aSheetType,
+ mozilla::dom::Element* aScopeElement,
+ nsCSSRuleProcessor* aPreviousCSSRuleProcessor,
+ bool aIsShared = false);
+ nsCSSRuleProcessor(sheet_array_type&& aSheets,
+ mozilla::SheetType aSheetType,
+ mozilla::dom::Element* aScopeElement,
+ nsCSSRuleProcessor* aPreviousCSSRuleProcessor,
+ bool aIsShared = false);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsCSSRuleProcessor)
+
+public:
+ nsresult ClearRuleCascades();
+
+ static void Startup();
+ static void Shutdown();
+ static void FreeSystemMetrics();
+ static bool HasSystemMetric(nsIAtom* aMetric);
+
+ /*
+ * Returns true if the given aElement matches one of the
+ * selectors in aSelectorList. Note that this method will assume
+ * the given aElement is not a relevant link. aSelectorList must not
+ * include any pseudo-element selectors. aSelectorList is allowed
+ * to be null; in this case false will be returned.
+ */
+ static bool SelectorListMatches(mozilla::dom::Element* aElement,
+ TreeMatchContext& aTreeMatchContext,
+ nsCSSSelectorList* aSelectorList);
+
+ /*
+ * Helper to get the content state for a content node. This may be
+ * slightly adjusted from IntrinsicState().
+ */
+ static mozilla::EventStates GetContentState(
+ mozilla::dom::Element* aElement,
+ const TreeMatchContext& aTreeMatchContext);
+
+ /*
+ * Helper to get the content state for :visited handling for an element
+ */
+ static mozilla::EventStates GetContentStateForVisitedHandling(
+ mozilla::dom::Element* aElement,
+ const TreeMatchContext& aTreeMatchContext,
+ nsRuleWalker::VisitedHandlingType aVisitedHandling,
+ bool aIsRelevantLink);
+
+ /*
+ * Helper to test whether a node is a link
+ */
+ static bool IsLink(const mozilla::dom::Element* aElement);
+
+ /**
+ * Returns true if the given aElement matches aSelector.
+ * Like nsCSSRuleProcessor.cpp's SelectorMatches (and unlike
+ * SelectorMatchesTree), this does not check an entire selector list
+ * separated by combinators.
+ *
+ * :visited and :link will match both visited and non-visited links,
+ * as if aTreeMatchContext->mVisitedHandling were eLinksVisitedOrUnvisited.
+ *
+ * aSelector is restricted to not containing pseudo-elements.
+ */
+ static bool RestrictedSelectorMatches(mozilla::dom::Element* aElement,
+ nsCSSSelector* aSelector,
+ TreeMatchContext& aTreeMatchContext);
+
+ // nsIStyleRuleProcessor
+ virtual void RulesMatching(ElementRuleProcessorData* aData) override;
+
+ virtual void RulesMatching(PseudoElementRuleProcessorData* aData) override;
+
+ virtual void RulesMatching(AnonBoxRuleProcessorData* aData) override;
+
+#ifdef MOZ_XUL
+ virtual void RulesMatching(XULTreeRuleProcessorData* aData) override;
+#endif
+
+ virtual nsRestyleHint HasStateDependentStyle(StateRuleProcessorData* aData) override;
+ virtual nsRestyleHint HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData) override;
+
+ virtual bool HasDocumentStateDependentStyle(StateRuleProcessorData* aData) override;
+
+ virtual nsRestyleHint
+ HasAttributeDependentStyle(AttributeRuleProcessorData* aData,
+ mozilla::RestyleHintData& aRestyleHintDataResult)
+ override;
+
+ virtual bool MediumFeaturesChanged(nsPresContext* aPresContext) override;
+
+ /**
+ * If this rule processor currently has a substantive media query
+ * result cache key, return a copy of it.
+ */
+ mozilla::UniquePtr<nsMediaQueryResultCacheKey> CloneMQCacheKey();
+
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
+ const MOZ_MUST_OVERRIDE override;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
+ const MOZ_MUST_OVERRIDE override;
+
+ // Append all the currently-active font face rules to aArray. Return
+ // true for success and false for failure.
+ bool AppendFontFaceRules(nsPresContext* aPresContext,
+ nsTArray<nsFontFaceRuleContainer>& aArray);
+
+ nsCSSKeyframesRule* KeyframesRuleForName(nsPresContext* aPresContext,
+ const nsString& aName);
+
+ nsCSSCounterStyleRule* CounterStyleRuleForName(nsPresContext* aPresContext,
+ const nsAString& aName);
+
+ bool AppendPageRules(nsPresContext* aPresContext,
+ nsTArray<nsCSSPageRule*>& aArray);
+
+ bool AppendFontFeatureValuesRules(nsPresContext* aPresContext,
+ nsTArray<nsCSSFontFeatureValuesRule*>& aArray);
+
+ /**
+ * Returns the scope element for the scoped style sheets this rule
+ * processor is for. If this is not a rule processor for scoped style
+ * sheets, it returns null.
+ */
+ mozilla::dom::Element* GetScopeElement() const { return mScopeElement; }
+
+ void TakeDocumentRulesAndCacheKey(
+ nsPresContext* aPresContext,
+ nsTArray<mozilla::css::DocumentRule*>& aDocumentRules,
+ nsDocumentRuleResultCacheKey& aDocumentRuleResultCacheKey);
+
+ bool IsShared() const { return mIsShared; }
+
+ nsExpirationState* GetExpirationState() { return &mExpirationState; }
+ void AddStyleSetRef();
+ void ReleaseStyleSetRef();
+ void SetInRuleProcessorCache(bool aVal) {
+ MOZ_ASSERT(mIsShared);
+ mInRuleProcessorCache = aVal;
+ }
+ bool IsInRuleProcessorCache() const { return mInRuleProcessorCache; }
+ bool IsUsedByMultipleStyleSets() const { return mStyleSetRefCnt > 1; }
+
+#ifdef XP_WIN
+ // Cached theme identifier for the moz-windows-theme media query.
+ static uint8_t GetWindowsThemeIdentifier();
+ static void SetWindowsThemeIdentifier(uint8_t aId) {
+ sWinThemeId = aId;
+ }
+#endif
+
+ struct StateSelector {
+ StateSelector(mozilla::EventStates aStates, nsCSSSelector* aSelector)
+ : mStates(aStates),
+ mSelector(aSelector)
+ {}
+
+ mozilla::EventStates mStates;
+ nsCSSSelector* mSelector;
+ };
+
+protected:
+ virtual ~nsCSSRuleProcessor();
+
+private:
+ static bool CascadeSheet(mozilla::CSSStyleSheet* aSheet,
+ CascadeEnumData* aData);
+
+ RuleCascadeData* GetRuleCascade(nsPresContext* aPresContext);
+ void RefreshRuleCascade(nsPresContext* aPresContext);
+
+ nsRestyleHint HasStateDependentStyle(ElementDependentRuleProcessorData* aData,
+ mozilla::dom::Element* aStatefulElement,
+ mozilla::CSSPseudoElementType aPseudoType,
+ mozilla::EventStates aStateMask);
+
+ void ClearSheets();
+
+ // The sheet order here is the same as in nsStyleSet::mSheets
+ sheet_array_type mSheets;
+
+ // active first, then cached (most recent first)
+ RuleCascadeData* mRuleCascades;
+
+ // If we cleared our mRuleCascades or replaced a previous rule
+ // processor, this is the media query result cache key that was used
+ // before we lost the old rule cascades.
+ mozilla::UniquePtr<nsMediaQueryResultCacheKey> mPreviousCacheKey;
+
+ // The last pres context for which GetRuleCascades was called.
+ nsPresContext *mLastPresContext;
+
+ // The scope element for this rule processor's scoped style sheets.
+ // Only used if mSheetType == nsStyleSet::eScopedDocSheet.
+ RefPtr<mozilla::dom::Element> mScopeElement;
+
+ nsTArray<mozilla::css::DocumentRule*> mDocumentRules;
+ nsDocumentRuleResultCacheKey mDocumentCacheKey;
+
+ nsExpirationState mExpirationState;
+ MozRefCountType mStyleSetRefCnt;
+
+ // type of stylesheet using this processor
+ mozilla::SheetType mSheetType;
+
+ const bool mIsShared;
+
+ // Whether we need to build up mDocumentCacheKey and mDocumentRules as
+ // we build a RuleCascadeData. Is true only for shared rule processors
+ // and only before we build the first RuleCascadeData. See comment in
+ // RefreshRuleCascade for why.
+ bool mMustGatherDocumentRules;
+
+ bool mInRuleProcessorCache;
+
+#ifdef DEBUG
+ bool mDocumentRulesAndCacheKeyValid;
+#endif
+
+#ifdef XP_WIN
+ static uint8_t sWinThemeId;
+#endif
+};
+
+#endif /* nsCSSRuleProcessor_h_ */
diff --git a/layout/style/nsCSSRules.cpp b/layout/style/nsCSSRules.cpp
new file mode 100644
index 000000000..a08d8af34
--- /dev/null
+++ b/layout/style/nsCSSRules.cpp
@@ -0,0 +1,3312 @@
+/* -*- 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/. */
+
+/* rules in a CSS stylesheet other than style rules (e.g., @import rules) */
+
+#include "mozilla/Attributes.h"
+
+#include "nsCSSRules.h"
+#include "nsCSSValue.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/ImportRule.h"
+#include "mozilla/css/NameSpaceRule.h"
+
+#include "nsString.h"
+#include "nsIAtom.h"
+
+#include "nsCSSProps.h"
+
+#include "nsCOMPtr.h"
+#include "nsIDOMCSSStyleSheet.h"
+#include "nsIMediaList.h"
+#include "mozilla/dom/CSSRuleList.h"
+#include "nsIDocument.h"
+#include "nsPresContext.h"
+
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsStyleUtil.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "nsCSSParser.h"
+#include "nsDOMClassInfoID.h"
+#include "mozilla/dom/CSSStyleDeclarationBinding.h"
+#include "StyleRule.h"
+#include "nsFont.h"
+#include "nsIURI.h"
+#include "mozAutoDocUpdate.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define IMPL_STYLE_RULE_INHERIT_GET_DOM_RULE_WEAK(class_, super_) \
+ /* virtual */ nsIDOMCSSRule* class_::GetDOMRule() \
+ { return this; } \
+ /* virtual */ nsIDOMCSSRule* class_::GetExistingDOMRule() \
+ { return this; }
+
+#define IMPL_STYLE_RULE_INHERIT(class_, super_) \
+IMPL_STYLE_RULE_INHERIT_GET_DOM_RULE_WEAK(class_, super_)
+
+// base class for all rule types in a CSS style sheet
+
+namespace mozilla {
+namespace css {
+
+/* virtual */ void
+Rule::SetStyleSheet(CSSStyleSheet* aSheet)
+{
+ // We don't reference count this up reference. The style sheet
+ // will tell us when it's going away or when we're detached from
+ // it.
+ mSheet = aSheet;
+}
+
+nsresult
+Rule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ if (mParentRule) {
+ NS_IF_ADDREF(*aParentRule = mParentRule->GetDOMRule());
+ } else {
+ *aParentRule = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult
+Rule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ NS_ENSURE_ARG_POINTER(aSheet);
+
+ NS_IF_ADDREF(*aSheet = GetStyleSheet());
+ return NS_OK;
+}
+
+css::Rule*
+Rule::GetCSSRule()
+{
+ return this;
+}
+
+// -------------------------------
+// Style Rule List for group rules
+//
+
+class GroupRuleRuleList final : public dom::CSSRuleList
+{
+public:
+ explicit GroupRuleRuleList(GroupRule *aGroupRule);
+
+ virtual CSSStyleSheet* GetParentObject() override;
+
+ virtual nsIDOMCSSRule*
+ IndexedGetter(uint32_t aIndex, bool& aFound) override;
+ virtual uint32_t
+ Length() override;
+
+ void DropReference() { mGroupRule = nullptr; }
+
+private:
+ ~GroupRuleRuleList();
+
+private:
+ GroupRule* mGroupRule;
+};
+
+GroupRuleRuleList::GroupRuleRuleList(GroupRule *aGroupRule)
+{
+ // Not reference counted to avoid circular references.
+ // The rule will tell us when its going away.
+ mGroupRule = aGroupRule;
+}
+
+GroupRuleRuleList::~GroupRuleRuleList()
+{
+}
+
+CSSStyleSheet*
+GroupRuleRuleList::GetParentObject()
+{
+ if (!mGroupRule) {
+ return nullptr;
+ }
+
+ return mGroupRule->GetStyleSheet();
+}
+
+uint32_t
+GroupRuleRuleList::Length()
+{
+ if (!mGroupRule) {
+ return 0;
+ }
+
+ return AssertedCast<uint32_t>(mGroupRule->StyleRuleCount());
+}
+
+nsIDOMCSSRule*
+GroupRuleRuleList::IndexedGetter(uint32_t aIndex, bool& aFound)
+{
+ aFound = false;
+
+ if (mGroupRule) {
+ RefPtr<Rule> rule = mGroupRule->GetStyleRuleAt(aIndex);
+ if (rule) {
+ aFound = true;
+ return rule->GetDOMRule();
+ }
+ }
+
+ return nullptr;
+}
+
+// -------------------------------------------
+// ImportRule
+//
+
+ImportRule::ImportRule(nsMediaList* aMedia, const nsString& aURLSpec,
+ uint32_t aLineNumber, uint32_t aColumnNumber)
+ : Rule(aLineNumber, aColumnNumber)
+ , mURLSpec(aURLSpec)
+ , mMedia(aMedia)
+{
+ // XXXbz This is really silly.... the mMedia here will be replaced
+ // with itself if we manage to load a sheet. Which should really
+ // never fail nowadays, in sane cases.
+}
+
+ImportRule::ImportRule(const ImportRule& aCopy)
+ : Rule(aCopy),
+ mURLSpec(aCopy.mURLSpec)
+{
+ // Whether or not an @import rule has a null sheet is a permanent
+ // property of that @import rule, since it is null only if the target
+ // sheet failed security checks.
+ if (aCopy.mChildSheet) {
+ RefPtr<CSSStyleSheet> sheet =
+ aCopy.mChildSheet->Clone(nullptr, this, nullptr, nullptr);
+ SetSheet(sheet);
+ // SetSheet sets mMedia appropriately
+ }
+}
+
+ImportRule::~ImportRule()
+{
+ if (mChildSheet) {
+ mChildSheet->SetOwnerRule(nullptr);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ImportRule)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ImportRule)
+
+// QueryInterface implementation for ImportRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImportRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSImportRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSImportRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT(ImportRule, Rule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ImportRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ImportRule)
+ if (tmp->mChildSheet) {
+ tmp->mChildSheet->SetOwnerRule(nullptr);
+ tmp->mChildSheet = nullptr;
+ }
+ tmp->mMedia = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ImportRule)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMedia)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildSheet)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#ifdef DEBUG
+/* virtual */ void
+ImportRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString str;
+ // Indent
+ for (int32_t indent = aIndent; --indent >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+
+ str.AppendLiteral("@import \"");
+ AppendUTF16toUTF8(mURLSpec, str);
+ str.AppendLiteral("\" ");
+
+ nsAutoString mediaText;
+ mMedia->GetText(mediaText);
+ AppendUTF16toUTF8(mediaText, str);
+ str.AppendLiteral("\n");
+ fprintf_stderr(out, "%s", str.get());
+}
+#endif
+
+/* virtual */ int32_t
+ImportRule::GetType() const
+{
+ return Rule::IMPORT_RULE;
+}
+
+/* virtual */ already_AddRefed<Rule>
+ImportRule::Clone() const
+{
+ RefPtr<Rule> clone = new ImportRule(*this);
+ return clone.forget();
+}
+
+void
+ImportRule::SetSheet(CSSStyleSheet* aSheet)
+{
+ NS_PRECONDITION(aSheet, "null arg");
+
+ // set the new sheet
+ mChildSheet = aSheet;
+ aSheet->SetOwnerRule(this);
+
+ // set our medialist to be the same as the sheet's medialist
+ mMedia = mChildSheet->Media();
+}
+
+NS_IMETHODIMP
+ImportRule::GetType(uint16_t* aType)
+{
+ NS_ENSURE_ARG_POINTER(aType);
+ *aType = nsIDOMCSSRule::IMPORT_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImportRule::GetCssText(nsAString& aCssText)
+{
+ aCssText.AssignLiteral("@import url(");
+ nsStyleUtil::AppendEscapedCSSString(mURLSpec, aCssText);
+ aCssText.Append(')');
+ if (mMedia) {
+ nsAutoString mediaText;
+ mMedia->GetText(mediaText);
+ if (!mediaText.IsEmpty()) {
+ aCssText.Append(' ');
+ aCssText.Append(mediaText);
+ }
+ }
+ aCssText.Append(';');
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImportRule::SetCssText(const nsAString& aCssText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ImportRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return Rule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+ImportRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return Rule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+ImportRule::GetCSSRule()
+{
+ return Rule::GetCSSRule();
+}
+
+NS_IMETHODIMP
+ImportRule::GetHref(nsAString & aHref)
+{
+ aHref = mURLSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImportRule::GetMedia(nsIDOMMediaList * *aMedia)
+{
+ NS_ENSURE_ARG_POINTER(aMedia);
+
+ NS_IF_ADDREF(*aMedia = mMedia);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImportRule::GetStyleSheet(nsIDOMCSSStyleSheet * *aStyleSheet)
+{
+ NS_ENSURE_ARG_POINTER(aStyleSheet);
+
+ NS_IF_ADDREF(*aStyleSheet = mChildSheet);
+ return NS_OK;
+}
+
+/* virtual */ size_t
+ImportRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mURLSpec
+ //
+ // The following members are not measured:
+ // - mMedia, because it is measured via CSSStyleSheet::mMedia
+ // - mChildSheet, because it is measured via CSSStyleSheetInner::mSheets
+}
+
+GroupRule::GroupRule(uint32_t aLineNumber, uint32_t aColumnNumber)
+ : Rule(aLineNumber, aColumnNumber)
+{
+}
+
+static bool
+SetParentRuleReference(Rule* aRule, void* aParentRule)
+{
+ GroupRule* parentRule = static_cast<GroupRule*>(aParentRule);
+ aRule->SetParentRule(parentRule);
+ return true;
+}
+
+GroupRule::GroupRule(const GroupRule& aCopy)
+ : Rule(aCopy)
+{
+ const_cast<GroupRule&>(aCopy).mRules.EnumerateForwards(GroupRule::CloneRuleInto, &mRules);
+ mRules.EnumerateForwards(SetParentRuleReference, this);
+}
+
+GroupRule::~GroupRule()
+{
+ MOZ_ASSERT(!mSheet, "SetStyleSheet should have been called");
+ mRules.EnumerateForwards(SetParentRuleReference, nullptr);
+ if (mRuleCollection) {
+ mRuleCollection->DropReference();
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GroupRule)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GroupRule)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GroupRule)
+NS_INTERFACE_MAP_END
+
+static bool
+SetStyleSheetReference(Rule* aRule, void* aSheet)
+{
+ CSSStyleSheet* sheet = (CSSStyleSheet*)aSheet;
+ aRule->SetStyleSheet(sheet);
+ return true;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(GroupRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(GroupRule)
+ tmp->mRules.EnumerateForwards(SetParentRuleReference, nullptr);
+ // If tmp does not have a stylesheet, neither do its descendants. In that
+ // case, don't try to null out their stylesheet, to avoid O(N^2) behavior in
+ // depth of group rule nesting. But if tmp _does_ have a stylesheet (which
+ // can happen if it gets unlinked earlier than its owning stylesheet), then we
+ // need to null out the stylesheet pointer on descendants now, before we clear
+ // tmp->mRules.
+ if (tmp->GetStyleSheet()) {
+ tmp->mRules.EnumerateForwards(SetStyleSheetReference, nullptr);
+ }
+ tmp->mRules.Clear();
+ if (tmp->mRuleCollection) {
+ tmp->mRuleCollection->DropReference();
+ tmp->mRuleCollection = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(GroupRule)
+ const nsCOMArray<Rule>& rules = tmp->mRules;
+ for (int32_t i = 0, count = rules.Count(); i < count; ++i) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mRules[i]");
+ cb.NoteXPCOMChild(rules[i]->GetExistingDOMRule());
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleCollection)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+/* virtual */ void
+GroupRule::SetStyleSheet(CSSStyleSheet* aSheet)
+{
+ // Don't set the sheet on the kids if it's already the same as the sheet we
+ // already have. This is needed to avoid O(N^2) behavior in group nesting
+ // depth when seting the sheet to null during unlink, if we happen to unlin in
+ // order from most nested rule up to least nested rule.
+ if (aSheet != GetStyleSheet()) {
+ mRules.EnumerateForwards(SetStyleSheetReference, aSheet);
+ Rule::SetStyleSheet(aSheet);
+ }
+}
+
+#ifdef DEBUG
+/* virtual */ void
+GroupRule::List(FILE* out, int32_t aIndent) const
+{
+ for (int32_t index = 0, count = mRules.Count(); index < count; ++index) {
+ mRules.ObjectAt(index)->List(out, aIndent + 1);
+ }
+}
+#endif
+
+void
+GroupRule::AppendStyleRule(Rule* aRule)
+{
+ mRules.AppendObject(aRule);
+ CSSStyleSheet* sheet = GetStyleSheet();
+ aRule->SetStyleSheet(sheet);
+ aRule->SetParentRule(this);
+ if (sheet) {
+ sheet->SetModifiedByChildRule();
+ }
+}
+
+Rule*
+GroupRule::GetStyleRuleAt(int32_t aIndex) const
+{
+ return mRules.SafeObjectAt(aIndex);
+}
+
+bool
+GroupRule::EnumerateRulesForwards(RuleEnumFunc aFunc, void * aData) const
+{
+ return
+ const_cast<GroupRule*>(this)->mRules.EnumerateForwards(aFunc, aData);
+}
+
+/*
+ * The next two methods (DeleteStyleRuleAt and InsertStyleRuleAt)
+ * should never be called unless you have first called WillDirty() on
+ * the parents stylesheet. After they are called, DidDirty() needs to
+ * be called on the sheet
+ */
+nsresult
+GroupRule::DeleteStyleRuleAt(uint32_t aIndex)
+{
+ Rule* rule = mRules.SafeObjectAt(aIndex);
+ if (rule) {
+ rule->SetStyleSheet(nullptr);
+ rule->SetParentRule(nullptr);
+ }
+ return mRules.RemoveObjectAt(aIndex) ? NS_OK : NS_ERROR_ILLEGAL_VALUE;
+}
+
+nsresult
+GroupRule::InsertStyleRuleAt(uint32_t aIndex, Rule* aRule)
+{
+ aRule->SetStyleSheet(GetStyleSheet());
+ aRule->SetParentRule(this);
+ if (! mRules.InsertObjectAt(aRule, aIndex)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void
+GroupRule::AppendRulesToCssText(nsAString& aCssText)
+{
+ aCssText.AppendLiteral(" {\n");
+
+ // get all the rules
+ for (int32_t index = 0, count = mRules.Count(); index < count; ++index) {
+ Rule* rule = mRules.ObjectAt(index);
+ nsIDOMCSSRule* domRule = rule->GetDOMRule();
+ if (domRule) {
+ nsAutoString cssText;
+ domRule->GetCssText(cssText);
+ aCssText.AppendLiteral(" ");
+ aCssText.Append(cssText);
+ aCssText.Append('\n');
+ }
+ }
+
+ aCssText.Append('}');
+}
+
+// nsIDOMCSSMediaRule or nsIDOMCSSMozDocumentRule methods
+nsresult
+GroupRule::GetCssRules(nsIDOMCSSRuleList* *aRuleList)
+{
+ if (!mRuleCollection) {
+ mRuleCollection = new css::GroupRuleRuleList(this);
+ }
+
+ NS_ADDREF(*aRuleList = mRuleCollection);
+ return NS_OK;
+}
+
+nsresult
+GroupRule::InsertRule(const nsAString & aRule, uint32_t aIndex, uint32_t* _retval)
+{
+ CSSStyleSheet* sheet = GetStyleSheet();
+ NS_ENSURE_TRUE(sheet, NS_ERROR_FAILURE);
+
+ if (aIndex > uint32_t(mRules.Count()))
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+
+ NS_ASSERTION(uint32_t(mRules.Count()) <= INT32_MAX,
+ "Too many style rules!");
+
+ return sheet->InsertRuleIntoGroup(aRule, this, aIndex, _retval);
+}
+
+nsresult
+GroupRule::DeleteRule(uint32_t aIndex)
+{
+ CSSStyleSheet* sheet = GetStyleSheet();
+ NS_ENSURE_TRUE(sheet, NS_ERROR_FAILURE);
+
+ if (aIndex >= uint32_t(mRules.Count()))
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+
+ NS_ASSERTION(uint32_t(mRules.Count()) <= INT32_MAX,
+ "Too many style rules!");
+
+ return sheet->DeleteRuleFromGroup(this, aIndex);
+}
+
+/* virtual */ size_t
+GroupRule::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = mRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mRules.Length(); i++) {
+ n += mRules[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mRuleCollection
+ return n;
+}
+
+
+// -------------------------------------------
+// nsICSSMediaRule
+//
+MediaRule::MediaRule(uint32_t aLineNumber, uint32_t aColumnNumber)
+ : GroupRule(aLineNumber, aColumnNumber)
+{
+}
+
+MediaRule::MediaRule(const MediaRule& aCopy)
+ : GroupRule(aCopy)
+{
+ if (aCopy.mMedia) {
+ mMedia = aCopy.mMedia->Clone();
+ // XXXldb This doesn't really make sense.
+ mMedia->SetStyleSheet(aCopy.GetStyleSheet());
+ }
+}
+
+MediaRule::~MediaRule()
+{
+ if (mMedia) {
+ mMedia->SetStyleSheet(nullptr);
+ }
+}
+
+NS_IMPL_ADDREF_INHERITED(MediaRule, GroupRule)
+NS_IMPL_RELEASE_INHERITED(MediaRule, GroupRule)
+
+// QueryInterface implementation for MediaRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSGroupingRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSConditionRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSMediaRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSMediaRule)
+NS_INTERFACE_MAP_END_INHERITING(GroupRule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRule, GroupRule)
+ if (tmp->mMedia) {
+ tmp->mMedia->SetStyleSheet(nullptr);
+ tmp->mMedia = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRule, GroupRule)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMedia)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+/* virtual */ void
+MediaRule::SetStyleSheet(CSSStyleSheet* aSheet)
+{
+ if (mMedia) {
+ // Set to null so it knows it's leaving one sheet and joining another.
+ mMedia->SetStyleSheet(nullptr);
+ mMedia->SetStyleSheet(aSheet);
+ }
+
+ GroupRule::SetStyleSheet(aSheet);
+}
+
+#ifdef DEBUG
+/* virtual */ void
+MediaRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indentStr;
+ for (int32_t indent = aIndent; --indent >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+
+ nsAutoCString str(indentStr);
+ str.AppendLiteral("@media ");
+
+ if (mMedia) {
+ nsAutoString mediaText;
+ mMedia->GetText(mediaText);
+ AppendUTF16toUTF8(mediaText, str);
+ }
+
+ str.AppendLiteral(" {\n");
+ fprintf_stderr(out, "%s", str.get());
+
+ GroupRule::List(out, aIndent);
+
+ fprintf_stderr(out, "%s}\n", indentStr.get());
+}
+#endif
+
+/* virtual */ int32_t
+MediaRule::GetType() const
+{
+ return Rule::MEDIA_RULE;
+}
+
+/* virtual */ already_AddRefed<Rule>
+MediaRule::Clone() const
+{
+ RefPtr<Rule> clone = new MediaRule(*this);
+ return clone.forget();
+}
+
+nsresult
+MediaRule::SetMedia(nsMediaList* aMedia)
+{
+ mMedia = aMedia;
+ if (aMedia)
+ mMedia->SetStyleSheet(GetStyleSheet());
+ return NS_OK;
+}
+
+// nsIDOMCSSRule methods
+NS_IMETHODIMP
+MediaRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::MEDIA_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaRule::GetCssText(nsAString& aCssText)
+{
+ aCssText.AssignLiteral("@media ");
+ AppendConditionText(aCssText);
+ GroupRule::AppendRulesToCssText(aCssText);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaRule::SetCssText(const nsAString& aCssText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MediaRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return GroupRule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+MediaRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return GroupRule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+MediaRule::GetCSSRule()
+{
+ return Rule::GetCSSRule();
+}
+
+// nsIDOMCSSGroupingRule methods
+NS_IMETHODIMP
+MediaRule::GetCssRules(nsIDOMCSSRuleList* *aRuleList)
+{
+ return GroupRule::GetCssRules(aRuleList);
+}
+
+NS_IMETHODIMP
+MediaRule::InsertRule(const nsAString & aRule, uint32_t aIndex, uint32_t* _retval)
+{
+ return GroupRule::InsertRule(aRule, aIndex, _retval);
+}
+
+NS_IMETHODIMP
+MediaRule::DeleteRule(uint32_t aIndex)
+{
+ return GroupRule::DeleteRule(aIndex);
+}
+
+// nsIDOMCSSConditionRule methods
+NS_IMETHODIMP
+MediaRule::GetConditionText(nsAString& aConditionText)
+{
+ aConditionText.Truncate(0);
+ AppendConditionText(aConditionText);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaRule::SetConditionText(const nsAString& aConditionText)
+{
+ if (!mMedia) {
+ RefPtr<nsMediaList> media = new nsMediaList();
+ media->SetStyleSheet(GetStyleSheet());
+ nsresult rv = media->SetMediaText(aConditionText);
+ if (NS_SUCCEEDED(rv)) {
+ mMedia = media;
+ }
+ return rv;
+ }
+
+ return mMedia->SetMediaText(aConditionText);
+}
+
+// nsIDOMCSSMediaRule methods
+NS_IMETHODIMP
+MediaRule::GetMedia(nsIDOMMediaList* *aMedia)
+{
+ NS_ENSURE_ARG_POINTER(aMedia);
+ NS_IF_ADDREF(*aMedia = mMedia);
+ return NS_OK;
+}
+
+// GroupRule interface
+/* virtual */ bool
+MediaRule::UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey)
+{
+ if (mMedia) {
+ return mMedia->Matches(aPresContext, &aKey);
+ }
+ return true;
+}
+
+/* virtual */ size_t
+MediaRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ n += GroupRule::SizeOfExcludingThis(aMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mMedia
+
+ return n;
+}
+
+void
+MediaRule::AppendConditionText(nsAString& aOutput)
+{
+ if (mMedia) {
+ nsAutoString mediaText;
+ mMedia->GetText(mediaText);
+ aOutput.Append(mediaText);
+ }
+}
+
+DocumentRule::DocumentRule(uint32_t aLineNumber, uint32_t aColumnNumber)
+ : GroupRule(aLineNumber, aColumnNumber)
+{
+}
+
+DocumentRule::DocumentRule(const DocumentRule& aCopy)
+ : GroupRule(aCopy)
+ , mURLs(new URL(*aCopy.mURLs))
+{
+}
+
+DocumentRule::~DocumentRule()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(DocumentRule, GroupRule)
+NS_IMPL_RELEASE_INHERITED(DocumentRule, GroupRule)
+
+// QueryInterface implementation for DocumentRule
+NS_INTERFACE_MAP_BEGIN(DocumentRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSGroupingRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSConditionRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSMozDocumentRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSMozDocumentRule)
+NS_INTERFACE_MAP_END_INHERITING(GroupRule)
+
+#ifdef DEBUG
+/* virtual */ void
+DocumentRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indentStr;
+ for (int32_t indent = aIndent; --indent >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+
+ nsAutoCString str;
+ str.AppendLiteral("@-moz-document ");
+ for (URL *url = mURLs; url; url = url->next) {
+ switch (url->func) {
+ case eURL:
+ str.AppendLiteral("url(\"");
+ break;
+ case eURLPrefix:
+ str.AppendLiteral("url-prefix(\"");
+ break;
+ case eDomain:
+ str.AppendLiteral("domain(\"");
+ break;
+ case eRegExp:
+ str.AppendLiteral("regexp(\"");
+ break;
+ }
+ nsAutoCString escapedURL(url->url);
+ escapedURL.ReplaceSubstring("\"", "\\\""); // escape quotes
+ str.Append(escapedURL);
+ str.AppendLiteral("\"), ");
+ }
+ str.Cut(str.Length() - 2, 1); // remove last ,
+ fprintf_stderr(out, "%s%s {\n", indentStr.get(), str.get());
+
+ GroupRule::List(out, aIndent);
+
+ fprintf_stderr(out, "%s}\n", indentStr.get());
+}
+#endif
+
+/* virtual */ int32_t
+DocumentRule::GetType() const
+{
+ return Rule::DOCUMENT_RULE;
+}
+
+/* virtual */ already_AddRefed<Rule>
+DocumentRule::Clone() const
+{
+ RefPtr<Rule> clone = new DocumentRule(*this);
+ return clone.forget();
+}
+
+// nsIDOMCSSRule methods
+NS_IMETHODIMP
+DocumentRule::GetType(uint16_t* aType)
+{
+ // XXX What should really happen here?
+ *aType = nsIDOMCSSRule::UNKNOWN_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentRule::GetCssText(nsAString& aCssText)
+{
+ aCssText.AssignLiteral("@-moz-document ");
+ AppendConditionText(aCssText);
+ GroupRule::AppendRulesToCssText(aCssText);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentRule::SetCssText(const nsAString& aCssText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+DocumentRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return GroupRule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+DocumentRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return GroupRule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+DocumentRule::GetCSSRule()
+{
+ return Rule::GetCSSRule();
+}
+
+// nsIDOMCSSGroupingRule methods
+NS_IMETHODIMP
+DocumentRule::GetCssRules(nsIDOMCSSRuleList* *aRuleList)
+{
+ return GroupRule::GetCssRules(aRuleList);
+}
+
+NS_IMETHODIMP
+DocumentRule::InsertRule(const nsAString & aRule, uint32_t aIndex, uint32_t* _retval)
+{
+ return GroupRule::InsertRule(aRule, aIndex, _retval);
+}
+
+NS_IMETHODIMP
+DocumentRule::DeleteRule(uint32_t aIndex)
+{
+ return GroupRule::DeleteRule(aIndex);
+}
+
+// nsIDOMCSSConditionRule methods
+NS_IMETHODIMP
+DocumentRule::GetConditionText(nsAString& aConditionText)
+{
+ aConditionText.Truncate(0);
+ AppendConditionText(aConditionText);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentRule::SetConditionText(const nsAString& aConditionText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// GroupRule interface
+/* virtual */ bool
+DocumentRule::UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey)
+{
+ return UseForPresentation(aPresContext);
+}
+
+bool
+DocumentRule::UseForPresentation(nsPresContext* aPresContext)
+{
+ nsIDocument *doc = aPresContext->Document();
+ nsIURI *docURI = doc->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);
+ }
+
+ for (URL *url = mURLs; url; url = url->next) {
+ switch (url->func) {
+ case eURL: {
+ if (docURISpec == url->url)
+ return true;
+ } break;
+ case eURLPrefix: {
+ if (StringBeginsWith(docURISpec, url->url))
+ return true;
+ } break;
+ case eDomain: {
+ nsAutoCString host;
+ if (docURI)
+ docURI->GetHost(host);
+ int32_t lenDiff = host.Length() - url->url.Length();
+ if (lenDiff == 0) {
+ if (host == url->url)
+ return true;
+ } else {
+ if (StringEndsWith(host, url->url) &&
+ host.CharAt(lenDiff - 1) == '.')
+ return true;
+ }
+ } break;
+ case eRegExp: {
+ NS_ConvertUTF8toUTF16 spec(docURISpec);
+ NS_ConvertUTF8toUTF16 regex(url->url);
+ if (nsContentUtils::IsPatternMatching(spec, regex, doc)) {
+ return true;
+ }
+ } break;
+ }
+ }
+
+ return false;
+}
+
+DocumentRule::URL::~URL()
+{
+ NS_CSS_DELETE_LIST_MEMBER(DocumentRule::URL, this, next);
+}
+
+/* virtual */ size_t
+DocumentRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ n += GroupRule::SizeOfExcludingThis(aMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mURLs
+
+ return n;
+}
+
+void
+DocumentRule::AppendConditionText(nsAString& aCssText)
+{
+ for (URL *url = mURLs; url; url = url->next) {
+ switch (url->func) {
+ case eURL:
+ aCssText.AppendLiteral("url(");
+ break;
+ case eURLPrefix:
+ aCssText.AppendLiteral("url-prefix(");
+ break;
+ case eDomain:
+ aCssText.AppendLiteral("domain(");
+ break;
+ case eRegExp:
+ aCssText.AppendLiteral("regexp(");
+ break;
+ }
+ nsStyleUtil::AppendEscapedCSSString(NS_ConvertUTF8toUTF16(url->url),
+ aCssText);
+ aCssText.AppendLiteral("), ");
+ }
+ aCssText.Truncate(aCssText.Length() - 2); // remove last ", "
+}
+
+// -------------------------------------------
+// NameSpaceRule
+//
+
+NameSpaceRule::NameSpaceRule(nsIAtom* aPrefix, const nsString& aURLSpec,
+ uint32_t aLineNumber, uint32_t aColumnNumber)
+ : Rule(aLineNumber, aColumnNumber),
+ mPrefix(aPrefix),
+ mURLSpec(aURLSpec)
+{
+}
+
+NameSpaceRule::NameSpaceRule(const NameSpaceRule& aCopy)
+ : Rule(aCopy),
+ mPrefix(aCopy.mPrefix),
+ mURLSpec(aCopy.mURLSpec)
+{
+}
+
+NameSpaceRule::~NameSpaceRule()
+{
+}
+
+NS_IMPL_ADDREF(NameSpaceRule)
+NS_IMPL_RELEASE(NameSpaceRule)
+
+// QueryInterface implementation for NameSpaceRule
+NS_INTERFACE_MAP_BEGIN(NameSpaceRule)
+ if (aIID.Equals(NS_GET_IID(css::NameSpaceRule))) {
+ *aInstancePtr = this;
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+ else
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSNameSpaceRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT(NameSpaceRule, Rule)
+
+#ifdef DEBUG
+/* virtual */ void
+NameSpaceRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString str;
+ for (int32_t indent = aIndent; --indent >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+
+ nsAutoString buffer;
+
+ str.AppendLiteral("@namespace ");
+
+ if (mPrefix) {
+ mPrefix->ToString(buffer);
+ AppendUTF16toUTF8(buffer, str);
+ str.Append(' ');
+ }
+
+ str.AppendLiteral("url(\"");
+ AppendUTF16toUTF8(mURLSpec, str);
+ str.AppendLiteral("\")\n");
+ fprintf_stderr(out, "%s", str.get());
+}
+#endif
+
+/* virtual */ int32_t
+NameSpaceRule::GetType() const
+{
+ return Rule::NAMESPACE_RULE;
+}
+
+/* virtual */ already_AddRefed<Rule>
+NameSpaceRule::Clone() const
+{
+ RefPtr<Rule> clone = new NameSpaceRule(*this);
+ return clone.forget();
+}
+
+NS_IMETHODIMP
+NameSpaceRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::NAMESPACE_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NameSpaceRule::GetCssText(nsAString& aCssText)
+{
+ aCssText.AssignLiteral("@namespace ");
+ if (mPrefix) {
+ aCssText.Append(nsDependentAtomString(mPrefix) + NS_LITERAL_STRING(" "));
+ }
+ aCssText.AppendLiteral("url(");
+ nsStyleUtil::AppendEscapedCSSString(mURLSpec, aCssText);
+ aCssText.AppendLiteral(");");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NameSpaceRule::SetCssText(const nsAString& aCssText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NameSpaceRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return Rule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+NameSpaceRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return Rule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+NameSpaceRule::GetCSSRule()
+{
+ return Rule::GetCSSRule();
+}
+
+/* virtual */ size_t
+NameSpaceRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mPrefix
+ // - mURLSpec
+}
+
+
+} // namespace css
+} // namespace mozilla
+
+// -------------------------------------------
+// nsCSSFontFaceStyleDecl and related routines
+//
+
+// Mapping from nsCSSFontDesc codes to CSSFontFaceDescriptors fields.
+nsCSSValue CSSFontFaceDescriptors::* const
+CSSFontFaceDescriptors::Fields[] = {
+#define CSS_FONT_DESC(name_, method_) &CSSFontFaceDescriptors::m##method_,
+#include "nsCSSFontDescList.h"
+#undef CSS_FONT_DESC
+};
+
+const nsCSSValue&
+CSSFontFaceDescriptors::Get(nsCSSFontDesc aFontDescID) const
+{
+ MOZ_ASSERT(aFontDescID > eCSSFontDesc_UNKNOWN &&
+ aFontDescID < eCSSFontDesc_COUNT);
+ return this->*CSSFontFaceDescriptors::Fields[aFontDescID];
+}
+
+nsCSSValue&
+CSSFontFaceDescriptors::Get(nsCSSFontDesc aFontDescID)
+{
+ MOZ_ASSERT(aFontDescID > eCSSFontDesc_UNKNOWN &&
+ aFontDescID < eCSSFontDesc_COUNT);
+ return this->*CSSFontFaceDescriptors::Fields[aFontDescID];
+}
+
+// QueryInterface implementation for nsCSSFontFaceStyleDecl
+NS_INTERFACE_MAP_BEGIN(nsCSSFontFaceStyleDecl)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSStyleDeclaration)
+ 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(nsCSSFontFaceStyleDecl, ContainingRule())
+NS_IMPL_RELEASE_USING_AGGREGATOR(nsCSSFontFaceStyleDecl, ContainingRule())
+
+// helper for string GetPropertyValue and RemovePropertyValue
+nsresult
+nsCSSFontFaceStyleDecl::GetPropertyValue(nsCSSFontDesc aFontDescID,
+ nsAString & aResult) const
+{
+ NS_ENSURE_ARG_RANGE(aFontDescID, eCSSFontDesc_UNKNOWN,
+ eCSSFontDesc_COUNT - 1);
+
+ aResult.Truncate();
+ if (aFontDescID == eCSSFontDesc_UNKNOWN)
+ return NS_OK;
+
+ const nsCSSValue& val = mDescriptors.Get(aFontDescID);
+
+ if (val.GetUnit() == eCSSUnit_Null) {
+ // Avoid having to check no-value in the Family and Src cases below.
+ return NS_OK;
+ }
+
+ switch (aFontDescID) {
+ case eCSSFontDesc_Family: {
+ // we don't use nsCSSValue::AppendToString here because it doesn't
+ // canonicalize the way we want, and anyway it's overkill when
+ // we know we have eCSSUnit_String
+ NS_ASSERTION(val.GetUnit() == eCSSUnit_String, "unexpected unit");
+ nsDependentString family(val.GetStringBufferValue());
+ nsStyleUtil::AppendEscapedCSSString(family, aResult);
+ return NS_OK;
+ }
+
+ case eCSSFontDesc_Style:
+ val.AppendToString(eCSSProperty_font_style, aResult,
+ nsCSSValue::eNormalized);
+ return NS_OK;
+
+ case eCSSFontDesc_Weight:
+ val.AppendToString(eCSSProperty_font_weight, aResult,
+ nsCSSValue::eNormalized);
+ return NS_OK;
+
+ case eCSSFontDesc_Stretch:
+ val.AppendToString(eCSSProperty_font_stretch, aResult,
+ nsCSSValue::eNormalized);
+ return NS_OK;
+
+ case eCSSFontDesc_FontFeatureSettings:
+ nsStyleUtil::AppendFontFeatureSettings(val, aResult);
+ return NS_OK;
+
+ case eCSSFontDesc_FontLanguageOverride:
+ val.AppendToString(eCSSProperty_font_language_override, aResult,
+ nsCSSValue::eNormalized);
+ return NS_OK;
+
+ case eCSSFontDesc_Display:
+ NS_ASSERTION(val.GetUnit() == eCSSUnit_Enumerated,
+ "unknown unit for font-display descriptor");
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(val.GetIntValue(),
+ nsCSSProps::kFontDisplayKTable), aResult);
+ return NS_OK;
+
+ case eCSSFontDesc_Src:
+ nsStyleUtil::AppendSerializedFontSrc(val, aResult);
+ return NS_OK;
+
+ case eCSSFontDesc_UnicodeRange:
+ nsStyleUtil::AppendUnicodeRange(val, aResult);
+ return NS_OK;
+
+ case eCSSFontDesc_UNKNOWN:
+ case eCSSFontDesc_COUNT:
+ ;
+ }
+ NS_NOTREACHED("nsCSSFontFaceStyleDecl::GetPropertyValue: "
+ "out-of-range value got to the switch");
+ return NS_ERROR_INVALID_ARG;
+}
+
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetCssText(nsAString & aCssText)
+{
+ nsAutoString descStr;
+
+ aCssText.Truncate();
+ for (nsCSSFontDesc id = nsCSSFontDesc(eCSSFontDesc_UNKNOWN + 1);
+ id < eCSSFontDesc_COUNT;
+ id = nsCSSFontDesc(id + 1)) {
+ if (mDescriptors.Get(id).GetUnit() != eCSSUnit_Null &&
+ NS_SUCCEEDED(GetPropertyValue(id, descStr))) {
+ NS_ASSERTION(descStr.Length() > 0,
+ "GetCssText: non-null unit, empty property value");
+ aCssText.AppendLiteral(" ");
+ aCssText.AppendASCII(nsCSSProps::GetStringValue(id).get());
+ aCssText.AppendLiteral(": ");
+ aCssText.Append(descStr);
+ aCssText.AppendLiteral(";\n");
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::SetCssText(const nsAString & aCssText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED; // bug 443978
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetPropertyValue(const nsAString & propertyName,
+ nsAString & aResult)
+{
+ return GetPropertyValue(nsCSSProps::LookupFontDesc(propertyName), aResult);
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetAuthoredPropertyValue(const nsAString& propertyName,
+ nsAString& aResult)
+{
+ // We don't return any authored property values different from
+ // GetPropertyValue, currently.
+ return GetPropertyValue(nsCSSProps::LookupFontDesc(propertyName), aResult);
+}
+
+already_AddRefed<dom::CSSValue>
+nsCSSFontFaceStyleDecl::GetPropertyCSSValue(const nsAString & propertyName,
+ ErrorResult& aRv)
+{
+ // ??? nsDOMCSSDeclaration returns null/NS_OK, but that seems wrong.
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::RemoveProperty(const nsAString & propertyName,
+ nsAString & aResult)
+{
+ nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(propertyName);
+ NS_ASSERTION(descID >= eCSSFontDesc_UNKNOWN &&
+ descID < eCSSFontDesc_COUNT,
+ "LookupFontDesc returned value out of range");
+
+ if (descID == eCSSFontDesc_UNKNOWN) {
+ aResult.Truncate();
+ } else {
+ nsresult rv = GetPropertyValue(descID, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDescriptors.Get(descID).Reset();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetPropertyPriority(const nsAString & propertyName,
+ nsAString & aResult)
+{
+ // font descriptors do not have priorities at present
+ aResult.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::SetProperty(const nsAString & propertyName,
+ const nsAString & value,
+ const nsAString & priority)
+{
+ return NS_ERROR_NOT_IMPLEMENTED; // bug 443978
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetLength(uint32_t *aLength)
+{
+ uint32_t len = 0;
+ for (nsCSSFontDesc id = nsCSSFontDesc(eCSSFontDesc_UNKNOWN + 1);
+ id < eCSSFontDesc_COUNT;
+ id = nsCSSFontDesc(id + 1))
+ if (mDescriptors.Get(id).GetUnit() != eCSSUnit_Null)
+ len++;
+
+ *aLength = len;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::Item(uint32_t aIndex, nsAString& aReturn)
+{
+ bool found;
+ IndexedGetter(aIndex, found, aReturn);
+ if (!found) {
+ aReturn.Truncate();
+ }
+ return NS_OK;
+}
+
+void
+nsCSSFontFaceStyleDecl::IndexedGetter(uint32_t index, bool& aFound, nsAString & aResult)
+{
+ int32_t nset = -1;
+ for (nsCSSFontDesc id = nsCSSFontDesc(eCSSFontDesc_UNKNOWN + 1);
+ id < eCSSFontDesc_COUNT;
+ id = nsCSSFontDesc(id + 1)) {
+ if (mDescriptors.Get(id).GetUnit() != eCSSUnit_Null) {
+ nset++;
+ if (nset == int32_t(index)) {
+ aFound = true;
+ aResult.AssignASCII(nsCSSProps::GetStringValue(id).get());
+ return;
+ }
+ }
+ }
+ aFound = false;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ NS_IF_ADDREF(*aParentRule = ContainingRule()->GetDOMRule());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetPropertyValue(const nsCSSPropertyID aPropID,
+ nsAString& aValue)
+{
+ return
+ GetPropertyValue(NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aPropID)),
+ aValue);
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::SetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsAString& aValue)
+{
+ return SetProperty(NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aPropID)),
+ aValue, EmptyString());
+}
+
+nsINode*
+nsCSSFontFaceStyleDecl::GetParentObject()
+{
+ return ContainingRule()->GetDocument();
+}
+
+JSObject*
+nsCSSFontFaceStyleDecl::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return mozilla::dom::CSSStyleDeclarationBinding::Wrap(cx, this, aGivenProto);
+}
+
+// -------------------------------------------
+// nsCSSFontFaceRule
+//
+
+/* virtual */ already_AddRefed<css::Rule>
+nsCSSFontFaceRule::Clone() const
+{
+ RefPtr<css::Rule> clone = new nsCSSFontFaceRule(*this);
+ return clone.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSFontFaceRule)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSFontFaceRule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsCSSFontFaceRule)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsCSSFontFaceRule)
+ // 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(nsCSSFontFaceRule)
+ // Unlink the wrapper for our declaraton. This just expands out
+ // NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER which we can't use
+ // directly because the wrapper is on the declaration, not on us.
+ tmp->mDecl.ReleaseWrapper(static_cast<nsISupports*>(p));
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCSSFontFaceRule)
+ // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS will call into our
+ // Trace hook, where we do the right thing with declarations already.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// QueryInterface implementation for nsCSSFontFaceRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSFontFaceRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSFontFaceRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSFontFaceRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT(nsCSSFontFaceRule, Rule)
+
+#ifdef DEBUG
+void
+nsCSSFontFaceRule::List(FILE* out, int32_t aIndent) const
+{
+ nsCString baseInd, descInd;
+ for (int32_t indent = aIndent; --indent >= 0; ) {
+ baseInd.AppendLiteral(" ");
+ descInd.AppendLiteral(" ");
+ }
+ descInd.AppendLiteral(" ");
+
+ nsString descStr;
+
+ fprintf_stderr(out, "%s@font-face {\n", baseInd.get());
+ for (nsCSSFontDesc id = nsCSSFontDesc(eCSSFontDesc_UNKNOWN + 1);
+ id < eCSSFontDesc_COUNT;
+ id = nsCSSFontDesc(id + 1))
+ if (mDecl.mDescriptors.Get(id).GetUnit() != eCSSUnit_Null) {
+ if (NS_FAILED(mDecl.GetPropertyValue(id, descStr)))
+ descStr.AssignLiteral("#<serialization error>");
+ else if (descStr.Length() == 0)
+ descStr.AssignLiteral("#<serialization missing>");
+ fprintf_stderr(out, "%s%s: %s\n",
+ descInd.get(), nsCSSProps::GetStringValue(id).get(),
+ NS_ConvertUTF16toUTF8(descStr).get());
+ }
+ fprintf_stderr(out, "%s}\n", baseInd.get());
+}
+#endif
+
+/* virtual */ int32_t
+nsCSSFontFaceRule::GetType() const
+{
+ return Rule::FONT_FACE_RULE;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::FONT_FACE_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetCssText(nsAString& aCssText)
+{
+ nsAutoString propText;
+ mDecl.GetCssText(propText);
+
+ aCssText.AssignLiteral("@font-face {\n");
+ aCssText.Append(propText);
+ aCssText.Append('}');
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::SetCssText(const nsAString& aCssText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED; // bug 443978
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return Rule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return Rule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+nsCSSFontFaceRule::GetCSSRule()
+{
+ return Rule::GetCSSRule();
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetStyle(nsIDOMCSSStyleDeclaration** aStyle)
+{
+ NS_IF_ADDREF(*aStyle = &mDecl);
+ return NS_OK;
+}
+
+// Arguably these should forward to nsCSSFontFaceStyleDecl methods.
+void
+nsCSSFontFaceRule::SetDesc(nsCSSFontDesc aDescID, nsCSSValue const & aValue)
+{
+ NS_PRECONDITION(aDescID > eCSSFontDesc_UNKNOWN &&
+ aDescID < eCSSFontDesc_COUNT,
+ "aDescID out of range in nsCSSFontFaceRule::SetDesc");
+
+ // FIXME: handle dynamic changes
+
+ mDecl.mDescriptors.Get(aDescID) = aValue;
+}
+
+void
+nsCSSFontFaceRule::GetDesc(nsCSSFontDesc aDescID, nsCSSValue & aValue)
+{
+ NS_PRECONDITION(aDescID > eCSSFontDesc_UNKNOWN &&
+ aDescID < eCSSFontDesc_COUNT,
+ "aDescID out of range in nsCSSFontFaceRule::GetDesc");
+
+ aValue = mDecl.mDescriptors.Get(aDescID);
+}
+
+/* virtual */ size_t
+nsCSSFontFaceRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mDecl
+}
+
+
+// -----------------------------------
+// nsCSSFontFeatureValuesRule
+//
+
+/* virtual */ already_AddRefed<css::Rule>
+nsCSSFontFeatureValuesRule::Clone() const
+{
+ RefPtr<css::Rule> clone = new nsCSSFontFeatureValuesRule(*this);
+ return clone.forget();
+}
+
+NS_IMPL_ADDREF(nsCSSFontFeatureValuesRule)
+NS_IMPL_RELEASE(nsCSSFontFeatureValuesRule)
+
+// QueryInterface implementation for nsCSSFontFeatureValuesRule
+NS_INTERFACE_MAP_BEGIN(nsCSSFontFeatureValuesRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSFontFeatureValuesRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSFontFeatureValuesRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT(nsCSSFontFeatureValuesRule, Rule)
+
+static void
+FeatureValuesToString(
+ const nsTArray<gfxFontFeatureValueSet::FeatureValues>& aFeatureValues,
+ nsAString& aOutStr)
+{
+ uint32_t i, n;
+
+ // append values
+ n = aFeatureValues.Length();
+ for (i = 0; i < n; i++) {
+ const gfxFontFeatureValueSet::FeatureValues& fv = aFeatureValues[i];
+
+ // @alternate
+ aOutStr.AppendLiteral(" @");
+ nsAutoString functAlt;
+ nsStyleUtil::GetFunctionalAlternatesName(fv.alternate, functAlt);
+ aOutStr.Append(functAlt);
+ aOutStr.AppendLiteral(" {");
+
+ // for each ident-values tuple
+ uint32_t j, numValues = fv.valuelist.Length();
+ for (j = 0; j < numValues; j++) {
+ aOutStr.Append(' ');
+ const gfxFontFeatureValueSet::ValueList& vlist = fv.valuelist[j];
+ nsStyleUtil::AppendEscapedCSSIdent(vlist.name, aOutStr);
+ aOutStr.Append(':');
+
+ uint32_t k, numSelectors = vlist.featureSelectors.Length();
+ for (k = 0; k < numSelectors; k++) {
+ aOutStr.Append(' ');
+ aOutStr.AppendInt(vlist.featureSelectors[k]);
+ }
+
+ aOutStr.Append(';');
+ }
+ aOutStr.AppendLiteral(" }\n");
+ }
+}
+
+static void
+FontFeatureValuesRuleToString(
+ const mozilla::FontFamilyList& aFamilyList,
+ const nsTArray<gfxFontFeatureValueSet::FeatureValues>& aFeatureValues,
+ nsAString& aOutStr)
+{
+ aOutStr.AssignLiteral("@font-feature-values ");
+ nsAutoString familyListStr, valueTextStr;
+ nsStyleUtil::AppendEscapedCSSFontFamilyList(aFamilyList, familyListStr);
+ aOutStr.Append(familyListStr);
+ aOutStr.AppendLiteral(" {\n");
+ FeatureValuesToString(aFeatureValues, valueTextStr);
+ aOutStr.Append(valueTextStr);
+ aOutStr.Append('}');
+}
+
+#ifdef DEBUG
+void
+nsCSSFontFeatureValuesRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoString text;
+ FontFeatureValuesRuleToString(mFamilyList, mFeatureValues, text);
+ NS_ConvertUTF16toUTF8 utf8(text);
+
+ // replace newlines with newlines plus indent spaces
+ char* indent = new char[(aIndent + 1) * 2];
+ int32_t i;
+ for (i = 1; i < (aIndent + 1) * 2 - 1; i++) {
+ indent[i] = 0x20;
+ }
+ indent[0] = 0xa;
+ indent[aIndent * 2 + 1] = 0;
+ utf8.ReplaceSubstring("\n", indent);
+ delete [] indent;
+
+ nsAutoCString indentStr;
+ for (i = aIndent; --i >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+ fprintf_stderr(out, "%s%s\n", indentStr.get(), utf8.get());
+}
+#endif
+
+/* virtual */ int32_t
+nsCSSFontFeatureValuesRule::GetType() const
+{
+ return Rule::FONT_FEATURE_VALUES_RULE;
+}
+
+NS_IMETHODIMP
+nsCSSFontFeatureValuesRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::FONT_FEATURE_VALUES_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFeatureValuesRule::GetCssText(nsAString& aCssText)
+{
+ FontFeatureValuesRuleToString(mFamilyList, mFeatureValues, aCssText);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFeatureValuesRule::SetCssText(const nsAString& aCssText)
+{
+ // FIXME: implement???
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCSSFontFeatureValuesRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return Rule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+nsCSSFontFeatureValuesRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return Rule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+nsCSSFontFeatureValuesRule::GetCSSRule()
+{
+ return Rule::GetCSSRule();
+}
+
+NS_IMETHODIMP
+nsCSSFontFeatureValuesRule::GetFontFamily(nsAString& aFamilyListStr)
+{
+ nsStyleUtil::AppendEscapedCSSFontFamilyList(mFamilyList, aFamilyListStr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFeatureValuesRule::SetFontFamily(const nsAString& aFontFamily)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCSSFontFeatureValuesRule::GetValueText(nsAString& aValueText)
+{
+ FeatureValuesToString(mFeatureValues, aValueText);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFeatureValuesRule::SetValueText(const nsAString& aValueText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+struct MakeFamilyArray {
+ explicit MakeFamilyArray(nsTArray<nsString>& aFamilyArray)
+ : familyArray(aFamilyArray), hasGeneric(false)
+ {}
+
+ static bool
+ AddFamily(const nsString& aFamily, bool aGeneric, void* aData)
+ {
+ MakeFamilyArray *familyArr = reinterpret_cast<MakeFamilyArray*> (aData);
+ if (!aGeneric && !aFamily.IsEmpty()) {
+ familyArr->familyArray.AppendElement(aFamily);
+ }
+ if (aGeneric) {
+ familyArr->hasGeneric = true;
+ }
+ return true;
+ }
+
+ nsTArray<nsString>& familyArray;
+ bool hasGeneric;
+};
+
+void
+nsCSSFontFeatureValuesRule::SetFamilyList(
+ const mozilla::FontFamilyList& aFamilyList)
+{
+ mFamilyList = aFamilyList;
+}
+
+void
+nsCSSFontFeatureValuesRule::AddValueList(int32_t aVariantAlternate,
+ nsTArray<gfxFontFeatureValueSet::ValueList>& aValueList)
+{
+ uint32_t i, len = mFeatureValues.Length();
+ bool foundAlternate = false;
+
+ // add to an existing list for a given property value
+ for (i = 0; i < len; i++) {
+ gfxFontFeatureValueSet::FeatureValues& f = mFeatureValues.ElementAt(i);
+
+ if (f.alternate == uint32_t(aVariantAlternate)) {
+ f.valuelist.AppendElements(aValueList);
+ foundAlternate = true;
+ break;
+ }
+ }
+
+ // create a new list for a given property value
+ if (!foundAlternate) {
+ gfxFontFeatureValueSet::FeatureValues &f = *mFeatureValues.AppendElement();
+ f.alternate = aVariantAlternate;
+ f.valuelist.AppendElements(aValueList);
+ }
+}
+
+size_t
+nsCSSFontFeatureValuesRule::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this);
+}
+
+// -------------------------------------------
+// nsCSSKeyframeStyleDeclaration
+//
+
+nsCSSKeyframeStyleDeclaration::nsCSSKeyframeStyleDeclaration(nsCSSKeyframeRule *aRule)
+ : mRule(aRule)
+{
+}
+
+nsCSSKeyframeStyleDeclaration::~nsCSSKeyframeStyleDeclaration()
+{
+ NS_ASSERTION(!mRule, "DropReference not called.");
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSKeyframeStyleDeclaration)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSKeyframeStyleDeclaration)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsCSSKeyframeStyleDeclaration)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSKeyframeStyleDeclaration)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
+
+DeclarationBlock*
+nsCSSKeyframeStyleDeclaration::GetCSSDeclaration(Operation aOperation)
+{
+ if (mRule) {
+ return mRule->Declaration();
+ } else {
+ return nullptr;
+ }
+}
+
+void
+nsCSSKeyframeStyleDeclaration::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
+{
+ GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeStyleDeclaration::GetParentRule(nsIDOMCSSRule **aParent)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+
+ NS_IF_ADDREF(*aParent = mRule);
+ return NS_OK;
+}
+
+nsresult
+nsCSSKeyframeStyleDeclaration::SetCSSDeclaration(DeclarationBlock* aDecl)
+{
+ MOZ_ASSERT(aDecl, "must be non-null");
+ mRule->ChangeDeclaration(aDecl->AsGecko());
+ return NS_OK;
+}
+
+nsIDocument*
+nsCSSKeyframeStyleDeclaration::DocToUpdate()
+{
+ return nullptr;
+}
+
+nsINode*
+nsCSSKeyframeStyleDeclaration::GetParentObject()
+{
+ return mRule ? mRule->GetDocument() : nullptr;
+}
+
+// -------------------------------------------
+// nsCSSKeyframeRule
+//
+
+nsCSSKeyframeRule::nsCSSKeyframeRule(const nsCSSKeyframeRule& aCopy)
+ // copy everything except our reference count and mDOMDeclaration
+ : Rule(aCopy)
+ , mKeys(aCopy.mKeys)
+ , mDeclaration(new css::Declaration(*aCopy.mDeclaration))
+{
+ mDeclaration->SetOwningRule(this);
+}
+
+nsCSSKeyframeRule::~nsCSSKeyframeRule()
+{
+ mDeclaration->SetOwningRule(nullptr);
+ if (mDOMDeclaration) {
+ mDOMDeclaration->DropReference();
+ }
+}
+
+/* virtual */ already_AddRefed<css::Rule>
+nsCSSKeyframeRule::Clone() const
+{
+ RefPtr<css::Rule> clone = new nsCSSKeyframeRule(*this);
+ return clone.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSKeyframeRule)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSKeyframeRule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsCSSKeyframeRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCSSKeyframeRule)
+ if (tmp->mDOMDeclaration) {
+ tmp->mDOMDeclaration->DropReference();
+ tmp->mDOMDeclaration = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCSSKeyframeRule)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMDeclaration)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// QueryInterface implementation for nsCSSKeyframeRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSKeyframeRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSKeyframeRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSKeyframeRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT_GET_DOM_RULE_WEAK(nsCSSKeyframeRule, Rule)
+
+#ifdef DEBUG
+void
+nsCSSKeyframeRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString str;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+
+ nsAutoString tmp;
+ DoGetKeyText(tmp);
+ AppendUTF16toUTF8(tmp, str);
+ str.AppendLiteral(" { ");
+ mDeclaration->ToString(tmp);
+ AppendUTF16toUTF8(tmp, str);
+ str.AppendLiteral("}\n");
+ fprintf_stderr(out, "%s", str.get());
+}
+#endif
+
+/* virtual */ int32_t
+nsCSSKeyframeRule::GetType() const
+{
+ return Rule::KEYFRAME_RULE;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::KEYFRAME_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetCssText(nsAString& aCssText)
+{
+ DoGetKeyText(aCssText);
+ aCssText.AppendLiteral(" { ");
+ nsAutoString tmp;
+ mDeclaration->ToString(tmp);
+ aCssText.Append(tmp);
+ aCssText.AppendLiteral(" }");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::SetCssText(const nsAString& aCssText)
+{
+ // FIXME: implement???
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return Rule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return Rule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+nsCSSKeyframeRule::GetCSSRule()
+{
+ return Rule::GetCSSRule();
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetKeyText(nsAString& aKeyText)
+{
+ DoGetKeyText(aKeyText);
+ return NS_OK;
+}
+
+void
+nsCSSKeyframeRule::DoGetKeyText(nsAString& aKeyText) const
+{
+ aKeyText.Truncate();
+ uint32_t i = 0, i_end = mKeys.Length();
+ MOZ_ASSERT(i_end != 0, "must have some keys");
+ for (;;) {
+ aKeyText.AppendFloat(mKeys[i] * 100.0f);
+ aKeyText.Append(char16_t('%'));
+ if (++i == i_end) {
+ break;
+ }
+ aKeyText.AppendLiteral(", ");
+ }
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::SetKeyText(const nsAString& aKeyText)
+{
+ nsCSSParser parser;
+
+ InfallibleTArray<float> newSelectors;
+ // FIXME: pass filename and line number
+ if (!parser.ParseKeyframeSelectorString(aKeyText, nullptr, 0, newSelectors)) {
+ // for now, we don't do anything if the parse fails
+ return NS_OK;
+ }
+
+ nsIDocument* doc = GetDocument();
+ MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
+
+ newSelectors.SwapElements(mKeys);
+
+ CSSStyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->SetModifiedByChildRule();
+
+ if (doc) {
+ doc->StyleRuleChanged(sheet, this);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetStyle(nsIDOMCSSStyleDeclaration** aStyle)
+{
+ if (!mDOMDeclaration) {
+ mDOMDeclaration = new nsCSSKeyframeStyleDeclaration(this);
+ }
+ NS_ADDREF(*aStyle = mDOMDeclaration);
+ return NS_OK;
+}
+
+void
+nsCSSKeyframeRule::ChangeDeclaration(css::Declaration* aDeclaration)
+{
+ // Our caller already did a BeginUpdate/EndUpdate, but with
+ // UPDATE_CONTENT, and we need UPDATE_STYLE to trigger work in
+ // PresShell::EndUpdate.
+ nsIDocument* doc = GetDocument();
+ MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
+
+ if (aDeclaration != mDeclaration) {
+ mDeclaration->SetOwningRule(nullptr);
+ mDeclaration = aDeclaration;
+ mDeclaration->SetOwningRule(this);
+ }
+
+ CSSStyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->SetModifiedByChildRule();
+
+ if (doc) {
+ doc->StyleRuleChanged(sheet, this);
+ }
+ }
+}
+
+/* virtual */ size_t
+nsCSSKeyframeRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mKeys
+ // - mDeclaration
+ // - mDOMDeclaration
+}
+
+
+// -------------------------------------------
+// nsCSSKeyframesRule
+//
+
+nsCSSKeyframesRule::nsCSSKeyframesRule(const nsCSSKeyframesRule& aCopy)
+ // copy everything except our reference count. GroupRule's copy
+ // constructor also doesn't copy the lazily-constructed
+ // mRuleCollection.
+ : GroupRule(aCopy),
+ mName(aCopy.mName)
+{
+}
+
+nsCSSKeyframesRule::~nsCSSKeyframesRule()
+{
+}
+
+/* virtual */ already_AddRefed<css::Rule>
+nsCSSKeyframesRule::Clone() const
+{
+ RefPtr<css::Rule> clone = new nsCSSKeyframesRule(*this);
+ return clone.forget();
+}
+
+NS_IMPL_ADDREF_INHERITED(nsCSSKeyframesRule, css::GroupRule)
+NS_IMPL_RELEASE_INHERITED(nsCSSKeyframesRule, css::GroupRule)
+
+// QueryInterface implementation for nsCSSKeyframesRule
+NS_INTERFACE_MAP_BEGIN(nsCSSKeyframesRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSKeyframesRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSKeyframesRule)
+NS_INTERFACE_MAP_END_INHERITING(GroupRule)
+
+#ifdef DEBUG
+void
+nsCSSKeyframesRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indentStr;
+ for (int32_t indent = aIndent; --indent >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+
+ fprintf_stderr(out, "%s@keyframes %s {\n",
+ indentStr.get(), NS_ConvertUTF16toUTF8(mName).get());
+
+ GroupRule::List(out, aIndent);
+
+ fprintf_stderr(out, "%s}\n", indentStr.get());
+}
+#endif
+
+/* virtual */ int32_t
+nsCSSKeyframesRule::GetType() const
+{
+ return Rule::KEYFRAMES_RULE;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::KEYFRAMES_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetCssText(nsAString& aCssText)
+{
+ aCssText.AssignLiteral("@keyframes ");
+ aCssText.Append(mName);
+ aCssText.AppendLiteral(" {\n");
+ nsAutoString tmp;
+ for (uint32_t i = 0, i_end = mRules.Count(); i != i_end; ++i) {
+ static_cast<nsCSSKeyframeRule*>(mRules[i])->GetCssText(tmp);
+ aCssText.Append(tmp);
+ aCssText.Append('\n');
+ }
+ aCssText.Append('}');
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::SetCssText(const nsAString& aCssText)
+{
+ // FIXME: implement???
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return GroupRule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return GroupRule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+nsCSSKeyframesRule::GetCSSRule()
+{
+ return GroupRule::GetCSSRule();
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetName(nsAString& aName)
+{
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::SetName(const nsAString& aName)
+{
+ if (mName == aName) {
+ return NS_OK;
+ }
+
+ nsIDocument* doc = GetDocument();
+ MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
+
+ mName = aName;
+
+ CSSStyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->SetModifiedByChildRule();
+
+ if (doc) {
+ doc->StyleRuleChanged(sheet, this);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetCssRules(nsIDOMCSSRuleList* *aRuleList)
+{
+ return GroupRule::GetCssRules(aRuleList);
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::AppendRule(const nsAString& aRule)
+{
+ // The spec is confusing, and I think we should just append the rule,
+ // which also turns out to match WebKit:
+ // http://lists.w3.org/Archives/Public/www-style/2011Apr/0034.html
+ nsCSSParser parser;
+
+ // FIXME: pass filename and line number
+ RefPtr<nsCSSKeyframeRule> rule =
+ parser.ParseKeyframeRule(aRule, nullptr, 0);
+ if (rule) {
+ nsIDocument* doc = GetDocument();
+ MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
+
+ AppendStyleRule(rule);
+
+ CSSStyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->SetModifiedByChildRule();
+
+ if (doc) {
+ doc->StyleRuleChanged(sheet, this);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static const uint32_t RULE_NOT_FOUND = uint32_t(-1);
+
+uint32_t
+nsCSSKeyframesRule::FindRuleIndexForKey(const nsAString& aKey)
+{
+ nsCSSParser parser;
+
+ InfallibleTArray<float> keys;
+ // FIXME: pass filename and line number
+ if (parser.ParseKeyframeSelectorString(aKey, nullptr, 0, keys)) {
+ // The spec isn't clear, but we'll match on the key list, which
+ // mostly matches what WebKit does, except we'll do last-match
+ // instead of first-match, and handling parsing differences better.
+ // http://lists.w3.org/Archives/Public/www-style/2011Apr/0036.html
+ // http://lists.w3.org/Archives/Public/www-style/2011Apr/0037.html
+ for (uint32_t i = mRules.Count(); i-- != 0; ) {
+ if (static_cast<nsCSSKeyframeRule*>(mRules[i])->GetKeys() == keys) {
+ return i;
+ }
+ }
+ }
+
+ return RULE_NOT_FOUND;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::DeleteRule(const nsAString& aKey)
+{
+ uint32_t index = FindRuleIndexForKey(aKey);
+ if (index != RULE_NOT_FOUND) {
+ nsIDocument* doc = GetDocument();
+ MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
+
+ DeleteStyleRuleAt(index);
+
+ CSSStyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->SetModifiedByChildRule();
+
+ if (doc) {
+ doc->StyleRuleChanged(sheet, this);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::FindRule(const nsAString& aKey,
+ nsIDOMCSSKeyframeRule** aResult)
+{
+ uint32_t index = FindRuleIndexForKey(aKey);
+ if (index == RULE_NOT_FOUND) {
+ *aResult = nullptr;
+ } else {
+ NS_ADDREF(*aResult = static_cast<nsCSSKeyframeRule*>(mRules[index]));
+ }
+ return NS_OK;
+}
+
+// GroupRule interface
+/* virtual */ bool
+nsCSSKeyframesRule::UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey)
+{
+ MOZ_ASSERT(false, "should not be called");
+ return false;
+}
+
+/* virtual */ size_t
+nsCSSKeyframesRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ n += GroupRule::SizeOfExcludingThis(aMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mName
+
+ return n;
+}
+
+// -------------------------------------------
+// nsCSSPageStyleDeclaration
+//
+
+nsCSSPageStyleDeclaration::nsCSSPageStyleDeclaration(nsCSSPageRule* aRule)
+ : mRule(aRule)
+{
+}
+
+nsCSSPageStyleDeclaration::~nsCSSPageStyleDeclaration()
+{
+ NS_ASSERTION(!mRule, "DropReference not called.");
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSPageStyleDeclaration)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSPageStyleDeclaration)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsCSSPageStyleDeclaration)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSPageStyleDeclaration)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
+
+DeclarationBlock*
+nsCSSPageStyleDeclaration::GetCSSDeclaration(Operation aOperation)
+{
+ if (mRule) {
+ return mRule->Declaration();
+ } else {
+ return nullptr;
+ }
+}
+
+void
+nsCSSPageStyleDeclaration::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
+{
+ GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
+}
+
+NS_IMETHODIMP
+nsCSSPageStyleDeclaration::GetParentRule(nsIDOMCSSRule** aParent)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+
+ NS_IF_ADDREF(*aParent = mRule);
+ return NS_OK;
+}
+
+nsresult
+nsCSSPageStyleDeclaration::SetCSSDeclaration(DeclarationBlock* aDecl)
+{
+ MOZ_ASSERT(aDecl, "must be non-null");
+ mRule->ChangeDeclaration(aDecl->AsGecko());
+ return NS_OK;
+}
+
+nsIDocument*
+nsCSSPageStyleDeclaration::DocToUpdate()
+{
+ return nullptr;
+}
+
+nsINode*
+nsCSSPageStyleDeclaration::GetParentObject()
+{
+ return mRule ? mRule->GetDocument() : nullptr;
+}
+
+// -------------------------------------------
+// nsCSSPageRule
+//
+
+nsCSSPageRule::nsCSSPageRule(const nsCSSPageRule& aCopy)
+ // copy everything except our reference count and mDOMDeclaration
+ : Rule(aCopy)
+ , mDeclaration(new css::Declaration(*aCopy.mDeclaration))
+{
+ mDeclaration->SetOwningRule(this);
+}
+
+nsCSSPageRule::~nsCSSPageRule()
+{
+ mDeclaration->SetOwningRule(nullptr);
+ if (mDOMDeclaration) {
+ mDOMDeclaration->DropReference();
+ }
+}
+
+/* virtual */ already_AddRefed<css::Rule>
+nsCSSPageRule::Clone() const
+{
+ RefPtr<css::Rule> clone = new nsCSSPageRule(*this);
+ return clone.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSPageRule)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSPageRule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsCSSPageRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCSSPageRule)
+ if (tmp->mDOMDeclaration) {
+ tmp->mDOMDeclaration->DropReference();
+ tmp->mDOMDeclaration = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCSSPageRule)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMDeclaration)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// QueryInterface implementation for nsCSSPageRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSPageRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSPageRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSPageRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT_GET_DOM_RULE_WEAK(nsCSSPageRule, Rule)
+
+#ifdef DEBUG
+void
+nsCSSPageRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString str;
+ for (int32_t indent = aIndent; --indent >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+
+ str.AppendLiteral("@page { ");
+ nsAutoString tmp;
+ mDeclaration->ToString(tmp);
+ AppendUTF16toUTF8(tmp, str);
+ str.AppendLiteral("}\n");
+ fprintf_stderr(out, "%s", str.get());
+}
+#endif
+
+/* virtual */ int32_t
+nsCSSPageRule::GetType() const
+{
+ return Rule::PAGE_RULE;
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::PAGE_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetCssText(nsAString& aCssText)
+{
+ aCssText.AppendLiteral("@page { ");
+ nsAutoString tmp;
+ mDeclaration->ToString(tmp);
+ aCssText.Append(tmp);
+ aCssText.AppendLiteral(" }");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::SetCssText(const nsAString& aCssText)
+{
+ // FIXME: implement???
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return Rule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return Rule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+nsCSSPageRule::GetCSSRule()
+{
+ return Rule::GetCSSRule();
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetStyle(nsIDOMCSSStyleDeclaration** aStyle)
+{
+ if (!mDOMDeclaration) {
+ mDOMDeclaration = new nsCSSPageStyleDeclaration(this);
+ }
+ NS_ADDREF(*aStyle = mDOMDeclaration);
+ return NS_OK;
+}
+
+void
+nsCSSPageRule::ChangeDeclaration(css::Declaration* aDeclaration)
+{
+ if (aDeclaration != mDeclaration) {
+ mDeclaration->SetOwningRule(nullptr);
+ mDeclaration = aDeclaration;
+ mDeclaration->SetOwningRule(this);
+ }
+
+ CSSStyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->SetModifiedByChildRule();
+ }
+}
+
+/* virtual */ size_t
+nsCSSPageRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this);
+}
+
+namespace mozilla {
+
+CSSSupportsRule::CSSSupportsRule(bool aConditionMet,
+ const nsString& aCondition,
+ uint32_t aLineNumber, uint32_t aColumnNumber)
+ : css::GroupRule(aLineNumber, aColumnNumber)
+ , mUseGroup(aConditionMet)
+ , mCondition(aCondition)
+{
+}
+
+CSSSupportsRule::~CSSSupportsRule()
+{
+}
+
+CSSSupportsRule::CSSSupportsRule(const CSSSupportsRule& aCopy)
+ : css::GroupRule(aCopy),
+ mUseGroup(aCopy.mUseGroup),
+ mCondition(aCopy.mCondition)
+{
+}
+
+#ifdef DEBUG
+/* virtual */ void
+CSSSupportsRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indentStr;
+ for (int32_t indent = aIndent; --indent >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+
+ fprintf_stderr(out, "%s@supports %s {\n",
+ indentStr.get(), NS_ConvertUTF16toUTF8(mCondition).get());
+
+ css::GroupRule::List(out, aIndent);
+
+ fprintf_stderr(out, "%s}\n", indentStr.get());
+}
+#endif
+
+/* virtual */ int32_t
+CSSSupportsRule::GetType() const
+{
+ return Rule::SUPPORTS_RULE;
+}
+
+/* virtual */ already_AddRefed<mozilla::css::Rule>
+CSSSupportsRule::Clone() const
+{
+ RefPtr<css::Rule> clone = new CSSSupportsRule(*this);
+ return clone.forget();
+}
+
+/* virtual */ bool
+CSSSupportsRule::UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey)
+{
+ return mUseGroup;
+}
+
+NS_IMPL_ADDREF_INHERITED(CSSSupportsRule, css::GroupRule)
+NS_IMPL_RELEASE_INHERITED(CSSSupportsRule, css::GroupRule)
+
+// QueryInterface implementation for CSSSupportsRule
+NS_INTERFACE_MAP_BEGIN(CSSSupportsRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSGroupingRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSConditionRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSSupportsRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSSupportsRule)
+NS_INTERFACE_MAP_END_INHERITING(GroupRule)
+
+// nsIDOMCSSRule methods
+NS_IMETHODIMP
+CSSSupportsRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::SUPPORTS_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CSSSupportsRule::GetCssText(nsAString& aCssText)
+{
+ aCssText.AssignLiteral("@supports ");
+ aCssText.Append(mCondition);
+ css::GroupRule::AppendRulesToCssText(aCssText);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CSSSupportsRule::SetCssText(const nsAString& aCssText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CSSSupportsRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return css::GroupRule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+CSSSupportsRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return css::GroupRule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+CSSSupportsRule::GetCSSRule()
+{
+ return css::GroupRule::GetCSSRule();
+}
+
+// nsIDOMCSSGroupingRule methods
+NS_IMETHODIMP
+CSSSupportsRule::GetCssRules(nsIDOMCSSRuleList* *aRuleList)
+{
+ return css::GroupRule::GetCssRules(aRuleList);
+}
+
+NS_IMETHODIMP
+CSSSupportsRule::InsertRule(const nsAString & aRule, uint32_t aIndex, uint32_t* _retval)
+{
+ return css::GroupRule::InsertRule(aRule, aIndex, _retval);
+}
+
+NS_IMETHODIMP
+CSSSupportsRule::DeleteRule(uint32_t aIndex)
+{
+ return css::GroupRule::DeleteRule(aIndex);
+}
+
+// nsIDOMCSSConditionRule methods
+NS_IMETHODIMP
+CSSSupportsRule::GetConditionText(nsAString& aConditionText)
+{
+ aConditionText.Assign(mCondition);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CSSSupportsRule::SetConditionText(const nsAString& aConditionText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* virtual */ size_t
+CSSSupportsRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ n += css::GroupRule::SizeOfExcludingThis(aMallocSizeOf);
+ n += mCondition.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ return n;
+}
+
+} // namespace mozilla
+
+// -------------------------------------------
+// nsCSSCounterStyleRule
+//
+
+nsCSSCounterStyleRule::nsCSSCounterStyleRule(const nsCSSCounterStyleRule& aCopy)
+ : Rule(aCopy)
+ , mName(aCopy.mName)
+ , mGeneration(aCopy.mGeneration)
+{
+ for (size_t i = 0; i < ArrayLength(mValues); ++i) {
+ mValues[i] = aCopy.mValues[i];
+ }
+}
+
+nsCSSCounterStyleRule::~nsCSSCounterStyleRule()
+{
+}
+
+/* virtual */ already_AddRefed<css::Rule>
+nsCSSCounterStyleRule::Clone() const
+{
+ RefPtr<css::Rule> clone = new nsCSSCounterStyleRule(*this);
+ return clone.forget();
+}
+
+nsCSSCounterStyleRule::Getter const
+nsCSSCounterStyleRule::kGetters[] = {
+#define CSS_COUNTER_DESC(name_, method_) &nsCSSCounterStyleRule::Get##method_,
+#include "nsCSSCounterDescList.h"
+#undef CSS_COUNTER_DESC
+};
+
+NS_IMPL_ADDREF(nsCSSCounterStyleRule)
+NS_IMPL_RELEASE(nsCSSCounterStyleRule)
+
+// QueryInterface implementation for nsCSSCounterStyleRule
+NS_INTERFACE_MAP_BEGIN(nsCSSCounterStyleRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSCounterStyleRule)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::css::Rule)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSCounterStyleRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT(nsCSSCounterStyleRule, css::Rule)
+
+#ifdef DEBUG
+void
+nsCSSCounterStyleRule::List(FILE* out, int32_t aIndent) const
+{
+ nsCString baseInd, descInd;
+ for (int32_t indent = aIndent; --indent >= 0; ) {
+ baseInd.AppendLiteral(" ");
+ }
+ descInd = baseInd;
+ descInd.AppendLiteral(" ");
+
+ fprintf_stderr(out, "%s@counter-style %s (rev.%u) {\n",
+ baseInd.get(), NS_ConvertUTF16toUTF8(mName).get(),
+ mGeneration);
+ // TODO
+ fprintf_stderr(out, "%s}\n", baseInd.get());
+}
+#endif
+
+/* virtual */ int32_t
+nsCSSCounterStyleRule::GetType() const
+{
+ return Rule::COUNTER_STYLE_RULE;
+}
+
+// nsIDOMCSSRule methods
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetType(uint16_t* aType)
+{
+ *aType = nsIDOMCSSRule::COUNTER_STYLE_RULE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetCssText(nsAString& aCssText)
+{
+ aCssText.AssignLiteral(u"@counter-style ");
+ nsStyleUtil::AppendEscapedCSSIdent(mName, aCssText);
+ aCssText.AppendLiteral(u" {\n");
+ for (nsCSSCounterDesc id = nsCSSCounterDesc(0);
+ id < eCSSCounterDesc_COUNT;
+ id = nsCSSCounterDesc(id + 1)) {
+ if (mValues[id].GetUnit() != eCSSUnit_Null) {
+ nsAutoString tmp;
+ (this->*kGetters[id])(tmp);
+ aCssText.AppendLiteral(u" ");
+ AppendASCIItoUTF16(nsCSSProps::GetStringValue(id), aCssText);
+ aCssText.AppendLiteral(u": ");
+ aCssText.Append(tmp);
+ aCssText.AppendLiteral(u";\n");
+ }
+ }
+ aCssText.AppendLiteral(u"}");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::SetCssText(const nsAString& aCssText)
+{
+ // FIXME: implement???
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+ return Rule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ return Rule::GetParentRule(aParentRule);
+}
+
+css::Rule*
+nsCSSCounterStyleRule::GetCSSRule()
+{
+ return Rule::GetCSSRule();
+}
+
+// nsIDOMCSSCounterStyleRule methods
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetName(nsAString& aName)
+{
+ aName.Truncate();
+ nsStyleUtil::AppendEscapedCSSIdent(mName, aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::SetName(const nsAString& aName)
+{
+ nsCSSParser parser;
+ nsAutoString name;
+ if (parser.ParseCounterStyleName(aName, nullptr, name)) {
+ nsIDocument* doc = GetDocument();
+ MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
+
+ mName = name;
+
+ CSSStyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->SetModifiedByChildRule();
+ if (doc) {
+ doc->StyleRuleChanged(sheet, this);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+int32_t
+nsCSSCounterStyleRule::GetSystem() const
+{
+ const nsCSSValue& system = GetDesc(eCSSCounterDesc_System);
+ switch (system.GetUnit()) {
+ case eCSSUnit_Enumerated:
+ return system.GetIntValue();
+ case eCSSUnit_Pair:
+ return system.GetPairValue().mXValue.GetIntValue();
+ default:
+ return NS_STYLE_COUNTER_SYSTEM_SYMBOLIC;
+ }
+}
+
+const nsCSSValue&
+nsCSSCounterStyleRule::GetSystemArgument() const
+{
+ const nsCSSValue& system = GetDesc(eCSSCounterDesc_System);
+ MOZ_ASSERT(system.GetUnit() == eCSSUnit_Pair,
+ "Invalid system value");
+ return system.GetPairValue().mYValue;
+}
+
+void
+nsCSSCounterStyleRule::SetDesc(nsCSSCounterDesc aDescID, const nsCSSValue& aValue)
+{
+ MOZ_ASSERT(aDescID >= 0 && aDescID < eCSSCounterDesc_COUNT,
+ "descriptor ID out of range");
+
+ nsIDocument* doc = GetDocument();
+ MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
+
+ mValues[aDescID] = aValue;
+ mGeneration++;
+
+ CSSStyleSheet* sheet = GetStyleSheet();
+ if (sheet) {
+ sheet->SetModifiedByChildRule();
+ if (doc) {
+ doc->StyleRuleChanged(sheet, this);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetSystem(nsAString& aSystem)
+{
+ const nsCSSValue& value = GetDesc(eCSSCounterDesc_System);
+ if (value.GetUnit() == eCSSUnit_Null) {
+ aSystem.Truncate();
+ return NS_OK;
+ }
+
+ aSystem = NS_ConvertASCIItoUTF16(nsCSSProps::ValueToKeyword(
+ GetSystem(), nsCSSProps::kCounterSystemKTable));
+ if (value.GetUnit() == eCSSUnit_Pair) {
+ aSystem.Append(' ');
+ GetSystemArgument().AppendToString(
+ eCSSProperty_UNKNOWN, aSystem, nsCSSValue::eNormalized);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetSymbols(nsAString& aSymbols)
+{
+ const nsCSSValue& value = GetDesc(eCSSCounterDesc_Symbols);
+
+ aSymbols.Truncate();
+ if (value.GetUnit() == eCSSUnit_List) {
+ for (const nsCSSValueList* item = value.GetListValue();
+ item; item = item->mNext) {
+ item->mValue.AppendToString(eCSSProperty_UNKNOWN,
+ aSymbols,
+ nsCSSValue::eNormalized);
+ if (item->mNext) {
+ aSymbols.Append(' ');
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetAdditiveSymbols(nsAString& aSymbols)
+{
+ const nsCSSValue& value = GetDesc(eCSSCounterDesc_AdditiveSymbols);
+
+ aSymbols.Truncate();
+ if (value.GetUnit() == eCSSUnit_PairList) {
+ for (const nsCSSValuePairList* item = value.GetPairListValue();
+ item; item = item->mNext) {
+ item->mXValue.AppendToString(eCSSProperty_UNKNOWN,
+ aSymbols, nsCSSValue::eNormalized);
+ aSymbols.Append(' ');
+ item->mYValue.AppendToString(eCSSProperty_UNKNOWN,
+ aSymbols, nsCSSValue::eNormalized);
+ if (item->mNext) {
+ aSymbols.AppendLiteral(", ");
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetRange(nsAString& aRange)
+{
+ const nsCSSValue& value = GetDesc(eCSSCounterDesc_Range);
+
+ switch (value.GetUnit()) {
+ case eCSSUnit_Auto:
+ aRange.AssignLiteral(u"auto");
+ break;
+
+ case eCSSUnit_PairList:
+ aRange.Truncate();
+ for (const nsCSSValuePairList* item = value.GetPairListValue();
+ item; item = item->mNext) {
+ const nsCSSValue& lower = item->mXValue;
+ const nsCSSValue& upper = item->mYValue;
+ if (lower.GetUnit() == eCSSUnit_Enumerated) {
+ NS_ASSERTION(lower.GetIntValue() ==
+ NS_STYLE_COUNTER_RANGE_INFINITE,
+ "Unrecognized keyword");
+ aRange.AppendLiteral("infinite");
+ } else {
+ aRange.AppendInt(lower.GetIntValue());
+ }
+ aRange.Append(' ');
+ if (upper.GetUnit() == eCSSUnit_Enumerated) {
+ NS_ASSERTION(upper.GetIntValue() ==
+ NS_STYLE_COUNTER_RANGE_INFINITE,
+ "Unrecognized keyword");
+ aRange.AppendLiteral("infinite");
+ } else {
+ aRange.AppendInt(upper.GetIntValue());
+ }
+ if (item->mNext) {
+ aRange.AppendLiteral(", ");
+ }
+ }
+ break;
+
+ default:
+ aRange.Truncate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSCounterStyleRule::GetSpeakAs(nsAString& aSpeakAs)
+{
+ const nsCSSValue& value = GetDesc(eCSSCounterDesc_SpeakAs);
+
+ switch (value.GetUnit()) {
+ case eCSSUnit_Enumerated:
+ switch (value.GetIntValue()) {
+ case NS_STYLE_COUNTER_SPEAKAS_BULLETS:
+ aSpeakAs.AssignLiteral(u"bullets");
+ break;
+ case NS_STYLE_COUNTER_SPEAKAS_NUMBERS:
+ aSpeakAs.AssignLiteral(u"numbers");
+ break;
+ case NS_STYLE_COUNTER_SPEAKAS_WORDS:
+ aSpeakAs.AssignLiteral(u"words");
+ break;
+ case NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT:
+ aSpeakAs.AssignLiteral(u"spell-out");
+ break;
+ default:
+ NS_NOTREACHED("Unknown speech synthesis");
+ }
+ break;
+
+ case eCSSUnit_Auto:
+ case eCSSUnit_Ident:
+ aSpeakAs.Truncate();
+ value.AppendToString(eCSSProperty_UNKNOWN,
+ aSpeakAs, nsCSSValue::eNormalized);
+ break;
+
+ default:
+ aSpeakAs.Truncate();
+ }
+ return NS_OK;
+}
+
+nsresult
+nsCSSCounterStyleRule::GetDescriptor(nsCSSCounterDesc aDescID,
+ nsAString& aValue)
+{
+ NS_ASSERTION(aDescID == eCSSCounterDesc_Negative ||
+ aDescID == eCSSCounterDesc_Prefix ||
+ aDescID == eCSSCounterDesc_Suffix ||
+ aDescID == eCSSCounterDesc_Pad ||
+ aDescID == eCSSCounterDesc_Fallback,
+ "Unexpected descriptor");
+ const nsCSSValue& value = GetDesc(aDescID);
+ aValue.Truncate();
+ if (value.GetUnit() != eCSSUnit_Null) {
+ value.AppendToString(
+ eCSSProperty_UNKNOWN, aValue, nsCSSValue::eNormalized);
+ }
+ return NS_OK;
+}
+
+#define CSS_COUNTER_DESC_GETTER(name_) \
+NS_IMETHODIMP \
+nsCSSCounterStyleRule::Get##name_(nsAString& a##name_) \
+{ \
+ return GetDescriptor(eCSSCounterDesc_##name_, a##name_);\
+}
+CSS_COUNTER_DESC_GETTER(Negative)
+CSS_COUNTER_DESC_GETTER(Prefix)
+CSS_COUNTER_DESC_GETTER(Suffix)
+CSS_COUNTER_DESC_GETTER(Pad)
+CSS_COUNTER_DESC_GETTER(Fallback)
+#undef CSS_COUNTER_DESC_GETTER
+
+/* static */ bool
+nsCSSCounterStyleRule::CheckDescValue(int32_t aSystem,
+ nsCSSCounterDesc aDescID,
+ const nsCSSValue& aValue)
+{
+ switch (aDescID) {
+ case eCSSCounterDesc_System:
+ if (aValue.GetUnit() != eCSSUnit_Pair) {
+ return aValue.GetIntValue() == aSystem;
+ } else {
+ return aValue.GetPairValue().mXValue.GetIntValue() == aSystem;
+ }
+
+ case eCSSCounterDesc_Symbols:
+ switch (aSystem) {
+ case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+ case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+ // for these two system, the list must contain at least 2 elements
+ return aValue.GetListValue()->mNext;
+ case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
+ // for extends system, no symbols should be set
+ return false;
+ default:
+ return true;
+ }
+
+ case eCSSCounterDesc_AdditiveSymbols:
+ switch (aSystem) {
+ case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
+ return false;
+ default:
+ return true;
+ }
+
+ default:
+ return true;
+ }
+}
+
+nsresult
+nsCSSCounterStyleRule::SetDescriptor(nsCSSCounterDesc aDescID,
+ const nsAString& aValue)
+{
+ nsCSSParser parser;
+ nsCSSValue value;
+ CSSStyleSheet* sheet = GetStyleSheet();
+ nsIURI* baseURL = nullptr;
+ nsIPrincipal* principal = nullptr;
+ if (sheet) {
+ baseURL = sheet->GetBaseURI();
+ principal = sheet->Principal();
+ }
+ if (parser.ParseCounterDescriptor(aDescID, aValue, nullptr,
+ baseURL, principal, value)) {
+ if (CheckDescValue(GetSystem(), aDescID, value)) {
+ SetDesc(aDescID, value);
+ }
+ }
+ return NS_OK;
+}
+
+#define CSS_COUNTER_DESC_SETTER(name_) \
+NS_IMETHODIMP \
+nsCSSCounterStyleRule::Set##name_(const nsAString& a##name_) \
+{ \
+ return SetDescriptor(eCSSCounterDesc_##name_, a##name_); \
+}
+CSS_COUNTER_DESC_SETTER(System)
+CSS_COUNTER_DESC_SETTER(Symbols)
+CSS_COUNTER_DESC_SETTER(AdditiveSymbols)
+CSS_COUNTER_DESC_SETTER(Negative)
+CSS_COUNTER_DESC_SETTER(Prefix)
+CSS_COUNTER_DESC_SETTER(Suffix)
+CSS_COUNTER_DESC_SETTER(Range)
+CSS_COUNTER_DESC_SETTER(Pad)
+CSS_COUNTER_DESC_SETTER(Fallback)
+CSS_COUNTER_DESC_SETTER(SpeakAs)
+#undef CSS_COUNTER_DESC_SETTER
+
+/* virtual */ size_t
+nsCSSCounterStyleRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this);
+}
diff --git a/layout/style/nsCSSRules.h b/layout/style/nsCSSRules.h
new file mode 100644
index 000000000..daefaf3f9
--- /dev/null
+++ b/layout/style/nsCSSRules.h
@@ -0,0 +1,673 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rules in a CSS stylesheet other than style rules (e.g., @import rules) */
+
+#ifndef nsCSSRules_h_
+#define nsCSSRules_h_
+
+#include "Declaration.h"
+#include "StyleRule.h"
+#include "gfxFontFeatures.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Move.h"
+#include "mozilla/SheetType.h"
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/dom/FontFace.h"
+#include "nsAutoPtr.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSValue.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsIDOMCSSConditionRule.h"
+#include "nsIDOMCSSCounterStyleRule.h"
+#include "nsIDOMCSSFontFaceRule.h"
+#include "nsIDOMCSSFontFeatureValuesRule.h"
+#include "nsIDOMCSSGroupingRule.h"
+#include "nsIDOMCSSMediaRule.h"
+#include "nsIDOMCSSMozDocumentRule.h"
+#include "nsIDOMCSSPageRule.h"
+#include "nsIDOMCSSSupportsRule.h"
+#include "nsIDOMCSSKeyframeRule.h"
+#include "nsIDOMCSSKeyframesRule.h"
+#include "nsTArray.h"
+
+class nsMediaList;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace css {
+
+class MediaRule final : public GroupRule,
+ public nsIDOMCSSMediaRule
+{
+public:
+ MediaRule(uint32_t aLineNumber, uint32_t aColumnNumber);
+private:
+ MediaRule(const MediaRule& aCopy);
+ ~MediaRule();
+public:
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaRule, GroupRule)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Rule methods
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual void SetStyleSheet(mozilla::CSSStyleSheet* aSheet) override; //override GroupRule
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<Rule> Clone() const override;
+ virtual nsIDOMCSSRule* GetDOMRule() override
+ {
+ return this;
+ }
+ virtual nsIDOMCSSRule* GetExistingDOMRule() override
+ {
+ return this;
+ }
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSGroupingRule interface
+ NS_DECL_NSIDOMCSSGROUPINGRULE
+
+ // nsIDOMCSSConditionRule interface
+ NS_DECL_NSIDOMCSSCONDITIONRULE
+
+ // nsIDOMCSSMediaRule interface
+ NS_DECL_NSIDOMCSSMEDIARULE
+
+ // rest of GroupRule
+ virtual bool UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey) override;
+
+ // @media rule methods
+ nsresult SetMedia(nsMediaList* aMedia);
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ const override MOZ_MUST_OVERRIDE;
+
+protected:
+ void AppendConditionText(nsAString& aOutput);
+
+ RefPtr<nsMediaList> mMedia;
+};
+
+class DocumentRule final : public GroupRule,
+ public nsIDOMCSSMozDocumentRule
+{
+public:
+ DocumentRule(uint32_t aLineNumber, uint32_t aColumnNumber);
+private:
+ DocumentRule(const DocumentRule& aCopy);
+ ~DocumentRule();
+public:
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Rule methods
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<Rule> Clone() const override;
+ virtual nsIDOMCSSRule* GetDOMRule() override
+ {
+ return this;
+ }
+ virtual nsIDOMCSSRule* GetExistingDOMRule() override
+ {
+ return this;
+ }
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSGroupingRule interface
+ NS_DECL_NSIDOMCSSGROUPINGRULE
+
+ // nsIDOMCSSConditionRule interface
+ NS_DECL_NSIDOMCSSCONDITIONRULE
+
+ // nsIDOMCSSMozDocumentRule interface
+ NS_DECL_NSIDOMCSSMOZDOCUMENTRULE
+
+ // rest of GroupRule
+ virtual bool UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey) override;
+
+ bool UseForPresentation(nsPresContext* aPresContext);
+
+ enum Function {
+ eURL,
+ eURLPrefix,
+ eDomain,
+ eRegExp
+ };
+
+ struct URL {
+ Function func;
+ nsCString url;
+ URL *next;
+
+ URL() : next(nullptr) {}
+ URL(const URL& aOther)
+ : func(aOther.func)
+ , url(aOther.url)
+ , next(aOther.next ? new URL(*aOther.next) : nullptr)
+ {
+ }
+ ~URL();
+ };
+
+ void SetURLs(URL *aURLs) { mURLs = aURLs; }
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ const override MOZ_MUST_OVERRIDE;
+
+protected:
+ void AppendConditionText(nsAString& aOutput);
+
+ nsAutoPtr<URL> mURLs; // linked list of |struct URL| above.
+};
+
+} // namespace css
+
+struct CSSFontFaceDescriptors
+{
+#define CSS_FONT_DESC(name_, method_) nsCSSValue m##method_;
+#include "nsCSSFontDescList.h"
+#undef CSS_FONT_DESC
+
+ const nsCSSValue& Get(nsCSSFontDesc aFontDescID) const;
+ nsCSSValue& Get(nsCSSFontDesc aFontDescID);
+
+private:
+ static nsCSSValue CSSFontFaceDescriptors::* const Fields[];
+};
+
+} // namespace mozilla
+
+// A nsCSSFontFaceStyleDecl is always embedded in a nsCSSFontFaceRule.
+class nsCSSFontFaceRule;
+class nsCSSFontFaceStyleDecl final : public nsICSSDeclaration
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMCSSSTYLEDECLARATION_HELPER
+ NS_DECL_NSICSSDECLARATION
+ virtual already_AddRefed<mozilla::dom::CSSValue>
+ GetPropertyCSSValue(const nsAString& aProp, mozilla::ErrorResult& aRv)
+ override;
+ using nsICSSDeclaration::GetPropertyCSSValue;
+
+ virtual nsINode *GetParentObject() override;
+ virtual void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aPropName) override;
+
+ nsresult GetPropertyValue(nsCSSFontDesc aFontDescID,
+ nsAString & aResult) const;
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ ~nsCSSFontFaceStyleDecl() {}
+
+ friend class nsCSSFontFaceRule;
+
+ inline nsCSSFontFaceRule* ContainingRule();
+ inline const nsCSSFontFaceRule* ContainingRule() const;
+
+ mozilla::CSSFontFaceDescriptors mDescriptors;
+
+private:
+ // NOT TO BE IMPLEMENTED
+ // This object cannot be allocated on its own, only as part of
+ // nsCSSFontFaceRule.
+ void* operator new(size_t size) CPP_THROW_NEW;
+};
+
+class nsCSSFontFaceRule final : public mozilla::css::Rule,
+ public nsIDOMCSSFontFaceRule
+{
+public:
+ nsCSSFontFaceRule(uint32_t aLineNumber, uint32_t aColumnNumber)
+ : mozilla::css::Rule(aLineNumber, aColumnNumber) {}
+
+ nsCSSFontFaceRule(const nsCSSFontFaceRule& aCopy)
+ // copy everything except our reference count
+ : mozilla::css::Rule(aCopy), mDecl(aCopy.mDecl) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsCSSFontFaceRule,
+ mozilla::css::Rule)
+
+ // Rule methods
+ DECL_STYLE_RULE_INHERIT
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSFontFaceRule interface
+ NS_DECL_NSIDOMCSSFONTFACERULE
+
+ void SetDesc(nsCSSFontDesc aDescID, nsCSSValue const & aValue);
+ void GetDesc(nsCSSFontDesc aDescID, nsCSSValue & aValue);
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ void GetDescriptors(mozilla::CSSFontFaceDescriptors& aDescriptors) const
+ { aDescriptors = mDecl.mDescriptors; }
+
+protected:
+ ~nsCSSFontFaceRule() {}
+
+ friend class nsCSSFontFaceStyleDecl;
+ nsCSSFontFaceStyleDecl mDecl;
+};
+
+// nsFontFaceRuleContainer - used for associating sheet type with
+// specific @font-face rules
+struct nsFontFaceRuleContainer {
+ RefPtr<nsCSSFontFaceRule> mRule;
+ mozilla::SheetType mSheetType;
+};
+
+inline nsCSSFontFaceRule*
+nsCSSFontFaceStyleDecl::ContainingRule()
+{
+ return reinterpret_cast<nsCSSFontFaceRule*>
+ (reinterpret_cast<char*>(this) - offsetof(nsCSSFontFaceRule, mDecl));
+}
+
+inline const nsCSSFontFaceRule*
+nsCSSFontFaceStyleDecl::ContainingRule() const
+{
+ return reinterpret_cast<const nsCSSFontFaceRule*>
+ (reinterpret_cast<const char*>(this) - offsetof(nsCSSFontFaceRule, mDecl));
+}
+
+class nsCSSFontFeatureValuesRule final : public mozilla::css::Rule,
+ public nsIDOMCSSFontFeatureValuesRule
+{
+public:
+ nsCSSFontFeatureValuesRule(uint32_t aLineNumber, uint32_t aColumnNumber)
+ : mozilla::css::Rule(aLineNumber, aColumnNumber) {}
+
+ nsCSSFontFeatureValuesRule(const nsCSSFontFeatureValuesRule& aCopy)
+ // copy everything except our reference count
+ : mozilla::css::Rule(aCopy),
+ mFamilyList(aCopy.mFamilyList),
+ mFeatureValues(aCopy.mFeatureValues) {}
+
+ NS_DECL_ISUPPORTS
+
+ // Rule methods
+ DECL_STYLE_RULE_INHERIT
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSFontFaceRule interface
+ NS_DECL_NSIDOMCSSFONTFEATUREVALUESRULE
+
+ const mozilla::FontFamilyList& GetFamilyList() { return mFamilyList; }
+ void SetFamilyList(const mozilla::FontFamilyList& aFamilyList);
+
+ void AddValueList(int32_t aVariantAlternate,
+ nsTArray<gfxFontFeatureValueSet::ValueList>& aValueList);
+
+ const nsTArray<gfxFontFeatureValueSet::FeatureValues>& GetFeatureValues()
+ {
+ return mFeatureValues;
+ }
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+protected:
+ ~nsCSSFontFeatureValuesRule() {}
+
+ mozilla::FontFamilyList mFamilyList;
+ nsTArray<gfxFontFeatureValueSet::FeatureValues> mFeatureValues;
+};
+
+class nsCSSKeyframeRule;
+
+class nsCSSKeyframeStyleDeclaration final : public nsDOMCSSDeclaration
+{
+public:
+ explicit nsCSSKeyframeStyleDeclaration(nsCSSKeyframeRule *aRule);
+
+ NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent) override;
+ void DropReference() { mRule = nullptr; }
+ virtual mozilla::DeclarationBlock* GetCSSDeclaration(Operation aOperation) override;
+ virtual nsresult SetCSSDeclaration(mozilla::DeclarationBlock* aDecl) override;
+ virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+ virtual nsIDocument* DocToUpdate() override;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsCSSKeyframeStyleDeclaration,
+ nsICSSDeclaration)
+
+ virtual nsINode* GetParentObject() override;
+
+protected:
+ virtual ~nsCSSKeyframeStyleDeclaration();
+
+ // This reference is not reference-counted. The rule object tells us
+ // when it's about to go away.
+ nsCSSKeyframeRule* MOZ_NON_OWNING_REF mRule;
+};
+
+class nsCSSKeyframeRule final : public mozilla::css::Rule,
+ public nsIDOMCSSKeyframeRule
+{
+public:
+ // Steals the contents of aKeys, and takes the reference in Declaration
+ nsCSSKeyframeRule(InfallibleTArray<float>&& aKeys,
+ already_AddRefed<mozilla::css::Declaration>&& aDeclaration,
+ uint32_t aLineNumber, uint32_t aColumnNumber)
+ : mozilla::css::Rule(aLineNumber, aColumnNumber)
+ , mKeys(mozilla::Move(aKeys))
+ , mDeclaration(mozilla::Move(aDeclaration))
+ {
+ mDeclaration->SetOwningRule(this);
+ }
+private:
+ nsCSSKeyframeRule(const nsCSSKeyframeRule& aCopy);
+ ~nsCSSKeyframeRule();
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsCSSKeyframeRule, mozilla::css::Rule)
+
+ // Rule methods
+ DECL_STYLE_RULE_INHERIT
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSKeyframeRule interface
+ NS_DECL_NSIDOMCSSKEYFRAMERULE
+
+ const nsTArray<float>& GetKeys() const { return mKeys; }
+ mozilla::css::Declaration* Declaration() { return mDeclaration; }
+
+ void ChangeDeclaration(mozilla::css::Declaration* aDeclaration);
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ void DoGetKeyText(nsAString &aKeyText) const;
+
+private:
+ nsTArray<float> mKeys;
+ RefPtr<mozilla::css::Declaration> mDeclaration;
+ // lazily created when needed:
+ RefPtr<nsCSSKeyframeStyleDeclaration> mDOMDeclaration;
+};
+
+class nsCSSKeyframesRule final : public mozilla::css::GroupRule,
+ public nsIDOMCSSKeyframesRule
+{
+public:
+ nsCSSKeyframesRule(const nsSubstring& aName,
+ uint32_t aLineNumber, uint32_t aColumnNumber)
+ : mozilla::css::GroupRule(aLineNumber, aColumnNumber)
+ , mName(aName)
+ {
+ }
+private:
+ nsCSSKeyframesRule(const nsCSSKeyframesRule& aCopy);
+ ~nsCSSKeyframesRule();
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Rule methods
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
+ virtual nsIDOMCSSRule* GetDOMRule() override
+ {
+ return this;
+ }
+ virtual nsIDOMCSSRule* GetExistingDOMRule() override
+ {
+ return this;
+ }
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSKeyframesRule interface
+ NS_DECL_NSIDOMCSSKEYFRAMESRULE
+
+ // rest of GroupRule
+ virtual bool UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey) override;
+
+ const nsString& GetName() { return mName; }
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+private:
+ uint32_t FindRuleIndexForKey(const nsAString& aKey);
+
+ nsString mName;
+};
+
+class nsCSSPageRule;
+
+class nsCSSPageStyleDeclaration final : public nsDOMCSSDeclaration
+{
+public:
+ explicit nsCSSPageStyleDeclaration(nsCSSPageRule *aRule);
+
+ NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent) override;
+ void DropReference() { mRule = nullptr; }
+ virtual mozilla::DeclarationBlock* GetCSSDeclaration(Operation aOperation) override;
+ virtual nsresult SetCSSDeclaration(mozilla::DeclarationBlock* aDecl) override;
+ virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+ virtual nsIDocument* DocToUpdate() override;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsCSSPageStyleDeclaration,
+ nsICSSDeclaration)
+
+ virtual nsINode *GetParentObject() override;
+
+protected:
+ virtual ~nsCSSPageStyleDeclaration();
+
+ // This reference is not reference-counted. The rule object tells us
+ // when it's about to go away.
+ nsCSSPageRule* MOZ_NON_OWNING_REF mRule;
+};
+
+class nsCSSPageRule final : public mozilla::css::Rule,
+ public nsIDOMCSSPageRule
+{
+public:
+ nsCSSPageRule(mozilla::css::Declaration* aDeclaration,
+ uint32_t aLineNumber, uint32_t aColumnNumber)
+ : mozilla::css::Rule(aLineNumber, aColumnNumber)
+ , mDeclaration(aDeclaration)
+ {
+ mDeclaration->SetOwningRule(this);
+ }
+private:
+ nsCSSPageRule(const nsCSSPageRule& aCopy);
+ ~nsCSSPageRule();
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsCSSPageRule, nsIDOMCSSPageRule)
+
+ // Rule methods
+ DECL_STYLE_RULE_INHERIT
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSPageRule interface
+ NS_DECL_NSIDOMCSSPAGERULE
+
+ mozilla::css::Declaration* Declaration() { return mDeclaration; }
+
+ void ChangeDeclaration(mozilla::css::Declaration* aDeclaration);
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+private:
+ RefPtr<mozilla::css::Declaration> mDeclaration;
+ // lazily created when needed:
+ RefPtr<nsCSSPageStyleDeclaration> mDOMDeclaration;
+};
+
+namespace mozilla {
+
+class CSSSupportsRule : public css::GroupRule,
+ public nsIDOMCSSSupportsRule
+{
+public:
+ CSSSupportsRule(bool aConditionMet, const nsString& aCondition,
+ uint32_t aLineNumber, uint32_t aColumnNumber);
+ CSSSupportsRule(const CSSSupportsRule& aCopy);
+
+ // Rule methods
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
+ virtual bool UseForPresentation(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey& aKey) override;
+ virtual nsIDOMCSSRule* GetDOMRule() override
+ {
+ return this;
+ }
+ virtual nsIDOMCSSRule* GetExistingDOMRule() override
+ {
+ return this;
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSGroupingRule interface
+ NS_DECL_NSIDOMCSSGROUPINGRULE
+
+ // nsIDOMCSSConditionRule interface
+ NS_DECL_NSIDOMCSSCONDITIONRULE
+
+ // nsIDOMCSSSupportsRule interface
+ NS_DECL_NSIDOMCSSSUPPORTSRULE
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+protected:
+ virtual ~CSSSupportsRule();
+
+ bool mUseGroup;
+ nsString mCondition;
+};
+
+} // namespace mozilla
+
+class nsCSSCounterStyleRule final : public mozilla::css::Rule,
+ public nsIDOMCSSCounterStyleRule
+{
+public:
+ explicit nsCSSCounterStyleRule(const nsAString& aName,
+ uint32_t aLineNumber, uint32_t aColumnNumber)
+ : mozilla::css::Rule(aLineNumber, aColumnNumber)
+ , mName(aName)
+ , mGeneration(0)
+ {
+ }
+
+private:
+ nsCSSCounterStyleRule(const nsCSSCounterStyleRule& aCopy);
+ ~nsCSSCounterStyleRule();
+
+public:
+ NS_DECL_ISUPPORTS
+
+ // Rule methods
+ DECL_STYLE_RULE_INHERIT
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+ virtual int32_t GetType() const override;
+ virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
+
+ // nsIDOMCSSRule interface
+ NS_DECL_NSIDOMCSSRULE
+
+ // nsIDOMCSSCounterStyleRule
+ NS_DECL_NSIDOMCSSCOUNTERSTYLERULE
+
+ // This function is only used to check whether a non-empty value, which has
+ // been accepted by parser, is valid for the given system and descriptor.
+ static bool CheckDescValue(int32_t aSystem,
+ nsCSSCounterDesc aDescID,
+ const nsCSSValue& aValue);
+
+ const nsString& GetName() const { return mName; }
+
+ uint32_t GetGeneration() const { return mGeneration; }
+
+ int32_t GetSystem() const;
+ const nsCSSValue& GetSystemArgument() const;
+
+ const nsCSSValue& GetDesc(nsCSSCounterDesc aDescID) const
+ {
+ MOZ_ASSERT(aDescID >= 0 && aDescID < eCSSCounterDesc_COUNT,
+ "descriptor ID out of range");
+ return mValues[aDescID];
+ }
+
+ void SetDesc(nsCSSCounterDesc aDescID, const nsCSSValue& aValue);
+
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+private:
+ typedef NS_STDCALL_FUNCPROTO(nsresult, Getter, nsCSSCounterStyleRule,
+ GetSymbols, (nsAString&));
+ static const Getter kGetters[];
+
+ nsresult GetDescriptor(nsCSSCounterDesc aDescID, nsAString& aValue);
+ nsresult SetDescriptor(nsCSSCounterDesc aDescID, const nsAString& aValue);
+
+ nsString mName;
+ nsCSSValue mValues[eCSSCounterDesc_COUNT];
+ uint32_t mGeneration;
+};
+
+#endif /* !defined(nsCSSRules_h_) */
diff --git a/layout/style/nsCSSScanner.cpp b/layout/style/nsCSSScanner.cpp
new file mode 100644
index 000000000..771c8936b
--- /dev/null
+++ b/layout/style/nsCSSScanner.cpp
@@ -0,0 +1,1380 @@
+/* -*- 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/. */
+
+
+/* tokenization of CSS style sheets */
+
+#include "nsCSSScanner.h"
+#include "nsStyleUtil.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/css/ErrorReporter.h"
+#include "mozilla/Likely.h"
+#include <algorithm>
+
+/* Character class tables and related helper functions. */
+
+static const uint8_t IS_HEX_DIGIT = 0x01;
+static const uint8_t IS_IDSTART = 0x02;
+static const uint8_t IS_IDCHAR = 0x04;
+static const uint8_t IS_URL_CHAR = 0x08;
+static const uint8_t IS_HSPACE = 0x10;
+static const uint8_t IS_VSPACE = 0x20;
+static const uint8_t IS_SPACE = IS_HSPACE|IS_VSPACE;
+static const uint8_t IS_STRING = 0x40;
+
+#define H IS_HSPACE
+#define V IS_VSPACE
+#define I IS_IDCHAR
+#define J IS_IDSTART
+#define U IS_URL_CHAR
+#define S IS_STRING
+#define X IS_HEX_DIGIT
+
+#define SH S|H
+#define SU S|U
+#define SUI S|U|I
+#define SUIJ S|U|I|J
+#define SUIX S|U|I|X
+#define SUIJX S|U|I|J|X
+
+static const uint8_t gLexTable[] = {
+// 00 01 02 03 04 05 06 07
+ 0, S, S, S, S, S, S, S,
+// 08 TAB LF 0B FF CR 0E 0F
+ S, SH, V, S, V, V, S, S,
+// 10 11 12 13 14 15 16 17
+ S, S, S, S, S, S, S, S,
+// 18 19 1A 1B 1C 1D 1E 1F
+ S, S, S, S, S, S, S, S,
+//SPC ! " # $ % & '
+ SH, SU, 0, SU, SU, SU, SU, 0,
+// ( ) * + , - . /
+ S, S, SU, SU, SU, SUI, SU, SU,
+// 0 1 2 3 4 5 6 7
+ SUIX, SUIX, SUIX, SUIX, SUIX, SUIX, SUIX, SUIX,
+// 8 9 : ; < = > ?
+ SUIX, SUIX, SU, SU, SU, SU, SU, SU,
+// @ A B C D E F G
+ SU,SUIJX,SUIJX,SUIJX,SUIJX,SUIJX,SUIJX, SUIJ,
+// H I J K L M N O
+ SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ,
+// P Q R S T U V W
+ SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ,
+// X Y Z [ \ ] ^ _
+ SUIJ, SUIJ, SUIJ, SU, J, SU, SU, SUIJ,
+// ` a b c d e f g
+ SU,SUIJX,SUIJX,SUIJX,SUIJX,SUIJX,SUIJX, SUIJ,
+// h i j k l m n o
+ SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ,
+// p q r s t u v w
+ SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ,
+// x y z { | } ~ 7F
+ SUIJ, SUIJ, SUIJ, SU, SU, SU, SU, S,
+};
+
+static_assert(MOZ_ARRAY_LENGTH(gLexTable) == 128,
+ "gLexTable expected to cover all 128 ASCII characters");
+
+#undef I
+#undef J
+#undef U
+#undef S
+#undef X
+#undef SH
+#undef SU
+#undef SUI
+#undef SUIJ
+#undef SUIX
+#undef SUIJX
+
+/**
+ * True if 'ch' is in character class 'cls', which should be one of
+ * the constants above or some combination of them. All characters
+ * above U+007F are considered to be in 'cls'. EOF is never in 'cls'.
+ */
+static inline bool
+IsOpenCharClass(int32_t ch, uint8_t cls) {
+ return ch >= 0 && (ch >= 128 || (gLexTable[ch] & cls) != 0);
+}
+
+/**
+ * True if 'ch' is in character class 'cls', which should be one of
+ * the constants above or some combination of them. No characters
+ * above U+007F are considered to be in 'cls'. EOF is never in 'cls'.
+ */
+static inline bool
+IsClosedCharClass(int32_t ch, uint8_t cls) {
+ return uint32_t(ch) < 128 && (gLexTable[ch] & cls) != 0;
+}
+
+/**
+ * True if 'ch' is CSS whitespace, i.e. any of the ASCII characters
+ * TAB, LF, FF, CR, or SPC.
+ */
+static inline bool
+IsWhitespace(int32_t ch) {
+ return IsClosedCharClass(ch, IS_SPACE);
+}
+
+/**
+ * True if 'ch' is horizontal whitespace, i.e. TAB or SPC.
+ */
+static inline bool
+IsHorzSpace(int32_t ch) {
+ return IsClosedCharClass(ch, IS_HSPACE);
+}
+
+/**
+ * True if 'ch' is vertical whitespace, i.e. LF, FF, or CR. Vertical
+ * whitespace requires special handling when consumed, see AdvanceLine.
+ */
+static inline bool
+IsVertSpace(int32_t ch) {
+ return IsClosedCharClass(ch, IS_VSPACE);
+}
+
+/**
+ * True if 'ch' is a character that can appear in the middle of an identifier.
+ * This includes U+0000 since it is handled as U+FFFD, but for purposes of
+ * GatherText it should not be included in IsOpenCharClass.
+ */
+static inline bool
+IsIdentChar(int32_t ch) {
+ return IsOpenCharClass(ch, IS_IDCHAR) || ch == 0;
+}
+
+/**
+ * True if 'ch' is a character that by itself begins an identifier.
+ * This includes U+0000 since it is handled as U+FFFD, but for purposes of
+ * GatherText it should not be included in IsOpenCharClass.
+ * (This is a subset of IsIdentChar.)
+ */
+static inline bool
+IsIdentStart(int32_t ch) {
+ return IsOpenCharClass(ch, IS_IDSTART) || ch == 0;
+}
+
+/**
+ * True if the two-character sequence aFirstChar+aSecondChar begins an
+ * identifier.
+ */
+static inline bool
+StartsIdent(int32_t aFirstChar, int32_t aSecondChar)
+{
+ return IsIdentStart(aFirstChar) ||
+ (aFirstChar == '-' && (aSecondChar == '-' || IsIdentStart(aSecondChar)));
+}
+
+/**
+ * True if 'ch' is a decimal digit.
+ */
+static inline bool
+IsDigit(int32_t ch) {
+ return (ch >= '0') && (ch <= '9');
+}
+
+/**
+ * True if 'ch' is a hexadecimal digit.
+ */
+static inline bool
+IsHexDigit(int32_t ch) {
+ return IsClosedCharClass(ch, IS_HEX_DIGIT);
+}
+
+/**
+ * Assuming that 'ch' is a decimal digit, return its numeric value.
+ */
+static inline uint32_t
+DecimalDigitValue(int32_t ch)
+{
+ return ch - '0';
+}
+
+/**
+ * Assuming that 'ch' is a hexadecimal digit, return its numeric value.
+ */
+static inline uint32_t
+HexDigitValue(int32_t ch)
+{
+ if (IsDigit(ch)) {
+ return DecimalDigitValue(ch);
+ } else {
+ // Note: c&7 just keeps the low three bits which causes
+ // upper and lower case alphabetics to both yield their
+ // "relative to 10" value for computing the hex value.
+ return (ch & 0x7) + 9;
+ }
+}
+
+/**
+ * If 'ch' can be the first character of a two-character match operator
+ * token, return the token type code for that token, otherwise return
+ * eCSSToken_Symbol to indicate that it can't.
+ */
+static inline nsCSSTokenType
+MatchOperatorType(int32_t ch)
+{
+ switch (ch) {
+ case '~': return eCSSToken_Includes;
+ case '|': return eCSSToken_Dashmatch;
+ case '^': return eCSSToken_Beginsmatch;
+ case '$': return eCSSToken_Endsmatch;
+ case '*': return eCSSToken_Containsmatch;
+ default: return eCSSToken_Symbol;
+ }
+}
+
+/* Out-of-line nsCSSToken methods. */
+
+/**
+ * Append the textual representation of |this| to |aBuffer|.
+ */
+void
+nsCSSToken::AppendToString(nsString& aBuffer) const
+{
+ switch (mType) {
+ case eCSSToken_Ident:
+ nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
+ break;
+
+ case eCSSToken_AtKeyword:
+ aBuffer.Append('@');
+ nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
+ break;
+
+ case eCSSToken_ID:
+ case eCSSToken_Hash:
+ aBuffer.Append('#');
+ nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
+ break;
+
+ case eCSSToken_Function:
+ nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
+ aBuffer.Append('(');
+ break;
+
+ case eCSSToken_URL:
+ case eCSSToken_Bad_URL:
+ aBuffer.AppendLiteral("url(");
+ if (mSymbol != char16_t(0)) {
+ nsStyleUtil::AppendEscapedCSSString(mIdent, aBuffer, mSymbol);
+ } else {
+ aBuffer.Append(mIdent);
+ }
+ if (mType == eCSSToken_URL) {
+ aBuffer.Append(char16_t(')'));
+ }
+ break;
+
+ case eCSSToken_Number:
+ if (mIntegerValid) {
+ aBuffer.AppendInt(mInteger, 10);
+ } else {
+ aBuffer.AppendFloat(mNumber);
+ }
+ break;
+
+ case eCSSToken_Percentage:
+ aBuffer.AppendFloat(mNumber * 100.0f);
+ aBuffer.Append(char16_t('%'));
+ break;
+
+ case eCSSToken_Dimension:
+ if (mIntegerValid) {
+ aBuffer.AppendInt(mInteger, 10);
+ } else {
+ aBuffer.AppendFloat(mNumber);
+ }
+ nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
+ break;
+
+ case eCSSToken_Bad_String:
+ nsStyleUtil::AppendEscapedCSSString(mIdent, aBuffer, mSymbol);
+ // remove the trailing quote character
+ aBuffer.Truncate(aBuffer.Length() - 1);
+ break;
+
+ case eCSSToken_String:
+ nsStyleUtil::AppendEscapedCSSString(mIdent, aBuffer, mSymbol);
+ break;
+
+ case eCSSToken_Symbol:
+ aBuffer.Append(mSymbol);
+ break;
+
+ case eCSSToken_Whitespace:
+ aBuffer.Append(' ');
+ break;
+
+ case eCSSToken_HTMLComment:
+ case eCSSToken_URange:
+ aBuffer.Append(mIdent);
+ break;
+
+ case eCSSToken_Includes:
+ aBuffer.AppendLiteral("~=");
+ break;
+ case eCSSToken_Dashmatch:
+ aBuffer.AppendLiteral("|=");
+ break;
+ case eCSSToken_Beginsmatch:
+ aBuffer.AppendLiteral("^=");
+ break;
+ case eCSSToken_Endsmatch:
+ aBuffer.AppendLiteral("$=");
+ break;
+ case eCSSToken_Containsmatch:
+ aBuffer.AppendLiteral("*=");
+ break;
+
+ default:
+ NS_ERROR("invalid token type");
+ break;
+ }
+}
+
+/* nsCSSScanner methods. */
+
+nsCSSScanner::nsCSSScanner(const nsAString& aBuffer, uint32_t aLineNumber)
+ : mBuffer(aBuffer.BeginReading())
+ , mOffset(0)
+ , mCount(aBuffer.Length())
+ , mLineNumber(aLineNumber)
+ , mLineOffset(0)
+ , mTokenLineNumber(aLineNumber)
+ , mTokenLineOffset(0)
+ , mTokenOffset(0)
+ , mRecordStartOffset(0)
+ , mEOFCharacters(eEOFCharacters_None)
+ , mReporter(nullptr)
+ , mSVGMode(false)
+ , mRecording(false)
+ , mSeenBadToken(false)
+ , mSeenVariableReference(false)
+{
+ MOZ_COUNT_CTOR(nsCSSScanner);
+}
+
+nsCSSScanner::~nsCSSScanner()
+{
+ MOZ_COUNT_DTOR(nsCSSScanner);
+}
+
+void
+nsCSSScanner::StartRecording()
+{
+ MOZ_ASSERT(!mRecording, "already started recording");
+ mRecording = true;
+ mRecordStartOffset = mOffset;
+}
+
+void
+nsCSSScanner::StopRecording()
+{
+ MOZ_ASSERT(mRecording, "haven't started recording");
+ mRecording = false;
+}
+
+void
+nsCSSScanner::StopRecording(nsString& aBuffer)
+{
+ MOZ_ASSERT(mRecording, "haven't started recording");
+ mRecording = false;
+ aBuffer.Append(mBuffer + mRecordStartOffset,
+ mOffset - mRecordStartOffset);
+}
+
+uint32_t
+nsCSSScanner::RecordingLength() const
+{
+ MOZ_ASSERT(mRecording, "haven't started recording");
+ return mOffset - mRecordStartOffset;
+}
+
+#ifdef DEBUG
+bool
+nsCSSScanner::IsRecording() const
+{
+ return mRecording;
+}
+#endif
+
+nsDependentSubstring
+nsCSSScanner::GetCurrentLine() const
+{
+ uint32_t end = mTokenOffset;
+ while (end < mCount && !IsVertSpace(mBuffer[end])) {
+ end++;
+ }
+ return nsDependentSubstring(mBuffer + mTokenLineOffset,
+ mBuffer + end);
+}
+
+/**
+ * Return the raw UTF-16 code unit at position |mOffset + n| within
+ * the read buffer. If that is beyond the end of the buffer, returns
+ * -1 to indicate end of input.
+ */
+inline int32_t
+nsCSSScanner::Peek(uint32_t n)
+{
+ if (mOffset + n >= mCount) {
+ return -1;
+ }
+ return mBuffer[mOffset + n];
+}
+
+/**
+ * Advance |mOffset| over |n| code units. Advance(0) is a no-op.
+ * If |n| is greater than the distance to end of input, will silently
+ * stop at the end. May not be used to advance over a line boundary;
+ * AdvanceLine() must be used instead.
+ */
+inline void
+nsCSSScanner::Advance(uint32_t n)
+{
+#ifdef DEBUG
+ while (mOffset < mCount && n > 0) {
+ MOZ_ASSERT(!IsVertSpace(mBuffer[mOffset]),
+ "may not Advance() over a line boundary");
+ mOffset++;
+ n--;
+ }
+#else
+ if (mOffset + n >= mCount || mOffset + n < mOffset)
+ mOffset = mCount;
+ else
+ mOffset += n;
+#endif
+}
+
+/**
+ * Advance |mOffset| over a line boundary.
+ */
+void
+nsCSSScanner::AdvanceLine()
+{
+ MOZ_ASSERT(IsVertSpace(mBuffer[mOffset]),
+ "may not AdvanceLine() over a horizontal character");
+ // Advance over \r\n as a unit.
+ if (mBuffer[mOffset] == '\r' && mOffset + 1 < mCount &&
+ mBuffer[mOffset+1] == '\n')
+ mOffset += 2;
+ else
+ mOffset += 1;
+ // 0 is a magical line number meaning that we don't know (i.e., script)
+ if (mLineNumber != 0)
+ mLineNumber++;
+ mLineOffset = mOffset;
+}
+
+/**
+ * Back up |mOffset| over |n| code units. Backup(0) is a no-op.
+ * If |n| is greater than the distance to beginning of input, will
+ * silently stop at the beginning. May not be used to back up over a
+ * line boundary.
+ */
+void
+nsCSSScanner::Backup(uint32_t n)
+{
+#ifdef DEBUG
+ while (mOffset > 0 && n > 0) {
+ MOZ_ASSERT(!IsVertSpace(mBuffer[mOffset-1]),
+ "may not Backup() over a line boundary");
+ mOffset--;
+ n--;
+ }
+#else
+ if (mOffset < n)
+ mOffset = 0;
+ else
+ mOffset -= n;
+#endif
+}
+
+void
+nsCSSScanner::SavePosition(nsCSSScannerPosition& aState)
+{
+ aState.mOffset = mOffset;
+ aState.mLineNumber = mLineNumber;
+ aState.mLineOffset = mLineOffset;
+ aState.mTokenLineNumber = mTokenLineNumber;
+ aState.mTokenLineOffset = mTokenLineOffset;
+ aState.mTokenOffset = mTokenOffset;
+ aState.mInitialized = true;
+}
+
+void
+nsCSSScanner::RestoreSavedPosition(const nsCSSScannerPosition& aState)
+{
+ MOZ_ASSERT(aState.mInitialized, "have not saved state");
+ if (aState.mInitialized) {
+ mOffset = aState.mOffset;
+ mLineNumber = aState.mLineNumber;
+ mLineOffset = aState.mLineOffset;
+ mTokenLineNumber = aState.mTokenLineNumber;
+ mTokenLineOffset = aState.mTokenLineOffset;
+ mTokenOffset = aState.mTokenOffset;
+ }
+}
+
+/**
+ * Skip over a sequence of whitespace characters (vertical or
+ * horizontal) starting at the current read position.
+ */
+void
+nsCSSScanner::SkipWhitespace()
+{
+ for (;;) {
+ int32_t ch = Peek();
+ if (!IsWhitespace(ch)) { // EOF counts as non-whitespace
+ break;
+ }
+ if (IsVertSpace(ch)) {
+ AdvanceLine();
+ } else {
+ Advance();
+ }
+ }
+}
+
+/**
+ * Skip over one CSS comment starting at the current read position.
+ */
+void
+nsCSSScanner::SkipComment()
+{
+ MOZ_ASSERT(Peek() == '/' && Peek(1) == '*', "should not have been called");
+ Advance(2);
+ for (;;) {
+ int32_t ch = Peek();
+ if (ch < 0) {
+ if (mReporter)
+ mReporter->ReportUnexpectedEOF("PECommentEOF");
+ SetEOFCharacters(eEOFCharacters_Asterisk | eEOFCharacters_Slash);
+ return;
+ }
+ if (ch == '*') {
+ Advance();
+ ch = Peek();
+ if (ch < 0) {
+ if (mReporter)
+ mReporter->ReportUnexpectedEOF("PECommentEOF");
+ SetEOFCharacters(eEOFCharacters_Slash);
+ return;
+ }
+ if (ch == '/') {
+ Advance();
+ return;
+ }
+ } else if (IsVertSpace(ch)) {
+ AdvanceLine();
+ } else {
+ Advance();
+ }
+ }
+}
+
+/**
+ * If there is a valid escape sequence starting at the current read
+ * position, consume it, decode it, append the result to |aOutput|,
+ * and return true. Otherwise, consume nothing, leave |aOutput|
+ * unmodified, and return false. If |aInString| is true, accept the
+ * additional form of escape sequence allowed within string-like tokens.
+ */
+bool
+nsCSSScanner::GatherEscape(nsString& aOutput, bool aInString)
+{
+ MOZ_ASSERT(Peek() == '\\', "should not have been called");
+ int32_t ch = Peek(1);
+ if (ch < 0) {
+ // If we are in a string (or a url() containing a string), we want to drop
+ // the backslash on the floor. Otherwise, we want to treat it as a U+FFFD
+ // character.
+ Advance();
+ if (aInString) {
+ SetEOFCharacters(eEOFCharacters_DropBackslash);
+ } else {
+ aOutput.Append(UCS2_REPLACEMENT_CHAR);
+ SetEOFCharacters(eEOFCharacters_ReplacementChar);
+ }
+ return true;
+ }
+ if (IsVertSpace(ch)) {
+ if (aInString) {
+ // In strings (and in url() containing a string), escaped
+ // newlines are completely removed, to allow splitting over
+ // multiple lines.
+ Advance();
+ AdvanceLine();
+ return true;
+ }
+ // Outside of strings, backslash followed by a newline is not an escape.
+ return false;
+ }
+
+ if (!IsHexDigit(ch)) {
+ // "Any character (except a hexadecimal digit, linefeed, carriage
+ // return, or form feed) can be escaped with a backslash to remove
+ // its special meaning." -- CSS2.1 section 4.1.3
+ Advance(2);
+ if (ch == 0) {
+ aOutput.Append(UCS2_REPLACEMENT_CHAR);
+ } else {
+ aOutput.Append(ch);
+ }
+ return true;
+ }
+
+ // "[at most six hexadecimal digits following a backslash] stand
+ // for the ISO 10646 character with that number, which must not be
+ // zero. (It is undefined in CSS 2.1 what happens if a style sheet
+ // does contain a character with Unicode codepoint zero.)"
+ // -- CSS2.1 section 4.1.3
+
+ // At this point we know we have \ followed by at least one
+ // hexadecimal digit, therefore the escape sequence is valid and we
+ // can go ahead and consume the backslash.
+ Advance();
+ uint32_t val = 0;
+ int i = 0;
+ do {
+ val = val * 16 + HexDigitValue(ch);
+ i++;
+ Advance();
+ ch = Peek();
+ } while (i < 6 && IsHexDigit(ch));
+
+ // "Interpret the hex digits as a hexadecimal number. If this number is zero,
+ // or is greater than the maximum allowed codepoint, return U+FFFD
+ // REPLACEMENT CHARACTER" -- CSS Syntax Level 3
+ if (MOZ_UNLIKELY(val == 0)) {
+ aOutput.Append(UCS2_REPLACEMENT_CHAR);
+ } else {
+ AppendUCS4ToUTF16(ENSURE_VALID_CHAR(val), aOutput);
+ }
+
+ // Consume exactly one whitespace character after a
+ // hexadecimal escape sequence.
+ if (IsVertSpace(ch)) {
+ AdvanceLine();
+ } else if (IsHorzSpace(ch)) {
+ Advance();
+ }
+ return true;
+}
+
+/**
+ * Consume a run of "text" beginning with the current read position,
+ * consisting of characters in the class |aClass| (which must be a
+ * suitable argument to IsOpenCharClass) plus escape sequences.
+ * Append the text to |aText|, after decoding escape sequences.
+ *
+ * Returns true if at least one character was appended to |aText|,
+ * false otherwise.
+ */
+bool
+nsCSSScanner::GatherText(uint8_t aClass, nsString& aText)
+{
+ // This is all of the character classes currently used with
+ // GatherText. If you have a need to use this function with a
+ // different class, go ahead and add it.
+ MOZ_ASSERT(aClass == IS_STRING ||
+ aClass == IS_IDCHAR ||
+ aClass == IS_URL_CHAR,
+ "possibly-inappropriate character class");
+
+ uint32_t start = mOffset;
+ bool inString = aClass == IS_STRING;
+
+ for (;;) {
+ // Consume runs of unescaped characters in one go.
+ uint32_t n = mOffset;
+ while (n < mCount && IsOpenCharClass(mBuffer[n], aClass)) {
+ n++;
+ }
+ if (n > mOffset) {
+ aText.Append(&mBuffer[mOffset], n - mOffset);
+ mOffset = n;
+ }
+ if (n == mCount) {
+ break;
+ }
+
+ int32_t ch = Peek();
+ MOZ_ASSERT(!IsOpenCharClass(ch, aClass),
+ "should not have exited the inner loop");
+ if (ch == 0) {
+ Advance();
+ aText.Append(UCS2_REPLACEMENT_CHAR);
+ continue;
+ }
+
+ if (ch != '\\') {
+ break;
+ }
+ if (!GatherEscape(aText, inString)) {
+ break;
+ }
+ }
+
+ return mOffset > start;
+}
+
+/**
+ * Scan an Ident token. This also handles Function and URL tokens,
+ * both of which begin indistinguishably from an identifier. It can
+ * produce a Symbol token when an apparent identifier actually led
+ * into an invalid escape sequence.
+ */
+bool
+nsCSSScanner::ScanIdent(nsCSSToken& aToken)
+{
+ if (MOZ_UNLIKELY(!GatherText(IS_IDCHAR, aToken.mIdent))) {
+ MOZ_ASSERT(Peek() == '\\',
+ "unexpected IsIdentStart character that did not begin an ident");
+ aToken.mSymbol = Peek();
+ Advance();
+ return true;
+ }
+
+ if (MOZ_LIKELY(Peek() != '(')) {
+ aToken.mType = eCSSToken_Ident;
+ return true;
+ }
+
+ Advance();
+ aToken.mType = eCSSToken_Function;
+ if (aToken.mIdent.LowerCaseEqualsLiteral("url")) {
+ NextURL(aToken);
+ } else if (aToken.mIdent.LowerCaseEqualsLiteral("var")) {
+ mSeenVariableReference = true;
+ }
+ return true;
+}
+
+/**
+ * Scan an AtKeyword token. Also handles production of Symbol when
+ * an '@' is not followed by an identifier.
+ */
+bool
+nsCSSScanner::ScanAtKeyword(nsCSSToken& aToken)
+{
+ MOZ_ASSERT(Peek() == '@', "should not have been called");
+
+ // Fall back for when '@' isn't followed by an identifier.
+ aToken.mSymbol = '@';
+ Advance();
+
+ int32_t ch = Peek();
+ if (StartsIdent(ch, Peek(1))) {
+ if (GatherText(IS_IDCHAR, aToken.mIdent)) {
+ aToken.mType = eCSSToken_AtKeyword;
+ }
+ }
+ return true;
+}
+
+/**
+ * Scan a Hash token. Handles the distinction between eCSSToken_ID
+ * and eCSSToken_Hash, and handles production of Symbol when a '#'
+ * is not followed by identifier characters.
+ */
+bool
+nsCSSScanner::ScanHash(nsCSSToken& aToken)
+{
+ MOZ_ASSERT(Peek() == '#', "should not have been called");
+
+ // Fall back for when '#' isn't followed by identifier characters.
+ aToken.mSymbol = '#';
+ Advance();
+
+ int32_t ch = Peek();
+ if (IsIdentChar(ch) || ch == '\\') {
+ nsCSSTokenType type =
+ StartsIdent(ch, Peek(1)) ? eCSSToken_ID : eCSSToken_Hash;
+ aToken.mIdent.SetLength(0);
+ if (GatherText(IS_IDCHAR, aToken.mIdent)) {
+ aToken.mType = type;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Scan a Number, Percentage, or Dimension token (all of which begin
+ * like a Number). Can produce a Symbol when a '.' is not followed by
+ * digits, or when '+' or '-' are not followed by either a digit or a
+ * '.' and then a digit. Can also produce a HTMLComment when it
+ * encounters '-->'.
+ */
+bool
+nsCSSScanner::ScanNumber(nsCSSToken& aToken)
+{
+ int32_t c = Peek();
+#ifdef DEBUG
+ {
+ int32_t c2 = Peek(1);
+ int32_t c3 = Peek(2);
+ MOZ_ASSERT(IsDigit(c) ||
+ (IsDigit(c2) && (c == '.' || c == '+' || c == '-')) ||
+ (IsDigit(c3) && (c == '+' || c == '-') && c2 == '.'),
+ "should not have been called");
+ }
+#endif
+
+ // Sign of the mantissa (-1 or 1).
+ int32_t sign = c == '-' ? -1 : 1;
+ // Absolute value of the integer part of the mantissa. This is a double so
+ // we don't run into overflow issues for consumers that only care about our
+ // floating-point value while still being able to express the full int32_t
+ // range for consumers who want integers.
+ double intPart = 0;
+ // Fractional part of the mantissa. This is a double so that when we convert
+ // to float at the end we'll end up rounding to nearest float instead of
+ // truncating down (as we would if fracPart were a float and we just
+ // effectively lost the last several digits).
+ double fracPart = 0;
+ // Absolute value of the power of 10 that we should multiply by (only
+ // relevant for numbers in scientific notation). Has to be a signed integer,
+ // because multiplication of signed by unsigned converts the unsigned to
+ // signed, so if we plan to actually multiply by expSign...
+ int32_t exponent = 0;
+ // Sign of the exponent.
+ int32_t expSign = 1;
+
+ aToken.mHasSign = (c == '+' || c == '-');
+ if (aToken.mHasSign) {
+ Advance();
+ c = Peek();
+ }
+
+ bool gotDot = (c == '.');
+
+ if (!gotDot) {
+ // Scan the integer part of the mantissa.
+ MOZ_ASSERT(IsDigit(c), "should have been excluded by logic above");
+ do {
+ intPart = 10*intPart + DecimalDigitValue(c);
+ Advance();
+ c = Peek();
+ } while (IsDigit(c));
+
+ gotDot = (c == '.') && IsDigit(Peek(1));
+ }
+
+ if (gotDot) {
+ // Scan the fractional part of the mantissa.
+ Advance();
+ c = Peek();
+ MOZ_ASSERT(IsDigit(c), "should have been excluded by logic above");
+ // Power of ten by which we need to divide our next digit
+ double divisor = 10;
+ do {
+ fracPart += DecimalDigitValue(c) / divisor;
+ divisor *= 10;
+ Advance();
+ c = Peek();
+ } while (IsDigit(c));
+ }
+
+ bool gotE = false;
+ if (c == 'e' || c == 'E') {
+ int32_t expSignChar = Peek(1);
+ int32_t nextChar = Peek(2);
+ if (IsDigit(expSignChar) ||
+ ((expSignChar == '-' || expSignChar == '+') && IsDigit(nextChar))) {
+ gotE = true;
+ if (expSignChar == '-') {
+ expSign = -1;
+ }
+ Advance(); // consumes the E
+ if (expSignChar == '-' || expSignChar == '+') {
+ Advance();
+ c = nextChar;
+ } else {
+ c = expSignChar;
+ }
+ MOZ_ASSERT(IsDigit(c), "should have been excluded by logic above");
+ do {
+ exponent = 10*exponent + DecimalDigitValue(c);
+ Advance();
+ c = Peek();
+ } while (IsDigit(c));
+ }
+ }
+
+ nsCSSTokenType type = eCSSToken_Number;
+
+ // Set mIntegerValid for all cases (except %, below) because we need
+ // it for the "2n" in :nth-child(2n).
+ aToken.mIntegerValid = false;
+
+ // Time to reassemble our number.
+ // Do all the math in double precision so it's truncated only once.
+ double value = sign * (intPart + fracPart);
+ if (gotE) {
+ // Avoid multiplication of 0 by Infinity.
+ if (value != 0.0) {
+ // Explicitly cast expSign*exponent to double to avoid issues with
+ // overloaded pow() on Windows.
+ value *= pow(10.0, double(expSign * exponent));
+ }
+ } else if (!gotDot) {
+ // Clamp values outside of integer range.
+ if (sign > 0) {
+ aToken.mInteger = int32_t(std::min(intPart, double(INT32_MAX)));
+ } else {
+ aToken.mInteger = int32_t(std::max(-intPart, double(INT32_MIN)));
+ }
+ aToken.mIntegerValid = true;
+ }
+
+ nsString& ident = aToken.mIdent;
+
+ // Check for Dimension and Percentage tokens.
+ if (c >= 0) {
+ if (StartsIdent(c, Peek(1))) {
+ if (GatherText(IS_IDCHAR, ident)) {
+ type = eCSSToken_Dimension;
+ }
+ } else if (c == '%') {
+ Advance();
+ type = eCSSToken_Percentage;
+ value = value / 100.0f;
+ aToken.mIntegerValid = false;
+ }
+ }
+ MOZ_ASSERT(!IsNaN(value), "The value should not be NaN");
+ aToken.mNumber = value;
+ aToken.mType = type;
+ return true;
+}
+
+/**
+ * Scan a string constant ('foo' or "foo"). Will always produce
+ * either a String or a Bad_String token; the latter occurs when the
+ * close quote is missing. Always returns true (for convenience in Next()).
+ */
+bool
+nsCSSScanner::ScanString(nsCSSToken& aToken)
+{
+ int32_t aStop = Peek();
+ MOZ_ASSERT(aStop == '"' || aStop == '\'', "should not have been called");
+ aToken.mType = eCSSToken_String;
+ aToken.mSymbol = char16_t(aStop); // Remember how it's quoted.
+ Advance();
+
+ for (;;) {
+ GatherText(IS_STRING, aToken.mIdent);
+
+ int32_t ch = Peek();
+ if (ch == -1) {
+ AddEOFCharacters(aStop == '"' ? eEOFCharacters_DoubleQuote :
+ eEOFCharacters_SingleQuote);
+ break; // EOF ends a string token with no error.
+ }
+ if (ch == aStop) {
+ Advance();
+ break;
+ }
+ // Both " and ' are excluded from IS_STRING.
+ if (ch == '"' || ch == '\'') {
+ aToken.mIdent.Append(ch);
+ Advance();
+ continue;
+ }
+
+ mSeenBadToken = true;
+ aToken.mType = eCSSToken_Bad_String;
+ if (mReporter)
+ mReporter->ReportUnexpected("SEUnterminatedString", aToken);
+ break;
+ }
+ return true;
+}
+
+/**
+ * Scan a unicode-range token. These match the regular expression
+ *
+ * u\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?
+ *
+ * However, some such tokens are "invalid". There are three valid forms:
+ *
+ * u+[0-9a-f]{x} 1 <= x <= 6
+ * u+[0-9a-f]{x}\?{y} 1 <= x+y <= 6
+ * u+[0-9a-f]{x}-[0-9a-f]{y} 1 <= x <= 6, 1 <= y <= 6
+ *
+ * All unicode-range tokens have their text recorded in mIdent; valid ones
+ * are also decoded into mInteger and mInteger2, and mIntegerValid is set.
+ * Note that this does not validate the numeric range, only the syntactic
+ * form.
+ */
+bool
+nsCSSScanner::ScanURange(nsCSSToken& aResult)
+{
+ int32_t intro1 = Peek();
+ int32_t intro2 = Peek(1);
+ int32_t ch = Peek(2);
+
+ MOZ_ASSERT((intro1 == 'u' || intro1 == 'U') &&
+ intro2 == '+' &&
+ (IsHexDigit(ch) || ch == '?'),
+ "should not have been called");
+
+ aResult.mIdent.Append(intro1);
+ aResult.mIdent.Append(intro2);
+ Advance(2);
+
+ bool valid = true;
+ bool haveQues = false;
+ uint32_t low = 0;
+ uint32_t high = 0;
+ int i = 0;
+
+ do {
+ aResult.mIdent.Append(ch);
+ if (IsHexDigit(ch)) {
+ if (haveQues) {
+ valid = false; // All question marks should be at the end.
+ }
+ low = low*16 + HexDigitValue(ch);
+ high = high*16 + HexDigitValue(ch);
+ } else {
+ haveQues = true;
+ low = low*16 + 0x0;
+ high = high*16 + 0xF;
+ }
+
+ i++;
+ Advance();
+ ch = Peek();
+ } while (i < 6 && (IsHexDigit(ch) || ch == '?'));
+
+ if (ch == '-' && IsHexDigit(Peek(1))) {
+ if (haveQues) {
+ valid = false;
+ }
+
+ aResult.mIdent.Append(ch);
+ Advance();
+ ch = Peek();
+ high = 0;
+ i = 0;
+ do {
+ aResult.mIdent.Append(ch);
+ high = high*16 + HexDigitValue(ch);
+
+ i++;
+ Advance();
+ ch = Peek();
+ } while (i < 6 && IsHexDigit(ch));
+ }
+
+ aResult.mInteger = low;
+ aResult.mInteger2 = high;
+ aResult.mIntegerValid = valid;
+ aResult.mType = eCSSToken_URange;
+ return true;
+}
+
+#ifdef DEBUG
+/* static */ void
+nsCSSScanner::AssertEOFCharactersValid(uint32_t c)
+{
+ MOZ_ASSERT(c == eEOFCharacters_None ||
+ c == eEOFCharacters_ReplacementChar ||
+ c == eEOFCharacters_Slash ||
+ c == (eEOFCharacters_Asterisk |
+ eEOFCharacters_Slash) ||
+ c == eEOFCharacters_DoubleQuote ||
+ c == eEOFCharacters_SingleQuote ||
+ c == (eEOFCharacters_DropBackslash |
+ eEOFCharacters_DoubleQuote) ||
+ c == (eEOFCharacters_DropBackslash |
+ eEOFCharacters_SingleQuote) ||
+ c == eEOFCharacters_CloseParen ||
+ c == (eEOFCharacters_ReplacementChar |
+ eEOFCharacters_CloseParen) ||
+ c == (eEOFCharacters_DoubleQuote |
+ eEOFCharacters_CloseParen) ||
+ c == (eEOFCharacters_SingleQuote |
+ eEOFCharacters_CloseParen) ||
+ c == (eEOFCharacters_DropBackslash |
+ eEOFCharacters_DoubleQuote |
+ eEOFCharacters_CloseParen) ||
+ c == (eEOFCharacters_DropBackslash |
+ eEOFCharacters_SingleQuote |
+ eEOFCharacters_CloseParen),
+ "invalid EOFCharacters value");
+}
+#endif
+
+void
+nsCSSScanner::SetEOFCharacters(uint32_t aEOFCharacters)
+{
+ mEOFCharacters = EOFCharacters(aEOFCharacters);
+}
+
+void
+nsCSSScanner::AddEOFCharacters(uint32_t aEOFCharacters)
+{
+ mEOFCharacters = EOFCharacters(mEOFCharacters | aEOFCharacters);
+}
+
+static const char16_t kImpliedEOFCharacters[] = {
+ UCS2_REPLACEMENT_CHAR, '*', '/', '"', '\'', ')', 0
+};
+
+/* static */ void
+nsCSSScanner::AppendImpliedEOFCharacters(EOFCharacters aEOFCharacters,
+ nsAString& aResult)
+{
+ // First, ignore eEOFCharacters_DropBackslash.
+ uint32_t c = aEOFCharacters >> 1;
+
+ // All of the remaining EOFCharacters bits represent appended characters,
+ // and the bits are in the order that they need appending.
+ for (const char16_t* p = kImpliedEOFCharacters; *p && c; p++, c >>= 1) {
+ if (c & 1) {
+ aResult.Append(*p);
+ }
+ }
+
+ MOZ_ASSERT(c == 0, "too many bits in mEOFCharacters");
+}
+
+/**
+ * Consume the part of an URL token after the initial 'url('. Caller
+ * is assumed to have consumed 'url(' already. Will always produce
+ * either an URL or a Bad_URL token.
+ *
+ * Exposed for use by nsCSSParser::ParseMozDocumentRule, which applies
+ * the special lexical rules for URL tokens in a nonstandard context.
+ */
+void
+nsCSSScanner::NextURL(nsCSSToken& aToken)
+{
+ SkipWhitespace();
+
+ // aToken.mIdent may be "url" at this point; clear that out
+ aToken.mIdent.Truncate();
+
+ int32_t ch = Peek();
+ // Do we have a string?
+ if (ch == '"' || ch == '\'') {
+ ScanString(aToken);
+ if (MOZ_UNLIKELY(aToken.mType == eCSSToken_Bad_String)) {
+ aToken.mType = eCSSToken_Bad_URL;
+ return;
+ }
+ MOZ_ASSERT(aToken.mType == eCSSToken_String, "unexpected token type");
+
+ } else {
+ // Otherwise, this is the start of a non-quoted url (which may be empty).
+ aToken.mSymbol = char16_t(0);
+ GatherText(IS_URL_CHAR, aToken.mIdent);
+ }
+
+ // Consume trailing whitespace and then look for a close parenthesis.
+ SkipWhitespace();
+ ch = Peek();
+ // ch can be less than zero indicating EOF
+ if (MOZ_LIKELY(ch < 0 || ch == ')')) {
+ Advance();
+ aToken.mType = eCSSToken_URL;
+ if (ch < 0) {
+ AddEOFCharacters(eEOFCharacters_CloseParen);
+ }
+ } else {
+ mSeenBadToken = true;
+ aToken.mType = eCSSToken_Bad_URL;
+ }
+}
+
+/**
+ * Primary scanner entry point. Consume one token and fill in
+ * |aToken| accordingly. Will skip over any number of comments first,
+ * and will also skip over rather than return whitespace and comment
+ * tokens, depending on the value of |aSkip|.
+ *
+ * Returns true if it successfully consumed a token, false if EOF has
+ * been reached. Will always advance the current read position by at
+ * least one character unless called when already at EOF.
+ */
+bool
+nsCSSScanner::Next(nsCSSToken& aToken, nsCSSScannerExclude aSkip)
+{
+ int32_t ch;
+
+ // do this here so we don't have to do it in dozens of other places
+ aToken.mIdent.Truncate();
+ aToken.mType = eCSSToken_Symbol;
+
+ for (;;) {
+ // Consume any number of comments, and possibly also whitespace tokens,
+ // in between other tokens.
+ mTokenOffset = mOffset;
+ mTokenLineOffset = mLineOffset;
+ mTokenLineNumber = mLineNumber;
+
+ ch = Peek();
+ if (IsWhitespace(ch)) {
+ SkipWhitespace();
+ if (aSkip != eCSSScannerExclude_WhitespaceAndComments) {
+ aToken.mType = eCSSToken_Whitespace;
+ return true;
+ }
+ continue; // start again at the beginning
+ }
+ if (ch == '/' && !IsSVGMode() && Peek(1) == '*') {
+ SkipComment();
+ if (aSkip == eCSSScannerExclude_None) {
+ aToken.mType = eCSSToken_Comment;
+ return true;
+ }
+ continue; // start again at the beginning
+ }
+ break;
+ }
+
+ // EOF
+ if (ch < 0) {
+ return false;
+ }
+
+ // 'u' could be UNICODE-RANGE or an identifier-family token
+ if (ch == 'u' || ch == 'U') {
+ int32_t c2 = Peek(1);
+ int32_t c3 = Peek(2);
+ if (c2 == '+' && (IsHexDigit(c3) || c3 == '?')) {
+ return ScanURange(aToken);
+ }
+ return ScanIdent(aToken);
+ }
+
+ // identifier family
+ if (IsIdentStart(ch)) {
+ return ScanIdent(aToken);
+ }
+
+ // number family
+ if (IsDigit(ch)) {
+ return ScanNumber(aToken);
+ }
+
+ if (ch == '.' && IsDigit(Peek(1))) {
+ return ScanNumber(aToken);
+ }
+
+ if (ch == '+') {
+ int32_t c2 = Peek(1);
+ if (IsDigit(c2) || (c2 == '.' && IsDigit(Peek(2)))) {
+ return ScanNumber(aToken);
+ }
+ }
+
+ // '-' can start an identifier-family token, a number-family token,
+ // or an HTML-comment
+ if (ch == '-') {
+ int32_t c2 = Peek(1);
+ int32_t c3 = Peek(2);
+ if (IsIdentStart(c2) || (c2 == '-' && c3 != '>')) {
+ return ScanIdent(aToken);
+ }
+ if (IsDigit(c2) || (c2 == '.' && IsDigit(c3))) {
+ return ScanNumber(aToken);
+ }
+ if (c2 == '-' && c3 == '>') {
+ Advance(3);
+ aToken.mType = eCSSToken_HTMLComment;
+ aToken.mIdent.AssignLiteral("-->");
+ return true;
+ }
+ }
+
+ // the other HTML-comment token
+ if (ch == '<' && Peek(1) == '!' && Peek(2) == '-' && Peek(3) == '-') {
+ Advance(4);
+ aToken.mType = eCSSToken_HTMLComment;
+ aToken.mIdent.AssignLiteral("<!--");
+ return true;
+ }
+
+ // AT_KEYWORD
+ if (ch == '@') {
+ return ScanAtKeyword(aToken);
+ }
+
+ // HASH
+ if (ch == '#') {
+ return ScanHash(aToken);
+ }
+
+ // STRING
+ if (ch == '"' || ch == '\'') {
+ return ScanString(aToken);
+ }
+
+ // Match operators: ~= |= ^= $= *=
+ nsCSSTokenType opType = MatchOperatorType(ch);
+ if (opType != eCSSToken_Symbol && Peek(1) == '=') {
+ aToken.mType = opType;
+ Advance(2);
+ return true;
+ }
+
+ // Otherwise, a symbol (DELIM).
+ aToken.mSymbol = ch;
+ Advance();
+ return true;
+}
+
+/* nsCSSGridTemplateAreaScanner methods. */
+
+nsCSSGridTemplateAreaScanner::nsCSSGridTemplateAreaScanner(const nsAString& aBuffer)
+ : mBuffer(aBuffer.BeginReading())
+ , mOffset(0)
+ , mCount(aBuffer.Length())
+{
+}
+
+bool
+nsCSSGridTemplateAreaScanner::Next(nsCSSGridTemplateAreaToken& aTokenResult)
+{
+ int32_t ch;
+ // Skip whitespace
+ do {
+ if (mOffset >= mCount) {
+ return false;
+ }
+ ch = mBuffer[mOffset];
+ mOffset++;
+ } while (IsWhitespace(ch));
+
+ if (IsOpenCharClass(ch, IS_IDCHAR)) {
+ // Named cell token
+ uint32_t start = mOffset - 1; // offset of |ch|
+ while (mOffset < mCount && IsOpenCharClass(mBuffer[mOffset], IS_IDCHAR)) {
+ mOffset++;
+ }
+ aTokenResult.mName.Assign(&mBuffer[start], mOffset - start);
+ aTokenResult.isTrash = false;
+ } else if (ch == '.') {
+ // Null cell token
+ // Skip any other '.'
+ while (mOffset < mCount && mBuffer[mOffset] == '.') {
+ mOffset++;
+ }
+ aTokenResult.mName.Truncate();
+ aTokenResult.isTrash = false;
+ } else {
+ // Trash token
+ aTokenResult.isTrash = true;
+ }
+ return true;
+}
diff --git a/layout/style/nsCSSScanner.h b/layout/style/nsCSSScanner.h
new file mode 100644
index 000000000..ef03958c8
--- /dev/null
+++ b/layout/style/nsCSSScanner.h
@@ -0,0 +1,397 @@
+/* -*- 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/. */
+
+/* tokenization of CSS style sheets */
+
+#ifndef nsCSSScanner_h___
+#define nsCSSScanner_h___
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace css {
+class ErrorReporter;
+} // namespace css
+} // namespace mozilla
+
+// Token types; in close but not perfect correspondence to the token
+// categorization in section 4.1.1 of CSS2.1. (The deviations are all
+// the fault of css3-selectors, which has requirements that can only be
+// met by changing the generic tokenization.) The comment on each line
+// illustrates the form of each identifier.
+
+enum nsCSSTokenType {
+ // White space of any kind. No value fields are used. Note that
+ // comments do *not* count as white space; comments separate tokens
+ // but are not themselves tokens.
+ eCSSToken_Whitespace, //
+ // A comment.
+ eCSSToken_Comment, // /*...*/
+
+ // Identifier-like tokens. mIdent is the text of the identifier.
+ // The difference between ID and Hash is: if the text after the #
+ // would have been a valid Ident if the # hadn't been there, the
+ // scanner produces an ID token. Otherwise it produces a Hash token.
+ // (This distinction is required by css3-selectors.)
+ eCSSToken_Ident, // word
+ eCSSToken_Function, // word(
+ eCSSToken_AtKeyword, // @word
+ eCSSToken_ID, // #word
+ eCSSToken_Hash, // #0word
+
+ // Numeric tokens. mNumber is the floating-point value of the
+ // number, and mHasSign indicates whether there was an explicit sign
+ // (+ or -) in front of the number. If mIntegerValid is true, the
+ // number had the lexical form of an integer, and mInteger is its
+ // integer value. Lexically integer values outside the range of a
+ // 32-bit signed number are clamped to the maximum values; mNumber
+ // will indicate a 'truer' value in that case. Percentage tokens
+ // are always considered not to be integers, even if their numeric
+ // value is integral (100% => mNumber = 1.0). For Dimension
+ // tokens, mIdent holds the text of the unit.
+ eCSSToken_Number, // 1 -5 +2e3 3.14159 7.297352e-3
+ eCSSToken_Dimension, // 24px 8.5in
+ eCSSToken_Percentage, // 85% 1280.4%
+
+ // String-like tokens. In all cases, mIdent holds the text
+ // belonging to the string, and mSymbol holds the delimiter
+ // character, which may be ', ", or zero (only for unquoted URLs).
+ // Bad_String and Bad_URL tokens are emitted when the closing
+ // delimiter or parenthesis was missing.
+ eCSSToken_String, // 'foo bar' "foo bar"
+ eCSSToken_Bad_String, // 'foo bar
+ eCSSToken_URL, // url(foobar) url("foo bar")
+ eCSSToken_Bad_URL, // url(foo
+
+ // Any one-character symbol. mSymbol holds the character.
+ eCSSToken_Symbol, // . ; { } ! *
+
+ // Match operators. These are single tokens rather than pairs of
+ // Symbol tokens because css3-selectors forbids the presence of
+ // comments between the two characters. No value fields are used;
+ // the token type indicates which operator.
+ eCSSToken_Includes, // ~=
+ eCSSToken_Dashmatch, // |=
+ eCSSToken_Beginsmatch, // ^=
+ eCSSToken_Endsmatch, // $=
+ eCSSToken_Containsmatch, // *=
+
+ // Unicode-range token: currently used only in @font-face.
+ // The lexical rule for this token includes several forms that are
+ // semantically invalid. Therefore, mIdent always holds the
+ // complete original text of the token (so we can print it
+ // accurately in diagnostics), and mIntegerValid is true iff the
+ // token is semantically valid. In that case, mInteger holds the
+ // lowest value included in the range, and mInteger2 holds the
+ // highest value included in the range.
+ eCSSToken_URange, // U+007e U+01?? U+2000-206F
+
+ // HTML comment delimiters, ignored as a unit when they appear at
+ // the top level of a style sheet, for compatibility with websites
+ // written for compatibility with pre-CSS browsers. This token type
+ // subsumes the css2.1 CDO and CDC tokens, which are always treated
+ // the same by the parser. mIdent holds the text of the token, for
+ // diagnostics.
+ eCSSToken_HTMLComment, // <!-- -->
+};
+
+// Classification of tokens used to determine if a "/**/" string must be
+// inserted if pasting token streams together when serializing. We include
+// values corresponding to eCSSToken_Dashmatch and eCSSToken_Containsmatch,
+// as css-syntax does not treat these as whole tokens, but we will still
+// need to insert a "/**/" string between a '|' delim and a '|=' dashmatch
+// and between a '/' delim and a '*=' containsmatch.
+//
+// https://drafts.csswg.org/css-syntax/#serialization
+enum nsCSSTokenSerializationType {
+ eCSSTokenSerialization_Nothing,
+ eCSSTokenSerialization_Whitespace,
+ eCSSTokenSerialization_AtKeyword_or_Hash,
+ eCSSTokenSerialization_Number,
+ eCSSTokenSerialization_Dimension,
+ eCSSTokenSerialization_Percentage,
+ eCSSTokenSerialization_URange,
+ eCSSTokenSerialization_URL_or_BadURL,
+ eCSSTokenSerialization_Function,
+ eCSSTokenSerialization_Ident,
+ eCSSTokenSerialization_CDC,
+ eCSSTokenSerialization_DashMatch,
+ eCSSTokenSerialization_ContainsMatch,
+ eCSSTokenSerialization_Symbol_Hash, // '#'
+ eCSSTokenSerialization_Symbol_At, // '@'
+ eCSSTokenSerialization_Symbol_Dot_or_Plus, // '.', '+'
+ eCSSTokenSerialization_Symbol_Minus, // '-'
+ eCSSTokenSerialization_Symbol_OpenParen, // '('
+ eCSSTokenSerialization_Symbol_Question, // '?'
+ eCSSTokenSerialization_Symbol_Assorted, // '$', '^', '~'
+ eCSSTokenSerialization_Symbol_Equals, // '='
+ eCSSTokenSerialization_Symbol_Bar, // '|'
+ eCSSTokenSerialization_Symbol_Slash, // '/'
+ eCSSTokenSerialization_Symbol_Asterisk, // '*'
+ eCSSTokenSerialization_Other // anything else
+};
+
+// A single token returned from the scanner. mType is always
+// meaningful; comments above describe which other fields are
+// meaningful for which token types.
+struct nsCSSToken {
+ nsAutoString mIdent;
+ float mNumber;
+ int32_t mInteger;
+ int32_t mInteger2;
+ nsCSSTokenType mType;
+ char16_t mSymbol;
+ bool mIntegerValid;
+ bool mHasSign;
+
+ nsCSSToken()
+ : mNumber(0), mInteger(0), mInteger2(0), mType(eCSSToken_Whitespace),
+ mSymbol('\0'), mIntegerValid(false), mHasSign(false)
+ {}
+
+ bool IsSymbol(char16_t aSymbol) const {
+ return mType == eCSSToken_Symbol && mSymbol == aSymbol;
+ }
+
+ void AppendToString(nsString& aBuffer) const;
+};
+
+// Represents an nsCSSScanner's saved position in the input buffer.
+class nsCSSScannerPosition {
+ friend class nsCSSScanner;
+public:
+ nsCSSScannerPosition() : mInitialized(false) { }
+
+ uint32_t LineNumber() {
+ MOZ_ASSERT(mInitialized);
+ return mLineNumber;
+ }
+
+ uint32_t LineOffset() {
+ MOZ_ASSERT(mInitialized);
+ return mLineOffset;
+ }
+
+private:
+ uint32_t mOffset;
+ uint32_t mLineNumber;
+ uint32_t mLineOffset;
+ uint32_t mTokenLineNumber;
+ uint32_t mTokenLineOffset;
+ uint32_t mTokenOffset;
+ bool mInitialized;
+};
+
+enum nsCSSScannerExclude {
+ // Return all tokens, including whitespace and comments.
+ eCSSScannerExclude_None,
+ // Include whitespace but exclude comments.
+ eCSSScannerExclude_Comments,
+ // Exclude whitespace and comments.
+ eCSSScannerExclude_WhitespaceAndComments
+};
+
+// nsCSSScanner tokenizes an input stream using the CSS2.1 forward
+// compatible tokenization rules. Used internally by nsCSSParser;
+// not available for use by other code.
+class nsCSSScanner {
+ public:
+ // |aLineNumber == 1| is the beginning of a file, use |aLineNumber == 0|
+ // when the line number is unknown. The scanner does not take
+ // ownership of |aBuffer|, so the caller must be sure to keep it
+ // alive for the lifetime of the scanner.
+ nsCSSScanner(const nsAString& aBuffer, uint32_t aLineNumber);
+ ~nsCSSScanner();
+
+ void SetErrorReporter(mozilla::css::ErrorReporter* aReporter) {
+ mReporter = aReporter;
+ }
+ // Set whether or not we are processing SVG
+ void SetSVGMode(bool aSVGMode) {
+ mSVGMode = aSVGMode;
+ }
+ bool IsSVGMode() const {
+ return mSVGMode;
+ }
+
+ // Reset or check whether a BAD_URL or BAD_STRING token has been seen.
+ void ClearSeenBadToken() { mSeenBadToken = false; }
+ bool SeenBadToken() const { return mSeenBadToken; }
+
+ // Reset or check whether a "var(" FUNCTION token has been seen.
+ void ClearSeenVariableReference() { mSeenVariableReference = false; }
+ bool SeenVariableReference() const { return mSeenVariableReference; }
+
+ // Get the 1-based line number of the last character of
+ // the most recently processed token.
+ uint32_t GetLineNumber() const { return mTokenLineNumber; }
+
+ // Get the 0-based column number of the first character of
+ // the most recently processed token.
+ uint32_t GetColumnNumber() const
+ { return mTokenOffset - mTokenLineOffset; }
+
+ uint32_t GetTokenOffset() const
+ { return mTokenOffset; }
+
+ uint32_t GetTokenEndOffset() const
+ { return mOffset; }
+
+ // Get the text of the line containing the first character of
+ // the most recently processed token.
+ nsDependentSubstring GetCurrentLine() const;
+
+ // Get the next token. Return false on EOF. aTokenResult is filled
+ // in with the data for the token. aSkip controls whether
+ // whitespace and/or comment tokens are ever returned.
+ bool Next(nsCSSToken& aTokenResult, nsCSSScannerExclude aSkip);
+
+ // Get the body of an URL token (everything after the 'url(').
+ // This is exposed for use by nsCSSParser::ParseMozDocumentRule,
+ // which, for historical reasons, must make additional function
+ // tokens behave like url(). Please do not add new uses to the
+ // parser.
+ void NextURL(nsCSSToken& aTokenResult);
+
+ // This is exposed for use by nsCSSParser::ParsePseudoClassWithNthPairArg,
+ // because "2n-1" is a single DIMENSION token, and "n-1" is a single
+ // IDENT token, but the :nth() selector syntax wants to interpret
+ // them the same as "2n -1" and "n -1" respectively. Please do not
+ // add new uses to the parser.
+ //
+ // Note: this function may not be used to back up over a line boundary.
+ void Backup(uint32_t n);
+
+ // Starts recording the input stream from the current position.
+ void StartRecording();
+
+ // Abandons recording of the input stream.
+ void StopRecording();
+
+ // Stops recording of the input stream and appends the recorded
+ // input to aBuffer.
+ void StopRecording(nsString& aBuffer);
+
+ // Returns the length of the current recording.
+ uint32_t RecordingLength() const;
+
+#ifdef DEBUG
+ bool IsRecording() const;
+#endif
+
+ // Stores the current scanner offset into the specified object.
+ void SavePosition(nsCSSScannerPosition& aState);
+
+ // Resets the scanner offset to a position saved by SavePosition.
+ void RestoreSavedPosition(const nsCSSScannerPosition& aState);
+
+ enum EOFCharacters {
+ eEOFCharacters_None = 0x0000,
+
+ // to handle \<EOF> inside strings
+ eEOFCharacters_DropBackslash = 0x0001,
+
+ // to handle \<EOF> outside strings
+ eEOFCharacters_ReplacementChar = 0x0002,
+
+ // to close comments
+ eEOFCharacters_Asterisk = 0x0004,
+ eEOFCharacters_Slash = 0x0008,
+
+ // to close double-quoted strings
+ eEOFCharacters_DoubleQuote = 0x0010,
+
+ // to close single-quoted strings
+ eEOFCharacters_SingleQuote = 0x0020,
+
+ // to close URLs
+ eEOFCharacters_CloseParen = 0x0040,
+ };
+
+ // Appends any characters to the specified string the input stream to make the
+ // last token not rely on special EOF handling behavior.
+ //
+ // If eEOFCharacters_DropBackslash is in aEOFCharacters, it is ignored.
+ static void AppendImpliedEOFCharacters(EOFCharacters aEOFCharacters,
+ nsAString& aString);
+
+ EOFCharacters GetEOFCharacters() const {
+#ifdef DEBUG
+ AssertEOFCharactersValid(mEOFCharacters);
+#endif
+ return mEOFCharacters;
+ }
+
+#ifdef DEBUG
+ static void AssertEOFCharactersValid(uint32_t c);
+#endif
+
+protected:
+ int32_t Peek(uint32_t n = 0);
+ void Advance(uint32_t n = 1);
+ void AdvanceLine();
+
+ void SkipWhitespace();
+ void SkipComment();
+
+ bool GatherEscape(nsString& aOutput, bool aInString);
+ bool GatherText(uint8_t aClass, nsString& aIdent);
+
+ bool ScanIdent(nsCSSToken& aResult);
+ bool ScanAtKeyword(nsCSSToken& aResult);
+ bool ScanHash(nsCSSToken& aResult);
+ bool ScanNumber(nsCSSToken& aResult);
+ bool ScanString(nsCSSToken& aResult);
+ bool ScanURange(nsCSSToken& aResult);
+
+ void SetEOFCharacters(uint32_t aEOFCharacters);
+ void AddEOFCharacters(uint32_t aEOFCharacters);
+
+ const char16_t *mBuffer;
+ uint32_t mOffset;
+ uint32_t mCount;
+
+ uint32_t mLineNumber;
+ uint32_t mLineOffset;
+
+ uint32_t mTokenLineNumber;
+ uint32_t mTokenLineOffset;
+ uint32_t mTokenOffset;
+
+ uint32_t mRecordStartOffset;
+ EOFCharacters mEOFCharacters;
+
+ mozilla::css::ErrorReporter *mReporter;
+
+ // True if we are in SVG mode; false in "normal" CSS
+ bool mSVGMode;
+ bool mRecording;
+ bool mSeenBadToken;
+ bool mSeenVariableReference;
+};
+
+// Token for the grid-template-areas micro-syntax
+// http://dev.w3.org/csswg/css-grid/#propdef-grid-template-areas
+struct MOZ_STACK_CLASS nsCSSGridTemplateAreaToken {
+ nsAutoString mName; // Empty for a null cell, non-empty for a named cell
+ bool isTrash; // True for a trash token, mName is ignored in this case.
+};
+
+// Scanner for the grid-template-areas micro-syntax
+class nsCSSGridTemplateAreaScanner {
+public:
+ explicit nsCSSGridTemplateAreaScanner(const nsAString& aBuffer);
+
+ // Get the next token. Return false on EOF.
+ // aTokenResult is filled in with the data for the token.
+ bool Next(nsCSSGridTemplateAreaToken& aTokenResult);
+
+private:
+ const char16_t *mBuffer;
+ uint32_t mOffset;
+ uint32_t mCount;
+};
+
+#endif /* nsCSSScanner_h___ */
diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp
new file mode 100644
index 000000000..baf5b7897
--- /dev/null
+++ b/layout/style/nsCSSValue.cpp
@@ -0,0 +1,3237 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/StyleSheetInlines.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Move.h"
+#include "mozilla/css/ImageLoader.h"
+#include "CSSCalc.h"
+#include "gfxFontConstants.h"
+#include "imgIRequest.h"
+#include "imgRequestProxy.h"
+#include "nsIDocument.h"
+#include "nsIPrincipal.h"
+#include "nsCSSProps.h"
+#include "nsNetUtil.h"
+#include "nsPresContext.h"
+#include "nsStyleUtil.h"
+#include "nsDeviceContext.h"
+#include "nsStyleSet.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+
+static bool
+IsLocalRefURL(nsStringBuffer* aString)
+{
+ // Find the first non-"C0 controls + space" character.
+ char16_t* current = static_cast<char16_t*>(aString->Data());
+ for (; *current != '\0'; current++) {
+ if (*current > 0x20) {
+ // if the first non-"C0 controls + space" character is '#', this is a
+ // local-ref URL.
+ return *current == '#';
+ }
+ }
+
+ return false;
+}
+
+nsCSSValue::nsCSSValue(int32_t aValue, nsCSSUnit aUnit)
+ : mUnit(aUnit)
+{
+ MOZ_ASSERT(aUnit == eCSSUnit_Integer || aUnit == eCSSUnit_Enumerated ||
+ aUnit == eCSSUnit_EnumColor,
+ "not an int value");
+ if (aUnit == eCSSUnit_Integer || aUnit == eCSSUnit_Enumerated ||
+ aUnit == eCSSUnit_EnumColor) {
+ mValue.mInt = aValue;
+ }
+ else {
+ mUnit = eCSSUnit_Null;
+ mValue.mInt = 0;
+ }
+}
+
+nsCSSValue::nsCSSValue(float aValue, nsCSSUnit aUnit)
+ : mUnit(aUnit)
+{
+ MOZ_ASSERT(eCSSUnit_Percent <= aUnit, "not a float value");
+ if (eCSSUnit_Percent <= aUnit) {
+ mValue.mFloat = aValue;
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+ }
+ else {
+ mUnit = eCSSUnit_Null;
+ mValue.mInt = 0;
+ }
+}
+
+nsCSSValue::nsCSSValue(const nsString& aValue, nsCSSUnit aUnit)
+ : mUnit(aUnit)
+{
+ MOZ_ASSERT(UnitHasStringValue(), "not a string value");
+ if (UnitHasStringValue()) {
+ mValue.mString = BufferFromString(aValue).take();
+ }
+ else {
+ mUnit = eCSSUnit_Null;
+ mValue.mInt = 0;
+ }
+}
+
+nsCSSValue::nsCSSValue(nsCSSValue::Array* aValue, nsCSSUnit aUnit)
+ : mUnit(aUnit)
+{
+ MOZ_ASSERT(UnitHasArrayValue(), "bad unit");
+ mValue.mArray = aValue;
+ mValue.mArray->AddRef();
+}
+
+nsCSSValue::nsCSSValue(mozilla::css::URLValue* aValue)
+ : mUnit(eCSSUnit_URL)
+{
+ mValue.mURL = aValue;
+ mValue.mURL->AddRef();
+}
+
+nsCSSValue::nsCSSValue(mozilla::css::ImageValue* aValue)
+ : mUnit(eCSSUnit_Image)
+{
+ mValue.mImage = aValue;
+ mValue.mImage->AddRef();
+}
+
+nsCSSValue::nsCSSValue(nsCSSValueGradient* aValue)
+ : mUnit(eCSSUnit_Gradient)
+{
+ mValue.mGradient = aValue;
+ mValue.mGradient->AddRef();
+}
+
+nsCSSValue::nsCSSValue(nsCSSValueTokenStream* aValue)
+ : mUnit(eCSSUnit_TokenStream)
+{
+ mValue.mTokenStream = aValue;
+ mValue.mTokenStream->AddRef();
+}
+
+nsCSSValue::nsCSSValue(mozilla::css::GridTemplateAreasValue* aValue)
+ : mUnit(eCSSUnit_GridTemplateAreas)
+{
+ mValue.mGridTemplateAreas = aValue;
+ mValue.mGridTemplateAreas->AddRef();
+}
+
+nsCSSValue::nsCSSValue(css::FontFamilyListRefCnt* aValue)
+ : mUnit(eCSSUnit_FontFamilyList)
+{
+ mValue.mFontFamilyList = aValue;
+ mValue.mFontFamilyList->AddRef();
+}
+
+nsCSSValue::nsCSSValue(const nsCSSValue& aCopy)
+ : mUnit(aCopy.mUnit)
+{
+ if (mUnit <= eCSSUnit_DummyInherit) {
+ // nothing to do, but put this important case first
+ }
+ else if (eCSSUnit_Percent <= mUnit) {
+ mValue.mFloat = aCopy.mValue.mFloat;
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+ }
+ else if (UnitHasStringValue()) {
+ mValue.mString = aCopy.mValue.mString;
+ mValue.mString->AddRef();
+ }
+ else if (eCSSUnit_Integer <= mUnit && mUnit <= eCSSUnit_EnumColor) {
+ mValue.mInt = aCopy.mValue.mInt;
+ }
+ else if (IsIntegerColorUnit()) {
+ mValue.mColor = aCopy.mValue.mColor;
+ }
+ else if (IsFloatColorUnit()) {
+ mValue.mFloatColor = aCopy.mValue.mFloatColor;
+ mValue.mFloatColor->AddRef();
+ }
+ else if (eCSSUnit_ComplexColor == mUnit) {
+ mValue.mComplexColor = aCopy.mValue.mComplexColor;
+ mValue.mComplexColor->AddRef();
+ }
+ else if (UnitHasArrayValue()) {
+ mValue.mArray = aCopy.mValue.mArray;
+ mValue.mArray->AddRef();
+ }
+ else if (eCSSUnit_URL == mUnit) {
+ mValue.mURL = aCopy.mValue.mURL;
+ mValue.mURL->AddRef();
+ }
+ else if (eCSSUnit_Image == mUnit) {
+ mValue.mImage = aCopy.mValue.mImage;
+ mValue.mImage->AddRef();
+ }
+ else if (eCSSUnit_Gradient == mUnit) {
+ mValue.mGradient = aCopy.mValue.mGradient;
+ mValue.mGradient->AddRef();
+ }
+ else if (eCSSUnit_TokenStream == mUnit) {
+ mValue.mTokenStream = aCopy.mValue.mTokenStream;
+ mValue.mTokenStream->AddRef();
+ }
+ else if (eCSSUnit_Pair == mUnit) {
+ mValue.mPair = aCopy.mValue.mPair;
+ mValue.mPair->AddRef();
+ }
+ else if (eCSSUnit_Triplet == mUnit) {
+ mValue.mTriplet = aCopy.mValue.mTriplet;
+ mValue.mTriplet->AddRef();
+ }
+ else if (eCSSUnit_Rect == mUnit) {
+ mValue.mRect = aCopy.mValue.mRect;
+ mValue.mRect->AddRef();
+ }
+ else if (eCSSUnit_List == mUnit) {
+ mValue.mList = aCopy.mValue.mList;
+ mValue.mList->AddRef();
+ }
+ else if (eCSSUnit_ListDep == mUnit) {
+ mValue.mListDependent = aCopy.mValue.mListDependent;
+ }
+ else if (eCSSUnit_SharedList == mUnit) {
+ mValue.mSharedList = aCopy.mValue.mSharedList;
+ mValue.mSharedList->AddRef();
+ }
+ else if (eCSSUnit_PairList == mUnit) {
+ mValue.mPairList = aCopy.mValue.mPairList;
+ mValue.mPairList->AddRef();
+ }
+ else if (eCSSUnit_PairListDep == mUnit) {
+ mValue.mPairListDependent = aCopy.mValue.mPairListDependent;
+ }
+ else if (eCSSUnit_GridTemplateAreas == mUnit) {
+ mValue.mGridTemplateAreas = aCopy.mValue.mGridTemplateAreas;
+ mValue.mGridTemplateAreas->AddRef();
+ }
+ else if (eCSSUnit_FontFamilyList == mUnit) {
+ mValue.mFontFamilyList = aCopy.mValue.mFontFamilyList;
+ mValue.mFontFamilyList->AddRef();
+ }
+ else {
+ MOZ_ASSERT(false, "unknown unit");
+ }
+}
+
+nsCSSValue& nsCSSValue::operator=(const nsCSSValue& aCopy)
+{
+ if (this != &aCopy) {
+ Reset();
+ 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
+{
+ MOZ_ASSERT(mUnit != eCSSUnit_ListDep &&
+ aOther.mUnit != eCSSUnit_ListDep &&
+ mUnit != eCSSUnit_PairListDep &&
+ aOther.mUnit != eCSSUnit_PairListDep,
+ "don't use operator== with dependent lists");
+
+ if (mUnit == aOther.mUnit) {
+ if (mUnit <= eCSSUnit_DummyInherit) {
+ return true;
+ }
+ else if (UnitHasStringValue()) {
+ return (NS_strcmp(GetBufferValue(mValue.mString),
+ GetBufferValue(aOther.mValue.mString)) == 0);
+ }
+ else if ((eCSSUnit_Integer <= mUnit) && (mUnit <= eCSSUnit_EnumColor)) {
+ return mValue.mInt == aOther.mValue.mInt;
+ }
+ else if (IsIntegerColorUnit()) {
+ return mValue.mColor == aOther.mValue.mColor;
+ }
+ else if (IsFloatColorUnit()) {
+ return *mValue.mFloatColor == *aOther.mValue.mFloatColor;
+ }
+ else if (eCSSUnit_ComplexColor == mUnit) {
+ return *mValue.mComplexColor == *aOther.mValue.mComplexColor;
+ }
+ else if (UnitHasArrayValue()) {
+ return *mValue.mArray == *aOther.mValue.mArray;
+ }
+ else if (eCSSUnit_URL == mUnit) {
+ return mValue.mURL->Equals(*aOther.mValue.mURL);
+ }
+ else if (eCSSUnit_Image == mUnit) {
+ return mValue.mImage->Equals(*aOther.mValue.mImage);
+ }
+ else if (eCSSUnit_Gradient == mUnit) {
+ return *mValue.mGradient == *aOther.mValue.mGradient;
+ }
+ else if (eCSSUnit_TokenStream == mUnit) {
+ return *mValue.mTokenStream == *aOther.mValue.mTokenStream;
+ }
+ else if (eCSSUnit_Pair == mUnit) {
+ return *mValue.mPair == *aOther.mValue.mPair;
+ }
+ else if (eCSSUnit_Triplet == mUnit) {
+ return *mValue.mTriplet == *aOther.mValue.mTriplet;
+ }
+ else if (eCSSUnit_Rect == mUnit) {
+ return *mValue.mRect == *aOther.mValue.mRect;
+ }
+ else if (eCSSUnit_List == mUnit) {
+ return nsCSSValueList::Equal(mValue.mList, aOther.mValue.mList);
+ }
+ else if (eCSSUnit_SharedList == mUnit) {
+ return *mValue.mSharedList == *aOther.mValue.mSharedList;
+ }
+ else if (eCSSUnit_PairList == mUnit) {
+ return nsCSSValuePairList::Equal(mValue.mPairList,
+ aOther.mValue.mPairList);
+ }
+ else if (eCSSUnit_GridTemplateAreas == mUnit) {
+ return *mValue.mGridTemplateAreas == *aOther.mValue.mGridTemplateAreas;
+ }
+ else if (eCSSUnit_FontFamilyList == mUnit) {
+ return *mValue.mFontFamilyList == *aOther.mValue.mFontFamilyList;
+ }
+ else {
+ return mValue.mFloat == aOther.mValue.mFloat;
+ }
+ }
+ return false;
+}
+
+double
+nsCSSValue::GetAngleValueInRadians() const
+{
+ double angle = GetFloatValue();
+
+ switch (GetUnit()) {
+ case eCSSUnit_Radian: return angle;
+ case eCSSUnit_Turn: return angle * 2 * M_PI;
+ case eCSSUnit_Degree: return angle * M_PI / 180.0;
+ case eCSSUnit_Grad: return angle * M_PI / 200.0;
+
+ default:
+ MOZ_ASSERT(false, "unrecognized angular unit");
+ return 0.0;
+ }
+}
+
+double
+nsCSSValue::GetAngleValueInDegrees() const
+{
+ double angle = GetFloatValue();
+
+ switch (GetUnit()) {
+ case eCSSUnit_Degree: return angle;
+ case eCSSUnit_Grad: return angle * 0.9; // grad / 400 * 360
+ case eCSSUnit_Radian: return angle * 180.0 / M_PI; // rad / 2pi * 360
+ case eCSSUnit_Turn: return angle * 360.0;
+
+ default:
+ MOZ_ASSERT(false, "unrecognized angular unit");
+ return 0.0;
+ }
+}
+
+imgRequestProxy* nsCSSValue::GetImageValue(nsIDocument* aDocument) const
+{
+ MOZ_ASSERT(mUnit == eCSSUnit_Image, "not an Image value");
+ return mValue.mImage->mRequests.GetWeak(aDocument);
+}
+
+already_AddRefed<imgRequestProxy>
+nsCSSValue::GetPossiblyStaticImageValue(nsIDocument* aDocument,
+ nsPresContext* aPresContext) const
+{
+ imgRequestProxy* req = GetImageValue(aDocument);
+ if (aPresContext->IsDynamic()) {
+ return do_AddRef(req);
+ }
+ return nsContentUtils::GetStaticRequest(req);
+}
+
+nscoord nsCSSValue::GetFixedLength(nsPresContext* aPresContext) const
+{
+ MOZ_ASSERT(mUnit == eCSSUnit_PhysicalMillimeter,
+ "not a fixed length unit");
+
+ float inches = mValue.mFloat / MM_PER_INCH_FLOAT;
+ return NSToCoordFloorClamped(inches *
+ float(aPresContext->DeviceContext()->AppUnitsPerPhysicalInch()));
+}
+
+nscoord nsCSSValue::GetPixelLength() const
+{
+ MOZ_ASSERT(IsPixelLengthUnit(), "not a fixed length unit");
+
+ double scaleFactor;
+ switch (mUnit) {
+ case eCSSUnit_Pixel: return nsPresContext::CSSPixelsToAppUnits(mValue.mFloat);
+ 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.mFloat*scaleFactor));
+}
+
+void nsCSSValue::DoReset()
+{
+ if (UnitHasStringValue()) {
+ mValue.mString->Release();
+ } else if (IsFloatColorUnit()) {
+ mValue.mFloatColor->Release();
+ } else if (eCSSUnit_ComplexColor == mUnit) {
+ mValue.mComplexColor->Release();
+ } else if (UnitHasArrayValue()) {
+ mValue.mArray->Release();
+ } else if (eCSSUnit_URL == mUnit) {
+ mValue.mURL->Release();
+ } else if (eCSSUnit_Image == mUnit) {
+ mValue.mImage->Release();
+ } else if (eCSSUnit_Gradient == mUnit) {
+ mValue.mGradient->Release();
+ } else if (eCSSUnit_TokenStream == mUnit) {
+ mValue.mTokenStream->Release();
+ } else if (eCSSUnit_Pair == mUnit) {
+ mValue.mPair->Release();
+ } else if (eCSSUnit_Triplet == mUnit) {
+ mValue.mTriplet->Release();
+ } else if (eCSSUnit_Rect == mUnit) {
+ mValue.mRect->Release();
+ } else if (eCSSUnit_List == mUnit) {
+ mValue.mList->Release();
+ } else if (eCSSUnit_SharedList == mUnit) {
+ mValue.mSharedList->Release();
+ } else if (eCSSUnit_PairList == mUnit) {
+ mValue.mPairList->Release();
+ } else if (eCSSUnit_GridTemplateAreas == mUnit) {
+ mValue.mGridTemplateAreas->Release();
+ } else if (eCSSUnit_FontFamilyList == mUnit) {
+ mValue.mFontFamilyList->Release();
+ }
+ mUnit = eCSSUnit_Null;
+}
+
+void nsCSSValue::SetIntValue(int32_t aValue, nsCSSUnit aUnit)
+{
+ MOZ_ASSERT(aUnit == eCSSUnit_Integer || aUnit == eCSSUnit_Enumerated ||
+ aUnit == eCSSUnit_EnumColor,
+ "not an int value");
+ Reset();
+ if (aUnit == eCSSUnit_Integer || aUnit == eCSSUnit_Enumerated ||
+ aUnit == eCSSUnit_EnumColor) {
+ mUnit = aUnit;
+ mValue.mInt = aValue;
+ }
+}
+
+void nsCSSValue::SetPercentValue(float aValue)
+{
+ Reset();
+ mUnit = eCSSUnit_Percent;
+ mValue.mFloat = aValue;
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+}
+
+void nsCSSValue::SetFloatValue(float aValue, nsCSSUnit aUnit)
+{
+ MOZ_ASSERT(IsFloatUnit(aUnit), "not a float value");
+ Reset();
+ if (IsFloatUnit(aUnit)) {
+ mUnit = aUnit;
+ mValue.mFloat = aValue;
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+ }
+}
+
+void nsCSSValue::SetStringValue(const nsString& aValue,
+ nsCSSUnit aUnit)
+{
+ Reset();
+ mUnit = aUnit;
+ MOZ_ASSERT(UnitHasStringValue(), "not a string unit");
+ if (UnitHasStringValue()) {
+ mValue.mString = BufferFromString(aValue).take();
+ } else
+ mUnit = eCSSUnit_Null;
+}
+
+void nsCSSValue::SetColorValue(nscolor aValue)
+{
+ SetIntegerColorValue(aValue, eCSSUnit_RGBAColor);
+}
+
+
+
+void nsCSSValue::SetIntegerColorValue(nscolor aValue, nsCSSUnit aUnit)
+{
+ Reset();
+ mUnit = aUnit;
+ MOZ_ASSERT(IsIntegerColorUnit(), "bad unit");
+ mValue.mColor = aValue;
+}
+
+void nsCSSValue::SetIntegerCoordValue(nscoord aValue)
+{
+ SetFloatValue(nsPresContext::AppUnitsToFloatCSSPixels(aValue),
+ eCSSUnit_Pixel);
+}
+
+void nsCSSValue::SetFloatColorValue(float aComponent1,
+ float aComponent2,
+ float aComponent3,
+ float aAlpha,
+ nsCSSUnit aUnit)
+{
+ Reset();
+ mUnit = aUnit;
+ MOZ_ASSERT(IsFloatColorUnit(), "bad unit");
+ mValue.mFloatColor =
+ new nsCSSValueFloatColor(aComponent1, aComponent2, aComponent3, aAlpha);
+ mValue.mFloatColor->AddRef();
+}
+
+void
+nsCSSValue::SetRGBAColorValue(const RGBAColorData& aValue)
+{
+ SetFloatColorValue(aValue.mR, aValue.mG, aValue.mB,
+ aValue.mA, eCSSUnit_PercentageRGBAColor);
+}
+
+void
+nsCSSValue::SetComplexColorValue(already_AddRefed<ComplexColorValue> aValue)
+{
+ Reset();
+ mUnit = eCSSUnit_ComplexColor;
+ mValue.mComplexColor = aValue.take();
+}
+
+void nsCSSValue::SetArrayValue(nsCSSValue::Array* aValue, nsCSSUnit aUnit)
+{
+ Reset();
+ mUnit = aUnit;
+ MOZ_ASSERT(UnitHasArrayValue(), "bad unit");
+ mValue.mArray = aValue;
+ mValue.mArray->AddRef();
+}
+
+void nsCSSValue::SetURLValue(mozilla::css::URLValue* aValue)
+{
+ Reset();
+ mUnit = eCSSUnit_URL;
+ mValue.mURL = aValue;
+ mValue.mURL->AddRef();
+}
+
+void nsCSSValue::SetImageValue(mozilla::css::ImageValue* aValue)
+{
+ Reset();
+ mUnit = eCSSUnit_Image;
+ mValue.mImage = aValue;
+ mValue.mImage->AddRef();
+}
+
+void nsCSSValue::SetGradientValue(nsCSSValueGradient* aValue)
+{
+ Reset();
+ mUnit = eCSSUnit_Gradient;
+ mValue.mGradient = aValue;
+ mValue.mGradient->AddRef();
+}
+
+void nsCSSValue::SetTokenStreamValue(nsCSSValueTokenStream* aValue)
+{
+ Reset();
+ mUnit = eCSSUnit_TokenStream;
+ mValue.mTokenStream = aValue;
+ mValue.mTokenStream->AddRef();
+}
+
+void nsCSSValue::SetGridTemplateAreas(mozilla::css::GridTemplateAreasValue* aValue)
+{
+ Reset();
+ mUnit = eCSSUnit_GridTemplateAreas;
+ mValue.mGridTemplateAreas = aValue;
+ mValue.mGridTemplateAreas->AddRef();
+}
+
+void nsCSSValue::SetFontFamilyListValue(css::FontFamilyListRefCnt* aValue)
+{
+ Reset();
+ mUnit = eCSSUnit_FontFamilyList;
+ mValue.mFontFamilyList = aValue;
+ mValue.mFontFamilyList->AddRef();
+}
+
+void nsCSSValue::SetPairValue(const nsCSSValuePair* aValue)
+{
+ // pairs should not be used for null/inherit/initial values
+ MOZ_ASSERT(aValue &&
+ aValue->mXValue.GetUnit() != eCSSUnit_Null &&
+ aValue->mYValue.GetUnit() != eCSSUnit_Null &&
+ aValue->mXValue.GetUnit() != eCSSUnit_Inherit &&
+ aValue->mYValue.GetUnit() != eCSSUnit_Inherit &&
+ aValue->mXValue.GetUnit() != eCSSUnit_Initial &&
+ aValue->mYValue.GetUnit() != eCSSUnit_Initial &&
+ aValue->mXValue.GetUnit() != eCSSUnit_Unset &&
+ aValue->mYValue.GetUnit() != eCSSUnit_Unset,
+ "missing or inappropriate pair value");
+ Reset();
+ mUnit = eCSSUnit_Pair;
+ mValue.mPair = new nsCSSValuePair_heap(aValue->mXValue, aValue->mYValue);
+ mValue.mPair->AddRef();
+}
+
+void nsCSSValue::SetPairValue(const nsCSSValue& xValue,
+ const nsCSSValue& yValue)
+{
+ MOZ_ASSERT(xValue.GetUnit() != eCSSUnit_Null &&
+ yValue.GetUnit() != eCSSUnit_Null &&
+ xValue.GetUnit() != eCSSUnit_Inherit &&
+ yValue.GetUnit() != eCSSUnit_Inherit &&
+ xValue.GetUnit() != eCSSUnit_Initial &&
+ yValue.GetUnit() != eCSSUnit_Initial &&
+ xValue.GetUnit() != eCSSUnit_Unset &&
+ yValue.GetUnit() != eCSSUnit_Unset,
+ "inappropriate pair value");
+ Reset();
+ mUnit = eCSSUnit_Pair;
+ mValue.mPair = new nsCSSValuePair_heap(xValue, yValue);
+ mValue.mPair->AddRef();
+}
+
+void nsCSSValue::SetTripletValue(const nsCSSValueTriplet* aValue)
+{
+ // triplet should not be used for null/inherit/initial values
+ MOZ_ASSERT(aValue &&
+ aValue->mXValue.GetUnit() != eCSSUnit_Null &&
+ aValue->mYValue.GetUnit() != eCSSUnit_Null &&
+ aValue->mZValue.GetUnit() != eCSSUnit_Null &&
+ aValue->mXValue.GetUnit() != eCSSUnit_Inherit &&
+ aValue->mYValue.GetUnit() != eCSSUnit_Inherit &&
+ aValue->mZValue.GetUnit() != eCSSUnit_Inherit &&
+ aValue->mXValue.GetUnit() != eCSSUnit_Initial &&
+ aValue->mYValue.GetUnit() != eCSSUnit_Initial &&
+ aValue->mZValue.GetUnit() != eCSSUnit_Initial &&
+ aValue->mXValue.GetUnit() != eCSSUnit_Unset &&
+ aValue->mYValue.GetUnit() != eCSSUnit_Unset &&
+ aValue->mZValue.GetUnit() != eCSSUnit_Unset,
+ "missing or inappropriate triplet value");
+ Reset();
+ mUnit = eCSSUnit_Triplet;
+ mValue.mTriplet = new nsCSSValueTriplet_heap(aValue->mXValue, aValue->mYValue, aValue->mZValue);
+ mValue.mTriplet->AddRef();
+}
+
+void nsCSSValue::SetTripletValue(const nsCSSValue& xValue,
+ const nsCSSValue& yValue,
+ const nsCSSValue& zValue)
+{
+ // Only allow Null for the z component
+ MOZ_ASSERT(xValue.GetUnit() != eCSSUnit_Null &&
+ yValue.GetUnit() != eCSSUnit_Null &&
+ xValue.GetUnit() != eCSSUnit_Inherit &&
+ yValue.GetUnit() != eCSSUnit_Inherit &&
+ zValue.GetUnit() != eCSSUnit_Inherit &&
+ xValue.GetUnit() != eCSSUnit_Initial &&
+ yValue.GetUnit() != eCSSUnit_Initial &&
+ zValue.GetUnit() != eCSSUnit_Initial &&
+ xValue.GetUnit() != eCSSUnit_Unset &&
+ yValue.GetUnit() != eCSSUnit_Unset &&
+ zValue.GetUnit() != eCSSUnit_Unset,
+ "inappropriate triplet value");
+ Reset();
+ mUnit = eCSSUnit_Triplet;
+ mValue.mTriplet = new nsCSSValueTriplet_heap(xValue, yValue, zValue);
+ mValue.mTriplet->AddRef();
+}
+
+nsCSSRect& nsCSSValue::SetRectValue()
+{
+ Reset();
+ mUnit = eCSSUnit_Rect;
+ mValue.mRect = new nsCSSRect_heap;
+ mValue.mRect->AddRef();
+ return *mValue.mRect;
+}
+
+nsCSSValueList* nsCSSValue::SetListValue()
+{
+ Reset();
+ mUnit = eCSSUnit_List;
+ mValue.mList = new nsCSSValueList_heap;
+ mValue.mList->AddRef();
+ return mValue.mList;
+}
+
+void nsCSSValue::SetSharedListValue(nsCSSValueSharedList* aList)
+{
+ Reset();
+ mUnit = eCSSUnit_SharedList;
+ mValue.mSharedList = aList;
+ mValue.mSharedList->AddRef();
+}
+
+void nsCSSValue::SetDependentListValue(nsCSSValueList* aList)
+{
+ Reset();
+ if (aList) {
+ mUnit = eCSSUnit_ListDep;
+ mValue.mListDependent = aList;
+ }
+}
+
+void
+nsCSSValue::AdoptListValue(UniquePtr<nsCSSValueList> aValue)
+{
+ // We have to copy the first element since for owned lists the first
+ // element should be an nsCSSValueList_heap object.
+ SetListValue();
+ mValue.mList->mValue = Move(aValue->mValue);
+ mValue.mList->mNext = aValue->mNext;
+ aValue->mNext = nullptr;
+ aValue.reset();
+}
+
+nsCSSValuePairList* nsCSSValue::SetPairListValue()
+{
+ Reset();
+ mUnit = eCSSUnit_PairList;
+ mValue.mPairList = new nsCSSValuePairList_heap;
+ mValue.mPairList->AddRef();
+ return mValue.mPairList;
+}
+
+void nsCSSValue::SetDependentPairListValue(nsCSSValuePairList* aList)
+{
+ Reset();
+ if (aList) {
+ mUnit = eCSSUnit_PairListDep;
+ mValue.mPairListDependent = aList;
+ }
+}
+
+void
+nsCSSValue::AdoptPairListValue(UniquePtr<nsCSSValuePairList> aValue)
+{
+ // We have to copy the first element, since for owned pair lists, the first
+ // element should be an nsCSSValuePairList_heap object.
+ SetPairListValue();
+ mValue.mPairList->mXValue = Move(aValue->mXValue);
+ mValue.mPairList->mYValue = Move(aValue->mYValue);
+ mValue.mPairList->mNext = aValue->mNext;
+ aValue->mNext = nullptr;
+ aValue.reset();
+}
+
+void nsCSSValue::SetAutoValue()
+{
+ Reset();
+ mUnit = eCSSUnit_Auto;
+}
+
+void nsCSSValue::SetInheritValue()
+{
+ Reset();
+ mUnit = eCSSUnit_Inherit;
+}
+
+void nsCSSValue::SetInitialValue()
+{
+ Reset();
+ mUnit = eCSSUnit_Initial;
+}
+
+void nsCSSValue::SetUnsetValue()
+{
+ Reset();
+ mUnit = eCSSUnit_Unset;
+}
+
+void nsCSSValue::SetNoneValue()
+{
+ Reset();
+ mUnit = eCSSUnit_None;
+}
+
+void nsCSSValue::SetAllValue()
+{
+ Reset();
+ mUnit = eCSSUnit_All;
+}
+
+void nsCSSValue::SetNormalValue()
+{
+ Reset();
+ mUnit = eCSSUnit_Normal;
+}
+
+void nsCSSValue::SetSystemFontValue()
+{
+ Reset();
+ mUnit = eCSSUnit_System_Font;
+}
+
+void nsCSSValue::SetDummyValue()
+{
+ Reset();
+ mUnit = eCSSUnit_Dummy;
+}
+
+void nsCSSValue::SetDummyInheritValue()
+{
+ Reset();
+ mUnit = eCSSUnit_DummyInherit;
+}
+
+void nsCSSValue::SetCalcValue(const nsStyleCoord::CalcValue* aCalc)
+{
+ RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(1);
+ if (!aCalc->mHasPercent) {
+ arr->Item(0).SetIntegerCoordValue(aCalc->mLength);
+ } else {
+ nsCSSValue::Array *arr2 = nsCSSValue::Array::Create(2);
+ arr->Item(0).SetArrayValue(arr2, eCSSUnit_Calc_Plus);
+ arr2->Item(0).SetIntegerCoordValue(aCalc->mLength);
+ arr2->Item(1).SetPercentValue(aCalc->mPercent);
+ }
+
+ SetArrayValue(arr, eCSSUnit_Calc);
+}
+
+void nsCSSValue::StartImageLoad(nsIDocument* aDocument) const
+{
+ MOZ_ASSERT(eCSSUnit_URL == mUnit, "Not a URL value!");
+ mozilla::css::ImageValue* image =
+ new mozilla::css::ImageValue(mValue.mURL->GetURI(),
+ mValue.mURL->mString,
+ mValue.mURL->mBaseURI,
+ mValue.mURL->mReferrer,
+ mValue.mURL->mOriginPrincipal,
+ aDocument);
+
+ nsCSSValue* writable = const_cast<nsCSSValue*>(this);
+ writable->SetImageValue(image);
+}
+
+nscolor nsCSSValue::GetColorValue() const
+{
+ MOZ_ASSERT(IsNumericColorUnit(), "not a color value");
+ if (IsFloatColorUnit()) {
+ return mValue.mFloatColor->GetColorValue(mUnit);
+ }
+ return mValue.mColor;
+}
+
+bool nsCSSValue::IsNonTransparentColor() const
+{
+ // We have the value in the form it was specified in at this point, so we
+ // have to look for both the keyword 'transparent' and its equivalent in
+ // rgba notation.
+ nsDependentString buf;
+ return
+ (IsIntegerColorUnit() && NS_GET_A(GetColorValue()) > 0) ||
+ (IsFloatColorUnit() && mValue.mFloatColor->IsNonTransparentColor()) ||
+ (mUnit == eCSSUnit_Ident &&
+ !nsGkAtoms::transparent->Equals(GetStringValue(buf))) ||
+ (mUnit == eCSSUnit_EnumColor);
+}
+
+nsCSSValue::Array*
+nsCSSValue::InitFunction(nsCSSKeyword aFunctionId, uint32_t aNumArgs)
+{
+ RefPtr<nsCSSValue::Array> func = Array::Create(aNumArgs + 1);
+ func->Item(0).SetIntValue(aFunctionId, eCSSUnit_Enumerated);
+ SetArrayValue(func, eCSSUnit_Function);
+ return func;
+}
+
+bool
+nsCSSValue::EqualsFunction(nsCSSKeyword aFunctionId) const
+{
+ if (mUnit != eCSSUnit_Function) {
+ return false;
+ }
+
+ nsCSSValue::Array* func = mValue.mArray;
+ MOZ_ASSERT(func && func->Count() >= 1 &&
+ func->Item(0).GetUnit() == eCSSUnit_Enumerated,
+ "illegally structured function value");
+
+ nsCSSKeyword thisFunctionId = func->Item(0).GetKeywordValue();
+ return thisFunctionId == aFunctionId;
+}
+
+// static
+already_AddRefed<nsStringBuffer>
+nsCSSValue::BufferFromString(const nsString& aValue)
+{
+ RefPtr<nsStringBuffer> buffer = nsStringBuffer::FromString(aValue);
+ if (buffer) {
+ return buffer.forget();
+ }
+
+ nsString::size_type length = aValue.Length();
+
+ // NOTE: Alloc prouduces a new, already-addref'd (refcnt = 1) buffer.
+ // NOTE: String buffer allocation is currently fallible.
+ size_t sz = (length + 1) * sizeof(char16_t);
+ buffer = nsStringBuffer::Alloc(sz);
+ if (MOZ_UNLIKELY(!buffer)) {
+ NS_ABORT_OOM(sz);
+ }
+
+ char16_t* data = static_cast<char16_t*>(buffer->Data());
+ nsCharTraits<char16_t>::copy(data, aValue.get(), length);
+ // Null-terminate.
+ data[length] = 0;
+ return buffer.forget();
+}
+
+namespace {
+
+struct CSSValueSerializeCalcOps {
+ CSSValueSerializeCalcOps(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aSerialization)
+ : mProperty(aProperty),
+ mResult(aResult),
+ mValueSerialization(aSerialization)
+ {
+ }
+
+ typedef nsCSSValue input_type;
+ typedef nsCSSValue::Array input_array_type;
+
+ static nsCSSUnit GetUnit(const input_type& aValue) {
+ return aValue.GetUnit();
+ }
+
+ void Append(const char* aString)
+ {
+ mResult.AppendASCII(aString);
+ }
+
+ void AppendLeafValue(const input_type& aValue)
+ {
+ MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Percent ||
+ aValue.IsLengthUnit() ||
+ aValue.GetUnit() == eCSSUnit_Number,
+ "unexpected unit");
+ aValue.AppendToString(mProperty, mResult, mValueSerialization);
+ }
+
+ void AppendNumber(const input_type& aValue)
+ {
+ MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
+ aValue.AppendToString(mProperty, mResult, mValueSerialization);
+ }
+
+private:
+ nsCSSPropertyID mProperty;
+ nsAString &mResult;
+ nsCSSValue::Serialization mValueSerialization;
+};
+
+} // namespace
+
+void
+nsCSSValue::AppendPolygonToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ Serialization aSerialization) const
+{
+ const nsCSSValue::Array* array = GetArrayValue();
+ MOZ_ASSERT(array->Count() > 1 && array->Count() <= 3,
+ "Polygons must have name and at least one more value.");
+ // When the array has 2 elements, the item on index 1 is the coordinate
+ // pair list.
+ // When the array has 3 elements, the item on index 1 is a fill-rule
+ // and item on index 2 is the coordinate pair list.
+ size_t index = 1;
+ if (array->Count() == 3) {
+ const nsCSSValue& fillRuleValue = array->Item(index);
+ MOZ_ASSERT(fillRuleValue.GetUnit() == eCSSUnit_Enumerated,
+ "Expected polygon fill rule.");
+ int32_t fillRule = fillRuleValue.GetIntValue();
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(fillRule,
+ nsCSSProps::kFillRuleKTable),
+ aResult);
+ aResult.AppendLiteral(", ");
+ ++index;
+ }
+ array->Item(index).AppendToString(aProperty, aResult, aSerialization);
+}
+
+inline void
+nsCSSValue::AppendPositionCoordinateToString(
+ const nsCSSValue& aValue, nsCSSPropertyID aProperty,
+ nsAString& aResult, Serialization aSerialization) const
+{
+ if (aValue.GetUnit() == eCSSUnit_Enumerated) {
+ int32_t intValue = aValue.GetIntValue();
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
+ nsCSSProps::kShapeRadiusKTable), aResult);
+ } else {
+ aValue.AppendToString(aProperty, aResult, aSerialization);
+ }
+}
+
+void
+nsCSSValue::AppendCircleOrEllipseToString(nsCSSKeyword aFunctionId,
+ nsCSSPropertyID aProperty,
+ nsAString& aResult,
+ Serialization aSerialization) const
+{
+ const nsCSSValue::Array* array = GetArrayValue();
+ size_t count = aFunctionId == eCSSKeyword_circle ? 2 : 3;
+ MOZ_ASSERT(array->Count() == count + 1, "wrong number of arguments");
+
+ bool hasRadii = array->Item(1).GetUnit() != eCSSUnit_Null;
+
+ // closest-side is the default, so we don't need to
+ // output it if all values are closest-side.
+ if (array->Item(1).GetUnit() == eCSSUnit_Enumerated &&
+ array->Item(1).GetIntValue() == NS_RADIUS_CLOSEST_SIDE &&
+ (aFunctionId == eCSSKeyword_circle ||
+ (array->Item(2).GetUnit() == eCSSUnit_Enumerated &&
+ array->Item(2).GetIntValue() == NS_RADIUS_CLOSEST_SIDE))) {
+ hasRadii = false;
+ } else {
+ AppendPositionCoordinateToString(array->Item(1), aProperty,
+ aResult, aSerialization);
+
+ if (hasRadii && aFunctionId == eCSSKeyword_ellipse) {
+ aResult.Append(' ');
+ AppendPositionCoordinateToString(array->Item(2), aProperty,
+ aResult, aSerialization);
+ }
+ }
+
+ if (hasRadii) {
+ aResult.Append(' ');
+ }
+
+ // Any position specified?
+ if (array->Item(count).GetUnit() != eCSSUnit_Array) {
+ MOZ_ASSERT(array->Item(count).GetUnit() == eCSSUnit_Null,
+ "unexpected value");
+ // We only serialize to the 2 or 4 value form
+ // |circle()| is valid, but should be expanded
+ // to |circle(at 50% 50%)|
+ aResult.AppendLiteral("at 50% 50%");
+ return;
+ }
+
+ aResult.AppendLiteral("at ");
+ array->Item(count).AppendBasicShapePositionToString(aResult, aSerialization);
+}
+
+// https://drafts.csswg.org/css-shapes/#basic-shape-serialization
+// basic-shape asks us to omit a lot of redundant things whilst serializing
+// position values. Other specs are not clear about this
+// (https://github.com/w3c/csswg-drafts/issues/368), so for now we special-case
+// basic shapes only
+void
+nsCSSValue::AppendBasicShapePositionToString(nsAString& aResult,
+ Serialization aSerialization) const
+{
+ const nsCSSValue::Array* array = GetArrayValue();
+ // We always parse these into an array of four elements
+ MOZ_ASSERT(array->Count() == 4,
+ "basic-shape position value doesn't have enough elements");
+
+ const nsCSSValue &xEdge = array->Item(0);
+ const nsCSSValue &xOffset = array->Item(1);
+ const nsCSSValue &yEdge = array->Item(2);
+ const nsCSSValue &yOffset = array->Item(3);
+
+ MOZ_ASSERT(xEdge.GetUnit() == eCSSUnit_Enumerated &&
+ yEdge.GetUnit() == eCSSUnit_Enumerated &&
+ xOffset.IsLengthPercentCalcUnit() &&
+ yOffset.IsLengthPercentCalcUnit() &&
+ xEdge.GetIntValue() != NS_STYLE_IMAGELAYER_POSITION_CENTER &&
+ yEdge.GetIntValue() != NS_STYLE_IMAGELAYER_POSITION_CENTER,
+ "Ensure invariants from ParsePositionValueBasicShape "
+ "haven't been modified");
+ if (xEdge.GetIntValue() == NS_STYLE_IMAGELAYER_POSITION_LEFT &&
+ yEdge.GetIntValue() == NS_STYLE_IMAGELAYER_POSITION_TOP) {
+ // We can omit these defaults
+ xOffset.AppendToString(eCSSProperty_UNKNOWN, aResult, aSerialization);
+ aResult.Append(' ');
+ yOffset.AppendToString(eCSSProperty_UNKNOWN, aResult, aSerialization);
+ } else {
+ // We only serialize to the two or four valued form
+ xEdge.AppendToString(eCSSProperty_object_position, aResult, aSerialization);
+ aResult.Append(' ');
+ xOffset.AppendToString(eCSSProperty_UNKNOWN, aResult, aSerialization);
+ aResult.Append(' ');
+ yEdge.AppendToString(eCSSProperty_object_position, aResult, aSerialization);
+ aResult.Append(' ');
+ yOffset.AppendToString(eCSSProperty_UNKNOWN, aResult, aSerialization);
+ }
+}
+
+// Helper to append |aString| with the shorthand sides notation used in e.g.
+// 'padding'. |aProperties| and |aValues| are expected to have 4 elements.
+/*static*/ void
+nsCSSValue::AppendSidesShorthandToString(const nsCSSPropertyID aProperties[],
+ const nsCSSValue* aValues[],
+ nsAString& aString,
+ nsCSSValue::Serialization
+ aSerialization)
+{
+ const nsCSSValue& value1 = *aValues[0];
+ const nsCSSValue& value2 = *aValues[1];
+ const nsCSSValue& value3 = *aValues[2];
+ const nsCSSValue& value4 = *aValues[3];
+
+ MOZ_ASSERT(value1.GetUnit() != eCSSUnit_Null, "null value 1");
+ value1.AppendToString(aProperties[0], aString, aSerialization);
+ if (value1 != value2 || value1 != value3 || value1 != value4) {
+ aString.Append(char16_t(' '));
+ MOZ_ASSERT(value2.GetUnit() != eCSSUnit_Null, "null value 2");
+ value2.AppendToString(aProperties[1], aString, aSerialization);
+ if (value1 != value3 || value2 != value4) {
+ aString.Append(char16_t(' '));
+ MOZ_ASSERT(value3.GetUnit() != eCSSUnit_Null, "null value 3");
+ value3.AppendToString(aProperties[2], aString, aSerialization);
+ if (value2 != value4) {
+ aString.Append(char16_t(' '));
+ MOZ_ASSERT(value4.GetUnit() != eCSSUnit_Null, "null value 4");
+ value4.AppendToString(aProperties[3], aString, aSerialization);
+ }
+ }
+ }
+}
+
+/*static*/ void
+nsCSSValue::AppendBasicShapeRadiusToString(const nsCSSPropertyID aProperties[],
+ const nsCSSValue* aValues[],
+ nsAString& aResult,
+ Serialization aSerialization)
+{
+ bool needY = false;
+ const nsCSSValue* xVals[4];
+ const nsCSSValue* yVals[4];
+ for (int i = 0; i < 4; i++) {
+ if (aValues[i]->GetUnit() == eCSSUnit_Pair) {
+ needY = true;
+ xVals[i] = &aValues[i]->GetPairValue().mXValue;
+ yVals[i] = &aValues[i]->GetPairValue().mYValue;
+ } else {
+ xVals[i] = yVals[i] = aValues[i];
+ }
+ }
+
+ AppendSidesShorthandToString(aProperties, xVals, aResult, aSerialization);
+ if (needY) {
+ aResult.AppendLiteral(" / ");
+ AppendSidesShorthandToString(aProperties, yVals, aResult, aSerialization);
+ }
+}
+
+void
+nsCSSValue::AppendInsetToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ Serialization aSerialization) const
+{
+ const nsCSSValue::Array* array = GetArrayValue();
+ MOZ_ASSERT(array->Count() == 6,
+ "inset function has wrong number of arguments");
+ if (array->Item(1).GetUnit() != eCSSUnit_Null) {
+ array->Item(1).AppendToString(aProperty, aResult, aSerialization);
+ if (array->Item(2).GetUnit() != eCSSUnit_Null) {
+ aResult.Append(' ');
+ array->Item(2).AppendToString(aProperty, aResult, aSerialization);
+ if (array->Item(3).GetUnit() != eCSSUnit_Null) {
+ aResult.Append(' ');
+ array->Item(3).AppendToString(aProperty, aResult, aSerialization);
+ if (array->Item(4).GetUnit() != eCSSUnit_Null) {
+ aResult.Append(' ');
+ array->Item(4).AppendToString(aProperty, aResult, aSerialization);
+ }
+ }
+ }
+ }
+
+ if (array->Item(5).GetUnit() == eCSSUnit_Array) {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_radius);
+ const nsCSSValue::Array* radius = array->Item(5).GetArrayValue();
+ MOZ_ASSERT(radius->Count() == 4, "expected 4 radii values");
+ const nsCSSValue* vals[4] = {
+ &(radius->Item(0)),
+ &(radius->Item(1)),
+ &(radius->Item(2)),
+ &(radius->Item(3))
+ };
+ aResult.AppendLiteral(" round ");
+ AppendBasicShapeRadiusToString(subprops, vals, aResult,
+ aSerialization);
+ } else {
+ MOZ_ASSERT(array->Item(5).GetUnit() == eCSSUnit_Null,
+ "unexpected value");
+ }
+}
+
+/* static */ void
+nsCSSValue::AppendAlignJustifyValueToString(int32_t aValue, nsAString& aResult)
+{
+ auto legacy = aValue & NS_STYLE_ALIGN_LEGACY;
+ if (legacy) {
+ aValue &= ~legacy;
+ aResult.AppendLiteral("legacy ");
+ }
+ auto overflowPos = aValue & (NS_STYLE_ALIGN_SAFE | NS_STYLE_ALIGN_UNSAFE);
+ aValue &= ~overflowPos;
+ MOZ_ASSERT(!(aValue & NS_STYLE_ALIGN_FLAG_BITS),
+ "unknown bits in align/justify value");
+ MOZ_ASSERT((aValue != NS_STYLE_ALIGN_AUTO &&
+ aValue != NS_STYLE_ALIGN_NORMAL &&
+ aValue != NS_STYLE_ALIGN_BASELINE &&
+ aValue != NS_STYLE_ALIGN_LAST_BASELINE) ||
+ (!legacy && !overflowPos),
+ "auto/normal/baseline/'last baseline' never have any flags");
+ MOZ_ASSERT(legacy == 0 || overflowPos == 0,
+ "'legacy' together with <overflow-position>");
+ if (aValue == NS_STYLE_ALIGN_LAST_BASELINE) {
+ aResult.AppendLiteral("last ");
+ aValue = NS_STYLE_ALIGN_BASELINE;
+ }
+ const auto& kwtable(nsCSSProps::kAlignAllKeywords);
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(aValue, kwtable), aResult);
+ // Don't serialize the 'unsafe' keyword; it's the default.
+ if (MOZ_UNLIKELY(overflowPos == NS_STYLE_ALIGN_SAFE)) {
+ aResult.Append(' ');
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(overflowPos, kwtable),
+ aResult);
+ }
+}
+
+void
+nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ Serialization aSerialization) const
+{
+ // eCSSProperty_UNKNOWN gets used for some recursive calls below.
+ MOZ_ASSERT((0 <= aProperty &&
+ aProperty <= eCSSProperty_COUNT_no_shorthands) ||
+ aProperty == eCSSProperty_UNKNOWN ||
+ aProperty == eCSSProperty_DOM,
+ "property ID out of range");
+
+ nsCSSUnit unit = GetUnit();
+ if (unit == eCSSUnit_Null) {
+ return;
+ }
+
+ if (eCSSUnit_String <= unit && unit <= eCSSUnit_Attr) {
+ if (unit == eCSSUnit_Attr) {
+ aResult.AppendLiteral("attr(");
+ }
+ nsAutoString buffer;
+ GetStringValue(buffer);
+ if (unit == eCSSUnit_String) {
+ nsStyleUtil::AppendEscapedCSSString(buffer, aResult);
+ } else {
+ nsStyleUtil::AppendEscapedCSSIdent(buffer, aResult);
+ }
+ }
+ else if (eCSSUnit_Array <= unit && unit <= eCSSUnit_Symbols) {
+ switch (unit) {
+ case eCSSUnit_Counter: aResult.AppendLiteral("counter("); break;
+ case eCSSUnit_Counters: aResult.AppendLiteral("counters("); break;
+ case eCSSUnit_Cubic_Bezier: aResult.AppendLiteral("cubic-bezier("); break;
+ case eCSSUnit_Steps: aResult.AppendLiteral("steps("); break;
+ case eCSSUnit_Symbols: aResult.AppendLiteral("symbols("); break;
+ default: break;
+ }
+
+ nsCSSValue::Array *array = GetArrayValue();
+ bool mark = false;
+ for (size_t i = 0, i_end = array->Count(); i < i_end; ++i) {
+ if (mark && array->Item(i).GetUnit() != eCSSUnit_Null) {
+ if ((unit == eCSSUnit_Array &&
+ eCSSProperty_transition_timing_function != aProperty) ||
+ unit == eCSSUnit_Symbols)
+ aResult.Append(' ');
+ else if (unit != eCSSUnit_Steps)
+ aResult.AppendLiteral(", ");
+ }
+ if (unit == eCSSUnit_Steps && i == 1) {
+ MOZ_ASSERT(array->Item(i).GetUnit() == eCSSUnit_Enumerated,
+ "unexpected value");
+ int32_t side = array->Item(i).GetIntValue();
+ MOZ_ASSERT(side == NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START ||
+ side == NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END ||
+ side == -1,
+ "unexpected value");
+ if (side == NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START) {
+ aResult.AppendLiteral(", start");
+ } else if (side == NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END) {
+ aResult.AppendLiteral(", end");
+ }
+ continue;
+ }
+ if (unit == eCSSUnit_Symbols && i == 0) {
+ MOZ_ASSERT(array->Item(i).GetUnit() == eCSSUnit_Enumerated,
+ "unexpected value");
+ int32_t system = array->Item(i).GetIntValue();
+ if (system != NS_STYLE_COUNTER_SYSTEM_SYMBOLIC) {
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(
+ system, nsCSSProps::kCounterSystemKTable), aResult);
+ mark = true;
+ }
+ continue;
+ }
+ nsCSSPropertyID prop =
+ ((eCSSUnit_Counter <= unit && unit <= eCSSUnit_Counters) &&
+ i == array->Count() - 1)
+ ? eCSSProperty_list_style_type : aProperty;
+ if (array->Item(i).GetUnit() != eCSSUnit_Null) {
+ array->Item(i).AppendToString(prop, aResult, aSerialization);
+ mark = true;
+ }
+ }
+ if (eCSSUnit_Array == unit &&
+ aProperty == eCSSProperty_transition_timing_function) {
+ aResult.Append(')');
+ }
+ }
+ /* Although Function is backed by an Array, we'll handle it separately
+ * because it's a bit quirky.
+ */
+ else if (eCSSUnit_Function == unit) {
+ const nsCSSValue::Array* array = GetArrayValue();
+ MOZ_ASSERT(array->Count() >= 1,
+ "Functions must have at least one element for the name.");
+
+ const nsCSSValue& functionName = array->Item(0);
+ MOZ_ASSERT(functionName.GetUnit() == eCSSUnit_Enumerated,
+ "Functions must have an enumerated name.");
+ // The first argument is always of nsCSSKeyword type.
+ const nsCSSKeyword functionId = functionName.GetKeywordValue();
+
+ // minmax(auto, <flex>) is equivalent to (and is our internal representation
+ // of) <flex>, and both are serialized as <flex>
+ if (functionId == eCSSKeyword_minmax &&
+ array->Count() == 3 &&
+ array->Item(1).GetUnit() == eCSSUnit_Auto &&
+ array->Item(2).GetUnit() == eCSSUnit_FlexFraction) {
+ array->Item(2).AppendToString(aProperty, aResult, aSerialization);
+ MOZ_ASSERT(aProperty == eCSSProperty_grid_template_columns ||
+ aProperty == eCSSProperty_grid_template_rows ||
+ aProperty == eCSSProperty_grid_auto_columns ||
+ aProperty == eCSSProperty_grid_auto_rows);
+ return;
+ }
+
+ /* Append the function name. */
+ NS_ConvertASCIItoUTF16 ident(nsCSSKeywords::GetStringValue(functionId));
+ // Bug 721136: Normalize the identifier to lowercase, except that things
+ // like scaleX should have the last character capitalized. This matches
+ // what other browsers do.
+ switch (functionId) {
+ case eCSSKeyword_rotatex:
+ case eCSSKeyword_scalex:
+ case eCSSKeyword_skewx:
+ case eCSSKeyword_translatex:
+ ident.Replace(ident.Length() - 1, 1, char16_t('X'));
+ break;
+
+ case eCSSKeyword_rotatey:
+ case eCSSKeyword_scaley:
+ case eCSSKeyword_skewy:
+ case eCSSKeyword_translatey:
+ ident.Replace(ident.Length() - 1, 1, char16_t('Y'));
+ break;
+
+ case eCSSKeyword_rotatez:
+ case eCSSKeyword_scalez:
+ case eCSSKeyword_translatez:
+ ident.Replace(ident.Length() - 1, 1, char16_t('Z'));
+ break;
+
+ default:
+ break;
+ }
+ nsStyleUtil::AppendEscapedCSSIdent(ident, aResult);
+ aResult.Append('(');
+
+ switch (functionId) {
+ case eCSSKeyword_polygon:
+ AppendPolygonToString(aProperty, aResult, aSerialization);
+ break;
+
+ case eCSSKeyword_circle:
+ case eCSSKeyword_ellipse:
+ AppendCircleOrEllipseToString(functionId, aProperty, aResult,
+ aSerialization);
+ break;
+
+ case eCSSKeyword_inset:
+ AppendInsetToString(aProperty, aResult, aSerialization);
+ break;
+
+ default: {
+ // Now, step through the function contents, writing each of
+ // them as we go.
+ for (size_t index = 1; index < array->Count(); ++index) {
+ array->Item(index).AppendToString(aProperty, aResult,
+ aSerialization);
+
+ /* If we're not at the final element, append a comma. */
+ if (index + 1 != array->Count())
+ aResult.AppendLiteral(", ");
+ }
+ }
+ }
+
+ /* Finally, append the closing parenthesis. */
+ aResult.Append(')');
+ }
+ else if (IsCalcUnit()) {
+ MOZ_ASSERT(GetUnit() == eCSSUnit_Calc, "unexpected unit");
+ CSSValueSerializeCalcOps ops(aProperty, aResult, aSerialization);
+ css::SerializeCalc(*this, ops);
+ }
+ else if (eCSSUnit_Integer == unit) {
+ aResult.AppendInt(GetIntValue(), 10);
+ }
+ else if (eCSSUnit_Enumerated == unit) {
+ int32_t intValue = GetIntValue();
+ switch(aProperty) {
+
+
+ case eCSSProperty_text_combine_upright:
+ if (intValue <= NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL) {
+ AppendASCIItoUTF16(nsCSSProps::LookupPropertyValue(aProperty, intValue),
+ aResult);
+ } else if (intValue == NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2) {
+ aResult.AppendLiteral("digits 2");
+ } else if (intValue == NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3) {
+ aResult.AppendLiteral("digits 3");
+ } else {
+ aResult.AppendLiteral("digits 4");
+ }
+ break;
+
+ case eCSSProperty_text_decoration_line:
+ if (NS_STYLE_TEXT_DECORATION_LINE_NONE == intValue) {
+ AppendASCIItoUTF16(nsCSSProps::LookupPropertyValue(aProperty, intValue),
+ aResult);
+ } else {
+ // Ignore the "override all" internal value.
+ // (It doesn't have a string representation.)
+ intValue &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL;
+ nsStyleUtil::AppendBitmaskCSSValue(
+ aProperty, intValue,
+ NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
+ NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS,
+ aResult);
+ }
+ break;
+
+ case eCSSProperty_paint_order:
+ static_assert
+ (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
+ "SVGStyleStruct::mPaintOrder and the following cast not big enough");
+ nsStyleUtil::AppendPaintOrderValue(static_cast<uint8_t>(GetIntValue()),
+ aResult);
+ break;
+
+ case eCSSProperty_font_synthesis:
+ nsStyleUtil::AppendBitmaskCSSValue(aProperty, intValue,
+ NS_FONT_SYNTHESIS_WEIGHT,
+ NS_FONT_SYNTHESIS_STYLE,
+ aResult);
+ break;
+
+ case eCSSProperty_font_variant_east_asian:
+ nsStyleUtil::AppendBitmaskCSSValue(aProperty, intValue,
+ NS_FONT_VARIANT_EAST_ASIAN_JIS78,
+ NS_FONT_VARIANT_EAST_ASIAN_RUBY,
+ aResult);
+ break;
+
+ case eCSSProperty_font_variant_ligatures:
+ nsStyleUtil::AppendBitmaskCSSValue(aProperty, intValue,
+ NS_FONT_VARIANT_LIGATURES_NONE,
+ NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL,
+ aResult);
+ break;
+
+ case eCSSProperty_font_variant_numeric:
+ nsStyleUtil::AppendBitmaskCSSValue(aProperty, intValue,
+ NS_FONT_VARIANT_NUMERIC_LINING,
+ NS_FONT_VARIANT_NUMERIC_ORDINAL,
+ aResult);
+ break;
+
+ case eCSSProperty_grid_auto_flow:
+ nsStyleUtil::AppendBitmaskCSSValue(aProperty, intValue,
+ NS_STYLE_GRID_AUTO_FLOW_ROW,
+ NS_STYLE_GRID_AUTO_FLOW_DENSE,
+ aResult);
+ break;
+
+ case eCSSProperty_grid_column_start:
+ case eCSSProperty_grid_column_end:
+ case eCSSProperty_grid_row_start:
+ case eCSSProperty_grid_row_end:
+ // "span" is the only enumerated-unit value for these properties
+ aResult.AppendLiteral("span");
+ break;
+
+ case eCSSProperty_touch_action:
+ nsStyleUtil::AppendBitmaskCSSValue(aProperty, intValue,
+ NS_STYLE_TOUCH_ACTION_NONE,
+ NS_STYLE_TOUCH_ACTION_MANIPULATION,
+ aResult);
+ break;
+
+ case eCSSProperty_clip_path:
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
+ nsCSSProps::kClipPathGeometryBoxKTable),
+ aResult);
+ break;
+
+ case eCSSProperty_shape_outside:
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
+ nsCSSProps::kShapeOutsideShapeBoxKTable),
+ aResult);
+ break;
+
+ case eCSSProperty_contain:
+ if (intValue & NS_STYLE_CONTAIN_STRICT) {
+ NS_ASSERTION(intValue == (NS_STYLE_CONTAIN_STRICT | NS_STYLE_CONTAIN_ALL_BITS),
+ "contain: strict should imply contain: layout style paint");
+ // Only output strict.
+ intValue = NS_STYLE_CONTAIN_STRICT;
+ }
+ nsStyleUtil::AppendBitmaskCSSValue(aProperty,
+ intValue,
+ NS_STYLE_CONTAIN_STRICT,
+ NS_STYLE_CONTAIN_PAINT,
+ aResult);
+ break;
+
+ case eCSSProperty_align_content:
+ case eCSSProperty_justify_content: {
+ AppendAlignJustifyValueToString(intValue & NS_STYLE_ALIGN_ALL_BITS, aResult);
+ auto fallback = intValue >> NS_STYLE_ALIGN_ALL_SHIFT;
+ if (fallback) {
+ MOZ_ASSERT(nsCSSProps::ValueToKeywordEnum(fallback & ~NS_STYLE_ALIGN_FLAG_BITS,
+ nsCSSProps::kAlignSelfPosition)
+ != eCSSKeyword_UNKNOWN, "unknown fallback value");
+ aResult.Append(' ');
+ AppendAlignJustifyValueToString(fallback, aResult);
+ }
+ break;
+ }
+
+ case eCSSProperty_align_items:
+ case eCSSProperty_align_self:
+ case eCSSProperty_justify_items:
+ case eCSSProperty_justify_self:
+ AppendAlignJustifyValueToString(intValue, aResult);
+ break;
+
+ case eCSSProperty_text_emphasis_position: {
+ nsStyleUtil::AppendBitmaskCSSValue(aProperty, intValue,
+ NS_STYLE_TEXT_EMPHASIS_POSITION_OVER,
+ NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT,
+ aResult);
+ break;
+ }
+
+ case eCSSProperty_text_emphasis_style: {
+ auto fill = intValue & NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK;
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(
+ fill, nsCSSProps::kTextEmphasisStyleFillKTable), aResult);
+ aResult.Append(' ');
+ auto shape = intValue & NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK;
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(
+ shape, nsCSSProps::kTextEmphasisStyleShapeKTable), aResult);
+ break;
+ }
+
+ default:
+ const nsAFlatCString& name = nsCSSProps::LookupPropertyValue(aProperty, intValue);
+ AppendASCIItoUTF16(name, aResult);
+ break;
+ }
+ }
+ else if (eCSSUnit_EnumColor == unit) {
+ // we can lookup the property in the ColorTable and then
+ // get a string mapping the name
+ nsAutoCString str;
+ if (nsCSSProps::GetColorName(GetIntValue(), str)){
+ AppendASCIItoUTF16(str, aResult);
+ } else {
+ MOZ_ASSERT(false, "bad color value");
+ }
+ }
+ else if (IsNumericColorUnit(unit)) {
+ if (aSerialization == eNormalized ||
+ unit == eCSSUnit_RGBColor ||
+ unit == eCSSUnit_RGBAColor) {
+ nscolor color = GetColorValue();
+ if (aSerialization == eNormalized &&
+ color == NS_RGBA(0, 0, 0, 0)) {
+ // Use the strictest match for 'transparent' so we do correct
+ // round-tripping of all other rgba() values.
+ aResult.AppendLiteral("transparent");
+ } else {
+ // For brevity, we omit the alpha component if it's equal to 255 (full
+ // opaque). Also, we use "rgba" rather than "rgb" when the color includes
+ // the non-opaque alpha value, for backwards-compat (even though they're
+ // aliases as of css-color-4).
+ // e.g.:
+ // rgba(1, 2, 3, 1.0) => rgb(1, 2, 3)
+ // rgba(1, 2, 3, 0.5) => rgba(1, 2, 3, 0.5)
+ uint8_t a = NS_GET_A(color);
+ bool showAlpha = (a != 255);
+
+ if (showAlpha) {
+ aResult.AppendLiteral("rgba(");
+ } else {
+ aResult.AppendLiteral("rgb(");
+ }
+
+ NS_NAMED_LITERAL_STRING(comma, ", ");
+
+ aResult.AppendInt(NS_GET_R(color), 10);
+ aResult.Append(comma);
+ aResult.AppendInt(NS_GET_G(color), 10);
+ aResult.Append(comma);
+ aResult.AppendInt(NS_GET_B(color), 10);
+ if (showAlpha) {
+ aResult.Append(comma);
+ aResult.AppendFloat(nsStyleUtil::ColorComponentToFloat(a));
+ }
+ aResult.Append(char16_t(')'));
+ }
+ } else if (eCSSUnit_HexColor == unit ||
+ eCSSUnit_HexColorAlpha == unit) {
+ nscolor color = GetColorValue();
+ aResult.Append('#');
+ aResult.AppendPrintf("%02x", NS_GET_R(color));
+ aResult.AppendPrintf("%02x", NS_GET_G(color));
+ aResult.AppendPrintf("%02x", NS_GET_B(color));
+ if (eCSSUnit_HexColorAlpha == unit) {
+ aResult.AppendPrintf("%02x", NS_GET_A(color));
+ }
+ } else if (eCSSUnit_ShortHexColor == unit ||
+ eCSSUnit_ShortHexColorAlpha == unit) {
+ nscolor color = GetColorValue();
+ aResult.Append('#');
+ aResult.AppendInt(NS_GET_R(color) / 0x11, 16);
+ aResult.AppendInt(NS_GET_G(color) / 0x11, 16);
+ aResult.AppendInt(NS_GET_B(color) / 0x11, 16);
+ if (eCSSUnit_ShortHexColorAlpha == unit) {
+ aResult.AppendInt(NS_GET_A(color) / 0x11, 16);
+ }
+ } else {
+ MOZ_ASSERT(IsFloatColorUnit());
+ mValue.mFloatColor->AppendToString(unit, aResult);
+ }
+ }
+ else if (eCSSUnit_ComplexColor == unit) {
+ StyleComplexColor color = GetStyleComplexColorValue();
+ nsCSSValue serializable;
+ if (color.IsCurrentColor()) {
+ serializable.SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ } else if (color.IsNumericColor()) {
+ serializable.SetColorValue(color.mColor);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Cannot serialize a complex color");
+ }
+ serializable.AppendToString(aProperty, aResult, aSerialization);
+ }
+ else if (eCSSUnit_URL == unit || eCSSUnit_Image == unit) {
+ aResult.AppendLiteral("url(");
+ nsStyleUtil::AppendEscapedCSSString(
+ nsDependentString(GetOriginalURLValue()), aResult);
+ aResult.Append(')');
+ }
+ else if (eCSSUnit_Element == unit) {
+ aResult.AppendLiteral("-moz-element(#");
+ nsAutoString tmpStr;
+ GetStringValue(tmpStr);
+ nsStyleUtil::AppendEscapedCSSIdent(tmpStr, aResult);
+ aResult.Append(')');
+ }
+ else if (eCSSUnit_Percent == unit) {
+ aResult.AppendFloat(GetPercentValue() * 100.0f);
+ }
+ else if (eCSSUnit_Percent < unit) { // length unit
+ aResult.AppendFloat(GetFloatValue());
+ }
+ else if (eCSSUnit_Gradient == unit) {
+ nsCSSValueGradient* gradient = GetGradientValue();
+
+ if (gradient->mIsLegacySyntax) {
+ aResult.AppendLiteral("-moz-");
+ }
+ if (gradient->mIsRepeating) {
+ aResult.AppendLiteral("repeating-");
+ }
+ if (gradient->mIsRadial) {
+ aResult.AppendLiteral("radial-gradient(");
+ } else {
+ aResult.AppendLiteral("linear-gradient(");
+ }
+
+ bool needSep = false;
+ if (gradient->mIsRadial && !gradient->mIsLegacySyntax) {
+ if (!gradient->mIsExplicitSize) {
+ if (gradient->GetRadialShape().GetUnit() != eCSSUnit_None) {
+ MOZ_ASSERT(gradient->GetRadialShape().GetUnit() ==
+ eCSSUnit_Enumerated,
+ "bad unit for radial gradient shape");
+ int32_t intValue = gradient->GetRadialShape().GetIntValue();
+ MOZ_ASSERT(intValue != NS_STYLE_GRADIENT_SHAPE_LINEAR,
+ "radial gradient with linear shape?!");
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
+ nsCSSProps::kRadialGradientShapeKTable),
+ aResult);
+ needSep = true;
+ }
+
+ if (gradient->GetRadialSize().GetUnit() != eCSSUnit_None) {
+ if (needSep) {
+ aResult.Append(' ');
+ }
+ MOZ_ASSERT(gradient->GetRadialSize().GetUnit() == eCSSUnit_Enumerated,
+ "bad unit for radial gradient size");
+ int32_t intValue = gradient->GetRadialSize().GetIntValue();
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
+ nsCSSProps::kRadialGradientSizeKTable),
+ aResult);
+ needSep = true;
+ }
+ } else {
+ MOZ_ASSERT(gradient->GetRadiusX().GetUnit() != eCSSUnit_None,
+ "bad unit for radial gradient explicit size");
+ gradient->GetRadiusX().AppendToString(aProperty, aResult,
+ aSerialization);
+ if (gradient->GetRadiusY().GetUnit() != eCSSUnit_None) {
+ aResult.Append(' ');
+ gradient->GetRadiusY().AppendToString(aProperty, aResult,
+ aSerialization);
+ }
+ needSep = true;
+ }
+ }
+ if (!gradient->mIsRadial && !gradient->mIsLegacySyntax) {
+ if (gradient->mBgPos.mXValue.GetUnit() != eCSSUnit_None ||
+ gradient->mBgPos.mYValue.GetUnit() != eCSSUnit_None) {
+ MOZ_ASSERT(gradient->mAngle.GetUnit() == eCSSUnit_None);
+ MOZ_ASSERT(gradient->mBgPos.mXValue.GetUnit() == eCSSUnit_Enumerated &&
+ gradient->mBgPos.mYValue.GetUnit() == eCSSUnit_Enumerated,
+ "unexpected unit");
+ aResult.AppendLiteral("to");
+ if (!(gradient->mBgPos.mXValue.GetIntValue() & NS_STYLE_IMAGELAYER_POSITION_CENTER)) {
+ aResult.Append(' ');
+ gradient->mBgPos.mXValue.AppendToString(eCSSProperty_background_position_x,
+ aResult, aSerialization);
+ }
+ if (!(gradient->mBgPos.mYValue.GetIntValue() & NS_STYLE_IMAGELAYER_POSITION_CENTER)) {
+ aResult.Append(' ');
+ gradient->mBgPos.mYValue.AppendToString(eCSSProperty_background_position_y,
+ aResult, aSerialization);
+ }
+ needSep = true;
+ } else if (gradient->mAngle.GetUnit() != eCSSUnit_None) {
+ gradient->mAngle.AppendToString(aProperty, aResult, aSerialization);
+ needSep = true;
+ }
+ } else if (gradient->mBgPos.mXValue.GetUnit() != eCSSUnit_None ||
+ gradient->mBgPos.mYValue.GetUnit() != eCSSUnit_None ||
+ gradient->mAngle.GetUnit() != eCSSUnit_None) {
+ if (needSep) {
+ aResult.Append(' ');
+ }
+ if (gradient->mIsRadial && !gradient->mIsLegacySyntax) {
+ aResult.AppendLiteral("at ");
+ }
+ if (gradient->mBgPos.mXValue.GetUnit() != eCSSUnit_None) {
+ gradient->mBgPos.mXValue.AppendToString(eCSSProperty_background_position_x,
+ aResult, aSerialization);
+ aResult.Append(' ');
+ }
+ if (gradient->mBgPos.mYValue.GetUnit() != eCSSUnit_None) {
+ gradient->mBgPos.mYValue.AppendToString(eCSSProperty_background_position_y,
+ aResult, aSerialization);
+ aResult.Append(' ');
+ }
+ if (gradient->mAngle.GetUnit() != eCSSUnit_None) {
+ MOZ_ASSERT(gradient->mIsLegacySyntax,
+ "angle is allowed only for legacy syntax");
+ gradient->mAngle.AppendToString(aProperty, aResult, aSerialization);
+ }
+ needSep = true;
+ }
+
+ if (gradient->mIsRadial && gradient->mIsLegacySyntax &&
+ (gradient->GetRadialShape().GetUnit() != eCSSUnit_None ||
+ gradient->GetRadialSize().GetUnit() != eCSSUnit_None)) {
+ MOZ_ASSERT(!gradient->mIsExplicitSize);
+ if (needSep) {
+ aResult.AppendLiteral(", ");
+ }
+ if (gradient->GetRadialShape().GetUnit() != eCSSUnit_None) {
+ MOZ_ASSERT(gradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated,
+ "bad unit for radial gradient shape");
+ int32_t intValue = gradient->GetRadialShape().GetIntValue();
+ MOZ_ASSERT(intValue != NS_STYLE_GRADIENT_SHAPE_LINEAR,
+ "radial gradient with linear shape?!");
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
+ nsCSSProps::kRadialGradientShapeKTable),
+ aResult);
+ aResult.Append(' ');
+ }
+
+ if (gradient->GetRadialSize().GetUnit() != eCSSUnit_None) {
+ MOZ_ASSERT(gradient->GetRadialSize().GetUnit() == eCSSUnit_Enumerated,
+ "bad unit for radial gradient size");
+ int32_t intValue = gradient->GetRadialSize().GetIntValue();
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
+ nsCSSProps::kRadialGradientSizeKTable),
+ aResult);
+ }
+ needSep = true;
+ }
+ if (needSep) {
+ aResult.AppendLiteral(", ");
+ }
+
+ for (uint32_t i = 0 ;;) {
+ bool isInterpolationHint = gradient->mStops[i].mIsInterpolationHint;
+ if (!isInterpolationHint) {
+ gradient->mStops[i].mColor.AppendToString(aProperty, aResult,
+ aSerialization);
+ }
+ if (gradient->mStops[i].mLocation.GetUnit() != eCSSUnit_None) {
+ if (!isInterpolationHint) {
+ aResult.Append(' ');
+ }
+ gradient->mStops[i].mLocation.AppendToString(aProperty, aResult,
+ aSerialization);
+ }
+ if (++i == gradient->mStops.Length()) {
+ break;
+ }
+ aResult.AppendLiteral(", ");
+ }
+
+ aResult.Append(')');
+ } else if (eCSSUnit_TokenStream == unit) {
+ nsCSSPropertyID shorthand = mValue.mTokenStream->mShorthandPropertyID;
+ if (shorthand == eCSSProperty_UNKNOWN ||
+ nsCSSProps::PropHasFlags(shorthand, CSS_PROPERTY_IS_ALIAS) ||
+ aProperty == eCSSProperty__x_system_font) {
+ // We treat serialization of aliases like '-moz-transform' as a special
+ // case, since it really wants to be serialized as if it were a longhand
+ // even though it is implemented as a shorthand. We also need to
+ // serialize -x-system-font's token stream value, even though the
+ // value is set through the font shorthand. This serialization
+ // of -x-system-font is needed when we need to output the
+ // 'font' shorthand followed by a number of overriding font
+ // longhand components.
+ aResult.Append(mValue.mTokenStream->mTokenStream);
+ }
+ } else if (eCSSUnit_Pair == unit) {
+ if (eCSSProperty_font_variant_alternates == aProperty) {
+ int32_t intValue = GetPairValue().mXValue.GetIntValue();
+ nsAutoString out;
+
+ // simple, enumerated values
+ nsStyleUtil::AppendBitmaskCSSValue(aProperty,
+ intValue & NS_FONT_VARIANT_ALTERNATES_ENUMERATED_MASK,
+ NS_FONT_VARIANT_ALTERNATES_HISTORICAL,
+ NS_FONT_VARIANT_ALTERNATES_HISTORICAL,
+ out);
+
+ // functional values
+ const nsCSSValueList *list = GetPairValue().mYValue.GetListValue();
+ AutoTArray<gfxAlternateValue,8> altValues;
+
+ nsStyleUtil::ComputeFunctionalAlternates(list, altValues);
+ nsStyleUtil::SerializeFunctionalAlternates(altValues, out);
+ aResult.Append(out);
+ } else {
+ GetPairValue().AppendToString(aProperty, aResult, aSerialization);
+ }
+ } else if (eCSSUnit_Triplet == unit) {
+ GetTripletValue().AppendToString(aProperty, aResult, aSerialization);
+ } else if (eCSSUnit_Rect == unit) {
+ GetRectValue().AppendToString(aProperty, aResult, aSerialization);
+ } else if (eCSSUnit_List == unit || eCSSUnit_ListDep == unit) {
+ GetListValue()->AppendToString(aProperty, aResult, aSerialization);
+ } else if (eCSSUnit_SharedList == unit) {
+ GetSharedListValue()->AppendToString(aProperty, aResult, aSerialization);
+ } else if (eCSSUnit_PairList == unit || eCSSUnit_PairListDep == unit) {
+ switch (aProperty) {
+ case eCSSProperty_font_feature_settings:
+ nsStyleUtil::AppendFontFeatureSettings(*this, aResult);
+ break;
+ default:
+ GetPairListValue()->AppendToString(aProperty, aResult, aSerialization);
+ break;
+ }
+ } else if (eCSSUnit_GridTemplateAreas == unit) {
+ const mozilla::css::GridTemplateAreasValue* areas = GetGridTemplateAreas();
+ MOZ_ASSERT(!areas->mTemplates.IsEmpty(),
+ "Unexpected empty array in GridTemplateAreasValue");
+ nsStyleUtil::AppendEscapedCSSString(areas->mTemplates[0], aResult);
+ for (uint32_t i = 1; i < areas->mTemplates.Length(); i++) {
+ aResult.Append(char16_t(' '));
+ nsStyleUtil::AppendEscapedCSSString(areas->mTemplates[i], aResult);
+ }
+ } else if (eCSSUnit_FontFamilyList == unit) {
+ nsStyleUtil::AppendEscapedCSSFontFamilyList(*mValue.mFontFamilyList,
+ aResult);
+ }
+
+ switch (unit) {
+ case eCSSUnit_Null: break;
+ case eCSSUnit_Auto: aResult.AppendLiteral("auto"); break;
+ case eCSSUnit_Inherit: aResult.AppendLiteral("inherit"); break;
+ case eCSSUnit_Initial: aResult.AppendLiteral("initial"); break;
+ case eCSSUnit_Unset: aResult.AppendLiteral("unset"); break;
+ case eCSSUnit_None: aResult.AppendLiteral("none"); break;
+ case eCSSUnit_Normal: aResult.AppendLiteral("normal"); break;
+ case eCSSUnit_System_Font: aResult.AppendLiteral("-moz-use-system-font"); break;
+ case eCSSUnit_All: aResult.AppendLiteral("all"); break;
+ case eCSSUnit_Dummy:
+ case eCSSUnit_DummyInherit:
+ MOZ_ASSERT(false, "should never serialize");
+ break;
+
+ case eCSSUnit_FontFamilyList: break;
+ case eCSSUnit_String: break;
+ case eCSSUnit_Ident: break;
+ case eCSSUnit_URL: break;
+ case eCSSUnit_Image: break;
+ case eCSSUnit_Element: break;
+ case eCSSUnit_Array: break;
+ case eCSSUnit_Attr:
+ case eCSSUnit_Cubic_Bezier:
+ case eCSSUnit_Steps:
+ case eCSSUnit_Symbols:
+ case eCSSUnit_Counter:
+ case eCSSUnit_Counters: aResult.Append(char16_t(')')); break;
+ case eCSSUnit_Local_Font: break;
+ case eCSSUnit_Font_Format: break;
+ case eCSSUnit_Function: break;
+ case eCSSUnit_Calc: break;
+ case eCSSUnit_Calc_Plus: break;
+ case eCSSUnit_Calc_Minus: break;
+ case eCSSUnit_Calc_Times_L: break;
+ case eCSSUnit_Calc_Times_R: break;
+ case eCSSUnit_Calc_Divided: break;
+ case eCSSUnit_Integer: break;
+ case eCSSUnit_Enumerated: break;
+ case eCSSUnit_EnumColor: break;
+ case eCSSUnit_RGBColor: break;
+ case eCSSUnit_RGBAColor: break;
+ case eCSSUnit_HexColor: break;
+ case eCSSUnit_ShortHexColor: break;
+ case eCSSUnit_HexColorAlpha: break;
+ case eCSSUnit_ShortHexColorAlpha: break;
+ case eCSSUnit_PercentageRGBColor: break;
+ case eCSSUnit_PercentageRGBAColor: break;
+ case eCSSUnit_HSLColor: break;
+ case eCSSUnit_HSLAColor: break;
+ case eCSSUnit_ComplexColor: break;
+ case eCSSUnit_Percent: aResult.Append(char16_t('%')); break;
+ case eCSSUnit_Number: break;
+ case eCSSUnit_Gradient: break;
+ case eCSSUnit_TokenStream: break;
+ case eCSSUnit_Pair: break;
+ case eCSSUnit_Triplet: break;
+ case eCSSUnit_Rect: break;
+ case eCSSUnit_List: break;
+ case eCSSUnit_ListDep: break;
+ case eCSSUnit_SharedList: break;
+ case eCSSUnit_PairList: break;
+ case eCSSUnit_PairListDep: break;
+ case eCSSUnit_GridTemplateAreas: break;
+
+ case eCSSUnit_Inch: aResult.AppendLiteral("in"); break;
+ case eCSSUnit_Millimeter: aResult.AppendLiteral("mm"); break;
+ case eCSSUnit_PhysicalMillimeter: aResult.AppendLiteral("mozmm"); break;
+ case eCSSUnit_Centimeter: aResult.AppendLiteral("cm"); break;
+ case eCSSUnit_Point: aResult.AppendLiteral("pt"); break;
+ case eCSSUnit_Pica: aResult.AppendLiteral("pc"); break;
+ case eCSSUnit_Quarter: aResult.AppendLiteral("q"); break;
+
+ case eCSSUnit_ViewportWidth: aResult.AppendLiteral("vw"); break;
+ case eCSSUnit_ViewportHeight: aResult.AppendLiteral("vh"); break;
+ case eCSSUnit_ViewportMin: aResult.AppendLiteral("vmin"); break;
+ case eCSSUnit_ViewportMax: aResult.AppendLiteral("vmax"); break;
+
+ case eCSSUnit_EM: aResult.AppendLiteral("em"); break;
+ case eCSSUnit_XHeight: aResult.AppendLiteral("ex"); break;
+ case eCSSUnit_Char: aResult.AppendLiteral("ch"); break;
+ case eCSSUnit_RootEM: aResult.AppendLiteral("rem"); break;
+
+ case eCSSUnit_Pixel: aResult.AppendLiteral("px"); break;
+
+ case eCSSUnit_Degree: aResult.AppendLiteral("deg"); break;
+ case eCSSUnit_Grad: aResult.AppendLiteral("grad"); break;
+ case eCSSUnit_Radian: aResult.AppendLiteral("rad"); break;
+ case eCSSUnit_Turn: aResult.AppendLiteral("turn"); break;
+
+ case eCSSUnit_Hertz: aResult.AppendLiteral("Hz"); break;
+ case eCSSUnit_Kilohertz: aResult.AppendLiteral("kHz"); break;
+
+ case eCSSUnit_Seconds: aResult.Append(char16_t('s')); break;
+ case eCSSUnit_Milliseconds: aResult.AppendLiteral("ms"); break;
+
+ case eCSSUnit_FlexFraction: aResult.AppendLiteral("fr"); break;
+ }
+}
+
+size_t
+nsCSSValue::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+
+ switch (GetUnit()) {
+ // No value: nothing extra to measure.
+ case eCSSUnit_Null:
+ case eCSSUnit_Auto:
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ case eCSSUnit_Normal:
+ case eCSSUnit_System_Font:
+ case eCSSUnit_All:
+ case eCSSUnit_Dummy:
+ case eCSSUnit_DummyInherit:
+ break;
+
+ // String
+ case eCSSUnit_String:
+ case eCSSUnit_Ident:
+ case eCSSUnit_Attr:
+ case eCSSUnit_Local_Font:
+ case eCSSUnit_Font_Format:
+ case eCSSUnit_Element:
+ n += mValue.mString->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
+ break;
+
+ // Array
+ case eCSSUnit_Array:
+ case eCSSUnit_Counter:
+ case eCSSUnit_Counters:
+ case eCSSUnit_Cubic_Bezier:
+ case eCSSUnit_Steps:
+ case eCSSUnit_Symbols:
+ case eCSSUnit_Function:
+ case eCSSUnit_Calc:
+ case eCSSUnit_Calc_Plus:
+ case eCSSUnit_Calc_Minus:
+ case eCSSUnit_Calc_Times_L:
+ case eCSSUnit_Calc_Times_R:
+ case eCSSUnit_Calc_Divided:
+ break;
+
+ // URL
+ case eCSSUnit_URL:
+ n += mValue.mURL->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // Image
+ case eCSSUnit_Image:
+ // Not yet measured. Measurement may be added later if DMD finds it
+ // worthwhile.
+ break;
+
+ // Gradient
+ case eCSSUnit_Gradient:
+ n += mValue.mGradient->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // TokenStream
+ case eCSSUnit_TokenStream:
+ n += mValue.mTokenStream->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // Pair
+ case eCSSUnit_Pair:
+ n += mValue.mPair->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // Triplet
+ case eCSSUnit_Triplet:
+ n += mValue.mTriplet->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // Rect
+ case eCSSUnit_Rect:
+ n += mValue.mRect->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // List
+ case eCSSUnit_List:
+ n += mValue.mList->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // ListDep: not measured because it's non-owning.
+ case eCSSUnit_ListDep:
+ break;
+
+ // SharedList
+ case eCSSUnit_SharedList:
+ // Makes more sense not to measure, since it most cases the list
+ // will be shared.
+ break;
+
+ // PairList
+ case eCSSUnit_PairList:
+ n += mValue.mPairList->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // PairListDep: not measured because it's non-owning.
+ case eCSSUnit_PairListDep:
+ break;
+
+ // GridTemplateAreas
+ case eCSSUnit_GridTemplateAreas:
+ n += mValue.mGridTemplateAreas->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ case eCSSUnit_FontFamilyList:
+ n += mValue.mFontFamilyList->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // Int: nothing extra to measure.
+ case eCSSUnit_Integer:
+ case eCSSUnit_Enumerated:
+ case eCSSUnit_EnumColor:
+ break;
+
+ // Integer Color: nothing extra to measure.
+ case eCSSUnit_RGBColor:
+ case eCSSUnit_RGBAColor:
+ case eCSSUnit_HexColor:
+ case eCSSUnit_ShortHexColor:
+ case eCSSUnit_HexColorAlpha:
+ case eCSSUnit_ShortHexColorAlpha:
+ break;
+
+ // Float Color
+ case eCSSUnit_PercentageRGBColor:
+ case eCSSUnit_PercentageRGBAColor:
+ case eCSSUnit_HSLColor:
+ case eCSSUnit_HSLAColor:
+ n += mValue.mFloatColor->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // Complex Color
+ case eCSSUnit_ComplexColor:
+ n += mValue.mComplexColor->SizeOfIncludingThis(aMallocSizeOf);
+ break;
+
+ // Float: nothing extra to measure.
+ case eCSSUnit_Percent:
+ case eCSSUnit_Number:
+ case eCSSUnit_PhysicalMillimeter:
+ case eCSSUnit_ViewportWidth:
+ case eCSSUnit_ViewportHeight:
+ case eCSSUnit_ViewportMin:
+ case eCSSUnit_ViewportMax:
+ case eCSSUnit_EM:
+ case eCSSUnit_XHeight:
+ case eCSSUnit_Char:
+ case eCSSUnit_RootEM:
+ case eCSSUnit_Point:
+ case eCSSUnit_Inch:
+ case eCSSUnit_Millimeter:
+ case eCSSUnit_Centimeter:
+ case eCSSUnit_Pica:
+ case eCSSUnit_Pixel:
+ case eCSSUnit_Quarter:
+ case eCSSUnit_Degree:
+ case eCSSUnit_Grad:
+ case eCSSUnit_Turn:
+ case eCSSUnit_Radian:
+ case eCSSUnit_Hertz:
+ case eCSSUnit_Kilohertz:
+ case eCSSUnit_Seconds:
+ case eCSSUnit_Milliseconds:
+ case eCSSUnit_FlexFraction:
+ break;
+
+ default:
+ MOZ_ASSERT(false, "bad nsCSSUnit");
+ break;
+ }
+
+ return n;
+}
+
+// --- nsCSSValueList -----------------
+
+nsCSSValueList::~nsCSSValueList()
+{
+ MOZ_COUNT_DTOR(nsCSSValueList);
+ NS_CSS_DELETE_LIST_MEMBER(nsCSSValueList, this, mNext);
+}
+
+nsCSSValueList*
+nsCSSValueList::Clone() const
+{
+ nsCSSValueList* result = new nsCSSValueList(*this);
+ nsCSSValueList* dest = result;
+ const nsCSSValueList* src = this->mNext;
+ while (src) {
+ dest->mNext = new nsCSSValueList(*src);
+ dest = dest->mNext;
+ src = src->mNext;
+ }
+
+ MOZ_ASSERT(result, "shouldn't return null; supposed to be infallible");
+ return result;
+}
+
+void
+nsCSSValueList::CloneInto(nsCSSValueList* aList) const
+{
+ NS_ASSERTION(!aList->mNext, "Must be an empty list!");
+ aList->mValue = mValue;
+ aList->mNext = mNext ? mNext->Clone() : nullptr;
+}
+
+static void
+AppendValueListToString(const nsCSSValueList* val,
+ nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aSerialization)
+{
+ for (;;) {
+ val->mValue.AppendToString(aProperty, aResult, aSerialization);
+ val = val->mNext;
+ if (!val)
+ break;
+
+ if (nsCSSProps::PropHasFlags(aProperty,
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS))
+ aResult.Append(char16_t(','));
+ aResult.Append(char16_t(' '));
+ }
+}
+
+static void
+AppendGridTemplateToString(const nsCSSValueList* val,
+ nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aSerialization)
+{
+ // This is called for the "list" that's the top-level value of the property.
+ bool isSubgrid = false;
+ for (;;) {
+ bool addSpaceSeparator = true;
+ nsCSSUnit unit = val->mValue.GetUnit();
+
+ if (unit == eCSSUnit_Enumerated &&
+ val->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) {
+ MOZ_ASSERT(!isSubgrid, "saw subgrid once already");
+ isSubgrid = true;
+ aResult.AppendLiteral("subgrid");
+
+ } else if (unit == eCSSUnit_Pair) {
+ // This is a repeat 'auto-fill' / 'auto-fit'.
+ const nsCSSValuePair& pair = val->mValue.GetPairValue();
+ switch (pair.mXValue.GetIntValue()) {
+ case NS_STYLE_GRID_REPEAT_AUTO_FILL:
+ aResult.AppendLiteral("repeat(auto-fill, ");
+ break;
+ case NS_STYLE_GRID_REPEAT_AUTO_FIT:
+ aResult.AppendLiteral("repeat(auto-fit, ");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected enum value");
+ }
+ const nsCSSValueList* repeatList = pair.mYValue.GetListValue();
+ if (repeatList->mValue.GetUnit() != eCSSUnit_Null) {
+ aResult.Append('[');
+ AppendValueListToString(repeatList->mValue.GetListValue(), aProperty,
+ aResult, aSerialization);
+ aResult.Append(']');
+ if (!isSubgrid) {
+ aResult.Append(' ');
+ }
+ } else if (isSubgrid) {
+ aResult.AppendLiteral("[]");
+ }
+ if (!isSubgrid) {
+ repeatList = repeatList->mNext;
+ repeatList->mValue.AppendToString(aProperty, aResult, aSerialization);
+ repeatList = repeatList->mNext;
+ if (repeatList->mValue.GetUnit() != eCSSUnit_Null) {
+ aResult.AppendLiteral(" [");
+ AppendValueListToString(repeatList->mValue.GetListValue(), aProperty,
+ aResult, aSerialization);
+ aResult.Append(']');
+ }
+ }
+ aResult.Append(')');
+
+ } else if (unit == eCSSUnit_Null) {
+ // Empty or omitted <line-names>.
+ if (isSubgrid) {
+ aResult.AppendLiteral("[]");
+ } else {
+ // Serializes to nothing.
+ addSpaceSeparator = false; // Avoid a double space.
+ }
+
+ } else if (unit == eCSSUnit_List || unit == eCSSUnit_ListDep) {
+ // Non-empty <line-names>
+ aResult.Append('[');
+ AppendValueListToString(val->mValue.GetListValue(), aProperty,
+ aResult, aSerialization);
+ aResult.Append(']');
+
+ } else {
+ // <track-size>
+ val->mValue.AppendToString(aProperty, aResult, aSerialization);
+ if (!isSubgrid &&
+ val->mNext &&
+ val->mNext->mValue.GetUnit() == eCSSUnit_Null &&
+ !val->mNext->mNext) {
+ // Break out of the loop early to avoid a trailing space.
+ break;
+ }
+ }
+
+ val = val->mNext;
+ if (!val) {
+ break;
+ }
+
+ if (addSpaceSeparator) {
+ aResult.Append(char16_t(' '));
+ }
+ }
+}
+
+void
+nsCSSValueList::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aSerialization) const
+{
+ if (aProperty == eCSSProperty_grid_template_columns ||
+ aProperty == eCSSProperty_grid_template_rows) {
+ AppendGridTemplateToString(this, aProperty, aResult, aSerialization);
+ } else {
+ AppendValueListToString(this, aProperty, aResult, aSerialization);
+ }
+}
+
+/* static */ bool
+nsCSSValueList::Equal(const nsCSSValueList* aList1,
+ const nsCSSValueList* aList2)
+{
+ if (aList1 == aList2) {
+ return true;
+ }
+
+ const nsCSSValueList *p1 = aList1, *p2 = aList2;
+ for ( ; p1 && p2; p1 = p1->mNext, p2 = p2->mNext) {
+ if (p1->mValue != p2->mValue)
+ return false;
+ }
+ return !p1 && !p2; // true if same length, false otherwise
+}
+
+size_t
+nsCSSValueList::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ const nsCSSValueList* v = this;
+ while (v) {
+ n += aMallocSizeOf(v);
+ n += v->mValue.SizeOfExcludingThis(aMallocSizeOf);
+ v = v->mNext;
+ }
+ return n;
+}
+
+size_t
+nsCSSValueList_heap::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += mValue.SizeOfExcludingThis(aMallocSizeOf);
+ n += mNext ? mNext->SizeOfIncludingThis(aMallocSizeOf) : 0;
+ }
+ return n;
+}
+
+// --- nsCSSValueSharedList -----------------
+
+nsCSSValueSharedList::~nsCSSValueSharedList()
+{
+ MOZ_COUNT_DTOR(nsCSSValueSharedList);
+ if (mHead) {
+ NS_CSS_DELETE_LIST_MEMBER(nsCSSValueList, mHead, mNext);
+ delete mHead;
+ }
+}
+
+void
+nsCSSValueSharedList::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aSerialization) const
+{
+ if (mHead) {
+ mHead->AppendToString(aProperty, aResult, aSerialization);
+ }
+}
+
+bool
+nsCSSValueSharedList::operator==(const nsCSSValueSharedList& aOther) const
+{
+ return nsCSSValueList::Equal(mHead, aOther.mHead);
+}
+
+size_t
+nsCSSValueSharedList::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += mHead->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+// --- nsCSSRect -----------------
+
+nsCSSRect::nsCSSRect(void)
+{
+ MOZ_COUNT_CTOR(nsCSSRect);
+}
+
+nsCSSRect::nsCSSRect(const nsCSSRect& aCopy)
+ : mTop(aCopy.mTop),
+ mRight(aCopy.mRight),
+ mBottom(aCopy.mBottom),
+ mLeft(aCopy.mLeft)
+{
+ MOZ_COUNT_CTOR(nsCSSRect);
+}
+
+nsCSSRect::~nsCSSRect()
+{
+ MOZ_COUNT_DTOR(nsCSSRect);
+}
+
+void
+nsCSSRect::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aSerialization) const
+{
+ MOZ_ASSERT(mTop.GetUnit() != eCSSUnit_Null &&
+ mTop.GetUnit() != eCSSUnit_Inherit &&
+ mTop.GetUnit() != eCSSUnit_Initial &&
+ mTop.GetUnit() != eCSSUnit_Unset,
+ "parser should have used a bare value");
+
+ if (eCSSProperty_border_image_slice == aProperty ||
+ eCSSProperty_border_image_width == aProperty ||
+ eCSSProperty_border_image_outset == aProperty ||
+ eCSSProperty_DOM == aProperty) {
+ NS_NAMED_LITERAL_STRING(space, " ");
+
+ mTop.AppendToString(aProperty, aResult, aSerialization);
+ aResult.Append(space);
+ mRight.AppendToString(aProperty, aResult, aSerialization);
+ aResult.Append(space);
+ mBottom.AppendToString(aProperty, aResult, aSerialization);
+ aResult.Append(space);
+ mLeft.AppendToString(aProperty, aResult, aSerialization);
+ } else {
+ NS_NAMED_LITERAL_STRING(comma, ", ");
+
+ aResult.AppendLiteral("rect(");
+ mTop.AppendToString(aProperty, aResult, aSerialization);
+ aResult.Append(comma);
+ mRight.AppendToString(aProperty, aResult, aSerialization);
+ aResult.Append(comma);
+ mBottom.AppendToString(aProperty, aResult, aSerialization);
+ aResult.Append(comma);
+ mLeft.AppendToString(aProperty, aResult, aSerialization);
+ aResult.Append(char16_t(')'));
+ }
+}
+
+void nsCSSRect::SetAllSidesTo(const nsCSSValue& aValue)
+{
+ mTop = aValue;
+ mRight = aValue;
+ mBottom = aValue;
+ mLeft = aValue;
+}
+
+size_t
+nsCSSRect_heap::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += mTop .SizeOfExcludingThis(aMallocSizeOf);
+ n += mRight .SizeOfExcludingThis(aMallocSizeOf);
+ n += mBottom.SizeOfExcludingThis(aMallocSizeOf);
+ n += mLeft .SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+static_assert(NS_SIDE_TOP == 0 && NS_SIDE_RIGHT == 1 &&
+ NS_SIDE_BOTTOM == 2 && NS_SIDE_LEFT == 3,
+ "box side constants not top/right/bottom/left == 0/1/2/3");
+
+/* static */ const nsCSSRect::side_type nsCSSRect::sides[4] = {
+ &nsCSSRect::mTop,
+ &nsCSSRect::mRight,
+ &nsCSSRect::mBottom,
+ &nsCSSRect::mLeft,
+};
+
+// --- nsCSSValuePair -----------------
+
+void
+nsCSSValuePair::AppendToString(nsCSSPropertyID aProperty,
+ nsAString& aResult,
+ nsCSSValue::Serialization aSerialization) const
+{
+ mXValue.AppendToString(aProperty, aResult, aSerialization);
+ if (mYValue.GetUnit() != eCSSUnit_Null) {
+ aResult.Append(char16_t(' '));
+ mYValue.AppendToString(aProperty, aResult, aSerialization);
+ }
+}
+
+size_t
+nsCSSValuePair::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ n += mXValue.SizeOfExcludingThis(aMallocSizeOf);
+ n += mYValue.SizeOfExcludingThis(aMallocSizeOf);
+ return n;
+}
+
+size_t
+nsCSSValuePair_heap::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += mXValue.SizeOfExcludingThis(aMallocSizeOf);
+ n += mYValue.SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+// --- nsCSSValueTriplet -----------------
+
+void
+nsCSSValueTriplet::AppendToString(nsCSSPropertyID aProperty,
+ nsAString& aResult,
+ nsCSSValue::Serialization aSerialization) const
+{
+ mXValue.AppendToString(aProperty, aResult, aSerialization);
+ if (mYValue.GetUnit() != eCSSUnit_Null) {
+ aResult.Append(char16_t(' '));
+ mYValue.AppendToString(aProperty, aResult, aSerialization);
+ if (mZValue.GetUnit() != eCSSUnit_Null) {
+ aResult.Append(char16_t(' '));
+ mZValue.AppendToString(aProperty, aResult, aSerialization);
+ }
+ }
+}
+
+size_t
+nsCSSValueTriplet_heap::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += mXValue.SizeOfExcludingThis(aMallocSizeOf);
+ n += mYValue.SizeOfExcludingThis(aMallocSizeOf);
+ n += mZValue.SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+// --- nsCSSValuePairList -----------------
+
+nsCSSValuePairList::~nsCSSValuePairList()
+{
+ MOZ_COUNT_DTOR(nsCSSValuePairList);
+ NS_CSS_DELETE_LIST_MEMBER(nsCSSValuePairList, this, mNext);
+}
+
+nsCSSValuePairList*
+nsCSSValuePairList::Clone() const
+{
+ nsCSSValuePairList* result = new nsCSSValuePairList(*this);
+ nsCSSValuePairList* dest = result;
+ const nsCSSValuePairList* src = this->mNext;
+ while (src) {
+ dest->mNext = new nsCSSValuePairList(*src);
+ dest = dest->mNext;
+ src = src->mNext;
+ }
+
+ MOZ_ASSERT(result, "shouldn't return null; supposed to be infallible");
+ return result;
+}
+
+void
+nsCSSValuePairList::AppendToString(nsCSSPropertyID aProperty,
+ nsAString& aResult,
+ nsCSSValue::Serialization aSerialization) const
+{
+ const nsCSSValuePairList* item = this;
+ for (;;) {
+ MOZ_ASSERT(item->mXValue.GetUnit() != eCSSUnit_Null,
+ "unexpected null unit");
+ item->mXValue.AppendToString(aProperty, aResult, aSerialization);
+ if (item->mXValue.GetUnit() != eCSSUnit_Inherit &&
+ item->mXValue.GetUnit() != eCSSUnit_Initial &&
+ item->mXValue.GetUnit() != eCSSUnit_Unset &&
+ item->mYValue.GetUnit() != eCSSUnit_Null) {
+ aResult.Append(char16_t(' '));
+ item->mYValue.AppendToString(aProperty, aResult, aSerialization);
+ }
+ item = item->mNext;
+ if (!item)
+ break;
+
+ if (nsCSSProps::PropHasFlags(aProperty,
+ CSS_PROPERTY_VALUE_LIST_USES_COMMAS) ||
+ aProperty == eCSSProperty_clip_path ||
+ aProperty == eCSSProperty_shape_outside)
+ aResult.Append(char16_t(','));
+ aResult.Append(char16_t(' '));
+ }
+}
+
+/* static */ bool
+nsCSSValuePairList::Equal(const nsCSSValuePairList* aList1,
+ const nsCSSValuePairList* aList2)
+{
+ if (aList1 == aList2) {
+ return true;
+ }
+
+ const nsCSSValuePairList *p1 = aList1, *p2 = aList2;
+ for ( ; p1 && p2; p1 = p1->mNext, p2 = p2->mNext) {
+ if (p1->mXValue != p2->mXValue ||
+ p1->mYValue != p2->mYValue)
+ return false;
+ }
+ return !p1 && !p2; // true if same length, false otherwise
+}
+
+size_t
+nsCSSValuePairList::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ const nsCSSValuePairList* v = this;
+ while (v) {
+ n += aMallocSizeOf(v);
+ n += v->mXValue.SizeOfExcludingThis(aMallocSizeOf);
+ n += v->mYValue.SizeOfExcludingThis(aMallocSizeOf);
+ v = v->mNext;
+ }
+ return n;
+}
+
+size_t
+nsCSSValuePairList_heap::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += mXValue.SizeOfExcludingThis(aMallocSizeOf);
+ n += mYValue.SizeOfExcludingThis(aMallocSizeOf);
+ n += mNext ? mNext->SizeOfIncludingThis(aMallocSizeOf) : 0;
+ }
+ return n;
+}
+
+size_t
+nsCSSValue::Array::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ for (size_t i = 0; i < mCount; i++) {
+ n += mArray[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+css::URLValueData::URLValueData(already_AddRefed<PtrHolder<nsIURI>> aURI,
+ nsStringBuffer* aString,
+ already_AddRefed<PtrHolder<nsIURI>> aBaseURI,
+ already_AddRefed<PtrHolder<nsIURI>> aReferrer,
+ already_AddRefed<PtrHolder<nsIPrincipal>>
+ aOriginPrincipal)
+ : mURI(Move(aURI))
+ , mBaseURI(Move(aBaseURI))
+ , mString(aString)
+ , mReferrer(Move(aReferrer))
+ , mOriginPrincipal(Move(aOriginPrincipal))
+ , mURIResolved(true)
+ , mIsLocalRef(IsLocalRefURL(aString))
+{
+ MOZ_ASSERT(mString);
+ MOZ_ASSERT(mBaseURI);
+ MOZ_ASSERT(mOriginPrincipal);
+}
+
+css::URLValueData::URLValueData(nsStringBuffer* aString,
+ already_AddRefed<PtrHolder<nsIURI>> aBaseURI,
+ already_AddRefed<PtrHolder<nsIURI>> aReferrer,
+ already_AddRefed<PtrHolder<nsIPrincipal>>
+ aOriginPrincipal)
+ : mBaseURI(Move(aBaseURI))
+ , mString(aString)
+ , mReferrer(Move(aReferrer))
+ , mOriginPrincipal(Move(aOriginPrincipal))
+ , mURIResolved(false)
+ , mIsLocalRef(IsLocalRefURL(aString))
+{
+ MOZ_ASSERT(aString);
+ MOZ_ASSERT(mBaseURI);
+ MOZ_ASSERT(mOriginPrincipal);
+}
+
+bool
+css::URLValueData::Equals(const URLValueData& aOther) const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool eq;
+ // Cast away const so we can call nsIPrincipal::Equals.
+ auto& self = *const_cast<URLValueData*>(this);
+ auto& other = const_cast<URLValueData&>(aOther);
+ return NS_strcmp(nsCSSValue::GetBufferValue(mString),
+ nsCSSValue::GetBufferValue(aOther.mString)) == 0 &&
+ (GetURI() == aOther.GetURI() || // handles null == null
+ (mURI && aOther.mURI &&
+ NS_SUCCEEDED(mURI->Equals(aOther.mURI, &eq)) &&
+ eq)) &&
+ (mBaseURI == aOther.mBaseURI ||
+ (NS_SUCCEEDED(self.mBaseURI.get()->Equals(other.mBaseURI.get(), &eq)) &&
+ eq)) &&
+ (mOriginPrincipal == aOther.mOriginPrincipal ||
+ self.mOriginPrincipal.get()->Equals(other.mOriginPrincipal.get())) &&
+ mIsLocalRef == aOther.mIsLocalRef;
+}
+
+bool
+css::URLValueData::DefinitelyEqualURIs(const URLValueData& aOther) const
+{
+ return mBaseURI == aOther.mBaseURI &&
+ (mString == aOther.mString ||
+ NS_strcmp(nsCSSValue::GetBufferValue(mString),
+ nsCSSValue::GetBufferValue(aOther.mString)) == 0);
+}
+
+bool
+css::URLValueData::DefinitelyEqualURIsAndPrincipal(
+ const URLValueData& aOther) const
+{
+ return mOriginPrincipal == aOther.mOriginPrincipal &&
+ DefinitelyEqualURIs(aOther);
+}
+
+nsIURI*
+css::URLValueData::GetURI() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mURIResolved) {
+ MOZ_ASSERT(!mURI);
+ nsCOMPtr<nsIURI> newURI;
+ NS_NewURI(getter_AddRefs(newURI),
+ NS_ConvertUTF16toUTF8(nsCSSValue::GetBufferValue(mString)),
+ nullptr, const_cast<nsIURI*>(mBaseURI.get()));
+ mURI = new PtrHolder<nsIURI>(newURI.forget());
+ mURIResolved = true;
+ }
+
+ return mURI;
+}
+
+already_AddRefed<nsIURI>
+css::URLValueData::ResolveLocalRef(nsIURI* aURI) const
+{
+ nsCOMPtr<nsIURI> result = GetURI();
+
+ if (result && mIsLocalRef) {
+ nsCString ref;
+ mURI->GetRef(ref);
+
+ aURI->Clone(getter_AddRefs(result));
+ result->SetRef(ref);
+ }
+
+ return result.forget();
+}
+
+already_AddRefed<nsIURI>
+css::URLValueData::ResolveLocalRef(nsIContent* aContent) const
+{
+ nsCOMPtr<nsIURI> url = aContent->GetBaseURI();
+ return ResolveLocalRef(url);
+}
+
+void
+css::URLValueData::GetSourceString(nsString& aRef) const
+{
+ nsIURI* uri = GetURI();
+ if (!uri) {
+ aRef.Truncate();
+ return;
+ }
+
+ nsCString cref;
+ if (mIsLocalRef) {
+ // XXXheycam It's possible we can just return mString in this case, since
+ // it should be the "#fragment" string the URLValueData was created with.
+ uri->GetRef(cref);
+ cref.Insert('#', 0);
+ } else {
+ // It's not entirely clear how to best handle failure here. Ensuring the
+ // string is empty seems safest.
+ nsresult rv = uri->GetSpec(cref);
+ if (NS_FAILED(rv)) {
+ cref.Truncate();
+ }
+ }
+
+ aRef = NS_ConvertUTF8toUTF16(cref);
+}
+
+bool
+css::URLValueData::EqualsExceptRef(nsIURI* aURI) const
+{
+ nsIURI* uri = GetURI();
+ if (!uri) {
+ return false;
+ }
+
+ bool ret = false;
+ uri->EqualsExceptRef(aURI, &ret);
+ return ret;
+}
+
+size_t
+css::URLValueData::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ n += mString->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mURI
+ // - mReferrer
+ // - mOriginPrincipal
+ return n;
+}
+
+URLValue::URLValue(nsStringBuffer* aString, nsIURI* aBaseURI, nsIURI* aReferrer,
+ nsIPrincipal* aOriginPrincipal)
+ : URLValueData(aString,
+ do_AddRef(new PtrHolder<nsIURI>(aBaseURI)),
+ do_AddRef(new PtrHolder<nsIURI>(aReferrer)),
+ do_AddRef(new PtrHolder<nsIPrincipal>(aOriginPrincipal)))
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+URLValue::URLValue(nsIURI* aURI, nsStringBuffer* aString, nsIURI* aBaseURI,
+ nsIURI* aReferrer, nsIPrincipal* aOriginPrincipal)
+ : URLValueData(do_AddRef(new PtrHolder<nsIURI>(aURI)),
+ aString,
+ do_AddRef(new PtrHolder<nsIURI>(aBaseURI)),
+ do_AddRef(new PtrHolder<nsIURI>(aReferrer)),
+ do_AddRef(new PtrHolder<nsIPrincipal>(aOriginPrincipal)))
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+size_t
+css::URLValue::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += URLValueData::SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+css::ImageValue::ImageValue(nsIURI* aURI, nsStringBuffer* aString,
+ nsIURI* aBaseURI, nsIURI* aReferrer,
+ nsIPrincipal* aOriginPrincipal,
+ nsIDocument* aDocument)
+ : URLValueData(do_AddRef(new PtrHolder<nsIURI>(aURI)),
+ aString,
+ do_AddRef(new PtrHolder<nsIURI>(aBaseURI, false)),
+ do_AddRef(new PtrHolder<nsIURI>(aReferrer)),
+ do_AddRef(new PtrHolder<nsIPrincipal>(aOriginPrincipal)))
+{
+ Initialize(aDocument);
+}
+
+css::ImageValue::ImageValue(
+ nsStringBuffer* aString,
+ already_AddRefed<PtrHolder<nsIURI>> aBaseURI,
+ already_AddRefed<PtrHolder<nsIURI>> aReferrer,
+ already_AddRefed<PtrHolder<nsIPrincipal>> aOriginPrincipal)
+ : URLValueData(aString, Move(aBaseURI), Move(aReferrer),
+ Move(aOriginPrincipal))
+{
+}
+
+void
+css::ImageValue::Initialize(nsIDocument* aDocument)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mInitialized);
+
+ // NB: If aDocument is not the original document, we may not be able to load
+ // images from aDocument. Instead we do the image load from the original doc
+ // and clone it to aDocument.
+ nsIDocument* loadingDoc = aDocument->GetOriginalDocument();
+ if (!loadingDoc) {
+ loadingDoc = aDocument;
+ }
+
+ loadingDoc->StyleImageLoader()->LoadImage(GetURI(), mOriginPrincipal,
+ mReferrer, this);
+
+ if (loadingDoc != aDocument) {
+ aDocument->StyleImageLoader()->MaybeRegisterCSSImage(this);
+ }
+
+#ifdef DEBUG
+ mInitialized = true;
+#endif
+}
+
+css::ImageValue::~ImageValue()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ nsIDocument* doc = iter.Key();
+ RefPtr<imgRequestProxy>& proxy = iter.Data();
+
+ if (doc) {
+ doc->StyleImageLoader()->DeregisterCSSImage(this);
+ }
+
+ if (proxy) {
+ proxy->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+
+ iter.Remove();
+ }
+}
+
+size_t
+css::ComplexColorValue::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ }
+ return n;
+}
+
+nsCSSValueGradientStop::nsCSSValueGradientStop()
+ : mLocation(eCSSUnit_None),
+ mColor(eCSSUnit_Null),
+ mIsInterpolationHint(false)
+{
+ MOZ_COUNT_CTOR(nsCSSValueGradientStop);
+}
+
+nsCSSValueGradientStop::nsCSSValueGradientStop(const nsCSSValueGradientStop& aOther)
+ : mLocation(aOther.mLocation),
+ mColor(aOther.mColor),
+ mIsInterpolationHint(aOther.mIsInterpolationHint)
+{
+ MOZ_COUNT_CTOR(nsCSSValueGradientStop);
+}
+
+nsCSSValueGradientStop::~nsCSSValueGradientStop()
+{
+ MOZ_COUNT_DTOR(nsCSSValueGradientStop);
+}
+
+size_t
+nsCSSValueGradientStop::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ n += mLocation.SizeOfExcludingThis(aMallocSizeOf);
+ n += mColor .SizeOfExcludingThis(aMallocSizeOf);
+ return n;
+}
+
+nsCSSValueGradient::nsCSSValueGradient(bool aIsRadial,
+ bool aIsRepeating)
+ : mIsRadial(aIsRadial),
+ mIsRepeating(aIsRepeating),
+ mIsLegacySyntax(false),
+ mIsExplicitSize(false),
+ mBgPos(eCSSUnit_None),
+ mAngle(eCSSUnit_None)
+{
+ mRadialValues[0].SetNoneValue();
+ mRadialValues[1].SetNoneValue();
+}
+
+size_t
+nsCSSValueGradient::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += mBgPos.SizeOfExcludingThis(aMallocSizeOf);
+ n += mAngle.SizeOfExcludingThis(aMallocSizeOf);
+ n += mRadialValues[0].SizeOfExcludingThis(aMallocSizeOf);
+ n += mRadialValues[1].SizeOfExcludingThis(aMallocSizeOf);
+ n += mStops.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ n += mStops[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+ }
+ return n;
+}
+
+// --- nsCSSValueTokenStream ------------
+
+nsCSSValueTokenStream::nsCSSValueTokenStream()
+ : mPropertyID(eCSSProperty_UNKNOWN)
+ , mShorthandPropertyID(eCSSProperty_UNKNOWN)
+ , mLevel(SheetType::Count)
+{
+ MOZ_COUNT_CTOR(nsCSSValueTokenStream);
+}
+
+nsCSSValueTokenStream::~nsCSSValueTokenStream()
+{
+ MOZ_COUNT_DTOR(nsCSSValueTokenStream);
+}
+
+size_t
+nsCSSValueTokenStream::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += mTokenStream.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+ return n;
+}
+
+// --- nsCSSValueFloatColor -------------
+
+bool
+nsCSSValueFloatColor::operator==(nsCSSValueFloatColor& aOther) const
+{
+ return mComponent1 == aOther.mComponent1 &&
+ mComponent2 == aOther.mComponent2 &&
+ mComponent3 == aOther.mComponent3 &&
+ mAlpha == aOther.mAlpha;
+}
+
+nscolor
+nsCSSValueFloatColor::GetColorValue(nsCSSUnit aUnit) const
+{
+ MOZ_ASSERT(nsCSSValue::IsFloatColorUnit(aUnit), "unexpected unit");
+
+ // We should clamp each component value since eCSSUnit_PercentageRGBColor
+ // and eCSSUnit_PercentageRGBAColor may store values greater than 1.0.
+ if (aUnit == eCSSUnit_PercentageRGBColor ||
+ aUnit == eCSSUnit_PercentageRGBAColor) {
+ return NS_RGBA(
+ // We need to clamp before multiplying by 255.0f to avoid overflow.
+ NSToIntRound(mozilla::clamped(mComponent1, 0.0f, 1.0f) * 255.0f),
+ NSToIntRound(mozilla::clamped(mComponent2, 0.0f, 1.0f) * 255.0f),
+ NSToIntRound(mozilla::clamped(mComponent3, 0.0f, 1.0f) * 255.0f),
+ NSToIntRound(mozilla::clamped(mAlpha, 0.0f, 1.0f) * 255.0f));
+ }
+
+ // HSL color
+ MOZ_ASSERT(aUnit == eCSSUnit_HSLColor ||
+ aUnit == eCSSUnit_HSLAColor);
+ nscolor hsl = NS_HSL2RGB(mComponent1, mComponent2, mComponent3);
+ return NS_RGBA(NS_GET_R(hsl),
+ NS_GET_G(hsl),
+ NS_GET_B(hsl),
+ NSToIntRound(mAlpha * 255.0f));
+}
+
+bool
+nsCSSValueFloatColor::IsNonTransparentColor() const
+{
+ return mAlpha > 0.0f;
+}
+
+void
+nsCSSValueFloatColor::AppendToString(nsCSSUnit aUnit, nsAString& aResult) const
+{
+ // Similar to the rgb()/rgba() case in nsCSSValue::AppendToString. We omit the
+ // alpha component if it's equal to 1.0f (full opaque). Also, we try to
+ // preserve the author-specified function name, unless it's rgba()/hsla() and
+ // we're omitting the alpha component - then we use rgb()/hsl().
+ MOZ_ASSERT(nsCSSValue::IsFloatColorUnit(aUnit), "unexpected unit");
+
+ bool showAlpha = (mAlpha != 1.0f);
+ bool isHSL = (aUnit == eCSSUnit_HSLColor ||
+ aUnit == eCSSUnit_HSLAColor);
+
+ if (isHSL) {
+ aResult.AppendLiteral("hsl");
+ } else {
+ aResult.AppendLiteral("rgb");
+ }
+ if (showAlpha && (aUnit == eCSSUnit_HSLAColor || aUnit == eCSSUnit_PercentageRGBAColor)) {
+ aResult.AppendLiteral("a(");
+ } else {
+ aResult.Append('(');
+ }
+ if (isHSL) {
+ aResult.AppendFloat(mComponent1 * 360.0f);
+ aResult.AppendLiteral(", ");
+ } else {
+ aResult.AppendFloat(mComponent1 * 100.0f);
+ aResult.AppendLiteral("%, ");
+ }
+ aResult.AppendFloat(mComponent2 * 100.0f);
+ aResult.AppendLiteral("%, ");
+ aResult.AppendFloat(mComponent3 * 100.0f);
+ if (showAlpha) {
+ aResult.AppendLiteral("%, ");
+ aResult.AppendFloat(mAlpha);
+ aResult.Append(')');
+ } else {
+ aResult.AppendLiteral("%)");
+ }
+}
+
+size_t
+nsCSSValueFloatColor::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ }
+ return n;
+}
+
+// --- nsCSSCornerSizes -----------------
+
+nsCSSCornerSizes::nsCSSCornerSizes(void)
+{
+ MOZ_COUNT_CTOR(nsCSSCornerSizes);
+}
+
+nsCSSCornerSizes::nsCSSCornerSizes(const nsCSSCornerSizes& aCopy)
+ : mTopLeft(aCopy.mTopLeft),
+ mTopRight(aCopy.mTopRight),
+ mBottomRight(aCopy.mBottomRight),
+ mBottomLeft(aCopy.mBottomLeft)
+{
+ MOZ_COUNT_CTOR(nsCSSCornerSizes);
+}
+
+nsCSSCornerSizes::~nsCSSCornerSizes()
+{
+ MOZ_COUNT_DTOR(nsCSSCornerSizes);
+}
+
+void
+nsCSSCornerSizes::Reset()
+{
+ NS_FOR_CSS_FULL_CORNERS(corner) {
+ this->GetCorner(corner).Reset();
+ }
+}
+
+static_assert(NS_CORNER_TOP_LEFT == 0 && NS_CORNER_TOP_RIGHT == 1 &&
+ NS_CORNER_BOTTOM_RIGHT == 2 && NS_CORNER_BOTTOM_LEFT == 3,
+ "box corner constants not tl/tr/br/bl == 0/1/2/3");
+
+/* static */ const nsCSSCornerSizes::corner_type
+nsCSSCornerSizes::corners[4] = {
+ &nsCSSCornerSizes::mTopLeft,
+ &nsCSSCornerSizes::mTopRight,
+ &nsCSSCornerSizes::mBottomRight,
+ &nsCSSCornerSizes::mBottomLeft,
+};
+
+size_t
+mozilla::css::GridTemplateAreasValue::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // Only measure it if it's unshared, to avoid double-counting.
+ size_t n = 0;
+ if (mRefCnt <= 1) {
+ n += aMallocSizeOf(this);
+ n += mNamedAreas.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mTemplates.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
diff --git a/layout/style/nsCSSValue.h b/layout/style/nsCSSValue.h
new file mode 100644
index 000000000..1721cc8ee
--- /dev/null
+++ b/layout/style/nsCSSValue.h
@@ -0,0 +1,1943 @@
+/* -*- 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/. */
+
+/* representation of simple property values within CSS declarations */
+
+#ifndef nsCSSValue_h___
+#define nsCSSValue_h___
+
+#include <type_traits>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/SheetType.h"
+#include "mozilla/StyleComplexColor.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsCSSKeywords.h"
+#include "nsCSSPropertyID.h"
+#include "nsCSSProps.h"
+#include "nsColor.h"
+#include "nsCoord.h"
+#include "nsProxyRelease.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsTArray.h"
+#include "nsStyleConsts.h"
+#include "nsStyleCoord.h"
+#include "gfxFontFamilyList.h"
+
+class imgRequestProxy;
+class nsIContent;
+class nsIDocument;
+class nsIPrincipal;
+class nsIURI;
+class nsPresContext;
+template <class T>
+class nsPtrHashKey;
+
+namespace mozilla {
+class CSSStyleSheet;
+} // namespace mozilla
+
+// Deletes a linked list iteratively to avoid blowing up the stack (bug 456196).
+#define NS_CSS_DELETE_LIST_MEMBER(type_, ptr_, member_) \
+ { \
+ type_ *cur = (ptr_)->member_; \
+ (ptr_)->member_ = nullptr; \
+ while (cur) { \
+ type_ *dlm_next = cur->member_; \
+ cur->member_ = nullptr; \
+ delete cur; \
+ cur = dlm_next; \
+ } \
+ }
+// Ditto, but use NS_RELEASE instead of 'delete' (bug 1221902).
+#define NS_CSS_NS_RELEASE_LIST_MEMBER(type_, ptr_, member_) \
+ { \
+ type_ *cur = (ptr_)->member_; \
+ (ptr_)->member_ = nullptr; \
+ while (cur) { \
+ type_ *dlm_next = cur->member_; \
+ cur->member_ = nullptr; \
+ NS_RELEASE(cur); \
+ cur = dlm_next; \
+ } \
+ }
+
+// Clones a linked list iteratively to avoid blowing up the stack.
+// If it fails to clone the entire list then 'to_' is deleted and
+// we return null.
+#define NS_CSS_CLONE_LIST_MEMBER(type_, from_, member_, to_, args_) \
+ { \
+ type_ *dest = (to_); \
+ (to_)->member_ = nullptr; \
+ for (const type_ *src = (from_)->member_; src; src = src->member_) { \
+ type_ *clm_clone = src->Clone args_; \
+ if (!clm_clone) { \
+ delete (to_); \
+ return nullptr; \
+ } \
+ dest->member_ = clm_clone; \
+ dest = clm_clone; \
+ } \
+ }
+
+namespace mozilla {
+namespace css {
+
+struct URLValueData
+{
+protected:
+ // Methods are not inline because using an nsIPrincipal means requiring
+ // caps, which leads to REQUIRES hell, since this header is included all
+ // over.
+
+ // For both constructors aString must not be null.
+ // For both constructors aOriginPrincipal must not be null.
+ // Construct with a base URI; this will create the actual URI lazily from
+ // aString and aBaseURI.
+ URLValueData(nsStringBuffer* aString,
+ already_AddRefed<PtrHolder<nsIURI>> aBaseURI,
+ already_AddRefed<PtrHolder<nsIURI>> aReferrer,
+ already_AddRefed<PtrHolder<nsIPrincipal>> aOriginPricinpal);
+ // Construct with the actual URI.
+ URLValueData(already_AddRefed<PtrHolder<nsIURI>> aURI,
+ nsStringBuffer* aString,
+ already_AddRefed<PtrHolder<nsIURI>> aBaseURI,
+ already_AddRefed<PtrHolder<nsIURI>> aReferrer,
+ already_AddRefed<PtrHolder<nsIPrincipal>> aOriginPrincipal);
+
+public:
+ // Returns true iff all fields of the two URLValueData objects are equal.
+ //
+ // Only safe to call on the main thread, since this will call Equals on the
+ // nsIURI and nsIPrincipal objects stored on the URLValueData objects.
+ bool Equals(const URLValueData& aOther) const;
+
+ // Returns true iff we know for sure, by comparing the mBaseURI pointer,
+ // the specified url() value mString, and the mIsLocalRef, that these
+ // two URLValueData objects represent the same computed url() value.
+ //
+ // Doesn't look at mReferrer or mOriginPrincipal.
+ //
+ // Safe to call from any thread.
+ bool DefinitelyEqualURIs(const URLValueData& aOther) const;
+
+ // Smae as DefinitelyEqualURIs but additionally compares the nsIPrincipal
+ // pointers of the two URLValueData objects.
+ bool DefinitelyEqualURIsAndPrincipal(const URLValueData& aOther) const;
+
+ nsIURI* GetURI() const;
+
+ bool IsLocalRef() const { return mIsLocalRef; }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLValueData)
+
+ // When matching a url with mIsLocalRef set, resolve it against aURI;
+ // Otherwise, ignore aURL and return mURL directly.
+ already_AddRefed<nsIURI> ResolveLocalRef(nsIURI* aURI) const;
+ already_AddRefed<nsIURI> ResolveLocalRef(nsIContent* aContent) const;
+
+ // Serializes mURI as a computed URI value, taking into account mIsLocalRef
+ // and serializing just the fragment if true.
+ void GetSourceString(nsString& aRef) const;
+
+ bool EqualsExceptRef(nsIURI* aURI) const;
+
+private:
+ // mURI stores the lazily resolved URI. This may be null if the URI is
+ // invalid, even once resolved.
+ mutable PtrHandle<nsIURI> mURI;
+public:
+ PtrHandle<nsIURI> mBaseURI;
+ RefPtr<nsStringBuffer> mString;
+ PtrHandle<nsIURI> mReferrer;
+ PtrHandle<nsIPrincipal> mOriginPrincipal;
+private:
+ mutable bool mURIResolved;
+ // mIsLocalRef is set when url starts with a U+0023 number sign(#) character.
+ bool mIsLocalRef;
+
+protected:
+ virtual ~URLValueData() = default;
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ URLValueData(const URLValueData& aOther) = delete;
+ URLValueData& operator=(const URLValueData& aOther) = delete;
+};
+
+struct URLValue final : public URLValueData
+{
+ // These two constructors are safe to call only on the main thread.
+ URLValue(nsStringBuffer* aString, nsIURI* aBaseURI, nsIURI* aReferrer,
+ nsIPrincipal* aOriginPrincipal);
+ URLValue(nsIURI* aURI, nsStringBuffer* aString, nsIURI* aBaseURI,
+ nsIURI* aReferrer, nsIPrincipal* aOriginPrincipal);
+
+ // This constructor is safe to call from any thread.
+ URLValue(nsStringBuffer* aString,
+ already_AddRefed<PtrHolder<nsIURI>> aBaseURI,
+ already_AddRefed<PtrHolder<nsIURI>> aReferrer,
+ already_AddRefed<PtrHolder<nsIPrincipal>> aOriginPrincipal)
+ : URLValueData(aString, Move(aBaseURI), Move(aReferrer),
+ Move(aOriginPrincipal)) {}
+
+ URLValue(const URLValue&) = delete;
+ URLValue& operator=(const URLValue&) = delete;
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+};
+
+struct ImageValue final : public URLValueData
+{
+ // Not making the constructor and destructor inline because that would
+ // force us to include imgIRequest.h, which leads to REQUIRES hell, since
+ // this header is included all over.
+ // aString must not be null.
+ //
+ // This constructor is only safe to call from the main thread.
+ ImageValue(nsIURI* aURI, nsStringBuffer* aString, nsIURI* aBaseURI,
+ nsIURI* aReferrer, nsIPrincipal* aOriginPrincipal,
+ nsIDocument* aDocument);
+
+ // This constructor is safe to call from any thread, but Initialize
+ // must be called later for the object to be useful.
+ ImageValue(nsStringBuffer* aString,
+ already_AddRefed<PtrHolder<nsIURI>> aBaseURI,
+ already_AddRefed<PtrHolder<nsIURI>> aReferrer,
+ already_AddRefed<PtrHolder<nsIPrincipal>> aOriginPrincipal);
+
+ ImageValue(const ImageValue&) = delete;
+ ImageValue& operator=(const ImageValue&) = delete;
+
+ void Initialize(nsIDocument* aDocument);
+
+ // XXXheycam We should have our own SizeOfIncludingThis method.
+
+protected:
+ ~ImageValue();
+
+public:
+ // Inherit Equals from URLValueData
+
+ nsRefPtrHashtable<nsPtrHashKey<nsIDocument>, imgRequestProxy> mRequests;
+
+private:
+#ifdef DEBUG
+ bool mInitialized = false;
+#endif
+};
+
+struct GridNamedArea {
+ nsString mName;
+ uint32_t mColumnStart;
+ uint32_t mColumnEnd;
+ uint32_t mRowStart;
+ uint32_t mRowEnd;
+};
+
+struct GridTemplateAreasValue final {
+ // Parsed value
+ nsTArray<GridNamedArea> mNamedAreas;
+
+ // Original <string> values. Length gives the number of rows,
+ // content makes serialization easier.
+ nsTArray<nsString> mTemplates;
+
+ // How many columns grid-template-areas contributes to the explicit grid.
+ // http://dev.w3.org/csswg/css-grid/#explicit-grid
+ uint32_t mNColumns;
+
+ // How many rows grid-template-areas contributes to the explicit grid.
+ // http://dev.w3.org/csswg/css-grid/#explicit-grid
+ uint32_t NRows() const {
+ return mTemplates.Length();
+ }
+
+ GridTemplateAreasValue()
+ : mNColumns(0)
+ // Default constructors for mNamedAreas and mTemplates: empty arrays.
+ {
+ }
+
+ bool operator==(const GridTemplateAreasValue& aOther) const
+ {
+ return mTemplates == aOther.mTemplates;
+ }
+
+ bool operator!=(const GridTemplateAreasValue& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(GridTemplateAreasValue)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ // Private destructor to make sure this isn't used as a stack variable
+ // or member variable.
+ ~GridTemplateAreasValue()
+ {
+ }
+
+ GridTemplateAreasValue(const GridTemplateAreasValue& aOther) = delete;
+ GridTemplateAreasValue&
+ operator=(const GridTemplateAreasValue& aOther) = delete;
+};
+
+class FontFamilyListRefCnt final : public FontFamilyList {
+public:
+ FontFamilyListRefCnt()
+ : FontFamilyList()
+ {
+ MOZ_COUNT_CTOR(FontFamilyListRefCnt);
+ }
+
+ explicit FontFamilyListRefCnt(FontFamilyType aGenericType)
+ : FontFamilyList(aGenericType)
+ {
+ MOZ_COUNT_CTOR(FontFamilyListRefCnt);
+ }
+
+ FontFamilyListRefCnt(const nsAString& aFamilyName,
+ QuotedName aQuoted)
+ : FontFamilyList(aFamilyName, aQuoted)
+ {
+ MOZ_COUNT_CTOR(FontFamilyListRefCnt);
+ }
+
+ FontFamilyListRefCnt(const FontFamilyListRefCnt& aOther)
+ : FontFamilyList(aOther)
+ {
+ MOZ_COUNT_CTOR(FontFamilyListRefCnt);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(FontFamilyListRefCnt);
+
+private:
+ ~FontFamilyListRefCnt() {
+ MOZ_COUNT_DTOR(FontFamilyListRefCnt);
+ }
+};
+
+struct RGBAColorData
+{
+ // 1.0 means 100% for all components, but the value may fall outside
+ // the range of [0.0, 1.0], so it is necessary to clamp them when
+ // converting to nscolor.
+ float mR;
+ float mG;
+ float mB;
+ float mA;
+
+ RGBAColorData() = default;
+ MOZ_IMPLICIT RGBAColorData(nscolor aColor)
+ : mR(NS_GET_R(aColor) * (1.0f / 255.0f))
+ , mG(NS_GET_G(aColor) * (1.0f / 255.0f))
+ , mB(NS_GET_B(aColor) * (1.0f / 255.0f))
+ , mA(NS_GET_A(aColor) * (1.0f / 255.0f))
+ {}
+ RGBAColorData(float aR, float aG, float aB, float aA)
+ : mR(aR), mG(aG), mB(aB), mA(aA) {}
+
+ bool operator==(const RGBAColorData& aOther) const
+ {
+ return mR == aOther.mR && mG == aOther.mG &&
+ mB == aOther.mB && mA == aOther.mA;
+ }
+ bool operator!=(const RGBAColorData& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ nscolor ToColor() const
+ {
+ return NS_RGBA(ClampColor(mR * 255.0f),
+ ClampColor(mG * 255.0f),
+ ClampColor(mB * 255.0f),
+ ClampColor(mA * 255.0f));
+ }
+
+ RGBAColorData WithAlpha(float aAlpha) const
+ {
+ RGBAColorData result = *this;
+ result.mA = aAlpha;
+ return result;
+ }
+};
+
+struct ComplexColorData
+{
+ RGBAColorData mColor;
+ float mForegroundRatio;
+
+ ComplexColorData() = default;
+ ComplexColorData(const RGBAColorData& aColor, float aForegroundRatio)
+ : mColor(aColor), mForegroundRatio(aForegroundRatio) {}
+ ComplexColorData(nscolor aColor, float aForegroundRatio)
+ : mColor(aColor), mForegroundRatio(aForegroundRatio) {}
+ explicit ComplexColorData(const StyleComplexColor& aColor)
+ : mColor(aColor.mColor)
+ , mForegroundRatio(aColor.mForegroundRatio * (1.0f / 255.0f)) {}
+
+ bool operator==(const ComplexColorData& aOther) const
+ {
+ return mForegroundRatio == aOther.mForegroundRatio &&
+ (IsCurrentColor() || mColor == aOther.mColor);
+ }
+ bool operator!=(const ComplexColorData& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ bool IsCurrentColor() const { return mForegroundRatio >= 1.0f; }
+ bool IsNumericColor() const { return mForegroundRatio <= 0.0f; }
+
+ StyleComplexColor ToComplexColor() const
+ {
+ return {mColor.ToColor(), ClampColor(mForegroundRatio * 255.0f)};
+ }
+};
+
+struct ComplexColorValue final : public ComplexColorData
+{
+ // Just redirect any parameter to the data struct.
+ template<typename... Args>
+ explicit ComplexColorValue(Args&&... aArgs)
+ : ComplexColorData(Forward<Args>(aArgs)...) {}
+ ComplexColorValue(const ComplexColorValue&) = delete;
+
+ NS_INLINE_DECL_REFCOUNTING(ComplexColorValue)
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+private:
+ ~ComplexColorValue() {}
+};
+
+} // namespace css
+} // namespace mozilla
+
+enum nsCSSUnit {
+ eCSSUnit_Null = 0, // (n/a) null unit, value is not specified
+ eCSSUnit_Auto = 1, // (n/a) value is algorithmic
+ eCSSUnit_Inherit = 2, // (n/a) value is inherited
+ eCSSUnit_Initial = 3, // (n/a) value is default UA value
+ eCSSUnit_Unset = 4, // (n/a) value equivalent to 'initial' if on a reset property, 'inherit' otherwise
+ eCSSUnit_None = 5, // (n/a) value is none
+ eCSSUnit_Normal = 6, // (n/a) value is normal (algorithmic, different than auto)
+ eCSSUnit_System_Font = 7, // (n/a) value is -moz-use-system-font
+ eCSSUnit_All = 8, // (n/a) value is all
+ eCSSUnit_Dummy = 9, // (n/a) a fake but specified value, used
+ // only in temporary values
+ eCSSUnit_DummyInherit = 10, // (n/a) a fake but specified value, used
+ // only in temporary values
+
+ eCSSUnit_String = 11, // (char16_t*) a string value
+ eCSSUnit_Ident = 12, // (char16_t*) a string value
+ eCSSUnit_Attr = 14, // (char16_t*) a attr(string) value
+ eCSSUnit_Local_Font = 15, // (char16_t*) a local font name
+ eCSSUnit_Font_Format = 16, // (char16_t*) a font format name
+ eCSSUnit_Element = 17, // (char16_t*) an element id
+
+ eCSSUnit_Array = 20, // (nsCSSValue::Array*) a list of values
+ eCSSUnit_Counter = 21, // (nsCSSValue::Array*) a counter(string,[string]) value
+ eCSSUnit_Counters = 22, // (nsCSSValue::Array*) a counters(string,string[,string]) value
+ eCSSUnit_Cubic_Bezier = 23, // (nsCSSValue::Array*) a list of float values
+ eCSSUnit_Steps = 24, // (nsCSSValue::Array*) a list of (integer, enumerated)
+ eCSSUnit_Symbols = 25, // (nsCSSValue::Array*) a symbols(enumerated, symbols) value
+ eCSSUnit_Function = 26, // (nsCSSValue::Array*) a function with
+ // parameters. First elem of array is name,
+ // an nsCSSKeyword as eCSSUnit_Enumerated,
+ // the rest of the values are arguments.
+
+ // The top level of a calc() expression is eCSSUnit_Calc. All
+ // remaining eCSSUnit_Calc_* units only occur inside these toplevel
+ // calc values.
+
+ // eCSSUnit_Calc has an array with exactly 1 element. eCSSUnit_Calc
+ // exists so we can distinguish calc(2em) from 2em as specified values
+ // (but we drop this distinction for nsStyleCoord when we store
+ // computed values).
+ eCSSUnit_Calc = 30, // (nsCSSValue::Array*) calc() value
+ // Plus, Minus, Times_* and Divided have arrays with exactly 2
+ // elements. a + b + c + d is grouped as ((a + b) + c) + d
+ eCSSUnit_Calc_Plus = 31, // (nsCSSValue::Array*) + node within calc()
+ eCSSUnit_Calc_Minus = 32, // (nsCSSValue::Array*) - within calc
+ eCSSUnit_Calc_Times_L = 33, // (nsCSSValue::Array*) num * val within calc
+ eCSSUnit_Calc_Times_R = 34, // (nsCSSValue::Array*) val * num within calc
+ eCSSUnit_Calc_Divided = 35, // (nsCSSValue::Array*) / within calc
+
+ eCSSUnit_URL = 40, // (nsCSSValue::URL*) value
+ eCSSUnit_Image = 41, // (nsCSSValue::Image*) value
+ eCSSUnit_Gradient = 42, // (nsCSSValueGradient*) value
+ eCSSUnit_TokenStream = 43, // (nsCSSValueTokenStream*) value
+ eCSSUnit_GridTemplateAreas = 44, // (GridTemplateAreasValue*)
+ // for grid-template-areas
+
+ eCSSUnit_Pair = 50, // (nsCSSValuePair*) pair of values
+ eCSSUnit_Triplet = 51, // (nsCSSValueTriplet*) triplet of values
+ eCSSUnit_Rect = 52, // (nsCSSRect*) rectangle (four values)
+ eCSSUnit_List = 53, // (nsCSSValueList*) list of values
+ eCSSUnit_ListDep = 54, // (nsCSSValueList*) same as List
+ // but does not own the list
+ eCSSUnit_SharedList = 55, // (nsCSSValueSharedList*) same as list
+ // but reference counted and shared
+ eCSSUnit_PairList = 56, // (nsCSSValuePairList*) list of value pairs
+ eCSSUnit_PairListDep = 57, // (nsCSSValuePairList*) same as PairList
+ // but does not own the list
+
+ eCSSUnit_FontFamilyList = 58, // (FontFamilyList*) value
+
+ eCSSUnit_Integer = 70, // (int) simple value
+ eCSSUnit_Enumerated = 71, // (int) value has enumerated meaning
+
+ eCSSUnit_EnumColor = 80, // (int) enumerated color (kColorKTable)
+ eCSSUnit_RGBColor = 81, // (nscolor) an opaque RGBA value specified as rgb()
+ eCSSUnit_RGBAColor = 82, // (nscolor) an RGBA value specified as rgba()
+ eCSSUnit_HexColor = 83, // (nscolor) an opaque RGBA value specified as #rrggbb
+ eCSSUnit_ShortHexColor = 84, // (nscolor) an opaque RGBA value specified as #rgb
+ eCSSUnit_HexColorAlpha = 85, // (nscolor) an opaque RGBA value specified as #rrggbbaa
+ eCSSUnit_ShortHexColorAlpha = 86, // (nscolor) an opaque RGBA value specified as #rgba
+ eCSSUnit_PercentageRGBColor = 87, // (nsCSSValueFloatColor*) an opaque
+ // RGBA value specified as rgb() with
+ // percentage components. Values over
+ // 100% are allowed.
+ eCSSUnit_PercentageRGBAColor = 88, // (nsCSSValueFloatColor*) an RGBA value
+ // specified as rgba() with percentage
+ // components. Values over 100% are
+ // allowed.
+ eCSSUnit_HSLColor = 89, // (nsCSSValueFloatColor*)
+ eCSSUnit_HSLAColor = 90, // (nsCSSValueFloatColor*)
+ eCSSUnit_ComplexColor = 91, // (ComplexColorValue*)
+
+ eCSSUnit_Percent = 100, // (float) 1.0 == 100%) value is percentage of something
+ eCSSUnit_Number = 101, // (float) value is numeric (usually multiplier, different behavior than percent)
+
+ // Physical length units
+ eCSSUnit_PhysicalMillimeter = 200, // (float) 1/25.4 inch
+
+ // Length units - relative
+ // Viewport relative measure
+ eCSSUnit_ViewportWidth = 700, // (float) 1% of the width of the initial containing block
+ eCSSUnit_ViewportHeight = 701, // (float) 1% of the height of the initial containing block
+ eCSSUnit_ViewportMin = 702, // (float) smaller of ViewportWidth and ViewportHeight
+ eCSSUnit_ViewportMax = 703, // (float) larger of ViewportWidth and ViewportHeight
+
+ // Font relative measure
+ eCSSUnit_EM = 800, // (float) == current font size
+ eCSSUnit_XHeight = 801, // (float) distance from top of lower case x to baseline
+ eCSSUnit_Char = 802, // (float) number of characters, used for width with monospace font
+ eCSSUnit_RootEM = 803, // (float) == root element font size
+
+ // Screen relative measure
+ eCSSUnit_Point = 900, // (float) 4/3 of a CSS pixel
+ eCSSUnit_Inch = 901, // (float) 96 CSS pixels
+ eCSSUnit_Millimeter = 902, // (float) 96/25.4 CSS pixels
+ eCSSUnit_Centimeter = 903, // (float) 96/2.54 CSS pixels
+ eCSSUnit_Pica = 904, // (float) 12 points == 16 CSS pixls
+ eCSSUnit_Quarter = 905, // (float) 96/101.6 CSS pixels
+ eCSSUnit_Pixel = 906, // (float) CSS pixel unit
+
+ // Angular units
+ eCSSUnit_Degree = 1000, // (float) 360 per circle
+ eCSSUnit_Grad = 1001, // (float) 400 per circle
+ eCSSUnit_Radian = 1002, // (float) 2*pi per circle
+ eCSSUnit_Turn = 1003, // (float) 1 per circle
+
+ // Frequency units
+ eCSSUnit_Hertz = 2000, // (float) 1/seconds
+ eCSSUnit_Kilohertz = 2001, // (float) 1000 Hertz
+
+ // Time units
+ eCSSUnit_Seconds = 3000, // (float) Standard time
+ eCSSUnit_Milliseconds = 3001, // (float) 1/1000 second
+
+ // Flexible fraction (CSS Grid)
+ eCSSUnit_FlexFraction = 4000 // (float) Fraction of free space
+};
+
+struct nsCSSValueGradient;
+struct nsCSSValuePair;
+struct nsCSSValuePair_heap;
+struct nsCSSValueTokenStream;
+struct nsCSSRect;
+struct nsCSSRect_heap;
+struct nsCSSValueList;
+struct nsCSSValueList_heap;
+struct nsCSSValueSharedList;
+struct nsCSSValuePairList;
+struct nsCSSValuePairList_heap;
+struct nsCSSValueTriplet;
+struct nsCSSValueTriplet_heap;
+class nsCSSValueFloatColor;
+
+class nsCSSValue {
+public:
+ struct Array;
+ friend struct Array;
+
+ friend struct mozilla::css::URLValueData;
+
+ friend struct mozilla::css::ImageValue;
+
+ // for valueless units only (null, auto, inherit, none, all, normal)
+ explicit nsCSSValue(nsCSSUnit aUnit = eCSSUnit_Null)
+ : mUnit(aUnit)
+ {
+ MOZ_ASSERT(aUnit <= eCSSUnit_DummyInherit, "not a valueless unit");
+ }
+
+ nsCSSValue(int32_t aValue, nsCSSUnit aUnit);
+ nsCSSValue(float aValue, nsCSSUnit aUnit);
+ nsCSSValue(const nsString& aValue, nsCSSUnit aUnit);
+ nsCSSValue(Array* aArray, nsCSSUnit aUnit);
+ explicit nsCSSValue(mozilla::css::URLValue* aValue);
+ explicit nsCSSValue(mozilla::css::ImageValue* aValue);
+ explicit nsCSSValue(nsCSSValueGradient* aValue);
+ explicit nsCSSValue(nsCSSValueTokenStream* aValue);
+ explicit nsCSSValue(mozilla::css::GridTemplateAreasValue* aValue);
+ explicit nsCSSValue(mozilla::css::FontFamilyListRefCnt* aValue);
+ nsCSSValue(const nsCSSValue& aCopy);
+ nsCSSValue(nsCSSValue&& aOther)
+ : mUnit(aOther.mUnit)
+ , mValue(aOther.mValue)
+ {
+ aOther.mUnit = eCSSUnit_Null;
+ }
+ ~nsCSSValue() { Reset(); }
+
+ nsCSSValue& operator=(const nsCSSValue& aCopy);
+ nsCSSValue& operator=(nsCSSValue&& aCopy);
+ bool operator==(const nsCSSValue& aOther) const;
+
+ bool operator!=(const nsCSSValue& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ // Enum for AppendToString's aValueSerialization argument.
+ enum Serialization { eNormalized, eAuthorSpecified };
+
+ /**
+ * Serialize |this| as a specified value for |aProperty| and append
+ * it to |aResult|.
+ */
+ void AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ Serialization aValueSerialization) const;
+
+ nsCSSUnit GetUnit() const { return mUnit; }
+ bool IsLengthUnit() const
+ { return eCSSUnit_PhysicalMillimeter <= mUnit && mUnit <= eCSSUnit_Pixel; }
+ bool IsLengthPercentCalcUnit() const
+ { return IsLengthUnit() || mUnit == eCSSUnit_Percent || IsCalcUnit(); }
+ /**
+ * A "fixed" length unit is one that means a specific physical length
+ * which we try to match based on the physical characteristics of an
+ * output device.
+ */
+ bool IsFixedLengthUnit() const
+ { return mUnit == eCSSUnit_PhysicalMillimeter; }
+ /**
+ * What the spec calls relative length units is, for us, split
+ * between relative length units and pixel length units.
+ *
+ * A "relative" length unit is a multiple of some derived metric,
+ * such as a font em-size, which itself was controlled by an input CSS
+ * length. Relative length units should not be scaled by zooming, since
+ * the underlying CSS length would already have been scaled.
+ */
+ bool IsRelativeLengthUnit() const
+ { return eCSSUnit_EM <= mUnit && mUnit <= eCSSUnit_RootEM; }
+ /**
+ * 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; }
+ bool IsPercentLengthUnit()
+ { return IsPercentLengthUnit(mUnit); }
+ static bool IsFloatUnit(nsCSSUnit aUnit)
+ { return eCSSUnit_Number <= aUnit; }
+ bool IsAngularUnit() const
+ { return eCSSUnit_Degree <= mUnit && mUnit <= eCSSUnit_Turn; }
+ bool IsFrequencyUnit() const
+ { return eCSSUnit_Hertz <= mUnit && mUnit <= eCSSUnit_Kilohertz; }
+ bool IsTimeUnit() const
+ { return eCSSUnit_Seconds <= mUnit && mUnit <= eCSSUnit_Milliseconds; }
+ bool IsCalcUnit() const
+ { return eCSSUnit_Calc <= mUnit && mUnit <= eCSSUnit_Calc_Divided; }
+
+ bool UnitHasStringValue() const
+ { return eCSSUnit_String <= mUnit && mUnit <= eCSSUnit_Element; }
+ bool UnitHasArrayValue() const
+ { return eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Calc_Divided; }
+
+ // Checks for the nsCSSValue being of a particular type of color unit:
+ //
+ // - IsIntegerColorUnit returns true for:
+ // eCSSUnit_RGBColor -- rgb(int,int,int)
+ // eCSSUnit_RGBAColor -- rgba(int,int,int,float)
+ // eCSSUnit_HexColor -- #rrggbb
+ // eCSSUnit_ShortHexColor -- #rgb
+ // eCSSUnit_HexColorAlpha -- #rrggbbaa
+ // eCSSUnit_ShortHexColorAlpha -- #rgba
+ //
+ // - IsFloatColorUnit returns true for:
+ // eCSSUnit_PercentageRGBColor -- rgb(%,%,%)
+ // eCSSUnit_PercentageRGBAColor -- rgba(%,%,%,float)
+ // eCSSUnit_HSLColor -- hsl(float,%,%)
+ // eCSSUnit_HSLAColor -- hsla(float,%,%,float)
+ //
+ // - IsNumericColorUnit returns true for any of the above units.
+ //
+ // Note that color keywords and system colors are represented by
+ // eCSSUnit_EnumColor and eCSSUnit_Ident.
+ bool IsIntegerColorUnit() const { return IsIntegerColorUnit(mUnit); }
+ bool IsFloatColorUnit() const { return IsFloatColorUnit(mUnit); }
+ bool IsNumericColorUnit() const { return IsNumericColorUnit(mUnit); }
+ static bool IsIntegerColorUnit(nsCSSUnit aUnit)
+ { return eCSSUnit_RGBColor <= aUnit && aUnit <= eCSSUnit_ShortHexColorAlpha; }
+ static bool IsFloatColorUnit(nsCSSUnit aUnit)
+ { return eCSSUnit_PercentageRGBColor <= aUnit &&
+ aUnit <= eCSSUnit_HSLAColor; }
+ static bool IsNumericColorUnit(nsCSSUnit aUnit)
+ { return IsIntegerColorUnit(aUnit) || IsFloatColorUnit(aUnit); }
+
+ int32_t GetIntValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_Integer ||
+ mUnit == eCSSUnit_Enumerated ||
+ mUnit == eCSSUnit_EnumColor,
+ "not an int value");
+ return mValue.mInt;
+ }
+
+ nsCSSKeyword GetKeywordValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_Enumerated, "not a keyword value");
+ return static_cast<nsCSSKeyword>(mValue.mInt);
+ }
+
+ float GetPercentValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_Percent, "not a percent value");
+ return mValue.mFloat;
+ }
+
+ float GetFloatValue() const
+ {
+ MOZ_ASSERT(eCSSUnit_Number <= mUnit, "not a float value");
+ MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
+ return mValue.mFloat;
+ }
+
+ float GetAngleValue() const
+ {
+ MOZ_ASSERT(eCSSUnit_Degree <= mUnit && mUnit <= eCSSUnit_Turn,
+ "not an angle value");
+ return mValue.mFloat;
+ }
+
+ // Converts any angle to radians.
+ double GetAngleValueInRadians() const;
+
+ // Converts any angle to degrees.
+ double GetAngleValueInDegrees() const;
+
+ nsAString& GetStringValue(nsAString& aBuffer) const
+ {
+ MOZ_ASSERT(UnitHasStringValue(), "not a string value");
+ aBuffer.Truncate();
+ uint32_t len = NS_strlen(GetBufferValue(mValue.mString));
+ mValue.mString->ToString(len, aBuffer);
+ return aBuffer;
+ }
+
+ const char16_t* GetStringBufferValue() const
+ {
+ MOZ_ASSERT(UnitHasStringValue(), "not a string value");
+ return GetBufferValue(mValue.mString);
+ }
+
+ nscolor GetColorValue() const;
+ bool IsNonTransparentColor() const;
+ mozilla::StyleComplexColor GetStyleComplexColorValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_ComplexColor);
+ return mValue.mComplexColor->ToComplexColor();
+ }
+
+ Array* GetArrayValue() const
+ {
+ MOZ_ASSERT(UnitHasArrayValue(), "not an array value");
+ return mValue.mArray;
+ }
+
+ nsIURI* GetURLValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_URL || mUnit == eCSSUnit_Image,
+ "not a URL value");
+ return mUnit == eCSSUnit_URL ?
+ mValue.mURL->GetURI() : mValue.mImage->GetURI();
+ }
+
+ nsCSSValueGradient* GetGradientValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_Gradient, "not a gradient value");
+ return mValue.mGradient;
+ }
+
+ nsCSSValueTokenStream* GetTokenStreamValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_TokenStream, "not a token stream value");
+ return mValue.mTokenStream;
+ }
+
+ nsCSSValueSharedList* GetSharedListValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_SharedList, "not a shared list value");
+ return mValue.mSharedList;
+ }
+
+ mozilla::FontFamilyList* GetFontFamilyListValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_FontFamilyList,
+ "not a font family list value");
+ NS_ASSERTION(mValue.mFontFamilyList != nullptr,
+ "font family list value should never be null");
+ return mValue.mFontFamilyList;
+ }
+
+ // bodies of these are below
+ inline nsCSSValuePair& GetPairValue();
+ inline const nsCSSValuePair& GetPairValue() const;
+
+ inline nsCSSRect& GetRectValue();
+ inline const nsCSSRect& GetRectValue() const;
+
+ inline nsCSSValueList* GetListValue();
+ inline const nsCSSValueList* GetListValue() const;
+
+ inline nsCSSValuePairList* GetPairListValue();
+ inline const nsCSSValuePairList* GetPairListValue() const;
+
+ inline nsCSSValueTriplet& GetTripletValue();
+ inline const nsCSSValueTriplet& GetTripletValue() const;
+
+
+ mozilla::css::URLValue* GetURLStructValue() const
+ {
+ // Not allowing this for Image values, because if the caller takes
+ // a ref to them they won't be able to delete them properly.
+ MOZ_ASSERT(mUnit == eCSSUnit_URL, "not a URL value");
+ return mValue.mURL;
+ }
+
+ mozilla::css::ImageValue* GetImageStructValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_Image, "not an Image value");
+ return mValue.mImage;
+ }
+
+ mozilla::css::GridTemplateAreasValue* GetGridTemplateAreas() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_GridTemplateAreas,
+ "not a grid-template-areas value");
+ return mValue.mGridTemplateAreas;
+ }
+
+ const char16_t* GetOriginalURLValue() const
+ {
+ MOZ_ASSERT(mUnit == eCSSUnit_URL || mUnit == eCSSUnit_Image,
+ "not a URL value");
+ return GetBufferValue(mUnit == eCSSUnit_URL ?
+ mValue.mURL->mString :
+ mValue.mImage->mString);
+ }
+
+ // Not making this inline because that would force us to include
+ // imgIRequest.h, which leads to REQUIRES hell, since this header is included
+ // all over.
+ imgRequestProxy* GetImageValue(nsIDocument* aDocument) const;
+
+ // Like GetImageValue, but additionally will pass the imgRequestProxy
+ // through nsContentUtils::GetStaticRequest if aPresContent is static.
+ already_AddRefed<imgRequestProxy> GetPossiblyStaticImageValue(
+ nsIDocument* aDocument, nsPresContext* aPresContext) const;
+
+ nscoord GetFixedLength(nsPresContext* aPresContext) const;
+ nscoord GetPixelLength() const;
+
+ nsCSSValueFloatColor* GetFloatColorValue() const
+ {
+ MOZ_ASSERT(IsFloatColorUnit(), "not a float color value");
+ return mValue.mFloatColor;
+ }
+
+ void Reset() // sets to null
+ {
+ if (mUnit != eCSSUnit_Null)
+ DoReset();
+ }
+private:
+ void DoReset();
+
+public:
+ void SetIntValue(int32_t aValue, nsCSSUnit aUnit);
+ template<typename T,
+ typename = typename std::enable_if<std::is_enum<T>::value>::type>
+ void SetIntValue(T aValue, nsCSSUnit aUnit)
+ {
+ static_assert(mozilla::EnumTypeFitsWithin<T, int32_t>::value,
+ "aValue must be an enum that fits within mValue.mInt");
+ SetIntValue(static_cast<int32_t>(aValue), aUnit);
+ }
+ void SetPercentValue(float aValue);
+ void SetFloatValue(float aValue, nsCSSUnit aUnit);
+ void SetStringValue(const nsString& aValue, nsCSSUnit aUnit);
+ void SetColorValue(nscolor aValue);
+ void SetIntegerColorValue(nscolor aValue, nsCSSUnit aUnit);
+ // converts the nscoord to pixels
+ void SetIntegerCoordValue(nscoord aCoord);
+ void SetFloatColorValue(float aComponent1,
+ float aComponent2,
+ float aComponent3,
+ float aAlpha, nsCSSUnit aUnit);
+ void SetRGBAColorValue(const mozilla::css::RGBAColorData& aValue);
+ void SetComplexColorValue(
+ already_AddRefed<mozilla::css::ComplexColorValue> aValue);
+ void SetArrayValue(nsCSSValue::Array* aArray, nsCSSUnit aUnit);
+ void SetURLValue(mozilla::css::URLValue* aURI);
+ void SetImageValue(mozilla::css::ImageValue* aImage);
+ void SetGradientValue(nsCSSValueGradient* aGradient);
+ void SetTokenStreamValue(nsCSSValueTokenStream* aTokenStream);
+ void SetGridTemplateAreas(mozilla::css::GridTemplateAreasValue* aValue);
+ void SetFontFamilyListValue(mozilla::css::FontFamilyListRefCnt* aFontListValue);
+ void SetPairValue(const nsCSSValuePair* aPair);
+ void SetPairValue(const nsCSSValue& xValue, const nsCSSValue& yValue);
+ void SetSharedListValue(nsCSSValueSharedList* aList);
+ void SetDependentListValue(nsCSSValueList* aList);
+ void SetDependentPairListValue(nsCSSValuePairList* aList);
+ void SetTripletValue(const nsCSSValueTriplet* aTriplet);
+ void SetTripletValue(const nsCSSValue& xValue, const nsCSSValue& yValue, const nsCSSValue& zValue);
+ void SetAutoValue();
+ void SetInheritValue();
+ void SetInitialValue();
+ void SetUnsetValue();
+ void SetNoneValue();
+ void SetAllValue();
+ void SetNormalValue();
+ void SetSystemFontValue();
+ void SetDummyValue();
+ void SetDummyInheritValue();
+
+ // Converts an nsStyleCoord::CalcValue back into a CSSValue
+ void SetCalcValue(const nsStyleCoord::CalcValue* aCalc);
+
+ // These are a little different - they allocate storage for you and
+ // return a handle.
+ nsCSSRect& SetRectValue();
+ nsCSSValueList* SetListValue();
+ nsCSSValuePairList* SetPairListValue();
+
+ // These take ownership of the passed-in resource.
+ void AdoptListValue(mozilla::UniquePtr<nsCSSValueList> aValue);
+ void AdoptPairListValue(mozilla::UniquePtr<nsCSSValuePairList> aValue);
+
+ void StartImageLoad(nsIDocument* aDocument) const; // Only pretend const
+
+ // Initializes as a function value with the specified function id.
+ Array* InitFunction(nsCSSKeyword aFunctionId, uint32_t aNumArgs);
+ // Checks if this is a function value with the specified function id.
+ bool EqualsFunction(nsCSSKeyword aFunctionId) const;
+
+ // Returns an already addrefed buffer. Guaranteed to return non-null.
+ // (Will abort on allocation failure.)
+ static already_AddRefed<nsStringBuffer>
+ BufferFromString(const nsString& aValue);
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ static void
+ AppendSidesShorthandToString(const nsCSSPropertyID aProperties[],
+ const nsCSSValue* aValues[],
+ nsAString& aString,
+ Serialization aSerialization);
+ static void
+ AppendBasicShapeRadiusToString(const nsCSSPropertyID aProperties[],
+ const nsCSSValue* aValues[],
+ nsAString& aResult,
+ Serialization aValueSerialization);
+ static void
+ AppendAlignJustifyValueToString(int32_t aValue, nsAString& aResult);
+
+private:
+ static const char16_t* GetBufferValue(nsStringBuffer* aBuffer) {
+ return static_cast<char16_t*>(aBuffer->Data());
+ }
+
+ void AppendPolygonToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ Serialization aValueSerialization) const;
+ void AppendPositionCoordinateToString(const nsCSSValue& aValue,
+ nsCSSPropertyID aProperty,
+ nsAString& aResult,
+ Serialization aSerialization) const;
+ void AppendCircleOrEllipseToString(
+ nsCSSKeyword aFunctionId,
+ nsCSSPropertyID aProperty, nsAString& aResult,
+ Serialization aValueSerialization) const;
+ void AppendBasicShapePositionToString(
+ nsAString& aResult,
+ Serialization aValueSerialization) const;
+ void AppendInsetToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ Serialization aValueSerialization) const;
+protected:
+ nsCSSUnit mUnit;
+ union {
+ int32_t mInt;
+ float mFloat;
+ // Note: the capacity of the buffer may exceed the length of the string.
+ // If we're of a string type, mString is not null.
+ nsStringBuffer* MOZ_OWNING_REF mString;
+ nscolor mColor;
+ Array* MOZ_OWNING_REF mArray;
+ mozilla::css::URLValue* MOZ_OWNING_REF mURL;
+ mozilla::css::ImageValue* MOZ_OWNING_REF mImage;
+ mozilla::css::GridTemplateAreasValue* MOZ_OWNING_REF mGridTemplateAreas;
+ nsCSSValueGradient* MOZ_OWNING_REF mGradient;
+ nsCSSValueTokenStream* MOZ_OWNING_REF mTokenStream;
+ nsCSSValuePair_heap* MOZ_OWNING_REF mPair;
+ nsCSSRect_heap* MOZ_OWNING_REF mRect;
+ nsCSSValueTriplet_heap* MOZ_OWNING_REF mTriplet;
+ nsCSSValueList_heap* MOZ_OWNING_REF mList;
+ nsCSSValueList* mListDependent;
+ nsCSSValueSharedList* MOZ_OWNING_REF mSharedList;
+ nsCSSValuePairList_heap* MOZ_OWNING_REF mPairList;
+ nsCSSValuePairList* mPairListDependent;
+ nsCSSValueFloatColor* MOZ_OWNING_REF mFloatColor;
+ mozilla::css::FontFamilyListRefCnt* MOZ_OWNING_REF mFontFamilyList;
+ mozilla::css::ComplexColorValue* MOZ_OWNING_REF mComplexColor;
+ } mValue;
+};
+
+struct nsCSSValue::Array final {
+
+ // return |Array| with reference count of zero
+ static Array* Create(size_t aItemCount) {
+ return new (aItemCount) Array(aItemCount);
+ }
+
+ nsCSSValue& operator[](size_t aIndex) {
+ MOZ_ASSERT(aIndex < mCount, "out of range");
+ return mArray[aIndex];
+ }
+
+ const nsCSSValue& operator[](size_t aIndex) const {
+ MOZ_ASSERT(aIndex < mCount, "out of range");
+ return mArray[aIndex];
+ }
+
+ nsCSSValue& Item(size_t aIndex) { return (*this)[aIndex]; }
+ const nsCSSValue& Item(size_t aIndex) const { return (*this)[aIndex]; }
+
+ size_t Count() const { return mCount; }
+
+ // callers depend on the items being contiguous
+ nsCSSValue* ItemStorage() {
+ return this->First();
+ }
+
+ bool operator==(const Array& aOther) const
+ {
+ if (mCount != aOther.mCount)
+ return false;
+ for (size_t i = 0; i < mCount; ++i)
+ if ((*this)[i] != aOther[i])
+ return false;
+ return true;
+ }
+
+ // XXXdholbert This uses a size_t ref count. Should we use a variant
+ // of NS_INLINE_DECL_REFCOUNTING that takes a type as an argument?
+ void AddRef() {
+ if (mRefCnt == size_t(-1)) { // really want SIZE_MAX
+ NS_WARNING("refcount overflow, leaking nsCSSValue::Array");
+ return;
+ }
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "nsCSSValue::Array", sizeof(*this));
+ }
+ void Release() {
+ if (mRefCnt == size_t(-1)) { // really want SIZE_MAX
+ NS_WARNING("refcount overflow, leaking nsCSSValue::Array");
+ return;
+ }
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "nsCSSValue::Array");
+ if (mRefCnt == 0)
+ delete this;
+ }
+
+private:
+
+ size_t mRefCnt;
+ const size_t mCount;
+ // This must be the last sub-object, since we extend this array to
+ // be of size mCount; it needs to be a sub-object so it gets proper
+ // alignment.
+ nsCSSValue mArray[1];
+
+ void* operator new(size_t aSelfSize, size_t aItemCount) CPP_THROW_NEW {
+ MOZ_ASSERT(aItemCount > 0, "cannot have a 0 item count");
+ return ::operator new(aSelfSize + sizeof(nsCSSValue) * (aItemCount - 1));
+ }
+
+ void operator delete(void* aPtr) { ::operator delete(aPtr); }
+
+ nsCSSValue* First() { return mArray; }
+
+ const nsCSSValue* First() const { return mArray; }
+
+#define CSSVALUE_LIST_FOR_EXTRA_VALUES(var) \
+ for (nsCSSValue *var = First() + 1, *var##_end = First() + mCount; \
+ var != var##_end; ++var)
+
+ explicit Array(size_t aItemCount)
+ : mRefCnt(0)
+ , mCount(aItemCount)
+ {
+ MOZ_COUNT_CTOR(nsCSSValue::Array);
+ CSSVALUE_LIST_FOR_EXTRA_VALUES(val) {
+ new (val) nsCSSValue();
+ }
+ }
+
+ ~Array()
+ {
+ MOZ_COUNT_DTOR(nsCSSValue::Array);
+ CSSVALUE_LIST_FOR_EXTRA_VALUES(val) {
+ val->~nsCSSValue();
+ }
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+#undef CSSVALUE_LIST_FOR_EXTRA_VALUES
+
+private:
+ Array(const Array& aOther) = delete;
+ Array& operator=(const Array& aOther) = delete;
+};
+
+// Prefer nsCSSValue::Array for lists of fixed size.
+struct nsCSSValueList {
+ nsCSSValueList() : mNext(nullptr) { MOZ_COUNT_CTOR(nsCSSValueList); }
+ ~nsCSSValueList();
+
+ nsCSSValueList* Clone() const; // makes a deep copy. Infallible.
+ void CloneInto(nsCSSValueList* aList) const; // makes a deep copy into aList
+ void AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aValueSerialization) const;
+
+ static bool Equal(const nsCSSValueList* aList1,
+ const nsCSSValueList* aList2);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ nsCSSValue mValue;
+ nsCSSValueList* mNext;
+
+private:
+ nsCSSValueList(const nsCSSValueList& aCopy) // makes a shallow copy
+ : mValue(aCopy.mValue), mNext(nullptr)
+ {
+ MOZ_COUNT_CTOR(nsCSSValueList);
+ }
+
+ // We don't want operator== or operator!= because they wouldn't be
+ // null-safe, which is generally what we need. Use |Equal| method
+ // above instead.
+ bool operator==(nsCSSValueList const& aOther) const = delete;
+ bool operator!=(const nsCSSValueList& aOther) const = delete;
+};
+
+// nsCSSValueList_heap differs from nsCSSValueList only in being
+// refcounted. It should not be necessary to use this class directly;
+// it's an implementation detail of nsCSSValue.
+struct nsCSSValueList_heap final : public nsCSSValueList {
+ NS_INLINE_DECL_REFCOUNTING(nsCSSValueList_heap)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSValueList_heap()
+ {
+ }
+};
+
+// This is a reference counted list value. Note that the object is
+// a wrapper for the reference count and a pointer to the head of the
+// list, whereas the other list types (such as nsCSSValueList) do
+// not have such a wrapper.
+struct nsCSSValueSharedList final {
+ nsCSSValueSharedList()
+ : mHead(nullptr)
+ {
+ MOZ_COUNT_CTOR(nsCSSValueSharedList);
+ }
+
+ // Takes ownership of aList.
+ explicit nsCSSValueSharedList(nsCSSValueList* aList)
+ : mHead(aList)
+ {
+ MOZ_COUNT_CTOR(nsCSSValueSharedList);
+ }
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSValueSharedList();
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCSSValueSharedList)
+
+ void AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aValueSerialization) const;
+
+ bool operator==(nsCSSValueSharedList const& aOther) const;
+ bool operator!=(const nsCSSValueSharedList& aOther) const
+ { return !(*this == aOther); }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ nsCSSValueList* mHead;
+};
+
+// This has to be here so that the relationship between nsCSSValueList
+// and nsCSSValueList_heap is visible.
+inline nsCSSValueList*
+nsCSSValue::GetListValue()
+{
+ if (mUnit == eCSSUnit_List)
+ return mValue.mList;
+ else {
+ MOZ_ASSERT(mUnit == eCSSUnit_ListDep, "not a list value");
+ return mValue.mListDependent;
+ }
+}
+
+inline const nsCSSValueList*
+nsCSSValue::GetListValue() const
+{
+ if (mUnit == eCSSUnit_List)
+ return mValue.mList;
+ else {
+ MOZ_ASSERT(mUnit == eCSSUnit_ListDep, "not a list value");
+ return mValue.mListDependent;
+ }
+}
+
+struct nsCSSRect {
+ nsCSSRect(void);
+ nsCSSRect(const nsCSSRect& aCopy);
+ ~nsCSSRect();
+
+ void AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aValueSerialization) const;
+
+ bool operator==(const nsCSSRect& aOther) const {
+ return mTop == aOther.mTop &&
+ mRight == aOther.mRight &&
+ mBottom == aOther.mBottom &&
+ mLeft == aOther.mLeft;
+ }
+
+ bool operator!=(const nsCSSRect& aOther) const {
+ return mTop != aOther.mTop ||
+ mRight != aOther.mRight ||
+ mBottom != aOther.mBottom ||
+ mLeft != aOther.mLeft;
+ }
+
+ void SetAllSidesTo(const nsCSSValue& aValue);
+
+ bool AllSidesEqualTo(const nsCSSValue& aValue) const {
+ return mTop == aValue &&
+ mRight == aValue &&
+ mBottom == aValue &&
+ mLeft == aValue;
+ }
+
+ void Reset() {
+ mTop.Reset();
+ mRight.Reset();
+ mBottom.Reset();
+ mLeft.Reset();
+ }
+
+ bool HasValue() const {
+ return
+ mTop.GetUnit() != eCSSUnit_Null ||
+ mRight.GetUnit() != eCSSUnit_Null ||
+ mBottom.GetUnit() != eCSSUnit_Null ||
+ mLeft.GetUnit() != eCSSUnit_Null;
+ }
+
+ nsCSSValue mTop;
+ nsCSSValue mRight;
+ nsCSSValue mBottom;
+ nsCSSValue mLeft;
+
+ typedef nsCSSValue nsCSSRect::*side_type;
+ static const side_type sides[4];
+};
+
+// nsCSSRect_heap differs from nsCSSRect only in being
+// refcounted. It should not be necessary to use this class directly;
+// it's an implementation detail of nsCSSValue.
+struct nsCSSRect_heap final : public nsCSSRect {
+ NS_INLINE_DECL_REFCOUNTING(nsCSSRect_heap)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSRect_heap()
+ {
+ }
+};
+
+// This has to be here so that the relationship between nsCSSRect
+// and nsCSSRect_heap is visible.
+inline nsCSSRect&
+nsCSSValue::GetRectValue()
+{
+ MOZ_ASSERT(mUnit == eCSSUnit_Rect, "not a rect value");
+ return *mValue.mRect;
+}
+
+inline const nsCSSRect&
+nsCSSValue::GetRectValue() const
+{
+ MOZ_ASSERT(mUnit == eCSSUnit_Rect, "not a rect value");
+ return *mValue.mRect;
+}
+
+struct nsCSSValuePair {
+ nsCSSValuePair()
+ {
+ MOZ_COUNT_CTOR(nsCSSValuePair);
+ }
+ explicit nsCSSValuePair(nsCSSUnit aUnit)
+ : mXValue(aUnit), mYValue(aUnit)
+ {
+ MOZ_COUNT_CTOR(nsCSSValuePair);
+ }
+ nsCSSValuePair(const nsCSSValue& aXValue, const nsCSSValue& aYValue)
+ : mXValue(aXValue), mYValue(aYValue)
+ {
+ MOZ_COUNT_CTOR(nsCSSValuePair);
+ }
+ nsCSSValuePair(const nsCSSValuePair& aCopy)
+ : mXValue(aCopy.mXValue), mYValue(aCopy.mYValue)
+ {
+ MOZ_COUNT_CTOR(nsCSSValuePair);
+ }
+ ~nsCSSValuePair()
+ {
+ MOZ_COUNT_DTOR(nsCSSValuePair);
+ }
+
+ nsCSSValuePair& operator=(const nsCSSValuePair& aOther) {
+ mXValue = aOther.mXValue;
+ mYValue = aOther.mYValue;
+ return *this;
+ }
+
+ bool operator==(const nsCSSValuePair& aOther) const {
+ return mXValue == aOther.mXValue &&
+ mYValue == aOther.mYValue;
+ }
+
+ bool operator!=(const nsCSSValuePair& aOther) const {
+ return mXValue != aOther.mXValue ||
+ mYValue != aOther.mYValue;
+ }
+
+ bool BothValuesEqualTo(const nsCSSValue& aValue) const {
+ return mXValue == aValue &&
+ mYValue == aValue;
+ }
+
+ void SetBothValuesTo(const nsCSSValue& aValue) {
+ mXValue = aValue;
+ mYValue = aValue;
+ }
+
+ void Reset() {
+ mXValue.Reset();
+ mYValue.Reset();
+ }
+
+ bool HasValue() const {
+ return mXValue.GetUnit() != eCSSUnit_Null ||
+ mYValue.GetUnit() != eCSSUnit_Null;
+ }
+
+ void AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aValueSerialization) const;
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ nsCSSValue mXValue;
+ nsCSSValue mYValue;
+};
+
+// nsCSSValuePair_heap differs from nsCSSValuePair only in being
+// refcounted. It should not be necessary to use this class directly;
+// it's an implementation detail of nsCSSValue.
+struct nsCSSValuePair_heap final : public nsCSSValuePair {
+ // forward constructor
+ nsCSSValuePair_heap(const nsCSSValue& aXValue, const nsCSSValue& aYValue)
+ : nsCSSValuePair(aXValue, aYValue)
+ {}
+
+ NS_INLINE_DECL_REFCOUNTING(nsCSSValuePair_heap)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSValuePair_heap()
+ {
+ }
+};
+
+struct nsCSSValueTriplet {
+ nsCSSValueTriplet()
+ {
+ MOZ_COUNT_CTOR(nsCSSValueTriplet);
+ }
+ explicit nsCSSValueTriplet(nsCSSUnit aUnit)
+ : mXValue(aUnit), mYValue(aUnit), mZValue(aUnit)
+ {
+ MOZ_COUNT_CTOR(nsCSSValueTriplet);
+ }
+ nsCSSValueTriplet(const nsCSSValue& aXValue,
+ const nsCSSValue& aYValue,
+ const nsCSSValue& aZValue)
+ : mXValue(aXValue), mYValue(aYValue), mZValue(aZValue)
+ {
+ MOZ_COUNT_CTOR(nsCSSValueTriplet);
+ }
+ nsCSSValueTriplet(const nsCSSValueTriplet& aCopy)
+ : mXValue(aCopy.mXValue), mYValue(aCopy.mYValue), mZValue(aCopy.mZValue)
+ {
+ MOZ_COUNT_CTOR(nsCSSValueTriplet);
+ }
+ ~nsCSSValueTriplet()
+ {
+ MOZ_COUNT_DTOR(nsCSSValueTriplet);
+ }
+
+ bool operator==(const nsCSSValueTriplet& aOther) const {
+ return mXValue == aOther.mXValue &&
+ mYValue == aOther.mYValue &&
+ mZValue == aOther.mZValue;
+ }
+
+ bool operator!=(const nsCSSValueTriplet& aOther) const {
+ return mXValue != aOther.mXValue ||
+ mYValue != aOther.mYValue ||
+ mZValue != aOther.mZValue;
+ }
+
+ bool AllValuesEqualTo(const nsCSSValue& aValue) const {
+ return mXValue == aValue &&
+ mYValue == aValue &&
+ mZValue == aValue;
+ }
+
+ void SetAllValuesTo(const nsCSSValue& aValue) {
+ mXValue = aValue;
+ mYValue = aValue;
+ mZValue = aValue;
+ }
+
+ void Reset() {
+ mXValue.Reset();
+ mYValue.Reset();
+ mZValue.Reset();
+ }
+
+ bool HasValue() const {
+ return mXValue.GetUnit() != eCSSUnit_Null ||
+ mYValue.GetUnit() != eCSSUnit_Null ||
+ mZValue.GetUnit() != eCSSUnit_Null;
+ }
+
+ void AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aValueSerialization) const;
+
+ nsCSSValue mXValue;
+ nsCSSValue mYValue;
+ nsCSSValue mZValue;
+};
+
+// nsCSSValueTriplet_heap differs from nsCSSValueTriplet only in being
+// refcounted. It should not be necessary to use this class directly;
+// it's an implementation detail of nsCSSValue.
+struct nsCSSValueTriplet_heap final : public nsCSSValueTriplet {
+ // forward constructor
+ nsCSSValueTriplet_heap(const nsCSSValue& aXValue, const nsCSSValue& aYValue, const nsCSSValue& aZValue)
+ : nsCSSValueTriplet(aXValue, aYValue, aZValue)
+ {}
+
+ NS_INLINE_DECL_REFCOUNTING(nsCSSValueTriplet_heap)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSValueTriplet_heap()
+ {
+ }
+};
+
+// This has to be here so that the relationship between nsCSSValuePair
+// and nsCSSValuePair_heap is visible.
+inline nsCSSValuePair&
+nsCSSValue::GetPairValue()
+{
+ MOZ_ASSERT(mUnit == eCSSUnit_Pair, "not a pair value");
+ return *mValue.mPair;
+}
+
+inline const nsCSSValuePair&
+nsCSSValue::GetPairValue() const
+{
+ MOZ_ASSERT(mUnit == eCSSUnit_Pair, "not a pair value");
+ return *mValue.mPair;
+}
+
+inline nsCSSValueTriplet&
+nsCSSValue::GetTripletValue()
+{
+ MOZ_ASSERT(mUnit == eCSSUnit_Triplet, "not a triplet value");
+ return *mValue.mTriplet;
+}
+
+inline const nsCSSValueTriplet&
+nsCSSValue::GetTripletValue() const
+{
+ MOZ_ASSERT(mUnit == eCSSUnit_Triplet, "not a triplet value");
+ return *mValue.mTriplet;
+}
+
+// Maybe should be replaced with nsCSSValueList and nsCSSValue::Array?
+struct nsCSSValuePairList {
+ nsCSSValuePairList() : mNext(nullptr) { MOZ_COUNT_CTOR(nsCSSValuePairList); }
+ ~nsCSSValuePairList();
+
+ nsCSSValuePairList* Clone() const; // makes a deep copy. Infallible.
+ void AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
+ nsCSSValue::Serialization aValueSerialization) const;
+
+ static bool Equal(const nsCSSValuePairList* aList1,
+ const nsCSSValuePairList* aList2);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ nsCSSValue mXValue;
+ nsCSSValue mYValue;
+ nsCSSValuePairList* mNext;
+
+private:
+ nsCSSValuePairList(const nsCSSValuePairList& aCopy) // makes a shallow copy
+ : mXValue(aCopy.mXValue), mYValue(aCopy.mYValue), mNext(nullptr)
+ {
+ MOZ_COUNT_CTOR(nsCSSValuePairList);
+ }
+
+ // We don't want operator== or operator!= because they wouldn't be
+ // null-safe, which is generally what we need. Use |Equal| method
+ // above instead.
+ bool operator==(const nsCSSValuePairList& aOther) const = delete;
+ bool operator!=(const nsCSSValuePairList& aOther) const = delete;
+};
+
+// nsCSSValuePairList_heap differs from nsCSSValuePairList only in being
+// refcounted. It should not be necessary to use this class directly;
+// it's an implementation detail of nsCSSValue.
+struct nsCSSValuePairList_heap final : public nsCSSValuePairList {
+ NS_INLINE_DECL_REFCOUNTING(nsCSSValuePairList_heap)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSValuePairList_heap()
+ {
+ }
+};
+
+// This has to be here so that the relationship between nsCSSValuePairList
+// and nsCSSValuePairList_heap is visible.
+inline nsCSSValuePairList*
+nsCSSValue::GetPairListValue()
+{
+ if (mUnit == eCSSUnit_PairList)
+ return mValue.mPairList;
+ else {
+ MOZ_ASSERT (mUnit == eCSSUnit_PairListDep, "not a pairlist value");
+ return mValue.mPairListDependent;
+ }
+}
+
+inline const nsCSSValuePairList*
+nsCSSValue::GetPairListValue() const
+{
+ if (mUnit == eCSSUnit_PairList)
+ return mValue.mPairList;
+ else {
+ MOZ_ASSERT (mUnit == eCSSUnit_PairListDep, "not a pairlist value");
+ return mValue.mPairListDependent;
+ }
+}
+
+struct nsCSSValueGradientStop {
+public:
+ nsCSSValueGradientStop();
+ // needed to keep bloat logs happy when we use the TArray
+ // in nsCSSValueGradient
+ nsCSSValueGradientStop(const nsCSSValueGradientStop& aOther);
+ ~nsCSSValueGradientStop();
+
+ nsCSSValue mLocation;
+ nsCSSValue mColor;
+ // If mIsInterpolationHint is true, there is no color, just
+ // a location.
+ bool mIsInterpolationHint;
+
+ bool operator==(const nsCSSValueGradientStop& aOther) const
+ {
+ return (mLocation == aOther.mLocation &&
+ mIsInterpolationHint == aOther.mIsInterpolationHint &&
+ (mIsInterpolationHint || mColor == aOther.mColor));
+ }
+
+ bool operator!=(const nsCSSValueGradientStop& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+};
+
+struct nsCSSValueGradient final {
+ nsCSSValueGradient(bool aIsRadial, bool aIsRepeating);
+
+ // true if gradient is radial, false if it is linear
+ bool mIsRadial;
+ bool mIsRepeating;
+ bool mIsLegacySyntax;
+ bool mIsExplicitSize;
+ // line position and angle
+ nsCSSValuePair mBgPos;
+ nsCSSValue mAngle;
+
+ // Only meaningful if mIsRadial is true
+private:
+ nsCSSValue mRadialValues[2];
+public:
+ nsCSSValue& GetRadialShape()
+ {
+ MOZ_ASSERT(!mIsExplicitSize);
+ return mRadialValues[0];
+ }
+ const nsCSSValue& GetRadialShape() const
+ {
+ MOZ_ASSERT(!mIsExplicitSize);
+ return mRadialValues[0];
+ }
+ nsCSSValue& GetRadialSize()
+ {
+ MOZ_ASSERT(!mIsExplicitSize);
+ return mRadialValues[1];
+ }
+ const nsCSSValue& GetRadialSize() const
+ {
+ MOZ_ASSERT(!mIsExplicitSize);
+ return mRadialValues[1];
+ }
+ nsCSSValue& GetRadiusX()
+ {
+ MOZ_ASSERT(mIsExplicitSize);
+ return mRadialValues[0];
+ }
+ const nsCSSValue& GetRadiusX() const
+ {
+ MOZ_ASSERT(mIsExplicitSize);
+ return mRadialValues[0];
+ }
+ nsCSSValue& GetRadiusY()
+ {
+ MOZ_ASSERT(mIsExplicitSize);
+ return mRadialValues[1];
+ }
+ const nsCSSValue& GetRadiusY() const
+ {
+ MOZ_ASSERT(mIsExplicitSize);
+ return mRadialValues[1];
+ }
+
+ InfallibleTArray<nsCSSValueGradientStop> mStops;
+
+ bool operator==(const nsCSSValueGradient& aOther) const
+ {
+ if (mIsRadial != aOther.mIsRadial ||
+ mIsRepeating != aOther.mIsRepeating ||
+ mIsLegacySyntax != aOther.mIsLegacySyntax ||
+ mIsExplicitSize != aOther.mIsExplicitSize ||
+ mBgPos != aOther.mBgPos ||
+ mAngle != aOther.mAngle ||
+ mRadialValues[0] != aOther.mRadialValues[0] ||
+ mRadialValues[1] != aOther.mRadialValues[1])
+ return false;
+
+ if (mStops.Length() != aOther.mStops.Length())
+ return false;
+
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ if (mStops[i] != aOther.mStops[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ bool operator!=(const nsCSSValueGradient& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(nsCSSValueGradient)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSValueGradient()
+ {
+ }
+
+ nsCSSValueGradient(const nsCSSValueGradient& aOther) = delete;
+ nsCSSValueGradient& operator=(const nsCSSValueGradient& aOther) = delete;
+};
+
+// A string value used primarily to represent variable references.
+//
+// Animation code, specifically the KeyframeUtils class, also uses this
+// type as a container for various string values including:
+//
+// * Shorthand property values
+// * Shorthand sentinel values used for testing failure conditions
+// * Invalid longhand property values
+//
+// For the most part, the above values are not passed to functions that
+// manipulate nsCSSValue objects in a generic fashion. Instead KeyframeUtils
+// extracts the string from the nsCSSValueTokenStream and passes that around
+// instead. The single exception is nsCSSValue::AppendToString which we use
+// to serialize the string contained in the nsCSSValueTokenStream by ensuring
+// the mShorthandPropertyID is set to eCSSProperty_UNKNOWN.
+struct nsCSSValueTokenStream final {
+ nsCSSValueTokenStream();
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSValueTokenStream();
+
+public:
+ bool operator==(const nsCSSValueTokenStream& aOther) const
+ {
+ bool eq;
+ return mPropertyID == aOther.mPropertyID &&
+ mShorthandPropertyID == aOther.mShorthandPropertyID &&
+ mTokenStream.Equals(aOther.mTokenStream) &&
+ mLevel == aOther.mLevel &&
+ (mBaseURI == aOther.mBaseURI ||
+ (mBaseURI && aOther.mBaseURI &&
+ NS_SUCCEEDED(mBaseURI->Equals(aOther.mBaseURI, &eq)) &&
+ eq)) &&
+ (mSheetURI == aOther.mSheetURI ||
+ (mSheetURI && aOther.mSheetURI &&
+ NS_SUCCEEDED(mSheetURI->Equals(aOther.mSheetURI, &eq)) &&
+ eq)) &&
+ (mSheetPrincipal == aOther.mSheetPrincipal ||
+ (mSheetPrincipal && aOther.mSheetPrincipal &&
+ NS_SUCCEEDED(mSheetPrincipal->Equals(aOther.mSheetPrincipal,
+ &eq)) &&
+ eq));
+ }
+
+ bool operator!=(const nsCSSValueTokenStream& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(nsCSSValueTokenStream)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // The property that has mTokenStream as its unparsed specified value.
+ // When a variable reference is used in a shorthand property, a
+ // TokenStream value is stored as the specified value for each of its
+ // component longhand properties.
+ nsCSSPropertyID mPropertyID;
+
+ // The shorthand property that had a value with a variable reference,
+ // which caused the longhand property identified by mPropertyID to have
+ // a TokenStream value.
+ nsCSSPropertyID mShorthandPropertyID;
+
+ // The unparsed CSS corresponding to the specified value of the property.
+ // When the value of a shorthand property has a variable reference, the
+ // same mTokenStream value is used on each of the nsCSSValueTokenStream
+ // objects that will be set by parsing the shorthand.
+ nsString mTokenStream;
+
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCOMPtr<nsIURI> mSheetURI;
+ nsCOMPtr<nsIPrincipal> mSheetPrincipal;
+ // XXX Should store sheet here (see Bug 952338)
+ // mozilla::CSSStyleSheet* mSheet;
+ uint32_t mLineNumber;
+ uint32_t mLineOffset;
+ mozilla::SheetType mLevel;
+
+private:
+ nsCSSValueTokenStream(const nsCSSValueTokenStream& aOther) = delete;
+ nsCSSValueTokenStream& operator=(const nsCSSValueTokenStream& aOther) = delete;
+};
+
+class nsCSSValueFloatColor final {
+public:
+ nsCSSValueFloatColor(float aComponent1, float aComponent2, float aComponent3,
+ float aAlpha)
+ : mComponent1(aComponent1)
+ , mComponent2(aComponent2)
+ , mComponent3(aComponent3)
+ , mAlpha(aAlpha)
+ {
+ MOZ_COUNT_CTOR(nsCSSValueFloatColor);
+ }
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSValueFloatColor()
+ {
+ MOZ_COUNT_DTOR(nsCSSValueFloatColor);
+ }
+
+public:
+ bool operator==(nsCSSValueFloatColor& aOther) const;
+
+ nscolor GetColorValue(nsCSSUnit aUnit) const;
+ float Comp1() const { return mComponent1; }
+ float Comp2() const { return mComponent2; }
+ float Comp3() const { return mComponent3; }
+ float Alpha() const { return mAlpha; }
+ bool IsNonTransparentColor() const;
+
+ void AppendToString(nsCSSUnit aUnit, nsAString& aResult) const;
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ NS_INLINE_DECL_REFCOUNTING(nsCSSValueFloatColor)
+
+private:
+ // The range of each component is.
+ // [0, 1] for HSLColor and HSLAColor. mComponent1 for hue, mComponent2 for
+ // saturation, mComponent3 for lightness.
+ // [0, 1] for saturation and lightness
+ // represents [0%, 100%].
+ // [0, 1] for hue represents
+ // [0deg, 360deg].
+ //
+ // [-float::max(), float::max()] for PercentageRGBColor, PercentageRGBAColor.
+ // 1.0 means 100%.
+ float mComponent1;
+ float mComponent2;
+ float mComponent3;
+ float mAlpha;
+
+ nsCSSValueFloatColor(const nsCSSValueFloatColor& aOther) = delete;
+ nsCSSValueFloatColor& operator=(const nsCSSValueFloatColor& aOther)
+ = delete;
+};
+
+struct nsCSSCornerSizes {
+ nsCSSCornerSizes(void);
+ nsCSSCornerSizes(const nsCSSCornerSizes& aCopy);
+ ~nsCSSCornerSizes();
+
+ // argument is a "full corner" constant from nsStyleConsts.h
+ nsCSSValue const & GetCorner(uint32_t aCorner) const {
+ return this->*corners[aCorner];
+ }
+ nsCSSValue & GetCorner(uint32_t aCorner) {
+ return this->*corners[aCorner];
+ }
+
+ bool operator==(const nsCSSCornerSizes& aOther) const {
+ NS_FOR_CSS_FULL_CORNERS(corner) {
+ if (this->GetCorner(corner) != aOther.GetCorner(corner))
+ return false;
+ }
+ return true;
+ }
+
+ bool operator!=(const nsCSSCornerSizes& aOther) const {
+ NS_FOR_CSS_FULL_CORNERS(corner) {
+ if (this->GetCorner(corner) != aOther.GetCorner(corner))
+ return true;
+ }
+ return false;
+ }
+
+ bool HasValue() const {
+ NS_FOR_CSS_FULL_CORNERS(corner) {
+ if (this->GetCorner(corner).GetUnit() != eCSSUnit_Null)
+ return true;
+ }
+ return false;
+ }
+
+ void Reset();
+
+ nsCSSValue mTopLeft;
+ nsCSSValue mTopRight;
+ nsCSSValue mBottomRight;
+ nsCSSValue mBottomLeft;
+
+protected:
+ typedef nsCSSValue nsCSSCornerSizes::*corner_type;
+ static const corner_type corners[4];
+};
+
+#endif /* nsCSSValue_h___ */
+
diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp
new file mode 100644
index 000000000..4eb24b76b
--- /dev/null
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -0,0 +1,6698 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Preferences.h"
+
+#include "nsError.h"
+#include "nsDOMString.h"
+#include "nsIDOMCSSPrimitiveValue.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsStyleContext.h"
+#include "nsIScrollableFrame.h"
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+
+#include "nsDOMCSSRect.h"
+#include "nsDOMCSSRGBColor.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 "nsIDocument.h"
+
+#include "nsCSSPseudoElements.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "imgIRequest.h"
+#include "nsLayoutUtils.h"
+#include "nsCSSKeywords.h"
+#include "nsStyleCoord.h"
+#include "nsDisplayList.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsStyleTransformMatrix.h"
+#include "mozilla/dom/Element.h"
+#include "prtime.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/AppUnits.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#if defined(DEBUG_bzbarsky) || defined(DEBUG_caillon)
+#define DEBUG_ComputedDOMStyle
+#endif
+
+/*
+ * 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,
+ nsIPresShell* aPresShell,
+ nsComputedDOMStyle::StyleType aStyleType)
+{
+ RefPtr<nsComputedDOMStyle> computedStyle;
+ computedStyle = new nsComputedDOMStyle(aElement, aPseudoElt, aPresShell,
+ aStyleType);
+ return computedStyle.forget();
+}
+
+/**
+ * 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 nsComputedStyleMap
+{
+ friend class nsComputedDOMStyle;
+
+ struct Entry
+ {
+ // Create a pointer-to-member-function type.
+ typedef already_AddRefed<CSSValue> (nsComputedDOMStyle::*ComputeMethod)();
+
+ nsCSSPropertyID mProperty;
+ ComputeMethod mGetter;
+
+ bool IsLayoutFlushNeeded() const
+ {
+ return nsCSSProps::PropHasFlags(mProperty,
+ CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH);
+ }
+
+ bool IsEnabled() const
+ {
+ return nsCSSProps::IsEnabled(mProperty, CSSEnabledState::eForAllContent);
+ }
+ };
+
+ // We define this enum just to count the total number of properties that can
+ // be exposed on an nsComputedDOMStyle, including properties that may be
+ // disabled.
+ enum {
+#define COMPUTED_STYLE_PROP(prop_, method_) \
+ eComputedStyleProperty_##prop_,
+#include "nsComputedDOMStylePropertyList.h"
+#undef COMPUTED_STYLE_PROP
+ eComputedStyleProperty_COUNT
+ };
+
+ /**
+ * Returns the number of properties that should be exposed on an
+ * nsComputedDOMStyle, ecxluding any disabled properties.
+ */
+ uint32_t Length()
+ {
+ Update();
+ return mExposedPropertyCount;
+ }
+
+ /**
+ * 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)
+ {
+ Update();
+ for (uint32_t i = 0; i < mExposedPropertyCount; i++) {
+ const Entry* entry = &kEntries[EntryIndex(i)];
+ if (entry->mProperty == aPropID) {
+ return entry;
+ }
+ }
+ return nullptr;
+ }
+
+ /**
+ * Records that mIndexMap needs updating, due to prefs changing that could
+ * affect the set of properties exposed on an nsComputedDOMStyle.
+ */
+ void MarkDirty() { mExposedPropertyCount = 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.
+
+ /**
+ * An entry for each property that can be exposed on an nsComputedDOMStyle.
+ */
+ const Entry kEntries[eComputedStyleProperty_COUNT];
+
+ /**
+ * 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 mExposedPropertyCount;
+
+ /**
+ * A map of indexes on the nsComputedDOMStyle object to indexes into kEntries.
+ */
+ uint32_t mIndexMap[eComputedStyleProperty_COUNT];
+
+private:
+ /**
+ * Returns whether mExposedPropertyCount and mIndexMap are out of date.
+ */
+ bool IsDirty() { return mExposedPropertyCount == 0; }
+
+ /**
+ * Updates mExposedPropertyCount 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 < mExposedPropertyCount);
+ return mIndexMap[aIndex];
+ }
+};
+
+void
+nsComputedStyleMap::Update()
+{
+ if (!IsDirty()) {
+ return;
+ }
+
+ uint32_t index = 0;
+ for (uint32_t i = 0; i < eComputedStyleProperty_COUNT; i++) {
+ if (kEntries[i].IsEnabled()) {
+ mIndexMap[index++] = i;
+ }
+ }
+ mExposedPropertyCount = index;
+}
+
+nsComputedDOMStyle::nsComputedDOMStyle(dom::Element* aElement,
+ const nsAString& aPseudoElt,
+ nsIPresShell* aPresShell,
+ StyleType aStyleType)
+ : mDocumentWeak(nullptr)
+ , mOuterFrame(nullptr)
+ , mInnerFrame(nullptr)
+ , mPresShell(nullptr)
+ , mStyleType(aStyleType)
+ , mStyleContextGeneration(0)
+ , mExposeVisitedStyle(false)
+ , mResolvedStyleContext(false)
+{
+ MOZ_ASSERT(aElement && aPresShell);
+
+ mDocumentWeak = do_GetWeakReference(aPresShell->GetDocument());
+
+ mContent = aElement;
+
+ if (!DOMStringIsNull(aPseudoElt) && !aPseudoElt.IsEmpty() &&
+ aPseudoElt.First() == char16_t(':')) {
+ // deal with two-colon forms of aPseudoElt
+ nsAString::const_iterator start, end;
+ aPseudoElt.BeginReading(start);
+ aPseudoElt.EndReading(end);
+ NS_ASSERTION(start != end, "aPseudoElt is not empty!");
+ ++start;
+ bool haveTwoColons = true;
+ if (start == end || *start != char16_t(':')) {
+ --start;
+ haveTwoColons = false;
+ }
+ mPseudo = NS_Atomize(Substring(start, end));
+ MOZ_ASSERT(mPseudo);
+
+ // There aren't any non-CSS2 pseudo-elements with a single ':'
+ if (!haveTwoColons &&
+ (!nsCSSPseudoElements::IsPseudoElement(mPseudo) ||
+ !nsCSSPseudoElements::IsCSS2PseudoElement(mPseudo))) {
+ // XXXbz I'd really rather we threw an exception or something, but
+ // the DOM spec sucks.
+ mPseudo = nullptr;
+ }
+ }
+
+ MOZ_ASSERT(aPresShell->GetPresContext());
+}
+
+nsComputedDOMStyle::~nsComputedDOMStyle()
+{
+ ClearStyleContext();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsComputedDOMStyle)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsComputedDOMStyle)
+ tmp->ClearStyleContext(); // remove observer before clearing mContent
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
+ 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(mContent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsComputedDOMStyle)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsComputedDOMStyle)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsComputedDOMStyle)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsComputedDOMStyle)
+ return tmp->IsBlack();
+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(nsComputedDOMStyle)
+
+NS_IMETHODIMP
+nsComputedDOMStyle::GetPropertyValue(const nsCSSPropertyID aPropID,
+ nsAString& aValue)
+{
+ // This is mostly to avoid code duplication with GetPropertyCSSValue(); if
+ // perf ever becomes an issue here (doubtful), we can look into changing
+ // this.
+ return GetPropertyValue(
+ NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(aPropID)),
+ aValue);
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::SetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsAString& aValue)
+{
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::GetCssText(nsAString& aCssText)
+{
+ aCssText.Truncate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::SetCssText(const nsAString& aCssText)
+{
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::GetLength(uint32_t* aLength)
+{
+ NS_PRECONDITION(aLength, "Null aLength! Prepare to die!");
+
+ uint32_t length = GetComputedStyleMap()->Length();
+
+ // Make sure we have up to date style so that we can include custom
+ // properties.
+ UpdateCurrentStyleSources(false);
+ if (mStyleContext) {
+ length += StyleVariables()->mVariables.Count();
+ }
+
+ *aLength = length;
+
+ ClearCurrentStyleSources();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+ *aParentRule = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::GetPropertyValue(const nsAString& aPropertyName,
+ nsAString& aReturn)
+{
+ aReturn.Truncate();
+
+ ErrorResult error;
+ RefPtr<CSSValue> val = GetPropertyCSSValue(aPropertyName, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ if (val) {
+ nsString text;
+ val->GetCssText(text, error);
+ aReturn.Assign(text);
+ return error.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::GetAuthoredPropertyValue(const nsAString& aPropertyName,
+ nsAString& aReturn)
+{
+ // Authored style doesn't make sense to return from computed DOM style,
+ // so just return whatever GetPropertyValue() returns.
+ return GetPropertyValue(aPropertyName, aReturn);
+}
+
+/* static */
+already_AddRefed<nsStyleContext>
+nsComputedDOMStyle::GetStyleContextForElement(Element* aElement,
+ nsIAtom* aPseudo,
+ nsIPresShell* aPresShell,
+ StyleType aStyleType)
+{
+ // 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.
+ nsCOMPtr<nsIPresShell> presShell = GetPresShellForContent(aElement);
+ if (!presShell) {
+ presShell = aPresShell;
+ if (!presShell)
+ return nullptr;
+ }
+
+ presShell->FlushPendingNotifications(Flush_Style);
+
+ return GetStyleContextForElementNoFlush(aElement, aPseudo, presShell,
+ aStyleType);
+}
+
+/* static */
+already_AddRefed<nsStyleContext>
+nsComputedDOMStyle::GetStyleContextForElementNoFlush(Element* aElement,
+ nsIAtom* aPseudo,
+ nsIPresShell* 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.
+ nsIPresShell *presShell = GetPresShellForContent(aElement);
+ bool inDocWithShell = true;
+ if (!presShell) {
+ inDocWithShell = false;
+ presShell = aPresShell;
+ if (!presShell)
+ 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 (!aPseudo && aStyleType == eAll && inDocWithShell &&
+ !aElement->IsHTMLElement(nsGkAtoms::area)) {
+ nsIFrame* frame = nsLayoutUtils::GetStyleFrame(aElement);
+ if (frame) {
+ nsStyleContext* result = frame->StyleContext();
+ // Don't use the style context if it was influenced by
+ // pseudo-elements, since then it's not the primary style
+ // for this element.
+ if (!result->HasPseudoElementData()) {
+ // this function returns an addrefed style context
+ RefPtr<nsStyleContext> ret = result;
+ return ret.forget();
+ }
+ }
+ }
+
+ // No frame has been created, or we have a pseudo, or we're looking
+ // for the default style, so resolve the style ourselves.
+ RefPtr<nsStyleContext> parentContext;
+ nsIContent* parent = aPseudo ? aElement : aElement->GetParent();
+ // Don't resolve parent context for document fragments.
+ if (parent && parent->IsElement())
+ parentContext = GetStyleContextForElementNoFlush(parent->AsElement(),
+ nullptr, presShell,
+ aStyleType);
+
+ nsPresContext *presContext = presShell->GetPresContext();
+ if (!presContext)
+ return nullptr;
+
+ StyleSetHandle styleSet = presShell->StyleSet();
+
+ RefPtr<nsStyleContext> sc;
+ if (aPseudo) {
+ CSSPseudoElementType type = nsCSSPseudoElements::
+ GetPseudoType(aPseudo, CSSEnabledState::eIgnoreEnabledState);
+ if (type >= CSSPseudoElementType::Count) {
+ return nullptr;
+ }
+ nsIFrame* frame = nsLayoutUtils::GetStyleFrame(aElement);
+ Element* pseudoElement =
+ frame && inDocWithShell ? frame->GetPseudoElement(type) : nullptr;
+ sc = styleSet->ResolvePseudoElementStyle(aElement, type, parentContext,
+ pseudoElement);
+ } else {
+ sc = styleSet->ResolveStyleFor(aElement, parentContext);
+ }
+
+ if (aStyleType == eDefaultOnly) {
+ if (styleSet->IsServo()) {
+ NS_ERROR("stylo: ServoStyleSets cannot supply UA-only styles yet");
+ return nullptr;
+ }
+
+ // We really only want the user and UA rules. Filter out the other ones.
+ nsTArray< nsCOMPtr<nsIStyleRule> > rules;
+ for (nsRuleNode* ruleNode = sc->RuleNode();
+ !ruleNode->IsRoot();
+ ruleNode = ruleNode->GetParent()) {
+ if (ruleNode->GetLevel() == SheetType::Agent ||
+ ruleNode->GetLevel() == SheetType::User) {
+ rules.AppendElement(ruleNode->GetRule());
+ }
+ }
+
+ // We want to build a list of user/ua rules that is in order from least to
+ // most important, so we have to reverse the list.
+ // Integer division to get "stop" is purposeful here: if length is odd, we
+ // don't have to do anything with the middle element of the array.
+ for (uint32_t i = 0, length = rules.Length(), stop = length / 2;
+ i < stop; ++i) {
+ rules[i].swap(rules[length - i - 1]);
+ }
+
+ sc = styleSet->AsGecko()->ResolveStyleForRules(parentContext, rules);
+ }
+
+ return sc.forget();
+}
+
+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 */
+nsIPresShell*
+nsComputedDOMStyle::GetPresShellForContent(nsIContent* aContent)
+{
+ nsIDocument* composedDoc = aContent->GetComposedDoc();
+ if (!composedDoc)
+ return nullptr;
+
+ return composedDoc->GetShell();
+}
+
+// nsDOMCSSDeclaration abstract methods which should never be called
+// on a nsComputedDOMStyle object, but must be defined to avoid
+// compile errors.
+DeclarationBlock*
+nsComputedDOMStyle::GetCSSDeclaration(Operation)
+{
+ NS_RUNTIMEABORT("called nsComputedDOMStyle::GetCSSDeclaration");
+ return nullptr;
+}
+
+nsresult
+nsComputedDOMStyle::SetCSSDeclaration(DeclarationBlock*)
+{
+ NS_RUNTIMEABORT("called nsComputedDOMStyle::SetCSSDeclaration");
+ return NS_ERROR_FAILURE;
+}
+
+nsIDocument*
+nsComputedDOMStyle::DocToUpdate()
+{
+ NS_RUNTIMEABORT("called nsComputedDOMStyle::DocToUpdate");
+ return nullptr;
+}
+
+void
+nsComputedDOMStyle::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
+{
+ NS_RUNTIMEABORT("called nsComputedDOMStyle::GetCSSParsingEnvironment");
+ // Just in case NS_RUNTIMEABORT ever stops killing us for some reason
+ aCSSParseEnv.mPrincipal = nullptr;
+}
+
+void
+nsComputedDOMStyle::ClearStyleContext()
+{
+ if (mResolvedStyleContext) {
+ mResolvedStyleContext = false;
+ mContent->RemoveMutationObserver(this);
+ }
+ mStyleContext = nullptr;
+}
+
+void
+nsComputedDOMStyle::SetResolvedStyleContext(RefPtr<nsStyleContext>&& aContext)
+{
+ if (!mResolvedStyleContext) {
+ mResolvedStyleContext = true;
+ mContent->AddMutationObserver(this);
+ }
+ mStyleContext = aContext;
+}
+
+void
+nsComputedDOMStyle::SetFrameStyleContext(nsStyleContext* aContext)
+{
+ ClearStyleContext();
+ mStyleContext = aContext;
+}
+
+void
+nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
+{
+ nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocumentWeak);
+ if (!document) {
+ ClearStyleContext();
+ return;
+ }
+
+ document->FlushPendingLinkUpdates();
+
+ // Flush _before_ getting the presshell, since that could create a new
+ // presshell. Also note that we want to flush the style on the document
+ // we're computing style in, not on the document mContent is in -- the two
+ // may be different.
+ document->FlushPendingNotifications(
+ aNeedsLayoutFlush ? Flush_Layout : Flush_Style);
+#ifdef DEBUG
+ mFlushedPendingReflows = aNeedsLayoutFlush;
+#endif
+
+ nsCOMPtr<nsIPresShell> presShellForContent = GetPresShellForContent(mContent);
+ if (presShellForContent && presShellForContent != document->GetShell()) {
+ presShellForContent->FlushPendingNotifications(Flush_Style);
+ }
+
+ mPresShell = document->GetShell();
+ if (!mPresShell || !mPresShell->GetPresContext()) {
+ ClearStyleContext();
+ return;
+ }
+
+ uint64_t currentGeneration =
+ mPresShell->GetPresContext()->GetRestyleGeneration();
+
+ if (mStyleContext) {
+ if (mStyleContextGeneration == currentGeneration) {
+ // Our cached style context is still valid.
+ return;
+ }
+ // We've processed some restyles, so the cached style context might
+ // be out of date.
+ mStyleContext = nullptr;
+ }
+
+ // XXX the !mContent->IsHTMLElement(nsGkAtoms::area)
+ // check is needed due to bug 135040 (to avoid using
+ // mPrimaryFrame). Remove it once that's fixed.
+ if (!mPseudo && mStyleType == eAll &&
+ !mContent->IsHTMLElement(nsGkAtoms::area)) {
+ mOuterFrame = mContent->GetPrimaryFrame();
+ mInnerFrame = mOuterFrame;
+ if (mOuterFrame) {
+ nsIAtom* type = mOuterFrame->GetType();
+ if (type == nsGkAtoms::tableWrapperFrame) {
+ // If the frame is a table wrapper frame then we should get the style
+ // from the inner table frame.
+ mInnerFrame = mOuterFrame->PrincipalChildList().FirstChild();
+ NS_ASSERTION(mInnerFrame, "table wrapper must have an inner");
+ NS_ASSERTION(!mInnerFrame->GetNextSibling(),
+ "table wrapper frames should have just one child, "
+ "the inner table");
+ }
+
+ SetFrameStyleContext(mInnerFrame->StyleContext());
+ NS_ASSERTION(mStyleContext, "Frame without style context?");
+ }
+ }
+
+ if (!mStyleContext || mStyleContext->HasPseudoElementData()) {
+#ifdef DEBUG
+ if (mStyleContext) {
+ // We want to check that going through this path because of
+ // HasPseudoElementData is rare, because it slows us down a good
+ // bit. So check that we're really inside something associated
+ // with a pseudo-element that contains elements.
+ nsStyleContext* topWithPseudoElementData = mStyleContext;
+ while (topWithPseudoElementData->GetParent()->HasPseudoElementData()) {
+ topWithPseudoElementData = topWithPseudoElementData->GetParent();
+ }
+ CSSPseudoElementType pseudo = topWithPseudoElementData->GetPseudoType();
+ nsIAtom* pseudoAtom = nsCSSPseudoElements::GetPseudoAtom(pseudo);
+ nsAutoString assertMsg(
+ NS_LITERAL_STRING("we should be in a pseudo-element that is expected to contain elements ("));
+ assertMsg.Append(nsDependentString(pseudoAtom->GetUTF16String()));
+ assertMsg.Append(')');
+ NS_ASSERTION(nsCSSPseudoElements::PseudoElementContainsElements(pseudo),
+ NS_LossyConvertUTF16toASCII(assertMsg).get());
+ }
+#endif
+ // Need to resolve a style context
+ RefPtr<nsStyleContext> resolvedStyleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(
+ mContent->AsElement(),
+ mPseudo,
+ presShellForContent ? presShellForContent.get() : mPresShell,
+ mStyleType);
+ if (!resolvedStyleContext) {
+ ClearStyleContext();
+ return;
+ }
+
+ // No need to re-get the generation, even though GetStyleContextForElement
+ // will flush, since we flushed style at the top of this function.
+ NS_ASSERTION(mPresShell &&
+ currentGeneration ==
+ mPresShell->GetPresContext()->GetRestyleGeneration(),
+ "why should we have flushed style again?");
+
+ SetResolvedStyleContext(Move(resolvedStyleContext));
+ NS_ASSERTION(mPseudo || !mStyleContext->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 && mStyleContext->RelevantLinkVisited()) {
+ nsStyleContext *styleIfVisited = mStyleContext->GetStyleIfVisited();
+ if (styleIfVisited) {
+ mStyleContext = styleIfVisited;
+ }
+ }
+}
+
+void
+nsComputedDOMStyle::ClearCurrentStyleSources()
+{
+ mOuterFrame = nullptr;
+ mInnerFrame = nullptr;
+ mPresShell = nullptr;
+
+ // Release the current style context if we got it off the frame.
+ // For a style context we resolved, keep it around so that we
+ // can re-use it next time this object is queried.
+ if (!mResolvedStyleContext) {
+ mStyleContext = nullptr;
+ }
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetPropertyCSSValue(const nsAString& aPropertyName, ErrorResult& aRv)
+{
+ nsCSSPropertyID prop =
+ nsCSSProps::LookupProperty(aPropertyName, CSSEnabledState::eForAllContent);
+
+ bool needsLayoutFlush;
+ nsComputedStyleMap::Entry::ComputeMethod getter;
+
+ if (prop == eCSSPropertyExtra_variable) {
+ needsLayoutFlush = false;
+ getter = nullptr;
+ } else {
+ // We don't (for now, anyway, though it may make sense to change it
+ // for all aliases, including those in nsCSSPropAliasList) want
+ // aliases to be enumerable (via GetLength and IndexedGetter), so
+ // handle them here rather than adding entries to
+ // GetQueryablePropertyMap.
+ if (prop != eCSSProperty_UNKNOWN &&
+ nsCSSProps::PropHasFlags(prop, CSS_PROPERTY_IS_ALIAS)) {
+ const nsCSSPropertyID* subprops = nsCSSProps::SubpropertyEntryFor(prop);
+ MOZ_ASSERT(subprops[1] == eCSSProperty_UNKNOWN,
+ "must have list of length 1");
+ prop = subprops[0];
+ }
+
+ const nsComputedStyleMap::Entry* propEntry =
+ GetComputedStyleMap()->FindEntryForProperty(prop);
+
+ if (!propEntry) {
+#ifdef DEBUG_ComputedDOMStyle
+ NS_WARNING(PromiseFlatCString(NS_ConvertUTF16toUTF8(aPropertyName) +
+ NS_LITERAL_CSTRING(" is not queryable!")).get());
+#endif
+
+ // NOTE: For branches, we should flush here for compatibility!
+ return nullptr;
+ }
+
+ needsLayoutFlush = propEntry->IsLayoutFlushNeeded();
+ getter = propEntry->mGetter;
+ }
+
+ UpdateCurrentStyleSources(needsLayoutFlush);
+ if (!mStyleContext) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<CSSValue> val;
+ if (prop == eCSSPropertyExtra_variable) {
+ val = DoGetCustomProperty(aPropertyName);
+ } else {
+ // Call our pointer-to-member-function.
+ val = (this->*getter)();
+ }
+
+ ClearCurrentStyleSources();
+
+ return val.forget();
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::RemoveProperty(const nsAString& aPropertyName,
+ nsAString& aReturn)
+{
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+}
+
+
+NS_IMETHODIMP
+nsComputedDOMStyle::GetPropertyPriority(const nsAString& aPropertyName,
+ nsAString& aReturn)
+{
+ aReturn.Truncate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::SetProperty(const nsAString& aPropertyName,
+ const nsAString& aValue,
+ const nsAString& aPriority)
+{
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+}
+
+NS_IMETHODIMP
+nsComputedDOMStyle::Item(uint32_t aIndex, nsAString& aReturn)
+{
+ return nsDOMCSSDeclaration::Item(aIndex, aReturn);
+}
+
+void
+nsComputedDOMStyle::IndexedGetter(uint32_t aIndex,
+ bool& aFound,
+ nsAString& aPropName)
+{
+ nsComputedStyleMap* map = GetComputedStyleMap();
+ uint32_t length = map->Length();
+
+ if (aIndex < length) {
+ aFound = true;
+ CopyASCIItoUTF16(nsCSSProps::GetStringValue(map->PropertyAt(aIndex)),
+ aPropName);
+ return;
+ }
+
+ // Custom properties are exposed with indexed properties just after all
+ // of the built-in properties.
+ UpdateCurrentStyleSources(false);
+ if (!mStyleContext) {
+ aFound = false;
+ return;
+ }
+
+ const nsStyleVariables* variables = StyleVariables();
+ if (aIndex - length < variables->mVariables.Count()) {
+ aFound = true;
+ nsString varName;
+ variables->mVariables.GetVariableAt(aIndex - length, varName);
+ aPropName.AssignLiteral("--");
+ aPropName.Append(varName);
+ } else {
+ aFound = false;
+ }
+
+ ClearCurrentStyleSources();
+}
+
+// Property getters...
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBinding()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleDisplay* display = StyleDisplay();
+
+ if (display->mBinding) {
+ val->SetURI(display->mBinding->GetURI());
+ } else {
+ val->SetIdent(eCSSKeyword_none);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetClear()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mBreakType,
+ nsCSSProps::kClearKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFloat()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mFloat,
+ nsCSSProps::kFloatKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBottom()
+{
+ return GetOffsetWidthFor(NS_SIDE_BOTTOM);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStackSizing()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(StyleXUL()->mStretchStack ? eCSSKeyword_stretch_to_fit :
+ eCSSKeyword_ignore);
+ return val.forget();
+}
+
+void
+nsComputedDOMStyle::SetToRGBAColor(nsROCSSPrimitiveValue* aValue,
+ nscolor aColor)
+{
+ if (NS_GET_A(aColor) == 0) {
+ aValue->SetIdent(eCSSKeyword_transparent);
+ return;
+ }
+
+ nsROCSSPrimitiveValue *red = new nsROCSSPrimitiveValue;
+ nsROCSSPrimitiveValue *green = new nsROCSSPrimitiveValue;
+ nsROCSSPrimitiveValue *blue = new nsROCSSPrimitiveValue;
+ nsROCSSPrimitiveValue *alpha = new nsROCSSPrimitiveValue;
+
+ uint8_t a = NS_GET_A(aColor);
+ nsDOMCSSRGBColor *rgbColor =
+ new nsDOMCSSRGBColor(red, green, blue, alpha, a < 255);
+
+ red->SetNumber(NS_GET_R(aColor));
+ green->SetNumber(NS_GET_G(aColor));
+ blue->SetNumber(NS_GET_B(aColor));
+ alpha->SetNumber(nsStyleUtil::ColorComponentToFloat(a));
+
+ aValue->SetColor(rgbColor);
+}
+
+void
+nsComputedDOMStyle::SetValueFromComplexColor(nsROCSSPrimitiveValue* aValue,
+ const StyleComplexColor& aColor)
+{
+ SetToRGBAColor(aValue, StyleColor()->CalcComplexColor(aColor));
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetToRGBAColor(val, StyleColor()->mColor);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColorAdjust()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleVisibility()->mColorAdjust,
+ nsCSSProps::kColorAdjustKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOpacity()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleEffects()->mOpacity);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColumnCount()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleColumn* column = StyleColumn();
+
+ if (column->mColumnCount == NS_STYLE_COLUMN_COUNT_AUTO) {
+ val->SetIdent(eCSSKeyword_auto);
+ } else {
+ val->SetNumber(column->mColumnCount);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColumnWidth()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ // XXX fix the auto case. When we actually have a column frame, I think
+ // we should return the computed column width.
+ SetValueToCoord(val, StyleColumn()->mColumnWidth, true);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColumnGap()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleColumn* column = StyleColumn();
+ if (column->mColumnGap.GetUnit() == eStyleUnit_Normal) {
+ val->SetAppUnits(StyleFont()->mFont.size);
+ } else {
+ SetValueToCoord(val, StyleColumn()->mColumnGap, true);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColumnFill()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleColumn()->mColumnFill,
+ nsCSSProps::kColumnFillKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColumnRuleWidth()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(StyleColumn()->GetComputedColumnRuleWidth());
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColumnRuleStyle()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleColumn()->mColumnRuleStyle,
+ nsCSSProps::kBorderStyleKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColumnRuleColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueFromComplexColor(val, StyleColumn()->mColumnRuleColor);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetContent()
+{
+ const nsStyleContent *content = StyleContent();
+
+ if (content->ContentCount() == 0) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+
+ if (content->ContentCount() == 1 &&
+ content->ContentAt(0).mType == eStyleContentType_AltContent) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword__moz_alt_content);
+ return val.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ for (uint32_t i = 0, i_end = content->ContentCount(); i < i_end; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleContentData &data = content->ContentAt(i);
+ switch (data.mType) {
+ case eStyleContentType_String:
+ {
+ nsAutoString str;
+ nsStyleUtil::AppendEscapedCSSString(
+ nsDependentString(data.mContent.mString), str);
+ val->SetString(str);
+ }
+ break;
+ case eStyleContentType_Image:
+ {
+ nsCOMPtr<nsIURI> uri;
+ if (data.mContent.mImage) {
+ data.mContent.mImage->GetURI(getter_AddRefs(uri));
+ }
+ val->SetURI(uri);
+ }
+ break;
+ case eStyleContentType_Attr:
+ {
+ nsAutoString str;
+ nsStyleUtil::AppendEscapedCSSIdent(
+ nsDependentString(data.mContent.mString), str);
+ val->SetString(str, nsIDOMCSSPrimitiveValue::CSS_ATTR);
+ }
+ break;
+ case eStyleContentType_Counter:
+ case eStyleContentType_Counters:
+ {
+ /* FIXME: counters should really use an object */
+ nsAutoString str;
+ if (data.mType == eStyleContentType_Counter) {
+ str.AppendLiteral("counter(");
+ }
+ else {
+ str.AppendLiteral("counters(");
+ }
+ // WRITE ME
+ nsCSSValue::Array *a = data.mContent.mCounters;
+
+ nsStyleUtil::AppendEscapedCSSIdent(
+ nsDependentString(a->Item(0).GetStringBufferValue()), str);
+ int32_t typeItem = 1;
+ if (data.mType == eStyleContentType_Counters) {
+ typeItem = 2;
+ str.AppendLiteral(", ");
+ nsStyleUtil::AppendEscapedCSSString(
+ nsDependentString(a->Item(1).GetStringBufferValue()), str);
+ }
+ MOZ_ASSERT(eCSSUnit_None != a->Item(typeItem).GetUnit(),
+ "'none' should be handled as identifier value");
+ nsString type;
+ a->Item(typeItem).AppendToString(eCSSProperty_list_style_type,
+ type, nsCSSValue::eNormalized);
+ if (!type.LowerCaseEqualsLiteral("decimal")) {
+ str.AppendLiteral(", ");
+ str.Append(type);
+ }
+
+ str.Append(char16_t(')'));
+ val->SetString(str, nsIDOMCSSPrimitiveValue::CSS_COUNTER);
+ }
+ break;
+ case eStyleContentType_OpenQuote:
+ val->SetIdent(eCSSKeyword_open_quote);
+ break;
+ case eStyleContentType_CloseQuote:
+ val->SetIdent(eCSSKeyword_close_quote);
+ break;
+ case eStyleContentType_NoOpenQuote:
+ val->SetIdent(eCSSKeyword_no_open_quote);
+ break;
+ case eStyleContentType_NoCloseQuote:
+ val->SetIdent(eCSSKeyword_no_close_quote);
+ break;
+ case eStyleContentType_AltContent:
+ default:
+ NS_NOTREACHED("unexpected type");
+ break;
+ }
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetCounterIncrement()
+{
+ const nsStyleContent *content = StyleContent();
+
+ if (content->CounterIncrementCount() == 0) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ for (uint32_t i = 0, i_end = content->CounterIncrementCount(); i < i_end; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> name = new nsROCSSPrimitiveValue;
+ RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
+
+ const nsStyleCounterData& data = content->CounterIncrementAt(i);
+ nsAutoString escaped;
+ nsStyleUtil::AppendEscapedCSSIdent(data.mCounter, escaped);
+ name->SetString(escaped);
+ value->SetNumber(data.mValue); // XXX This should really be integer
+
+ valueList->AppendCSSValue(name.forget());
+ valueList->AppendCSSValue(value.forget());
+ }
+
+ return valueList.forget();
+}
+
+/* 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 nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsROCSSPrimitiveValue> width = new nsROCSSPrimitiveValue;
+ SetValueToCoord(width, display->mTransformOrigin[0], false,
+ &nsComputedDOMStyle::GetFrameBoundsWidthForTransform);
+ valueList->AppendCSSValue(width.forget());
+
+ RefPtr<nsROCSSPrimitiveValue> height = new nsROCSSPrimitiveValue;
+ SetValueToCoord(height, display->mTransformOrigin[1], false,
+ &nsComputedDOMStyle::GetFrameBoundsHeightForTransform);
+ valueList->AppendCSSValue(height.forget());
+
+ if (display->mTransformOrigin[2].GetUnit() != eStyleUnit_Coord ||
+ display->mTransformOrigin[2].GetCoordValue() != 0) {
+ RefPtr<nsROCSSPrimitiveValue> depth = new nsROCSSPrimitiveValue;
+ SetValueToCoord(depth, display->mTransformOrigin[2], false,
+ nullptr);
+ 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 nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsROCSSPrimitiveValue> width = new nsROCSSPrimitiveValue;
+ SetValueToCoord(width, display->mPerspectiveOrigin[0], false,
+ &nsComputedDOMStyle::GetFrameBoundsWidthForTransform);
+ valueList->AppendCSSValue(width.forget());
+
+ RefPtr<nsROCSSPrimitiveValue> height = new nsROCSSPrimitiveValue;
+ SetValueToCoord(height, display->mPerspectiveOrigin[1], false,
+ &nsComputedDOMStyle::GetFrameBoundsHeightForTransform);
+ valueList->AppendCSSValue(height.forget());
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPerspective()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StyleDisplay()->mChildPerspective, false);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackfaceVisibility()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mBackfaceVisibility,
+ nsCSSProps::kBackfaceVisibilityKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTransformStyle()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mTransformStyle,
+ nsCSSProps::kTransformStyleKTable));
+ return val.forget();
+}
+
+/* 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::DoGetTransform()
+{
+ /* First, get the display data. We'll need it. */
+ const nsStyleDisplay* display = StyleDisplay();
+
+ /* If there are no transforms, then we should construct a single-element
+ * entry and hand it back.
+ */
+ if (!display->mSpecifiedTransform) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ /* Set it to "none." */
+ val->SetIdent(eCSSKeyword_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,
+ nsSize(0, 0));
+
+ RuleNodeCacheConditions dummy;
+ bool dummyBool;
+ gfx::Matrix4x4 matrix =
+ nsStyleTransformMatrix::ReadTransforms(display->mSpecifiedTransform->mHead,
+ mStyleContext,
+ mStyleContext->PresContext(),
+ dummy,
+ refBox,
+ float(mozilla::AppUnitsPerCSSPixel()),
+ &dummyBool);
+
+ return MatrixToCSSValue(matrix);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTransformBox()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mTransformBox,
+ nsCSSProps::kTransformBoxKTable));
+ return val.forget();
+}
+
+/* static */ already_AddRefed<nsROCSSPrimitiveValue>
+nsComputedDOMStyle::MatrixToCSSValue(const mozilla::gfx::Matrix4x4& matrix)
+{
+ bool is3D = !matrix.Is2D();
+
+ nsAutoString resultString(NS_LITERAL_STRING("matrix"));
+ 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::DoGetCounterReset()
+{
+ const nsStyleContent *content = StyleContent();
+
+ if (content->CounterResetCount() == 0) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ for (uint32_t i = 0, i_end = content->CounterResetCount(); i < i_end; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> name = new nsROCSSPrimitiveValue;
+ RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
+
+ const nsStyleCounterData& data = content->CounterResetAt(i);
+ nsAutoString escaped;
+ nsStyleUtil::AppendEscapedCSSIdent(data.mCounter, escaped);
+ name->SetString(escaped);
+ value->SetNumber(data.mValue); // XXX This should really be integer
+
+ valueList->AppendCSSValue(name.forget());
+ valueList->AppendCSSValue(value.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetQuotes()
+{
+ const auto& quotePairs = StyleList()->GetQuotePairs();
+
+ if (quotePairs.IsEmpty()) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ for (const auto& quotePair : quotePairs) {
+ RefPtr<nsROCSSPrimitiveValue> openVal = new nsROCSSPrimitiveValue;
+ RefPtr<nsROCSSPrimitiveValue> closeVal = new nsROCSSPrimitiveValue;
+
+ nsAutoString s;
+ nsStyleUtil::AppendEscapedCSSString(quotePair.first, s);
+ openVal->SetString(s);
+ s.Truncate();
+ nsStyleUtil::AppendEscapedCSSString(quotePair.second, s);
+ closeVal->SetString(s);
+
+ valueList->AppendCSSValue(openVal.forget());
+ valueList->AppendCSSValue(closeVal.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontFamily()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleFont* font = StyleFont();
+ nsAutoString fontlistStr;
+ nsStyleUtil::AppendEscapedCSSFontFamilyList(font->mFont.fontlist,
+ fontlistStr);
+ val->SetString(fontlistStr);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontSize()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ // Note: StyleFont()->mSize is the 'computed size';
+ // StyleFont()->mFont.size is the 'actual size'
+ val->SetAppUnits(StyleFont()->mSize);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontSizeAdjust()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleFont *font = StyleFont();
+
+ if (font->mFont.sizeAdjust >= 0.0f) {
+ val->SetNumber(font->mFont.sizeAdjust);
+ } else {
+ val->SetIdent(eCSSKeyword_none);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOsxFontSmoothing()
+{
+ if (nsContentUtils::ShouldResistFingerprinting(
+ mPresShell->GetPresContext()->GetDocShell()))
+ return nullptr;
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleFont()->mFont.smoothing,
+ nsCSSProps::kFontSmoothingKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontStretch()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleFont()->mFont.stretch,
+ nsCSSProps::kFontStretchKTable));
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontStyle()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleFont()->mFont.style,
+ nsCSSProps::kFontStyleKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontWeight()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleFont* font = StyleFont();
+
+ uint16_t weight = font->mFont.weight;
+ NS_ASSERTION(weight % 100 == 0, "unexpected value of font-weight");
+ val->SetNumber(weight);
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontFeatureSettings()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleFont* font = StyleFont();
+ if (font->mFont.fontFeatureSettings.IsEmpty()) {
+ val->SetIdent(eCSSKeyword_normal);
+ } else {
+ nsAutoString result;
+ nsStyleUtil::AppendFontFeatureSettings(font->mFont.fontFeatureSettings,
+ result);
+ val->SetString(result);
+ }
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontKerning()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleFont()->mFont.kerning,
+ nsCSSProps::kFontKerningKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontLanguageOverride()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleFont* font = StyleFont();
+ if (font->mFont.languageOverride.IsEmpty()) {
+ val->SetIdent(eCSSKeyword_normal);
+ } else {
+ nsAutoString str;
+ nsStyleUtil::AppendEscapedCSSString(font->mFont.languageOverride, str);
+ val->SetString(str);
+ }
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontSynthesis()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t intValue = StyleFont()->mFont.synthesis;
+
+ if (0 == intValue) {
+ val->SetIdent(eCSSKeyword_none);
+ } else {
+ nsAutoString valueStr;
+
+ nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_font_synthesis,
+ intValue, NS_FONT_SYNTHESIS_WEIGHT,
+ NS_FONT_SYNTHESIS_STYLE, valueStr);
+ val->SetString(valueStr);
+ }
+
+ return val.forget();
+}
+
+// return a value *only* for valid longhand values from CSS 2.1, either
+// normal or small-caps only
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontVariant()
+{
+ const nsFont& f = StyleFont()->mFont;
+
+ // if any of the other font-variant subproperties other than
+ // font-variant-caps are not normal then can't calculate a computed value
+ if (f.variantAlternates || f.variantEastAsian || f.variantLigatures ||
+ f.variantNumeric || f.variantPosition) {
+ return nullptr;
+ }
+
+ nsCSSKeyword keyword;
+ switch (f.variantCaps) {
+ case 0:
+ keyword = eCSSKeyword_normal;
+ break;
+ case NS_FONT_VARIANT_CAPS_SMALLCAPS:
+ keyword = eCSSKeyword_small_caps;
+ break;
+ default:
+ return nullptr;
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(keyword);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontVariantAlternates()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t intValue = StyleFont()->mFont.variantAlternates;
+
+ if (0 == intValue) {
+ val->SetIdent(eCSSKeyword_normal);
+ return val.forget();
+ }
+
+ // first, include enumerated values
+ nsAutoString valueStr;
+
+ nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_font_variant_alternates,
+ intValue & NS_FONT_VARIANT_ALTERNATES_ENUMERATED_MASK,
+ NS_FONT_VARIANT_ALTERNATES_HISTORICAL,
+ NS_FONT_VARIANT_ALTERNATES_HISTORICAL, valueStr);
+
+ // next, include functional values if present
+ if (intValue & NS_FONT_VARIANT_ALTERNATES_FUNCTIONAL_MASK) {
+ nsStyleUtil::SerializeFunctionalAlternates(StyleFont()->mFont.alternateValues,
+ valueStr);
+ }
+
+ val->SetString(valueStr);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontVariantCaps()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t intValue = StyleFont()->mFont.variantCaps;
+
+ if (0 == intValue) {
+ val->SetIdent(eCSSKeyword_normal);
+ } else {
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(intValue,
+ nsCSSProps::kFontVariantCapsKTable));
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontVariantEastAsian()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t intValue = StyleFont()->mFont.variantEastAsian;
+
+ if (0 == intValue) {
+ val->SetIdent(eCSSKeyword_normal);
+ } else {
+ nsAutoString valueStr;
+
+ nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_font_variant_east_asian,
+ intValue, NS_FONT_VARIANT_EAST_ASIAN_JIS78,
+ NS_FONT_VARIANT_EAST_ASIAN_RUBY, valueStr);
+ val->SetString(valueStr);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontVariantLigatures()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t intValue = StyleFont()->mFont.variantLigatures;
+
+ if (0 == intValue) {
+ val->SetIdent(eCSSKeyword_normal);
+ } else if (NS_FONT_VARIANT_LIGATURES_NONE == intValue) {
+ val->SetIdent(eCSSKeyword_none);
+ } else {
+ nsAutoString valueStr;
+
+ nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_font_variant_ligatures,
+ intValue, NS_FONT_VARIANT_LIGATURES_NONE,
+ NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL, valueStr);
+ val->SetString(valueStr);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontVariantNumeric()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t intValue = StyleFont()->mFont.variantNumeric;
+
+ if (0 == intValue) {
+ val->SetIdent(eCSSKeyword_normal);
+ } else {
+ nsAutoString valueStr;
+
+ nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_font_variant_numeric,
+ intValue, NS_FONT_VARIANT_NUMERIC_LINING,
+ NS_FONT_VARIANT_NUMERIC_ORDINAL, valueStr);
+ val->SetString(valueStr);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontVariantPosition()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t intValue = StyleFont()->mFont.variantPosition;
+
+ if (0 == intValue) {
+ val->SetIdent(eCSSKeyword_normal);
+ } else {
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(intValue,
+ nsCSSProps::kFontVariantPositionKTable));
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetBackgroundList(uint8_t nsStyleImageLayers::Layer::* aMember,
+ uint32_t nsStyleImageLayers::* aCount,
+ const nsStyleImageLayers& aLayers,
+ const KTableEntry aTable[])
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ for (uint32_t i = 0, i_end = aLayers.*aCount; i < i_end; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(aLayers.mLayers[i].*aMember,
+ aTable));
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundAttachment()
+{
+ return GetBackgroundList(&nsStyleImageLayers::Layer::mAttachment,
+ &nsStyleImageLayers::mAttachmentCount,
+ StyleBackground()->mImage,
+ nsCSSProps::kImageLayerAttachmentKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundClip()
+{
+ return GetBackgroundList(&nsStyleImageLayers::Layer::mClip,
+ &nsStyleImageLayers::mClipCount,
+ StyleBackground()->mImage,
+ nsCSSProps::kBackgroundClipKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetToRGBAColor(val, StyleBackground()->mBackgroundColor);
+ return val.forget();
+}
+
+static void
+SetValueToCalc(const nsStyleCoord::CalcValue* aCalc,
+ nsROCSSPrimitiveValue* aValue)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString tmp, result;
+
+ result.AppendLiteral("calc(");
+
+ val->SetAppUnits(aCalc->mLength);
+ val->GetCssText(tmp);
+ result.Append(tmp);
+
+ if (aCalc->mHasPercent) {
+ result.AppendLiteral(" + ");
+
+ val->SetPercent(aCalc->mPercent);
+ val->GetCssText(tmp);
+ result.Append(tmp);
+ }
+
+ result.Append(')');
+
+ aValue->SetString(result); // not really SetString
+}
+
+static void
+AppendCSSGradientLength(const nsStyleCoord& aValue,
+ nsROCSSPrimitiveValue* aPrimitive,
+ nsAString& aString)
+{
+ nsAutoString tokenString;
+ if (aValue.IsCalcUnit())
+ SetValueToCalc(aValue.GetCalcValue(), aPrimitive);
+ else if (aValue.GetUnit() == eStyleUnit_Coord)
+ aPrimitive->SetAppUnits(aValue.GetCoordValue());
+ else
+ aPrimitive->SetPercent(aValue.GetPercentValue());
+ aPrimitive->GetCssText(tokenString);
+ aString.Append(tokenString);
+}
+
+static void
+AppendCSSGradientToBoxPosition(const nsStyleGradient* aGradient,
+ nsAString& aString,
+ bool& aNeedSep)
+{
+ float xValue = aGradient->mBgPosX.GetPercentValue();
+ float yValue = aGradient->mBgPosY.GetPercentValue();
+
+ if (yValue == 1.0f && xValue == 0.5f) {
+ // omit "to bottom"
+ return;
+ }
+ NS_ASSERTION(yValue != 0.5f || xValue != 0.5f, "invalid box position");
+
+ aString.AppendLiteral("to");
+
+ if (yValue == 0.0f) {
+ aString.AppendLiteral(" top");
+ } else if (yValue == 1.0f) {
+ aString.AppendLiteral(" bottom");
+ } else if (yValue != 0.5f) { // do not write "center" keyword
+ NS_NOTREACHED("invalid box position");
+ }
+
+ if (xValue == 0.0f) {
+ aString.AppendLiteral(" left");
+ } else if (xValue == 1.0f) {
+ aString.AppendLiteral(" right");
+ } else if (xValue != 0.5f) { // do not write "center" keyword
+ NS_NOTREACHED("invalid box position");
+ }
+
+ aNeedSep = true;
+}
+
+void
+nsComputedDOMStyle::GetCSSGradientString(const nsStyleGradient* aGradient,
+ nsAString& aString)
+{
+ if (!aGradient->mLegacySyntax) {
+ aString.Truncate();
+ } else {
+ aString.AssignLiteral("-moz-");
+ }
+ if (aGradient->mRepeating) {
+ aString.AppendLiteral("repeating-");
+ }
+ bool isRadial = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR;
+ if (isRadial) {
+ aString.AppendLiteral("radial-gradient(");
+ } else {
+ aString.AppendLiteral("linear-gradient(");
+ }
+
+ bool needSep = false;
+ nsAutoString tokenString;
+ RefPtr<nsROCSSPrimitiveValue> tmpVal = new nsROCSSPrimitiveValue;
+
+ if (isRadial && !aGradient->mLegacySyntax) {
+ if (aGradient->mSize != NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE) {
+ if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
+ aString.AppendLiteral("circle");
+ needSep = true;
+ }
+ if (aGradient->mSize != NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER) {
+ if (needSep) {
+ aString.Append(' ');
+ }
+ AppendASCIItoUTF16(nsCSSProps::
+ ValueToKeyword(aGradient->mSize,
+ nsCSSProps::kRadialGradientSizeKTable),
+ aString);
+ needSep = true;
+ }
+ } else {
+ AppendCSSGradientLength(aGradient->mRadiusX, tmpVal, aString);
+ if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
+ aString.Append(' ');
+ AppendCSSGradientLength(aGradient->mRadiusY, tmpVal, aString);
+ }
+ needSep = true;
+ }
+ }
+ if (aGradient->mBgPosX.GetUnit() != eStyleUnit_None) {
+ MOZ_ASSERT(aGradient->mBgPosY.GetUnit() != eStyleUnit_None);
+ if (!isRadial && !aGradient->mLegacySyntax) {
+ AppendCSSGradientToBoxPosition(aGradient, aString, needSep);
+ } else if (aGradient->mBgPosX.GetUnit() != eStyleUnit_Percent ||
+ aGradient->mBgPosX.GetPercentValue() != 0.5f ||
+ aGradient->mBgPosY.GetUnit() != eStyleUnit_Percent ||
+ aGradient->mBgPosY.GetPercentValue() != (isRadial ? 0.5f : 1.0f)) {
+ if (isRadial && !aGradient->mLegacySyntax) {
+ if (needSep) {
+ aString.Append(' ');
+ }
+ aString.AppendLiteral("at ");
+ needSep = false;
+ }
+ AppendCSSGradientLength(aGradient->mBgPosX, tmpVal, aString);
+ if (aGradient->mBgPosY.GetUnit() != eStyleUnit_None) {
+ aString.Append(' ');
+ AppendCSSGradientLength(aGradient->mBgPosY, tmpVal, aString);
+ }
+ needSep = true;
+ }
+ }
+ if (aGradient->mAngle.GetUnit() != eStyleUnit_None) {
+ MOZ_ASSERT(!isRadial || aGradient->mLegacySyntax);
+ if (needSep) {
+ aString.Append(' ');
+ }
+ nsStyleUtil::AppendAngleValue(aGradient->mAngle, aString);
+ needSep = true;
+ }
+
+ if (isRadial && aGradient->mLegacySyntax &&
+ (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR ||
+ aGradient->mSize != NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER)) {
+ MOZ_ASSERT(aGradient->mSize != NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE);
+ if (needSep) {
+ aString.AppendLiteral(", ");
+ needSep = false;
+ }
+ if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
+ aString.AppendLiteral("circle");
+ needSep = true;
+ }
+ if (aGradient->mSize != NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER) {
+ if (needSep) {
+ aString.Append(' ');
+ }
+ AppendASCIItoUTF16(nsCSSProps::
+ ValueToKeyword(aGradient->mSize,
+ nsCSSProps::kRadialGradientSizeKTable),
+ aString);
+ }
+ needSep = true;
+ }
+
+
+ // color stops
+ for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) {
+ if (needSep) {
+ aString.AppendLiteral(", ");
+ }
+
+ const auto& stop = aGradient->mStops[i];
+ if (!stop.mIsInterpolationHint) {
+ SetToRGBAColor(tmpVal, stop.mColor);
+ tmpVal->GetCssText(tokenString);
+ aString.Append(tokenString);
+ }
+
+ if (stop.mLocation.GetUnit() != eStyleUnit_None) {
+ if (!stop.mIsInterpolationHint) {
+ aString.Append(' ');
+ }
+ AppendCSSGradientLength(stop.mLocation, tmpVal, aString);
+ }
+ needSep = true;
+ }
+
+ aString.Append(')');
+}
+
+// -moz-image-rect(<uri>, <top>, <right>, <bottom>, <left>)
+void
+nsComputedDOMStyle::GetImageRectString(nsIURI* aURI,
+ const nsStyleSides& aCropRect,
+ nsString& aString)
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ // <uri>
+ RefPtr<nsROCSSPrimitiveValue> valURI = new nsROCSSPrimitiveValue;
+ valURI->SetURI(aURI);
+ valueList->AppendCSSValue(valURI.forget());
+
+ // <top>, <right>, <bottom>, <left>
+ NS_FOR_CSS_SIDES(side) {
+ RefPtr<nsROCSSPrimitiveValue> valSide = new nsROCSSPrimitiveValue;
+ SetValueToCoord(valSide, aCropRect.Get(side), false);
+ valueList->AppendCSSValue(valSide.forget());
+ }
+
+ nsAutoString argumentString;
+ valueList->GetCssText(argumentString);
+
+ aString = NS_LITERAL_STRING("-moz-image-rect(") +
+ argumentString +
+ NS_LITERAL_STRING(")");
+}
+
+void
+nsComputedDOMStyle::SetValueToStyleImage(const nsStyleImage& aStyleImage,
+ nsROCSSPrimitiveValue* aValue)
+{
+ switch (aStyleImage.GetType()) {
+ case eStyleImageType_Image:
+ {
+ imgIRequest* req = aStyleImage.GetImageData();
+ if (!req) {
+ // XXXheycam If we had some problem resolving the imgRequestProxy,
+ // maybe we should just use the URL stored in the nsStyleImage's
+ // mImageValue? (Similarly in DoGetListStyleImage.)
+ aValue->SetIdent(eCSSKeyword_none);
+ break;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ req->GetURI(getter_AddRefs(uri));
+
+ const UniquePtr<nsStyleSides>& cropRect = aStyleImage.GetCropRect();
+ if (cropRect) {
+ nsAutoString imageRectString;
+ GetImageRectString(uri, *cropRect, imageRectString);
+ aValue->SetString(imageRectString);
+ } else {
+ aValue->SetURI(uri);
+ }
+ break;
+ }
+ case eStyleImageType_Gradient:
+ {
+ nsAutoString gradientString;
+ GetCSSGradientString(aStyleImage.GetGradientData(),
+ gradientString);
+ aValue->SetString(gradientString);
+ break;
+ }
+ case eStyleImageType_Element:
+ {
+ nsAutoString elementId;
+ nsStyleUtil::AppendEscapedCSSIdent(
+ nsDependentString(aStyleImage.GetElementId()), elementId);
+ nsAutoString elementString = NS_LITERAL_STRING("-moz-element(#") +
+ elementId +
+ NS_LITERAL_STRING(")");
+ aValue->SetString(elementString);
+ break;
+ }
+ case eStyleImageType_Null:
+ aValue->SetIdent(eCSSKeyword_none);
+ break;
+ default:
+ NS_NOTREACHED("unexpected image type");
+ break;
+ }
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetImageLayerImage(const nsStyleImageLayers& aLayers)
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ for (uint32_t i = 0, i_end = aLayers.mImageCount; i < i_end; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleImage& image = aLayers.mLayers[i].mImage;
+ // Layer::mImage::GetType() returns eStyleImageType_Null in two conditions:
+ // 1. The value of mask-image/bg-image is 'none'.
+ // Since this layer does not refer to any source, Layer::mSourceURI must
+ // be nullptr too.
+ // 2. This layer refers to a local resource, e.g. mask-image:url(#mymask).
+ // For local references, there is no need to download any external
+ // resource, so Layer::mImage is not used.
+ // Instead, we store the local URI in one place -- on Layer::mSourceURI.
+ // Hence, we must serialize using mSourceURI (instead of
+ // SetValueToStyleImage()/mImage) in this case.
+ if (aLayers.mLayers[i].mSourceURI &&
+ aLayers.mLayers[i].mSourceURI->IsLocalRef()) {
+ // This is how we represent a 'mask-image' reference for a local URI,
+ // such as 'mask-image:url(#mymask)' or 'mask:url(#mymask)'
+ SetValueToURLValue(aLayers.mLayers[i].mSourceURI, val);
+ } else {
+ SetValueToStyleImage(image, val);
+ }
+
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ return valueList.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();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetImageLayerPositionX(const nsStyleImageLayers& aLayers)
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+ for (uint32_t i = 0, i_end = aLayers.mPositionXCount; i < i_end; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToPositionCoord(aLayers.mLayers[i].mPosition.mXPosition, val);
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetImageLayerPositionY(const nsStyleImageLayers& aLayers)
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+ for (uint32_t i = 0, i_end = aLayers.mPositionYCount; i < i_end; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToPositionCoord(aLayers.mLayers[i].mPosition.mYPosition, val);
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetImageLayerRepeat(const nsStyleImageLayers& aLayers)
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ for (uint32_t i = 0, i_end = aLayers.mRepeatCount; i < i_end; ++i) {
+ RefPtr<nsDOMCSSValueList> itemList = GetROCSSValueList(false);
+ RefPtr<nsROCSSPrimitiveValue> valX = new nsROCSSPrimitiveValue;
+
+ const uint8_t& xRepeat = aLayers.mLayers[i].mRepeat.mXRepeat;
+ const uint8_t& yRepeat = aLayers.mLayers[i].mRepeat.mYRepeat;
+
+ bool hasContraction = true;
+ unsigned contraction;
+ if (xRepeat == yRepeat) {
+ contraction = xRepeat;
+ } else if (xRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
+ yRepeat == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT) {
+ contraction = NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X;
+ } else if (xRepeat == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT &&
+ yRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT) {
+ contraction = NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y;
+ } else {
+ hasContraction = false;
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> valY;
+ if (hasContraction) {
+ valX->SetIdent(nsCSSProps::ValueToKeywordEnum(contraction,
+ nsCSSProps::kImageLayerRepeatKTable));
+ } else {
+ valY = new nsROCSSPrimitiveValue;
+
+ valX->SetIdent(nsCSSProps::ValueToKeywordEnum(xRepeat,
+ nsCSSProps::kImageLayerRepeatKTable));
+ valY->SetIdent(nsCSSProps::ValueToKeywordEnum(yRepeat,
+ nsCSSProps::kImageLayerRepeatKTable));
+ }
+ itemList->AppendCSSValue(valX.forget());
+ if (valY) {
+ itemList->AppendCSSValue(valY.forget());
+ }
+ valueList->AppendCSSValue(itemList.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetImageLayerSize(const nsStyleImageLayers& aLayers)
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ for (uint32_t i = 0, i_end = aLayers.mSizeCount; i < i_end; ++i) {
+ const nsStyleImageLayers::Size &size = aLayers.mLayers[i].mSize;
+
+ switch (size.mWidthType) {
+ case nsStyleImageLayers::Size::eContain:
+ case nsStyleImageLayers::Size::eCover: {
+ MOZ_ASSERT(size.mWidthType == size.mHeightType,
+ "unsynced types");
+ nsCSSKeyword keyword = size.mWidthType == nsStyleImageLayers::Size::eContain
+ ? eCSSKeyword_contain
+ : eCSSKeyword_cover;
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(keyword);
+ valueList->AppendCSSValue(val.forget());
+ break;
+ }
+ default: {
+ RefPtr<nsDOMCSSValueList> itemList = GetROCSSValueList(false);
+
+ RefPtr<nsROCSSPrimitiveValue> valX = new nsROCSSPrimitiveValue;
+ RefPtr<nsROCSSPrimitiveValue> valY = new nsROCSSPrimitiveValue;
+
+ if (size.mWidthType == nsStyleImageLayers::Size::eAuto) {
+ valX->SetIdent(eCSSKeyword_auto);
+ } else {
+ MOZ_ASSERT(size.mWidthType ==
+ nsStyleImageLayers::Size::eLengthPercentage,
+ "bad mWidthType");
+ if (!size.mWidth.mHasPercent &&
+ // negative values must have come from calc()
+ size.mWidth.mLength >= 0) {
+ MOZ_ASSERT(size.mWidth.mPercent == 0.0f,
+ "Shouldn't have mPercent");
+ valX->SetAppUnits(size.mWidth.mLength);
+ } else if (size.mWidth.mLength == 0 &&
+ // negative values must have come from calc()
+ size.mWidth.mPercent >= 0.0f) {
+ valX->SetPercent(size.mWidth.mPercent);
+ } else {
+ SetValueToCalc(&size.mWidth, valX);
+ }
+ }
+
+ if (size.mHeightType == nsStyleImageLayers::Size::eAuto) {
+ valY->SetIdent(eCSSKeyword_auto);
+ } else {
+ MOZ_ASSERT(size.mHeightType ==
+ nsStyleImageLayers::Size::eLengthPercentage,
+ "bad mHeightType");
+ if (!size.mHeight.mHasPercent &&
+ // negative values must have come from calc()
+ size.mHeight.mLength >= 0) {
+ MOZ_ASSERT(size.mHeight.mPercent == 0.0f,
+ "Shouldn't have mPercent");
+ valY->SetAppUnits(size.mHeight.mLength);
+ } else if (size.mHeight.mLength == 0 &&
+ // negative values must have come from calc()
+ size.mHeight.mPercent >= 0.0f) {
+ valY->SetPercent(size.mHeight.mPercent);
+ } else {
+ SetValueToCalc(&size.mHeight, valY);
+ }
+ }
+ itemList->AppendCSSValue(valX.forget());
+ itemList->AppendCSSValue(valY.forget());
+ valueList->AppendCSSValue(itemList.forget());
+ break;
+ }
+ }
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundImage()
+{
+ const nsStyleImageLayers& layers = StyleBackground()->mImage;
+ return DoGetImageLayerImage(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundBlendMode()
+{
+ return GetBackgroundList(&nsStyleImageLayers::Layer::mBlendMode,
+ &nsStyleImageLayers::mBlendModeCount,
+ StyleBackground()->mImage,
+ nsCSSProps::kBlendModeKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundOrigin()
+{
+ return GetBackgroundList(&nsStyleImageLayers::Layer::mOrigin,
+ &nsStyleImageLayers::mOriginCount,
+ StyleBackground()->mImage,
+ nsCSSProps::kImageLayerOriginKTable);
+}
+
+void
+nsComputedDOMStyle::SetValueToPositionCoord(
+ const Position::Coord& aCoord,
+ nsROCSSPrimitiveValue* aValue)
+{
+ if (!aCoord.mHasPercent) {
+ MOZ_ASSERT(aCoord.mPercent == 0.0f,
+ "Shouldn't have mPercent!");
+ aValue->SetAppUnits(aCoord.mLength);
+ } else if (aCoord.mLength == 0) {
+ aValue->SetPercent(aCoord.mPercent);
+ } else {
+ SetValueToCalc(&aCoord, aValue);
+ }
+}
+
+void
+nsComputedDOMStyle::SetValueToPosition(
+ const Position& aPosition,
+ nsDOMCSSValueList* aValueList)
+{
+ RefPtr<nsROCSSPrimitiveValue> valX = new nsROCSSPrimitiveValue;
+ SetValueToPositionCoord(aPosition.mXPosition, valX);
+ aValueList->AppendCSSValue(valX.forget());
+
+ RefPtr<nsROCSSPrimitiveValue> valY = new nsROCSSPrimitiveValue;
+ SetValueToPositionCoord(aPosition.mYPosition, valY);
+ aValueList->AppendCSSValue(valY.forget());
+}
+
+
+void
+nsComputedDOMStyle::SetValueToURLValue(const css::URLValueData* aURL,
+ nsROCSSPrimitiveValue* aValue)
+{
+ if (aURL && aURL->IsLocalRef()) {
+ nsString fragment;
+ aURL->GetSourceString(fragment);
+ fragment.Insert(u"url(\"", 0);
+ fragment.Append(u"\")");
+ aValue->SetString(fragment);
+ } else {
+ nsCOMPtr<nsIURI> url;
+ if (aURL && (url = aURL->GetURI())) {
+ aValue->SetURI(url);
+ } else {
+ aValue->SetIdent(eCSSKeyword_none);
+ }
+ }
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundPosition()
+{
+ const nsStyleImageLayers& layers = StyleBackground()->mImage;
+ return DoGetImageLayerPosition(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundPositionX()
+{
+ const nsStyleImageLayers& layers = StyleBackground()->mImage;
+ return DoGetImageLayerPositionX(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundPositionY()
+{
+ const nsStyleImageLayers& layers = StyleBackground()->mImage;
+ return DoGetImageLayerPositionY(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundRepeat()
+{
+ const nsStyleImageLayers& layers = StyleBackground()->mImage;
+ return DoGetImageLayerRepeat(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBackgroundSize()
+{
+ const nsStyleImageLayers& layers = StyleBackground()->mImage;
+ return DoGetImageLayerSize(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridTemplateAreas()
+{
+ const css::GridTemplateAreasValue* areas =
+ StylePosition()->mGridTemplateAreas;
+ if (!areas) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+
+ MOZ_ASSERT(!areas->mTemplates.IsEmpty(),
+ "Unexpected empty array in GridTemplateAreasValue");
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ for (uint32_t i = 0; i < areas->mTemplates.Length(); i++) {
+ nsAutoString str;
+ nsStyleUtil::AppendEscapedCSSString(areas->mTemplates[i], str);
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetString(str);
+ valueList->AppendCSSValue(val.forget());
+ }
+ return valueList.forget();
+}
+
+void
+nsComputedDOMStyle::AppendGridLineNames(nsString& aResult,
+ const nsTArray<nsString>& aLineNames)
+{
+ uint32_t numLines = aLineNames.Length();
+ if (numLines == 0) {
+ return;
+ }
+ for (uint32_t i = 0;;) {
+ nsStyleUtil::AppendEscapedCSSIdent(aLineNames[i], aResult);
+ if (++i == numLines) {
+ break;
+ }
+ aResult.Append(' ');
+ }
+}
+
+void
+nsComputedDOMStyle::AppendGridLineNames(nsDOMCSSValueList* aValueList,
+ const nsTArray<nsString>& aLineNames,
+ bool aSuppressEmptyList)
+{
+ if (aLineNames.IsEmpty() && aSuppressEmptyList) {
+ return;
+ }
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString lineNamesString;
+ lineNamesString.Assign('[');
+ AppendGridLineNames(lineNamesString, aLineNames);
+ lineNamesString.Append(']');
+ val->SetString(lineNamesString);
+ aValueList->AppendCSSValue(val.forget());
+}
+
+void
+nsComputedDOMStyle::AppendGridLineNames(nsDOMCSSValueList* aValueList,
+ const nsTArray<nsString>& aLineNames1,
+ const nsTArray<nsString>& aLineNames2)
+{
+ if (aLineNames1.IsEmpty() && aLineNames2.IsEmpty()) {
+ return;
+ }
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString lineNamesString;
+ lineNamesString.Assign('[');
+ if (!aLineNames1.IsEmpty()) {
+ AppendGridLineNames(lineNamesString, aLineNames1);
+ }
+ if (!aLineNames2.IsEmpty()) {
+ if (!aLineNames1.IsEmpty()) {
+ lineNamesString.Append(' ');
+ }
+ AppendGridLineNames(lineNamesString, aLineNames2);
+ }
+ lineNamesString.Append(']');
+ val->SetString(lineNamesString);
+ aValueList->AppendCSSValue(val.forget());
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetGridTrackSize(const nsStyleCoord& aMinValue,
+ const nsStyleCoord& aMaxValue)
+{
+ if (aMinValue.GetUnit() == eStyleUnit_None) {
+ // A fit-content() function.
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString argumentStr, fitContentStr;
+ fitContentStr.AppendLiteral("fit-content(");
+ MOZ_ASSERT(aMaxValue.IsCoordPercentCalcUnit(),
+ "unexpected unit for fit-content() argument value");
+ SetValueToCoord(val, aMaxValue, true);
+ val->GetCssText(argumentStr);
+ fitContentStr.Append(argumentStr);
+ fitContentStr.Append(char16_t(')'));
+ val->SetString(fitContentStr);
+ return val.forget();
+ }
+
+ if (aMinValue == aMaxValue) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, aMinValue, true,
+ nullptr, nsCSSProps::kGridTrackBreadthKTable);
+ return val.forget();
+ }
+
+ // minmax(auto, <flex>) is equivalent to (and is our internal representation
+ // of) <flex>, and both compute to <flex>
+ if (aMinValue.GetUnit() == eStyleUnit_Auto &&
+ aMaxValue.GetUnit() == eStyleUnit_FlexFraction) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, aMaxValue, true);
+ return val.forget();
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString argumentStr, minmaxStr;
+ minmaxStr.AppendLiteral("minmax(");
+
+ SetValueToCoord(val, aMinValue, true,
+ nullptr, nsCSSProps::kGridTrackBreadthKTable);
+ val->GetCssText(argumentStr);
+ minmaxStr.Append(argumentStr);
+
+ minmaxStr.AppendLiteral(", ");
+
+ SetValueToCoord(val, aMaxValue, true,
+ nullptr, nsCSSProps::kGridTrackBreadthKTable);
+ val->GetCssText(argumentStr);
+ minmaxStr.Append(argumentStr);
+
+ minmaxStr.Append(char16_t(')'));
+ val->SetString(minmaxStr);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetGridTemplateColumnsRows(
+ const nsStyleGridTemplate& aTrackList,
+ const ComputedGridTrackInfo* aTrackInfo)
+{
+ if (aTrackList.mIsSubgrid) {
+ // XXX TODO: add support for repeat(auto-fill) for 'subgrid' (bug 1234311)
+ NS_ASSERTION(aTrackList.mMinTrackSizingFunctions.IsEmpty() &&
+ aTrackList.mMaxTrackSizingFunctions.IsEmpty(),
+ "Unexpected sizing functions with subgrid");
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ RefPtr<nsROCSSPrimitiveValue> subgridKeyword = new nsROCSSPrimitiveValue;
+ subgridKeyword->SetIdent(eCSSKeyword_subgrid);
+ valueList->AppendCSSValue(subgridKeyword.forget());
+
+ for (uint32_t i = 0, len = aTrackList.mLineNameLists.Length(); ; ++i) {
+ if (MOZ_UNLIKELY(aTrackList.IsRepeatAutoIndex(i))) {
+ MOZ_ASSERT(aTrackList.mIsAutoFill, "subgrid can only have 'auto-fill'");
+ MOZ_ASSERT(aTrackList.mRepeatAutoLineNameListAfter.IsEmpty(),
+ "mRepeatAutoLineNameListAfter isn't used for subgrid");
+ RefPtr<nsROCSSPrimitiveValue> start = new nsROCSSPrimitiveValue;
+ start->SetString(NS_LITERAL_STRING("repeat(auto-fill,"));
+ valueList->AppendCSSValue(start.forget());
+ AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListBefore,
+ /*aSuppressEmptyList*/ false);
+ RefPtr<nsROCSSPrimitiveValue> end = new nsROCSSPrimitiveValue;
+ end->SetString(NS_LITERAL_STRING(")"));
+ valueList->AppendCSSValue(end.forget());
+ }
+ if (i == len) {
+ break;
+ }
+ AppendGridLineNames(valueList, aTrackList.mLineNameLists[i],
+ /*aSuppressEmptyList*/ false);
+ }
+ return valueList.forget();
+ }
+
+ uint32_t numSizes = aTrackList.mMinTrackSizingFunctions.Length();
+ MOZ_ASSERT(aTrackList.mMaxTrackSizingFunctions.Length() == numSizes,
+ "Different number of min and max track sizing functions");
+ if (aTrackInfo) {
+ DebugOnly<bool> isAutoFill =
+ aTrackList.HasRepeatAuto() && aTrackList.mIsAutoFill;
+ DebugOnly<bool> isAutoFit =
+ aTrackList.HasRepeatAuto() && !aTrackList.mIsAutoFill;
+ DebugOnly<uint32_t> numExplicitTracks = aTrackInfo->mNumExplicitTracks;
+ MOZ_ASSERT(numExplicitTracks == numSizes ||
+ (isAutoFill && numExplicitTracks >= numSizes) ||
+ (isAutoFit && numExplicitTracks + 1 >= numSizes),
+ "expected all explicit tracks (or possibly one less, if there's "
+ "an 'auto-fit' track, since that can collapse away)");
+ numSizes = aTrackInfo->mSizes.Length();
+ }
+
+ // An empty <track-list> without repeats is represented as "none" in syntax.
+ if (numSizes == 0 && !aTrackList.HasRepeatAuto()) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ if (aTrackInfo) {
+ // 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.
+ const nsTArray<nscoord>& trackSizes = aTrackInfo->mSizes;
+ const uint32_t numExplicitTracks = aTrackInfo->mNumExplicitTracks;
+ const uint32_t numLeadingImplicitTracks = aTrackInfo->mNumLeadingImplicitTracks;
+ MOZ_ASSERT(numSizes >= numLeadingImplicitTracks + numExplicitTracks);
+
+ // Add any leading implicit tracks.
+ for (uint32_t i = 0; i < numLeadingImplicitTracks; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(trackSizes[i]);
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ // Then add any explicit tracks and removed auto-fit tracks.
+ if (numExplicitTracks || aTrackList.HasRepeatAuto()) {
+ int32_t endOfRepeat = 0; // first index after any repeat() tracks
+ int32_t offsetToLastRepeat = 0;
+ if (aTrackList.HasRepeatAuto()) {
+ // offsetToLastRepeat is -1 if all repeat(auto-fit) tracks are empty
+ offsetToLastRepeat = numExplicitTracks + 1 - aTrackList.mLineNameLists.Length();
+ endOfRepeat = aTrackList.mRepeatAutoIndex + offsetToLastRepeat + 1;
+ }
+
+ uint32_t repeatIndex = 0;
+ uint32_t numRepeatTracks = aTrackInfo->mRemovedRepeatTracks.Length();
+ enum LinePlacement { LinesPrecede, LinesFollow, LinesBetween };
+ auto AppendRemovedAutoFits = [this, aTrackInfo, &valueList, aTrackList,
+ &repeatIndex,
+ numRepeatTracks](LinePlacement aPlacement)
+ {
+ // Add in removed auto-fit tracks and lines here, if necessary
+ bool atLeastOneTrackReported = false;
+ while (repeatIndex < numRepeatTracks &&
+ aTrackInfo->mRemovedRepeatTracks[repeatIndex]) {
+ if ((aPlacement == LinesPrecede) ||
+ ((aPlacement == LinesBetween) && atLeastOneTrackReported)) {
+ // Precede it with the lines between repeats.
+ AppendGridLineNames(valueList,
+ aTrackList.mRepeatAutoLineNameListAfter,
+ aTrackList.mRepeatAutoLineNameListBefore);
+ }
+
+ // Removed 'auto-fit' tracks are reported as 0px.
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(0);
+ valueList->AppendCSSValue(val.forget());
+ atLeastOneTrackReported = true;
+
+ if (aPlacement == LinesFollow) {
+ // Follow it with the lines between repeats.
+ AppendGridLineNames(valueList,
+ aTrackList.mRepeatAutoLineNameListAfter,
+ aTrackList.mRepeatAutoLineNameListBefore);
+ }
+ repeatIndex++;
+ }
+ repeatIndex++;
+ };
+
+ for (int32_t i = 0;; i++) {
+ if (aTrackList.HasRepeatAuto()) {
+ if (i == aTrackList.mRepeatAutoIndex) {
+ const nsTArray<nsString>& lineNames = aTrackList.mLineNameLists[i];
+ if (i == endOfRepeat) {
+ // All auto-fit tracks are empty, but we report them anyway.
+ AppendGridLineNames(valueList, lineNames,
+ aTrackList.mRepeatAutoLineNameListBefore);
+
+ AppendRemovedAutoFits(LinesBetween);
+
+ AppendGridLineNames(valueList,
+ aTrackList.mRepeatAutoLineNameListAfter,
+ aTrackList.mLineNameLists[i + 1]);
+ } else {
+ AppendGridLineNames(valueList, lineNames,
+ aTrackList.mRepeatAutoLineNameListBefore);
+ AppendRemovedAutoFits(LinesFollow);
+ }
+ } else if (i == endOfRepeat) {
+ // Before appending the last line, finish off any removed auto-fits.
+ AppendRemovedAutoFits(LinesPrecede);
+
+ const nsTArray<nsString>& lineNames =
+ aTrackList.mLineNameLists[aTrackList.mRepeatAutoIndex + 1];
+ AppendGridLineNames(valueList,
+ aTrackList.mRepeatAutoLineNameListAfter,
+ lineNames);
+ } else if (i > aTrackList.mRepeatAutoIndex && i < endOfRepeat) {
+ AppendGridLineNames(valueList,
+ aTrackList.mRepeatAutoLineNameListAfter,
+ aTrackList.mRepeatAutoLineNameListBefore);
+ AppendRemovedAutoFits(LinesFollow);
+ } else {
+ uint32_t j = i > endOfRepeat ? i - offsetToLastRepeat : i;
+ const nsTArray<nsString>& lineNames = aTrackList.mLineNameLists[j];
+ AppendGridLineNames(valueList, lineNames);
+ }
+ } else {
+ const nsTArray<nsString>& lineNames = aTrackList.mLineNameLists[i];
+ AppendGridLineNames(valueList, lineNames);
+ }
+ if (uint32_t(i) == numExplicitTracks) {
+ break;
+ }
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(trackSizes[i + numLeadingImplicitTracks]);
+ valueList->AppendCSSValue(val.forget());
+ }
+ }
+
+ // Add any trailing implicit tracks.
+ for (uint32_t i = numLeadingImplicitTracks + numExplicitTracks;
+ i < numSizes; ++i) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(trackSizes[i]);
+ valueList->AppendCSSValue(val.forget());
+ }
+ } else {
+ // We don't have a frame. So, we'll just return a serialization of
+ // the tracks from the style (without resolved sizes).
+ for (uint32_t i = 0;; i++) {
+ const nsTArray<nsString>& lineNames = aTrackList.mLineNameLists[i];
+ if (!lineNames.IsEmpty()) {
+ AppendGridLineNames(valueList, lineNames);
+ }
+ if (i == numSizes) {
+ break;
+ }
+ if (MOZ_UNLIKELY(aTrackList.IsRepeatAutoIndex(i))) {
+ RefPtr<nsROCSSPrimitiveValue> start = new nsROCSSPrimitiveValue;
+ start->SetString(aTrackList.mIsAutoFill ? NS_LITERAL_STRING("repeat(auto-fill,")
+ : NS_LITERAL_STRING("repeat(auto-fit,"));
+ valueList->AppendCSSValue(start.forget());
+ if (!aTrackList.mRepeatAutoLineNameListBefore.IsEmpty()) {
+ AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListBefore);
+ }
+
+ valueList->AppendCSSValue(
+ GetGridTrackSize(aTrackList.mMinTrackSizingFunctions[i],
+ aTrackList.mMaxTrackSizingFunctions[i]));
+ if (!aTrackList.mRepeatAutoLineNameListAfter.IsEmpty()) {
+ AppendGridLineNames(valueList, aTrackList.mRepeatAutoLineNameListAfter);
+ }
+ RefPtr<nsROCSSPrimitiveValue> end = new nsROCSSPrimitiveValue;
+ end->SetString(NS_LITERAL_STRING(")"));
+ valueList->AppendCSSValue(end.forget());
+ } else {
+ valueList->AppendCSSValue(
+ GetGridTrackSize(aTrackList.mMinTrackSizingFunctions[i],
+ aTrackList.mMaxTrackSizingFunctions[i]));
+ }
+ }
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridAutoFlow()
+{
+ nsAutoString str;
+ nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_grid_auto_flow,
+ StylePosition()->mGridAutoFlow,
+ NS_STYLE_GRID_AUTO_FLOW_ROW,
+ NS_STYLE_GRID_AUTO_FLOW_DENSE,
+ str);
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetString(str);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridAutoColumns()
+{
+ return GetGridTrackSize(StylePosition()->mGridAutoColumnsMin,
+ StylePosition()->mGridAutoColumnsMax);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridAutoRows()
+{
+ return GetGridTrackSize(StylePosition()->mGridAutoRowsMin,
+ StylePosition()->mGridAutoRowsMax);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridTemplateColumns()
+{
+ const ComputedGridTrackInfo* info = nullptr;
+
+ nsGridContainerFrame* gridFrame =
+ nsGridContainerFrame::GetGridFrameWithComputedInfo(
+ mContent->GetPrimaryFrame());
+
+ if (gridFrame) {
+ info = gridFrame->GetComputedTemplateColumns();
+ }
+
+ return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateColumns, info);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridTemplateRows()
+{
+ const ComputedGridTrackInfo* info = nullptr;
+
+ nsGridContainerFrame* gridFrame =
+ nsGridContainerFrame::GetGridFrameWithComputedInfo(
+ mContent->GetPrimaryFrame());
+
+ if (gridFrame) {
+ info = gridFrame->GetComputedTemplateRows();
+ }
+
+ return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateRows, info);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetGridLine(const nsStyleGridLine& aGridLine)
+{
+ if (aGridLine.IsAuto()) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_auto);
+ return val.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ if (aGridLine.mHasSpan) {
+ RefPtr<nsROCSSPrimitiveValue> span = new nsROCSSPrimitiveValue;
+ span->SetIdent(eCSSKeyword_span);
+ valueList->AppendCSSValue(span.forget());
+ }
+
+ if (aGridLine.mInteger != 0) {
+ RefPtr<nsROCSSPrimitiveValue> integer = new nsROCSSPrimitiveValue;
+ integer->SetNumber(aGridLine.mInteger);
+ valueList->AppendCSSValue(integer.forget());
+ }
+
+ if (!aGridLine.mLineName.IsEmpty()) {
+ RefPtr<nsROCSSPrimitiveValue> lineName = new nsROCSSPrimitiveValue;
+ nsString escapedLineName;
+ nsStyleUtil::AppendEscapedCSSIdent(aGridLine.mLineName, escapedLineName);
+ lineName->SetString(escapedLineName);
+ valueList->AppendCSSValue(lineName.forget());
+ }
+
+ NS_ASSERTION(valueList->Length() > 0,
+ "Should have appended at least one value");
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridColumnStart()
+{
+ return GetGridLine(StylePosition()->mGridColumnStart);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridColumnEnd()
+{
+ return GetGridLine(StylePosition()->mGridColumnEnd);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridRowStart()
+{
+ return GetGridLine(StylePosition()->mGridRowStart);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridRowEnd()
+{
+ return GetGridLine(StylePosition()->mGridRowEnd);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridColumnGap()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StylePosition()->mGridColumnGap, true);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetGridRowGap()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StylePosition()->mGridRowGap, true);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPaddingTop()
+{
+ return GetPaddingWidthFor(NS_SIDE_TOP);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPaddingBottom()
+{
+ return GetPaddingWidthFor(NS_SIDE_BOTTOM);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPaddingLeft()
+{
+ return GetPaddingWidthFor(NS_SIDE_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPaddingRight()
+{
+ return GetPaddingWidthFor(NS_SIDE_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderCollapse()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleTableBorder()->mBorderCollapse,
+ nsCSSProps::kBorderCollapseKTable));
+ return val.forget();
+}
+
+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::DoGetCaptionSide()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleTableBorder()->mCaptionSide,
+ nsCSSProps::kCaptionSideKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetEmptyCells()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleTableBorder()->mEmptyCells,
+ nsCSSProps::kEmptyCellsKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTableLayout()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleTable()->mLayoutStrategy,
+ nsCSSProps::kTableLayoutKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderTopStyle()
+{
+ return GetBorderStyleFor(NS_SIDE_TOP);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderBottomStyle()
+{
+ return GetBorderStyleFor(NS_SIDE_BOTTOM);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderLeftStyle()
+{
+ return GetBorderStyleFor(NS_SIDE_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderRightStyle()
+{
+ return GetBorderStyleFor(NS_SIDE_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderBottomColors()
+{
+ return GetBorderColorsFor(NS_SIDE_BOTTOM);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderLeftColors()
+{
+ return GetBorderColorsFor(NS_SIDE_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderRightColors()
+{
+ return GetBorderColorsFor(NS_SIDE_RIGHT);
+}
+
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderTopColors()
+{
+ return GetBorderColorsFor(NS_SIDE_TOP);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderBottomLeftRadius()
+{
+ return GetEllipseRadii(StyleBorder()->mBorderRadius,
+ NS_CORNER_BOTTOM_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderBottomRightRadius()
+{
+ return GetEllipseRadii(StyleBorder()->mBorderRadius,
+ NS_CORNER_BOTTOM_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderTopLeftRadius()
+{
+ return GetEllipseRadii(StyleBorder()->mBorderRadius,
+ NS_CORNER_TOP_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderTopRightRadius()
+{
+ return GetEllipseRadii(StyleBorder()->mBorderRadius,
+ NS_CORNER_TOP_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderTopWidth()
+{
+ return GetBorderWidthFor(NS_SIDE_TOP);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderBottomWidth()
+{
+ return GetBorderWidthFor(NS_SIDE_BOTTOM);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderLeftWidth()
+{
+ return GetBorderWidthFor(NS_SIDE_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderRightWidth()
+{
+ return GetBorderWidthFor(NS_SIDE_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderTopColor()
+{
+ return GetBorderColorFor(NS_SIDE_TOP);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderBottomColor()
+{
+ return GetBorderColorFor(NS_SIDE_BOTTOM);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderLeftColor()
+{
+ return GetBorderColorFor(NS_SIDE_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderRightColor()
+{
+ return GetBorderColorFor(NS_SIDE_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMarginTopWidth()
+{
+ return GetMarginWidthFor(NS_SIDE_TOP);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMarginBottomWidth()
+{
+ return GetMarginWidthFor(NS_SIDE_BOTTOM);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMarginLeftWidth()
+{
+ return GetMarginWidthFor(NS_SIDE_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMarginRightWidth()
+{
+ return GetMarginWidthFor(NS_SIDE_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOrient()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mOrient,
+ nsCSSProps::kOrientKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetScrollBehavior()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mScrollBehavior,
+ nsCSSProps::kScrollBehaviorKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetScrollSnapType()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+ if (display->mScrollSnapTypeX != display->mScrollSnapTypeY) {
+ // No value to return. We can't express this combination of
+ // values as a shorthand.
+ return nullptr;
+ }
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mScrollSnapTypeX,
+ nsCSSProps::kScrollSnapTypeKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetScrollSnapTypeX()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mScrollSnapTypeX,
+ nsCSSProps::kScrollSnapTypeKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetScrollSnapTypeY()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mScrollSnapTypeY,
+ nsCSSProps::kScrollSnapTypeKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetScrollSnapPoints(const nsStyleCoord& aCoord)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ if (aCoord.GetUnit() == eStyleUnit_None) {
+ val->SetIdent(eCSSKeyword_none);
+ } else {
+ nsAutoString argumentString;
+ SetCssTextToCoord(argumentString, aCoord);
+ nsAutoString tmp;
+ tmp.AppendLiteral("repeat(");
+ tmp.Append(argumentString);
+ tmp.Append(')');
+ val->SetString(tmp);
+ }
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetScrollSnapPointsX()
+{
+ return GetScrollSnapPoints(StyleDisplay()->mScrollSnapPointsX);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetScrollSnapPointsY()
+{
+ return GetScrollSnapPoints(StyleDisplay()->mScrollSnapPointsY);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetScrollSnapDestination()
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ SetValueToPosition(StyleDisplay()->mScrollSnapDestination, valueList);
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetScrollSnapCoordinate()
+{
+ const nsStyleDisplay* sd = StyleDisplay();
+ if (sd->mScrollSnapCoordinate.IsEmpty()) {
+ // Having no snap coordinates is interpreted as "none"
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ } else {
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+ for (size_t i = 0, i_end = sd->mScrollSnapCoordinate.Length(); i < i_end; ++i) {
+ RefPtr<nsDOMCSSValueList> itemList = GetROCSSValueList(false);
+ SetValueToPosition(sd->mScrollSnapCoordinate[i], itemList);
+ valueList->AppendCSSValue(itemList.forget());
+ }
+ return valueList.forget();
+ }
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOutlineWidth()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleOutline* outline = StyleOutline();
+
+ nscoord width;
+ if (outline->mOutlineStyle == NS_STYLE_BORDER_STYLE_NONE) {
+ NS_ASSERTION(outline->GetOutlineWidth() == 0, "unexpected width");
+ width = 0;
+ } else {
+ width = outline->GetOutlineWidth();
+ }
+ val->SetAppUnits(width);
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOutlineStyle()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleOutline()->mOutlineStyle,
+ nsCSSProps::kOutlineStyleKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOutlineOffset()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(StyleOutline()->mOutlineOffset);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOutlineRadiusBottomLeft()
+{
+ return GetEllipseRadii(StyleOutline()->mOutlineRadius,
+ NS_CORNER_BOTTOM_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOutlineRadiusBottomRight()
+{
+ return GetEllipseRadii(StyleOutline()->mOutlineRadius,
+ NS_CORNER_BOTTOM_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOutlineRadiusTopLeft()
+{
+ return GetEllipseRadii(StyleOutline()->mOutlineRadius,
+ NS_CORNER_TOP_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOutlineRadiusTopRight()
+{
+ return GetEllipseRadii(StyleOutline()->mOutlineRadius,
+ NS_CORNER_TOP_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOutlineColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueFromComplexColor(val, StyleOutline()->mOutlineColor);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetEllipseRadii(const nsStyleCorners& aRadius,
+ uint8_t aFullCorner)
+{
+ nsStyleCoord radiusX = aRadius.Get(NS_FULL_TO_HALF_CORNER(aFullCorner, false));
+ nsStyleCoord radiusY = aRadius.Get(NS_FULL_TO_HALF_CORNER(aFullCorner, true));
+
+ // for compatibility, return a single value if X and Y are equal
+ if (radiusX == radiusY) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, radiusX, true);
+ return val.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ RefPtr<nsROCSSPrimitiveValue> valX = new nsROCSSPrimitiveValue;
+ RefPtr<nsROCSSPrimitiveValue> valY = new nsROCSSPrimitiveValue;
+
+ SetValueToCoord(valX, radiusX, true);
+ SetValueToCoord(valY, radiusY, true);
+
+ valueList->AppendCSSValue(valX.forget());
+ valueList->AppendCSSValue(valY.forget());
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetCSSShadowArray(nsCSSShadowArray* aArray,
+ const nscolor& aDefaultColor,
+ bool aIsBoxShadow)
+{
+ if (!aArray) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+
+ static nscoord nsCSSShadowItem::* const shadowValuesNoSpread[] = {
+ &nsCSSShadowItem::mXOffset,
+ &nsCSSShadowItem::mYOffset,
+ &nsCSSShadowItem::mRadius
+ };
+
+ static nscoord nsCSSShadowItem::* const shadowValuesWithSpread[] = {
+ &nsCSSShadowItem::mXOffset,
+ &nsCSSShadowItem::mYOffset,
+ &nsCSSShadowItem::mRadius,
+ &nsCSSShadowItem::mSpread
+ };
+
+ nscoord nsCSSShadowItem::* const * shadowValues;
+ uint32_t shadowValuesLength;
+ if (aIsBoxShadow) {
+ shadowValues = shadowValuesWithSpread;
+ shadowValuesLength = ArrayLength(shadowValuesWithSpread);
+ } else {
+ shadowValues = shadowValuesNoSpread;
+ shadowValuesLength = ArrayLength(shadowValuesNoSpread);
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ for (nsCSSShadowItem *item = aArray->ShadowAt(0),
+ *item_end = item + aArray->Length();
+ item < item_end; ++item) {
+ RefPtr<nsDOMCSSValueList> itemList = GetROCSSValueList(false);
+
+ // Color is either the specified shadow color or the foreground color
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nscolor shadowColor;
+ if (item->mHasColor) {
+ shadowColor = item->mColor;
+ } else {
+ shadowColor = aDefaultColor;
+ }
+ SetToRGBAColor(val, shadowColor);
+ itemList->AppendCSSValue(val.forget());
+
+ // Set the offsets, blur radius, and spread if available
+ for (uint32_t i = 0; i < shadowValuesLength; ++i) {
+ val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(item->*(shadowValues[i]));
+ itemList->AppendCSSValue(val.forget());
+ }
+
+ if (item->mInset && aIsBoxShadow) {
+ // This is an inset box-shadow
+ val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(
+ uint8_t(StyleBoxShadowType::Inset),
+ nsCSSProps::kBoxShadowTypeKTable));
+ itemList->AppendCSSValue(val.forget());
+ }
+ valueList->AppendCSSValue(itemList.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBoxDecorationBreak()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleBorder()->mBoxDecorationBreak,
+ nsCSSProps::kBoxDecorationBreakKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBoxShadow()
+{
+ return GetCSSShadowArray(StyleEffects()->mBoxShadow,
+ StyleColor()->mColor,
+ true);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetZIndex()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StylePosition()->mZIndex, false);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetListStyleImage()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleList* list = StyleList();
+
+ // XXXheycam As in SetValueToStyleImage, we might want to use the
+ // URL stored in the nsStyleImageRequest's mImageValue if we
+ // failed to resolve the imgRequestProxy.
+
+ imgRequestProxy* image = list->GetListStyleImage();
+ if (!image) {
+ val->SetIdent(eCSSKeyword_none);
+ } else {
+ nsCOMPtr<nsIURI> uri;
+ image->GetURI(getter_AddRefs(uri));
+ val->SetURI(uri);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetListStylePosition()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleList()->mListStylePosition,
+ nsCSSProps::kListStylePositionKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetListStyleType()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ CounterStyle* style = StyleList()->GetCounterStyle();
+ AnonymousCounterStyle* anonymous = style->AsAnonymous();
+ nsAutoString tmp;
+ if (!anonymous) {
+ // want SetIdent
+ nsString type;
+ StyleList()->GetListStyleType(type);
+ nsStyleUtil::AppendEscapedCSSIdent(type, tmp);
+ } else if (anonymous->IsSingleString()) {
+ const nsTArray<nsString>& symbols = anonymous->GetSymbols();
+ MOZ_ASSERT(symbols.Length() == 1);
+ nsStyleUtil::AppendEscapedCSSString(symbols[0], tmp);
+ } else {
+ tmp.AppendLiteral("symbols(");
+
+ uint8_t system = anonymous->GetSystem();
+ NS_ASSERTION(system == NS_STYLE_COUNTER_SYSTEM_CYCLIC ||
+ system == NS_STYLE_COUNTER_SYSTEM_NUMERIC ||
+ system == NS_STYLE_COUNTER_SYSTEM_ALPHABETIC ||
+ system == NS_STYLE_COUNTER_SYSTEM_SYMBOLIC ||
+ system == NS_STYLE_COUNTER_SYSTEM_FIXED,
+ "Invalid system for anonymous counter style.");
+ if (system != NS_STYLE_COUNTER_SYSTEM_SYMBOLIC) {
+ AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(
+ system, nsCSSProps::kCounterSystemKTable), tmp);
+ tmp.Append(' ');
+ }
+
+ const nsTArray<nsString>& symbols = anonymous->GetSymbols();
+ NS_ASSERTION(symbols.Length() > 0,
+ "No symbols in the anonymous counter style");
+ for (size_t i = 0, iend = symbols.Length(); i < iend; i++) {
+ nsStyleUtil::AppendEscapedCSSString(symbols[i], tmp);
+ tmp.Append(' ');
+ }
+ tmp.Replace(tmp.Length() - 1, 1, char16_t(')'));
+ }
+ val->SetString(tmp);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetImageRegion()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleList* list = StyleList();
+
+ if (list->mImageRegion.width <= 0 || list->mImageRegion.height <= 0) {
+ val->SetIdent(eCSSKeyword_auto);
+ } else {
+ // create the cssvalues for the sides, stick them in the rect object
+ nsROCSSPrimitiveValue *topVal = new nsROCSSPrimitiveValue;
+ nsROCSSPrimitiveValue *rightVal = new nsROCSSPrimitiveValue;
+ nsROCSSPrimitiveValue *bottomVal = new nsROCSSPrimitiveValue;
+ nsROCSSPrimitiveValue *leftVal = new nsROCSSPrimitiveValue;
+ nsDOMCSSRect * domRect = new nsDOMCSSRect(topVal, rightVal,
+ bottomVal, leftVal);
+ topVal->SetAppUnits(list->mImageRegion.y);
+ rightVal->SetAppUnits(list->mImageRegion.width + list->mImageRegion.x);
+ bottomVal->SetAppUnits(list->mImageRegion.height + list->mImageRegion.y);
+ leftVal->SetAppUnits(list->mImageRegion.x);
+ val->SetRect(domRect);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetInitialLetter()
+{
+ const nsStyleTextReset* textReset = StyleTextReset();
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ if (textReset->mInitialLetterSink == 0) {
+ val->SetIdent(eCSSKeyword_normal);
+ return val.forget();
+ } else {
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ val->SetNumber(textReset->mInitialLetterSize);
+ valueList->AppendCSSValue(val.forget());
+ RefPtr<nsROCSSPrimitiveValue> second = new nsROCSSPrimitiveValue;
+ second->SetNumber(textReset->mInitialLetterSink);
+ valueList->AppendCSSValue(second.forget());
+ return valueList.forget();
+ }
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetLineHeight()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ nscoord lineHeight;
+ if (GetLineHeightCoord(lineHeight)) {
+ val->SetAppUnits(lineHeight);
+ } else {
+ SetValueToCoord(val, StyleText()->mLineHeight, true,
+ nullptr, nsCSSProps::kLineHeightKTable);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetRubyAlign()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(
+ StyleText()->mRubyAlign, nsCSSProps::kRubyAlignKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetRubyPosition()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(
+ StyleText()->mRubyPosition, nsCSSProps::kRubyPositionKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetVerticalAlign()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StyleDisplay()->mVerticalAlign, false,
+ nullptr, nsCSSProps::kVerticalAlignKTable);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::CreateTextAlignValue(uint8_t aAlign, bool aAlignTrue,
+ const KTableEntry aTable[])
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(aAlign, aTable));
+ if (!aAlignTrue) {
+ return val.forget();
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> first = new nsROCSSPrimitiveValue;
+ first->SetIdent(eCSSKeyword_unsafe);
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ valueList->AppendCSSValue(first.forget());
+ valueList->AppendCSSValue(val.forget());
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextAlign()
+{
+ const nsStyleText* style = StyleText();
+ return CreateTextAlignValue(style->mTextAlign, style->mTextAlignTrue,
+ nsCSSProps::kTextAlignKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextAlignLast()
+{
+ const nsStyleText* style = StyleText();
+ return CreateTextAlignValue(style->mTextAlignLast, style->mTextAlignLastTrue,
+ nsCSSProps::kTextAlignLastKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextCombineUpright()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ uint8_t tch = StyleText()->mTextCombineUpright;
+
+ if (tch <= NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL) {
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(tch,
+ nsCSSProps::kTextCombineUprightKTable));
+ } else if (tch <= NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2) {
+ val->SetString(NS_LITERAL_STRING("digits 2"));
+ } else if (tch <= NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3) {
+ val->SetString(NS_LITERAL_STRING("digits 3"));
+ } else {
+ val->SetString(NS_LITERAL_STRING("digits 4"));
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextDecoration()
+{
+ const nsStyleTextReset* textReset = StyleTextReset();
+
+ bool isInitialStyle =
+ textReset->mTextDecorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ StyleComplexColor color = textReset->mTextDecorationColor;
+
+ if (isInitialStyle && color.IsCurrentColor()) {
+ return DoGetTextDecorationLine();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ valueList->AppendCSSValue(DoGetTextDecorationLine());
+ if (!isInitialStyle) {
+ valueList->AppendCSSValue(DoGetTextDecorationStyle());
+ }
+ if (!color.IsCurrentColor()) {
+ valueList->AppendCSSValue(DoGetTextDecorationColor());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextDecorationColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueFromComplexColor(val, StyleTextReset()->mTextDecorationColor);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextDecorationLine()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t intValue = StyleTextReset()->mTextDecorationLine;
+
+ if (NS_STYLE_TEXT_DECORATION_LINE_NONE == intValue) {
+ val->SetIdent(eCSSKeyword_none);
+ } else {
+ nsAutoString decorationLineString;
+ // Clear the -moz-anchor-decoration bit and the OVERRIDE_ALL bits -- we
+ // don't want these to appear in the computed style.
+ intValue &= ~(NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS |
+ NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL);
+ nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_text_decoration_line,
+ intValue, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
+ NS_STYLE_TEXT_DECORATION_LINE_BLINK, decorationLineString);
+ val->SetString(decorationLineString);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextDecorationStyle()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleTextReset()->mTextDecorationStyle,
+ nsCSSProps::kTextDecorationStyleKTable));
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextEmphasisColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueFromComplexColor(val, StyleText()->mTextEmphasisColor);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextEmphasisPosition()
+{
+ auto position = StyleText()->mTextEmphasisPosition;
+
+ MOZ_ASSERT(!(position & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER) !=
+ !(position & NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER));
+ RefPtr<nsROCSSPrimitiveValue> first = new nsROCSSPrimitiveValue;
+ first->SetIdent((position & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER) ?
+ eCSSKeyword_over : eCSSKeyword_under);
+
+ MOZ_ASSERT(!(position & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT) !=
+ !(position & NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT));
+ RefPtr<nsROCSSPrimitiveValue> second = new nsROCSSPrimitiveValue;
+ second->SetIdent((position & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT) ?
+ eCSSKeyword_left : eCSSKeyword_right);
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ valueList->AppendCSSValue(first.forget());
+ valueList->AppendCSSValue(second.forget());
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextEmphasisStyle()
+{
+ auto style = StyleText()->mTextEmphasisStyle;
+ if (style == NS_STYLE_TEXT_EMPHASIS_STYLE_NONE) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+ if (style == NS_STYLE_TEXT_EMPHASIS_STYLE_STRING) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString tmp;
+ nsStyleUtil::AppendEscapedCSSString(
+ StyleText()->mTextEmphasisStyleString, tmp);
+ val->SetString(tmp);
+ return val.forget();
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> fillVal = new nsROCSSPrimitiveValue;
+ if ((style & NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) ==
+ NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED) {
+ fillVal->SetIdent(eCSSKeyword_filled);
+ } else {
+ MOZ_ASSERT((style & NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) ==
+ NS_STYLE_TEXT_EMPHASIS_STYLE_OPEN);
+ fillVal->SetIdent(eCSSKeyword_open);
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> shapeVal = new nsROCSSPrimitiveValue;
+ shapeVal->SetIdent(nsCSSProps::ValueToKeywordEnum(
+ style & NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK,
+ nsCSSProps::kTextEmphasisStyleShapeKTable));
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ valueList->AppendCSSValue(fillVal.forget());
+ valueList->AppendCSSValue(shapeVal.forget());
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextIndent()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StyleText()->mTextIndent, false);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextOrientation()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleVisibility()->mTextOrientation,
+ nsCSSProps::kTextOrientationKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextOverflow()
+{
+ const nsStyleTextReset *style = StyleTextReset();
+ RefPtr<nsROCSSPrimitiveValue> first = new nsROCSSPrimitiveValue;
+ const nsStyleTextOverflowSide *side = style->mTextOverflow.GetFirstValue();
+ if (side->mType == NS_STYLE_TEXT_OVERFLOW_STRING) {
+ nsAutoString str;
+ nsStyleUtil::AppendEscapedCSSString(side->mString, str);
+ first->SetString(str);
+ } else {
+ first->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(side->mType,
+ nsCSSProps::kTextOverflowKTable));
+ }
+ side = style->mTextOverflow.GetSecondValue();
+ if (!side) {
+ return first.forget();
+ }
+ RefPtr<nsROCSSPrimitiveValue> second = new nsROCSSPrimitiveValue;
+ if (side->mType == NS_STYLE_TEXT_OVERFLOW_STRING) {
+ nsAutoString str;
+ nsStyleUtil::AppendEscapedCSSString(side->mString, str);
+ second->SetString(str);
+ } else {
+ second->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(side->mType,
+ nsCSSProps::kTextOverflowKTable));
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ valueList->AppendCSSValue(first.forget());
+ valueList->AppendCSSValue(second.forget());
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextShadow()
+{
+ return GetCSSShadowArray(StyleText()->mTextShadow,
+ StyleColor()->mColor,
+ false);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextTransform()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleText()->mTextTransform,
+ nsCSSProps::kTextTransformKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTabSize()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleText()->mTabSize);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetLetterSpacing()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StyleText()->mLetterSpacing, false);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWordSpacing()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StyleText()->mWordSpacing, false);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWhiteSpace()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleText()->mWhiteSpace,
+ nsCSSProps::kWhitespaceKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWindowDragging()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleUIReset()->mWindowDragging,
+ nsCSSProps::kWindowDraggingKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWindowShadow()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleUIReset()->mWindowShadow,
+ nsCSSProps::kWindowShadowKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWordBreak()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleText()->mWordBreak,
+ nsCSSProps::kWordBreakKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOverflowWrap()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleText()->mOverflowWrap,
+ nsCSSProps::kOverflowWrapKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetHyphens()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleText()->mHyphens,
+ nsCSSProps::kHyphensKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextSizeAdjust()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ switch (StyleText()->mTextSizeAdjust) {
+ default:
+ NS_NOTREACHED("unexpected value");
+ MOZ_FALLTHROUGH;
+ case NS_STYLE_TEXT_SIZE_ADJUST_AUTO:
+ val->SetIdent(eCSSKeyword_auto);
+ break;
+ case NS_STYLE_TEXT_SIZE_ADJUST_NONE:
+ val->SetIdent(eCSSKeyword_none);
+ break;
+ }
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWebkitTextFillColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueFromComplexColor(val, StyleText()->mWebkitTextFillColor);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWebkitTextStrokeColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueFromComplexColor(val, StyleText()->mWebkitTextStrokeColor);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWebkitTextStrokeWidth()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(StyleText()->mWebkitTextStrokeWidth.GetCoordValue());
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPointerEvents()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleUserInterface()->mPointerEvents,
+ nsCSSProps::kPointerEventsKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetVisibility()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleVisibility()->mVisible,
+ nsCSSProps::kVisibilityKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWritingMode()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleVisibility()->mWritingMode,
+ nsCSSProps::kWritingModeKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetDirection()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleVisibility()->mDirection,
+ nsCSSProps::kDirectionKTable));
+ return val.forget();
+}
+
+static_assert(NS_STYLE_UNICODE_BIDI_NORMAL == 0,
+ "unicode-bidi style constants not as expected");
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetUnicodeBidi()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleTextReset()->mUnicodeBidi,
+ nsCSSProps::kUnicodeBidiKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetCursor()
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ const nsStyleUserInterface *ui = StyleUserInterface();
+
+ for (const nsCursorImage& item : ui->mCursorImages) {
+ RefPtr<nsDOMCSSValueList> itemList = GetROCSSValueList(false);
+
+ nsCOMPtr<nsIURI> uri;
+ item.GetImage()->GetURI(getter_AddRefs(uri));
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetURI(uri);
+ itemList->AppendCSSValue(val.forget());
+
+ if (item.mHaveHotspot) {
+ RefPtr<nsROCSSPrimitiveValue> valX = new nsROCSSPrimitiveValue;
+ RefPtr<nsROCSSPrimitiveValue> valY = new nsROCSSPrimitiveValue;
+
+ valX->SetNumber(item.mHotspotX);
+ valY->SetNumber(item.mHotspotY);
+
+ itemList->AppendCSSValue(valX.forget());
+ itemList->AppendCSSValue(valY.forget());
+ }
+ valueList->AppendCSSValue(itemList.forget());
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(ui->mCursor,
+ nsCSSProps::kCursorKTable));
+ valueList->AppendCSSValue(val.forget());
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAppearance()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mAppearance,
+ nsCSSProps::kAppearanceKTable));
+ return val.forget();
+}
+
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBoxAlign()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleXUL()->mBoxAlign,
+ nsCSSProps::kBoxAlignKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBoxDirection()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleXUL()->mBoxDirection,
+ nsCSSProps::kBoxDirectionKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBoxFlex()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleXUL()->mBoxFlex);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBoxOrdinalGroup()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleXUL()->mBoxOrdinal);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBoxOrient()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleXUL()->mBoxOrient,
+ nsCSSProps::kBoxOrientKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBoxPack()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleXUL()->mBoxPack,
+ nsCSSProps::kBoxPackKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBoxSizing()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StylePosition()->mBoxSizing,
+ nsCSSProps::kBoxSizingKTable));
+ return val.forget();
+}
+
+/* Border image properties */
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderImageSource()
+{
+ const nsStyleBorder* border = StyleBorder();
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ const nsStyleImage& image = border->mBorderImageSource;
+ SetValueToStyleImage(image, val);
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderImageSlice()
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ const nsStyleBorder* border = StyleBorder();
+ // Four slice numbers.
+ NS_FOR_CSS_SIDES (side) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, border->mBorderImageSlice.Get(side), true, nullptr);
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ // Fill keyword.
+ if (NS_STYLE_BORDER_IMAGE_SLICE_FILL == border->mBorderImageFill) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_fill);
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderImageWidth()
+{
+ const nsStyleBorder* border = StyleBorder();
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ NS_FOR_CSS_SIDES (side) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, border->mBorderImageWidth.Get(side),
+ true, nullptr);
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderImageOutset()
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ const nsStyleBorder* border = StyleBorder();
+ // four slice numbers
+ NS_FOR_CSS_SIDES (side) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, border->mBorderImageOutset.Get(side),
+ true, nullptr);
+ valueList->AppendCSSValue(val.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetBorderImageRepeat()
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ const nsStyleBorder* border = StyleBorder();
+
+ // horizontal repeat
+ RefPtr<nsROCSSPrimitiveValue> valX = new nsROCSSPrimitiveValue;
+ valX->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(border->mBorderImageRepeatH,
+ nsCSSProps::kBorderImageRepeatKTable));
+ valueList->AppendCSSValue(valX.forget());
+
+ // vertical repeat
+ RefPtr<nsROCSSPrimitiveValue> valY = new nsROCSSPrimitiveValue;
+ valY->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(border->mBorderImageRepeatV,
+ nsCSSProps::kBorderImageRepeatKTable));
+ valueList->AppendCSSValue(valY.forget());
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFlexBasis()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ // XXXdholbert We could make this more automagic and resolve percentages
+ // if we wanted, by passing in a PercentageBaseGetter instead of nullptr
+ // below. Logic would go like this:
+ // if (i'm a flex item) {
+ // if (my flex container is horizontal) {
+ // percentageBaseGetter = &nsComputedDOMStyle::GetCBContentWidth;
+ // } else {
+ // percentageBaseGetter = &nsComputedDOMStyle::GetCBContentHeight;
+ // }
+ // }
+
+ SetValueToCoord(val, StylePosition()->mFlexBasis, true,
+ nullptr, nsCSSProps::kWidthKTable);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFlexDirection()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StylePosition()->mFlexDirection,
+ nsCSSProps::kFlexDirectionKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFlexGrow()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StylePosition()->mFlexGrow);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFlexShrink()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StylePosition()->mFlexShrink);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFlexWrap()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StylePosition()->mFlexWrap,
+ nsCSSProps::kFlexWrapKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOrder()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StylePosition()->mOrder);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAlignContent()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString str;
+ auto align = StylePosition()->mAlignContent;
+ nsCSSValue::AppendAlignJustifyValueToString(align & NS_STYLE_ALIGN_ALL_BITS, str);
+ auto fallback = align >> NS_STYLE_ALIGN_ALL_SHIFT;
+ if (fallback) {
+ str.Append(' ');
+ nsCSSValue::AppendAlignJustifyValueToString(fallback, str);
+ }
+ val->SetString(str);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAlignItems()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString str;
+ auto align = StylePosition()->mAlignItems;
+ nsCSSValue::AppendAlignJustifyValueToString(align, str);
+ val->SetString(str);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAlignSelf()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString str;
+ auto align = StylePosition()->mAlignSelf;
+ nsCSSValue::AppendAlignJustifyValueToString(align, str);
+ val->SetString(str);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetJustifyContent()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString str;
+ auto justify = StylePosition()->mJustifyContent;
+ nsCSSValue::AppendAlignJustifyValueToString(justify & NS_STYLE_JUSTIFY_ALL_BITS, str);
+ auto fallback = justify >> NS_STYLE_JUSTIFY_ALL_SHIFT;
+ if (fallback) {
+ MOZ_ASSERT(nsCSSProps::ValueToKeywordEnum(fallback & ~NS_STYLE_JUSTIFY_FLAG_BITS,
+ nsCSSProps::kAlignSelfPosition)
+ != eCSSKeyword_UNKNOWN, "unknown fallback value");
+ str.Append(' ');
+ nsCSSValue::AppendAlignJustifyValueToString(fallback, str);
+ }
+ val->SetString(str);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetJustifyItems()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString str;
+ auto justify =
+ StylePosition()->ComputedJustifyItems(mStyleContext->GetParent());
+ nsCSSValue::AppendAlignJustifyValueToString(justify, str);
+ val->SetString(str);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetJustifySelf()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString str;
+ auto justify = StylePosition()->mJustifySelf;
+ nsCSSValue::AppendAlignJustifyValueToString(justify, str);
+ val->SetString(str);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFloatEdge()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(uint8_t(StyleBorder()->mFloatEdge),
+ nsCSSProps::kFloatEdgeKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetForceBrokenImageIcon()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleUIReset()->mForceBrokenImageIcon);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetImageOrientation()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString string;
+ nsStyleImageOrientation orientation = StyleVisibility()->mImageOrientation;
+
+ if (orientation.IsFromImage()) {
+ string.AppendLiteral("from-image");
+ } else {
+ nsStyleUtil::AppendAngleValue(orientation.AngleAsCoord(), string);
+
+ if (orientation.IsFlipped()) {
+ string.AppendLiteral(" flip");
+ }
+ }
+
+ val->SetString(string);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetIMEMode()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleUIReset()->mIMEMode,
+ nsCSSProps::kIMEModeKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetUserFocus()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(uint8_t(StyleUserInterface()->mUserFocus),
+ nsCSSProps::kUserFocusKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetUserInput()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleUserInterface()->mUserInput,
+ nsCSSProps::kUserInputKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetUserModify()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleUserInterface()->mUserModify,
+ nsCSSProps::kUserModifyKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetUserSelect()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleUIReset()->mUserSelect,
+ nsCSSProps::kUserSelectKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetDisplay()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mDisplay,
+ nsCSSProps::kDisplayKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetContain()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t mask = StyleDisplay()->mContain;
+
+ if (mask == 0) {
+ val->SetIdent(eCSSKeyword_none);
+ } else if (mask & NS_STYLE_CONTAIN_STRICT) {
+ NS_ASSERTION(mask == (NS_STYLE_CONTAIN_STRICT | NS_STYLE_CONTAIN_ALL_BITS),
+ "contain: strict should imply contain: layout style paint");
+ val->SetIdent(eCSSKeyword_strict);
+ } else {
+ nsAutoString valueStr;
+
+ nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_contain,
+ mask, NS_STYLE_CONTAIN_LAYOUT,
+ NS_STYLE_CONTAIN_PAINT, valueStr);
+ val->SetString(valueStr);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPosition()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mPosition,
+ nsCSSProps::kPositionKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetClip()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleEffects* effects = StyleEffects();
+
+ if (effects->mClipFlags == NS_STYLE_CLIP_AUTO) {
+ val->SetIdent(eCSSKeyword_auto);
+ } else {
+ // create the cssvalues for the sides, stick them in the rect object
+ nsROCSSPrimitiveValue *topVal = new nsROCSSPrimitiveValue;
+ nsROCSSPrimitiveValue *rightVal = new nsROCSSPrimitiveValue;
+ nsROCSSPrimitiveValue *bottomVal = new nsROCSSPrimitiveValue;
+ nsROCSSPrimitiveValue *leftVal = new nsROCSSPrimitiveValue;
+ nsDOMCSSRect * domRect = new nsDOMCSSRect(topVal, rightVal,
+ bottomVal, leftVal);
+ if (effects->mClipFlags & NS_STYLE_CLIP_TOP_AUTO) {
+ topVal->SetIdent(eCSSKeyword_auto);
+ } else {
+ topVal->SetAppUnits(effects->mClip.y);
+ }
+
+ if (effects->mClipFlags & NS_STYLE_CLIP_RIGHT_AUTO) {
+ rightVal->SetIdent(eCSSKeyword_auto);
+ } else {
+ rightVal->SetAppUnits(effects->mClip.width + effects->mClip.x);
+ }
+
+ if (effects->mClipFlags & NS_STYLE_CLIP_BOTTOM_AUTO) {
+ bottomVal->SetIdent(eCSSKeyword_auto);
+ } else {
+ bottomVal->SetAppUnits(effects->mClip.height + effects->mClip.y);
+ }
+
+ if (effects->mClipFlags & NS_STYLE_CLIP_LEFT_AUTO) {
+ leftVal->SetIdent(eCSSKeyword_auto);
+ } else {
+ leftVal->SetAppUnits(effects->mClip.x);
+ }
+ val->SetRect(domRect);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWillChange()
+{
+ const nsTArray<nsString>& willChange = StyleDisplay()->mWillChange;
+
+ if (willChange.IsEmpty()) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_auto);
+ return val.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+ for (size_t i = 0; i < willChange.Length(); i++) {
+ const nsString& willChangeIdentifier = willChange[i];
+ RefPtr<nsROCSSPrimitiveValue> property = new nsROCSSPrimitiveValue;
+ property->SetString(willChangeIdentifier);
+ valueList->AppendCSSValue(property.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOverflow()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ if (display->mOverflowX != display->mOverflowY) {
+ // No value to return. We can't express this combination of
+ // values as a shorthand.
+ return nullptr;
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(display->mOverflowX,
+ nsCSSProps::kOverflowKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOverflowX()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mOverflowX,
+ nsCSSProps::kOverflowSubKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOverflowY()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mOverflowY,
+ nsCSSProps::kOverflowSubKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetOverflowClipBox()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mOverflowClipBox,
+ nsCSSProps::kOverflowClipBoxKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetResize()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mResize,
+ nsCSSProps::kResizeKTable));
+ return val.forget();
+}
+
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPageBreakAfter()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleDisplay *display = StyleDisplay();
+
+ if (display->mBreakAfter) {
+ val->SetIdent(eCSSKeyword_always);
+ } else {
+ val->SetIdent(eCSSKeyword_auto);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPageBreakBefore()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleDisplay *display = StyleDisplay();
+
+ if (display->mBreakBefore) {
+ val->SetIdent(eCSSKeyword_always);
+ } else {
+ val->SetIdent(eCSSKeyword_auto);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPageBreakInside()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mBreakInside,
+ nsCSSProps::kPageBreakInsideKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTouchAction()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ int32_t intValue = StyleDisplay()->mTouchAction;
+
+ // None and Auto and Manipulation values aren't allowed
+ // to be in conjunction with other values.
+ // But there are all checks in CSSParserImpl::ParseTouchAction
+ nsAutoString valueStr;
+ nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_touch_action, intValue,
+ NS_STYLE_TOUCH_ACTION_NONE, NS_STYLE_TOUCH_ACTION_MANIPULATION,
+ valueStr);
+ val->SetString(valueStr);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetHeight()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ bool calcHeight = false;
+
+ if (mInnerFrame) {
+ calcHeight = true;
+
+ const nsStyleDisplay* displayData = StyleDisplay();
+ if (displayData->mDisplay == mozilla::StyleDisplay::Inline &&
+ !(mInnerFrame->IsFrameOfType(nsIFrame::eReplaced)) &&
+ // An outer SVG frame should behave the same as eReplaced in this case
+ mInnerFrame->GetType() != nsGkAtoms::svgOuterSVGFrame) {
+
+ calcHeight = false;
+ }
+ }
+
+ if (calcHeight) {
+ AssertFlushedPendingReflows();
+ nsMargin adjustedValues = GetAdjustedValuesForBoxSizing();
+ val->SetAppUnits(mInnerFrame->GetContentRect().height +
+ adjustedValues.TopBottom());
+ } else {
+ const nsStylePosition *positionData = StylePosition();
+
+ nscoord minHeight =
+ StyleCoordToNSCoord(positionData->mMinHeight,
+ &nsComputedDOMStyle::GetCBContentHeight, 0, true);
+
+ nscoord maxHeight =
+ StyleCoordToNSCoord(positionData->mMaxHeight,
+ &nsComputedDOMStyle::GetCBContentHeight,
+ nscoord_MAX, true);
+
+ SetValueToCoord(val, positionData->mHeight, true, nullptr,
+ nsCSSProps::kWidthKTable, minHeight, maxHeight);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWidth()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ bool calcWidth = false;
+
+ if (mInnerFrame) {
+ calcWidth = true;
+
+ const nsStyleDisplay *displayData = StyleDisplay();
+ if (displayData->mDisplay == mozilla::StyleDisplay::Inline &&
+ !(mInnerFrame->IsFrameOfType(nsIFrame::eReplaced)) &&
+ // An outer SVG frame should behave the same as eReplaced in this case
+ mInnerFrame->GetType() != nsGkAtoms::svgOuterSVGFrame) {
+
+ calcWidth = false;
+ }
+ }
+
+ if (calcWidth) {
+ AssertFlushedPendingReflows();
+ nsMargin adjustedValues = GetAdjustedValuesForBoxSizing();
+ val->SetAppUnits(mInnerFrame->GetContentRect().width +
+ adjustedValues.LeftRight());
+ } else {
+ const nsStylePosition *positionData = StylePosition();
+
+ nscoord minWidth =
+ StyleCoordToNSCoord(positionData->mMinWidth,
+ &nsComputedDOMStyle::GetCBContentWidth, 0, true);
+
+ nscoord maxWidth =
+ StyleCoordToNSCoord(positionData->mMaxWidth,
+ &nsComputedDOMStyle::GetCBContentWidth,
+ nscoord_MAX, true);
+
+ SetValueToCoord(val, positionData->mWidth, true, nullptr,
+ nsCSSProps::kWidthKTable, minWidth, maxWidth);
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaxHeight()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StylePosition()->mMaxHeight, true,
+ nullptr, nsCSSProps::kWidthKTable);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaxWidth()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StylePosition()->mMaxWidth, true,
+ nullptr, nsCSSProps::kWidthKTable);
+ return val.forget();
+}
+
+bool
+nsComputedDOMStyle::ShouldHonorMinSizeAutoInAxis(PhysicalAxis aAxis)
+{
+ // A {flex,grid} item's min-{width|height} "auto" value gets special
+ // treatment in getComputedStyle().
+ // https://drafts.csswg.org/css-flexbox-1/#valdef-min-width-auto
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ // In most cases, "min-{width|height}: auto" is mapped to "0px", unless
+ // we're a flex item (and the min-size is in the flex container's main
+ // axis), or we're a grid item, AND we also have overflow:visible.
+
+ // Note: We only need to bother checking one "overflow" subproperty for
+ // "visible", because a non-"visible" value in either axis would force the
+ // other axis to also be non-"visible" as well.
+
+ if (mOuterFrame) {
+ nsIFrame* containerFrame = mOuterFrame->GetParent();
+ if (containerFrame &&
+ StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) {
+ auto containerType = containerFrame->GetType();
+ if (containerType == nsGkAtoms::flexContainerFrame &&
+ (static_cast<nsFlexContainerFrame*>(containerFrame)->IsHorizontal() ==
+ (aAxis == eAxisHorizontal))) {
+ return true;
+ }
+ if (containerType == nsGkAtoms::gridContainerFrame) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMinHeight()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsStyleCoord minHeight = StylePosition()->mMinHeight;
+
+ if (eStyleUnit_Auto == minHeight.GetUnit() &&
+ !ShouldHonorMinSizeAutoInAxis(eAxisVertical)) {
+ minHeight.SetCoordValue(0);
+ }
+
+ SetValueToCoord(val, minHeight, true, nullptr, nsCSSProps::kWidthKTable);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMinWidth()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ nsStyleCoord minWidth = StylePosition()->mMinWidth;
+
+ if (eStyleUnit_Auto == minWidth.GetUnit() &&
+ !ShouldHonorMinSizeAutoInAxis(eAxisHorizontal)) {
+ minWidth.SetCoordValue(0);
+ }
+
+ SetValueToCoord(val, minWidth, true, nullptr, nsCSSProps::kWidthKTable);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMixBlendMode()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleEffects()->mMixBlendMode,
+ nsCSSProps::kBlendModeKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetIsolation()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mIsolation,
+ nsCSSProps::kIsolationKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetObjectFit()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StylePosition()->mObjectFit,
+ nsCSSProps::kObjectFitKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetObjectPosition()
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ SetValueToPosition(StylePosition()->mObjectPosition, valueList);
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetLeft()
+{
+ return GetOffsetWidthFor(NS_SIDE_LEFT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetRight()
+{
+ return GetOffsetWidthFor(NS_SIDE_RIGHT);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTop()
+{
+ return GetOffsetWidthFor(NS_SIDE_TOP);
+}
+
+nsDOMCSSValueList*
+nsComputedDOMStyle::GetROCSSValueList(bool aCommaDelimited)
+{
+ return new nsDOMCSSValueList(aCommaDelimited, true);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetOffsetWidthFor(mozilla::css::Side aSide)
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ AssertFlushedPendingReflows();
+
+ uint8_t position = display->mPosition;
+ if (!mOuterFrame) {
+ // GetRelativeOffset and GetAbsoluteOffset don't handle elements
+ // without frames in any sensible way. GetStaticOffset, however,
+ // is perfect for that case.
+ position = NS_STYLE_POSITION_STATIC;
+ }
+
+ switch (position) {
+ case NS_STYLE_POSITION_STATIC:
+ return GetStaticOffset(aSide);
+ case NS_STYLE_POSITION_RELATIVE:
+ return GetRelativeOffset(aSide);
+ case NS_STYLE_POSITION_STICKY:
+ return GetStickyOffset(aSide);
+ case NS_STYLE_POSITION_ABSOLUTE:
+ case NS_STYLE_POSITION_FIXED:
+ return GetAbsoluteOffset(aSide);
+ default:
+ NS_ERROR("Invalid position");
+ return nullptr;
+ }
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetAbsoluteOffset(mozilla::css::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->GetType() == nsGkAtoms::viewportFrame) {
+ // 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();
+ }
+ }
+
+ nscoord offset = 0;
+ switch (aSide) {
+ case NS_SIDE_TOP:
+ offset = rect.y - margin.top - border.top - scrollbarSizes.top;
+
+ break;
+ case NS_SIDE_RIGHT:
+ offset = containerRect.width - rect.width -
+ rect.x - margin.right - border.right - scrollbarSizes.right;
+
+ break;
+ case NS_SIDE_BOTTOM:
+ offset = containerRect.height - rect.height -
+ rect.y - margin.bottom - border.bottom - scrollbarSizes.bottom;
+
+ break;
+ case NS_SIDE_LEFT:
+ offset = rect.x - margin.left - border.left - scrollbarSizes.left;
+
+ break;
+ default:
+ NS_ERROR("Invalid side");
+ break;
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetAppUnits(offset);
+ return val.forget();
+}
+
+static_assert(NS_SIDE_TOP == 0 && NS_SIDE_RIGHT == 1 &&
+ NS_SIDE_BOTTOM == 2 && NS_SIDE_LEFT == 3,
+ "box side constants not as expected for NS_OPPOSITE_SIDE");
+#define NS_OPPOSITE_SIDE(s_) mozilla::css::Side(((s_) + 2) & 3)
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetRelativeOffset(mozilla::css::Side aSide)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStylePosition* positionData = StylePosition();
+ int32_t sign = 1;
+ nsStyleCoord coord = positionData->mOffset.Get(aSide);
+
+ NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord ||
+ coord.GetUnit() == eStyleUnit_Percent ||
+ coord.GetUnit() == eStyleUnit_Auto ||
+ coord.IsCalcUnit(),
+ "Unexpected unit");
+
+ if (coord.GetUnit() == eStyleUnit_Auto) {
+ coord = positionData->mOffset.Get(NS_OPPOSITE_SIDE(aSide));
+ sign = -1;
+ }
+ PercentageBaseGetter baseGetter;
+ if (aSide == NS_SIDE_LEFT || aSide == NS_SIDE_RIGHT) {
+ baseGetter = &nsComputedDOMStyle::GetCBContentWidth;
+ } else {
+ baseGetter = &nsComputedDOMStyle::GetCBContentHeight;
+ }
+
+ val->SetAppUnits(sign * StyleCoordToNSCoord(coord, baseGetter, 0, false));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetStickyOffset(mozilla::css::Side aSide)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStylePosition* positionData = StylePosition();
+ nsStyleCoord coord = positionData->mOffset.Get(aSide);
+
+ NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord ||
+ coord.GetUnit() == eStyleUnit_Percent ||
+ coord.GetUnit() == eStyleUnit_Auto ||
+ coord.IsCalcUnit(),
+ "Unexpected unit");
+
+ if (coord.GetUnit() == eStyleUnit_Auto) {
+ val->SetIdent(eCSSKeyword_auto);
+ return val.forget();
+ }
+ PercentageBaseGetter baseGetter;
+ if (aSide == NS_SIDE_LEFT || aSide == NS_SIDE_RIGHT) {
+ baseGetter = &nsComputedDOMStyle::GetScrollFrameContentWidth;
+ } else {
+ baseGetter = &nsComputedDOMStyle::GetScrollFrameContentHeight;
+ }
+
+ val->SetAppUnits(StyleCoordToNSCoord(coord, baseGetter, 0, false));
+ return val.forget();
+}
+
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetStaticOffset(mozilla::css::Side aSide)
+
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StylePosition()->mOffset.Get(aSide), false);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetPaddingWidthFor(mozilla::css::Side aSide)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ if (!mInnerFrame) {
+ SetValueToCoord(val, StylePadding()->mPadding.Get(aSide), true);
+ } else {
+ AssertFlushedPendingReflows();
+
+ val->SetAppUnits(mInnerFrame->GetUsedPadding().Side(aSide));
+ }
+
+ return val.forget();
+}
+
+bool
+nsComputedDOMStyle::GetLineHeightCoord(nscoord& aCoord)
+{
+ AssertFlushedPendingReflows();
+
+ nscoord blockHeight = NS_AUTOHEIGHT;
+ if (StyleText()->mLineHeight.GetUnit() == eStyleUnit_Enumerated) {
+ if (!mInnerFrame)
+ return false;
+
+ if (nsLayoutUtils::IsNonWrapperBlock(mInnerFrame)) {
+ blockHeight = mInnerFrame->GetContentRect().height;
+ } else {
+ GetCBContentHeight(blockHeight);
+ }
+ }
+
+ // lie about font size inflation since we lie about font size (since
+ // the inflation only applies to text)
+ aCoord = ReflowInput::CalcLineHeight(mContent, mStyleContext,
+ blockHeight, 1.0f);
+
+ // CalcLineHeight uses font->mFont.size, but we want to use
+ // font->mSize as the font size. Adjust for that. Also adjust for
+ // the text zoom, if any.
+ const nsStyleFont* font = StyleFont();
+ float fCoord = float(aCoord);
+ if (font->mAllowZoom) {
+ fCoord /= mPresShell->GetPresContext()->TextZoom();
+ }
+ if (font->mFont.size != font->mSize) {
+ fCoord = fCoord * (float(font->mSize) / float(font->mFont.size));
+ }
+ aCoord = NSToCoordRound(fCoord);
+
+ return true;
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetBorderColorsFor(mozilla::css::Side aSide)
+{
+ const nsStyleBorder *border = StyleBorder();
+
+ if (border->mBorderColors) {
+ nsBorderColors* borderColors = border->mBorderColors[aSide];
+ if (borderColors) {
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+ do {
+ RefPtr<nsROCSSPrimitiveValue> primitive = new nsROCSSPrimitiveValue;
+
+ SetToRGBAColor(primitive, borderColors->mColor);
+
+ valueList->AppendCSSValue(primitive.forget());
+ borderColors = borderColors->mNext;
+ } while (borderColors);
+
+ return valueList.forget();
+ }
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetBorderWidthFor(mozilla::css::Side aSide)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ nscoord width;
+ if (mInnerFrame) {
+ AssertFlushedPendingReflows();
+ width = mInnerFrame->GetUsedBorder().Side(aSide);
+ } else {
+ width = StyleBorder()->GetComputedBorderWidth(aSide);
+ }
+ val->SetAppUnits(width);
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetBorderColorFor(mozilla::css::Side aSide)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueFromComplexColor(val, StyleBorder()->mBorderColor[aSide]);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetMarginWidthFor(mozilla::css::Side aSide)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ if (!mInnerFrame) {
+ SetValueToCoord(val, StyleMargin()->mMargin.Get(aSide), 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();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetBorderStyleFor(mozilla::css::Side aSide)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleBorder()->GetBorderStyle(aSide),
+ nsCSSProps::kBorderStyleKTable));
+ return val.forget();
+}
+
+void
+nsComputedDOMStyle::SetValueToCoord(nsROCSSPrimitiveValue* aValue,
+ const nsStyleCoord& aCoord,
+ bool aClampNegativeCalc,
+ PercentageBaseGetter aPercentageBaseGetter,
+ const KTableEntry aTable[],
+ nscoord aMinAppUnits,
+ nscoord aMaxAppUnits)
+{
+ NS_PRECONDITION(aValue, "Must have a value to work with");
+
+ switch (aCoord.GetUnit()) {
+ case eStyleUnit_Normal:
+ aValue->SetIdent(eCSSKeyword_normal);
+ break;
+
+ case eStyleUnit_Auto:
+ aValue->SetIdent(eCSSKeyword_auto);
+ break;
+
+ case eStyleUnit_Percent:
+ {
+ nscoord percentageBase;
+ if (aPercentageBaseGetter &&
+ (this->*aPercentageBaseGetter)(percentageBase)) {
+ nscoord val = NSCoordSaturatingMultiply(percentageBase,
+ aCoord.GetPercentValue());
+ aValue->SetAppUnits(std::max(aMinAppUnits, std::min(val, aMaxAppUnits)));
+ } else {
+ aValue->SetPercent(aCoord.GetPercentValue());
+ }
+ }
+ break;
+
+ case eStyleUnit_Factor:
+ aValue->SetNumber(aCoord.GetFactorValue());
+ break;
+
+ case eStyleUnit_Coord:
+ {
+ nscoord val = aCoord.GetCoordValue();
+ aValue->SetAppUnits(std::max(aMinAppUnits, std::min(val, aMaxAppUnits)));
+ }
+ break;
+
+ case eStyleUnit_Integer:
+ aValue->SetNumber(aCoord.GetIntValue());
+ break;
+
+ case eStyleUnit_Enumerated:
+ NS_ASSERTION(aTable, "Must have table to handle this case");
+ aValue->SetIdent(nsCSSProps::ValueToKeywordEnum(aCoord.GetIntValue(),
+ aTable));
+ break;
+
+ case eStyleUnit_None:
+ aValue->SetIdent(eCSSKeyword_none);
+ break;
+
+ case eStyleUnit_Calc:
+ nscoord percentageBase;
+ if (!aCoord.CalcHasPercent()) {
+ nscoord val = nsRuleNode::ComputeCoordPercentCalc(aCoord, 0);
+ if (aClampNegativeCalc && val < 0) {
+ MOZ_ASSERT(aCoord.IsCalcUnit(),
+ "parser should have rejected value");
+ val = 0;
+ }
+ aValue->SetAppUnits(std::max(aMinAppUnits, std::min(val, aMaxAppUnits)));
+ } else if (aPercentageBaseGetter &&
+ (this->*aPercentageBaseGetter)(percentageBase)) {
+ nscoord val =
+ nsRuleNode::ComputeCoordPercentCalc(aCoord, percentageBase);
+ if (aClampNegativeCalc && val < 0) {
+ MOZ_ASSERT(aCoord.IsCalcUnit(),
+ "parser should have rejected value");
+ val = 0;
+ }
+ aValue->SetAppUnits(std::max(aMinAppUnits, std::min(val, aMaxAppUnits)));
+ } else {
+ nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
+ SetValueToCalc(calc, aValue);
+ }
+ break;
+
+ case eStyleUnit_Degree:
+ aValue->SetDegree(aCoord.GetAngleValue());
+ break;
+
+ case eStyleUnit_Grad:
+ aValue->SetGrad(aCoord.GetAngleValue());
+ break;
+
+ case eStyleUnit_Radian:
+ aValue->SetRadian(aCoord.GetAngleValue());
+ break;
+
+ case eStyleUnit_Turn:
+ aValue->SetTurn(aCoord.GetAngleValue());
+ break;
+
+ case eStyleUnit_FlexFraction: {
+ nsAutoString tmpStr;
+ nsStyleUtil::AppendCSSNumber(aCoord.GetFlexFractionValue(), tmpStr);
+ tmpStr.AppendLiteral("fr");
+ aValue->SetString(tmpStr);
+ break;
+ }
+
+ default:
+ NS_ERROR("Can't handle this unit");
+ break;
+ }
+}
+
+nscoord
+nsComputedDOMStyle::StyleCoordToNSCoord(const nsStyleCoord& aCoord,
+ PercentageBaseGetter aPercentageBaseGetter,
+ nscoord aDefaultValue,
+ bool aClampNegativeCalc)
+{
+ NS_PRECONDITION(aPercentageBaseGetter, "Must have a percentage base getter");
+ if (aCoord.GetUnit() == eStyleUnit_Coord) {
+ return aCoord.GetCoordValue();
+ }
+ if (aCoord.GetUnit() == eStyleUnit_Percent || aCoord.IsCalcUnit()) {
+ nscoord percentageBase;
+ if ((this->*aPercentageBaseGetter)(percentageBase)) {
+ nscoord result =
+ nsRuleNode::ComputeCoordPercentCalc(aCoord, percentageBase);
+ if (aClampNegativeCalc && result < 0) {
+ // It's expected that we can get a negative value here with calc().
+ // We can also get a negative value with a percentage value if
+ // percentageBase is negative; this isn't expected, but can happen
+ // when large length values overflow.
+ NS_WARNING_ASSERTION(
+ percentageBase >= 0,
+ "percentage base value overflowed to become negative for a property "
+ "that disallows negative values");
+ MOZ_ASSERT(aCoord.IsCalcUnit() ||
+ (aCoord.HasPercent() && percentageBase < 0),
+ "parser should have rejected value");
+ result = 0;
+ }
+ return result;
+ }
+ // Fall through to returning aDefaultValue if we have no percentage base.
+ }
+
+ return aDefaultValue;
+}
+
+bool
+nsComputedDOMStyle::GetCBContentWidth(nscoord& aWidth)
+{
+ if (!mOuterFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ nsIFrame* container = mOuterFrame->GetContainingBlock();
+ aWidth = container->GetContentRect().width;
+ return true;
+}
+
+bool
+nsComputedDOMStyle::GetCBContentHeight(nscoord& aHeight)
+{
+ if (!mOuterFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ nsIFrame* container = mOuterFrame->GetContainingBlock();
+ aHeight = container->GetContentRect().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;
+}
+
+bool
+nsComputedDOMStyle::GetFrameBoundsWidthForTransform(nscoord& aWidth)
+{
+ // We need a frame to work with.
+ if (!mInnerFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ aWidth = nsStyleTransformMatrix::TransformReferenceBox(mInnerFrame).Width();
+ return true;
+}
+
+bool
+nsComputedDOMStyle::GetFrameBoundsHeightForTransform(nscoord& aHeight)
+{
+ // We need a frame to work with.
+ if (!mInnerFrame) {
+ return false;
+ }
+
+ AssertFlushedPendingReflows();
+
+ aHeight = nsStyleTransformMatrix::TransformReferenceBox(mInnerFrame).Height();
+ return true;
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetSVGPaintFor(bool aFill)
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ const nsStyleSVG* svg = StyleSVG();
+ const nsStyleSVGPaint* paint = nullptr;
+
+ if (aFill)
+ paint = &svg->mFill;
+ else
+ paint = &svg->mStroke;
+
+ nsAutoString paintString;
+
+ switch (paint->Type()) {
+ case eStyleSVGPaintType_None:
+ val->SetIdent(eCSSKeyword_none);
+ break;
+ case eStyleSVGPaintType_Color:
+ SetToRGBAColor(val, paint->GetColor());
+ break;
+ case eStyleSVGPaintType_Server: {
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ RefPtr<nsROCSSPrimitiveValue> fallback = new nsROCSSPrimitiveValue;
+ SetValueToURLValue(paint->GetPaintServer(), val);
+ SetToRGBAColor(fallback, paint->GetFallbackColor());
+
+ valueList->AppendCSSValue(val.forget());
+ valueList->AppendCSSValue(fallback.forget());
+ return valueList.forget();
+ }
+ case eStyleSVGPaintType_ContextFill:
+ val->SetIdent(eCSSKeyword_context_fill);
+ // XXXheycam context-fill and context-stroke can have fallback colors,
+ // so they should be serialized here too
+ break;
+ case eStyleSVGPaintType_ContextStroke:
+ val->SetIdent(eCSSKeyword_context_stroke);
+ break;
+ }
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFill()
+{
+ return GetSVGPaintFor(true);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStroke()
+{
+ return GetSVGPaintFor(false);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMarkerEnd()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToURLValue(StyleSVG()->mMarkerEnd, val);
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMarkerMid()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToURLValue(StyleSVG()->mMarkerMid, val);
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMarkerStart()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToURLValue(StyleSVG()->mMarkerStart, val);
+
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStrokeDasharray()
+{
+ const nsStyleSVG* svg = StyleSVG();
+
+ if (svg->mStrokeDasharray.IsEmpty()) {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ for (uint32_t i = 0; i < svg->mStrokeDasharray.Length(); i++) {
+ RefPtr<nsROCSSPrimitiveValue> dash = new nsROCSSPrimitiveValue;
+ SetValueToCoord(dash, svg->mStrokeDasharray[i], true);
+ valueList->AppendCSSValue(dash.forget());
+ }
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStrokeDashoffset()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StyleSVG()->mStrokeDashoffset, false);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStrokeWidth()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToCoord(val, StyleSVG()->mStrokeWidth, true);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetVectorEffect()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleSVGReset()->mVectorEffect,
+ nsCSSProps::kVectorEffectKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFillOpacity()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleSVG()->mFillOpacity);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFloodOpacity()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleSVGReset()->mFloodOpacity);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStopOpacity()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleSVGReset()->mStopOpacity);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStrokeMiterlimit()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleSVG()->mStrokeMiterlimit);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStrokeOpacity()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetNumber(StyleSVG()->mStrokeOpacity);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetClipRule()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(
+ StyleSVG()->mClipRule, nsCSSProps::kFillRuleKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFillRule()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(
+ StyleSVG()->mFillRule, nsCSSProps::kFillRuleKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStrokeLinecap()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleSVG()->mStrokeLinecap,
+ nsCSSProps::kStrokeLinecapKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStrokeLinejoin()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleSVG()->mStrokeLinejoin,
+ nsCSSProps::kStrokeLinejoinKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextAnchor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleSVG()->mTextAnchor,
+ nsCSSProps::kTextAnchorKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColorInterpolation()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleSVG()->mColorInterpolation,
+ nsCSSProps::kColorInterpolationKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetColorInterpolationFilters()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleSVG()->mColorInterpolationFilters,
+ nsCSSProps::kColorInterpolationKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetDominantBaseline()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleSVGReset()->mDominantBaseline,
+ nsCSSProps::kDominantBaselineKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetImageRendering()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleVisibility()->mImageRendering,
+ nsCSSProps::kImageRenderingKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetShapeRendering()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleSVG()->mShapeRendering,
+ nsCSSProps::kShapeRenderingKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextRendering()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleText()->mTextRendering,
+ nsCSSProps::kTextRenderingKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFloodColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetToRGBAColor(val, StyleSVGReset()->mFloodColor);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetLightingColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetToRGBAColor(val, StyleSVGReset()->mLightingColor);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetStopColor()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetToRGBAColor(val, StyleSVGReset()->mStopColor);
+ return val.forget();
+}
+
+void
+nsComputedDOMStyle::BoxValuesToString(nsAString& aString,
+ const nsTArray<nsStyleCoord>& aBoxValues)
+{
+ MOZ_ASSERT(aBoxValues.Length() == 4, "wrong number of box values");
+ nsAutoString value1, value2, value3, value4;
+ SetCssTextToCoord(value1, aBoxValues[0]);
+ SetCssTextToCoord(value2, aBoxValues[1]);
+ SetCssTextToCoord(value3, aBoxValues[2]);
+ SetCssTextToCoord(value4, aBoxValues[3]);
+
+ // nsROCSSPrimitiveValue do not have binary comparison operators.
+ // Compare string results instead.
+ aString.Append(value1);
+ if (value1 != value2 || value1 != value3 || value1 != value4) {
+ aString.Append(' ');
+ aString.Append(value2);
+ if (value1 != value3 || value2 != value4) {
+ aString.Append(' ');
+ aString.Append(value3);
+ if (value2 != value4) {
+ aString.Append(' ');
+ aString.Append(value4);
+ }
+ }
+ }
+}
+
+void
+nsComputedDOMStyle::BasicShapeRadiiToString(nsAString& aCssText,
+ const nsStyleCorners& aCorners)
+{
+ nsTArray<nsStyleCoord> horizontal, vertical;
+ nsAutoString horizontalString, verticalString;
+ NS_FOR_CSS_FULL_CORNERS(corner) {
+ horizontal.AppendElement(
+ aCorners.Get(NS_FULL_TO_HALF_CORNER(corner, false)));
+ vertical.AppendElement(
+ aCorners.Get(NS_FULL_TO_HALF_CORNER(corner, true)));
+ }
+ BoxValuesToString(horizontalString, horizontal);
+ BoxValuesToString(verticalString, vertical);
+ aCssText.Append(horizontalString);
+ if (horizontalString == verticalString) {
+ return;
+ }
+ aCssText.AppendLiteral(" / ");
+ aCssText.Append(verticalString);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::CreatePrimitiveValueForBasicShape(
+ const StyleBasicShape* aStyleBasicShape)
+{
+ MOZ_ASSERT(aStyleBasicShape, "Expect a valid basic shape pointer!");
+
+ StyleBasicShapeType type = aStyleBasicShape->GetShapeType();
+ // Shape function name and opening parenthesis.
+ nsAutoString shapeFunctionString;
+ AppendASCIItoUTF16(nsCSSKeywords::GetStringValue(
+ aStyleBasicShape->GetShapeTypeName()),
+ shapeFunctionString);
+ shapeFunctionString.Append('(');
+ switch (type) {
+ case StyleBasicShapeType::Polygon: {
+ bool hasEvenOdd = aStyleBasicShape->GetFillRule() ==
+ StyleFillRule::Evenodd;
+ if (hasEvenOdd) {
+ shapeFunctionString.AppendLiteral("evenodd");
+ }
+ for (size_t i = 0;
+ i < aStyleBasicShape->Coordinates().Length(); i += 2) {
+ nsAutoString coordString;
+ if (i > 0 || hasEvenOdd) {
+ shapeFunctionString.AppendLiteral(", ");
+ }
+ SetCssTextToCoord(coordString,
+ aStyleBasicShape->Coordinates()[i]);
+ shapeFunctionString.Append(coordString);
+ shapeFunctionString.Append(' ');
+ SetCssTextToCoord(coordString,
+ aStyleBasicShape->Coordinates()[i + 1]);
+ shapeFunctionString.Append(coordString);
+ }
+ break;
+ }
+ case StyleBasicShapeType::Circle:
+ case StyleBasicShapeType::Ellipse: {
+ const nsTArray<nsStyleCoord>& radii = aStyleBasicShape->Coordinates();
+ MOZ_ASSERT(radii.Length() ==
+ (type == StyleBasicShapeType::Circle ? 1 : 2),
+ "wrong number of radii");
+ for (size_t i = 0; i < radii.Length(); ++i) {
+ nsAutoString radius;
+ RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
+ bool clampNegativeCalc = true;
+ SetValueToCoord(value, radii[i], clampNegativeCalc, nullptr,
+ nsCSSProps::kShapeRadiusKTable);
+ value->GetCssText(radius);
+ shapeFunctionString.Append(radius);
+ shapeFunctionString.Append(' ');
+ }
+ shapeFunctionString.AppendLiteral("at ");
+
+ RefPtr<nsDOMCSSValueList> position = GetROCSSValueList(false);
+ nsAutoString positionString;
+ SetValueToPosition(aStyleBasicShape->GetPosition(), position);
+ position->GetCssText(positionString);
+ shapeFunctionString.Append(positionString);
+ break;
+ }
+ case StyleBasicShapeType::Inset: {
+ BoxValuesToString(shapeFunctionString, aStyleBasicShape->Coordinates());
+ if (aStyleBasicShape->HasRadius()) {
+ shapeFunctionString.AppendLiteral(" round ");
+ nsAutoString radiiString;
+ BasicShapeRadiiToString(radiiString, aStyleBasicShape->GetRadius());
+ shapeFunctionString.Append(radiiString);
+ }
+ break;
+ }
+ default:
+ NS_NOTREACHED("unexpected type");
+ }
+ shapeFunctionString.Append(')');
+ RefPtr<nsROCSSPrimitiveValue> functionValue = new nsROCSSPrimitiveValue;
+ functionValue->SetString(shapeFunctionString);
+ return functionValue.forget();
+}
+
+template<typename ReferenceBox>
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::CreatePrimitiveValueForShapeSource(
+ const StyleBasicShape* aStyleBasicShape,
+ ReferenceBox aReferenceBox,
+ const KTableEntry aBoxKeywordTable[])
+{
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ if (aStyleBasicShape) {
+ valueList->AppendCSSValue(
+ CreatePrimitiveValueForBasicShape(aStyleBasicShape));
+ }
+
+ if (aReferenceBox == ReferenceBox::NoBox) {
+ return valueList.forget();
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(nsCSSProps::ValueToKeywordEnum(aReferenceBox, aBoxKeywordTable));
+ valueList->AppendCSSValue(val.forget());
+
+ return valueList.forget();
+}
+
+template<typename ReferenceBox>
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetShapeSource(
+ const StyleShapeSource<ReferenceBox>& aShapeSource,
+ const KTableEntry aBoxKeywordTable[])
+{
+ switch (aShapeSource.GetType()) {
+ case StyleShapeSourceType::Shape:
+ return CreatePrimitiveValueForShapeSource(aShapeSource.GetBasicShape(),
+ aShapeSource.GetReferenceBox(),
+ aBoxKeywordTable);
+ case StyleShapeSourceType::Box:
+ return CreatePrimitiveValueForShapeSource(nullptr,
+ aShapeSource.GetReferenceBox(),
+ aBoxKeywordTable);
+ case StyleShapeSourceType::URL: {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ SetValueToURLValue(aShapeSource.GetURL(), val);
+ return val.forget();
+ }
+ case StyleShapeSourceType::None: {
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(eCSSKeyword_none);
+ return val.forget();
+ }
+ default:
+ NS_NOTREACHED("unexpected type");
+ }
+ return nullptr;
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetClipPath()
+{
+ return GetShapeSource(StyleSVGReset()->mClipPath,
+ nsCSSProps::kClipPathGeometryBoxKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetShapeOutside()
+{
+ return GetShapeSource(StyleDisplay()->mShapeOutside,
+ nsCSSProps::kShapeOutsideShapeBoxKTable);
+}
+
+void
+nsComputedDOMStyle::SetCssTextToCoord(nsAString& aCssText,
+ const nsStyleCoord& aCoord)
+{
+ RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
+ bool clampNegativeCalc = true;
+ SetValueToCoord(value, aCoord, clampNegativeCalc);
+ value->GetCssText(aCssText);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::CreatePrimitiveValueForStyleFilter(
+ const nsStyleFilter& aStyleFilter)
+{
+ RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
+ // Handle url().
+ if (aStyleFilter.GetType() == NS_STYLE_FILTER_URL) {
+ MOZ_ASSERT(aStyleFilter.GetURL() &&
+ aStyleFilter.GetURL()->GetURI());
+ SetValueToURLValue(aStyleFilter.GetURL(), value);
+ return value.forget();
+ }
+
+ // Filter function name and opening parenthesis.
+ nsAutoString filterFunctionString;
+ AppendASCIItoUTF16(
+ nsCSSProps::ValueToKeyword(aStyleFilter.GetType(),
+ nsCSSProps::kFilterFunctionKTable),
+ filterFunctionString);
+ filterFunctionString.Append('(');
+
+ nsAutoString argumentString;
+ if (aStyleFilter.GetType() == NS_STYLE_FILTER_DROP_SHADOW) {
+ // Handle drop-shadow()
+ RefPtr<CSSValue> shadowValue =
+ GetCSSShadowArray(aStyleFilter.GetDropShadow(),
+ StyleColor()->mColor,
+ false);
+ ErrorResult dummy;
+ shadowValue->GetCssText(argumentString, dummy);
+ } else {
+ // Filter function argument.
+ SetCssTextToCoord(argumentString, aStyleFilter.GetFilterParameter());
+ }
+ filterFunctionString.Append(argumentString);
+
+ // Filter function closing parenthesis.
+ filterFunctionString.Append(')');
+
+ value->SetString(filterFunctionString);
+ return value.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFilter()
+{
+ const nsTArray<nsStyleFilter>& filters = StyleEffects()->mFilters;
+
+ if (filters.IsEmpty()) {
+ RefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
+ value->SetIdent(eCSSKeyword_none);
+ return value.forget();
+ }
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+ for(uint32_t i = 0; i < filters.Length(); i++) {
+ RefPtr<CSSValue> value = CreatePrimitiveValueForStyleFilter(filters[i]);
+ valueList->AppendCSSValue(value.forget());
+ }
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMask()
+{
+ const nsStyleSVGReset* svg = StyleSVGReset();
+ const nsStyleImageLayers::Layer& firstLayer = svg->mMask.mLayers[0];
+
+ // Mask is now a shorthand, but it used to be a longhand, so that we
+ // need to support computed style for the cases where it used to be
+ // a longhand.
+ if (svg->mMask.mImageCount > 1 ||
+ firstLayer.mClip != NS_STYLE_IMAGELAYER_CLIP_BORDER ||
+ firstLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_BORDER ||
+ firstLayer.mComposite != NS_STYLE_MASK_COMPOSITE_ADD ||
+ firstLayer.mMaskMode != NS_STYLE_MASK_MODE_MATCH_SOURCE ||
+ !nsStyleImageLayers::IsInitialPositionForLayerType(
+ firstLayer.mPosition, nsStyleImageLayers::LayerType::Mask) ||
+ !firstLayer.mRepeat.IsInitialValue() ||
+ !firstLayer.mSize.IsInitialValue() ||
+ !(firstLayer.mImage.GetType() == eStyleImageType_Null ||
+ firstLayer.mImage.GetType() == eStyleImageType_Image)) {
+ return nullptr;
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+ SetValueToURLValue(firstLayer.mSourceURI, val);
+
+ return val.forget();
+}
+
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskClip()
+{
+ return GetBackgroundList(&nsStyleImageLayers::Layer::mClip,
+ &nsStyleImageLayers::mClipCount,
+ StyleSVGReset()->mMask,
+ nsCSSProps::kImageLayerOriginKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskComposite()
+{
+ return GetBackgroundList(&nsStyleImageLayers::Layer::mComposite,
+ &nsStyleImageLayers::mCompositeCount,
+ StyleSVGReset()->mMask,
+ nsCSSProps::kImageLayerCompositeKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskImage()
+{
+ const nsStyleImageLayers& layers = StyleSVGReset()->mMask;
+ return DoGetImageLayerImage(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskMode()
+{
+ return GetBackgroundList(&nsStyleImageLayers::Layer::mMaskMode,
+ &nsStyleImageLayers::mMaskModeCount,
+ StyleSVGReset()->mMask,
+ nsCSSProps::kImageLayerModeKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskOrigin()
+{
+ return GetBackgroundList(&nsStyleImageLayers::Layer::mOrigin,
+ &nsStyleImageLayers::mOriginCount,
+ StyleSVGReset()->mMask,
+ nsCSSProps::kImageLayerOriginKTable);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskPosition()
+{
+ const nsStyleImageLayers& layers = StyleSVGReset()->mMask;
+ return DoGetImageLayerPosition(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskPositionX()
+{
+ const nsStyleImageLayers& layers = StyleSVGReset()->mMask;
+ return DoGetImageLayerPositionX(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskPositionY()
+{
+ const nsStyleImageLayers& layers = StyleSVGReset()->mMask;
+ return DoGetImageLayerPositionY(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskRepeat()
+{
+ const nsStyleImageLayers& layers = StyleSVGReset()->mMask;
+ return DoGetImageLayerRepeat(layers);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskSize()
+{
+ const nsStyleImageLayers& layers = StyleSVGReset()->mMask;
+ return DoGetImageLayerSize(layers);
+}
+#endif
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetMaskType()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleSVGReset()->mMaskType,
+ nsCSSProps::kMaskTypeKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetPaintOrder()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ nsAutoString string;
+ uint8_t paintOrder = StyleSVG()->mPaintOrder;
+ nsStyleUtil::AppendPaintOrderValue(paintOrder, string);
+ val->SetString(string);
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTransitionDelay()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mTransitionDelayCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleTransition *transition = &display->mTransitions[i];
+ RefPtr<nsROCSSPrimitiveValue> delay = new nsROCSSPrimitiveValue;
+ delay->SetTime((float)transition->GetDelay() / (float)PR_MSEC_PER_SEC);
+ valueList->AppendCSSValue(delay.forget());
+ } while (++i < display->mTransitionDelayCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTransitionDuration()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mTransitionDurationCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleTransition *transition = &display->mTransitions[i];
+ RefPtr<nsROCSSPrimitiveValue> duration = new nsROCSSPrimitiveValue;
+
+ duration->SetTime((float)transition->GetDuration() / (float)PR_MSEC_PER_SEC);
+ valueList->AppendCSSValue(duration.forget());
+ } while (++i < display->mTransitionDurationCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTransitionProperty()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mTransitionPropertyCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleTransition *transition = &display->mTransitions[i];
+ RefPtr<nsROCSSPrimitiveValue> property = new nsROCSSPrimitiveValue;
+ nsCSSPropertyID cssprop = transition->GetProperty();
+ if (cssprop == eCSSPropertyExtra_all_properties)
+ property->SetIdent(eCSSKeyword_all);
+ else if (cssprop == eCSSPropertyExtra_no_properties)
+ property->SetIdent(eCSSKeyword_none);
+ else if (cssprop == eCSSProperty_UNKNOWN ||
+ cssprop == eCSSPropertyExtra_variable)
+ {
+ nsAutoString escaped;
+ nsStyleUtil::AppendEscapedCSSIdent(
+ nsDependentAtomString(transition->GetUnknownProperty()), escaped);
+ property->SetString(escaped); // really want SetIdent
+ }
+ else
+ property->SetString(nsCSSProps::GetStringValue(cssprop));
+
+ valueList->AppendCSSValue(property.forget());
+ } while (++i < display->mTransitionPropertyCount);
+
+ return valueList.forget();
+}
+
+void
+nsComputedDOMStyle::AppendTimingFunction(nsDOMCSSValueList *aValueList,
+ const nsTimingFunction& aTimingFunction)
+{
+ RefPtr<nsROCSSPrimitiveValue> timingFunction = new nsROCSSPrimitiveValue;
+
+ nsAutoString tmp;
+ switch (aTimingFunction.mType) {
+ case nsTimingFunction::Type::CubicBezier:
+ nsStyleUtil::AppendCubicBezierTimingFunction(aTimingFunction.mFunc.mX1,
+ aTimingFunction.mFunc.mY1,
+ aTimingFunction.mFunc.mX2,
+ aTimingFunction.mFunc.mY2,
+ tmp);
+ break;
+ case nsTimingFunction::Type::StepStart:
+ case nsTimingFunction::Type::StepEnd:
+ nsStyleUtil::AppendStepsTimingFunction(aTimingFunction.mType,
+ aTimingFunction.mSteps,
+ tmp);
+ break;
+ default:
+ nsStyleUtil::AppendCubicBezierKeywordTimingFunction(aTimingFunction.mType,
+ tmp);
+ break;
+ }
+ timingFunction->SetString(tmp);
+ aValueList->AppendCSSValue(timingFunction.forget());
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTransitionTimingFunction()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mTransitionTimingFunctionCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ AppendTimingFunction(valueList,
+ display->mTransitions[i].GetTimingFunction());
+ } while (++i < display->mTransitionTimingFunctionCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAnimationName()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mAnimationNameCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleAnimation *animation = &display->mAnimations[i];
+ RefPtr<nsROCSSPrimitiveValue> property = new nsROCSSPrimitiveValue;
+
+ const nsString& name = animation->GetName();
+ if (name.IsEmpty()) {
+ property->SetIdent(eCSSKeyword_none);
+ } else {
+ nsAutoString escaped;
+ nsStyleUtil::AppendEscapedCSSIdent(animation->GetName(), escaped);
+ property->SetString(escaped); // really want SetIdent
+ }
+ valueList->AppendCSSValue(property.forget());
+ } while (++i < display->mAnimationNameCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAnimationDelay()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mAnimationDelayCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleAnimation *animation = &display->mAnimations[i];
+ RefPtr<nsROCSSPrimitiveValue> delay = new nsROCSSPrimitiveValue;
+ delay->SetTime((float)animation->GetDelay() / (float)PR_MSEC_PER_SEC);
+ valueList->AppendCSSValue(delay.forget());
+ } while (++i < display->mAnimationDelayCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAnimationDuration()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mAnimationDurationCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleAnimation *animation = &display->mAnimations[i];
+ RefPtr<nsROCSSPrimitiveValue> duration = new nsROCSSPrimitiveValue;
+
+ duration->SetTime((float)animation->GetDuration() / (float)PR_MSEC_PER_SEC);
+ valueList->AppendCSSValue(duration.forget());
+ } while (++i < display->mAnimationDurationCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAnimationTimingFunction()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mAnimationTimingFunctionCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ AppendTimingFunction(valueList,
+ display->mAnimations[i].GetTimingFunction());
+ } while (++i < display->mAnimationTimingFunctionCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAnimationDirection()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mAnimationDirectionCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleAnimation *animation = &display->mAnimations[i];
+ RefPtr<nsROCSSPrimitiveValue> direction = new nsROCSSPrimitiveValue;
+ direction->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(
+ static_cast<int32_t>(animation->GetDirection()),
+ nsCSSProps::kAnimationDirectionKTable));
+
+ valueList->AppendCSSValue(direction.forget());
+ } while (++i < display->mAnimationDirectionCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAnimationFillMode()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mAnimationFillModeCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleAnimation *animation = &display->mAnimations[i];
+ RefPtr<nsROCSSPrimitiveValue> fillMode = new nsROCSSPrimitiveValue;
+ fillMode->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(
+ static_cast<int32_t>(animation->GetFillMode()),
+ nsCSSProps::kAnimationFillModeKTable));
+
+ valueList->AppendCSSValue(fillMode.forget());
+ } while (++i < display->mAnimationFillModeCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAnimationIterationCount()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mAnimationIterationCountCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleAnimation *animation = &display->mAnimations[i];
+ RefPtr<nsROCSSPrimitiveValue> iterationCount = new nsROCSSPrimitiveValue;
+
+ float f = animation->GetIterationCount();
+ /* Need a nasty hack here to work around an optimizer bug in gcc
+ 4.2 on Mac, which somehow gets confused when directly comparing
+ a float to the return value of NS_IEEEPositiveInfinity when
+ building 32-bit builds. */
+#ifdef XP_MACOSX
+ volatile
+#endif
+ float inf = NS_IEEEPositiveInfinity();
+ if (f == inf) {
+ iterationCount->SetIdent(eCSSKeyword_infinite);
+ } else {
+ iterationCount->SetNumber(f);
+ }
+ valueList->AppendCSSValue(iterationCount.forget());
+ } while (++i < display->mAnimationIterationCountCount);
+
+ return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetAnimationPlayState()
+{
+ const nsStyleDisplay* display = StyleDisplay();
+
+ RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
+
+ MOZ_ASSERT(display->mAnimationPlayStateCount > 0,
+ "first item must be explicit");
+ uint32_t i = 0;
+ do {
+ const StyleAnimation *animation = &display->mAnimations[i];
+ RefPtr<nsROCSSPrimitiveValue> playState = new nsROCSSPrimitiveValue;
+ playState->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(animation->GetPlayState(),
+ nsCSSProps::kAnimationPlayStateKTable));
+ valueList->AppendCSSValue(playState.forget());
+ } while (++i < display->mAnimationPlayStateCount);
+
+ return valueList.forget();
+}
+
+static void
+MarkComputedStyleMapDirty(const char* aPref, void* aData)
+{
+ static_cast<nsComputedStyleMap*>(aData)->MarkDirty();
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetCustomProperty(const nsAString& aPropertyName)
+{
+ MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aPropertyName));
+
+ const nsStyleVariables* variables = StyleVariables();
+
+ nsString variableValue;
+ const nsAString& name = Substring(aPropertyName,
+ CSS_CUSTOM_NAME_PREFIX_LENGTH);
+ if (!variables->mVariables.Get(name, variableValue)) {
+ return nullptr;
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetString(variableValue);
+
+ return val.forget();
+}
+
+void
+nsComputedDOMStyle::ParentChainChanged(nsIContent* aContent)
+{
+ NS_ASSERTION(mContent == aContent, "didn't we register mContent?");
+ NS_ASSERTION(mResolvedStyleContext,
+ "should have only registered an observer when "
+ "mResolvedStyleContext is true");
+
+ ClearStyleContext();
+}
+
+/* static */ nsComputedStyleMap*
+nsComputedDOMStyle::GetComputedStyleMap()
+{
+ static nsComputedStyleMap map = {
+ {
+#define COMPUTED_STYLE_PROP(prop_, method_) \
+ { eCSSProperty_##prop_, &nsComputedDOMStyle::DoGet##method_ },
+#include "nsComputedDOMStylePropertyList.h"
+#undef COMPUTED_STYLE_PROP
+ }
+ };
+ return &map;
+}
+
+/* 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 nsCSSPropList.h based on the
+ // entries iterated in nsComputedDOMStylePropertyList.h.
+ nsComputedStyleMap* data = GetComputedStyleMap();
+#define REGISTER_CALLBACK(pref_) \
+ if (pref_[0]) { \
+ Preferences::RegisterCallback(MarkComputedStyleMapDirty, pref_, data); \
+ }
+#define CSS_PROP(prop_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ REGISTER_CALLBACK(pref_)
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+#undef REGISTER_CALLBACK
+}
+
+/* static */ void
+nsComputedDOMStyle::UnregisterPrefChangeCallbacks()
+{
+ nsComputedStyleMap* data = GetComputedStyleMap();
+#define UNREGISTER_CALLBACK(pref_) \
+ if (pref_[0]) { \
+ Preferences::UnregisterCallback(MarkComputedStyleMapDirty, pref_, data); \
+ }
+#define CSS_PROP(prop_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ UNREGISTER_CALLBACK(pref_)
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+#undef UNREGISTER_CALLBACK
+}
diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h
new file mode 100644
index 000000000..223b29a14
--- /dev/null
+++ b/layout/style/nsComputedDOMStyle.h
@@ -0,0 +1,749 @@
+/* -*- 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/. */
+
+/* DOM object returned from element.getComputedStyle() */
+
+#ifndef nsComputedDOMStyle_h__
+#define nsComputedDOMStyle_h__
+
+#include "mozilla/ArenaRefPtr.h"
+#include "mozilla/ArenaRefPtrInlines.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StyleComplexColor.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsCSSProps.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsStyleContext.h"
+#include "nsIWeakReferenceUtils.h"
+#include "mozilla/gfx/Types.h"
+#include "nsCoord.h"
+#include "nsColor.h"
+#include "nsIContent.h"
+#include "nsStyleStruct.h"
+#include "mozilla/WritingModes.h"
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+struct ComputedGridTrackInfo;
+} // namespace mozilla
+
+struct nsComputedStyleMap;
+class nsIFrame;
+class nsIPresShell;
+class nsDOMCSSValueList;
+struct nsMargin;
+class nsROCSSPrimitiveValue;
+class nsStyleCoord;
+class nsStyleCorners;
+struct nsStyleFilter;
+class nsStyleGradient;
+struct nsStyleImage;
+class nsStyleSides;
+struct nsTimingFunction;
+
+class nsComputedDOMStyle final : public nsDOMCSSDeclaration
+ , public nsStubMutationObserver
+{
+private:
+ // Convenience typedefs:
+ typedef nsCSSProps::KTableEntry KTableEntry;
+ typedef mozilla::dom::CSSValue CSSValue;
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsComputedDOMStyle,
+ nsICSSDeclaration)
+
+ NS_DECL_NSICSSDECLARATION
+
+ NS_DECL_NSIDOMCSSSTYLEDECLARATION_HELPER
+ virtual already_AddRefed<CSSValue>
+ GetPropertyCSSValue(const nsAString& aProp, mozilla::ErrorResult& aRv)
+ override;
+ using nsICSSDeclaration::GetPropertyCSSValue;
+ virtual void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aPropName) override;
+
+ enum StyleType {
+ eDefaultOnly, // Only includes UA and user sheets
+ eAll // Includes all stylesheets
+ };
+
+ nsComputedDOMStyle(mozilla::dom::Element* aElement,
+ const nsAString& aPseudoElt,
+ nsIPresShell* aPresShell,
+ StyleType aStyleType);
+
+ virtual nsINode *GetParentObject() override
+ {
+ return mContent;
+ }
+
+ static already_AddRefed<nsStyleContext>
+ GetStyleContextForElement(mozilla::dom::Element* aElement, nsIAtom* aPseudo,
+ nsIPresShell* aPresShell,
+ StyleType aStyleType = eAll);
+
+ static already_AddRefed<nsStyleContext>
+ GetStyleContextForElementNoFlush(mozilla::dom::Element* aElement,
+ nsIAtom* aPseudo,
+ nsIPresShell* aPresShell,
+ StyleType aStyleType = eAll);
+
+ static nsIPresShell*
+ GetPresShellForContent(nsIContent* aContent);
+
+ // Helper for nsDOMWindowUtils::GetVisitedDependentComputedStyle
+ void SetExposeVisitedStyle(bool aExpose) {
+ NS_ASSERTION(aExpose != mExposeVisitedStyle, "should always be changing");
+ mExposeVisitedStyle = aExpose;
+ }
+
+ // nsDOMCSSDeclaration abstract methods which should never be called
+ // on a nsComputedDOMStyle object, but must be defined to avoid
+ // compile errors.
+ virtual mozilla::DeclarationBlock* GetCSSDeclaration(Operation) override;
+ virtual nsresult SetCSSDeclaration(mozilla::DeclarationBlock*) override;
+ virtual nsIDocument* DocToUpdate() override;
+ virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+
+ static already_AddRefed<nsROCSSPrimitiveValue>
+ MatrixToCSSValue(const mozilla::gfx::Matrix4x4& aMatrix);
+
+ static void RegisterPrefChangeCallbacks();
+ static void UnregisterPrefChangeCallbacks();
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
+
+private:
+ virtual ~nsComputedDOMStyle();
+
+ void AssertFlushedPendingReflows() {
+ NS_ASSERTION(mFlushedPendingReflows,
+ "property getter should have been marked layout-dependent");
+ }
+
+ nsMargin GetAdjustedValuesForBoxSizing();
+
+ // Helper method for DoGetTextAlign[Last].
+ already_AddRefed<CSSValue> CreateTextAlignValue(uint8_t aAlign,
+ bool aAlignTrue,
+ const KTableEntry aTable[]);
+ // This indicates error by leaving mStyleContext null.
+ void UpdateCurrentStyleSources(bool aNeedsLayoutFlush);
+ void ClearCurrentStyleSources();
+
+ // Helper functions called by UpdateCurrentStyleSources.
+ void ClearStyleContext();
+ void SetResolvedStyleContext(RefPtr<nsStyleContext>&& aContext);
+ void SetFrameStyleContext(nsStyleContext* aContext);
+
+#define STYLE_STRUCT(name_, checkdata_cb_) \
+ const nsStyle##name_ * Style##name_() { \
+ return mStyleContext->Style##name_(); \
+ }
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ already_AddRefed<CSSValue> GetEllipseRadii(const nsStyleCorners& aRadius,
+ uint8_t aFullCorner);
+
+ already_AddRefed<CSSValue> GetOffsetWidthFor(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetAbsoluteOffset(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetRelativeOffset(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetStickyOffset(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetStaticOffset(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetPaddingWidthFor(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetBorderColorsFor(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetBorderStyleFor(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetBorderWidthFor(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetBorderColorFor(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetMarginWidthFor(mozilla::css::Side aSide);
+
+ already_AddRefed<CSSValue> GetSVGPaintFor(bool aFill);
+
+ // Appends all aLineNames (may be empty) space-separated to aResult.
+ void AppendGridLineNames(nsString& aResult,
+ const nsTArray<nsString>& aLineNames);
+ // Appends aLineNames as a CSSValue* to aValueList. If aLineNames is empty
+ // a value ("[]") is only appended if aSuppressEmptyList is false.
+ void AppendGridLineNames(nsDOMCSSValueList* aValueList,
+ const nsTArray<nsString>& aLineNames,
+ bool aSuppressEmptyList = true);
+ // Appends aLineNames1/2 (if non-empty) as a CSSValue* to aValueList.
+ void AppendGridLineNames(nsDOMCSSValueList* aValueList,
+ const nsTArray<nsString>& aLineNames1,
+ const nsTArray<nsString>& aLineNames2);
+ already_AddRefed<CSSValue> GetGridTrackSize(const nsStyleCoord& aMinSize,
+ const nsStyleCoord& aMaxSize);
+ already_AddRefed<CSSValue> GetGridTemplateColumnsRows(
+ const nsStyleGridTemplate& aTrackList,
+ const mozilla::ComputedGridTrackInfo* aTrackInfo);
+ already_AddRefed<CSSValue> GetGridLine(const nsStyleGridLine& aGridLine);
+
+ bool GetLineHeightCoord(nscoord& aCoord);
+
+ already_AddRefed<CSSValue> GetCSSShadowArray(nsCSSShadowArray* aArray,
+ const nscolor& aDefaultColor,
+ bool aIsBoxShadow);
+
+ already_AddRefed<CSSValue> GetBackgroundList(
+ uint8_t nsStyleImageLayers::Layer::* aMember,
+ uint32_t nsStyleImageLayers::* aCount,
+ const nsStyleImageLayers& aLayers,
+ const KTableEntry aTable[]);
+
+ void GetCSSGradientString(const nsStyleGradient* aGradient,
+ nsAString& aString);
+ void GetImageRectString(nsIURI* aURI,
+ const nsStyleSides& aCropRect,
+ nsString& aString);
+ already_AddRefed<CSSValue> GetScrollSnapPoints(const nsStyleCoord& aCoord);
+ void AppendTimingFunction(nsDOMCSSValueList *aValueList,
+ const nsTimingFunction& aTimingFunction);
+
+ bool ShouldHonorMinSizeAutoInAxis(mozilla::PhysicalAxis aAxis);
+
+ /* Properties queryable as CSSValues.
+ * To avoid a name conflict with nsIDOM*CSS2Properties, these are all
+ * DoGetXXX instead of GetXXX.
+ */
+
+ already_AddRefed<CSSValue> DoGetAppearance();
+
+ /* Box properties */
+ already_AddRefed<CSSValue> DoGetBoxAlign();
+ already_AddRefed<CSSValue> DoGetBoxDecorationBreak();
+ already_AddRefed<CSSValue> DoGetBoxDirection();
+ already_AddRefed<CSSValue> DoGetBoxFlex();
+ already_AddRefed<CSSValue> DoGetBoxOrdinalGroup();
+ already_AddRefed<CSSValue> DoGetBoxOrient();
+ already_AddRefed<CSSValue> DoGetBoxPack();
+ already_AddRefed<CSSValue> DoGetBoxSizing();
+
+ 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> DoGetMixBlendMode();
+ already_AddRefed<CSSValue> DoGetIsolation();
+ already_AddRefed<CSSValue> DoGetObjectFit();
+ already_AddRefed<CSSValue> DoGetObjectPosition();
+ already_AddRefed<CSSValue> DoGetLeft();
+ already_AddRefed<CSSValue> DoGetTop();
+ already_AddRefed<CSSValue> DoGetRight();
+ already_AddRefed<CSSValue> DoGetBottom();
+ already_AddRefed<CSSValue> DoGetStackSizing();
+
+ /* Font properties */
+ already_AddRefed<CSSValue> DoGetColor();
+ already_AddRefed<CSSValue> DoGetFontFamily();
+ already_AddRefed<CSSValue> DoGetFontFeatureSettings();
+ already_AddRefed<CSSValue> DoGetFontKerning();
+ already_AddRefed<CSSValue> DoGetFontLanguageOverride();
+ already_AddRefed<CSSValue> DoGetFontSize();
+ already_AddRefed<CSSValue> DoGetFontSizeAdjust();
+ already_AddRefed<CSSValue> DoGetOsxFontSmoothing();
+ already_AddRefed<CSSValue> DoGetFontStretch();
+ already_AddRefed<CSSValue> DoGetFontStyle();
+ already_AddRefed<CSSValue> DoGetFontSynthesis();
+ already_AddRefed<CSSValue> DoGetFontVariant();
+ already_AddRefed<CSSValue> DoGetFontVariantAlternates();
+ already_AddRefed<CSSValue> DoGetFontVariantCaps();
+ already_AddRefed<CSSValue> DoGetFontVariantEastAsian();
+ already_AddRefed<CSSValue> DoGetFontVariantLigatures();
+ already_AddRefed<CSSValue> DoGetFontVariantNumeric();
+ already_AddRefed<CSSValue> DoGetFontVariantPosition();
+ already_AddRefed<CSSValue> DoGetFontWeight();
+
+ /* Grid properties */
+ already_AddRefed<CSSValue> DoGetGridAutoFlow();
+ already_AddRefed<CSSValue> DoGetGridAutoColumns();
+ already_AddRefed<CSSValue> DoGetGridAutoRows();
+ already_AddRefed<CSSValue> DoGetGridTemplateAreas();
+ already_AddRefed<CSSValue> DoGetGridTemplateColumns();
+ already_AddRefed<CSSValue> DoGetGridTemplateRows();
+ already_AddRefed<CSSValue> DoGetGridColumnStart();
+ already_AddRefed<CSSValue> DoGetGridColumnEnd();
+ already_AddRefed<CSSValue> DoGetGridRowStart();
+ already_AddRefed<CSSValue> DoGetGridRowEnd();
+ already_AddRefed<CSSValue> DoGetGridColumnGap();
+ already_AddRefed<CSSValue> DoGetGridRowGap();
+
+ /* StyleImageLayer properties */
+ already_AddRefed<CSSValue> DoGetImageLayerImage(const nsStyleImageLayers& aLayers);
+ already_AddRefed<CSSValue> DoGetImageLayerPosition(const nsStyleImageLayers& aLayers);
+ already_AddRefed<CSSValue> DoGetImageLayerPositionX(const nsStyleImageLayers& aLayers);
+ already_AddRefed<CSSValue> DoGetImageLayerPositionY(const nsStyleImageLayers& aLayers);
+ already_AddRefed<CSSValue> DoGetImageLayerRepeat(const nsStyleImageLayers& aLayers);
+ already_AddRefed<CSSValue> DoGetImageLayerSize(const nsStyleImageLayers& aLayers);
+
+ /* Background properties */
+ already_AddRefed<CSSValue> DoGetBackgroundAttachment();
+ already_AddRefed<CSSValue> DoGetBackgroundColor();
+ already_AddRefed<CSSValue> DoGetBackgroundImage();
+ already_AddRefed<CSSValue> DoGetBackgroundPosition();
+ already_AddRefed<CSSValue> DoGetBackgroundPositionX();
+ already_AddRefed<CSSValue> DoGetBackgroundPositionY();
+ already_AddRefed<CSSValue> DoGetBackgroundRepeat();
+ already_AddRefed<CSSValue> DoGetBackgroundClip();
+ already_AddRefed<CSSValue> DoGetBackgroundBlendMode();
+ already_AddRefed<CSSValue> DoGetBackgroundOrigin();
+ already_AddRefed<CSSValue> DoGetBackgroundSize();
+
+ /* Mask properties */
+ already_AddRefed<CSSValue> DoGetMask();
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ already_AddRefed<CSSValue> DoGetMaskImage();
+ already_AddRefed<CSSValue> DoGetMaskPosition();
+ already_AddRefed<CSSValue> DoGetMaskPositionX();
+ already_AddRefed<CSSValue> DoGetMaskPositionY();
+ already_AddRefed<CSSValue> DoGetMaskRepeat();
+ already_AddRefed<CSSValue> DoGetMaskClip();
+ already_AddRefed<CSSValue> DoGetMaskOrigin();
+ already_AddRefed<CSSValue> DoGetMaskSize();
+ already_AddRefed<CSSValue> DoGetMaskMode();
+ already_AddRefed<CSSValue> DoGetMaskComposite();
+#endif
+ /* Padding properties */
+ already_AddRefed<CSSValue> DoGetPaddingTop();
+ already_AddRefed<CSSValue> DoGetPaddingBottom();
+ already_AddRefed<CSSValue> DoGetPaddingLeft();
+ already_AddRefed<CSSValue> DoGetPaddingRight();
+
+ /* Table Properties */
+ already_AddRefed<CSSValue> DoGetBorderCollapse();
+ already_AddRefed<CSSValue> DoGetBorderSpacing();
+ already_AddRefed<CSSValue> DoGetCaptionSide();
+ already_AddRefed<CSSValue> DoGetEmptyCells();
+ already_AddRefed<CSSValue> DoGetTableLayout();
+ already_AddRefed<CSSValue> DoGetVerticalAlign();
+
+ /* Border Properties */
+ already_AddRefed<CSSValue> DoGetBorderTopStyle();
+ already_AddRefed<CSSValue> DoGetBorderBottomStyle();
+ already_AddRefed<CSSValue> DoGetBorderLeftStyle();
+ already_AddRefed<CSSValue> DoGetBorderRightStyle();
+ already_AddRefed<CSSValue> DoGetBorderTopWidth();
+ already_AddRefed<CSSValue> DoGetBorderBottomWidth();
+ already_AddRefed<CSSValue> DoGetBorderLeftWidth();
+ already_AddRefed<CSSValue> DoGetBorderRightWidth();
+ already_AddRefed<CSSValue> DoGetBorderTopColor();
+ already_AddRefed<CSSValue> DoGetBorderBottomColor();
+ already_AddRefed<CSSValue> DoGetBorderLeftColor();
+ already_AddRefed<CSSValue> DoGetBorderRightColor();
+ already_AddRefed<CSSValue> DoGetBorderBottomColors();
+ already_AddRefed<CSSValue> DoGetBorderLeftColors();
+ already_AddRefed<CSSValue> DoGetBorderRightColors();
+ already_AddRefed<CSSValue> DoGetBorderTopColors();
+ already_AddRefed<CSSValue> DoGetBorderBottomLeftRadius();
+ already_AddRefed<CSSValue> DoGetBorderBottomRightRadius();
+ already_AddRefed<CSSValue> DoGetBorderTopLeftRadius();
+ already_AddRefed<CSSValue> DoGetBorderTopRightRadius();
+ already_AddRefed<CSSValue> DoGetFloatEdge();
+
+ /* Border Image */
+ already_AddRefed<CSSValue> DoGetBorderImageSource();
+ already_AddRefed<CSSValue> DoGetBorderImageSlice();
+ already_AddRefed<CSSValue> DoGetBorderImageWidth();
+ already_AddRefed<CSSValue> DoGetBorderImageOutset();
+ already_AddRefed<CSSValue> DoGetBorderImageRepeat();
+
+ /* Box Shadow */
+ already_AddRefed<CSSValue> DoGetBoxShadow();
+
+ /* Window Shadow */
+ already_AddRefed<CSSValue> DoGetWindowShadow();
+
+ /* Margin Properties */
+ already_AddRefed<CSSValue> DoGetMarginTopWidth();
+ already_AddRefed<CSSValue> DoGetMarginBottomWidth();
+ already_AddRefed<CSSValue> DoGetMarginLeftWidth();
+ already_AddRefed<CSSValue> DoGetMarginRightWidth();
+
+ /* Outline Properties */
+ already_AddRefed<CSSValue> DoGetOutlineWidth();
+ already_AddRefed<CSSValue> DoGetOutlineStyle();
+ already_AddRefed<CSSValue> DoGetOutlineColor();
+ already_AddRefed<CSSValue> DoGetOutlineOffset();
+ already_AddRefed<CSSValue> DoGetOutlineRadiusBottomLeft();
+ already_AddRefed<CSSValue> DoGetOutlineRadiusBottomRight();
+ already_AddRefed<CSSValue> DoGetOutlineRadiusTopLeft();
+ already_AddRefed<CSSValue> DoGetOutlineRadiusTopRight();
+
+ /* Content Properties */
+ already_AddRefed<CSSValue> DoGetContent();
+ already_AddRefed<CSSValue> DoGetCounterIncrement();
+ already_AddRefed<CSSValue> DoGetCounterReset();
+
+ /* Quotes Properties */
+ already_AddRefed<CSSValue> DoGetQuotes();
+
+ /* z-index */
+ already_AddRefed<CSSValue> DoGetZIndex();
+
+ /* List properties */
+ already_AddRefed<CSSValue> DoGetListStyleImage();
+ already_AddRefed<CSSValue> DoGetListStylePosition();
+ already_AddRefed<CSSValue> DoGetListStyleType();
+ already_AddRefed<CSSValue> DoGetImageRegion();
+
+ /* Text Properties */
+ already_AddRefed<CSSValue> DoGetInitialLetter();
+ already_AddRefed<CSSValue> DoGetLineHeight();
+ already_AddRefed<CSSValue> DoGetRubyAlign();
+ already_AddRefed<CSSValue> DoGetRubyPosition();
+ already_AddRefed<CSSValue> DoGetTextAlign();
+ already_AddRefed<CSSValue> DoGetTextAlignLast();
+ already_AddRefed<CSSValue> DoGetTextCombineUpright();
+ already_AddRefed<CSSValue> DoGetTextDecoration();
+ already_AddRefed<CSSValue> DoGetTextDecorationColor();
+ already_AddRefed<CSSValue> DoGetTextDecorationLine();
+ already_AddRefed<CSSValue> DoGetTextDecorationStyle();
+ already_AddRefed<CSSValue> DoGetTextEmphasisColor();
+ already_AddRefed<CSSValue> DoGetTextEmphasisPosition();
+ already_AddRefed<CSSValue> DoGetTextEmphasisStyle();
+ already_AddRefed<CSSValue> DoGetTextIndent();
+ already_AddRefed<CSSValue> DoGetTextOrientation();
+ already_AddRefed<CSSValue> DoGetTextOverflow();
+ already_AddRefed<CSSValue> DoGetTextTransform();
+ already_AddRefed<CSSValue> DoGetTextShadow();
+ already_AddRefed<CSSValue> DoGetLetterSpacing();
+ already_AddRefed<CSSValue> DoGetWordSpacing();
+ already_AddRefed<CSSValue> DoGetWhiteSpace();
+ already_AddRefed<CSSValue> DoGetWordBreak();
+ already_AddRefed<CSSValue> DoGetOverflowWrap();
+ already_AddRefed<CSSValue> DoGetHyphens();
+ already_AddRefed<CSSValue> DoGetTabSize();
+ already_AddRefed<CSSValue> DoGetTextSizeAdjust();
+ already_AddRefed<CSSValue> DoGetWebkitTextFillColor();
+ already_AddRefed<CSSValue> DoGetWebkitTextStrokeColor();
+ already_AddRefed<CSSValue> DoGetWebkitTextStrokeWidth();
+
+ /* Visibility properties */
+ already_AddRefed<CSSValue> DoGetColorAdjust();
+ already_AddRefed<CSSValue> DoGetOpacity();
+ already_AddRefed<CSSValue> DoGetPointerEvents();
+ already_AddRefed<CSSValue> DoGetVisibility();
+ already_AddRefed<CSSValue> DoGetWritingMode();
+
+ /* Direction properties */
+ already_AddRefed<CSSValue> DoGetDirection();
+ already_AddRefed<CSSValue> DoGetUnicodeBidi();
+
+ /* Display properties */
+ already_AddRefed<CSSValue> DoGetBinding();
+ already_AddRefed<CSSValue> DoGetClear();
+ already_AddRefed<CSSValue> DoGetFloat();
+ already_AddRefed<CSSValue> DoGetDisplay();
+ already_AddRefed<CSSValue> DoGetContain();
+ already_AddRefed<CSSValue> DoGetPosition();
+ already_AddRefed<CSSValue> DoGetClip();
+ already_AddRefed<CSSValue> DoGetImageOrientation();
+ already_AddRefed<CSSValue> DoGetWillChange();
+ already_AddRefed<CSSValue> DoGetOverflow();
+ already_AddRefed<CSSValue> DoGetOverflowX();
+ already_AddRefed<CSSValue> DoGetOverflowY();
+ already_AddRefed<CSSValue> DoGetOverflowClipBox();
+ already_AddRefed<CSSValue> DoGetResize();
+ already_AddRefed<CSSValue> DoGetPageBreakAfter();
+ already_AddRefed<CSSValue> DoGetPageBreakBefore();
+ already_AddRefed<CSSValue> DoGetPageBreakInside();
+ already_AddRefed<CSSValue> DoGetTouchAction();
+ already_AddRefed<CSSValue> DoGetTransform();
+ already_AddRefed<CSSValue> DoGetTransformBox();
+ already_AddRefed<CSSValue> DoGetTransformOrigin();
+ already_AddRefed<CSSValue> DoGetPerspective();
+ already_AddRefed<CSSValue> DoGetBackfaceVisibility();
+ already_AddRefed<CSSValue> DoGetPerspectiveOrigin();
+ already_AddRefed<CSSValue> DoGetTransformStyle();
+ already_AddRefed<CSSValue> DoGetOrient();
+ already_AddRefed<CSSValue> DoGetScrollBehavior();
+ already_AddRefed<CSSValue> DoGetScrollSnapType();
+ already_AddRefed<CSSValue> DoGetScrollSnapTypeX();
+ already_AddRefed<CSSValue> DoGetScrollSnapTypeY();
+ already_AddRefed<CSSValue> DoGetScrollSnapPointsX();
+ already_AddRefed<CSSValue> DoGetScrollSnapPointsY();
+ already_AddRefed<CSSValue> DoGetScrollSnapDestination();
+ already_AddRefed<CSSValue> DoGetScrollSnapCoordinate();
+ already_AddRefed<CSSValue> DoGetShapeOutside();
+
+ /* User interface properties */
+ already_AddRefed<CSSValue> DoGetCursor();
+ already_AddRefed<CSSValue> DoGetForceBrokenImageIcon();
+ already_AddRefed<CSSValue> DoGetIMEMode();
+ already_AddRefed<CSSValue> DoGetUserFocus();
+ already_AddRefed<CSSValue> DoGetUserInput();
+ already_AddRefed<CSSValue> DoGetUserModify();
+ already_AddRefed<CSSValue> DoGetUserSelect();
+ already_AddRefed<CSSValue> DoGetWindowDragging();
+
+ /* Column properties */
+ already_AddRefed<CSSValue> DoGetColumnCount();
+ already_AddRefed<CSSValue> DoGetColumnFill();
+ already_AddRefed<CSSValue> DoGetColumnWidth();
+ already_AddRefed<CSSValue> DoGetColumnGap();
+ already_AddRefed<CSSValue> DoGetColumnRuleWidth();
+ already_AddRefed<CSSValue> DoGetColumnRuleStyle();
+ already_AddRefed<CSSValue> DoGetColumnRuleColor();
+
+ /* CSS Transitions */
+ already_AddRefed<CSSValue> DoGetTransitionProperty();
+ already_AddRefed<CSSValue> DoGetTransitionDuration();
+ already_AddRefed<CSSValue> DoGetTransitionDelay();
+ already_AddRefed<CSSValue> DoGetTransitionTimingFunction();
+
+ /* CSS Animations */
+ already_AddRefed<CSSValue> DoGetAnimationName();
+ already_AddRefed<CSSValue> DoGetAnimationDuration();
+ already_AddRefed<CSSValue> DoGetAnimationDelay();
+ already_AddRefed<CSSValue> DoGetAnimationTimingFunction();
+ already_AddRefed<CSSValue> DoGetAnimationDirection();
+ already_AddRefed<CSSValue> DoGetAnimationFillMode();
+ already_AddRefed<CSSValue> DoGetAnimationIterationCount();
+ already_AddRefed<CSSValue> DoGetAnimationPlayState();
+
+ /* CSS Flexbox properties */
+ already_AddRefed<CSSValue> DoGetFlexBasis();
+ already_AddRefed<CSSValue> DoGetFlexDirection();
+ already_AddRefed<CSSValue> DoGetFlexGrow();
+ already_AddRefed<CSSValue> DoGetFlexShrink();
+ already_AddRefed<CSSValue> DoGetFlexWrap();
+
+ /* CSS Flexbox/Grid properties */
+ already_AddRefed<CSSValue> DoGetOrder();
+
+ /* CSS Box Alignment properties */
+ already_AddRefed<CSSValue> DoGetAlignContent();
+ already_AddRefed<CSSValue> DoGetAlignItems();
+ already_AddRefed<CSSValue> DoGetAlignSelf();
+ already_AddRefed<CSSValue> DoGetJustifyContent();
+ already_AddRefed<CSSValue> DoGetJustifyItems();
+ already_AddRefed<CSSValue> DoGetJustifySelf();
+
+ /* SVG properties */
+ already_AddRefed<CSSValue> DoGetFill();
+ already_AddRefed<CSSValue> DoGetStroke();
+ already_AddRefed<CSSValue> DoGetMarkerEnd();
+ already_AddRefed<CSSValue> DoGetMarkerMid();
+ already_AddRefed<CSSValue> DoGetMarkerStart();
+ already_AddRefed<CSSValue> DoGetStrokeDasharray();
+
+ already_AddRefed<CSSValue> DoGetStrokeDashoffset();
+ already_AddRefed<CSSValue> DoGetStrokeWidth();
+ already_AddRefed<CSSValue> DoGetVectorEffect();
+
+ already_AddRefed<CSSValue> DoGetFillOpacity();
+ already_AddRefed<CSSValue> DoGetFloodOpacity();
+ already_AddRefed<CSSValue> DoGetStopOpacity();
+ already_AddRefed<CSSValue> DoGetStrokeMiterlimit();
+ already_AddRefed<CSSValue> DoGetStrokeOpacity();
+
+ already_AddRefed<CSSValue> DoGetClipRule();
+ already_AddRefed<CSSValue> DoGetFillRule();
+ already_AddRefed<CSSValue> DoGetStrokeLinecap();
+ already_AddRefed<CSSValue> DoGetStrokeLinejoin();
+ already_AddRefed<CSSValue> DoGetTextAnchor();
+
+ already_AddRefed<CSSValue> DoGetColorInterpolation();
+ already_AddRefed<CSSValue> DoGetColorInterpolationFilters();
+ already_AddRefed<CSSValue> DoGetDominantBaseline();
+ already_AddRefed<CSSValue> DoGetImageRendering();
+ already_AddRefed<CSSValue> DoGetShapeRendering();
+ already_AddRefed<CSSValue> DoGetTextRendering();
+
+ already_AddRefed<CSSValue> DoGetFloodColor();
+ already_AddRefed<CSSValue> DoGetLightingColor();
+ already_AddRefed<CSSValue> DoGetStopColor();
+
+ already_AddRefed<CSSValue> DoGetClipPath();
+ already_AddRefed<CSSValue> DoGetFilter();
+ already_AddRefed<CSSValue> DoGetMaskType();
+ already_AddRefed<CSSValue> DoGetPaintOrder();
+
+ /* Custom properties */
+ already_AddRefed<CSSValue> DoGetCustomProperty(const nsAString& aPropertyName);
+
+ nsDOMCSSValueList* GetROCSSValueList(bool aCommaDelimited);
+
+ /* Helper functions */
+ void SetToRGBAColor(nsROCSSPrimitiveValue* aValue, nscolor aColor);
+ void SetValueFromComplexColor(nsROCSSPrimitiveValue* aValue,
+ const mozilla::StyleComplexColor& aColor);
+ void SetValueToStyleImage(const nsStyleImage& aStyleImage,
+ nsROCSSPrimitiveValue* aValue);
+ void SetValueToPositionCoord(const mozilla::Position::Coord& aCoord,
+ nsROCSSPrimitiveValue* aValue);
+ void SetValueToPosition(const mozilla::Position& aPosition,
+ nsDOMCSSValueList* aValueList);
+ void SetValueToURLValue(const mozilla::css::URLValueData* aURL,
+ nsROCSSPrimitiveValue* aValue);
+
+ /**
+ * 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&);
+
+ /**
+ * Method to set aValue to aCoord. If aCoord is a percentage value and
+ * aPercentageBaseGetter is not null, aPercentageBaseGetter is called. If it
+ * returns true, the percentage base it outputs in its out param is used
+ * to compute an nscoord value. If the getter is null or returns false,
+ * the percent value of aCoord is set as a percent value on aValue. aTable,
+ * if not null, is the keyword table to handle eStyleUnit_Enumerated. When
+ * calling SetAppUnits on aValue (for coord or percent values), the value
+ * passed in will be clamped to be no less than aMinAppUnits and no more than
+ * aMaxAppUnits.
+ *
+ * XXXbz should caller pass in some sort of bitfield indicating which units
+ * can be expected or something?
+ */
+ void SetValueToCoord(nsROCSSPrimitiveValue* aValue,
+ const nsStyleCoord& aCoord,
+ bool aClampNegativeCalc,
+ PercentageBaseGetter aPercentageBaseGetter = nullptr,
+ const KTableEntry aTable[] = nullptr,
+ nscoord aMinAppUnits = nscoord_MIN,
+ nscoord aMaxAppUnits = nscoord_MAX);
+
+ /**
+ * If aCoord is a eStyleUnit_Coord returns the nscoord. If it's
+ * eStyleUnit_Percent, attempts to resolve the percentage base and returns
+ * the resulting nscoord. If it's some other unit or a percentge base can't
+ * be determined, returns aDefaultValue.
+ */
+ nscoord StyleCoordToNSCoord(const nsStyleCoord& aCoord,
+ PercentageBaseGetter aPercentageBaseGetter,
+ nscoord aDefaultValue,
+ bool aClampNegativeCalc);
+
+ bool GetCBContentWidth(nscoord& aWidth);
+ bool GetCBContentHeight(nscoord& aWidth);
+ bool GetScrollFrameContentWidth(nscoord& aWidth);
+ bool GetScrollFrameContentHeight(nscoord& aHeight);
+ bool GetFrameBoundsWidthForTransform(nscoord &aWidth);
+ bool GetFrameBoundsHeightForTransform(nscoord &aHeight);
+ bool GetFrameBorderRectWidth(nscoord& aWidth);
+ bool GetFrameBorderRectHeight(nscoord& aHeight);
+
+ /* Helper functions for computing the filter property style. */
+ void SetCssTextToCoord(nsAString& aCssText, const nsStyleCoord& aCoord);
+ already_AddRefed<CSSValue> CreatePrimitiveValueForStyleFilter(
+ const nsStyleFilter& aStyleFilter);
+
+ template<typename ReferenceBox>
+ already_AddRefed<CSSValue>
+ GetShapeSource(const mozilla::StyleShapeSource<ReferenceBox>& aShapeSource,
+ const KTableEntry aBoxKeywordTable[]);
+
+ template<typename ReferenceBox>
+ already_AddRefed<CSSValue>
+ CreatePrimitiveValueForShapeSource(
+ const mozilla::StyleBasicShape* aStyleBasicShape,
+ ReferenceBox aReferenceBox,
+ const KTableEntry aBoxKeywordTable[]);
+
+ // Helper function for computing basic shape styles.
+ already_AddRefed<CSSValue> CreatePrimitiveValueForBasicShape(
+ const mozilla::StyleBasicShape* aStyleBasicShape);
+ void BoxValuesToString(nsAString& aString,
+ const nsTArray<nsStyleCoord>& aBoxValues);
+ void BasicShapeRadiiToString(nsAString& aCssText,
+ const nsStyleCorners& aCorners);
+
+
+ static nsComputedStyleMap* GetComputedStyleMap();
+
+ // We don't really have a good immutable representation of "presentation".
+ // Given the way GetComputedStyle is currently used, we should just grab the
+ // 0th presshell, if any, from the document.
+ nsWeakPtr mDocumentWeak;
+ nsCOMPtr<nsIContent> mContent;
+
+ /**
+ * Strong reference to the style context we access data from. This can be
+ * either a style context we resolved ourselves or a style context we got
+ * from our frame.
+ *
+ * If we got the style context from the frame, we clear out mStyleContext
+ * in ClearCurrentStyleSources. If we resolved one ourselves, then
+ * ClearCurrentStyleSources leaves it in mStyleContext for use the next
+ * time this nsComputedDOMStyle object is queried. UpdateCurrentStyleSources
+ * in this case will check that the style context is still valid to be used,
+ * by checking whether flush styles results in any restyles having been
+ * processed.
+ *
+ * Since an ArenaRefPtr is used to hold the style context, it will be cleared
+ * if the pres arena from which it was allocated goes away.
+ */
+ mozilla::ArenaRefPtr<nsStyleContext> mStyleContext;
+ nsCOMPtr<nsIAtom> mPseudo;
+
+ /*
+ * 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.
+ */
+ nsIPresShell* mPresShell;
+
+ /*
+ * The kind of styles we should be returning.
+ */
+ StyleType mStyleType;
+
+ /**
+ * The nsComputedDOMStyle generation at the time we last resolved a style
+ * context and stored it in mStyleContext.
+ */
+ uint64_t mStyleContextGeneration;
+
+ bool mExposeVisitedStyle;
+
+ /**
+ * Whether we resolved a style context last time we called
+ * UpdateCurrentStyleSources. Initially false.
+ */
+ bool mResolvedStyleContext;
+
+#ifdef DEBUG
+ bool mFlushedPendingReflows;
+#endif
+};
+
+already_AddRefed<nsComputedDOMStyle>
+NS_NewComputedDOMStyle(mozilla::dom::Element* aElement,
+ const nsAString& aPseudoElt,
+ nsIPresShell* aPresShell,
+ nsComputedDOMStyle::StyleType aStyleType =
+ nsComputedDOMStyle::eAll);
+
+#endif /* nsComputedDOMStyle_h__ */
diff --git a/layout/style/nsComputedDOMStylePropertyList.h b/layout/style/nsComputedDOMStylePropertyList.h
new file mode 100644
index 000000000..7c0457e34
--- /dev/null
+++ b/layout/style/nsComputedDOMStylePropertyList.h
@@ -0,0 +1,361 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * a list of the CSS properties that are exposed on nsComputedDOMStyle
+ * objects, for preprocessing
+ */
+
+/******
+
+ This file contains the list of CSS properties that are exposed
+ on nsComputedDOMStyle objects. It is designed to be included in
+ nsComputedDOMStyle.cpp to generate the "computed style map", a
+ table of property IDs and corresponding functions on nsComputedDOMStyle
+ that return the CSSValue representing that property's computed value.
+
+ The COMPUTED_STYLE_PROP macro is defined for each such property.
+ Its arguments are:
+
+ -. 'id' the nsCSSPropertyID ID, without the leading "nsCSSProperty_".
+
+ -. 'method' the nsComputedDOMStyle method name that returns the
+ CSSValue representing that property's computed value, without the leading
+ "Do".
+
+ ******/
+
+/* ******************************************************************* *\
+ * Properties below are listed in alphabetical order. *
+ * Please keep them that way. *
+ * *
+ * Properties commented out with // are not yet implemented *
+ * Properties commented out with //// are shorthands and not queryable *
+\* ******************************************************************* */
+
+/* ***************************** *\
+ * Implementations of CSS styles *
+\* ***************************** */
+
+COMPUTED_STYLE_PROP(align_content, AlignContent)
+COMPUTED_STYLE_PROP(align_items, AlignItems)
+COMPUTED_STYLE_PROP(align_self, AlignSelf)
+//// COMPUTED_STYLE_PROP(animation, Animation)
+COMPUTED_STYLE_PROP(animation_delay, AnimationDelay)
+COMPUTED_STYLE_PROP(animation_direction, AnimationDirection)
+COMPUTED_STYLE_PROP(animation_duration, AnimationDuration)
+COMPUTED_STYLE_PROP(animation_fill_mode, AnimationFillMode)
+COMPUTED_STYLE_PROP(animation_iteration_count, AnimationIterationCount)
+COMPUTED_STYLE_PROP(animation_name, AnimationName)
+COMPUTED_STYLE_PROP(animation_play_state, AnimationPlayState)
+COMPUTED_STYLE_PROP(animation_timing_function, AnimationTimingFunction)
+COMPUTED_STYLE_PROP(backface_visibility, BackfaceVisibility)
+//// COMPUTED_STYLE_PROP(background, Background)
+COMPUTED_STYLE_PROP(background_attachment, BackgroundAttachment)
+COMPUTED_STYLE_PROP(background_blend_mode, BackgroundBlendMode)
+COMPUTED_STYLE_PROP(background_clip, BackgroundClip)
+COMPUTED_STYLE_PROP(background_color, BackgroundColor)
+COMPUTED_STYLE_PROP(background_image, BackgroundImage)
+COMPUTED_STYLE_PROP(background_origin, BackgroundOrigin)
+COMPUTED_STYLE_PROP(background_position, BackgroundPosition)
+COMPUTED_STYLE_PROP(background_position_x, BackgroundPositionX)
+COMPUTED_STYLE_PROP(background_position_y, BackgroundPositionY)
+COMPUTED_STYLE_PROP(background_repeat, BackgroundRepeat)
+COMPUTED_STYLE_PROP(background_size, BackgroundSize)
+//// COMPUTED_STYLE_PROP(border, Border)
+//// COMPUTED_STYLE_PROP(border_bottom, BorderBottom)
+COMPUTED_STYLE_PROP(border_bottom_color, BorderBottomColor)
+COMPUTED_STYLE_PROP(border_bottom_left_radius, BorderBottomLeftRadius)
+COMPUTED_STYLE_PROP(border_bottom_right_radius, BorderBottomRightRadius)
+COMPUTED_STYLE_PROP(border_bottom_style, BorderBottomStyle)
+COMPUTED_STYLE_PROP(border_bottom_width, BorderBottomWidth)
+COMPUTED_STYLE_PROP(border_collapse, BorderCollapse)
+//// COMPUTED_STYLE_PROP(border_color, BorderColor)
+//// COMPUTED_STYLE_PROP(border_image, BorderImage)
+COMPUTED_STYLE_PROP(border_image_outset, BorderImageOutset)
+COMPUTED_STYLE_PROP(border_image_repeat, BorderImageRepeat)
+COMPUTED_STYLE_PROP(border_image_slice, BorderImageSlice)
+COMPUTED_STYLE_PROP(border_image_source, BorderImageSource)
+COMPUTED_STYLE_PROP(border_image_width, BorderImageWidth)
+//// COMPUTED_STYLE_PROP(border_left, BorderLeft)
+COMPUTED_STYLE_PROP(border_left_color, BorderLeftColor)
+COMPUTED_STYLE_PROP(border_left_style, BorderLeftStyle)
+COMPUTED_STYLE_PROP(border_left_width, BorderLeftWidth)
+//// COMPUTED_STYLE_PROP(border_right, BorderRight)
+COMPUTED_STYLE_PROP(border_right_color, BorderRightColor)
+COMPUTED_STYLE_PROP(border_right_style, BorderRightStyle)
+COMPUTED_STYLE_PROP(border_right_width, BorderRightWidth)
+COMPUTED_STYLE_PROP(border_spacing, BorderSpacing)
+//// COMPUTED_STYLE_PROP(border_style, BorderStyle)
+//// COMPUTED_STYLE_PROP(border_top, BorderTop)
+COMPUTED_STYLE_PROP(border_top_color, BorderTopColor)
+COMPUTED_STYLE_PROP(border_top_left_radius, BorderTopLeftRadius)
+COMPUTED_STYLE_PROP(border_top_right_radius, BorderTopRightRadius)
+COMPUTED_STYLE_PROP(border_top_style, BorderTopStyle)
+COMPUTED_STYLE_PROP(border_top_width, BorderTopWidth)
+//// COMPUTED_STYLE_PROP(border_width, BorderWidth)
+COMPUTED_STYLE_PROP(bottom, Bottom)
+COMPUTED_STYLE_PROP(box_decoration_break, BoxDecorationBreak)
+COMPUTED_STYLE_PROP(box_shadow, BoxShadow)
+COMPUTED_STYLE_PROP(box_sizing, BoxSizing)
+COMPUTED_STYLE_PROP(caption_side, CaptionSide)
+COMPUTED_STYLE_PROP(clear, Clear)
+COMPUTED_STYLE_PROP(clip, Clip)
+COMPUTED_STYLE_PROP(color, Color)
+COMPUTED_STYLE_PROP(color_adjust, ColorAdjust)
+COMPUTED_STYLE_PROP(column_count, ColumnCount)
+COMPUTED_STYLE_PROP(column_fill, ColumnFill)
+COMPUTED_STYLE_PROP(column_gap, ColumnGap)
+//// COMPUTED_STYLE_PROP(column_rule, ColumnRule)
+COMPUTED_STYLE_PROP(column_rule_color, ColumnRuleColor)
+COMPUTED_STYLE_PROP(column_rule_style, ColumnRuleStyle)
+COMPUTED_STYLE_PROP(column_rule_width, ColumnRuleWidth)
+COMPUTED_STYLE_PROP(column_width, ColumnWidth)
+COMPUTED_STYLE_PROP(contain, Contain)
+COMPUTED_STYLE_PROP(content, Content)
+COMPUTED_STYLE_PROP(counter_increment, CounterIncrement)
+COMPUTED_STYLE_PROP(counter_reset, CounterReset)
+COMPUTED_STYLE_PROP(cursor, Cursor)
+COMPUTED_STYLE_PROP(direction, Direction)
+COMPUTED_STYLE_PROP(display, Display)
+COMPUTED_STYLE_PROP(empty_cells, EmptyCells)
+COMPUTED_STYLE_PROP(flex_basis, FlexBasis)
+COMPUTED_STYLE_PROP(flex_direction, FlexDirection)
+COMPUTED_STYLE_PROP(flex_grow, FlexGrow)
+COMPUTED_STYLE_PROP(flex_shrink, FlexShrink)
+COMPUTED_STYLE_PROP(flex_wrap, FlexWrap)
+COMPUTED_STYLE_PROP(float_, Float)
+//// COMPUTED_STYLE_PROP(font, Font)
+COMPUTED_STYLE_PROP(font_family, FontFamily)
+COMPUTED_STYLE_PROP(font_feature_settings, FontFeatureSettings)
+COMPUTED_STYLE_PROP(font_kerning, FontKerning)
+COMPUTED_STYLE_PROP(font_language_override, FontLanguageOverride)
+COMPUTED_STYLE_PROP(font_size, FontSize)
+COMPUTED_STYLE_PROP(font_size_adjust, FontSizeAdjust)
+COMPUTED_STYLE_PROP(font_stretch, FontStretch)
+COMPUTED_STYLE_PROP(font_style, FontStyle)
+COMPUTED_STYLE_PROP(font_synthesis, FontSynthesis)
+COMPUTED_STYLE_PROP(font_variant, FontVariant)
+COMPUTED_STYLE_PROP(font_variant_alternates, FontVariantAlternates)
+COMPUTED_STYLE_PROP(font_variant_caps, FontVariantCaps)
+COMPUTED_STYLE_PROP(font_variant_east_asian, FontVariantEastAsian)
+COMPUTED_STYLE_PROP(font_variant_ligatures, FontVariantLigatures)
+COMPUTED_STYLE_PROP(font_variant_numeric, FontVariantNumeric)
+COMPUTED_STYLE_PROP(font_variant_position, FontVariantPosition)
+COMPUTED_STYLE_PROP(font_weight, FontWeight)
+COMPUTED_STYLE_PROP(grid_auto_columns, GridAutoColumns)
+COMPUTED_STYLE_PROP(grid_auto_flow, GridAutoFlow)
+COMPUTED_STYLE_PROP(grid_auto_rows, GridAutoRows)
+COMPUTED_STYLE_PROP(grid_column_end, GridColumnEnd)
+COMPUTED_STYLE_PROP(grid_column_gap, GridColumnGap)
+COMPUTED_STYLE_PROP(grid_column_start, GridColumnStart)
+COMPUTED_STYLE_PROP(grid_row_end, GridRowEnd)
+COMPUTED_STYLE_PROP(grid_row_gap, GridRowGap)
+COMPUTED_STYLE_PROP(grid_row_start, GridRowStart)
+COMPUTED_STYLE_PROP(grid_template_areas, GridTemplateAreas)
+COMPUTED_STYLE_PROP(grid_template_columns, GridTemplateColumns)
+COMPUTED_STYLE_PROP(grid_template_rows, GridTemplateRows)
+COMPUTED_STYLE_PROP(height, Height)
+COMPUTED_STYLE_PROP(hyphens, Hyphens)
+COMPUTED_STYLE_PROP(image_orientation, ImageOrientation)
+COMPUTED_STYLE_PROP(ime_mode, IMEMode)
+COMPUTED_STYLE_PROP(initial_letter, InitialLetter)
+COMPUTED_STYLE_PROP(isolation, Isolation)
+COMPUTED_STYLE_PROP(justify_content, JustifyContent)
+COMPUTED_STYLE_PROP(justify_items, JustifyItems)
+COMPUTED_STYLE_PROP(justify_self, JustifySelf)
+COMPUTED_STYLE_PROP(left, Left)
+COMPUTED_STYLE_PROP(letter_spacing, LetterSpacing)
+COMPUTED_STYLE_PROP(line_height, LineHeight)
+//// COMPUTED_STYLE_PROP(list_style, ListStyle)
+COMPUTED_STYLE_PROP(list_style_image, ListStyleImage)
+COMPUTED_STYLE_PROP(list_style_position, ListStylePosition)
+COMPUTED_STYLE_PROP(list_style_type, ListStyleType)
+//// COMPUTED_STYLE_PROP(margin, Margin)
+COMPUTED_STYLE_PROP(margin_bottom, MarginBottomWidth)
+COMPUTED_STYLE_PROP(margin_left, MarginLeftWidth)
+COMPUTED_STYLE_PROP(margin_right, MarginRightWidth)
+COMPUTED_STYLE_PROP(margin_top, MarginTopWidth)
+// COMPUTED_STYLE_PROP(marks, Marks)
+COMPUTED_STYLE_PROP(max_height, MaxHeight)
+COMPUTED_STYLE_PROP(max_width, MaxWidth)
+COMPUTED_STYLE_PROP(min_height, MinHeight)
+COMPUTED_STYLE_PROP(min_width, MinWidth)
+COMPUTED_STYLE_PROP(mix_blend_mode, MixBlendMode)
+COMPUTED_STYLE_PROP(object_fit, ObjectFit)
+COMPUTED_STYLE_PROP(object_position, ObjectPosition)
+COMPUTED_STYLE_PROP(opacity, Opacity)
+// COMPUTED_STYLE_PROP(orphans, Orphans)
+//// COMPUTED_STYLE_PROP(outline, Outline)
+COMPUTED_STYLE_PROP(order, Order)
+COMPUTED_STYLE_PROP(outline_color, OutlineColor)
+COMPUTED_STYLE_PROP(outline_offset, OutlineOffset)
+COMPUTED_STYLE_PROP(outline_style, OutlineStyle)
+COMPUTED_STYLE_PROP(outline_width, OutlineWidth)
+COMPUTED_STYLE_PROP(overflow, Overflow)
+COMPUTED_STYLE_PROP(overflow_clip_box, OverflowClipBox)
+COMPUTED_STYLE_PROP(overflow_wrap, OverflowWrap)
+COMPUTED_STYLE_PROP(overflow_x, OverflowX)
+COMPUTED_STYLE_PROP(overflow_y, OverflowY)
+//// COMPUTED_STYLE_PROP(padding, Padding)
+COMPUTED_STYLE_PROP(padding_bottom, PaddingBottom)
+COMPUTED_STYLE_PROP(padding_left, PaddingLeft)
+COMPUTED_STYLE_PROP(padding_right, PaddingRight)
+COMPUTED_STYLE_PROP(padding_top, PaddingTop)
+// COMPUTED_STYLE_PROP(page, Page)
+COMPUTED_STYLE_PROP(page_break_after, PageBreakAfter)
+COMPUTED_STYLE_PROP(page_break_before, PageBreakBefore)
+COMPUTED_STYLE_PROP(page_break_inside, PageBreakInside)
+COMPUTED_STYLE_PROP(perspective, Perspective)
+COMPUTED_STYLE_PROP(perspective_origin, PerspectiveOrigin)
+COMPUTED_STYLE_PROP(pointer_events, PointerEvents)
+COMPUTED_STYLE_PROP(position, Position)
+COMPUTED_STYLE_PROP(quotes, Quotes)
+COMPUTED_STYLE_PROP(resize, Resize)
+COMPUTED_STYLE_PROP(right, Right)
+COMPUTED_STYLE_PROP(ruby_align, RubyAlign)
+COMPUTED_STYLE_PROP(ruby_position, RubyPosition)
+COMPUTED_STYLE_PROP(scroll_behavior, ScrollBehavior)
+COMPUTED_STYLE_PROP(scroll_snap_coordinate, ScrollSnapCoordinate)
+COMPUTED_STYLE_PROP(scroll_snap_destination, ScrollSnapDestination)
+COMPUTED_STYLE_PROP(scroll_snap_points_x, ScrollSnapPointsX)
+COMPUTED_STYLE_PROP(scroll_snap_points_y, ScrollSnapPointsY)
+COMPUTED_STYLE_PROP(scroll_snap_type_x, ScrollSnapTypeX)
+COMPUTED_STYLE_PROP(scroll_snap_type_y, ScrollSnapTypeY)
+COMPUTED_STYLE_PROP(shape_outside, ShapeOutside)
+//// COMPUTED_STYLE_PROP(size, Size)
+COMPUTED_STYLE_PROP(table_layout, TableLayout)
+COMPUTED_STYLE_PROP(text_align, TextAlign)
+COMPUTED_STYLE_PROP(text_align_last, TextAlignLast)
+COMPUTED_STYLE_PROP(text_combine_upright, TextCombineUpright)
+COMPUTED_STYLE_PROP(text_decoration, TextDecoration)
+COMPUTED_STYLE_PROP(text_decoration_color, TextDecorationColor)
+COMPUTED_STYLE_PROP(text_decoration_line, TextDecorationLine)
+COMPUTED_STYLE_PROP(text_decoration_style, TextDecorationStyle)
+//// COMPUTED_STYLE_PROP(text_emphasis, TextEmphasis)
+COMPUTED_STYLE_PROP(text_emphasis_color, TextEmphasisColor)
+COMPUTED_STYLE_PROP(text_emphasis_position, TextEmphasisPosition)
+COMPUTED_STYLE_PROP(text_emphasis_style, TextEmphasisStyle)
+COMPUTED_STYLE_PROP(text_indent, TextIndent)
+COMPUTED_STYLE_PROP(text_orientation, TextOrientation)
+COMPUTED_STYLE_PROP(text_overflow, TextOverflow)
+COMPUTED_STYLE_PROP(text_shadow, TextShadow)
+COMPUTED_STYLE_PROP(text_transform, TextTransform)
+COMPUTED_STYLE_PROP(top, Top)
+COMPUTED_STYLE_PROP(touch_action, TouchAction)
+COMPUTED_STYLE_PROP(transform, Transform)
+COMPUTED_STYLE_PROP(transform_box, TransformBox)
+COMPUTED_STYLE_PROP(transform_origin, TransformOrigin)
+COMPUTED_STYLE_PROP(transform_style, TransformStyle)
+//// COMPUTED_STYLE_PROP(transition, Transition)
+COMPUTED_STYLE_PROP(transition_delay, TransitionDelay)
+COMPUTED_STYLE_PROP(transition_duration, TransitionDuration)
+COMPUTED_STYLE_PROP(transition_property, TransitionProperty)
+COMPUTED_STYLE_PROP(transition_timing_function, TransitionTimingFunction)
+COMPUTED_STYLE_PROP(unicode_bidi, UnicodeBidi)
+COMPUTED_STYLE_PROP(vertical_align, VerticalAlign)
+COMPUTED_STYLE_PROP(visibility, Visibility)
+COMPUTED_STYLE_PROP(white_space, WhiteSpace)
+// COMPUTED_STYLE_PROP(widows, Widows)
+COMPUTED_STYLE_PROP(width, Width)
+COMPUTED_STYLE_PROP(will_change, WillChange)
+COMPUTED_STYLE_PROP(word_break, WordBreak)
+COMPUTED_STYLE_PROP(word_spacing, WordSpacing)
+COMPUTED_STYLE_PROP(writing_mode, WritingMode)
+COMPUTED_STYLE_PROP(z_index, ZIndex)
+
+/* ******************************* *\
+ * Implementations of -moz- styles *
+\* ******************************* */
+
+COMPUTED_STYLE_PROP(appearance, Appearance)
+COMPUTED_STYLE_PROP(binding, Binding)
+COMPUTED_STYLE_PROP(border_bottom_colors, BorderBottomColors)
+COMPUTED_STYLE_PROP(border_left_colors, BorderLeftColors)
+COMPUTED_STYLE_PROP(border_right_colors, BorderRightColors)
+COMPUTED_STYLE_PROP(border_top_colors, BorderTopColors)
+COMPUTED_STYLE_PROP(box_align, BoxAlign)
+COMPUTED_STYLE_PROP(box_direction, BoxDirection)
+COMPUTED_STYLE_PROP(box_flex, BoxFlex)
+COMPUTED_STYLE_PROP(box_ordinal_group, BoxOrdinalGroup)
+COMPUTED_STYLE_PROP(box_orient, BoxOrient)
+COMPUTED_STYLE_PROP(box_pack, BoxPack)
+COMPUTED_STYLE_PROP(float_edge, FloatEdge)
+COMPUTED_STYLE_PROP(force_broken_image_icon, ForceBrokenImageIcon)
+COMPUTED_STYLE_PROP(image_region, ImageRegion)
+COMPUTED_STYLE_PROP(orient, Orient)
+COMPUTED_STYLE_PROP(osx_font_smoothing, OsxFontSmoothing)
+COMPUTED_STYLE_PROP(_moz_outline_radius_bottomLeft, OutlineRadiusBottomLeft)
+COMPUTED_STYLE_PROP(_moz_outline_radius_bottomRight,OutlineRadiusBottomRight)
+COMPUTED_STYLE_PROP(_moz_outline_radius_topLeft, OutlineRadiusTopLeft)
+COMPUTED_STYLE_PROP(_moz_outline_radius_topRight, OutlineRadiusTopRight)
+COMPUTED_STYLE_PROP(stack_sizing, StackSizing)
+COMPUTED_STYLE_PROP(_moz_tab_size, TabSize)
+COMPUTED_STYLE_PROP(text_size_adjust, TextSizeAdjust)
+COMPUTED_STYLE_PROP(user_focus, UserFocus)
+COMPUTED_STYLE_PROP(user_input, UserInput)
+COMPUTED_STYLE_PROP(user_modify, UserModify)
+COMPUTED_STYLE_PROP(user_select, UserSelect)
+COMPUTED_STYLE_PROP(_moz_window_dragging, WindowDragging)
+COMPUTED_STYLE_PROP(_moz_window_shadow, WindowShadow)
+
+/* ********************************** *\
+ * Implementations of -webkit- styles *
+\* ********************************** */
+
+COMPUTED_STYLE_PROP(_webkit_text_fill_color, WebkitTextFillColor)
+//// COMPUTED_STYLE_PROP(webkit-text-stroke, WebkitTextStroke)
+COMPUTED_STYLE_PROP(_webkit_text_stroke_color, WebkitTextStrokeColor)
+COMPUTED_STYLE_PROP(_webkit_text_stroke_width, WebkitTextStrokeWidth)
+
+/* ***************************** *\
+ * Implementations of SVG styles *
+\* ***************************** */
+
+COMPUTED_STYLE_PROP(clip_path, ClipPath)
+COMPUTED_STYLE_PROP(clip_rule, ClipRule)
+COMPUTED_STYLE_PROP(color_interpolation, ColorInterpolation)
+COMPUTED_STYLE_PROP(color_interpolation_filters, ColorInterpolationFilters)
+COMPUTED_STYLE_PROP(dominant_baseline, DominantBaseline)
+COMPUTED_STYLE_PROP(fill, Fill)
+COMPUTED_STYLE_PROP(fill_opacity, FillOpacity)
+COMPUTED_STYLE_PROP(fill_rule, FillRule)
+COMPUTED_STYLE_PROP(filter, Filter)
+COMPUTED_STYLE_PROP(flood_color, FloodColor)
+COMPUTED_STYLE_PROP(flood_opacity, FloodOpacity)
+COMPUTED_STYLE_PROP(image_rendering, ImageRendering)
+COMPUTED_STYLE_PROP(lighting_color, LightingColor)
+COMPUTED_STYLE_PROP(marker_end, MarkerEnd)
+COMPUTED_STYLE_PROP(marker_mid, MarkerMid)
+COMPUTED_STYLE_PROP(marker_start, MarkerStart)
+COMPUTED_STYLE_PROP(mask, Mask)
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+COMPUTED_STYLE_PROP(mask_clip, MaskClip)
+COMPUTED_STYLE_PROP(mask_composite, MaskComposite)
+COMPUTED_STYLE_PROP(mask_image, MaskImage)
+COMPUTED_STYLE_PROP(mask_mode, MaskMode)
+COMPUTED_STYLE_PROP(mask_origin, MaskOrigin)
+COMPUTED_STYLE_PROP(mask_position, MaskPosition)
+COMPUTED_STYLE_PROP(mask_position_x, MaskPositionX)
+COMPUTED_STYLE_PROP(mask_position_y, MaskPositionY)
+COMPUTED_STYLE_PROP(mask_repeat, MaskRepeat)
+COMPUTED_STYLE_PROP(mask_size, MaskSize)
+#endif
+COMPUTED_STYLE_PROP(mask_type, MaskType)
+COMPUTED_STYLE_PROP(paint_order, PaintOrder)
+COMPUTED_STYLE_PROP(shape_rendering, ShapeRendering)
+COMPUTED_STYLE_PROP(stop_color, StopColor)
+COMPUTED_STYLE_PROP(stop_opacity, StopOpacity)
+COMPUTED_STYLE_PROP(stroke, Stroke)
+COMPUTED_STYLE_PROP(stroke_dasharray, StrokeDasharray)
+COMPUTED_STYLE_PROP(stroke_dashoffset, StrokeDashoffset)
+COMPUTED_STYLE_PROP(stroke_linecap, StrokeLinecap)
+COMPUTED_STYLE_PROP(stroke_linejoin, StrokeLinejoin)
+COMPUTED_STYLE_PROP(stroke_miterlimit, StrokeMiterlimit)
+COMPUTED_STYLE_PROP(stroke_opacity, StrokeOpacity)
+COMPUTED_STYLE_PROP(stroke_width, StrokeWidth)
+COMPUTED_STYLE_PROP(text_anchor, TextAnchor)
+COMPUTED_STYLE_PROP(text_rendering, TextRendering)
+COMPUTED_STYLE_PROP(vector_effect, VectorEffect)
diff --git a/layout/style/nsDOMCSSAttrDeclaration.cpp b/layout/style/nsDOMCSSAttrDeclaration.cpp
new file mode 100644
index 000000000..ce638a9c2
--- /dev/null
+++ b/layout/style/nsDOMCSSAttrDeclaration.cpp
@@ -0,0 +1,204 @@
+/* -*- 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/. */
+
+/* DOM object for element.style */
+
+#include "nsDOMCSSAttrDeclaration.h"
+
+#include "mozilla/css/Declaration.h"
+#include "mozilla/css/StyleRule.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDocument.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsIURI.h"
+#include "nsNodeUtils.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsIFrame.h"
+#include "ActiveLayerTracker.h"
+
+using namespace mozilla;
+
+nsDOMCSSAttributeDeclaration::nsDOMCSSAttributeDeclaration(dom::Element* aElement,
+ bool aIsSMILOverride)
+ : mElement(aElement)
+ , mIsSMILOverride(aIsSMILOverride)
+{
+ MOZ_COUNT_CTOR(nsDOMCSSAttributeDeclaration);
+
+ NS_ASSERTION(aElement, "Inline style for a NULL element?");
+}
+
+nsDOMCSSAttributeDeclaration::~nsDOMCSSAttributeDeclaration()
+{
+ MOZ_COUNT_DTOR(nsDOMCSSAttributeDeclaration);
+}
+
+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()) {
+ // This marks the wrapper black.
+ tmp->GetWrapper();
+ }
+ return true;
+ }
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMCSSAttributeDeclaration)
+ return tmp->IsBlack() ||
+ (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->IsBlack() ||
+ (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_IMPL_QUERY_TAIL_INHERITING(nsDOMCSSDeclaration)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCSSAttributeDeclaration)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCSSAttributeDeclaration)
+
+nsresult
+nsDOMCSSAttributeDeclaration::SetCSSDeclaration(DeclarationBlock* aDecl)
+{
+ NS_ASSERTION(mElement, "Must have Element to set the declaration!");
+ return mIsSMILOverride
+ ? mElement->SetSMILOverrideStyleDeclaration(aDecl, true)
+ : mElement->SetInlineStyleDeclaration(aDecl, nullptr, true);
+}
+
+nsIDocument*
+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::GetCSSDeclaration(Operation aOperation)
+{
+ if (!mElement)
+ return nullptr;
+
+ DeclarationBlock* declaration;
+ if (mIsSMILOverride) {
+ declaration = mElement->GetSMILOverrideStyleDeclaration();
+ } else {
+ declaration = mElement->GetInlineStyleDeclaration();
+ }
+
+ // Notify observers that our style="" attribute is going to change
+ // unless:
+ // * this is a declaration that holds SMIL animation values (which
+ // aren't reflected in the DOM style="" attribute), or
+ // * we're getting the declaration for reading, or
+ // * we're getting it for property removal but we don't currently have
+ // a declaration.
+
+ // XXXbz this is a bit of a hack, especially doing it before the
+ // BeginUpdate(), but this is a good chokepoint where we know we
+ // plan to modify the CSSDeclaration, so need to notify
+ // AttributeWillChange if this is inline style.
+ if (!mIsSMILOverride &&
+ ((aOperation == eOperation_Modify) ||
+ (aOperation == eOperation_RemoveProperty && declaration))) {
+ nsNodeUtils::AttributeWillChange(mElement, kNameSpaceID_None,
+ nsGkAtoms::style,
+ nsIDOMMutationEvent::MODIFICATION,
+ nullptr);
+ }
+
+ if (declaration) {
+ return declaration;
+ }
+
+ if (aOperation != eOperation_Modify) {
+ return nullptr;
+ }
+
+ // cannot fail
+ RefPtr<DeclarationBlock> decl;
+ if (mElement->IsStyledByServo()) {
+ decl = new ServoDeclarationBlock();
+ } else {
+ decl = new css::Declaration();
+ decl->AsGecko()->InitializeEmpty();
+ }
+
+ // this *can* fail (inside SetAttrAndNotify, at least).
+ nsresult rv;
+ if (mIsSMILOverride) {
+ rv = mElement->SetSMILOverrideStyleDeclaration(decl, false);
+ } else {
+ rv = mElement->SetInlineStyleDeclaration(decl, nullptr, false);
+ }
+
+ if (NS_FAILED(rv)) {
+ return nullptr; // the decl will be destroyed along with the style rule
+ }
+
+ return decl;
+}
+
+void
+nsDOMCSSAttributeDeclaration::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
+{
+ NS_ASSERTION(mElement, "Something is severely broken -- there should be an Element here!");
+
+ nsIDocument* doc = mElement->OwnerDoc();
+ aCSSParseEnv.mSheetURI = doc->GetDocumentURI();
+ aCSSParseEnv.mBaseURI = mElement->GetBaseURI();
+ aCSSParseEnv.mPrincipal = mElement->NodePrincipal();
+ aCSSParseEnv.mCSSLoader = doc->CSSLoader();
+}
+
+NS_IMETHODIMP
+nsDOMCSSAttributeDeclaration::GetParentRule(nsIDOMCSSRule **aParent)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+
+ *aParent = nullptr;
+ return NS_OK;
+}
+
+/* virtual */ nsINode*
+nsDOMCSSAttributeDeclaration::GetParentObject()
+{
+ return mElement;
+}
+
+NS_IMETHODIMP
+nsDOMCSSAttributeDeclaration::SetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsAString& aValue)
+{
+ // Scripted modifications to style.opacity or style.transform
+ // could immediately force us into the animated state if heuristics suggest
+ // this is scripted animation.
+ // FIXME: This is missing the margin shorthand and the logical versions of
+ // the margin properties, see bug 1266287.
+ if (aPropID == eCSSProperty_opacity || aPropID == eCSSProperty_transform ||
+ aPropID == eCSSProperty_left || aPropID == eCSSProperty_top ||
+ aPropID == eCSSProperty_right || aPropID == eCSSProperty_bottom ||
+ aPropID == eCSSProperty_margin_left || aPropID == eCSSProperty_margin_top ||
+ aPropID == eCSSProperty_margin_right || aPropID == eCSSProperty_margin_bottom ||
+ aPropID == eCSSProperty_background_position_x ||
+ aPropID == eCSSProperty_background_position_y ||
+ aPropID == eCSSProperty_background_position) {
+ nsIFrame* frame = mElement->GetPrimaryFrame();
+ if (frame) {
+ ActiveLayerTracker::NotifyInlineStyleRuleModified(frame, aPropID, aValue, this);
+ }
+ }
+ return nsDOMCSSDeclaration::SetPropertyValue(aPropID, aValue);
+}
diff --git a/layout/style/nsDOMCSSAttrDeclaration.h b/layout/style/nsDOMCSSAttrDeclaration.h
new file mode 100644
index 000000000..7c0fbacc0
--- /dev/null
+++ b/layout/style/nsDOMCSSAttrDeclaration.h
@@ -0,0 +1,57 @@
+/* -*- 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/. */
+
+/* DOM object for element.style */
+
+#ifndef nsDOMCSSAttributeDeclaration_h
+#define nsDOMCSSAttributeDeclaration_h
+
+#include "mozilla/Attributes.h"
+#include "nsDOMCSSDeclaration.h"
+
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class nsDOMCSSAttributeDeclaration final : public nsDOMCSSDeclaration
+{
+public:
+ typedef mozilla::dom::Element Element;
+ nsDOMCSSAttributeDeclaration(Element* aContent, bool aIsSMILOverride);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDOMCSSAttributeDeclaration,
+ nsICSSDeclaration)
+
+ // If GetCSSDeclaration returns non-null, then the decl it returns
+ // is owned by our current style rule.
+ virtual mozilla::DeclarationBlock* GetCSSDeclaration(Operation aOperation) override;
+ virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) override;
+ NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent) override;
+
+ virtual nsINode* GetParentObject() override;
+
+ NS_IMETHOD SetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsAString& aValue) override;
+
+protected:
+ ~nsDOMCSSAttributeDeclaration();
+
+ virtual nsresult SetCSSDeclaration(mozilla::DeclarationBlock* aDecl) override;
+ virtual nsIDocument* 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;
+};
+
+#endif /* nsDOMCSSAttributeDeclaration_h */
diff --git a/layout/style/nsDOMCSSDeclaration.cpp b/layout/style/nsDOMCSSDeclaration.cpp
new file mode 100644
index 000000000..bd6c6069d
--- /dev/null
+++ b/layout/style/nsDOMCSSDeclaration.cpp
@@ -0,0 +1,406 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for DOM objects for element.style and cssStyleRule.style */
+
+#include "nsDOMCSSDeclaration.h"
+
+#include "nsCSSParser.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Rule.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "mozilla/dom/CSS2PropertiesBinding.h"
+#include "nsCSSProps.h"
+#include "nsCOMPtr.h"
+#include "mozAutoDocUpdate.h"
+#include "nsIURI.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsContentUtils.h"
+#include "nsQueryObject.h"
+#include "mozilla/layers/ScrollLinkedEffectDetector.h"
+
+using namespace mozilla;
+
+nsDOMCSSDeclaration::~nsDOMCSSDeclaration()
+{
+}
+
+/* virtual */ JSObject*
+nsDOMCSSDeclaration::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::CSS2PropertiesBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_INTERFACE_TABLE_HEAD(nsDOMCSSDeclaration)
+ NS_INTERFACE_TABLE(nsDOMCSSDeclaration,
+ nsICSSDeclaration,
+ nsIDOMCSSStyleDeclaration)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::GetPropertyValue(const nsCSSPropertyID aPropID,
+ nsAString& aValue)
+{
+ NS_PRECONDITION(aPropID != eCSSProperty_UNKNOWN,
+ "Should never pass eCSSProperty_UNKNOWN around");
+
+ aValue.Truncate();
+ if (DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read)) {
+ decl->GetPropertyValueByID(aPropID, aValue);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::SetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsAString& aValue)
+{
+ switch (aPropID) {
+ case eCSSProperty_background_position:
+ case eCSSProperty_background_position_x:
+ case eCSSProperty_background_position_y:
+ case eCSSProperty_transform:
+ 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:
+ mozilla::layers::ScrollLinkedEffectDetector::PositioningPropertyMutated();
+ break;
+ default:
+ break;
+ }
+
+ if (aValue.IsEmpty()) {
+ // If the new value of the property is an empty string we remove the
+ // property.
+ return RemovePropertyInternal(aPropID);
+ }
+
+ return ParsePropertyValue(aPropID, aValue, false);
+}
+
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::GetCssText(nsAString& aCssText)
+{
+ DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read);
+ aCssText.Truncate();
+
+ if (decl) {
+ decl->ToString(aCssText);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::SetCssText(const nsAString& aCssText)
+{
+ // We don't need to *do* anything with the old declaration, but we need
+ // to ensure that it exists, or else SetCSSDeclaration may crash.
+ DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_Modify);
+ if (!olddecl) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CSSParsingEnvironment env;
+ GetCSSParsingEnvironment(env);
+ if (!env.mPrincipal) {
+ 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).
+ mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
+
+ RefPtr<DeclarationBlock> newdecl;
+ if (olddecl->IsServo()) {
+ newdecl = ServoDeclarationBlock::FromCssText(aCssText);
+ } else {
+ RefPtr<css::Declaration> decl(new css::Declaration());
+ decl->InitializeEmpty();
+ nsCSSParser cssParser(env.mCSSLoader);
+ bool changed;
+ nsresult result = cssParser.ParseDeclarations(aCssText, env.mSheetURI,
+ env.mBaseURI, env.mPrincipal,
+ decl, &changed);
+ if (NS_FAILED(result) || !changed) {
+ return result;
+ }
+ newdecl = decl.forget();
+ }
+
+ return SetCSSDeclaration(newdecl);
+}
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::GetLength(uint32_t* aLength)
+{
+ DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read);
+
+ if (decl) {
+ *aLength = decl->Count();
+ } else {
+ *aLength = 0;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<dom::CSSValue>
+nsDOMCSSDeclaration::GetPropertyCSSValue(const nsAString& aPropertyName, ErrorResult& aRv)
+{
+ // We don't support CSSValue yet so we'll just return null...
+
+ return nullptr;
+}
+
+void
+nsDOMCSSDeclaration::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aPropName)
+{
+ DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read);
+ aFound = decl && decl->GetNthProperty(aIndex, aPropName);
+}
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::GetPropertyValue(const nsAString& aPropertyName,
+ nsAString& aReturn)
+{
+ aReturn.Truncate();
+ if (DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read)) {
+ decl->GetPropertyValue(aPropertyName, aReturn);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::GetAuthoredPropertyValue(const nsAString& aPropertyName,
+ nsAString& aReturn)
+{
+ if (DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read)) {
+ decl->GetAuthoredPropertyValue(aPropertyName, aReturn);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::GetPropertyPriority(const nsAString& aPropertyName,
+ nsAString& aReturn)
+{
+ DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read);
+
+ aReturn.Truncate();
+ if (decl && decl->GetPropertyIsImportant(aPropertyName)) {
+ aReturn.AssignLiteral("important");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::SetProperty(const nsAString& aPropertyName,
+ const nsAString& aValue,
+ const nsAString& aPriority)
+{
+ 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);
+ }
+
+ // In the common (and fast) cases we can use the property id
+ nsCSSPropertyID propID =
+ nsCSSProps::LookupProperty(aPropertyName, CSSEnabledState::eForAllContent);
+ if (propID == eCSSProperty_UNKNOWN) {
+ return NS_OK;
+ }
+
+ bool important;
+ if (aPriority.IsEmpty()) {
+ important = false;
+ } else if (aPriority.EqualsLiteral("important")) {
+ important = true;
+ } else {
+ // XXX silent failure?
+ return NS_OK;
+ }
+
+ if (propID == eCSSPropertyExtra_variable) {
+ return ParseCustomPropertyValue(aPropertyName, aValue, important);
+ }
+ return ParsePropertyValue(propID, aValue, important);
+}
+
+NS_IMETHODIMP
+nsDOMCSSDeclaration::RemoveProperty(const nsAString& aPropertyName,
+ nsAString& aReturn)
+{
+ nsresult rv = GetPropertyValue(aPropertyName, aReturn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return RemovePropertyInternal(aPropertyName);
+}
+
+/* static */ void
+nsDOMCSSDeclaration::GetCSSParsingEnvironmentForRule(css::Rule* aRule,
+ CSSParsingEnvironment& aCSSParseEnv)
+{
+ CSSStyleSheet* sheet = aRule ? aRule->GetStyleSheet() : nullptr;
+ if (!sheet) {
+ aCSSParseEnv.mPrincipal = nullptr;
+ return;
+ }
+
+ nsIDocument* document = sheet->GetOwningDocument();
+ aCSSParseEnv.mSheetURI = sheet->GetSheetURI();
+ aCSSParseEnv.mBaseURI = sheet->GetBaseURI();
+ aCSSParseEnv.mPrincipal = sheet->Principal();
+ aCSSParseEnv.mCSSLoader = document ? document->CSSLoader() : nullptr;
+}
+
+nsresult
+nsDOMCSSDeclaration::ParsePropertyValue(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ bool aIsImportant)
+{
+ DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_Modify);
+ if (!olddecl) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CSSParsingEnvironment env;
+ GetCSSParsingEnvironment(env);
+ if (!env.mPrincipal) {
+ 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).
+ mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
+ RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
+
+ bool changed;
+ if (decl->IsGecko()) {
+ nsCSSParser cssParser(env.mCSSLoader);
+ cssParser.ParseProperty(aPropID, aPropValue,
+ env.mSheetURI, env.mBaseURI, env.mPrincipal,
+ decl->AsGecko(), &changed, aIsImportant);
+ } else {
+ nsIAtom* atom = nsCSSProps::AtomForProperty(aPropID);
+ NS_ConvertUTF16toUTF8 value(aPropValue);
+ changed = Servo_DeclarationBlock_SetProperty(
+ decl->AsServo()->Raw(), atom, false, &value, aIsImportant);
+ }
+ if (!changed) {
+ // Parsing failed -- but we don't throw an exception for that.
+ return NS_OK;
+ }
+
+ return SetCSSDeclaration(decl);
+}
+
+nsresult
+nsDOMCSSDeclaration::ParseCustomPropertyValue(const nsAString& aPropertyName,
+ const nsAString& aPropValue,
+ bool aIsImportant)
+{
+ MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aPropertyName));
+
+ DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_Modify);
+ if (!olddecl) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CSSParsingEnvironment env;
+ GetCSSParsingEnvironment(env);
+ if (!env.mPrincipal) {
+ 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).
+ mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
+ RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
+
+ bool changed;
+ auto propName = Substring(aPropertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
+ if (decl->IsGecko()) {
+ nsCSSParser cssParser(env.mCSSLoader);
+ cssParser.ParseVariable(propName, aPropValue, env.mSheetURI,
+ env.mBaseURI, env.mPrincipal, decl->AsGecko(),
+ &changed, aIsImportant);
+ } else {
+ RefPtr<nsIAtom> atom = NS_Atomize(propName);
+ NS_ConvertUTF16toUTF8 value(aPropValue);
+ changed = Servo_DeclarationBlock_SetProperty(
+ decl->AsServo()->Raw(), atom, true, &value, aIsImportant);
+ }
+ if (!changed) {
+ // Parsing failed -- but we don't throw an exception for that.
+ return NS_OK;
+ }
+
+ return SetCSSDeclaration(decl);
+}
+
+nsresult
+nsDOMCSSDeclaration::RemovePropertyInternal(nsCSSPropertyID aPropID)
+{
+ DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_RemoveProperty);
+ if (!olddecl) {
+ return NS_OK; // 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).
+ mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
+
+ RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
+ decl->RemovePropertyByID(aPropID);
+ return SetCSSDeclaration(decl);
+}
+
+nsresult
+nsDOMCSSDeclaration::RemovePropertyInternal(const nsAString& aPropertyName)
+{
+ DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_RemoveProperty);
+ if (!olddecl) {
+ return NS_OK; // 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).
+ mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
+
+ RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
+ decl->RemoveProperty(aPropertyName);
+ return SetCSSDeclaration(decl);
+}
diff --git a/layout/style/nsDOMCSSDeclaration.h b/layout/style/nsDOMCSSDeclaration.h
new file mode 100644
index 000000000..5b2308e4a
--- /dev/null
+++ b/layout/style/nsDOMCSSDeclaration.h
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for DOM objects for element.style and cssStyleRule.style */
+
+#ifndef nsDOMCSSDeclaration_h___
+#define nsDOMCSSDeclaration_h___
+
+#include "nsICSSDeclaration.h"
+
+#include "mozilla/Attributes.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+
+class nsIPrincipal;
+class nsIDocument;
+struct JSContext;
+class JSObject;
+
+namespace mozilla {
+class DeclarationBlock;
+namespace css {
+class Loader;
+class Rule;
+} // namespace css
+} // 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;
+
+ NS_DECL_NSICSSDECLARATION
+ using nsICSSDeclaration::GetLength;
+
+ // Require subclasses to implement |GetParentRule|.
+ //NS_DECL_NSIDOMCSSSTYLEDECLARATION
+ NS_IMETHOD GetCssText(nsAString & aCssText) override;
+ NS_IMETHOD SetCssText(const nsAString & aCssText) override;
+ NS_IMETHOD GetPropertyValue(const nsAString & propertyName,
+ nsAString & _retval) override;
+ virtual already_AddRefed<mozilla::dom::CSSValue>
+ GetPropertyCSSValue(const nsAString & propertyName,
+ mozilla::ErrorResult& aRv) override;
+ using nsICSSDeclaration::GetPropertyCSSValue;
+ NS_IMETHOD RemoveProperty(const nsAString & propertyName,
+ nsAString & _retval) override;
+ NS_IMETHOD GetPropertyPriority(const nsAString & propertyName,
+ nsAString & _retval) override;
+ NS_IMETHOD SetProperty(const nsAString & propertyName,
+ const nsAString & value, const nsAString & priority) override;
+ NS_IMETHOD GetLength(uint32_t *aLength) override;
+ NS_IMETHOD GetParentRule(nsIDOMCSSRule * *aParentRule) override = 0;
+
+ // WebIDL interface for CSS2Properties
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_
+#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ void \
+ Get##method_(nsAString& aValue, mozilla::ErrorResult& rv) \
+ { \
+ rv = GetPropertyValue(eCSSProperty_##id_, aValue); \
+ } \
+ \
+ void \
+ Set##method_(const nsAString& aValue, mozilla::ErrorResult& rv) \
+ { \
+ rv = SetPropertyValue(eCSSProperty_##id_, aValue); \
+ }
+
+#define CSS_PROP_LIST_EXCLUDE_INTERNAL
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+ CSS_PROP(name_, id_, method_, flags_, pref_, X, X, X, X, X)
+#include "nsCSSPropList.h"
+
+#define CSS_PROP_ALIAS(aliasname_, propid_, aliasmethod_, pref_) \
+ CSS_PROP(X, propid_, aliasmethod_, X, pref_, X, X, X, X, X)
+#include "nsCSSPropAliasList.h"
+#undef CSS_PROP_ALIAS
+
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP_LIST_EXCLUDE_INTERNAL
+#undef CSS_PROP
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+
+ virtual void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aPropName) override;
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ // The reason for calling GetCSSDeclaration.
+ enum Operation {
+ // We are calling GetCSSDeclaration 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.
+ eOperation_Read,
+
+ // We are calling GetCSSDeclaration 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 and calls AttributeWillChange. A nullptr return value
+ // indicates an error allocating the declaration.
+ eOperation_Modify,
+
+ // We are calling GetCSSDeclaration so that we can remove a property from
+ // it. Does not allocates a new declaration if we don't have one yet;
+ // returns nullptr in this case. If we do have a declaration, calls
+ // AttributeWillChange.
+ eOperation_RemoveProperty
+ };
+ virtual mozilla::DeclarationBlock* GetCSSDeclaration(Operation aOperation) = 0;
+ virtual nsresult SetCSSDeclaration(mozilla::DeclarationBlock* aDecl) = 0;
+ // Document that we must call BeginUpdate/EndUpdate on around the
+ // calls to SetCSSDeclaration and the style rule mutation that leads
+ // to it.
+ virtual nsIDocument* DocToUpdate() = 0;
+
+ // Information neded to parse a declaration. We need the mSheetURI
+ // for error reporting, mBaseURI to resolve relative URIs,
+ // mPrincipal for subresource loads, and mCSSLoader for determining
+ // whether we're in quirks mode. mBaseURI needs to be a strong
+ // pointer because of xml:base possibly creating base URIs on the
+ // fly. This is why we don't use CSSParsingEnvironment as a return
+ // value, to avoid multiple-refcounting of mBaseURI.
+ struct CSSParsingEnvironment {
+ nsIURI* MOZ_UNSAFE_REF("user of CSSParsingEnviroment must hold an owning "
+ "reference; reference counting here has unacceptable "
+ "performance overhead (see bug 649163)") mSheetURI;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsIPrincipal* MOZ_UNSAFE_REF("user of CSSParsingEnviroment must hold an owning "
+ "reference; reference counting here has unacceptable "
+ "performance overhead (see bug 649163)") mPrincipal;
+ mozilla::css::Loader* MOZ_UNSAFE_REF("user of CSSParsingEnviroment must hold an owning "
+ "reference; reference counting here has unacceptable "
+ "performance overhead (see bug 649163)") mCSSLoader;
+ };
+
+ // On failure, mPrincipal should be set to null in aCSSParseEnv.
+ // If mPrincipal is null, the other members may not be set to
+ // anything meaningful.
+ virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) = 0;
+
+ // An implementation for GetCSSParsingEnvironment for callers wrapping
+ // an css::Rule.
+ static void GetCSSParsingEnvironmentForRule(mozilla::css::Rule* aRule,
+ CSSParsingEnvironment& aCSSParseEnv);
+
+ nsresult ParsePropertyValue(const nsCSSPropertyID aPropID,
+ const nsAString& aPropValue,
+ bool aIsImportant);
+
+ nsresult ParseCustomPropertyValue(const nsAString& aPropertyName,
+ const nsAString& aPropValue,
+ bool aIsImportant);
+
+ nsresult RemovePropertyInternal(nsCSSPropertyID aPropID);
+ nsresult RemovePropertyInternal(const nsAString& aProperty);
+
+protected:
+ virtual ~nsDOMCSSDeclaration();
+};
+
+#endif // nsDOMCSSDeclaration_h___
diff --git a/layout/style/nsDOMCSSRGBColor.cpp b/layout/style/nsDOMCSSRGBColor.cpp
new file mode 100644
index 000000000..e8acb81c0
--- /dev/null
+++ b/layout/style/nsDOMCSSRGBColor.cpp
@@ -0,0 +1,38 @@
+/* -*- 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/. */
+
+/* DOM object representing color values in DOM computed style */
+
+#include "nsDOMCSSRGBColor.h"
+
+#include "mozilla/dom/RGBColorBinding.h"
+#include "nsROCSSPrimitiveValue.h"
+
+using namespace mozilla;
+
+nsDOMCSSRGBColor::nsDOMCSSRGBColor(nsROCSSPrimitiveValue* aRed,
+ nsROCSSPrimitiveValue* aGreen,
+ nsROCSSPrimitiveValue* aBlue,
+ nsROCSSPrimitiveValue* aAlpha,
+ bool aHasAlpha)
+ : mRed(aRed), mGreen(aGreen), mBlue(aBlue), mAlpha(aAlpha)
+ , mHasAlpha(aHasAlpha)
+{
+}
+
+nsDOMCSSRGBColor::~nsDOMCSSRGBColor(void)
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMCSSRGBColor, mAlpha, mBlue, mGreen, mRed)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsDOMCSSRGBColor, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsDOMCSSRGBColor, Release)
+
+JSObject*
+nsDOMCSSRGBColor::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::RGBColorBinding::Wrap(aCx, this, aGivenProto);
+}
+
diff --git a/layout/style/nsDOMCSSRGBColor.h b/layout/style/nsDOMCSSRGBColor.h
new file mode 100644
index 000000000..0a745d561
--- /dev/null
+++ b/layout/style/nsDOMCSSRGBColor.h
@@ -0,0 +1,67 @@
+/* -*- 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/. */
+
+/* DOM object representing color values in DOM computed style */
+
+#ifndef nsDOMCSSRGBColor_h__
+#define nsDOMCSSRGBColor_h__
+
+#include "mozilla/Attributes.h"
+#include "nsWrapperCache.h"
+
+class nsROCSSPrimitiveValue;
+
+class nsDOMCSSRGBColor : public nsWrapperCache
+{
+public:
+ nsDOMCSSRGBColor(nsROCSSPrimitiveValue* aRed,
+ nsROCSSPrimitiveValue* aGreen,
+ nsROCSSPrimitiveValue* aBlue,
+ nsROCSSPrimitiveValue* aAlpha,
+ bool aHasAlpha);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsDOMCSSRGBColor)
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(nsDOMCSSRGBColor)
+
+ bool HasAlpha() const { return mHasAlpha; }
+
+ // RGBColor webidl interface
+ nsROCSSPrimitiveValue* Red() const
+ {
+ return mRed;
+ }
+ nsROCSSPrimitiveValue* Green() const
+ {
+ return mGreen;
+ }
+ nsROCSSPrimitiveValue* Blue() const
+ {
+ return mBlue;
+ }
+ nsROCSSPrimitiveValue* Alpha() const
+ {
+ return mAlpha;
+ }
+
+ nsISupports* GetParentObject() const
+ {
+ return nullptr;
+ }
+
+ virtual JSObject *WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+ override final;
+
+private:
+ virtual ~nsDOMCSSRGBColor(void);
+
+ RefPtr<nsROCSSPrimitiveValue> mRed;
+ RefPtr<nsROCSSPrimitiveValue> mGreen;
+ RefPtr<nsROCSSPrimitiveValue> mBlue;
+ RefPtr<nsROCSSPrimitiveValue> mAlpha;
+ bool mHasAlpha;
+};
+
+#endif // nsDOMCSSRGBColor_h__
diff --git a/layout/style/nsDOMCSSRect.cpp b/layout/style/nsDOMCSSRect.cpp
new file mode 100644
index 000000000..7bd5dee71
--- /dev/null
+++ b/layout/style/nsDOMCSSRect.cpp
@@ -0,0 +1,77 @@
+/* -*- 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/. */
+
+/* DOM object representing rectangle values in DOM computed style */
+
+#include "mozilla/dom/RectBinding.h"
+#include "nsROCSSPrimitiveValue.h"
+#include "nsDOMCSSRect.h"
+
+using namespace mozilla;
+
+nsDOMCSSRect::nsDOMCSSRect(nsROCSSPrimitiveValue* aTop,
+ nsROCSSPrimitiveValue* aRight,
+ nsROCSSPrimitiveValue* aBottom,
+ nsROCSSPrimitiveValue* aLeft)
+ : mTop(aTop), mRight(aRight), mBottom(aBottom), mLeft(aLeft)
+{
+}
+
+nsDOMCSSRect::~nsDOMCSSRect(void)
+{
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCSSRect)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMRect)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCSSRect)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCSSRect)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMCSSRect, mTop, mBottom, mLeft, mRight)
+
+JSObject*
+nsDOMCSSRect::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::RectBinding::Wrap(cx, this, aGivenProto);
+}
+
+NS_IMETHODIMP
+nsDOMCSSRect::GetTop(nsIDOMCSSPrimitiveValue** aTop)
+{
+ NS_ENSURE_TRUE(mTop, NS_ERROR_NOT_INITIALIZED);
+ *aTop = mTop;
+ NS_ADDREF(*aTop);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMCSSRect::GetRight(nsIDOMCSSPrimitiveValue** aRight)
+{
+ NS_ENSURE_TRUE(mRight, NS_ERROR_NOT_INITIALIZED);
+ *aRight = mRight;
+ NS_ADDREF(*aRight);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMCSSRect::GetBottom(nsIDOMCSSPrimitiveValue** aBottom)
+{
+ NS_ENSURE_TRUE(mBottom, NS_ERROR_NOT_INITIALIZED);
+ *aBottom = mBottom;
+ NS_ADDREF(*aBottom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMCSSRect::GetLeft(nsIDOMCSSPrimitiveValue** aLeft)
+{
+ NS_ENSURE_TRUE(mLeft, NS_ERROR_NOT_INITIALIZED);
+ *aLeft = mLeft;
+ NS_ADDREF(*aLeft);
+ return NS_OK;
+}
diff --git a/layout/style/nsDOMCSSRect.h b/layout/style/nsDOMCSSRect.h
new file mode 100644
index 000000000..7610773c7
--- /dev/null
+++ b/layout/style/nsDOMCSSRect.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object representing rectangle values in DOM computed style */
+
+#ifndef nsDOMCSSRect_h_
+#define nsDOMCSSRect_h_
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMRect.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+class nsROCSSPrimitiveValue;
+
+class nsDOMCSSRect final : public nsIDOMRect,
+ public nsWrapperCache
+{
+public:
+ nsDOMCSSRect(nsROCSSPrimitiveValue* aTop,
+ nsROCSSPrimitiveValue* aRight,
+ nsROCSSPrimitiveValue* aBottom,
+ nsROCSSPrimitiveValue* aLeft);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIDOMRECT
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMCSSRect)
+
+ nsROCSSPrimitiveValue* Top() const { return mTop; }
+ nsROCSSPrimitiveValue* Right() const { return mRight; }
+ nsROCSSPrimitiveValue* Bottom() const { return mBottom; }
+ nsROCSSPrimitiveValue* Left() const { return mLeft; }
+
+ nsISupports* GetParentObject() const { return nullptr; }
+
+ virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+ override final;
+
+protected:
+ virtual ~nsDOMCSSRect(void);
+
+private:
+ RefPtr<nsROCSSPrimitiveValue> mTop;
+ RefPtr<nsROCSSPrimitiveValue> mRight;
+ RefPtr<nsROCSSPrimitiveValue> mBottom;
+ RefPtr<nsROCSSPrimitiveValue> mLeft;
+};
+
+#endif /* nsDOMCSSRect_h_ */
diff --git a/layout/style/nsDOMCSSValueList.cpp b/layout/style/nsDOMCSSValueList.cpp
new file mode 100644
index 000000000..458628d5e
--- /dev/null
+++ b/layout/style/nsDOMCSSValueList.cpp
@@ -0,0 +1,129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/dom/CSSValueListBinding.h"
+#include "mozilla/Move.h"
+
+using namespace mozilla;
+
+nsDOMCSSValueList::nsDOMCSSValueList(bool aCommaDelimited, bool aReadonly)
+ : CSSValue(), mCommaDelimited(aCommaDelimited), mReadonly(aReadonly)
+{
+}
+
+nsDOMCSSValueList::~nsDOMCSSValueList()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCSSValueList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCSSValueList)
+
+// QueryInterface implementation for nsDOMCSSValueList
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCSSValueList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSValue)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSValueList)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, CSSValue)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMCSSValueList, mCSSValues)
+
+JSObject*
+nsDOMCSSValueList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::CSSValueListBinding::Wrap(cx, this, aGivenProto);
+}
+
+void
+nsDOMCSSValueList::AppendCSSValue(already_AddRefed<CSSValue> aValue)
+{
+ RefPtr<CSSValue> val = aValue;
+ mCSSValues.AppendElement(Move(val));
+}
+
+// nsIDOMCSSValue
+
+NS_IMETHODIMP
+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!");
+ ErrorResult dummy;
+ if (cssValue) {
+ cssValue->GetCssText(tmpStr, dummy);
+
+ 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);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsDOMCSSValueList::GetCssText(nsString& aText, ErrorResult& aRv)
+{
+ aRv = GetCssText(aText);
+}
+
+NS_IMETHODIMP
+nsDOMCSSValueList::SetCssText(const nsAString& aCssText)
+{
+ if (mReadonly) {
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+ }
+
+ NS_NOTYETIMPLEMENTED("Can't SetCssText yet: please write me!");
+ return NS_OK;
+}
+
+void
+nsDOMCSSValueList::SetCssText(const nsAString& aText, ErrorResult& aRv)
+{
+ aRv = SetCssText(aText);
+}
+
+NS_IMETHODIMP
+nsDOMCSSValueList::GetCssValueType(uint16_t* aValueType)
+{
+ NS_ENSURE_ARG_POINTER(aValueType);
+ *aValueType = nsIDOMCSSValue::CSS_VALUE_LIST;
+ return NS_OK;
+}
+
+uint16_t
+nsDOMCSSValueList::CssValueType() const
+{
+ return nsIDOMCSSValue::CSS_VALUE_LIST;
+}
diff --git a/layout/style/nsDOMCSSValueList.h b/layout/style/nsDOMCSSValueList.h
new file mode 100644
index 000000000..39207c773
--- /dev/null
+++ b/layout/style/nsDOMCSSValueList.h
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object representing lists of values in DOM computed style */
+
+#ifndef nsDOMCSSValueList_h___
+#define nsDOMCSSValueList_h___
+
+#include "nsIDOMCSSValueList.h"
+#include "CSSValue.h"
+#include "nsTArray.h"
+
+class nsDOMCSSValueList final : public mozilla::dom::CSSValue,
+ public nsIDOMCSSValueList
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDOMCSSValueList, mozilla::dom::CSSValue)
+
+ // nsIDOMCSSValue
+ NS_DECL_NSIDOMCSSVALUE
+
+ // nsDOMCSSValueList
+ nsDOMCSSValueList(bool aCommaDelimited, bool aReadonly);
+
+ /**
+ * Adds a value to this list.
+ */
+ void AppendCSSValue(already_AddRefed<CSSValue> aValue);
+
+ virtual void GetCssText(nsString& aText, mozilla::ErrorResult& aRv)
+ override final;
+ virtual void SetCssText(const nsAString& aText,
+ mozilla::ErrorResult& aRv) override final;
+ virtual uint16_t CssValueType() const override final;
+
+ CSSValue* IndexedGetter(uint32_t aIdx, bool& aFound) const
+ {
+ aFound = aIdx <= Length();
+ return Item(aIdx);
+ }
+
+ CSSValue* Item(uint32_t aIndex) const
+ {
+ return mCSSValues.SafeElementAt(aIndex);
+ }
+
+ uint32_t Length() const
+ {
+ return mCSSValues.Length();
+ }
+
+ nsISupports* GetParentObject()
+ {
+ return nullptr;
+ }
+
+ virtual JSObject *WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ ~nsDOMCSSValueList();
+
+ bool mCommaDelimited; // some value lists use a comma
+ // as the delimiter, some just use
+ // spaces.
+
+ bool mReadonly; // Are we read-only?
+
+ InfallibleTArray<RefPtr<CSSValue> > mCSSValues;
+};
+
+#endif /* nsDOMCSSValueList_h___ */
diff --git a/layout/style/nsFontFaceLoader.cpp b/layout/style/nsFontFaceLoader.cpp
new file mode 100644
index 000000000..f5a0a9f34
--- /dev/null
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* code for loading in @font-face defined font data */
+
+#include "mozilla/Logging.h"
+
+#include "nsFontFaceLoader.h"
+
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "FontFaceSet.h"
+#include "nsPresContext.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIHttpChannel.h"
+#include "nsIContentPolicy.h"
+#include "nsContentPolicyUtils.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,
+ nsIURI* aFontURI,
+ FontFaceSet* aFontFaceSet,
+ nsIChannel* aChannel)
+ : mUserFontEntry(aUserFontEntry),
+ mFontURI(aFontURI),
+ mFontFaceSet(aFontFaceSet),
+ mChannel(aChannel)
+{
+ mStartTime = TimeStamp::Now();
+}
+
+nsFontFaceLoader::~nsFontFaceLoader()
+{
+ if (mUserFontEntry) {
+ mUserFontEntry->mLoader = nullptr;
+ }
+ if (mLoadTimer) {
+ mLoadTimer->Cancel();
+ mLoadTimer = nullptr;
+ }
+ if (mFontFaceSet) {
+ mFontFaceSet->RemoveLoader(this);
+ }
+}
+
+void
+nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader)
+{
+ int32_t loadTimeout;
+ uint8_t fontDisplay = GetFontDisplay();
+ if (fontDisplay == NS_FONT_DISPLAY_AUTO ||
+ fontDisplay == NS_FONT_DISPLAY_BLOCK) {
+ loadTimeout = GetFallbackDelay();
+ } else {
+ loadTimeout = GetShortFallbackDelay();
+ }
+
+ if (loadTimeout > 0) {
+ mLoadTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mLoadTimer) {
+ mLoadTimer->InitWithFuncCallback(LoadTimerCallback,
+ static_cast<void*>(this),
+ loadTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ } else {
+ mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+ }
+ mStreamLoader = aStreamLoader;
+}
+
+/* static */ void
+nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure);
+
+ if (!loader->mFontFaceSet) {
+ // We've been canceled
+ return;
+ }
+
+ gfxUserFontEntry* ufe = loader->mUserFontEntry.get();
+ uint8_t 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 NS_FONT_DISPLAY_AUTO:
+ case NS_FONT_DISPLAY_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->InitWithFuncCallback(LoadTimerCallback,
+ static_cast<void*>(loader),
+ delay >> 1,
+ nsITimer::TYPE_ONE_SHOT);
+ updateUserFontSet = false;
+ LOG(("userfonts (%p) 75%% done, resetting timer\n", loader));
+ }
+ }
+ if (updateUserFontSet) {
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+ }
+ break;
+ case NS_FONT_DISPLAY_SWAP:
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+ break;
+ case NS_FONT_DISPLAY_FALLBACK: {
+ if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+ } else {
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
+ updateUserFontSet = false;
+ }
+ break;
+ }
+ case NS_FONT_DISPLAY_OPTIONAL:
+ ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
+ break;
+
+ default:
+ NS_NOTREACHED("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<gfxUserFontSet*> fontSets;
+ ufe->GetUserFontSets(fontSets);
+ for (gfxUserFontSet* fontSet : fontSets) {
+ nsPresContext* ctx = FontFaceSet::GetPresContextFor(fontSet);
+ if (ctx) {
+ fontSet->IncrementGeneration();
+ ctx->UserFontSetUpdated(ufe);
+ LOG(("userfonts (%p) timeout reflow for pres context %p display %d\n",
+ loader, ctx, fontDisplay));
+ }
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver)
+
+NS_IMETHODIMP
+nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ uint32_t aStringLen,
+ const uint8_t* aString)
+{
+ if (!mFontFaceSet) {
+ // We've been canceled
+ return aStatus;
+ }
+
+ mFontFaceSet->RemoveLoader(this);
+
+ TimeStamp doneTime = TimeStamp::Now();
+ TimeDuration downloadTime = doneTime - mStartTime;
+ uint32_t downloadTimeMS = uint32_t(downloadTime.ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::WEBFONT_DOWNLOAD_TIME, downloadTimeMS);
+
+ if (GetFontDisplay() == NS_FONT_DISPLAY_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.8x\n",
+ this, mFontURI->GetSpecOrDefault().get(), 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;
+ }
+ }
+ }
+
+ // 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.
+ bool fontUpdate =
+ mUserFontEntry->FontDataDownloadComplete(aString, aStringLen, aStatus);
+
+ mFontFaceSet->GetUserFontSet()->RecordFontLoadDone(aStringLen, doneTime);
+
+ // when new font loaded, need to reflow
+ if (fontUpdate) {
+ nsTArray<gfxUserFontSet*> fontSets;
+ mUserFontEntry->GetUserFontSets(fontSets);
+ for (gfxUserFontSet* fontSet : fontSets) {
+ nsPresContext* ctx = FontFaceSet::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));
+ }
+ }
+ }
+
+ // done with font set
+ mFontFaceSet = nullptr;
+ if (mLoadTimer) {
+ mLoadTimer->Cancel();
+ mLoadTimer = nullptr;
+ }
+
+ return NS_SUCCESS_ADOPTED_DATA;
+}
+
+void
+nsFontFaceLoader::Cancel()
+{
+ mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::NOT_LOADING;
+ mUserFontEntry->mLoader = nullptr;
+ mFontFaceSet = nullptr;
+ if (mLoadTimer) {
+ mLoadTimer->Cancel();
+ mLoadTimer = nullptr;
+ }
+ mChannel->Cancel(NS_BINDING_ABORTED);
+}
+
+uint8_t
+nsFontFaceLoader::GetFontDisplay()
+{
+ uint8_t fontDisplay = NS_FONT_DISPLAY_AUTO;
+ if (Preferences::GetBool("layout.css.font-display.enabled")) {
+ fontDisplay = mUserFontEntry->GetFontDisplay();
+ }
+ return fontDisplay;
+}
diff --git a/layout/style/nsFontFaceLoader.h b/layout/style/nsFontFaceLoader.h
new file mode 100644
index 000000000..34ff64039
--- /dev/null
+++ b/layout/style/nsFontFaceLoader.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* code for loading in @font-face defined font data */
+
+#ifndef nsFontFaceLoader_h_
+#define nsFontFaceLoader_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsIStreamLoader.h"
+#include "nsIChannel.h"
+#include "gfxUserFontSet.h"
+#include "nsHashKeys.h"
+#include "nsTHashtable.h"
+#include "nsCSSRules.h"
+
+class nsIPrincipal;
+
+class nsFontFaceLoader : public nsIStreamLoaderObserver
+{
+public:
+ nsFontFaceLoader(gfxUserFontEntry* aFontToLoad, nsIURI* aFontURI,
+ mozilla::dom::FontFaceSet* aFontFaceSet,
+ nsIChannel* aChannel);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ // initiate the load
+ nsresult Init();
+ // 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; }
+
+protected:
+ virtual ~nsFontFaceLoader();
+
+ // helper method for determining the font-display value
+ uint8_t GetFontDisplay();
+
+private:
+ RefPtr<gfxUserFontEntry> mUserFontEntry;
+ nsCOMPtr<nsIURI> mFontURI;
+ RefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsITimer> mLoadTimer;
+ mozilla::TimeStamp mStartTime;
+ nsIStreamLoader* mStreamLoader;
+};
+
+#endif /* !defined(nsFontFaceLoader_h_) */
diff --git a/layout/style/nsFontFaceUtils.cpp b/layout/style/nsFontFaceUtils.cpp
new file mode 100644
index 000000000..d715dfe06
--- /dev/null
+++ b/layout/style/nsFontFaceUtils.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxUserFontSet.h"
+#include "nsFontFaceUtils.h"
+#include "nsFontMetrics.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTArray.h"
+#include "SVGTextFrame.h"
+
+static bool
+StyleContextContainsFont(nsStyleContext* aStyleContext,
+ const gfxUserFontSet* aUserFontSet,
+ const gfxUserFontEntry* aFont)
+{
+ // if the font is null, simply check to see whether fontlist includes
+ // downloadable fonts
+ if (!aFont) {
+ const mozilla::FontFamilyList& fontlist =
+ aStyleContext->StyleFont()->mFont.fontlist;
+ return aUserFontSet->ContainsUserFontSetFonts(fontlist);
+ }
+
+ // first, check if the family name is in the fontlist
+ const nsString& familyName = aFont->FamilyName();
+ if (!aStyleContext->StyleFont()->mFont.fontlist.Contains(familyName)) {
+ return false;
+ }
+
+ // family name is in the fontlist, check to see if the font group
+ // associated with the frame includes the specific userfont
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForStyleContext(aStyleContext, 1.0f);
+
+ if (fm->GetThebesFontGroup()->ContainsUserFont(aFont)) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+FrameUsesFont(nsIFrame* aFrame, const gfxUserFontEntry* aFont)
+{
+ // check the style context of the frame
+ gfxUserFontSet* ufs = aFrame->PresContext()->GetUserFontSet();
+ if (StyleContextContainsFont(aFrame->StyleContext(), ufs, aFont)) {
+ return true;
+ }
+
+ // check additional style contexts
+ int32_t contextIndex = 0;
+ for (nsStyleContext* extraContext;
+ (extraContext = aFrame->GetAdditionalStyleContext(contextIndex));
+ ++contextIndex) {
+ if (StyleContextContainsFont(extraContext, ufs, aFont)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void
+ScheduleReflow(nsIPresShell* aShell, nsIFrame* aFrame)
+{
+ nsIFrame* f = aFrame;
+ if (f->IsFrameOfType(nsIFrame::eSVG) || f->IsSVGText()) {
+ // SVG frames (and the non-SVG descendants of an SVGTextFrame) need special
+ // reflow handling. We need to search upwards for the first displayed
+ // nsSVGOuterSVGFrame or non-SVG frame, which is the frame we can call
+ // FrameNeedsReflow on. (This logic is based on
+ // nsSVGUtils::ScheduleReflowSVG and
+ // SVGTextFrame::ScheduleReflowSVGNonDisplayText.)
+ if (f->GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+ while (f) {
+ if (!(f->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+ if (NS_SUBTREE_DIRTY(f)) {
+ // 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->GetStateBits() & NS_STATE_IS_OUTER_SVG ||
+ !(f->IsFrameOfType(nsIFrame::eSVG) || f->IsSVGText())) {
+ break;
+ }
+ f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ f = f->GetParent();
+ }
+ MOZ_ASSERT(f, "should have found an ancestor frame to reflow");
+ }
+ }
+
+ aShell->FrameNeedsReflow(f, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+}
+
+/* static */ void
+nsFontFaceUtils::MarkDirtyForFontChange(nsIFrame* aSubtreeRoot,
+ const gfxUserFontEntry* aFont)
+{
+ AutoTArray<nsIFrame*, 4> subtrees;
+ subtrees.AppendElement(aSubtreeRoot);
+
+ nsIPresShell* ps = aSubtreeRoot->PresContext()->PresShell();
+
+ // check descendants, iterating over subtrees that may include
+ // additional subtrees associated with placeholders
+ do {
+ nsIFrame* subtreeRoot = subtrees.ElementAt(subtrees.Length() - 1);
+ subtrees.RemoveElementAt(subtrees.Length() - 1);
+
+ // Check all descendants to see if they use the font
+ AutoTArray<nsIFrame*, 32> stack;
+ stack.AppendElement(subtreeRoot);
+
+ do {
+ nsIFrame* f = stack.ElementAt(stack.Length() - 1);
+ stack.RemoveElementAt(stack.Length() - 1);
+
+ // if this frame uses the font, mark its descendants dirty
+ // and skip checking its children
+ if (FrameUsesFont(f, aFont)) {
+ ScheduleReflow(ps, f);
+ } else {
+ if (f->GetType() == nsGkAtoms::placeholderFrame) {
+ nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
+ if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
+ // We have another distinct subtree we need to mark.
+ subtrees.AppendElement(oof);
+ }
+ }
+
+ nsIFrame::ChildListIterator lists(f);
+ for (; !lists.IsDone(); lists.Next()) {
+ nsFrameList::Enumerator childFrames(lists.CurrentList());
+ for (; !childFrames.AtEnd(); childFrames.Next()) {
+ nsIFrame* kid = childFrames.get();
+ stack.AppendElement(kid);
+ }
+ }
+ }
+ } while (!stack.IsEmpty());
+ } while (!subtrees.IsEmpty());
+}
diff --git a/layout/style/nsFontFaceUtils.h b/layout/style/nsFontFaceUtils.h
new file mode 100644
index 000000000..e608d025e
--- /dev/null
+++ b/layout/style/nsFontFaceUtils.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* 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/nsHTMLCSSStyleSheet.cpp b/layout/style/nsHTMLCSSStyleSheet.cpp
new file mode 100644
index 000000000..f5e8cde0b
--- /dev/null
+++ b/layout/style/nsHTMLCSSStyleSheet.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 style attributes
+ */
+
+#include "nsHTMLCSSStyleSheet.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/StyleRule.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "nsIStyleRuleProcessor.h"
+#include "nsPresContext.h"
+#include "nsRuleWalker.h"
+#include "nsRuleProcessorData.h"
+#include "mozilla/dom/Element.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsCSSPseudoElements.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsHTMLCSSStyleSheet::nsHTMLCSSStyleSheet()
+{
+}
+
+nsHTMLCSSStyleSheet::~nsHTMLCSSStyleSheet()
+{
+ // 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->SetHTMLCSSStyleSheet(nullptr);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
+ }
+
+ value->mValue.mCached = 0;
+ iter.Remove();
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsHTMLCSSStyleSheet, nsIStyleRuleProcessor)
+
+/* virtual */ void
+nsHTMLCSSStyleSheet::RulesMatching(ElementRuleProcessorData* aData)
+{
+ ElementRulesMatching(aData->mPresContext, aData->mElement,
+ aData->mRuleWalker);
+}
+
+void
+nsHTMLCSSStyleSheet::ElementRulesMatching(nsPresContext* aPresContext,
+ Element* aElement,
+ nsRuleWalker* aRuleWalker)
+{
+ // just get the one and only style rule from the content's STYLE attribute
+ DeclarationBlock* declaration = aElement->GetInlineStyleDeclaration();
+ if (declaration) {
+ declaration->SetImmutable();
+ aRuleWalker->Forward(declaration->AsGecko());
+ }
+
+ declaration = aElement->GetSMILOverrideStyleDeclaration();
+ if (declaration) {
+ MOZ_ASSERT(aPresContext->RestyleManager()->IsGecko(),
+ "stylo: ElementRulesMatching must not be called when we have "
+ "a Servo-backed style system");
+ RestyleManager* restyleManager = aPresContext->RestyleManager()->AsGecko();
+ if (!restyleManager->SkipAnimationRules()) {
+ // Animation restyle (or non-restyle traversal of rules)
+ // Now we can walk SMIL overrride style, without triggering transitions.
+ declaration->SetImmutable();
+ aRuleWalker->Forward(declaration->AsGecko());
+ }
+ }
+}
+
+void
+nsHTMLCSSStyleSheet::PseudoElementRulesMatching(Element* aPseudoElement,
+ CSSPseudoElementType
+ aPseudoType,
+ nsRuleWalker* aRuleWalker)
+{
+ MOZ_ASSERT(nsCSSPseudoElements::
+ PseudoElementSupportsStyleAttribute(aPseudoType));
+ MOZ_ASSERT(aPseudoElement);
+
+ // just get the one and only style rule from the content's STYLE attribute
+ DeclarationBlock* declaration = aPseudoElement->GetInlineStyleDeclaration();
+ if (declaration) {
+ declaration->SetImmutable();
+ aRuleWalker->Forward(declaration->AsGecko());
+ }
+}
+
+/* virtual */ void
+nsHTMLCSSStyleSheet::RulesMatching(PseudoElementRuleProcessorData* aData)
+{
+ if (nsCSSPseudoElements::PseudoElementSupportsStyleAttribute(aData->mPseudoType) &&
+ aData->mPseudoElement) {
+ PseudoElementRulesMatching(aData->mPseudoElement, aData->mPseudoType,
+ aData->mRuleWalker);
+ }
+}
+
+/* virtual */ void
+nsHTMLCSSStyleSheet::RulesMatching(AnonBoxRuleProcessorData* aData)
+{
+}
+
+#ifdef MOZ_XUL
+/* virtual */ void
+nsHTMLCSSStyleSheet::RulesMatching(XULTreeRuleProcessorData* aData)
+{
+}
+#endif
+
+// Test if style is dependent on content state
+/* virtual */ nsRestyleHint
+nsHTMLCSSStyleSheet::HasStateDependentStyle(StateRuleProcessorData* aData)
+{
+ return nsRestyleHint(0);
+}
+
+/* virtual */ nsRestyleHint
+nsHTMLCSSStyleSheet::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData)
+{
+ return nsRestyleHint(0);
+}
+
+/* virtual */ bool
+nsHTMLCSSStyleSheet::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
+{
+ return false;
+}
+
+// Test if style is dependent on attribute
+/* virtual */ nsRestyleHint
+nsHTMLCSSStyleSheet::HasAttributeDependentStyle(
+ AttributeRuleProcessorData* aData,
+ RestyleHintData& aRestyleHintDataResult)
+{
+ // Perhaps should check that it's XUL, SVG, (or HTML) namespace, but
+ // it doesn't really matter.
+ if (aData->mAttrHasChanged && aData->mAttribute == nsGkAtoms::style) {
+ return eRestyle_StyleAttribute;
+ }
+
+ return nsRestyleHint(0);
+}
+
+/* virtual */ bool
+nsHTMLCSSStyleSheet::MediumFeaturesChanged(nsPresContext* aPresContext)
+{
+ return false;
+}
+
+/* virtual */ size_t
+nsHTMLCSSStyleSheet::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // The size of mCachedStyleAttrs's mTable member (a PLDHashTable) is
+ // significant in itself, but more significant is the size of the nsString
+ // members of the nsStringHashKeys.
+ size_t n = 0;
+ n += mCachedStyleAttrs.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mCachedStyleAttrs.ConstIter(); !iter.Done(); iter.Next()) {
+ // We don't own the MiscContainers (the hash table values) so we don't
+ // count them. We do care about the size of the nsString members in the
+ // keys though.
+ n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+ return n;
+}
+
+/* virtual */ size_t
+nsHTMLCSSStyleSheet::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void
+nsHTMLCSSStyleSheet::CacheStyleAttr(const nsAString& aSerialized,
+ MiscContainer* aValue)
+{
+ mCachedStyleAttrs.Put(aSerialized, aValue);
+}
+
+void
+nsHTMLCSSStyleSheet::EvictStyleAttr(const nsAString& aSerialized,
+ MiscContainer* aValue)
+{
+#ifdef DEBUG
+ {
+ NS_ASSERTION(aValue == mCachedStyleAttrs.Get(aSerialized),
+ "Cached value does not match?!");
+ }
+#endif
+ mCachedStyleAttrs.Remove(aSerialized);
+}
+
+MiscContainer*
+nsHTMLCSSStyleSheet::LookupStyleAttr(const nsAString& aSerialized)
+{
+ return mCachedStyleAttrs.Get(aSerialized);
+}
diff --git a/layout/style/nsHTMLCSSStyleSheet.h b/layout/style/nsHTMLCSSStyleSheet.h
new file mode 100644
index 000000000..2b7ea7ae8
--- /dev/null
+++ b/layout/style/nsHTMLCSSStyleSheet.h
@@ -0,0 +1,80 @@
+/* -*- 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/. */
+
+/*
+ * style sheet and style rule processor representing style attributes
+ */
+
+#ifndef nsHTMLCSSStyleSheet_h_
+#define nsHTMLCSSStyleSheet_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "nsDataHashtable.h"
+#include "nsIStyleRuleProcessor.h"
+
+class nsRuleWalker;
+struct MiscContainer;
+
+namespace mozilla {
+enum class CSSPseudoElementType : uint8_t;
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class nsHTMLCSSStyleSheet final : public nsIStyleRuleProcessor
+{
+public:
+ nsHTMLCSSStyleSheet();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIStyleRuleProcessor
+ virtual void RulesMatching(ElementRuleProcessorData* aData) override;
+ virtual void RulesMatching(PseudoElementRuleProcessorData* aData) override;
+ virtual void RulesMatching(AnonBoxRuleProcessorData* aData) override;
+#ifdef MOZ_XUL
+ virtual void RulesMatching(XULTreeRuleProcessorData* aData) override;
+#endif
+ virtual nsRestyleHint HasStateDependentStyle(StateRuleProcessorData* aData) override;
+ virtual nsRestyleHint HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData) override;
+ virtual bool HasDocumentStateDependentStyle(StateRuleProcessorData* aData) override;
+ virtual nsRestyleHint
+ HasAttributeDependentStyle(AttributeRuleProcessorData* aData,
+ mozilla::RestyleHintData& aRestyleHintDataResult) override;
+ virtual bool MediumFeaturesChanged(nsPresContext* aPresContext) override;
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ const MOZ_MUST_OVERRIDE override;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ const MOZ_MUST_OVERRIDE override;
+
+ // Variants of RulesMatching method above that is specific to this
+ // rule processor.
+ void ElementRulesMatching(nsPresContext* aPresContext,
+ mozilla::dom::Element* aElement,
+ nsRuleWalker* aRuleWalker);
+ // aPseudoElement here is the content node for the pseudo-element, not
+ // its corresponding real element.
+ void PseudoElementRulesMatching(mozilla::dom::Element* aPseudoElement,
+ mozilla::CSSPseudoElementType aPseudoType,
+ nsRuleWalker* aRuleWalker);
+
+ void CacheStyleAttr(const nsAString& aSerialized, MiscContainer* aValue);
+ void EvictStyleAttr(const nsAString& aSerialized, MiscContainer* aValue);
+ MiscContainer* LookupStyleAttr(const nsAString& aSerialized);
+
+private:
+ ~nsHTMLCSSStyleSheet();
+
+ nsHTMLCSSStyleSheet(const nsHTMLCSSStyleSheet& aCopy) = delete;
+ nsHTMLCSSStyleSheet& operator=(const nsHTMLCSSStyleSheet& aCopy) = delete;
+
+protected:
+ nsDataHashtable<nsStringHashKey, MiscContainer*> mCachedStyleAttrs;
+};
+
+#endif /* !defined(nsHTMLCSSStyleSheet_h_) */
diff --git a/layout/style/nsHTMLStyleSheet.cpp b/layout/style/nsHTMLStyleSheet.cpp
new file mode 100644
index 000000000..70a5c7f41
--- /dev/null
+++ b/layout/style/nsHTMLStyleSheet.cpp
@@ -0,0 +1,592 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+/*
+ * style sheet and style rule processor representing data from presentational
+ * HTML attributes
+ */
+
+#include "nsHTMLStyleSheet.h"
+#include "nsMappedAttributes.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "mozilla/EventStates.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsStyleConsts.h"
+#include "nsRuleWalker.h"
+#include "nsRuleData.h"
+#include "nsError.h"
+#include "nsRuleProcessorData.h"
+#include "nsCSSRuleProcessor.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Element.h"
+#include "nsHashKeys.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsHTMLStyleSheet::HTMLColorRule, nsIStyleRule)
+
+/* virtual */ void
+nsHTMLStyleSheet::HTMLColorRule::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Color)) {
+ nsCSSValue* color = aRuleData->ValueForColor();
+ if (color->GetUnit() == eCSSUnit_Null &&
+ aRuleData->mPresContext->UseDocumentColors())
+ color->SetColorValue(mColor);
+ }
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::HTMLColorRule::MightMapInheritedStyleData()
+{
+ return true;
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::HTMLColorRule::
+GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue)
+{
+ MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+ return false;
+}
+
+#ifdef DEBUG
+/* virtual */ void
+nsHTMLStyleSheet::HTMLColorRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indentStr;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+ fprintf_stderr(out, "%s[html color rule] {}\n", indentStr.get());
+}
+#endif
+
+
+NS_IMPL_ISUPPORTS(nsHTMLStyleSheet::GenericTableRule, nsIStyleRule)
+
+#ifdef DEBUG
+/* virtual */ void
+nsHTMLStyleSheet::GenericTableRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indentStr;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+ fprintf_stderr(out, "%s[generic table rule] {}\n", indentStr.get());
+}
+#endif
+
+/* virtual */ void
+nsHTMLStyleSheet::TableTHRule::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+ nsCSSValue* textAlign = aRuleData->ValueForTextAlign();
+ if (textAlign->GetUnit() == eCSSUnit_Null) {
+ textAlign->SetIntValue(NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT,
+ eCSSUnit_Enumerated);
+ }
+ }
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::TableTHRule::MightMapInheritedStyleData()
+{
+ return true;
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::TableTHRule::
+GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue)
+{
+ MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+ return false;
+}
+
+/* virtual */ void
+nsHTMLStyleSheet::TableQuirkColorRule::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Color)) {
+ nsCSSValue* color = aRuleData->ValueForColor();
+ // We do not check UseDocumentColors() here, because we want to
+ // use the body color no matter what.
+ if (color->GetUnit() == eCSSUnit_Null)
+ color->SetIntValue(NS_STYLE_COLOR_INHERIT_FROM_BODY,
+ eCSSUnit_Enumerated);
+ }
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::TableQuirkColorRule::MightMapInheritedStyleData()
+{
+ return true;
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::TableQuirkColorRule::
+GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue)
+{
+ MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+ return false;
+}
+
+NS_IMPL_ISUPPORTS(nsHTMLStyleSheet::LangRule, nsIStyleRule)
+
+/* virtual */ void
+nsHTMLStyleSheet::LangRule::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Font)) {
+ nsCSSValue* lang = aRuleData->ValueForLang();
+ if (lang->GetUnit() == eCSSUnit_Null) {
+ lang->SetStringValue(mLang, eCSSUnit_Ident);
+ }
+ }
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::LangRule::MightMapInheritedStyleData()
+{
+ return true;
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::LangRule::
+GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue)
+{
+ MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+ return false;
+}
+
+#ifdef DEBUG
+/* virtual */ void
+nsHTMLStyleSheet::LangRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString str;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+ str.AppendLiteral("[lang rule] { language: \"");
+ AppendUTF16toUTF8(mLang, str);
+ str.AppendLiteral("\" }\n");
+ fprintf_stderr(out, "%s", str.get());
+}
+#endif
+
+// -----------------------------------------------------------
+
+struct MappedAttrTableEntry : public PLDHashEntryHdr {
+ nsMappedAttributes *mAttributes;
+};
+
+static PLDHashNumber
+MappedAttrTable_HashKey(const void *key)
+{
+ nsMappedAttributes *attributes =
+ static_cast<nsMappedAttributes*>(const_cast<void*>(key));
+
+ return attributes->HashValue();
+}
+
+static void
+MappedAttrTable_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
+{
+ MappedAttrTableEntry *entry = static_cast<MappedAttrTableEntry*>(hdr);
+
+ entry->mAttributes->DropStyleSheetReference();
+ memset(entry, 0, sizeof(MappedAttrTableEntry));
+}
+
+static bool
+MappedAttrTable_MatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ nsMappedAttributes *attributes =
+ static_cast<nsMappedAttributes*>(const_cast<void*>(key));
+ const MappedAttrTableEntry *entry =
+ static_cast<const MappedAttrTableEntry*>(hdr);
+
+ return attributes->Equals(entry->mAttributes);
+}
+
+static const PLDHashTableOps MappedAttrTable_Ops = {
+ MappedAttrTable_HashKey,
+ MappedAttrTable_MatchEntry,
+ PLDHashTable::MoveEntryStub,
+ MappedAttrTable_ClearEntry,
+ nullptr
+};
+
+// -----------------------------------------------------------
+
+struct LangRuleTableEntry : public PLDHashEntryHdr {
+ RefPtr<nsHTMLStyleSheet::LangRule> mRule;
+};
+
+static PLDHashNumber
+LangRuleTable_HashKey(const void *key)
+{
+ const nsString *lang = static_cast<const nsString*>(key);
+ return HashString(*lang);
+}
+
+static void
+LangRuleTable_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
+{
+ LangRuleTableEntry *entry = static_cast<LangRuleTableEntry*>(hdr);
+
+ entry->~LangRuleTableEntry();
+ memset(entry, 0, sizeof(LangRuleTableEntry));
+}
+
+static bool
+LangRuleTable_MatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+{
+ const nsString *lang = static_cast<const nsString*>(key);
+ const LangRuleTableEntry *entry = static_cast<const LangRuleTableEntry*>(hdr);
+
+ return entry->mRule->mLang == *lang;
+}
+
+static void
+LangRuleTable_InitEntry(PLDHashEntryHdr *hdr, const void *key)
+{
+ const nsString *lang = static_cast<const nsString*>(key);
+
+ LangRuleTableEntry *entry = new (KnownNotNull, hdr) LangRuleTableEntry();
+
+ // Create the unique rule for this language
+ entry->mRule = new nsHTMLStyleSheet::LangRule(*lang);
+}
+
+static const PLDHashTableOps LangRuleTable_Ops = {
+ LangRuleTable_HashKey,
+ LangRuleTable_MatchEntry,
+ PLDHashTable::MoveEntryStub,
+ LangRuleTable_ClearEntry,
+ LangRuleTable_InitEntry
+};
+
+// -----------------------------------------------------------
+
+nsHTMLStyleSheet::nsHTMLStyleSheet(nsIDocument* aDocument)
+ : mDocument(aDocument)
+ , mTableQuirkColorRule(new TableQuirkColorRule())
+ , mTableTHRule(new TableTHRule())
+ , mMappedAttrTable(&MappedAttrTable_Ops, sizeof(MappedAttrTableEntry))
+ , mLangRuleTable(&LangRuleTable_Ops, sizeof(LangRuleTableEntry))
+{
+ MOZ_ASSERT(aDocument);
+}
+
+NS_IMPL_ISUPPORTS(nsHTMLStyleSheet, nsIStyleRuleProcessor)
+
+/* virtual */ void
+nsHTMLStyleSheet::RulesMatching(ElementRuleProcessorData* aData)
+{
+ nsRuleWalker *ruleWalker = aData->mRuleWalker;
+ if (!ruleWalker->AuthorStyleDisabled()) {
+ // if we have anchor colors, check if this is an anchor with an href
+ if (aData->mElement->IsHTMLElement(nsGkAtoms::a)) {
+ if (mLinkRule || mVisitedRule || mActiveRule) {
+ EventStates state =
+ nsCSSRuleProcessor::GetContentStateForVisitedHandling(
+ aData->mElement,
+ aData->mTreeMatchContext,
+ aData->mTreeMatchContext.VisitedHandling(),
+ // If the node being matched is a link,
+ // it's the relevant link.
+ nsCSSRuleProcessor::IsLink(aData->mElement));
+ if (mLinkRule && state.HasState(NS_EVENT_STATE_UNVISITED)) {
+ ruleWalker->Forward(mLinkRule);
+ aData->mTreeMatchContext.SetHaveRelevantLink();
+ }
+ else if (mVisitedRule && state.HasState(NS_EVENT_STATE_VISITED)) {
+ ruleWalker->Forward(mVisitedRule);
+ aData->mTreeMatchContext.SetHaveRelevantLink();
+ }
+
+ // No need to add to the active rule if it's not a link
+ if (mActiveRule && nsCSSRuleProcessor::IsLink(aData->mElement) &&
+ state.HasState(NS_EVENT_STATE_ACTIVE)) {
+ ruleWalker->Forward(mActiveRule);
+ }
+ } // end link/visited/active rules
+ } // end A tag
+ // add the rule to handle text-align for a <th>
+ else if (aData->mElement->IsHTMLElement(nsGkAtoms::th)) {
+ ruleWalker->Forward(mTableTHRule);
+ }
+ else if (aData->mElement->IsHTMLElement(nsGkAtoms::table)) {
+ if (aData->mTreeMatchContext.mCompatMode == eCompatibility_NavQuirks) {
+ ruleWalker->Forward(mTableQuirkColorRule);
+ }
+ }
+ } // end html element
+
+ // just get the style rules from the content. For SVG we do this even if
+ // author style is disabled, because SVG presentational hints aren't
+ // considered style.
+ if (!ruleWalker->AuthorStyleDisabled() || aData->mElement->IsSVGElement()) {
+ aData->mElement->WalkContentStyleRules(ruleWalker);
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#language
+ // says that the xml:lang attribute overrides HTML's lang attribute,
+ // so we need to do this after WalkContentStyleRules.
+ nsString lang;
+ if (aData->mElement->GetAttr(kNameSpaceID_XML, nsGkAtoms::lang, lang)) {
+ ruleWalker->Forward(LangRuleFor(lang));
+ }
+
+ // Set the language to "x-math" on the <math> element, so that appropriate
+ // font settings are used for MathML.
+ if (aData->mElement->IsMathMLElement(nsGkAtoms::math)) {
+ nsGkAtoms::x_math->ToString(lang);
+ ruleWalker->Forward(LangRuleFor(lang));
+ }
+}
+
+// Test if style is dependent on content state
+/* virtual */ nsRestyleHint
+nsHTMLStyleSheet::HasStateDependentStyle(StateRuleProcessorData* aData)
+{
+ if (aData->mElement->IsHTMLElement(nsGkAtoms::a) &&
+ nsCSSRuleProcessor::IsLink(aData->mElement) &&
+ ((mActiveRule && aData->mStateMask.HasState(NS_EVENT_STATE_ACTIVE)) ||
+ (mLinkRule && aData->mStateMask.HasState(NS_EVENT_STATE_VISITED)) ||
+ (mVisitedRule && aData->mStateMask.HasState(NS_EVENT_STATE_VISITED)))) {
+ return eRestyle_Self;
+ }
+
+ return nsRestyleHint(0);
+}
+
+/* virtual */ nsRestyleHint
+nsHTMLStyleSheet::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData)
+{
+ return nsRestyleHint(0);
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
+{
+ return false;
+}
+
+/* virtual */ nsRestyleHint
+nsHTMLStyleSheet::HasAttributeDependentStyle(
+ AttributeRuleProcessorData* aData,
+ RestyleHintData& aRestyleHintDataResult)
+{
+ // Do nothing on before-change checks
+ if (!aData->mAttrHasChanged) {
+ return nsRestyleHint(0);
+ }
+
+ // Note: no need to worry about whether some states changed with this
+ // attribute here, because we handle that under HasStateDependentStyle() as
+ // needed.
+
+ // Result is true for |href| changes on HTML links if we have link rules.
+ Element *element = aData->mElement;
+ if (aData->mAttribute == nsGkAtoms::href &&
+ (mLinkRule || mVisitedRule || mActiveRule) &&
+ element->IsHTMLElement(nsGkAtoms::a)) {
+ return eRestyle_Self;
+ }
+
+ // Don't worry about the mDocumentColorRule since it only applies
+ // to descendants of body, when we're already reresolving.
+
+ // Handle the content style rules.
+ if (element->IsAttributeMapped(aData->mAttribute)) {
+ // cellpadding on tables is special and requires reresolving all
+ // the cells in the table
+ if (aData->mAttribute == nsGkAtoms::cellpadding &&
+ element->IsHTMLElement(nsGkAtoms::table)) {
+ return eRestyle_Subtree;
+ }
+ return eRestyle_Self;
+ }
+
+ return nsRestyleHint(0);
+}
+
+/* virtual */ bool
+nsHTMLStyleSheet::MediumFeaturesChanged(nsPresContext* aPresContext)
+{
+ return false;
+}
+
+/* virtual */ size_t
+nsHTMLStyleSheet::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return 0; // nsHTMLStyleSheets are charged to the DOM, not layout
+}
+
+/* virtual */ size_t
+nsHTMLStyleSheet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return 0; // nsHTMLStyleSheets are charged to the DOM, not layout
+}
+
+/* virtual */ void
+nsHTMLStyleSheet::RulesMatching(PseudoElementRuleProcessorData* aData)
+{
+}
+
+/* virtual */ void
+nsHTMLStyleSheet::RulesMatching(AnonBoxRuleProcessorData* aData)
+{
+}
+
+#ifdef MOZ_XUL
+/* virtual */ void
+nsHTMLStyleSheet::RulesMatching(XULTreeRuleProcessorData* aData)
+{
+}
+#endif
+
+void
+nsHTMLStyleSheet::SetOwningDocument(nsIDocument* aDocument)
+{
+ mDocument = aDocument; // not refcounted
+}
+
+void
+nsHTMLStyleSheet::Reset()
+{
+ mLinkRule = nullptr;
+ mVisitedRule = nullptr;
+ mActiveRule = nullptr;
+
+ mLangRuleTable.Clear();
+ mMappedAttrTable.Clear();
+}
+
+nsresult
+nsHTMLStyleSheet::ImplLinkColorSetter(RefPtr<HTMLColorRule>& aRule, nscolor aColor)
+{
+ if (aRule && aRule->mColor == aColor) {
+ return NS_OK;
+ }
+
+ aRule = new HTMLColorRule();
+ if (!aRule)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aRule->mColor = aColor;
+ // Now make sure we restyle any links that might need it. This
+ // shouldn't happen often, so just rebuilding everything is ok.
+ if (mDocument && mDocument->GetShell()) {
+ Element* root = mDocument->GetRootElement();
+ if (root) {
+ mDocument->GetShell()->GetPresContext()->RestyleManager()->
+ PostRestyleEvent(root, eRestyle_Subtree, nsChangeHint(0));
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsHTMLStyleSheet::SetLinkColor(nscolor aColor)
+{
+ return ImplLinkColorSetter(mLinkRule, aColor);
+}
+
+
+nsresult
+nsHTMLStyleSheet::SetActiveLinkColor(nscolor aColor)
+{
+ return ImplLinkColorSetter(mActiveRule, aColor);
+}
+
+nsresult
+nsHTMLStyleSheet::SetVisitedLinkColor(nscolor aColor)
+{
+ return ImplLinkColorSetter(mVisitedRule, aColor);
+}
+
+already_AddRefed<nsMappedAttributes>
+nsHTMLStyleSheet::UniqueMappedAttributes(nsMappedAttributes* aMapped)
+{
+ auto entry = static_cast<MappedAttrTableEntry*>
+ (mMappedAttrTable.Add(aMapped, fallible));
+ if (!entry)
+ return nullptr;
+ if (!entry->mAttributes) {
+ // We added a new entry to the hashtable, so we have a new unique set.
+ entry->mAttributes = aMapped;
+ }
+ RefPtr<nsMappedAttributes> ret = entry->mAttributes;
+ return ret.forget();
+}
+
+void
+nsHTMLStyleSheet::DropMappedAttributes(nsMappedAttributes* aMapped)
+{
+ NS_ENSURE_TRUE_VOID(aMapped);
+
+#ifdef DEBUG
+ uint32_t entryCount = mMappedAttrTable.EntryCount() - 1;
+#endif
+
+ mMappedAttrTable.Remove(aMapped);
+
+ NS_ASSERTION(entryCount == mMappedAttrTable.EntryCount(), "not removed");
+}
+
+nsIStyleRule*
+nsHTMLStyleSheet::LangRuleFor(const nsString& aLanguage)
+{
+ auto entry =
+ static_cast<LangRuleTableEntry*>(mLangRuleTable.Add(&aLanguage, fallible));
+ if (!entry) {
+ NS_ASSERTION(false, "out of memory");
+ return nullptr;
+ }
+ return entry->mRule;
+}
+
+size_t
+nsHTMLStyleSheet::DOMSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ n += mMappedAttrTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mMappedAttrTable.ConstIter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<MappedAttrTableEntry*>(iter.Get());
+ n += entry->mAttributes->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mURL
+ // - mLinkRule
+ // - mVisitedRule
+ // - mActiveRule
+ // - mTableQuirkColorRule
+ // - mTableTHRule
+ // - mLangRuleTable
+ //
+ // The following members are not measured:
+ // - mDocument, because it's non-owning
+
+ return n;
+}
diff --git a/layout/style/nsHTMLStyleSheet.h b/layout/style/nsHTMLStyleSheet.h
new file mode 100644
index 000000000..a522fc3b4
--- /dev/null
+++ b/layout/style/nsHTMLStyleSheet.h
@@ -0,0 +1,181 @@
+/* -*- 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/. */
+
+/*
+ * style sheet and style rule processor representing data from presentational
+ * HTML attributes
+ */
+
+#ifndef nsHTMLStyleSheet_h_
+#define nsHTMLStyleSheet_h_
+
+#include "nsColor.h"
+#include "nsCOMPtr.h"
+#include "nsIStyleRule.h"
+#include "nsIStyleRuleProcessor.h"
+#include "PLDHashTable.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsString.h"
+
+class nsIDocument;
+class nsMappedAttributes;
+
+class nsHTMLStyleSheet final : public nsIStyleRuleProcessor
+{
+public:
+ explicit nsHTMLStyleSheet(nsIDocument* aDocument);
+
+ void SetOwningDocument(nsIDocument* aDocument);
+
+ NS_DECL_ISUPPORTS
+
+ // nsIStyleRuleProcessor API
+ virtual void RulesMatching(ElementRuleProcessorData* aData) override;
+ virtual void RulesMatching(PseudoElementRuleProcessorData* aData) override;
+ virtual void RulesMatching(AnonBoxRuleProcessorData* aData) override;
+#ifdef MOZ_XUL
+ virtual void RulesMatching(XULTreeRuleProcessorData* aData) override;
+#endif
+ virtual nsRestyleHint HasStateDependentStyle(StateRuleProcessorData* aData) override;
+ virtual nsRestyleHint HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData) override;
+ virtual bool HasDocumentStateDependentStyle(StateRuleProcessorData* aData) override;
+ virtual nsRestyleHint
+ HasAttributeDependentStyle(AttributeRuleProcessorData* aData,
+ mozilla::RestyleHintData& aRestyleHintDataResult) override;
+ virtual bool MediumFeaturesChanged(nsPresContext* aPresContext) override;
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ const MOZ_MUST_OVERRIDE override;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ const MOZ_MUST_OVERRIDE override;
+ size_t DOMSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ void Reset();
+ nsresult SetLinkColor(nscolor aColor);
+ nsresult SetActiveLinkColor(nscolor aColor);
+ nsresult SetVisitedLinkColor(nscolor aColor);
+
+ // Mapped Attribute management methods
+ already_AddRefed<nsMappedAttributes>
+ UniqueMappedAttributes(nsMappedAttributes* aMapped);
+ void DropMappedAttributes(nsMappedAttributes* aMapped);
+
+ nsIStyleRule* LangRuleFor(const nsString& aLanguage);
+
+private:
+ nsHTMLStyleSheet(const nsHTMLStyleSheet& aCopy) = delete;
+ nsHTMLStyleSheet& operator=(const nsHTMLStyleSheet& aCopy) = delete;
+
+ ~nsHTMLStyleSheet() {}
+
+ class HTMLColorRule;
+ friend class HTMLColorRule;
+ class HTMLColorRule final : public nsIStyleRule {
+ private:
+ ~HTMLColorRule() {}
+ public:
+ HTMLColorRule() {}
+
+ NS_DECL_ISUPPORTS
+
+ // nsIStyleRule interface
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+ #ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+ #endif
+
+ nscolor mColor;
+ };
+
+ // Implementation of SetLink/VisitedLink/ActiveLinkColor
+ nsresult ImplLinkColorSetter(RefPtr<HTMLColorRule>& aRule, nscolor aColor);
+
+ class GenericTableRule;
+ friend class GenericTableRule;
+ class GenericTableRule : public nsIStyleRule {
+ protected:
+ virtual ~GenericTableRule() {}
+ public:
+ GenericTableRule() {}
+
+ NS_DECL_ISUPPORTS
+
+ // nsIStyleRule interface
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override = 0;
+ virtual bool MightMapInheritedStyleData() override = 0;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override = 0;
+ #ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+ #endif
+ };
+
+ // this rule handles <th> inheritance
+ class TableTHRule;
+ friend class TableTHRule;
+ class TableTHRule final : public GenericTableRule {
+ public:
+ TableTHRule() {}
+
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+ };
+
+ // Rule to handle quirk table colors
+ class TableQuirkColorRule final : public GenericTableRule {
+ public:
+ TableQuirkColorRule() {}
+
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+ };
+
+public: // for mLangRuleTable structures only
+
+ // Rule to handle xml:lang attributes, of which we have exactly one
+ // per language string, maintained in mLangRuleTable.
+ // We also create one extra rule for the "x-math" language string, used on
+ // <math> elements.
+ class LangRule final : public nsIStyleRule {
+ private:
+ ~LangRule() {}
+ public:
+ explicit LangRule(const nsSubstring& aLang) : mLang(aLang) {}
+
+ NS_DECL_ISUPPORTS
+
+ // nsIStyleRule interface
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+ #ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+ #endif
+
+ nsString mLang;
+ };
+
+private:
+ nsIDocument* mDocument;
+ RefPtr<HTMLColorRule> mLinkRule;
+ RefPtr<HTMLColorRule> mVisitedRule;
+ RefPtr<HTMLColorRule> mActiveRule;
+ RefPtr<TableQuirkColorRule> mTableQuirkColorRule;
+ RefPtr<TableTHRule> mTableTHRule;
+
+ PLDHashTable mMappedAttrTable;
+ PLDHashTable mLangRuleTable;
+};
+
+#endif /* !defined(nsHTMLStyleSheet_h_) */
diff --git a/layout/style/nsICSSDeclaration.h b/layout/style/nsICSSDeclaration.h
new file mode 100644
index 000000000..ff6ec4a78
--- /dev/null
+++ b/layout/style/nsICSSDeclaration.h
@@ -0,0 +1,174 @@
+/* -*- 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/. */
+
+/*
+ * faster version of nsIDOMCSSStyleDeclaration using enums instead of
+ * strings, for internal use
+ */
+
+#ifndef nsICSSDeclaration_h__
+#define nsICSSDeclaration_h__
+
+/**
+ * This interface provides access to methods analogous to those of
+ * nsIDOMCSSStyleDeclaration; the difference is that these use
+ * nsCSSPropertyID enums for the prop names instead of using strings.
+ * This is meant for use in performance-sensitive code only! Most
+ * consumers should continue to use nsIDOMCSSStyleDeclaration.
+ */
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsCSSPropertyID.h"
+#include "CSSValue.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+#include "nsIDOMCSSRule.h"
+#include "nsIDOMCSSValue.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+
+class nsINode;
+
+// dbeabbfa-6cb3-4f5c-aec2-dd558d9d681f
+#define NS_ICSSDECLARATION_IID \
+{ 0xdbeabbfa, 0x6cb3, 0x4f5c, \
+ { 0xae, 0xc2, 0xdd, 0x55, 0x8d, 0x9d, 0x68, 0x1f } }
+
+class nsICSSDeclaration : public nsIDOMCSSStyleDeclaration,
+ public nsWrapperCache
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICSSDECLARATION_IID)
+
+ /**
+ * Method analogous to nsIDOMCSSStyleDeclaration::GetPropertyValue,
+ * which obeys all the same restrictions.
+ */
+ NS_IMETHOD GetPropertyValue(const nsCSSPropertyID aPropID,
+ nsAString& aValue) = 0;
+
+ NS_IMETHOD GetAuthoredPropertyValue(const nsAString& aPropName,
+ nsAString& aValue) = 0;
+
+ /**
+ * Method analogous to nsIDOMCSSStyleDeclaration::SetProperty. This
+ * method does NOT allow setting a priority (the priority will
+ * always be set to default priority).
+ */
+ NS_IMETHOD SetPropertyValue(const nsCSSPropertyID aPropID,
+ const nsAString& aValue) = 0;
+
+ virtual nsINode *GetParentObject() = 0;
+
+ // Also have to declare all the nsIDOMCSSStyleDeclaration methods,
+ // since we want to be able to call them from the WebIDL versions.
+ NS_IMETHOD GetCssText(nsAString& aCssText) override = 0;
+ NS_IMETHOD SetCssText(const nsAString& aCssText) override = 0;
+ NS_IMETHOD GetPropertyValue(const nsAString& aPropName,
+ nsAString& aValue) override = 0;
+ virtual already_AddRefed<mozilla::dom::CSSValue>
+ GetPropertyCSSValue(const nsAString& aPropertyName,
+ mozilla::ErrorResult& aRv) = 0;
+ NS_IMETHOD GetPropertyCSSValue(const nsAString& aProp, nsIDOMCSSValue** aVal) override
+ {
+ mozilla::ErrorResult error;
+ RefPtr<mozilla::dom::CSSValue> val = GetPropertyCSSValue(aProp, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ nsCOMPtr<nsIDOMCSSValue> xpVal = do_QueryInterface(val);
+ xpVal.forget(aVal);
+ return NS_OK;
+ }
+ NS_IMETHOD RemoveProperty(const nsAString& aPropertyName,
+ nsAString& aReturn) override = 0;
+ NS_IMETHOD GetPropertyPriority(const nsAString& aPropertyName,
+ nsAString& aReturn) override = 0;
+ NS_IMETHOD SetProperty(const nsAString& aPropertyName,
+ const nsAString& aValue,
+ const nsAString& aPriority) override = 0;
+ NS_IMETHOD GetLength(uint32_t* aLength) override = 0;
+ NS_IMETHOD Item(uint32_t aIndex, nsAString& aReturn) override
+ {
+ bool found;
+ IndexedGetter(aIndex, found, aReturn);
+ if (!found) {
+ aReturn.Truncate();
+ }
+ return NS_OK;
+ }
+ NS_IMETHOD GetParentRule(nsIDOMCSSRule * *aParentRule) override = 0;
+
+ // WebIDL interface for CSSStyleDeclaration
+ void SetCssText(const nsAString& aString, mozilla::ErrorResult& rv) {
+ rv = SetCssText(aString);
+ }
+ void GetCssText(nsString& aString) {
+ // Cast to nsAString& so we end up calling our virtual
+ // |GetCssText(nsAString& aCssText)| overload, which does the real work.
+ GetCssText(static_cast<nsAString&>(aString));
+ }
+ uint32_t Length() {
+ uint32_t length;
+ GetLength(&length);
+ return length;
+ }
+ void Item(uint32_t aIndex, nsString& aPropName) {
+ Item(aIndex, static_cast<nsAString&>(aPropName));
+ }
+
+ // The actual implementation of the Item method and the WebIDL indexed getter
+ virtual void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aPropName) = 0;
+
+ void GetPropertyValue(const nsAString& aPropName, nsString& aValue,
+ mozilla::ErrorResult& rv) {
+ rv = GetPropertyValue(aPropName, aValue);
+ }
+ void GetAuthoredPropertyValue(const nsAString& aPropName, nsString& aValue,
+ mozilla::ErrorResult& rv) {
+ rv = GetAuthoredPropertyValue(aPropName, aValue);
+ }
+ void GetPropertyPriority(const nsAString& aPropName, nsString& aPriority) {
+ GetPropertyPriority(aPropName, static_cast<nsAString&>(aPriority));
+ }
+ void SetProperty(const nsAString& aPropName, const nsAString& aValue,
+ const nsAString& aPriority, mozilla::ErrorResult& rv) {
+ rv = SetProperty(aPropName, aValue, aPriority);
+ }
+ void RemoveProperty(const nsAString& aPropName, nsString& aRetval,
+ mozilla::ErrorResult& rv) {
+ rv = RemoveProperty(aPropName, aRetval);
+ }
+ already_AddRefed<nsIDOMCSSRule> GetParentRule() {
+ nsCOMPtr<nsIDOMCSSRule> rule;
+ GetParentRule(getter_AddRefs(rule));
+ return rule.forget();
+ }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICSSDeclaration, NS_ICSSDECLARATION_IID)
+
+#define NS_DECL_NSICSSDECLARATION \
+ NS_IMETHOD GetPropertyValue(const nsCSSPropertyID aPropID, \
+ nsAString& aValue) override; \
+ NS_IMETHOD GetAuthoredPropertyValue(const nsAString& aPropName, \
+ nsAString& aValue) override; \
+ NS_IMETHOD SetPropertyValue(const nsCSSPropertyID aPropID, \
+ const nsAString& aValue) override;
+
+#define NS_DECL_NSIDOMCSSSTYLEDECLARATION_HELPER \
+ NS_IMETHOD GetCssText(nsAString & aCssText) override; \
+ NS_IMETHOD SetCssText(const nsAString & aCssText) override; \
+ NS_IMETHOD GetPropertyValue(const nsAString & propertyName, nsAString & _retval) override; \
+ NS_IMETHOD RemoveProperty(const nsAString & propertyName, nsAString & _retval) override; \
+ NS_IMETHOD GetPropertyPriority(const nsAString & propertyName, nsAString & _retval) override; \
+ NS_IMETHOD SetProperty(const nsAString & propertyName, const nsAString & value, const nsAString & priority) override; \
+ NS_IMETHOD GetLength(uint32_t *aLength) override; \
+ NS_IMETHOD Item(uint32_t index, nsAString & _retval) override; \
+ NS_IMETHOD GetParentRule(nsIDOMCSSRule * *aParentRule) override;
+
+#endif // nsICSSDeclaration_h__
diff --git a/layout/style/nsICSSLoaderObserver.h b/layout/style/nsICSSLoaderObserver.h
new file mode 100644
index 000000000..acacf50f9
--- /dev/null
+++ b/layout/style/nsICSSLoaderObserver.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* internal interface for observing CSS style sheet loads */
+
+#ifndef nsICSSLoaderObserver_h___
+#define nsICSSLoaderObserver_h___
+
+#include "nsISupports.h"
+#include "mozilla/StyleSheet.h"
+
+#define NS_ICSSLOADEROBSERVER_IID \
+{ 0xf51fbf2c, 0xfe4b, 0x4a15, \
+ { 0xaf, 0x7e, 0x5e, 0x20, 0x64, 0x5f, 0xaf, 0x58 } }
+
+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 aWasAlternate whether the sheet was an alternate. This will always
+ * match the value LoadStyleLink or LoadInlineStyle returned in
+ * aIsAlternate if one of those methods were used to load the sheet,
+ * and will always be false otherwise.
+ * @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 aWasAlternate,
+ nsresult aStatus) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICSSLoaderObserver, NS_ICSSLOADEROBSERVER_IID)
+
+#endif // nsICSSLoaderObserver_h___
diff --git a/layout/style/nsICSSPseudoComparator.h b/layout/style/nsICSSPseudoComparator.h
new file mode 100644
index 000000000..0ea207891
--- /dev/null
+++ b/layout/style/nsICSSPseudoComparator.h
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+/* internal interface for implementing complex pseudo-classes */
+
+#ifndef nsICSSPseudoComparator_h___
+#define nsICSSPseudoComparator_h___
+
+struct nsCSSSelector;
+
+class nsICSSPseudoComparator
+{
+public:
+ virtual bool PseudoMatches(nsCSSSelector* aSelector)=0;
+};
+
+#endif /* nsICSSPseudoComparator_h___ */
diff --git a/layout/style/nsICSSStyleRuleDOMWrapper.h b/layout/style/nsICSSStyleRuleDOMWrapper.h
new file mode 100644
index 000000000..038cca086
--- /dev/null
+++ b/layout/style/nsICSSStyleRuleDOMWrapper.h
@@ -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/. */
+
+/*
+ * interface to provide DOM inspector with access to internal interfaces
+ * via DOM interface
+ */
+
+#ifndef nsICSSStyleRuleDOMWrapper_h_
+#define nsICSSStyleRuleDOMWrapper_h_
+
+#include "nsIDOMCSSStyleRule.h"
+
+// IID for the nsICSSStyleRuleDOMWrapper interface
+// {cee1bbb6-0a32-4cf3-8d42-ba3938e9ecaa}
+#define NS_ICSS_STYLE_RULE_DOM_WRAPPER_IID \
+{0xcee1bbb6, 0x0a32, 0x4cf3, {0x8d, 0x42, 0xba, 0x39, 0x38, 0xe9, 0xec, 0xaa}}
+
+
+class nsICSSStyleRuleDOMWrapper : public nsIDOMCSSStyleRule {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICSS_STYLE_RULE_DOM_WRAPPER_IID)
+
+ NS_IMETHOD GetCSSStyleRule(mozilla::css::StyleRule** aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICSSStyleRuleDOMWrapper,
+ NS_ICSS_STYLE_RULE_DOM_WRAPPER_IID)
+
+#endif /* !defined(nsICSSStyleRuleDOMWrapper_h_) */
diff --git a/layout/style/nsICSSUnprefixingService.idl b/layout/style/nsICSSUnprefixingService.idl
new file mode 100644
index 000000000..11c3bf43f
--- /dev/null
+++ b/layout/style/nsICSSUnprefixingService.idl
@@ -0,0 +1,76 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* interface for a service that converts certain vendor-prefixed CSS properties
+ to their unprefixed equivalents */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a5d6e2f4-d3ec-11e4-b002-782bcbaebb28)]
+interface nsICSSUnprefixingService : nsISupports
+{
+ /**
+ * This function helps to convert unsupported vendor-prefixed CSS into
+ * supported unprefixed CSS. Given a vendor-prefixed property name and a
+ * value (or e.g. value + trailing junk like " !important;}"), this function
+ * will attempt to produce an equivalent CSS declaration that uses a
+ * supported unprefixed CSS property.
+ *
+ * @param aPropName
+ * The vendor-prefixed property name.
+ *
+ * @param aRightHalfOfDecl
+ * Everything after the ":" in the CSS declaration. This includes
+ * the property's value, along with possibly some leading whitespace
+ * and trailing text like "!important", and possibly a ';' and/or
+ * '}' (along with any other bogus text the author happens to
+ * include before those, which will probably make the decl invalid).
+ *
+ * @param aUnprefixedDecl[out]
+ * The resulting unprefixed declaration, if we return true.
+ *
+ * @return true if we were able to unprefix -- i.e. if we were able to
+ * convert the property to a known unprefixed equivalent, and we also
+ * performed any known-to-be-necessary fixup on the value, and we put
+ * the result in aUnprefixedDecl.
+ * Otherwise, this function returns false.
+ */
+ boolean generateUnprefixedDeclaration(in AString aPropName,
+ in AString aRightHalfOfDecl,
+ out AString aUnprefixedDecl);
+
+ /**
+ * @param aPrefixedFuncName
+ * The webkit-prefixed gradient function: either
+ * "-webkit-gradient", "-webkit-linear-gradient", or
+ * "-webkit-radial-gradient".
+ *
+ * @param aPrefixedFuncBody
+ * The body of the gradient function, inside (& not including) the
+ * parenthesis.
+ *
+ * @param aUnprefixedFuncName[out]
+ * The resulting unprefixed gradient function name:
+ * either "linear-gradient" or "radial-gradient".
+ *
+ * @param aUnprefixedFuncBody[out]
+ * The resulting unprefixed gradient function body, suitable for
+ * including in a "linear-gradient(...)" or "radial-gradient(...)"
+ * expression.
+ *
+ * @returns true if we were able to successfully parse aWebkitGradientStr
+ * and populate the outparams accordingly; false otherwise.
+ *
+ */
+ boolean generateUnprefixedGradientValue(in AString aPrefixedFuncName,
+ in AString aPrefixedFuncBody,
+ out AString aUnprefixedFuncName,
+ out AString aUnprefixedFuncBody);
+};
+
+%{C++
+#define NS_CSSUNPREFIXINGSERVICE_CONTRACTID \
+ "@mozilla.org/css-unprefixing-service;1"
+%}
diff --git a/layout/style/nsIMediaList.h b/layout/style/nsIMediaList.h
new file mode 100644
index 000000000..43b631ae8
--- /dev/null
+++ b/layout/style/nsIMediaList.h
@@ -0,0 +1,318 @@
+/* -*- 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/. */
+
+/*
+ * representation of media lists used when linking to style sheets or by
+ * @media rules
+ */
+
+#ifndef nsIMediaList_h_
+#define nsIMediaList_h_
+
+#include "nsAutoPtr.h"
+#include "nsIDOMMediaList.h"
+#include "nsTArray.h"
+#include "nsIAtom.h"
+#include "nsCSSValue.h"
+#include "nsWrapperCache.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+
+class nsPresContext;
+class nsAString;
+struct nsMediaFeature;
+
+namespace mozilla {
+class CSSStyleSheet;
+namespace css {
+class DocumentRule;
+} // namespace css
+} // namespace mozilla
+
+struct nsMediaExpression {
+ enum Range { eMin, eMax, eEqual };
+
+ const nsMediaFeature *mFeature;
+ Range mRange;
+ nsCSSValue mValue;
+
+ // aActualValue must be obtained from mFeature->mGetter
+ bool Matches(nsPresContext* aPresContext,
+ const nsCSSValue& aActualValue) const;
+
+ bool operator==(const nsMediaExpression& aOther) const {
+ return mFeature == aOther.mFeature && // pointer equality fine (atom-like)
+ mRange == aOther.mRange &&
+ mValue == aOther.mValue;
+ }
+ bool operator!=(const nsMediaExpression& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+/**
+ * An nsMediaQueryResultCacheKey records what feature/value combinations
+ * a set of media query results are valid for. This allows the caller
+ * to quickly learn whether a prior result of media query evaluation is
+ * still valid (e.g., due to a window size change) without rerunning all
+ * of the evaluation and rebuilding the list of rules.
+ *
+ * This object may not be used after any media rules in any of the
+ * sheets it was given to have been modified. However, this is
+ * generally not a problem since ClearRuleCascades is called on the
+ * sheet whenever this happens, and these objects are stored inside the
+ * rule cascades. (FIXME: We're not actually doing this all the time.)
+ *
+ * The implementation could be further optimized in the future to store
+ * ranges (combinations of less-than, less-than-or-equal, greater-than,
+ * greater-than-or-equal, equal, not-equal, present, not-present) for
+ * each feature rather than simply storing the list of expressions.
+ * However, this requires combining any such ranges.
+ */
+class nsMediaQueryResultCacheKey {
+public:
+ explicit nsMediaQueryResultCacheKey(nsIAtom* aMedium)
+ : mMedium(aMedium)
+ {}
+
+ /**
+ * Record that aExpression was tested while building the cached set
+ * that this cache key is for, and that aExpressionMatches was whether
+ * it matched.
+ */
+ void AddExpression(const nsMediaExpression* aExpression,
+ bool aExpressionMatches);
+ bool Matches(nsPresContext* aPresContext) const;
+ bool HasFeatureConditions() const {
+ return !mFeatureCache.IsEmpty();
+ }
+
+ /**
+ * An operator== that implements list equality, which isn't quite as
+ * good as set equality, but catches the trivial equality cases.
+ */
+ bool operator==(const nsMediaQueryResultCacheKey& aOther) const {
+ return mMedium == aOther.mMedium &&
+ mFeatureCache == aOther.mFeatureCache;
+ }
+ bool operator!=(const nsMediaQueryResultCacheKey& aOther) const {
+ return !(*this == aOther);
+ }
+private:
+ struct ExpressionEntry {
+ // FIXME: if we were better at maintaining invariants about clearing
+ // rule cascades when media lists change, this could be a |const
+ // nsMediaExpression*| instead.
+ nsMediaExpression mExpression;
+ bool mExpressionMatches;
+
+ bool operator==(const ExpressionEntry& aOther) const {
+ return mExpression == aOther.mExpression &&
+ mExpressionMatches == aOther.mExpressionMatches;
+ }
+ bool operator!=(const ExpressionEntry& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+ struct FeatureEntry {
+ const nsMediaFeature *mFeature;
+ InfallibleTArray<ExpressionEntry> mExpressions;
+
+ bool operator==(const FeatureEntry& aOther) const {
+ return mFeature == aOther.mFeature &&
+ mExpressions == aOther.mExpressions;
+ }
+ bool operator!=(const FeatureEntry& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+ nsCOMPtr<nsIAtom> mMedium;
+ nsTArray<FeatureEntry> mFeatureCache;
+};
+
+/**
+ * nsDocumentRuleResultCacheKey is analagous to nsMediaQueryResultCacheKey
+ * and stores the result of matching the @-moz-document rules from a set
+ * of style sheets. nsCSSRuleProcessor builds up an
+ * nsDocumentRuleResultCacheKey as it visits the @-moz-document rules
+ * while building its RuleCascadeData.
+ *
+ * Rather than represent the result using a list of both the matching and
+ * non-matching rules, we just store the matched rules. The assumption is
+ * that in situations where we have a large number of rules -- such as the
+ * thousands added by AdBlock Plus -- that only a small number will be
+ * matched. Thus to check if the nsDocumentRuleResultCacheKey matches a
+ * given nsPresContext, we also need the entire list of @-moz-document
+ * rules to know which rules must not match.
+ */
+class nsDocumentRuleResultCacheKey
+{
+public:
+#ifdef DEBUG
+ nsDocumentRuleResultCacheKey()
+ : mFinalized(false) {}
+#endif
+
+ bool AddMatchingRule(mozilla::css::DocumentRule* aRule);
+ bool Matches(nsPresContext* aPresContext,
+ const nsTArray<mozilla::css::DocumentRule*>& aRules) const;
+
+ bool operator==(const nsDocumentRuleResultCacheKey& aOther) const {
+ MOZ_ASSERT(mFinalized);
+ MOZ_ASSERT(aOther.mFinalized);
+ return mMatchingRules == aOther.mMatchingRules;
+ }
+ bool operator!=(const nsDocumentRuleResultCacheKey& aOther) const {
+ return !(*this == aOther);
+ }
+
+ void Swap(nsDocumentRuleResultCacheKey& aOther) {
+ mMatchingRules.SwapElements(aOther.mMatchingRules);
+#ifdef DEBUG
+ std::swap(mFinalized, aOther.mFinalized);
+#endif
+ }
+
+ void Finalize();
+
+#ifdef DEBUG
+ void List(FILE* aOut = stdout, int32_t aIndex = 0) const;
+#endif
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ nsTArray<mozilla::css::DocumentRule*> mMatchingRules;
+#ifdef DEBUG
+ bool mFinalized;
+#endif
+};
+
+class nsMediaQuery {
+public:
+ nsMediaQuery()
+ : mNegated(false)
+ , mHasOnly(false)
+ , mTypeOmitted(false)
+ , mHadUnknownExpression(false)
+ {
+ }
+
+private:
+ // for Clone only
+ nsMediaQuery(const nsMediaQuery& aOther)
+ : mNegated(aOther.mNegated)
+ , mHasOnly(aOther.mHasOnly)
+ , mTypeOmitted(aOther.mTypeOmitted)
+ , mHadUnknownExpression(aOther.mHadUnknownExpression)
+ , mMediaType(aOther.mMediaType)
+ , mExpressions(aOther.mExpressions)
+ {
+ MOZ_ASSERT(mExpressions.Length() == aOther.mExpressions.Length());
+ }
+
+public:
+
+ void SetNegated() { mNegated = true; }
+ void SetHasOnly() { mHasOnly = true; }
+ void SetTypeOmitted() { mTypeOmitted = true; }
+ void SetHadUnknownExpression() { mHadUnknownExpression = true; }
+ void SetType(nsIAtom* aMediaType) {
+ NS_ASSERTION(aMediaType,
+ "expected non-null");
+ mMediaType = aMediaType;
+ }
+
+ // Return a new nsMediaExpression in the array for the caller to fill
+ // in. The caller must either fill it in completely, or call
+ // SetHadUnknownExpression on this nsMediaQuery.
+ // Returns null on out-of-memory.
+ nsMediaExpression* NewExpression() { return mExpressions.AppendElement(); }
+
+ void AppendToString(nsAString& aString) const;
+
+ nsMediaQuery* Clone() const;
+
+ // Does this query apply to the presentation?
+ // If |aKey| is non-null, add cache information to it.
+ bool Matches(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey* aKey) const;
+
+private:
+ bool mNegated;
+ bool mHasOnly; // only needed for serialization
+ bool mTypeOmitted; // only needed for serialization
+ bool mHadUnknownExpression;
+ nsCOMPtr<nsIAtom> mMediaType;
+ nsTArray<nsMediaExpression> mExpressions;
+};
+
+class nsMediaList final : public nsIDOMMediaList
+ , public nsWrapperCache
+{
+public:
+ typedef mozilla::ErrorResult ErrorResult;
+
+ nsMediaList();
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() const
+ {
+ return nullptr;
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsMediaList)
+
+ NS_DECL_NSIDOMMEDIALIST
+
+ void GetText(nsAString& aMediaText);
+ void SetText(const nsAString& aMediaText);
+
+ // Does this query apply to the presentation?
+ // If |aKey| is non-null, add cache information to it.
+ bool Matches(nsPresContext* aPresContext,
+ nsMediaQueryResultCacheKey* aKey);
+
+ void SetStyleSheet(mozilla::CSSStyleSheet* aSheet);
+ void AppendQuery(nsAutoPtr<nsMediaQuery>& aQuery) {
+ // Takes ownership of aQuery
+ mArray.AppendElement(aQuery.forget());
+ }
+
+ already_AddRefed<nsMediaList> Clone();
+
+ nsMediaQuery* MediumAt(int32_t aIndex) { return mArray[aIndex]; }
+ void Clear() { mArray.Clear(); }
+
+ // WebIDL
+ // XPCOM GetMediaText and SetMediaText are fine.
+ uint32_t Length() { return mArray.Length(); }
+ void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aReturn);
+ // XPCOM Item is fine.
+ void DeleteMedium(const nsAString& aMedium, ErrorResult& aRv)
+ {
+ aRv = DeleteMedium(aMedium);
+ }
+ void AppendMedium(const nsAString& aMedium, ErrorResult& aRv)
+ {
+ aRv = AppendMedium(aMedium);
+ }
+
+protected:
+ ~nsMediaList();
+
+ nsresult Delete(const nsAString & aOldMedium);
+ nsresult Append(const nsAString & aOldMedium);
+
+ InfallibleTArray<nsAutoPtr<nsMediaQuery> > mArray;
+ // not refcounted; sheet will let us know when it goes away
+ // mStyleSheet is the sheet that needs to be dirtied when this medialist
+ // changes
+ mozilla::CSSStyleSheet* mStyleSheet;
+};
+#endif /* !defined(nsIMediaList_h_) */
diff --git a/layout/style/nsIStyleRule.h b/layout/style/nsIStyleRule.h
new file mode 100644
index 000000000..01259170d
--- /dev/null
+++ b/layout/style/nsIStyleRule.h
@@ -0,0 +1,96 @@
+/* -*- 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/. */
+
+/*
+ * internal abstract interface for objects providing immutable style
+ * information
+ */
+
+#ifndef nsIStyleRule_h___
+#define nsIStyleRule_h___
+
+#include <stdio.h>
+
+#include "nsCSSPropertyID.h"
+#include "nsISupports.h"
+
+class nsCSSValue;
+struct nsRuleData;
+
+// IID for the nsIStyleRule interface {f75f3f70-435d-43a6-a01b-65970489ca26}
+#define NS_ISTYLE_RULE_IID \
+{ 0xf75f3f70, 0x435d, 0x43a6, \
+ { 0xa0, 0x1b, 0x65, 0x97, 0x04, 0x89, 0xca, 0x26 } }
+
+/**
+ * An object implementing |nsIStyleRule| (henceforth, a rule) represents
+ * immutable stylistic information that either applies or does not apply
+ * to a given element. It belongs to an object or group of objects that
+ * implement |nsIStyleSheet| and |nsIStyleRuleProcessor| (henceforth, a
+ * sheet).
+ *
+ * A rule becomes relevant to the computation of style data when
+ * |nsIStyleRuleProcessor::RulesMatching| creates a rule node that
+ * points to the rule. (A rule node, |nsRuleNode|, is a node in the
+ * rule tree, which is a lexicographic tree indexed by rules. The path
+ * from the root of the rule tree to the |nsRuleNode| for a given
+ * |nsStyleContext| contains exactly the rules that match the element
+ * that the style context is for, in priority (weight, origin,
+ * specificity) order.)
+ *
+ * The computation of style data uses the rule tree, which calls
+ * |nsIStyleRule::MapRuleInfoInto| below.
+ *
+ * It is worth emphasizing that the data represented by a rule
+ * implementation are immutable. When the data need to be changed, a
+ * new rule object must be created. Failing to do this will lead to
+ * bugs in the handling of dynamic style changes, since the rule tree
+ * caches the results of |MapRuleInfoInto|.
+ *
+ * |nsIStyleRule| objects are owned by |nsRuleNode| objects (in addition
+ * to typically being owned by their sheet), which are in turn garbage
+ * collected (with the garbage collection roots being style contexts).
+ */
+
+
+class nsIStyleRule : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISTYLE_RULE_IID)
+
+ /**
+ * |nsIStyleRule::MapRuleInfoInto| is a request to copy all stylistic
+ * data represented by the rule that:
+ * + are relevant for any structs in |aRuleData->mSIDs| (style
+ * struct ID bits)
+ * + are not already filled into the data struct
+ * into the appropriate data struct in |aRuleData|. It is important
+ * that only empty data are filled in, since the rule tree is walked
+ * from highest priority rule to least, so that the walk can stop if
+ * all needed data are found. Thus overwriting non-empty data will
+ * break CSS cascading rules.
+ */
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData)=0;
+
+ /**
+ * Returns whether this style rule has any style data for inherited
+ * properties.
+ */
+ virtual bool MightMapInheritedStyleData() = 0;
+
+ /**
+ * Gets and sets given aProperty's value to aValue.
+ * Returns true, if aValue is filled in this rule.
+ */
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) = 0;
+
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const = 0;
+#endif
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIStyleRule, NS_ISTYLE_RULE_IID)
+
+#endif /* nsIStyleRule_h___ */
diff --git a/layout/style/nsIStyleRuleProcessor.h b/layout/style/nsIStyleRuleProcessor.h
new file mode 100644
index 000000000..2075050d4
--- /dev/null
+++ b/layout/style/nsIStyleRuleProcessor.h
@@ -0,0 +1,140 @@
+/* -*- 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/. */
+
+/*
+ * internal abstract interface for containers (roughly origins within
+ * the CSS cascade) that provide style rules matching an element or
+ * pseudo-element
+ */
+
+#ifndef nsIStyleRuleProcessor_h___
+#define nsIStyleRuleProcessor_h___
+
+#include "mozilla/MemoryReporting.h"
+#include "nsISupports.h"
+#include "nsChangeHint.h"
+
+struct RuleProcessorData;
+struct ElementRuleProcessorData;
+struct PseudoElementRuleProcessorData;
+struct AnonBoxRuleProcessorData;
+#ifdef MOZ_XUL
+struct XULTreeRuleProcessorData;
+#endif
+struct StateRuleProcessorData;
+struct PseudoElementStateRuleProcessorData;
+struct AttributeRuleProcessorData;
+class nsPresContext;
+
+// IID for the nsIStyleRuleProcessor interface
+// {c1d6001e-4fcb-4c40-bce1-5eba80bfd8f3}
+#define NS_ISTYLE_RULE_PROCESSOR_IID \
+{ 0xc1d6001e, 0x4fcb, 0x4c40, \
+ {0xbc, 0xe1, 0x5e, 0xba, 0x80, 0xbf, 0xd8, 0xf3} }
+
+
+/* The style rule processor interface is a mechanism to separate the matching
+ * of style rules from style sheet instances.
+ * Simple style sheets can and will act as their own processor.
+ * Sheets where rule ordering interlaces between multiple sheets, will need to
+ * share a single rule processor between them (CSS sheets do this for cascading order)
+ *
+ * @see nsIStyleRule (for significantly more detailed comments)
+ */
+class nsIStyleRuleProcessor : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISTYLE_RULE_PROCESSOR_IID)
+
+ // Shorthand for:
+ // nsCOMArray<nsIStyleRuleProcessor>::nsCOMArrayEnumFunc
+ typedef bool (* EnumFunc)(nsIStyleRuleProcessor*, void*);
+
+ /**
+ * Find the |nsIStyleRule|s matching the given content node and
+ * position the given |nsRuleWalker| at the |nsRuleNode| in the rule
+ * tree representing that ordered list of rules (with higher
+ * precedence being farther from the root of the lexicographic tree).
+ */
+ virtual void RulesMatching(ElementRuleProcessorData* aData) = 0;
+
+ /**
+ * Just like the previous |RulesMatching|, except for a given content
+ * node <em>and pseudo-element</em>.
+ */
+ virtual void RulesMatching(PseudoElementRuleProcessorData* aData) = 0;
+
+ /**
+ * Just like the previous |RulesMatching|, except for a given anonymous box.
+ */
+ virtual void RulesMatching(AnonBoxRuleProcessorData* aData) = 0;
+
+#ifdef MOZ_XUL
+ /**
+ * Just like the previous |RulesMatching|, except for a given content
+ * node <em>and tree pseudo</em>.
+ */
+ virtual void RulesMatching(XULTreeRuleProcessorData* aData) = 0;
+#endif
+
+ /**
+ * Return whether style can depend on a change of the given document state.
+ *
+ * Document states are defined in nsIDocument.h.
+ */
+ virtual bool
+ HasDocumentStateDependentStyle(StateRuleProcessorData* aData) = 0;
+
+ /**
+ * Return how (as described by nsRestyleHint) style can depend on a
+ * change of the given content state on the given content node. This
+ * test is used for optimization only, and may err on the side of
+ * reporting more dependencies than really exist.
+ *
+ * Event states are defined in mozilla/EventStates.h.
+ */
+ virtual nsRestyleHint
+ HasStateDependentStyle(StateRuleProcessorData* aData) = 0;
+ virtual nsRestyleHint
+ HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData) = 0;
+
+ /**
+ * This method will be called twice for every attribute change.
+ * During the first call, aData->mAttrHasChanged will be false and
+ * the attribute change will not have happened yet. During the
+ * second call, aData->mAttrHasChanged will be true and the
+ * change will have already happened. The bitwise OR of the two
+ * return values must describe the style changes that are needed due
+ * to the attribute change. It's up to the rule processor
+ * implementation to decide how to split the bits up amongst the two
+ * return values. For example, it could return the bits needed by
+ * rules that might stop matching the node from the first call and
+ * the bits needed by rules that might have started matching the
+ * node from the second call. This test is used for optimization
+ * only, and may err on the side of reporting more dependencies than
+ * really exist.
+ */
+ virtual nsRestyleHint HasAttributeDependentStyle(
+ AttributeRuleProcessorData* aData,
+ mozilla::RestyleHintData& aRestyleHintDataResult) = 0;
+
+ /**
+ * Do any processing that needs to happen as a result of a change in
+ * the characteristics of the medium, and return whether this rule
+ * processor's rules have changed (e.g., because of media queries).
+ */
+ virtual bool MediumFeaturesChanged(nsPresContext* aPresContext) = 0;
+
+ /**
+ * Report the size of this style rule processor to about:memory. A
+ * processor may return 0.
+ */
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const = 0;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIStyleRuleProcessor,
+ NS_ISTYLE_RULE_PROCESSOR_IID)
+
+#endif /* nsIStyleRuleProcessor_h___ */
diff --git a/layout/style/nsLayoutStylesheetCache.cpp b/layout/style/nsLayoutStylesheetCache.cpp
new file mode 100644
index 000000000..f8aea5541
--- /dev/null
+++ b/layout/style/nsLayoutStylesheetCache.cpp
@@ -0,0 +1,1000 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsLayoutStylesheetCache.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "nsIConsoleService.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIXULRuntime.h"
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+
+// Includes for the crash report annotation in ErrorLoadingSheet.
+#ifdef MOZ_CRASHREPORTER
+#include "mozilla/Omnijar.h"
+#include "nsDirectoryService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsExceptionHandler.h"
+#include "nsIChromeRegistry.h"
+#include "nsISimpleEnumerator.h"
+#include "nsISubstitutingProtocolHandler.h"
+#include "zlib.h"
+#include "nsZipArchive.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::css;
+
+static bool sNumberControlEnabled;
+
+#define NUMBER_CONTROL_PREF "dom.forms.number"
+
+NS_IMPL_ISUPPORTS(
+ nsLayoutStylesheetCache, nsIObserver, nsIMemoryReporter)
+
+nsresult
+nsLayoutStylesheetCache::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 if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 ||
+ strcmp(aTopic, "chrome-flush-caches") == 0) {
+ mScrollbarsSheet = nullptr;
+ mFormsSheet = nullptr;
+ mNumberControlSheet = nullptr;
+ }
+ else {
+ NS_NOTREACHED("Unexpected observer topic.");
+ }
+ return NS_OK;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::ScrollbarsSheet()
+{
+ if (!mScrollbarsSheet) {
+ // Scrollbars don't need access to unsafe rules
+ LoadSheetURL("chrome://global/skin/scrollbars.css",
+ &mScrollbarsSheet, eAuthorSheetFeatures, eCrash);
+ }
+
+ return mScrollbarsSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::FormsSheet()
+{
+ if (!mFormsSheet) {
+ // forms.css needs access to unsafe rules
+ LoadSheetURL("resource://gre-resources/forms.css",
+ &mFormsSheet, eAgentSheetFeatures, eCrash);
+ }
+
+ return mFormsSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::NumberControlSheet()
+{
+ if (!sNumberControlEnabled) {
+ return nullptr;
+ }
+
+ if (!mNumberControlSheet) {
+ LoadSheetURL("resource://gre-resources/number-control.css",
+ &mNumberControlSheet, eAgentSheetFeatures, eCrash);
+ }
+
+ return mNumberControlSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::UserContentSheet()
+{
+ return mUserContentSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::UserChromeSheet()
+{
+ return mUserChromeSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::UASheet()
+{
+ if (!mUASheet) {
+ LoadSheetURL("resource://gre-resources/ua.css",
+ &mUASheet, eAgentSheetFeatures, eCrash);
+ }
+
+ return mUASheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::HTMLSheet()
+{
+ if (!mHTMLSheet) {
+ LoadSheetURL("resource://gre-resources/html.css",
+ &mHTMLSheet, eAgentSheetFeatures, eCrash);
+ }
+
+ return mHTMLSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::MinimalXULSheet()
+{
+ return mMinimalXULSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::XULSheet()
+{
+ return mXULSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::QuirkSheet()
+{
+ return mQuirkSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::SVGSheet()
+{
+ return mSVGSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::MathMLSheet()
+{
+ if (!mMathMLSheet) {
+ LoadSheetURL("resource://gre-resources/mathml.css",
+ &mMathMLSheet, eAgentSheetFeatures, eCrash);
+ }
+
+ return mMathMLSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::CounterStylesSheet()
+{
+ return mCounterStylesSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::NoScriptSheet()
+{
+ if (!mNoScriptSheet) {
+ LoadSheetURL("resource://gre-resources/noscript.css",
+ &mNoScriptSheet, eAgentSheetFeatures, eCrash);
+ }
+
+ return mNoScriptSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::NoFramesSheet()
+{
+ if (!mNoFramesSheet) {
+ LoadSheetURL("resource://gre-resources/noframes.css",
+ &mNoFramesSheet, eAgentSheetFeatures, eCrash);
+ }
+
+ return mNoFramesSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::ChromePreferenceSheet(nsPresContext* aPresContext)
+{
+ if (!mChromePreferenceSheet) {
+ BuildPreferenceSheet(&mChromePreferenceSheet, aPresContext);
+ }
+
+ return mChromePreferenceSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::ContentPreferenceSheet(nsPresContext* aPresContext)
+{
+ if (!mContentPreferenceSheet) {
+ BuildPreferenceSheet(&mContentPreferenceSheet, aPresContext);
+ }
+
+ return mContentPreferenceSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::ContentEditableSheet()
+{
+ if (!mContentEditableSheet) {
+ LoadSheetURL("resource://gre/res/contenteditable.css",
+ &mContentEditableSheet, eAgentSheetFeatures, eCrash);
+ }
+
+ return mContentEditableSheet;
+}
+
+StyleSheet*
+nsLayoutStylesheetCache::DesignModeSheet()
+{
+ if (!mDesignModeSheet) {
+ LoadSheetURL("resource://gre/res/designmode.css",
+ &mDesignModeSheet, eAgentSheetFeatures, eCrash);
+ }
+
+ return mDesignModeSheet;
+}
+
+void
+nsLayoutStylesheetCache::Shutdown()
+{
+ gCSSLoader_Gecko = nullptr;
+ gCSSLoader_Servo = nullptr;
+ gStyleCache_Gecko = nullptr;
+ gStyleCache_Servo = nullptr;
+ MOZ_ASSERT(!gUserContentSheetURL, "Got the URL but never used?");
+}
+
+void
+nsLayoutStylesheetCache::SetUserContentCSSURL(nsIURI* aURI)
+{
+ MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
+ gUserContentSheetURL = aURI;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(LayoutStylesheetCacheMallocSizeOf)
+
+NS_IMETHODIMP
+nsLayoutStylesheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ MOZ_COLLECT_REPORT(
+ "explicit/layout/style-sheet-cache", KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(LayoutStylesheetCacheMallocSizeOf),
+ "Memory used for some built-in style sheets.");
+
+ return NS_OK;
+}
+
+
+size_t
+nsLayoutStylesheetCache::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ #define MEASURE(s) n += s ? s->SizeOfIncludingThis(aMallocSizeOf) : 0;
+
+ MEASURE(mChromePreferenceSheet);
+ MEASURE(mContentEditableSheet);
+ MEASURE(mContentPreferenceSheet);
+ MEASURE(mCounterStylesSheet);
+ MEASURE(mDesignModeSheet);
+ MEASURE(mFormsSheet);
+ MEASURE(mHTMLSheet);
+ MEASURE(mMathMLSheet);
+ MEASURE(mMinimalXULSheet);
+ MEASURE(mNoFramesSheet);
+ MEASURE(mNoScriptSheet);
+ MEASURE(mNumberControlSheet);
+ MEASURE(mQuirkSheet);
+ MEASURE(mSVGSheet);
+ MEASURE(mScrollbarsSheet);
+ MEASURE(mUASheet);
+ MEASURE(mUserChromeSheet);
+ MEASURE(mUserContentSheet);
+ MEASURE(mXULSheet);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - gCSSLoader_Gecko
+ // - gCSSLoader_Servo
+
+ return n;
+}
+
+nsLayoutStylesheetCache::nsLayoutStylesheetCache(StyleBackendType aType)
+ : mBackendType(aType)
+{
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::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);
+ obsSvc->AddObserver(this, "chrome-flush-skin-caches", false);
+ obsSvc->AddObserver(this, "chrome-flush-caches", false);
+ }
+
+ InitFromProfile();
+
+ // And make sure that we load our UA sheets. No need to do this
+ // per-profile, since they're profile-invariant.
+ LoadSheetURL("resource://gre-resources/counterstyles.css",
+ &mCounterStylesSheet, eAgentSheetFeatures, eCrash);
+ LoadSheetURL("chrome://global/content/minimal-xul.css",
+ &mMinimalXULSheet, eAgentSheetFeatures, eCrash);
+ LoadSheetURL("resource://gre-resources/quirk.css",
+ &mQuirkSheet, eAgentSheetFeatures, eCrash);
+ LoadSheetURL("resource://gre/res/svg.css",
+ &mSVGSheet, eAgentSheetFeatures, eCrash);
+ LoadSheetURL("chrome://global/content/xul.css",
+ &mXULSheet, eAgentSheetFeatures, eCrash);
+
+ if (gUserContentSheetURL) {
+ MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
+ LoadSheet(gUserContentSheetURL, &mUserContentSheet, eUserSheetFeatures, eLogToConsole);
+ gUserContentSheetURL = nullptr;
+ }
+
+ // The remaining sheets are created on-demand do to their use being rarer
+ // (which helps save memory for Firefox OS apps) or because they need to
+ // be re-loadable in DependentPrefChanged.
+}
+
+nsLayoutStylesheetCache::~nsLayoutStylesheetCache()
+{
+ mozilla::UnregisterWeakMemoryReporter(this);
+}
+
+void
+nsLayoutStylesheetCache::InitMemoryReporter()
+{
+ mozilla::RegisterWeakMemoryReporter(this);
+}
+
+/* static */ nsLayoutStylesheetCache*
+nsLayoutStylesheetCache::For(StyleBackendType aType)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool mustInit = !gStyleCache_Gecko && !gStyleCache_Servo;
+ auto& cache = aType == StyleBackendType::Gecko ? gStyleCache_Gecko :
+ gStyleCache_Servo;
+
+ if (!cache) {
+ cache = new nsLayoutStylesheetCache(aType);
+ cache->InitMemoryReporter();
+ }
+
+ if (mustInit) {
+ // Initialization that only needs to be done once for both
+ // nsLayoutStylesheetCaches.
+
+ Preferences::AddBoolVarCache(&sNumberControlEnabled, NUMBER_CONTROL_PREF,
+ true);
+
+ // 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");
+ Preferences::RegisterCallback(&DependentPrefChanged,
+ "layout.css.grid.enabled");
+ Preferences::RegisterCallback(&DependentPrefChanged,
+ "dom.details_element.enabled");
+ }
+
+ return cache;
+}
+
+void
+nsLayoutStylesheetCache::InitFromProfile()
+{
+ 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(NS_LITERAL_STRING("userContent.css"));
+ chromeFile->Append(NS_LITERAL_STRING("userChrome.css"));
+
+ LoadSheetFile(contentFile, &mUserContentSheet, eUserSheetFeatures, eLogToConsole);
+ LoadSheetFile(chromeFile, &mUserChromeSheet, eUserSheetFeatures, eLogToConsole);
+}
+
+void
+nsLayoutStylesheetCache::LoadSheetURL(const char* aURL,
+ RefPtr<StyleSheet>* aSheet,
+ SheetParsingMode aParsingMode,
+ FailureAction aFailureAction)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aURL);
+ LoadSheet(uri, aSheet, aParsingMode, aFailureAction);
+ if (!aSheet) {
+ NS_ERROR(nsPrintfCString("Could not load %s", aURL).get());
+ }
+}
+
+void
+nsLayoutStylesheetCache::LoadSheetFile(nsIFile* aFile,
+ RefPtr<StyleSheet>* aSheet,
+ SheetParsingMode aParsingMode,
+ FailureAction aFailureAction)
+{
+ bool exists = false;
+ aFile->Exists(&exists);
+
+ if (!exists) return;
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewFileURI(getter_AddRefs(uri), aFile);
+
+ LoadSheet(uri, aSheet, aParsingMode, aFailureAction);
+}
+
+#ifdef MOZ_CRASHREPORTER
+static inline nsresult
+ComputeCRC32(nsIFile* aFile, uint32_t* aResult)
+{
+ PRFileDesc* fd;
+ nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t crc = crc32(0, nullptr, 0);
+
+ unsigned char buf[512];
+ int32_t n;
+ while ((n = PR_Read(fd, buf, sizeof(buf))) > 0) {
+ crc = crc32(crc, buf, n);
+ }
+ PR_Close(fd);
+
+ if (n < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = crc;
+ return NS_OK;
+}
+
+static void
+ListInterestingFiles(nsString& aAnnotation, nsIFile* aFile,
+ const nsTArray<nsString>& aInterestingFilenames)
+{
+ nsString filename;
+ aFile->GetLeafName(filename);
+ for (const nsString& interestingFilename : aInterestingFilenames) {
+ if (interestingFilename == filename) {
+ nsString path;
+ aFile->GetPath(path);
+ aAnnotation.AppendLiteral(" ");
+ aAnnotation.Append(path);
+ aAnnotation.AppendLiteral(" (");
+ int64_t size;
+ if (NS_SUCCEEDED(aFile->GetFileSize(&size))) {
+ aAnnotation.AppendPrintf("%ld", size);
+ } else {
+ aAnnotation.AppendLiteral("???");
+ }
+ aAnnotation.AppendLiteral(" bytes, crc32 = ");
+ uint32_t crc;
+ nsresult rv = ComputeCRC32(aFile, &crc);
+ if (NS_SUCCEEDED(rv)) {
+ aAnnotation.AppendPrintf("0x%08x)\n", crc);
+ } else {
+ aAnnotation.AppendPrintf("error 0x%08x)\n", uint32_t(rv));
+ }
+ return;
+ }
+ }
+
+ bool isDir = false;
+ aFile->IsDirectory(&isDir);
+
+ if (!isDir) {
+ return;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ if (NS_FAILED(aFile->GetDirectoryEntries(getter_AddRefs(entries)))) {
+ aAnnotation.AppendLiteral(" (failed to enumerated directory)\n");
+ return;
+ }
+
+ for (;;) {
+ bool hasMore = false;
+ if (NS_FAILED(entries->HasMoreElements(&hasMore))) {
+ aAnnotation.AppendLiteral(" (failed during directory enumeration)\n");
+ return;
+ }
+ if (!hasMore) {
+ break;
+ }
+
+ nsCOMPtr<nsISupports> entry;
+ if (NS_FAILED(entries->GetNext(getter_AddRefs(entry)))) {
+ aAnnotation.AppendLiteral(" (failed during directory enumeration)\n");
+ return;
+ }
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ if (file) {
+ ListInterestingFiles(aAnnotation, file, aInterestingFilenames);
+ }
+ }
+}
+
+// Generate a crash report annotation to help debug issues with style
+// sheets failing to load (bug 1194856).
+static void
+AnnotateCrashReport(nsIURI* aURI)
+{
+ nsAutoCString spec;
+ nsAutoCString scheme;
+ nsDependentCSubstring filename;
+ if (aURI) {
+ spec = aURI->GetSpecOrDefault();
+ aURI->GetScheme(scheme);
+ int32_t i = spec.RFindChar('/');
+ if (i != -1) {
+ filename.Rebind(spec, i + 1);
+ }
+ }
+
+ nsString annotation;
+
+ // The URL of the sheet that failed to load.
+ annotation.AppendLiteral("Error loading sheet: ");
+ annotation.Append(NS_ConvertUTF8toUTF16(spec).get());
+ annotation.Append('\n');
+
+ annotation.AppendLiteral("NS_ERROR_FILE_CORRUPTION reason: ");
+ if (nsZipArchive::sFileCorruptedReason) {
+ annotation.Append(NS_ConvertUTF8toUTF16(nsZipArchive::sFileCorruptedReason).get());
+ annotation.Append('\n');
+ } else {
+ annotation.AppendLiteral("(none)\n");
+ }
+
+ // The jar: or file: URL that the sheet's resource: or chrome: URL
+ // resolves to.
+ if (scheme.EqualsLiteral("resource")) {
+ annotation.AppendLiteral("Real location: ");
+ nsCOMPtr<nsISubstitutingProtocolHandler> handler;
+ nsCOMPtr<nsIIOService> io(do_GetIOService());
+ if (io) {
+ nsCOMPtr<nsIProtocolHandler> ph;
+ io->GetProtocolHandler(scheme.get(), getter_AddRefs(ph));
+ if (ph) {
+ handler = do_QueryInterface(ph);
+ }
+ }
+ if (!handler) {
+ annotation.AppendLiteral("(ResolveURI failed)\n");
+ } else {
+ nsAutoCString resolvedSpec;
+ handler->ResolveURI(aURI, resolvedSpec);
+ annotation.Append(NS_ConvertUTF8toUTF16(resolvedSpec));
+ annotation.Append('\n');
+ }
+ } else if (scheme.EqualsLiteral("chrome")) {
+ annotation.AppendLiteral("Real location: ");
+ nsCOMPtr<nsIChromeRegistry> reg =
+ mozilla::services::GetChromeRegistryService();
+ if (!reg) {
+ annotation.AppendLiteral("(no chrome registry)\n");
+ } else {
+ nsCOMPtr<nsIURI> resolvedURI;
+ reg->ConvertChromeURL(aURI, getter_AddRefs(resolvedURI));
+ if (!resolvedURI) {
+ annotation.AppendLiteral("(ConvertChromeURL failed)\n");
+ } else {
+ annotation.Append(
+ NS_ConvertUTF8toUTF16(resolvedURI->GetSpecOrDefault()));
+ annotation.Append('\n');
+ }
+ }
+ }
+
+ nsTArray<nsString> interestingFiles;
+ interestingFiles.AppendElement(NS_LITERAL_STRING("chrome.manifest"));
+ interestingFiles.AppendElement(NS_LITERAL_STRING("omni.ja"));
+ interestingFiles.AppendElement(NS_ConvertUTF8toUTF16(filename));
+
+ annotation.AppendLiteral("GRE directory: ");
+ nsCOMPtr<nsIFile> file;
+ nsDirectoryService::gService->Get(NS_GRE_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(file));
+ if (file) {
+ // The Firefox installation directory.
+ nsString path;
+ file->GetPath(path);
+ annotation.Append(path);
+ annotation.Append('\n');
+
+ // List interesting files -- any chrome.manifest or omni.ja file or any file
+ // whose name is the sheet's filename -- under the Firefox installation
+ // directory.
+ annotation.AppendLiteral("Interesting files in the GRE directory:\n");
+ ListInterestingFiles(annotation, file, interestingFiles);
+
+ // If the Firefox installation directory has a chrome.manifest file, let's
+ // see what's in it.
+ file->Append(NS_LITERAL_STRING("chrome.manifest"));
+ bool exists = false;
+ file->Exists(&exists);
+ if (exists) {
+ annotation.AppendLiteral("Contents of chrome.manifest:\n[[[\n");
+ PRFileDesc* fd;
+ if (NS_SUCCEEDED(file->OpenNSPRFileDesc(PR_RDONLY, 0, &fd))) {
+ nsCString contents;
+ char buf[512];
+ int32_t n;
+ while ((n = PR_Read(fd, buf, sizeof(buf))) > 0) {
+ contents.Append(buf, n);
+ }
+ if (n < 0) {
+ annotation.AppendLiteral(" (error while reading)\n");
+ } else {
+ annotation.Append(NS_ConvertUTF8toUTF16(contents));
+ }
+ PR_Close(fd);
+ }
+ annotation.AppendLiteral("]]]\n");
+ }
+ } else {
+ annotation.AppendLiteral("(none)\n");
+ }
+
+ // The jar: or file: URL prefix that chrome: and resource: URLs get translated
+ // to.
+ annotation.AppendLiteral("GRE omnijar URI string: ");
+ nsCString uri;
+ nsresult rv = Omnijar::GetURIString(Omnijar::GRE, uri);
+ if (NS_FAILED(rv)) {
+ annotation.AppendLiteral("(failed)\n");
+ } else {
+ annotation.Append(NS_ConvertUTF8toUTF16(uri));
+ annotation.Append('\n');
+ }
+
+ RefPtr<nsZipArchive> zip = Omnijar::GetReader(Omnijar::GRE);
+ if (zip) {
+ // List interesting files in the GRE omnijar.
+ annotation.AppendLiteral("Interesting files in the GRE omnijar:\n");
+ nsZipFind* find;
+ rv = zip->FindInit(nullptr, &find);
+ if (NS_FAILED(rv)) {
+ annotation.AppendPrintf(" (FindInit failed with 0x%08x)\n", rv);
+ } else if (!find) {
+ annotation.AppendLiteral(" (FindInit returned null)\n");
+ } else {
+ const char* result;
+ uint16_t len;
+ while (NS_SUCCEEDED(find->FindNext(&result, &len))) {
+ nsCString itemPathname;
+ nsString itemFilename;
+ itemPathname.Append(result, len);
+ int32_t i = itemPathname.RFindChar('/');
+ if (i != -1) {
+ itemFilename = NS_ConvertUTF8toUTF16(Substring(itemPathname, i + 1));
+ }
+ for (const nsString& interestingFile : interestingFiles) {
+ if (interestingFile == itemFilename) {
+ annotation.AppendLiteral(" ");
+ annotation.Append(NS_ConvertUTF8toUTF16(itemPathname));
+ nsZipItem* item = zip->GetItem(itemPathname.get());
+ if (!item) {
+ annotation.AppendLiteral(" (GetItem failed)\n");
+ } else {
+ annotation.AppendPrintf(" (%d bytes, crc32 = 0x%08x)\n",
+ item->RealSize(),
+ item->CRC32());
+ }
+ break;
+ }
+ }
+ }
+ delete find;
+ }
+ } else {
+ annotation.AppendLiteral("No GRE omnijar\n");
+ }
+
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("SheetLoadFailure"),
+ NS_ConvertUTF16toUTF8(annotation));
+}
+#endif
+
+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;
+ }
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ AnnotateCrashReport(aURI);
+#endif
+ NS_RUNTIMEABORT(errorMessage.get());
+}
+
+void
+nsLayoutStylesheetCache::LoadSheet(nsIURI* aURI,
+ RefPtr<StyleSheet>* aSheet,
+ SheetParsingMode aParsingMode,
+ FailureAction aFailureAction)
+{
+ if (!aURI) {
+ ErrorLoadingSheet(aURI, "null URI", eCrash);
+ return;
+ }
+
+ auto& loader = mBackendType == StyleBackendType::Gecko ?
+ gCSSLoader_Gecko :
+ gCSSLoader_Servo;
+
+ if (!loader) {
+ loader = new mozilla::css::Loader(mBackendType);
+ if (!loader) {
+ ErrorLoadingSheet(aURI, "no Loader", eCrash);
+ return;
+ }
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ nsZipArchive::sFileCorruptedReason = nullptr;
+#endif
+ nsresult rv = loader->LoadSheetSync(aURI, aParsingMode, true, aSheet);
+ if (NS_FAILED(rv)) {
+ ErrorLoadingSheet(aURI,
+ nsPrintfCString("LoadSheetSync failed with error %x", rv).get(),
+ aFailureAction);
+ }
+}
+
+/* static */ void
+nsLayoutStylesheetCache::InvalidateSheet(RefPtr<StyleSheet>* aGeckoSheet,
+ RefPtr<StyleSheet>* aServoSheet)
+{
+ MOZ_ASSERT(gCSSLoader_Gecko || gCSSLoader_Servo,
+ "pref changed before we loaded a sheet?");
+
+ const bool gotGeckoSheet = aGeckoSheet && *aGeckoSheet;
+ const bool gotServoSheet = aServoSheet && *aServoSheet;
+
+ // Make sure sheets have the expected types
+ MOZ_ASSERT(!gotGeckoSheet || (*aGeckoSheet)->IsGecko());
+ MOZ_ASSERT(!gotServoSheet || (*aServoSheet)->IsServo());
+ // Make sure the URIs match
+ MOZ_ASSERT(!gotServoSheet || !gotGeckoSheet ||
+ (*aGeckoSheet)->GetSheetURI() == (*aServoSheet)->GetSheetURI(),
+ "Sheets passed should have the same URI");
+
+ nsIURI* uri;
+ if (gotGeckoSheet) {
+ uri = (*aGeckoSheet)->GetSheetURI();
+ } else if (gotServoSheet) {
+ uri = (*aServoSheet)->GetSheetURI();
+ } else {
+ return;
+ }
+
+ if (gCSSLoader_Gecko) {
+ gCSSLoader_Gecko->ObsoleteSheet(uri);
+ }
+ if (gCSSLoader_Servo) {
+ gCSSLoader_Servo->ObsoleteSheet(uri);
+ }
+ if (gotGeckoSheet) {
+ *aGeckoSheet = nullptr;
+ }
+ if (gotServoSheet) {
+ *aServoSheet = nullptr;
+ }
+}
+
+/* static */ void
+nsLayoutStylesheetCache::DependentPrefChanged(const char* aPref, void* aData)
+{
+ MOZ_ASSERT(gStyleCache_Gecko || gStyleCache_Servo,
+ "pref changed after shutdown?");
+
+ // Cause any UA style sheets whose parsing depends on the value of prefs
+ // to be re-parsed by dropping the sheet from gCSSLoader_{Gecko,Servo}'s cache
+ // then setting our cached sheet pointer to null. This will only work for
+ // sheets that are loaded lazily.
+
+#define INVALIDATE(sheet_) \
+ InvalidateSheet(gStyleCache_Gecko ? &gStyleCache_Gecko->sheet_ : nullptr, \
+ gStyleCache_Servo ? &gStyleCache_Servo->sheet_ : nullptr);
+
+ INVALIDATE(mUASheet); // for layout.css.grid.enabled
+ INVALIDATE(mHTMLSheet); // for dom.details_element.enabled
+
+#undef INVALIDATE
+}
+
+/* static */ void
+nsLayoutStylesheetCache::InvalidatePreferenceSheets()
+{
+ if (gStyleCache_Gecko) {
+ gStyleCache_Gecko->mContentPreferenceSheet = nullptr;
+ gStyleCache_Gecko->mChromePreferenceSheet = nullptr;
+ }
+ if (gStyleCache_Servo) {
+ gStyleCache_Servo->mContentPreferenceSheet = nullptr;
+ gStyleCache_Servo->mChromePreferenceSheet = nullptr;
+ }
+}
+
+void
+nsLayoutStylesheetCache::BuildPreferenceSheet(RefPtr<StyleSheet>* aSheet,
+ nsPresContext* aPresContext)
+{
+ if (mBackendType == StyleBackendType::Gecko) {
+ *aSheet = new CSSStyleSheet(eAgentSheetFeatures, CORS_NONE,
+ mozilla::net::RP_Default);
+ } else {
+ *aSheet = new ServoStyleSheet(eAgentSheetFeatures, CORS_NONE,
+ mozilla::net::RP_Default, dom::SRIMetadata());
+ }
+
+ StyleSheet* sheet = *aSheet;
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:PreferenceStyleSheet", nullptr);
+ MOZ_ASSERT(uri, "URI creation shouldn't fail");
+
+ sheet->SetURIs(uri, uri, uri);
+ sheet->SetComplete();
+
+ static const uint32_t kPreallocSize = 1024;
+
+ nsString sheetText;
+ sheetText.SetCapacity(kPreallocSize);
+
+#define NS_GET_R_G_B(color_) \
+ NS_GET_R(color_), NS_GET_G(color_), NS_GET_B(color_)
+
+ sheetText.AppendLiteral(
+ "@namespace url(http://www.w3.org/1999/xhtml);\n"
+ "@namespace svg url(http://www.w3.org/2000/svg);\n");
+
+ // Rules for link styling.
+ nscolor linkColor = aPresContext->DefaultLinkColor();
+ nscolor activeColor = aPresContext->DefaultActiveLinkColor();
+ nscolor visitedColor = aPresContext->DefaultVisitedLinkColor();
+
+ sheetText.AppendPrintf(
+ "*|*:link { color: #%02x%02x%02x; }\n"
+ "*|*:any-link:active { color: #%02x%02x%02x; }\n"
+ "*|*:visited { color: #%02x%02x%02x; }\n",
+ NS_GET_R_G_B(linkColor),
+ NS_GET_R_G_B(activeColor),
+ NS_GET_R_G_B(visitedColor));
+
+ bool underlineLinks =
+ aPresContext->GetCachedBoolPref(kPresContext_UnderlineLinks);
+ sheetText.AppendPrintf(
+ "*|*:any-link%s { text-decoration: %s; }\n",
+ underlineLinks ? ":not(svg|a)" : "",
+ underlineLinks ? "underline" : "none");
+
+ // Rules for focus styling.
+
+ bool focusRingOnAnything = aPresContext->GetFocusRingOnAnything();
+ uint8_t focusRingWidth = aPresContext->FocusRingWidth();
+ uint8_t focusRingStyle = aPresContext->GetFocusRingStyle();
+
+ if ((focusRingWidth != 1 && focusRingWidth <= 4) || focusRingOnAnything) {
+ if (focusRingWidth != 1) {
+ // If the focus ring width is different from the default, fix buttons
+ // with rings.
+ sheetText.AppendPrintf(
+ "button::-moz-focus-inner, input[type=\"reset\"]::-moz-focus-inner, "
+ "input[type=\"button\"]::-moz-focus-inner, "
+ "input[type=\"submit\"]::-moz-focus-inner { "
+ "padding: 1px 2px 1px 2px; "
+ "border: %dpx %s transparent !important; }\n",
+ focusRingWidth,
+ focusRingStyle == 0 ? "solid" : "dotted");
+
+ sheetText.AppendLiteral(
+ "button:focus::-moz-focus-inner, "
+ "input[type=\"reset\"]:focus::-moz-focus-inner, "
+ "input[type=\"button\"]:focus::-moz-focus-inner, "
+ "input[type=\"submit\"]:focus::-moz-focus-inner { "
+ "border-color: ButtonText !important; }\n");
+ }
+
+ sheetText.AppendPrintf(
+ "%s { outline: %dpx %s !important; %s}\n",
+ focusRingOnAnything ?
+ ":focus" :
+ "*|*:link:focus, *|*:visited:focus",
+ focusRingWidth,
+ focusRingStyle == 0 ? // solid
+ "solid -moz-mac-focusring" : "dotted WindowText",
+ focusRingStyle == 0 ? // solid
+ "-moz-outline-radius: 3px; outline-offset: 1px; " : "");
+ }
+
+ if (aPresContext->GetUseFocusColors()) {
+ nscolor focusText = aPresContext->FocusTextColor();
+ nscolor focusBG = aPresContext->FocusBackgroundColor();
+ sheetText.AppendPrintf(
+ "*:focus, *:focus > font { color: #%02x%02x%02x !important; "
+ "background-color: #%02x%02x%02x !important; }\n",
+ NS_GET_R_G_B(focusText),
+ NS_GET_R_G_B(focusBG));
+ }
+
+ NS_ASSERTION(sheetText.Length() <= kPreallocSize,
+ "kPreallocSize should be big enough to build preference style "
+ "sheet without reallocation");
+
+ if (sheet->IsGecko()) {
+ sheet->AsGecko()->ReparseSheet(sheetText);
+ } else {
+ nsresult rv = sheet->AsServo()->ParseSheet(sheetText, uri, uri, nullptr, 0);
+ // Parsing the about:PreferenceStyleSheet URI can only fail on OOM. If we
+ // are OOM before we parsed any documents we might as well abort.
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+#undef NS_GET_R_G_B
+}
+
+mozilla::StaticRefPtr<nsLayoutStylesheetCache>
+nsLayoutStylesheetCache::gStyleCache_Gecko;
+
+mozilla::StaticRefPtr<nsLayoutStylesheetCache>
+nsLayoutStylesheetCache::gStyleCache_Servo;
+
+mozilla::StaticRefPtr<mozilla::css::Loader>
+nsLayoutStylesheetCache::gCSSLoader_Gecko;
+
+mozilla::StaticRefPtr<mozilla::css::Loader>
+nsLayoutStylesheetCache::gCSSLoader_Servo;
+
+mozilla::StaticRefPtr<nsIURI>
+nsLayoutStylesheetCache::gUserContentSheetURL;
diff --git a/layout/style/nsLayoutStylesheetCache.h b/layout/style/nsLayoutStylesheetCache.h
new file mode 100644
index 000000000..48391cc7a
--- /dev/null
+++ b/layout/style/nsLayoutStylesheetCache.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/. */
+
+#ifndef nsLayoutStylesheetCache_h__
+#define nsLayoutStylesheetCache_h__
+
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/StyleBackendType.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
+};
+
+}
+}
+
+class nsLayoutStylesheetCache final
+ : public nsIObserver
+ , public nsIMemoryReporter
+{
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMEMORYREPORTER
+
+ /**
+ * Returns the nsLayoutStylesheetCache for the given style backend type.
+ * Callers should pass in a value for aType that matches the style system
+ * backend type for the style set in use. (A process may call For
+ * and obtain nsLayoutStylesheetCache objects for both backend types,
+ * and a particular UA style sheet might be cached in both, one or neither
+ * nsLayoutStylesheetCache.)
+ */
+ static nsLayoutStylesheetCache* For(mozilla::StyleBackendType aType);
+
+ mozilla::StyleSheet* ScrollbarsSheet();
+ mozilla::StyleSheet* FormsSheet();
+ // This function is expected to return nullptr when the dom.forms.number
+ // pref is disabled.
+ mozilla::StyleSheet* NumberControlSheet();
+ mozilla::StyleSheet* UserContentSheet();
+ mozilla::StyleSheet* UserChromeSheet();
+ mozilla::StyleSheet* UASheet();
+ mozilla::StyleSheet* HTMLSheet();
+ mozilla::StyleSheet* MinimalXULSheet();
+ mozilla::StyleSheet* XULSheet();
+ mozilla::StyleSheet* QuirkSheet();
+ mozilla::StyleSheet* SVGSheet();
+ mozilla::StyleSheet* MathMLSheet();
+ mozilla::StyleSheet* CounterStylesSheet();
+ mozilla::StyleSheet* NoScriptSheet();
+ mozilla::StyleSheet* NoFramesSheet();
+ mozilla::StyleSheet* ChromePreferenceSheet(nsPresContext* aPresContext);
+ mozilla::StyleSheet* ContentPreferenceSheet(nsPresContext* aPresContext);
+ mozilla::StyleSheet* ContentEditableSheet();
+ mozilla::StyleSheet* DesignModeSheet();
+
+ static void InvalidatePreferenceSheets();
+
+ static void Shutdown();
+
+ static void SetUserContentCSSURL(nsIURI* aURI);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ explicit nsLayoutStylesheetCache(mozilla::StyleBackendType aImpl);
+ ~nsLayoutStylesheetCache();
+
+ void InitFromProfile();
+ void InitMemoryReporter();
+ void LoadSheetURL(const char* aURL,
+ RefPtr<mozilla::StyleSheet>* aSheet,
+ mozilla::css::SheetParsingMode aParsingMode,
+ mozilla::css::FailureAction aFailureAction);
+ void LoadSheetFile(nsIFile* aFile,
+ RefPtr<mozilla::StyleSheet>* aSheet,
+ mozilla::css::SheetParsingMode aParsingMode,
+ mozilla::css::FailureAction aFailureAction);
+ void LoadSheet(nsIURI* aURI, RefPtr<mozilla::StyleSheet>* aSheet,
+ mozilla::css::SheetParsingMode aParsingMode,
+ mozilla::css::FailureAction aFailureAction);
+ static void InvalidateSheet(RefPtr<mozilla::StyleSheet>* aGeckoSheet,
+ RefPtr<mozilla::StyleSheet>* aServoSheet);
+ static void DependentPrefChanged(const char* aPref, void* aData);
+ void BuildPreferenceSheet(RefPtr<mozilla::StyleSheet>* aSheet,
+ nsPresContext* aPresContext);
+
+ static mozilla::StaticRefPtr<nsLayoutStylesheetCache> gStyleCache_Gecko;
+ static mozilla::StaticRefPtr<nsLayoutStylesheetCache> gStyleCache_Servo;
+ static mozilla::StaticRefPtr<mozilla::css::Loader> gCSSLoader_Gecko;
+ static mozilla::StaticRefPtr<mozilla::css::Loader> gCSSLoader_Servo;
+ static mozilla::StaticRefPtr<nsIURI> gUserContentSheetURL;
+ mozilla::StyleBackendType mBackendType;
+ RefPtr<mozilla::StyleSheet> mChromePreferenceSheet;
+ RefPtr<mozilla::StyleSheet> mContentEditableSheet;
+ RefPtr<mozilla::StyleSheet> mContentPreferenceSheet;
+ RefPtr<mozilla::StyleSheet> mCounterStylesSheet;
+ RefPtr<mozilla::StyleSheet> mDesignModeSheet;
+ RefPtr<mozilla::StyleSheet> mFormsSheet;
+ RefPtr<mozilla::StyleSheet> mHTMLSheet;
+ RefPtr<mozilla::StyleSheet> mMathMLSheet;
+ RefPtr<mozilla::StyleSheet> mMinimalXULSheet;
+ RefPtr<mozilla::StyleSheet> mNoFramesSheet;
+ RefPtr<mozilla::StyleSheet> mNoScriptSheet;
+ RefPtr<mozilla::StyleSheet> mNumberControlSheet;
+ RefPtr<mozilla::StyleSheet> mQuirkSheet;
+ RefPtr<mozilla::StyleSheet> mSVGSheet;
+ RefPtr<mozilla::StyleSheet> mScrollbarsSheet;
+ RefPtr<mozilla::StyleSheet> mUASheet;
+ RefPtr<mozilla::StyleSheet> mUserChromeSheet;
+ RefPtr<mozilla::StyleSheet> mUserContentSheet;
+ RefPtr<mozilla::StyleSheet> mXULSheet;
+};
+
+#endif
diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp
new file mode 100644
index 000000000..c07a4123e
--- /dev/null
+++ b/layout/style/nsMediaFeatures.cpp
@@ -0,0 +1,813 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsMediaFeatures.h"
+#include "nsGkAtoms.h"
+#include "nsCSSKeywords.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsCSSValue.h"
+#ifdef XP_WIN
+#include "mozilla/LookAndFeel.h"
+#endif
+#include "nsCSSRuleProcessor.h"
+#include "nsDeviceContext.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+using namespace mozilla;
+
+static const nsCSSProps::KTableEntry kOrientationKeywords[] = {
+ { eCSSKeyword_portrait, NS_STYLE_ORIENTATION_PORTRAIT },
+ { eCSSKeyword_landscape, NS_STYLE_ORIENTATION_LANDSCAPE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+static const nsCSSProps::KTableEntry kScanKeywords[] = {
+ { eCSSKeyword_progressive, NS_STYLE_SCAN_PROGRESSIVE },
+ { eCSSKeyword_interlace, NS_STYLE_SCAN_INTERLACE },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+static const nsCSSProps::KTableEntry kDisplayModeKeywords[] = {
+ { eCSSKeyword_browser, NS_STYLE_DISPLAY_MODE_BROWSER },
+ { eCSSKeyword_minimal_ui, NS_STYLE_DISPLAY_MODE_MINIMAL_UI },
+ { eCSSKeyword_standalone, NS_STYLE_DISPLAY_MODE_STANDALONE },
+ { eCSSKeyword_fullscreen, NS_STYLE_DISPLAY_MODE_FULLSCREEN },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
+#ifdef XP_WIN
+struct WindowsThemeName {
+ LookAndFeel::WindowsTheme id;
+ const wchar_t* name;
+};
+
+// Windows theme identities used in the -moz-windows-theme media query.
+const WindowsThemeName themeStrings[] = {
+ { LookAndFeel::eWindowsTheme_Aero, L"aero" },
+ { LookAndFeel::eWindowsTheme_AeroLite, L"aero-lite" },
+ { LookAndFeel::eWindowsTheme_LunaBlue, L"luna-blue" },
+ { LookAndFeel::eWindowsTheme_LunaOlive, L"luna-olive" },
+ { LookAndFeel::eWindowsTheme_LunaSilver, L"luna-silver" },
+ { LookAndFeel::eWindowsTheme_Royale, L"royale" },
+ { LookAndFeel::eWindowsTheme_Zune, L"zune" },
+ { LookAndFeel::eWindowsTheme_Generic, L"generic" }
+};
+
+struct OperatingSystemVersionInfo {
+ LookAndFeel::OperatingSystemVersion id;
+ const wchar_t* name;
+};
+
+// Os version identities used in the -moz-os-version media query.
+const OperatingSystemVersionInfo osVersionStrings[] = {
+ { LookAndFeel::eOperatingSystemVersion_WindowsXP, L"windows-xp" },
+ { LookAndFeel::eOperatingSystemVersion_WindowsVista, L"windows-vista" },
+ { LookAndFeel::eOperatingSystemVersion_Windows7, L"windows-win7" },
+ { LookAndFeel::eOperatingSystemVersion_Windows8, L"windows-win8" },
+ { LookAndFeel::eOperatingSystemVersion_Windows10, L"windows-win10" }
+};
+#endif
+
+// A helper for four features below
+static nsSize
+GetSize(nsPresContext* aPresContext)
+{
+ nsSize size;
+ if (aPresContext->IsRootPaginatedDocument())
+ // We want the page size, including unprintable areas and margins.
+ size = aPresContext->GetPageSize();
+ else
+ size = aPresContext->GetVisibleArea().Size();
+ return size;
+}
+
+static nsresult
+GetWidth(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ nsSize size = GetSize(aPresContext);
+ float pixelWidth = aPresContext->AppUnitsToFloatCSSPixels(size.width);
+ aResult.SetFloatValue(pixelWidth, eCSSUnit_Pixel);
+ return NS_OK;
+}
+
+static nsresult
+GetHeight(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ nsSize size = GetSize(aPresContext);
+ float pixelHeight = aPresContext->AppUnitsToFloatCSSPixels(size.height);
+ aResult.SetFloatValue(pixelHeight, eCSSUnit_Pixel);
+ return NS_OK;
+}
+
+inline static nsDeviceContext*
+GetDeviceContextFor(nsPresContext* aPresContext)
+{
+ // 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 aPresContext->DeviceContext();
+}
+
+static bool
+ShouldResistFingerprinting(nsPresContext* aPresContext)
+{
+ return nsContentUtils::ShouldResistFingerprinting(aPresContext->GetDocShell());
+}
+
+// A helper for three features below.
+static nsSize
+GetDeviceSize(nsPresContext* aPresContext)
+{
+ nsSize size;
+
+ if (ShouldResistFingerprinting(aPresContext) || aPresContext->IsDeviceSizePageSize()) {
+ size = GetSize(aPresContext);
+ } else if (aPresContext->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?
+ size = aPresContext->GetPageSize();
+ } else {
+ GetDeviceContextFor(aPresContext)->
+ GetDeviceSurfaceDimensions(size.width, size.height);
+ }
+ return size;
+}
+
+static nsresult
+GetDeviceWidth(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ nsSize size = GetDeviceSize(aPresContext);
+ float pixelWidth = aPresContext->AppUnitsToFloatCSSPixels(size.width);
+ aResult.SetFloatValue(pixelWidth, eCSSUnit_Pixel);
+ return NS_OK;
+}
+
+static nsresult
+GetDeviceHeight(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ nsSize size = GetDeviceSize(aPresContext);
+ float pixelHeight = aPresContext->AppUnitsToFloatCSSPixels(size.height);
+ aResult.SetFloatValue(pixelHeight, eCSSUnit_Pixel);
+ return NS_OK;
+}
+
+static nsresult
+GetOrientation(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ nsSize size = GetSize(aPresContext);
+ int32_t orientation;
+ if (size.width > size.height) {
+ orientation = NS_STYLE_ORIENTATION_LANDSCAPE;
+ } else {
+ // Per spec, square viewports should be 'portrait'
+ orientation = NS_STYLE_ORIENTATION_PORTRAIT;
+ }
+
+ aResult.SetIntValue(orientation, eCSSUnit_Enumerated);
+ return NS_OK;
+}
+
+static nsresult
+GetDeviceOrientation(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ nsSize size = GetDeviceSize(aPresContext);
+ int32_t orientation;
+ if (size.width > size.height) {
+ orientation = NS_STYLE_ORIENTATION_LANDSCAPE;
+ } else {
+ // Per spec, square viewports should be 'portrait'
+ orientation = NS_STYLE_ORIENTATION_PORTRAIT;
+ }
+
+ aResult.SetIntValue(orientation, eCSSUnit_Enumerated);
+ return NS_OK;
+}
+
+static nsresult
+GetIsResourceDocument(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ nsIDocument* doc = aPresContext->Document();
+ aResult.SetIntValue(doc && doc->IsResourceDoc() ? 1 : 0, eCSSUnit_Integer);
+ return NS_OK;
+}
+
+// Helper for two features below
+static nsresult
+MakeArray(const nsSize& aSize, nsCSSValue& aResult)
+{
+ RefPtr<nsCSSValue::Array> a = nsCSSValue::Array::Create(2);
+
+ a->Item(0).SetIntValue(aSize.width, eCSSUnit_Integer);
+ a->Item(1).SetIntValue(aSize.height, eCSSUnit_Integer);
+
+ aResult.SetArrayValue(a, eCSSUnit_Array);
+ return NS_OK;
+}
+
+static nsresult
+GetAspectRatio(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ return MakeArray(GetSize(aPresContext), aResult);
+}
+
+static nsresult
+GetDeviceAspectRatio(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ return MakeArray(GetDeviceSize(aPresContext), aResult);
+}
+
+static nsresult
+GetColor(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ uint32_t depth = 24; // Use depth of 24 when resisting fingerprinting.
+
+ if (!ShouldResistFingerprinting(aPresContext)) {
+ // FIXME: This implementation is bogus. nsDeviceContext
+ // doesn't provide reliable information (should be fixed in bug
+ // 424386).
+ // FIXME: On a monochrome device, return 0!
+ nsDeviceContext *dx = GetDeviceContextFor(aPresContext);
+ dx->GetDepth(depth);
+ }
+
+ // 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.
+ depth /= 3;
+ aResult.SetIntValue(int32_t(depth), eCSSUnit_Integer);
+ return NS_OK;
+}
+
+static nsresult
+GetColorIndex(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ // We should return zero if the device does not use a color lookup
+ // table. Stuart says that our handling of displays with 8-bit
+ // color is bad enough that we never change the lookup table to
+ // match what we're trying to display, so perhaps we should always
+ // return zero. Given that there isn't any better information
+ // exposed, we don't have much other choice.
+ aResult.SetIntValue(0, eCSSUnit_Integer);
+ return NS_OK;
+}
+
+static nsresult
+GetMonochrome(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ // For color devices we should return 0.
+ // FIXME: On a monochrome device, return the actual color depth, not
+ // 0!
+ aResult.SetIntValue(0, eCSSUnit_Integer);
+ return NS_OK;
+}
+
+static nsresult
+GetResolution(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ float dpi = 96; // Use 96 when resisting fingerprinting.
+
+ if (!ShouldResistFingerprinting(aPresContext)) {
+ // Resolution measures device pixels per CSS (inch/cm/pixel). We
+ // return it in device pixels per CSS inches.
+ dpi = float(nsPresContext::AppUnitsPerCSSInch()) /
+ float(aPresContext->AppUnitsPerDevPixel());
+ }
+
+ aResult.SetFloatValue(dpi, eCSSUnit_Inch);
+ return NS_OK;
+}
+
+static nsresult
+GetScan(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ // Since Gecko doesn't support the 'tv' media type, the 'scan'
+ // feature is never present.
+ aResult.Reset();
+ return NS_OK;
+}
+
+static nsresult
+GetDisplayMode(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ nsCOMPtr<nsISupports> container;
+ if (aPresContext) {
+ // Calling GetRootPresContext() can be slow, so make sure to call it
+ // just once.
+ nsRootPresContext* root = aPresContext->GetRootPresContext();
+ if (root && root->Document()) {
+ container = root->Document()->GetContainer();
+ }
+ }
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
+ if (!baseWindow) {
+ aResult.SetIntValue(NS_STYLE_DISPLAY_MODE_BROWSER, eCSSUnit_Enumerated);
+ return NS_OK;
+ }
+ nsCOMPtr<nsIWidget> mainWidget;
+ baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+ int32_t displayMode;
+ nsSizeMode mode = mainWidget ? mainWidget->SizeMode() : nsSizeMode_Normal;
+ // Background tabs are always in 'browser' mode for now.
+ // If new modes are supported, please ensure not cause the regression in
+ // Bug 1259641.
+ switch (mode) {
+ case nsSizeMode_Fullscreen:
+ displayMode = NS_STYLE_DISPLAY_MODE_FULLSCREEN;
+ break;
+ default:
+ displayMode = NS_STYLE_DISPLAY_MODE_BROWSER;
+ break;
+ }
+
+ aResult.SetIntValue(displayMode, eCSSUnit_Enumerated);
+ return NS_OK;
+}
+
+static nsresult
+GetGrid(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ // Gecko doesn't support grid devices (e.g., ttys), so the 'grid'
+ // feature is always 0.
+ aResult.SetIntValue(0, eCSSUnit_Integer);
+ return NS_OK;
+}
+
+static nsresult
+GetDevicePixelRatio(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ if (!ShouldResistFingerprinting(aPresContext)) {
+ float ratio = aPresContext->CSSPixelsToDevPixels(1.0f);
+ aResult.SetFloatValue(ratio, eCSSUnit_Number);
+ } else {
+ aResult.SetFloatValue(1.0, eCSSUnit_Number);
+ }
+ return NS_OK;
+}
+
+static nsresult
+GetTransform3d(nsPresContext* aPresContext, const nsMediaFeature*,
+ nsCSSValue& aResult)
+{
+ // Gecko supports 3d transforms, so this feature is always 1.
+ aResult.SetIntValue(1, eCSSUnit_Integer);
+ return NS_OK;
+}
+
+static nsresult
+GetSystemMetric(nsPresContext* aPresContext, const nsMediaFeature* aFeature,
+ nsCSSValue& aResult)
+{
+ aResult.Reset();
+ if (ShouldResistFingerprinting(aPresContext)) {
+ // If "privacy.resistFingerprinting" is enabled, then we simply don't
+ // return any system-backed media feature values. (No spoofed values returned.)
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aFeature->mValueType == nsMediaFeature::eBoolInteger,
+ "unexpected type");
+ nsIAtom *metricAtom = *aFeature->mData.mMetric;
+ bool hasMetric = nsCSSRuleProcessor::HasSystemMetric(metricAtom);
+ aResult.SetIntValue(hasMetric ? 1 : 0, eCSSUnit_Integer);
+ return NS_OK;
+}
+
+static nsresult
+GetWindowsTheme(nsPresContext* aPresContext, const nsMediaFeature* aFeature,
+ nsCSSValue& aResult)
+{
+ aResult.Reset();
+ if (ShouldResistFingerprinting(aPresContext)) {
+ return NS_OK;
+ }
+
+#ifdef XP_WIN
+ uint8_t windowsThemeId =
+ nsCSSRuleProcessor::GetWindowsThemeIdentifier();
+
+ // Classic mode should fail to match.
+ if (windowsThemeId == LookAndFeel::eWindowsTheme_Classic)
+ return NS_OK;
+
+ // Look up the appropriate theme string
+ for (size_t i = 0; i < ArrayLength(themeStrings); ++i) {
+ if (windowsThemeId == themeStrings[i].id) {
+ aResult.SetStringValue(nsDependentString(themeStrings[i].name),
+ eCSSUnit_Ident);
+ break;
+ }
+ }
+#endif
+ return NS_OK;
+}
+
+static nsresult
+GetOperatingSystemVersion(nsPresContext* aPresContext, const nsMediaFeature* aFeature,
+ nsCSSValue& aResult)
+{
+ aResult.Reset();
+ if (ShouldResistFingerprinting(aPresContext)) {
+ return NS_OK;
+ }
+
+#ifdef XP_WIN
+ int32_t metricResult;
+ if (NS_SUCCEEDED(
+ LookAndFeel::GetInt(LookAndFeel::eIntID_OperatingSystemVersionIdentifier,
+ &metricResult))) {
+ for (size_t i = 0; i < ArrayLength(osVersionStrings); ++i) {
+ if (metricResult == osVersionStrings[i].id) {
+ aResult.SetStringValue(nsDependentString(osVersionStrings[i].name),
+ eCSSUnit_Ident);
+ break;
+ }
+ }
+ }
+#endif
+ return NS_OK;
+}
+
+static nsresult
+GetIsGlyph(nsPresContext* aPresContext, const nsMediaFeature* aFeature,
+ nsCSSValue& aResult)
+{
+ aResult.SetIntValue(aPresContext->IsGlyph() ? 1 : 0, eCSSUnit_Integer);
+ return NS_OK;
+}
+
+/*
+ * Adding new media features requires (1) adding the new feature to this
+ * array, with appropriate entries (and potentially any new code needed
+ * to support new types in these entries and (2) ensuring that either
+ * nsPresContext::MediaFeatureValuesChanged or
+ * nsPresContext::PostMediaFeatureValuesChangedEvent is called when the
+ * value that would be returned by the entry's mGetter changes.
+ */
+
+/* static */ const nsMediaFeature
+nsMediaFeatures::features[] = {
+ {
+ &nsGkAtoms::width,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eLength,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetWidth
+ },
+ {
+ &nsGkAtoms::height,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eLength,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetHeight
+ },
+ {
+ &nsGkAtoms::deviceWidth,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eLength,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetDeviceWidth
+ },
+ {
+ &nsGkAtoms::deviceHeight,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eLength,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetDeviceHeight
+ },
+ {
+ &nsGkAtoms::orientation,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eEnumerated,
+ nsMediaFeature::eNoRequirements,
+ { kOrientationKeywords },
+ GetOrientation
+ },
+ {
+ &nsGkAtoms::aspectRatio,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eIntRatio,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetAspectRatio
+ },
+ {
+ &nsGkAtoms::deviceAspectRatio,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eIntRatio,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetDeviceAspectRatio
+ },
+ {
+ &nsGkAtoms::color,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eInteger,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetColor
+ },
+ {
+ &nsGkAtoms::colorIndex,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eInteger,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetColorIndex
+ },
+ {
+ &nsGkAtoms::monochrome,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eInteger,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetMonochrome
+ },
+ {
+ &nsGkAtoms::resolution,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eResolution,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetResolution
+ },
+ {
+ &nsGkAtoms::scan,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eEnumerated,
+ nsMediaFeature::eNoRequirements,
+ { kScanKeywords },
+ GetScan
+ },
+ {
+ &nsGkAtoms::grid,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetGrid
+ },
+ {
+ &nsGkAtoms::displayMode,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eEnumerated,
+ nsMediaFeature::eNoRequirements,
+ { kDisplayModeKeywords },
+ GetDisplayMode
+ },
+
+ // Webkit extensions that we support for de-facto web compatibility
+ // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
+ {
+ &nsGkAtoms::devicePixelRatio,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eFloat,
+ nsMediaFeature::eHasWebkitPrefix |
+ nsMediaFeature::eWebkitDevicePixelRatioPrefEnabled,
+ { nullptr },
+ GetDevicePixelRatio
+ },
+ // -webkit-transform-3d:
+ {
+ &nsGkAtoms::transform_3d,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eHasWebkitPrefix,
+ { nullptr },
+ GetTransform3d
+ },
+
+ // Mozilla extensions
+ {
+ &nsGkAtoms::_moz_device_pixel_ratio,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eFloat,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetDevicePixelRatio
+ },
+ {
+ &nsGkAtoms::_moz_device_orientation,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eEnumerated,
+ nsMediaFeature::eNoRequirements,
+ { kOrientationKeywords },
+ GetDeviceOrientation
+ },
+ {
+ &nsGkAtoms::_moz_is_resource_document,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetIsResourceDocument
+ },
+ {
+ &nsGkAtoms::_moz_color_picker_available,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::color_picker_available },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_scrollbar_start_backward,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::scrollbar_start_backward },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_scrollbar_start_forward,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::scrollbar_start_forward },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_scrollbar_end_backward,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::scrollbar_end_backward },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_scrollbar_end_forward,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::scrollbar_end_forward },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_scrollbar_thumb_proportional,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::scrollbar_thumb_proportional },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_overlay_scrollbars,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::overlay_scrollbars },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_windows_default_theme,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::windows_default_theme },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_mac_graphite_theme,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::mac_graphite_theme },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_mac_yosemite_theme,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::mac_yosemite_theme },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_windows_compositor,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::windows_compositor },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_windows_classic,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::windows_classic },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_windows_glass,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::windows_glass },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_touch_enabled,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::touch_enabled },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_menubar_drag,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::menubar_drag },
+ GetSystemMetric
+ },
+ {
+ &nsGkAtoms::_moz_windows_theme,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eIdent,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetWindowsTheme
+ },
+ {
+ &nsGkAtoms::_moz_os_version,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eIdent,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetOperatingSystemVersion
+ },
+
+ {
+ &nsGkAtoms::_moz_swipe_animation_enabled,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::swipe_animation_enabled },
+ GetSystemMetric
+ },
+
+ {
+ &nsGkAtoms::_moz_physical_home_button,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { &nsGkAtoms::physical_home_button },
+ GetSystemMetric
+ },
+
+ // Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
+ // Internal because it is really only useful in the user agent anyway
+ // and therefore not worth standardizing.
+ {
+ &nsGkAtoms::_moz_is_glyph,
+ nsMediaFeature::eMinMaxNotAllowed,
+ nsMediaFeature::eBoolInteger,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ GetIsGlyph
+ },
+ // Null-mName terminator:
+ {
+ nullptr,
+ nsMediaFeature::eMinMaxAllowed,
+ nsMediaFeature::eInteger,
+ nsMediaFeature::eNoRequirements,
+ { nullptr },
+ nullptr
+ },
+};
diff --git a/layout/style/nsMediaFeatures.h b/layout/style/nsMediaFeatures.h
new file mode 100644
index 000000000..76e94837d
--- /dev/null
+++ b/layout/style/nsMediaFeatures.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/. */
+
+/* the features that media queries can test */
+
+#ifndef nsMediaFeatures_h_
+#define nsMediaFeatures_h_
+
+#include "nsCSSProps.h"
+
+class nsIAtom;
+class nsPresContext;
+class nsCSSValue;
+
+struct nsMediaFeature;
+typedef nsresult
+(* nsMediaFeatureValueGetter)(nsPresContext* aPresContext,
+ const nsMediaFeature* aFeature,
+ nsCSSValue& aResult);
+
+struct nsMediaFeature
+{
+ nsIAtom **mName; // extra indirection to point to nsGkAtoms members
+
+ enum RangeType { eMinMaxAllowed, eMinMaxNotAllowed };
+ RangeType mRangeType;
+
+ enum ValueType {
+ // All value types allow eCSSUnit_Null to indicate that no value
+ // was given (in addition to the types listed below).
+ eLength, // values are such that nsCSSValue::IsLengthUnit() is true
+ eInteger, // values are eCSSUnit_Integer
+ eFloat, // values are eCSSUnit_Number
+ eBoolInteger,// values are eCSSUnit_Integer (0, -0, or 1 only)
+ eIntRatio, // values are eCSSUnit_Array of two eCSSUnit_Integer
+ eResolution, // values are in eCSSUnit_Inch (for dpi),
+ // eCSSUnit_Pixel (for dppx), or
+ // eCSSUnit_Centimeter (for dpcm)
+ eEnumerated, // values are eCSSUnit_Enumerated (uses keyword table)
+ eIdent // values are eCSSUnit_Ident
+ // Note that a number of pieces of code (both for parsing and
+ // for matching of valueless expressions) assume that all numeric
+ // value types cannot be negative. The parsing code also does
+ // not allow zeros in eIntRatio types.
+ };
+ ValueType mValueType;
+
+ enum RequirementFlags : uint8_t {
+ // Bitfield of requirements that must be satisfied in order for this
+ // media feature to be active.
+ eNoRequirements = 0,
+ eHasWebkitPrefix = 1 << 0, // Feature name must start w/ "-webkit-", even
+ // before any "min-"/"max-" qualifier.
+
+ // Feature is only supported if the pref
+ // "layout.css.prefixes.device-pixel-ratio-webkit" is enabled.
+ // (Should only be used for -webkit-device-pixel-ratio.)
+ eWebkitDevicePixelRatioPrefEnabled = 1 << 1
+ };
+ uint8_t mReqFlags;
+
+ union {
+ // In static arrays, it's the first member that's initialized. We
+ // need that to be void* so we can initialize both other types.
+ // This member should never be accessed by name.
+ const void* mInitializer_;
+ // If mValueType == eEnumerated: const int32_t*: keyword table in
+ // the same format as the keyword tables in nsCSSProps.
+ const nsCSSProps::KTableEntry* mKeywordTable;
+ // If mGetter == GetSystemMetric (which implies mValueType ==
+ // eBoolInteger): nsIAtom * const *, for the system metric.
+ nsIAtom * const * mMetric;
+ } mData;
+
+ // A function that returns the current value for this feature for a
+ // given presentation. If it returns eCSSUnit_Null, the feature is
+ // not present.
+ nsMediaFeatureValueGetter mGetter;
+};
+
+class nsMediaFeatures
+{
+public:
+ // Terminated with an entry whose mName is null.
+ static const nsMediaFeature features[];
+};
+
+#endif /* !defined(nsMediaFeatures_h_) */
diff --git a/layout/style/nsNthIndexCache.cpp b/layout/style/nsNthIndexCache.cpp
new file mode 100644
index 000000000..970c070f2
--- /dev/null
+++ b/layout/style/nsNthIndexCache.cpp
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A class that computes and caches the indices used for :nth-* pseudo-class
+ * matching.
+ */
+
+#include "nsNthIndexCache.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/NodeInfoInlines.h"
+
+nsNthIndexCache::nsNthIndexCache()
+{
+}
+
+nsNthIndexCache::~nsNthIndexCache()
+{
+}
+
+void
+nsNthIndexCache::Reset()
+{
+ mCaches[0][0].clear();
+ mCaches[0][1].clear();
+ mCaches[1][0].clear();
+ mCaches[1][1].clear();
+}
+
+inline bool
+nsNthIndexCache::SiblingMatchesElement(nsIContent* aSibling, Element* aElement,
+ bool aIsOfType)
+{
+ return aSibling->IsElement() &&
+ (!aIsOfType ||
+ aSibling->NodeInfo()->NameAndNamespaceEquals(aElement->NodeInfo()));
+}
+
+inline bool
+nsNthIndexCache::IndexDeterminedFromPreviousSibling(nsIContent* aSibling,
+ Element* aChild,
+ bool aIsOfType,
+ bool aIsFromEnd,
+ const Cache& aCache,
+ int32_t& aResult)
+{
+ if (SiblingMatchesElement(aSibling, aChild, aIsOfType)) {
+ Cache::Ptr siblingEntry = aCache.lookup(aSibling);
+ if (siblingEntry) {
+ int32_t siblingIndex = siblingEntry->value();
+ NS_ASSERTION(siblingIndex != 0,
+ "How can a non-anonymous node have an anonymous sibling?");
+ if (siblingIndex > 0) {
+ // At this point, aResult is a count of how many elements matching
+ // aChild we have seen after aSibling, including aChild itself.
+ // |siblingIndex| is the index of aSibling.
+ // So if aIsFromEnd, we want |aResult = siblingIndex - aResult| and
+ // otherwise we want |aResult = siblingIndex + aResult|.
+ aResult = siblingIndex + aResult * (1 - 2 * aIsFromEnd);
+ return true;
+ }
+ }
+
+ ++aResult;
+ }
+
+ return false;
+}
+
+int32_t
+nsNthIndexCache::GetNthIndex(Element* aChild, bool aIsOfType,
+ bool aIsFromEnd, bool aCheckEdgeOnly)
+{
+ if (aChild->IsRootOfAnonymousSubtree()) {
+ return 0;
+ }
+
+ Cache& cache = mCaches[aIsOfType][aIsFromEnd];
+
+ if (!cache.initialized() && !cache.init()) {
+ // Give up and just don't match.
+ return 0;
+ }
+
+ Cache::AddPtr entry = cache.lookupForAdd(aChild);
+
+ // Default the value to -2 when adding
+ if (!entry && !cache.add(entry, aChild, -2)) {
+ // No good; don't match.
+ return 0;
+ }
+
+ int32_t& slot = entry->value();
+ if (slot != -2 && (slot != -1 || aCheckEdgeOnly)) {
+ return slot;
+ }
+
+ int32_t result = 1;
+ if (aCheckEdgeOnly) {
+ // The caller only cares whether or not the result is 1, so we can
+ // stop as soon as we see any other elements that match us.
+ if (aIsFromEnd) {
+ for (nsIContent *cur = aChild->GetNextSibling();
+ cur;
+ cur = cur->GetNextSibling()) {
+ if (SiblingMatchesElement(cur, aChild, aIsOfType)) {
+ result = -1;
+ break;
+ }
+ }
+ } else {
+ for (nsIContent *cur = aChild->GetPreviousSibling();
+ cur;
+ cur = cur->GetPreviousSibling()) {
+ if (SiblingMatchesElement(cur, aChild, aIsOfType)) {
+ result = -1;
+ break;
+ }
+ }
+ }
+ } else {
+ // In the common case, we already have a cached index for one of
+ // our previous siblings, so check that first.
+ for (nsIContent *cur = aChild->GetPreviousSibling();
+ cur;
+ cur = cur->GetPreviousSibling()) {
+ if (IndexDeterminedFromPreviousSibling(cur, aChild, aIsOfType,
+ aIsFromEnd, cache, result)) {
+ slot = result;
+ return result;
+ }
+ }
+
+ // Now if aIsFromEnd we lose: need to actually compute our index,
+ // since looking at previous siblings wouldn't have told us
+ // anything about it. Note that it doesn't make sense to do cache
+ // lookups on our following siblings, since chances are the cache
+ // is not primed for them.
+ if (aIsFromEnd) {
+ result = 1;
+ for (nsIContent *cur = aChild->GetNextSibling();
+ cur;
+ cur = cur->GetNextSibling()) {
+ if (SiblingMatchesElement(cur, aChild, aIsOfType)) {
+ ++result;
+ }
+ }
+ }
+ }
+
+ slot = result;
+ return result;
+}
diff --git a/layout/style/nsNthIndexCache.h b/layout/style/nsNthIndexCache.h
new file mode 100644
index 000000000..e24d98737
--- /dev/null
+++ b/layout/style/nsNthIndexCache.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsContentIndexCache_h__
+#define nsContentIndexCache_h__
+
+#include "js/HashTable.h"
+
+class nsIContent;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+/*
+ * A class that computes and caches the indices used for :nth-* pseudo-class
+ * matching.
+ */
+
+class nsNthIndexCache {
+private:
+ typedef mozilla::dom::Element Element;
+
+public:
+ /**
+ * Constructor and destructor out of line so that we don't try to
+ * instantiate the hashtable template all over the place.
+ */
+ nsNthIndexCache();
+ ~nsNthIndexCache();
+
+ // Returns a 1-based index of the child in its parent. If the child
+ // is not in its parent's child list (i.e., it is anonymous content),
+ // returns 0.
+ // If aCheckEdgeOnly is true, the function will return 1 if the result
+ // is 1, and something other than 1 (maybe or maybe not a valid
+ // result) otherwise.
+ // This must only be called on nodes which have a non-null parent.
+ int32_t GetNthIndex(Element* aChild, bool aIsOfType, bool aIsFromEnd,
+ bool aCheckEdgeOnly);
+
+ void Reset();
+
+private:
+ /**
+ * Returns true if aSibling and aElement should be considered in the same
+ * list for nth-index purposes, taking aIsOfType into account.
+ */
+ inline bool SiblingMatchesElement(nsIContent* aSibling, Element* aElement,
+ bool aIsOfType);
+
+ // This node's index for this cache.
+ // If -2, needs to be computed.
+ // If -1, needs to be computed but known not to be 1.
+ // If 0, the node is not at any index in its parent.
+ typedef int32_t CacheEntry;
+
+ class SystemAllocPolicy {
+ public:
+ void *malloc_(size_t bytes) { return ::malloc(bytes); }
+
+ template <typename T>
+ T *maybe_pod_calloc(size_t numElems) {
+ return static_cast<T *>(::calloc(numElems, sizeof(T)));
+ }
+
+ template <typename T>
+ T *pod_calloc(size_t numElems) {
+ return maybe_pod_calloc<T>(numElems);
+ }
+
+ void *realloc_(void *p, size_t bytes) { return ::realloc(p, bytes); }
+ void free_(void *p) { ::free(p); }
+ void reportAllocOverflow() const {}
+ bool checkSimulatedOOM() const { return true; }
+ };
+
+ typedef js::HashMap<nsIContent*, CacheEntry, js::DefaultHasher<nsIContent*>,
+ SystemAllocPolicy> Cache;
+
+ /**
+ * Returns true if aResult has been set to the correct value for aChild and
+ * no more work needs to be done. Returns false otherwise.
+ *
+ * aResult is an inout parameter. The in value is the number of elements
+ * that are in the half-open range (aSibling, aChild] (so including aChild
+ * but not including aSibling) that match aChild. The out value is the
+ * correct index for aChild if this function returns true and the number of
+ * elements in the closed range [aSibling, aChild] that match aChild
+ * otherwise.
+ */
+ inline bool IndexDeterminedFromPreviousSibling(nsIContent* aSibling,
+ Element* aChild,
+ bool aIsOfType,
+ bool aIsFromEnd,
+ const Cache& aCache,
+ int32_t& aResult);
+
+ // Caches of indices for :nth-child(), :nth-last-child(),
+ // :nth-of-type(), :nth-last-of-type(), keyed by Element*.
+ //
+ // The first subscript is 0 for -child and 1 for -of-type, the second
+ // subscript is 0 for nth- and 1 for nth-last-.
+ Cache mCaches[2][2];
+};
+
+#endif /* nsContentIndexCache_h__ */
diff --git a/layout/style/nsROCSSPrimitiveValue.cpp b/layout/style/nsROCSSPrimitiveValue.cpp
new file mode 100644
index 000000000..cbc715eb2
--- /dev/null
+++ b/layout/style/nsROCSSPrimitiveValue.cpp
@@ -0,0 +1,722 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/CSSPrimitiveValueBinding.h"
+#include "nsPresContext.h"
+#include "nsStyleUtil.h"
+#include "nsDOMCSSRGBColor.h"
+#include "nsDOMCSSRect.h"
+#include "nsIURI.h"
+#include "nsError.h"
+
+using namespace mozilla;
+
+nsROCSSPrimitiveValue::nsROCSSPrimitiveValue()
+ : CSSValue(), mType(CSS_PX)
+{
+ mValue.mAppUnits = 0;
+}
+
+
+nsROCSSPrimitiveValue::~nsROCSSPrimitiveValue()
+{
+ Reset();
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsROCSSPrimitiveValue)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsROCSSPrimitiveValue)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsROCSSPrimitiveValue)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSPrimitiveValue)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCSSValue)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, CSSValue)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsROCSSPrimitiveValue)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsROCSSPrimitiveValue)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsROCSSPrimitiveValue)
+ if (tmp->mType == CSS_URI) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mValue.mURI)
+ } else if (tmp->mType == CSS_RGBCOLOR) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mValue.mColor)
+ } else if (tmp->mType == CSS_RECT) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mValue.mRect)
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsROCSSPrimitiveValue)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->Reset();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+JSObject*
+nsROCSSPrimitiveValue::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::CSSPrimitiveValueBinding::Wrap(cx, this, aGivenProto);
+}
+
+// nsIDOMCSSValue
+
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::GetCssText(nsAString& aCssText)
+{
+ nsAutoString tmpStr;
+ aCssText.Truncate();
+ nsresult result = NS_OK;
+
+ switch (mType) {
+ case CSS_PX :
+ {
+ float val = nsPresContext::AppUnitsToFloatCSSPixels(mValue.mAppUnits);
+ nsStyleUtil::AppendCSSNumber(val, tmpStr);
+ tmpStr.AppendLiteral("px");
+ break;
+ }
+ case CSS_IDENT :
+ {
+ AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(mValue.mKeyword),
+ tmpStr);
+ break;
+ }
+ case CSS_STRING :
+ case CSS_COUNTER : /* FIXME: COUNTER should use an object */
+ {
+ tmpStr.Append(mValue.mString);
+ break;
+ }
+ case CSS_URI :
+ {
+ if (mValue.mURI) {
+ nsAutoCString specUTF8;
+ nsresult rv = mValue.mURI->GetSpec(specUTF8);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ tmpStr.AssignLiteral("url(");
+ nsStyleUtil::AppendEscapedCSSString(NS_ConvertUTF8toUTF16(specUTF8),
+ tmpStr);
+ tmpStr.Append(')');
+ } else {
+ // http://dev.w3.org/csswg/css3-values/#attr defines
+ // 'about:invalid' as the default value for url attributes,
+ // so let's also use it here as the default computed value
+ // for invalid URLs.
+ tmpStr.AssignLiteral(u"url(about:invalid)");
+ }
+ break;
+ }
+ case CSS_ATTR :
+ {
+ tmpStr.AppendLiteral("attr(");
+ tmpStr.Append(mValue.mString);
+ tmpStr.Append(char16_t(')'));
+ 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_GRAD :
+ {
+ nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr);
+ tmpStr.AppendLiteral("grad");
+ break;
+ }
+ case CSS_RAD :
+ {
+ nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr);
+ tmpStr.AppendLiteral("rad");
+ break;
+ }
+ case CSS_TURN :
+ {
+ nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr);
+ tmpStr.AppendLiteral("turn");
+ break;
+ }
+ case CSS_RECT :
+ {
+ NS_ASSERTION(mValue.mRect, "mValue.mRect should never be null");
+ NS_NAMED_LITERAL_STRING(comma, ", ");
+ nsCOMPtr<nsIDOMCSSPrimitiveValue> sideCSSValue;
+ nsAutoString sideValue;
+ tmpStr.AssignLiteral("rect(");
+ // get the top
+ result = mValue.mRect->GetTop(getter_AddRefs(sideCSSValue));
+ if (NS_FAILED(result))
+ break;
+ result = sideCSSValue->GetCssText(sideValue);
+ if (NS_FAILED(result))
+ break;
+ tmpStr.Append(sideValue + comma);
+ // get the right
+ result = mValue.mRect->GetRight(getter_AddRefs(sideCSSValue));
+ if (NS_FAILED(result))
+ break;
+ result = sideCSSValue->GetCssText(sideValue);
+ if (NS_FAILED(result))
+ break;
+ tmpStr.Append(sideValue + comma);
+ // get the bottom
+ result = mValue.mRect->GetBottom(getter_AddRefs(sideCSSValue));
+ if (NS_FAILED(result))
+ break;
+ result = sideCSSValue->GetCssText(sideValue);
+ if (NS_FAILED(result))
+ break;
+ tmpStr.Append(sideValue + comma);
+ // get the left
+ result = mValue.mRect->GetLeft(getter_AddRefs(sideCSSValue));
+ if (NS_FAILED(result))
+ break;
+ result = sideCSSValue->GetCssText(sideValue);
+ if (NS_FAILED(result))
+ break;
+ tmpStr.Append(sideValue + NS_LITERAL_STRING(")"));
+ break;
+ }
+ case CSS_RGBCOLOR :
+ {
+ NS_ASSERTION(mValue.mColor, "mValue.mColor should never be null");
+ ErrorResult error;
+ NS_NAMED_LITERAL_STRING(comma, ", ");
+ nsAutoString colorValue;
+ if (mValue.mColor->HasAlpha())
+ tmpStr.AssignLiteral("rgba(");
+ else
+ tmpStr.AssignLiteral("rgb(");
+
+ // get the red component
+ mValue.mColor->Red()->GetCssText(colorValue, error);
+ if (error.Failed())
+ break;
+ tmpStr.Append(colorValue + comma);
+
+ // get the green component
+ mValue.mColor->Green()->GetCssText(colorValue, error);
+ if (error.Failed())
+ break;
+ tmpStr.Append(colorValue + comma);
+
+ // get the blue component
+ mValue.mColor->Blue()->GetCssText(colorValue, error);
+ if (error.Failed())
+ break;
+ tmpStr.Append(colorValue);
+
+ if (mValue.mColor->HasAlpha()) {
+ // get the alpha component
+ mValue.mColor->Alpha()->GetCssText(colorValue, error);
+ if (error.Failed())
+ break;
+ tmpStr.Append(comma + colorValue);
+ }
+
+ tmpStr.Append(')');
+
+ break;
+ }
+ case CSS_S :
+ {
+ nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr);
+ tmpStr.Append('s');
+ break;
+ }
+ case CSS_CM :
+ case CSS_MM :
+ case CSS_IN :
+ case CSS_PT :
+ case CSS_PC :
+ case CSS_UNKNOWN :
+ case CSS_EMS :
+ case CSS_EXS :
+ case CSS_MS :
+ case CSS_HZ :
+ case CSS_KHZ :
+ case CSS_DIMENSION :
+ NS_ERROR("We have a bogus value set. This should not happen");
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ if (NS_SUCCEEDED(result)) {
+ aCssText.Assign(tmpStr);
+ }
+
+ return NS_OK;
+}
+
+void
+nsROCSSPrimitiveValue::GetCssText(nsString& aText, ErrorResult& aRv)
+{
+ aRv = GetCssText(aText);
+}
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::SetCssText(const nsAString& aCssText)
+{
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+}
+
+void
+nsROCSSPrimitiveValue::SetCssText(const nsAString& aText, ErrorResult& aRv)
+{
+ aRv = SetCssText(aText);
+}
+
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::GetCssValueType(uint16_t* aValueType)
+{
+ NS_ENSURE_ARG_POINTER(aValueType);
+ *aValueType = nsIDOMCSSValue::CSS_PRIMITIVE_VALUE;
+ return NS_OK;
+}
+
+uint16_t
+nsROCSSPrimitiveValue::CssValueType() const
+{
+ return nsIDOMCSSValue::CSS_PRIMITIVE_VALUE;
+}
+
+
+// nsIDOMCSSPrimitiveValue
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::GetPrimitiveType(uint16_t* aPrimitiveType)
+{
+ NS_ENSURE_ARG_POINTER(aPrimitiveType);
+ *aPrimitiveType = PrimitiveType();
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::SetFloatValue(uint16_t aUnitType, float aFloatValue)
+{
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+}
+
+void
+nsROCSSPrimitiveValue::SetFloatValue(uint16_t aType, float aVal,
+ ErrorResult& aRv)
+{
+ aRv = SetFloatValue(aType, aVal);
+}
+
+float
+nsROCSSPrimitiveValue::GetFloatValue(uint16_t aUnitType, ErrorResult& aRv)
+{
+ switch(aUnitType) {
+ case CSS_PX :
+ if (mType == CSS_PX) {
+ return nsPresContext::AppUnitsToFloatCSSPixels(mValue.mAppUnits);
+ }
+
+ break;
+ case CSS_CM :
+ if (mType == CSS_PX) {
+ return mValue.mAppUnits * CM_PER_INCH_FLOAT /
+ nsPresContext::AppUnitsPerCSSInch();
+ }
+
+ break;
+ case CSS_MM :
+ if (mType == CSS_PX) {
+ return mValue.mAppUnits * MM_PER_INCH_FLOAT /
+ nsPresContext::AppUnitsPerCSSInch();
+ }
+
+ break;
+ case CSS_IN :
+ if (mType == CSS_PX) {
+ return mValue.mAppUnits / nsPresContext::AppUnitsPerCSSInch();
+ }
+
+ break;
+ case CSS_PT :
+ if (mType == CSS_PX) {
+ return mValue.mAppUnits * POINTS_PER_INCH_FLOAT /
+ nsPresContext::AppUnitsPerCSSInch();
+ }
+
+ break;
+ case CSS_PC :
+ if (mType == CSS_PX) {
+ return mValue.mAppUnits * 6.0f /
+ nsPresContext::AppUnitsPerCSSInch();
+ }
+
+ break;
+ case CSS_PERCENTAGE :
+ if (mType == CSS_PERCENTAGE) {
+ return mValue.mFloat * 100;
+ }
+
+ break;
+ case CSS_NUMBER :
+ if (mType == CSS_NUMBER) {
+ return mValue.mFloat;
+ }
+ if (mType == CSS_NUMBER_INT32) {
+ return mValue.mInt32;
+ }
+ if (mType == CSS_NUMBER_UINT32) {
+ return mValue.mUint32;
+ }
+
+ break;
+ case CSS_UNKNOWN :
+ case CSS_EMS :
+ case CSS_EXS :
+ case CSS_DEG :
+ case CSS_RAD :
+ case CSS_GRAD :
+ case CSS_MS :
+ case CSS_S :
+ case CSS_HZ :
+ case CSS_KHZ :
+ case CSS_DIMENSION :
+ case CSS_STRING :
+ case CSS_URI :
+ case CSS_IDENT :
+ case CSS_ATTR :
+ case CSS_COUNTER :
+ case CSS_RECT :
+ case CSS_RGBCOLOR :
+ break;
+ }
+
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return 0;
+}
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::GetFloatValue(uint16_t aType, float *aVal)
+{
+ ErrorResult rv;
+ *aVal = GetFloatValue(aType, rv);
+ return rv.StealNSResult();
+}
+
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::SetStringValue(uint16_t aStringType,
+ const nsAString& aStringValue)
+{
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+}
+
+void
+nsROCSSPrimitiveValue::SetStringValue(uint16_t aType, const nsAString& aString,
+ mozilla::ErrorResult& aRv)
+{
+ aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
+}
+
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::GetStringValue(nsAString& aReturn)
+{
+ switch (mType) {
+ case CSS_IDENT:
+ CopyUTF8toUTF16(nsCSSKeywords::GetStringValue(mValue.mKeyword), aReturn);
+ break;
+ case CSS_STRING:
+ case CSS_ATTR:
+ aReturn.Assign(mValue.mString);
+ break;
+ case CSS_URI: {
+ nsAutoCString spec;
+ if (mValue.mURI) {
+ nsresult rv = mValue.mURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ CopyUTF8toUTF16(spec, aReturn);
+ break;
+ }
+ default:
+ aReturn.Truncate();
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+ return NS_OK;
+}
+
+void
+nsROCSSPrimitiveValue::GetStringValue(nsString& aString, ErrorResult& aRv)
+{
+ aRv = GetStringValue(aString);
+}
+
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::GetCounterValue(nsIDOMCounter** aReturn)
+{
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+}
+
+already_AddRefed<nsIDOMCounter>
+nsROCSSPrimitiveValue::GetCounterValue(ErrorResult& aRv)
+{
+ aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
+ return nullptr;
+}
+
+nsDOMCSSRect*
+nsROCSSPrimitiveValue::GetRectValue(ErrorResult& aRv)
+{
+ if (mType != CSS_RECT) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+
+ NS_ASSERTION(mValue.mRect, "mValue.mRect should never be null");
+ return mValue.mRect;
+}
+
+NS_IMETHODIMP
+nsROCSSPrimitiveValue::GetRectValue(nsIDOMRect** aRect)
+{
+ ErrorResult error;
+ NS_IF_ADDREF(*aRect = GetRectValue(error));
+ return error.StealNSResult();
+}
+
+nsDOMCSSRGBColor*
+nsROCSSPrimitiveValue::GetRGBColorValue(ErrorResult& aRv)
+{
+ if (mType != CSS_RGBCOLOR) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+
+ NS_ASSERTION(mValue.mColor, "mValue.mColor should never be null");
+ return mValue.mColor;
+}
+
+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::SetGrad(float aValue)
+{
+ Reset();
+ mValue.mFloat = aValue;
+ mType = CSS_GRAD;
+}
+
+void
+nsROCSSPrimitiveValue::SetRadian(float aValue)
+{
+ Reset();
+ mValue.mFloat = aValue;
+ mType = CSS_RAD;
+}
+
+void
+nsROCSSPrimitiveValue::SetTurn(float aValue)
+{
+ Reset();
+ mValue.mFloat = aValue;
+ mType = CSS_TURN;
+}
+
+void
+nsROCSSPrimitiveValue::SetAppUnits(nscoord aValue)
+{
+ Reset();
+ mValue.mAppUnits = aValue;
+ mType = CSS_PX;
+}
+
+void
+nsROCSSPrimitiveValue::SetAppUnits(float aValue)
+{
+ SetAppUnits(NSToCoordRound(aValue));
+}
+
+void
+nsROCSSPrimitiveValue::SetIdent(nsCSSKeyword aKeyword)
+{
+ NS_PRECONDITION(aKeyword != eCSSKeyword_UNKNOWN &&
+ 0 <= aKeyword && aKeyword < eCSSKeyword_COUNT,
+ "bad keyword");
+ Reset();
+ mValue.mKeyword = aKeyword;
+ mType = CSS_IDENT;
+}
+
+// FIXME: CSS_STRING should imply a string with "" and a need for escaping.
+void
+nsROCSSPrimitiveValue::SetString(const nsACString& aString, uint16_t aType)
+{
+ Reset();
+ mValue.mString = ToNewUnicode(aString);
+ if (mValue.mString) {
+ mType = aType;
+ } else {
+ // XXXcaa We should probably let the caller know we are out of memory
+ mType = CSS_UNKNOWN;
+ }
+}
+
+// FIXME: CSS_STRING should imply a string with "" and a need for escaping.
+void
+nsROCSSPrimitiveValue::SetString(const nsAString& aString, uint16_t aType)
+{
+ Reset();
+ mValue.mString = ToNewUnicode(aString);
+ if (mValue.mString) {
+ mType = aType;
+ } else {
+ // XXXcaa We should probably let the caller know we are out of memory
+ mType = CSS_UNKNOWN;
+ }
+}
+
+void
+nsROCSSPrimitiveValue::SetURI(nsIURI *aURI)
+{
+ Reset();
+ mValue.mURI = aURI;
+ NS_IF_ADDREF(mValue.mURI);
+ mType = CSS_URI;
+}
+
+void
+nsROCSSPrimitiveValue::SetColor(nsDOMCSSRGBColor* aColor)
+{
+ NS_PRECONDITION(aColor, "Null RGBColor being set!");
+ Reset();
+ mValue.mColor = aColor;
+ if (mValue.mColor) {
+ NS_ADDREF(mValue.mColor);
+ mType = CSS_RGBCOLOR;
+ }
+ else {
+ mType = CSS_UNKNOWN;
+ }
+}
+
+void
+nsROCSSPrimitiveValue::SetRect(nsDOMCSSRect* aRect)
+{
+ NS_PRECONDITION(aRect, "Null rect being set!");
+ Reset();
+ mValue.mRect = aRect;
+ if (mValue.mRect) {
+ NS_ADDREF(mValue.mRect);
+ mType = CSS_RECT;
+ }
+ else {
+ mType = CSS_UNKNOWN;
+ }
+}
+
+void
+nsROCSSPrimitiveValue::SetTime(float aValue)
+{
+ Reset();
+ mValue.mFloat = aValue;
+ mType = CSS_S;
+}
+
+void
+nsROCSSPrimitiveValue::Reset()
+{
+ switch (mType) {
+ case CSS_IDENT:
+ break;
+ case CSS_STRING:
+ case CSS_ATTR:
+ case CSS_COUNTER: // FIXME: Counter should use an object
+ NS_ASSERTION(mValue.mString, "Null string should never happen");
+ free(mValue.mString);
+ mValue.mString = nullptr;
+ break;
+ case CSS_URI:
+ NS_IF_RELEASE(mValue.mURI);
+ break;
+ case CSS_RECT:
+ NS_ASSERTION(mValue.mRect, "Null Rect should never happen");
+ NS_RELEASE(mValue.mRect);
+ break;
+ case CSS_RGBCOLOR:
+ NS_ASSERTION(mValue.mColor, "Null RGBColor should never happen");
+ NS_RELEASE(mValue.mColor);
+ break;
+ }
+
+ mType = CSS_UNKNOWN;
+}
diff --git a/layout/style/nsROCSSPrimitiveValue.h b/layout/style/nsROCSSPrimitiveValue.h
new file mode 100644
index 000000000..ae99b8445
--- /dev/null
+++ b/layout/style/nsROCSSPrimitiveValue.h
@@ -0,0 +1,135 @@
+/* -*- 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/. */
+
+/* DOM object representing values in DOM computed style */
+
+#ifndef nsROCSSPrimitiveValue_h___
+#define nsROCSSPrimitiveValue_h___
+
+#include "nsIDOMCSSValue.h"
+#include "nsIDOMCSSPrimitiveValue.h"
+#include "nsCSSKeywords.h"
+#include "CSSValue.h"
+#include "nsCOMPtr.h"
+#include "nsCoord.h"
+
+class nsIURI;
+class nsDOMCSSRect;
+class nsDOMCSSRGBColor;
+
+// There is no CSS_TURN constant on the CSSPrimitiveValue interface,
+// since that unit is newer than DOM Level 2 Style, and CSS OM will
+// probably expose CSS values in some other way in the future. We
+// use this value in mType for "turn"-unit angles, but we define it
+// here to avoid exposing it to content.
+#define CSS_TURN 30U
+// Likewise we have some internal aliases for CSS_NUMBER that we don't
+// want to expose.
+#define CSS_NUMBER_INT32 31U
+#define CSS_NUMBER_UINT32 32U
+
+/**
+ * Read-only CSS primitive value - a DOM object representing values in DOM
+ * computed style.
+ */
+class nsROCSSPrimitiveValue final : public mozilla::dom::CSSValue,
+ public nsIDOMCSSPrimitiveValue
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsROCSSPrimitiveValue, mozilla::dom::CSSValue)
+
+ // nsIDOMCSSPrimitiveValue
+ NS_DECL_NSIDOMCSSPRIMITIVEVALUE
+
+ // nsIDOMCSSValue
+ NS_DECL_NSIDOMCSSVALUE
+
+ // CSSValue
+ virtual void GetCssText(nsString& aText, mozilla::ErrorResult& aRv) override final;
+ virtual void SetCssText(const nsAString& aText, mozilla::ErrorResult& aRv) override final;
+ virtual uint16_t CssValueType() const override final;
+
+ // CSSPrimitiveValue
+ uint16_t 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;
+ }
+ void SetFloatValue(uint16_t aUnitType, float aValue,
+ mozilla::ErrorResult& aRv);
+ float GetFloatValue(uint16_t aUnitType, mozilla::ErrorResult& aRv);
+ void GetStringValue(nsString& aString, mozilla::ErrorResult& aRv);
+ void SetStringValue(uint16_t aUnitType, const nsAString& aString,
+ mozilla::ErrorResult& aRv);
+ already_AddRefed<nsIDOMCounter> GetCounterValue(mozilla::ErrorResult& aRv);
+ nsDOMCSSRect* GetRectValue(mozilla::ErrorResult& aRv);
+ nsDOMCSSRGBColor *GetRGBColorValue(mozilla::ErrorResult& aRv);
+
+ // 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 SetGrad(float aValue);
+ void SetRadian(float aValue);
+ void SetTurn(float aValue);
+ void SetAppUnits(nscoord aValue);
+ void SetAppUnits(float aValue);
+ void SetIdent(nsCSSKeyword aKeyword);
+ // FIXME: CSS_STRING should imply a string with "" and a need for escaping.
+ void SetString(const nsACString& aString, uint16_t aType = CSS_STRING);
+ // FIXME: CSS_STRING should imply a string with "" and a need for escaping.
+ void SetString(const nsAString& aString, uint16_t aType = CSS_STRING);
+ void SetURI(nsIURI *aURI);
+ void SetColor(nsDOMCSSRGBColor* aColor);
+ void SetRect(nsDOMCSSRect* aRect);
+ void SetTime(float aValue);
+ void Reset();
+
+ nsISupports* GetParentObject() const
+ {
+ return nullptr;
+ }
+
+ virtual JSObject *WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ ~nsROCSSPrimitiveValue();
+
+ uint16_t mType;
+
+ union {
+ nscoord mAppUnits;
+ float mFloat;
+ int32_t mInt32;
+ uint32_t mUint32;
+ // These can't be nsCOMPtr/nsRefPtr's because they are used inside a union.
+ nsDOMCSSRGBColor* MOZ_OWNING_REF mColor;
+ nsDOMCSSRect* MOZ_OWNING_REF mRect;
+ char16_t* mString;
+ nsIURI* MOZ_OWNING_REF mURI;
+ nsCSSKeyword mKeyword;
+ } mValue;
+};
+
+inline nsROCSSPrimitiveValue *mozilla::dom::CSSValue::AsPrimitiveValue()
+{
+ return CssValueType() == nsIDOMCSSValue::CSS_PRIMITIVE_VALUE ?
+ static_cast<nsROCSSPrimitiveValue*>(this) : nullptr;
+}
+
+#endif /* nsROCSSPrimitiveValue_h___ */
+
diff --git a/layout/style/nsRuleData.cpp b/layout/style/nsRuleData.cpp
new file mode 100644
index 000000000..a4ad5268a
--- /dev/null
+++ b/layout/style/nsRuleData.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRuleData.h"
+
+#include "mozilla/Poison.h"
+#include <stdint.h>
+
+inline size_t
+nsRuleData::GetPoisonOffset()
+{
+ // Fill in mValueOffsets such that mValueStorage + mValueOffsets[i]
+ // will yield the frame poison value for all uninitialized value
+ // offsets.
+ static_assert(sizeof(uintptr_t) == sizeof(size_t),
+ "expect uintptr_t and size_t to be the same size");
+ static_assert(uintptr_t(-1) > uintptr_t(0),
+ "expect uintptr_t to be unsigned");
+ static_assert(size_t(-1) > size_t(0),
+ "expect size_t to be unsigned");
+ uintptr_t framePoisonValue = mozPoisonValue();
+ return size_t(framePoisonValue - uintptr_t(mValueStorage)) /
+ sizeof(nsCSSValue);
+}
+
+nsRuleData::nsRuleData(uint32_t aSIDs, nsCSSValue* aValueStorage,
+ nsPresContext* aContext, nsStyleContext* aStyleContext)
+ : mSIDs(aSIDs),
+ mPresContext(aContext),
+ mStyleContext(aStyleContext),
+ mValueStorage(aValueStorage)
+{
+#ifndef MOZ_VALGRIND
+ size_t framePoisonOffset = GetPoisonOffset();
+ for (size_t i = 0; i < nsStyleStructID_Length; ++i) {
+ mValueOffsets[i] = framePoisonOffset;
+ }
+#endif
+}
+
+#ifdef DEBUG
+nsRuleData::~nsRuleData()
+{
+#ifndef MOZ_VALGRIND
+ // assert nothing in mSIDs has poison value
+ size_t framePoisonOffset = GetPoisonOffset();
+ for (size_t i = 0; i < nsStyleStructID_Length; ++i) {
+ MOZ_ASSERT(!(mSIDs & (1 << i)) || mValueOffsets[i] != framePoisonOffset,
+ "value in SIDs was left with poison offset");
+ }
+#endif
+}
+#endif
diff --git a/layout/style/nsRuleData.h b/layout/style/nsRuleData.h
new file mode 100644
index 000000000..d51d50cb3
--- /dev/null
+++ b/layout/style/nsRuleData.h
@@ -0,0 +1,128 @@
+/* -*- 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/. */
+
+/*
+ * temporary (expanded) representation of property-value pairs used to
+ * hold data from matched rules during style data computation.
+ */
+
+#ifndef nsRuleData_h_
+#define nsRuleData_h_
+
+#include "mozilla/CSSVariableDeclarations.h"
+#include "mozilla/RuleNodeCacheConditions.h"
+#include "mozilla/SheetType.h"
+#include "nsAutoPtr.h"
+#include "nsCSSProps.h"
+#include "nsCSSValue.h"
+#include "nsStyleStructFwd.h"
+
+class nsPresContext;
+class nsStyleContext;
+struct nsRuleData;
+
+typedef void (*nsPostResolveFunc)(void* aStyleStruct, nsRuleData* aData);
+
+struct nsRuleData
+{
+ const uint32_t mSIDs;
+ mozilla::RuleNodeCacheConditions mConditions;
+ bool mIsImportantRule;
+ mozilla::SheetType mLevel;
+ nsPresContext* const mPresContext;
+ nsStyleContext* const mStyleContext;
+
+ // We store nsCSSValues needed to compute the data for one or more
+ // style structs (specified by the bitfield mSIDs). These are stored
+ // in a single array allocation (which our caller allocates; see
+ // AutoCSSValueArray) The offset of each property |prop| in
+ // mValueStorage is the sum of
+ // mValueOffsets[nsCSSProps::kSIDTable[prop]] and
+ // nsCSSProps::PropertyIndexInStruct(prop). The only place we gather
+ // more than one style struct's data at a time is
+ // nsRuleNode::HasAuthorSpecifiedRules; therefore some code that we
+ // know is not called from HasAuthorSpecifiedRules assumes that the
+ // mValueOffsets for the one struct in mSIDs is zero.
+ nsCSSValue* const mValueStorage; // our user owns this array
+ size_t mValueOffsets[nsStyleStructID_Length];
+
+ nsAutoPtr<mozilla::CSSVariableDeclarations> mVariables;
+
+ nsRuleData(uint32_t aSIDs, nsCSSValue* aValueStorage,
+ nsPresContext* aContext, nsStyleContext* aStyleContext);
+
+#ifdef DEBUG
+ ~nsRuleData();
+#else
+ ~nsRuleData() {}
+#endif
+
+ /**
+ * Return a pointer to the value object within |this| corresponding
+ * to property |aProperty|.
+ *
+ * This function must only be called if the given property is in
+ * mSIDs.
+ */
+ nsCSSValue* ValueFor(nsCSSPropertyID aProperty)
+ {
+ MOZ_ASSERT(aProperty < eCSSProperty_COUNT_no_shorthands,
+ "invalid or shorthand property");
+
+ nsStyleStructID sid = nsCSSProps::kSIDTable[aProperty];
+ size_t indexInStruct = nsCSSProps::PropertyIndexInStruct(aProperty);
+
+ // This should really be nsCachedStyleData::GetBitForSID, but we can't
+ // include that here since it includes us.
+ MOZ_ASSERT(mSIDs & (1 << sid),
+ "calling nsRuleData::ValueFor on property not in mSIDs");
+ MOZ_ASSERT(indexInStruct != size_t(-1),
+ "logical property");
+
+ return mValueStorage + mValueOffsets[sid] + indexInStruct;
+ }
+
+ const nsCSSValue* ValueFor(nsCSSPropertyID aProperty) const {
+ return const_cast<nsRuleData*>(this)->ValueFor(aProperty);
+ }
+
+ /**
+ * Getters like ValueFor(aProperty), but for each property by name
+ * (ValueForBackgroundColor, etc.), and more efficient than ValueFor.
+ * These use the names used for the property on DOM interfaces (the
+ * 'method' field in nsCSSPropList.h).
+ *
+ * Like ValueFor(), the caller must check that the property is within
+ * mSIDs.
+ */
+ #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
+ #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \
+ nsCSSValue* ValueFor##method_() { \
+ MOZ_ASSERT(mSIDs & NS_STYLE_INHERIT_BIT(stylestruct_), \
+ "Calling nsRuleData::ValueFor" #method_ " without " \
+ "NS_STYLE_INHERIT_BIT(" #stylestruct_ " in mSIDs."); \
+ nsStyleStructID sid = eStyleStruct_##stylestruct_; \
+ size_t indexInStruct = \
+ nsCSSProps::PropertyIndexInStruct(eCSSProperty_##id_); \
+ MOZ_ASSERT(indexInStruct != size_t(-1), \
+ "logical property"); \
+ return mValueStorage + mValueOffsets[sid] + indexInStruct; \
+ } \
+ const nsCSSValue* ValueFor##method_() const { \
+ return const_cast<nsRuleData*>(this)->ValueFor##method_(); \
+ }
+ #define CSS_PROP_LIST_EXCLUDE_LOGICAL
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP_LIST_EXCLUDE_LOGICAL
+ #undef CSS_PROP
+ #undef CSS_PROP_PUBLIC_OR_PRIVATE
+
+private:
+ inline size_t GetPoisonOffset();
+
+};
+
+#endif
diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp
new file mode 100644
index 000000000..b22002d87
--- /dev/null
+++ b/layout/style/nsRuleNode.cpp
@@ -0,0 +1,10736 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 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/. */
+
+/*
+ * a node in the lexicographic tree of rules that match an element,
+ * responsible for converting the rules' information into computed style
+ */
+
+#include <algorithm>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Function.h"
+#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for PlaybackDirection
+#include "mozilla/Likely.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/Unused.h"
+
+#include "mozilla/css/Declaration.h"
+
+#include "nsAlgorithm.h" // for clamped()
+#include "nsRuleNode.h"
+#include "nscore.h"
+#include "nsIWidget.h"
+#include "nsIPresShell.h"
+#include "nsFontMetrics.h"
+#include "gfxFont.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSPseudoElements.h"
+#include "nsThemeConstants.h"
+#include "PLDHashTable.h"
+#include "nsStyleContext.h"
+#include "nsStyleSet.h"
+#include "nsStyleStruct.h"
+#include "nsSize.h"
+#include "nsRuleData.h"
+#include "nsIStyleRule.h"
+#include "nsBidiUtils.h"
+#include "nsStyleStructInlines.h"
+#include "nsCSSProps.h"
+#include "nsTArray.h"
+#include "nsContentUtils.h"
+#include "CSSCalc.h"
+#include "nsPrintfCString.h"
+#include "nsRenderingContext.h"
+#include "nsStyleUtil.h"
+#include "nsIDocument.h"
+#include "prtime.h"
+#include "CSSVariableResolver.h"
+#include "nsCSSParser.h"
+#include "CounterStyleManager.h"
+#include "nsCSSPropertyIDSet.h"
+#include "mozilla/RuleNodeCacheConditions.h"
+#include "nsDeviceContext.h"
+#include "nsQueryObject.h"
+#include "nsUnicodeProperties.h"
+
+#if defined(_MSC_VER) || defined(__MINGW32__)
+#include <malloc.h>
+#ifdef _MSC_VER
+#define alloca _alloca
+#endif
+#endif
+#ifdef SOLARIS
+#include <alloca.h>
+#endif
+
+using std::max;
+using std::min;
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+enum UnsetAction
+{
+ eUnsetInitial,
+ eUnsetInherit
+};
+
+} // namespace mozilla
+
+void*
+nsConditionalResetStyleData::GetConditionalStyleData(nsStyleStructID aSID,
+ nsStyleContext* aStyleContext) const
+{
+ Entry* e = static_cast<Entry*>(mEntries[aSID]);
+ MOZ_ASSERT(e, "if mConditionalBits bit is set, we must have at least one "
+ "conditional style struct");
+ do {
+ if (e->mConditions.Matches(aStyleContext)) {
+ void* data = e->mStyleStruct;
+
+ // For reset structs with conditions, we cache the data on the
+ // style context.
+ // Tell the style context that it doesn't own the data
+ aStyleContext->AddStyleBit(GetBitForSID(aSID));
+ aStyleContext->SetStyle(aSID, data);
+
+ return data;
+ }
+ e = e->mNext;
+ } while (e);
+ return nullptr;
+}
+
+// Creates and returns an imgRequestProxy based on the specified
+// value in aValue.
+static imgRequestProxy*
+GetImageRequest(nsPresContext* aPresContext, const nsCSSValue& aValue)
+{
+ return aValue.GetImageValue(aPresContext->Document());
+}
+
+// Creates an imgRequestProxy based on the specified value in
+// aValue and calls aCallback with it. If the nsPresContext
+// is static (e.g. for printing), then a static request (i.e.
+// showing the first frame, without animation) will be created.
+// (The expectation is then that aCallback will set the resulting
+// imgRequestProxy in a style struct somewhere.)
+static void
+SetImageRequest(function<void(imgRequestProxy*)> aCallback,
+ nsPresContext* aPresContext,
+ const nsCSSValue& aValue)
+{
+ RefPtr<imgRequestProxy> req =
+ aValue.GetPossiblyStaticImageValue(aPresContext->Document(),
+ aPresContext);
+ aCallback(req);
+}
+
+static void
+SetStyleImageRequest(function<void(nsStyleImageRequest*)> aCallback,
+ nsPresContext* aPresContext,
+ const nsCSSValue& aValue,
+ nsStyleImageRequest::Mode aModeFlags =
+ nsStyleImageRequest::Mode::Track)
+{
+ SetImageRequest([&](imgRequestProxy* aProxy) {
+ RefPtr<nsStyleImageRequest> request;
+ if (aProxy) {
+ css::ImageValue* imageValue = aValue.GetImageStructValue();
+ ImageTracker* imageTracker =
+ (aModeFlags & nsStyleImageRequest::Mode::Track)
+ ? aPresContext->Document()->ImageTracker()
+ : nullptr;
+ request =
+ new nsStyleImageRequest(aModeFlags, aProxy, imageValue, imageTracker);
+ }
+ aCallback(request);
+ }, aPresContext, aValue);
+}
+
+template<typename ReferenceBox>
+static void
+SetStyleShapeSourceToCSSValue(StyleShapeSource<ReferenceBox>* aShapeSource,
+ const nsCSSValue* aValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions);
+
+/* Helper function to convert a CSS <position> specified value into its
+ * computed-style form. */
+static void
+ComputePositionValue(nsStyleContext* aStyleContext,
+ const nsCSSValue& aValue,
+ Position& aComputedValue,
+ RuleNodeCacheConditions& aConditions);
+
+/*
+ * For storage of an |nsRuleNode|'s children in a PLDHashTable.
+ */
+
+struct ChildrenHashEntry : public PLDHashEntryHdr {
+ // key is |mRuleNode->GetKey()|
+ nsRuleNode *mRuleNode;
+};
+
+/* static */ PLDHashNumber
+nsRuleNode::ChildrenHashHashKey(const void *aKey)
+{
+ const nsRuleNode::Key *key =
+ static_cast<const nsRuleNode::Key*>(aKey);
+ // Disagreement on importance and level for the same rule is extremely
+ // rare, so hash just on the rule.
+ return PLDHashTable::HashVoidPtrKeyStub(key->mRule);
+}
+
+/* static */ bool
+nsRuleNode::ChildrenHashMatchEntry(const PLDHashEntryHdr *aHdr,
+ const void *aKey)
+{
+ const ChildrenHashEntry *entry =
+ static_cast<const ChildrenHashEntry*>(aHdr);
+ const nsRuleNode::Key *key =
+ static_cast<const nsRuleNode::Key*>(aKey);
+ return entry->mRuleNode->GetKey() == *key;
+}
+
+/* static */ const PLDHashTableOps
+nsRuleNode::ChildrenHashOps = {
+ // It's probably better to allocate the table itself using malloc and
+ // free rather than the pres shell's arena because the table doesn't
+ // grow very often and the pres shell's arena doesn't recycle very
+ // large size allocations.
+ ChildrenHashHashKey,
+ ChildrenHashMatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+
+// EnsureBlockDisplay:
+// Never change display:none or display:contents *ever*, otherwise:
+// - if the display value (argument) is not a block-type
+// then we set it to a valid block display value
+// - For enforcing the floated/positioned element CSS2 rules
+// - We allow the behavior of "list-item" to be customized.
+// CSS21 says that position/float do not convert 'list-item' to 'block',
+// but it explicitly does not define whether 'list-item' should be
+// converted to block *on the root node*. To allow for flexibility
+// (so that we don't have to support a list-item root node), this method
+// lets the caller pick either behavior, using the 'aConvertListItem' arg.
+// Reference: http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
+/* static */
+void
+nsRuleNode::EnsureBlockDisplay(StyleDisplay& display,
+ bool aConvertListItem /* = false */)
+{
+ // see if the display value is already a block
+ switch (display) {
+ case StyleDisplay::ListItem:
+ if (aConvertListItem) {
+ display = StyleDisplay::Block;
+ break;
+ } // else, fall through to share the 'break' for non-changing display vals
+ MOZ_FALLTHROUGH;
+ case StyleDisplay::None:
+ case StyleDisplay::Contents:
+ // never change display:none or display:contents *ever*
+ case StyleDisplay::Table:
+ case StyleDisplay::Block:
+ case StyleDisplay::Flex:
+ case StyleDisplay::WebkitBox:
+ case StyleDisplay::Grid:
+ // do not muck with these at all - already blocks
+ // This is equivalent to nsStyleDisplay::IsBlockOutside. (XXX Maybe we
+ // should just call that?)
+ // This needs to match the check done in
+ // nsCSSFrameConstructor::FindMathMLData for <math>.
+ break;
+
+ case StyleDisplay::InlineTable:
+ // make inline tables into tables
+ display = StyleDisplay::Table;
+ break;
+
+ case StyleDisplay::InlineFlex:
+ // make inline flex containers into flex containers
+ display = StyleDisplay::Flex;
+ break;
+
+ case StyleDisplay::WebkitInlineBox:
+ // make -webkit-inline-box containers into -webkit-box containers
+ display = StyleDisplay::WebkitBox;
+ break;
+
+ case StyleDisplay::InlineGrid:
+ // make inline grid containers into grid containers
+ display = StyleDisplay::Grid;
+ break;
+
+ default:
+ // make it a block
+ display = StyleDisplay::Block;
+ }
+}
+
+// EnsureInlineDisplay:
+// - if the display value (argument) is not an inline type
+// then we set it to a valid inline display value
+/* static */
+void
+nsRuleNode::EnsureInlineDisplay(StyleDisplay& display)
+{
+ // see if the display value is already inline
+ switch (display) {
+ case StyleDisplay::Block:
+ display = StyleDisplay::InlineBlock;
+ break;
+ case StyleDisplay::Table:
+ display = StyleDisplay::InlineTable;
+ break;
+ case StyleDisplay::Flex:
+ display = StyleDisplay::InlineFlex;
+ break;
+ case StyleDisplay::WebkitBox:
+ display = StyleDisplay::WebkitInlineBox;
+ break;
+ case StyleDisplay::Grid:
+ display = StyleDisplay::InlineGrid;
+ break;
+ case StyleDisplay::Box:
+ display = StyleDisplay::InlineBox;
+ break;
+ case StyleDisplay::Stack:
+ display = StyleDisplay::InlineStack;
+ break;
+ default:
+ break; // Do nothing
+ }
+}
+
+static nscoord CalcLengthWith(const nsCSSValue& aValue,
+ nscoord aFontSize,
+ const nsStyleFont* aStyleFont,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ bool aUseProvidedRootEmSize,
+ bool aUseUserFontSet,
+ RuleNodeCacheConditions& aConditions);
+
+struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
+ public css::NumbersAlreadyNormalizedOps
+{
+ // All of the parameters to CalcLengthWith except aValue.
+ const nscoord mFontSize;
+ const nsStyleFont* const mStyleFont;
+ nsStyleContext* const mStyleContext;
+ nsPresContext* const mPresContext;
+ const bool mUseProvidedRootEmSize;
+ const bool mUseUserFontSet;
+ RuleNodeCacheConditions& mConditions;
+
+ CalcLengthCalcOps(nscoord aFontSize, const nsStyleFont* aStyleFont,
+ nsStyleContext* aStyleContext, nsPresContext* aPresContext,
+ bool aUseProvidedRootEmSize, bool aUseUserFontSet,
+ RuleNodeCacheConditions& aConditions)
+ : mFontSize(aFontSize),
+ mStyleFont(aStyleFont),
+ mStyleContext(aStyleContext),
+ mPresContext(aPresContext),
+ mUseProvidedRootEmSize(aUseProvidedRootEmSize),
+ mUseUserFontSet(aUseUserFontSet),
+ mConditions(aConditions)
+ {
+ }
+
+ result_type ComputeLeafValue(const nsCSSValue& aValue)
+ {
+ return CalcLengthWith(aValue, mFontSize, mStyleFont,
+ mStyleContext, mPresContext, mUseProvidedRootEmSize,
+ mUseUserFontSet, mConditions);
+ }
+};
+
+static inline nscoord ScaleCoordRound(const nsCSSValue& aValue, float aFactor)
+{
+ return NSToCoordRoundWithClamp(aValue.GetFloatValue() * aFactor);
+}
+
+static inline nscoord ScaleViewportCoordTrunc(const nsCSSValue& aValue,
+ nscoord aViewportSize)
+{
+ // For units (like percentages and viewport units) where authors might
+ // repeatedly use a value and expect some multiple of the value to be
+ // smaller than a container, we need to use floor rather than round.
+ // We need to use division by 100.0 rather than multiplication by 0.1f
+ // to avoid introducing error.
+ return NSToCoordTruncClamped(aValue.GetFloatValue() *
+ aViewportSize / 100.0f);
+}
+
+already_AddRefed<nsFontMetrics>
+GetMetricsFor(nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ const nsStyleFont* aStyleFont,
+ nscoord aFontSize, // overrides value from aStyleFont
+ bool aUseUserFontSet)
+{
+ nsFont font = aStyleFont->mFont;
+ font.size = aFontSize;
+ gfxFont::Orientation orientation = gfxFont::eHorizontal;
+ if (aStyleContext) {
+ WritingMode wm(aStyleContext);
+ if (wm.IsVertical() && !wm.IsSideways()) {
+ orientation = gfxFont::eVertical;
+ }
+ }
+ nsFontMetrics::Params params;
+ params.language = aStyleFont->mLanguage;
+ params.explicitLanguage = aStyleFont->mExplicitLanguage;
+ params.orientation = orientation;
+ params.userFontSet =
+ aUseUserFontSet ? aPresContext->GetUserFontSet() : nullptr;
+ params.textPerf = aPresContext->GetTextPerfMetrics();
+ return aPresContext->DeviceContext()->GetMetricsFor(font, params);
+}
+
+
+static nsSize CalcViewportUnitsScale(nsPresContext* aPresContext)
+{
+ // The caller is making use of viewport units, so notify the pres context
+ // that it will need to rebuild the rule tree if the size of the viewport
+ // changes.
+ aPresContext->SetUsesViewportUnits(true);
+
+ // The default (when we have 'overflow: auto' on the root element, or
+ // trivially for 'overflow: hidden' since we never have scrollbars in that
+ // case) is to define the scale of the viewport units without considering
+ // scrollbars.
+ nsSize viewportSize(aPresContext->GetVisibleArea().Size());
+
+ // Check for 'overflow: scroll' styles on the root scroll frame. If we find
+ // any, the standard requires us to take scrollbars into account.
+ nsIScrollableFrame* scrollFrame =
+ aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
+ if (scrollFrame) {
+ ScrollbarStyles styles(scrollFrame->GetScrollbarStyles());
+
+ if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL ||
+ styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
+ // Gather scrollbar size information.
+ nsRenderingContext context(
+ aPresContext->PresShell()->CreateReferenceRenderingContext());
+ nsMargin sizes(scrollFrame->GetDesiredScrollbarSizes(aPresContext, &context));
+
+ if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
+ // 'overflow-x: scroll' means we must consider the horizontal scrollbar,
+ // which affects the scale of viewport height units.
+ viewportSize.height -= sizes.TopBottom();
+ }
+
+ if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
+ // 'overflow-y: scroll' means we must consider the vertical scrollbar,
+ // which affects the scale of viewport width units.
+ viewportSize.width -= sizes.LeftRight();
+ }
+ }
+ }
+
+ return viewportSize;
+}
+
+// If |aStyleFont| is nullptr, aStyleContext->StyleFont() is used.
+//
+// In case that |aValue| is rem unit, if |aStyleContext| is null, callers must
+// specify a valid |aStyleFont| and |aUseProvidedRootEmSize| must be true so
+// that we can get the length from |aStyleFont|.
+static nscoord CalcLengthWith(const nsCSSValue& aValue,
+ nscoord aFontSize,
+ const nsStyleFont* aStyleFont,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ bool aUseProvidedRootEmSize,
+ // aUseUserFontSet should always be true
+ // except when called from
+ // CalcLengthWithInitialFont.
+ bool aUseUserFontSet,
+ RuleNodeCacheConditions& aConditions)
+{
+ NS_ASSERTION(aValue.IsLengthUnit() || aValue.IsCalcUnit(),
+ "not a length or calc unit");
+ NS_ASSERTION(aStyleFont || aStyleContext,
+ "Must have style data");
+ NS_ASSERTION(aStyleContext || aUseProvidedRootEmSize,
+ "Must have style context or specify aUseProvidedRootEmSize");
+ NS_ASSERTION(aPresContext, "Must have prescontext");
+
+ if (aValue.IsFixedLengthUnit()) {
+ return aValue.GetFixedLength(aPresContext);
+ }
+ if (aValue.IsPixelLengthUnit()) {
+ return aValue.GetPixelLength();
+ }
+ if (aValue.IsCalcUnit()) {
+ // For properties for which lengths are the *only* units accepted in
+ // calc(), we can handle calc() here and just compute a final
+ // result. We ensure that we don't get to this code for other
+ // properties by not calling CalcLength in those cases: SetCoord
+ // only calls CalcLength for a calc when it is appropriate to do so.
+ CalcLengthCalcOps ops(aFontSize, aStyleFont,
+ aStyleContext, aPresContext,
+ aUseProvidedRootEmSize, aUseUserFontSet,
+ aConditions);
+ return css::ComputeCalc(aValue, ops);
+ }
+ switch (aValue.GetUnit()) {
+ // nsPresContext::SetVisibleArea and
+ // nsPresContext::MediaFeatureValuesChanged handle dynamic changes
+ // of the basis for viewport units by rebuilding the rule tree and
+ // style context tree. Not caching them in the rule tree wouldn't
+ // be sufficient to handle these changes because we also need a way
+ // to get rid of cached values in the style context tree without any
+ // changes in specified style. We can either do this by not caching
+ // in the rule tree and then throwing away the style context tree
+ // for dynamic viewport size changes, or by allowing caching in the
+ // rule tree and using the existing rebuild style data path that
+ // throws away the style context and the rule tree.
+ // Thus we do cache viewport units in the rule tree. This allows us
+ // to benefit from the performance advantages of the rule tree
+ // (e.g., faster dynamic changes on other things, like transforms)
+ // and allows us not to need an additional code path, in exchange
+ // for an increased cost to dynamic changes to the viewport size
+ // when viewport units are in use.
+ case eCSSUnit_ViewportWidth: {
+ nscoord viewportWidth = CalcViewportUnitsScale(aPresContext).width;
+ return ScaleViewportCoordTrunc(aValue, viewportWidth);
+ }
+ case eCSSUnit_ViewportHeight: {
+ nscoord viewportHeight = CalcViewportUnitsScale(aPresContext).height;
+ return ScaleViewportCoordTrunc(aValue, viewportHeight);
+ }
+ case eCSSUnit_ViewportMin: {
+ nsSize vuScale(CalcViewportUnitsScale(aPresContext));
+ nscoord viewportMin = min(vuScale.width, vuScale.height);
+ return ScaleViewportCoordTrunc(aValue, viewportMin);
+ }
+ case eCSSUnit_ViewportMax: {
+ nsSize vuScale(CalcViewportUnitsScale(aPresContext));
+ nscoord viewportMax = max(vuScale.width, vuScale.height);
+ return ScaleViewportCoordTrunc(aValue, viewportMax);
+ }
+ // While we could deal with 'rem' units correctly by simply not
+ // caching any data that uses them in the rule tree, it's valuable
+ // to store them in the rule tree (for faster dynamic changes of
+ // other things). And since the font size of the root element
+ // changes rarely, we instead handle dynamic changes to the root
+ // element's font size by rebuilding all style data in
+ // nsCSSFrameConstructor::RestyleElement.
+ case eCSSUnit_RootEM: {
+ aPresContext->SetUsesRootEMUnits(true);
+ nscoord rootFontSize;
+
+ // NOTE: Be very careful with |styleFont|, since we haven't added any
+ // conditions to aConditions or set it to uncacheable yet, so we don't
+ // want to introduce any dependencies on aStyleContext's data here.
+ const nsStyleFont *styleFont =
+ aStyleFont ? aStyleFont : aStyleContext->StyleFont();
+
+ if (aUseProvidedRootEmSize) {
+ // We should use the provided aFontSize as the reference length to
+ // scale. This only happens when we are calculating font-size or
+ // an equivalent (scriptminsize or CalcLengthWithInitialFont) on
+ // the root element, in which case aFontSize is already the
+ // value we want.
+ if (aFontSize == -1) {
+ // XXX Should this be styleFont->mSize instead to avoid taking
+ // minfontsize prefs into account?
+ aFontSize = styleFont->mFont.size;
+ }
+ rootFontSize = aFontSize;
+ } else if (aStyleContext && !aStyleContext->GetParent()) {
+ // This is the root element (XXX we don't really know this, but
+ // nsRuleNode::SetFont makes the same assumption!), so we should
+ // use StyleFont on this context to get the root element's
+ // font size.
+ rootFontSize = styleFont->mFont.size;
+ } else {
+ // This is not the root element or we are calculating something other
+ // than font size, so rem is relative to the root element's font size.
+ // Find the root style context by walking up the style context tree.
+ nsStyleContext* rootStyle = aStyleContext;
+ while (rootStyle->GetParent()) {
+ rootStyle = rootStyle->GetParent();
+ }
+
+ const nsStyleFont *rootStyleFont = rootStyle->StyleFont();
+ rootFontSize = rootStyleFont->mFont.size;
+ }
+
+ return ScaleCoordRound(aValue, float(rootFontSize));
+ }
+ default:
+ // Fall through to the code for units that can't be stored in the
+ // rule tree because they depend on font data.
+ break;
+ }
+ // Common code for units that depend on the element's font data and
+ // thus can't be stored in the rule tree:
+ const nsStyleFont *styleFont =
+ aStyleFont ? aStyleFont : aStyleContext->StyleFont();
+ if (aFontSize == -1) {
+ // XXX Should this be styleFont->mSize instead to avoid taking minfontsize
+ // prefs into account?
+ aFontSize = styleFont->mFont.size;
+ }
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_EM: {
+ if (aValue.GetFloatValue() == 0.0f) {
+ // Don't call SetFontSizeDependency for '0em'.
+ return 0;
+ }
+ // CSS2.1 specifies that this unit scales to the computed font
+ // size, not the em-width in the font metrics, despite the name.
+ aConditions.SetFontSizeDependency(aFontSize);
+ return ScaleCoordRound(aValue, float(aFontSize));
+ }
+ case eCSSUnit_XHeight: {
+ aPresContext->SetUsesExChUnits(true);
+ RefPtr<nsFontMetrics> fm =
+ GetMetricsFor(aPresContext, aStyleContext, styleFont,
+ aFontSize, aUseUserFontSet);
+ aConditions.SetUncacheable();
+ return ScaleCoordRound(aValue, float(fm->XHeight()));
+ }
+ case eCSSUnit_Char: {
+ aPresContext->SetUsesExChUnits(true);
+ RefPtr<nsFontMetrics> fm =
+ GetMetricsFor(aPresContext, aStyleContext, styleFont,
+ aFontSize, aUseUserFontSet);
+ gfxFloat zeroWidth =
+ fm->GetThebesFontGroup()->GetFirstValidFont()->
+ GetMetrics(fm->Orientation()).zeroOrAveCharWidth;
+
+ aConditions.SetUncacheable();
+ return ScaleCoordRound(aValue, ceil(aPresContext->AppUnitsPerDevPixel() *
+ zeroWidth));
+ }
+ default:
+ NS_NOTREACHED("unexpected unit");
+ break;
+ }
+ return 0;
+}
+
+/* static */ nscoord
+nsRuleNode::CalcLength(const nsCSSValue& aValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ NS_ASSERTION(aStyleContext, "Must have style data");
+
+ return CalcLengthWith(aValue, -1, nullptr,
+ aStyleContext, aPresContext,
+ false, true, aConditions);
+}
+
+/* Inline helper function to redirect requests to CalcLength. */
+static inline nscoord CalcLength(const nsCSSValue& aValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ return nsRuleNode::CalcLength(aValue, aStyleContext,
+ aPresContext, aConditions);
+}
+
+/* static */ nscoord
+nsRuleNode::CalcLengthWithInitialFont(nsPresContext* aPresContext,
+ const nsCSSValue& aValue)
+{
+ nsStyleFont defaultFont(aPresContext); // FIXME: best language?
+ RuleNodeCacheConditions conditions;
+ return CalcLengthWith(aValue, -1, &defaultFont,
+ nullptr, aPresContext,
+ true, false, conditions);
+}
+
+struct LengthPercentPairCalcOps : public css::NumbersAlreadyNormalizedOps
+{
+ typedef nsRuleNode::ComputedCalc result_type;
+
+ LengthPercentPairCalcOps(nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+ : mContext(aContext),
+ mPresContext(aPresContext),
+ mConditions(aConditions),
+ mHasPercent(false) {}
+
+ nsStyleContext* mContext;
+ nsPresContext* mPresContext;
+ RuleNodeCacheConditions& mConditions;
+ bool mHasPercent;
+
+ result_type ComputeLeafValue(const nsCSSValue& aValue)
+ {
+ if (aValue.GetUnit() == eCSSUnit_Percent) {
+ mHasPercent = true;
+ return result_type(0, aValue.GetPercentValue());
+ }
+ return result_type(CalcLength(aValue, mContext, mPresContext,
+ mConditions),
+ 0.0f);
+ }
+
+ result_type
+ MergeAdditive(nsCSSUnit aCalcFunction,
+ result_type aValue1, result_type aValue2)
+ {
+ if (aCalcFunction == eCSSUnit_Calc_Plus) {
+ return result_type(NSCoordSaturatingAdd(aValue1.mLength,
+ aValue2.mLength),
+ aValue1.mPercent + aValue2.mPercent);
+ }
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
+ "min() and max() are not allowed in calc() on transform");
+ return result_type(NSCoordSaturatingSubtract(aValue1.mLength,
+ aValue2.mLength, 0),
+ aValue1.mPercent - aValue2.mPercent);
+ }
+
+ result_type
+ MergeMultiplicativeL(nsCSSUnit aCalcFunction,
+ float aValue1, result_type aValue2)
+ {
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
+ "unexpected unit");
+ return result_type(NSCoordSaturatingMultiply(aValue2.mLength, aValue1),
+ aValue1 * aValue2.mPercent);
+ }
+
+ result_type
+ MergeMultiplicativeR(nsCSSUnit aCalcFunction,
+ result_type aValue1, float aValue2)
+ {
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_R ||
+ aCalcFunction == eCSSUnit_Calc_Divided,
+ "unexpected unit");
+ if (aCalcFunction == eCSSUnit_Calc_Divided) {
+ aValue2 = 1.0f / aValue2;
+ }
+ return result_type(NSCoordSaturatingMultiply(aValue1.mLength, aValue2),
+ aValue1.mPercent * aValue2);
+ }
+
+};
+
+static void
+SpecifiedCalcToComputedCalc(const nsCSSValue& aValue, nsStyleCoord& aCoord,
+ nsStyleContext* aStyleContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ LengthPercentPairCalcOps ops(aStyleContext, aStyleContext->PresContext(),
+ aConditions);
+ nsRuleNode::ComputedCalc vals = ComputeCalc(aValue, ops);
+
+ nsStyleCoord::Calc* calcObj = new nsStyleCoord::Calc;
+
+ calcObj->mLength = vals.mLength;
+ calcObj->mPercent = vals.mPercent;
+ calcObj->mHasPercent = ops.mHasPercent;
+
+ aCoord.SetCalcValue(calcObj);
+}
+
+/* static */ nsRuleNode::ComputedCalc
+nsRuleNode::SpecifiedCalcToComputedCalc(const nsCSSValue& aValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ LengthPercentPairCalcOps ops(aStyleContext, aPresContext,
+ aConditions);
+ return ComputeCalc(aValue, ops);
+}
+
+// This is our public API for handling calc() expressions that involve
+// percentages.
+/* static */ nscoord
+nsRuleNode::ComputeComputedCalc(const nsStyleCoord& aValue,
+ nscoord aPercentageBasis)
+{
+ nsStyleCoord::Calc* calc = aValue.GetCalcValue();
+ return calc->mLength +
+ NSToCoordFloorClamped(aPercentageBasis * calc->mPercent);
+}
+
+/* static */ nscoord
+nsRuleNode::ComputeCoordPercentCalc(const nsStyleCoord& aCoord,
+ nscoord aPercentageBasis)
+{
+ switch (aCoord.GetUnit()) {
+ case eStyleUnit_Coord:
+ return aCoord.GetCoordValue();
+ case eStyleUnit_Percent:
+ return NSToCoordFloorClamped(aPercentageBasis * aCoord.GetPercentValue());
+ case eStyleUnit_Calc:
+ return ComputeComputedCalc(aCoord, aPercentageBasis);
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ return 0;
+ }
+}
+
+/* Given an enumerated value that represents a box position, converts it to
+ * a float representing the percentage of the box it corresponds to. For
+ * example, "center" becomes 0.5f.
+ *
+ * @param aEnumValue The enumerated value.
+ * @return The float percent it corresponds to.
+ */
+static float
+GetFloatFromBoxPosition(int32_t aEnumValue)
+{
+ switch (aEnumValue) {
+ case NS_STYLE_IMAGELAYER_POSITION_LEFT:
+ case NS_STYLE_IMAGELAYER_POSITION_TOP:
+ return 0.0f;
+ case NS_STYLE_IMAGELAYER_POSITION_RIGHT:
+ case NS_STYLE_IMAGELAYER_POSITION_BOTTOM:
+ return 1.0f;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("unexpected box position value");
+ case NS_STYLE_IMAGELAYER_POSITION_CENTER:
+ return 0.5f;
+ }
+}
+
+#define SETCOORD_NORMAL 0x01 // N
+#define SETCOORD_AUTO 0x02 // A
+#define SETCOORD_INHERIT 0x04 // H
+#define SETCOORD_PERCENT 0x08 // P
+#define SETCOORD_FACTOR 0x10 // F
+#define SETCOORD_LENGTH 0x20 // L
+#define SETCOORD_INTEGER 0x40 // I
+#define SETCOORD_ENUMERATED 0x80 // E
+#define SETCOORD_NONE 0x100 // O
+#define SETCOORD_INITIAL_ZERO 0x200
+#define SETCOORD_INITIAL_AUTO 0x400
+#define SETCOORD_INITIAL_NONE 0x800
+#define SETCOORD_INITIAL_NORMAL 0x1000
+#define SETCOORD_INITIAL_HALF 0x2000
+#define SETCOORD_INITIAL_HUNDRED_PCT 0x00004000
+#define SETCOORD_INITIAL_FACTOR_ONE 0x00008000
+#define SETCOORD_INITIAL_FACTOR_ZERO 0x00010000
+#define SETCOORD_CALC_LENGTH_ONLY 0x00020000
+#define SETCOORD_CALC_CLAMP_NONNEGATIVE 0x00040000 // modifier for CALC_LENGTH_ONLY
+#define SETCOORD_STORE_CALC 0x00080000
+#define SETCOORD_BOX_POSITION 0x00100000 // exclusive with _ENUMERATED
+#define SETCOORD_ANGLE 0x00200000
+#define SETCOORD_UNSET_INHERIT 0x00400000
+#define SETCOORD_UNSET_INITIAL 0x00800000
+
+#define SETCOORD_LP (SETCOORD_LENGTH | SETCOORD_PERCENT)
+#define SETCOORD_LH (SETCOORD_LENGTH | SETCOORD_INHERIT)
+#define SETCOORD_AH (SETCOORD_AUTO | SETCOORD_INHERIT)
+#define SETCOORD_LAH (SETCOORD_AUTO | SETCOORD_LENGTH | SETCOORD_INHERIT)
+#define SETCOORD_LPH (SETCOORD_LP | SETCOORD_INHERIT)
+#define SETCOORD_LPAH (SETCOORD_LP | SETCOORD_AH)
+#define SETCOORD_LPE (SETCOORD_LP | SETCOORD_ENUMERATED)
+#define SETCOORD_LPEH (SETCOORD_LPE | SETCOORD_INHERIT)
+#define SETCOORD_LPAEH (SETCOORD_LPAH | SETCOORD_ENUMERATED)
+#define SETCOORD_LPO (SETCOORD_LP | SETCOORD_NONE)
+#define SETCOORD_LPOH (SETCOORD_LPH | SETCOORD_NONE)
+#define SETCOORD_LPOEH (SETCOORD_LPOH | SETCOORD_ENUMERATED)
+#define SETCOORD_LE (SETCOORD_LENGTH | SETCOORD_ENUMERATED)
+#define SETCOORD_LEH (SETCOORD_LE | SETCOORD_INHERIT)
+#define SETCOORD_IA (SETCOORD_INTEGER | SETCOORD_AUTO)
+#define SETCOORD_LAE (SETCOORD_LENGTH | SETCOORD_AUTO | SETCOORD_ENUMERATED)
+
+// changes aCoord iff it returns true
+static bool SetCoord(const nsCSSValue& aValue, nsStyleCoord& aCoord,
+ const nsStyleCoord& aParentCoord,
+ int32_t aMask, nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ bool result = true;
+ if (aValue.GetUnit() == eCSSUnit_Null) {
+ result = false;
+ }
+ else if ((((aMask & SETCOORD_LENGTH) != 0) &&
+ aValue.IsLengthUnit()) ||
+ (((aMask & SETCOORD_CALC_LENGTH_ONLY) != 0) &&
+ aValue.IsCalcUnit())) {
+ nscoord len = CalcLength(aValue, aStyleContext, aPresContext,
+ aConditions);
+ if ((aMask & SETCOORD_CALC_CLAMP_NONNEGATIVE) && len < 0) {
+ NS_ASSERTION(aValue.IsCalcUnit(),
+ "parser should have ensured no nonnegative lengths");
+ len = 0;
+ }
+ aCoord.SetCoordValue(len);
+ }
+ else if (((aMask & SETCOORD_PERCENT) != 0) &&
+ (aValue.GetUnit() == eCSSUnit_Percent)) {
+ aCoord.SetPercentValue(aValue.GetPercentValue());
+ }
+ else if (((aMask & SETCOORD_INTEGER) != 0) &&
+ (aValue.GetUnit() == eCSSUnit_Integer)) {
+ aCoord.SetIntValue(aValue.GetIntValue(), eStyleUnit_Integer);
+ }
+ else if (((aMask & SETCOORD_ENUMERATED) != 0) &&
+ (aValue.GetUnit() == eCSSUnit_Enumerated)) {
+ aCoord.SetIntValue(aValue.GetIntValue(), eStyleUnit_Enumerated);
+ }
+ else if (((aMask & SETCOORD_BOX_POSITION) != 0) &&
+ (aValue.GetUnit() == eCSSUnit_Enumerated)) {
+ aCoord.SetPercentValue(GetFloatFromBoxPosition(aValue.GetIntValue()));
+ }
+ else if (((aMask & SETCOORD_AUTO) != 0) &&
+ (aValue.GetUnit() == eCSSUnit_Auto)) {
+ aCoord.SetAutoValue();
+ }
+ else if ((((aMask & SETCOORD_INHERIT) != 0) &&
+ aValue.GetUnit() == eCSSUnit_Inherit) ||
+ (((aMask & SETCOORD_UNSET_INHERIT) != 0) &&
+ aValue.GetUnit() == eCSSUnit_Unset)) {
+ aCoord = aParentCoord; // just inherit value from parent
+ aConditions.SetUncacheable();
+ }
+ else if (((aMask & SETCOORD_NORMAL) != 0) &&
+ (aValue.GetUnit() == eCSSUnit_Normal)) {
+ aCoord.SetNormalValue();
+ }
+ else if (((aMask & SETCOORD_NONE) != 0) &&
+ (aValue.GetUnit() == eCSSUnit_None)) {
+ aCoord.SetNoneValue();
+ }
+ else if (((aMask & SETCOORD_FACTOR) != 0) &&
+ (aValue.GetUnit() == eCSSUnit_Number)) {
+ aCoord.SetFactorValue(aValue.GetFloatValue());
+ }
+ else if (((aMask & SETCOORD_STORE_CALC) != 0) &&
+ (aValue.IsCalcUnit())) {
+ SpecifiedCalcToComputedCalc(aValue, aCoord, aStyleContext,
+ aConditions);
+ }
+ else if (aValue.GetUnit() == eCSSUnit_Initial ||
+ (aValue.GetUnit() == eCSSUnit_Unset &&
+ ((aMask & SETCOORD_UNSET_INITIAL) != 0))) {
+ if ((aMask & SETCOORD_INITIAL_AUTO) != 0) {
+ aCoord.SetAutoValue();
+ }
+ else if ((aMask & SETCOORD_INITIAL_ZERO) != 0) {
+ aCoord.SetCoordValue(0);
+ }
+ else if ((aMask & SETCOORD_INITIAL_FACTOR_ZERO) != 0) {
+ aCoord.SetFactorValue(0.0f);
+ }
+ else if ((aMask & SETCOORD_INITIAL_NONE) != 0) {
+ aCoord.SetNoneValue();
+ }
+ else if ((aMask & SETCOORD_INITIAL_NORMAL) != 0) {
+ aCoord.SetNormalValue();
+ }
+ else if ((aMask & SETCOORD_INITIAL_HALF) != 0) {
+ aCoord.SetPercentValue(0.5f);
+ }
+ else if ((aMask & SETCOORD_INITIAL_HUNDRED_PCT) != 0) {
+ aCoord.SetPercentValue(1.0f);
+ }
+ else if ((aMask & SETCOORD_INITIAL_FACTOR_ONE) != 0) {
+ aCoord.SetFactorValue(1.0f);
+ }
+ else {
+ result = false; // didn't set anything
+ }
+ }
+ else if ((aMask & SETCOORD_ANGLE) != 0 &&
+ (aValue.IsAngularUnit())) {
+ nsStyleUnit unit;
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Degree: unit = eStyleUnit_Degree; break;
+ case eCSSUnit_Grad: unit = eStyleUnit_Grad; break;
+ case eCSSUnit_Radian: unit = eStyleUnit_Radian; break;
+ case eCSSUnit_Turn: unit = eStyleUnit_Turn; break;
+ default: NS_NOTREACHED("unrecognized angular unit");
+ unit = eStyleUnit_Degree;
+ }
+ aCoord.SetAngleValue(aValue.GetAngleValue(), unit);
+ }
+ else {
+ result = false; // didn't set anything
+ }
+ return result;
+}
+
+// This inline function offers a shortcut for SetCoord() by refusing to accept
+// SETCOORD_LENGTH, SETCOORD_INHERIT and SETCOORD_UNSET_* masks.
+static inline bool SetAbsCoord(const nsCSSValue& aValue,
+ nsStyleCoord& aCoord,
+ int32_t aMask)
+{
+ MOZ_ASSERT((aMask & (SETCOORD_LH | SETCOORD_UNSET_INHERIT |
+ SETCOORD_UNSET_INITIAL)) == 0,
+ "does not handle SETCOORD_LENGTH, SETCOORD_INHERIT and "
+ "SETCOORD_UNSET_*");
+
+ // The values of the following variables will never be used; so it does not
+ // matter what to set.
+ const nsStyleCoord dummyParentCoord;
+ nsStyleContext* dummyStyleContext = nullptr;
+ nsPresContext* dummyPresContext = nullptr;
+ RuleNodeCacheConditions dummyCacheKey;
+
+ bool rv = SetCoord(aValue, aCoord, dummyParentCoord, aMask,
+ dummyStyleContext, dummyPresContext,
+ dummyCacheKey);
+ MOZ_ASSERT(dummyCacheKey.CacheableWithoutDependencies(),
+ "SetCoord() should not modify dummyCacheKey.");
+
+ return rv;
+}
+
+/* Given a specified value that might be a pair value, call SetCoord twice,
+ * either using each member of the pair, or using the unpaired value twice.
+ */
+static bool
+SetPairCoords(const nsCSSValue& aValue,
+ nsStyleCoord& aCoordX, nsStyleCoord& aCoordY,
+ const nsStyleCoord& aParentX, const nsStyleCoord& aParentY,
+ int32_t aMask, nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext, RuleNodeCacheConditions& aConditions)
+{
+ const nsCSSValue& valX =
+ aValue.GetUnit() == eCSSUnit_Pair ? aValue.GetPairValue().mXValue : aValue;
+ const nsCSSValue& valY =
+ aValue.GetUnit() == eCSSUnit_Pair ? aValue.GetPairValue().mYValue : aValue;
+
+ bool cX = SetCoord(valX, aCoordX, aParentX, aMask, aStyleContext,
+ aPresContext, aConditions);
+ mozilla::DebugOnly<bool> cY = SetCoord(valY, aCoordY, aParentY, aMask,
+ aStyleContext, aPresContext, aConditions);
+ MOZ_ASSERT(cX == cY, "changed one but not the other");
+ return cX;
+}
+
+static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor,
+ nsPresContext* aPresContext, nsStyleContext *aContext,
+ nscolor& aResult, RuleNodeCacheConditions& aConditions)
+{
+ bool result = false;
+ nsCSSUnit unit = aValue.GetUnit();
+
+ if (aValue.IsNumericColorUnit()) {
+ aResult = aValue.GetColorValue();
+ result = true;
+ }
+ else if (eCSSUnit_Ident == unit) {
+ nsAutoString value;
+ aValue.GetStringValue(value);
+ nscolor rgba;
+ if (NS_ColorNameToRGB(value, &rgba)) {
+ aResult = rgba;
+ result = true;
+ }
+ }
+ else if (eCSSUnit_EnumColor == unit) {
+ int32_t intValue = aValue.GetIntValue();
+ if (0 <= intValue) {
+ LookAndFeel::ColorID colorID = (LookAndFeel::ColorID) intValue;
+ bool useStandinsForNativeColors = aPresContext &&
+ !aPresContext->IsChrome();
+ if (NS_SUCCEEDED(LookAndFeel::GetColor(colorID,
+ useStandinsForNativeColors, &aResult))) {
+ result = true;
+ }
+ }
+ else {
+ aResult = NS_RGB(0, 0, 0);
+ result = false;
+ switch (intValue) {
+ case NS_COLOR_MOZ_HYPERLINKTEXT:
+ if (aPresContext) {
+ aResult = aPresContext->DefaultLinkColor();
+ result = true;
+ }
+ break;
+ case NS_COLOR_MOZ_VISITEDHYPERLINKTEXT:
+ if (aPresContext) {
+ aResult = aPresContext->DefaultVisitedLinkColor();
+ result = true;
+ }
+ break;
+ case NS_COLOR_MOZ_ACTIVEHYPERLINKTEXT:
+ if (aPresContext) {
+ aResult = aPresContext->DefaultActiveLinkColor();
+ result = true;
+ }
+ break;
+ case NS_COLOR_CURRENTCOLOR:
+ // The data computed from this can't be shared in the rule tree
+ // because they could be used on a node with a different color
+ aConditions.SetUncacheable();
+ if (aContext) {
+ aResult = aContext->StyleColor()->mColor;
+ result = true;
+ }
+ break;
+ case NS_COLOR_MOZ_DEFAULT_COLOR:
+ if (aPresContext) {
+ aResult = aPresContext->DefaultColor();
+ result = true;
+ }
+ break;
+ case NS_COLOR_MOZ_DEFAULT_BACKGROUND_COLOR:
+ if (aPresContext) {
+ aResult = aPresContext->DefaultBackgroundColor();
+ result = true;
+ }
+ break;
+ default:
+ NS_NOTREACHED("Should never have an unknown negative colorID.");
+ break;
+ }
+ }
+ }
+ else if (eCSSUnit_Inherit == unit) {
+ aResult = aParentColor;
+ result = true;
+ aConditions.SetUncacheable();
+ }
+ else if (eCSSUnit_Enumerated == unit &&
+ aValue.GetIntValue() == NS_STYLE_COLOR_INHERIT_FROM_BODY) {
+ NS_ASSERTION(aPresContext->CompatibilityMode() == eCompatibility_NavQuirks,
+ "Should only get this value in quirks mode");
+ // We just grab the color from the prescontext, and rely on the fact that
+ // if the body color ever changes all its descendants will get new style
+ // contexts (but NOT necessarily new rulenodes).
+ aResult = aPresContext->BodyTextColor();
+ result = true;
+ aConditions.SetUncacheable();
+ }
+ return result;
+}
+
+template<UnsetAction UnsetTo>
+static void
+SetComplexColor(const nsCSSValue& aValue,
+ const StyleComplexColor& aParentColor,
+ const StyleComplexColor& aInitialColor,
+ nsPresContext* aPresContext,
+ StyleComplexColor& aResult,
+ RuleNodeCacheConditions& aConditions)
+{
+ nsCSSUnit unit = aValue.GetUnit();
+ if (unit == eCSSUnit_Null) {
+ return;
+ }
+ if (unit == eCSSUnit_Initial ||
+ (UnsetTo == eUnsetInitial && unit == eCSSUnit_Unset)) {
+ aResult = aInitialColor;
+ } else if (unit == eCSSUnit_Inherit ||
+ (UnsetTo == eUnsetInherit && unit == eCSSUnit_Unset)) {
+ aConditions.SetUncacheable();
+ aResult = aParentColor;
+ } else if (unit == eCSSUnit_EnumColor &&
+ aValue.GetIntValue() == NS_COLOR_CURRENTCOLOR) {
+ aResult = StyleComplexColor::CurrentColor();
+ } else if (unit == eCSSUnit_ComplexColor) {
+ aResult = aValue.GetStyleComplexColorValue();
+ } else {
+ if (!SetColor(aValue, aParentColor.mColor, aPresContext,
+ nullptr, aResult.mColor, aConditions)) {
+ MOZ_ASSERT_UNREACHABLE("Unknown color value");
+ return;
+ }
+ aResult.mForegroundRatio = 0;
+ }
+}
+
+static void SetGradientCoord(const nsCSSValue& aValue, nsPresContext* aPresContext,
+ nsStyleContext* aContext, nsStyleCoord& aResult,
+ RuleNodeCacheConditions& aConditions)
+{
+ // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
+ if (!SetCoord(aValue, aResult, nsStyleCoord(),
+ SETCOORD_LPO | SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC,
+ aContext, aPresContext, aConditions)) {
+ NS_NOTREACHED("unexpected unit for gradient anchor point");
+ aResult.SetNoneValue();
+ }
+}
+
+static void SetGradient(const nsCSSValue& aValue, nsPresContext* aPresContext,
+ nsStyleContext* aContext, nsStyleGradient& aResult,
+ RuleNodeCacheConditions& aConditions)
+{
+ MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Gradient,
+ "The given data is not a gradient");
+
+ const nsCSSValueGradient* gradient = aValue.GetGradientValue();
+
+ if (gradient->mIsExplicitSize) {
+ SetCoord(gradient->GetRadiusX(), aResult.mRadiusX, nsStyleCoord(),
+ SETCOORD_LP | SETCOORD_STORE_CALC,
+ aContext, aPresContext, aConditions);
+ if (gradient->GetRadiusY().GetUnit() != eCSSUnit_None) {
+ SetCoord(gradient->GetRadiusY(), aResult.mRadiusY, nsStyleCoord(),
+ SETCOORD_LP | SETCOORD_STORE_CALC,
+ aContext, aPresContext, aConditions);
+ aResult.mShape = NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL;
+ } else {
+ aResult.mRadiusY = aResult.mRadiusX;
+ aResult.mShape = NS_STYLE_GRADIENT_SHAPE_CIRCULAR;
+ }
+ aResult.mSize = NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE;
+ } else if (gradient->mIsRadial) {
+ if (gradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated) {
+ aResult.mShape = gradient->GetRadialShape().GetIntValue();
+ } else {
+ NS_ASSERTION(gradient->GetRadialShape().GetUnit() == eCSSUnit_None,
+ "bad unit for radial shape");
+ aResult.mShape = NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL;
+ }
+ if (gradient->GetRadialSize().GetUnit() == eCSSUnit_Enumerated) {
+ aResult.mSize = gradient->GetRadialSize().GetIntValue();
+ } else {
+ NS_ASSERTION(gradient->GetRadialSize().GetUnit() == eCSSUnit_None,
+ "bad unit for radial shape");
+ aResult.mSize = NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER;
+ }
+ } else {
+ NS_ASSERTION(gradient->GetRadialShape().GetUnit() == eCSSUnit_None,
+ "bad unit for linear shape");
+ NS_ASSERTION(gradient->GetRadialSize().GetUnit() == eCSSUnit_None,
+ "bad unit for linear size");
+ aResult.mShape = NS_STYLE_GRADIENT_SHAPE_LINEAR;
+ aResult.mSize = NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER;
+ }
+
+ aResult.mLegacySyntax = gradient->mIsLegacySyntax;
+
+ // bg-position
+ SetGradientCoord(gradient->mBgPos.mXValue, aPresContext, aContext,
+ aResult.mBgPosX, aConditions);
+
+ SetGradientCoord(gradient->mBgPos.mYValue, aPresContext, aContext,
+ aResult.mBgPosY, aConditions);
+
+ aResult.mRepeating = gradient->mIsRepeating;
+
+ // angle
+ const nsStyleCoord dummyParentCoord;
+ if (!SetCoord(gradient->mAngle, aResult.mAngle, dummyParentCoord, SETCOORD_ANGLE,
+ aContext, aPresContext, aConditions)) {
+ NS_ASSERTION(gradient->mAngle.GetUnit() == eCSSUnit_None,
+ "bad unit for gradient angle");
+ aResult.mAngle.SetNoneValue();
+ }
+
+ // stops
+ for (uint32_t i = 0; i < gradient->mStops.Length(); i++) {
+ nsStyleGradientStop stop;
+ const nsCSSValueGradientStop &valueStop = gradient->mStops[i];
+
+ if (!SetCoord(valueStop.mLocation, stop.mLocation,
+ nsStyleCoord(), SETCOORD_LPO | SETCOORD_STORE_CALC,
+ aContext, aPresContext, aConditions)) {
+ NS_NOTREACHED("unexpected unit for gradient stop location");
+ }
+
+ stop.mIsInterpolationHint = valueStop.mIsInterpolationHint;
+
+ // inherit is not a valid color for stops, so we pass in a dummy
+ // parent color
+ NS_ASSERTION(valueStop.mColor.GetUnit() != eCSSUnit_Inherit,
+ "inherit is not a valid color for gradient stops");
+ if (!valueStop.mIsInterpolationHint) {
+ SetColor(valueStop.mColor, NS_RGB(0, 0, 0), aPresContext,
+ aContext, stop.mColor, aConditions);
+ } else {
+ // Always initialize to the same color so we don't need to worry
+ // about comparisons.
+ stop.mColor = NS_RGB(0, 0, 0);
+ }
+
+ aResult.mStops.AppendElement(stop);
+ }
+}
+
+// -moz-image-rect(<uri>, <top>, <right>, <bottom>, <left>)
+static void SetStyleImageToImageRect(nsStyleContext* aStyleContext,
+ const nsCSSValue& aValue,
+ nsStyleImage& aResult)
+{
+ MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Function &&
+ aValue.EqualsFunction(eCSSKeyword__moz_image_rect),
+ "the value is not valid -moz-image-rect()");
+
+ nsCSSValue::Array* arr = aValue.GetArrayValue();
+ MOZ_ASSERT(arr && arr->Count() == 6, "invalid number of arguments");
+
+ // <uri>
+ if (arr->Item(1).GetUnit() == eCSSUnit_Image) {
+ SetStyleImageRequest([&](nsStyleImageRequest* req) {
+ aResult.SetImageRequest(do_AddRef(req));
+ }, aStyleContext->PresContext(), arr->Item(1));
+ } else {
+ NS_WARNING("nsCSSValue::Image::Image() failed?");
+ }
+
+ // <top>, <right>, <bottom>, <left>
+ nsStyleSides cropRect;
+ NS_FOR_CSS_SIDES(side) {
+ nsStyleCoord coord;
+ const nsCSSValue& val = arr->Item(2 + side);
+
+#ifdef DEBUG
+ bool unitOk =
+#endif
+ SetAbsCoord(val, coord, SETCOORD_FACTOR | SETCOORD_PERCENT);
+ MOZ_ASSERT(unitOk, "Incorrect data structure created by CSS parser");
+ cropRect.Set(side, coord);
+ }
+ aResult.SetCropRect(MakeUnique<nsStyleSides>(cropRect));
+}
+
+static void SetStyleImage(nsStyleContext* aStyleContext,
+ const nsCSSValue& aValue,
+ nsStyleImage& aResult,
+ RuleNodeCacheConditions& aConditions)
+{
+ if (aValue.GetUnit() == eCSSUnit_Null) {
+ return;
+ }
+
+ aResult.SetNull();
+
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Image:
+ SetStyleImageRequest([&](nsStyleImageRequest* req) {
+ aResult.SetImageRequest(do_AddRef(req));
+ }, aStyleContext->PresContext(), aValue);
+ break;
+ case eCSSUnit_Function:
+ if (aValue.EqualsFunction(eCSSKeyword__moz_image_rect)) {
+ SetStyleImageToImageRect(aStyleContext, aValue, aResult);
+ } else {
+ NS_NOTREACHED("-moz-image-rect() is the only expected function");
+ }
+ break;
+ case eCSSUnit_Gradient:
+ {
+ nsStyleGradient* gradient = new nsStyleGradient();
+ SetGradient(aValue, aStyleContext->PresContext(), aStyleContext,
+ *gradient, aConditions);
+ aResult.SetGradientData(gradient);
+ break;
+ }
+ case eCSSUnit_Element:
+ aResult.SetElementId(aValue.GetStringBufferValue());
+ break;
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ break;
+ case eCSSUnit_URL:
+ {
+#ifdef DEBUG
+ // eCSSUnit_URL is expected only if
+ // 1. we have eCSSUnit_URL values for if-visited style contexts, which
+ // we can safely treat like 'none'.
+ // 2. aValue is a local-ref URL, e.g. url(#foo).
+ // 3. aValue is a not a local-ref URL, but it refers to an element in
+ // the current document. For example, the url of the current document
+ // is "http://foo.html" and aValue is url(http://foo.html#foo).
+ //
+ // We skip image download in TryToStartImageLoadOnValue under #2 and #3,
+ // and that's part of reasons we get eCSSUnit_URL instead of
+ // eCSSUnit_Image here.
+
+ // Check #2.
+ bool isLocalRef = aValue.GetURLStructValue()->IsLocalRef();
+
+ // Check #3.
+ bool isEqualExceptRef = false;
+ if (!isLocalRef) {
+ nsIDocument* currentDoc = aStyleContext->PresContext()->Document();
+ nsIURI* docURI = currentDoc->GetDocumentURI();
+ nsIURI* imageURI = aValue.GetURLValue();
+ imageURI->EqualsExceptRef(docURI, &isEqualExceptRef);
+ }
+
+ MOZ_ASSERT(aStyleContext->IsStyleIfVisited() || isEqualExceptRef ||
+ isLocalRef,
+ "unexpected unit; maybe nsCSSValue::Image::Image() failed?");
+#endif
+
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected Unit type.");
+ break;
+ }
+}
+
+struct SetEnumValueHelper
+{
+ template<typename FieldT>
+ static void SetIntegerValue(FieldT&, const nsCSSValue&)
+ {
+ // FIXME Is it possible to turn this assertion into a compilation error?
+ MOZ_ASSERT_UNREACHABLE("inappropriate unit");
+ }
+
+#define DEFINE_ENUM_CLASS_SETTER(type_, min_, max_) \
+ static void SetEnumeratedValue(type_& aField, const nsCSSValue& aValue) \
+ { \
+ auto value = aValue.GetIntValue(); \
+ MOZ_ASSERT(value >= static_cast<decltype(value)>(type_::min_) && \
+ value <= static_cast<decltype(value)>(type_::max_), \
+ "inappropriate value"); \
+ aField = static_cast<type_>(value); \
+ }
+
+ DEFINE_ENUM_CLASS_SETTER(StyleBoxAlign, Stretch, End)
+ DEFINE_ENUM_CLASS_SETTER(StyleBoxDecorationBreak, Slice, Clone)
+ DEFINE_ENUM_CLASS_SETTER(StyleBoxDirection, Normal, Reverse)
+ DEFINE_ENUM_CLASS_SETTER(StyleBoxOrient, Horizontal, Vertical)
+ DEFINE_ENUM_CLASS_SETTER(StyleBoxPack, Start, Justify)
+ DEFINE_ENUM_CLASS_SETTER(StyleBoxSizing, Content, Border)
+ DEFINE_ENUM_CLASS_SETTER(StyleClear, None, Both)
+ DEFINE_ENUM_CLASS_SETTER(StyleFillRule, Nonzero, Evenodd)
+ DEFINE_ENUM_CLASS_SETTER(StyleFloat, None, InlineEnd)
+ DEFINE_ENUM_CLASS_SETTER(StyleFloatEdge, ContentBox, MarginBox)
+ DEFINE_ENUM_CLASS_SETTER(StyleUserFocus, None, SelectMenu)
+ DEFINE_ENUM_CLASS_SETTER(StyleUserSelect, None, MozText)
+ DEFINE_ENUM_CLASS_SETTER(StyleUserInput, None, Auto)
+ DEFINE_ENUM_CLASS_SETTER(StyleUserModify, ReadOnly, WriteOnly)
+ DEFINE_ENUM_CLASS_SETTER(StyleWindowDragging, Default, NoDrag)
+ DEFINE_ENUM_CLASS_SETTER(StyleOrient, Inline, Vertical)
+#ifdef MOZ_XUL
+ DEFINE_ENUM_CLASS_SETTER(StyleDisplay, None, Popup)
+#else
+ DEFINE_ENUM_CLASS_SETTER(StyleDisplay, None, InlineBox)
+#endif
+
+#undef DEF_SET_ENUMERATED_VALUE
+};
+
+template<typename FieldT>
+struct SetIntegerValueHelper
+{
+ static void SetIntegerValue(FieldT& aField, const nsCSSValue& aValue)
+ {
+ aField = aValue.GetIntValue();
+ }
+ static void SetEnumeratedValue(FieldT& aField, const nsCSSValue& aValue)
+ {
+ aField = aValue.GetIntValue();
+ }
+};
+
+template<typename FieldT>
+struct SetValueHelper : Conditional<IsEnum<FieldT>::value,
+ SetEnumValueHelper,
+ SetIntegerValueHelper<FieldT>>::Type
+{
+ template<typename ValueT>
+ static void SetValue(FieldT& aField, const ValueT& aValue)
+ {
+ aField = aValue;
+ }
+ static void SetValue(FieldT&, unused_t)
+ {
+ // FIXME Is it possible to turn this assertion into a compilation error?
+ MOZ_ASSERT_UNREACHABLE("inappropriate unit");
+ }
+};
+
+
+// flags for SetValue - align values with SETCOORD_* constants
+// where possible
+
+#define SETVAL_INTEGER 0x40 // I
+#define SETVAL_ENUMERATED 0x80 // E
+#define SETVAL_UNSET_INHERIT 0x00400000
+#define SETVAL_UNSET_INITIAL 0x00800000
+
+// no caller cares whether aField was changed or not
+template<typename FieldT, typename InitialT,
+ typename AutoT, typename NoneT, typename NormalT, typename SysFontT>
+static void
+SetValue(const nsCSSValue& aValue, FieldT& aField,
+ RuleNodeCacheConditions& aConditions, uint32_t aMask,
+ FieldT aParentValue,
+ InitialT aInitialValue,
+ AutoT aAutoValue,
+ NoneT aNoneValue,
+ NormalT aNormalValue,
+ SysFontT aSystemFontValue)
+{
+ typedef SetValueHelper<FieldT> Helper;
+
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ return;
+
+ // every caller of SetValue provides inherit and initial
+ // alternatives, so we don't require them to say so in the mask
+ case eCSSUnit_Inherit:
+ aConditions.SetUncacheable();
+ aField = aParentValue;
+ return;
+
+ case eCSSUnit_Initial:
+ Helper::SetValue(aField, aInitialValue);
+ return;
+
+ // every caller provides one or other of these alternatives,
+ // but they have to say which
+ case eCSSUnit_Enumerated:
+ if (aMask & SETVAL_ENUMERATED) {
+ Helper::SetEnumeratedValue(aField, aValue);
+ return;
+ }
+ break;
+
+ case eCSSUnit_Integer:
+ if (aMask & SETVAL_INTEGER) {
+ Helper::SetIntegerValue(aField, aValue);
+ return;
+ }
+ break;
+
+ // remaining possibilities in descending order of frequency of use
+ case eCSSUnit_Auto:
+ Helper::SetValue(aField, aAutoValue);
+ return;
+
+ case eCSSUnit_None:
+ Helper::SetValue(aField, aNoneValue);
+ return;
+
+ case eCSSUnit_Normal:
+ Helper::SetValue(aField, aNormalValue);
+ return;
+
+ case eCSSUnit_System_Font:
+ Helper::SetValue(aField, aSystemFontValue);
+ return;
+
+ case eCSSUnit_Unset:
+ if (aMask & SETVAL_UNSET_INHERIT) {
+ aConditions.SetUncacheable();
+ aField = aParentValue;
+ return;
+ }
+ if (aMask & SETVAL_UNSET_INITIAL) {
+ Helper::SetValue(aField, aInitialValue);
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ NS_NOTREACHED("SetValue: inappropriate unit");
+}
+
+template <typename FieldT, typename T1>
+static void
+SetValue(const nsCSSValue& aValue, FieldT& aField,
+ RuleNodeCacheConditions& aConditions, uint32_t aMask,
+ FieldT aParentValue, T1 aInitialValue)
+{
+ SetValue(aValue, aField, aConditions, aMask, aParentValue,
+ aInitialValue, Unused, Unused, Unused, Unused);
+}
+
+// flags for SetFactor
+#define SETFCT_POSITIVE 0x01 // assert value is >= 0.0f
+#define SETFCT_OPACITY 0x02 // clamp value to [0.0f .. 1.0f]
+#define SETFCT_NONE 0x04 // allow _None (uses aInitialValue).
+#define SETFCT_UNSET_INHERIT 0x00400000
+#define SETFCT_UNSET_INITIAL 0x00800000
+
+static void
+SetFactor(const nsCSSValue& aValue, float& aField, RuleNodeCacheConditions& aConditions,
+ float aParentValue, float aInitialValue, uint32_t aFlags = 0)
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ return;
+
+ case eCSSUnit_Number:
+ aField = aValue.GetFloatValue();
+ if (aFlags & SETFCT_POSITIVE) {
+ NS_ASSERTION(aField >= 0.0f, "negative value for positive-only property");
+ if (aField < 0.0f)
+ aField = 0.0f;
+ }
+ if (aFlags & SETFCT_OPACITY) {
+ if (aField < 0.0f)
+ aField = 0.0f;
+ if (aField > 1.0f)
+ aField = 1.0f;
+ }
+ return;
+
+ case eCSSUnit_Inherit:
+ aConditions.SetUncacheable();
+ aField = aParentValue;
+ return;
+
+ case eCSSUnit_Initial:
+ aField = aInitialValue;
+ return;
+
+ case eCSSUnit_None:
+ if (aFlags & SETFCT_NONE) {
+ aField = aInitialValue;
+ return;
+ }
+ break;
+
+ case eCSSUnit_Unset:
+ if (aFlags & SETFCT_UNSET_INHERIT) {
+ aConditions.SetUncacheable();
+ aField = aParentValue;
+ return;
+ }
+ if (aFlags & SETFCT_UNSET_INITIAL) {
+ aField = aInitialValue;
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ NS_NOTREACHED("SetFactor: inappropriate unit");
+}
+
+void*
+nsRuleNode::operator new(size_t sz, nsPresContext* aPresContext)
+{
+ // Check the recycle list first.
+ return aPresContext->PresShell()->AllocateByObjectID(eArenaObjectID_nsRuleNode, sz);
+}
+
+// Overridden to prevent the global delete from being called, since the memory
+// came out of an nsIArena instead of the global delete operator's heap.
+void
+nsRuleNode::Destroy()
+{
+ // Destroy ourselves.
+ this->~nsRuleNode();
+
+ // Don't let the memory be freed, since it will be recycled
+ // instead. Don't call the global operator delete.
+ mPresContext->PresShell()->FreeByObjectID(eArenaObjectID_nsRuleNode, this);
+}
+
+already_AddRefed<nsRuleNode>
+nsRuleNode::CreateRootNode(nsPresContext* aPresContext)
+{
+ return do_AddRef(new (aPresContext)
+ nsRuleNode(aPresContext, nullptr, nullptr, SheetType::Unknown, false));
+}
+
+nsRuleNode::nsRuleNode(nsPresContext* aContext, nsRuleNode* aParent,
+ nsIStyleRule* aRule, SheetType aLevel,
+ bool aIsImportant)
+ : mPresContext(aContext),
+ mParent(aParent),
+ mRule(aRule),
+ mNextSibling(nullptr),
+ mDependentBits((uint32_t(aLevel) << NS_RULE_NODE_LEVEL_SHIFT) |
+ (aIsImportant ? NS_RULE_NODE_IS_IMPORTANT : 0)),
+ mNoneBits(aParent ? aParent->mNoneBits & NS_RULE_NODE_HAS_ANIMATION_DATA :
+ 0),
+ mRefCnt(0)
+{
+ MOZ_ASSERT(aContext);
+ MOZ_ASSERT(IsRoot() == !aRule,
+ "non-root rule nodes must have a rule");
+
+ mChildren.asVoid = nullptr;
+ MOZ_COUNT_CTOR(nsRuleNode);
+
+ NS_ASSERTION(IsRoot() || GetLevel() == aLevel, "not enough bits");
+ NS_ASSERTION(IsRoot() || IsImportantRule() == aIsImportant, "yikes");
+ MOZ_ASSERT(aContext->StyleSet()->IsGecko(),
+ "ServoStyleSets should not have rule nodes");
+ aContext->StyleSet()->AsGecko()->RuleNodeUnused(this, /* aMayGC = */ false);
+
+ // nsStyleSet::GetContext depends on there being only one animation
+ // rule.
+ MOZ_ASSERT(IsRoot() || GetLevel() != SheetType::Animation ||
+ mParent->IsRoot() ||
+ mParent->GetLevel() != SheetType::Animation,
+ "must be only one rule at animation level");
+}
+
+nsRuleNode::~nsRuleNode()
+{
+ MOZ_ASSERT(!HaveChildren());
+ MOZ_COUNT_DTOR(nsRuleNode);
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+
+ if (mStyleData.mResetData || mStyleData.mInheritedData)
+ mStyleData.Destroy(mDependentBits, mPresContext);
+}
+
+nsRuleNode*
+nsRuleNode::Transition(nsIStyleRule* aRule, SheetType aLevel,
+ bool aIsImportantRule)
+{
+#ifdef DEBUG
+ {
+ RefPtr<css::Declaration> declaration(do_QueryObject(aRule));
+ MOZ_ASSERT(!declaration || !declaration->IsMutable(),
+ "caller must call Declaration::SetImmutable first");
+ }
+#endif
+
+ nsRuleNode* next = nullptr;
+ nsRuleNode::Key key(aRule, aLevel, aIsImportantRule);
+
+ if (HaveChildren() && !ChildrenAreHashed()) {
+ int32_t numKids = 0;
+ nsRuleNode* curr = ChildrenList();
+ while (curr && curr->GetKey() != key) {
+ curr = curr->mNextSibling;
+ ++numKids;
+ }
+ if (curr)
+ next = curr;
+ else if (numKids >= kMaxChildrenInList)
+ ConvertChildrenToHash(numKids);
+ }
+
+ if (ChildrenAreHashed()) {
+ auto entry =
+ static_cast<ChildrenHashEntry*>(ChildrenHash()->Add(&key, fallible));
+ if (!entry) {
+ NS_WARNING("out of memory");
+ return this;
+ }
+ if (entry->mRuleNode)
+ next = entry->mRuleNode;
+ else {
+ next = entry->mRuleNode = new (mPresContext)
+ nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule);
+ }
+ } else if (!next) {
+ // Create the new entry in our list.
+ next = new (mPresContext)
+ nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule);
+ next->mNextSibling = ChildrenList();
+ SetChildrenList(next);
+ }
+
+ return next;
+}
+
+nsRuleNode*
+nsRuleNode::RuleTree()
+{
+ nsRuleNode* n = this;
+ while (n->mParent) {
+ n = n->mParent;
+ }
+ return n;
+}
+
+void nsRuleNode::SetUsedDirectly()
+{
+ mDependentBits |= NS_RULE_NODE_USED_DIRECTLY;
+
+ // Maintain the invariant that any rule node that is used directly has
+ // all structs that live in the rule tree cached (which
+ // nsRuleNode::GetStyleData depends on for speed).
+ if (mDependentBits & NS_STYLE_INHERIT_MASK) {
+ for (nsStyleStructID sid = nsStyleStructID(0); sid < nsStyleStructID_Length;
+ sid = nsStyleStructID(sid + 1)) {
+ uint32_t bit = nsCachedStyleData::GetBitForSID(sid);
+ if (mDependentBits & bit) {
+ nsRuleNode *source = mParent;
+ while ((source->mDependentBits & bit) && !source->IsUsedDirectly()) {
+ source = source->mParent;
+ }
+ void *data = source->mStyleData.GetStyleData(sid);
+ NS_ASSERTION(data, "unexpected null struct");
+ mStyleData.SetStyleData(sid, mPresContext, data);
+ }
+ }
+ }
+}
+
+void
+nsRuleNode::ConvertChildrenToHash(int32_t aNumKids)
+{
+ NS_ASSERTION(!ChildrenAreHashed() && HaveChildren(),
+ "must have a non-empty list of children");
+ PLDHashTable *hash = new PLDHashTable(&ChildrenHashOps,
+ sizeof(ChildrenHashEntry),
+ aNumKids);
+ for (nsRuleNode* curr = ChildrenList(); curr; curr = curr->mNextSibling) {
+ Key key = curr->GetKey();
+ // This will never fail because of the initial size we gave the table.
+ auto entry =
+ static_cast<ChildrenHashEntry*>(hash->Add(&key));
+ NS_ASSERTION(!entry->mRuleNode, "duplicate entries in list");
+ entry->mRuleNode = curr;
+ }
+ SetChildrenHash(hash);
+}
+
+void
+nsRuleNode::RemoveChild(nsRuleNode* aNode)
+{
+ MOZ_ASSERT(HaveChildren());
+ if (ChildrenAreHashed()) {
+ PLDHashTable* children = ChildrenHash();
+ Key key = aNode->GetKey();
+ MOZ_ASSERT(children->Search(&key));
+ children->Remove(&key);
+ if (children->EntryCount() == 0) {
+ delete children;
+ mChildren.asVoid = nullptr;
+ }
+ } else {
+ // This linear traversal is unfortunate, but we do the same thing when
+ // adding nodes. The traversal is bounded by kMaxChildrenInList.
+ nsRuleNode** curr = &mChildren.asList;
+ while (*curr != aNode) {
+ curr = &((*curr)->mNextSibling);
+ MOZ_ASSERT(*curr);
+ }
+ *curr = (*curr)->mNextSibling;
+
+ // If there was one element in the list, this sets mChildren.asList
+ // to 0, and HaveChildren() will return false.
+ }
+}
+
+inline void
+nsRuleNode::PropagateNoneBit(uint32_t aBit, nsRuleNode* aHighestNode)
+{
+ nsRuleNode* curr = this;
+ for (;;) {
+ NS_ASSERTION(!(curr->mNoneBits & aBit), "propagating too far");
+ curr->mNoneBits |= aBit;
+ if (curr == aHighestNode)
+ break;
+ curr = curr->mParent;
+ }
+}
+
+inline void
+nsRuleNode::PropagateDependentBit(nsStyleStructID aSID, nsRuleNode* aHighestNode,
+ void* aStruct)
+{
+ NS_ASSERTION(aStruct, "expected struct");
+
+ uint32_t bit = nsCachedStyleData::GetBitForSID(aSID);
+ for (nsRuleNode* curr = this; curr != aHighestNode; curr = curr->mParent) {
+ if (curr->mDependentBits & bit) {
+#ifdef DEBUG
+ while (curr != aHighestNode) {
+ NS_ASSERTION(curr->mDependentBits & bit, "bit not set");
+ curr = curr->mParent;
+ }
+#endif
+ break;
+ }
+
+ curr->mDependentBits |= bit;
+
+ if (curr->IsUsedDirectly()) {
+ curr->mStyleData.SetStyleData(aSID, mPresContext, aStruct);
+ }
+ }
+}
+
+/* static */ void
+nsRuleNode::PropagateGrandancestorBit(nsStyleContext* aContext,
+ nsStyleContext* aContextInheritedFrom)
+{
+ MOZ_ASSERT(aContext);
+ MOZ_ASSERT(aContextInheritedFrom &&
+ aContextInheritedFrom != aContext,
+ "aContextInheritedFrom must be an ancestor of aContext");
+
+ for (nsStyleContext* context = aContext->GetParent();
+ context != aContextInheritedFrom;
+ context = context->GetParent()) {
+ if (!context) {
+ MOZ_ASSERT(false, "aContextInheritedFrom must be an ancestor of "
+ "aContext's parent");
+ break;
+ }
+ context->AddStyleBit(NS_STYLE_CHILD_USES_GRANDANCESTOR_STYLE);
+ }
+}
+
+/*
+ * The following "Check" functions are used for determining what type of
+ * sharing can be used for the data on this rule node. MORE HERE...
+ */
+
+/*
+ * a callback function that that can revise the result of
+ * CheckSpecifiedProperties before finishing; aResult is the current
+ * result, and it returns the revised one.
+ */
+typedef nsRuleNode::RuleDetail
+ (* CheckCallbackFn)(const nsRuleData* aRuleData,
+ nsRuleNode::RuleDetail aResult);
+
+/**
+ * @param aValue the value being examined
+ * @param aSpecifiedCount to be incremented by one if the value is specified
+ * @param aInheritedCount to be incremented by one if the value is set to inherit
+ * @param aUnsetCount to be incremented by one if the value is set to unset
+ */
+inline void
+ExamineCSSValue(const nsCSSValue& aValue,
+ uint32_t& aSpecifiedCount,
+ uint32_t& aInheritedCount,
+ uint32_t& aUnsetCount)
+{
+ if (aValue.GetUnit() != eCSSUnit_Null) {
+ ++aSpecifiedCount;
+ if (aValue.GetUnit() == eCSSUnit_Inherit) {
+ ++aInheritedCount;
+ } else if (aValue.GetUnit() == eCSSUnit_Unset) {
+ ++aUnsetCount;
+ }
+ }
+}
+
+static nsRuleNode::RuleDetail
+CheckFontCallback(const nsRuleData* aRuleData,
+ nsRuleNode::RuleDetail aResult)
+{
+ // em, ex, percent, 'larger', and 'smaller' values on font-size depend
+ // on the parent context's font-size
+ // Likewise, 'lighter' and 'bolder' values of 'font-weight', and 'wider'
+ // and 'narrower' values of 'font-stretch' depend on the parent.
+ const nsCSSValue& size = *aRuleData->ValueForFontSize();
+ const nsCSSValue& weight = *aRuleData->ValueForFontWeight();
+ if ((size.IsRelativeLengthUnit() && size.GetUnit() != eCSSUnit_RootEM) ||
+ size.GetUnit() == eCSSUnit_Percent ||
+ (size.GetUnit() == eCSSUnit_Enumerated &&
+ (size.GetIntValue() == NS_STYLE_FONT_SIZE_SMALLER ||
+ size.GetIntValue() == NS_STYLE_FONT_SIZE_LARGER)) ||
+ aRuleData->ValueForScriptLevel()->GetUnit() == eCSSUnit_Integer ||
+ (weight.GetUnit() == eCSSUnit_Enumerated &&
+ (weight.GetIntValue() == NS_STYLE_FONT_WEIGHT_BOLDER ||
+ weight.GetIntValue() == NS_STYLE_FONT_WEIGHT_LIGHTER))) {
+ NS_ASSERTION(aResult == nsRuleNode::eRulePartialReset ||
+ aResult == nsRuleNode::eRuleFullReset ||
+ aResult == nsRuleNode::eRulePartialMixed ||
+ aResult == nsRuleNode::eRuleFullMixed,
+ "we know we already have a reset-counted property");
+ // Promote reset to mixed since we have something that depends on
+ // the parent. But never promote to inherited since that could
+ // cause inheritance of the exact value.
+ if (aResult == nsRuleNode::eRulePartialReset)
+ aResult = nsRuleNode::eRulePartialMixed;
+ else if (aResult == nsRuleNode::eRuleFullReset)
+ aResult = nsRuleNode::eRuleFullMixed;
+ }
+
+ return aResult;
+}
+
+static nsRuleNode::RuleDetail
+CheckColorCallback(const nsRuleData* aRuleData,
+ nsRuleNode::RuleDetail aResult)
+{
+ // currentColor values for color require inheritance
+ const nsCSSValue* colorValue = aRuleData->ValueForColor();
+ if (colorValue->GetUnit() == eCSSUnit_EnumColor &&
+ colorValue->GetIntValue() == NS_COLOR_CURRENTCOLOR) {
+ NS_ASSERTION(aResult == nsRuleNode::eRuleFullReset,
+ "we should already be counted as full-reset");
+ aResult = nsRuleNode::eRuleFullInherited;
+ }
+
+ return aResult;
+}
+
+static nsRuleNode::RuleDetail
+CheckTextCallback(const nsRuleData* aRuleData,
+ nsRuleNode::RuleDetail aResult)
+{
+ const nsCSSValue* textAlignValue = aRuleData->ValueForTextAlign();
+ if (textAlignValue->GetUnit() == eCSSUnit_Enumerated &&
+ (textAlignValue->GetIntValue() ==
+ NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT ||
+ textAlignValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_MATCH_PARENT)) {
+ // Promote reset to mixed since we have something that depends on
+ // the parent.
+ if (aResult == nsRuleNode::eRulePartialReset)
+ aResult = nsRuleNode::eRulePartialMixed;
+ else if (aResult == nsRuleNode::eRuleFullReset)
+ aResult = nsRuleNode::eRuleFullMixed;
+ }
+
+ return aResult;
+}
+
+static nsRuleNode::RuleDetail
+CheckVariablesCallback(const nsRuleData* aRuleData,
+ nsRuleNode::RuleDetail aResult)
+{
+ // We don't actually have any properties on nsStyleVariables, so we do
+ // all of the RuleDetail calculation in here.
+ if (aRuleData->mVariables) {
+ return nsRuleNode::eRulePartialMixed;
+ }
+ return nsRuleNode::eRuleNone;
+}
+
+#define FLAG_DATA_FOR_PROPERTY(name_, id_, method_, flags_, pref_, \
+ parsevariant_, kwtable_, stylestructoffset_, \
+ animtype_) \
+ flags_,
+
+// The order here must match the enums in *CheckCounter in nsCSSProps.cpp.
+
+static const uint32_t gFontFlags[] = {
+#define CSS_PROP_FONT FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_FONT
+};
+
+static const uint32_t gDisplayFlags[] = {
+#define CSS_PROP_DISPLAY FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_DISPLAY
+};
+
+static const uint32_t gVisibilityFlags[] = {
+#define CSS_PROP_VISIBILITY FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_VISIBILITY
+};
+
+static const uint32_t gMarginFlags[] = {
+#define CSS_PROP_MARGIN FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_MARGIN
+};
+
+static const uint32_t gBorderFlags[] = {
+#define CSS_PROP_BORDER FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_BORDER
+};
+
+static const uint32_t gPaddingFlags[] = {
+#define CSS_PROP_PADDING FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_PADDING
+};
+
+static const uint32_t gOutlineFlags[] = {
+#define CSS_PROP_OUTLINE FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_OUTLINE
+};
+
+static const uint32_t gListFlags[] = {
+#define CSS_PROP_LIST FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_LIST
+};
+
+static const uint32_t gColorFlags[] = {
+#define CSS_PROP_COLOR FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_COLOR
+};
+
+static const uint32_t gBackgroundFlags[] = {
+#define CSS_PROP_BACKGROUND FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_BACKGROUND
+};
+
+static const uint32_t gPositionFlags[] = {
+#define CSS_PROP_POSITION FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_POSITION
+};
+
+static const uint32_t gTableFlags[] = {
+#define CSS_PROP_TABLE FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_TABLE
+};
+
+static const uint32_t gTableBorderFlags[] = {
+#define CSS_PROP_TABLEBORDER FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_TABLEBORDER
+};
+
+static const uint32_t gContentFlags[] = {
+#define CSS_PROP_CONTENT FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_CONTENT
+};
+
+static const uint32_t gTextFlags[] = {
+#define CSS_PROP_TEXT FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_TEXT
+};
+
+static const uint32_t gTextResetFlags[] = {
+#define CSS_PROP_TEXTRESET FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_TEXTRESET
+};
+
+static const uint32_t gUserInterfaceFlags[] = {
+#define CSS_PROP_USERINTERFACE FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_USERINTERFACE
+};
+
+static const uint32_t gUIResetFlags[] = {
+#define CSS_PROP_UIRESET FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_UIRESET
+};
+
+static const uint32_t gXULFlags[] = {
+#define CSS_PROP_XUL FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_XUL
+};
+
+static const uint32_t gSVGFlags[] = {
+#define CSS_PROP_SVG FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_SVG
+};
+
+static const uint32_t gSVGResetFlags[] = {
+#define CSS_PROP_SVGRESET FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_SVGRESET
+};
+
+static const uint32_t gColumnFlags[] = {
+#define CSS_PROP_COLUMN FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_COLUMN
+};
+
+// There are no properties in nsStyleVariables, but we can't have a
+// zero length array.
+static const uint32_t gVariablesFlags[] = {
+ 0,
+#define CSS_PROP_VARIABLES FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_VARIABLES
+};
+static_assert(sizeof(gVariablesFlags) == sizeof(uint32_t),
+ "if nsStyleVariables has properties now you can remove the dummy "
+ "gVariablesFlags entry");
+
+static const uint32_t gEffectsFlags[] = {
+#define CSS_PROP_EFFECTS FLAG_DATA_FOR_PROPERTY
+#include "nsCSSPropList.h"
+#undef CSS_PROP_EFFECTS
+};
+
+#undef FLAG_DATA_FOR_PROPERTY
+
+static const uint32_t* gFlagsByStruct[] = {
+
+#define STYLE_STRUCT(name, checkdata_cb) \
+ g##name##Flags,
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+};
+
+static const CheckCallbackFn gCheckCallbacks[] = {
+
+#define STYLE_STRUCT(name, checkdata_cb) \
+ checkdata_cb,
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+};
+
+#ifdef DEBUG
+static bool
+AreAllMathMLPropertiesUndefined(const nsRuleData* aRuleData)
+{
+ return
+ aRuleData->ValueForScriptLevel()->GetUnit() == eCSSUnit_Null &&
+ aRuleData->ValueForScriptSizeMultiplier()->GetUnit() == eCSSUnit_Null &&
+ aRuleData->ValueForScriptMinSize()->GetUnit() == eCSSUnit_Null &&
+ aRuleData->ValueForMathVariant()->GetUnit() == eCSSUnit_Null &&
+ aRuleData->ValueForMathDisplay()->GetUnit() == eCSSUnit_Null;
+}
+#endif
+
+inline nsRuleNode::RuleDetail
+nsRuleNode::CheckSpecifiedProperties(const nsStyleStructID aSID,
+ const nsRuleData* aRuleData)
+{
+ // Build a count of the:
+ uint32_t total = 0, // total number of props in the struct
+ specified = 0, // number that were specified for this node
+ inherited = 0, // number that were 'inherit' (and not
+ // eCSSUnit_Inherit) for this node
+ unset = 0; // number that were 'unset'
+
+ // See comment in nsRuleData.h above mValueOffsets.
+ MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0,
+ "we assume the value offset is zero instead of adding it");
+ for (nsCSSValue *values = aRuleData->mValueStorage,
+ *values_end = values + nsCSSProps::PropertyCountInStruct(aSID);
+ values != values_end; ++values) {
+ ++total;
+ ExamineCSSValue(*values, specified, inherited, unset);
+ }
+
+ if (!nsCachedStyleData::IsReset(aSID)) {
+ // For inherited properties, 'unset' means the same as 'inherit'.
+ inherited += unset;
+ unset = 0;
+ }
+
+#if 0
+ printf("CheckSpecifiedProperties: SID=%d total=%d spec=%d inh=%d.\n",
+ aSID, total, specified, inherited);
+#endif
+
+ NS_ASSERTION(aSID != eStyleStruct_Font ||
+ mPresContext->Document()->GetMathMLEnabled() ||
+ AreAllMathMLPropertiesUndefined(aRuleData),
+ "MathML style property was defined even though MathML is disabled");
+
+ /*
+ * Return the most specific information we can: prefer None or Full
+ * over Partial, and Reset or Inherited over Mixed, since we can
+ * optimize based on the edge cases and not the in-between cases.
+ */
+ nsRuleNode::RuleDetail result;
+ if (inherited == total)
+ result = eRuleFullInherited;
+ else if (specified == total
+ // MathML defines 5 properties in Font that will never be set when
+ // MathML is not in use. Therefore if all but five
+ // properties have been set, and MathML is not enabled, we can treat
+ // this as fully specified. Code in nsMathMLElementFactory will
+ // rebuild the rule tree and style data when MathML is first enabled
+ // (see nsMathMLElement::BindToTree).
+ || (aSID == eStyleStruct_Font && specified + 5 == total &&
+ !mPresContext->Document()->GetMathMLEnabled())
+ ) {
+ if (inherited == 0)
+ result = eRuleFullReset;
+ else
+ result = eRuleFullMixed;
+ } else if (specified == 0)
+ result = eRuleNone;
+ else if (specified == inherited)
+ result = eRulePartialInherited;
+ else if (inherited == 0)
+ result = eRulePartialReset;
+ else
+ result = eRulePartialMixed;
+
+ CheckCallbackFn cb = gCheckCallbacks[aSID];
+ if (cb) {
+ result = (*cb)(aRuleData, result);
+ }
+
+ return result;
+}
+
+// If we need to restrict which properties apply to the style context,
+// return the bit to check in nsCSSProp's flags table. Otherwise,
+// return 0.
+inline uint32_t
+GetPseudoRestriction(nsStyleContext *aContext)
+{
+ // This needs to match nsStyleSet::WalkRestrictionRule.
+ uint32_t pseudoRestriction = 0;
+ nsIAtom *pseudoType = aContext->GetPseudo();
+ if (pseudoType) {
+ if (pseudoType == nsCSSPseudoElements::firstLetter) {
+ pseudoRestriction = CSS_PROPERTY_APPLIES_TO_FIRST_LETTER;
+ } else if (pseudoType == nsCSSPseudoElements::firstLine) {
+ pseudoRestriction = CSS_PROPERTY_APPLIES_TO_FIRST_LINE;
+ } else if (pseudoType == nsCSSPseudoElements::placeholder) {
+ pseudoRestriction = CSS_PROPERTY_APPLIES_TO_PLACEHOLDER;
+ }
+ }
+ return pseudoRestriction;
+}
+
+static void
+UnsetPropertiesWithoutFlags(const nsStyleStructID aSID,
+ nsRuleData* aRuleData,
+ uint32_t aFlags)
+{
+ NS_ASSERTION(aFlags != 0, "aFlags must be nonzero");
+
+ const uint32_t *flagData = gFlagsByStruct[aSID];
+
+ // See comment in nsRuleData.h above mValueOffsets.
+ MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0,
+ "we assume the value offset is zero instead of adding it");
+ nsCSSValue *values = aRuleData->mValueStorage;
+
+ for (size_t i = 0, i_end = nsCSSProps::PropertyCountInStruct(aSID);
+ i != i_end; ++i) {
+ if ((flagData[i] & aFlags) != aFlags)
+ values[i].Reset();
+ }
+}
+
+/**
+ * We allocate arrays of CSS values with alloca. (These arrays are a
+ * fixed size per style struct, but we don't want to waste the
+ * allocation and construction/destruction costs of the big structs when
+ * we're handling much smaller ones.) Since the lifetime of an alloca
+ * allocation is the life of the calling function, the caller must call
+ * alloca. However, to ensure that constructors and destructors are
+ * balanced, we do the constructor and destructor calling from this RAII
+ * class, AutoCSSValueArray.
+ */
+struct AutoCSSValueArray {
+ /**
+ * aStorage must be the result of alloca(aCount * sizeof(nsCSSValue))
+ */
+ AutoCSSValueArray(void* aStorage, size_t aCount) {
+ MOZ_ASSERT(size_t(aStorage) % NS_ALIGNMENT_OF(nsCSSValue) == 0,
+ "bad alignment from alloca");
+ mCount = aCount;
+ // Don't use placement new[], since it might store extra data
+ // for the count (on Windows!).
+ mArray = static_cast<nsCSSValue*>(aStorage);
+ for (size_t i = 0; i < mCount; ++i) {
+ new (KnownNotNull, mArray + i) nsCSSValue();
+ }
+ }
+
+ ~AutoCSSValueArray() {
+ for (size_t i = 0; i < mCount; ++i) {
+ mArray[i].~nsCSSValue();
+ }
+ }
+
+ nsCSSValue* get() { return mArray; }
+
+private:
+ nsCSSValue *mArray;
+ size_t mCount;
+};
+
+/* static */ bool
+nsRuleNode::ResolveVariableReferences(const nsStyleStructID aSID,
+ nsRuleData* aRuleData,
+ nsStyleContext* aContext)
+{
+ MOZ_ASSERT(aSID != eStyleStruct_Variables);
+ MOZ_ASSERT(aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(aSID));
+ MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0);
+
+ nsCSSParser parser;
+ bool anyTokenStreams = false;
+
+ // Look at each property in the nsRuleData for the given style struct.
+ size_t nprops = nsCSSProps::PropertyCountInStruct(aSID);
+ for (nsCSSValue* value = aRuleData->mValueStorage,
+ *values_end = aRuleData->mValueStorage + nprops;
+ value != values_end; value++) {
+ if (value->GetUnit() != eCSSUnit_TokenStream) {
+ continue;
+ }
+
+ const CSSVariableValues* variables =
+ &aContext->StyleVariables()->mVariables;
+ nsCSSValueTokenStream* tokenStream = value->GetTokenStreamValue();
+
+ MOZ_ASSERT(tokenStream->mLevel != SheetType::Count,
+ "Token stream should have a defined level");
+
+ AutoRestore<SheetType> saveLevel(aRuleData->mLevel);
+ aRuleData->mLevel = tokenStream->mLevel;
+
+ // Note that ParsePropertyWithVariableReferences relies on the fact
+ // that the nsCSSValue in aRuleData for the property we are re-parsing
+ // is still the token stream value. When
+ // ParsePropertyWithVariableReferences calls
+ // nsCSSExpandedDataBlock::MapRuleInfoInto, that function will add
+ // the ImageValue that is created into the token stream object's
+ // mImageValues table; see the comment above mImageValues for why.
+
+ // XXX Should pass in sheet here (see bug 952338).
+ parser.ParsePropertyWithVariableReferences(
+ tokenStream->mPropertyID, tokenStream->mShorthandPropertyID,
+ tokenStream->mTokenStream, variables, aRuleData,
+ tokenStream->mSheetURI, tokenStream->mBaseURI,
+ tokenStream->mSheetPrincipal, nullptr,
+ tokenStream->mLineNumber, tokenStream->mLineOffset);
+ aRuleData->mConditions.SetUncacheable();
+ anyTokenStreams = true;
+ }
+
+ return anyTokenStreams;
+}
+
+const void*
+nsRuleNode::WalkRuleTree(const nsStyleStructID aSID,
+ nsStyleContext* aContext)
+{
+ // use placement new[] on the result of alloca() to allocate a
+ // variable-sized stack array, including execution of constructors,
+ // and use an RAII class to run the destructors too.
+ size_t nprops = nsCSSProps::PropertyCountInStruct(aSID);
+ void* dataStorage = alloca(nprops * sizeof(nsCSSValue));
+ AutoCSSValueArray dataArray(dataStorage, nprops);
+
+ nsRuleData ruleData(nsCachedStyleData::GetBitForSID(aSID),
+ dataArray.get(), mPresContext, aContext);
+ ruleData.mValueOffsets[aSID] = 0;
+
+ // We start at the most specific rule in the tree.
+ void* startStruct = nullptr;
+
+ nsRuleNode* ruleNode = this;
+ nsRuleNode* highestNode = nullptr; // The highest node in the rule tree
+ // that has the same properties
+ // specified for struct |aSID| as
+ // |this| does.
+ nsRuleNode* rootNode = this; // After the loop below, this will be the
+ // highest node that we've walked without
+ // finding cached data on the rule tree.
+ // If we don't find any cached data, it
+ // will be the root. (XXX misnamed)
+ RuleDetail detail = eRuleNone;
+ uint32_t bit = nsCachedStyleData::GetBitForSID(aSID);
+
+ while (ruleNode) {
+ // See if this rule node has cached the fact that the remaining
+ // nodes along this path specify no data whatsoever.
+ if (ruleNode->mNoneBits & bit)
+ break;
+
+ // If the dependent bit is set on a rule node for this struct, that
+ // means its rule won't have any information to add, so skip it.
+ // NOTE: If we exit the loop because of the !IsUsedDirectly() check,
+ // then we're guaranteed to break immediately afterwards due to a
+ // non-null startStruct.
+ while ((ruleNode->mDependentBits & bit) && !ruleNode->IsUsedDirectly()) {
+ NS_ASSERTION(ruleNode->mStyleData.GetStyleData(aSID) == nullptr,
+ "dependent bit with cached data makes no sense");
+ // Climb up to the next rule in the tree (a less specific rule).
+ rootNode = ruleNode;
+ ruleNode = ruleNode->mParent;
+ NS_ASSERTION(!(ruleNode->mNoneBits & bit), "can't have both bits set");
+ }
+
+ // Check for cached data after the inner loop above -- otherwise
+ // we'll miss it.
+ startStruct = ruleNode->mStyleData.GetStyleData(aSID);
+ if (startStruct)
+ break; // We found a rule with fully specified data. We don't
+ // need to go up the tree any further, since the remainder
+ // of this branch has already been computed.
+
+ // Ask the rule to fill in the properties that it specifies.
+ nsIStyleRule *rule = ruleNode->mRule;
+ if (rule) {
+ ruleData.mLevel = ruleNode->GetLevel();
+ ruleData.mIsImportantRule = ruleNode->IsImportantRule();
+ rule->MapRuleInfoInto(&ruleData);
+ }
+
+ // Now we check to see how many properties have been specified by
+ // the rules we've examined so far.
+ RuleDetail oldDetail = detail;
+ detail = CheckSpecifiedProperties(aSID, &ruleData);
+
+ if (oldDetail == eRuleNone && detail != eRuleNone)
+ highestNode = ruleNode;
+
+ if (detail == eRuleFullReset ||
+ detail == eRuleFullMixed ||
+ detail == eRuleFullInherited)
+ break; // We don't need to examine any more rules. All properties
+ // have been fully specified.
+
+ // Climb up to the next rule in the tree (a less specific rule).
+ rootNode = ruleNode;
+ ruleNode = ruleNode->mParent;
+ }
+
+ bool recomputeDetail = false;
+
+ // If we are computing a style struct other than nsStyleVariables, and
+ // ruleData has any properties with variable references (nsCSSValues of
+ // type eCSSUnit_TokenStream), then we need to resolve these.
+ if (aSID != eStyleStruct_Variables) {
+ // A property's value might have became 'inherit' after resolving
+ // variable references. (This happens when an inherited property
+ // fails to parse its resolved value.) We need to recompute
+ // |detail| in case this happened.
+ recomputeDetail = ResolveVariableReferences(aSID, &ruleData, aContext);
+ }
+
+ // If needed, unset the properties that don't have a flag that allows
+ // them to be set for this style context. (For example, only some
+ // properties apply to :first-line and :first-letter.)
+ uint32_t pseudoRestriction = GetPseudoRestriction(aContext);
+ if (pseudoRestriction) {
+ UnsetPropertiesWithoutFlags(aSID, &ruleData, pseudoRestriction);
+
+ // We need to recompute |detail| based on the restrictions we just applied.
+ // We can adjust |detail| arbitrarily because of the restriction
+ // rule added in nsStyleSet::WalkRestrictionRule.
+ recomputeDetail = true;
+ }
+
+ if (recomputeDetail) {
+ detail = CheckSpecifiedProperties(aSID, &ruleData);
+ }
+
+ NS_ASSERTION(!startStruct || (detail != eRuleFullReset &&
+ detail != eRuleFullMixed &&
+ detail != eRuleFullInherited),
+ "can't have start struct and be fully specified");
+
+ bool isReset = nsCachedStyleData::IsReset(aSID);
+ if (!highestNode)
+ highestNode = rootNode;
+
+ MOZ_ASSERT(!(aSID == eStyleStruct_Variables && startStruct),
+ "if we start caching Variables structs in the rule tree, then "
+ "not forcing detail to eRulePartialMixed just below is no "
+ "longer valid");
+
+ if (detail == eRuleNone && isReset) {
+ // We specified absolutely no rule information for a reset struct, and we
+ // may or may not have found a parent rule in the tree that specified all
+ // the rule information. Regardless, we don't need to use any cache
+ // conditions if we cache this struct in the rule tree.
+ //
+ // Normally ruleData.mConditions would already indicate that the struct
+ // is cacheable without conditions if detail is eRuleNone, but because
+ // of the UnsetPropertiesWithoutFlags call above, we may have encountered
+ // some rules with dependencies, which we then cleared out of ruleData.
+ //
+ // ruleData.mConditions could also indicate we are not cacheable at all,
+ // such as when AnimValuesStyleRule prevents us from caching structs
+ // when attempting to apply animations to pseudos.
+ //
+ // So if we we are uncacheable, we leave it, but if we are cacheable
+ // with dependencies, we convert that to cacheable without dependencies.
+ if (ruleData.mConditions.CacheableWithDependencies()) {
+ MOZ_ASSERT(pseudoRestriction,
+ "should only be cacheable with dependencies if we had a "
+ "pseudo restriction");
+ ruleData.mConditions.Clear();
+ } else {
+ // XXXheycam We shouldn't have `|| GetLevel() == SheetType::Transition`
+ // in the assertion condition, but rule nodes created by
+ // ResolveStyleByAddingRules don't call SetIsAnimationRule().
+ MOZ_ASSERT(ruleData.mConditions.CacheableWithoutDependencies() ||
+ ((HasAnimationData() ||
+ GetLevel() == SheetType::Transition) &&
+ aContext->GetParent() &&
+ aContext->GetParent()->HasPseudoElementData()),
+ "should only be uncacheable if we had an animation rule "
+ "and we're inside a pseudo");
+ }
+ }
+
+ if (!ruleData.mConditions.CacheableWithoutDependencies() &&
+ aSID != eStyleStruct_Variables) {
+ // Treat as though some data is specified to avoid the optimizations and
+ // force data computation.
+ //
+ // We don't need to do this for Variables structs since we know those are
+ // never cached in the rule tree, and it avoids wasteful computation of a
+ // new Variables struct when we have no additional variable declarations,
+ // which otherwise could happen when there is an AnimValuesStyleRule
+ // (which calls SetUncacheable for style contexts with pseudo data).
+ detail = eRulePartialMixed;
+ }
+
+ if (detail == eRuleNone && startStruct) {
+ // We specified absolutely no rule information, but a parent rule in the tree
+ // specified all the rule information. We set a bit along the branch from our
+ // node in the tree to the node that specified the data that tells nodes on that
+ // branch that they never need to examine their rules for this particular struct type
+ // ever again.
+ PropagateDependentBit(aSID, ruleNode, startStruct);
+ // For inherited structs, mark the struct (which will be set on
+ // the context by our caller) as not being owned by the context.
+ if (!isReset) {
+ aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
+ } else if (HasAnimationData()) {
+ // If we have animation data, the struct should be cached on the style
+ // context so that we can peek the struct.
+ // See comment in AnimValuesStyleRule::MapRuleInfoInto.
+ StoreStyleOnContext(aContext, aSID, startStruct);
+ }
+
+ return startStruct;
+ }
+ if ((!startStruct && !isReset &&
+ (detail == eRuleNone || detail == eRulePartialInherited)) ||
+ detail == eRuleFullInherited) {
+ // We specified no non-inherited information and neither did any of
+ // our parent rules.
+
+ // We set a bit along the branch from the highest node (ruleNode)
+ // down to our node (this) indicating that no non-inherited data was
+ // specified. This bit is guaranteed to be set already on the path
+ // from the highest node to the root node in the case where
+ // (detail == eRuleNone), which is the most common case here.
+ // We must check |!isReset| because the Compute*Data functions for
+ // reset structs wouldn't handle none bits correctly.
+ if (highestNode != this && !isReset)
+ PropagateNoneBit(bit, highestNode);
+
+ // All information must necessarily be inherited from our parent style context.
+ // In the absence of any computed data in the rule tree and with
+ // no rules specified that didn't have values of 'inherit', we should check our parent.
+ nsStyleContext* parentContext = aContext->GetParent();
+ if (isReset) {
+ /* Reset structs don't inherit from first-line. */
+ /* See similar code in COMPUTE_START_RESET */
+ while (parentContext &&
+ parentContext->GetPseudo() == nsCSSPseudoElements::firstLine) {
+ parentContext = parentContext->GetParent();
+ }
+ if (parentContext && parentContext != aContext->GetParent()) {
+ PropagateGrandancestorBit(aContext, parentContext);
+ }
+ }
+ if (parentContext) {
+ // We have a parent, and so we should just inherit from the parent.
+ // Set the inherit bits on our context. These bits tell the style context that
+ // it never has to go back to the rule tree for data. Instead the style context tree
+ // should be walked to find the data.
+ const void* parentStruct = parentContext->StyleData(aSID);
+ aContext->AddStyleBit(bit); // makes const_cast OK.
+ aContext->SetStyle(aSID, const_cast<void*>(parentStruct));
+ if (isReset) {
+ parentContext->AddStyleBit(NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE);
+ }
+ return parentStruct;
+ }
+ else
+ // We are the root. In the case of fonts, the default values just
+ // come from the pres context.
+ return SetDefaultOnRoot(aSID, aContext);
+ }
+
+ typedef const void* (nsRuleNode::*ComputeFunc)(void*, const nsRuleData*,
+ nsStyleContext*, nsRuleNode*,
+ RuleDetail,
+ const RuleNodeCacheConditions);
+ static const ComputeFunc sComputeFuncs[] = {
+#define STYLE_STRUCT(name, checkdata_cb) &nsRuleNode::Compute##name##Data,
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+ };
+
+ // We need to compute the data from the information that the rules specified.
+ return (this->*sComputeFuncs[aSID])(startStruct, &ruleData, aContext,
+ highestNode, detail,
+ ruleData.mConditions);
+}
+
+const void*
+nsRuleNode::SetDefaultOnRoot(const nsStyleStructID aSID, nsStyleContext* aContext)
+{
+ switch (aSID) {
+ case eStyleStruct_Font:
+ {
+ nsStyleFont* fontData = new (mPresContext) nsStyleFont(mPresContext);
+ nscoord minimumFontSize = mPresContext->MinFontSize(fontData->mLanguage);
+
+ if (minimumFontSize > 0 && !mPresContext->IsChrome()) {
+ fontData->mFont.size = std::max(fontData->mSize, minimumFontSize);
+ }
+ else {
+ fontData->mFont.size = fontData->mSize;
+ }
+ aContext->SetStyle(eStyleStruct_Font, fontData);
+ return fontData;
+ }
+ case eStyleStruct_Display:
+ {
+ nsStyleDisplay* disp = new (mPresContext) nsStyleDisplay(mPresContext);
+ aContext->SetStyle(eStyleStruct_Display, disp);
+ return disp;
+ }
+ case eStyleStruct_Visibility:
+ {
+ nsStyleVisibility* vis = new (mPresContext) nsStyleVisibility(mPresContext);
+ aContext->SetStyle(eStyleStruct_Visibility, vis);
+ return vis;
+ }
+ case eStyleStruct_Text:
+ {
+ nsStyleText* text = new (mPresContext) nsStyleText(mPresContext);
+ aContext->SetStyle(eStyleStruct_Text, text);
+ return text;
+ }
+ case eStyleStruct_TextReset:
+ {
+ nsStyleTextReset* text = new (mPresContext) nsStyleTextReset(mPresContext);
+ aContext->SetStyle(eStyleStruct_TextReset, text);
+ return text;
+ }
+ case eStyleStruct_Color:
+ {
+ nsStyleColor* color = new (mPresContext) nsStyleColor(mPresContext);
+ aContext->SetStyle(eStyleStruct_Color, color);
+ return color;
+ }
+ case eStyleStruct_Background:
+ {
+ nsStyleBackground* bg = new (mPresContext) nsStyleBackground(mPresContext);
+ aContext->SetStyle(eStyleStruct_Background, bg);
+ return bg;
+ }
+ case eStyleStruct_Margin:
+ {
+ nsStyleMargin* margin = new (mPresContext) nsStyleMargin(mPresContext);
+ aContext->SetStyle(eStyleStruct_Margin, margin);
+ return margin;
+ }
+ case eStyleStruct_Border:
+ {
+ nsStyleBorder* border = new (mPresContext) nsStyleBorder(mPresContext);
+ aContext->SetStyle(eStyleStruct_Border, border);
+ return border;
+ }
+ case eStyleStruct_Padding:
+ {
+ nsStylePadding* padding = new (mPresContext) nsStylePadding(mPresContext);
+ aContext->SetStyle(eStyleStruct_Padding, padding);
+ return padding;
+ }
+ case eStyleStruct_Outline:
+ {
+ nsStyleOutline* outline = new (mPresContext) nsStyleOutline(mPresContext);
+ aContext->SetStyle(eStyleStruct_Outline, outline);
+ return outline;
+ }
+ case eStyleStruct_List:
+ {
+ nsStyleList* list = new (mPresContext) nsStyleList(mPresContext);
+ aContext->SetStyle(eStyleStruct_List, list);
+ return list;
+ }
+ case eStyleStruct_Position:
+ {
+ nsStylePosition* pos = new (mPresContext) nsStylePosition(mPresContext);
+ aContext->SetStyle(eStyleStruct_Position, pos);
+ return pos;
+ }
+ case eStyleStruct_Table:
+ {
+ nsStyleTable* table = new (mPresContext) nsStyleTable(mPresContext);
+ aContext->SetStyle(eStyleStruct_Table, table);
+ return table;
+ }
+ case eStyleStruct_TableBorder:
+ {
+ nsStyleTableBorder* table = new (mPresContext) nsStyleTableBorder(mPresContext);
+ aContext->SetStyle(eStyleStruct_TableBorder, table);
+ return table;
+ }
+ case eStyleStruct_Content:
+ {
+ nsStyleContent* content = new (mPresContext) nsStyleContent(mPresContext);
+ aContext->SetStyle(eStyleStruct_Content, content);
+ return content;
+ }
+ case eStyleStruct_UserInterface:
+ {
+ nsStyleUserInterface* ui = new (mPresContext) nsStyleUserInterface(mPresContext);
+ aContext->SetStyle(eStyleStruct_UserInterface, ui);
+ return ui;
+ }
+ case eStyleStruct_UIReset:
+ {
+ nsStyleUIReset* ui = new (mPresContext) nsStyleUIReset(mPresContext);
+ aContext->SetStyle(eStyleStruct_UIReset, ui);
+ return ui;
+ }
+ case eStyleStruct_XUL:
+ {
+ nsStyleXUL* xul = new (mPresContext) nsStyleXUL(mPresContext);
+ aContext->SetStyle(eStyleStruct_XUL, xul);
+ return xul;
+ }
+ case eStyleStruct_Column:
+ {
+ nsStyleColumn* column = new (mPresContext) nsStyleColumn(mPresContext);
+ aContext->SetStyle(eStyleStruct_Column, column);
+ return column;
+ }
+ case eStyleStruct_SVG:
+ {
+ nsStyleSVG* svg = new (mPresContext) nsStyleSVG(mPresContext);
+ aContext->SetStyle(eStyleStruct_SVG, svg);
+ return svg;
+ }
+ case eStyleStruct_SVGReset:
+ {
+ nsStyleSVGReset* svgReset = new (mPresContext) nsStyleSVGReset(mPresContext);
+ aContext->SetStyle(eStyleStruct_SVGReset, svgReset);
+ return svgReset;
+ }
+ case eStyleStruct_Variables:
+ {
+ nsStyleVariables* vars = new (mPresContext) nsStyleVariables(mPresContext);
+ aContext->SetStyle(eStyleStruct_Variables, vars);
+ return vars;
+ }
+ case eStyleStruct_Effects:
+ {
+ nsStyleEffects* effects = new (mPresContext) nsStyleEffects(mPresContext);
+ aContext->SetStyle(eStyleStruct_Effects, effects);
+ return effects;
+ }
+ default:
+ /*
+ * unhandled case: nsStyleStructID_Length.
+ * last item of nsStyleStructID, to know its length.
+ */
+ MOZ_ASSERT(false, "unexpected SID");
+ return nullptr;
+ }
+ return nullptr;
+}
+
+/**
+ * Begin an nsRuleNode::Compute*Data function for an inherited struct.
+ *
+ * @param type_ The nsStyle* type this function computes.
+ * @param data_ Variable (declared here) holding the result of this
+ * function.
+ * @param parentdata_ Variable (declared here) holding the parent style
+ * context's data for this struct.
+ */
+#define COMPUTE_START_INHERITED(type_, data_, parentdata_) \
+ NS_ASSERTION(aRuleDetail != eRuleFullInherited, \
+ "should not have bothered calling Compute*Data"); \
+ \
+ nsStyleContext* parentContext = aContext->GetParent(); \
+ \
+ nsStyle##type_* data_ = nullptr; \
+ mozilla::Maybe<nsStyle##type_> maybeFakeParentData; \
+ const nsStyle##type_* parentdata_ = nullptr; \
+ RuleNodeCacheConditions conditions = aConditions; \
+ \
+ /* If |conditions.Cacheable()| might be true by the time we're done, we */ \
+ /* can't call parentContext->Style##type_() since it could recur into */ \
+ /* setting the same struct on the same rule node, causing a leak. */ \
+ if (aRuleDetail != eRuleFullReset && \
+ (!aStartStruct || (aRuleDetail != eRulePartialReset && \
+ aRuleDetail != eRuleNone))) { \
+ if (parentContext) { \
+ parentdata_ = parentContext->Style##type_(); \
+ } else { \
+ maybeFakeParentData.emplace(mPresContext); \
+ parentdata_ = maybeFakeParentData.ptr(); \
+ } \
+ } \
+ if (eStyleStruct_##type_ == eStyleStruct_Variables) \
+ /* no need to copy construct an nsStyleVariables, as we will copy */ \
+ /* inherited variables (and call SetUncacheable()) in */ \
+ /* ComputeVariablesData */ \
+ data_ = new (mPresContext) nsStyle##type_(mPresContext); \
+ else if (aStartStruct) \
+ /* We only need to compute the delta between this computed data and */ \
+ /* our computed data. */ \
+ data_ = new (mPresContext) \
+ nsStyle##type_(*static_cast<nsStyle##type_*>(aStartStruct)); \
+ else { \
+ if (aRuleDetail != eRuleFullMixed && aRuleDetail != eRuleFullReset) { \
+ /* No question. We will have to inherit. Go ahead and init */ \
+ /* with inherited vals from parent. */ \
+ conditions.SetUncacheable(); \
+ if (parentdata_) \
+ data_ = new (mPresContext) nsStyle##type_(*parentdata_); \
+ else \
+ data_ = new (mPresContext) nsStyle##type_(mPresContext); \
+ } \
+ else \
+ data_ = new (mPresContext) nsStyle##type_(mPresContext); \
+ } \
+ \
+ if (!parentdata_) \
+ parentdata_ = data_;
+
+/**
+ * Begin an nsRuleNode::Compute*Data function for a reset struct.
+ *
+ * @param type_ The nsStyle* type this function computes.
+ * @param data_ Variable (declared here) holding the result of this
+ * function.
+ * @param parentdata_ Variable (declared here) holding the parent style
+ * context's data for this struct.
+ */
+#define COMPUTE_START_RESET(type_, data_, parentdata_) \
+ NS_ASSERTION(aRuleDetail != eRuleFullInherited, \
+ "should not have bothered calling Compute*Data"); \
+ \
+ nsStyleContext* parentContext = aContext->GetParent(); \
+ /* Reset structs don't inherit from first-line */ \
+ /* See similar code in WalkRuleTree */ \
+ while (parentContext && \
+ parentContext->GetPseudo() == nsCSSPseudoElements::firstLine) { \
+ parentContext = parentContext->GetParent(); \
+ } \
+ \
+ nsStyle##type_* data_; \
+ if (aStartStruct) \
+ /* We only need to compute the delta between this computed data and */ \
+ /* our computed data. */ \
+ data_ = new (mPresContext) \
+ nsStyle##type_(*static_cast<nsStyle##type_*>(aStartStruct)); \
+ else \
+ data_ = new (mPresContext) nsStyle##type_(mPresContext); \
+ \
+ /* If |conditions.Cacheable()| might be true by the time we're done, we */ \
+ /* can't call parentContext->Style##type_() since it could recur into */ \
+ /* setting the same struct on the same rule node, causing a leak. */ \
+ mozilla::Maybe<nsStyle##type_> maybeFakeParentData; \
+ const nsStyle##type_* parentdata_ = data_; \
+ if (aRuleDetail != eRuleFullReset && \
+ aRuleDetail != eRulePartialReset && \
+ aRuleDetail != eRuleNone) { \
+ if (parentContext) { \
+ parentdata_ = parentContext->Style##type_(); \
+ } else { \
+ maybeFakeParentData.emplace(mPresContext); \
+ parentdata_ = maybeFakeParentData.ptr(); \
+ } \
+ } \
+ RuleNodeCacheConditions conditions = aConditions;
+
+/**
+ * End an nsRuleNode::Compute*Data function for an inherited struct.
+ *
+ * @param type_ The nsStyle* type this function computes.
+ * @param data_ Variable holding the result of this function.
+ */
+#define COMPUTE_END_INHERITED(type_, data_) \
+ NS_POSTCONDITION(!conditions.CacheableWithoutDependencies() || \
+ aRuleDetail == eRuleFullReset || \
+ (aStartStruct && aRuleDetail == eRulePartialReset), \
+ "conditions.CacheableWithoutDependencies() must be false " \
+ "for inherited structs unless all properties have been " \
+ "specified with values other than inherit"); \
+ if (conditions.CacheableWithoutDependencies()) { \
+ /* We were fully specified and can therefore be cached right on the */ \
+ /* rule node. */ \
+ if (!aHighestNode->mStyleData.mInheritedData) { \
+ aHighestNode->mStyleData.mInheritedData = \
+ new (mPresContext) nsInheritedStyleData; \
+ } \
+ NS_ASSERTION(!aHighestNode->mStyleData.mInheritedData-> \
+ mStyleStructs[eStyleStruct_##type_], \
+ "Going to leak style data"); \
+ aHighestNode->mStyleData.mInheritedData-> \
+ mStyleStructs[eStyleStruct_##type_] = data_; \
+ /* Propagate the bit down. */ \
+ PropagateDependentBit(eStyleStruct_##type_, aHighestNode, data_); \
+ /* Tell the style context that it doesn't own the data */ \
+ aContext->AddStyleBit(NS_STYLE_INHERIT_BIT(type_)); \
+ } \
+ /* For inherited structs, our caller will cache the data on the */ \
+ /* style context */ \
+ \
+ return data_;
+
+/**
+ * End an nsRuleNode::Compute*Data function for a reset struct.
+ *
+ * @param type_ The nsStyle* type this function computes.
+ * @param data_ Variable holding the result of this function.
+ */
+#define COMPUTE_END_RESET(type_, data_) \
+ NS_POSTCONDITION(!conditions.CacheableWithoutDependencies() || \
+ aRuleDetail == eRuleNone || \
+ aRuleDetail == eRulePartialReset || \
+ aRuleDetail == eRuleFullReset, \
+ "conditions.CacheableWithoutDependencies() must be false " \
+ "for reset structs if any properties were specified as " \
+ "inherit"); \
+ if (conditions.CacheableWithoutDependencies()) { \
+ /* We were fully specified and can therefore be cached right on the */ \
+ /* rule node. */ \
+ if (!aHighestNode->mStyleData.mResetData) { \
+ aHighestNode->mStyleData.mResetData = \
+ new (mPresContext) nsConditionalResetStyleData; \
+ } \
+ NS_ASSERTION(!aHighestNode->mStyleData.mResetData-> \
+ GetStyleData(eStyleStruct_##type_), \
+ "Going to leak style data"); \
+ aHighestNode->mStyleData.mResetData-> \
+ SetStyleData(eStyleStruct_##type_, data_); \
+ /* Propagate the bit down. */ \
+ PropagateDependentBit(eStyleStruct_##type_, aHighestNode, data_); \
+ if (HasAnimationData()) { \
+ /* If we have animation data, the struct should be cached on the */ \
+ /* style context so that we can peek the struct. */ \
+ /* See comment in AnimValuesStyleRule::MapRuleInfoInto. */ \
+ StoreStyleOnContext(aContext, eStyleStruct_##type_, data_); \
+ } \
+ } else if (conditions.Cacheable()) { \
+ if (!mStyleData.mResetData) { \
+ mStyleData.mResetData = new (mPresContext) nsConditionalResetStyleData; \
+ } \
+ mStyleData.mResetData-> \
+ SetStyleData(eStyleStruct_##type_, mPresContext, data_, conditions); \
+ /* Tell the style context that it doesn't own the data */ \
+ aContext->AddStyleBit(NS_STYLE_INHERIT_BIT(type_)); \
+ aContext->SetStyle(eStyleStruct_##type_, data_); \
+ } else { \
+ /* We can't be cached in the rule node. We have to be put right */ \
+ /* on the style context. */ \
+ aContext->SetStyle(eStyleStruct_##type_, data_); \
+ if (aContext->GetParent()) { \
+ /* This is pessimistic; we could be uncacheable because we had a */ \
+ /* relative font-weight, for example, which does not need to defeat */ \
+ /* the restyle optimizations in RestyleManager.cpp that look at */ \
+ /* NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE. */ \
+ aContext->GetParent()-> \
+ AddStyleBit(NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE); \
+ } \
+ } \
+ \
+ return data_;
+
+// This function figures out how much scaling should be suppressed to
+// satisfy scriptminsize. This is our attempt to implement
+// http://www.w3.org/TR/MathML2/chapter3.html#id.3.3.4.2.2
+// This is called after mScriptLevel, mScriptMinSize and mScriptSizeMultiplier
+// have been set in aFont.
+//
+// Here are the invariants we enforce:
+// 1) A decrease in size must not reduce the size below minscriptsize.
+// 2) An increase in size must not increase the size above the size we would
+// have if minscriptsize had not been applied anywhere.
+// 3) The scriptlevel-induced size change must between 1.0 and the parent's
+// scriptsizemultiplier^(new script level - old script level), as close to the
+// latter as possible subject to constraints 1 and 2.
+static nscoord
+ComputeScriptLevelSize(const nsStyleFont* aFont, const nsStyleFont* aParentFont,
+ nsPresContext* aPresContext, nscoord* aUnconstrainedSize)
+{
+ int32_t scriptLevelChange =
+ aFont->mScriptLevel - aParentFont->mScriptLevel;
+ if (scriptLevelChange == 0) {
+ *aUnconstrainedSize = aParentFont->mScriptUnconstrainedSize;
+ // Constraint #3 says that we cannot change size, and #1 and #2 are always
+ // satisfied with no change. It's important this be fast because it covers
+ // all non-MathML content.
+ return aParentFont->mSize;
+ }
+
+ // Compute actual value of minScriptSize
+ nscoord minScriptSize = aParentFont->mScriptMinSize;
+ if (aFont->mAllowZoom) {
+ minScriptSize = nsStyleFont::ZoomText(aPresContext, minScriptSize);
+ }
+
+ double scriptLevelScale =
+ pow(aParentFont->mScriptSizeMultiplier, scriptLevelChange);
+ // Compute the size we would have had if minscriptsize had never been
+ // applied, also prevent overflow (bug 413274)
+ *aUnconstrainedSize =
+ NSToCoordRoundWithClamp(aParentFont->mScriptUnconstrainedSize*scriptLevelScale);
+ // Compute the size we could get via scriptlevel change
+ nscoord scriptLevelSize =
+ NSToCoordRoundWithClamp(aParentFont->mSize*scriptLevelScale);
+ if (scriptLevelScale <= 1.0) {
+ if (aParentFont->mSize <= minScriptSize) {
+ // We can't decrease the font size at all, so just stick to no change
+ // (authors are allowed to explicitly set the font size smaller than
+ // minscriptsize)
+ return aParentFont->mSize;
+ }
+ // We can decrease, so apply constraint #1
+ return std::max(minScriptSize, scriptLevelSize);
+ } else {
+ // scriptminsize can only make sizes larger than the unconstrained size
+ NS_ASSERTION(*aUnconstrainedSize <= scriptLevelSize, "How can this ever happen?");
+ // Apply constraint #2
+ return std::min(scriptLevelSize, std::max(*aUnconstrainedSize, minScriptSize));
+ }
+}
+
+
+/* static */ nscoord
+nsRuleNode::CalcFontPointSize(int32_t aHTMLSize, int32_t aBasePointSize,
+ nsPresContext* aPresContext,
+ nsFontSizeType aFontSizeType)
+{
+#define sFontSizeTableMin 9
+#define sFontSizeTableMax 16
+
+// This table seems to be the one used by MacIE5. We hope its adoption in Mozilla
+// and eventually in WinIE5.5 will help to establish a standard rendering across
+// platforms and browsers. For now, it is used only in Strict mode. More can be read
+// in the document written by Todd Farhner at:
+// http://style.verso.com/font_size_intervals/altintervals.html
+//
+ static int32_t sStrictFontSizeTable[sFontSizeTableMax - sFontSizeTableMin + 1][8] =
+ {
+ { 9, 9, 9, 9, 11, 14, 18, 27},
+ { 9, 9, 9, 10, 12, 15, 20, 30},
+ { 9, 9, 10, 11, 13, 17, 22, 33},
+ { 9, 9, 10, 12, 14, 18, 24, 36},
+ { 9, 10, 12, 13, 16, 20, 26, 39},
+ { 9, 10, 12, 14, 17, 21, 28, 42},
+ { 9, 10, 13, 15, 18, 23, 30, 45},
+ { 9, 10, 13, 16, 18, 24, 32, 48}
+ };
+// HTML 1 2 3 4 5 6 7
+// CSS xxs xs s m l xl xxl
+// |
+// user pref
+//
+//------------------------------------------------------------
+//
+// This table gives us compatibility with WinNav4 for the default fonts only.
+// In WinNav4, the default fonts were:
+//
+// Times/12pt == Times/16px at 96ppi
+// Courier/10pt == Courier/13px at 96ppi
+//
+// The 2 lines below marked "anchored" have the exact pixel sizes used by
+// WinNav4 for Times/12pt and Courier/10pt at 96ppi. As you can see, the
+// HTML size 3 (user pref) for those 2 anchored lines is 13px and 16px.
+//
+// All values other than the anchored values were filled in by hand, never
+// going below 9px, and maintaining a "diagonal" relationship. See for
+// example the 13s -- they follow a diagonal line through the table.
+//
+ static int32_t sQuirksFontSizeTable[sFontSizeTableMax - sFontSizeTableMin + 1][8] =
+ {
+ { 9, 9, 9, 9, 11, 14, 18, 28 },
+ { 9, 9, 9, 10, 12, 15, 20, 31 },
+ { 9, 9, 9, 11, 13, 17, 22, 34 },
+ { 9, 9, 10, 12, 14, 18, 24, 37 },
+ { 9, 9, 10, 13, 16, 20, 26, 40 }, // anchored (13)
+ { 9, 9, 11, 14, 17, 21, 28, 42 },
+ { 9, 10, 12, 15, 17, 23, 30, 45 },
+ { 9, 10, 13, 16, 18, 24, 32, 48 } // anchored (16)
+ };
+// HTML 1 2 3 4 5 6 7
+// CSS xxs xs s m l xl xxl
+// |
+// user pref
+
+#if 0
+//
+// These are the exact pixel values used by WinIE5 at 96ppi.
+//
+ { ?, 8, 11, 12, 13, 16, 21, 32 }, // smallest
+ { ?, 9, 12, 13, 16, 21, 27, 40 }, // smaller
+ { ?, 10, 13, 16, 18, 24, 32, 48 }, // medium
+ { ?, 13, 16, 19, 21, 27, 37, ?? }, // larger
+ { ?, 16, 19, 21, 24, 32, 43, ?? } // largest
+//
+// HTML 1 2 3 4 5 6 7
+// CSS ? ? ? ? ? ? ? ?
+//
+// (CSS not tested yet.)
+//
+#endif
+
+ static int32_t sFontSizeFactors[8] = { 60,75,89,100,120,150,200,300 };
+
+ static int32_t sCSSColumns[7] = {0, 1, 2, 3, 4, 5, 6}; // xxs...xxl
+ static int32_t sHTMLColumns[7] = {1, 2, 3, 4, 5, 6, 7}; // 1...7
+
+ double dFontSize;
+
+ if (aFontSizeType == eFontSize_HTML) {
+ aHTMLSize--; // input as 1-7
+ }
+
+ if (aHTMLSize < 0)
+ aHTMLSize = 0;
+ else if (aHTMLSize > 6)
+ aHTMLSize = 6;
+
+ int32_t* column;
+ switch (aFontSizeType)
+ {
+ case eFontSize_HTML: column = sHTMLColumns; break;
+ case eFontSize_CSS: column = sCSSColumns; break;
+ }
+
+ // Make special call specifically for fonts (needed PrintPreview)
+ int32_t fontSize = nsPresContext::AppUnitsToIntCSSPixels(aBasePointSize);
+
+ if ((fontSize >= sFontSizeTableMin) && (fontSize <= sFontSizeTableMax))
+ {
+ int32_t row = fontSize - sFontSizeTableMin;
+
+ if (aPresContext->CompatibilityMode() == eCompatibility_NavQuirks) {
+ dFontSize = nsPresContext::CSSPixelsToAppUnits(sQuirksFontSizeTable[row][column[aHTMLSize]]);
+ } else {
+ dFontSize = nsPresContext::CSSPixelsToAppUnits(sStrictFontSizeTable[row][column[aHTMLSize]]);
+ }
+ }
+ else
+ {
+ int32_t factor = sFontSizeFactors[column[aHTMLSize]];
+ dFontSize = (factor * aBasePointSize) / 100;
+ }
+
+
+ if (1.0 < dFontSize) {
+ return (nscoord)dFontSize;
+ }
+ return (nscoord)1;
+}
+
+
+//------------------------------------------------------------------------------
+//
+//------------------------------------------------------------------------------
+
+/* static */ nscoord
+nsRuleNode::FindNextSmallerFontSize(nscoord aFontSize, int32_t aBasePointSize,
+ nsPresContext* aPresContext,
+ nsFontSizeType aFontSizeType)
+{
+ int32_t index;
+ int32_t indexMin;
+ int32_t indexMax;
+ float relativePosition;
+ nscoord smallerSize;
+ nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning
+ nscoord smallestIndexFontSize;
+ nscoord largestIndexFontSize;
+ nscoord smallerIndexFontSize;
+ nscoord largerIndexFontSize;
+
+ nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1);
+
+ if (aFontSizeType == eFontSize_HTML) {
+ indexMin = 1;
+ indexMax = 7;
+ } else {
+ indexMin = 0;
+ indexMax = 6;
+ }
+
+ smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aPresContext, aFontSizeType);
+ largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aPresContext, aFontSizeType);
+ if (aFontSize > smallestIndexFontSize) {
+ if (aFontSize < NSToCoordRound(float(largestIndexFontSize) * 1.5)) { // smaller will be in HTML table
+ // find largest index smaller than current
+ for (index = indexMax; index >= indexMin; index--) {
+ indexFontSize = CalcFontPointSize(index, aBasePointSize, aPresContext, aFontSizeType);
+ if (indexFontSize < aFontSize)
+ break;
+ }
+ // set up points beyond table for interpolation purposes
+ if (indexFontSize == smallestIndexFontSize) {
+ smallerIndexFontSize = indexFontSize - onePx;
+ largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
+ } else if (indexFontSize == largestIndexFontSize) {
+ smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
+ largerIndexFontSize = NSToCoordRound(float(largestIndexFontSize) * 1.5);
+ } else {
+ smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
+ largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
+ }
+ // compute the relative position of the parent size between the two closest indexed sizes
+ relativePosition = float(aFontSize - indexFontSize) / float(largerIndexFontSize - indexFontSize);
+ // set the new size to have the same relative position between the next smallest two indexed sizes
+ smallerSize = smallerIndexFontSize + NSToCoordRound(relativePosition * (indexFontSize - smallerIndexFontSize));
+ }
+ else { // larger than HTML table, drop by 33%
+ smallerSize = NSToCoordRound(float(aFontSize) / 1.5);
+ }
+ }
+ else { // smaller than HTML table, drop by 1px
+ smallerSize = std::max(aFontSize - onePx, onePx);
+ }
+ return smallerSize;
+}
+
+//------------------------------------------------------------------------------
+//
+//------------------------------------------------------------------------------
+
+/* static */ nscoord
+nsRuleNode::FindNextLargerFontSize(nscoord aFontSize, int32_t aBasePointSize,
+ nsPresContext* aPresContext,
+ nsFontSizeType aFontSizeType)
+{
+ int32_t index;
+ int32_t indexMin;
+ int32_t indexMax;
+ float relativePosition;
+ nscoord adjustment;
+ nscoord largerSize;
+ nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning
+ nscoord smallestIndexFontSize;
+ nscoord largestIndexFontSize;
+ nscoord smallerIndexFontSize;
+ nscoord largerIndexFontSize;
+
+ nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1);
+
+ if (aFontSizeType == eFontSize_HTML) {
+ indexMin = 1;
+ indexMax = 7;
+ } else {
+ indexMin = 0;
+ indexMax = 6;
+ }
+
+ smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aPresContext, aFontSizeType);
+ largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aPresContext, aFontSizeType);
+ if (aFontSize > (smallestIndexFontSize - onePx)) {
+ if (aFontSize < largestIndexFontSize) { // larger will be in HTML table
+ // find smallest index larger than current
+ for (index = indexMin; index <= indexMax; index++) {
+ indexFontSize = CalcFontPointSize(index, aBasePointSize, aPresContext, aFontSizeType);
+ if (indexFontSize > aFontSize)
+ break;
+ }
+ // set up points beyond table for interpolation purposes
+ if (indexFontSize == smallestIndexFontSize) {
+ smallerIndexFontSize = indexFontSize - onePx;
+ largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
+ } else if (indexFontSize == largestIndexFontSize) {
+ smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
+ largerIndexFontSize = NSCoordSaturatingMultiply(largestIndexFontSize, 1.5);
+ } else {
+ smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
+ largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
+ }
+ // compute the relative position of the parent size between the two closest indexed sizes
+ relativePosition = float(aFontSize - smallerIndexFontSize) / float(indexFontSize - smallerIndexFontSize);
+ // set the new size to have the same relative position between the next largest two indexed sizes
+ adjustment = NSCoordSaturatingNonnegativeMultiply(largerIndexFontSize - indexFontSize, relativePosition);
+ largerSize = NSCoordSaturatingAdd(indexFontSize, adjustment);
+ }
+ else { // larger than HTML table, increase by 50%
+ largerSize = NSCoordSaturatingMultiply(aFontSize, 1.5);
+ }
+ }
+ else { // smaller than HTML table, increase by 1px
+ largerSize = NSCoordSaturatingAdd(aFontSize, onePx);
+ }
+ return largerSize;
+}
+
+struct SetFontSizeCalcOps : public css::BasicCoordCalcOps,
+ public css::NumbersAlreadyNormalizedOps
+{
+ // The parameters beyond aValue that we need for CalcLengthWith.
+ const nscoord mParentSize;
+ const nsStyleFont* const mParentFont;
+ nsPresContext* const mPresContext;
+ nsStyleContext* const mStyleContext;
+ const bool mAtRoot;
+ RuleNodeCacheConditions& mConditions;
+
+ SetFontSizeCalcOps(nscoord aParentSize, const nsStyleFont* aParentFont,
+ nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ bool aAtRoot,
+ RuleNodeCacheConditions& aConditions)
+ : mParentSize(aParentSize),
+ mParentFont(aParentFont),
+ mPresContext(aPresContext),
+ mStyleContext(aStyleContext),
+ mAtRoot(aAtRoot),
+ mConditions(aConditions)
+ {
+ }
+
+ result_type ComputeLeafValue(const nsCSSValue& aValue)
+ {
+ nscoord size;
+ if (aValue.IsLengthUnit()) {
+ // Note that font-based length units use the parent's size
+ // unadjusted for scriptlevel changes. A scriptlevel change
+ // between us and the parent is simply ignored.
+ size = CalcLengthWith(aValue, mParentSize,
+ mParentFont,
+ mStyleContext, mPresContext, mAtRoot,
+ true, mConditions);
+ if (!aValue.IsRelativeLengthUnit() && mParentFont->mAllowZoom) {
+ size = nsStyleFont::ZoomText(mPresContext, size);
+ }
+ }
+ else if (eCSSUnit_Percent == aValue.GetUnit()) {
+ mConditions.SetUncacheable();
+ // Note that % units use the parent's size unadjusted for scriptlevel
+ // changes. A scriptlevel change between us and the parent is simply
+ // ignored.
+ // aValue.GetPercentValue() may be negative for, e.g., calc(-50%)
+ size = NSCoordSaturatingMultiply(mParentSize, aValue.GetPercentValue());
+ } else {
+ MOZ_ASSERT(false, "unexpected value");
+ size = mParentSize;
+ }
+
+ return size;
+ }
+};
+
+/* static */ void
+nsRuleNode::SetFontSize(nsPresContext* aPresContext,
+ nsStyleContext* aContext,
+ const nsRuleData* aRuleData,
+ const nsStyleFont* aFont,
+ const nsStyleFont* aParentFont,
+ nscoord* aSize,
+ const nsFont& aSystemFont,
+ nscoord aParentSize,
+ nscoord aScriptLevelAdjustedParentSize,
+ bool aUsedStartStruct,
+ bool aAtRoot,
+ RuleNodeCacheConditions& aConditions)
+{
+ // If false, means that *aSize has not been zoomed. If true, means that
+ // *aSize has been zoomed iff aParentFont->mAllowZoom is true.
+ bool sizeIsZoomedAccordingToParent = false;
+
+ int32_t baseSize = (int32_t) aPresContext->
+ GetDefaultFont(aFont->mGenericID, aFont->mLanguage)->size;
+ const nsCSSValue* sizeValue = aRuleData->ValueForFontSize();
+ if (eCSSUnit_Enumerated == sizeValue->GetUnit()) {
+ int32_t value = sizeValue->GetIntValue();
+
+ if ((NS_STYLE_FONT_SIZE_XXSMALL <= value) &&
+ (value <= NS_STYLE_FONT_SIZE_XXLARGE)) {
+ *aSize = CalcFontPointSize(value, baseSize,
+ aPresContext, eFontSize_CSS);
+ }
+ else if (NS_STYLE_FONT_SIZE_XXXLARGE == value) {
+ // <font size="7"> is not specified in CSS, so we don't use eFontSize_CSS.
+ *aSize = CalcFontPointSize(value, baseSize, aPresContext);
+ }
+ else if (NS_STYLE_FONT_SIZE_LARGER == value ||
+ NS_STYLE_FONT_SIZE_SMALLER == value) {
+ aConditions.SetUncacheable();
+
+ // Un-zoom so we use the tables correctly. We'll then rezoom due
+ // to the |zoom = true| above.
+ // Note that relative units here use the parent's size unadjusted
+ // for scriptlevel changes. A scriptlevel change between us and the parent
+ // is simply ignored.
+ nscoord parentSize = aParentSize;
+ if (aParentFont->mAllowZoom) {
+ parentSize = nsStyleFont::UnZoomText(aPresContext, parentSize);
+ }
+
+ if (NS_STYLE_FONT_SIZE_LARGER == value) {
+ *aSize = FindNextLargerFontSize(parentSize,
+ baseSize, aPresContext, eFontSize_CSS);
+
+ NS_ASSERTION(*aSize >= parentSize,
+ "FindNextLargerFontSize failed");
+ }
+ else {
+ *aSize = FindNextSmallerFontSize(parentSize,
+ baseSize, aPresContext, eFontSize_CSS);
+ NS_ASSERTION(*aSize < parentSize ||
+ parentSize <= nsPresContext::CSSPixelsToAppUnits(1),
+ "FindNextSmallerFontSize failed");
+ }
+ } else {
+ NS_NOTREACHED("unexpected value");
+ }
+ }
+ else if (sizeValue->IsLengthUnit() ||
+ sizeValue->GetUnit() == eCSSUnit_Percent ||
+ sizeValue->IsCalcUnit()) {
+ SetFontSizeCalcOps ops(aParentSize, aParentFont,
+ aPresContext, aContext,
+ aAtRoot,
+ aConditions);
+ *aSize = css::ComputeCalc(*sizeValue, ops);
+ if (*aSize < 0) {
+ MOZ_ASSERT(sizeValue->IsCalcUnit(),
+ "negative lengths and percents should be rejected by parser");
+ *aSize = 0;
+ }
+ // The calc ops will always zoom its result according to the value
+ // of aParentFont->mAllowZoom.
+ sizeIsZoomedAccordingToParent = true;
+ }
+ else if (eCSSUnit_System_Font == sizeValue->GetUnit()) {
+ // this becomes our cascading size
+ *aSize = aSystemFont.size;
+ }
+ else if (eCSSUnit_Inherit == sizeValue->GetUnit() ||
+ eCSSUnit_Unset == sizeValue->GetUnit()) {
+ aConditions.SetUncacheable();
+ // We apply scriptlevel change for this case, because the default is
+ // to inherit and we don't want explicit "inherit" to differ from the
+ // default.
+ *aSize = aScriptLevelAdjustedParentSize;
+ sizeIsZoomedAccordingToParent = true;
+ }
+ else if (eCSSUnit_Initial == sizeValue->GetUnit()) {
+ // The initial value is 'medium', which has magical sizing based on
+ // the generic font family, so do that here too.
+ *aSize = baseSize;
+ } else {
+ NS_ASSERTION(eCSSUnit_Null == sizeValue->GetUnit(),
+ "What kind of font-size value is this?");
+ // if aUsedStartStruct is true, then every single property in the
+ // font struct is being set all at once. This means scriptlevel is not
+ // going to have any influence on the font size; there is no need to
+ // do anything here.
+ if (!aUsedStartStruct && aParentSize != aScriptLevelAdjustedParentSize) {
+ // There was no rule affecting the size but the size has been
+ // affected by the parent's size via scriptlevel change. So we cannot
+ // store the data in the rule tree.
+ aConditions.SetUncacheable();
+ *aSize = aScriptLevelAdjustedParentSize;
+ sizeIsZoomedAccordingToParent = true;
+ } else {
+ return;
+ }
+ }
+
+ // We want to zoom the cascaded size so that em-based measurements,
+ // line-heights, etc., work.
+ bool currentlyZoomed = sizeIsZoomedAccordingToParent &&
+ aParentFont->mAllowZoom;
+ if (!currentlyZoomed && aFont->mAllowZoom) {
+ *aSize = nsStyleFont::ZoomText(aPresContext, *aSize);
+ } else if (currentlyZoomed && !aFont->mAllowZoom) {
+ *aSize = nsStyleFont::UnZoomText(aPresContext, *aSize);
+ }
+}
+
+static int8_t ClampTo8Bit(int32_t aValue) {
+ if (aValue < -128)
+ return -128;
+ if (aValue > 127)
+ return 127;
+ return int8_t(aValue);
+}
+
+/* static */ void
+nsRuleNode::SetFont(nsPresContext* aPresContext, nsStyleContext* aContext,
+ uint8_t aGenericFontID, const nsRuleData* aRuleData,
+ const nsStyleFont* aParentFont,
+ nsStyleFont* aFont, bool aUsedStartStruct,
+ RuleNodeCacheConditions& aConditions)
+{
+ bool atRoot = !aContext->GetParent();
+
+ // -x-text-zoom: none, inherit, initial
+ bool allowZoom;
+ const nsCSSValue* textZoomValue = aRuleData->ValueForTextZoom();
+ if (eCSSUnit_Null != textZoomValue->GetUnit()) {
+ if (eCSSUnit_Inherit == textZoomValue->GetUnit()) {
+ allowZoom = aParentFont->mAllowZoom;
+ } else if (eCSSUnit_None == textZoomValue->GetUnit()) {
+ allowZoom = false;
+ } else {
+ MOZ_ASSERT(eCSSUnit_Initial == textZoomValue->GetUnit(),
+ "unexpected unit");
+ allowZoom = true;
+ }
+ aFont->EnableZoom(aPresContext, allowZoom);
+ }
+
+ // mLanguage must be set before before any of the CalcLengthWith calls
+ // (direct calls or calls via SetFontSize) for the cases where |aParentFont|
+ // is the same as |aFont|.
+ //
+ // -x-lang: string, inherit
+ // This is not a real CSS property, it is an HTML attribute mapped to CSS.
+ const nsCSSValue* langValue = aRuleData->ValueForLang();
+ if (eCSSUnit_Ident == langValue->GetUnit()) {
+ nsAutoString lang;
+ langValue->GetStringValue(lang);
+
+ nsContentUtils::ASCIIToLower(lang);
+ aFont->mLanguage = NS_Atomize(lang);
+ aFont->mExplicitLanguage = true;
+ }
+
+ const nsFont* defaultVariableFont =
+ aPresContext->GetDefaultFont(kPresContext_DefaultVariableFont_ID,
+ aFont->mLanguage);
+
+ // -moz-system-font: enum (never inherit!)
+ static_assert(
+ NS_STYLE_FONT_CAPTION == LookAndFeel::eFont_Caption &&
+ NS_STYLE_FONT_ICON == LookAndFeel::eFont_Icon &&
+ NS_STYLE_FONT_MENU == LookAndFeel::eFont_Menu &&
+ NS_STYLE_FONT_MESSAGE_BOX == LookAndFeel::eFont_MessageBox &&
+ NS_STYLE_FONT_SMALL_CAPTION == LookAndFeel::eFont_SmallCaption &&
+ NS_STYLE_FONT_STATUS_BAR == LookAndFeel::eFont_StatusBar &&
+ NS_STYLE_FONT_WINDOW == LookAndFeel::eFont_Window &&
+ NS_STYLE_FONT_DOCUMENT == LookAndFeel::eFont_Document &&
+ NS_STYLE_FONT_WORKSPACE == LookAndFeel::eFont_Workspace &&
+ NS_STYLE_FONT_DESKTOP == LookAndFeel::eFont_Desktop &&
+ NS_STYLE_FONT_INFO == LookAndFeel::eFont_Info &&
+ NS_STYLE_FONT_DIALOG == LookAndFeel::eFont_Dialog &&
+ NS_STYLE_FONT_BUTTON == LookAndFeel::eFont_Button &&
+ NS_STYLE_FONT_PULL_DOWN_MENU == LookAndFeel::eFont_PullDownMenu &&
+ NS_STYLE_FONT_LIST == LookAndFeel::eFont_List &&
+ NS_STYLE_FONT_FIELD == LookAndFeel::eFont_Field,
+ "LookAndFeel.h system-font constants out of sync with nsStyleConsts.h");
+
+ // Fall back to defaultVariableFont.
+ nsFont systemFont = *defaultVariableFont;
+ const nsCSSValue* systemFontValue = aRuleData->ValueForSystemFont();
+ if (eCSSUnit_Enumerated == systemFontValue->GetUnit()) {
+ gfxFontStyle fontStyle;
+ LookAndFeel::FontID fontID =
+ (LookAndFeel::FontID)systemFontValue->GetIntValue();
+ float devPerCSS =
+ (float)nsPresContext::AppUnitsPerCSSPixel() /
+ aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+ nsAutoString systemFontName;
+ if (LookAndFeel::GetFont(fontID, systemFontName, fontStyle, devPerCSS)) {
+ systemFontName.Trim("\"'");
+ systemFont.fontlist = FontFamilyList(systemFontName, eUnquotedName);
+ systemFont.fontlist.SetDefaultFontType(eFamily_none);
+ systemFont.style = fontStyle.style;
+ systemFont.systemFont = fontStyle.systemFont;
+ systemFont.weight = fontStyle.weight;
+ systemFont.stretch = fontStyle.stretch;
+ systemFont.size =
+ NSFloatPixelsToAppUnits(fontStyle.size,
+ aPresContext->DeviceContext()->
+ AppUnitsPerDevPixelAtUnitFullZoom());
+ //systemFont.langGroup = fontStyle.langGroup;
+ systemFont.sizeAdjust = fontStyle.sizeAdjust;
+
+#ifdef XP_WIN
+ // XXXldb This platform-specific stuff should be in the
+ // LookAndFeel implementation, not here.
+ // XXXzw Should we even still *have* this code? It looks to be making
+ // old, probably obsolete assumptions.
+
+ if (fontID == LookAndFeel::eFont_Field ||
+ fontID == LookAndFeel::eFont_Button ||
+ fontID == LookAndFeel::eFont_List) {
+ // As far as I can tell the system default fonts and sizes
+ // on MS-Windows for Buttons, Listboxes/Comboxes and Text Fields are
+ // all pre-determined and cannot be changed by either the control panel
+ // or programmatically.
+ // Fields (text fields)
+ // Button and Selects (listboxes/comboboxes)
+ // We use whatever font is defined by the system. Which it appears
+ // (and the assumption is) it is always a proportional font. Then we
+ // always use 2 points smaller than what the browser has defined as
+ // the default proportional font.
+ // Assumption: system defined font is proportional
+ systemFont.size =
+ std::max(defaultVariableFont->size -
+ nsPresContext::CSSPointsToAppUnits(2), 0);
+ }
+#endif
+ }
+ }
+
+ // font-family: font family list, enum, inherit
+ const nsCSSValue* familyValue = aRuleData->ValueForFontFamily();
+ NS_ASSERTION(eCSSUnit_Enumerated != familyValue->GetUnit(),
+ "system fonts should not be in mFamily anymore");
+ if (eCSSUnit_FontFamilyList == familyValue->GetUnit()) {
+ // set the correct font if we are using DocumentFonts OR we are overriding for XUL
+ // MJA: bug 31816
+ bool useDocumentFonts =
+ aPresContext->GetCachedBoolPref(kPresContext_UseDocumentFonts);
+ if (aGenericFontID == kGenericFont_NONE ||
+ (!useDocumentFonts && (aGenericFontID == kGenericFont_cursive ||
+ aGenericFontID == kGenericFont_fantasy))) {
+ FontFamilyType defaultGeneric =
+ defaultVariableFont->fontlist.FirstGeneric();
+ MOZ_ASSERT(defaultVariableFont->fontlist.Length() == 1 &&
+ (defaultGeneric == eFamily_serif ||
+ defaultGeneric == eFamily_sans_serif));
+ if (defaultGeneric != eFamily_none) {
+ if (useDocumentFonts) {
+ aFont->mFont.fontlist.SetDefaultFontType(defaultGeneric);
+ } else {
+ // Either prioritize the first generic in the list,
+ // or (if there isn't one) prepend the default variable font.
+ if (!aFont->mFont.fontlist.PrioritizeFirstGeneric()) {
+ aFont->mFont.fontlist.PrependGeneric(defaultGeneric);
+ }
+ }
+ }
+ } else {
+ aFont->mFont.fontlist.SetDefaultFontType(eFamily_none);
+ }
+ aFont->mFont.systemFont = false;
+ // Technically this is redundant with the code below, but it's good
+ // to have since we'll still want it once we get rid of
+ // SetGenericFont (bug 380915).
+ aFont->mGenericID = aGenericFontID;
+ }
+ else if (eCSSUnit_System_Font == familyValue->GetUnit()) {
+ aFont->mFont.fontlist = systemFont.fontlist;
+ aFont->mFont.systemFont = true;
+ aFont->mGenericID = kGenericFont_NONE;
+ }
+ else if (eCSSUnit_Inherit == familyValue->GetUnit() ||
+ eCSSUnit_Unset == familyValue->GetUnit()) {
+ aConditions.SetUncacheable();
+ aFont->mFont.fontlist = aParentFont->mFont.fontlist;
+ aFont->mFont.systemFont = aParentFont->mFont.systemFont;
+ aFont->mGenericID = aParentFont->mGenericID;
+ }
+ else if (eCSSUnit_Initial == familyValue->GetUnit()) {
+ aFont->mFont.fontlist = defaultVariableFont->fontlist;
+ aFont->mFont.systemFont = defaultVariableFont->systemFont;
+ aFont->mGenericID = kGenericFont_NONE;
+ }
+
+ // When we're in the loop in SetGenericFont, we must ensure that we
+ // always keep aFont->mFlags set to the correct generic. But we have
+ // to be careful not to touch it when we're called directly from
+ // ComputeFontData, because we could have a start struct.
+ if (aGenericFontID != kGenericFont_NONE) {
+ aFont->mGenericID = aGenericFontID;
+ }
+
+ // -moz-math-variant: enum, inherit, initial
+ SetValue(*aRuleData->ValueForMathVariant(), aFont->mMathVariant,
+ aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mMathVariant, NS_MATHML_MATHVARIANT_NONE);
+
+ // -moz-math-display: enum, inherit, initial
+ SetValue(*aRuleData->ValueForMathDisplay(), aFont->mMathDisplay,
+ aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mMathDisplay, NS_MATHML_DISPLAYSTYLE_INLINE);
+
+ // font-smoothing: enum, inherit, initial
+ SetValue(*aRuleData->ValueForOsxFontSmoothing(),
+ aFont->mFont.smoothing, aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.smoothing,
+ defaultVariableFont->smoothing);
+
+ // font-style: enum, inherit, initial, -moz-system-font
+ if (aFont->mMathVariant != NS_MATHML_MATHVARIANT_NONE) {
+ // -moz-math-variant overrides font-style
+ aFont->mFont.style = NS_FONT_STYLE_NORMAL;
+ } else {
+ SetValue(*aRuleData->ValueForFontStyle(),
+ aFont->mFont.style, aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.style,
+ defaultVariableFont->style,
+ Unused, Unused, Unused, systemFont.style);
+ }
+
+ // font-weight: int, enum, inherit, initial, -moz-system-font
+ // special handling for enum
+ const nsCSSValue* weightValue = aRuleData->ValueForFontWeight();
+ if (aFont->mMathVariant != NS_MATHML_MATHVARIANT_NONE) {
+ // -moz-math-variant overrides font-weight
+ aFont->mFont.weight = NS_FONT_WEIGHT_NORMAL;
+ } else if (eCSSUnit_Enumerated == weightValue->GetUnit()) {
+ int32_t value = weightValue->GetIntValue();
+ switch (value) {
+ case NS_STYLE_FONT_WEIGHT_NORMAL:
+ case NS_STYLE_FONT_WEIGHT_BOLD:
+ aFont->mFont.weight = value;
+ break;
+ case NS_STYLE_FONT_WEIGHT_BOLDER: {
+ aConditions.SetUncacheable();
+ int32_t inheritedValue = aParentFont->mFont.weight;
+ if (inheritedValue <= 300) {
+ aFont->mFont.weight = 400;
+ } else if (inheritedValue <= 500) {
+ aFont->mFont.weight = 700;
+ } else {
+ aFont->mFont.weight = 900;
+ }
+ break;
+ }
+ case NS_STYLE_FONT_WEIGHT_LIGHTER: {
+ aConditions.SetUncacheable();
+ int32_t inheritedValue = aParentFont->mFont.weight;
+ if (inheritedValue < 600) {
+ aFont->mFont.weight = 100;
+ } else if (inheritedValue < 800) {
+ aFont->mFont.weight = 400;
+ } else {
+ aFont->mFont.weight = 700;
+ }
+ break;
+ }
+ }
+ } else
+ SetValue(*weightValue, aFont->mFont.weight, aConditions,
+ SETVAL_INTEGER | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.weight,
+ defaultVariableFont->weight,
+ Unused, Unused, Unused, systemFont.weight);
+
+ // font-stretch: enum, inherit, initial, -moz-system-font
+ SetValue(*aRuleData->ValueForFontStretch(),
+ aFont->mFont.stretch, aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.stretch,
+ defaultVariableFont->stretch,
+ Unused, Unused, Unused, systemFont.stretch);
+
+ // Compute scriptlevel, scriptminsize and scriptsizemultiplier now so
+ // they're available for font-size computation.
+
+ // -moz-script-min-size: length
+ const nsCSSValue* scriptMinSizeValue = aRuleData->ValueForScriptMinSize();
+ if (scriptMinSizeValue->IsLengthUnit()) {
+ // scriptminsize in font units (em, ex) has to be interpreted relative
+ // to the parent font, or the size definitions are circular and we
+ //
+ aFont->mScriptMinSize =
+ CalcLengthWith(*scriptMinSizeValue, aParentFont->mSize,
+ aParentFont,
+ aContext, aPresContext, atRoot, true /* aUseUserFontSet */,
+ aConditions);
+ }
+
+ // -moz-script-size-multiplier: factor, inherit, initial
+ SetFactor(*aRuleData->ValueForScriptSizeMultiplier(),
+ aFont->mScriptSizeMultiplier,
+ aConditions, aParentFont->mScriptSizeMultiplier,
+ NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER,
+ SETFCT_POSITIVE | SETFCT_UNSET_INHERIT);
+
+ // -moz-script-level: integer, number, inherit
+ const nsCSSValue* scriptLevelValue = aRuleData->ValueForScriptLevel();
+ if (eCSSUnit_Integer == scriptLevelValue->GetUnit()) {
+ // "relative"
+ aConditions.SetUncacheable();
+ aFont->mScriptLevel = ClampTo8Bit(aParentFont->mScriptLevel + scriptLevelValue->GetIntValue());
+ }
+ else if (eCSSUnit_Number == scriptLevelValue->GetUnit()) {
+ // "absolute"
+ aFont->mScriptLevel = ClampTo8Bit(int32_t(scriptLevelValue->GetFloatValue()));
+ }
+ else if (eCSSUnit_Auto == scriptLevelValue->GetUnit()) {
+ // auto
+ aConditions.SetUncacheable();
+ aFont->mScriptLevel = ClampTo8Bit(aParentFont->mScriptLevel +
+ (aParentFont->mMathDisplay ==
+ NS_MATHML_DISPLAYSTYLE_INLINE ? 1 : 0));
+ }
+ else if (eCSSUnit_Inherit == scriptLevelValue->GetUnit() ||
+ eCSSUnit_Unset == scriptLevelValue->GetUnit()) {
+ aConditions.SetUncacheable();
+ aFont->mScriptLevel = aParentFont->mScriptLevel;
+ }
+ else if (eCSSUnit_Initial == scriptLevelValue->GetUnit()) {
+ aFont->mScriptLevel = 0;
+ }
+
+ // font-kerning: none, enum, inherit, initial, -moz-system-font
+ SetValue(*aRuleData->ValueForFontKerning(),
+ aFont->mFont.kerning, aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.kerning,
+ defaultVariableFont->kerning,
+ Unused, Unused, Unused, systemFont.kerning);
+
+ // font-synthesis: none, enum (bit field), inherit, initial, -moz-system-font
+ SetValue(*aRuleData->ValueForFontSynthesis(),
+ aFont->mFont.synthesis, aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.synthesis,
+ defaultVariableFont->synthesis,
+ Unused, /* none */ 0, Unused, systemFont.synthesis);
+
+ // font-variant-alternates: normal, enum (bit field) + functions, inherit,
+ // initial, -moz-system-font
+ const nsCSSValue* variantAlternatesValue =
+ aRuleData->ValueForFontVariantAlternates();
+ int32_t variantAlternates = 0;
+
+ switch (variantAlternatesValue->GetUnit()) {
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Unset:
+ aFont->mFont.CopyAlternates(aParentFont->mFont);
+ aConditions.SetUncacheable();
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Normal:
+ aFont->mFont.variantAlternates = 0;
+ aFont->mFont.alternateValues.Clear();
+ aFont->mFont.featureValueLookup = nullptr;
+ break;
+
+ case eCSSUnit_Pair:
+ NS_ASSERTION(variantAlternatesValue->GetPairValue().mXValue.GetUnit() ==
+ eCSSUnit_Enumerated, "strange unit for variantAlternates");
+ variantAlternates =
+ variantAlternatesValue->GetPairValue().mXValue.GetIntValue();
+ aFont->mFont.variantAlternates = variantAlternates;
+
+ if (variantAlternates & NS_FONT_VARIANT_ALTERNATES_FUNCTIONAL_MASK) {
+ // fetch the feature lookup object from the styleset
+ MOZ_ASSERT(aPresContext->StyleSet()->IsGecko(),
+ "ServoStyleSets should not have rule nodes");
+ aFont->mFont.featureValueLookup =
+ aPresContext->StyleSet()->AsGecko()->GetFontFeatureValuesLookup();
+
+ NS_ASSERTION(variantAlternatesValue->GetPairValue().mYValue.GetUnit() ==
+ eCSSUnit_List, "function list not a list value");
+ nsStyleUtil::ComputeFunctionalAlternates(
+ variantAlternatesValue->GetPairValue().mYValue.GetListValue(),
+ aFont->mFont.alternateValues);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // font-variant-caps: normal, enum, inherit, initial, -moz-system-font
+ SetValue(*aRuleData->ValueForFontVariantCaps(),
+ aFont->mFont.variantCaps, aConditions,
+ SETVAL_ENUMERATED |SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.variantCaps,
+ defaultVariableFont->variantCaps,
+ Unused, Unused, /* normal */ 0, systemFont.variantCaps);
+
+ // font-variant-east-asian: normal, enum (bit field), inherit, initial,
+ // -moz-system-font
+ SetValue(*aRuleData->ValueForFontVariantEastAsian(),
+ aFont->mFont.variantEastAsian, aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.variantEastAsian,
+ defaultVariableFont->variantEastAsian,
+ Unused, Unused, /* normal */ 0, systemFont.variantEastAsian);
+
+ // font-variant-ligatures: normal, none, enum (bit field), inherit, initial,
+ // -moz-system-font
+ SetValue(*aRuleData->ValueForFontVariantLigatures(),
+ aFont->mFont.variantLigatures, aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.variantLigatures,
+ defaultVariableFont->variantLigatures,
+ Unused, NS_FONT_VARIANT_LIGATURES_NONE, /* normal */ 0,
+ systemFont.variantLigatures);
+
+ // font-variant-numeric: normal, enum (bit field), inherit, initial,
+ // -moz-system-font
+ SetValue(*aRuleData->ValueForFontVariantNumeric(),
+ aFont->mFont.variantNumeric, aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.variantNumeric,
+ defaultVariableFont->variantNumeric,
+ Unused, Unused, /* normal */ 0, systemFont.variantNumeric);
+
+ // font-variant-position: normal, enum, inherit, initial,
+ // -moz-system-font
+ SetValue(*aRuleData->ValueForFontVariantPosition(),
+ aFont->mFont.variantPosition, aConditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ aParentFont->mFont.variantPosition,
+ defaultVariableFont->variantPosition,
+ Unused, Unused, /* normal */ 0, systemFont.variantPosition);
+
+ // font-feature-settings
+ const nsCSSValue* featureSettingsValue =
+ aRuleData->ValueForFontFeatureSettings();
+
+ switch (featureSettingsValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Normal:
+ case eCSSUnit_Initial:
+ aFont->mFont.fontFeatureSettings.Clear();
+ break;
+
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Unset:
+ aConditions.SetUncacheable();
+ aFont->mFont.fontFeatureSettings = aParentFont->mFont.fontFeatureSettings;
+ break;
+
+ case eCSSUnit_System_Font:
+ aFont->mFont.fontFeatureSettings = systemFont.fontFeatureSettings;
+ break;
+
+ case eCSSUnit_PairList:
+ case eCSSUnit_PairListDep:
+ ComputeFontFeatures(featureSettingsValue->GetPairListValue(),
+ aFont->mFont.fontFeatureSettings);
+ break;
+
+ default:
+ MOZ_ASSERT(false, "unexpected value unit");
+ break;
+ }
+
+ // font-language-override
+ const nsCSSValue* languageOverrideValue =
+ aRuleData->ValueForFontLanguageOverride();
+ if (eCSSUnit_Inherit == languageOverrideValue->GetUnit() ||
+ eCSSUnit_Unset == languageOverrideValue->GetUnit()) {
+ aConditions.SetUncacheable();
+ aFont->mFont.languageOverride = aParentFont->mFont.languageOverride;
+ } else if (eCSSUnit_Normal == languageOverrideValue->GetUnit() ||
+ eCSSUnit_Initial == languageOverrideValue->GetUnit()) {
+ aFont->mFont.languageOverride.Truncate();
+ } else if (eCSSUnit_System_Font == languageOverrideValue->GetUnit()) {
+ aFont->mFont.languageOverride = systemFont.languageOverride;
+ } else if (eCSSUnit_String == languageOverrideValue->GetUnit()) {
+ languageOverrideValue->GetStringValue(aFont->mFont.languageOverride);
+ }
+
+ // -moz-min-font-size-ratio: percent, inherit
+ const nsCSSValue* minFontSizeRatio = aRuleData->ValueForMinFontSizeRatio();
+ switch (minFontSizeRatio->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_Unset:
+ case eCSSUnit_Inherit:
+ aFont->mMinFontSizeRatio = aParentFont->mMinFontSizeRatio;
+ aConditions.SetUncacheable();
+ break;
+ case eCSSUnit_Initial:
+ aFont->mMinFontSizeRatio = 100; // 100%
+ break;
+ case eCSSUnit_Percent: {
+ // While percentages are parsed as floating point numbers, we
+ // only store an integer in the range [0, 255] since that's all
+ // we need for now.
+ float percent = minFontSizeRatio->GetPercentValue() * 100;
+ if (percent < 0) {
+ percent = 0;
+ } else if (percent > 255) {
+ percent = 255;
+ }
+ aFont->mMinFontSizeRatio = uint8_t(percent);
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown unit for -moz-min-font-size-ratio");
+ }
+
+ nscoord scriptLevelAdjustedUnconstrainedParentSize;
+
+ // font-size: enum, length, percent, inherit
+ nscoord scriptLevelAdjustedParentSize =
+ ComputeScriptLevelSize(aFont, aParentFont, aPresContext,
+ &scriptLevelAdjustedUnconstrainedParentSize);
+ NS_ASSERTION(!aUsedStartStruct || aFont->mScriptUnconstrainedSize == aFont->mSize,
+ "If we have a start struct, we should have reset everything coming in here");
+
+ // Compute whether we're affected by scriptMinSize *before* calling
+ // SetFontSize, since aParentFont might be the same as aFont. If it
+ // is, calling SetFontSize might throw off our calculation.
+ bool affectedByScriptMinSize =
+ aParentFont->mSize != aParentFont->mScriptUnconstrainedSize ||
+ scriptLevelAdjustedParentSize !=
+ scriptLevelAdjustedUnconstrainedParentSize;
+
+ SetFontSize(aPresContext, aContext,
+ aRuleData, aFont, aParentFont,
+ &aFont->mSize,
+ systemFont, aParentFont->mSize, scriptLevelAdjustedParentSize,
+ aUsedStartStruct, atRoot, aConditions);
+ if (!aPresContext->Document()->GetMathMLEnabled()) {
+ MOZ_ASSERT(!affectedByScriptMinSize);
+ // If MathML is not enabled, we don't need to mark this node as
+ // uncacheable. If it becomes enabled, code in
+ // nsMathMLElementFactory will rebuild the rule tree and style data
+ // when MathML is first enabled (see nsMathMLElement::BindToTree).
+ aFont->mScriptUnconstrainedSize = aFont->mSize;
+ } else if (!affectedByScriptMinSize) {
+ // Fast path: we have not been affected by scriptminsize so we don't
+ // need to call SetFontSize again to compute the
+ // scriptminsize-unconstrained size. This is OK even if we have a
+ // start struct, because if we have a start struct then 'font-size'
+ // was specified and so scriptminsize has no effect.
+ aFont->mScriptUnconstrainedSize = aFont->mSize;
+ // It's possible we could, in the future, have a different parent,
+ // which would lead to a different affectedByScriptMinSize.
+ aConditions.SetUncacheable();
+ } else {
+ // see previous else-if
+ aConditions.SetUncacheable();
+
+ // Use a separate conditions object because it might get a
+ // *different* font-size dependency. We can ignore it because we've
+ // already called SetUncacheable.
+ RuleNodeCacheConditions unconstrainedConditions;
+
+ SetFontSize(aPresContext, aContext,
+ aRuleData, aFont, aParentFont,
+ &aFont->mScriptUnconstrainedSize,
+ systemFont, aParentFont->mScriptUnconstrainedSize,
+ scriptLevelAdjustedUnconstrainedParentSize,
+ aUsedStartStruct, atRoot, unconstrainedConditions);
+ }
+ NS_ASSERTION(aFont->mScriptUnconstrainedSize <= aFont->mSize,
+ "scriptminsize should never be making things bigger");
+
+ nscoord fontSize = aFont->mSize;
+
+ // enforce the user' specified minimum font-size on the value that we expose
+ // (but don't change font-size:0, since that would unhide hidden text)
+ if (fontSize > 0) {
+ nscoord minFontSize = aPresContext->MinFontSize(aFont->mLanguage);
+ if (minFontSize < 0) {
+ minFontSize = 0;
+ } else {
+ minFontSize = (minFontSize * aFont->mMinFontSizeRatio) / 100;
+ }
+ if (fontSize < minFontSize && !aPresContext->IsChrome()) {
+ // override the minimum font-size constraint
+ fontSize = minFontSize;
+ }
+ }
+ aFont->mFont.size = fontSize;
+
+ // font-size-adjust: number, none, inherit, initial, -moz-system-font
+ const nsCSSValue* sizeAdjustValue = aRuleData->ValueForFontSizeAdjust();
+ if (eCSSUnit_System_Font == sizeAdjustValue->GetUnit()) {
+ aFont->mFont.sizeAdjust = systemFont.sizeAdjust;
+ } else
+ SetFactor(*sizeAdjustValue, aFont->mFont.sizeAdjust,
+ aConditions, aParentFont->mFont.sizeAdjust, -1.0f,
+ SETFCT_NONE | SETFCT_UNSET_INHERIT);
+}
+
+/* static */ void
+nsRuleNode::ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList,
+ nsTArray<gfxFontFeature>& aFeatureSettings)
+{
+ aFeatureSettings.Clear();
+ for (const nsCSSValuePairList* p = aFeaturesList; p; p = p->mNext) {
+ gfxFontFeature feat = {0, 0};
+
+ MOZ_ASSERT(aFeaturesList->mXValue.GetUnit() == eCSSUnit_String,
+ "unexpected value unit");
+
+ // tag is a 4-byte ASCII sequence
+ nsAutoString tag;
+ p->mXValue.GetStringValue(tag);
+ if (tag.Length() != 4) {
+ continue;
+ }
+ // parsing validates that these are ASCII chars
+ // tags are always big-endian
+ feat.mTag = (tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8) | tag[3];
+
+ // value
+ NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Integer,
+ "should have found an integer unit");
+ feat.mValue = p->mYValue.GetIntValue();
+
+ aFeatureSettings.AppendElement(feat);
+ }
+}
+
+// This should die (bug 380915).
+//
+// SetGenericFont:
+// - backtrack to an ancestor with the same generic font name (possibly
+// up to the root where default values come from the presentation context)
+// - re-apply cascading rules from there without caching intermediate values
+/* static */ void
+nsRuleNode::SetGenericFont(nsPresContext* aPresContext,
+ nsStyleContext* aContext,
+ uint8_t aGenericFontID,
+ nsStyleFont* aFont)
+{
+ // walk up the contexts until a context with the desired generic font
+ AutoTArray<nsStyleContext*, 8> contextPath;
+ contextPath.AppendElement(aContext);
+ nsStyleContext* higherContext = aContext->GetParent();
+ while (higherContext) {
+ if (higherContext->StyleFont()->mGenericID == aGenericFontID) {
+ // done walking up the higher contexts
+ break;
+ }
+ contextPath.AppendElement(higherContext);
+ higherContext = higherContext->GetParent();
+ }
+
+ // re-apply the cascading rules, starting from the higher context
+
+ // If we stopped earlier because we reached the root of the style tree,
+ // we will start with the default generic font from the presentation
+ // context. Otherwise we start with the higher context.
+ const nsFont* defaultFont =
+ aPresContext->GetDefaultFont(aGenericFontID, aFont->mLanguage);
+ nsStyleFont parentFont(*defaultFont, aPresContext);
+ if (higherContext) {
+ const nsStyleFont* tmpFont = higherContext->StyleFont();
+ parentFont = *tmpFont;
+ }
+ *aFont = parentFont;
+
+ uint32_t fontBit = nsCachedStyleData::GetBitForSID(eStyleStruct_Font);
+
+ // use placement new[] on the result of alloca() to allocate a
+ // variable-sized stack array, including execution of constructors,
+ // and use an RAII class to run the destructors too.
+ size_t nprops = nsCSSProps::PropertyCountInStruct(eStyleStruct_Font);
+ void* dataStorage = alloca(nprops * sizeof(nsCSSValue));
+
+ for (int32_t i = contextPath.Length() - 1; i >= 0; --i) {
+ nsStyleContext* context = contextPath[i];
+ AutoCSSValueArray dataArray(dataStorage, nprops);
+
+ nsRuleData ruleData(NS_STYLE_INHERIT_BIT(Font), dataArray.get(),
+ aPresContext, context);
+ ruleData.mValueOffsets[eStyleStruct_Font] = 0;
+
+ // Trimmed down version of ::WalkRuleTree() to re-apply the style rules
+ // Note that we *do* need to do this for our own data, since what is
+ // in |fontData| in ComputeFontData is only for the rules below
+ // aStartStruct.
+ for (nsRuleNode* ruleNode = context->RuleNode(); ruleNode;
+ ruleNode = ruleNode->GetParent()) {
+ if (ruleNode->mNoneBits & fontBit)
+ // no more font rules on this branch, get out
+ break;
+
+ nsIStyleRule *rule = ruleNode->GetRule();
+ if (rule) {
+ ruleData.mLevel = ruleNode->GetLevel();
+ ruleData.mIsImportantRule = ruleNode->IsImportantRule();
+ rule->MapRuleInfoInto(&ruleData);
+ }
+ }
+
+ // Compute the delta from the information that the rules specified
+
+ // Avoid unnecessary operations in SetFont(). But we care if it's
+ // the final value that we're computing.
+ if (i != 0)
+ ruleData.ValueForFontFamily()->Reset();
+
+ ResolveVariableReferences(eStyleStruct_Font, &ruleData, aContext);
+
+ RuleNodeCacheConditions dummy;
+ nsRuleNode::SetFont(aPresContext, context,
+ aGenericFontID, &ruleData, &parentFont, aFont,
+ false, dummy);
+
+ parentFont = *aFont;
+ }
+
+ if (higherContext && contextPath.Length() > 1) {
+ // contextPath is a list of all ancestor style contexts, so it must have
+ // at least two elements for it to result in a dependency on grandancestor
+ // styles.
+ PropagateGrandancestorBit(aContext, higherContext);
+ }
+}
+
+const void*
+nsRuleNode::ComputeFontData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_INHERITED(Font, font, parentFont)
+
+ // NOTE: The |aRuleDetail| passed in is a little bit conservative due
+ // to the -moz-system-font property. We really don't need to consider
+ // it here in determining whether to cache in the rule tree. However,
+ // we do need to consider it in WalkRuleTree when deciding whether to
+ // walk further up the tree. So this means that when the font struct
+ // is fully specified using *longhand* properties (excluding
+ // -moz-system-font), we won't cache in the rule tree even though we
+ // could. However, it's pretty unlikely authors will do that
+ // (although there is a pretty good chance they'll fully specify it
+ // using the 'font' shorthand).
+
+ // Figure out if we are a generic font
+ uint8_t generic = kGenericFont_NONE;
+ // XXXldb What if we would have had a string if we hadn't been doing
+ // the optimization with a non-null aStartStruct?
+ const nsCSSValue* familyValue = aRuleData->ValueForFontFamily();
+ if (eCSSUnit_FontFamilyList == familyValue->GetUnit()) {
+ const FontFamilyList* fontlist = familyValue->GetFontFamilyListValue();
+ FontFamilyList& fl = font->mFont.fontlist;
+ fl = *fontlist;
+
+ // extract the first generic in the fontlist, if exists
+ FontFamilyType fontType = fontlist->FirstGeneric();
+
+ // if only a single generic, set the generic type
+ if (fontlist->Length() == 1) {
+ switch (fontType) {
+ case eFamily_serif:
+ generic = kGenericFont_serif;
+ break;
+ case eFamily_sans_serif:
+ generic = kGenericFont_sans_serif;
+ break;
+ case eFamily_monospace:
+ generic = kGenericFont_monospace;
+ break;
+ case eFamily_cursive:
+ generic = kGenericFont_cursive;
+ break;
+ case eFamily_fantasy:
+ generic = kGenericFont_fantasy;
+ break;
+ case eFamily_moz_fixed:
+ generic = kGenericFont_moz_fixed;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // Now compute our font struct
+ if (generic == kGenericFont_NONE) {
+ // continue the normal processing
+ nsRuleNode::SetFont(mPresContext, aContext, generic,
+ aRuleData, parentFont, font,
+ aStartStruct != nullptr, conditions);
+ }
+ else {
+ // re-calculate the font as a generic font
+ conditions.SetUncacheable();
+ nsRuleNode::SetGenericFont(mPresContext, aContext, generic,
+ font);
+ }
+
+ COMPUTE_END_INHERITED(Font, font)
+}
+
+template <typename T>
+inline uint32_t ListLength(const T* aList)
+{
+ uint32_t len = 0;
+ while (aList) {
+ len++;
+ aList = aList->mNext;
+ }
+ return len;
+}
+
+static already_AddRefed<nsCSSShadowArray>
+GetShadowData(const nsCSSValueList* aList,
+ nsStyleContext* aContext,
+ bool aIsBoxShadow,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ uint32_t arrayLength = ListLength(aList);
+
+ MOZ_ASSERT(arrayLength > 0,
+ "Non-null text-shadow list, yet we counted 0 items.");
+ RefPtr<nsCSSShadowArray> shadowList =
+ new(arrayLength) nsCSSShadowArray(arrayLength);
+
+ if (!shadowList)
+ return nullptr;
+
+ nsStyleCoord tempCoord;
+ DebugOnly<bool> unitOK;
+ for (nsCSSShadowItem* item = shadowList->ShadowAt(0);
+ aList;
+ aList = aList->mNext, ++item) {
+ MOZ_ASSERT(aList->mValue.GetUnit() == eCSSUnit_Array,
+ "expecting a plain array value");
+ nsCSSValue::Array *arr = aList->mValue.GetArrayValue();
+ // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
+ unitOK = SetCoord(arr->Item(0), tempCoord, nsStyleCoord(),
+ SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
+ aContext, aPresContext, aConditions);
+ NS_ASSERTION(unitOK, "unexpected unit");
+ item->mXOffset = tempCoord.GetCoordValue();
+
+ unitOK = SetCoord(arr->Item(1), tempCoord, nsStyleCoord(),
+ SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
+ aContext, aPresContext, aConditions);
+ NS_ASSERTION(unitOK, "unexpected unit");
+ item->mYOffset = tempCoord.GetCoordValue();
+
+ // Blur radius is optional in the current box-shadow spec
+ if (arr->Item(2).GetUnit() != eCSSUnit_Null) {
+ unitOK = SetCoord(arr->Item(2), tempCoord, nsStyleCoord(),
+ SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY |
+ SETCOORD_CALC_CLAMP_NONNEGATIVE,
+ aContext, aPresContext, aConditions);
+ NS_ASSERTION(unitOK, "unexpected unit");
+ item->mRadius = tempCoord.GetCoordValue();
+ } else {
+ item->mRadius = 0;
+ }
+
+ // Find the spread radius
+ if (aIsBoxShadow && arr->Item(3).GetUnit() != eCSSUnit_Null) {
+ unitOK = SetCoord(arr->Item(3), tempCoord, nsStyleCoord(),
+ SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
+ aContext, aPresContext, aConditions);
+ NS_ASSERTION(unitOK, "unexpected unit");
+ item->mSpread = tempCoord.GetCoordValue();
+ } else {
+ item->mSpread = 0;
+ }
+
+ if (arr->Item(4).GetUnit() != eCSSUnit_Null) {
+ item->mHasColor = true;
+ // 2nd argument can be bogus since inherit is not a valid color
+ unitOK = SetColor(arr->Item(4), 0, aPresContext, aContext, item->mColor,
+ aConditions);
+ NS_ASSERTION(unitOK, "unexpected unit");
+ }
+
+ if (aIsBoxShadow && arr->Item(5).GetUnit() == eCSSUnit_Enumerated) {
+ NS_ASSERTION(arr->Item(5).GetIntValue()
+ == uint8_t(StyleBoxShadowType::Inset),
+ "invalid keyword type for box shadow");
+ item->mInset = true;
+ } else {
+ item->mInset = false;
+ }
+ }
+
+ return shadowList.forget();
+}
+
+struct TextEmphasisChars
+{
+ const char16_t* mFilled;
+ const char16_t* mOpen;
+};
+
+#define TEXT_EMPHASIS_CHARS_LIST() \
+ TEXT_EMPHASIS_CHARS_ITEM(u"", u"", NONE) \
+ TEXT_EMPHASIS_CHARS_ITEM(u"\u2022", u"\u25e6", DOT) \
+ TEXT_EMPHASIS_CHARS_ITEM(u"\u25cf", u"\u25cb", CIRCLE) \
+ TEXT_EMPHASIS_CHARS_ITEM(u"\u25c9", u"\u25ce", DOUBLE_CIRCLE) \
+ TEXT_EMPHASIS_CHARS_ITEM(u"\u25b2", u"\u25b3", TRIANGLE) \
+ TEXT_EMPHASIS_CHARS_ITEM(u"\ufe45", u"\ufe46", SESAME)
+
+static constexpr TextEmphasisChars kTextEmphasisChars[] =
+{
+#define TEXT_EMPHASIS_CHARS_ITEM(filled_, open_, type_) \
+ { filled_, open_ }, // type_
+ TEXT_EMPHASIS_CHARS_LIST()
+#undef TEXT_EMPHASIS_CHARS_ITEM
+};
+
+#define TEXT_EMPHASIS_CHARS_ITEM(filled_, open_, type_) \
+ static_assert(ArrayLength(filled_) <= 2 && \
+ ArrayLength(open_) <= 2, \
+ "emphasis marks should have no more than one char"); \
+ static_assert( \
+ *kTextEmphasisChars[NS_STYLE_TEXT_EMPHASIS_STYLE_##type_].mFilled == \
+ *filled_, "filled " #type_ " should be " #filled_); \
+ static_assert( \
+ *kTextEmphasisChars[NS_STYLE_TEXT_EMPHASIS_STYLE_##type_].mOpen == \
+ *open_, "open " #type_ " should be " #open_);
+TEXT_EMPHASIS_CHARS_LIST()
+#undef TEXT_EMPHASIS_CHARS_ITEM
+
+#undef TEXT_EMPHASIS_CHARS_LIST
+
+static void
+TruncateStringToSingleGrapheme(nsAString& aStr)
+{
+ unicode::ClusterIterator iter(aStr.Data(), aStr.Length());
+ if (!iter.AtEnd()) {
+ iter.Next();
+ if (!iter.AtEnd()) {
+ // Not mutating the string for common cases helps memory use
+ // since we share the buffer from the specified style into the
+ // computed style.
+ aStr.Truncate(iter - aStr.Data());
+ }
+ }
+}
+
+struct LineHeightCalcObj
+{
+ float mLineHeight;
+ bool mIsNumber;
+};
+
+struct SetLineHeightCalcOps : public css::NumbersAlreadyNormalizedOps
+{
+ typedef LineHeightCalcObj result_type;
+ nsStyleContext* const mStyleContext;
+ nsPresContext* const mPresContext;
+ RuleNodeCacheConditions& mConditions;
+
+ SetLineHeightCalcOps(nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+ : mStyleContext(aStyleContext),
+ mPresContext(aPresContext),
+ mConditions(aConditions)
+ {
+ }
+
+ result_type
+ MergeAdditive(nsCSSUnit aCalcFunction,
+ result_type aValue1, result_type aValue2)
+ {
+ MOZ_ASSERT(aValue1.mIsNumber == aValue2.mIsNumber);
+
+ LineHeightCalcObj result;
+ result.mIsNumber = aValue1.mIsNumber;
+ if (aCalcFunction == eCSSUnit_Calc_Plus) {
+ result.mLineHeight = aValue1.mLineHeight + aValue2.mLineHeight;
+ return result;
+ }
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
+ "unexpected unit");
+ result.mLineHeight = aValue1.mLineHeight - aValue2.mLineHeight;
+ return result;
+ }
+
+ result_type
+ MergeMultiplicativeL(nsCSSUnit aCalcFunction,
+ float aValue1, result_type aValue2)
+ {
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
+ "unexpected unit");
+ LineHeightCalcObj result;
+ result.mIsNumber = aValue2.mIsNumber;
+ result.mLineHeight = aValue1 * aValue2.mLineHeight;
+ return result;
+ }
+
+ result_type
+ MergeMultiplicativeR(nsCSSUnit aCalcFunction,
+ result_type aValue1, float aValue2)
+ {
+ LineHeightCalcObj result;
+ result.mIsNumber = aValue1.mIsNumber;
+ if (aCalcFunction == eCSSUnit_Calc_Times_R) {
+ result.mLineHeight = aValue1.mLineHeight * aValue2;
+ return result;
+ }
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided,
+ "unexpected unit");
+ result.mLineHeight = aValue1.mLineHeight / aValue2;
+ return result;
+ }
+
+ result_type ComputeLeafValue(const nsCSSValue& aValue)
+ {
+ LineHeightCalcObj result;
+ if (aValue.IsLengthUnit()) {
+ result.mIsNumber = false;
+ result.mLineHeight = CalcLength(aValue, mStyleContext,
+ mPresContext, mConditions);
+ }
+ else if (eCSSUnit_Percent == aValue.GetUnit()) {
+ mConditions.SetUncacheable();
+ result.mIsNumber = false;
+ nscoord fontSize = mStyleContext->StyleFont()->mFont.size;
+ result.mLineHeight = fontSize * aValue.GetPercentValue();
+ }
+ else if (eCSSUnit_Number == aValue.GetUnit()) {
+ result.mIsNumber = true;
+ result.mLineHeight = aValue.GetFloatValue();
+ } else {
+ MOZ_ASSERT(false, "unexpected value");
+ result.mIsNumber = true;
+ result.mLineHeight = 1.0f;
+ }
+
+ return result;
+ }
+};
+
+const void*
+nsRuleNode::ComputeTextData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_INHERITED(Text, text, parentText)
+
+ auto setComplexColor = [&](const nsCSSValue* aValue,
+ StyleComplexColor nsStyleText::* aField) {
+ SetComplexColor<eUnsetInherit>(*aValue, parentText->*aField,
+ StyleComplexColor::CurrentColor(),
+ mPresContext, text->*aField, conditions);
+ };
+
+ // tab-size: integer, inherit
+ SetValue(*aRuleData->ValueForTabSize(),
+ text->mTabSize, conditions,
+ SETVAL_INTEGER | SETVAL_UNSET_INHERIT, parentText->mTabSize,
+ NS_STYLE_TABSIZE_INITIAL);
+
+ // letter-spacing: normal, length, inherit
+ SetCoord(*aRuleData->ValueForLetterSpacing(),
+ text->mLetterSpacing, parentText->mLetterSpacing,
+ SETCOORD_LH | SETCOORD_NORMAL | SETCOORD_INITIAL_NORMAL |
+ SETCOORD_CALC_LENGTH_ONLY | SETCOORD_UNSET_INHERIT,
+ aContext, mPresContext, conditions);
+
+ // text-shadow: none, list, inherit, initial
+ const nsCSSValue* textShadowValue = aRuleData->ValueForTextShadow();
+ if (textShadowValue->GetUnit() != eCSSUnit_Null) {
+ text->mTextShadow = nullptr;
+
+ // Don't need to handle none/initial explicitly: The above assignment
+ // takes care of that
+ if (textShadowValue->GetUnit() == eCSSUnit_Inherit ||
+ textShadowValue->GetUnit() == eCSSUnit_Unset) {
+ conditions.SetUncacheable();
+ text->mTextShadow = parentText->mTextShadow;
+ } else if (textShadowValue->GetUnit() == eCSSUnit_List ||
+ textShadowValue->GetUnit() == eCSSUnit_ListDep) {
+ // List of arrays
+ text->mTextShadow = GetShadowData(textShadowValue->GetListValue(),
+ aContext, false, mPresContext, conditions);
+ }
+ }
+
+ // line-height: normal, number, length, percent, calc, inherit
+ const nsCSSValue* lineHeightValue = aRuleData->ValueForLineHeight();
+ if (eCSSUnit_Percent == lineHeightValue->GetUnit()) {
+ conditions.SetUncacheable();
+ // Use |mFont.size| to pick up minimum font size.
+ text->mLineHeight.SetCoordValue(
+ NSToCoordRound(float(aContext->StyleFont()->mFont.size) *
+ lineHeightValue->GetPercentValue()));
+ }
+ else if (eCSSUnit_Initial == lineHeightValue->GetUnit() ||
+ eCSSUnit_System_Font == lineHeightValue->GetUnit()) {
+ text->mLineHeight.SetNormalValue();
+ }
+ else if (eCSSUnit_Calc == lineHeightValue->GetUnit()) {
+ SetLineHeightCalcOps ops(aContext, mPresContext, conditions);
+ LineHeightCalcObj obj = css::ComputeCalc(*lineHeightValue, ops);
+ if (obj.mIsNumber) {
+ text->mLineHeight.SetFactorValue(obj.mLineHeight);
+ } else {
+ text->mLineHeight.SetCoordValue(
+ NSToCoordRoundWithClamp(obj.mLineHeight));
+ }
+ }
+ else {
+ SetCoord(*lineHeightValue, text->mLineHeight, parentText->mLineHeight,
+ SETCOORD_LEH | SETCOORD_FACTOR | SETCOORD_NORMAL |
+ SETCOORD_UNSET_INHERIT,
+ aContext, mPresContext, conditions);
+ if (lineHeightValue->IsLengthUnit() &&
+ !lineHeightValue->IsRelativeLengthUnit()) {
+ nscoord lh = nsStyleFont::ZoomText(mPresContext,
+ text->mLineHeight.GetCoordValue());
+
+ conditions.SetUncacheable();
+ const nsStyleFont *font = aContext->StyleFont();
+ nscoord minimumFontSize = mPresContext->MinFontSize(font->mLanguage);
+
+ if (minimumFontSize > 0 && !mPresContext->IsChrome()) {
+ if (font->mSize != 0) {
+ lh = nscoord(float(lh) * float(font->mFont.size) / float(font->mSize));
+ } else {
+ lh = minimumFontSize;
+ }
+ }
+ text->mLineHeight.SetCoordValue(lh);
+ }
+ }
+
+
+ // text-align: enum, string, pair(enum|string), inherit, initial
+ // NOTE: string is not implemented yet.
+ const nsCSSValue* textAlignValue = aRuleData->ValueForTextAlign();
+ text->mTextAlignTrue = false;
+ if (eCSSUnit_String == textAlignValue->GetUnit()) {
+ NS_NOTYETIMPLEMENTED("align string");
+ } else if (eCSSUnit_Enumerated == textAlignValue->GetUnit() &&
+ NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT ==
+ textAlignValue->GetIntValue()) {
+ conditions.SetUncacheable();
+ uint8_t parentAlign = parentText->mTextAlign;
+ text->mTextAlign = (NS_STYLE_TEXT_ALIGN_START == parentAlign) ?
+ NS_STYLE_TEXT_ALIGN_CENTER : parentAlign;
+ } else if (eCSSUnit_Enumerated == textAlignValue->GetUnit() &&
+ NS_STYLE_TEXT_ALIGN_MATCH_PARENT ==
+ textAlignValue->GetIntValue()) {
+ conditions.SetUncacheable();
+ nsStyleContext* parent = aContext->GetParent();
+ if (parent) {
+ uint8_t parentAlign = parentText->mTextAlign;
+ uint8_t parentDirection = parent->StyleVisibility()->mDirection;
+ switch (parentAlign) {
+ case NS_STYLE_TEXT_ALIGN_START:
+ text->mTextAlign = parentDirection == NS_STYLE_DIRECTION_RTL ?
+ NS_STYLE_TEXT_ALIGN_RIGHT : NS_STYLE_TEXT_ALIGN_LEFT;
+ break;
+
+ case NS_STYLE_TEXT_ALIGN_END:
+ text->mTextAlign = parentDirection == NS_STYLE_DIRECTION_RTL ?
+ NS_STYLE_TEXT_ALIGN_LEFT : NS_STYLE_TEXT_ALIGN_RIGHT;
+ break;
+
+ default:
+ text->mTextAlign = parentAlign;
+ }
+ }
+ } else {
+ if (eCSSUnit_Pair == textAlignValue->GetUnit()) {
+ // Two values were specified, one must be 'true'.
+ text->mTextAlignTrue = true;
+ const nsCSSValuePair& textAlignValuePair = textAlignValue->GetPairValue();
+ textAlignValue = &textAlignValuePair.mXValue;
+ if (eCSSUnit_Enumerated == textAlignValue->GetUnit()) {
+ if (textAlignValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
+ textAlignValue = &textAlignValuePair.mYValue;
+ }
+ } else if (eCSSUnit_String == textAlignValue->GetUnit()) {
+ NS_NOTYETIMPLEMENTED("align string");
+ }
+ } else if (eCSSUnit_Inherit == textAlignValue->GetUnit() ||
+ eCSSUnit_Unset == textAlignValue->GetUnit()) {
+ text->mTextAlignTrue = parentText->mTextAlignTrue;
+ }
+ SetValue(*textAlignValue, text->mTextAlign, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mTextAlign,
+ NS_STYLE_TEXT_ALIGN_START);
+ }
+
+ // text-align-last: enum, pair(enum), inherit, initial
+ const nsCSSValue* textAlignLastValue = aRuleData->ValueForTextAlignLast();
+ text->mTextAlignLastTrue = false;
+ if (eCSSUnit_Pair == textAlignLastValue->GetUnit()) {
+ // Two values were specified, one must be 'true'.
+ text->mTextAlignLastTrue = true;
+ const nsCSSValuePair& textAlignLastValuePair = textAlignLastValue->GetPairValue();
+ textAlignLastValue = &textAlignLastValuePair.mXValue;
+ if (eCSSUnit_Enumerated == textAlignLastValue->GetUnit()) {
+ if (textAlignLastValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
+ textAlignLastValue = &textAlignLastValuePair.mYValue;
+ }
+ }
+ } else if (eCSSUnit_Inherit == textAlignLastValue->GetUnit() ||
+ eCSSUnit_Unset == textAlignLastValue->GetUnit()) {
+ text->mTextAlignLastTrue = parentText->mTextAlignLastTrue;
+ }
+ SetValue(*textAlignLastValue, text->mTextAlignLast,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mTextAlignLast,
+ NS_STYLE_TEXT_ALIGN_AUTO);
+
+ // text-indent: length, percent, calc, inherit, initial
+ SetCoord(*aRuleData->ValueForTextIndent(), text->mTextIndent, parentText->mTextIndent,
+ SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INHERIT,
+ aContext, mPresContext, conditions);
+
+ // text-transform: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTextTransform(), text->mTextTransform, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mTextTransform,
+ NS_STYLE_TEXT_TRANSFORM_NONE);
+
+ // white-space: enum, inherit, initial
+ SetValue(*aRuleData->ValueForWhiteSpace(), text->mWhiteSpace, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mWhiteSpace,
+ NS_STYLE_WHITESPACE_NORMAL);
+
+ // word-break: enum, inherit, initial
+ SetValue(*aRuleData->ValueForWordBreak(), text->mWordBreak, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mWordBreak,
+ NS_STYLE_WORDBREAK_NORMAL);
+
+ // word-spacing: normal, length, percent, inherit
+ const nsCSSValue* wordSpacingValue = aRuleData->ValueForWordSpacing();
+ if (wordSpacingValue->GetUnit() == eCSSUnit_Normal) {
+ // Do this so that "normal" computes to 0px, as the CSS 2.1 spec requires.
+ text->mWordSpacing.SetCoordValue(0);
+ } else {
+ SetCoord(*aRuleData->ValueForWordSpacing(),
+ text->mWordSpacing, parentText->mWordSpacing,
+ SETCOORD_LPH | SETCOORD_INITIAL_ZERO |
+ SETCOORD_STORE_CALC | SETCOORD_UNSET_INHERIT,
+ aContext, mPresContext, conditions);
+ }
+
+ // overflow-wrap: enum, inherit, initial
+ SetValue(*aRuleData->ValueForOverflowWrap(), text->mOverflowWrap, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mOverflowWrap,
+ NS_STYLE_OVERFLOWWRAP_NORMAL);
+
+ // hyphens: enum, inherit, initial
+ SetValue(*aRuleData->ValueForHyphens(), text->mHyphens, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mHyphens,
+ NS_STYLE_HYPHENS_MANUAL);
+
+ // ruby-align: enum, inherit, initial
+ SetValue(*aRuleData->ValueForRubyAlign(),
+ text->mRubyAlign, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mRubyAlign,
+ NS_STYLE_RUBY_ALIGN_SPACE_AROUND);
+
+ // ruby-position: enum, inherit, initial
+ SetValue(*aRuleData->ValueForRubyPosition(),
+ text->mRubyPosition, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mRubyPosition,
+ NS_STYLE_RUBY_POSITION_OVER);
+
+ // text-size-adjust: none, auto, inherit, initial
+ SetValue(*aRuleData->ValueForTextSizeAdjust(), text->mTextSizeAdjust,
+ conditions, SETVAL_UNSET_INHERIT,
+ parentText->mTextSizeAdjust,
+ /* initial */ NS_STYLE_TEXT_SIZE_ADJUST_AUTO,
+ /* auto */ NS_STYLE_TEXT_SIZE_ADJUST_AUTO,
+ /* none */ NS_STYLE_TEXT_SIZE_ADJUST_NONE, Unused, Unused);
+
+ // text-combine-upright: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTextCombineUpright(),
+ text->mTextCombineUpright,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mTextCombineUpright,
+ NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE);
+
+ // text-emphasis-color: color, string, inherit, initial
+ setComplexColor(aRuleData->ValueForTextEmphasisColor(),
+ &nsStyleText::mTextEmphasisColor);
+
+ // text-emphasis-position: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTextEmphasisPosition(),
+ text->mTextEmphasisPosition,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mTextEmphasisPosition,
+ NS_STYLE_TEXT_EMPHASIS_POSITION_OVER |
+ NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT);
+
+ // text-emphasis-style: string, enum, inherit, initial
+ const nsCSSValue* textEmphasisStyleValue =
+ aRuleData->ValueForTextEmphasisStyle();
+ switch (textEmphasisStyleValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_Initial:
+ case eCSSUnit_None: {
+ text->mTextEmphasisStyle = NS_STYLE_TEXT_EMPHASIS_STYLE_NONE;
+ text->mTextEmphasisStyleString = u"";
+ break;
+ }
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Unset: {
+ conditions.SetUncacheable();
+ text->mTextEmphasisStyle = parentText->mTextEmphasisStyle;
+ text->mTextEmphasisStyleString = parentText->mTextEmphasisStyleString;
+ break;
+ }
+ case eCSSUnit_Enumerated: {
+ auto style = textEmphasisStyleValue->GetIntValue();
+ // If shape part is not specified, compute it according to the
+ // writing-mode. Note that, if the fill part (filled/open) is not
+ // specified, we compute it to filled per spec. Since that value
+ // is zero, no additional computation is needed. See the assertion
+ // in CSSParserImpl::ParseTextEmphasisStyle().
+ if (!(style & NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK)) {
+ conditions.SetUncacheable();
+ if (WritingMode(aContext).IsVertical()) {
+ style |= NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME;
+ } else {
+ style |= NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE;
+ }
+ }
+ text->mTextEmphasisStyle = style;
+ size_t shape = style & NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK;
+ MOZ_ASSERT(shape > 0 && shape < ArrayLength(kTextEmphasisChars));
+ const TextEmphasisChars& chars = kTextEmphasisChars[shape];
+ text->mTextEmphasisStyleString =
+ (style & NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) ==
+ NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED ? chars.mFilled : chars.mOpen;
+ break;
+ }
+ case eCSSUnit_String: {
+ text->mTextEmphasisStyle = NS_STYLE_TEXT_EMPHASIS_STYLE_STRING;
+ nsString strValue;
+ textEmphasisStyleValue->GetStringValue(strValue);
+ TruncateStringToSingleGrapheme(strValue);
+ text->mTextEmphasisStyleString = strValue;
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown value unit type");
+ }
+
+ // text-rendering: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTextRendering(),
+ text->mTextRendering, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mTextRendering,
+ NS_STYLE_TEXT_RENDERING_AUTO);
+
+ // -webkit-text-fill-color: color, string, inherit, initial
+ setComplexColor(aRuleData->ValueForWebkitTextFillColor(),
+ &nsStyleText::mWebkitTextFillColor);
+
+ // -webkit-text-stroke-color: color, string, inherit, initial
+ setComplexColor(aRuleData->ValueForWebkitTextStrokeColor(),
+ &nsStyleText::mWebkitTextStrokeColor);
+
+ // -webkit-text-stroke-width: length, inherit, initial, enum
+ const nsCSSValue*
+ webkitTextStrokeWidthValue = aRuleData->ValueForWebkitTextStrokeWidth();
+ if (webkitTextStrokeWidthValue->GetUnit() == eCSSUnit_Enumerated) {
+ NS_ASSERTION(webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
+ webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
+ webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
+ "Unexpected enum value");
+ text->mWebkitTextStrokeWidth.SetCoordValue(
+ mPresContext->GetBorderWidthTable()[webkitTextStrokeWidthValue->GetIntValue()]);
+ } else {
+ SetCoord(*webkitTextStrokeWidthValue, text->mWebkitTextStrokeWidth,
+ parentText->mWebkitTextStrokeWidth,
+ SETCOORD_LH | SETCOORD_CALC_LENGTH_ONLY |
+ SETCOORD_CALC_CLAMP_NONNEGATIVE |
+ SETCOORD_INITIAL_ZERO | SETCOORD_UNSET_INHERIT,
+ aContext, mPresContext, conditions);
+ }
+
+ // -moz-control-character-visibility: enum, inherit, initial
+ SetValue(*aRuleData->ValueForControlCharacterVisibility(),
+ text->mControlCharacterVisibility,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mControlCharacterVisibility,
+ nsCSSParser::ControlCharVisibilityDefault());
+
+ COMPUTE_END_INHERITED(Text, text)
+}
+
+const void*
+nsRuleNode::ComputeTextResetData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(TextReset, text, parentText)
+
+ // text-decoration-line: enum (bit field), inherit, initial
+ const nsCSSValue* decorationLineValue =
+ aRuleData->ValueForTextDecorationLine();
+ if (eCSSUnit_Enumerated == decorationLineValue->GetUnit()) {
+ int32_t td = decorationLineValue->GetIntValue();
+ text->mTextDecorationLine = td;
+ if (td & NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS) {
+ bool underlineLinks =
+ mPresContext->GetCachedBoolPref(kPresContext_UnderlineLinks);
+ if (underlineLinks) {
+ text->mTextDecorationLine |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
+ }
+ else {
+ text->mTextDecorationLine &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
+ }
+ }
+ } else if (eCSSUnit_Inherit == decorationLineValue->GetUnit()) {
+ conditions.SetUncacheable();
+ text->mTextDecorationLine = parentText->mTextDecorationLine;
+ } else if (eCSSUnit_Initial == decorationLineValue->GetUnit() ||
+ eCSSUnit_Unset == decorationLineValue->GetUnit()) {
+ text->mTextDecorationLine = NS_STYLE_TEXT_DECORATION_LINE_NONE;
+ }
+
+ // text-decoration-color: color, string, enum, inherit, initial
+ SetComplexColor<eUnsetInitial>(*aRuleData->ValueForTextDecorationColor(),
+ parentText->mTextDecorationColor,
+ StyleComplexColor::CurrentColor(),
+ mPresContext,
+ text->mTextDecorationColor, conditions);
+
+ // text-decoration-style: enum, inherit, initial
+ const nsCSSValue* decorationStyleValue =
+ aRuleData->ValueForTextDecorationStyle();
+ if (eCSSUnit_Enumerated == decorationStyleValue->GetUnit()) {
+ text->mTextDecorationStyle = decorationStyleValue->GetIntValue();
+ } else if (eCSSUnit_Inherit == decorationStyleValue->GetUnit()) {
+ text->mTextDecorationStyle = parentText->mTextDecorationStyle;
+ conditions.SetUncacheable();
+ } else if (eCSSUnit_Initial == decorationStyleValue->GetUnit() ||
+ eCSSUnit_Unset == decorationStyleValue->GetUnit()) {
+ text->mTextDecorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ }
+
+ // text-overflow: enum, string, pair(enum|string), inherit, initial
+ const nsCSSValue* textOverflowValue =
+ aRuleData->ValueForTextOverflow();
+ if (eCSSUnit_Initial == textOverflowValue->GetUnit() ||
+ eCSSUnit_Unset == textOverflowValue->GetUnit()) {
+ text->mTextOverflow = nsStyleTextOverflow();
+ } else if (eCSSUnit_Inherit == textOverflowValue->GetUnit()) {
+ conditions.SetUncacheable();
+ text->mTextOverflow = parentText->mTextOverflow;
+ } else if (eCSSUnit_Enumerated == textOverflowValue->GetUnit()) {
+ // A single enumerated value.
+ SetValue(*textOverflowValue, text->mTextOverflow.mRight.mType,
+ conditions,
+ SETVAL_ENUMERATED, parentText->mTextOverflow.mRight.mType,
+ NS_STYLE_TEXT_OVERFLOW_CLIP);
+ text->mTextOverflow.mRight.mString.Truncate();
+ text->mTextOverflow.mLeft.mType = NS_STYLE_TEXT_OVERFLOW_CLIP;
+ text->mTextOverflow.mLeft.mString.Truncate();
+ text->mTextOverflow.mLogicalDirections = true;
+ } else if (eCSSUnit_String == textOverflowValue->GetUnit()) {
+ // A single string value.
+ text->mTextOverflow.mRight.mType = NS_STYLE_TEXT_OVERFLOW_STRING;
+ textOverflowValue->GetStringValue(text->mTextOverflow.mRight.mString);
+ text->mTextOverflow.mLeft.mType = NS_STYLE_TEXT_OVERFLOW_CLIP;
+ text->mTextOverflow.mLeft.mString.Truncate();
+ text->mTextOverflow.mLogicalDirections = true;
+ } else if (eCSSUnit_Pair == textOverflowValue->GetUnit()) {
+ // Two values were specified.
+ text->mTextOverflow.mLogicalDirections = false;
+ const nsCSSValuePair& textOverflowValuePair =
+ textOverflowValue->GetPairValue();
+
+ const nsCSSValue *textOverflowLeftValue = &textOverflowValuePair.mXValue;
+ if (eCSSUnit_Enumerated == textOverflowLeftValue->GetUnit()) {
+ SetValue(*textOverflowLeftValue, text->mTextOverflow.mLeft.mType,
+ conditions,
+ SETVAL_ENUMERATED, parentText->mTextOverflow.mLeft.mType,
+ NS_STYLE_TEXT_OVERFLOW_CLIP);
+ text->mTextOverflow.mLeft.mString.Truncate();
+ } else if (eCSSUnit_String == textOverflowLeftValue->GetUnit()) {
+ textOverflowLeftValue->GetStringValue(text->mTextOverflow.mLeft.mString);
+ text->mTextOverflow.mLeft.mType = NS_STYLE_TEXT_OVERFLOW_STRING;
+ }
+
+ const nsCSSValue *textOverflowRightValue = &textOverflowValuePair.mYValue;
+ if (eCSSUnit_Enumerated == textOverflowRightValue->GetUnit()) {
+ SetValue(*textOverflowRightValue, text->mTextOverflow.mRight.mType,
+ conditions,
+ SETVAL_ENUMERATED, parentText->mTextOverflow.mRight.mType,
+ NS_STYLE_TEXT_OVERFLOW_CLIP);
+ text->mTextOverflow.mRight.mString.Truncate();
+ } else if (eCSSUnit_String == textOverflowRightValue->GetUnit()) {
+ textOverflowRightValue->GetStringValue(text->mTextOverflow.mRight.mString);
+ text->mTextOverflow.mRight.mType = NS_STYLE_TEXT_OVERFLOW_STRING;
+ }
+ }
+
+ // unicode-bidi: enum, inherit, initial
+ SetValue(*aRuleData->ValueForUnicodeBidi(), text->mUnicodeBidi, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentText->mUnicodeBidi,
+ NS_STYLE_UNICODE_BIDI_NORMAL);
+
+ // initial-letter: normal, number, array(number, integer?), initial
+ const nsCSSValue* initialLetterValue = aRuleData->ValueForInitialLetter();
+ if (initialLetterValue->GetUnit() == eCSSUnit_Null) {
+ // We don't want to change anything in this case.
+ } else if (initialLetterValue->GetUnit() == eCSSUnit_Inherit) {
+ conditions.SetUncacheable();
+ text->mInitialLetterSink = parentText->mInitialLetterSink;
+ text->mInitialLetterSize = parentText->mInitialLetterSize;
+ } else if (initialLetterValue->GetUnit() == eCSSUnit_Initial ||
+ initialLetterValue->GetUnit() == eCSSUnit_Unset ||
+ initialLetterValue->GetUnit() == eCSSUnit_Normal) {
+ // Use invalid values in initial-letter property to mean normal. So we can
+ // determine whether it is normal by checking mInitialLetterSink == 0.
+ text->mInitialLetterSink = 0;
+ text->mInitialLetterSize = 0.0f;
+ } else if (initialLetterValue->GetUnit() == eCSSUnit_Array) {
+ const nsCSSValue& firstValue = initialLetterValue->GetArrayValue()->Item(0);
+ const nsCSSValue& secondValue = initialLetterValue->GetArrayValue()->Item(1);
+ MOZ_ASSERT(firstValue.GetUnit() == eCSSUnit_Number &&
+ secondValue.GetUnit() == eCSSUnit_Integer,
+ "unexpected value unit");
+ text->mInitialLetterSize = firstValue.GetFloatValue();
+ text->mInitialLetterSink = secondValue.GetIntValue();
+ } else if (initialLetterValue->GetUnit() == eCSSUnit_Number) {
+ text->mInitialLetterSize = initialLetterValue->GetFloatValue();
+ text->mInitialLetterSink = NSToCoordFloorClamped(text->mInitialLetterSize);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unknown unit for initial-letter");
+ }
+
+ COMPUTE_END_RESET(TextReset, text)
+}
+
+const void*
+nsRuleNode::ComputeUserInterfaceData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_INHERITED(UserInterface, ui, parentUI)
+
+ // cursor: enum, url, inherit
+ const nsCSSValue* cursorValue = aRuleData->ValueForCursor();
+ nsCSSUnit cursorUnit = cursorValue->GetUnit();
+ if (cursorUnit != eCSSUnit_Null) {
+ ui->mCursorImages.Clear();
+
+ if (cursorUnit == eCSSUnit_Inherit ||
+ cursorUnit == eCSSUnit_Unset) {
+ conditions.SetUncacheable();
+ ui->mCursor = parentUI->mCursor;
+ ui->mCursorImages = parentUI->mCursorImages;
+ }
+ else if (cursorUnit == eCSSUnit_Initial) {
+ ui->mCursor = NS_STYLE_CURSOR_AUTO;
+ }
+ else {
+ // The parser will never create a list that is *all* URL values --
+ // that's invalid.
+ MOZ_ASSERT(cursorUnit == eCSSUnit_List || cursorUnit == eCSSUnit_ListDep,
+ "unrecognized cursor unit");
+ const nsCSSValueList* list = cursorValue->GetListValue();
+ for ( ; list->mValue.GetUnit() == eCSSUnit_Array; list = list->mNext) {
+ nsCSSValue::Array* arr = list->mValue.GetArrayValue();
+ imgRequestProxy* req =
+ GetImageRequest(aContext->PresContext(), arr->Item(0));
+ if (req) {
+ nsCursorImage* item = ui->mCursorImages.AppendElement();
+ item->SetImage(req);
+ if (arr->Item(1).GetUnit() != eCSSUnit_Null) {
+ item->mHaveHotspot = true;
+ item->mHotspotX = arr->Item(1).GetFloatValue();
+ item->mHotspotY = arr->Item(2).GetFloatValue();
+ }
+ }
+ }
+
+ NS_ASSERTION(list, "Must have non-array value at the end");
+ NS_ASSERTION(list->mValue.GetUnit() == eCSSUnit_Enumerated,
+ "Unexpected fallback value at end of cursor list");
+ ui->mCursor = list->mValue.GetIntValue();
+ }
+ }
+
+ // user-input: enum, inherit, initial
+ SetValue(*aRuleData->ValueForUserInput(),
+ ui->mUserInput, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentUI->mUserInput,
+ StyleUserInput::Auto);
+
+ // user-modify: enum, inherit, initial
+ SetValue(*aRuleData->ValueForUserModify(),
+ ui->mUserModify, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentUI->mUserModify,
+ StyleUserModify::ReadOnly);
+
+ // user-focus: enum, inherit, initial
+ SetValue(*aRuleData->ValueForUserFocus(),
+ ui->mUserFocus, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentUI->mUserFocus,
+ StyleUserFocus::None);
+
+ // pointer-events: enum, inherit, initial
+ SetValue(*aRuleData->ValueForPointerEvents(), ui->mPointerEvents,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentUI->mPointerEvents,
+ NS_STYLE_POINTER_EVENTS_AUTO);
+
+ COMPUTE_END_INHERITED(UserInterface, ui)
+}
+
+const void*
+nsRuleNode::ComputeUIResetData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(UIReset, ui, parentUI)
+
+ // user-select: enum, inherit, initial
+ SetValue(*aRuleData->ValueForUserSelect(),
+ ui->mUserSelect, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentUI->mUserSelect,
+ StyleUserSelect::Auto);
+
+ // ime-mode: enum, inherit, initial
+ SetValue(*aRuleData->ValueForImeMode(),
+ ui->mIMEMode, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentUI->mIMEMode,
+ NS_STYLE_IME_MODE_AUTO);
+
+ // force-broken-image-icons: integer, inherit, initial
+ SetValue(*aRuleData->ValueForForceBrokenImageIcon(),
+ ui->mForceBrokenImageIcon,
+ conditions,
+ SETVAL_INTEGER | SETVAL_UNSET_INITIAL,
+ parentUI->mForceBrokenImageIcon, 0);
+
+ // -moz-window-dragging: enum, inherit, initial
+ SetValue(*aRuleData->ValueForWindowDragging(),
+ ui->mWindowDragging, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentUI->mWindowDragging,
+ StyleWindowDragging::Default);
+
+ // -moz-window-shadow: enum, inherit, initial
+ SetValue(*aRuleData->ValueForWindowShadow(),
+ ui->mWindowShadow, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentUI->mWindowShadow,
+ NS_STYLE_WINDOW_SHADOW_DEFAULT);
+
+ COMPUTE_END_RESET(UIReset, ui)
+}
+
+// Information about each transition or animation property that is
+// constant.
+struct TransitionPropInfo {
+ nsCSSPropertyID property;
+ // Location of the count of the property's computed value.
+ uint32_t nsStyleDisplay::* sdCount;
+};
+
+// Each property's index in this array must match its index in the
+// mutable array |transitionPropData| below.
+static const TransitionPropInfo transitionPropInfo[4] = {
+ { eCSSProperty_transition_delay,
+ &nsStyleDisplay::mTransitionDelayCount },
+ { eCSSProperty_transition_duration,
+ &nsStyleDisplay::mTransitionDurationCount },
+ { eCSSProperty_transition_property,
+ &nsStyleDisplay::mTransitionPropertyCount },
+ { eCSSProperty_transition_timing_function,
+ &nsStyleDisplay::mTransitionTimingFunctionCount },
+};
+
+// Each property's index in this array must match its index in the
+// mutable array |animationPropData| below.
+static const TransitionPropInfo animationPropInfo[8] = {
+ { eCSSProperty_animation_delay,
+ &nsStyleDisplay::mAnimationDelayCount },
+ { eCSSProperty_animation_duration,
+ &nsStyleDisplay::mAnimationDurationCount },
+ { eCSSProperty_animation_name,
+ &nsStyleDisplay::mAnimationNameCount },
+ { eCSSProperty_animation_timing_function,
+ &nsStyleDisplay::mAnimationTimingFunctionCount },
+ { eCSSProperty_animation_direction,
+ &nsStyleDisplay::mAnimationDirectionCount },
+ { eCSSProperty_animation_fill_mode,
+ &nsStyleDisplay::mAnimationFillModeCount },
+ { eCSSProperty_animation_play_state,
+ &nsStyleDisplay::mAnimationPlayStateCount },
+ { eCSSProperty_animation_iteration_count,
+ &nsStyleDisplay::mAnimationIterationCountCount },
+};
+
+// Information about each transition or animation property that changes
+// during ComputeDisplayData.
+struct TransitionPropData {
+ const nsCSSValueList *list;
+ nsCSSUnit unit;
+ uint32_t num;
+};
+
+static uint32_t
+CountTransitionProps(const TransitionPropInfo* aInfo,
+ TransitionPropData* aData,
+ size_t aLength,
+ nsStyleDisplay* aDisplay,
+ const nsStyleDisplay* aParentDisplay,
+ const nsRuleData* aRuleData,
+ RuleNodeCacheConditions& aConditions)
+{
+ // The four transition properties or eight animation properties are
+ // stored in nsCSSDisplay in a single array for all properties. The
+ // number of transitions is equal to the number of items in the
+ // longest property's value. Properties that have fewer values than
+ // the longest are filled in by repeating the list. However, this
+ // repetition does not extend the computed value of that particular
+ // property (for purposes of inheritance, or, in our code, for when
+ // other properties are overridden by a more specific rule).
+
+ // But actually, since the spec isn't clear yet, we'll fully compute
+ // all of them (so we can switch easily later), but only care about
+ // the ones up to the number of items for 'transition-property', per
+ // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
+
+ // Transitions are difficult to handle correctly because of this. For
+ // example, we need to handle scenarios such as:
+ // * a more general rule specifies transition-property: a, b, c;
+ // * a more specific rule overrides as transition-property: d;
+ //
+ // If only the general rule applied, we would fill in the extra
+ // properties (duration, delay, etc) with initial values to create 3
+ // fully-specified transitions. But when the more specific rule
+ // applies, we should only create a single transition. In order to do
+ // this we need to remember which properties were explicitly specified
+ // and which ones were just filled in with initial values to get a
+ // fully-specified transition, which we do by remembering the number
+ // of values for each property.
+
+ uint32_t numTransitions = 0;
+ for (size_t i = 0; i < aLength; ++i) {
+ const TransitionPropInfo& info = aInfo[i];
+ TransitionPropData& data = aData[i];
+
+ // cache whether any of the properties are specified as 'inherit' so
+ // we can use it below
+
+ const nsCSSValue& value = *aRuleData->ValueFor(info.property);
+ data.unit = value.GetUnit();
+ data.list = (value.GetUnit() == eCSSUnit_List ||
+ value.GetUnit() == eCSSUnit_ListDep)
+ ? value.GetListValue() : nullptr;
+
+ // General algorithm to determine how many total transitions we need
+ // to build. For each property:
+ // - if there is no value specified in for the property in
+ // displayData, use the values from the start struct, but only if
+ // they were explicitly specified
+ // - if there is a value specified for the property in displayData:
+ // - if the value is 'inherit', count the number of values for
+ // that property are specified by the parent, but only those
+ // that were explicitly specified
+ // - otherwise, count the number of values specified in displayData
+
+
+ // calculate number of elements
+ if (data.unit == eCSSUnit_Inherit) {
+ data.num = aParentDisplay->*(info.sdCount);
+ aConditions.SetUncacheable();
+ } else if (data.list) {
+ data.num = ListLength(data.list);
+ } else {
+ data.num = aDisplay->*(info.sdCount);
+ }
+ if (data.num > numTransitions)
+ numTransitions = data.num;
+ }
+
+ return numTransitions;
+}
+
+/* static */ void
+nsRuleNode::ComputeTimingFunction(const nsCSSValue& aValue,
+ nsTimingFunction& aResult)
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Enumerated:
+ aResult = nsTimingFunction(aValue.GetIntValue());
+ break;
+ case eCSSUnit_Cubic_Bezier:
+ {
+ nsCSSValue::Array* array = aValue.GetArrayValue();
+ NS_ASSERTION(array && array->Count() == 4,
+ "Need 4 control points");
+ aResult = nsTimingFunction(array->Item(0).GetFloatValue(),
+ array->Item(1).GetFloatValue(),
+ array->Item(2).GetFloatValue(),
+ array->Item(3).GetFloatValue());
+ }
+ break;
+ case eCSSUnit_Steps:
+ {
+ nsCSSValue::Array* array = aValue.GetArrayValue();
+ NS_ASSERTION(array && array->Count() == 2,
+ "Need 2 items");
+ NS_ASSERTION(array->Item(0).GetUnit() == eCSSUnit_Integer,
+ "unexpected first value");
+ NS_ASSERTION(array->Item(1).GetUnit() == eCSSUnit_Enumerated &&
+ (array->Item(1).GetIntValue() ==
+ NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START ||
+ array->Item(1).GetIntValue() ==
+ NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END ||
+ array->Item(1).GetIntValue() == -1),
+ "unexpected second value");
+ nsTimingFunction::Type type =
+ (array->Item(1).GetIntValue() ==
+ NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START) ?
+ nsTimingFunction::Type::StepStart :
+ nsTimingFunction::Type::StepEnd;
+ aResult = nsTimingFunction(type, array->Item(0).GetIntValue());
+ }
+ break;
+ default:
+ NS_NOTREACHED("Invalid transition property unit");
+ }
+}
+
+static uint8_t
+GetWillChangeBitFieldFromPropFlags(const nsCSSPropertyID& aProp)
+{
+ uint8_t willChangeBitField = 0;
+ if (nsCSSProps::PropHasFlags(aProp, CSS_PROPERTY_CREATES_STACKING_CONTEXT)) {
+ willChangeBitField |= NS_STYLE_WILL_CHANGE_STACKING_CONTEXT;
+ }
+
+ if (nsCSSProps::PropHasFlags(aProp, CSS_PROPERTY_FIXPOS_CB)) {
+ willChangeBitField |= NS_STYLE_WILL_CHANGE_FIXPOS_CB;
+ }
+
+ if (nsCSSProps::PropHasFlags(aProp, CSS_PROPERTY_ABSPOS_CB)) {
+ willChangeBitField |= NS_STYLE_WILL_CHANGE_ABSPOS_CB;
+ }
+
+ return willChangeBitField;
+}
+
+const void*
+nsRuleNode::ComputeDisplayData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Display, display, parentDisplay)
+
+ // We may have ended up with aStartStruct's values of mDisplay and
+ // mFloat, but those may not be correct if our style data overrides
+ // its position or float properties. Reset to mOriginalDisplay and
+ // mOriginalFloat; if it turns out we still need the display/floats
+ // adjustments, we'll do them below.
+ display->mDisplay = display->mOriginalDisplay;
+ display->mFloat = display->mOriginalFloat;
+
+ // Each property's index in this array must match its index in the
+ // const array |transitionPropInfo| above.
+ TransitionPropData transitionPropData[4];
+ TransitionPropData& delay = transitionPropData[0];
+ TransitionPropData& duration = transitionPropData[1];
+ TransitionPropData& property = transitionPropData[2];
+ TransitionPropData& timingFunction = transitionPropData[3];
+
+#define FOR_ALL_TRANSITION_PROPS(var_) \
+ for (uint32_t var_ = 0; var_ < 4; ++var_)
+
+ // CSS Transitions
+ uint32_t numTransitions =
+ CountTransitionProps(transitionPropInfo, transitionPropData,
+ ArrayLength(transitionPropData),
+ display, parentDisplay, aRuleData,
+ conditions);
+
+ display->mTransitions.SetLengthNonZero(numTransitions);
+
+ FOR_ALL_TRANSITION_PROPS(p) {
+ const TransitionPropInfo& i = transitionPropInfo[p];
+ TransitionPropData& d = transitionPropData[p];
+
+ display->*(i.sdCount) = d.num;
+ }
+
+ // Fill in the transitions we just allocated with the appropriate values.
+ for (uint32_t i = 0; i < numTransitions; ++i) {
+ StyleTransition *transition = &display->mTransitions[i];
+
+ if (i >= delay.num) {
+ MOZ_ASSERT(delay.num, "delay.num must be greater than 0");
+ transition->SetDelay(display->mTransitions[i % delay.num].GetDelay());
+ } else if (delay.unit == eCSSUnit_Inherit) {
+ // FIXME (Bug 522599) (for all transition properties): write a test that
+ // detects when this was wrong for i >= delay.num if parent had
+ // count for this property not equal to length
+ MOZ_ASSERT(i < parentDisplay->mTransitionDelayCount,
+ "delay.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ transition->SetDelay(parentDisplay->mTransitions[i].GetDelay());
+ } else if (delay.unit == eCSSUnit_Initial ||
+ delay.unit == eCSSUnit_Unset) {
+ transition->SetDelay(0.0);
+ } else if (delay.list) {
+ switch (delay.list->mValue.GetUnit()) {
+ case eCSSUnit_Seconds:
+ transition->SetDelay(PR_MSEC_PER_SEC *
+ delay.list->mValue.GetFloatValue());
+ break;
+ case eCSSUnit_Milliseconds:
+ transition->SetDelay(delay.list->mValue.GetFloatValue());
+ break;
+ default:
+ NS_NOTREACHED("Invalid delay unit");
+ }
+ }
+
+ if (i >= duration.num) {
+ MOZ_ASSERT(duration.num, "duration.num must be greater than 0");
+ transition->SetDuration(
+ display->mTransitions[i % duration.num].GetDuration());
+ } else if (duration.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mTransitionDurationCount,
+ "duration.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ transition->SetDuration(parentDisplay->mTransitions[i].GetDuration());
+ } else if (duration.unit == eCSSUnit_Initial ||
+ duration.unit == eCSSUnit_Unset) {
+ transition->SetDuration(0.0);
+ } else if (duration.list) {
+ switch (duration.list->mValue.GetUnit()) {
+ case eCSSUnit_Seconds:
+ transition->SetDuration(PR_MSEC_PER_SEC *
+ duration.list->mValue.GetFloatValue());
+ break;
+ case eCSSUnit_Milliseconds:
+ transition->SetDuration(duration.list->mValue.GetFloatValue());
+ break;
+ default:
+ NS_NOTREACHED("Invalid duration unit");
+ }
+ }
+
+ if (i >= property.num) {
+ MOZ_ASSERT(property.num, "property.num must be greater than 0");
+ transition->CopyPropertyFrom(display->mTransitions[i % property.num]);
+ } else if (property.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mTransitionPropertyCount,
+ "property.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ transition->CopyPropertyFrom(parentDisplay->mTransitions[i]);
+ } else if (property.unit == eCSSUnit_Initial ||
+ property.unit == eCSSUnit_Unset) {
+ transition->SetProperty(eCSSPropertyExtra_all_properties);
+ } else if (property.unit == eCSSUnit_None) {
+ transition->SetProperty(eCSSPropertyExtra_no_properties);
+ } else if (property.list) {
+ const nsCSSValue &val = property.list->mValue;
+
+ if (val.GetUnit() == eCSSUnit_Ident) {
+ nsDependentString
+ propertyStr(property.list->mValue.GetStringBufferValue());
+ nsCSSPropertyID prop =
+ nsCSSProps::LookupProperty(propertyStr,
+ CSSEnabledState::eForAllContent);
+ if (prop == eCSSProperty_UNKNOWN ||
+ prop == eCSSPropertyExtra_variable) {
+ transition->SetUnknownProperty(prop, propertyStr);
+ } else {
+ transition->SetProperty(prop);
+ }
+ } else {
+ MOZ_ASSERT(val.GetUnit() == eCSSUnit_All,
+ "Invalid transition property unit");
+ transition->SetProperty(eCSSPropertyExtra_all_properties);
+ }
+ }
+
+ if (i >= timingFunction.num) {
+ MOZ_ASSERT(timingFunction.num,
+ "timingFunction.num must be greater than 0");
+ transition->SetTimingFunction(
+ display->mTransitions[i % timingFunction.num].GetTimingFunction());
+ } else if (timingFunction.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mTransitionTimingFunctionCount,
+ "timingFunction.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ transition->SetTimingFunction(
+ parentDisplay->mTransitions[i].GetTimingFunction());
+ } else if (timingFunction.unit == eCSSUnit_Initial ||
+ timingFunction.unit == eCSSUnit_Unset) {
+ transition->SetTimingFunction(
+ nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
+ } else if (timingFunction.list) {
+ ComputeTimingFunction(timingFunction.list->mValue,
+ transition->TimingFunctionSlot());
+ }
+
+ FOR_ALL_TRANSITION_PROPS(p) {
+ const TransitionPropInfo& info = transitionPropInfo[p];
+ TransitionPropData& d = transitionPropData[p];
+
+ // if we're at the end of the list, start at the beginning and repeat
+ // until we're out of transitions to populate
+ if (d.list) {
+ d.list = d.list->mNext ? d.list->mNext :
+ aRuleData->ValueFor(info.property)->GetListValue();
+ }
+ }
+ }
+
+ // Each property's index in this array must match its index in the
+ // const array |animationPropInfo| above.
+ TransitionPropData animationPropData[8];
+ TransitionPropData& animDelay = animationPropData[0];
+ TransitionPropData& animDuration = animationPropData[1];
+ TransitionPropData& animName = animationPropData[2];
+ TransitionPropData& animTimingFunction = animationPropData[3];
+ TransitionPropData& animDirection = animationPropData[4];
+ TransitionPropData& animFillMode = animationPropData[5];
+ TransitionPropData& animPlayState = animationPropData[6];
+ TransitionPropData& animIterationCount = animationPropData[7];
+
+#define FOR_ALL_ANIMATION_PROPS(var_) \
+ for (uint32_t var_ = 0; var_ < 8; ++var_)
+
+ // CSS Animations.
+
+ uint32_t numAnimations =
+ CountTransitionProps(animationPropInfo, animationPropData,
+ ArrayLength(animationPropData),
+ display, parentDisplay, aRuleData,
+ conditions);
+
+ display->mAnimations.SetLengthNonZero(numAnimations);
+
+ FOR_ALL_ANIMATION_PROPS(p) {
+ const TransitionPropInfo& i = animationPropInfo[p];
+ TransitionPropData& d = animationPropData[p];
+
+ display->*(i.sdCount) = d.num;
+ }
+
+ // Fill in the animations we just allocated with the appropriate values.
+ for (uint32_t i = 0; i < numAnimations; ++i) {
+ StyleAnimation *animation = &display->mAnimations[i];
+
+ if (i >= animDelay.num) {
+ MOZ_ASSERT(animDelay.num, "animDelay.num must be greater than 0");
+ animation->SetDelay(display->mAnimations[i % animDelay.num].GetDelay());
+ } else if (animDelay.unit == eCSSUnit_Inherit) {
+ // FIXME (Bug 522599) (for all animation properties): write a test that
+ // detects when this was wrong for i >= animDelay.num if parent had
+ // count for this property not equal to length
+ MOZ_ASSERT(i < parentDisplay->mAnimationDelayCount,
+ "animDelay.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ animation->SetDelay(parentDisplay->mAnimations[i].GetDelay());
+ } else if (animDelay.unit == eCSSUnit_Initial ||
+ animDelay.unit == eCSSUnit_Unset) {
+ animation->SetDelay(0.0);
+ } else if (animDelay.list) {
+ switch (animDelay.list->mValue.GetUnit()) {
+ case eCSSUnit_Seconds:
+ animation->SetDelay(PR_MSEC_PER_SEC *
+ animDelay.list->mValue.GetFloatValue());
+ break;
+ case eCSSUnit_Milliseconds:
+ animation->SetDelay(animDelay.list->mValue.GetFloatValue());
+ break;
+ default:
+ NS_NOTREACHED("Invalid delay unit");
+ }
+ }
+
+ if (i >= animDuration.num) {
+ MOZ_ASSERT(animDuration.num, "animDuration.num must be greater than 0");
+ animation->SetDuration(
+ display->mAnimations[i % animDuration.num].GetDuration());
+ } else if (animDuration.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mAnimationDurationCount,
+ "animDuration.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ animation->SetDuration(parentDisplay->mAnimations[i].GetDuration());
+ } else if (animDuration.unit == eCSSUnit_Initial ||
+ animDuration.unit == eCSSUnit_Unset) {
+ animation->SetDuration(0.0);
+ } else if (animDuration.list) {
+ switch (animDuration.list->mValue.GetUnit()) {
+ case eCSSUnit_Seconds:
+ animation->SetDuration(PR_MSEC_PER_SEC *
+ animDuration.list->mValue.GetFloatValue());
+ break;
+ case eCSSUnit_Milliseconds:
+ animation->SetDuration(animDuration.list->mValue.GetFloatValue());
+ break;
+ default:
+ NS_NOTREACHED("Invalid duration unit");
+ }
+ }
+
+ if (i >= animName.num) {
+ MOZ_ASSERT(animName.num, "animName.num must be greater than 0");
+ animation->SetName(display->mAnimations[i % animName.num].GetName());
+ } else if (animName.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mAnimationNameCount,
+ "animName.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ animation->SetName(parentDisplay->mAnimations[i].GetName());
+ } else if (animName.unit == eCSSUnit_Initial ||
+ animName.unit == eCSSUnit_Unset) {
+ animation->SetName(EmptyString());
+ } else if (animName.list) {
+ switch (animName.list->mValue.GetUnit()) {
+ case eCSSUnit_Ident: {
+ nsDependentString
+ nameStr(animName.list->mValue.GetStringBufferValue());
+ animation->SetName(nameStr);
+ break;
+ }
+ case eCSSUnit_None: {
+ animation->SetName(EmptyString());
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Invalid animation-name unit");
+ }
+ }
+
+ if (i >= animTimingFunction.num) {
+ MOZ_ASSERT(animTimingFunction.num,
+ "animTimingFunction.num must be greater than 0");
+ animation->SetTimingFunction(
+ display->mAnimations[i % animTimingFunction.num].GetTimingFunction());
+ } else if (animTimingFunction.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mAnimationTimingFunctionCount,
+ "animTimingFunction.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ animation->SetTimingFunction(
+ parentDisplay->mAnimations[i].GetTimingFunction());
+ } else if (animTimingFunction.unit == eCSSUnit_Initial ||
+ animTimingFunction.unit == eCSSUnit_Unset) {
+ animation->SetTimingFunction(
+ nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
+ } else if (animTimingFunction.list) {
+ ComputeTimingFunction(animTimingFunction.list->mValue,
+ animation->TimingFunctionSlot());
+ }
+
+ if (i >= animDirection.num) {
+ MOZ_ASSERT(animDirection.num,
+ "animDirection.num must be greater than 0");
+ animation->SetDirection(display->mAnimations[i % animDirection.num].GetDirection());
+ } else if (animDirection.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mAnimationDirectionCount,
+ "animDirection.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ animation->SetDirection(parentDisplay->mAnimations[i].GetDirection());
+ } else if (animDirection.unit == eCSSUnit_Initial ||
+ animDirection.unit == eCSSUnit_Unset) {
+ animation->SetDirection(dom::PlaybackDirection::Normal);
+ } else if (animDirection.list) {
+ MOZ_ASSERT(animDirection.list->mValue.GetUnit() == eCSSUnit_Enumerated,
+ "Invalid animation-direction unit");
+
+ animation->SetDirection(
+ static_cast<dom::PlaybackDirection>(animDirection.list->mValue.GetIntValue()));
+ }
+
+ if (i >= animFillMode.num) {
+ MOZ_ASSERT(animFillMode.num, "animFillMode.num must be greater than 0");
+ animation->SetFillMode(display->mAnimations[i % animFillMode.num].GetFillMode());
+ } else if (animFillMode.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mAnimationFillModeCount,
+ "animFillMode.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ animation->SetFillMode(parentDisplay->mAnimations[i].GetFillMode());
+ } else if (animFillMode.unit == eCSSUnit_Initial ||
+ animFillMode.unit == eCSSUnit_Unset) {
+ animation->SetFillMode(dom::FillMode::None);
+ } else if (animFillMode.list) {
+ MOZ_ASSERT(animFillMode.list->mValue.GetUnit() == eCSSUnit_Enumerated,
+ "Invalid animation-fill-mode unit");
+
+ animation->SetFillMode(
+ static_cast<dom::FillMode>(animFillMode.list->mValue.GetIntValue()));
+ }
+
+ if (i >= animPlayState.num) {
+ MOZ_ASSERT(animPlayState.num,
+ "animPlayState.num must be greater than 0");
+ animation->SetPlayState(display->mAnimations[i % animPlayState.num].GetPlayState());
+ } else if (animPlayState.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mAnimationPlayStateCount,
+ "animPlayState.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ animation->SetPlayState(parentDisplay->mAnimations[i].GetPlayState());
+ } else if (animPlayState.unit == eCSSUnit_Initial ||
+ animPlayState.unit == eCSSUnit_Unset) {
+ animation->SetPlayState(NS_STYLE_ANIMATION_PLAY_STATE_RUNNING);
+ } else if (animPlayState.list) {
+ MOZ_ASSERT(animPlayState.list->mValue.GetUnit() == eCSSUnit_Enumerated,
+ "Invalid animation-play-state unit");
+
+ animation->SetPlayState(animPlayState.list->mValue.GetIntValue());
+ }
+
+ if (i >= animIterationCount.num) {
+ MOZ_ASSERT(animIterationCount.num,
+ "animIterationCount.num must be greater than 0");
+ animation->SetIterationCount(display->mAnimations[i % animIterationCount.num].GetIterationCount());
+ } else if (animIterationCount.unit == eCSSUnit_Inherit) {
+ MOZ_ASSERT(i < parentDisplay->mAnimationIterationCountCount,
+ "animIterationCount.num computed incorrectly");
+ MOZ_ASSERT(!conditions.Cacheable(),
+ "should have made conditions.Cacheable() false above");
+ animation->SetIterationCount(parentDisplay->mAnimations[i].GetIterationCount());
+ } else if (animIterationCount.unit == eCSSUnit_Initial ||
+ animIterationCount.unit == eCSSUnit_Unset) {
+ animation->SetIterationCount(1.0f);
+ } else if (animIterationCount.list) {
+ switch (animIterationCount.list->mValue.GetUnit()) {
+ case eCSSUnit_Enumerated:
+ MOZ_ASSERT(animIterationCount.list->mValue.GetIntValue() ==
+ NS_STYLE_ANIMATION_ITERATION_COUNT_INFINITE,
+ "unexpected value");
+ animation->SetIterationCount(NS_IEEEPositiveInfinity());
+ break;
+ case eCSSUnit_Number:
+ animation->SetIterationCount(
+ animIterationCount.list->mValue.GetFloatValue());
+ break;
+ default:
+ MOZ_ASSERT(false,
+ "unexpected animation-iteration-count unit");
+ }
+ }
+
+ FOR_ALL_ANIMATION_PROPS(p) {
+ const TransitionPropInfo& info = animationPropInfo[p];
+ TransitionPropData& d = animationPropData[p];
+
+ // if we're at the end of the list, start at the beginning and repeat
+ // until we're out of animations to populate
+ if (d.list) {
+ d.list = d.list->mNext ? d.list->mNext :
+ aRuleData->ValueFor(info.property)->GetListValue();
+ }
+ }
+ }
+
+ // display: enum, inherit, initial
+ SetValue(*aRuleData->ValueForDisplay(), display->mDisplay, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mDisplay,
+ StyleDisplay::Inline);
+
+ // contain: none, enum, inherit, initial
+ SetValue(*aRuleData->ValueForContain(), display->mContain, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mContain,
+ NS_STYLE_CONTAIN_NONE, Unused,
+ NS_STYLE_CONTAIN_NONE, Unused, Unused);
+
+ // scroll-behavior: enum, inherit, initial
+ SetValue(*aRuleData->ValueForScrollBehavior(), display->mScrollBehavior,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mScrollBehavior, NS_STYLE_SCROLL_BEHAVIOR_AUTO);
+
+ // scroll-snap-type-x: none, enum, inherit, initial
+ SetValue(*aRuleData->ValueForScrollSnapTypeX(), display->mScrollSnapTypeX,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mScrollSnapTypeX, NS_STYLE_SCROLL_SNAP_TYPE_NONE);
+
+ // scroll-snap-type-y: none, enum, inherit, initial
+ SetValue(*aRuleData->ValueForScrollSnapTypeY(), display->mScrollSnapTypeY,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mScrollSnapTypeY, NS_STYLE_SCROLL_SNAP_TYPE_NONE);
+
+ // scroll-snap-points-x: none, inherit, initial
+ const nsCSSValue& scrollSnapPointsX = *aRuleData->ValueForScrollSnapPointsX();
+ switch (scrollSnapPointsX.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ display->mScrollSnapPointsX.SetNoneValue();
+ break;
+ case eCSSUnit_Inherit:
+ display->mScrollSnapPointsX = parentDisplay->mScrollSnapPointsX;
+ conditions.SetUncacheable();
+ break;
+ case eCSSUnit_Function: {
+ nsCSSValue::Array* func = scrollSnapPointsX.GetArrayValue();
+ NS_ASSERTION(func->Item(0).GetKeywordValue() == eCSSKeyword_repeat,
+ "Expected repeat(), got another function name");
+ nsStyleCoord coord;
+ if (SetCoord(func->Item(1), coord, nsStyleCoord(),
+ SETCOORD_LP | SETCOORD_STORE_CALC |
+ SETCOORD_CALC_CLAMP_NONNEGATIVE,
+ aContext, mPresContext, conditions)) {
+ NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord ||
+ coord.GetUnit() == eStyleUnit_Percent ||
+ coord.GetUnit() == eStyleUnit_Calc,
+ "unexpected unit");
+ display->mScrollSnapPointsX = coord;
+ }
+ break;
+ }
+ default:
+ NS_NOTREACHED("unexpected unit");
+ }
+
+ // scroll-snap-points-y: none, inherit, initial
+ const nsCSSValue& scrollSnapPointsY = *aRuleData->ValueForScrollSnapPointsY();
+ switch (scrollSnapPointsY.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ display->mScrollSnapPointsY.SetNoneValue();
+ break;
+ case eCSSUnit_Inherit:
+ display->mScrollSnapPointsY = parentDisplay->mScrollSnapPointsY;
+ conditions.SetUncacheable();
+ break;
+ case eCSSUnit_Function: {
+ nsCSSValue::Array* func = scrollSnapPointsY.GetArrayValue();
+ NS_ASSERTION(func->Item(0).GetKeywordValue() == eCSSKeyword_repeat,
+ "Expected repeat(), got another function name");
+ nsStyleCoord coord;
+ if (SetCoord(func->Item(1), coord, nsStyleCoord(),
+ SETCOORD_LP | SETCOORD_STORE_CALC |
+ SETCOORD_CALC_CLAMP_NONNEGATIVE,
+ aContext, mPresContext, conditions)) {
+ NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord ||
+ coord.GetUnit() == eStyleUnit_Percent ||
+ coord.GetUnit() == eStyleUnit_Calc,
+ "unexpected unit");
+ display->mScrollSnapPointsY = coord;
+ }
+ break;
+ }
+ default:
+ NS_NOTREACHED("unexpected unit");
+ }
+
+ // scroll-snap-destination: inherit, initial
+ const nsCSSValue& snapDestination = *aRuleData->ValueForScrollSnapDestination();
+ switch (snapDestination.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ display->mScrollSnapDestination.SetInitialZeroValues();
+ break;
+ case eCSSUnit_Inherit:
+ display->mScrollSnapDestination = parentDisplay->mScrollSnapDestination;
+ conditions.SetUncacheable();
+ break;
+ default: {
+ ComputePositionValue(aContext, snapDestination,
+ display->mScrollSnapDestination, conditions);
+ }
+ }
+
+ // scroll-snap-coordinate: none, inherit, initial
+ const nsCSSValue& snapCoordinate = *aRuleData->ValueForScrollSnapCoordinate();
+ switch (snapCoordinate.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ // Unset and Initial is none, indicated by an empty array
+ display->mScrollSnapCoordinate.Clear();
+ break;
+ case eCSSUnit_Inherit:
+ display->mScrollSnapCoordinate = parentDisplay->mScrollSnapCoordinate;
+ conditions.SetUncacheable();
+ break;
+ case eCSSUnit_List: {
+ display->mScrollSnapCoordinate.Clear();
+ const nsCSSValueList* item = snapCoordinate.GetListValue();
+ do {
+ NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null &&
+ item->mValue.GetUnit() != eCSSUnit_Inherit &&
+ item->mValue.GetUnit() != eCSSUnit_Initial &&
+ item->mValue.GetUnit() != eCSSUnit_Unset,
+ "unexpected unit");
+ Position* pos = display->mScrollSnapCoordinate.AppendElement();
+ ComputePositionValue(aContext, item->mValue, *pos, conditions);
+ item = item->mNext;
+ } while(item);
+ break;
+ }
+ default:
+ NS_NOTREACHED("unexpected unit");
+ }
+
+ // isolation: enum, inherit, initial
+ SetValue(*aRuleData->ValueForIsolation(), display->mIsolation,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mIsolation, NS_STYLE_ISOLATION_AUTO);
+
+ // -moz-top-layer: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTopLayer(), display->mTopLayer,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mTopLayer, NS_STYLE_TOP_LAYER_NONE);
+
+ // Backup original display value for calculation of a hypothetical
+ // box (CSS2 10.6.4/10.6.5), in addition to getting our style data right later.
+ // See ReflowInput::CalculateHypotheticalBox
+ display->mOriginalDisplay = display->mDisplay;
+
+ // appearance: enum, inherit, initial
+ SetValue(*aRuleData->ValueForAppearance(),
+ display->mAppearance, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mAppearance,
+ NS_THEME_NONE);
+
+ // binding: url, none, inherit
+ const nsCSSValue* bindingValue = aRuleData->ValueForBinding();
+ if (eCSSUnit_URL == bindingValue->GetUnit()) {
+ mozilla::css::URLValue* url = bindingValue->GetURLStructValue();
+ NS_ASSERTION(url, "What's going on here?");
+
+ if (MOZ_LIKELY(url->GetURI())) {
+ display->mBinding = url;
+ } else {
+ display->mBinding = nullptr;
+ }
+ }
+ else if (eCSSUnit_None == bindingValue->GetUnit() ||
+ eCSSUnit_Initial == bindingValue->GetUnit() ||
+ eCSSUnit_Unset == bindingValue->GetUnit()) {
+ display->mBinding = nullptr;
+ }
+ else if (eCSSUnit_Inherit == bindingValue->GetUnit()) {
+ conditions.SetUncacheable();
+ display->mBinding = parentDisplay->mBinding;
+ }
+
+ // position: enum, inherit, initial
+ SetValue(*aRuleData->ValueForPosition(), display->mPosition, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mPosition,
+ NS_STYLE_POSITION_STATIC);
+ // If an element is put in the top layer, while it is not absolutely
+ // positioned, the position value should be computed to 'absolute' per
+ // the Fullscreen API spec.
+ if (display->mTopLayer != NS_STYLE_TOP_LAYER_NONE &&
+ !display->IsAbsolutelyPositionedStyle()) {
+ display->mPosition = NS_STYLE_POSITION_ABSOLUTE;
+ // We cannot cache this struct because otherwise it may be used as
+ // an aStartStruct for some other elements.
+ conditions.SetUncacheable();
+ }
+
+ // clear: enum, inherit, initial
+ SetValue(*aRuleData->ValueForClear(), display->mBreakType, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mBreakType,
+ StyleClear::None);
+
+ // temp fix for bug 24000
+ // Map 'auto' and 'avoid' to false, and 'always', 'left', and
+ // 'right' to true.
+ // "A conforming user agent may interpret the values 'left' and
+ // 'right' as 'always'." - CSS2.1, section 13.3.1
+ const nsCSSValue* breakBeforeValue = aRuleData->ValueForPageBreakBefore();
+ if (eCSSUnit_Enumerated == breakBeforeValue->GetUnit()) {
+ display->mBreakBefore =
+ (NS_STYLE_PAGE_BREAK_AVOID != breakBeforeValue->GetIntValue() &&
+ NS_STYLE_PAGE_BREAK_AUTO != breakBeforeValue->GetIntValue());
+ }
+ else if (eCSSUnit_Initial == breakBeforeValue->GetUnit() ||
+ eCSSUnit_Unset == breakBeforeValue->GetUnit()) {
+ display->mBreakBefore = false;
+ }
+ else if (eCSSUnit_Inherit == breakBeforeValue->GetUnit()) {
+ conditions.SetUncacheable();
+ display->mBreakBefore = parentDisplay->mBreakBefore;
+ }
+
+ const nsCSSValue* breakAfterValue = aRuleData->ValueForPageBreakAfter();
+ if (eCSSUnit_Enumerated == breakAfterValue->GetUnit()) {
+ display->mBreakAfter =
+ (NS_STYLE_PAGE_BREAK_AVOID != breakAfterValue->GetIntValue() &&
+ NS_STYLE_PAGE_BREAK_AUTO != breakAfterValue->GetIntValue());
+ }
+ else if (eCSSUnit_Initial == breakAfterValue->GetUnit() ||
+ eCSSUnit_Unset == breakAfterValue->GetUnit()) {
+ display->mBreakAfter = false;
+ }
+ else if (eCSSUnit_Inherit == breakAfterValue->GetUnit()) {
+ conditions.SetUncacheable();
+ display->mBreakAfter = parentDisplay->mBreakAfter;
+ }
+ // end temp fix
+
+ // page-break-inside: enum, inherit, initial
+ SetValue(*aRuleData->ValueForPageBreakInside(),
+ display->mBreakInside, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mBreakInside,
+ NS_STYLE_PAGE_BREAK_AUTO);
+
+ // touch-action: none, auto, enum, inherit, initial
+ SetValue(*aRuleData->ValueForTouchAction(), display->mTouchAction,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mTouchAction,
+ /* initial */ NS_STYLE_TOUCH_ACTION_AUTO,
+ /* auto */ NS_STYLE_TOUCH_ACTION_AUTO,
+ /* none */ NS_STYLE_TOUCH_ACTION_NONE, Unused, Unused);
+
+ // float: enum, inherit, initial
+ SetValue(*aRuleData->ValueForFloat(),
+ display->mFloat, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mFloat,
+ StyleFloat::None);
+ // Save mFloat in mOriginalFloat in case we need it later
+ display->mOriginalFloat = display->mFloat;
+
+ // overflow-x: enum, inherit, initial
+ SetValue(*aRuleData->ValueForOverflowX(),
+ display->mOverflowX, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mOverflowX,
+ NS_STYLE_OVERFLOW_VISIBLE);
+
+ // overflow-y: enum, inherit, initial
+ SetValue(*aRuleData->ValueForOverflowY(),
+ display->mOverflowY, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mOverflowY,
+ NS_STYLE_OVERFLOW_VISIBLE);
+
+ // CSS3 overflow-x and overflow-y require some fixup as well in some
+ // cases. NS_STYLE_OVERFLOW_VISIBLE and NS_STYLE_OVERFLOW_CLIP are
+ // meaningful only when used in both dimensions.
+ if (display->mOverflowX != display->mOverflowY &&
+ (display->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE ||
+ display->mOverflowX == NS_STYLE_OVERFLOW_CLIP ||
+ display->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE ||
+ display->mOverflowY == NS_STYLE_OVERFLOW_CLIP)) {
+ // We can't store in the rule tree since a more specific rule might
+ // change these conditions.
+ conditions.SetUncacheable();
+
+ // NS_STYLE_OVERFLOW_CLIP is a deprecated value, so if it's specified
+ // in only one dimension, convert it to NS_STYLE_OVERFLOW_HIDDEN.
+ if (display->mOverflowX == NS_STYLE_OVERFLOW_CLIP)
+ display->mOverflowX = NS_STYLE_OVERFLOW_HIDDEN;
+ if (display->mOverflowY == NS_STYLE_OVERFLOW_CLIP)
+ display->mOverflowY = NS_STYLE_OVERFLOW_HIDDEN;
+
+ // If 'visible' is specified but doesn't match the other dimension, it
+ // turns into 'auto'.
+ if (display->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE)
+ display->mOverflowX = NS_STYLE_OVERFLOW_AUTO;
+ if (display->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE)
+ display->mOverflowY = NS_STYLE_OVERFLOW_AUTO;
+ }
+
+ // When 'contain: paint', update overflow from 'visible' to 'clip'.
+ if (display->IsContainPaint()) {
+ // XXX This actually sets overflow-[x|y] to -moz-hidden-unscrollable.
+ if (display->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) {
+ // This uncacheability (and the one below) could be fixed by adding
+ // mOriginalOverflowX and mOriginalOverflowY fields, if necessary.
+ display->mOverflowX = NS_STYLE_OVERFLOW_CLIP;
+ conditions.SetUncacheable();
+ }
+ if (display->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE) {
+ display->mOverflowY = NS_STYLE_OVERFLOW_CLIP;
+ conditions.SetUncacheable();
+ }
+ }
+
+ SetValue(*aRuleData->ValueForOverflowClipBox(), display->mOverflowClipBox,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mOverflowClipBox,
+ NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX);
+
+ SetValue(*aRuleData->ValueForResize(), display->mResize, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mResize,
+ NS_STYLE_RESIZE_NONE);
+
+ if (display->mDisplay != StyleDisplay::None) {
+ // CSS2 9.7 specifies display type corrections dealing with 'float'
+ // and 'position'. Since generated content can't be floated or
+ // positioned, we can deal with it here.
+
+ nsIAtom* pseudo = aContext->GetPseudo();
+ if (pseudo && display->mDisplay == StyleDisplay::Contents) {
+ // We don't want to create frames for anonymous content using a parent
+ // frame that is for content above the root of the anon tree.
+ // (XXX what we really should check here is not GetPseudo() but if there's
+ // a 'content' property value that implies anon content but we can't
+ // check that here since that's a different struct(?))
+ // We might get display:contents to work for CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS
+ // pseudos (:first-letter etc) in the future, but those have a lot of
+ // special handling in frame construction so they are also unsupported
+ // for now.
+ display->mOriginalDisplay = display->mDisplay = StyleDisplay::Inline;
+ conditions.SetUncacheable();
+ }
+
+ // Inherit a <fieldset> grid/flex display type into its anon content frame.
+ if (pseudo == nsCSSAnonBoxes::fieldsetContent) {
+ MOZ_ASSERT(display->mDisplay == StyleDisplay::Block,
+ "forms.css should have set 'display:block'");
+ switch (parentDisplay->mDisplay) {
+ case StyleDisplay::Grid:
+ case StyleDisplay::InlineGrid:
+ display->mDisplay = StyleDisplay::Grid;
+ conditions.SetUncacheable();
+ break;
+ case StyleDisplay::Flex:
+ case StyleDisplay::InlineFlex:
+ display->mDisplay = StyleDisplay::Flex;
+ conditions.SetUncacheable();
+ break;
+ default:
+ break; // Do nothing
+ }
+ }
+
+ if (nsCSSPseudoElements::firstLetter == pseudo) {
+ // a non-floating first-letter must be inline
+ // XXX this fix can go away once bug 103189 is fixed correctly
+ // Note that we reset mOriginalDisplay to enforce the invariant that it equals mDisplay if we're not positioned or floating.
+ display->mOriginalDisplay = display->mDisplay = StyleDisplay::Inline;
+
+ // We can't cache the data in the rule tree since if a more specific
+ // rule has 'float: left' we'll end up with the wrong 'display'
+ // property.
+ conditions.SetUncacheable();
+ }
+
+ if (display->IsAbsolutelyPositionedStyle()) {
+ // 1) if position is 'absolute' or 'fixed' then display must be
+ // block-level and float must be 'none'
+ EnsureBlockDisplay(display->mDisplay);
+ display->mFloat = StyleFloat::None;
+
+ // Note that it's OK to cache this struct in the ruletree
+ // because it's fine as-is for any style context that points to
+ // it directly, and any use of it as aStartStruct (e.g. if a
+ // more specific rule sets "position: static") will use
+ // mOriginalDisplay and mOriginalFloat, which we have carefully
+ // not changed.
+ } else if (display->mFloat != StyleFloat::None) {
+ // 2) if float is not none, and display is not none, then we must
+ // set a block-level 'display' type per CSS2.1 section 9.7.
+ EnsureBlockDisplay(display->mDisplay);
+
+ // Note that it's OK to cache this struct in the ruletree
+ // because it's fine as-is for any style context that points to
+ // it directly, and any use of it as aStartStruct (e.g. if a
+ // more specific rule sets "float: none") will use
+ // mOriginalDisplay, which we have carefully not changed.
+ }
+
+ if (display->IsContainPaint()) {
+ // An element with contain:paint or contain:layout needs to "be a
+ // formatting context". For the purposes of the "display" property, that
+ // just means we need to promote "display:inline" to "inline-block".
+ // XXX We may also need to promote ruby display vals; see bug 1179349.
+
+ // It's okay to cache this change in the rule tree for the same
+ // reasons as floats in the previous condition.
+ if (display->mDisplay == StyleDisplay::Inline) {
+ display->mDisplay = StyleDisplay::InlineBlock;
+ }
+ }
+ }
+
+ /* Convert the nsCSSValueList into an nsTArray<nsTransformFunction *>. */
+ const nsCSSValue* transformValue = aRuleData->ValueForTransform();
+ switch (transformValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ display->mSpecifiedTransform = nullptr;
+ break;
+
+ case eCSSUnit_Inherit:
+ display->mSpecifiedTransform = parentDisplay->mSpecifiedTransform;
+ conditions.SetUncacheable();
+ break;
+
+ case eCSSUnit_SharedList: {
+ nsCSSValueSharedList* list = transformValue->GetSharedListValue();
+ nsCSSValueList* head = list->mHead;
+ MOZ_ASSERT(head, "transform list must have at least one item");
+ // can get a _None in here from transform animation
+ if (head->mValue.GetUnit() == eCSSUnit_None) {
+ MOZ_ASSERT(head->mNext == nullptr, "none must be alone");
+ display->mSpecifiedTransform = nullptr;
+ } else {
+ display->mSpecifiedTransform = list;
+ }
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unrecognized transform unit");
+ }
+
+ /* Convert the nsCSSValueList into a will-change bitfield for fast lookup */
+ const nsCSSValue* willChangeValue = aRuleData->ValueForWillChange();
+ switch (willChangeValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_List:
+ case eCSSUnit_ListDep: {
+ display->mWillChange.Clear();
+ display->mWillChangeBitField = 0;
+ for (const nsCSSValueList* item = willChangeValue->GetListValue();
+ item; item = item->mNext)
+ {
+ if (item->mValue.UnitHasStringValue()) {
+ nsAutoString buffer;
+ item->mValue.GetStringValue(buffer);
+ display->mWillChange.AppendElement(buffer);
+
+ if (buffer.EqualsLiteral("transform")) {
+ display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_TRANSFORM;
+ }
+ if (buffer.EqualsLiteral("opacity")) {
+ display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_OPACITY;
+ }
+ if (buffer.EqualsLiteral("scroll-position")) {
+ display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_SCROLL;
+ }
+
+ nsCSSPropertyID prop =
+ nsCSSProps::LookupProperty(buffer, CSSEnabledState::eForAllContent);
+ if (prop != eCSSProperty_UNKNOWN &&
+ prop != eCSSPropertyExtra_variable) {
+ // If the property given is a shorthand, it indicates the expectation
+ // for all the longhands the shorthand expands to.
+ if (nsCSSProps::IsShorthand(prop)) {
+ for (const nsCSSPropertyID* shorthands =
+ nsCSSProps::SubpropertyEntryFor(prop);
+ *shorthands != eCSSProperty_UNKNOWN; ++shorthands) {
+ display->mWillChangeBitField |= GetWillChangeBitFieldFromPropFlags(*shorthands);
+ }
+ } else {
+ display->mWillChangeBitField |= GetWillChangeBitFieldFromPropFlags(prop);
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case eCSSUnit_Inherit:
+ display->mWillChange = parentDisplay->mWillChange;
+ display->mWillChangeBitField = parentDisplay->mWillChangeBitField;
+ conditions.SetUncacheable();
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_Auto:
+ display->mWillChange.Clear();
+ display->mWillChangeBitField = 0;
+ break;
+
+ default:
+ MOZ_ASSERT(false, "unrecognized will-change unit");
+ }
+
+ // vertical-align: enum, length, percent, calc, inherit
+ const nsCSSValue* verticalAlignValue = aRuleData->ValueForVerticalAlign();
+ if (!SetCoord(*verticalAlignValue, display->mVerticalAlign,
+ parentDisplay->mVerticalAlign,
+ SETCOORD_LPH | SETCOORD_ENUMERATED | SETCOORD_STORE_CALC,
+ aContext, mPresContext, conditions)) {
+ if (eCSSUnit_Initial == verticalAlignValue->GetUnit() ||
+ eCSSUnit_Unset == verticalAlignValue->GetUnit()) {
+ display->mVerticalAlign.SetIntValue(NS_STYLE_VERTICAL_ALIGN_BASELINE,
+ eStyleUnit_Enumerated);
+ }
+ }
+
+ /* Convert -moz-transform-origin. */
+ const nsCSSValue* transformOriginValue =
+ aRuleData->ValueForTransformOrigin();
+ if (transformOriginValue->GetUnit() != eCSSUnit_Null) {
+ const nsCSSValue& valX =
+ transformOriginValue->GetUnit() == eCSSUnit_Triplet ?
+ transformOriginValue->GetTripletValue().mXValue : *transformOriginValue;
+ const nsCSSValue& valY =
+ transformOriginValue->GetUnit() == eCSSUnit_Triplet ?
+ transformOriginValue->GetTripletValue().mYValue : *transformOriginValue;
+ const nsCSSValue& valZ =
+ transformOriginValue->GetUnit() == eCSSUnit_Triplet ?
+ transformOriginValue->GetTripletValue().mZValue : *transformOriginValue;
+
+ mozilla::DebugOnly<bool> cX =
+ SetCoord(valX, display->mTransformOrigin[0],
+ parentDisplay->mTransformOrigin[0],
+ SETCOORD_LPH | SETCOORD_INITIAL_HALF |
+ SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ mozilla::DebugOnly<bool> cY =
+ SetCoord(valY, display->mTransformOrigin[1],
+ parentDisplay->mTransformOrigin[1],
+ SETCOORD_LPH | SETCOORD_INITIAL_HALF |
+ SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ if (valZ.GetUnit() == eCSSUnit_Null) {
+ // Null for the z component means a 0 translation, not
+ // unspecified, as we have already checked the triplet
+ // value for Null.
+ display->mTransformOrigin[2].SetCoordValue(0);
+ } else {
+ mozilla::DebugOnly<bool> cZ =
+ SetCoord(valZ, display->mTransformOrigin[2],
+ parentDisplay->mTransformOrigin[2],
+ SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+ MOZ_ASSERT(cY == cZ, "changed one but not the other");
+ }
+ MOZ_ASSERT(cX == cY, "changed one but not the other");
+ NS_ASSERTION(cX, "Malformed -moz-transform-origin parse!");
+ }
+
+ const nsCSSValue* perspectiveOriginValue =
+ aRuleData->ValueForPerspectiveOrigin();
+ if (perspectiveOriginValue->GetUnit() != eCSSUnit_Null) {
+ mozilla::DebugOnly<bool> result =
+ SetPairCoords(*perspectiveOriginValue,
+ display->mPerspectiveOrigin[0],
+ display->mPerspectiveOrigin[1],
+ parentDisplay->mPerspectiveOrigin[0],
+ parentDisplay->mPerspectiveOrigin[1],
+ SETCOORD_LPH | SETCOORD_INITIAL_HALF |
+ SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+ NS_ASSERTION(result, "Malformed -moz-perspective-origin parse!");
+ }
+
+ SetCoord(*aRuleData->ValueForPerspective(),
+ display->mChildPerspective, parentDisplay->mChildPerspective,
+ SETCOORD_LAH | SETCOORD_INITIAL_NONE | SETCOORD_NONE |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ SetValue(*aRuleData->ValueForBackfaceVisibility(),
+ display->mBackfaceVisibility, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mBackfaceVisibility,
+ NS_STYLE_BACKFACE_VISIBILITY_VISIBLE);
+
+ // transform-style: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTransformStyle(),
+ display->mTransformStyle, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mTransformStyle,
+ NS_STYLE_TRANSFORM_STYLE_FLAT);
+
+ // transform-box: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTransformBox(),
+ display->mTransformBox, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mTransformBox,
+ NS_STYLE_TRANSFORM_BOX_BORDER_BOX);
+
+ // orient: enum, inherit, initial
+ SetValue(*aRuleData->ValueForOrient(),
+ display->mOrient, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentDisplay->mOrient,
+ StyleOrient::Inline);
+
+ // shape-outside: none | [ <basic-shape> || <shape-box> ] | <image>
+ const nsCSSValue* shapeOutsideValue = aRuleData->ValueForShapeOutside();
+ switch (shapeOutsideValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_None:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ display->mShapeOutside = StyleShapeOutside();
+ break;
+ case eCSSUnit_Inherit:
+ conditions.SetUncacheable();
+ display->mShapeOutside = parentDisplay->mShapeOutside;
+ break;
+ case eCSSUnit_URL: {
+ display->mShapeOutside = StyleShapeOutside();
+ display->mShapeOutside.SetURL(shapeOutsideValue->GetURLStructValue());
+ break;
+ }
+ case eCSSUnit_Array: {
+ display->mShapeOutside = StyleShapeOutside();
+ SetStyleShapeSourceToCSSValue(&display->mShapeOutside, shapeOutsideValue,
+ aContext, mPresContext, conditions);
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unrecognized shape-outside unit!");
+ }
+
+ COMPUTE_END_RESET(Display, display)
+}
+
+const void*
+nsRuleNode::ComputeVisibilityData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_INHERITED(Visibility, visibility, parentVisibility)
+
+ // IMPORTANT: No properties in this struct have lengths in them. We
+ // depend on this since CalcLengthWith can call StyleVisibility()
+ // to get the language for resolving fonts!
+
+ // direction: enum, inherit, initial
+ SetValue(*aRuleData->ValueForDirection(), visibility->mDirection,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentVisibility->mDirection,
+ (GET_BIDI_OPTION_DIRECTION(mPresContext->GetBidi())
+ == IBMBIDI_TEXTDIRECTION_RTL)
+ ? NS_STYLE_DIRECTION_RTL : NS_STYLE_DIRECTION_LTR);
+
+ // visibility: enum, inherit, initial
+ SetValue(*aRuleData->ValueForVisibility(), visibility->mVisible,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentVisibility->mVisible,
+ NS_STYLE_VISIBILITY_VISIBLE);
+
+ // image-rendering: enum, inherit
+ SetValue(*aRuleData->ValueForImageRendering(),
+ visibility->mImageRendering, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentVisibility->mImageRendering,
+ NS_STYLE_IMAGE_RENDERING_AUTO);
+
+ // writing-mode: enum, inherit, initial
+ SetValue(*aRuleData->ValueForWritingMode(), visibility->mWritingMode,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentVisibility->mWritingMode,
+ NS_STYLE_WRITING_MODE_HORIZONTAL_TB);
+
+ // text-orientation: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTextOrientation(), visibility->mTextOrientation,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentVisibility->mTextOrientation,
+ NS_STYLE_TEXT_ORIENTATION_MIXED);
+
+ // image-orientation: enum, inherit, initial
+ const nsCSSValue* orientation = aRuleData->ValueForImageOrientation();
+ if (orientation->GetUnit() == eCSSUnit_Inherit ||
+ orientation->GetUnit() == eCSSUnit_Unset) {
+ conditions.SetUncacheable();
+ visibility->mImageOrientation = parentVisibility->mImageOrientation;
+ } else if (orientation->GetUnit() == eCSSUnit_Initial) {
+ visibility->mImageOrientation = nsStyleImageOrientation();
+ } else if (orientation->IsAngularUnit()) {
+ double angle = orientation->GetAngleValueInRadians();
+ visibility->mImageOrientation =
+ nsStyleImageOrientation::CreateAsAngleAndFlip(angle, false);
+ } else if (orientation->GetUnit() == eCSSUnit_Array) {
+ const nsCSSValue::Array* array = orientation->GetArrayValue();
+ MOZ_ASSERT(array->Item(0).IsAngularUnit(),
+ "First image-orientation value is not an angle");
+ MOZ_ASSERT(array->Item(1).GetUnit() == eCSSUnit_Enumerated &&
+ array->Item(1).GetIntValue() == NS_STYLE_IMAGE_ORIENTATION_FLIP,
+ "Second image-orientation value is not 'flip'");
+ double angle = array->Item(0).GetAngleValueInRadians();
+ visibility->mImageOrientation =
+ nsStyleImageOrientation::CreateAsAngleAndFlip(angle, true);
+
+ } else if (orientation->GetUnit() == eCSSUnit_Enumerated) {
+ switch (orientation->GetIntValue()) {
+ case NS_STYLE_IMAGE_ORIENTATION_FLIP:
+ visibility->mImageOrientation = nsStyleImageOrientation::CreateAsFlip();
+ break;
+ case NS_STYLE_IMAGE_ORIENTATION_FROM_IMAGE:
+ visibility->mImageOrientation = nsStyleImageOrientation::CreateAsFromImage();
+ break;
+ default:
+ NS_NOTREACHED("Invalid image-orientation enumerated value");
+ }
+ } else {
+ MOZ_ASSERT(orientation->GetUnit() == eCSSUnit_Null, "Should be null unit");
+ }
+
+ SetValue(*aRuleData->ValueForColorAdjust(), visibility->mColorAdjust,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentVisibility->mColorAdjust,
+ NS_STYLE_COLOR_ADJUST_ECONOMY);
+
+ COMPUTE_END_INHERITED(Visibility, visibility)
+}
+
+const void*
+nsRuleNode::ComputeColorData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_INHERITED(Color, color, parentColor)
+
+ // color: color, string, inherit
+ // Special case for currentColor. According to CSS3, setting color to 'currentColor'
+ // should behave as if it is inherited
+ const nsCSSValue* colorValue = aRuleData->ValueForColor();
+ if ((colorValue->GetUnit() == eCSSUnit_EnumColor &&
+ colorValue->GetIntValue() == NS_COLOR_CURRENTCOLOR) ||
+ colorValue->GetUnit() == eCSSUnit_Unset) {
+ color->mColor = parentColor->mColor;
+ conditions.SetUncacheable();
+ }
+ else if (colorValue->GetUnit() == eCSSUnit_Initial) {
+ color->mColor = mPresContext->DefaultColor();
+ }
+ else {
+ SetColor(*colorValue, parentColor->mColor, mPresContext, aContext,
+ color->mColor, conditions);
+ }
+
+ COMPUTE_END_INHERITED(Color, color)
+}
+
+// information about how to compute values for background-* properties
+template <class SpecifiedValueItem, class ComputedValueItem>
+struct BackgroundItemComputer {
+};
+
+template <>
+struct BackgroundItemComputer<nsCSSValueList, uint8_t>
+{
+ static void ComputeValue(nsStyleContext* aStyleContext,
+ const nsCSSValueList* aSpecifiedValue,
+ uint8_t& aComputedValue,
+ RuleNodeCacheConditions& aConditions)
+ {
+ SetValue(aSpecifiedValue->mValue, aComputedValue, aConditions,
+ SETVAL_ENUMERATED, uint8_t(0), 0);
+ }
+};
+
+template <>
+struct BackgroundItemComputer<nsCSSValuePairList, nsStyleImageLayers::Repeat>
+{
+ static void ComputeValue(nsStyleContext* aStyleContext,
+ const nsCSSValuePairList* aSpecifiedValue,
+ nsStyleImageLayers::Repeat& aComputedValue,
+ RuleNodeCacheConditions& aConditions)
+ {
+ NS_ASSERTION(aSpecifiedValue->mXValue.GetUnit() == eCSSUnit_Enumerated &&
+ (aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Enumerated ||
+ aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Null),
+ "Invalid unit");
+
+ bool hasContraction = true;
+ uint8_t value = aSpecifiedValue->mXValue.GetIntValue();
+ switch (value) {
+ case NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X:
+ aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+ aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
+ break;
+ case NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y:
+ aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
+ aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+ break;
+ default:
+ NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT ||
+ value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+ value == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
+ value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value");
+ aComputedValue.mXRepeat = value;
+ hasContraction = false;
+ break;
+ }
+
+ if (hasContraction) {
+ NS_ASSERTION(aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Null,
+ "Invalid unit.");
+ return;
+ }
+
+ switch (aSpecifiedValue->mYValue.GetUnit()) {
+ case eCSSUnit_Null:
+ aComputedValue.mYRepeat = aComputedValue.mXRepeat;
+ break;
+ case eCSSUnit_Enumerated:
+ value = aSpecifiedValue->mYValue.GetIntValue();
+ NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT ||
+ value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+ value == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
+ value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value");
+ aComputedValue.mYRepeat = value;
+ break;
+ default:
+ NS_NOTREACHED("Unexpected CSS value");
+ break;
+ }
+ }
+};
+
+template <>
+struct BackgroundItemComputer<nsCSSValueList, nsStyleImage>
+{
+ static void ComputeValue(nsStyleContext* aStyleContext,
+ const nsCSSValueList* aSpecifiedValue,
+ nsStyleImage& aComputedValue,
+ RuleNodeCacheConditions& aConditions)
+ {
+ SetStyleImage(aStyleContext, aSpecifiedValue->mValue, aComputedValue,
+ aConditions);
+ }
+};
+
+template <>
+struct BackgroundItemComputer<nsCSSValueList, RefPtr<css::URLValueData>>
+{
+ static void ComputeValue(nsStyleContext* aStyleContext,
+ const nsCSSValueList* aSpecifiedValue,
+ RefPtr<css::URLValueData>& aComputedValue,
+ RuleNodeCacheConditions& aConditions)
+ {
+ switch (aSpecifiedValue->mValue.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_URL:
+ aComputedValue = aSpecifiedValue->mValue.GetURLStructValue();
+ break;
+ case eCSSUnit_Image:
+ aComputedValue = aSpecifiedValue->mValue.GetImageStructValue();
+ break;
+ default:
+ aComputedValue = nullptr;
+ break;
+ }
+ }
+};
+
+/* Helper function for ComputePositionValue.
+ * This function computes a single PositionCoord from two nsCSSValue objects,
+ * which represent an edge and an offset from that edge.
+ */
+static void
+ComputePositionCoord(nsStyleContext* aStyleContext,
+ const nsCSSValue& aEdge,
+ const nsCSSValue& aOffset,
+ Position::Coord* aResult,
+ RuleNodeCacheConditions& aConditions)
+{
+ if (eCSSUnit_Percent == aOffset.GetUnit()) {
+ aResult->mLength = 0;
+ aResult->mPercent = aOffset.GetPercentValue();
+ aResult->mHasPercent = true;
+ } else if (aOffset.IsLengthUnit()) {
+ aResult->mLength = CalcLength(aOffset, aStyleContext,
+ aStyleContext->PresContext(),
+ aConditions);
+ aResult->mPercent = 0.0f;
+ aResult->mHasPercent = false;
+ } else if (aOffset.IsCalcUnit()) {
+ LengthPercentPairCalcOps ops(aStyleContext,
+ aStyleContext->PresContext(),
+ aConditions);
+ nsRuleNode::ComputedCalc vals = ComputeCalc(aOffset, ops);
+ aResult->mLength = vals.mLength;
+ aResult->mPercent = vals.mPercent;
+ aResult->mHasPercent = ops.mHasPercent;
+ } else {
+ aResult->mLength = 0;
+ aResult->mPercent = 0.0f;
+ aResult->mHasPercent = false;
+ NS_ASSERTION(aOffset.GetUnit() == eCSSUnit_Null, "unexpected unit");
+ }
+
+ if (eCSSUnit_Enumerated == aEdge.GetUnit()) {
+ int sign;
+ if (aEdge.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_BOTTOM |
+ NS_STYLE_IMAGELAYER_POSITION_RIGHT)) {
+ sign = -1;
+ } else {
+ sign = 1;
+ }
+ aResult->mPercent = GetFloatFromBoxPosition(aEdge.GetIntValue()) +
+ sign * aResult->mPercent;
+ aResult->mLength = sign * aResult->mLength;
+ aResult->mHasPercent = true;
+ } else {
+ NS_ASSERTION(eCSSUnit_Null == aEdge.GetUnit(), "unexpected unit");
+ }
+}
+
+/* Helper function to convert a CSS <position> specified value into its
+ * computed-style form. */
+static void
+ComputePositionValue(nsStyleContext* aStyleContext,
+ const nsCSSValue& aValue,
+ Position& aComputedValue,
+ RuleNodeCacheConditions& aConditions)
+{
+ NS_ASSERTION(aValue.GetUnit() == eCSSUnit_Array,
+ "unexpected unit for CSS <position> value");
+
+ RefPtr<nsCSSValue::Array> positionArray = aValue.GetArrayValue();
+ NS_ASSERTION(positionArray->Count() == 4,
+ "unexpected number of values in CSS <position> value");
+
+ const nsCSSValue &xEdge = positionArray->Item(0);
+ const nsCSSValue &xOffset = positionArray->Item(1);
+ const nsCSSValue &yEdge = positionArray->Item(2);
+ const nsCSSValue &yOffset = positionArray->Item(3);
+
+ NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit() ||
+ eCSSUnit_Null == xEdge.GetUnit()) &&
+ (eCSSUnit_Enumerated == yEdge.GetUnit() ||
+ eCSSUnit_Null == yEdge.GetUnit()) &&
+ eCSSUnit_Enumerated != xOffset.GetUnit() &&
+ eCSSUnit_Enumerated != yOffset.GetUnit(),
+ "Invalid background position");
+
+ ComputePositionCoord(aStyleContext, xEdge, xOffset,
+ &aComputedValue.mXPosition,
+ aConditions);
+
+ ComputePositionCoord(aStyleContext, yEdge, yOffset,
+ &aComputedValue.mYPosition,
+ aConditions);
+}
+
+/* Helper function to convert the -x or -y part of a CSS <position> specified
+ * value into its computed-style form. */
+static void
+ComputePositionCoordValue(nsStyleContext* aStyleContext,
+ const nsCSSValue& aValue,
+ Position::Coord& aComputedValue,
+ RuleNodeCacheConditions& aConditions)
+{
+ NS_ASSERTION(aValue.GetUnit() == eCSSUnit_Array,
+ "unexpected unit for position coord value");
+
+ RefPtr<nsCSSValue::Array> positionArray = aValue.GetArrayValue();
+ NS_ASSERTION(positionArray->Count() == 2,
+ "unexpected number of values, expecting one edge and one offset");
+
+ const nsCSSValue &edge = positionArray->Item(0);
+ const nsCSSValue &offset = positionArray->Item(1);
+
+ NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() ||
+ eCSSUnit_Null == edge.GetUnit()) &&
+ eCSSUnit_Enumerated != offset.GetUnit(),
+ "Invalid background position");
+
+ ComputePositionCoord(aStyleContext, edge, offset,
+ &aComputedValue,
+ aConditions);
+}
+
+struct BackgroundSizeAxis {
+ nsCSSValue nsCSSValuePairList::* specified;
+ nsStyleImageLayers::Size::Dimension nsStyleImageLayers::Size::* result;
+ uint8_t nsStyleImageLayers::Size::* type;
+};
+
+static const BackgroundSizeAxis gBGSizeAxes[] = {
+ { &nsCSSValuePairList::mXValue,
+ &nsStyleImageLayers::Size::mWidth,
+ &nsStyleImageLayers::Size::mWidthType },
+ { &nsCSSValuePairList::mYValue,
+ &nsStyleImageLayers::Size::mHeight,
+ &nsStyleImageLayers::Size::mHeightType }
+};
+
+template <>
+struct BackgroundItemComputer<nsCSSValuePairList, nsStyleImageLayers::Size>
+{
+ static void ComputeValue(nsStyleContext* aStyleContext,
+ const nsCSSValuePairList* aSpecifiedValue,
+ nsStyleImageLayers::Size& aComputedValue,
+ RuleNodeCacheConditions& aConditions)
+ {
+ nsStyleImageLayers::Size &size = aComputedValue;
+ for (const BackgroundSizeAxis *axis = gBGSizeAxes,
+ *axis_end = ArrayEnd(gBGSizeAxes);
+ axis < axis_end; ++axis) {
+ const nsCSSValue &specified = aSpecifiedValue->*(axis->specified);
+ if (eCSSUnit_Auto == specified.GetUnit()) {
+ size.*(axis->type) = nsStyleImageLayers::Size::eAuto;
+ }
+ else if (eCSSUnit_Enumerated == specified.GetUnit()) {
+ static_assert(nsStyleImageLayers::Size::eContain ==
+ NS_STYLE_IMAGELAYER_SIZE_CONTAIN &&
+ nsStyleImageLayers::Size::eCover ==
+ NS_STYLE_IMAGELAYER_SIZE_COVER,
+ "background size constants out of sync");
+ MOZ_ASSERT(specified.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_CONTAIN ||
+ specified.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_COVER,
+ "invalid enumerated value for size coordinate");
+ size.*(axis->type) = specified.GetIntValue();
+ }
+ else if (eCSSUnit_Null == specified.GetUnit()) {
+ MOZ_ASSERT(axis == gBGSizeAxes + 1,
+ "null allowed only as height value, and only "
+ "for contain/cover/initial/inherit");
+#ifdef DEBUG
+ {
+ const nsCSSValue &widthValue = aSpecifiedValue->mXValue;
+ MOZ_ASSERT(widthValue.GetUnit() != eCSSUnit_Inherit &&
+ widthValue.GetUnit() != eCSSUnit_Initial &&
+ widthValue.GetUnit() != eCSSUnit_Unset,
+ "initial/inherit/unset should already have been handled");
+ MOZ_ASSERT(widthValue.GetUnit() == eCSSUnit_Enumerated &&
+ (widthValue.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_CONTAIN ||
+ widthValue.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_COVER),
+ "null height value not corresponding to allowable "
+ "non-null width value");
+ }
+#endif
+ size.*(axis->type) = size.mWidthType;
+ }
+ else if (eCSSUnit_Percent == specified.GetUnit()) {
+ (size.*(axis->result)).mLength = 0;
+ (size.*(axis->result)).mPercent = specified.GetPercentValue();
+ (size.*(axis->result)).mHasPercent = true;
+ size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage;
+ }
+ else if (specified.IsLengthUnit()) {
+ (size.*(axis->result)).mLength =
+ CalcLength(specified, aStyleContext, aStyleContext->PresContext(),
+ aConditions);
+ (size.*(axis->result)).mPercent = 0.0f;
+ (size.*(axis->result)).mHasPercent = false;
+ size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage;
+ } else {
+ MOZ_ASSERT(specified.IsCalcUnit(), "unexpected unit");
+ LengthPercentPairCalcOps ops(aStyleContext,
+ aStyleContext->PresContext(),
+ aConditions);
+ nsRuleNode::ComputedCalc vals = ComputeCalc(specified, ops);
+ (size.*(axis->result)).mLength = vals.mLength;
+ (size.*(axis->result)).mPercent = vals.mPercent;
+ (size.*(axis->result)).mHasPercent = ops.mHasPercent;
+ size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage;
+ }
+ }
+
+ MOZ_ASSERT(size.mWidthType < nsStyleImageLayers::Size::eDimensionType_COUNT,
+ "bad width type");
+ MOZ_ASSERT(size.mHeightType < nsStyleImageLayers::Size::eDimensionType_COUNT,
+ "bad height type");
+ MOZ_ASSERT((size.mWidthType != nsStyleImageLayers::Size::eContain &&
+ size.mWidthType != nsStyleImageLayers::Size::eCover) ||
+ size.mWidthType == size.mHeightType,
+ "contain/cover apply to both dimensions or to neither");
+ }
+};
+
+template <class ComputedValueItem>
+static void
+SetImageLayerList(nsStyleContext* aStyleContext,
+ const nsCSSValue& aValue,
+ nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
+ const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers,
+ ComputedValueItem nsStyleImageLayers::Layer::* aResultLocation,
+ ComputedValueItem aInitialValue,
+ uint32_t aParentItemCount,
+ uint32_t& aItemCount,
+ uint32_t& aMaxItemCount,
+ bool& aRebuild,
+ RuleNodeCacheConditions& aConditions)
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Inherit:
+ aRebuild = true;
+ aConditions.SetUncacheable();
+ aLayers.EnsureLengthAtLeast(aParentItemCount);
+ aItemCount = aParentItemCount;
+ for (uint32_t i = 0; i < aParentItemCount; ++i) {
+ aLayers[i].*aResultLocation = aParentLayers[i].*aResultLocation;
+ }
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ aRebuild = true;
+ aItemCount = 1;
+ aLayers[0].*aResultLocation = aInitialValue;
+ break;
+
+ case eCSSUnit_List:
+ case eCSSUnit_ListDep: {
+ aRebuild = true;
+ aItemCount = 0;
+ const nsCSSValueList* item = aValue.GetListValue();
+ do {
+ NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null &&
+ item->mValue.GetUnit() != eCSSUnit_Inherit &&
+ item->mValue.GetUnit() != eCSSUnit_Initial &&
+ item->mValue.GetUnit() != eCSSUnit_Unset,
+ "unexpected unit");
+ ++aItemCount;
+ aLayers.EnsureLengthAtLeast(aItemCount);
+ BackgroundItemComputer<nsCSSValueList, ComputedValueItem>
+ ::ComputeValue(aStyleContext, item,
+ aLayers[aItemCount-1].*aResultLocation,
+ aConditions);
+ item = item->mNext;
+ } while (item);
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ }
+
+ if (aItemCount > aMaxItemCount)
+ aMaxItemCount = aItemCount;
+}
+
+// The same as SetImageLayerList, but for values stored in
+// layer.mPosition.*aResultLocation instead of layer.*aResultLocation.
+// This code is duplicated because it would be annoying to make
+// SetImageLayerList generic enough to handle both cases.
+static void
+SetImageLayerPositionCoordList(
+ nsStyleContext* aStyleContext,
+ const nsCSSValue& aValue,
+ nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
+ const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers,
+ Position::Coord
+ Position::* aResultLocation,
+ Position::Coord aInitialValue,
+ uint32_t aParentItemCount,
+ uint32_t& aItemCount,
+ uint32_t& aMaxItemCount,
+ bool& aRebuild,
+ RuleNodeCacheConditions& aConditions)
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Inherit:
+ aRebuild = true;
+ aConditions.SetUncacheable();
+ aLayers.EnsureLengthAtLeast(aParentItemCount);
+ aItemCount = aParentItemCount;
+ for (uint32_t i = 0; i < aParentItemCount; ++i) {
+ aLayers[i].mPosition.*aResultLocation = aParentLayers[i].mPosition.*aResultLocation;
+ }
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ aRebuild = true;
+ aItemCount = 1;
+ aLayers[0].mPosition.*aResultLocation = aInitialValue;
+ break;
+
+ case eCSSUnit_List:
+ case eCSSUnit_ListDep: {
+ aRebuild = true;
+ aItemCount = 0;
+ const nsCSSValueList* item = aValue.GetListValue();
+ do {
+ NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null &&
+ item->mValue.GetUnit() != eCSSUnit_Inherit &&
+ item->mValue.GetUnit() != eCSSUnit_Initial &&
+ item->mValue.GetUnit() != eCSSUnit_Unset,
+ "unexpected unit");
+ ++aItemCount;
+ aLayers.EnsureLengthAtLeast(aItemCount);
+
+ ComputePositionCoordValue(aStyleContext, item->mValue,
+ aLayers[aItemCount-1].mPosition.*aResultLocation,
+ aConditions);
+ item = item->mNext;
+ } while (item);
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ }
+
+ if (aItemCount > aMaxItemCount)
+ aMaxItemCount = aItemCount;
+}
+
+template <class ComputedValueItem>
+static void
+SetImageLayerPairList(nsStyleContext* aStyleContext,
+ const nsCSSValue& aValue,
+ nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
+ const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers,
+ ComputedValueItem nsStyleImageLayers::Layer::*
+ aResultLocation,
+ ComputedValueItem aInitialValue,
+ uint32_t aParentItemCount,
+ uint32_t& aItemCount,
+ uint32_t& aMaxItemCount,
+ bool& aRebuild,
+ RuleNodeCacheConditions& aConditions)
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Inherit:
+ aRebuild = true;
+ aConditions.SetUncacheable();
+ aLayers.EnsureLengthAtLeast(aParentItemCount);
+ aItemCount = aParentItemCount;
+ for (uint32_t i = 0; i < aParentItemCount; ++i) {
+ aLayers[i].*aResultLocation = aParentLayers[i].*aResultLocation;
+ }
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ aRebuild = true;
+ aItemCount = 1;
+ aLayers[0].*aResultLocation = aInitialValue;
+ break;
+
+ case eCSSUnit_PairList:
+ case eCSSUnit_PairListDep: {
+ aRebuild = true;
+ aItemCount = 0;
+ const nsCSSValuePairList* item = aValue.GetPairListValue();
+ do {
+ NS_ASSERTION(item->mXValue.GetUnit() != eCSSUnit_Inherit &&
+ item->mXValue.GetUnit() != eCSSUnit_Initial &&
+ item->mXValue.GetUnit() != eCSSUnit_Unset &&
+ item->mYValue.GetUnit() != eCSSUnit_Inherit &&
+ item->mYValue.GetUnit() != eCSSUnit_Initial &&
+ item->mYValue.GetUnit() != eCSSUnit_Unset,
+ "unexpected unit");
+ ++aItemCount;
+ aLayers.EnsureLengthAtLeast(aItemCount);
+ BackgroundItemComputer<nsCSSValuePairList, ComputedValueItem>
+ ::ComputeValue(aStyleContext, item,
+ aLayers[aItemCount-1].*aResultLocation,
+ aConditions);
+ item = item->mNext;
+ } while (item);
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unexpected unit");
+ }
+
+ if (aItemCount > aMaxItemCount)
+ aMaxItemCount = aItemCount;
+}
+
+template <class ComputedValueItem>
+static void
+FillImageLayerList(
+ nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
+ ComputedValueItem nsStyleImageLayers::Layer::* aResultLocation,
+ uint32_t aItemCount, uint32_t aFillCount)
+{
+ NS_PRECONDITION(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,
+ Position::Coord
+ Position::* aResultLocation,
+ uint32_t aItemCount, uint32_t aFillCount)
+{
+ NS_PRECONDITION(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;
+ }
+}
+
+/* static */
+void
+nsRuleNode::FillAllBackgroundLists(nsStyleImageLayers& aImage,
+ uint32_t aMaxItemCount)
+{
+ // Delete any extra items. We need to keep layers in which any
+ // property was specified.
+ aImage.mLayers.TruncateLengthNonZero(aMaxItemCount);
+
+ uint32_t fillCount = aImage.mImageCount;
+ FillImageLayerList(aImage.mLayers,
+ &nsStyleImageLayers::Layer::mImage,
+ aImage.mImageCount, fillCount);
+ FillImageLayerList(aImage.mLayers,
+ &nsStyleImageLayers::Layer::mRepeat,
+ aImage.mRepeatCount, fillCount);
+ FillImageLayerList(aImage.mLayers,
+ &nsStyleImageLayers::Layer::mAttachment,
+ aImage.mAttachmentCount, fillCount);
+ FillImageLayerList(aImage.mLayers,
+ &nsStyleImageLayers::Layer::mClip,
+ aImage.mClipCount, fillCount);
+ FillImageLayerList(aImage.mLayers,
+ &nsStyleImageLayers::Layer::mBlendMode,
+ aImage.mBlendModeCount, fillCount);
+ FillImageLayerList(aImage.mLayers,
+ &nsStyleImageLayers::Layer::mOrigin,
+ aImage.mOriginCount, fillCount);
+ FillImageLayerPositionCoordList(aImage.mLayers,
+ &Position::mXPosition,
+ aImage.mPositionXCount, fillCount);
+ FillImageLayerPositionCoordList(aImage.mLayers,
+ &Position::mYPosition,
+ aImage.mPositionYCount, fillCount);
+ FillImageLayerList(aImage.mLayers,
+ &nsStyleImageLayers::Layer::mSize,
+ aImage.mSizeCount, fillCount);
+}
+
+const void*
+nsRuleNode::ComputeBackgroundData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Background, bg, parentBG)
+
+ // background-color: color, string, inherit
+ const nsCSSValue* backColorValue = aRuleData->ValueForBackgroundColor();
+ if (eCSSUnit_Initial == backColorValue->GetUnit() ||
+ eCSSUnit_Unset == backColorValue->GetUnit()) {
+ bg->mBackgroundColor = NS_RGBA(0, 0, 0, 0);
+ } else if (!SetColor(*backColorValue, parentBG->mBackgroundColor,
+ mPresContext, aContext, bg->mBackgroundColor,
+ conditions)) {
+ NS_ASSERTION(eCSSUnit_Null == backColorValue->GetUnit(),
+ "unexpected color unit");
+ }
+
+ uint32_t maxItemCount = 1;
+ bool rebuild = false;
+
+ // background-image: url (stored as image), none, inherit [list]
+ nsStyleImage initialImage;
+ SetImageLayerList(aContext, *aRuleData->ValueForBackgroundImage(),
+ bg->mImage.mLayers,
+ parentBG->mImage.mLayers,
+ &nsStyleImageLayers::Layer::mImage,
+ initialImage, parentBG->mImage.mImageCount,
+ bg->mImage.mImageCount,
+ maxItemCount, rebuild, conditions);
+
+ // background-repeat: enum, inherit, initial [pair list]
+ nsStyleImageLayers::Repeat initialRepeat;
+ initialRepeat.SetInitialValues();
+ SetImageLayerPairList(aContext, *aRuleData->ValueForBackgroundRepeat(),
+ bg->mImage.mLayers,
+ parentBG->mImage.mLayers,
+ &nsStyleImageLayers::Layer::mRepeat,
+ initialRepeat, parentBG->mImage.mRepeatCount,
+ bg->mImage.mRepeatCount, maxItemCount, rebuild,
+ conditions);
+
+ // background-attachment: enum, inherit, initial [list]
+ SetImageLayerList(aContext, *aRuleData->ValueForBackgroundAttachment(),
+ bg->mImage.mLayers, parentBG->mImage.mLayers,
+ &nsStyleImageLayers::Layer::mAttachment,
+ uint8_t(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL),
+ parentBG->mImage.mAttachmentCount,
+ bg->mImage.mAttachmentCount, maxItemCount, rebuild,
+ conditions);
+
+ // background-clip: enum, inherit, initial [list]
+ SetImageLayerList(aContext, *aRuleData->ValueForBackgroundClip(),
+ bg->mImage.mLayers,
+ parentBG->mImage.mLayers,
+ &nsStyleImageLayers::Layer::mClip,
+ uint8_t(NS_STYLE_IMAGELAYER_CLIP_BORDER),
+ parentBG->mImage.mClipCount,
+ bg->mImage.mClipCount, maxItemCount, rebuild, conditions);
+
+ // background-blend-mode: enum, inherit, initial [list]
+ SetImageLayerList(aContext, *aRuleData->ValueForBackgroundBlendMode(),
+ bg->mImage.mLayers,
+ parentBG->mImage.mLayers,
+ &nsStyleImageLayers::Layer::mBlendMode,
+ uint8_t(NS_STYLE_BLEND_NORMAL),
+ parentBG->mImage.mBlendModeCount,
+ bg->mImage.mBlendModeCount, maxItemCount, rebuild,
+ conditions);
+
+ // background-origin: enum, inherit, initial [list]
+ SetImageLayerList(aContext, *aRuleData->ValueForBackgroundOrigin(),
+ bg->mImage.mLayers,
+ parentBG->mImage.mLayers,
+ &nsStyleImageLayers::Layer::mOrigin,
+ uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_PADDING),
+ parentBG->mImage.mOriginCount,
+ bg->mImage.mOriginCount, maxItemCount, rebuild,
+ conditions);
+
+ // background-position-x/y: enum, length, percent (flags), inherit [list]
+ Position::Coord initialPositionCoord;
+ initialPositionCoord.mPercent = 0.0f;
+ initialPositionCoord.mLength = 0;
+ initialPositionCoord.mHasPercent = true;
+
+ SetImageLayerPositionCoordList(
+ aContext, *aRuleData->ValueForBackgroundPositionX(),
+ bg->mImage.mLayers,
+ parentBG->mImage.mLayers,
+ &Position::mXPosition,
+ initialPositionCoord, parentBG->mImage.mPositionXCount,
+ bg->mImage.mPositionXCount, maxItemCount, rebuild,
+ conditions);
+ SetImageLayerPositionCoordList(
+ aContext, *aRuleData->ValueForBackgroundPositionY(),
+ bg->mImage.mLayers,
+ parentBG->mImage.mLayers,
+ &Position::mYPosition,
+ initialPositionCoord, parentBG->mImage.mPositionYCount,
+ bg->mImage.mPositionYCount, maxItemCount, rebuild,
+ conditions);
+
+ // background-size: enum, length, auto, inherit, initial [pair list]
+ nsStyleImageLayers::Size initialSize;
+ initialSize.SetInitialValues();
+ SetImageLayerPairList(aContext, *aRuleData->ValueForBackgroundSize(),
+ bg->mImage.mLayers,
+ parentBG->mImage.mLayers,
+ &nsStyleImageLayers::Layer::mSize,
+ initialSize, parentBG->mImage.mSizeCount,
+ bg->mImage.mSizeCount, maxItemCount, rebuild,
+ conditions);
+
+ if (rebuild) {
+ FillAllBackgroundLists(bg->mImage, maxItemCount);
+ }
+
+ COMPUTE_END_RESET(Background, bg)
+}
+
+const void*
+nsRuleNode::ComputeMarginData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Margin, margin, parentMargin)
+
+ // margin: length, percent, calc, inherit
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_margin);
+ nsStyleCoord coord;
+ NS_FOR_CSS_SIDES(side) {
+ nsStyleCoord parentCoord = parentMargin->mMargin.Get(side);
+ if (SetCoord(*aRuleData->ValueFor(subprops[side]),
+ coord, parentCoord,
+ SETCOORD_LPAH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ margin->mMargin.Set(side, coord);
+ }
+ }
+
+ COMPUTE_END_RESET(Margin, margin)
+}
+
+static void
+SetBorderImageRect(const nsCSSValue& aValue,
+ /** outparam */ nsCSSRect& aRect)
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ aRect.Reset();
+ break;
+ case eCSSUnit_Rect:
+ aRect = aValue.GetRectValue();
+ break;
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ aRect.SetAllSidesTo(aValue);
+ break;
+ default:
+ NS_ASSERTION(false, "Unexpected border image value for rect.");
+ }
+}
+
+static void
+SetBorderImagePair(const nsCSSValue& aValue,
+ /** outparam */ nsCSSValuePair& aPair)
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ aPair.Reset();
+ break;
+ case eCSSUnit_Pair:
+ aPair = aValue.GetPairValue();
+ break;
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ aPair.SetBothValuesTo(aValue);
+ break;
+ default:
+ NS_ASSERTION(false, "Unexpected border image value for pair.");
+ }
+}
+
+static void
+SetBorderImageSlice(const nsCSSValue& aValue,
+ /** outparam */ nsCSSValue& aSlice,
+ /** outparam */ nsCSSValue& aFill)
+{
+ const nsCSSValueList* valueList;
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ aSlice.Reset();
+ aFill.Reset();
+ break;
+ case eCSSUnit_List:
+ // Get slice dimensions.
+ valueList = aValue.GetListValue();
+ aSlice = valueList->mValue;
+
+ // Get "fill" keyword.
+ valueList = valueList->mNext;
+ if (valueList) {
+ aFill = valueList->mValue;
+ } else {
+ aFill.SetInitialValue();
+ }
+ break;
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ aSlice = aValue;
+ aFill = aValue;
+ break;
+ default:
+ NS_ASSERTION(false, "Unexpected border image value for pair.");
+ }
+}
+
+const void*
+nsRuleNode::ComputeBorderData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Border, border, parentBorder)
+
+ // box-decoration-break: enum, inherit, initial
+ SetValue(*aRuleData->ValueForBoxDecorationBreak(),
+ border->mBoxDecorationBreak, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentBorder->mBoxDecorationBreak,
+ StyleBoxDecorationBreak::Slice);
+
+ // border-width, border-*-width: length, enum, inherit
+ nsStyleCoord coord;
+ {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_width);
+ NS_FOR_CSS_SIDES(side) {
+ const nsCSSValue& value = *aRuleData->ValueFor(subprops[side]);
+ NS_ASSERTION(eCSSUnit_Percent != value.GetUnit(),
+ "Percentage borders not implemented yet "
+ "If implementing, make sure to fix all consumers of "
+ "nsStyleBorder, the IsPercentageAwareChild method, "
+ "the nsAbsoluteContainingBlock::FrameDependsOnContainer "
+ "method, the "
+ "nsLineLayout::IsPercentageAwareReplacedElement method "
+ "and probably some other places");
+ if (eCSSUnit_Enumerated == value.GetUnit()) {
+ NS_ASSERTION(value.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
+ value.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
+ value.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
+ "Unexpected enum value");
+ border->SetBorderWidth(side,
+ (mPresContext->GetBorderWidthTable())[value.GetIntValue()]);
+ }
+ // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
+ else if (SetCoord(value, coord, nsStyleCoord(),
+ SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
+ aContext, mPresContext, conditions)) {
+ NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
+ // clamp negative calc() to 0.
+ border->SetBorderWidth(side, std::max(coord.GetCoordValue(), 0));
+ }
+ else if (eCSSUnit_Inherit == value.GetUnit()) {
+ conditions.SetUncacheable();
+ border->SetBorderWidth(side,
+ parentBorder->GetComputedBorder().Side(side));
+ }
+ else if (eCSSUnit_Initial == value.GetUnit() ||
+ eCSSUnit_Unset == value.GetUnit()) {
+ border->SetBorderWidth(side,
+ (mPresContext->GetBorderWidthTable())[NS_STYLE_BORDER_WIDTH_MEDIUM]);
+ }
+ else {
+ NS_ASSERTION(eCSSUnit_Null == value.GetUnit(),
+ "missing case handling border width");
+ }
+ }
+ }
+
+ // border-style, border-*-style: enum, inherit
+ {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_style);
+ NS_FOR_CSS_SIDES(side) {
+ const nsCSSValue& value = *aRuleData->ValueFor(subprops[side]);
+ nsCSSUnit unit = value.GetUnit();
+ MOZ_ASSERT(eCSSUnit_None != unit,
+ "'none' should be handled as enumerated value");
+ if (eCSSUnit_Enumerated == unit) {
+ border->SetBorderStyle(side, value.GetIntValue());
+ }
+ else if (eCSSUnit_Initial == unit ||
+ eCSSUnit_Unset == unit) {
+ border->SetBorderStyle(side, NS_STYLE_BORDER_STYLE_NONE);
+ }
+ else if (eCSSUnit_Inherit == unit) {
+ conditions.SetUncacheable();
+ border->SetBorderStyle(side, parentBorder->GetBorderStyle(side));
+ }
+ }
+ }
+
+ // -moz-border-*-colors: color, string, enum, none, inherit/initial
+ nscolor borderColor;
+ nscolor unused = NS_RGB(0,0,0);
+
+ static const nsCSSPropertyID borderColorsProps[] = {
+ eCSSProperty_border_top_colors,
+ eCSSProperty_border_right_colors,
+ eCSSProperty_border_bottom_colors,
+ eCSSProperty_border_left_colors
+ };
+
+ NS_FOR_CSS_SIDES(side) {
+ const nsCSSValue& value = *aRuleData->ValueFor(borderColorsProps[side]);
+ switch (value.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ border->ClearBorderColors(side);
+ break;
+
+ case eCSSUnit_Inherit: {
+ conditions.SetUncacheable();
+ border->ClearBorderColors(side);
+ if (parentContext) {
+ nsBorderColors *parentColors;
+ parentBorder->GetCompositeColors(side, &parentColors);
+ if (parentColors) {
+ border->EnsureBorderColors();
+ border->mBorderColors[side] = parentColors->Clone();
+ }
+ }
+ break;
+ }
+
+ case eCSSUnit_List:
+ case eCSSUnit_ListDep: {
+ // Some composite border color information has been specified for this
+ // border side.
+ border->EnsureBorderColors();
+ border->ClearBorderColors(side);
+ const nsCSSValueList* list = value.GetListValue();
+ while (list) {
+ if (SetColor(list->mValue, unused, mPresContext,
+ aContext, borderColor, conditions))
+ border->AppendBorderColor(side, borderColor);
+ else {
+ NS_NOTREACHED("unexpected item in -moz-border-*-colors list");
+ }
+ list = list->mNext;
+ }
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unrecognized border color unit");
+ }
+ }
+
+ // border-color, border-*-color: color, string, enum, inherit
+ {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color);
+ NS_FOR_CSS_SIDES(side) {
+ SetComplexColor<eUnsetInitial>(*aRuleData->ValueFor(subprops[side]),
+ parentBorder->mBorderColor[side],
+ StyleComplexColor::CurrentColor(),
+ mPresContext,
+ border->mBorderColor[side], conditions);
+ }
+ }
+
+ // border-radius: length, percent, inherit
+ {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_radius);
+ NS_FOR_CSS_FULL_CORNERS(corner) {
+ int cx = NS_FULL_TO_HALF_CORNER(corner, false);
+ int cy = NS_FULL_TO_HALF_CORNER(corner, true);
+ const nsCSSValue& radius = *aRuleData->ValueFor(subprops[corner]);
+ nsStyleCoord parentX = parentBorder->mBorderRadius.Get(cx);
+ nsStyleCoord parentY = parentBorder->mBorderRadius.Get(cy);
+ nsStyleCoord coordX, coordY;
+
+ if (SetPairCoords(radius, coordX, coordY, parentX, parentY,
+ SETCOORD_LPH | SETCOORD_INITIAL_ZERO |
+ SETCOORD_STORE_CALC | SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ border->mBorderRadius.Set(cx, coordX);
+ border->mBorderRadius.Set(cy, coordY);
+ }
+ }
+ }
+
+ // float-edge: enum, inherit, initial
+ SetValue(*aRuleData->ValueForFloatEdge(),
+ border->mFloatEdge, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentBorder->mFloatEdge,
+ StyleFloatEdge::ContentBox);
+
+ // border-image-source
+ const nsCSSValue* borderImageSource = aRuleData->ValueForBorderImageSource();
+ if (borderImageSource->GetUnit() == eCSSUnit_Inherit) {
+ conditions.SetUncacheable();
+ border->mBorderImageSource = parentBorder->mBorderImageSource;
+ } else {
+ SetStyleImage(aContext,
+ *borderImageSource,
+ border->mBorderImageSource,
+ conditions);
+ }
+
+ nsCSSValue borderImageSliceValue;
+ nsCSSValue borderImageSliceFill;
+ SetBorderImageSlice(*aRuleData->ValueForBorderImageSlice(),
+ borderImageSliceValue, borderImageSliceFill);
+
+ // border-image-slice: fill
+ SetValue(borderImageSliceFill,
+ border->mBorderImageFill,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentBorder->mBorderImageFill,
+ NS_STYLE_BORDER_IMAGE_SLICE_NOFILL);
+
+ nsCSSRect borderImageSlice;
+ SetBorderImageRect(borderImageSliceValue, borderImageSlice);
+
+ nsCSSRect borderImageWidth;
+ SetBorderImageRect(*aRuleData->ValueForBorderImageWidth(),
+ borderImageWidth);
+
+ nsCSSRect borderImageOutset;
+ SetBorderImageRect(*aRuleData->ValueForBorderImageOutset(),
+ borderImageOutset);
+
+ NS_FOR_CSS_SIDES (side) {
+ // border-image-slice
+ if (SetCoord(borderImageSlice.*(nsCSSRect::sides[side]), coord,
+ parentBorder->mBorderImageSlice.Get(side),
+ SETCOORD_FACTOR | SETCOORD_PERCENT |
+ SETCOORD_INHERIT | SETCOORD_INITIAL_HUNDRED_PCT |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ border->mBorderImageSlice.Set(side, coord);
+ }
+
+ // border-image-width
+ // 'auto' here means "same as slice"
+ if (SetCoord(borderImageWidth.*(nsCSSRect::sides[side]), coord,
+ parentBorder->mBorderImageWidth.Get(side),
+ SETCOORD_LPAH | SETCOORD_FACTOR | SETCOORD_INITIAL_FACTOR_ONE |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ border->mBorderImageWidth.Set(side, coord);
+ }
+
+ // border-image-outset
+ if (SetCoord(borderImageOutset.*(nsCSSRect::sides[side]), coord,
+ parentBorder->mBorderImageOutset.Get(side),
+ SETCOORD_LENGTH | SETCOORD_FACTOR |
+ SETCOORD_INHERIT | SETCOORD_INITIAL_FACTOR_ZERO |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ border->mBorderImageOutset.Set(side, coord);
+ }
+ }
+
+ // border-image-repeat
+ nsCSSValuePair borderImageRepeat;
+ SetBorderImagePair(*aRuleData->ValueForBorderImageRepeat(),
+ borderImageRepeat);
+
+ SetValue(borderImageRepeat.mXValue,
+ border->mBorderImageRepeatH,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentBorder->mBorderImageRepeatH,
+ NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH);
+
+ SetValue(borderImageRepeat.mYValue,
+ border->mBorderImageRepeatV,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentBorder->mBorderImageRepeatV,
+ NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH);
+
+ COMPUTE_END_RESET(Border, border)
+}
+
+const void*
+nsRuleNode::ComputePaddingData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Padding, padding, parentPadding)
+
+ // padding: length, percent, calc, inherit
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_padding);
+ nsStyleCoord coord;
+ NS_FOR_CSS_SIDES(side) {
+ nsStyleCoord parentCoord = parentPadding->mPadding.Get(side);
+ if (SetCoord(*aRuleData->ValueFor(subprops[side]),
+ coord, parentCoord,
+ SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ padding->mPadding.Set(side, coord);
+ }
+ }
+
+ COMPUTE_END_RESET(Padding, padding)
+}
+
+const void*
+nsRuleNode::ComputeOutlineData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Outline, outline, parentOutline)
+
+ // outline-width: length, enum, inherit
+ const nsCSSValue* outlineWidthValue = aRuleData->ValueForOutlineWidth();
+ if (eCSSUnit_Initial == outlineWidthValue->GetUnit() ||
+ eCSSUnit_Unset == outlineWidthValue->GetUnit()) {
+ outline->mOutlineWidth =
+ nsStyleCoord(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated);
+ }
+ else {
+ SetCoord(*outlineWidthValue, outline->mOutlineWidth,
+ parentOutline->mOutlineWidth,
+ SETCOORD_LEH | SETCOORD_CALC_LENGTH_ONLY, aContext,
+ mPresContext, conditions);
+ }
+
+ // outline-offset: length, inherit
+ nsStyleCoord tempCoord;
+ const nsCSSValue* outlineOffsetValue = aRuleData->ValueForOutlineOffset();
+ if (SetCoord(*outlineOffsetValue, tempCoord,
+ nsStyleCoord(parentOutline->mOutlineOffset,
+ nsStyleCoord::CoordConstructor),
+ SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_CALC_LENGTH_ONLY |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ outline->mOutlineOffset = tempCoord.GetCoordValue();
+ } else {
+ NS_ASSERTION(outlineOffsetValue->GetUnit() == eCSSUnit_Null,
+ "unexpected unit");
+ }
+
+ // outline-color: color, string, enum, inherit
+ SetComplexColor<eUnsetInitial>(*aRuleData->ValueForOutlineColor(),
+ parentOutline->mOutlineColor,
+ StyleComplexColor::CurrentColor(),
+ mPresContext,
+ outline->mOutlineColor, conditions);
+
+ // -moz-outline-radius: length, percent, inherit
+ {
+ const nsCSSPropertyID* subprops =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty__moz_outline_radius);
+ NS_FOR_CSS_FULL_CORNERS(corner) {
+ int cx = NS_FULL_TO_HALF_CORNER(corner, false);
+ int cy = NS_FULL_TO_HALF_CORNER(corner, true);
+ const nsCSSValue& radius = *aRuleData->ValueFor(subprops[corner]);
+ nsStyleCoord parentX = parentOutline->mOutlineRadius.Get(cx);
+ nsStyleCoord parentY = parentOutline->mOutlineRadius.Get(cy);
+ nsStyleCoord coordX, coordY;
+
+ if (SetPairCoords(radius, coordX, coordY, parentX, parentY,
+ SETCOORD_LPH | SETCOORD_INITIAL_ZERO |
+ SETCOORD_STORE_CALC | SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ outline->mOutlineRadius.Set(cx, coordX);
+ outline->mOutlineRadius.Set(cy, coordY);
+ }
+ }
+ }
+
+ // outline-style: enum, inherit, initial
+ // cannot use SetValue because of SetOutlineStyle
+ const nsCSSValue* outlineStyleValue = aRuleData->ValueForOutlineStyle();
+ nsCSSUnit unit = outlineStyleValue->GetUnit();
+ MOZ_ASSERT(eCSSUnit_None != unit && eCSSUnit_Auto != unit,
+ "'none' and 'auto' should be handled as enumerated values");
+ if (eCSSUnit_Enumerated == unit) {
+ outline->mOutlineStyle = outlineStyleValue->GetIntValue();
+ } else if (eCSSUnit_Initial == unit ||
+ eCSSUnit_Unset == unit) {
+ outline->mOutlineStyle = NS_STYLE_BORDER_STYLE_NONE;
+ } else if (eCSSUnit_Inherit == unit) {
+ conditions.SetUncacheable();
+ outline->mOutlineStyle = parentOutline->mOutlineStyle;
+ }
+
+ outline->RecalcData();
+ COMPUTE_END_RESET(Outline, outline)
+}
+
+const void*
+nsRuleNode::ComputeListData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_INHERITED(List, list, parentList)
+
+ // quotes: inherit, initial, none, [string string]+
+ const nsCSSValue* quotesValue = aRuleData->ValueForQuotes();
+ switch (quotesValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Unset:
+ conditions.SetUncacheable();
+ list->SetQuotesInherit(parentList);
+ break;
+ case eCSSUnit_Initial:
+ list->SetQuotesInitial();
+ break;
+ case eCSSUnit_None:
+ list->SetQuotesNone();
+ break;
+ case eCSSUnit_PairList:
+ case eCSSUnit_PairListDep: {
+ const nsCSSValuePairList* ourQuotes = quotesValue->GetPairListValue();
+
+ nsStyleQuoteValues::QuotePairArray quotePairs;
+ quotePairs.SetLength(ListLength(ourQuotes));
+
+ size_t index = 0;
+ nsAutoString buffer;
+ while (ourQuotes) {
+ MOZ_ASSERT(ourQuotes->mXValue.GetUnit() == eCSSUnit_String &&
+ ourQuotes->mYValue.GetUnit() == eCSSUnit_String,
+ "improper list contents for quotes");
+ quotePairs[index].first = ourQuotes->mXValue.GetStringValue(buffer);
+ quotePairs[index].second = ourQuotes->mYValue.GetStringValue(buffer);
+ ++index;
+ ourQuotes = ourQuotes->mNext;
+ }
+ list->SetQuotes(Move(quotePairs));
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "unexpected value unit");
+ }
+
+ // list-style-type: string, none, inherit, initial
+ const nsCSSValue* typeValue = aRuleData->ValueForListStyleType();
+ switch (typeValue->GetUnit()) {
+ case eCSSUnit_Unset:
+ case eCSSUnit_Inherit: {
+ conditions.SetUncacheable();
+ list->SetCounterStyle(parentList->GetCounterStyle());
+ break;
+ }
+ case eCSSUnit_Initial:
+ list->SetListStyleType(NS_LITERAL_STRING("disc"), mPresContext);
+ break;
+ case eCSSUnit_Ident: {
+ nsString typeIdent;
+ typeValue->GetStringValue(typeIdent);
+ list->SetListStyleType(typeIdent, mPresContext);
+ break;
+ }
+ case eCSSUnit_String: {
+ nsString str;
+ typeValue->GetStringValue(str);
+ list->SetCounterStyle(new AnonymousCounterStyle(str));
+ break;
+ }
+ case eCSSUnit_Enumerated: {
+ // For compatibility with html attribute map.
+ // This branch should never be called for value from CSS.
+ int32_t intValue = typeValue->GetIntValue();
+ nsAutoString name;
+ switch (intValue) {
+ case NS_STYLE_LIST_STYLE_LOWER_ROMAN:
+ name.AssignLiteral(u"lower-roman");
+ break;
+ case NS_STYLE_LIST_STYLE_UPPER_ROMAN:
+ name.AssignLiteral(u"upper-roman");
+ break;
+ case NS_STYLE_LIST_STYLE_LOWER_ALPHA:
+ name.AssignLiteral(u"lower-alpha");
+ break;
+ case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
+ name.AssignLiteral(u"upper-alpha");
+ break;
+ default:
+ CopyASCIItoUTF16(nsCSSProps::ValueToKeyword(
+ intValue, nsCSSProps::kListStyleKTable), name);
+ break;
+ }
+ list->SetListStyleType(name, mPresContext);
+ break;
+ }
+ case eCSSUnit_Symbols:
+ list->SetCounterStyle(new AnonymousCounterStyle(typeValue->GetArrayValue()));
+ break;
+ case eCSSUnit_Null:
+ break;
+ default:
+ NS_NOTREACHED("Unexpected value unit");
+ }
+
+ // list-style-image: url, none, inherit
+ const nsCSSValue* imageValue = aRuleData->ValueForListStyleImage();
+ if (eCSSUnit_Image == imageValue->GetUnit()) {
+ SetStyleImageRequest([&](nsStyleImageRequest* req) {
+ list->mListStyleImage = req;
+ }, mPresContext, *imageValue, nsStyleImageRequest::Mode(0));
+ }
+ else if (eCSSUnit_None == imageValue->GetUnit() ||
+ eCSSUnit_Initial == imageValue->GetUnit()) {
+ list->mListStyleImage = nullptr;
+ }
+ else if (eCSSUnit_Inherit == imageValue->GetUnit() ||
+ eCSSUnit_Unset == imageValue->GetUnit()) {
+ conditions.SetUncacheable();
+ list->mListStyleImage = parentList->mListStyleImage;
+ }
+
+ // list-style-position: enum, inherit, initial
+ SetValue(*aRuleData->ValueForListStylePosition(),
+ list->mListStylePosition, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentList->mListStylePosition,
+ NS_STYLE_LIST_STYLE_POSITION_OUTSIDE);
+
+ // image region property: length, auto, inherit
+ const nsCSSValue* imageRegionValue = aRuleData->ValueForImageRegion();
+ switch (imageRegionValue->GetUnit()) {
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Unset:
+ conditions.SetUncacheable();
+ list->mImageRegion = parentList->mImageRegion;
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Auto:
+ list->mImageRegion.SetRect(0,0,0,0);
+ break;
+
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Rect: {
+ const nsCSSRect& rgnRect = imageRegionValue->GetRectValue();
+
+ if (rgnRect.mTop.GetUnit() == eCSSUnit_Auto)
+ list->mImageRegion.y = 0;
+ else if (rgnRect.mTop.IsLengthUnit())
+ list->mImageRegion.y =
+ CalcLength(rgnRect.mTop, aContext, mPresContext, conditions);
+
+ if (rgnRect.mBottom.GetUnit() == eCSSUnit_Auto)
+ list->mImageRegion.height = 0;
+ else if (rgnRect.mBottom.IsLengthUnit())
+ list->mImageRegion.height =
+ CalcLength(rgnRect.mBottom, aContext, mPresContext,
+ conditions) - list->mImageRegion.y;
+
+ if (rgnRect.mLeft.GetUnit() == eCSSUnit_Auto)
+ list->mImageRegion.x = 0;
+ else if (rgnRect.mLeft.IsLengthUnit())
+ list->mImageRegion.x =
+ CalcLength(rgnRect.mLeft, aContext, mPresContext, conditions);
+
+ if (rgnRect.mRight.GetUnit() == eCSSUnit_Auto)
+ list->mImageRegion.width = 0;
+ else if (rgnRect.mRight.IsLengthUnit())
+ list->mImageRegion.width =
+ CalcLength(rgnRect.mRight, aContext, mPresContext,
+ conditions) - list->mImageRegion.x;
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unrecognized image-region unit");
+ }
+
+ COMPUTE_END_INHERITED(List, list)
+}
+
+static void
+SetGridTrackBreadth(const nsCSSValue& aValue,
+ nsStyleCoord& aResult,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ nsCSSUnit unit = aValue.GetUnit();
+ if (unit == eCSSUnit_FlexFraction) {
+ aResult.SetFlexFractionValue(aValue.GetFloatValue());
+ } else if (unit == eCSSUnit_Auto) {
+ aResult.SetAutoValue();
+ } else if (unit == eCSSUnit_None) {
+ // For fit-content().
+ aResult.SetNoneValue();
+ } else {
+ MOZ_ASSERT(unit != eCSSUnit_Inherit && unit != eCSSUnit_Unset,
+ "Unexpected value that would use dummyParentCoord");
+ const nsStyleCoord dummyParentCoord;
+ DebugOnly<bool> stored =
+ SetCoord(aValue, aResult, dummyParentCoord,
+ SETCOORD_LPE | SETCOORD_STORE_CALC,
+ aStyleContext, aPresContext, aConditions);
+ MOZ_ASSERT(stored, "invalid <track-size> value");
+ }
+}
+
+static void
+SetGridTrackSize(const nsCSSValue& aValue,
+ nsStyleCoord& aResultMin,
+ nsStyleCoord& aResultMax,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ if (aValue.GetUnit() == eCSSUnit_Function) {
+ nsCSSValue::Array* func = aValue.GetArrayValue();
+ auto funcName = func->Item(0).GetKeywordValue();
+ if (funcName == eCSSKeyword_minmax) {
+ SetGridTrackBreadth(func->Item(1), aResultMin,
+ aStyleContext, aPresContext, aConditions);
+ SetGridTrackBreadth(func->Item(2), aResultMax,
+ aStyleContext, aPresContext, aConditions);
+ } else if (funcName == eCSSKeyword_fit_content) {
+ // We represent fit-content(L) as 'none' min-sizing and L max-sizing.
+ SetGridTrackBreadth(nsCSSValue(eCSSUnit_None), aResultMin,
+ aStyleContext, aPresContext, aConditions);
+ SetGridTrackBreadth(func->Item(1), aResultMax,
+ aStyleContext, aPresContext, aConditions);
+ } else {
+ NS_ERROR("Expected minmax() or fit-content(), got another function name");
+ }
+ } else {
+ // A single <track-breadth>,
+ // specifies identical min and max sizing functions.
+ SetGridTrackBreadth(aValue, aResultMin,
+ aStyleContext, aPresContext, aConditions);
+ aResultMax = aResultMin;
+ }
+}
+
+static void
+SetGridAutoColumnsRows(const nsCSSValue& aValue,
+ nsStyleCoord& aResultMin,
+ nsStyleCoord& aResultMax,
+ const nsStyleCoord& aParentValueMin,
+ const nsStyleCoord& aParentValueMax,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Inherit:
+ aConditions.SetUncacheable();
+ aResultMin = aParentValueMin;
+ aResultMax = aParentValueMax;
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ // The initial value is 'auto',
+ // which computes to 'minmax(auto, auto)'.
+ // (Explicitly-specified 'auto' values are handled in SetGridTrackSize.)
+ aResultMin.SetAutoValue();
+ aResultMax.SetAutoValue();
+ break;
+
+ default:
+ SetGridTrackSize(aValue, aResultMin, aResultMax,
+ aStyleContext, aPresContext, aConditions);
+ }
+}
+
+static void
+AppendGridLineNames(const nsCSSValue& aValue,
+ nsTArray<nsString>& aNameList)
+{
+ // Compute a <line-names> value
+ // Null unit means empty list, nothing more to do.
+ if (aValue.GetUnit() != eCSSUnit_Null) {
+ const nsCSSValueList* item = aValue.GetListValue();
+ do {
+ nsString* name = aNameList.AppendElement();
+ item->mValue.GetStringValue(*name);
+ item = item->mNext;
+ } while (item);
+ }
+}
+
+static void
+SetGridTrackList(const nsCSSValue& aValue,
+ nsStyleGridTemplate& aResult,
+ const nsStyleGridTemplate& aParentValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Inherit:
+ aConditions.SetUncacheable();
+ aResult.mIsSubgrid = aParentValue.mIsSubgrid;
+ aResult.mLineNameLists = aParentValue.mLineNameLists;
+ aResult.mMinTrackSizingFunctions = aParentValue.mMinTrackSizingFunctions;
+ aResult.mMaxTrackSizingFunctions = aParentValue.mMaxTrackSizingFunctions;
+ aResult.mRepeatAutoLineNameListBefore = aParentValue.mRepeatAutoLineNameListBefore;
+ aResult.mRepeatAutoLineNameListAfter = aParentValue.mRepeatAutoLineNameListAfter;
+ aResult.mRepeatAutoIndex = aParentValue.mRepeatAutoIndex;
+ aResult.mIsAutoFill = aParentValue.mIsAutoFill;
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ aResult.mIsSubgrid = false;
+ aResult.mLineNameLists.Clear();
+ aResult.mMinTrackSizingFunctions.Clear();
+ aResult.mMaxTrackSizingFunctions.Clear();
+ aResult.mRepeatAutoLineNameListBefore.Clear();
+ aResult.mRepeatAutoLineNameListAfter.Clear();
+ aResult.mRepeatAutoIndex = -1;
+ aResult.mIsAutoFill = false;
+ break;
+
+ default:
+ aResult.mLineNameLists.Clear();
+ aResult.mMinTrackSizingFunctions.Clear();
+ aResult.mMaxTrackSizingFunctions.Clear();
+ aResult.mRepeatAutoLineNameListBefore.Clear();
+ aResult.mRepeatAutoLineNameListAfter.Clear();
+ aResult.mRepeatAutoIndex = -1;
+ aResult.mIsAutoFill = false;
+ const nsCSSValueList* item = aValue.GetListValue();
+ if (item->mValue.GetUnit() == eCSSUnit_Enumerated &&
+ item->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) {
+ // subgrid <line-name-list>?
+ aResult.mIsSubgrid = true;
+ item = item->mNext;
+ for (int32_t i = 0; item && i < nsStyleGridLine::kMaxLine; ++i) {
+ if (item->mValue.GetUnit() == eCSSUnit_Pair) {
+ // This is a 'auto-fill' <name-repeat> expression.
+ const nsCSSValuePair& pair = item->mValue.GetPairValue();
+ MOZ_ASSERT(aResult.mRepeatAutoIndex == -1,
+ "can only have one <name-repeat> with auto-fill");
+ aResult.mRepeatAutoIndex = i;
+ aResult.mIsAutoFill = true;
+ MOZ_ASSERT(pair.mXValue.GetIntValue() == NS_STYLE_GRID_REPEAT_AUTO_FILL,
+ "unexpected repeat() enum value for subgrid");
+ const nsCSSValueList* list = pair.mYValue.GetListValue();
+ AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListBefore);
+ } else {
+ AppendGridLineNames(item->mValue,
+ *aResult.mLineNameLists.AppendElement());
+ }
+ item = item->mNext;
+ }
+ } else {
+ // <track-list>
+ // The list is expected to have odd number of items, at least 3
+ // starting with a <line-names> (sub list of identifiers),
+ // and alternating between that and <track-size>.
+ aResult.mIsSubgrid = false;
+ for (int32_t line = 1; ; ++line) {
+ AppendGridLineNames(item->mValue,
+ *aResult.mLineNameLists.AppendElement());
+ item = item->mNext;
+
+ if (!item || line == nsStyleGridLine::kMaxLine) {
+ break;
+ }
+
+ if (item->mValue.GetUnit() == eCSSUnit_Pair) {
+ // This is a 'auto-fill' / 'auto-fit' <auto-repeat> expression.
+ const nsCSSValuePair& pair = item->mValue.GetPairValue();
+ MOZ_ASSERT(aResult.mRepeatAutoIndex == -1,
+ "can only have one <auto-repeat>");
+ aResult.mRepeatAutoIndex = line - 1;
+ switch (pair.mXValue.GetIntValue()) {
+ case NS_STYLE_GRID_REPEAT_AUTO_FILL:
+ aResult.mIsAutoFill = true;
+ break;
+ case NS_STYLE_GRID_REPEAT_AUTO_FIT:
+ aResult.mIsAutoFill = false;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected repeat() enum value");
+ }
+ const nsCSSValueList* list = pair.mYValue.GetListValue();
+ AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListBefore);
+ list = list->mNext;
+ nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement();
+ nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement();
+ SetGridTrackSize(list->mValue, min, max,
+ aStyleContext, aPresContext, aConditions);
+ list = list->mNext;
+ AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListAfter);
+ } else {
+ nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement();
+ nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement();
+ SetGridTrackSize(item->mValue, min, max,
+ aStyleContext, aPresContext, aConditions);
+ }
+
+ item = item->mNext;
+ MOZ_ASSERT(item, "Expected a eCSSUnit_List of odd length");
+ }
+ MOZ_ASSERT(!aResult.mMinTrackSizingFunctions.IsEmpty() &&
+ aResult.mMinTrackSizingFunctions.Length() ==
+ aResult.mMaxTrackSizingFunctions.Length() &&
+ aResult.mMinTrackSizingFunctions.Length() + 1 ==
+ aResult.mLineNameLists.Length(),
+ "Inconstistent array lengths for nsStyleGridTemplate");
+ }
+ }
+}
+
+static void
+SetGridTemplateAreas(const nsCSSValue& aValue,
+ RefPtr<css::GridTemplateAreasValue>* aResult,
+ css::GridTemplateAreasValue* aParentValue,
+ RuleNodeCacheConditions& aConditions)
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Inherit:
+ aConditions.SetUncacheable();
+ *aResult = aParentValue;
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ *aResult = nullptr;
+ break;
+
+ default:
+ *aResult = aValue.GetGridTemplateAreas();
+ }
+}
+
+static void
+SetGridLine(const nsCSSValue& aValue,
+ nsStyleGridLine& aResult,
+ const nsStyleGridLine& aParentValue,
+ RuleNodeCacheConditions& aConditions)
+
+{
+ switch (aValue.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Inherit:
+ aConditions.SetUncacheable();
+ aResult = aParentValue;
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_Auto:
+ aResult.SetAuto();
+ break;
+
+ default:
+ aResult.SetAuto(); // Reset any existing value.
+ const nsCSSValueList* item = aValue.GetListValue();
+ do {
+ if (item->mValue.GetUnit() == eCSSUnit_Enumerated) {
+ aResult.mHasSpan = true;
+ } else if (item->mValue.GetUnit() == eCSSUnit_Integer) {
+ aResult.mInteger = clamped(item->mValue.GetIntValue(),
+ nsStyleGridLine::kMinLine,
+ nsStyleGridLine::kMaxLine);
+ } else if (item->mValue.GetUnit() == eCSSUnit_Ident) {
+ item->mValue.GetStringValue(aResult.mLineName);
+ } else {
+ NS_ASSERTION(false, "Unexpected unit");
+ }
+ item = item->mNext;
+ } while (item);
+ MOZ_ASSERT(!aResult.IsAuto(),
+ "should have set something away from default value");
+ }
+}
+
+const void*
+nsRuleNode::ComputePositionData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Position, pos, parentPos)
+
+ // box offsets: length, percent, calc, auto, inherit
+ static const nsCSSPropertyID offsetProps[] = {
+ eCSSProperty_top,
+ eCSSProperty_right,
+ eCSSProperty_bottom,
+ eCSSProperty_left
+ };
+ nsStyleCoord coord;
+ NS_FOR_CSS_SIDES(side) {
+ nsStyleCoord parentCoord = parentPos->mOffset.Get(side);
+ if (SetCoord(*aRuleData->ValueFor(offsetProps[side]),
+ coord, parentCoord,
+ SETCOORD_LPAH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ pos->mOffset.Set(side, coord);
+ }
+ }
+
+ // We allow the enumerated box size property values -moz-min-content, etc. to
+ // be specified on both the {,min-,max-}width properties and the
+ // {,min-,max-}height properties, regardless of the writing mode. This is
+ // because the writing mode is not determined until here, at computed value
+ // time. Since we do not support layout behavior of these keywords on the
+ // block-axis properties, we turn them into unset if we find them in
+ // that case.
+
+ WritingMode wm(aContext);
+ bool vertical = wm.IsVertical();
+
+ const nsCSSValue* width = aRuleData->ValueForWidth();
+ if (width->GetUnit() == eCSSUnit_Enumerated) {
+ conditions.SetWritingModeDependency(wm.GetBits());
+ }
+ SetCoord(width->GetUnit() == eCSSUnit_Enumerated && vertical ?
+ nsCSSValue(eCSSUnit_Unset) : *width,
+ pos->mWidth, parentPos->mWidth,
+ SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ const nsCSSValue* minWidth = aRuleData->ValueForMinWidth();
+ if (minWidth->GetUnit() == eCSSUnit_Enumerated) {
+ conditions.SetWritingModeDependency(wm.GetBits());
+ }
+ SetCoord(minWidth->GetUnit() == eCSSUnit_Enumerated && vertical ?
+ nsCSSValue(eCSSUnit_Unset) : *minWidth,
+ pos->mMinWidth, parentPos->mMinWidth,
+ SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ const nsCSSValue* maxWidth = aRuleData->ValueForMaxWidth();
+ if (maxWidth->GetUnit() == eCSSUnit_Enumerated) {
+ conditions.SetWritingModeDependency(wm.GetBits());
+ }
+ SetCoord(maxWidth->GetUnit() == eCSSUnit_Enumerated && vertical ?
+ nsCSSValue(eCSSUnit_Unset) : *maxWidth,
+ pos->mMaxWidth, parentPos->mMaxWidth,
+ SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ const nsCSSValue* height = aRuleData->ValueForHeight();
+ if (height->GetUnit() == eCSSUnit_Enumerated) {
+ conditions.SetWritingModeDependency(wm.GetBits());
+ }
+ SetCoord(height->GetUnit() == eCSSUnit_Enumerated && !vertical ?
+ nsCSSValue(eCSSUnit_Unset) : *height,
+ pos->mHeight, parentPos->mHeight,
+ SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ const nsCSSValue* minHeight = aRuleData->ValueForMinHeight();
+ if (minHeight->GetUnit() == eCSSUnit_Enumerated) {
+ conditions.SetWritingModeDependency(wm.GetBits());
+ }
+ SetCoord(minHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ?
+ nsCSSValue(eCSSUnit_Unset) : *minHeight,
+ pos->mMinHeight, parentPos->mMinHeight,
+ SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ const nsCSSValue* maxHeight = aRuleData->ValueForMaxHeight();
+ if (maxHeight->GetUnit() == eCSSUnit_Enumerated) {
+ conditions.SetWritingModeDependency(wm.GetBits());
+ }
+ SetCoord(maxHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ?
+ nsCSSValue(eCSSUnit_Unset) : *maxHeight,
+ pos->mMaxHeight, parentPos->mMaxHeight,
+ SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ // box-sizing: enum, inherit, initial
+ SetValue(*aRuleData->ValueForBoxSizing(),
+ pos->mBoxSizing, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mBoxSizing,
+ StyleBoxSizing::Content);
+
+ // align-content: enum, inherit, initial
+ SetValue(*aRuleData->ValueForAlignContent(),
+ pos->mAlignContent, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mAlignContent,
+ NS_STYLE_ALIGN_NORMAL);
+
+ // align-items: enum, inherit, initial
+ SetValue(*aRuleData->ValueForAlignItems(),
+ pos->mAlignItems, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mAlignItems,
+ NS_STYLE_ALIGN_NORMAL);
+
+ // align-self: enum, inherit, initial
+ SetValue(*aRuleData->ValueForAlignSelf(),
+ pos->mAlignSelf, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mAlignSelf,
+ NS_STYLE_ALIGN_AUTO);
+
+ // justify-content: enum, inherit, initial
+ SetValue(*aRuleData->ValueForJustifyContent(),
+ pos->mJustifyContent, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mJustifyContent,
+ NS_STYLE_JUSTIFY_NORMAL);
+
+ // justify-items: enum, inherit, initial
+ const auto& justifyItemsValue = *aRuleData->ValueForJustifyItems();
+ if (MOZ_UNLIKELY(justifyItemsValue.GetUnit() == eCSSUnit_Inherit)) {
+ if (MOZ_LIKELY(parentContext)) {
+ pos->mJustifyItems =
+ parentPos->ComputedJustifyItems(parentContext->GetParent());
+ } else {
+ pos->mJustifyItems = NS_STYLE_JUSTIFY_NORMAL;
+ }
+ conditions.SetUncacheable();
+ } else {
+ SetValue(justifyItemsValue,
+ pos->mJustifyItems, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mJustifyItems, // unused, we handle 'inherit' above
+ NS_STYLE_JUSTIFY_AUTO);
+ }
+
+ // justify-self: enum, inherit, initial
+ SetValue(*aRuleData->ValueForJustifySelf(),
+ pos->mJustifySelf, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mJustifySelf,
+ NS_STYLE_JUSTIFY_AUTO);
+
+ // flex-basis: auto, length, percent, enum, calc, inherit, initial
+ // (Note: The flags here should match those used for 'width' property above.)
+ SetCoord(*aRuleData->ValueForFlexBasis(), pos->mFlexBasis, parentPos->mFlexBasis,
+ SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ // flex-direction: enum, inherit, initial
+ SetValue(*aRuleData->ValueForFlexDirection(),
+ pos->mFlexDirection, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mFlexDirection,
+ NS_STYLE_FLEX_DIRECTION_ROW);
+
+ // flex-grow: float, inherit, initial
+ SetFactor(*aRuleData->ValueForFlexGrow(),
+ pos->mFlexGrow, conditions,
+ parentPos->mFlexGrow, 0.0f,
+ SETFCT_UNSET_INITIAL);
+
+ // flex-shrink: float, inherit, initial
+ SetFactor(*aRuleData->ValueForFlexShrink(),
+ pos->mFlexShrink, conditions,
+ parentPos->mFlexShrink, 1.0f,
+ SETFCT_UNSET_INITIAL);
+
+ // flex-wrap: enum, inherit, initial
+ SetValue(*aRuleData->ValueForFlexWrap(),
+ pos->mFlexWrap, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mFlexWrap,
+ NS_STYLE_FLEX_WRAP_NOWRAP);
+
+ // order: integer, inherit, initial
+ SetValue(*aRuleData->ValueForOrder(),
+ pos->mOrder, conditions,
+ SETVAL_INTEGER | SETVAL_UNSET_INITIAL,
+ parentPos->mOrder,
+ NS_STYLE_ORDER_INITIAL);
+
+ // object-fit: enum, inherit, initial
+ SetValue(*aRuleData->ValueForObjectFit(),
+ pos->mObjectFit, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentPos->mObjectFit,
+ NS_STYLE_OBJECT_FIT_FILL);
+
+ // object-position
+ const nsCSSValue& objectPosition = *aRuleData->ValueForObjectPosition();
+ switch (objectPosition.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_Inherit:
+ conditions.SetUncacheable();
+ pos->mObjectPosition = parentPos->mObjectPosition;
+ break;
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ pos->mObjectPosition.SetInitialPercentValues(0.5f);
+ break;
+ default:
+ ComputePositionValue(aContext, objectPosition,
+ pos->mObjectPosition, conditions);
+ }
+
+ // grid-auto-flow
+ const nsCSSValue& gridAutoFlow = *aRuleData->ValueForGridAutoFlow();
+ switch (gridAutoFlow.GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_Inherit:
+ conditions.SetUncacheable();
+ pos->mGridAutoFlow = parentPos->mGridAutoFlow;
+ break;
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ pos->mGridAutoFlow = NS_STYLE_GRID_AUTO_FLOW_ROW;
+ break;
+ default:
+ NS_ASSERTION(gridAutoFlow.GetUnit() == eCSSUnit_Enumerated,
+ "Unexpected unit");
+ pos->mGridAutoFlow = gridAutoFlow.GetIntValue();
+ }
+
+ // grid-auto-columns
+ SetGridAutoColumnsRows(*aRuleData->ValueForGridAutoColumns(),
+ pos->mGridAutoColumnsMin,
+ pos->mGridAutoColumnsMax,
+ parentPos->mGridAutoColumnsMin,
+ parentPos->mGridAutoColumnsMax,
+ aContext, mPresContext, conditions);
+
+ // grid-auto-rows
+ SetGridAutoColumnsRows(*aRuleData->ValueForGridAutoRows(),
+ pos->mGridAutoRowsMin,
+ pos->mGridAutoRowsMax,
+ parentPos->mGridAutoRowsMin,
+ parentPos->mGridAutoRowsMax,
+ aContext, mPresContext, conditions);
+
+ // grid-template-columns
+ SetGridTrackList(*aRuleData->ValueForGridTemplateColumns(),
+ pos->mGridTemplateColumns, parentPos->mGridTemplateColumns,
+ aContext, mPresContext, conditions);
+
+ // grid-template-rows
+ SetGridTrackList(*aRuleData->ValueForGridTemplateRows(),
+ pos->mGridTemplateRows, parentPos->mGridTemplateRows,
+ aContext, mPresContext, conditions);
+
+ // grid-tempate-areas
+ SetGridTemplateAreas(*aRuleData->ValueForGridTemplateAreas(),
+ &pos->mGridTemplateAreas,
+ parentPos->mGridTemplateAreas,
+ conditions);
+
+ // grid-column-start
+ SetGridLine(*aRuleData->ValueForGridColumnStart(),
+ pos->mGridColumnStart,
+ parentPos->mGridColumnStart,
+ conditions);
+
+ // grid-column-end
+ SetGridLine(*aRuleData->ValueForGridColumnEnd(),
+ pos->mGridColumnEnd,
+ parentPos->mGridColumnEnd,
+ conditions);
+
+ // grid-row-start
+ SetGridLine(*aRuleData->ValueForGridRowStart(),
+ pos->mGridRowStart,
+ parentPos->mGridRowStart,
+ conditions);
+
+ // grid-row-end
+ SetGridLine(*aRuleData->ValueForGridRowEnd(),
+ pos->mGridRowEnd,
+ parentPos->mGridRowEnd,
+ conditions);
+
+ // grid-column-gap
+ if (SetCoord(*aRuleData->ValueForGridColumnGap(),
+ pos->mGridColumnGap, parentPos->mGridColumnGap,
+ SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
+ SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ } else {
+ MOZ_ASSERT(aRuleData->ValueForGridColumnGap()->GetUnit() == eCSSUnit_Null,
+ "unexpected unit");
+ }
+
+ // grid-row-gap
+ if (SetCoord(*aRuleData->ValueForGridRowGap(),
+ pos->mGridRowGap, parentPos->mGridRowGap,
+ SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
+ SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions)) {
+ } else {
+ MOZ_ASSERT(aRuleData->ValueForGridRowGap()->GetUnit() == eCSSUnit_Null,
+ "unexpected unit");
+ }
+
+ // z-index
+ const nsCSSValue* zIndexValue = aRuleData->ValueForZIndex();
+ if (! SetCoord(*zIndexValue, pos->mZIndex, parentPos->mZIndex,
+ SETCOORD_IA | SETCOORD_INITIAL_AUTO | SETCOORD_UNSET_INITIAL,
+ aContext, nullptr, conditions)) {
+ if (eCSSUnit_Inherit == zIndexValue->GetUnit()) {
+ // handle inherit, because it's ok to inherit 'auto' here
+ conditions.SetUncacheable();
+ pos->mZIndex = parentPos->mZIndex;
+ }
+ }
+
+ COMPUTE_END_RESET(Position, pos)
+}
+
+const void*
+nsRuleNode::ComputeTableData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Table, table, parentTable)
+
+ // table-layout: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTableLayout(),
+ table->mLayoutStrategy, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentTable->mLayoutStrategy,
+ NS_STYLE_TABLE_LAYOUT_AUTO);
+
+ // span: pixels (not a real CSS prop)
+ const nsCSSValue* spanValue = aRuleData->ValueForSpan();
+ if (eCSSUnit_Enumerated == spanValue->GetUnit() ||
+ eCSSUnit_Integer == spanValue->GetUnit())
+ table->mSpan = spanValue->GetIntValue();
+
+ COMPUTE_END_RESET(Table, table)
+}
+
+const void*
+nsRuleNode::ComputeTableBorderData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_INHERITED(TableBorder, table, parentTable)
+
+ // border-collapse: enum, inherit, initial
+ SetValue(*aRuleData->ValueForBorderCollapse(), table->mBorderCollapse,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentTable->mBorderCollapse,
+ NS_STYLE_BORDER_SEPARATE);
+
+ const nsCSSValue* borderSpacingValue = aRuleData->ValueForBorderSpacing();
+ // border-spacing: pair(length), inherit
+ if (borderSpacingValue->GetUnit() != eCSSUnit_Null) {
+ nsStyleCoord parentCol(parentTable->mBorderSpacingCol,
+ nsStyleCoord::CoordConstructor);
+ nsStyleCoord parentRow(parentTable->mBorderSpacingRow,
+ nsStyleCoord::CoordConstructor);
+ nsStyleCoord coordCol, coordRow;
+
+#ifdef DEBUG
+ bool result =
+#endif
+ SetPairCoords(*borderSpacingValue,
+ coordCol, coordRow, parentCol, parentRow,
+ SETCOORD_LH | SETCOORD_INITIAL_ZERO |
+ SETCOORD_CALC_LENGTH_ONLY |
+ SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INHERIT,
+ aContext, mPresContext, conditions);
+ NS_ASSERTION(result, "malformed table border value");
+ table->mBorderSpacingCol = coordCol.GetCoordValue();
+ table->mBorderSpacingRow = coordRow.GetCoordValue();
+ }
+
+ // caption-side: enum, inherit, initial
+ SetValue(*aRuleData->ValueForCaptionSide(),
+ table->mCaptionSide, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentTable->mCaptionSide,
+ NS_STYLE_CAPTION_SIDE_TOP);
+
+ // empty-cells: enum, inherit, initial
+ SetValue(*aRuleData->ValueForEmptyCells(),
+ table->mEmptyCells, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentTable->mEmptyCells,
+ NS_STYLE_TABLE_EMPTY_CELLS_SHOW);
+
+ COMPUTE_END_INHERITED(TableBorder, table)
+}
+
+const void*
+nsRuleNode::ComputeContentData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ uint32_t count;
+ nsAutoString buffer;
+
+ COMPUTE_START_RESET(Content, content, parentContent)
+
+ // content: [string, url, counter, attr, enum]+, normal, none, inherit
+ const nsCSSValue* contentValue = aRuleData->ValueForContent();
+ switch (contentValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Normal:
+ case eCSSUnit_None:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ // "normal", "none", "initial" and "unset" all mean no content
+ content->AllocateContents(0);
+ break;
+
+ case eCSSUnit_Inherit:
+ conditions.SetUncacheable();
+ count = parentContent->ContentCount();
+ content->AllocateContents(count);
+ while (0 < count--) {
+ content->ContentAt(count) = parentContent->ContentAt(count);
+ }
+ break;
+
+ case eCSSUnit_Enumerated: {
+ MOZ_ASSERT(contentValue->GetIntValue() == NS_STYLE_CONTENT_ALT_CONTENT,
+ "unrecognized solitary content keyword");
+ content->AllocateContents(1);
+ nsStyleContentData& data = content->ContentAt(0);
+ data.mType = eStyleContentType_AltContent;
+ data.mContent.mString = nullptr;
+ break;
+ }
+
+ case eCSSUnit_List:
+ case eCSSUnit_ListDep: {
+ const nsCSSValueList* contentValueList = contentValue->GetListValue();
+ count = 0;
+ while (contentValueList) {
+ count++;
+ contentValueList = contentValueList->mNext;
+ }
+ content->AllocateContents(count);
+ const nsAutoString nullStr;
+ count = 0;
+ contentValueList = contentValue->GetListValue();
+ while (contentValueList) {
+ const nsCSSValue& value = contentValueList->mValue;
+ nsCSSUnit unit = value.GetUnit();
+ nsStyleContentType type;
+ nsStyleContentData &data = content->ContentAt(count++);
+ switch (unit) {
+ case eCSSUnit_String: type = eStyleContentType_String; break;
+ case eCSSUnit_Image: type = eStyleContentType_Image; break;
+ case eCSSUnit_Attr: type = eStyleContentType_Attr; break;
+ case eCSSUnit_Counter: type = eStyleContentType_Counter; break;
+ case eCSSUnit_Counters: type = eStyleContentType_Counters; break;
+ case eCSSUnit_Enumerated:
+ switch (value.GetIntValue()) {
+ case NS_STYLE_CONTENT_OPEN_QUOTE:
+ type = eStyleContentType_OpenQuote; break;
+ case NS_STYLE_CONTENT_CLOSE_QUOTE:
+ type = eStyleContentType_CloseQuote; break;
+ case NS_STYLE_CONTENT_NO_OPEN_QUOTE:
+ type = eStyleContentType_NoOpenQuote; break;
+ case NS_STYLE_CONTENT_NO_CLOSE_QUOTE:
+ type = eStyleContentType_NoCloseQuote; break;
+ default:
+ NS_ERROR("bad content value");
+ type = eStyleContentType_Uninitialized;
+ }
+ break;
+ default:
+ NS_ERROR("bad content type");
+ type = eStyleContentType_Uninitialized;
+ }
+ data.mType = type;
+ if (type == eStyleContentType_Image) {
+ SetImageRequest([&](imgRequestProxy* req) {
+ data.SetImage(req);
+ }, mPresContext, value);
+ } else if (type <= eStyleContentType_Attr) {
+ value.GetStringValue(buffer);
+ data.mContent.mString = NS_strdup(buffer.get());
+ } else if (type <= eStyleContentType_Counters) {
+ data.mContent.mCounters = value.GetArrayValue();
+ data.mContent.mCounters->AddRef();
+ } else {
+ data.mContent.mString = nullptr;
+ }
+ contentValueList = contentValueList->mNext;
+ }
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unrecognized content unit");
+ }
+
+ // counter-increment: [string [int]]+, none, inherit
+ const nsCSSValue* counterIncrementValue =
+ aRuleData->ValueForCounterIncrement();
+ switch (counterIncrementValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_None:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ content->AllocateCounterIncrements(0);
+ break;
+
+ case eCSSUnit_Inherit:
+ conditions.SetUncacheable();
+ count = parentContent->CounterIncrementCount();
+ content->AllocateCounterIncrements(count);
+ while (count--) {
+ const nsStyleCounterData& data = parentContent->CounterIncrementAt(count);
+ content->SetCounterIncrementAt(count, data.mCounter, data.mValue);
+ }
+ break;
+
+ case eCSSUnit_PairList:
+ case eCSSUnit_PairListDep: {
+ const nsCSSValuePairList* ourIncrement =
+ counterIncrementValue->GetPairListValue();
+ MOZ_ASSERT(ourIncrement->mXValue.GetUnit() == eCSSUnit_Ident,
+ "unexpected value unit");
+ count = ListLength(ourIncrement);
+ content->AllocateCounterIncrements(count);
+
+ count = 0;
+ for (const nsCSSValuePairList* p = ourIncrement; p; p = p->mNext, count++) {
+ int32_t increment;
+ if (p->mYValue.GetUnit() == eCSSUnit_Integer) {
+ increment = p->mYValue.GetIntValue();
+ } else {
+ increment = 1;
+ }
+ p->mXValue.GetStringValue(buffer);
+ content->SetCounterIncrementAt(count, buffer, increment);
+ }
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unexpected value unit");
+ }
+
+ // counter-reset: [string [int]]+, none, inherit
+ const nsCSSValue* counterResetValue = aRuleData->ValueForCounterReset();
+ switch (counterResetValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_None:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ content->AllocateCounterResets(0);
+ break;
+
+ case eCSSUnit_Inherit:
+ conditions.SetUncacheable();
+ count = parentContent->CounterResetCount();
+ content->AllocateCounterResets(count);
+ while (0 < count--) {
+ const nsStyleCounterData& data = parentContent->CounterResetAt(count);
+ content->SetCounterResetAt(count, data.mCounter, data.mValue);
+ }
+ break;
+
+ case eCSSUnit_PairList:
+ case eCSSUnit_PairListDep: {
+ const nsCSSValuePairList* ourReset =
+ counterResetValue->GetPairListValue();
+ MOZ_ASSERT(ourReset->mXValue.GetUnit() == eCSSUnit_Ident,
+ "unexpected value unit");
+ count = ListLength(ourReset);
+ content->AllocateCounterResets(count);
+ count = 0;
+ for (const nsCSSValuePairList* p = ourReset; p; p = p->mNext, count++) {
+ int32_t reset;
+ if (p->mYValue.GetUnit() == eCSSUnit_Integer) {
+ reset = p->mYValue.GetIntValue();
+ } else {
+ reset = 0;
+ }
+ p->mXValue.GetStringValue(buffer);
+ content->SetCounterResetAt(count, buffer, reset);
+ }
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unexpected value unit");
+ }
+
+ // If we ended up with an image, track it.
+ for (uint32_t i = 0; i < content->ContentCount(); ++i) {
+ if ((content->ContentAt(i).mType == eStyleContentType_Image) &&
+ content->ContentAt(i).mContent.mImage) {
+ content->ContentAt(i).TrackImage(
+ aContext->PresContext()->Document()->ImageTracker());
+ }
+ }
+
+ COMPUTE_END_RESET(Content, content)
+}
+
+const void*
+nsRuleNode::ComputeXULData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(XUL, xul, parentXUL)
+
+ // box-align: enum, inherit, initial
+ SetValue(*aRuleData->ValueForBoxAlign(),
+ xul->mBoxAlign, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentXUL->mBoxAlign,
+ StyleBoxAlign::Stretch);
+
+ // box-direction: enum, inherit, initial
+ SetValue(*aRuleData->ValueForBoxDirection(),
+ xul->mBoxDirection, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentXUL->mBoxDirection,
+ StyleBoxDirection::Normal);
+
+ // box-flex: factor, inherit
+ SetFactor(*aRuleData->ValueForBoxFlex(),
+ xul->mBoxFlex, conditions,
+ parentXUL->mBoxFlex, 0.0f,
+ SETFCT_UNSET_INITIAL);
+
+ // box-orient: enum, inherit, initial
+ SetValue(*aRuleData->ValueForBoxOrient(),
+ xul->mBoxOrient, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentXUL->mBoxOrient,
+ StyleBoxOrient::Horizontal);
+
+ // box-pack: enum, inherit, initial
+ SetValue(*aRuleData->ValueForBoxPack(),
+ xul->mBoxPack, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentXUL->mBoxPack,
+ StyleBoxPack::Start);
+
+ // box-ordinal-group: integer, inherit, initial
+ SetValue(*aRuleData->ValueForBoxOrdinalGroup(),
+ xul->mBoxOrdinal, conditions,
+ SETVAL_INTEGER | SETVAL_UNSET_INITIAL,
+ parentXUL->mBoxOrdinal, 1);
+
+ const nsCSSValue* stackSizingValue = aRuleData->ValueForStackSizing();
+ if (eCSSUnit_Inherit == stackSizingValue->GetUnit()) {
+ conditions.SetUncacheable();
+ xul->mStretchStack = parentXUL->mStretchStack;
+ } else if (eCSSUnit_Initial == stackSizingValue->GetUnit() ||
+ eCSSUnit_Unset == stackSizingValue->GetUnit()) {
+ xul->mStretchStack = true;
+ } else if (eCSSUnit_Enumerated == stackSizingValue->GetUnit()) {
+ xul->mStretchStack = stackSizingValue->GetIntValue() ==
+ NS_STYLE_STACK_SIZING_STRETCH_TO_FIT;
+ }
+
+ COMPUTE_END_RESET(XUL, xul)
+}
+
+const void*
+nsRuleNode::ComputeColumnData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Column, column, parent)
+
+ // column-width: length, auto, inherit
+ SetCoord(*aRuleData->ValueForColumnWidth(),
+ column->mColumnWidth, parent->mColumnWidth,
+ SETCOORD_LAH | SETCOORD_INITIAL_AUTO |
+ SETCOORD_CALC_LENGTH_ONLY | SETCOORD_CALC_CLAMP_NONNEGATIVE |
+ SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+
+ // column-gap: length, inherit, normal
+ SetCoord(*aRuleData->ValueForColumnGap(),
+ column->mColumnGap, parent->mColumnGap,
+ SETCOORD_LH | SETCOORD_NORMAL | SETCOORD_INITIAL_NORMAL |
+ SETCOORD_CALC_LENGTH_ONLY | SETCOORD_UNSET_INITIAL,
+ aContext, mPresContext, conditions);
+ // clamp negative calc() to 0
+ if (column->mColumnGap.GetUnit() == eStyleUnit_Coord) {
+ column->mColumnGap.SetCoordValue(
+ std::max(column->mColumnGap.GetCoordValue(), 0));
+ }
+
+ // column-count: auto, integer, inherit
+ const nsCSSValue* columnCountValue = aRuleData->ValueForColumnCount();
+ if (eCSSUnit_Auto == columnCountValue->GetUnit() ||
+ eCSSUnit_Initial == columnCountValue->GetUnit() ||
+ eCSSUnit_Unset == columnCountValue->GetUnit()) {
+ column->mColumnCount = NS_STYLE_COLUMN_COUNT_AUTO;
+ } else if (eCSSUnit_Integer == columnCountValue->GetUnit()) {
+ column->mColumnCount = columnCountValue->GetIntValue();
+ // Max kMaxColumnCount columns - wallpaper for bug 345583.
+ column->mColumnCount = std::min(column->mColumnCount,
+ nsStyleColumn::kMaxColumnCount);
+ } else if (eCSSUnit_Inherit == columnCountValue->GetUnit()) {
+ conditions.SetUncacheable();
+ column->mColumnCount = parent->mColumnCount;
+ }
+
+ // column-rule-width: length, enum, inherit
+ const nsCSSValue& widthValue = *aRuleData->ValueForColumnRuleWidth();
+ if (eCSSUnit_Initial == widthValue.GetUnit() ||
+ eCSSUnit_Unset == widthValue.GetUnit()) {
+ column->SetColumnRuleWidth(
+ (mPresContext->GetBorderWidthTable())[NS_STYLE_BORDER_WIDTH_MEDIUM]);
+ }
+ else if (eCSSUnit_Enumerated == widthValue.GetUnit()) {
+ NS_ASSERTION(widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
+ widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
+ widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
+ "Unexpected enum value");
+ column->SetColumnRuleWidth(
+ (mPresContext->GetBorderWidthTable())[widthValue.GetIntValue()]);
+ }
+ else if (eCSSUnit_Inherit == widthValue.GetUnit()) {
+ column->SetColumnRuleWidth(parent->GetComputedColumnRuleWidth());
+ conditions.SetUncacheable();
+ }
+ else if (widthValue.IsLengthUnit() || widthValue.IsCalcUnit()) {
+ nscoord len =
+ CalcLength(widthValue, aContext, mPresContext, conditions);
+ if (len < 0) {
+ // FIXME: This is untested (by test_value_storage.html) for
+ // column-rule-width since it gets covered up by the border
+ // rounding code.
+ NS_ASSERTION(widthValue.IsCalcUnit(),
+ "parser should have rejected negative length");
+ len = 0;
+ }
+ column->SetColumnRuleWidth(len);
+ }
+
+ // column-rule-style: enum, inherit
+ const nsCSSValue& styleValue = *aRuleData->ValueForColumnRuleStyle();
+ MOZ_ASSERT(eCSSUnit_None != styleValue.GetUnit(),
+ "'none' should be handled as enumerated value");
+ if (eCSSUnit_Enumerated == styleValue.GetUnit()) {
+ column->mColumnRuleStyle = styleValue.GetIntValue();
+ }
+ else if (eCSSUnit_Initial == styleValue.GetUnit() ||
+ eCSSUnit_Unset == styleValue.GetUnit()) {
+ column->mColumnRuleStyle = NS_STYLE_BORDER_STYLE_NONE;
+ }
+ else if (eCSSUnit_Inherit == styleValue.GetUnit()) {
+ conditions.SetUncacheable();
+ column->mColumnRuleStyle = parent->mColumnRuleStyle;
+ }
+
+ // column-rule-color: color, inherit
+ SetComplexColor<eUnsetInitial>(*aRuleData->ValueForColumnRuleColor(),
+ parent->mColumnRuleColor,
+ StyleComplexColor::CurrentColor(),
+ mPresContext,
+ column->mColumnRuleColor, conditions);
+
+ // column-fill: enum
+ SetValue(*aRuleData->ValueForColumnFill(),
+ column->mColumnFill, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parent->mColumnFill,
+ NS_STYLE_COLUMN_FILL_BALANCE);
+
+ COMPUTE_END_RESET(Column, column)
+}
+
+static void
+SetSVGPaint(const nsCSSValue& aValue, const nsStyleSVGPaint& parentPaint,
+ nsPresContext* aPresContext, nsStyleContext *aContext,
+ nsStyleSVGPaint& aResult, nsStyleSVGPaintType aInitialPaintType,
+ RuleNodeCacheConditions& aConditions)
+{
+ MOZ_ASSERT(aInitialPaintType == eStyleSVGPaintType_None ||
+ aInitialPaintType == eStyleSVGPaintType_Color,
+ "SetSVGPaint only supports initial values being either 'black' "
+ "(represented by eStyleSVGPaintType_Color) or none (by "
+ "eStyleSVGPaintType_None)");
+
+ nscolor color;
+
+ if (aValue.GetUnit() == eCSSUnit_Inherit ||
+ aValue.GetUnit() == eCSSUnit_Unset) {
+ aResult = parentPaint;
+ aConditions.SetUncacheable();
+ } else if (aValue.GetUnit() == eCSSUnit_None) {
+ aResult.SetNone();
+ } else if (aValue.GetUnit() == eCSSUnit_Initial) {
+ if (aInitialPaintType == eStyleSVGPaintType_None) {
+ aResult.SetNone();
+ } else {
+ aResult.SetColor(NS_RGB(0, 0, 0));
+ }
+ } else if (SetColor(aValue, NS_RGB(0, 0, 0), aPresContext, aContext,
+ color, aConditions)) {
+ aResult.SetColor(color);
+ } else if (aValue.GetUnit() == eCSSUnit_Pair) {
+ const nsCSSValuePair& pair = aValue.GetPairValue();
+
+ nscolor fallback;
+ if (pair.mYValue.GetUnit() == eCSSUnit_None) {
+ fallback = NS_RGBA(0, 0, 0, 0);
+ } else {
+ MOZ_ASSERT(pair.mYValue.GetUnit() != eCSSUnit_Inherit,
+ "cannot inherit fallback colour");
+ SetColor(pair.mYValue, NS_RGB(0, 0, 0), aPresContext, aContext,
+ fallback, aConditions);
+ }
+
+ if (pair.mXValue.GetUnit() == eCSSUnit_URL) {
+ aResult.SetPaintServer(pair.mXValue.GetURLStructValue(), fallback);
+ } else if (pair.mXValue.GetUnit() == eCSSUnit_Enumerated) {
+
+ switch (pair.mXValue.GetIntValue()) {
+ case NS_COLOR_CONTEXT_FILL:
+ aResult.SetContextValue(eStyleSVGPaintType_ContextFill, fallback);
+ break;
+ case NS_COLOR_CONTEXT_STROKE:
+ aResult.SetContextValue(eStyleSVGPaintType_ContextStroke, fallback);
+ break;
+ default:
+ NS_NOTREACHED("unknown keyword as paint server value");
+ }
+
+ } else {
+ NS_NOTREACHED("malformed paint server value");
+ }
+
+ } else {
+ MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null,
+ "malformed paint server value");
+ }
+}
+
+static void
+SetSVGOpacity(const nsCSSValue& aValue,
+ float& aOpacityField, nsStyleSVGOpacitySource& aOpacityTypeField,
+ RuleNodeCacheConditions& aConditions,
+ float aParentOpacity, nsStyleSVGOpacitySource aParentOpacityType)
+{
+ if (eCSSUnit_Enumerated == aValue.GetUnit()) {
+ switch (aValue.GetIntValue()) {
+ case NS_STYLE_CONTEXT_FILL_OPACITY:
+ aOpacityTypeField = eStyleSVGOpacitySource_ContextFillOpacity;
+ break;
+ case NS_STYLE_CONTEXT_STROKE_OPACITY:
+ aOpacityTypeField = eStyleSVGOpacitySource_ContextStrokeOpacity;
+ break;
+ default:
+ NS_NOTREACHED("SetSVGOpacity: Unknown keyword");
+ }
+ // Fall back on fully opaque
+ aOpacityField = 1.0f;
+ } else if (eCSSUnit_Inherit == aValue.GetUnit() ||
+ eCSSUnit_Unset == aValue.GetUnit()) {
+ aConditions.SetUncacheable();
+ aOpacityField = aParentOpacity;
+ aOpacityTypeField = aParentOpacityType;
+ } else if (eCSSUnit_Null != aValue.GetUnit()) {
+ SetFactor(aValue, aOpacityField, aConditions,
+ aParentOpacity, 1.0f, SETFCT_OPACITY);
+ aOpacityTypeField = eStyleSVGOpacitySource_Normal;
+ }
+}
+
+/* static */
+void
+nsRuleNode::FillAllMaskLists(nsStyleImageLayers& aMask,
+ uint32_t aMaxItemCount)
+{
+
+ // Delete any extra items. We need to keep layers in which any
+ // property was specified.
+ aMask.mLayers.TruncateLengthNonZero(aMaxItemCount);
+
+ uint32_t fillCount = aMask.mImageCount;
+
+ FillImageLayerList(aMask.mLayers,
+ &nsStyleImageLayers::Layer::mImage,
+ aMask.mImageCount, fillCount);
+ FillImageLayerList(aMask.mLayers,
+ &nsStyleImageLayers::Layer::mSourceURI,
+ aMask.mImageCount, fillCount);
+ FillImageLayerList(aMask.mLayers,
+ &nsStyleImageLayers::Layer::mRepeat,
+ aMask.mRepeatCount, fillCount);
+ FillImageLayerList(aMask.mLayers,
+ &nsStyleImageLayers::Layer::mClip,
+ aMask.mClipCount, fillCount);
+ FillImageLayerList(aMask.mLayers,
+ &nsStyleImageLayers::Layer::mOrigin,
+ aMask.mOriginCount, fillCount);
+ FillImageLayerPositionCoordList(aMask.mLayers,
+ &Position::mXPosition,
+ aMask.mPositionXCount, fillCount);
+ FillImageLayerPositionCoordList(aMask.mLayers,
+ &Position::mYPosition,
+ aMask.mPositionYCount, fillCount);
+ FillImageLayerList(aMask.mLayers,
+ &nsStyleImageLayers::Layer::mSize,
+ aMask.mSizeCount, fillCount);
+ FillImageLayerList(aMask.mLayers,
+ &nsStyleImageLayers::Layer::mMaskMode,
+ aMask.mMaskModeCount, fillCount);
+ FillImageLayerList(aMask.mLayers,
+ &nsStyleImageLayers::Layer::mComposite,
+ aMask.mCompositeCount, fillCount);
+}
+
+const void*
+nsRuleNode::ComputeSVGData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_INHERITED(SVG, svg, parentSVG)
+
+ // clip-rule: enum, inherit, initial
+ SetValue(*aRuleData->ValueForClipRule(),
+ svg->mClipRule, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentSVG->mClipRule,
+ StyleFillRule::Nonzero);
+
+ // color-interpolation: enum, inherit, initial
+ SetValue(*aRuleData->ValueForColorInterpolation(),
+ svg->mColorInterpolation, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentSVG->mColorInterpolation,
+ NS_STYLE_COLOR_INTERPOLATION_SRGB);
+
+ // color-interpolation-filters: enum, inherit, initial
+ SetValue(*aRuleData->ValueForColorInterpolationFilters(),
+ svg->mColorInterpolationFilters, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentSVG->mColorInterpolationFilters,
+ NS_STYLE_COLOR_INTERPOLATION_LINEARRGB);
+
+ // fill:
+ SetSVGPaint(*aRuleData->ValueForFill(),
+ parentSVG->mFill, mPresContext, aContext,
+ svg->mFill, eStyleSVGPaintType_Color, conditions);
+
+ // fill-opacity: factor, inherit, initial,
+ // context-fill-opacity, context-stroke-opacity
+ nsStyleSVGOpacitySource contextFillOpacity = svg->FillOpacitySource();
+ SetSVGOpacity(*aRuleData->ValueForFillOpacity(),
+ svg->mFillOpacity, contextFillOpacity, conditions,
+ parentSVG->mFillOpacity, parentSVG->FillOpacitySource());
+ svg->SetFillOpacitySource(contextFillOpacity);
+
+ // fill-rule: enum, inherit, initial
+ SetValue(*aRuleData->ValueForFillRule(),
+ svg->mFillRule, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentSVG->mFillRule,
+ StyleFillRule::Nonzero);
+
+ // marker-end: url, none, inherit
+ const nsCSSValue* markerEndValue = aRuleData->ValueForMarkerEnd();
+ if (eCSSUnit_URL == markerEndValue->GetUnit()) {
+ svg->mMarkerEnd = markerEndValue->GetURLStructValue();
+ } else if (eCSSUnit_None == markerEndValue->GetUnit() ||
+ eCSSUnit_Initial == markerEndValue->GetUnit()) {
+ svg->mMarkerEnd = nullptr;
+ } else if (eCSSUnit_Inherit == markerEndValue->GetUnit() ||
+ eCSSUnit_Unset == markerEndValue->GetUnit()) {
+ conditions.SetUncacheable();
+ svg->mMarkerEnd = parentSVG->mMarkerEnd;
+ }
+
+ // marker-mid: url, none, inherit
+ const nsCSSValue* markerMidValue = aRuleData->ValueForMarkerMid();
+ if (eCSSUnit_URL == markerMidValue->GetUnit()) {
+ svg->mMarkerMid = markerMidValue->GetURLStructValue();
+ } else if (eCSSUnit_None == markerMidValue->GetUnit() ||
+ eCSSUnit_Initial == markerMidValue->GetUnit()) {
+ svg->mMarkerMid = nullptr;
+ } else if (eCSSUnit_Inherit == markerMidValue->GetUnit() ||
+ eCSSUnit_Unset == markerMidValue->GetUnit()) {
+ conditions.SetUncacheable();
+ svg->mMarkerMid = parentSVG->mMarkerMid;
+ }
+
+ // marker-start: url, none, inherit
+ const nsCSSValue* markerStartValue = aRuleData->ValueForMarkerStart();
+ if (eCSSUnit_URL == markerStartValue->GetUnit()) {
+ svg->mMarkerStart = markerStartValue->GetURLStructValue();
+ } else if (eCSSUnit_None == markerStartValue->GetUnit() ||
+ eCSSUnit_Initial == markerStartValue->GetUnit()) {
+ svg->mMarkerStart = nullptr;
+ } else if (eCSSUnit_Inherit == markerStartValue->GetUnit() ||
+ eCSSUnit_Unset == markerStartValue->GetUnit()) {
+ conditions.SetUncacheable();
+ svg->mMarkerStart = parentSVG->mMarkerStart;
+ }
+
+ // paint-order: enum (bit field), inherit, initial
+ const nsCSSValue* paintOrderValue = aRuleData->ValueForPaintOrder();
+ switch (paintOrderValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Enumerated:
+ static_assert
+ (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
+ "SVGStyleStruct::mPaintOrder not big enough");
+ svg->mPaintOrder = static_cast<uint8_t>(paintOrderValue->GetIntValue());
+ break;
+
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Unset:
+ conditions.SetUncacheable();
+ svg->mPaintOrder = parentSVG->mPaintOrder;
+ break;
+
+ case eCSSUnit_Initial:
+ svg->mPaintOrder = NS_STYLE_PAINT_ORDER_NORMAL;
+ break;
+
+ default:
+ NS_NOTREACHED("unexpected unit");
+ }
+
+ // shape-rendering: enum, inherit
+ SetValue(*aRuleData->ValueForShapeRendering(),
+ svg->mShapeRendering, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentSVG->mShapeRendering,
+ NS_STYLE_SHAPE_RENDERING_AUTO);
+
+ // stroke:
+ SetSVGPaint(*aRuleData->ValueForStroke(),
+ parentSVG->mStroke, mPresContext, aContext,
+ svg->mStroke, eStyleSVGPaintType_None, conditions);
+
+ // stroke-dasharray: <dasharray>, none, inherit, context-value
+ const nsCSSValue* strokeDasharrayValue = aRuleData->ValueForStrokeDasharray();
+ switch (strokeDasharrayValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Inherit:
+ case eCSSUnit_Unset:
+ conditions.SetUncacheable();
+ svg->SetStrokeDasharrayFromObject(parentSVG->StrokeDasharrayFromObject());
+ svg->mStrokeDasharray = parentSVG->mStrokeDasharray;
+ break;
+
+ case eCSSUnit_Enumerated:
+ MOZ_ASSERT(strokeDasharrayValue->GetIntValue() ==
+ NS_STYLE_STROKE_PROP_CONTEXT_VALUE,
+ "Unknown keyword for stroke-dasharray");
+ svg->SetStrokeDasharrayFromObject(true);
+ svg->mStrokeDasharray.Clear();
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_None:
+ svg->SetStrokeDasharrayFromObject(false);
+ svg->mStrokeDasharray.Clear();
+ break;
+
+ case eCSSUnit_List:
+ case eCSSUnit_ListDep: {
+ svg->SetStrokeDasharrayFromObject(false);
+ svg->mStrokeDasharray.Clear();
+
+ // count number of values
+ const nsCSSValueList *value = strokeDasharrayValue->GetListValue();
+ uint32_t strokeDasharrayLength = ListLength(value);
+
+ MOZ_ASSERT(strokeDasharrayLength != 0, "no dasharray items");
+
+ svg->mStrokeDasharray.SetLength(strokeDasharrayLength);
+
+ uint32_t i = 0;
+ while (nullptr != value) {
+ SetCoord(value->mValue,
+ svg->mStrokeDasharray[i++], nsStyleCoord(),
+ SETCOORD_LP | SETCOORD_FACTOR,
+ aContext, mPresContext, conditions);
+ value = value->mNext;
+ }
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unrecognized dasharray unit");
+ }
+
+ // stroke-dashoffset: <dashoffset>, inherit
+ const nsCSSValue *strokeDashoffsetValue =
+ aRuleData->ValueForStrokeDashoffset();
+ svg->SetStrokeDashoffsetFromObject(
+ strokeDashoffsetValue->GetUnit() == eCSSUnit_Enumerated &&
+ strokeDashoffsetValue->GetIntValue() == NS_STYLE_STROKE_PROP_CONTEXT_VALUE);
+ if (svg->StrokeDashoffsetFromObject()) {
+ svg->mStrokeDashoffset.SetCoordValue(0);
+ } else {
+ SetCoord(*aRuleData->ValueForStrokeDashoffset(),
+ svg->mStrokeDashoffset, parentSVG->mStrokeDashoffset,
+ SETCOORD_LPH | SETCOORD_FACTOR | SETCOORD_INITIAL_ZERO |
+ SETCOORD_UNSET_INHERIT,
+ aContext, mPresContext, conditions);
+ }
+
+ // stroke-linecap: enum, inherit, initial
+ SetValue(*aRuleData->ValueForStrokeLinecap(),
+ svg->mStrokeLinecap, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentSVG->mStrokeLinecap,
+ NS_STYLE_STROKE_LINECAP_BUTT);
+
+ // stroke-linejoin: enum, inherit, initial
+ SetValue(*aRuleData->ValueForStrokeLinejoin(),
+ svg->mStrokeLinejoin, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentSVG->mStrokeLinejoin,
+ NS_STYLE_STROKE_LINEJOIN_MITER);
+
+ // stroke-miterlimit: <miterlimit>, inherit
+ SetFactor(*aRuleData->ValueForStrokeMiterlimit(),
+ svg->mStrokeMiterlimit,
+ conditions,
+ parentSVG->mStrokeMiterlimit, 4.0f,
+ SETFCT_UNSET_INHERIT);
+
+ // stroke-opacity:
+ nsStyleSVGOpacitySource contextStrokeOpacity = svg->StrokeOpacitySource();
+ SetSVGOpacity(*aRuleData->ValueForStrokeOpacity(),
+ svg->mStrokeOpacity, contextStrokeOpacity, conditions,
+ parentSVG->mStrokeOpacity, parentSVG->StrokeOpacitySource());
+ svg->SetStrokeOpacitySource(contextStrokeOpacity);
+
+ // stroke-width:
+ const nsCSSValue* strokeWidthValue = aRuleData->ValueForStrokeWidth();
+ switch (strokeWidthValue->GetUnit()) {
+ case eCSSUnit_Enumerated:
+ MOZ_ASSERT(strokeWidthValue->GetIntValue() ==
+ NS_STYLE_STROKE_PROP_CONTEXT_VALUE,
+ "Unrecognized keyword for stroke-width");
+ svg->SetStrokeWidthFromObject(true);
+ svg->mStrokeWidth.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(1));
+ break;
+
+ case eCSSUnit_Initial:
+ svg->SetStrokeWidthFromObject(false);
+ svg->mStrokeWidth.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(1));
+ break;
+
+ default:
+ svg->SetStrokeWidthFromObject(false);
+ SetCoord(*strokeWidthValue,
+ svg->mStrokeWidth, parentSVG->mStrokeWidth,
+ SETCOORD_LPH | SETCOORD_FACTOR | SETCOORD_UNSET_INHERIT,
+ aContext, mPresContext, conditions);
+ }
+
+ // text-anchor: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTextAnchor(),
+ svg->mTextAnchor, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentSVG->mTextAnchor,
+ NS_STYLE_TEXT_ANCHOR_START);
+
+ COMPUTE_END_INHERITED(SVG, svg)
+}
+
+static already_AddRefed<StyleBasicShape>
+GetStyleBasicShapeFromCSSValue(const nsCSSValue& aValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ RefPtr<StyleBasicShape> basicShape;
+
+ nsCSSValue::Array* shapeFunction = aValue.GetArrayValue();
+ nsCSSKeyword functionName =
+ (nsCSSKeyword)shapeFunction->Item(0).GetIntValue();
+
+ if (functionName == eCSSKeyword_polygon) {
+ MOZ_ASSERT(!basicShape, "did not expect value");
+ basicShape = new StyleBasicShape(StyleBasicShapeType::Polygon);
+ MOZ_ASSERT(shapeFunction->Count() > 1,
+ "polygon has wrong number of arguments");
+ size_t j = 1;
+ if (shapeFunction->Item(j).GetUnit() == eCSSUnit_Enumerated) {
+ StyleFillRule rule;
+ SetEnumValueHelper::SetEnumeratedValue(rule, shapeFunction->Item(j));
+ basicShape->SetFillRule(rule);
+ ++j;
+ }
+ const int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH |
+ SETCOORD_STORE_CALC;
+ const nsCSSValuePairList* curPair =
+ shapeFunction->Item(j).GetPairListValue();
+ nsTArray<nsStyleCoord>& coordinates = basicShape->Coordinates();
+ while (curPair) {
+ nsStyleCoord xCoord, yCoord;
+ DebugOnly<bool> didSetCoordX = SetCoord(curPair->mXValue, xCoord,
+ nsStyleCoord(), mask,
+ aStyleContext, aPresContext,
+ aConditions);
+ coordinates.AppendElement(xCoord);
+ MOZ_ASSERT(didSetCoordX, "unexpected x coordinate unit");
+ DebugOnly<bool> didSetCoordY = SetCoord(curPair->mYValue, yCoord,
+ nsStyleCoord(), mask,
+ aStyleContext, aPresContext,
+ aConditions);
+ coordinates.AppendElement(yCoord);
+ MOZ_ASSERT(didSetCoordY, "unexpected y coordinate unit");
+ curPair = curPair->mNext;
+ }
+ } else if (functionName == eCSSKeyword_circle ||
+ functionName == eCSSKeyword_ellipse) {
+ StyleBasicShapeType type = functionName == eCSSKeyword_circle ?
+ StyleBasicShapeType::Circle :
+ StyleBasicShapeType::Ellipse;
+ MOZ_ASSERT(!basicShape, "did not expect value");
+ basicShape = new StyleBasicShape(type);
+ const int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH |
+ SETCOORD_STORE_CALC | SETCOORD_ENUMERATED;
+ size_t count = type == StyleBasicShapeType::Circle ? 2 : 3;
+ MOZ_ASSERT(shapeFunction->Count() == count + 1,
+ "unexpected arguments count");
+ MOZ_ASSERT(type == StyleBasicShapeType::Circle ||
+ (shapeFunction->Item(1).GetUnit() == eCSSUnit_Null) ==
+ (shapeFunction->Item(2).GetUnit() == eCSSUnit_Null),
+ "ellipse should have two radii or none");
+ for (size_t j = 1; j < count; ++j) {
+ const nsCSSValue& val = shapeFunction->Item(j);
+ nsStyleCoord radius;
+ if (val.GetUnit() != eCSSUnit_Null) {
+ DebugOnly<bool> didSetRadius = SetCoord(val, radius,
+ nsStyleCoord(), mask,
+ aStyleContext,
+ aPresContext,
+ aConditions);
+ MOZ_ASSERT(didSetRadius, "unexpected radius unit");
+ } else {
+ radius.SetIntValue(NS_RADIUS_CLOSEST_SIDE, eStyleUnit_Enumerated);
+ }
+ basicShape->Coordinates().AppendElement(radius);
+ }
+ const nsCSSValue& positionVal = shapeFunction->Item(count);
+ if (positionVal.GetUnit() == eCSSUnit_Array) {
+ ComputePositionValue(aStyleContext, positionVal,
+ basicShape->GetPosition(),
+ aConditions);
+ } else {
+ MOZ_ASSERT(positionVal.GetUnit() == eCSSUnit_Null,
+ "expected no value");
+ }
+ } else if (functionName == eCSSKeyword_inset) {
+ MOZ_ASSERT(!basicShape, "did not expect value");
+ basicShape = new StyleBasicShape(StyleBasicShapeType::Inset);
+ MOZ_ASSERT(shapeFunction->Count() == 6,
+ "inset function has wrong number of arguments");
+ MOZ_ASSERT(shapeFunction->Item(1).GetUnit() != eCSSUnit_Null,
+ "no shape arguments defined");
+ const int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH |
+ SETCOORD_STORE_CALC;
+ nsTArray<nsStyleCoord>& coords = basicShape->Coordinates();
+ for (size_t j = 1; j <= 4; ++j) {
+ const nsCSSValue& val = shapeFunction->Item(j);
+ nsStyleCoord inset;
+ // Fill missing values to get 4 at the end.
+ if (val.GetUnit() == eCSSUnit_Null) {
+ if (j == 4) {
+ inset = coords[1];
+ } else {
+ MOZ_ASSERT(j != 1, "first argument not specified");
+ inset = coords[0];
+ }
+ } else {
+ DebugOnly<bool> didSetInset = SetCoord(val, inset,
+ nsStyleCoord(), mask,
+ aStyleContext, aPresContext,
+ aConditions);
+ MOZ_ASSERT(didSetInset, "unexpected inset unit");
+ }
+ coords.AppendElement(inset);
+ }
+
+ nsStyleCorners& insetRadius = basicShape->GetRadius();
+ if (shapeFunction->Item(5).GetUnit() == eCSSUnit_Array) {
+ nsCSSValue::Array* radiiArray = shapeFunction->Item(5).GetArrayValue();
+ NS_FOR_CSS_FULL_CORNERS(corner) {
+ int cx = NS_FULL_TO_HALF_CORNER(corner, false);
+ int cy = NS_FULL_TO_HALF_CORNER(corner, true);
+ const nsCSSValue& radius = radiiArray->Item(corner);
+ nsStyleCoord coordX, coordY;
+ DebugOnly<bool> didSetRadii = SetPairCoords(radius, coordX, coordY,
+ nsStyleCoord(),
+ nsStyleCoord(), mask,
+ aStyleContext,
+ aPresContext,
+ aConditions);
+ MOZ_ASSERT(didSetRadii, "unexpected radius unit");
+ insetRadius.Set(cx, coordX);
+ insetRadius.Set(cy, coordY);
+ }
+ } else {
+ MOZ_ASSERT(shapeFunction->Item(5).GetUnit() == eCSSUnit_Null,
+ "unexpected value");
+ // Initialize border-radius
+ nsStyleCoord zero;
+ zero.SetCoordValue(0);
+ NS_FOR_CSS_HALF_CORNERS(j) {
+ insetRadius.Set(j, zero);
+ }
+ }
+ } else {
+ NS_NOTREACHED("unexpected basic shape function");
+ }
+
+ return basicShape.forget();
+}
+
+template<typename ReferenceBox>
+static void
+SetStyleShapeSourceToCSSValue(
+ StyleShapeSource<ReferenceBox>* aShapeSource,
+ const nsCSSValue* aValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Array,
+ "expected a basic shape or reference box");
+
+ const nsCSSValue::Array* array = aValue->GetArrayValue();
+ MOZ_ASSERT(array->Count() == 1 || array->Count() == 2,
+ "Expect one or both of a shape function and a reference box");
+
+ ReferenceBox referenceBox = ReferenceBox::NoBox;
+ RefPtr<StyleBasicShape> basicShape;
+
+ for (size_t i = 0; i < array->Count(); ++i) {
+ const nsCSSValue& item = array->Item(i);
+ if (item.GetUnit() == eCSSUnit_Enumerated) {
+ referenceBox = static_cast<ReferenceBox>(item.GetIntValue());
+ } else if (item.GetUnit() == eCSSUnit_Function) {
+ basicShape = GetStyleBasicShapeFromCSSValue(item, aStyleContext,
+ aPresContext, aConditions);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected unit!");
+ return;
+ }
+ }
+
+ if (basicShape) {
+ aShapeSource->SetBasicShape(basicShape, referenceBox);
+ } else {
+ aShapeSource->SetReferenceBox(referenceBox);
+ }
+}
+
+// Returns true if the nsStyleFilter was successfully set using the nsCSSValue.
+static bool
+SetStyleFilterToCSSValue(nsStyleFilter* aStyleFilter,
+ const nsCSSValue& aValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ nsCSSUnit unit = aValue.GetUnit();
+ if (unit == eCSSUnit_URL) {
+ return aStyleFilter->SetURL(aValue.GetURLStructValue());
+ }
+
+ MOZ_ASSERT(unit == eCSSUnit_Function, "expected a filter function");
+
+ nsCSSValue::Array* filterFunction = aValue.GetArrayValue();
+ nsCSSKeyword functionName =
+ (nsCSSKeyword)filterFunction->Item(0).GetIntValue();
+
+ int32_t type;
+ DebugOnly<bool> foundKeyword =
+ nsCSSProps::FindKeyword(functionName,
+ nsCSSProps::kFilterFunctionKTable,
+ type);
+ MOZ_ASSERT(foundKeyword, "unknown filter type");
+ if (type == NS_STYLE_FILTER_DROP_SHADOW) {
+ RefPtr<nsCSSShadowArray> shadowArray = GetShadowData(
+ filterFunction->Item(1).GetListValue(),
+ aStyleContext,
+ false,
+ aPresContext,
+ aConditions);
+ aStyleFilter->SetDropShadow(shadowArray);
+ return true;
+ }
+
+ int32_t mask = SETCOORD_PERCENT | SETCOORD_FACTOR;
+ if (type == NS_STYLE_FILTER_BLUR) {
+ mask = SETCOORD_LENGTH |
+ SETCOORD_CALC_LENGTH_ONLY |
+ SETCOORD_CALC_CLAMP_NONNEGATIVE;
+ } else if (type == NS_STYLE_FILTER_HUE_ROTATE) {
+ mask = SETCOORD_ANGLE;
+ }
+
+ MOZ_ASSERT(filterFunction->Count() == 2,
+ "all filter functions should have exactly one argument");
+
+ nsCSSValue& arg = filterFunction->Item(1);
+ nsStyleCoord filterParameter;
+ DebugOnly<bool> didSetCoord = SetCoord(arg, filterParameter,
+ nsStyleCoord(), mask,
+ aStyleContext, aPresContext,
+ aConditions);
+ aStyleFilter->SetFilterParameter(filterParameter, type);
+ MOZ_ASSERT(didSetCoord, "unexpected unit");
+ return true;
+}
+
+const void*
+nsRuleNode::ComputeSVGResetData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(SVGReset, svgReset, parentSVGReset)
+
+ // stop-color:
+ const nsCSSValue* stopColorValue = aRuleData->ValueForStopColor();
+ if (eCSSUnit_Initial == stopColorValue->GetUnit() ||
+ eCSSUnit_Unset == stopColorValue->GetUnit()) {
+ svgReset->mStopColor = NS_RGB(0, 0, 0);
+ } else {
+ SetColor(*stopColorValue, parentSVGReset->mStopColor,
+ mPresContext, aContext, svgReset->mStopColor, conditions);
+ }
+
+ // flood-color:
+ const nsCSSValue* floodColorValue = aRuleData->ValueForFloodColor();
+ if (eCSSUnit_Initial == floodColorValue->GetUnit() ||
+ eCSSUnit_Unset == floodColorValue->GetUnit()) {
+ svgReset->mFloodColor = NS_RGB(0, 0, 0);
+ } else {
+ SetColor(*floodColorValue, parentSVGReset->mFloodColor,
+ mPresContext, aContext, svgReset->mFloodColor, conditions);
+ }
+
+ // lighting-color:
+ const nsCSSValue* lightingColorValue = aRuleData->ValueForLightingColor();
+ if (eCSSUnit_Initial == lightingColorValue->GetUnit() ||
+ eCSSUnit_Unset == lightingColorValue->GetUnit()) {
+ svgReset->mLightingColor = NS_RGB(255, 255, 255);
+ } else {
+ SetColor(*lightingColorValue, parentSVGReset->mLightingColor,
+ mPresContext, aContext, svgReset->mLightingColor,
+ conditions);
+ }
+
+ // clip-path: url, <basic-shape> || <geometry-box>, none, inherit
+ const nsCSSValue* clipPathValue = aRuleData->ValueForClipPath();
+ switch (clipPathValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_None:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ svgReset->mClipPath = StyleClipPath();
+ break;
+ case eCSSUnit_Inherit:
+ conditions.SetUncacheable();
+ svgReset->mClipPath = parentSVGReset->mClipPath;
+ break;
+ case eCSSUnit_URL: {
+ svgReset->mClipPath = StyleClipPath();
+ svgReset->mClipPath.SetURL(clipPathValue->GetURLStructValue());
+ break;
+ }
+ case eCSSUnit_Array: {
+ svgReset->mClipPath = StyleClipPath();
+ SetStyleShapeSourceToCSSValue(&svgReset->mClipPath, clipPathValue, aContext,
+ mPresContext, conditions);
+ break;
+ }
+ default:
+ NS_NOTREACHED("unexpected unit");
+ }
+
+ // stop-opacity:
+ SetFactor(*aRuleData->ValueForStopOpacity(),
+ svgReset->mStopOpacity, conditions,
+ parentSVGReset->mStopOpacity, 1.0f,
+ SETFCT_OPACITY | SETFCT_UNSET_INITIAL);
+
+ // flood-opacity:
+ SetFactor(*aRuleData->ValueForFloodOpacity(),
+ svgReset->mFloodOpacity, conditions,
+ parentSVGReset->mFloodOpacity, 1.0f,
+ SETFCT_OPACITY | SETFCT_UNSET_INITIAL);
+
+ // dominant-baseline: enum, inherit, initial
+ SetValue(*aRuleData->ValueForDominantBaseline(),
+ svgReset->mDominantBaseline,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentSVGReset->mDominantBaseline,
+ NS_STYLE_DOMINANT_BASELINE_AUTO);
+
+ // vector-effect: enum, inherit, initial
+ SetValue(*aRuleData->ValueForVectorEffect(),
+ svgReset->mVectorEffect,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentSVGReset->mVectorEffect,
+ NS_STYLE_VECTOR_EFFECT_NONE);
+
+ // mask-type: enum, inherit, initial
+ SetValue(*aRuleData->ValueForMaskType(),
+ svgReset->mMaskType,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentSVGReset->mMaskType,
+ NS_STYLE_MASK_TYPE_LUMINANCE);
+
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ uint32_t maxItemCount = 1;
+ bool rebuild = false;
+
+ // mask-image: none | <url> | <image-list> | <element-reference> | <gradient>
+ nsStyleImage initialImage;
+ SetImageLayerList(aContext, *aRuleData->ValueForMaskImage(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &nsStyleImageLayers::Layer::mImage,
+ initialImage, parentSVGReset->mMask.mImageCount,
+ svgReset->mMask.mImageCount,
+ maxItemCount, rebuild, conditions);
+ SetImageLayerList(aContext, *aRuleData->ValueForMaskImage(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &nsStyleImageLayers::Layer::mSourceURI,
+ RefPtr<css::URLValueData>(),
+ parentSVGReset->mMask.mImageCount,
+ svgReset->mMask.mImageCount,
+ maxItemCount, rebuild, conditions);
+
+ // mask-repeat: enum, inherit, initial [pair list]
+ nsStyleImageLayers::Repeat initialRepeat;
+ initialRepeat.SetInitialValues();
+ SetImageLayerPairList(aContext, *aRuleData->ValueForMaskRepeat(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &nsStyleImageLayers::Layer::mRepeat,
+ initialRepeat, parentSVGReset->mMask.mRepeatCount,
+ svgReset->mMask.mRepeatCount, maxItemCount, rebuild,
+ conditions);
+
+ // mask-clip: enum, inherit, initial [list]
+ SetImageLayerList(aContext, *aRuleData->ValueForMaskClip(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &nsStyleImageLayers::Layer::mClip,
+ uint8_t(NS_STYLE_IMAGELAYER_CLIP_BORDER),
+ parentSVGReset->mMask.mClipCount,
+ svgReset->mMask.mClipCount, maxItemCount, rebuild,
+ conditions);
+
+ // mask-origin: enum, inherit, initial [list]
+ SetImageLayerList(aContext, *aRuleData->ValueForMaskOrigin(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &nsStyleImageLayers::Layer::mOrigin,
+ uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_BORDER),
+ parentSVGReset->mMask.mOriginCount,
+ svgReset->mMask.mOriginCount, maxItemCount, rebuild,
+ conditions);
+
+ // mask-position-x/y: enum, length, percent (flags), inherit [list]
+ Position::Coord initialPositionCoord;
+ initialPositionCoord.mPercent = 0.0f;
+ initialPositionCoord.mLength = 0;
+ initialPositionCoord.mHasPercent = true;
+
+ SetImageLayerPositionCoordList(
+ aContext, *aRuleData->ValueForMaskPositionX(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &Position::mXPosition,
+ initialPositionCoord, parentSVGReset->mMask.mPositionXCount,
+ svgReset->mMask.mPositionXCount, maxItemCount, rebuild,
+ conditions);
+ SetImageLayerPositionCoordList(
+ aContext, *aRuleData->ValueForMaskPositionY(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &Position::mYPosition,
+ initialPositionCoord, parentSVGReset->mMask.mPositionYCount,
+ svgReset->mMask.mPositionYCount, maxItemCount, rebuild,
+ conditions);
+
+ // mask-size: enum, length, auto, inherit, initial [pair list]
+ nsStyleImageLayers::Size initialSize;
+ initialSize.SetInitialValues();
+ SetImageLayerPairList(aContext, *aRuleData->ValueForMaskSize(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &nsStyleImageLayers::Layer::mSize,
+ initialSize, parentSVGReset->mMask.mSizeCount,
+ svgReset->mMask.mSizeCount, maxItemCount, rebuild,
+ conditions);
+
+ // mask-mode: enum, inherit, initial [list]
+ SetImageLayerList(aContext, *aRuleData->ValueForMaskMode(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &nsStyleImageLayers::Layer::mMaskMode,
+ uint8_t(NS_STYLE_MASK_MODE_MATCH_SOURCE),
+ parentSVGReset->mMask.mMaskModeCount,
+ svgReset->mMask.mMaskModeCount, maxItemCount, rebuild, conditions);
+
+ // mask-composite: enum, inherit, initial [list]
+ SetImageLayerList(aContext, *aRuleData->ValueForMaskComposite(),
+ svgReset->mMask.mLayers,
+ parentSVGReset->mMask.mLayers,
+ &nsStyleImageLayers::Layer::mComposite,
+ uint8_t(NS_STYLE_MASK_COMPOSITE_ADD),
+ parentSVGReset->mMask.mCompositeCount,
+ svgReset->mMask.mCompositeCount, maxItemCount, rebuild, conditions);
+
+ if (rebuild) {
+ FillAllBackgroundLists(svgReset->mMask, maxItemCount);
+ }
+#else
+ // mask: none | <url>
+ const nsCSSValue* maskValue = aRuleData->ValueForMask();
+ if (eCSSUnit_URL == maskValue->GetUnit()) {
+ svgReset->mMask.mLayers[0].mSourceURI = maskValue->GetURLStructValue();
+ } else if (eCSSUnit_None == maskValue->GetUnit() ||
+ eCSSUnit_Initial == maskValue->GetUnit() ||
+ eCSSUnit_Unset == maskValue->GetUnit()) {
+ svgReset->mMask.mLayers[0].mSourceURI = nullptr;
+ } else if (eCSSUnit_Inherit == maskValue->GetUnit()) {
+ conditions.SetUncacheable();
+ svgReset->mMask.mLayers[0].mSourceURI =
+ parentSVGReset->mMask.mLayers[0].mSourceURI;
+ }
+#endif
+
+ COMPUTE_END_RESET(SVGReset, svgReset)
+}
+
+const void*
+nsRuleNode::ComputeVariablesData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_INHERITED(Variables, variables, parentVariables)
+
+ MOZ_ASSERT(aRuleData->mVariables,
+ "shouldn't be in ComputeVariablesData if there were no variable "
+ "declarations specified");
+
+ CSSVariableResolver resolver(&variables->mVariables);
+ resolver.Resolve(&parentVariables->mVariables,
+ aRuleData->mVariables);
+ conditions.SetUncacheable();
+
+ COMPUTE_END_INHERITED(Variables, variables)
+}
+
+const void*
+nsRuleNode::ComputeEffectsData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ const RuleDetail aRuleDetail,
+ const RuleNodeCacheConditions aConditions)
+{
+ COMPUTE_START_RESET(Effects, effects, parentEffects)
+
+ // filter: url, none, inherit
+ const nsCSSValue* filterValue = aRuleData->ValueForFilter();
+ switch (filterValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+ case eCSSUnit_None:
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ effects->mFilters.Clear();
+ break;
+ case eCSSUnit_Inherit:
+ conditions.SetUncacheable();
+ effects->mFilters = parentEffects->mFilters;
+ break;
+ case eCSSUnit_List:
+ case eCSSUnit_ListDep: {
+ effects->mFilters.Clear();
+ const nsCSSValueList* cur = filterValue->GetListValue();
+ while (cur) {
+ nsStyleFilter styleFilter;
+ if (!SetStyleFilterToCSSValue(&styleFilter, cur->mValue, aContext,
+ mPresContext, conditions)) {
+ effects->mFilters.Clear();
+ break;
+ }
+ MOZ_ASSERT(styleFilter.GetType() != NS_STYLE_FILTER_NONE,
+ "filter should be set");
+ effects->mFilters.AppendElement(styleFilter);
+ cur = cur->mNext;
+ }
+ break;
+ }
+ default:
+ NS_NOTREACHED("unexpected unit");
+ }
+
+ // box-shadow: none, list, inherit, initial
+ const nsCSSValue* boxShadowValue = aRuleData->ValueForBoxShadow();
+ switch (boxShadowValue->GetUnit()) {
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_None:
+ effects->mBoxShadow = nullptr;
+ break;
+
+ case eCSSUnit_Inherit:
+ effects->mBoxShadow = parentEffects->mBoxShadow;
+ conditions.SetUncacheable();
+ break;
+
+ case eCSSUnit_List:
+ case eCSSUnit_ListDep:
+ effects->mBoxShadow = GetShadowData(boxShadowValue->GetListValue(),
+ aContext, true, mPresContext, conditions);
+ break;
+
+ default:
+ MOZ_ASSERT(false, "unrecognized shadow unit");
+ }
+
+ // clip property: length, auto, inherit
+ const nsCSSValue* clipValue = aRuleData->ValueForClip();
+ switch (clipValue->GetUnit()) {
+ case eCSSUnit_Inherit:
+ conditions.SetUncacheable();
+ effects->mClipFlags = parentEffects->mClipFlags;
+ effects->mClip = parentEffects->mClip;
+ break;
+
+ case eCSSUnit_Initial:
+ case eCSSUnit_Unset:
+ case eCSSUnit_Auto:
+ effects->mClipFlags = NS_STYLE_CLIP_AUTO;
+ effects->mClip.SetRect(0,0,0,0);
+ break;
+
+ case eCSSUnit_Null:
+ break;
+
+ case eCSSUnit_Rect: {
+ const nsCSSRect& clipRect = clipValue->GetRectValue();
+
+ effects->mClipFlags = NS_STYLE_CLIP_RECT;
+
+ if (clipRect.mTop.GetUnit() == eCSSUnit_Auto) {
+ effects->mClip.y = 0;
+ effects->mClipFlags |= NS_STYLE_CLIP_TOP_AUTO;
+ }
+ else if (clipRect.mTop.IsLengthUnit()) {
+ effects->mClip.y = CalcLength(clipRect.mTop, aContext,
+ mPresContext, conditions);
+ }
+
+ if (clipRect.mBottom.GetUnit() == eCSSUnit_Auto) {
+ // Setting to NS_MAXSIZE for the 'auto' case ensures that
+ // the clip rect is nonempty. It is important that mClip be
+ // nonempty if the actual clip rect could be nonempty.
+ effects->mClip.height = NS_MAXSIZE;
+ effects->mClipFlags |= NS_STYLE_CLIP_BOTTOM_AUTO;
+ }
+ else if (clipRect.mBottom.IsLengthUnit()) {
+ effects->mClip.height = CalcLength(clipRect.mBottom, aContext,
+ mPresContext, conditions) -
+ effects->mClip.y;
+ }
+
+ if (clipRect.mLeft.GetUnit() == eCSSUnit_Auto) {
+ effects->mClip.x = 0;
+ effects->mClipFlags |= NS_STYLE_CLIP_LEFT_AUTO;
+ }
+ else if (clipRect.mLeft.IsLengthUnit()) {
+ effects->mClip.x = CalcLength(clipRect.mLeft, aContext,
+ mPresContext, conditions);
+ }
+
+ if (clipRect.mRight.GetUnit() == eCSSUnit_Auto) {
+ // Setting to NS_MAXSIZE for the 'auto' case ensures that
+ // the clip rect is nonempty. It is important that mClip be
+ // nonempty if the actual clip rect could be nonempty.
+ effects->mClip.width = NS_MAXSIZE;
+ effects->mClipFlags |= NS_STYLE_CLIP_RIGHT_AUTO;
+ }
+ else if (clipRect.mRight.IsLengthUnit()) {
+ effects->mClip.width = CalcLength(clipRect.mRight, aContext,
+ mPresContext, conditions) -
+ effects->mClip.x;
+ }
+ break;
+ }
+
+ default:
+ MOZ_ASSERT(false, "unrecognized clip unit");
+ }
+
+ // opacity: factor, inherit, initial
+ SetFactor(*aRuleData->ValueForOpacity(), effects->mOpacity, conditions,
+ parentEffects->mOpacity, 1.0f,
+ SETFCT_OPACITY | SETFCT_UNSET_INITIAL);
+
+ // mix-blend-mode: enum, inherit, initial
+ SetValue(*aRuleData->ValueForMixBlendMode(), effects->mMixBlendMode,
+ conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
+ parentEffects->mMixBlendMode, NS_STYLE_BLEND_NORMAL);
+
+ COMPUTE_END_RESET(Effects, effects)
+}
+
+const void*
+nsRuleNode::GetStyleData(nsStyleStructID aSID,
+ nsStyleContext* aContext,
+ bool aComputeData)
+{
+ NS_ASSERTION(IsUsedDirectly(),
+ "if we ever call this on rule nodes that aren't used "
+ "directly, we should adjust handling of mDependentBits "
+ "in some way.");
+ MOZ_ASSERT(!aContext->GetCachedStyleData(aSID),
+ "style context should not have cached data for struct");
+
+ const void *data;
+
+ // Never use cached data for animated style inside a pseudo-element;
+ // see comment on cacheability in AnimValuesStyleRule::MapRuleInfoInto.
+ if (!(HasAnimationData() && ParentHasPseudoElementData(aContext))) {
+ data = mStyleData.GetStyleData(aSID, aContext, aComputeData);
+ if (MOZ_LIKELY(data != nullptr)) {
+ // For inherited structs, mark the struct (which will be set on
+ // the context by our caller) as not being owned by the context.
+ if (!nsCachedStyleData::IsReset(aSID)) {
+ aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
+ } else if (HasAnimationData()) {
+ // If we have animation data, the struct should be cached on the style
+ // context so that we can peek the struct.
+ // See comment in AnimValuesStyleRule::MapRuleInfoInto.
+ StoreStyleOnContext(aContext, aSID, const_cast<void*>(data));
+ }
+
+ return data; // We have a fully specified struct. Just return it.
+ }
+ }
+
+ if (MOZ_UNLIKELY(!aComputeData))
+ return nullptr;
+
+ // Nothing is cached. We'll have to delve further and examine our rules.
+ data = WalkRuleTree(aSID, aContext);
+
+ MOZ_ASSERT(data, "should have aborted on out-of-memory");
+ return data;
+}
+
+void
+nsRuleNode::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue)
+{
+ for (nsRuleNode* node = this; node; node = node->GetParent()) {
+ nsIStyleRule* rule = node->GetRule();
+ if (!rule) {
+ continue;
+ }
+ if (rule->GetDiscretelyAnimatedCSSValue(aProperty, aValue)) {
+ return;
+ }
+ }
+}
+
+/* static */ bool
+nsRuleNode::HasAuthorSpecifiedRules(nsStyleContext* aStyleContext,
+ uint32_t ruleTypeMask,
+ bool aAuthorColorsAllowed)
+{
+#ifdef MOZ_STYLO
+ if (aStyleContext->StyleSource().IsServoComputedValues()) {
+ NS_WARNING("stylo: nsRuleNode::HasAuthorSpecifiedRules not implemented");
+ return true;
+ }
+#endif
+
+ uint32_t inheritBits = 0;
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND)
+ inheritBits |= NS_STYLE_INHERIT_BIT(Background);
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER)
+ inheritBits |= NS_STYLE_INHERIT_BIT(Border);
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING)
+ inheritBits |= NS_STYLE_INHERIT_BIT(Padding);
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW)
+ inheritBits |= NS_STYLE_INHERIT_BIT(Text);
+
+ // properties in the SIDS, whether or not we care about them
+ size_t nprops = 0,
+ backgroundOffset, borderOffset, paddingOffset, textShadowOffset;
+
+ // We put the reset properties the start of the nsCSSValue array....
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) {
+ backgroundOffset = nprops;
+ nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Background);
+ }
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) {
+ borderOffset = nprops;
+ nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Border);
+ }
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) {
+ paddingOffset = nprops;
+ nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Padding);
+ }
+
+ // ...and the inherited properties at the end of the array.
+ size_t inheritedOffset = nprops;
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) {
+ textShadowOffset = nprops;
+ nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Text);
+ }
+
+ void* dataStorage = alloca(nprops * sizeof(nsCSSValue));
+ AutoCSSValueArray dataArray(dataStorage, nprops);
+
+ /* We're relying on the use of |aStyleContext| not mutating it! */
+ nsRuleData ruleData(inheritBits, dataArray.get(),
+ aStyleContext->PresContext(), aStyleContext);
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) {
+ ruleData.mValueOffsets[eStyleStruct_Background] = backgroundOffset;
+ }
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) {
+ ruleData.mValueOffsets[eStyleStruct_Border] = borderOffset;
+ }
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) {
+ ruleData.mValueOffsets[eStyleStruct_Padding] = paddingOffset;
+ }
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) {
+ ruleData.mValueOffsets[eStyleStruct_Text] = textShadowOffset;
+ }
+
+ static const nsCSSPropertyID backgroundValues[] = {
+ eCSSProperty_background_color,
+ eCSSProperty_background_image,
+ };
+
+ static const nsCSSPropertyID borderValues[] = {
+ eCSSProperty_border_top_color,
+ eCSSProperty_border_top_style,
+ eCSSProperty_border_top_width,
+ eCSSProperty_border_right_color,
+ eCSSProperty_border_right_style,
+ eCSSProperty_border_right_width,
+ eCSSProperty_border_bottom_color,
+ eCSSProperty_border_bottom_style,
+ eCSSProperty_border_bottom_width,
+ eCSSProperty_border_left_color,
+ eCSSProperty_border_left_style,
+ eCSSProperty_border_left_width,
+ eCSSProperty_border_top_left_radius,
+ eCSSProperty_border_top_right_radius,
+ eCSSProperty_border_bottom_right_radius,
+ eCSSProperty_border_bottom_left_radius,
+ };
+
+ static const nsCSSPropertyID paddingValues[] = {
+ eCSSProperty_padding_top,
+ eCSSProperty_padding_right,
+ eCSSProperty_padding_bottom,
+ eCSSProperty_padding_left,
+ };
+
+ static const nsCSSPropertyID textShadowValues[] = {
+ eCSSProperty_text_shadow
+ };
+
+ // Number of properties we care about
+ size_t nValues = 0;
+
+ nsCSSValue* values[MOZ_ARRAY_LENGTH(backgroundValues) +
+ MOZ_ARRAY_LENGTH(borderValues) +
+ MOZ_ARRAY_LENGTH(paddingValues) +
+ MOZ_ARRAY_LENGTH(textShadowValues)];
+
+ nsCSSPropertyID properties[MOZ_ARRAY_LENGTH(backgroundValues) +
+ MOZ_ARRAY_LENGTH(borderValues) +
+ MOZ_ARRAY_LENGTH(paddingValues) +
+ MOZ_ARRAY_LENGTH(textShadowValues)];
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) {
+ for (uint32_t i = 0, i_end = ArrayLength(backgroundValues);
+ i < i_end; ++i) {
+ properties[nValues] = backgroundValues[i];
+ values[nValues++] = ruleData.ValueFor(backgroundValues[i]);
+ }
+ }
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) {
+ for (uint32_t i = 0, i_end = ArrayLength(borderValues);
+ i < i_end; ++i) {
+ properties[nValues] = borderValues[i];
+ values[nValues++] = ruleData.ValueFor(borderValues[i]);
+ }
+ }
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) {
+ for (uint32_t i = 0, i_end = ArrayLength(paddingValues);
+ i < i_end; ++i) {
+ properties[nValues] = paddingValues[i];
+ values[nValues++] = ruleData.ValueFor(paddingValues[i]);
+ }
+ }
+
+ if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) {
+ for (uint32_t i = 0, i_end = ArrayLength(textShadowValues);
+ i < i_end; ++i) {
+ properties[nValues] = textShadowValues[i];
+ values[nValues++] = ruleData.ValueFor(textShadowValues[i]);
+ }
+ }
+
+ nsStyleContext* styleContext = aStyleContext;
+
+ // We need to be careful not to count styles covered up by user-important or
+ // UA-important declarations. But we do want to catch explicit inherit
+ // styling in those and check our parent style context to see whether we have
+ // user styling for those properties. Note that we don't care here about
+ // inheritance due to lack of a specified value, since all the properties we
+ // care about are reset properties.
+ bool haveExplicitUAInherit;
+ do {
+ haveExplicitUAInherit = false;
+ for (nsRuleNode* ruleNode = styleContext->RuleNode(); ruleNode;
+ ruleNode = ruleNode->GetParent()) {
+ nsIStyleRule *rule = ruleNode->GetRule();
+ if (rule) {
+ ruleData.mLevel = ruleNode->GetLevel();
+ ruleData.mIsImportantRule = ruleNode->IsImportantRule();
+
+ rule->MapRuleInfoInto(&ruleData);
+
+ if (ruleData.mLevel == SheetType::Agent ||
+ ruleData.mLevel == SheetType::User) {
+ // This is a rule whose effect we want to ignore, so if any of
+ // the properties we care about were set, set them to the dummy
+ // value that they'll never otherwise get.
+ for (uint32_t i = 0; i < nValues; ++i) {
+ nsCSSUnit unit = values[i]->GetUnit();
+ if (unit != eCSSUnit_Null &&
+ unit != eCSSUnit_Dummy &&
+ unit != eCSSUnit_DummyInherit) {
+ if (unit == eCSSUnit_Inherit ||
+ (i >= inheritedOffset && unit == eCSSUnit_Unset)) {
+ haveExplicitUAInherit = true;
+ values[i]->SetDummyInheritValue();
+ } else {
+ values[i]->SetDummyValue();
+ }
+ }
+ }
+ } else {
+ // If any of the values we care about was set by the above rule,
+ // we have author style.
+ for (uint32_t i = 0; i < nValues; ++i) {
+ if (values[i]->GetUnit() != eCSSUnit_Null &&
+ values[i]->GetUnit() != eCSSUnit_Dummy && // see above
+ values[i]->GetUnit() != eCSSUnit_DummyInherit) {
+ // If author colors are not allowed, only claim to have
+ // author-specified rules if we're looking at a non-color
+ // property or if we're looking at the background color and it's
+ // set to transparent. Anything else should get set to a dummy
+ // value instead.
+ if (aAuthorColorsAllowed ||
+ !nsCSSProps::PropHasFlags(properties[i],
+ CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED) ||
+ (properties[i] == eCSSProperty_background_color &&
+ !values[i]->IsNonTransparentColor())) {
+ return true;
+ }
+
+ values[i]->SetDummyValue();
+ }
+ }
+ }
+ }
+ }
+
+ if (haveExplicitUAInherit) {
+ // reset all the eCSSUnit_Null values to eCSSUnit_Dummy (since they're
+ // not styled by the author, or by anyone else), and then reset all the
+ // eCSSUnit_DummyInherit values to eCSSUnit_Null (so we will be able to
+ // detect them being styled by the author) and move up to our parent
+ // style context.
+ for (uint32_t i = 0; i < nValues; ++i)
+ if (values[i]->GetUnit() == eCSSUnit_Null)
+ values[i]->SetDummyValue();
+ for (uint32_t i = 0; i < nValues; ++i)
+ if (values[i]->GetUnit() == eCSSUnit_DummyInherit)
+ values[i]->Reset();
+ styleContext = styleContext->GetParent();
+ }
+ } while (haveExplicitUAInherit && styleContext);
+
+ return false;
+}
+
+/* static */ void
+nsRuleNode::ComputePropertiesOverridingAnimation(
+ const nsTArray<nsCSSPropertyID>& aProperties,
+ nsStyleContext* aStyleContext,
+ nsCSSPropertyIDSet& aPropertiesOverridden)
+{
+ /*
+ * Set up an nsRuleData with all the structs needed for all of the
+ * properties in aProperties.
+ */
+ uint32_t structBits = 0;
+ size_t nprops = 0;
+ size_t offsets[nsStyleStructID_Length];
+ for (size_t propIdx = 0, propEnd = aProperties.Length();
+ propIdx < propEnd; ++propIdx) {
+ nsCSSPropertyID prop = aProperties[propIdx];
+ nsStyleStructID sid = nsCSSProps::kSIDTable[prop];
+ uint32_t bit = nsCachedStyleData::GetBitForSID(sid);
+ if (!(structBits & bit)) {
+ structBits |= bit;
+ offsets[sid] = nprops;
+ nprops += nsCSSProps::PropertyCountInStruct(sid);
+ }
+ }
+
+ void* dataStorage = alloca(nprops * sizeof(nsCSSValue));
+ AutoCSSValueArray dataArray(dataStorage, nprops);
+
+ // We're relying on the use of |aStyleContext| not mutating it!
+ nsRuleData ruleData(structBits, dataArray.get(),
+ aStyleContext->PresContext(), aStyleContext);
+ for (nsStyleStructID sid = nsStyleStructID(0);
+ sid < nsStyleStructID_Length; sid = nsStyleStructID(sid + 1)) {
+ if (structBits & nsCachedStyleData::GetBitForSID(sid)) {
+ ruleData.mValueOffsets[sid] = offsets[sid];
+ }
+ }
+
+ /*
+ * Actually walk up the rule tree until we're someplace less
+ * specific than animations.
+ */
+ for (nsRuleNode* ruleNode = aStyleContext->RuleNode(); ruleNode;
+ ruleNode = ruleNode->GetParent()) {
+ nsIStyleRule *rule = ruleNode->GetRule();
+ if (rule) {
+ ruleData.mLevel = ruleNode->GetLevel();
+ ruleData.mIsImportantRule = ruleNode->IsImportantRule();
+
+ // Transitions are the only non-!important level overriding
+ // animations in the cascade ordering. They also don't actually
+ // override animations, since transitions are suppressed when both
+ // are present. And since we might not have called
+ // UpdateCascadeResults (which updates when they are suppressed
+ // due to the presence of animations for the same element and
+ // property) for transitions yet (which will make their
+ // MapRuleInfoInto skip the properties that are currently
+ // animating), we should skip them explicitly.
+ if (ruleData.mLevel == SheetType::Transition) {
+ continue;
+ }
+
+ if (!ruleData.mIsImportantRule) {
+ // We're now equal to or less than the animation level; stop.
+ break;
+ }
+
+ rule->MapRuleInfoInto(&ruleData);
+ }
+ }
+
+ /*
+ * Fill in which properties were overridden.
+ */
+ for (size_t propIdx = 0, propEnd = aProperties.Length();
+ propIdx < propEnd; ++propIdx) {
+ nsCSSPropertyID prop = aProperties[propIdx];
+ if (ruleData.ValueFor(prop)->GetUnit() != eCSSUnit_Null) {
+ aPropertiesOverridden.AddProperty(prop);
+ }
+ }
+}
+
+/* static */
+bool
+nsRuleNode::ComputeColor(const nsCSSValue& aValue, nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext, nscolor& aResult)
+{
+ MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Inherit,
+ "aValue shouldn't have eCSSUnit_Inherit");
+ MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Initial,
+ "aValue shouldn't have eCSSUnit_Initial");
+ MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Unset,
+ "aValue shouldn't have eCSSUnit_Unset");
+
+ RuleNodeCacheConditions conditions;
+ bool ok = SetColor(aValue, NS_RGB(0, 0, 0), aPresContext, aStyleContext,
+ aResult, conditions);
+ MOZ_ASSERT(ok || !(aPresContext && aStyleContext));
+ return ok;
+}
+
+/* static */ bool
+nsRuleNode::ParentHasPseudoElementData(nsStyleContext* aContext)
+{
+ nsStyleContext* parent = aContext->GetParent();
+ return parent && parent->HasPseudoElementData();
+}
+
+/* static */ void
+nsRuleNode::StoreStyleOnContext(nsStyleContext* aContext,
+ nsStyleStructID aSID,
+ void* aStruct)
+{
+ aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
+ aContext->SetStyle(aSID, aStruct);
+}
+
+#ifdef DEBUG
+bool
+nsRuleNode::ContextHasCachedData(nsStyleContext* aContext,
+ nsStyleStructID aSID)
+{
+ return !!aContext->GetCachedStyleData(aSID);
+}
+#endif
diff --git a/layout/style/nsRuleNode.h b/layout/style/nsRuleNode.h
new file mode 100644
index 000000000..f9e71618b
--- /dev/null
+++ b/layout/style/nsRuleNode.h
@@ -0,0 +1,1090 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * a node in the lexicographic tree of rules that match an element,
+ * responsible for converting the rules' information into computed style
+ */
+
+#ifndef nsRuleNode_h___
+#define nsRuleNode_h___
+
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/RangedArray.h"
+#include "mozilla/RuleNodeCacheConditions.h"
+#include "mozilla/SheetType.h"
+#include "nsPresContext.h"
+#include "nsStyleStruct.h"
+
+class nsCSSPropertyIDSet;
+class nsCSSValue;
+class nsIStyleRule;
+class nsStyleContext;
+class nsStyleCoord;
+struct nsCSSRect;
+struct nsCSSValueList;
+struct nsCSSValuePairList;
+struct nsRuleData;
+
+struct nsInheritedStyleData
+{
+ mozilla::RangedArray<void*,
+ nsStyleStructID_Inherited_Start,
+ nsStyleStructID_Inherited_Count> mStyleStructs;
+
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsInheritedStyleData, sz);
+ }
+
+ void DestroyStructs(uint64_t aBits, nsPresContext* aContext) {
+#define STYLE_STRUCT_INHERITED(name, checkdata_cb) \
+ void *name##Data = mStyleStructs[eStyleStruct_##name]; \
+ if (name##Data && !(aBits & NS_STYLE_INHERIT_BIT(name))) \
+ static_cast<nsStyle##name*>(name##Data)->Destroy(aContext);
+#define STYLE_STRUCT_RESET(name, checkdata_cb)
+
+#include "nsStyleStructList.h"
+
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+ }
+
+ void Destroy(uint64_t aBits, nsPresContext* aContext) {
+ DestroyStructs(aBits, aContext);
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsInheritedStyleData, this);
+ }
+
+ nsInheritedStyleData() {
+ for (nsStyleStructID i = nsStyleStructID_Inherited_Start;
+ i < nsStyleStructID_Inherited_Start + nsStyleStructID_Inherited_Count;
+ i = nsStyleStructID(i + 1)) {
+ mStyleStructs[i] = nullptr;
+ }
+ }
+};
+
+struct nsResetStyleData
+{
+ mozilla::RangedArray<void*,
+ nsStyleStructID_Reset_Start,
+ nsStyleStructID_Reset_Count> mStyleStructs;
+
+ nsResetStyleData()
+ {
+ for (nsStyleStructID i = nsStyleStructID_Reset_Start;
+ i < nsStyleStructID_Reset_Start + nsStyleStructID_Reset_Count;
+ i = nsStyleStructID(i + 1)) {
+ mStyleStructs[i] = nullptr;
+ }
+ }
+
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsResetStyleData, sz);
+ }
+
+ void Destroy(uint64_t aBits, nsPresContext* aContext) {
+#define STYLE_STRUCT_RESET(name, checkdata_cb) \
+ void *name##Data = mStyleStructs[eStyleStruct_##name]; \
+ if (name##Data && !(aBits & NS_STYLE_INHERIT_BIT(name))) \
+ static_cast<nsStyle##name*>(name##Data)->Destroy(aContext);
+#define STYLE_STRUCT_INHERITED(name, checkdata_cb)
+
+#include "nsStyleStructList.h"
+
+#undef STYLE_STRUCT_RESET
+#undef STYLE_STRUCT_INHERITED
+
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsResetStyleData, this);
+ }
+};
+
+struct nsConditionalResetStyleData
+{
+ static uint32_t GetBitForSID(const nsStyleStructID aSID) {
+ return 1 << aSID;
+ }
+
+ struct Entry
+ {
+ Entry(const mozilla::RuleNodeCacheConditions& aConditions,
+ void* aStyleStruct,
+ Entry* aNext)
+ : mConditions(aConditions), mStyleStruct(aStyleStruct), mNext(aNext) {}
+
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->AllocateByObjectID(
+ mozilla::eArenaObjectID_nsConditionalResetStyleDataEntry, sz);
+ }
+
+ const mozilla::RuleNodeCacheConditions mConditions;
+ void* const mStyleStruct;
+ Entry* const mNext;
+ };
+
+ // Each entry is either a pointer to a style struct or a
+ // pointer to an Entry object. A bit in mConditionalBits
+ // means that it is an Entry.
+ mozilla::RangedArray<void*,
+ nsStyleStructID_Reset_Start,
+ nsStyleStructID_Reset_Count> mEntries;
+
+ uint32_t mConditionalBits;
+
+ nsConditionalResetStyleData()
+ {
+ for (nsStyleStructID i = nsStyleStructID_Reset_Start;
+ i < nsStyleStructID_Reset_Start + nsStyleStructID_Reset_Count;
+ i = nsStyleStructID(i + 1)) {
+ mEntries[i] = nullptr;
+ }
+ mConditionalBits = 0;
+ }
+
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->AllocateByObjectID(
+ mozilla::eArenaObjectID_nsConditionalResetStyleData, sz);
+ }
+
+ void* GetStyleData(nsStyleStructID aSID) const {
+ if (mConditionalBits & GetBitForSID(aSID)) {
+ return nullptr;
+ }
+ return mEntries[aSID];
+ }
+
+ void* GetStyleData(nsStyleStructID aSID,
+ nsStyleContext* aStyleContext,
+ bool aCanComputeData) const {
+ if (!(mConditionalBits & GetBitForSID(aSID))) {
+ return mEntries[aSID];
+ }
+ if (!aCanComputeData) {
+ // If aCanComputeData is false, then any previously-computed data
+ // would have been cached on the style context. Therefore it's
+ // unnecessary to check the conditional data. It's also
+ // incorrect, because calling e->mConditions.Matches() below could
+ // cause additional structs to be computed, which is incorrect
+ // during CalcStyleDifference.
+ return nullptr;
+ }
+ return GetConditionalStyleData(aSID, aStyleContext);
+ }
+
+private:
+ // non-inline helper for GetStyleData
+ void* GetConditionalStyleData(nsStyleStructID aSID,
+ nsStyleContext* aStyleContext) const;
+
+public:
+ void SetStyleData(nsStyleStructID aSID, void* aStyleStruct) {
+ MOZ_ASSERT(!(mConditionalBits & GetBitForSID(aSID)),
+ "rule node should not have unconditional and conditional style "
+ "data for a given struct");
+ mConditionalBits &= ~GetBitForSID(aSID);
+ mEntries[aSID] = aStyleStruct;
+ }
+
+ void SetStyleData(nsStyleStructID aSID,
+ nsPresContext* aPresContext,
+ void* aStyleStruct,
+ const mozilla::RuleNodeCacheConditions& aConditions) {
+ if (!(mConditionalBits & GetBitForSID(aSID))) {
+ MOZ_ASSERT(!mEntries[aSID],
+ "rule node should not have unconditional and conditional "
+ "style data for a given struct");
+ mEntries[aSID] = nullptr;
+ }
+
+ MOZ_ASSERT(aConditions.CacheableWithDependencies(),
+ "don't call SetStyleData with a cache key that has no "
+ "conditions or is uncacheable");
+
+#ifdef DEBUG
+ for (Entry* e = static_cast<Entry*>(mEntries[aSID]); e; e = e->mNext) {
+ NS_WARNING_ASSERTION(e->mConditions != aConditions,
+ "wasteful to have duplicate conditional style data");
+ }
+#endif
+
+ mConditionalBits |= GetBitForSID(aSID);
+ mEntries[aSID] =
+ new (aPresContext) Entry(aConditions, aStyleStruct,
+ static_cast<Entry*>(mEntries[aSID]));
+ }
+
+ void Destroy(uint64_t aBits, nsPresContext* aContext) {
+#define STYLE_STRUCT_RESET(name, checkdata_cb) \
+ void* name##Ptr = mEntries[eStyleStruct_##name]; \
+ if (name##Ptr) { \
+ if (!(mConditionalBits & NS_STYLE_INHERIT_BIT(name))) { \
+ if (!(aBits & NS_STYLE_INHERIT_BIT(name))) { \
+ static_cast<nsStyle##name*>(name##Ptr)->Destroy(aContext); \
+ } \
+ } else { \
+ Entry* e = static_cast<Entry*>(name##Ptr); \
+ MOZ_ASSERT(e, "if mConditionalBits bit is set, we must have at least " \
+ "one conditional style struct"); \
+ do { \
+ static_cast<nsStyle##name*>(e->mStyleStruct)->Destroy(aContext); \
+ Entry* next = e->mNext; \
+ aContext->PresShell()->FreeByObjectID( \
+ mozilla::eArenaObjectID_nsConditionalResetStyleDataEntry, e); \
+ e = next; \
+ } while (e); \
+ } \
+ }
+#define STYLE_STRUCT_INHERITED(name, checkdata_cb)
+
+#include "nsStyleStructList.h"
+
+#undef STYLE_STRUCT_RESET
+#undef STYLE_STRUCT_INHERITED
+
+ aContext->PresShell()->FreeByObjectID(
+ mozilla::eArenaObjectID_nsConditionalResetStyleData, this);
+ }
+
+};
+
+struct nsCachedStyleData
+{
+ nsInheritedStyleData* mInheritedData;
+ nsConditionalResetStyleData* mResetData;
+
+ static bool IsReset(const nsStyleStructID aSID) {
+ MOZ_ASSERT(0 <= aSID && aSID < nsStyleStructID_Length,
+ "must be an inherited or reset SID");
+ return nsStyleStructID_Reset_Start <= aSID;
+ }
+
+ static bool IsInherited(const nsStyleStructID aSID) {
+ return !IsReset(aSID);
+ }
+
+ static uint32_t GetBitForSID(const nsStyleStructID aSID) {
+ return nsConditionalResetStyleData::GetBitForSID(aSID);
+ }
+
+ void* NS_FASTCALL GetStyleData(const nsStyleStructID aSID) {
+ if (IsReset(aSID)) {
+ if (mResetData) {
+ return mResetData->GetStyleData(aSID);
+ }
+ } else {
+ if (mInheritedData) {
+ return mInheritedData->mStyleStructs[aSID];
+ }
+ }
+ return nullptr;
+ }
+
+ void* NS_FASTCALL GetStyleData(const nsStyleStructID aSID,
+ nsStyleContext* aStyleContext,
+ bool aCanComputeData) {
+ if (IsReset(aSID)) {
+ if (mResetData) {
+ return mResetData->GetStyleData(aSID, aStyleContext, aCanComputeData);
+ }
+ } else {
+ if (mInheritedData) {
+ return mInheritedData->mStyleStructs[aSID];
+ }
+ }
+ return nullptr;
+ }
+
+ void NS_FASTCALL SetStyleData(const nsStyleStructID aSID,
+ nsPresContext *aPresContext, void *aData) {
+ if (IsReset(aSID)) {
+ if (!mResetData) {
+ mResetData = new (aPresContext) nsConditionalResetStyleData;
+ }
+ mResetData->SetStyleData(aSID, aData);
+ } else {
+ if (!mInheritedData) {
+ mInheritedData = new (aPresContext) nsInheritedStyleData;
+ }
+ mInheritedData->mStyleStructs[aSID] = aData;
+ }
+ }
+
+ // Typesafe and faster versions of the above
+ #define STYLE_STRUCT_INHERITED(name_, checkdata_cb_) \
+ nsStyle##name_ * NS_FASTCALL GetStyle##name_ () { \
+ return mInheritedData ? static_cast<nsStyle##name_*>( \
+ mInheritedData->mStyleStructs[eStyleStruct_##name_]) : nullptr; \
+ }
+ #define STYLE_STRUCT_RESET(name_, checkdata_cb_) \
+ nsStyle##name_ * NS_FASTCALL GetStyle##name_ (nsStyleContext* aContext, \
+ bool aCanComputeData) { \
+ return mResetData ? static_cast<nsStyle##name_*>( \
+ mResetData->GetStyleData(eStyleStruct_##name_, aContext, \
+ aCanComputeData)) \
+ : nullptr; \
+ }
+ #include "nsStyleStructList.h"
+ #undef STYLE_STRUCT_RESET
+ #undef STYLE_STRUCT_INHERITED
+
+ void Destroy(uint64_t aBits, nsPresContext* aContext) {
+ if (mResetData)
+ mResetData->Destroy(aBits, aContext);
+ if (mInheritedData)
+ mInheritedData->Destroy(aBits, aContext);
+ mResetData = nullptr;
+ mInheritedData = nullptr;
+ }
+
+ nsCachedStyleData() :mInheritedData(nullptr), mResetData(nullptr) {}
+ ~nsCachedStyleData() {}
+};
+
+/**
+ * nsRuleNode is a node in a lexicographic tree (the "rule tree")
+ * indexed by style rules (implementations of nsIStyleRule).
+ *
+ * The rule tree is owned by the nsStyleSet and is destroyed when the
+ * presentation of the document goes away. Its entries are reference-
+ * counted, with strong references held by child nodes, style structs
+ * and (for the root), the style set. Rule nodes are not immediately
+ * destroyed when their reference-count drops to zero, but are instead
+ * destroyed during a GC sweep.
+ *
+ * An nsStyleContext, which represents the computed style data for an
+ * element, points to an nsRuleNode. The path from the root of the rule
+ * tree to the nsStyleContext's mRuleNode gives the list of the rules
+ * matched, from least important in the cascading order to most
+ * important in the cascading order.
+ *
+ * The reason for using a lexicographic tree is that it allows for
+ * sharing of style data, which saves both memory (for storing the
+ * computed style data) and time (for computing them). This sharing
+ * depends on the computed style data being stored in structs (nsStyle*)
+ * that contain only properties that are inherited by default
+ * ("inherited structs") or structs that contain only properties that
+ * are not inherited by default ("reset structs"). The optimization
+ * depends on the normal case being that style rules specify relatively
+ * few properties and even that elements generally have relatively few
+ * properties specified. This allows sharing in the following ways:
+ * 1. [mainly reset structs] When a style data struct will contain the
+ * same computed value for any elements that match the same set of
+ * rules (common for reset structs), it can be stored on the
+ * nsRuleNode instead of on the nsStyleContext.
+ * 2. [only? reset structs] When (1) occurs, and an nsRuleNode doesn't
+ * have any rules that change the values in the struct, the
+ * nsRuleNode can share that struct with its parent nsRuleNode.
+ * 3. [mainly inherited structs] When an element doesn't match any
+ * rules that change the value of a property (or, in the edge case,
+ * when all the values specified are 'inherit'), the nsStyleContext
+ * can use the same nsStyle* struct as its parent nsStyleContext.
+ *
+ * Since the data represented by an nsIStyleRule are immutable, the data
+ * represented by an nsRuleNode are also immutable.
+ */
+
+enum nsFontSizeType {
+ eFontSize_HTML = 1,
+ eFontSize_CSS = 2
+};
+
+// Note: This LinkedListElement is used for storing unused nodes in the
+// linked list on nsStyleSet. We use mNextSibling for the singly-linked
+// sibling list.
+class nsRuleNode : public mozilla::LinkedListElement<nsRuleNode> {
+public:
+ enum RuleDetail {
+ eRuleNone, // No props have been specified at all.
+ eRulePartialReset, // At least one prop with a non-"inherit" value
+ // has been specified. No props have been
+ // specified with an "inherit" value. At least
+ // one prop remains unspecified.
+ eRulePartialMixed, // At least one prop with a non-"inherit" value
+ // has been specified. Some props may also have
+ // been specified with an "inherit" value. At
+ // least one prop remains unspecified.
+ eRulePartialInherited, // Only props with "inherit" values have
+ // have been specified. At least one prop
+ // remains unspecified.
+ eRuleFullReset, // All props have been specified. None has an
+ // "inherit" value.
+ eRuleFullMixed, // All props have been specified. At least one has
+ // a non-"inherit" value.
+ eRuleFullInherited // All props have been specified with "inherit"
+ // values.
+ };
+
+private:
+ nsPresContext* const mPresContext; // Our pres context.
+
+ const RefPtr<nsRuleNode> mParent; // A pointer to the parent node in the tree.
+ // This enables us to walk backwards from the
+ // most specific rule matched to the least
+ // specific rule (which is the optimal order to
+ // use for lookups of style properties.
+
+ const nsCOMPtr<nsIStyleRule> mRule; // A pointer to our specific rule.
+
+ nsRuleNode* mNextSibling; // This value should be used only by the
+ // parent, since the parent may store
+ // children in a hash, which means this
+ // pointer is not meaningful. Order of
+ // siblings is also not meaningful.
+
+ struct Key {
+ nsIStyleRule* mRule;
+ mozilla::SheetType mLevel;
+ bool mIsImportantRule;
+
+ Key(nsIStyleRule* aRule, mozilla::SheetType aLevel, bool aIsImportantRule)
+ : mRule(aRule), mLevel(aLevel), mIsImportantRule(aIsImportantRule)
+ {}
+
+ bool operator==(const Key& aOther) const
+ {
+ return mRule == aOther.mRule &&
+ mLevel == aOther.mLevel &&
+ mIsImportantRule == aOther.mIsImportantRule;
+ }
+
+ bool operator!=(const Key& aOther) const
+ {
+ return !(*this == aOther);
+ }
+ };
+
+ static PLDHashNumber
+ ChildrenHashHashKey(const void *aKey);
+
+ static bool
+ ChildrenHashMatchEntry(const PLDHashEntryHdr *aHdr, const void *aKey);
+
+ static const PLDHashTableOps ChildrenHashOps;
+
+ Key GetKey() const {
+ return Key(mRule, GetLevel(), IsImportantRule());
+ }
+
+ // The children of this node are stored in either a hashtable or list
+ // that maps from rules to our nsRuleNode children. When matching
+ // rules, we use this mapping to transition from node to node
+ // (constructing new nodes as needed to flesh out the tree).
+
+ union {
+ void* asVoid;
+ nsRuleNode* asList;
+ PLDHashTable* asHash;
+ } mChildren; // Accessed only through the methods below.
+
+ enum {
+ kTypeMask = 0x1,
+ kListType = 0x0,
+ kHashType = 0x1
+ };
+ enum {
+ // Maximum to have in a list before converting to a hashtable.
+ // XXX Need to optimize this.
+ kMaxChildrenInList = 32
+ };
+
+ bool HaveChildren() const {
+ return mChildren.asVoid != nullptr;
+ }
+ bool ChildrenAreHashed() {
+ return (intptr_t(mChildren.asVoid) & kTypeMask) == kHashType;
+ }
+ nsRuleNode* ChildrenList() {
+ return mChildren.asList;
+ }
+ nsRuleNode** ChildrenListPtr() {
+ return &mChildren.asList;
+ }
+ PLDHashTable* ChildrenHash() {
+ return (PLDHashTable*) (intptr_t(mChildren.asHash) & ~intptr_t(kTypeMask));
+ }
+ void SetChildrenList(nsRuleNode *aList) {
+ NS_ASSERTION(!(intptr_t(aList) & kTypeMask),
+ "pointer not 2-byte aligned");
+ mChildren.asList = aList;
+ }
+ void SetChildrenHash(PLDHashTable *aHashtable) {
+ NS_ASSERTION(!(intptr_t(aHashtable) & kTypeMask),
+ "pointer not 2-byte aligned");
+ mChildren.asHash = (PLDHashTable*)(intptr_t(aHashtable) | kHashType);
+ }
+ void ConvertChildrenToHash(int32_t aNumKids);
+
+ void RemoveChild(nsRuleNode* aNode);
+
+ nsCachedStyleData mStyleData; // Any data we cached on the rule node.
+
+ uint32_t mDependentBits; // Used to cache the fact that we can look up
+ // cached data under a parent rule.
+
+ uint32_t mNoneBits; // Used to cache the fact that the branch to this
+ // node specifies no non-inherited data for a
+ // given struct type. (This usually implies that
+ // the entire branch specifies no non-inherited
+ // data, although not necessarily, if a
+ // non-inherited value is overridden by an
+ // explicit 'inherit' value.) For example, if an
+ // entire rule branch specifies no color
+ // information, then a bit will be set along every
+ // rule node on that branch, so that you can break
+ // out of the rule tree early and just inherit
+ // from the parent style context. The presence of
+ // this bit means we should just get inherited
+ // data from the parent style context, and it is
+ // never used for reset structs since their
+ // Compute*Data functions don't initialize from
+ // inherited data.
+
+ // Reference count. Style contexts hold strong references to their rule node,
+ // and rule nodes hold strong references to their parent.
+ //
+ // When the refcount drops to zero, we don't necessarily free the node.
+ // Instead, we notify the style set, which performs periodic sweeps.
+ uint32_t mRefCnt;
+
+public:
+ // Infallible overloaded new operator that allocates from a presShell arena.
+ void* operator new(size_t sz, nsPresContext* aContext);
+ void Destroy();
+
+ // Implemented in nsStyleSet.h, since it needs to know about nsStyleSet.
+ inline void AddRef();
+
+ // Implemented in nsStyleSet.h, since it needs to know about nsStyleSet.
+ inline void Release();
+
+protected:
+ void PropagateDependentBit(nsStyleStructID aSID, nsRuleNode* aHighestNode,
+ void* aStruct);
+ void PropagateNoneBit(uint32_t aBit, nsRuleNode* aHighestNode);
+ static void PropagateGrandancestorBit(nsStyleContext* aContext,
+ nsStyleContext* aContextInheritedFrom);
+
+ const void* SetDefaultOnRoot(const nsStyleStructID aSID,
+ nsStyleContext* aContext);
+
+ /**
+ * Resolves any property values in aRuleData for a given style struct that
+ * have eCSSUnit_TokenStream values, by resolving them against the computed
+ * variable values on the style context and re-parsing the property.
+ *
+ * @return Whether any properties with eCSSUnit_TokenStream values were
+ * encountered.
+ */
+ static bool ResolveVariableReferences(const nsStyleStructID aSID,
+ nsRuleData* aRuleData,
+ nsStyleContext* aContext);
+
+ const void*
+ WalkRuleTree(const nsStyleStructID aSID, nsStyleContext* aContext);
+
+ const void*
+ ComputeDisplayData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeVisibilityData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeFontData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeColorData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeBackgroundData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeMarginData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeBorderData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputePaddingData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeOutlineData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeListData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputePositionData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeTableData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeTableBorderData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeContentData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeTextData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeTextResetData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeUserInterfaceData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext,
+ nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeUIResetData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeXULData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeColumnData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeSVGData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeSVGResetData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeVariablesData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ const void*
+ ComputeEffectsData(void* aStartStruct,
+ const nsRuleData* aRuleData,
+ nsStyleContext* aContext, nsRuleNode* aHighestNode,
+ RuleDetail aRuleDetail,
+ const mozilla::RuleNodeCacheConditions aConditions);
+
+ // helpers for |ComputeFontData| that need access to |mNoneBits|:
+ static void SetFontSize(nsPresContext* aPresContext,
+ nsStyleContext* aContext,
+ const nsRuleData* aRuleData,
+ const nsStyleFont* aFont,
+ const nsStyleFont* aParentFont,
+ nscoord* aSize,
+ const nsFont& aSystemFont,
+ nscoord aParentSize,
+ nscoord aScriptLevelAdjustedParentSize,
+ bool aUsedStartStruct,
+ bool aAtRoot,
+ mozilla::RuleNodeCacheConditions& aConditions);
+
+ static void SetFont(nsPresContext* aPresContext,
+ nsStyleContext* aContext,
+ uint8_t aGenericFontID,
+ const nsRuleData* aRuleData,
+ const nsStyleFont* aParentFont,
+ nsStyleFont* aFont,
+ bool aStartStruct,
+ mozilla::RuleNodeCacheConditions& aConditions);
+
+ static void SetGenericFont(nsPresContext* aPresContext,
+ nsStyleContext* aContext,
+ uint8_t aGenericFontID,
+ nsStyleFont* aFont);
+
+ inline RuleDetail CheckSpecifiedProperties(const nsStyleStructID aSID,
+ const nsRuleData* aRuleData);
+
+private:
+ nsRuleNode(nsPresContext* aPresContext, nsRuleNode* aParent,
+ nsIStyleRule* aRule, mozilla::SheetType aLevel, bool aIsImportant);
+ ~nsRuleNode();
+
+public:
+ // This is infallible; it will never return nullptr.
+ static already_AddRefed<nsRuleNode> CreateRootNode(nsPresContext* aPresContext);
+
+ static void EnsureBlockDisplay(mozilla::StyleDisplay& display,
+ bool aConvertListItem = false);
+ static void EnsureInlineDisplay(mozilla::StyleDisplay& display);
+
+ // Transition never returns null; on out of memory it'll just return |this|.
+ nsRuleNode* Transition(nsIStyleRule* aRule, mozilla::SheetType aLevel,
+ bool aIsImportantRule);
+ nsRuleNode* GetParent() const { return mParent; }
+ bool IsRoot() const { return mParent == nullptr; }
+
+ // Return the root of the rule tree that this rule node is in.
+ nsRuleNode* RuleTree();
+ const nsRuleNode* RuleTree() const {
+ return const_cast<nsRuleNode*>(this)->RuleTree();
+ }
+
+ mozilla::SheetType GetLevel() const {
+ NS_ASSERTION(!IsRoot(), "can't call on root");
+ return mozilla::SheetType(
+ (mDependentBits & NS_RULE_NODE_LEVEL_MASK) >>
+ NS_RULE_NODE_LEVEL_SHIFT);
+ }
+ bool IsImportantRule() const {
+ NS_ASSERTION(!IsRoot(), "can't call on root");
+ return (mDependentBits & NS_RULE_NODE_IS_IMPORTANT) != 0;
+ }
+
+ /**
+ * Has this rule node at some time in its lifetime been the mRuleNode
+ * of some style context (as opposed to only being the ancestor of
+ * some style context's mRuleNode)?
+ */
+ void SetUsedDirectly();
+ bool IsUsedDirectly() const {
+ return (mDependentBits & NS_RULE_NODE_USED_DIRECTLY) != 0;
+ }
+
+ /**
+ * Is the mRule of this rule node an AnimValuesStyleRule?
+ */
+ void SetIsAnimationRule() {
+ MOZ_ASSERT(!HaveChildren() ||
+ (mDependentBits & NS_RULE_NODE_IS_ANIMATION_RULE),
+ "SetIsAnimationRule must only set the IS_ANIMATION_RULE bit "
+ "before the rule node has children");
+ mDependentBits |= NS_RULE_NODE_IS_ANIMATION_RULE;
+ mNoneBits |= NS_RULE_NODE_HAS_ANIMATION_DATA;
+ }
+ bool IsAnimationRule() const {
+ return (mDependentBits & NS_RULE_NODE_IS_ANIMATION_RULE) != 0;
+ }
+
+ /**
+ * Is the mRule of this rule node or any of its ancestors an
+ * AnimValuesStyleRule?
+ */
+ bool HasAnimationData() const {
+ return (mNoneBits & NS_RULE_NODE_HAS_ANIMATION_DATA) != 0;
+ }
+
+ // NOTE: Does not |AddRef|. Null only for the root.
+ nsIStyleRule* GetRule() const { return mRule; }
+ // NOTE: Does not |AddRef|. Never null.
+ nsPresContext* PresContext() const { return mPresContext; }
+
+ const void* GetStyleData(nsStyleStructID aSID,
+ nsStyleContext* aContext,
+ bool aComputeData);
+
+ void GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue);
+
+ // See comments in GetStyleData for an explanation of what the
+ // code below does.
+ #define STYLE_STRUCT_INHERITED(name_, checkdata_cb_) \
+ template<bool aComputeData> \
+ const nsStyle##name_* \
+ GetStyle##name_(nsStyleContext* aContext, uint64_t& aContextStyleBits) \
+ { \
+ NS_ASSERTION(IsUsedDirectly(), \
+ "if we ever call this on rule nodes that aren't used " \
+ "directly, we should adjust handling of mDependentBits " \
+ "in some way."); \
+ MOZ_ASSERT(!ContextHasCachedData(aContext, eStyleStruct_##name_), \
+ "style context should not have cached data for struct"); \
+ \
+ const nsStyle##name_ *data; \
+ \
+ /* Never use cached data for animated style inside a pseudo-element; */ \
+ /* see comment on cacheability in AnimValuesStyleRule::MapRuleInfoInto */ \
+ if (!(HasAnimationData() && ParentHasPseudoElementData(aContext))) { \
+ data = mStyleData.GetStyle##name_(); \
+ if (data != nullptr) { \
+ /* For inherited structs, mark the struct (which will be set on */ \
+ /* the context by our caller) as not being owned by the context. */ \
+ /* Normally this would be aContext->AddStyleBit(), but aContext is */ \
+ /* an incomplete type here, so we work around that with a param. */ \
+ aContextStyleBits |= NS_STYLE_INHERIT_BIT(name_); \
+ /* Our caller will cache the data on the style context. */ \
+ return data; \
+ } \
+ } \
+ \
+ if (!aComputeData) \
+ return nullptr; \
+ \
+ data = static_cast<const nsStyle##name_ *> \
+ (WalkRuleTree(eStyleStruct_##name_, aContext)); \
+ \
+ MOZ_ASSERT(data, "should have aborted on out-of-memory"); \
+ return data; \
+ }
+
+ #define STYLE_STRUCT_RESET(name_, checkdata_cb_) \
+ template<bool aComputeData> \
+ const nsStyle##name_* \
+ GetStyle##name_(nsStyleContext* aContext) \
+ { \
+ NS_ASSERTION(IsUsedDirectly(), \
+ "if we ever call this on rule nodes that aren't used " \
+ "directly, we should adjust handling of mDependentBits " \
+ "in some way."); \
+ MOZ_ASSERT(!ContextHasCachedData(aContext, eStyleStruct_##name_), \
+ "style context should not have cached data for struct"); \
+ \
+ const nsStyle##name_ *data; \
+ \
+ /* Never use cached data for animated style inside a pseudo-element; */ \
+ /* see comment on cacheability in AnimValuesStyleRule::MapRuleInfoInto */ \
+ if (!(HasAnimationData() && ParentHasPseudoElementData(aContext))) { \
+ data = mStyleData.GetStyle##name_(aContext, aComputeData); \
+ if (MOZ_LIKELY(data != nullptr)) { \
+ if (HasAnimationData()) { \
+ /* If we have animation data, the struct should be cached on the */ \
+ /* style context so that we can peek the struct. */ \
+ /* See comment in AnimValuesStyleRule::MapRuleInfoInto. */ \
+ StoreStyleOnContext(aContext, \
+ eStyleStruct_##name_, \
+ const_cast<nsStyle##name_*>(data)); \
+ } \
+ return data; \
+ } \
+ } \
+ \
+ if (!aComputeData) \
+ return nullptr; \
+ \
+ data = static_cast<const nsStyle##name_ *> \
+ (WalkRuleTree(eStyleStruct_##name_, aContext)); \
+ \
+ MOZ_ASSERT(data, "should have aborted on out-of-memory"); \
+ return data; \
+ }
+
+ #include "nsStyleStructList.h"
+
+ #undef STYLE_STRUCT_RESET
+ #undef STYLE_STRUCT_INHERITED
+
+ static bool
+ HasAuthorSpecifiedRules(nsStyleContext* aStyleContext,
+ uint32_t ruleTypeMask,
+ bool aAuthorColorsAllowed);
+
+ /**
+ * Fill in to aPropertiesOverridden all of the properties in aProperties
+ * that, for this rule node, have a declaration that is higher than the
+ * animation level in the CSS Cascade.
+ */
+ static void
+ ComputePropertiesOverridingAnimation(
+ const nsTArray<nsCSSPropertyID>& aProperties,
+ nsStyleContext* aStyleContext,
+ nsCSSPropertyIDSet& aPropertiesOverridden);
+
+ // Expose this so media queries can use it
+ static nscoord CalcLengthWithInitialFont(nsPresContext* aPresContext,
+ const nsCSSValue& aValue);
+ // Expose this so nsTransformFunctions can use it.
+ static nscoord CalcLength(const nsCSSValue& aValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ mozilla::RuleNodeCacheConditions& aConditions);
+
+ struct ComputedCalc {
+ nscoord mLength;
+ float mPercent;
+
+ ComputedCalc(nscoord aLength, float aPercent)
+ : mLength(aLength), mPercent(aPercent) {}
+ };
+ static ComputedCalc
+ SpecifiedCalcToComputedCalc(const nsCSSValue& aValue,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ mozilla::RuleNodeCacheConditions& aConditions);
+
+ // Compute the value of an nsStyleCoord that IsCalcUnit().
+ // (Values that don't require aPercentageBasis should be handled
+ // inside nsRuleNode rather than through this API.)
+ // @note the caller is expected to handle percentage of an indefinite size
+ // and NOT call this method with aPercentageBasis == NS_UNCONSTRAINEDSIZE.
+ // @note the return value may be negative, e.g. for "calc(a - b%)"
+ static nscoord ComputeComputedCalc(const nsStyleCoord& aCoord,
+ nscoord aPercentageBasis);
+
+ // Compute the value of an nsStyleCoord that is either a coord, a
+ // percent, or a calc expression.
+ // @note the caller is expected to handle percentage of an indefinite size
+ // and NOT call this method with aPercentageBasis == NS_UNCONSTRAINEDSIZE.
+ // @note the return value may be negative, e.g. for "calc(a - b%)"
+ static nscoord ComputeCoordPercentCalc(const nsStyleCoord& aCoord,
+ nscoord aPercentageBasis);
+
+ // Return whether the rule tree for which this node is the root has
+ // cached data such that we need to do dynamic change handling for
+ // changes that change the results of media queries or require
+ // rebuilding all style data.
+ bool TreeHasCachedData() const {
+ NS_ASSERTION(IsRoot(), "should only be called on root of rule tree");
+ return HaveChildren() || mStyleData.mInheritedData || mStyleData.mResetData;
+ }
+
+ // Note that this will return false if we have cached conditional
+ // style structs.
+ bool NodeHasCachedUnconditionalData(const nsStyleStructID aSID) {
+ return !!mStyleData.GetStyleData(aSID);
+ }
+
+ static void ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList,
+ nsTArray<gfxFontFeature>& aFeatureSettings);
+
+ static nscoord CalcFontPointSize(int32_t aHTMLSize, int32_t aBasePointSize,
+ nsPresContext* aPresContext,
+ nsFontSizeType aFontSizeType = eFontSize_HTML);
+
+ static nscoord FindNextSmallerFontSize(nscoord aFontSize, int32_t aBasePointSize,
+ nsPresContext* aPresContext,
+ nsFontSizeType aFontSizeType = eFontSize_HTML);
+
+ static nscoord FindNextLargerFontSize(nscoord aFontSize, int32_t aBasePointSize,
+ nsPresContext* aPresContext,
+ nsFontSizeType aFontSizeType = eFontSize_HTML);
+
+ /**
+ * @param aValue The color value, returned from nsCSSParser::ParseColorString
+ * @param aPresContext Presentation context whose preferences are used
+ * for certain enumerated colors
+ * @param aStyleContext Style context whose color is used for 'currentColor'
+ *
+ * @note aPresContext and aStyleContext may be null, but in that case, fully
+ * opaque black will be returned for the values that rely on these
+ * objects to compute the color. (For example, -moz-hyperlinktext.)
+ *
+ * @return false if we fail to extract a color; this will not happen if both
+ * aPresContext and aStyleContext are non-null
+ */
+ static bool ComputeColor(const nsCSSValue& aValue,
+ nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ nscolor& aResult);
+
+ static bool ParentHasPseudoElementData(nsStyleContext* aContext);
+
+ static void ComputeTimingFunction(const nsCSSValue& aValue,
+ nsTimingFunction& aResult);
+
+ // Fill unspecified layers by cycling through their values
+ // till they all are of length aMaxItemCount
+ static void FillAllBackgroundLists(nsStyleImageLayers& aLayers,
+ uint32_t aMaxItemCount);
+
+ static void FillAllMaskLists(nsStyleImageLayers& aLayers,
+ uint32_t aMaxItemCount);
+
+private:
+#ifdef DEBUG
+ // non-inline helper function to allow assertions without incomplete
+ // type errors
+ bool ContextHasCachedData(nsStyleContext* aContext, nsStyleStructID aSID);
+#endif
+
+ // Store style struct on the style context and tell the style context
+ // that it doesn't own the data
+ static void StoreStyleOnContext(nsStyleContext* aContext,
+ nsStyleStructID aSID,
+ void* aStruct);
+};
+
+#endif
diff --git a/layout/style/nsRuleProcessorData.h b/layout/style/nsRuleProcessorData.h
new file mode 100644
index 000000000..6f8cba895
--- /dev/null
+++ b/layout/style/nsRuleProcessorData.h
@@ -0,0 +1,595 @@
+/* -*- 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/. */
+
+/*
+ * data structures passed to nsIStyleRuleProcessor methods (to pull loop
+ * invariant computations out of the loop)
+ */
+
+#ifndef nsRuleProcessorData_h_
+#define nsRuleProcessorData_h_
+
+#include "nsAutoPtr.h"
+#include "nsChangeHint.h"
+#include "nsCompatibility.h"
+#include "nsCSSPseudoElements.h"
+#include "nsRuleWalker.h"
+#include "nsNthIndexCache.h"
+#include "nsILoadContext.h"
+#include "nsIDocument.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BloomFilter.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/GuardObjects.h"
+#include "mozilla/dom/Element.h"
+
+class nsIAtom;
+class nsIContent;
+class nsICSSPseudoComparator;
+struct TreeMatchContext;
+
+/**
+ * An AncestorFilter is used to keep track of ancestors so that we can
+ * quickly tell that a particular selector is not relevant to a given
+ * element.
+ */
+class MOZ_STACK_CLASS AncestorFilter {
+ friend struct TreeMatchContext;
+ public:
+ /* Maintenance of our ancestor state */
+ void PushAncestor(mozilla::dom::Element *aElement);
+ void PopAncestor();
+
+ /* Check whether we might have an ancestor matching one of the given
+ atom hashes. |hashes| must have length hashListLength */
+ template<size_t hashListLength>
+ bool MightHaveMatchingAncestor(const uint32_t* aHashes) const
+ {
+ MOZ_ASSERT(mFilter);
+ for (size_t i = 0; i < hashListLength && aHashes[i]; ++i) {
+ if (!mFilter->mightContain(aHashes[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool HasFilter() const { return mFilter; }
+
+#ifdef DEBUG
+ void AssertHasAllAncestors(mozilla::dom::Element *aElement) const;
+#endif
+
+ private:
+ // Using 2^12 slots makes the Bloom filter a nice round page in
+ // size, so let's do that. We get a false positive rate of 1% or
+ // less even with several hundred things in the filter. Note that
+ // we allocate the filter lazily, because not all tree match
+ // contexts can use one effectively.
+ typedef mozilla::BloomFilter<12, nsIAtom> Filter;
+ nsAutoPtr<Filter> mFilter;
+
+ // Stack of indices to pop to. These are indices into mHashes.
+ nsTArray<uint32_t> mPopTargets;
+
+ // List of hashes; this is what we pop using mPopTargets. We store
+ // hashes of our ancestor element tag names, ids, and classes in
+ // here.
+ nsTArray<uint32_t> mHashes;
+
+ // A debug-only stack of Elements for use in assertions
+#ifdef DEBUG
+ nsTArray<mozilla::dom::Element*> mElements;
+#endif
+};
+
+/**
+ * A |TreeMatchContext| has data about a matching operation. The
+ * data are not node-specific but are invariants of the DOM tree the
+ * nodes being matched against are in.
+ *
+ * Most of the members are in parameters to selector matching. The
+ * one out parameter is mHaveRelevantLink. Consumers that use a
+ * TreeMatchContext for more than one matching operation and care
+ * about :visited and mHaveRelevantLink need to
+ * ResetForVisitedMatching() and ResetForUnvisitedMatching() as
+ * needed.
+ */
+struct MOZ_STACK_CLASS TreeMatchContext {
+ // Reset this context for matching for the style-if-:visited.
+ void ResetForVisitedMatching() {
+ NS_PRECONDITION(mForStyling, "Why is this being called?");
+ mHaveRelevantLink = false;
+ mVisitedHandling = nsRuleWalker::eRelevantLinkVisited;
+ }
+
+ void ResetForUnvisitedMatching() {
+ NS_PRECONDITION(mForStyling, "Why is this being called?");
+ mHaveRelevantLink = false;
+ mVisitedHandling = nsRuleWalker::eRelevantLinkUnvisited;
+ }
+
+ void SetHaveRelevantLink() { mHaveRelevantLink = true; }
+ bool HaveRelevantLink() const { return mHaveRelevantLink; }
+
+ nsRuleWalker::VisitedHandlingType VisitedHandling() const
+ {
+ return mVisitedHandling;
+ }
+
+ void AddScopeElement(mozilla::dom::Element* aElement) {
+ NS_PRECONDITION(mHaveSpecifiedScope,
+ "Should be set before calling AddScopeElement()");
+ mScopes.AppendElement(aElement);
+ }
+ bool IsScopeElement(mozilla::dom::Element* aElement) const {
+ return mScopes.Contains(aElement);
+ }
+ void SetHasSpecifiedScope() {
+ mHaveSpecifiedScope = true;
+ }
+ bool HasSpecifiedScope() const {
+ return mHaveSpecifiedScope;
+ }
+
+ /**
+ * Initialize the ancestor filter and list of style scopes. If aElement is
+ * not null, it and all its ancestors will be passed to
+ * mAncestorFilter.PushAncestor and PushStyleScope, starting from the root and
+ * going down the tree. Must only be called for elements in a document.
+ */
+ void InitAncestors(mozilla::dom::Element *aElement);
+
+ /**
+ * Like InitAncestors, but only initializes the style scope list, not the
+ * ancestor filter. May be called for elements outside a document.
+ */
+ void InitStyleScopes(mozilla::dom::Element* aElement);
+
+ void PushStyleScope(mozilla::dom::Element* aElement)
+ {
+ NS_PRECONDITION(aElement, "aElement must not be null");
+ if (aElement->IsScopedStyleRoot()) {
+ mStyleScopes.AppendElement(aElement);
+ }
+ }
+
+ void PopStyleScope(mozilla::dom::Element* aElement)
+ {
+ NS_PRECONDITION(aElement, "aElement must not be null");
+ if (mStyleScopes.SafeLastElement(nullptr) == aElement) {
+ mStyleScopes.TruncateLength(mStyleScopes.Length() - 1);
+ }
+ }
+
+ bool PopStyleScopeForSelectorMatching(mozilla::dom::Element* aElement)
+ {
+ NS_ASSERTION(mForScopedStyle, "only call PopStyleScopeForSelectorMatching "
+ "when mForScopedStyle is true");
+
+ if (!mCurrentStyleScope) {
+ return false;
+ }
+ if (mCurrentStyleScope == aElement) {
+ mCurrentStyleScope = nullptr;
+ }
+ return true;
+ }
+
+#ifdef DEBUG
+ void AssertHasAllStyleScopes(mozilla::dom::Element* aElement) const;
+#endif
+
+ bool SetStyleScopeForSelectorMatching(mozilla::dom::Element* aSubject,
+ mozilla::dom::Element* aScope)
+ {
+#ifdef DEBUG
+ AssertHasAllStyleScopes(aSubject);
+#endif
+
+ mForScopedStyle = !!aScope;
+ if (!aScope) {
+ // This is not for a scoped style sheet; return true, as we want
+ // selector matching to proceed.
+ mCurrentStyleScope = nullptr;
+ return true;
+ }
+ if (aScope == aSubject) {
+ // Although the subject is the same element as the scope, as soon
+ // as we continue with selector matching up the tree we don't want
+ // to match any more elements. So we return true to indicate that
+ // we want to do the initial selector matching, but set
+ // mCurrentStyleScope to null so that no ancestor elements will match.
+ mCurrentStyleScope = nullptr;
+ return true;
+ }
+ if (mStyleScopes.Contains(aScope)) {
+ // mStyleScopes contains all of the scope elements that are ancestors of
+ // aSubject, so if aScope is in mStyleScopes, then we do want selector
+ // matching to proceed.
+ mCurrentStyleScope = aScope;
+ return true;
+ }
+ // Otherwise, we're not in the scope, and we don't want to proceed
+ // with selector matching.
+ mCurrentStyleScope = nullptr;
+ return false;
+ }
+
+ bool IsWithinStyleScopeForSelectorMatching() const
+ {
+ NS_ASSERTION(mForScopedStyle, "only call IsWithinScopeForSelectorMatching "
+ "when mForScopedStyle is true");
+ return mCurrentStyleScope;
+ }
+
+ /* Helper class for maintaining the ancestor state */
+ class MOZ_RAII AutoAncestorPusher {
+ public:
+ explicit AutoAncestorPusher(TreeMatchContext& aTreeMatchContext
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mPushedAncestor(false)
+ , mPushedStyleScope(false)
+ , mTreeMatchContext(aTreeMatchContext)
+ , mElement(nullptr)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ }
+
+ void PushAncestorAndStyleScope(mozilla::dom::Element* aElement) {
+ MOZ_ASSERT(!mElement);
+ if (aElement) {
+ mElement = aElement;
+ mPushedAncestor = true;
+ mPushedStyleScope = true;
+ mTreeMatchContext.mAncestorFilter.PushAncestor(aElement);
+ mTreeMatchContext.PushStyleScope(aElement);
+ }
+ }
+
+ void PushAncestorAndStyleScope(nsIContent* aContent) {
+ if (aContent && aContent->IsElement()) {
+ PushAncestorAndStyleScope(aContent->AsElement());
+ }
+ }
+
+ void PushStyleScope(mozilla::dom::Element* aElement) {
+ MOZ_ASSERT(!mElement);
+ if (aElement) {
+ mElement = aElement;
+ mPushedStyleScope = true;
+ mTreeMatchContext.PushStyleScope(aElement);
+ }
+ }
+
+ void PushStyleScope(nsIContent* aContent) {
+ if (aContent && aContent->IsElement()) {
+ PushStyleScope(aContent->AsElement());
+ }
+ }
+
+ ~AutoAncestorPusher() {
+ if (mPushedAncestor) {
+ mTreeMatchContext.mAncestorFilter.PopAncestor();
+ }
+ if (mPushedStyleScope) {
+ mTreeMatchContext.PopStyleScope(mElement);
+ }
+ }
+
+ private:
+ bool mPushedAncestor;
+ bool mPushedStyleScope;
+ TreeMatchContext& mTreeMatchContext;
+ mozilla::dom::Element* mElement;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+ };
+
+ /* Helper class for tracking whether we're skipping the ApplyStyleFixups
+ * code for special cases where child element style is modified based on
+ * parent display value.
+ *
+ * The optional second parameter aSkipParentDisplayBasedStyleFixup allows
+ * this class to be instantiated but only conditionally activated (e.g.
+ * in cases where we may or may not want to be skipping flex/grid-item
+ * style fixup for a particular chunk of code).
+ */
+ class MOZ_RAII AutoParentDisplayBasedStyleFixupSkipper {
+ public:
+ explicit AutoParentDisplayBasedStyleFixupSkipper(TreeMatchContext& aTreeMatchContext,
+ bool aSkipParentDisplayBasedStyleFixup = true
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mAutoRestorer(aTreeMatchContext.mSkippingParentDisplayBasedStyleFixup)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ if (aSkipParentDisplayBasedStyleFixup) {
+ aTreeMatchContext.mSkippingParentDisplayBasedStyleFixup = true;
+ }
+ }
+
+ private:
+ mozilla::AutoRestore<bool> mAutoRestorer;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+ };
+
+ // Is this matching operation for the creation of a style context?
+ // (If it is, we need to set slow selector bits on nodes indicating
+ // that certain restyling needs to happen.)
+ const bool mForStyling;
+
+ private:
+ // When mVisitedHandling is eRelevantLinkUnvisited, this is set to true if a
+ // relevant link (see explanation in definition of VisitedHandling enum) was
+ // encountered during the matching process, which means that matching needs
+ // to be rerun with eRelevantLinkVisited. Otherwise, its behavior is
+ // undefined (it might get set appropriately, or might not).
+ bool mHaveRelevantLink;
+
+ // If true, then our contextual reference element set is specified,
+ // and is given by mScopes.
+ bool mHaveSpecifiedScope;
+
+ // How matching should be performed. See the documentation for
+ // nsRuleWalker::VisitedHandlingType.
+ nsRuleWalker::VisitedHandlingType mVisitedHandling;
+
+ // For matching :scope
+ AutoTArray<mozilla::dom::Element*, 1> mScopes;
+ public:
+ // The document we're working with.
+ nsIDocument* const mDocument;
+
+ // Root of scoped stylesheet (set and unset by the supplier of the
+ // scoped stylesheet).
+ nsIContent* mScopedRoot;
+
+ // Whether our document is HTML (as opposed to XML of some sort,
+ // including XHTML).
+ // XXX XBL2 issue: Should we be caching this? What should it be for XBL2?
+ const bool mIsHTMLDocument;
+
+ // Possibly remove use of mCompatMode in SelectorMatches?
+ // XXX XBL2 issue: Should we be caching this? What should it be for XBL2?
+ const nsCompatibility mCompatMode;
+
+ // The nth-index cache we should use
+ nsNthIndexCache mNthIndexCache;
+
+ // An ancestor filter
+ AncestorFilter mAncestorFilter;
+
+ // Whether this document is using PB mode
+ bool mUsingPrivateBrowsing;
+
+ // Whether we're currently skipping the part of ApplyStyleFixups that changes
+ // style of child elements based on their parent's display value
+ // (e.g. for children of elements that have a mandatory frame-type for which
+ // we ignore "display:flex/grid").
+ bool mSkippingParentDisplayBasedStyleFixup;
+
+ // Whether this TreeMatchContext is being used with an nsCSSRuleProcessor
+ // for an HTML5 scoped style sheet.
+ bool mForScopedStyle;
+
+ enum MatchVisited {
+ eNeverMatchVisited,
+ eMatchVisitedDefault
+ };
+
+ // List of ancestor elements that define a style scope (due to having a
+ // <style scoped> child).
+ AutoTArray<mozilla::dom::Element*, 1> mStyleScopes;
+
+ // The current style scope element for selector matching.
+ mozilla::dom::Element* mCurrentStyleScope;
+
+ // Constructor to use when creating a tree match context for styling
+ TreeMatchContext(bool aForStyling,
+ nsRuleWalker::VisitedHandlingType aVisitedHandling,
+ nsIDocument* aDocument,
+ MatchVisited aMatchVisited = eMatchVisitedDefault)
+ : mForStyling(aForStyling)
+ , mHaveRelevantLink(false)
+ , mHaveSpecifiedScope(false)
+ , mVisitedHandling(aVisitedHandling)
+ , mDocument(aDocument)
+ , mScopedRoot(nullptr)
+ , mIsHTMLDocument(aDocument->IsHTMLDocument())
+ , mCompatMode(aDocument->GetCompatibilityMode())
+ , mUsingPrivateBrowsing(false)
+ , mSkippingParentDisplayBasedStyleFixup(false)
+ , mForScopedStyle(false)
+ , mCurrentStyleScope(nullptr)
+ {
+ if (aMatchVisited != eNeverMatchVisited) {
+ nsILoadContext* loadContext = mDocument->GetLoadContext();
+ if (loadContext) {
+ mUsingPrivateBrowsing = loadContext->UsePrivateBrowsing();
+ }
+ }
+ }
+};
+
+struct MOZ_STACK_CLASS RuleProcessorData {
+ RuleProcessorData(nsPresContext* aPresContext,
+ nsRuleWalker* aRuleWalker)
+ : mPresContext(aPresContext),
+ mRuleWalker(aRuleWalker),
+ mScope(nullptr)
+ {
+ NS_PRECONDITION(mPresContext, "Must have prescontext");
+ }
+
+ nsPresContext* const mPresContext;
+ nsRuleWalker* const mRuleWalker; // Used to add rules to our results.
+ mozilla::dom::Element* mScope;
+};
+
+struct MOZ_STACK_CLASS ElementDependentRuleProcessorData :
+ public RuleProcessorData {
+ ElementDependentRuleProcessorData(nsPresContext* aPresContext,
+ mozilla::dom::Element* aElement,
+ nsRuleWalker* aRuleWalker,
+ TreeMatchContext& aTreeMatchContext)
+ : RuleProcessorData(aPresContext, aRuleWalker)
+ , mElement(aElement)
+ , mTreeMatchContext(aTreeMatchContext)
+ {
+ NS_ASSERTION(aElement, "null element leaked into SelectorMatches");
+ NS_ASSERTION(aElement->OwnerDoc(), "Document-less node here?");
+ NS_PRECONDITION(aTreeMatchContext.mForStyling == !!aRuleWalker,
+ "Should be styling if and only if we have a rule walker");
+ }
+
+ mozilla::dom::Element* const mElement; // weak ref, must not be null
+ TreeMatchContext& mTreeMatchContext;
+};
+
+struct MOZ_STACK_CLASS ElementRuleProcessorData :
+ public ElementDependentRuleProcessorData {
+ ElementRuleProcessorData(nsPresContext* aPresContext,
+ mozilla::dom::Element* aElement,
+ nsRuleWalker* aRuleWalker,
+ TreeMatchContext& aTreeMatchContext)
+ : ElementDependentRuleProcessorData(aPresContext, aElement, aRuleWalker,
+ aTreeMatchContext)
+ {
+ NS_PRECONDITION(aTreeMatchContext.mForStyling, "Styling here!");
+ NS_PRECONDITION(aRuleWalker, "Must have rule walker");
+ }
+};
+
+struct MOZ_STACK_CLASS PseudoElementRuleProcessorData :
+ public ElementDependentRuleProcessorData {
+ PseudoElementRuleProcessorData(nsPresContext* aPresContext,
+ mozilla::dom::Element* aParentElement,
+ nsRuleWalker* aRuleWalker,
+ mozilla::CSSPseudoElementType aPseudoType,
+ TreeMatchContext& aTreeMatchContext,
+ mozilla::dom::Element* aPseudoElement)
+ : ElementDependentRuleProcessorData(aPresContext, aParentElement, aRuleWalker,
+ aTreeMatchContext),
+ mPseudoType(aPseudoType),
+ mPseudoElement(aPseudoElement)
+ {
+ NS_PRECONDITION(aPseudoType < mozilla::CSSPseudoElementType::Count,
+ "invalid aPseudoType value");
+ NS_PRECONDITION(aTreeMatchContext.mForStyling, "Styling here!");
+ NS_PRECONDITION(aRuleWalker, "Must have rule walker");
+ }
+
+ mozilla::CSSPseudoElementType mPseudoType;
+ mozilla::dom::Element* const mPseudoElement; // weak ref
+};
+
+struct MOZ_STACK_CLASS AnonBoxRuleProcessorData : public RuleProcessorData {
+ AnonBoxRuleProcessorData(nsPresContext* aPresContext,
+ nsIAtom* aPseudoTag,
+ nsRuleWalker* aRuleWalker)
+ : RuleProcessorData(aPresContext, aRuleWalker),
+ mPseudoTag(aPseudoTag)
+ {
+ NS_PRECONDITION(aPseudoTag, "Must have pseudo tag");
+ NS_PRECONDITION(aRuleWalker, "Must have rule walker");
+ }
+
+ nsIAtom* mPseudoTag;
+};
+
+#ifdef MOZ_XUL
+struct MOZ_STACK_CLASS XULTreeRuleProcessorData :
+ public ElementDependentRuleProcessorData {
+ XULTreeRuleProcessorData(nsPresContext* aPresContext,
+ mozilla::dom::Element* aParentElement,
+ nsRuleWalker* aRuleWalker,
+ nsIAtom* aPseudoTag,
+ nsICSSPseudoComparator* aComparator,
+ TreeMatchContext& aTreeMatchContext)
+ : ElementDependentRuleProcessorData(aPresContext, aParentElement,
+ aRuleWalker, aTreeMatchContext),
+ mPseudoTag(aPseudoTag),
+ mComparator(aComparator)
+ {
+ NS_PRECONDITION(aPseudoTag, "null pointer");
+ NS_PRECONDITION(aRuleWalker, "Must have rule walker");
+ NS_PRECONDITION(aComparator, "must have a comparator");
+ NS_PRECONDITION(aTreeMatchContext.mForStyling, "Styling here!");
+ }
+
+ nsIAtom* mPseudoTag;
+ nsICSSPseudoComparator* mComparator;
+};
+#endif
+
+struct MOZ_STACK_CLASS StateRuleProcessorData :
+ public ElementDependentRuleProcessorData {
+ StateRuleProcessorData(nsPresContext* aPresContext,
+ mozilla::dom::Element* aElement,
+ mozilla::EventStates aStateMask,
+ TreeMatchContext& aTreeMatchContext)
+ : ElementDependentRuleProcessorData(aPresContext, aElement, nullptr,
+ aTreeMatchContext),
+ mStateMask(aStateMask)
+ {
+ NS_PRECONDITION(!aTreeMatchContext.mForStyling, "Not styling here!");
+ }
+ // |HasStateDependentStyle| for which state(s)?
+ // Constants defined in mozilla/EventStates.h .
+ const mozilla::EventStates mStateMask;
+};
+
+struct MOZ_STACK_CLASS PseudoElementStateRuleProcessorData :
+ public StateRuleProcessorData {
+ PseudoElementStateRuleProcessorData(nsPresContext* aPresContext,
+ mozilla::dom::Element* aElement,
+ mozilla::EventStates aStateMask,
+ mozilla::CSSPseudoElementType aPseudoType,
+ TreeMatchContext& aTreeMatchContext,
+ mozilla::dom::Element* aPseudoElement)
+ : StateRuleProcessorData(aPresContext, aElement, aStateMask,
+ aTreeMatchContext),
+ mPseudoType(aPseudoType),
+ mPseudoElement(aPseudoElement)
+ {
+ NS_PRECONDITION(!aTreeMatchContext.mForStyling, "Not styling here!");
+ }
+
+ // We kind of want to inherit from both StateRuleProcessorData and
+ // PseudoElementRuleProcessorData. Instead we've just copied those
+ // members from PseudoElementRuleProcessorData to this struct.
+ mozilla::CSSPseudoElementType mPseudoType;
+ mozilla::dom::Element* const mPseudoElement; // weak ref
+};
+
+struct MOZ_STACK_CLASS AttributeRuleProcessorData :
+ public ElementDependentRuleProcessorData {
+ AttributeRuleProcessorData(nsPresContext* aPresContext,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ bool aAttrHasChanged,
+ const nsAttrValue* aOtherValue,
+ TreeMatchContext& aTreeMatchContext)
+ : ElementDependentRuleProcessorData(aPresContext, aElement, nullptr,
+ aTreeMatchContext),
+ mNameSpaceID(aNameSpaceID),
+ mAttribute(aAttribute),
+ mOtherValue(aOtherValue),
+ mModType(aModType),
+ mAttrHasChanged(aAttrHasChanged)
+ {
+ NS_PRECONDITION(!aTreeMatchContext.mForStyling, "Not styling here!");
+ }
+ int32_t mNameSpaceID; // Namespace of the attribute involved.
+ nsIAtom* mAttribute; // |HasAttributeDependentStyle| for which attribute?
+ // non-null if we have the value.
+ const nsAttrValue* mOtherValue;
+ int32_t mModType; // The type of modification (see nsIDOMMutationEvent).
+ bool mAttrHasChanged; // Whether the attribute has already changed.
+};
+
+#endif /* !defined(nsRuleProcessorData_h_) */
diff --git a/layout/style/nsRuleWalker.h b/layout/style/nsRuleWalker.h
new file mode 100644
index 000000000..ea0783722
--- /dev/null
+++ b/layout/style/nsRuleWalker.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * a class that walks the lexicographic tree of rule nodes as style
+ * rules are matched
+ */
+
+#ifndef nsRuleWalker_h_
+#define nsRuleWalker_h_
+
+#include "nsRuleNode.h"
+#include "nsIStyleRule.h"
+#include "Declaration.h"
+#include "nsQueryObject.h"
+
+class nsRuleWalker {
+public:
+ nsRuleNode* CurrentNode() { return mCurrent; }
+ void SetCurrentNode(nsRuleNode* aNode) {
+ NS_ASSERTION(aNode, "Must have node here!");
+ mCurrent = aNode;
+ }
+
+ nsPresContext* PresContext() const { return mRoot->PresContext(); }
+
+protected:
+ void DoForward(nsIStyleRule* aRule) {
+ mCurrent = mCurrent->Transition(aRule, mLevel, mImportance);
+ NS_POSTCONDITION(mCurrent, "Transition messed up");
+ }
+
+public:
+ void Forward(nsIStyleRule* aRule) {
+ NS_PRECONDITION(!RefPtr<mozilla::css::Declaration>(do_QueryObject(aRule)),
+ "Calling the wrong Forward() overload");
+ DoForward(aRule);
+ }
+ void Forward(mozilla::css::Declaration* aRule) {
+ DoForward(aRule);
+ mCheckForImportantRules =
+ mCheckForImportantRules && !aRule->HasImportantData();
+ }
+ // ForwardOnPossiblyCSSRule should only be used by callers that have
+ // an explicit list of rules they need to walk, with the list
+ // already containing any important rules they care about.
+ void ForwardOnPossiblyCSSRule(nsIStyleRule* aRule) {
+ DoForward(aRule);
+ }
+
+ void Reset() { mCurrent = mRoot; }
+
+ bool AtRoot() { return mCurrent == mRoot; }
+
+ void SetLevel(mozilla::SheetType aLevel, bool aImportance,
+ bool aCheckForImportantRules) {
+ NS_ASSERTION(!aCheckForImportantRules || !aImportance,
+ "Shouldn't be checking for important rules while walking "
+ "important rules");
+ mLevel = aLevel;
+ mImportance = aImportance;
+ mCheckForImportantRules = aCheckForImportantRules;
+ }
+ mozilla::SheetType GetLevel() const { return mLevel; }
+ bool GetImportance() const { return mImportance; }
+ bool GetCheckForImportantRules() const { return mCheckForImportantRules; }
+
+ bool AuthorStyleDisabled() const { return mAuthorStyleDisabled; }
+
+ // We define the visited-relevant link to be the link that is the
+ // nearest self-or-ancestor to the node being matched.
+ enum VisitedHandlingType {
+ // Do rule matching as though all links are unvisited.
+ eRelevantLinkUnvisited,
+ // Do rule matching as though the relevant link is visited and all
+ // other links are unvisited.
+ eRelevantLinkVisited,
+ // Do rule matching as though a rule should match if it would match
+ // given any set of visitedness states. (used by users other than
+ // nsRuleWalker)
+ eLinksVisitedOrUnvisited
+ };
+
+private:
+ nsRuleNode* mCurrent; // Our current position. Never null.
+ nsRuleNode* mRoot; // The root of the tree we're walking.
+ mozilla::SheetType mLevel;
+ bool mImportance;
+ bool mCheckForImportantRules; // If true, check for important rules as
+ // we walk and set to false if we find
+ // one.
+ bool mAuthorStyleDisabled;
+
+public:
+ nsRuleWalker(nsRuleNode* aRoot, bool aAuthorStyleDisabled)
+ : mCurrent(aRoot)
+ , mRoot(aRoot)
+ , mAuthorStyleDisabled(aAuthorStyleDisabled)
+ {
+ NS_ASSERTION(mCurrent, "Caller screwed up and gave us null node");
+ MOZ_COUNT_CTOR(nsRuleWalker);
+ }
+ ~nsRuleWalker() { MOZ_COUNT_DTOR(nsRuleWalker); }
+};
+
+#endif /* !defined(nsRuleWalker_h_) */
diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h
new file mode 100644
index 000000000..ee78dcb64
--- /dev/null
+++ b/layout/style/nsStyleConsts.h
@@ -0,0 +1,1289 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* constants used in the style struct data provided by nsStyleContext */
+
+#ifndef nsStyleConsts_h___
+#define nsStyleConsts_h___
+
+#include "gfxRect.h"
+#include "nsFont.h"
+#include "mozilla/MacroArgs.h" // for MOZ_CONCAT
+#include "X11UndefineNone.h"
+
+// XXX fold this into nsStyleContext and group by nsStyleXXX struct
+
+namespace mozilla {
+namespace css {
+typedef mozilla::Side Side;
+} // namespace css
+
+// Creates a for loop that walks over the four mozilla::css::Side values.
+// We use an int32_t helper variable (instead of a Side) for our loop counter,
+// to avoid triggering undefined behavior just before we exit the loop (at
+// which point the counter is incremented beyond the largest valid Side value).
+#define NS_FOR_CSS_SIDES(var_) \
+ int32_t MOZ_CONCAT(var_,__LINE__) = NS_SIDE_TOP; \
+ for (mozilla::css::Side var_; \
+ MOZ_CONCAT(var_,__LINE__) <= NS_SIDE_LEFT && \
+ ((var_ = mozilla::css::Side(MOZ_CONCAT(var_,__LINE__))), true); \
+ MOZ_CONCAT(var_,__LINE__)++)
+
+static inline css::Side operator++(css::Side& side, int) {
+ NS_PRECONDITION(side >= NS_SIDE_TOP &&
+ side <= NS_SIDE_LEFT, "Out of range side");
+ side = css::Side(side + 1);
+ return side;
+}
+
+#define NS_FOR_CSS_FULL_CORNERS(var_) for (int32_t var_ = 0; var_ < 4; ++var_)
+
+// Indices into "half corner" arrays (nsStyleCorners e.g.)
+#define NS_CORNER_TOP_LEFT_X 0
+#define NS_CORNER_TOP_LEFT_Y 1
+#define NS_CORNER_TOP_RIGHT_X 2
+#define NS_CORNER_TOP_RIGHT_Y 3
+#define NS_CORNER_BOTTOM_RIGHT_X 4
+#define NS_CORNER_BOTTOM_RIGHT_Y 5
+#define NS_CORNER_BOTTOM_LEFT_X 6
+#define NS_CORNER_BOTTOM_LEFT_Y 7
+
+#define NS_FOR_CSS_HALF_CORNERS(var_) for (int32_t var_ = 0; var_ < 8; ++var_)
+
+// The results of these conversion macros are exhaustively checked in
+// nsStyleCoord.cpp.
+// Arguments must not have side effects.
+
+#define NS_HALF_CORNER_IS_X(var_) (!((var_)%2))
+#define NS_HALF_TO_FULL_CORNER(var_) ((var_)/2)
+#define NS_FULL_TO_HALF_CORNER(var_, vert_) ((var_)*2 + !!(vert_))
+
+#define NS_SIDE_IS_VERTICAL(side_) ((side_) % 2)
+#define NS_SIDE_TO_FULL_CORNER(side_, second_) \
+ (((side_) + !!(second_)) % 4)
+#define NS_SIDE_TO_HALF_CORNER(side_, second_, parallel_) \
+ ((((side_) + !!(second_))*2 + ((side_) + !(parallel_))%2) % 8)
+
+// Basic shapes
+enum class StyleBasicShapeType : uint8_t {
+ Polygon,
+ Circle,
+ Ellipse,
+ Inset,
+};
+
+// 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,
+};
+
+// clear
+enum class StyleClear : uint8_t {
+ None = 0,
+ Left,
+ Right,
+ InlineStart,
+ InlineEnd,
+ Both,
+ // StyleClear::Line can be added to one of the other values in layout
+ // so it needs to use a bit value that none of the other values can have.
+ Line = 8,
+ Max = 13 // Max = (Both | Line)
+};
+
+// clip-path geometry box
+enum class StyleClipPathGeometryBox : uint8_t {
+ NoBox,
+ Content,
+ Padding,
+ Border,
+ Margin,
+ Fill,
+ Stroke,
+ View,
+};
+
+// fill-rule
+enum class StyleFillRule : uint8_t {
+ Nonzero,
+ Evenodd,
+};
+
+// float
+// https://developer.mozilla.org/en-US/docs/Web/CSS/float
+enum class StyleFloat : uint8_t {
+ None,
+ Left,
+ Right,
+ InlineStart,
+ InlineEnd
+};
+
+// float-edge
+enum class StyleFloatEdge : uint8_t {
+ ContentBox,
+ MarginBox,
+};
+
+// shape-box for shape-outside
+enum class StyleShapeOutsideShapeBox : uint8_t {
+ NoBox,
+ Content,
+ Padding,
+ Border,
+ Margin
+};
+
+// Shape source type
+enum class StyleShapeSourceType : uint8_t {
+ None,
+ URL,
+ Shape,
+ Box,
+};
+
+// user-focus
+enum class StyleUserFocus : uint8_t {
+ None,
+ Ignore,
+ Normal,
+ SelectAll,
+ SelectBefore,
+ SelectAfter,
+ SelectSame,
+ SelectMenu,
+};
+
+// user-select
+enum class StyleUserSelect : uint8_t {
+ None,
+ Text,
+ Element,
+ Elements,
+ All,
+ Toggle,
+ TriState,
+ Auto, // internal value - please use nsFrame::IsSelectable()
+ MozAll, // force selection of all children, unless an ancestor has NONE set - bug 48096
+ MozText, // Like TEXT, except that it won't get overridden by ancestors having ALL.
+};
+
+// user-input
+enum class StyleUserInput : uint8_t {
+ None,
+ Enabled,
+ Disabled,
+ Auto,
+};
+
+// user-modify
+enum class StyleUserModify : uint8_t {
+ ReadOnly,
+ ReadWrite,
+ WriteOnly,
+};
+
+// -moz-window-dragging
+enum class StyleWindowDragging : uint8_t {
+ Default,
+ Drag,
+ NoDrag,
+};
+
+// orient
+enum class StyleOrient : uint8_t {
+ Inline,
+ Block,
+ Horizontal,
+ Vertical,
+};
+
+#define NS_RADIUS_FARTHEST_SIDE 0
+#define NS_RADIUS_CLOSEST_SIDE 1
+
+// stack-sizing
+#define NS_STYLE_STACK_SIZING_IGNORE 0
+#define NS_STYLE_STACK_SIZING_STRETCH_TO_FIT 1
+
+// Azimuth - See nsStyleAural
+#define NS_STYLE_AZIMUTH_LEFT_SIDE 0x00
+#define NS_STYLE_AZIMUTH_FAR_LEFT 0x01
+#define NS_STYLE_AZIMUTH_LEFT 0x02
+#define NS_STYLE_AZIMUTH_CENTER_LEFT 0x03
+#define NS_STYLE_AZIMUTH_CENTER 0x04
+#define NS_STYLE_AZIMUTH_CENTER_RIGHT 0x05
+#define NS_STYLE_AZIMUTH_RIGHT 0x06
+#define NS_STYLE_AZIMUTH_FAR_RIGHT 0x07
+#define NS_STYLE_AZIMUTH_RIGHT_SIDE 0x08
+#define NS_STYLE_AZIMUTH_BEHIND 0x80 // bits
+#define NS_STYLE_AZIMUTH_LEFTWARDS 0x10 // bits
+#define NS_STYLE_AZIMUTH_RIGHTWARDS 0x20 // bits
+
+// See nsStyleAural
+#define NS_STYLE_ELEVATION_BELOW 1
+#define NS_STYLE_ELEVATION_LEVEL 2
+#define NS_STYLE_ELEVATION_ABOVE 3
+#define NS_STYLE_ELEVATION_HIGHER 4
+#define NS_STYLE_ELEVATION_LOWER 5
+
+// See nsStyleAural
+#define NS_STYLE_PITCH_X_LOW 1
+#define NS_STYLE_PITCH_LOW 2
+#define NS_STYLE_PITCH_MEDIUM 3
+#define NS_STYLE_PITCH_HIGH 4
+#define NS_STYLE_PITCH_X_HIGH 5
+
+// See nsStyleAural
+#define NS_STYLE_SPEAK_NONE 0
+#define NS_STYLE_SPEAK_NORMAL 1
+#define NS_STYLE_SPEAK_SPELL_OUT 2
+
+// See nsStyleAural
+#define NS_STYLE_SPEAK_HEADER_ONCE 0
+#define NS_STYLE_SPEAK_HEADER_ALWAYS 1
+
+// See nsStyleAural
+#define NS_STYLE_SPEAK_NUMERAL_DIGITS 0
+#define NS_STYLE_SPEAK_NUMERAL_CONTINUOUS 1
+
+// See nsStyleAural
+#define NS_STYLE_SPEAK_PUNCTUATION_NONE 0
+#define NS_STYLE_SPEAK_PUNCTUATION_CODE 1
+
+// See nsStyleAural
+#define NS_STYLE_SPEECH_RATE_X_SLOW 0
+#define NS_STYLE_SPEECH_RATE_SLOW 1
+#define NS_STYLE_SPEECH_RATE_MEDIUM 2
+#define NS_STYLE_SPEECH_RATE_FAST 3
+#define NS_STYLE_SPEECH_RATE_X_FAST 4
+#define NS_STYLE_SPEECH_RATE_FASTER 10
+#define NS_STYLE_SPEECH_RATE_SLOWER 11
+
+// See nsStyleAural
+#define NS_STYLE_VOLUME_SILENT 0
+#define NS_STYLE_VOLUME_X_SOFT 1
+#define NS_STYLE_VOLUME_SOFT 2
+#define NS_STYLE_VOLUME_MEDIUM 3
+#define NS_STYLE_VOLUME_LOUD 4
+#define NS_STYLE_VOLUME_X_LOUD 5
+
+// See nsStyleColor
+#define NS_STYLE_COLOR_INHERIT_FROM_BODY 2 /* Can't come from CSS directly */
+
+// See nsStyleColor
+#define NS_COLOR_CURRENTCOLOR -1
+#define NS_COLOR_MOZ_DEFAULT_COLOR -2
+#define NS_COLOR_MOZ_DEFAULT_BACKGROUND_COLOR -3
+#define NS_COLOR_MOZ_HYPERLINKTEXT -4
+#define NS_COLOR_MOZ_VISITEDHYPERLINKTEXT -5
+#define NS_COLOR_MOZ_ACTIVEHYPERLINKTEXT -6
+// Only valid as paints in SVG glyphs
+#define NS_COLOR_CONTEXT_FILL -7
+#define NS_COLOR_CONTEXT_STROKE -8
+
+// See nsStyleDisplay
+#define NS_STYLE_WILL_CHANGE_STACKING_CONTEXT (1<<0)
+#define NS_STYLE_WILL_CHANGE_TRANSFORM (1<<1)
+#define NS_STYLE_WILL_CHANGE_SCROLL (1<<2)
+#define NS_STYLE_WILL_CHANGE_OPACITY (1<<3)
+#define NS_STYLE_WILL_CHANGE_FIXPOS_CB (1<<4)
+#define NS_STYLE_WILL_CHANGE_ABSPOS_CB (1<<5)
+
+// See AnimationEffectReadOnly.webidl
+// and mozilla/dom/AnimationEffectReadOnlyBinding.h
+namespace dom {
+enum class PlaybackDirection : uint32_t;
+enum class FillMode : uint32_t;
+}
+
+// See nsStyleDisplay
+#define NS_STYLE_ANIMATION_ITERATION_COUNT_INFINITE 0
+
+// See nsStyleDisplay
+#define NS_STYLE_ANIMATION_PLAY_STATE_RUNNING 0
+#define NS_STYLE_ANIMATION_PLAY_STATE_PAUSED 1
+
+// See nsStyleImageLayers
+#define NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL 0
+#define NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED 1
+#define NS_STYLE_IMAGELAYER_ATTACHMENT_LOCAL 2
+
+// See nsStyleImageLayers
+// Code depends on these constants having the same values as IMAGELAYER_ORIGIN_*
+#define NS_STYLE_IMAGELAYER_CLIP_BORDER 0
+#define NS_STYLE_IMAGELAYER_CLIP_PADDING 1
+#define NS_STYLE_IMAGELAYER_CLIP_CONTENT 2
+// One extra constant which does not exist in IMAGELAYER_ORIGIN_*
+#define NS_STYLE_IMAGELAYER_CLIP_TEXT 3
+
+// 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 NS_STYLE_IMAGELAYER_CLIP_PADDING because using that
+// causes antialiasing seams between the background and border. This
+// is a backend-only value.
+#define NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING 127
+
+// See nsStyleImageLayers
+// Code depends on these constants having the same values as BG_CLIP_*
+#define NS_STYLE_IMAGELAYER_ORIGIN_BORDER 0
+#define NS_STYLE_IMAGELAYER_ORIGIN_PADDING 1
+#define NS_STYLE_IMAGELAYER_ORIGIN_CONTENT 2
+
+// See nsStyleImageLayers
+// The parser code depends on |ing these values together.
+#define NS_STYLE_IMAGELAYER_POSITION_CENTER (1<<0)
+#define NS_STYLE_IMAGELAYER_POSITION_TOP (1<<1)
+#define NS_STYLE_IMAGELAYER_POSITION_BOTTOM (1<<2)
+#define NS_STYLE_IMAGELAYER_POSITION_LEFT (1<<3)
+#define NS_STYLE_IMAGELAYER_POSITION_RIGHT (1<<4)
+
+// See nsStyleImageLayers
+#define NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT 0x00
+#define NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X 0x01
+#define NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y 0x02
+#define NS_STYLE_IMAGELAYER_REPEAT_REPEAT 0x03
+#define NS_STYLE_IMAGELAYER_REPEAT_SPACE 0x04
+#define NS_STYLE_IMAGELAYER_REPEAT_ROUND 0x05
+
+// See nsStyleImageLayers
+#define NS_STYLE_IMAGELAYER_SIZE_CONTAIN 0
+#define NS_STYLE_IMAGELAYER_SIZE_COVER 1
+
+// Mask mode
+#define NS_STYLE_MASK_MODE_ALPHA 0
+#define NS_STYLE_MASK_MODE_LUMINANCE 1
+#define NS_STYLE_MASK_MODE_MATCH_SOURCE 2
+
+// See nsStyleBackground
+#define NS_STYLE_BG_INLINE_POLICY_EACH_BOX 0
+#define NS_STYLE_BG_INLINE_POLICY_CONTINUOUS 1
+#define NS_STYLE_BG_INLINE_POLICY_BOUNDING_BOX 2
+
+// See nsStyleTable
+#define NS_STYLE_BORDER_COLLAPSE 0
+#define NS_STYLE_BORDER_SEPARATE 1
+
+// Possible enumerated specified values of border-*-width, used by nsCSSMargin
+#define NS_STYLE_BORDER_WIDTH_THIN 0
+#define NS_STYLE_BORDER_WIDTH_MEDIUM 1
+#define NS_STYLE_BORDER_WIDTH_THICK 2
+// XXX chopping block #define NS_STYLE_BORDER_WIDTH_LENGTH_VALUE 3
+
+// See nsStyleBorder mBorderStyle
+#define NS_STYLE_BORDER_STYLE_NONE 0
+#define NS_STYLE_BORDER_STYLE_GROOVE 1
+#define NS_STYLE_BORDER_STYLE_RIDGE 2
+#define NS_STYLE_BORDER_STYLE_DOTTED 3
+#define NS_STYLE_BORDER_STYLE_DASHED 4
+#define NS_STYLE_BORDER_STYLE_SOLID 5
+#define NS_STYLE_BORDER_STYLE_DOUBLE 6
+#define NS_STYLE_BORDER_STYLE_INSET 7
+#define NS_STYLE_BORDER_STYLE_OUTSET 8
+#define NS_STYLE_BORDER_STYLE_HIDDEN 9
+#define NS_STYLE_BORDER_STYLE_AUTO 10 // for outline-style only
+
+// See nsStyleBorder mBorderImage
+#define NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH 0
+#define NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT 1
+#define NS_STYLE_BORDER_IMAGE_REPEAT_ROUND 2
+#define NS_STYLE_BORDER_IMAGE_REPEAT_SPACE 3
+
+#define NS_STYLE_BORDER_IMAGE_SLICE_NOFILL 0
+#define NS_STYLE_BORDER_IMAGE_SLICE_FILL 1
+
+// See nsStyleContent
+#define NS_STYLE_CONTENT_OPEN_QUOTE 0
+#define NS_STYLE_CONTENT_CLOSE_QUOTE 1
+#define NS_STYLE_CONTENT_NO_OPEN_QUOTE 2
+#define NS_STYLE_CONTENT_NO_CLOSE_QUOTE 3
+#define NS_STYLE_CONTENT_ALT_CONTENT 4
+
+// See nsStyleColor
+#define NS_STYLE_CURSOR_AUTO 1
+#define NS_STYLE_CURSOR_CROSSHAIR 2
+#define NS_STYLE_CURSOR_DEFAULT 3 // ie: an arrow
+#define NS_STYLE_CURSOR_POINTER 4 // for links
+#define NS_STYLE_CURSOR_MOVE 5
+#define NS_STYLE_CURSOR_E_RESIZE 6
+#define NS_STYLE_CURSOR_NE_RESIZE 7
+#define NS_STYLE_CURSOR_NW_RESIZE 8
+#define NS_STYLE_CURSOR_N_RESIZE 9
+#define NS_STYLE_CURSOR_SE_RESIZE 10
+#define NS_STYLE_CURSOR_SW_RESIZE 11
+#define NS_STYLE_CURSOR_S_RESIZE 12
+#define NS_STYLE_CURSOR_W_RESIZE 13
+#define NS_STYLE_CURSOR_TEXT 14 // ie: i-beam
+#define NS_STYLE_CURSOR_WAIT 15
+#define NS_STYLE_CURSOR_HELP 16
+#define NS_STYLE_CURSOR_COPY 17 // CSS3
+#define NS_STYLE_CURSOR_ALIAS 18
+#define NS_STYLE_CURSOR_CONTEXT_MENU 19
+#define NS_STYLE_CURSOR_CELL 20
+#define NS_STYLE_CURSOR_GRAB 21
+#define NS_STYLE_CURSOR_GRABBING 22
+#define NS_STYLE_CURSOR_SPINNING 23
+#define NS_STYLE_CURSOR_ZOOM_IN 24
+#define NS_STYLE_CURSOR_ZOOM_OUT 25
+#define NS_STYLE_CURSOR_NOT_ALLOWED 26
+#define NS_STYLE_CURSOR_COL_RESIZE 27
+#define NS_STYLE_CURSOR_ROW_RESIZE 28
+#define NS_STYLE_CURSOR_NO_DROP 29
+#define NS_STYLE_CURSOR_VERTICAL_TEXT 30
+#define NS_STYLE_CURSOR_ALL_SCROLL 31
+#define NS_STYLE_CURSOR_NESW_RESIZE 32
+#define NS_STYLE_CURSOR_NWSE_RESIZE 33
+#define NS_STYLE_CURSOR_NS_RESIZE 34
+#define NS_STYLE_CURSOR_EW_RESIZE 35
+#define NS_STYLE_CURSOR_NONE 36
+
+// See nsStyleVisibility
+#define NS_STYLE_DIRECTION_LTR 0
+#define NS_STYLE_DIRECTION_RTL 1
+
+// See nsStyleVisibility
+// NOTE: WritingModes.h depends on the particular values used here.
+#define NS_STYLE_WRITING_MODE_HORIZONTAL_TB 0
+#define NS_STYLE_WRITING_MODE_VERTICAL_RL 1
+// #define NS_STYLE_WRITING_MODE_HORIZONTAL_BT 2 // hypothetical
+#define NS_STYLE_WRITING_MODE_VERTICAL_LR 3
+
+// Single-bit flag, used in combination with VERTICAL_LR 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.)
+#define NS_STYLE_WRITING_MODE_SIDEWAYS_MASK 4
+
+#define NS_STYLE_WRITING_MODE_SIDEWAYS_RL \
+ (NS_STYLE_WRITING_MODE_VERTICAL_RL | \
+ NS_STYLE_WRITING_MODE_SIDEWAYS_MASK)
+#define NS_STYLE_WRITING_MODE_SIDEWAYS_LR \
+ (NS_STYLE_WRITING_MODE_VERTICAL_LR | \
+ NS_STYLE_WRITING_MODE_SIDEWAYS_MASK)
+
+// See nsStyleDisplay
+//
+// NOTE: Order is important! If you change it, make sure to take a look at
+// the FrameConstructorDataByDisplay stuff (both the XUL and non-XUL version),
+// and ensure it's still correct!
+enum class StyleDisplay : uint8_t {
+ None = 0,
+ Block,
+ Inline,
+ InlineBlock,
+ ListItem,
+ Table,
+ InlineTable,
+ TableRowGroup,
+ TableColumn,
+ TableColumnGroup,
+ TableHeaderGroup,
+ TableFooterGroup,
+ TableRow,
+ TableCell,
+ TableCaption,
+ Flex,
+ InlineFlex,
+ Grid,
+ InlineGrid,
+ Ruby,
+ RubyBase,
+ RubyBaseContainer,
+ RubyText,
+ RubyTextContainer,
+ Contents,
+ WebkitBox,
+ WebkitInlineBox,
+ Box,
+ InlineBox,
+#ifdef MOZ_XUL
+ XulGrid,
+ InlineXulGrid,
+ XulGridGroup,
+ XulGridLine,
+ Stack,
+ InlineStack,
+ Deck,
+ Groupbox,
+ Popup,
+#endif
+};
+
+// See nsStyleDisplay
+// If these are re-ordered, nsComputedDOMStyle::DoGetContain() and
+// nsCSSValue::AppendToString() must be updated.
+#define NS_STYLE_CONTAIN_NONE 0
+#define NS_STYLE_CONTAIN_STRICT 0x1
+#define NS_STYLE_CONTAIN_LAYOUT 0x2
+#define NS_STYLE_CONTAIN_STYLE 0x4
+#define NS_STYLE_CONTAIN_PAINT 0x8
+// NS_STYLE_CONTAIN_ALL_BITS does not correspond to a keyword.
+#define NS_STYLE_CONTAIN_ALL_BITS (NS_STYLE_CONTAIN_LAYOUT | \
+ NS_STYLE_CONTAIN_STYLE | \
+ NS_STYLE_CONTAIN_PAINT)
+
+// Shared constants for all align/justify properties (nsStylePosition):
+#define NS_STYLE_ALIGN_AUTO 0
+#define NS_STYLE_ALIGN_NORMAL 1
+#define NS_STYLE_ALIGN_START 2
+#define NS_STYLE_ALIGN_END 3
+#define NS_STYLE_ALIGN_FLEX_START 4
+#define NS_STYLE_ALIGN_FLEX_END 5
+#define NS_STYLE_ALIGN_CENTER 6
+#define NS_STYLE_ALIGN_LEFT 7
+#define NS_STYLE_ALIGN_RIGHT 8
+#define NS_STYLE_ALIGN_BASELINE 9
+#define NS_STYLE_ALIGN_LAST_BASELINE 10
+#define NS_STYLE_ALIGN_STRETCH 11
+#define NS_STYLE_ALIGN_SELF_START 12
+#define NS_STYLE_ALIGN_SELF_END 13
+#define NS_STYLE_ALIGN_SPACE_BETWEEN 14
+#define NS_STYLE_ALIGN_SPACE_AROUND 15
+#define NS_STYLE_ALIGN_SPACE_EVENLY 16
+#define NS_STYLE_ALIGN_LEGACY 0x20 // mutually exclusive w. SAFE & UNSAFE
+#define NS_STYLE_ALIGN_SAFE 0x40
+#define NS_STYLE_ALIGN_UNSAFE 0x80 // mutually exclusive w. SAFE
+#define NS_STYLE_ALIGN_FLAG_BITS 0xE0
+#define NS_STYLE_ALIGN_ALL_BITS 0xFF
+#define NS_STYLE_ALIGN_ALL_SHIFT 8
+
+#define NS_STYLE_JUSTIFY_AUTO NS_STYLE_ALIGN_AUTO
+#define NS_STYLE_JUSTIFY_NORMAL NS_STYLE_ALIGN_NORMAL
+#define NS_STYLE_JUSTIFY_START NS_STYLE_ALIGN_START
+#define NS_STYLE_JUSTIFY_END NS_STYLE_ALIGN_END
+#define NS_STYLE_JUSTIFY_FLEX_START NS_STYLE_ALIGN_FLEX_START
+#define NS_STYLE_JUSTIFY_FLEX_END NS_STYLE_ALIGN_FLEX_END
+#define NS_STYLE_JUSTIFY_CENTER NS_STYLE_ALIGN_CENTER
+#define NS_STYLE_JUSTIFY_LEFT NS_STYLE_ALIGN_LEFT
+#define NS_STYLE_JUSTIFY_RIGHT NS_STYLE_ALIGN_RIGHT
+#define NS_STYLE_JUSTIFY_BASELINE NS_STYLE_ALIGN_BASELINE
+#define NS_STYLE_JUSTIFY_LAST_BASELINE NS_STYLE_ALIGN_LAST_BASELINE
+#define NS_STYLE_JUSTIFY_STRETCH NS_STYLE_ALIGN_STRETCH
+#define NS_STYLE_JUSTIFY_SELF_START NS_STYLE_ALIGN_SELF_START
+#define NS_STYLE_JUSTIFY_SELF_END NS_STYLE_ALIGN_SELF_END
+#define NS_STYLE_JUSTIFY_SPACE_BETWEEN NS_STYLE_ALIGN_SPACE_BETWEEN
+#define NS_STYLE_JUSTIFY_SPACE_AROUND NS_STYLE_ALIGN_SPACE_AROUND
+#define NS_STYLE_JUSTIFY_SPACE_EVENLY NS_STYLE_ALIGN_SPACE_EVENLY
+#define NS_STYLE_JUSTIFY_LEGACY NS_STYLE_ALIGN_LEGACY
+#define NS_STYLE_JUSTIFY_SAFE NS_STYLE_ALIGN_SAFE
+#define NS_STYLE_JUSTIFY_UNSAFE NS_STYLE_ALIGN_UNSAFE
+#define NS_STYLE_JUSTIFY_FLAG_BITS NS_STYLE_ALIGN_FLAG_BITS
+#define NS_STYLE_JUSTIFY_ALL_BITS NS_STYLE_ALIGN_ALL_BITS
+#define NS_STYLE_JUSTIFY_ALL_SHIFT NS_STYLE_ALIGN_ALL_SHIFT
+
+// See nsStylePosition
+#define NS_STYLE_FLEX_DIRECTION_ROW 0
+#define NS_STYLE_FLEX_DIRECTION_ROW_REVERSE 1
+#define NS_STYLE_FLEX_DIRECTION_COLUMN 2
+#define NS_STYLE_FLEX_DIRECTION_COLUMN_REVERSE 3
+
+// See nsStylePosition
+#define NS_STYLE_FLEX_WRAP_NOWRAP 0
+#define NS_STYLE_FLEX_WRAP_WRAP 1
+#define NS_STYLE_FLEX_WRAP_WRAP_REVERSE 2
+
+// See nsStylePosition
+// NOTE: This is the initial value of the integer-valued 'order' property
+// (rather than an internal numerical representation of some keyword).
+#define NS_STYLE_ORDER_INITIAL 0
+
+// XXX remove in a later patch after updating flexbox code with the new names
+#define NS_STYLE_JUSTIFY_CONTENT_FLEX_START NS_STYLE_JUSTIFY_FLEX_START
+#define NS_STYLE_JUSTIFY_CONTENT_FLEX_END NS_STYLE_JUSTIFY_FLEX_END
+#define NS_STYLE_JUSTIFY_CONTENT_CENTER NS_STYLE_JUSTIFY_CENTER
+#define NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN NS_STYLE_JUSTIFY_SPACE_BETWEEN
+#define NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND NS_STYLE_JUSTIFY_SPACE_AROUND
+
+// See nsStyleFilter
+#define NS_STYLE_FILTER_NONE 0
+#define NS_STYLE_FILTER_URL 1
+#define NS_STYLE_FILTER_BLUR 2
+#define NS_STYLE_FILTER_BRIGHTNESS 3
+#define NS_STYLE_FILTER_CONTRAST 4
+#define NS_STYLE_FILTER_GRAYSCALE 5
+#define NS_STYLE_FILTER_INVERT 6
+#define NS_STYLE_FILTER_OPACITY 7
+#define NS_STYLE_FILTER_SATURATE 8
+#define NS_STYLE_FILTER_SEPIA 9
+#define NS_STYLE_FILTER_HUE_ROTATE 10
+#define NS_STYLE_FILTER_DROP_SHADOW 11
+
+// See nsStyleFont
+// We should eventually stop using the NS_STYLE_* variants here.
+#define NS_STYLE_FONT_STYLE_NORMAL NS_FONT_STYLE_NORMAL
+#define NS_STYLE_FONT_STYLE_ITALIC NS_FONT_STYLE_ITALIC
+#define NS_STYLE_FONT_STYLE_OBLIQUE NS_FONT_STYLE_OBLIQUE
+
+// See nsStyleFont
+// We should eventually stop using the NS_STYLE_* variants here.
+#define NS_STYLE_FONT_WEIGHT_NORMAL NS_FONT_WEIGHT_NORMAL
+#define NS_STYLE_FONT_WEIGHT_BOLD NS_FONT_WEIGHT_BOLD
+// The constants below appear only in style sheets and not computed style.
+#define NS_STYLE_FONT_WEIGHT_BOLDER (-1)
+#define NS_STYLE_FONT_WEIGHT_LIGHTER (-2)
+
+// See nsStyleFont
+#define NS_STYLE_FONT_SIZE_XXSMALL 0
+#define NS_STYLE_FONT_SIZE_XSMALL 1
+#define NS_STYLE_FONT_SIZE_SMALL 2
+#define NS_STYLE_FONT_SIZE_MEDIUM 3
+#define NS_STYLE_FONT_SIZE_LARGE 4
+#define NS_STYLE_FONT_SIZE_XLARGE 5
+#define NS_STYLE_FONT_SIZE_XXLARGE 6
+#define NS_STYLE_FONT_SIZE_XXXLARGE 7 // Only used by <font size="7">. Not specifiable in CSS.
+#define NS_STYLE_FONT_SIZE_LARGER 8
+#define NS_STYLE_FONT_SIZE_SMALLER 9
+
+// See nsStyleFont
+// We should eventually stop using the NS_STYLE_* variants here.
+#define NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED NS_FONT_STRETCH_ULTRA_CONDENSED
+#define NS_STYLE_FONT_STRETCH_EXTRA_CONDENSED NS_FONT_STRETCH_EXTRA_CONDENSED
+#define NS_STYLE_FONT_STRETCH_CONDENSED NS_FONT_STRETCH_CONDENSED
+#define NS_STYLE_FONT_STRETCH_SEMI_CONDENSED NS_FONT_STRETCH_SEMI_CONDENSED
+#define NS_STYLE_FONT_STRETCH_NORMAL NS_FONT_STRETCH_NORMAL
+#define NS_STYLE_FONT_STRETCH_SEMI_EXPANDED NS_FONT_STRETCH_SEMI_EXPANDED
+#define NS_STYLE_FONT_STRETCH_EXPANDED NS_FONT_STRETCH_EXPANDED
+#define NS_STYLE_FONT_STRETCH_EXTRA_EXPANDED NS_FONT_STRETCH_EXTRA_EXPANDED
+#define NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED NS_FONT_STRETCH_ULTRA_EXPANDED
+
+// See nsStyleFont - system fonts
+#define NS_STYLE_FONT_CAPTION 1 // css2
+#define NS_STYLE_FONT_ICON 2
+#define NS_STYLE_FONT_MENU 3
+#define NS_STYLE_FONT_MESSAGE_BOX 4
+#define NS_STYLE_FONT_SMALL_CAPTION 5
+#define NS_STYLE_FONT_STATUS_BAR 6
+#define NS_STYLE_FONT_WINDOW 7 // css3
+#define NS_STYLE_FONT_DOCUMENT 8
+#define NS_STYLE_FONT_WORKSPACE 9
+#define NS_STYLE_FONT_DESKTOP 10
+#define NS_STYLE_FONT_INFO 11
+#define NS_STYLE_FONT_DIALOG 12
+#define NS_STYLE_FONT_BUTTON 13
+#define NS_STYLE_FONT_PULL_DOWN_MENU 14
+#define NS_STYLE_FONT_LIST 15
+#define NS_STYLE_FONT_FIELD 16
+
+// grid-auto-flow keywords
+#define NS_STYLE_GRID_AUTO_FLOW_ROW (1 << 0)
+#define NS_STYLE_GRID_AUTO_FLOW_COLUMN (1 << 1)
+#define NS_STYLE_GRID_AUTO_FLOW_DENSE (1 << 2)
+
+// 'subgrid' keyword in grid-template-{columns,rows}
+#define NS_STYLE_GRID_TEMPLATE_SUBGRID 0
+
+// CSS Grid <track-breadth> keywords
+// Should not overlap with NS_STYLE_GRID_TEMPLATE_SUBGRID
+#define NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT 1
+#define NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT 2
+
+// CSS Grid keywords for <auto-repeat>
+#define NS_STYLE_GRID_REPEAT_AUTO_FILL 0
+#define NS_STYLE_GRID_REPEAT_AUTO_FIT 1
+
+// defaults per MathML spec
+#define NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER 0.71f
+#define NS_MATHML_DEFAULT_SCRIPT_MIN_SIZE_PT 8
+
+// See nsStyleFont
+#define NS_MATHML_MATHVARIANT_NONE 0
+#define NS_MATHML_MATHVARIANT_NORMAL 1
+#define NS_MATHML_MATHVARIANT_BOLD 2
+#define NS_MATHML_MATHVARIANT_ITALIC 3
+#define NS_MATHML_MATHVARIANT_BOLD_ITALIC 4
+#define NS_MATHML_MATHVARIANT_SCRIPT 5
+#define NS_MATHML_MATHVARIANT_BOLD_SCRIPT 6
+#define NS_MATHML_MATHVARIANT_FRAKTUR 7
+#define NS_MATHML_MATHVARIANT_DOUBLE_STRUCK 8
+#define NS_MATHML_MATHVARIANT_BOLD_FRAKTUR 9
+#define NS_MATHML_MATHVARIANT_SANS_SERIF 10
+#define NS_MATHML_MATHVARIANT_BOLD_SANS_SERIF 11
+#define NS_MATHML_MATHVARIANT_SANS_SERIF_ITALIC 12
+#define NS_MATHML_MATHVARIANT_SANS_SERIF_BOLD_ITALIC 13
+#define NS_MATHML_MATHVARIANT_MONOSPACE 14
+#define NS_MATHML_MATHVARIANT_INITIAL 15
+#define NS_MATHML_MATHVARIANT_TAILED 16
+#define NS_MATHML_MATHVARIANT_LOOPED 17
+#define NS_MATHML_MATHVARIANT_STRETCHED 18
+
+// See nsStyleFont::mMathDisplay
+#define NS_MATHML_DISPLAYSTYLE_INLINE 0
+#define NS_MATHML_DISPLAYSTYLE_BLOCK 1
+
+// See nsStylePosition::mWidth, mMinWidth, mMaxWidth
+#define NS_STYLE_WIDTH_MAX_CONTENT 0
+#define NS_STYLE_WIDTH_MIN_CONTENT 1
+#define NS_STYLE_WIDTH_FIT_CONTENT 2
+#define NS_STYLE_WIDTH_AVAILABLE 3
+
+// See nsStyleDisplay.mPosition
+#define NS_STYLE_POSITION_STATIC 0
+#define NS_STYLE_POSITION_RELATIVE 1
+#define NS_STYLE_POSITION_ABSOLUTE 2
+#define NS_STYLE_POSITION_FIXED 3
+#define NS_STYLE_POSITION_STICKY 4
+
+// See nsStyleEffects.mClip, mClipFlags
+#define NS_STYLE_CLIP_AUTO 0x00
+#define NS_STYLE_CLIP_RECT 0x01
+#define NS_STYLE_CLIP_TYPE_MASK 0x0F
+#define NS_STYLE_CLIP_LEFT_AUTO 0x10
+#define NS_STYLE_CLIP_TOP_AUTO 0x20
+#define NS_STYLE_CLIP_RIGHT_AUTO 0x40
+#define NS_STYLE_CLIP_BOTTOM_AUTO 0x80
+
+// FRAME/FRAMESET/IFRAME specific values including backward compatibility. Boolean values with
+// the same meaning (e.g. 1 & yes) may need to be distinguished for correct mode processing
+#define NS_STYLE_FRAME_YES 0
+#define NS_STYLE_FRAME_NO 1
+#define NS_STYLE_FRAME_0 2
+#define NS_STYLE_FRAME_1 3
+#define NS_STYLE_FRAME_ON 4
+#define NS_STYLE_FRAME_OFF 5
+#define NS_STYLE_FRAME_AUTO 6
+#define NS_STYLE_FRAME_SCROLL 7
+#define NS_STYLE_FRAME_NOSCROLL 8
+
+// See nsStyleDisplay.mOverflow
+#define NS_STYLE_OVERFLOW_VISIBLE 0
+#define NS_STYLE_OVERFLOW_HIDDEN 1
+#define NS_STYLE_OVERFLOW_SCROLL 2
+#define NS_STYLE_OVERFLOW_AUTO 3
+#define NS_STYLE_OVERFLOW_CLIP 4
+#define NS_STYLE_OVERFLOW_SCROLLBARS_HORIZONTAL 5
+#define NS_STYLE_OVERFLOW_SCROLLBARS_VERTICAL 6
+
+// See nsStyleDisplay.mOverflowClipBox
+#define NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX 0
+#define NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX 1
+
+// See nsStyleList
+#define NS_STYLE_LIST_STYLE_CUSTOM -1 // for @counter-style
+#define NS_STYLE_LIST_STYLE_NONE 0
+#define NS_STYLE_LIST_STYLE_DISC 1
+#define NS_STYLE_LIST_STYLE_CIRCLE 2
+#define NS_STYLE_LIST_STYLE_SQUARE 3
+#define NS_STYLE_LIST_STYLE_DECIMAL 4
+#define NS_STYLE_LIST_STYLE_HEBREW 5
+#define NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL 6
+#define NS_STYLE_LIST_STYLE_JAPANESE_FORMAL 7
+#define NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL 8
+#define NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL 9
+#define NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL 10
+#define NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL 11
+#define NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL 12
+#define NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL 13
+#define NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL 14
+#define NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC 15
+#define NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED 16
+#define NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN 17
+#define NS_STYLE_LIST_STYLE__MAX 18
+// These styles are handled as custom styles defined in counterstyles.css.
+// They are preserved here only for html attribute map.
+#define NS_STYLE_LIST_STYLE_LOWER_ROMAN 100
+#define NS_STYLE_LIST_STYLE_UPPER_ROMAN 101
+#define NS_STYLE_LIST_STYLE_LOWER_ALPHA 102
+#define NS_STYLE_LIST_STYLE_UPPER_ALPHA 103
+
+// See nsStyleList
+#define NS_STYLE_LIST_STYLE_POSITION_INSIDE 0
+#define NS_STYLE_LIST_STYLE_POSITION_OUTSIDE 1
+
+// See nsStyleMargin
+#define NS_STYLE_MARGIN_SIZE_AUTO 0
+
+// See nsStyleVisibility
+#define NS_STYLE_POINTER_EVENTS_NONE 0
+#define NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED 1
+#define NS_STYLE_POINTER_EVENTS_VISIBLEFILL 2
+#define NS_STYLE_POINTER_EVENTS_VISIBLESTROKE 3
+#define NS_STYLE_POINTER_EVENTS_VISIBLE 4
+#define NS_STYLE_POINTER_EVENTS_PAINTED 5
+#define NS_STYLE_POINTER_EVENTS_FILL 6
+#define NS_STYLE_POINTER_EVENTS_STROKE 7
+#define NS_STYLE_POINTER_EVENTS_ALL 8
+#define NS_STYLE_POINTER_EVENTS_AUTO 9
+
+// See nsStyleVisibility.mImageOrientationType
+#define NS_STYLE_IMAGE_ORIENTATION_FLIP 0
+#define NS_STYLE_IMAGE_ORIENTATION_FROM_IMAGE 1
+
+// See nsStyleDisplay
+#define NS_STYLE_ISOLATION_AUTO 0
+#define NS_STYLE_ISOLATION_ISOLATE 1
+
+// See nsStylePosition.mObjectFit
+#define NS_STYLE_OBJECT_FIT_FILL 0
+#define NS_STYLE_OBJECT_FIT_CONTAIN 1
+#define NS_STYLE_OBJECT_FIT_COVER 2
+#define NS_STYLE_OBJECT_FIT_NONE 3
+#define NS_STYLE_OBJECT_FIT_SCALE_DOWN 4
+
+// See nsStyleDisplay
+#define NS_STYLE_RESIZE_NONE 0
+#define NS_STYLE_RESIZE_BOTH 1
+#define NS_STYLE_RESIZE_HORIZONTAL 2
+#define NS_STYLE_RESIZE_VERTICAL 3
+
+// See nsStyleText
+#define NS_STYLE_TEXT_ALIGN_START 0
+#define NS_STYLE_TEXT_ALIGN_LEFT 1
+#define NS_STYLE_TEXT_ALIGN_RIGHT 2
+#define NS_STYLE_TEXT_ALIGN_CENTER 3
+#define NS_STYLE_TEXT_ALIGN_JUSTIFY 4
+#define NS_STYLE_TEXT_ALIGN_CHAR 5 //align based on a certain character, for table cell
+#define NS_STYLE_TEXT_ALIGN_END 6
+#define NS_STYLE_TEXT_ALIGN_AUTO 7
+#define NS_STYLE_TEXT_ALIGN_MOZ_CENTER 8
+#define NS_STYLE_TEXT_ALIGN_MOZ_RIGHT 9
+#define NS_STYLE_TEXT_ALIGN_MOZ_LEFT 10
+// NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT is only used in data structs; it
+// is never present in stylesheets or computed data.
+#define NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT 11
+#define NS_STYLE_TEXT_ALIGN_UNSAFE 12
+#define NS_STYLE_TEXT_ALIGN_MATCH_PARENT 13
+// Note: make sure that the largest NS_STYLE_TEXT_ALIGN_* value is smaller than
+// the smallest NS_STYLE_VERTICAL_ALIGN_* value below!
+
+// See nsStyleText, nsStyleFont
+#define NS_STYLE_TEXT_DECORATION_LINE_NONE 0
+#define NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE 0x01
+#define NS_STYLE_TEXT_DECORATION_LINE_OVERLINE 0x02
+#define NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH 0x04
+#define NS_STYLE_TEXT_DECORATION_LINE_BLINK 0x08
+#define NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS 0x10
+// OVERRIDE_ALL does not occur in stylesheets; it only comes from HTML
+// attribute mapping (and thus appears in computed data)
+#define NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL 0x20
+#define NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE | NS_STYLE_TEXT_DECORATION_LINE_OVERLINE | NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH)
+
+// See nsStyleText
+#define NS_STYLE_TEXT_DECORATION_STYLE_NONE 0 // not in CSS spec, mapped to -moz-none
+#define NS_STYLE_TEXT_DECORATION_STYLE_DOTTED 1
+#define NS_STYLE_TEXT_DECORATION_STYLE_DASHED 2
+#define NS_STYLE_TEXT_DECORATION_STYLE_SOLID 3
+#define NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE 4
+#define NS_STYLE_TEXT_DECORATION_STYLE_WAVY 5
+#define NS_STYLE_TEXT_DECORATION_STYLE_MAX NS_STYLE_TEXT_DECORATION_STYLE_WAVY
+
+// See nsStyleTextOverflow
+#define NS_STYLE_TEXT_OVERFLOW_CLIP 0
+#define NS_STYLE_TEXT_OVERFLOW_ELLIPSIS 1
+#define NS_STYLE_TEXT_OVERFLOW_STRING 2
+
+// See nsStyleText
+#define NS_STYLE_TEXT_TRANSFORM_NONE 0
+#define NS_STYLE_TEXT_TRANSFORM_CAPITALIZE 1
+#define NS_STYLE_TEXT_TRANSFORM_LOWERCASE 2
+#define NS_STYLE_TEXT_TRANSFORM_UPPERCASE 3
+#define NS_STYLE_TEXT_TRANSFORM_FULL_WIDTH 4
+
+// See nsStyleDisplay
+#define NS_STYLE_TOUCH_ACTION_NONE (1 << 0)
+#define NS_STYLE_TOUCH_ACTION_AUTO (1 << 1)
+#define NS_STYLE_TOUCH_ACTION_PAN_X (1 << 2)
+#define NS_STYLE_TOUCH_ACTION_PAN_Y (1 << 3)
+#define NS_STYLE_TOUCH_ACTION_MANIPULATION (1 << 4)
+
+// See nsStyleDisplay
+#define NS_STYLE_TOP_LAYER_NONE 0 // not in the top layer
+#define NS_STYLE_TOP_LAYER_TOP 1 // in the top layer
+
+// See nsStyleDisplay
+#define NS_STYLE_TRANSFORM_BOX_BORDER_BOX 0
+#define NS_STYLE_TRANSFORM_BOX_FILL_BOX 1
+#define NS_STYLE_TRANSFORM_BOX_VIEW_BOX 2
+
+// See nsStyleDisplay
+#define NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE 0
+#define NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR 1
+#define NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN 2
+#define NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_OUT 3
+#define NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN_OUT 4
+#define NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START 5
+#define NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END 6
+
+// See nsStyleText
+// Note: these values pickup after the text-align values because there
+// are a few html cases where an object can have both types of
+// alignment applied with a single attribute
+#define NS_STYLE_VERTICAL_ALIGN_BASELINE 14
+#define NS_STYLE_VERTICAL_ALIGN_SUB 15
+#define NS_STYLE_VERTICAL_ALIGN_SUPER 16
+#define NS_STYLE_VERTICAL_ALIGN_TOP 17
+#define NS_STYLE_VERTICAL_ALIGN_TEXT_TOP 18
+#define NS_STYLE_VERTICAL_ALIGN_MIDDLE 19
+#define NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM 20
+#define NS_STYLE_VERTICAL_ALIGN_BOTTOM 21
+#define NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE 22
+
+// See nsStyleVisibility
+#define NS_STYLE_VISIBILITY_HIDDEN 0
+#define NS_STYLE_VISIBILITY_VISIBLE 1
+#define NS_STYLE_VISIBILITY_COLLAPSE 2
+
+// See nsStyleText
+#define NS_STYLE_TABSIZE_INITIAL 8
+
+// See nsStyleText
+#define NS_STYLE_WHITESPACE_NORMAL 0
+#define NS_STYLE_WHITESPACE_PRE 1
+#define NS_STYLE_WHITESPACE_NOWRAP 2
+#define NS_STYLE_WHITESPACE_PRE_WRAP 3
+#define NS_STYLE_WHITESPACE_PRE_LINE 4
+#define NS_STYLE_WHITESPACE_PRE_SPACE 5
+
+// See nsStyleText
+#define NS_STYLE_WORDBREAK_NORMAL 0
+#define NS_STYLE_WORDBREAK_BREAK_ALL 1
+#define NS_STYLE_WORDBREAK_KEEP_ALL 2
+
+// See nsStyleText
+#define NS_STYLE_OVERFLOWWRAP_NORMAL 0
+#define NS_STYLE_OVERFLOWWRAP_BREAK_WORD 1
+
+// See nsStyleText
+#define NS_STYLE_HYPHENS_NONE 0
+#define NS_STYLE_HYPHENS_MANUAL 1
+#define NS_STYLE_HYPHENS_AUTO 2
+
+// ruby-align, see nsStyleText
+#define NS_STYLE_RUBY_ALIGN_START 0
+#define NS_STYLE_RUBY_ALIGN_CENTER 1
+#define NS_STYLE_RUBY_ALIGN_SPACE_BETWEEN 2
+#define NS_STYLE_RUBY_ALIGN_SPACE_AROUND 3
+
+// ruby-position, see nsStyleText
+#define NS_STYLE_RUBY_POSITION_OVER 0
+#define NS_STYLE_RUBY_POSITION_UNDER 1
+#define NS_STYLE_RUBY_POSITION_INTER_CHARACTER 2 /* placeholder, not yet parsed */
+
+// See nsStyleText
+#define NS_STYLE_TEXT_SIZE_ADJUST_NONE 0
+#define NS_STYLE_TEXT_SIZE_ADJUST_AUTO 1
+
+// See nsStyleText
+#define NS_STYLE_TEXT_ORIENTATION_MIXED 0
+#define NS_STYLE_TEXT_ORIENTATION_UPRIGHT 1
+#define NS_STYLE_TEXT_ORIENTATION_SIDEWAYS 2
+
+// See nsStyleText
+#define NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE 0
+#define NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL 1
+#define NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2 2
+#define NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3 3
+#define NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_4 4
+
+// See nsStyleText
+#define NS_STYLE_LINE_HEIGHT_BLOCK_HEIGHT 0
+
+// See nsStyleText
+#define NS_STYLE_UNICODE_BIDI_NORMAL 0x0
+#define NS_STYLE_UNICODE_BIDI_EMBED 0x1
+#define NS_STYLE_UNICODE_BIDI_ISOLATE 0x2
+#define NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE 0x4
+#define NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE 0x6
+#define NS_STYLE_UNICODE_BIDI_PLAINTEXT 0x8
+
+#define NS_STYLE_TABLE_LAYOUT_AUTO 0
+#define NS_STYLE_TABLE_LAYOUT_FIXED 1
+
+#define NS_STYLE_TABLE_EMPTY_CELLS_HIDE 0
+#define NS_STYLE_TABLE_EMPTY_CELLS_SHOW 1
+
+// Constants for the caption-side property. Note that despite having "physical"
+// names, these are actually interpreted according to the table's writing-mode:
+// TOP and BOTTOM are treated as block-start and -end respectively, and LEFT
+// and RIGHT are treated as line-left and -right.
+#define NS_STYLE_CAPTION_SIDE_TOP 0
+#define NS_STYLE_CAPTION_SIDE_RIGHT 1
+#define NS_STYLE_CAPTION_SIDE_BOTTOM 2
+#define NS_STYLE_CAPTION_SIDE_LEFT 3
+#define NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE 4
+#define NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE 5
+
+// constants for cell "scope" attribute
+#define NS_STYLE_CELL_SCOPE_ROW 0
+#define NS_STYLE_CELL_SCOPE_COL 1
+#define NS_STYLE_CELL_SCOPE_ROWGROUP 2
+#define NS_STYLE_CELL_SCOPE_COLGROUP 3
+
+// See nsStylePage
+#define NS_STYLE_PAGE_MARKS_NONE 0x00
+#define NS_STYLE_PAGE_MARKS_CROP 0x01
+#define NS_STYLE_PAGE_MARKS_REGISTER 0x02
+
+// See nsStylePage
+#define NS_STYLE_PAGE_SIZE_AUTO 0
+#define NS_STYLE_PAGE_SIZE_PORTRAIT 1
+#define NS_STYLE_PAGE_SIZE_LANDSCAPE 2
+
+// See nsStyleBreaks
+#define NS_STYLE_PAGE_BREAK_AUTO 0
+#define NS_STYLE_PAGE_BREAK_ALWAYS 1
+#define NS_STYLE_PAGE_BREAK_AVOID 2
+#define NS_STYLE_PAGE_BREAK_LEFT 3
+#define NS_STYLE_PAGE_BREAK_RIGHT 4
+
+// See nsStyleColumn
+#define NS_STYLE_COLUMN_COUNT_AUTO 0
+#define NS_STYLE_COLUMN_COUNT_UNLIMITED (-1)
+
+#define NS_STYLE_COLUMN_FILL_AUTO 0
+#define NS_STYLE_COLUMN_FILL_BALANCE 1
+
+// See nsStyleUIReset
+#define NS_STYLE_IME_MODE_AUTO 0
+#define NS_STYLE_IME_MODE_NORMAL 1
+#define NS_STYLE_IME_MODE_ACTIVE 2
+#define NS_STYLE_IME_MODE_DISABLED 3
+#define NS_STYLE_IME_MODE_INACTIVE 4
+
+// See nsStyleGradient
+#define NS_STYLE_GRADIENT_SHAPE_LINEAR 0
+#define NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL 1
+#define NS_STYLE_GRADIENT_SHAPE_CIRCULAR 2
+
+#define NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE 0
+#define NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER 1
+#define NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE 2
+#define NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER 3
+#define NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE 4
+
+// See nsStyleSVG
+
+// dominant-baseline
+#define NS_STYLE_DOMINANT_BASELINE_AUTO 0
+#define NS_STYLE_DOMINANT_BASELINE_USE_SCRIPT 1
+#define NS_STYLE_DOMINANT_BASELINE_NO_CHANGE 2
+#define NS_STYLE_DOMINANT_BASELINE_RESET_SIZE 3
+#define NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC 4
+#define NS_STYLE_DOMINANT_BASELINE_ALPHABETIC 5
+#define NS_STYLE_DOMINANT_BASELINE_HANGING 6
+#define NS_STYLE_DOMINANT_BASELINE_MATHEMATICAL 7
+#define NS_STYLE_DOMINANT_BASELINE_CENTRAL 8
+#define NS_STYLE_DOMINANT_BASELINE_MIDDLE 9
+#define NS_STYLE_DOMINANT_BASELINE_TEXT_AFTER_EDGE 10
+#define NS_STYLE_DOMINANT_BASELINE_TEXT_BEFORE_EDGE 11
+
+// image-rendering
+#define NS_STYLE_IMAGE_RENDERING_AUTO 0
+#define NS_STYLE_IMAGE_RENDERING_OPTIMIZESPEED 1
+#define NS_STYLE_IMAGE_RENDERING_OPTIMIZEQUALITY 2
+#define NS_STYLE_IMAGE_RENDERING_CRISPEDGES 3
+
+// mask-type
+#define NS_STYLE_MASK_TYPE_LUMINANCE 0
+#define NS_STYLE_MASK_TYPE_ALPHA 1
+
+// paint-order
+#define NS_STYLE_PAINT_ORDER_NORMAL 0
+#define NS_STYLE_PAINT_ORDER_FILL 1
+#define NS_STYLE_PAINT_ORDER_STROKE 2
+#define NS_STYLE_PAINT_ORDER_MARKERS 3
+#define NS_STYLE_PAINT_ORDER_LAST_VALUE NS_STYLE_PAINT_ORDER_MARKERS
+// NS_STYLE_PAINT_ORDER_BITWIDTH is the number of bits required to store
+// a single paint-order component value.
+#define NS_STYLE_PAINT_ORDER_BITWIDTH 2
+
+// shape-rendering
+#define NS_STYLE_SHAPE_RENDERING_AUTO 0
+#define NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED 1
+#define NS_STYLE_SHAPE_RENDERING_CRISPEDGES 2
+#define NS_STYLE_SHAPE_RENDERING_GEOMETRICPRECISION 3
+
+// stroke-linecap
+#define NS_STYLE_STROKE_LINECAP_BUTT 0
+#define NS_STYLE_STROKE_LINECAP_ROUND 1
+#define NS_STYLE_STROKE_LINECAP_SQUARE 2
+
+// stroke-linejoin
+#define NS_STYLE_STROKE_LINEJOIN_MITER 0
+#define NS_STYLE_STROKE_LINEJOIN_ROUND 1
+#define NS_STYLE_STROKE_LINEJOIN_BEVEL 2
+
+// stroke-dasharray, stroke-dashoffset, stroke-width
+#define NS_STYLE_STROKE_PROP_CONTEXT_VALUE 0
+
+// text-anchor
+#define NS_STYLE_TEXT_ANCHOR_START 0
+#define NS_STYLE_TEXT_ANCHOR_MIDDLE 1
+#define NS_STYLE_TEXT_ANCHOR_END 2
+
+// text-emphasis-position
+#define NS_STYLE_TEXT_EMPHASIS_POSITION_OVER (1 << 0)
+#define NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER (1 << 1)
+#define NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT (1 << 2)
+#define NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT (1 << 3)
+#define NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT \
+ (NS_STYLE_TEXT_EMPHASIS_POSITION_OVER | \
+ NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT)
+#define NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH \
+ (NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER | \
+ NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT)
+
+// text-emphasis-style
+// Note that filled and none here both have zero as their value. This is
+// not an problem because:
+// * In specified style, none is represented as eCSSUnit_None.
+// * In computed style, 'filled' always has its shape computed, and thus
+// the combined value is never zero.
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_NONE 0
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK (1 << 3)
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED (0 << 3)
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_OPEN (1 << 3)
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK 7
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_DOT 1
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE 2
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_DOUBLE_CIRCLE 3
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_TRIANGLE 4
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME 5
+#define NS_STYLE_TEXT_EMPHASIS_STYLE_STRING 255
+
+// text-rendering
+#define NS_STYLE_TEXT_RENDERING_AUTO 0
+#define NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED 1
+#define NS_STYLE_TEXT_RENDERING_OPTIMIZELEGIBILITY 2
+#define NS_STYLE_TEXT_RENDERING_GEOMETRICPRECISION 3
+
+// adjust-color
+#define NS_STYLE_COLOR_ADJUST_ECONOMY 0
+#define NS_STYLE_COLOR_ADJUST_EXACT 1
+
+// color-interpolation and color-interpolation-filters
+#define NS_STYLE_COLOR_INTERPOLATION_AUTO 0
+#define NS_STYLE_COLOR_INTERPOLATION_SRGB 1
+#define NS_STYLE_COLOR_INTERPOLATION_LINEARRGB 2
+
+// vector-effect
+#define NS_STYLE_VECTOR_EFFECT_NONE 0
+#define NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE 1
+
+// 3d Transforms - Backface visibility
+#define NS_STYLE_BACKFACE_VISIBILITY_VISIBLE 1
+#define NS_STYLE_BACKFACE_VISIBILITY_HIDDEN 0
+
+#define NS_STYLE_TRANSFORM_STYLE_FLAT 0
+#define NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D 1
+
+// object {fill,stroke}-opacity inherited from context for SVG glyphs
+#define NS_STYLE_CONTEXT_FILL_OPACITY 0
+#define NS_STYLE_CONTEXT_STROKE_OPACITY 1
+
+// blending
+#define NS_STYLE_BLEND_NORMAL 0
+#define NS_STYLE_BLEND_MULTIPLY 1
+#define NS_STYLE_BLEND_SCREEN 2
+#define NS_STYLE_BLEND_OVERLAY 3
+#define NS_STYLE_BLEND_DARKEN 4
+#define NS_STYLE_BLEND_LIGHTEN 5
+#define NS_STYLE_BLEND_COLOR_DODGE 6
+#define NS_STYLE_BLEND_COLOR_BURN 7
+#define NS_STYLE_BLEND_HARD_LIGHT 8
+#define NS_STYLE_BLEND_SOFT_LIGHT 9
+#define NS_STYLE_BLEND_DIFFERENCE 10
+#define NS_STYLE_BLEND_EXCLUSION 11
+#define NS_STYLE_BLEND_HUE 12
+#define NS_STYLE_BLEND_SATURATION 13
+#define NS_STYLE_BLEND_COLOR 14
+#define NS_STYLE_BLEND_LUMINOSITY 15
+
+// composite
+#define NS_STYLE_MASK_COMPOSITE_ADD 0
+#define NS_STYLE_MASK_COMPOSITE_SUBTRACT 1
+#define NS_STYLE_MASK_COMPOSITE_INTERSECT 2
+#define NS_STYLE_MASK_COMPOSITE_EXCLUDE 3
+
+// See nsStyleText::mControlCharacterVisibility
+#define NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN 0
+#define NS_STYLE_CONTROL_CHARACTER_VISIBILITY_VISIBLE 1
+
+// counter system
+#define NS_STYLE_COUNTER_SYSTEM_CYCLIC 0
+#define NS_STYLE_COUNTER_SYSTEM_NUMERIC 1
+#define NS_STYLE_COUNTER_SYSTEM_ALPHABETIC 2
+#define NS_STYLE_COUNTER_SYSTEM_SYMBOLIC 3
+#define NS_STYLE_COUNTER_SYSTEM_ADDITIVE 4
+#define NS_STYLE_COUNTER_SYSTEM_FIXED 5
+#define NS_STYLE_COUNTER_SYSTEM_EXTENDS 6
+
+#define NS_STYLE_COUNTER_RANGE_INFINITE 0
+
+#define NS_STYLE_COUNTER_SPEAKAS_BULLETS 0
+#define NS_STYLE_COUNTER_SPEAKAS_NUMBERS 1
+#define NS_STYLE_COUNTER_SPEAKAS_WORDS 2
+#define NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT 3
+#define NS_STYLE_COUNTER_SPEAKAS_OTHER 255 // refer to another style
+
+// See nsStyleDisplay::mScrollBehavior
+#define NS_STYLE_SCROLL_BEHAVIOR_AUTO 0
+#define NS_STYLE_SCROLL_BEHAVIOR_SMOOTH 1
+
+// See nsStyleDisplay::mScrollSnapType{X,Y}
+#define NS_STYLE_SCROLL_SNAP_TYPE_NONE 0
+#define NS_STYLE_SCROLL_SNAP_TYPE_MANDATORY 1
+#define NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY 2
+
+/*****************************************************************************
+ * Constants for media features. *
+ *****************************************************************************/
+
+// orientation
+#define NS_STYLE_ORIENTATION_PORTRAIT 0
+#define NS_STYLE_ORIENTATION_LANDSCAPE 1
+
+// scan
+#define NS_STYLE_SCAN_PROGRESSIVE 0
+#define NS_STYLE_SCAN_INTERLACE 1
+
+// display-mode
+#define NS_STYLE_DISPLAY_MODE_BROWSER 0
+#define NS_STYLE_DISPLAY_MODE_MINIMAL_UI 1
+#define NS_STYLE_DISPLAY_MODE_STANDALONE 2
+#define NS_STYLE_DISPLAY_MODE_FULLSCREEN 3
+
+} // namespace mozilla
+
+#endif /* nsStyleConsts_h___ */
diff --git a/layout/style/nsStyleContext.cpp b/layout/style/nsStyleContext.cpp
new file mode 100644
index 000000000..7ad260f1b
--- /dev/null
+++ b/layout/style/nsStyleContext.cpp
@@ -0,0 +1,1836 @@
+/* -*- 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/. */
+
+/* the interface (to internal code) for retrieving computed style data */
+
+#include "CSSVariableImageTable.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSPseudoElements.h"
+#include "nsStyleConsts.h"
+#include "nsString.h"
+#include "nsPresContext.h"
+#include "nsIStyleRule.h"
+
+#include "nsCOMPtr.h"
+#include "nsStyleSet.h"
+#include "nsIPresShell.h"
+
+#include "nsRuleNode.h"
+#include "nsStyleContext.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "GeckoProfiler.h"
+#include "nsIDocument.h"
+#include "nsPrintfCString.h"
+#include "RubyUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+
+#include "mozilla/ReflowInput.h"
+#include "nsLayoutUtils.h"
+#include "nsCoord.h"
+
+// Ensure the binding function declarations in nsStyleContext.h matches
+// those in ServoBindings.h.
+#include "mozilla/ServoBindings.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------
+
+#ifdef DEBUG
+
+// Check that the style struct IDs are in the same order as they are
+// in nsStyleStructList.h, since when we set up the IDs, we include
+// the inherited and reset structs spearately from nsStyleStructList.h
+enum DebugStyleStruct {
+#define STYLE_STRUCT(name, checkdata_cb) eDebugStyleStruct_##name,
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+};
+
+#define STYLE_STRUCT(name, checkdata_cb) \
+ static_assert(static_cast<int>(eDebugStyleStruct_##name) == \
+ static_cast<int>(eStyleStruct_##name), \
+ "Style struct IDs are not declared in order?");
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+const uint32_t nsStyleContext::sDependencyTable[] = {
+#define STYLE_STRUCT(name, checkdata_cb)
+#define STYLE_STRUCT_DEP(dep) NS_STYLE_INHERIT_BIT(dep) |
+#define STYLE_STRUCT_END() 0,
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+#undef STYLE_STRUCT_DEP
+#undef STYLE_STRUCT_END
+};
+
+// Whether to perform expensive assertions in the nsStyleContext destructor.
+static bool sExpensiveStyleStructAssertionsEnabled;
+#endif
+
+nsStyleContext::nsStyleContext(nsStyleContext* aParent,
+ OwningStyleContextSource&& aSource,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType)
+ : mParent(aParent)
+ , mChild(nullptr)
+ , mEmptyChild(nullptr)
+ , mPseudoTag(aPseudoTag)
+ , mSource(Move(aSource))
+#ifdef MOZ_STYLO
+ , mPresContext(nullptr)
+#endif
+ , mCachedResetData(nullptr)
+ , mBits(((uint64_t)aPseudoType) << NS_STYLE_CONTEXT_TYPE_SHIFT)
+ , mRefCnt(0)
+#ifdef MOZ_STYLO
+ , mStoredChangeHint(nsChangeHint(0))
+#ifdef DEBUG
+ , mConsumedChangeHint(false)
+#endif
+#endif
+#ifdef DEBUG
+ , mFrameRefCnt(0)
+ , mComputingStruct(nsStyleStructID_None)
+#endif
+{
+ MOZ_COUNT_CTOR(nsStyleContext);
+}
+
+nsStyleContext::nsStyleContext(nsStyleContext* aParent,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType,
+ already_AddRefed<nsRuleNode> aRuleNode,
+ bool aSkipParentDisplayBasedStyleFixup)
+ : nsStyleContext(aParent, OwningStyleContextSource(Move(aRuleNode)),
+ aPseudoTag, aPseudoType)
+{
+#ifdef MOZ_STYLO
+ mPresContext = mSource.AsGeckoRuleNode()->PresContext();
+#endif
+
+ if (aParent) {
+#ifdef DEBUG
+ nsRuleNode *r1 = mParent->RuleNode(), *r2 = mSource.AsGeckoRuleNode();
+ while (r1->GetParent())
+ r1 = r1->GetParent();
+ while (r2->GetParent())
+ r2 = r2->GetParent();
+ NS_ASSERTION(r1 == r2, "must be in the same rule tree as parent");
+#endif
+ } else {
+ PresContext()->PresShell()->StyleSet()->RootStyleContextAdded();
+ }
+
+ mSource.AsGeckoRuleNode()->SetUsedDirectly(); // before ApplyStyleFixups()!
+ FinishConstruction(aSkipParentDisplayBasedStyleFixup);
+}
+
+nsStyleContext::nsStyleContext(nsStyleContext* aParent,
+ nsPresContext* aPresContext,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType,
+ already_AddRefed<ServoComputedValues> aComputedValues,
+ bool aSkipParentDisplayBasedStyleFixup)
+ : nsStyleContext(aParent, OwningStyleContextSource(Move(aComputedValues)),
+ aPseudoTag, aPseudoType)
+{
+#ifdef MOZ_STYLO
+ mPresContext = aPresContext;
+#endif
+
+ FinishConstruction(aSkipParentDisplayBasedStyleFixup);
+}
+
+void
+nsStyleContext::FinishConstruction(bool aSkipParentDisplayBasedStyleFixup)
+{
+ // This check has to be done "backward", because if it were written the
+ // more natural way it wouldn't fail even when it needed to.
+ static_assert((UINT64_MAX >> NS_STYLE_CONTEXT_TYPE_SHIFT) >=
+ static_cast<CSSPseudoElementTypeBase>(
+ CSSPseudoElementType::MAX),
+ "pseudo element bits no longer fit in a uint64_t");
+ MOZ_ASSERT(!mSource.IsNull());
+
+#ifdef DEBUG
+ static_assert(MOZ_ARRAY_LENGTH(nsStyleContext::sDependencyTable)
+ == nsStyleStructID_Length,
+ "Number of items in dependency table doesn't match IDs");
+#endif
+
+ mNextSibling = this;
+ mPrevSibling = this;
+ if (mParent) {
+ mParent->AddChild(this);
+ }
+
+ SetStyleBits();
+ if (!mSource.IsServoComputedValues()) {
+ ApplyStyleFixups(aSkipParentDisplayBasedStyleFixup);
+ }
+
+ #define eStyleStruct_LastItem (nsStyleStructID_Length - 1)
+ NS_ASSERTION(NS_STYLE_INHERIT_MASK & NS_STYLE_INHERIT_BIT(LastItem),
+ "NS_STYLE_INHERIT_MASK must be bigger, and other bits shifted");
+ #undef eStyleStruct_LastItem
+}
+
+nsStyleContext::~nsStyleContext()
+{
+ MOZ_COUNT_DTOR(nsStyleContext);
+ NS_ASSERTION((nullptr == mChild) && (nullptr == mEmptyChild), "destructing context with children");
+
+#ifdef DEBUG
+ if (sExpensiveStyleStructAssertionsEnabled) {
+ // Assert that the style structs we are about to destroy are not referenced
+ // anywhere else in the style context tree. These checks are expensive,
+ // which is why they are not enabled by default.
+ nsStyleContext* root = this;
+ while (root->mParent) {
+ root = root->mParent;
+ }
+ root->AssertStructsNotUsedElsewhere(this,
+ std::numeric_limits<int32_t>::max());
+ } else {
+ // In DEBUG builds when the pref is not enabled, we perform a more limited
+ // check just of the children of this style context.
+ AssertStructsNotUsedElsewhere(this, 2);
+ }
+#endif
+
+ nsPresContext *presContext = PresContext();
+ DebugOnly<nsStyleSet*> geckoStyleSet = presContext->PresShell()->StyleSet()->GetAsGecko();
+ NS_ASSERTION(!geckoStyleSet ||
+ geckoStyleSet->GetRuleTree() == mSource.AsGeckoRuleNode()->RuleTree() ||
+ geckoStyleSet->IsInRuleTreeReconstruct(),
+ "destroying style context from old rule tree too late");
+
+ if (mParent) {
+ mParent->RemoveChild(this);
+ } else {
+ presContext->StyleSet()->RootStyleContextRemoved();
+ }
+
+ // Free up our data structs.
+ mCachedInheritedData.DestroyStructs(mBits, presContext);
+ if (mCachedResetData) {
+ mCachedResetData->Destroy(mBits, presContext);
+ }
+
+ // Free any ImageValues we were holding on to for CSS variable values.
+ CSSVariableImageTable::RemoveAll(this);
+}
+
+#ifdef DEBUG
+void
+nsStyleContext::AssertStructsNotUsedElsewhere(
+ nsStyleContext* aDestroyingContext,
+ int32_t aLevels) const
+{
+ if (aLevels == 0) {
+ return;
+ }
+
+ void* data;
+
+ if (mBits & NS_STYLE_IS_GOING_AWAY) {
+ return;
+ }
+
+ if (this != aDestroyingContext) {
+ nsInheritedStyleData& destroyingInheritedData =
+ aDestroyingContext->mCachedInheritedData;
+#define STYLE_STRUCT_INHERITED(name_, checkdata_cb) \
+ data = destroyingInheritedData.mStyleStructs[eStyleStruct_##name_]; \
+ if (data && \
+ !(aDestroyingContext->mBits & NS_STYLE_INHERIT_BIT(name_)) && \
+ (mCachedInheritedData.mStyleStructs[eStyleStruct_##name_] == data)) { \
+ printf_stderr("style struct %p found on style context %p\n", data, this);\
+ nsString url; \
+ nsresult rv = PresContext()->Document()->GetURL(url); \
+ if (NS_SUCCEEDED(rv)) { \
+ printf_stderr(" in %s\n", NS_ConvertUTF16toUTF8(url).get()); \
+ } \
+ MOZ_ASSERT(false, "destroying " #name_ " style struct still present " \
+ "in style context tree"); \
+ }
+#define STYLE_STRUCT_RESET(name_, checkdata_cb)
+
+#include "nsStyleStructList.h"
+
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+
+ if (mCachedResetData) {
+ nsResetStyleData* destroyingResetData =
+ aDestroyingContext->mCachedResetData;
+ if (destroyingResetData) {
+#define STYLE_STRUCT_INHERITED(name_, checkdata_cb_)
+#define STYLE_STRUCT_RESET(name_, checkdata_cb) \
+ data = destroyingResetData->mStyleStructs[eStyleStruct_##name_]; \
+ if (data && \
+ !(aDestroyingContext->mBits & NS_STYLE_INHERIT_BIT(name_)) && \
+ (mCachedResetData->mStyleStructs[eStyleStruct_##name_] == data)) { \
+ printf_stderr("style struct %p found on style context %p\n", data, \
+ this); \
+ nsString url; \
+ nsresult rv = PresContext()->Document()->GetURL(url); \
+ if (NS_SUCCEEDED(rv)) { \
+ printf_stderr(" in %s\n", NS_ConvertUTF16toUTF8(url).get()); \
+ } \
+ MOZ_ASSERT(false, "destroying " #name_ " style struct still present "\
+ "in style context tree"); \
+ }
+
+#include "nsStyleStructList.h"
+
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+ }
+ }
+ }
+
+ if (mChild) {
+ const nsStyleContext* child = mChild;
+ do {
+ child->AssertStructsNotUsedElsewhere(aDestroyingContext, aLevels - 1);
+ child = child->mNextSibling;
+ } while (child != mChild);
+ }
+
+ if (mEmptyChild) {
+ const nsStyleContext* child = mEmptyChild;
+ do {
+ child->AssertStructsNotUsedElsewhere(aDestroyingContext, aLevels - 1);
+ child = child->mNextSibling;
+ } while (child != mEmptyChild);
+ }
+}
+#endif
+
+void nsStyleContext::AddChild(nsStyleContext* aChild)
+{
+ NS_ASSERTION(aChild->mPrevSibling == aChild &&
+ aChild->mNextSibling == aChild,
+ "child already in a child list");
+
+ nsStyleContext **listPtr = aChild->mSource.MatchesNoRules() ? &mEmptyChild : &mChild;
+ // Explicitly dereference listPtr so that compiler doesn't have to know that mNextSibling
+ // etc. don't alias with what ever listPtr points at.
+ nsStyleContext *list = *listPtr;
+
+ // Insert at the beginning of the list. See also FindChildWithRules.
+ if (list) {
+ // Link into existing elements, if there are any.
+ aChild->mNextSibling = list;
+ aChild->mPrevSibling = list->mPrevSibling;
+ list->mPrevSibling->mNextSibling = aChild;
+ list->mPrevSibling = aChild;
+ }
+ (*listPtr) = aChild;
+}
+
+void nsStyleContext::RemoveChild(nsStyleContext* aChild)
+{
+ NS_PRECONDITION(nullptr != aChild && this == aChild->mParent, "bad argument");
+
+ nsStyleContext **list = aChild->mSource.MatchesNoRules() ? &mEmptyChild : &mChild;
+
+ if (aChild->mPrevSibling != aChild) { // has siblings
+ if ((*list) == aChild) {
+ (*list) = (*list)->mNextSibling;
+ }
+ }
+ else {
+ NS_ASSERTION((*list) == aChild, "bad sibling pointers");
+ (*list) = nullptr;
+ }
+
+ aChild->mPrevSibling->mNextSibling = aChild->mNextSibling;
+ aChild->mNextSibling->mPrevSibling = aChild->mPrevSibling;
+ aChild->mNextSibling = aChild;
+ aChild->mPrevSibling = aChild;
+}
+
+void
+nsStyleContext::MoveTo(nsStyleContext* aNewParent)
+{
+ MOZ_ASSERT(aNewParent != mParent);
+
+ // This function shouldn't be getting called if the parents have different
+ // values for some flags in mBits (unless the flag is also set on this style
+ // context) because if that were the case we would need to recompute those
+ // bits for |this|.
+
+#define CHECK_FLAG(bit_) \
+ MOZ_ASSERT((mParent->mBits & (bit_)) == (aNewParent->mBits & (bit_)) || \
+ (mBits & (bit_)), \
+ "MoveTo cannot be called if " #bit_ " value on old and new " \
+ "style context parents do not match, unless the flag is set " \
+ "on this style context");
+
+ CHECK_FLAG(NS_STYLE_HAS_PSEUDO_ELEMENT_DATA)
+ CHECK_FLAG(NS_STYLE_IN_DISPLAY_NONE_SUBTREE)
+ CHECK_FLAG(NS_STYLE_HAS_TEXT_DECORATION_LINES)
+ CHECK_FLAG(NS_STYLE_RELEVANT_LINK_VISITED)
+
+#undef CHECK_FLAG
+
+ // Assertions checking for visited style are just to avoid some tricky
+ // cases we can't be bothered handling at the moment.
+ MOZ_ASSERT(!IsStyleIfVisited());
+ MOZ_ASSERT(!mParent->IsStyleIfVisited());
+ MOZ_ASSERT(!aNewParent->IsStyleIfVisited());
+ MOZ_ASSERT(!mStyleIfVisited || mStyleIfVisited->mParent == mParent);
+
+ if (mParent->HasChildThatUsesResetStyle()) {
+ aNewParent->AddStyleBit(NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE);
+ }
+
+ mParent->RemoveChild(this);
+ mParent = aNewParent;
+ mParent->AddChild(this);
+
+ if (mStyleIfVisited) {
+ mStyleIfVisited->mParent->RemoveChild(mStyleIfVisited);
+ mStyleIfVisited->mParent = aNewParent;
+ mStyleIfVisited->mParent->AddChild(mStyleIfVisited);
+ }
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleContext::FindChildWithRules(const nsIAtom* aPseudoTag,
+ NonOwningStyleContextSource aSource,
+ NonOwningStyleContextSource aSourceIfVisited,
+ bool aRelevantLinkVisited)
+{
+ uint32_t threshold = 10; // The # of siblings we're willing to examine
+ // before just giving this whole thing up.
+
+ RefPtr<nsStyleContext> result;
+ nsStyleContext *list = aSource.MatchesNoRules() ? mEmptyChild : mChild;
+
+ if (list) {
+ nsStyleContext *child = list;
+ do {
+ if (child->mSource.AsRaw() == aSource &&
+ child->mPseudoTag == aPseudoTag &&
+ !child->IsStyleIfVisited() &&
+ child->RelevantLinkVisited() == aRelevantLinkVisited) {
+ bool match = false;
+ if (!aSourceIfVisited.IsNull()) {
+ match = child->GetStyleIfVisited() &&
+ child->GetStyleIfVisited()->mSource.AsRaw() == aSourceIfVisited;
+ } else {
+ match = !child->GetStyleIfVisited();
+ }
+ if (match && !(child->mBits & NS_STYLE_INELIGIBLE_FOR_SHARING)) {
+ result = child;
+ break;
+ }
+ }
+ child = child->mNextSibling;
+ threshold--;
+ if (threshold == 0)
+ break;
+ } while (child != list);
+ }
+
+ if (result) {
+ if (result != list) {
+ // Move result to the front of the list.
+ RemoveChild(result);
+ AddChild(result);
+ }
+ result->mBits |= NS_STYLE_IS_SHARED;
+ }
+
+ return result.forget();
+}
+
+const void* nsStyleContext::StyleData(nsStyleStructID aSID)
+{
+ const void* cachedData = GetCachedStyleData(aSID);
+ if (cachedData)
+ return cachedData; // We have computed data stored on this node in the context tree.
+ // Our style source will take care of it for us.
+ const void* newData;
+ if (mSource.IsGeckoRuleNode()) {
+ newData = mSource.AsGeckoRuleNode()->GetStyleData(aSID, this, true);
+ if (!nsCachedStyleData::IsReset(aSID)) {
+ // always cache inherited data on the style context; the rule
+ // node set the bit in mBits for us if needed.
+ mCachedInheritedData.mStyleStructs[aSID] = const_cast<void*>(newData);
+ }
+ } else {
+ newData = StyleStructFromServoComputedValues(aSID);
+
+ // perform any remaining main thread work on the struct
+ switch (aSID) {
+#define STYLE_STRUCT(name_, checkdata_cb_) \
+ case eStyleStruct_##name_: { \
+ auto data = static_cast<const nsStyle##name_*>(newData); \
+ const_cast<nsStyle##name_*>(data)->FinishStyle(PresContext()); \
+ break; \
+ }
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected nsStyleStructID value");
+ break;
+ }
+
+ // The Servo-backed StyleContextSource owns the struct.
+ AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
+
+ // XXXbholley: Unconditionally caching reset structs here defeats the memory
+ // optimization where we lazily allocate mCachedResetData, so that we can avoid
+ // performing an FFI call each time we want to get the style structs. We should
+ // measure the tradeoffs at some point. If the FFI overhead is low and the memory
+ // win significant, we should consider _always_ grabbing the struct over FFI, and
+ // potentially giving mCachedInheritedData the same treatment.
+ //
+ // Note that there is a similar comment in the struct getters in nsStyleContext.h.
+ SetStyle(aSID, const_cast<void*>(newData));
+ }
+ return newData;
+}
+
+// This is an evil evil function, since it forces you to alloc your own separate copy of
+// style data! Do not use this function unless you absolutely have to! You should avoid
+// this at all costs! -dwh
+void*
+nsStyleContext::GetUniqueStyleData(const nsStyleStructID& aSID)
+{
+ MOZ_ASSERT(!mSource.IsServoComputedValues(),
+ "Can't COW-mutate servo values from Gecko!");
+
+ // If we already own the struct and no kids could depend on it, then
+ // just return it. (We leak in this case if there are kids -- and this
+ // function really shouldn't be called for style contexts that could
+ // have kids depending on the data. ClearStyleData would be OK, but
+ // this test for no mChild or mEmptyChild doesn't catch that case.)
+ const void *current = StyleData(aSID);
+ if (!mChild && !mEmptyChild &&
+ !(mBits & nsCachedStyleData::GetBitForSID(aSID)) &&
+ GetCachedStyleData(aSID))
+ return const_cast<void*>(current);
+
+ void* result;
+ nsPresContext *presContext = PresContext();
+ switch (aSID) {
+
+#define UNIQUE_CASE(c_) \
+ case eStyleStruct_##c_: \
+ result = new (presContext) nsStyle##c_( \
+ * static_cast<const nsStyle##c_ *>(current)); \
+ break;
+
+ UNIQUE_CASE(Font)
+ UNIQUE_CASE(Display)
+ UNIQUE_CASE(Text)
+ UNIQUE_CASE(TextReset)
+ UNIQUE_CASE(Visibility)
+
+#undef UNIQUE_CASE
+
+ default:
+ NS_ERROR("Struct type not supported. Please find another way to do this if you can!");
+ return nullptr;
+ }
+
+ SetStyle(aSID, result);
+ mBits &= ~static_cast<uint64_t>(nsCachedStyleData::GetBitForSID(aSID));
+
+ return result;
+}
+
+// This is an evil function, but less evil than GetUniqueStyleData. It
+// creates an empty style struct for this nsStyleContext.
+void*
+nsStyleContext::CreateEmptyStyleData(const nsStyleStructID& aSID)
+{
+ MOZ_ASSERT(!mChild && !mEmptyChild &&
+ !(mBits & nsCachedStyleData::GetBitForSID(aSID)) &&
+ !GetCachedStyleData(aSID),
+ "This style should not have been computed");
+
+ void* result;
+ nsPresContext* presContext = PresContext();
+ switch (aSID) {
+#define UNIQUE_CASE(c_) \
+ case eStyleStruct_##c_: \
+ result = new (presContext) nsStyle##c_(presContext); \
+ break;
+
+ UNIQUE_CASE(Border)
+ UNIQUE_CASE(Padding)
+
+#undef UNIQUE_CASE
+
+ default:
+ NS_ERROR("Struct type not supported.");
+ return nullptr;
+ }
+
+ // The new struct is owned by this style context, but that we don't
+ // need to clear the bit in mBits because we've asserted that at the
+ // top of this function.
+ SetStyle(aSID, result);
+ return result;
+}
+
+void
+nsStyleContext::SetStyle(nsStyleStructID aSID, void* aStruct)
+{
+ // This method should only be called from nsRuleNode! It is not a public
+ // method!
+
+ NS_ASSERTION(aSID >= 0 && aSID < nsStyleStructID_Length, "out of bounds");
+
+ // NOTE: nsCachedStyleData::GetStyleData works roughly the same way.
+ // See the comments there (in nsRuleNode.h) for more details about
+ // what this is doing and why.
+
+ void** dataSlot;
+ if (nsCachedStyleData::IsReset(aSID)) {
+ if (!mCachedResetData) {
+ mCachedResetData = new (PresContext()) nsResetStyleData;
+ }
+ dataSlot = &mCachedResetData->mStyleStructs[aSID];
+ } else {
+ dataSlot = &mCachedInheritedData.mStyleStructs[aSID];
+ }
+ NS_ASSERTION(!*dataSlot || (mBits & nsCachedStyleData::GetBitForSID(aSID)),
+ "Going to leak style data");
+ *dataSlot = aStruct;
+}
+
+static bool
+ShouldSuppressLineBreak(const nsStyleContext* aContext,
+ const nsStyleDisplay* aDisplay,
+ const nsStyleContext* aParentContext,
+ const nsStyleDisplay* aParentDisplay)
+{
+ // The display change should only occur for "in-flow" children
+ if (aDisplay->IsOutOfFlowStyle()) {
+ return false;
+ }
+ // Display value of any anonymous box should not be touched. In most
+ // cases, anonymous boxes are actually not in ruby frame, but instead,
+ // some other frame with a ruby display value. Non-element pseudos
+ // which represents text frames, as well as ruby pseudos are excluded
+ // because we still want to set the flag for them.
+ if (aContext->GetPseudoType() == CSSPseudoElementType::AnonBox &&
+ !nsCSSAnonBoxes::IsNonElement(aContext->GetPseudo()) &&
+ !RubyUtils::IsRubyPseudo(aContext->GetPseudo())) {
+ return false;
+ }
+ if (aParentContext->ShouldSuppressLineBreak()) {
+ // Line break suppressing bit is propagated to any children of
+ // line participants, which include inline, contents, and inline
+ // ruby boxes.
+ if (aParentDisplay->mDisplay == mozilla::StyleDisplay::Inline ||
+ aParentDisplay->mDisplay == mozilla::StyleDisplay::Contents ||
+ aParentDisplay->mDisplay == mozilla::StyleDisplay::Ruby ||
+ aParentDisplay->mDisplay == mozilla::StyleDisplay::RubyBaseContainer) {
+ return true;
+ }
+ }
+ // Any descendant of ruby level containers is non-breakable, but
+ // the level containers themselves are breakable. We have to check
+ // the container display type against all ruby display type here
+ // because any of the ruby boxes could be anonymous.
+ // Note that, when certain HTML tags, e.g. form controls, have ruby
+ // level container display type, they could also escape from this flag
+ // while they shouldn't. However, it is generally fine since they
+ // won't usually break the assertion that there is no line break
+ // inside ruby, because:
+ // 1. their display types, the ruby level container types, are inline-
+ // outside, which means they won't cause any forced line break; and
+ // 2. they never start an inline span, which means their children, if
+ // any, won't be able to break the line its ruby ancestor lays; and
+ // 3. their parent frame is always a ruby content frame (due to
+ // anonymous ruby box generation), which makes line layout suppress
+ // any optional line break around this frame.
+ // However, there is one special case which is BR tag, because it
+ // directly affects the line layout. This case is handled by the BR
+ // frame which checks the flag of its parent frame instead of itself.
+ if ((aParentDisplay->IsRubyDisplayType() &&
+ aDisplay->mDisplay != mozilla::StyleDisplay::RubyBaseContainer &&
+ aDisplay->mDisplay != mozilla::StyleDisplay::RubyTextContainer) ||
+ // Since ruby base and ruby text may exist themselves without any
+ // non-anonymous frame outside, we should also check them.
+ aDisplay->mDisplay == mozilla::StyleDisplay::RubyBase ||
+ aDisplay->mDisplay == mozilla::StyleDisplay::RubyText) {
+ return true;
+ }
+ return false;
+}
+
+// Flex & grid containers blockify their children.
+// "The display value of a flex item is blockified"
+// https://drafts.csswg.org/css-flexbox-1/#flex-items
+// "The display value of a grid item is blockified"
+// https://drafts.csswg.org/css-grid/#grid-items
+static bool
+ShouldBlockifyChildren(const nsStyleDisplay* aStyleDisp)
+{
+ auto displayVal = aStyleDisp->mDisplay;
+ return mozilla::StyleDisplay::Flex == displayVal ||
+ mozilla::StyleDisplay::InlineFlex == displayVal ||
+ mozilla::StyleDisplay::Grid == displayVal ||
+ mozilla::StyleDisplay::InlineGrid == displayVal;
+}
+
+void
+nsStyleContext::SetStyleBits()
+{
+ // XXXbholley: We should get this information directly from the
+ // ServoComputedValues rather than computing it here. This setup for
+ // ServoComputedValues-backed nsStyleContexts is probably not something
+ // we should ship.
+ //
+ // For example, NS_STYLE_IS_TEXT_COMBINED is still set in ApplyStyleFixups,
+ // which isn't called for ServoComputedValues.
+
+ // See if we have any text decorations.
+ // First see if our parent has text decorations. If our parent does, then we inherit the bit.
+ if (mParent && mParent->HasTextDecorationLines()) {
+ mBits |= NS_STYLE_HAS_TEXT_DECORATION_LINES;
+ } else {
+ // We might have defined a decoration.
+ if (StyleTextReset()->HasTextDecorationLines()) {
+ mBits |= NS_STYLE_HAS_TEXT_DECORATION_LINES;
+ }
+ }
+
+ if ((mParent && mParent->HasPseudoElementData()) || IsPseudoElement()) {
+ mBits |= NS_STYLE_HAS_PSEUDO_ELEMENT_DATA;
+ }
+
+ // Set the NS_STYLE_IN_DISPLAY_NONE_SUBTREE bit
+ const nsStyleDisplay* disp = StyleDisplay();
+ if ((mParent && mParent->IsInDisplayNoneSubtree()) ||
+ disp->mDisplay == mozilla::StyleDisplay::None) {
+ mBits |= NS_STYLE_IN_DISPLAY_NONE_SUBTREE;
+ }
+}
+
+void
+nsStyleContext::ApplyStyleFixups(bool aSkipParentDisplayBasedStyleFixup)
+{
+ MOZ_ASSERT(!mSource.IsServoComputedValues(),
+ "Can't do Gecko style fixups on Servo values");
+
+#define GET_UNIQUE_STYLE_DATA(name_) \
+ static_cast<nsStyle##name_*>(GetUniqueStyleData(eStyleStruct_##name_))
+
+ // CSS Inline Layout Level 3 - 3.5 Sizing Initial Letters:
+ // For an N-line drop initial in a Western script, the cap-height of the
+ // letter needs to be (N – 1) times the line-height, plus the cap-height
+ // of the surrounding text.
+ if (mPseudoTag == nsCSSPseudoElements::firstLetter) {
+ const nsStyleTextReset* textReset = StyleTextReset();
+ if (textReset->mInitialLetterSize != 0.0f) {
+ nsStyleContext* containerSC = mParent;
+ const nsStyleDisplay* containerDisp = containerSC->StyleDisplay();
+ while (containerDisp->mDisplay == mozilla::StyleDisplay::Contents) {
+ if (!containerSC->GetParent()) {
+ break;
+ }
+ containerSC = containerSC->GetParent();
+ containerDisp = containerSC->StyleDisplay();
+ }
+ nscoord containerLH =
+ ReflowInput::CalcLineHeight(nullptr, containerSC, NS_AUTOHEIGHT, 1.0f);
+ RefPtr<nsFontMetrics> containerFM =
+ nsLayoutUtils::GetFontMetricsForStyleContext(containerSC);
+ MOZ_ASSERT(containerFM, "Should have fontMetrics!!");
+ nscoord containerCH = containerFM->CapHeight();
+ RefPtr<nsFontMetrics> firstLetterFM =
+ nsLayoutUtils::GetFontMetricsForStyleContext(this);
+ MOZ_ASSERT(firstLetterFM, "Should have fontMetrics!!");
+ nscoord firstLetterCH = firstLetterFM->CapHeight();
+ nsStyleFont* mutableStyleFont = GET_UNIQUE_STYLE_DATA(Font);
+ float invCapHeightRatio =
+ mutableStyleFont->mFont.size / NSCoordToFloat(firstLetterCH);
+ mutableStyleFont->mFont.size =
+ NSToCoordRound(((textReset->mInitialLetterSize - 1) * containerLH +
+ containerCH) *
+ invCapHeightRatio);
+ }
+ }
+
+ // Change writing mode of text frame for text-combine-upright. We use
+ // style structs of the parent to avoid triggering computation before
+ // we change the writing mode.
+ // It is safe to look at the parent's style because we are looking at
+ // inherited properties, and ::-moz-text never matches any rules.
+ if (mPseudoTag == nsCSSAnonBoxes::mozText && mParent &&
+ mParent->StyleVisibility()->mWritingMode !=
+ NS_STYLE_WRITING_MODE_HORIZONTAL_TB &&
+ mParent->StyleText()->mTextCombineUpright ==
+ NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL) {
+ MOZ_ASSERT(!PeekStyleVisibility(), "If StyleVisibility was already "
+ "computed, some properties may have been computed "
+ "incorrectly based on the old writing mode value");
+ nsStyleVisibility* mutableVis = GET_UNIQUE_STYLE_DATA(Visibility);
+ mutableVis->mWritingMode = NS_STYLE_WRITING_MODE_HORIZONTAL_TB;
+ AddStyleBit(NS_STYLE_IS_TEXT_COMBINED);
+ }
+
+ // CSS 2.1 10.1: Propagate the root element's 'direction' to the ICB.
+ // (PageContentFrame/CanvasFrame etc will inherit 'direction')
+ if (mPseudoTag == nsCSSAnonBoxes::viewport) {
+ nsPresContext* presContext = PresContext();
+ mozilla::dom::Element* docElement = presContext->Document()->GetRootElement();
+ if (docElement) {
+ RefPtr<nsStyleContext> rootStyle =
+ presContext->StyleSet()->ResolveStyleFor(docElement, nullptr);
+ auto dir = rootStyle->StyleVisibility()->mDirection;
+ if (dir != StyleVisibility()->mDirection) {
+ nsStyleVisibility* uniqueVisibility = GET_UNIQUE_STYLE_DATA(Visibility);
+ uniqueVisibility->mDirection = dir;
+ }
+ }
+ }
+
+ // Correct tables.
+ const nsStyleDisplay* disp = StyleDisplay();
+ if (disp->mDisplay == mozilla::StyleDisplay::Table) {
+ // -moz-center and -moz-right are used for HTML's alignment
+ // This is covering the <div align="right"><table>...</table></div> case.
+ // In this case, we don't want to inherit the text alignment into the table.
+ const nsStyleText* text = StyleText();
+
+ if (text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_LEFT ||
+ text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_CENTER ||
+ text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_RIGHT)
+ {
+ nsStyleText* uniqueText = GET_UNIQUE_STYLE_DATA(Text);
+ uniqueText->mTextAlign = NS_STYLE_TEXT_ALIGN_START;
+ }
+ }
+
+ // CSS2.1 section 9.2.4 specifies fixups for the 'display' property of
+ // the root element. We can't implement them in nsRuleNode because we
+ // don't want to store all display structs that aren't 'block',
+ // 'inline', or 'table' in the style context tree on the off chance
+ // that the root element has its style reresolved later. So do them
+ // here if needed, by changing the style data, so that other code
+ // doesn't get confused by looking at the style data.
+ if (!mParent) {
+ auto displayVal = disp->mDisplay;
+ if (displayVal != mozilla::StyleDisplay::Contents) {
+ nsRuleNode::EnsureBlockDisplay(displayVal, true);
+ } else {
+ // http://dev.w3.org/csswg/css-display/#transformations
+ // "... a display-outside of 'contents' computes to block-level
+ // on the root element."
+ displayVal = mozilla::StyleDisplay::Block;
+ }
+ if (displayVal != disp->mDisplay) {
+ nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display);
+ disp = mutable_display;
+
+ // If we're in this code, then mOriginalDisplay doesn't matter
+ // for purposes of the cascade (because this nsStyleDisplay
+ // isn't living in the ruletree anyway), and for determining
+ // hypothetical boxes it's better to have mOriginalDisplay
+ // matching mDisplay here.
+ mutable_display->mOriginalDisplay = mutable_display->mDisplay =
+ displayVal;
+ }
+ }
+
+ // Adjust the "display" values of flex and grid items (but not for raw text
+ // or placeholders). CSS3 Flexbox section 4 says:
+ // # The computed 'display' of a flex item is determined
+ // # by applying the table in CSS 2.1 Chapter 9.7.
+ // ...which converts inline-level elements to their block-level equivalents.
+ // Any block-level element directly contained by elements with ruby display
+ // values are converted to their inline-level equivalents.
+ if (!aSkipParentDisplayBasedStyleFixup && mParent) {
+ // Skip display:contents ancestors to reach the potential container.
+ // (If there are only display:contents ancestors between this node and
+ // a flex/grid container ancestor, then this node is a flex/grid item, since
+ // its parent *in the frame tree* will be the flex/grid container. So we treat
+ // it like a flex/grid item here.)
+ nsStyleContext* containerContext = mParent;
+ const nsStyleDisplay* containerDisp = containerContext->StyleDisplay();
+ while (containerDisp->mDisplay == mozilla::StyleDisplay::Contents) {
+ if (!containerContext->GetParent()) {
+ break;
+ }
+ containerContext = containerContext->GetParent();
+ containerDisp = containerContext->StyleDisplay();
+ }
+ if (ShouldBlockifyChildren(containerDisp) &&
+ !nsCSSAnonBoxes::IsNonElement(GetPseudo())) {
+ // NOTE: Technically, we shouldn't modify the 'display' value of
+ // positioned elements, since they aren't flex/grid items. However,
+ // we don't need to worry about checking for that, because if we're
+ // positioned, we'll have already been through a call to
+ // EnsureBlockDisplay() in nsRuleNode, so this call here won't change
+ // anything. So we're OK.
+ auto displayVal = disp->mDisplay;
+ nsRuleNode::EnsureBlockDisplay(displayVal);
+ if (displayVal != disp->mDisplay) {
+ NS_ASSERTION(!disp->IsAbsolutelyPositionedStyle(),
+ "We shouldn't be changing the display value of "
+ "positioned content (and we should have already "
+ "converted its display value to be block-level...)");
+ nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display);
+ disp = mutable_display;
+ mutable_display->mDisplay = displayVal;
+ }
+ }
+ }
+
+ // Note: This must come after the blockification above, otherwise we fail
+ // the grid-item-blockifying-001.html reftest.
+ if (mParent && ::ShouldSuppressLineBreak(this, disp, mParent,
+ mParent->StyleDisplay())) {
+ mBits |= NS_STYLE_SUPPRESS_LINEBREAK;
+ auto displayVal = disp->mDisplay;
+ nsRuleNode::EnsureInlineDisplay(displayVal);
+ if (displayVal != disp->mDisplay) {
+ nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display);
+ disp = mutable_display;
+ mutable_display->mDisplay = displayVal;
+ }
+ }
+ // Suppress border/padding of ruby level containers
+ if (disp->mDisplay == mozilla::StyleDisplay::RubyBaseContainer ||
+ disp->mDisplay == mozilla::StyleDisplay::RubyTextContainer) {
+ CreateEmptyStyleData(eStyleStruct_Border);
+ CreateEmptyStyleData(eStyleStruct_Padding);
+ }
+ if (disp->IsRubyDisplayType()) {
+ // Per CSS Ruby spec section Bidi Reordering, for all ruby boxes,
+ // the 'normal' and 'embed' values of 'unicode-bidi' should compute to
+ // 'isolate', and 'bidi-override' should compute to 'isolate-override'.
+ const nsStyleTextReset* textReset = StyleTextReset();
+ uint8_t unicodeBidi = textReset->mUnicodeBidi;
+ if (unicodeBidi == NS_STYLE_UNICODE_BIDI_NORMAL ||
+ unicodeBidi == NS_STYLE_UNICODE_BIDI_EMBED) {
+ unicodeBidi = NS_STYLE_UNICODE_BIDI_ISOLATE;
+ } else if (unicodeBidi == NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE) {
+ unicodeBidi = NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE;
+ }
+ if (unicodeBidi != textReset->mUnicodeBidi) {
+ nsStyleTextReset* mutableTextReset = GET_UNIQUE_STYLE_DATA(TextReset);
+ mutableTextReset->mUnicodeBidi = unicodeBidi;
+ }
+ }
+
+ /*
+ * According to https://drafts.csswg.org/css-writing-modes-3/#block-flow:
+ *
+ * If a box has a different block flow direction than its containing block:
+ * * If the box has a specified display of inline, its display computes
+ * to inline-block. [CSS21]
+ * ...etc.
+ */
+ if (disp->mDisplay == mozilla::StyleDisplay::Inline &&
+ !nsCSSAnonBoxes::IsNonElement(mPseudoTag) &&
+ mParent) {
+ auto cbContext = mParent;
+ while (cbContext->StyleDisplay()->mDisplay == mozilla::StyleDisplay::Contents) {
+ cbContext = cbContext->mParent;
+ }
+ MOZ_ASSERT(cbContext, "the root context can't have display:contents");
+ // We don't need the full mozilla::WritingMode value (incorporating dir
+ // and text-orientation) here; just the writing-mode property is enough.
+ if (StyleVisibility()->mWritingMode !=
+ cbContext->StyleVisibility()->mWritingMode) {
+ nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display);
+ disp = mutable_display;
+ mutable_display->mOriginalDisplay = mutable_display->mDisplay =
+ mozilla::StyleDisplay::InlineBlock;
+ }
+ }
+
+ // Compute User Interface style, to trigger loads of cursors
+ StyleUserInterface();
+#undef GET_UNIQUE_STYLE_DATA
+}
+
+template<class StyleContextLike>
+nsChangeHint
+nsStyleContext::CalcStyleDifferenceInternal(StyleContextLike* aNewContext,
+ nsChangeHint aParentHintsNotHandledForDescendants,
+ uint32_t* aEqualStructs,
+ uint32_t* aSamePointerStructs)
+{
+ PROFILER_LABEL("nsStyleContext", "CalcStyleDifference",
+ js::ProfileEntry::Category::CSS);
+
+ MOZ_ASSERT(NS_IsHintSubset(aParentHintsNotHandledForDescendants,
+ nsChangeHint_Hints_NotHandledForDescendants),
+ "caller is passing inherited hints, but shouldn't be");
+
+ static_assert(nsStyleStructID_Length <= 32,
+ "aEqualStructs is not big enough");
+
+ *aEqualStructs = 0;
+
+ nsChangeHint hint = nsChangeHint(0);
+ NS_ENSURE_TRUE(aNewContext, hint);
+ // 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.
+
+ // If our sources are the same, then any differences in style data
+ // are already accounted for by differences on ancestors. We know
+ // this because CalcStyleDifference is always called on two style
+ // contexts that point to the same element, so we know that our
+ // position in the style context tree is the same and our position in
+ // the rule node tree (if applicable) is also the same.
+ // However, if there were noninherited style change hints on the
+ // parent, we might produce these same noninherited hints on this
+ // style context's frame due to 'inherit' values, so we do need to
+ // compare.
+ // (Things like 'em' units are handled by the change hint produced
+ // by font-size changing, so we don't need to worry about them like
+ // we worry about 'inherit' values.)
+ bool compare = StyleSource() != aNewContext->StyleSource();
+
+ DebugOnly<uint32_t> structsFound = 0;
+
+ // If we had any change in variable values, then we'll need to examine
+ // all of the other style structs too, even if the new style context has
+ // the same source as the old one.
+ const nsStyleVariables* thisVariables = PeekStyleVariables();
+ if (thisVariables) {
+ structsFound |= NS_STYLE_INHERIT_BIT(Variables);
+ const nsStyleVariables* otherVariables = aNewContext->StyleVariables();
+ if (thisVariables->mVariables == otherVariables->mVariables) {
+ *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
+ } else {
+ compare = true;
+ }
+ } else {
+ *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
+ }
+
+ DebugOnly<int> styleStructCount = 1; // count Variables already
+
+#define DO_STRUCT_DIFFERENCE(struct_) \
+ PR_BEGIN_MACRO \
+ const nsStyle##struct_* this##struct_ = PeekStyle##struct_(); \
+ if (this##struct_) { \
+ structsFound |= NS_STYLE_INHERIT_BIT(struct_); \
+ const nsStyle##struct_* other##struct_ = aNewContext->Style##struct_(); \
+ nsChangeHint maxDifference = nsStyle##struct_::MaxDifference(); \
+ nsChangeHint differenceAlwaysHandledForDescendants = \
+ nsStyle##struct_::DifferenceAlwaysHandledForDescendants(); \
+ if (this##struct_ == other##struct_) { \
+ /* The very same struct, so we know that there will be no */ \
+ /* differences. */ \
+ *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \
+ } else if (compare || \
+ ((maxDifference & ~differenceAlwaysHandledForDescendants) & \
+ aParentHintsNotHandledForDescendants)) { \
+ nsChangeHint difference = \
+ this##struct_->CalcDifference(*other##struct_ EXTRA_DIFF_ARGS); \
+ NS_ASSERTION(NS_IsHintSubset(difference, maxDifference), \
+ "CalcDifference() returned bigger hint than " \
+ "MaxDifference()"); \
+ hint |= difference; \
+ if (!difference) { \
+ *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \
+ } \
+ } else { \
+ /* We still must call CalcDifference to see if there were any */ \
+ /* changes so that we can set *aEqualStructs appropriately. */ \
+ nsChangeHint difference = \
+ this##struct_->CalcDifference(*other##struct_ EXTRA_DIFF_ARGS); \
+ NS_ASSERTION(NS_IsHintSubset(difference, maxDifference), \
+ "CalcDifference() returned bigger hint than " \
+ "MaxDifference()"); \
+ if (!difference) { \
+ *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \
+ } \
+ } \
+ } else { \
+ *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \
+ } \
+ styleStructCount++; \
+ PR_END_MACRO
+
+ // In general, we want to examine structs starting with those that can
+ // cause the largest style change, down to those that can cause the
+ // smallest. This lets us skip later ones if we already have a hint
+ // that subsumes their MaxDifference. (As the hints get
+ // finer-grained, this optimization is becoming less useful, though.)
+#define EXTRA_DIFF_ARGS /* nothing */
+ DO_STRUCT_DIFFERENCE(Display);
+ DO_STRUCT_DIFFERENCE(XUL);
+ DO_STRUCT_DIFFERENCE(Column);
+ DO_STRUCT_DIFFERENCE(Content);
+ DO_STRUCT_DIFFERENCE(UserInterface);
+ 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(List);
+ DO_STRUCT_DIFFERENCE(SVGReset);
+ DO_STRUCT_DIFFERENCE(SVG);
+#undef EXTRA_DIFF_ARGS
+#define EXTRA_DIFF_ARGS , PeekStyleVisibility()
+ DO_STRUCT_DIFFERENCE(Position);
+#undef EXTRA_DIFF_ARGS
+#define EXTRA_DIFF_ARGS /* nothing */
+ 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(Color);
+#undef EXTRA_DIFF_ARGS
+
+#undef DO_STRUCT_DIFFERENCE
+
+ MOZ_ASSERT(styleStructCount == nsStyleStructID_Length,
+ "missing a call to DO_STRUCT_DIFFERENCE");
+
+#ifdef DEBUG
+ #define STYLE_STRUCT(name_, callback_) \
+ MOZ_ASSERT(!!(structsFound & NS_STYLE_INHERIT_BIT(name_)) == \
+ !!PeekStyle##name_(), \
+ "PeekStyleData results must not change in the middle of " \
+ "difference calculation.");
+ #include "nsStyleStructList.h"
+ #undef STYLE_STRUCT
+#endif
+
+ // We check for struct pointer equality here rather than as part of the
+ // DO_STRUCT_DIFFERENCE calls, since those calls can result in structs
+ // we previously examined and found to be null on this style context
+ // getting computed by later DO_STRUCT_DIFFERENCE calls (which can
+ // happen when the nsRuleNode::ComputeXXXData method looks up another
+ // struct.) This is important for callers in RestyleManager that
+ // need to know the equality or not of the final set of cached struct
+ // pointers.
+ *aSamePointerStructs = 0;
+
+#define STYLE_STRUCT(name_, callback_) \
+ { \
+ const nsStyle##name_* data = PeekStyle##name_(); \
+ if (!data || data == aNewContext->Style##name_()) { \
+ *aSamePointerStructs |= NS_STYLE_INHERIT_BIT(name_); \
+ } \
+ }
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ // Note that we do not check whether this->RelevantLinkVisited() !=
+ // aNewContext->RelevantLinkVisited(); we don't need to since
+ // nsCSSFrameConstructor::DoContentStateChanged always adds
+ // nsChangeHint_RepaintFrame for NS_EVENT_STATE_VISITED changes (and
+ // needs to, since HasStateDependentStyle probably doesn't work right
+ // for NS_EVENT_STATE_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.
+ nsStyleContext *thisVis = GetStyleIfVisited(),
+ *otherVis = aNewContext->GetStyleIfVisited();
+ if (!thisVis != !otherVis) {
+ // One style context has a style-if-visited and the other doesn't.
+ // Presume a difference.
+ hint |= nsChangeHint_RepaintFrame;
+ } else if (thisVis && !NS_IsHintSubset(nsChangeHint_RepaintFrame, hint)) {
+ // Both style contexts 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 context
+ // not having a style-if-visited), but not the other way around.
+ if (PeekStyleColor()) {
+ if (thisVis->StyleColor()->mColor !=
+ otherVis->StyleColor()->mColor) {
+ change = true;
+ }
+ }
+
+ // NB: Calling Peek on |this|, not |thisVis| (see above).
+ if (!change && PeekStyleBackground()) {
+ if (thisVis->StyleBackground()->mBackgroundColor !=
+ otherVis->StyleBackground()->mBackgroundColor) {
+ change = true;
+ }
+ }
+
+ // NB: Calling Peek on |this|, not |thisVis| (see above).
+ if (!change && PeekStyleBorder()) {
+ const nsStyleBorder *thisVisBorder = thisVis->StyleBorder();
+ const nsStyleBorder *otherVisBorder = otherVis->StyleBorder();
+ NS_FOR_CSS_SIDES(side) {
+ if (thisVisBorder->mBorderColor[side] !=
+ otherVisBorder->mBorderColor[side]) {
+ change = true;
+ break;
+ }
+ }
+ }
+
+ // NB: Calling Peek on |this|, not |thisVis| (see above).
+ if (!change && PeekStyleOutline()) {
+ const nsStyleOutline *thisVisOutline = thisVis->StyleOutline();
+ const nsStyleOutline *otherVisOutline = otherVis->StyleOutline();
+ if (thisVisOutline->mOutlineColor != otherVisOutline->mOutlineColor) {
+ change = true;
+ }
+ }
+
+ // NB: Calling Peek on |this|, not |thisVis| (see above).
+ if (!change && PeekStyleColumn()) {
+ const nsStyleColumn *thisVisColumn = thisVis->StyleColumn();
+ const nsStyleColumn *otherVisColumn = otherVis->StyleColumn();
+ if (thisVisColumn->mColumnRuleColor != otherVisColumn->mColumnRuleColor) {
+ change = true;
+ }
+ }
+
+ // NB: Calling Peek on |this|, not |thisVis| (see above).
+ if (!change && PeekStyleText()) {
+ const nsStyleText* thisVisText = thisVis->StyleText();
+ const nsStyleText* otherVisText = otherVis->StyleText();
+ if (thisVisText->mTextEmphasisColor != otherVisText->mTextEmphasisColor ||
+ thisVisText->mWebkitTextFillColor != otherVisText->mWebkitTextFillColor ||
+ thisVisText->mWebkitTextStrokeColor != otherVisText->mWebkitTextStrokeColor) {
+ change = true;
+ }
+ }
+
+ // NB: Calling Peek on |this|, not |thisVis| (see above).
+ if (!change && PeekStyleTextReset()) {
+ const nsStyleTextReset *thisVisTextReset = thisVis->StyleTextReset();
+ const nsStyleTextReset *otherVisTextReset = otherVis->StyleTextReset();
+ if (thisVisTextReset->mTextDecorationColor !=
+ otherVisTextReset->mTextDecorationColor) {
+ change = true;
+ }
+ }
+
+ // NB: Calling Peek on |this|, not |thisVis| (see above).
+ if (!change && PeekStyleSVG()) {
+ const nsStyleSVG *thisVisSVG = thisVis->StyleSVG();
+ const nsStyleSVG *otherVisSVG = otherVis->StyleSVG();
+ if (thisVisSVG->mFill != otherVisSVG->mFill ||
+ thisVisSVG->mStroke != otherVisSVG->mStroke) {
+ change = true;
+ }
+ }
+
+ if (change) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ }
+
+ if (hint & nsChangeHint_UpdateContainingBlock) {
+ // 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 changed.
+
+ // This depends on data in nsStyleDisplay and nsStyleEffects, so we
+ // do it here.
+
+ // Note that it's perhaps good for this test to be last because it
+ // doesn't use Peek* functions to get the structs on the old
+ // context. But this isn't a big concern because these struct
+ // getters should be called during frame construction anyway.
+ if (StyleDisplay()->IsAbsPosContainingBlockForAppropriateFrame(this) ==
+ aNewContext->StyleDisplay()->
+ IsAbsPosContainingBlockForAppropriateFrame(aNewContext) &&
+ StyleDisplay()->IsFixedPosContainingBlockForAppropriateFrame(this) ==
+ aNewContext->StyleDisplay()->
+ IsFixedPosContainingBlockForAppropriateFrame(aNewContext)) {
+ // While some styles that cause the frame to be a containing block
+ // has changed, the overall result hasn't.
+ hint &= ~nsChangeHint_UpdateContainingBlock;
+ }
+ }
+
+ MOZ_ASSERT(NS_IsHintSubset(hint, nsChangeHint_AllHints),
+ "Added a new hint without bumping AllHints?");
+ return hint & ~nsChangeHint_NeutralChange;
+}
+
+nsChangeHint
+nsStyleContext::CalcStyleDifference(nsStyleContext* aNewContext,
+ nsChangeHint aParentHintsNotHandledForDescendants,
+ uint32_t* aEqualStructs,
+ uint32_t* aSamePointerStructs)
+{
+ return CalcStyleDifferenceInternal(aNewContext, aParentHintsNotHandledForDescendants,
+ aEqualStructs, aSamePointerStructs);
+}
+
+class MOZ_STACK_CLASS FakeStyleContext
+{
+public:
+ explicit FakeStyleContext(const ServoComputedValues* aComputedValues)
+ : mComputedValues(aComputedValues) {}
+
+ mozilla::NonOwningStyleContextSource StyleSource() const {
+ return mozilla::NonOwningStyleContextSource(mComputedValues);
+ }
+
+ nsStyleContext* GetStyleIfVisited() {
+ // XXXbholley: This is wrong. Need to implement to get visited handling
+ // corrrect!
+ return nullptr;
+ }
+
+ #define STYLE_STRUCT(name_, checkdata_cb_) \
+ const nsStyle##name_ * Style##name_() { \
+ return Servo_GetStyle##name_(mComputedValues); \
+ }
+ #include "nsStyleStructList.h"
+ #undef STYLE_STRUCT
+
+private:
+ const ServoComputedValues* MOZ_NON_OWNING_REF mComputedValues;
+};
+
+nsChangeHint
+nsStyleContext::CalcStyleDifference(const ServoComputedValues* aNewComputedValues,
+ nsChangeHint aParentHintsNotHandledForDescendants,
+ uint32_t* aEqualStructs,
+ uint32_t* aSamePointerStructs)
+{
+ FakeStyleContext newContext(aNewComputedValues);
+ return CalcStyleDifferenceInternal(&newContext, aParentHintsNotHandledForDescendants,
+ aEqualStructs, aSamePointerStructs);
+}
+
+#ifdef DEBUG
+void nsStyleContext::List(FILE* out, int32_t aIndent, bool aListDescendants)
+{
+ nsAutoCString str;
+ // Indent
+ int32_t ix;
+ for (ix = aIndent; --ix >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+ str.Append(nsPrintfCString("%p(%d) parent=%p ",
+ (void*)this, mRefCnt, (void *)mParent));
+ if (mPseudoTag) {
+ nsAutoString buffer;
+ mPseudoTag->ToString(buffer);
+ AppendUTF16toUTF8(buffer, str);
+ str.Append(' ');
+ }
+
+ if (mSource.IsServoComputedValues()) {
+ fprintf_stderr(out, "%s{ServoComputedValues}\n", str.get());
+ } else if (mSource.IsGeckoRuleNode()) {
+ fprintf_stderr(out, "%s{\n", str.get());
+ str.Truncate();
+ nsRuleNode* ruleNode = mSource.AsGeckoRuleNode();
+ while (ruleNode) {
+ nsIStyleRule *styleRule = ruleNode->GetRule();
+ if (styleRule) {
+ styleRule->List(out, aIndent + 1);
+ }
+ ruleNode = ruleNode->GetParent();
+ }
+ for (ix = aIndent; --ix >= 0; ) {
+ str.AppendLiteral(" ");
+ }
+ fprintf_stderr(out, "%s}\n", str.get());
+ }
+ else {
+ fprintf_stderr(out, "%s{}\n", str.get());
+ }
+
+ if (aListDescendants) {
+ if (nullptr != mChild) {
+ nsStyleContext* child = mChild;
+ do {
+ child->List(out, aIndent + 1, aListDescendants);
+ child = child->mNextSibling;
+ } while (mChild != child);
+ }
+ if (nullptr != mEmptyChild) {
+ nsStyleContext* child = mEmptyChild;
+ do {
+ child->List(out, aIndent + 1, aListDescendants);
+ child = child->mNextSibling;
+ } while (mEmptyChild != child);
+ }
+ }
+}
+#endif
+
+// Overloaded new operator. Initializes the memory to 0 and relies on an arena
+// (which comes from the presShell) to perform the allocation.
+void*
+nsStyleContext::operator new(size_t sz, nsPresContext* aPresContext)
+{
+ // Check the recycle list first.
+ return aPresContext->PresShell()->
+ AllocateByObjectID(eArenaObjectID_nsStyleContext, sz);
+}
+
+// Overridden to prevent the global delete from being called, since the memory
+// came out of an nsIArena instead of the global delete operator's heap.
+void
+nsStyleContext::Destroy()
+{
+ // Get the pres context.
+ RefPtr<nsPresContext> presContext = PresContext();
+
+ // Call our destructor.
+ this->~nsStyleContext();
+
+ // Don't let the memory be freed, since it will be recycled
+ // instead. Don't call the global operator delete.
+ presContext->PresShell()->
+ FreeByObjectID(eArenaObjectID_nsStyleContext, this);
+}
+
+already_AddRefed<nsStyleContext>
+NS_NewStyleContext(nsStyleContext* aParentContext,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType,
+ nsRuleNode* aRuleNode,
+ bool aSkipParentDisplayBasedStyleFixup)
+{
+ RefPtr<nsRuleNode> node = aRuleNode;
+ RefPtr<nsStyleContext> context =
+ new (aRuleNode->PresContext())
+ nsStyleContext(aParentContext, aPseudoTag, aPseudoType, node.forget(),
+ aSkipParentDisplayBasedStyleFixup);
+ return context.forget();
+}
+
+already_AddRefed<nsStyleContext>
+NS_NewStyleContext(nsStyleContext* aParentContext,
+ nsPresContext* aPresContext,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType,
+ already_AddRefed<ServoComputedValues> aComputedValues,
+ bool aSkipParentDisplayBasedStyleFixup)
+{
+ RefPtr<nsStyleContext> context =
+ new (aPresContext)
+ nsStyleContext(aParentContext, aPresContext, aPseudoTag, aPseudoType,
+ Move(aComputedValues), aSkipParentDisplayBasedStyleFixup);
+ return context.forget();
+}
+
+nsIPresShell*
+nsStyleContext::Arena()
+{
+ return PresContext()->PresShell();
+}
+
+static inline void
+ExtractAnimationValue(nsCSSPropertyID aProperty,
+ nsStyleContext* aStyleContext,
+ StyleAnimationValue& aResult)
+{
+ DebugOnly<bool> success =
+ StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
+ aResult);
+ MOZ_ASSERT(success,
+ "aProperty must be extractable by StyleAnimationValue");
+}
+
+static Maybe<nscolor>
+ExtractColor(nsCSSPropertyID aProperty,
+ nsStyleContext *aStyleContext)
+{
+ StyleAnimationValue val;
+ ExtractAnimationValue(aProperty, aStyleContext, val);
+ switch (val.GetUnit()) {
+ case StyleAnimationValue::eUnit_Color:
+ return Some(val.GetCSSValueValue()->GetColorValue());
+ case StyleAnimationValue::eUnit_CurrentColor:
+ return Some(aStyleContext->StyleColor()->mColor);
+ case StyleAnimationValue::eUnit_ComplexColor:
+ return Some(aStyleContext->StyleColor()->
+ CalcComplexColor(val.GetStyleComplexColorValue()));
+ default:
+ return Nothing();
+ }
+}
+
+static nscolor
+ExtractColorLenient(nsCSSPropertyID aProperty,
+ nsStyleContext *aStyleContext)
+{
+ return ExtractColor(aProperty, aStyleContext).valueOr(NS_RGBA(0, 0, 0, 0));
+}
+
+struct ColorIndexSet {
+ uint8_t colorIndex, alphaIndex;
+};
+
+static const ColorIndexSet gVisitedIndices[2] = { { 0, 0 }, { 1, 0 } };
+
+nscolor
+nsStyleContext::GetVisitedDependentColor(nsCSSPropertyID aProperty)
+{
+ NS_ASSERTION(aProperty == eCSSProperty_color ||
+ aProperty == eCSSProperty_background_color ||
+ aProperty == eCSSProperty_border_top_color ||
+ aProperty == eCSSProperty_border_right_color ||
+ aProperty == eCSSProperty_border_bottom_color ||
+ aProperty == eCSSProperty_border_left_color ||
+ aProperty == eCSSProperty_outline_color ||
+ aProperty == eCSSProperty_column_rule_color ||
+ aProperty == eCSSProperty_text_decoration_color ||
+ aProperty == eCSSProperty_text_emphasis_color ||
+ aProperty == eCSSProperty__webkit_text_fill_color ||
+ aProperty == eCSSProperty__webkit_text_stroke_color ||
+ aProperty == eCSSProperty_fill ||
+ aProperty == eCSSProperty_stroke,
+ "we need to add to nsStyleContext::CalcStyleDifference");
+
+ bool isPaintProperty = aProperty == eCSSProperty_fill ||
+ aProperty == eCSSProperty_stroke;
+
+ nscolor colors[2];
+ colors[0] = isPaintProperty ? ExtractColorLenient(aProperty, this)
+ : ExtractColor(aProperty, this).value();
+
+ nsStyleContext *visitedStyle = this->GetStyleIfVisited();
+ if (!visitedStyle) {
+ return colors[0];
+ }
+
+ colors[1] = isPaintProperty ? ExtractColorLenient(aProperty, visitedStyle)
+ : ExtractColor(aProperty, visitedStyle).value();
+
+ return nsStyleContext::CombineVisitedColors(colors,
+ this->RelevantLinkVisited());
+}
+
+/* static */ nscolor
+nsStyleContext::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 */ void
+nsStyleContext::AssertStyleStructMaxDifferenceValid()
+{
+#define STYLE_STRUCT(name, checkdata_cb) \
+ MOZ_ASSERT(NS_IsHintSubset(nsStyle##name::DifferenceAlwaysHandledForDescendants(), \
+ nsStyle##name::MaxDifference()));
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+}
+
+/* static */ const char*
+nsStyleContext::StructName(nsStyleStructID aSID)
+{
+ switch (aSID) {
+#define STYLE_STRUCT(name_, checkdata_cb) \
+ case eStyleStruct_##name_: \
+ return #name_;
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+ default:
+ return "Unknown";
+ }
+}
+
+/* static */ bool
+nsStyleContext::LookupStruct(const nsACString& aName, nsStyleStructID& aResult)
+{
+ if (false)
+ ;
+#define STYLE_STRUCT(name_, checkdata_cb_) \
+ else if (aName.EqualsLiteral(#name_)) \
+ aResult = eStyleStruct_##name_;
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+ else
+ return false;
+ return true;
+}
+#endif
+
+void
+nsStyleContext::SwapStyleData(nsStyleContext* aNewContext, uint32_t aStructs)
+{
+ static_assert(nsStyleStructID_Length <= 32, "aStructs is not big enough");
+
+ for (nsStyleStructID i = nsStyleStructID_Inherited_Start;
+ i < nsStyleStructID_Inherited_Start + nsStyleStructID_Inherited_Count;
+ i = nsStyleStructID(i + 1)) {
+ uint32_t bit = nsCachedStyleData::GetBitForSID(i);
+ if (!(aStructs & bit)) {
+ continue;
+ }
+ void*& thisData = mCachedInheritedData.mStyleStructs[i];
+ void*& otherData = aNewContext->mCachedInheritedData.mStyleStructs[i];
+ if (mBits & bit) {
+ if (thisData == otherData) {
+ thisData = nullptr;
+ }
+ } else if (!(aNewContext->mBits & bit) && thisData && otherData) {
+ std::swap(thisData, otherData);
+ }
+ }
+
+ for (nsStyleStructID i = nsStyleStructID_Reset_Start;
+ i < nsStyleStructID_Reset_Start + nsStyleStructID_Reset_Count;
+ i = nsStyleStructID(i + 1)) {
+ uint32_t bit = nsCachedStyleData::GetBitForSID(i);
+ if (!(aStructs & bit)) {
+ continue;
+ }
+ if (!mCachedResetData) {
+ mCachedResetData = new (PresContext()) nsResetStyleData;
+ }
+ if (!aNewContext->mCachedResetData) {
+ aNewContext->mCachedResetData = new (PresContext()) nsResetStyleData;
+ }
+ void*& thisData = mCachedResetData->mStyleStructs[i];
+ void*& otherData = aNewContext->mCachedResetData->mStyleStructs[i];
+ if (mBits & bit) {
+ if (thisData == otherData) {
+ thisData = nullptr;
+ }
+ } else if (!(aNewContext->mBits & bit) && thisData && otherData) {
+ std::swap(thisData, otherData);
+ }
+ }
+}
+
+void
+nsStyleContext::ClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs)
+{
+ if (mChild) {
+ nsStyleContext* child = mChild;
+ do {
+ child->DoClearCachedInheritedStyleDataOnDescendants(aStructs);
+ child = child->mNextSibling;
+ } while (mChild != child);
+ }
+ if (mEmptyChild) {
+ nsStyleContext* child = mEmptyChild;
+ do {
+ child->DoClearCachedInheritedStyleDataOnDescendants(aStructs);
+ child = child->mNextSibling;
+ } while (mEmptyChild != child);
+ }
+}
+
+void
+nsStyleContext::DoClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs)
+{
+ NS_ASSERTION(mFrameRefCnt == 0, "frame still referencing style context");
+ for (nsStyleStructID i = nsStyleStructID_Inherited_Start;
+ i < nsStyleStructID_Inherited_Start + nsStyleStructID_Inherited_Count;
+ i = nsStyleStructID(i + 1)) {
+ uint32_t bit = nsCachedStyleData::GetBitForSID(i);
+ if (aStructs & bit) {
+ if (!(mBits & bit) && mCachedInheritedData.mStyleStructs[i]) {
+ aStructs &= ~bit;
+ } else {
+ mCachedInheritedData.mStyleStructs[i] = nullptr;
+ }
+ }
+ }
+
+ if (mCachedResetData) {
+ for (nsStyleStructID i = nsStyleStructID_Reset_Start;
+ i < nsStyleStructID_Reset_Start + nsStyleStructID_Reset_Count;
+ i = nsStyleStructID(i + 1)) {
+ uint32_t bit = nsCachedStyleData::GetBitForSID(i);
+ if (aStructs & bit) {
+ if (!(mBits & bit) && mCachedResetData->mStyleStructs[i]) {
+ aStructs &= ~bit;
+ } else {
+ mCachedResetData->mStyleStructs[i] = nullptr;
+ }
+ }
+ }
+ }
+
+ if (aStructs == 0) {
+ return;
+ }
+
+ ClearCachedInheritedStyleDataOnDescendants(aStructs);
+}
+
+void
+nsStyleContext::SetIneligibleForSharing()
+{
+ if (mBits & NS_STYLE_INELIGIBLE_FOR_SHARING) {
+ return;
+ }
+ mBits |= NS_STYLE_INELIGIBLE_FOR_SHARING;
+ if (mChild) {
+ nsStyleContext* child = mChild;
+ do {
+ child->SetIneligibleForSharing();
+ child = child->mNextSibling;
+ } while (mChild != child);
+ }
+ if (mEmptyChild) {
+ nsStyleContext* child = mEmptyChild;
+ do {
+ child->SetIneligibleForSharing();
+ child = child->mNextSibling;
+ } while (mEmptyChild != child);
+ }
+}
+
+#ifdef RESTYLE_LOGGING
+nsCString
+nsStyleContext::GetCachedStyleDataAsString(uint32_t aStructs)
+{
+ nsCString structs;
+ for (nsStyleStructID i = nsStyleStructID(0);
+ i < nsStyleStructID_Length;
+ i = nsStyleStructID(i + 1)) {
+ if (aStructs & nsCachedStyleData::GetBitForSID(i)) {
+ const void* data = GetCachedStyleData(i);
+ if (!structs.IsEmpty()) {
+ structs.Append(' ');
+ }
+ structs.AppendPrintf("%s=%p", StructName(i), data);
+ if (HasCachedDependentStyleData(i)) {
+ structs.AppendLiteral("(dependent)");
+ } else {
+ structs.AppendLiteral("(owned)");
+ }
+ }
+ }
+ return structs;
+}
+
+int32_t&
+nsStyleContext::LoggingDepth()
+{
+ static int32_t depth = 0;
+ return depth;
+}
+
+void
+nsStyleContext::LogStyleContextTree(int32_t aLoggingDepth, uint32_t aStructs)
+{
+ LoggingDepth() = aLoggingDepth;
+ LogStyleContextTree(true, aStructs);
+}
+
+void
+nsStyleContext::LogStyleContextTree(bool aFirst, uint32_t aStructs)
+{
+ nsCString structs = GetCachedStyleDataAsString(aStructs);
+ if (!structs.IsEmpty()) {
+ structs.Append(' ');
+ }
+
+ nsCString pseudo;
+ if (mPseudoTag) {
+ nsAutoString pseudoTag;
+ mPseudoTag->ToString(pseudoTag);
+ AppendUTF16toUTF8(pseudoTag, pseudo);
+ pseudo.Append(' ');
+ }
+
+ nsCString flags;
+ if (IsStyleIfVisited()) {
+ flags.AppendLiteral("IS_STYLE_IF_VISITED ");
+ }
+ if (HasChildThatUsesGrandancestorStyle()) {
+ flags.AppendLiteral("CHILD_USES_GRANDANCESTOR_STYLE ");
+ }
+ if (IsShared()) {
+ flags.AppendLiteral("IS_SHARED ");
+ }
+
+ nsCString parent;
+ if (aFirst) {
+ parent.AppendPrintf("parent=%p ", mParent.get());
+ }
+
+ LOG_RESTYLE("%p(%d) %s%s%s%s",
+ this, mRefCnt,
+ structs.get(), pseudo.get(), flags.get(), parent.get());
+
+ LOG_RESTYLE_INDENT();
+
+ if (nullptr != mChild) {
+ nsStyleContext* child = mChild;
+ do {
+ child->LogStyleContextTree(false, aStructs);
+ child = child->mNextSibling;
+ } while (mChild != child);
+ }
+ if (nullptr != mEmptyChild) {
+ nsStyleContext* child = mEmptyChild;
+ do {
+ child->LogStyleContextTree(false, aStructs);
+ child = child->mNextSibling;
+ } while (mEmptyChild != child);
+ }
+}
+#endif
+
+#ifdef DEBUG
+/* static */ void
+nsStyleContext::Initialize()
+{
+ Preferences::AddBoolVarCache(
+ &sExpensiveStyleStructAssertionsEnabled,
+ "layout.css.expensive-style-struct-assertions.enabled");
+}
+#endif
diff --git a/layout/style/nsStyleContext.h b/layout/style/nsStyleContext.h
new file mode 100644
index 000000000..b0b4896a3
--- /dev/null
+++ b/layout/style/nsStyleContext.h
@@ -0,0 +1,853 @@
+/* -*- 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/. */
+
+/* the interface (to internal code) for retrieving computed style data */
+
+#ifndef _nsStyleContext_h_
+#define _nsStyleContext_h_
+
+#include "mozilla/Assertions.h"
+#include "mozilla/RestyleLogging.h"
+#include "mozilla/StyleContextSource.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsStyleSet.h"
+
+class nsIAtom;
+class nsPresContext;
+
+namespace mozilla {
+enum class CSSPseudoElementType : uint8_t;
+} // namespace mozilla
+
+extern "C" {
+#define STYLE_STRUCT(name_, checkdata_cb_) \
+ struct nsStyle##name_; \
+ const nsStyle##name_* Servo_GetStyle##name_( \
+ ServoComputedValuesBorrowedOrNull computed_values);
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+}
+
+/**
+ * An nsStyleContext represents the computed style data for an element.
+ * The computed style data are stored in a set of structs (see
+ * nsStyleStruct.h) that are cached either on the style context or in
+ * the rule tree (see nsRuleNode.h for a description of this caching and
+ * how the cached structs are shared).
+ *
+ * Since the data in |nsIStyleRule|s and |nsRuleNode|s are immutable
+ * (with a few exceptions, like system color changes), the data in an
+ * nsStyleContext are also immutable (with the additional exception of
+ * GetUniqueStyleData). When style data change,
+ * ElementRestyler::Restyle creates a new style context.
+ *
+ * Style contexts are reference counted. References are generally held
+ * by:
+ * 1. the |nsIFrame|s that are using the style context and
+ * 2. any *child* style contexts (this might be the reverse of
+ * expectation, but it makes sense in this case)
+ */
+
+class nsStyleContext final
+{
+public:
+ /**
+ * Create a new style context.
+ * @param aParent The parent of a style context is used for CSS
+ * inheritance. When the element or pseudo-element
+ * this style context represents the style data of
+ * inherits a CSS property, the value comes from the
+ * parent style context. This means style context
+ * parentage must match the definitions of inheritance
+ * in the CSS specification.
+ * @param aPseudoTag The pseudo-element or anonymous box for which
+ * this style context represents style. Null if
+ * this style context is for a normal DOM element.
+ * @param aPseudoType Must match aPseudoTag.
+ * @param aRuleNode A rule node representing the ordered sequence of
+ * rules that any element, pseudo-element, or
+ * anonymous box that this style context is for
+ * matches. See |nsRuleNode| and |nsIStyleRule|.
+ * @param aSkipParentDisplayBasedStyleFixup
+ * If set, this flag indicates that we should skip
+ * the chunk of ApplyStyleFixups() that applies to
+ * special cases where a child element's style may
+ * need to be modified based on its parent's display
+ * value.
+ */
+ nsStyleContext(nsStyleContext* aParent, nsIAtom* aPseudoTag,
+ mozilla::CSSPseudoElementType aPseudoType,
+ already_AddRefed<nsRuleNode> aRuleNode,
+ bool aSkipParentDisplayBasedStyleFixup);
+
+ // Version of the above that takes a ServoComputedValues instead of a Gecko
+ // nsRuleNode.
+ nsStyleContext(nsStyleContext* aParent,
+ nsPresContext* aPresContext,
+ nsIAtom* aPseudoTag,
+ mozilla::CSSPseudoElementType aPseudoType,
+ already_AddRefed<ServoComputedValues> aComputedValues,
+ bool aSkipParentDisplayBasedStyleFixup);
+
+ void* operator new(size_t sz, nsPresContext* aPresContext);
+ void Destroy();
+
+ // These two methods are for use by ArenaRefPtr.
+ static mozilla::ArenaObjectID ArenaObjectID()
+ {
+ return mozilla::eArenaObjectID_nsStyleContext;
+ }
+ nsIPresShell* Arena();
+
+#ifdef DEBUG
+ /**
+ * Initializes a cached pref, which is only used in DEBUG code.
+ */
+ static void Initialize();
+#endif
+
+ nsrefcnt AddRef() {
+ if (mRefCnt == UINT32_MAX) {
+ NS_WARNING("refcount overflow, leaking object");
+ return mRefCnt;
+ }
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "nsStyleContext", sizeof(nsStyleContext));
+ return mRefCnt;
+ }
+
+ nsrefcnt Release() {
+ if (mRefCnt == UINT32_MAX) {
+ NS_WARNING("refcount overflow, leaking object");
+ return mRefCnt;
+ }
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "nsStyleContext");
+ if (mRefCnt == 0) {
+ Destroy();
+ return 0;
+ }
+ return mRefCnt;
+ }
+
+#ifdef DEBUG
+ void FrameAddRef() {
+ ++mFrameRefCnt;
+ }
+
+ void FrameRelease() {
+ --mFrameRefCnt;
+ }
+
+ uint32_t FrameRefCnt() const {
+ return mFrameRefCnt;
+ }
+#endif
+
+ bool HasSingleReference() const {
+ NS_ASSERTION(mRefCnt != 0,
+ "do not call HasSingleReference on a newly created "
+ "nsStyleContext with no references yet");
+ return mRefCnt == 1;
+ }
+
+ nsPresContext* PresContext() const {
+#ifdef MOZ_STYLO
+ return mPresContext;
+#else
+ return mSource.AsGeckoRuleNode()->PresContext();
+#endif
+ }
+
+ nsStyleContext* GetParent() const { return mParent; }
+
+ nsIAtom* GetPseudo() const { return mPseudoTag; }
+ mozilla::CSSPseudoElementType GetPseudoType() const {
+ return static_cast<mozilla::CSSPseudoElementType>(
+ mBits >> NS_STYLE_CONTEXT_TYPE_SHIFT);
+ }
+
+ bool IsAnonBox() const {
+ return GetPseudoType() == mozilla::CSSPseudoElementType::AnonBox;
+ }
+ bool IsPseudoElement() const { return mPseudoTag && !IsAnonBox(); }
+
+
+ // Find, if it already exists *and is easily findable* (i.e., near the
+ // start of the child list), a style context whose:
+ // * GetPseudo() matches aPseudoTag
+ // * mSource matches aSource
+ // * !!GetStyleIfVisited() == !!aSourceIfVisited, and, if they're
+ // non-null, GetStyleIfVisited()->mSource == aSourceIfVisited
+ // * RelevantLinkVisited() == aRelevantLinkVisited
+ already_AddRefed<nsStyleContext>
+ FindChildWithRules(const nsIAtom* aPseudoTag,
+ mozilla::NonOwningStyleContextSource aSource,
+ mozilla::NonOwningStyleContextSource aSourceIfVisited,
+ bool aRelevantLinkVisited);
+
+ // Does this style context 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 !!(mBits & NS_STYLE_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 !!(mBits & NS_STYLE_SUPPRESS_LINEBREAK); }
+
+ // Does this style context or any of its ancestors have display:none set?
+ bool IsInDisplayNoneSubtree() const
+ { return !!(mBits & NS_STYLE_IN_DISPLAY_NONE_SUBTREE); }
+
+ // Is this horizontal-in-vertical (tate-chu-yoko) text? This flag is
+ // only set on style contexts whose pseudo is nsCSSAnonBoxes::mozText.
+ bool IsTextCombined() const
+ { return !!(mBits & NS_STYLE_IS_TEXT_COMBINED); }
+
+ // Does this style context represent the style for a pseudo-element or
+ // inherit data from such a style context? Whether this returns true
+ // is equivalent to whether it or any of its ancestors returns
+ // non-null for IsPseudoElement().
+ bool HasPseudoElementData() const
+ { return !!(mBits & NS_STYLE_HAS_PSEUDO_ELEMENT_DATA); }
+
+ bool HasChildThatUsesResetStyle() const
+ { return mBits & NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE; }
+
+ // Is the only link whose visitedness is allowed to influence the
+ // style of the node this style context is for (which is that element
+ // or its nearest ancestor that is a link) visited?
+ bool RelevantLinkVisited() const
+ { return !!(mBits & NS_STYLE_RELEVANT_LINK_VISITED); }
+
+ // Is this a style context for a link?
+ bool IsLinkContext() const {
+ return
+ GetStyleIfVisited() && GetStyleIfVisited()->GetParent() == GetParent();
+ }
+
+ // Is this style context the GetStyleIfVisited() for some other style
+ // context?
+ bool IsStyleIfVisited() const
+ { return !!(mBits & NS_STYLE_IS_STYLE_IF_VISITED); }
+
+ // Tells this style context that it should return true from
+ // IsStyleIfVisited.
+ void SetIsStyleIfVisited()
+ { mBits |= NS_STYLE_IS_STYLE_IF_VISITED; }
+
+ // Return the style context 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 style context 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 nsStyleContext::CalcStyleDifference.
+ nsStyleContext* GetStyleIfVisited() const
+ { return mStyleIfVisited; }
+
+ // To be called only from nsStyleSet.
+ void SetStyleIfVisited(already_AddRefed<nsStyleContext> aStyleIfVisited)
+ {
+ MOZ_ASSERT(!IsStyleIfVisited(), "this context is not visited data");
+ NS_ASSERTION(!mStyleIfVisited, "should only be set once");
+
+ mStyleIfVisited = aStyleIfVisited;
+
+ MOZ_ASSERT(mStyleIfVisited->IsStyleIfVisited(),
+ "other context is visited data");
+ MOZ_ASSERT(!mStyleIfVisited->GetStyleIfVisited(),
+ "other context does not have visited data");
+ NS_ASSERTION(GetStyleIfVisited()->GetPseudo() == GetPseudo(),
+ "pseudo tag mismatch");
+ if (GetParent() && GetParent()->GetStyleIfVisited()) {
+ NS_ASSERTION(GetStyleIfVisited()->GetParent() ==
+ GetParent()->GetStyleIfVisited() ||
+ GetStyleIfVisited()->GetParent() == GetParent(),
+ "parent mismatch");
+ } else {
+ NS_ASSERTION(GetStyleIfVisited()->GetParent() == GetParent(),
+ "parent mismatch");
+ }
+ }
+
+ // Does any descendant of this style context have any style values
+ // that were computed based on this style context's ancestors?
+ bool HasChildThatUsesGrandancestorStyle() const
+ { return !!(mBits & NS_STYLE_CHILD_USES_GRANDANCESTOR_STYLE); }
+
+ // Is this style context shared with a sibling or cousin?
+ // (See nsStyleSet::GetContext.)
+ bool IsShared() const
+ { return !!(mBits & NS_STYLE_IS_SHARED); }
+
+ // Tell this style context to cache aStruct as the struct for aSID
+ void SetStyle(nsStyleStructID aSID, void* aStruct);
+
+ /**
+ * Returns whether this style context has cached style data for a
+ * given style struct and it does NOT own that struct. This can
+ * happen because it was inherited from the parent style context, or
+ * because it was stored conditionally on the rule node.
+ */
+ bool HasCachedDependentStyleData(nsStyleStructID aSID) {
+ return mBits & nsCachedStyleData::GetBitForSID(aSID);
+ }
+
+ nsRuleNode* RuleNode() {
+ MOZ_RELEASE_ASSERT(mSource.IsGeckoRuleNode());
+ return mSource.AsGeckoRuleNode();
+ }
+
+ void AddStyleBit(const uint64_t& aBit) { mBits |= aBit; }
+
+ /*
+ * Get the style data for a style struct. This is the most important
+ * member function of nsStyleContext. It fills in a const pointer
+ * to a style data struct that is appropriate for the style context's
+ * frame. This struct may be shared with other contexts (either in
+ * the rule tree or the style context tree), so it should not be
+ * modified.
+ *
+ * This function will NOT return null (even when out of memory) when
+ * given a valid style struct ID, so the result does not need to be
+ * null-checked.
+ *
+ * The typesafe functions below are preferred to the use of this
+ * function, both because they're easier to read and because they're
+ * faster.
+ */
+ const void* NS_FASTCALL StyleData(nsStyleStructID aSID);
+
+ /**
+ * Define typesafe getter functions for each style struct by
+ * preprocessing the list of style structs. These functions are the
+ * preferred way to get style data. The macro creates functions like:
+ * const nsStyleBorder* StyleBorder();
+ * const nsStyleColor* StyleColor();
+ */
+ #define STYLE_STRUCT(name_, checkdata_cb_) \
+ const nsStyle##name_ * Style##name_() { \
+ return DoGetStyle##name_<true>(); \
+ }
+ #include "nsStyleStructList.h"
+ #undef STYLE_STRUCT
+
+ /**
+ * PeekStyle* is like Style* but doesn't trigger style
+ * computation if the data is not cached on either the style context
+ * or the rule node.
+ *
+ * Perhaps this shouldn't be a public nsStyleContext API.
+ */
+ #define STYLE_STRUCT(name_, checkdata_cb_) \
+ const nsStyle##name_ * PeekStyle##name_() { \
+ return DoGetStyle##name_<false>(); \
+ }
+ #include "nsStyleStructList.h"
+ #undef STYLE_STRUCT
+
+ /**
+ * 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. The caller must pass in any
+ * non-inherited hints that resulted from the parent style context's
+ * style change. The caller *may* pass more hints than needed, but
+ * must not pass less than needed; therefore if the caller doesn't
+ * know, the caller should pass
+ * nsChangeHint_Hints_NotHandledForDescendants.
+ *
+ * aEqualStructs must not be null. Into it will be stored a bitfield
+ * representing which structs were compared to be non-equal.
+ */
+ nsChangeHint CalcStyleDifference(nsStyleContext* aNewContext,
+ nsChangeHint aParentHintsNotHandledForDescendants,
+ uint32_t* aEqualStructs,
+ uint32_t* aSamePointerStructs);
+
+ /**
+ * Like the above, but allows comparing ServoComputedValues instead of needing
+ * a full-fledged style context.
+ */
+ nsChangeHint CalcStyleDifference(const ServoComputedValues* aNewComputedValues,
+ nsChangeHint aParentHintsNotHandledForDescendants,
+ uint32_t* aEqualStructs,
+ uint32_t* aSamePointerStructs);
+
+private:
+ template<class StyleContextLike>
+ nsChangeHint CalcStyleDifferenceInternal(StyleContextLike* aNewContext,
+ nsChangeHint aParentHintsNotHandledForDescendants,
+ uint32_t* aEqualStructs,
+ uint32_t* aSamePointerStructs);
+
+public:
+ /**
+ * Get a color that depends on link-visitedness using this and
+ * this->GetStyleIfVisited().
+ *
+ * aProperty must be a color-valued property that StyleAnimationValue
+ * knows how to extract. It must also be a property that we know to
+ * do change handling for in nsStyleContext::CalcDifference.
+ */
+ nscolor GetVisitedDependentColor(nsCSSPropertyID aProperty);
+
+ /**
+ * 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 the background image loads for this style context.
+ */
+ void StartBackgroundImageLoads() {
+ // Just get our background struct; that should do the trick
+ StyleBackground();
+ }
+
+ /**
+ * Moves this style context to a new parent.
+ *
+ * This function violates style context tree immutability, and
+ * is a very low-level function and should only be used after verifying
+ * many conditions that make it safe to call.
+ */
+ void MoveTo(nsStyleContext* aNewParent);
+
+ /**
+ * Swaps owned style struct pointers between this and aNewContext, on
+ * the assumption that aNewContext is the new style context for a frame
+ * and this is the old one. aStructs indicates which structs to consider
+ * swapping; only those which are owned in both this and aNewContext
+ * will be swapped.
+ *
+ * Additionally, if there are identical struct pointers for one of the
+ * structs indicated by aStructs, and it is not an owned struct on this,
+ * then the cached struct slot on this will be set to null. If the struct
+ * has been swapped on an ancestor, this style context (being the old one)
+ * will be left caching the struct pointer on the new ancestor, despite
+ * inheriting from the old ancestor. This is not normally a problem, as
+ * this style context will usually be destroyed by being released at the
+ * end of ElementRestyler::Restyle; but for style contexts held on to outside
+ * of the frame, we need to clear out the cached pointer so that if we need
+ * it again we'll re-fetch it from the new ancestor.
+ */
+ void SwapStyleData(nsStyleContext* aNewContext, uint32_t aStructs);
+
+ /**
+ * On each descendant of this style context, clears out any cached inherited
+ * structs indicated in aStructs.
+ */
+ void ClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs);
+
+ /**
+ * Sets the NS_STYLE_INELIGIBLE_FOR_SHARING bit on this style context
+ * and its descendants. If it finds a descendant that has the bit
+ * already set, assumes that it can skip that subtree.
+ */
+ void SetIneligibleForSharing();
+
+#ifdef DEBUG
+ void List(FILE* out, int32_t aIndent, bool aListDescendants = true);
+ static void AssertStyleStructMaxDifferenceValid();
+ static const char* StructName(nsStyleStructID aSID);
+ static bool LookupStruct(const nsACString& aName, nsStyleStructID& aResult);
+#endif
+
+#ifdef RESTYLE_LOGGING
+ nsCString GetCachedStyleDataAsString(uint32_t aStructs);
+ void LogStyleContextTree(int32_t aLoggingDepth, uint32_t aStructs);
+ int32_t& LoggingDepth();
+#endif
+
+ /**
+ * Return style data that is currently cached on the style context.
+ * Only returns the structs we cache ourselves; never consults the
+ * rule tree.
+ *
+ * For "internal" use only in nsStyleContext and nsRuleNode.
+ */
+ const void* GetCachedStyleData(nsStyleStructID aSID)
+ {
+ const void* cachedData;
+ if (nsCachedStyleData::IsReset(aSID)) {
+ if (mCachedResetData) {
+ cachedData = mCachedResetData->mStyleStructs[aSID];
+ } else {
+ cachedData = nullptr;
+ }
+ } else {
+ cachedData = mCachedInheritedData.mStyleStructs[aSID];
+ }
+ return cachedData;
+ }
+
+ mozilla::NonOwningStyleContextSource StyleSource() const { return mSource.AsRaw(); }
+
+#ifdef MOZ_STYLO
+ // NOTE: It'd be great to assert here that the previous change hint is always
+ // consumed.
+ //
+ // This is not the case right now, since the changes of childs of frames that
+ // go through frame construction are not consumed.
+ void StoreChangeHint(nsChangeHint aHint)
+ {
+ MOZ_ASSERT(!IsShared());
+ mStoredChangeHint = aHint;
+#ifdef DEBUG
+ mConsumedChangeHint = false;
+#endif
+ }
+
+ nsChangeHint ConsumeStoredChangeHint()
+ {
+ MOZ_ASSERT(!mConsumedChangeHint, "Re-consuming the same change hint!");
+ nsChangeHint result = mStoredChangeHint;
+ mStoredChangeHint = nsChangeHint(0);
+#ifdef DEBUG
+ mConsumedChangeHint = true;
+#endif
+ return result;
+ }
+#else
+ void StoreChangeHint(nsChangeHint aHint)
+ {
+ MOZ_CRASH("stylo: Called nsStyleContext::StoreChangeHint in a non MOZ_STYLO "
+ "build.");
+ }
+
+ nsChangeHint ConsumeStoredChangeHint()
+ {
+ MOZ_CRASH("stylo: Called nsStyleContext::ComsumeStoredChangeHint in a non "
+ "MOZ_STYLO build.");
+ }
+#endif
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsStyleContext();
+
+ // Delegated Helper constructor.
+ nsStyleContext(nsStyleContext* aParent,
+ mozilla::OwningStyleContextSource&& aSource,
+ nsIAtom* aPseudoTag,
+ mozilla::CSSPseudoElementType aPseudoType);
+
+ // Helper post-contruct hook.
+ void FinishConstruction(bool aSkipParentDisplayBasedStyleFixup);
+
+ void AddChild(nsStyleContext* aChild);
+ void RemoveChild(nsStyleContext* aChild);
+
+ void* GetUniqueStyleData(const nsStyleStructID& aSID);
+ void* CreateEmptyStyleData(const nsStyleStructID& aSID);
+
+ void SetStyleBits();
+ void ApplyStyleFixups(bool aSkipParentDisplayBasedStyleFixup);
+
+ const void* StyleStructFromServoComputedValues(nsStyleStructID aSID) {
+ switch (aSID) {
+#define STYLE_STRUCT(name_, checkdata_cb_) \
+ case eStyleStruct_##name_: \
+ return Servo_GetStyle##name_(mSource.AsServoComputedValues());
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected nsStyleStructID value");
+ return nullptr;
+ }
+ }
+
+#ifdef DEBUG
+ struct AutoCheckDependency {
+
+ nsStyleContext* mStyleContext;
+ nsStyleStructID mOuterSID;
+
+ AutoCheckDependency(nsStyleContext* aContext, nsStyleStructID aInnerSID)
+ : mStyleContext(aContext)
+ {
+ mOuterSID = aContext->mComputingStruct;
+ MOZ_ASSERT(mOuterSID == nsStyleStructID_None ||
+ DependencyAllowed(mOuterSID, aInnerSID),
+ "Undeclared dependency, see generate-stylestructlist.py");
+ aContext->mComputingStruct = aInnerSID;
+ }
+
+ ~AutoCheckDependency()
+ {
+ mStyleContext->mComputingStruct = mOuterSID;
+ }
+
+ };
+
+#define AUTO_CHECK_DEPENDENCY(sid_) \
+ AutoCheckDependency checkNesting_(this, sid_)
+#else
+#define AUTO_CHECK_DEPENDENCY(sid_)
+#endif
+
+ // Helper functions for GetStyle* and PeekStyle*
+ #define STYLE_STRUCT_INHERITED(name_, checkdata_cb_) \
+ template<bool aComputeData> \
+ const nsStyle##name_ * DoGetStyle##name_() { \
+ const nsStyle##name_ * cachedData = \
+ static_cast<nsStyle##name_*>( \
+ mCachedInheritedData.mStyleStructs[eStyleStruct_##name_]); \
+ if (cachedData) /* Have it cached already, yay */ \
+ return cachedData; \
+ if (!aComputeData) { \
+ /* We always cache inherited structs on the context when we */ \
+ /* compute them. */ \
+ return nullptr; \
+ } \
+ /* Have the rulenode deal */ \
+ AUTO_CHECK_DEPENDENCY(eStyleStruct_##name_); \
+ const nsStyle##name_ * newData; \
+ if (mSource.IsGeckoRuleNode()) { \
+ newData = mSource.AsGeckoRuleNode()-> \
+ GetStyle##name_<aComputeData>(this, mBits); \
+ } else { \
+ /** \
+ * Reach the parent to grab the inherited style struct if \
+ * we're a text node. \
+ * \
+ * This causes the parent element's style context to cache any \
+ * inherited structs we request for a text node, which means we \
+ * don't have to compute change hints for the text node, as \
+ * handling the change on the parent element is sufficient. \
+ * \
+ * Note that adding the inherit bit is ok, because the struct \
+ * pointer returned by the parent and the child is owned by \
+ * Servo. This is fine if the pointers are the same (as it \
+ * should, read below), because both style context sources will \
+ * hold it. \
+ * \
+ * In the case of a mishandled frame, we could end up with the \
+ * pointer to and old parent style, but that's fine too, since \
+ * the parent style context will remain alive until we reframe, \
+ * in which case we'll discard both style contexts. Also, we \
+ * hold a strong reference to the parent style context, which \
+ * makes it a non-issue. \
+ * \
+ * Also, note that the assertion below should be true, except \
+ * for those frames we still don't handle correctly, like \
+ * anonymous table wrappers, in which case the pointers will \
+ * differ. \
+ * \
+ * That means we're not going to restyle correctly text frames \
+ * of anonymous table wrappers, for example. It's kind of \
+ * embarrassing, but I think it's not worth it to add more \
+ * logic here unconditionally, given that's going to be fixed. \
+ * \
+ * TODO(emilio): Convert to a strong assertion once we support \
+ * all kinds of random frames. In fact, this can be a great \
+ * assertion to debug them. \
+ */ \
+ if (mPseudoTag == nsCSSAnonBoxes::mozText) { \
+ MOZ_ASSERT(mParent); \
+ newData = mParent->DoGetStyle##name_<true>(); \
+ NS_WARNING_ASSERTION( \
+ newData == Servo_GetStyle##name_(mSource.AsServoComputedValues()), \
+ "bad newData"); \
+ } else { \
+ newData = \
+ Servo_GetStyle##name_(mSource.AsServoComputedValues()); \
+ } \
+ /* perform any remaining main thread work on the struct */ \
+ const_cast<nsStyle##name_*>(newData)->FinishStyle(PresContext());\
+ /* the Servo-backed StyleContextSource owns the struct */ \
+ AddStyleBit(NS_STYLE_INHERIT_BIT(name_)); \
+ } \
+ /* always cache inherited data on the style context; the rule */ \
+ /* node set the bit in mBits for us if needed. */ \
+ mCachedInheritedData.mStyleStructs[eStyleStruct_##name_] = \
+ const_cast<nsStyle##name_ *>(newData); \
+ return newData; \
+ }
+ #define STYLE_STRUCT_RESET(name_, checkdata_cb_) \
+ template<bool aComputeData> \
+ const nsStyle##name_ * DoGetStyle##name_() { \
+ if (mCachedResetData) { \
+ const nsStyle##name_ * cachedData = \
+ static_cast<nsStyle##name_*>( \
+ mCachedResetData->mStyleStructs[eStyleStruct_##name_]); \
+ if (cachedData) /* Have it cached already, yay */ \
+ return cachedData; \
+ } \
+ /* Have the rulenode deal */ \
+ AUTO_CHECK_DEPENDENCY(eStyleStruct_##name_); \
+ const nsStyle##name_ * newData; \
+ if (mSource.IsGeckoRuleNode()) { \
+ newData = mSource.AsGeckoRuleNode()-> \
+ GetStyle##name_<aComputeData>(this); \
+ } else { \
+ newData = \
+ Servo_GetStyle##name_(mSource.AsServoComputedValues()); \
+ /* perform any remaining main thread work on the struct */ \
+ const_cast<nsStyle##name_*>(newData)->FinishStyle(PresContext());\
+ /* The Servo-backed StyleContextSource owns the struct. \
+ * \
+ * XXXbholley: Unconditionally caching reset structs here \
+ * defeats the memory optimization where we lazily allocate \
+ * mCachedResetData, so that we can avoid performing an FFI \
+ * call each time we want to get the style structs. We should \
+ * measure the tradeoffs at some point. If the FFI overhead is \
+ * low and the memory win significant, we should consider \
+ * _always_ grabbing the struct over FFI, and potentially \
+ * giving mCachedInheritedData the same treatment. \
+ * \
+ * Note that there is a similar comment in StyleData(). \
+ */ \
+ AddStyleBit(NS_STYLE_INHERIT_BIT(name_)); \
+ SetStyle(eStyleStruct_##name_, \
+ const_cast<nsStyle##name_*>(newData)); \
+ } \
+ return newData; \
+ }
+ #include "nsStyleStructList.h"
+ #undef STYLE_STRUCT_RESET
+ #undef STYLE_STRUCT_INHERITED
+
+ // Helper for ClearCachedInheritedStyleDataOnDescendants.
+ void DoClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs);
+
+#ifdef DEBUG
+ void AssertStructsNotUsedElsewhere(nsStyleContext* aDestroyingContext,
+ int32_t aLevels) const;
+#endif
+
+#ifdef RESTYLE_LOGGING
+ void LogStyleContextTree(bool aFirst, uint32_t aStructs);
+
+ // This only gets called under call trees where we've already checked
+ // that PresContext()->RestyleManager()->ShouldLogRestyle() returned true.
+ // It exists here just to satisfy LOG_RESTYLE's expectations.
+ bool ShouldLogRestyle() { return true; }
+#endif
+
+ RefPtr<nsStyleContext> mParent;
+
+ // Children are kept in two circularly-linked lists. The list anchor
+ // is not part of the list (null for empty), and we point to the first
+ // child.
+ // mEmptyChild for children whose rule node is the root rule node, and
+ // mChild for other children. The order of children is not
+ // meaningful.
+ nsStyleContext* mChild;
+ nsStyleContext* mEmptyChild;
+ nsStyleContext* mPrevSibling;
+ nsStyleContext* mNextSibling;
+
+ // Style to be used instead for the R, G, and B components of color,
+ // background-color, and border-*-color if the nearest ancestor link
+ // element is visited (see RelevantLinkVisited()).
+ RefPtr<nsStyleContext> mStyleIfVisited;
+
+ // If this style context is for a pseudo-element or anonymous box,
+ // the relevant atom.
+ nsCOMPtr<nsIAtom> mPseudoTag;
+
+ // The source for our style data, either a Gecko nsRuleNode or a Servo
+ // ComputedValues struct. This never changes after construction, except
+ // when it's released and nulled out during teardown.
+ const mozilla::OwningStyleContextSource mSource;
+
+#ifdef MOZ_STYLO
+ // In Gecko, we can get this off the rule node. We make this conditional
+ // on stylo builds to avoid the memory bloat on release.
+ nsPresContext* mPresContext;
+#endif
+
+ // mCachedInheritedData and mCachedResetData point to both structs that
+ // are owned by this style context and structs that are owned by one of
+ // this style context's ancestors (which are indirectly owned since this
+ // style context owns a reference to its parent). If the bit in |mBits|
+ // is set for a struct, that means that the pointer for that struct is
+ // owned by an ancestor or by the rule node rather than by this style context.
+ // Since style contexts typically have some inherited data but only sometimes
+ // have reset data, we always allocate the mCachedInheritedData, but only
+ // sometimes allocate the mCachedResetData.
+ nsResetStyleData* mCachedResetData; // Cached reset style data.
+ nsInheritedStyleData mCachedInheritedData; // Cached inherited style data
+
+ // mBits stores a number of things:
+ // - It records (using the style struct bits) which structs are
+ // inherited from the parent context or owned by the rule node (i.e.,
+ // not owned by the style context).
+ // - It also stores the additional bits listed at the top of
+ // nsStyleStruct.h.
+ uint64_t mBits;
+
+ uint32_t mRefCnt;
+
+ // For now we store change hints on the style context during parallel traversal.
+ // We should improve this - see bug 1289861.
+#ifdef MOZ_STYLO
+ nsChangeHint mStoredChangeHint;
+#ifdef DEBUG
+ bool mConsumedChangeHint;
+#endif
+#endif
+
+#ifdef DEBUG
+ uint32_t mFrameRefCnt; // number of frames that use this
+ // as their style context
+
+ nsStyleStructID mComputingStruct;
+
+ static bool DependencyAllowed(nsStyleStructID aOuterSID,
+ nsStyleStructID aInnerSID)
+ {
+ return !!(sDependencyTable[aOuterSID] &
+ nsCachedStyleData::GetBitForSID(aInnerSID));
+ }
+
+ static const uint32_t sDependencyTable[];
+#endif
+};
+
+already_AddRefed<nsStyleContext>
+NS_NewStyleContext(nsStyleContext* aParentContext,
+ nsIAtom* aPseudoTag,
+ mozilla::CSSPseudoElementType aPseudoType,
+ nsRuleNode* aRuleNode,
+ bool aSkipParentDisplayBasedStyleFixup);
+
+already_AddRefed<nsStyleContext>
+NS_NewStyleContext(nsStyleContext* aParentContext,
+ nsPresContext* aPresContext,
+ nsIAtom* aPseudoTag,
+ mozilla::CSSPseudoElementType aPseudoType,
+ already_AddRefed<ServoComputedValues> aComputedValues,
+ bool aSkipParentDisplayBasedStyleFixup);
+
+#endif
diff --git a/layout/style/nsStyleCoord.cpp b/layout/style/nsStyleCoord.cpp
new file mode 100644
index 000000000..27bf43694
--- /dev/null
+++ b/layout/style/nsStyleCoord.cpp
@@ -0,0 +1,421 @@
+/* -*- 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/. */
+
+/* representation of length values in computed style data */
+
+#include "nsStyleCoord.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/PodOperations.h"
+
+nsStyleCoord::nsStyleCoord(nsStyleUnit aUnit)
+ : mUnit(aUnit)
+{
+ NS_ASSERTION(aUnit < eStyleUnit_Percent, "not a valueless unit");
+ if (aUnit >= eStyleUnit_Percent) {
+ mUnit = eStyleUnit_Null;
+ }
+ mValue.mInt = 0;
+}
+
+nsStyleCoord::nsStyleCoord(int32_t aValue, nsStyleUnit aUnit)
+ : mUnit(aUnit)
+{
+ //if you want to pass in eStyleUnit_Coord, don't. instead, use the
+ //constructor just above this one... MMP
+ NS_ASSERTION((aUnit == eStyleUnit_Enumerated) ||
+ (aUnit == eStyleUnit_Integer), "not an int value");
+ if ((aUnit == eStyleUnit_Enumerated) ||
+ (aUnit == eStyleUnit_Integer)) {
+ mValue.mInt = aValue;
+ }
+ else {
+ mUnit = eStyleUnit_Null;
+ mValue.mInt = 0;
+ }
+}
+
+nsStyleCoord::nsStyleCoord(float aValue, nsStyleUnit aUnit)
+ : mUnit(aUnit)
+{
+ if (aUnit < eStyleUnit_Percent || aUnit >= eStyleUnit_Coord) {
+ NS_NOTREACHED("not a float value");
+ mUnit = eStyleUnit_Null;
+ mValue.mInt = 0;
+ } else {
+ mValue.mFloat = aValue;
+ }
+}
+
+bool nsStyleCoord::operator==(const nsStyleCoord& aOther) const
+{
+ if (mUnit != aOther.mUnit) {
+ return false;
+ }
+ switch (mUnit) {
+ case eStyleUnit_Null:
+ case eStyleUnit_Normal:
+ case eStyleUnit_Auto:
+ case eStyleUnit_None:
+ return true;
+ case eStyleUnit_Percent:
+ case eStyleUnit_Factor:
+ case eStyleUnit_Degree:
+ case eStyleUnit_Grad:
+ case eStyleUnit_Radian:
+ case eStyleUnit_Turn:
+ case eStyleUnit_FlexFraction:
+ return mValue.mFloat == aOther.mValue.mFloat;
+ case eStyleUnit_Coord:
+ case eStyleUnit_Integer:
+ case eStyleUnit_Enumerated:
+ return mValue.mInt == aOther.mValue.mInt;
+ case eStyleUnit_Calc:
+ return *this->GetCalcValue() == *aOther.GetCalcValue();
+ }
+ MOZ_ASSERT(false, "unexpected unit");
+ return false;
+}
+
+uint32_t nsStyleCoord::HashValue(uint32_t aHash = 0) const
+{
+ aHash = mozilla::AddToHash(aHash, mUnit);
+
+ switch (mUnit) {
+ case eStyleUnit_Null:
+ case eStyleUnit_Normal:
+ case eStyleUnit_Auto:
+ case eStyleUnit_None:
+ return mozilla::AddToHash(aHash, true);
+ case eStyleUnit_Percent:
+ case eStyleUnit_Factor:
+ case eStyleUnit_Degree:
+ case eStyleUnit_Grad:
+ case eStyleUnit_Radian:
+ case eStyleUnit_Turn:
+ case eStyleUnit_FlexFraction:
+ return mozilla::AddToHash(aHash, mValue.mFloat);
+ case eStyleUnit_Coord:
+ case eStyleUnit_Integer:
+ case eStyleUnit_Enumerated:
+ return mozilla::AddToHash(aHash, mValue.mInt);
+ case eStyleUnit_Calc:
+ Calc* calcValue = GetCalcValue();
+ aHash = mozilla::AddToHash(aHash, calcValue->mLength);
+ if (HasPercent()) {
+ return mozilla::AddToHash(aHash, calcValue->mPercent);
+ }
+ return aHash;
+ }
+ MOZ_ASSERT(false, "unexpected unit");
+ return aHash;
+}
+
+void nsStyleCoord::Reset()
+{
+ Reset(mUnit, mValue);
+}
+
+void nsStyleCoord::SetCoordValue(nscoord aValue)
+{
+ Reset();
+ mUnit = eStyleUnit_Coord;
+ mValue.mInt = aValue;
+}
+
+void nsStyleCoord::SetIntValue(int32_t aValue, nsStyleUnit aUnit)
+{
+ NS_ASSERTION((aUnit == eStyleUnit_Enumerated) ||
+ (aUnit == eStyleUnit_Integer), "not an int value");
+ Reset();
+ if ((aUnit == eStyleUnit_Enumerated) ||
+ (aUnit == eStyleUnit_Integer)) {
+ mUnit = aUnit;
+ mValue.mInt = aValue;
+ }
+}
+
+void nsStyleCoord::SetPercentValue(float aValue)
+{
+ Reset();
+ mUnit = eStyleUnit_Percent;
+ mValue.mFloat = aValue;
+}
+
+void nsStyleCoord::SetFactorValue(float aValue)
+{
+ Reset();
+ mUnit = eStyleUnit_Factor;
+ mValue.mFloat = aValue;
+}
+
+void nsStyleCoord::SetAngleValue(float aValue, nsStyleUnit aUnit)
+{
+ Reset();
+ if (aUnit == eStyleUnit_Degree ||
+ aUnit == eStyleUnit_Grad ||
+ aUnit == eStyleUnit_Radian ||
+ aUnit == eStyleUnit_Turn) {
+ mUnit = aUnit;
+ mValue.mFloat = aValue;
+ } else {
+ NS_NOTREACHED("not an angle value");
+ }
+}
+
+void nsStyleCoord::SetFlexFractionValue(float aValue)
+{
+ Reset();
+ mUnit = eStyleUnit_FlexFraction;
+ mValue.mFloat = aValue;
+}
+
+void nsStyleCoord::SetCalcValue(Calc* aValue)
+{
+ Reset();
+ mUnit = eStyleUnit_Calc;
+ mValue.mPointer = aValue;
+ aValue->AddRef();
+}
+
+void nsStyleCoord::SetNormalValue()
+{
+ Reset();
+ mUnit = eStyleUnit_Normal;
+ mValue.mInt = 0;
+}
+
+void nsStyleCoord::SetAutoValue()
+{
+ Reset();
+ mUnit = eStyleUnit_Auto;
+ mValue.mInt = 0;
+}
+
+void nsStyleCoord::SetNoneValue()
+{
+ Reset();
+ mUnit = eStyleUnit_None;
+ mValue.mInt = 0;
+}
+
+// accessors that are not inlined
+
+double
+nsStyleCoord::GetAngleValueInDegrees() const
+{
+ return GetAngleValueInRadians() * (180.0 / M_PI);
+}
+
+double
+nsStyleCoord::GetAngleValueInRadians() const
+{
+ double angle = mValue.mFloat;
+
+ switch (GetUnit()) {
+ case eStyleUnit_Radian: return angle;
+ case eStyleUnit_Turn: return angle * 2 * M_PI;
+ case eStyleUnit_Degree: return angle * M_PI / 180.0;
+ case eStyleUnit_Grad: return angle * M_PI / 200.0;
+
+ default:
+ NS_NOTREACHED("unrecognized angular unit");
+ return 0.0;
+ }
+}
+
+nsStyleSides::nsStyleSides()
+{
+ NS_FOR_CSS_SIDES(i) {
+ mUnits[i] = eStyleUnit_Null;
+ }
+ mozilla::PodArrayZero(mValues);
+}
+
+nsStyleSides::nsStyleSides(const nsStyleSides& aOther)
+{
+ NS_FOR_CSS_SIDES(i) {
+ mUnits[i] = eStyleUnit_Null;
+ }
+ *this = aOther;
+}
+
+nsStyleSides::~nsStyleSides()
+{
+ Reset();
+}
+
+nsStyleSides&
+nsStyleSides::operator=(const nsStyleSides& aCopy)
+{
+ if (this != &aCopy) {
+ NS_FOR_CSS_SIDES(i) {
+ nsStyleCoord::SetValue(mUnits[i], mValues[i],
+ aCopy.mUnits[i], aCopy.mValues[i]);
+ }
+ }
+ return *this;
+}
+
+bool nsStyleSides::operator==(const nsStyleSides& aOther) const
+{
+ NS_FOR_CSS_SIDES(i) {
+ if (nsStyleCoord(mValues[i], (nsStyleUnit)mUnits[i]) !=
+ nsStyleCoord(aOther.mValues[i], (nsStyleUnit)aOther.mUnits[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void nsStyleSides::Reset()
+{
+ NS_FOR_CSS_SIDES(i) {
+ nsStyleCoord::Reset(mUnits[i], mValues[i]);
+ }
+}
+
+nsStyleCorners::nsStyleCorners()
+{
+ NS_FOR_CSS_HALF_CORNERS(i) {
+ mUnits[i] = eStyleUnit_Null;
+ }
+ mozilla::PodArrayZero(mValues);
+}
+
+nsStyleCorners::nsStyleCorners(const nsStyleCorners& aOther)
+{
+ NS_FOR_CSS_HALF_CORNERS(i) {
+ mUnits[i] = eStyleUnit_Null;
+ }
+ *this = aOther;
+}
+
+nsStyleCorners::~nsStyleCorners()
+{
+ Reset();
+}
+
+nsStyleCorners&
+nsStyleCorners::operator=(const nsStyleCorners& aCopy)
+{
+ if (this != &aCopy) {
+ NS_FOR_CSS_HALF_CORNERS(i) {
+ nsStyleCoord::SetValue(mUnits[i], mValues[i],
+ aCopy.mUnits[i], aCopy.mValues[i]);
+ }
+ }
+ return *this;
+}
+
+bool
+nsStyleCorners::operator==(const nsStyleCorners& aOther) const
+{
+ NS_FOR_CSS_HALF_CORNERS(i) {
+ if (nsStyleCoord(mValues[i], (nsStyleUnit)mUnits[i]) !=
+ nsStyleCoord(aOther.mValues[i], (nsStyleUnit)aOther.mUnits[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void nsStyleCorners::Reset()
+{
+ NS_FOR_CSS_HALF_CORNERS(i) {
+ nsStyleCoord::Reset(mUnits[i], mValues[i]);
+ }
+}
+
+// Validation of NS_SIDE_IS_VERTICAL and NS_HALF_CORNER_IS_X.
+#define CASE(side, result) \
+ static_assert(NS_SIDE_IS_VERTICAL(side) == result, \
+ "NS_SIDE_IS_VERTICAL is wrong")
+CASE(NS_SIDE_TOP, false);
+CASE(NS_SIDE_RIGHT, true);
+CASE(NS_SIDE_BOTTOM, false);
+CASE(NS_SIDE_LEFT, true);
+#undef CASE
+
+#define CASE(corner, result) \
+ static_assert(NS_HALF_CORNER_IS_X(corner) == result, \
+ "NS_HALF_CORNER_IS_X is wrong")
+CASE(NS_CORNER_TOP_LEFT_X, true);
+CASE(NS_CORNER_TOP_LEFT_Y, false);
+CASE(NS_CORNER_TOP_RIGHT_X, true);
+CASE(NS_CORNER_TOP_RIGHT_Y, false);
+CASE(NS_CORNER_BOTTOM_RIGHT_X, true);
+CASE(NS_CORNER_BOTTOM_RIGHT_Y, false);
+CASE(NS_CORNER_BOTTOM_LEFT_X, true);
+CASE(NS_CORNER_BOTTOM_LEFT_Y, false);
+#undef CASE
+
+// Validation of NS_HALF_TO_FULL_CORNER.
+#define CASE(corner, result) \
+ static_assert(NS_HALF_TO_FULL_CORNER(corner) == result, \
+ "NS_HALF_TO_FULL_CORNER is wrong")
+CASE(NS_CORNER_TOP_LEFT_X, NS_CORNER_TOP_LEFT);
+CASE(NS_CORNER_TOP_LEFT_Y, NS_CORNER_TOP_LEFT);
+CASE(NS_CORNER_TOP_RIGHT_X, NS_CORNER_TOP_RIGHT);
+CASE(NS_CORNER_TOP_RIGHT_Y, NS_CORNER_TOP_RIGHT);
+CASE(NS_CORNER_BOTTOM_RIGHT_X, NS_CORNER_BOTTOM_RIGHT);
+CASE(NS_CORNER_BOTTOM_RIGHT_Y, NS_CORNER_BOTTOM_RIGHT);
+CASE(NS_CORNER_BOTTOM_LEFT_X, NS_CORNER_BOTTOM_LEFT);
+CASE(NS_CORNER_BOTTOM_LEFT_Y, NS_CORNER_BOTTOM_LEFT);
+#undef CASE
+
+// Validation of NS_FULL_TO_HALF_CORNER.
+#define CASE(corner, vert, result) \
+ static_assert(NS_FULL_TO_HALF_CORNER(corner, vert) == result, \
+ "NS_FULL_TO_HALF_CORNER is wrong")
+CASE(NS_CORNER_TOP_LEFT, false, NS_CORNER_TOP_LEFT_X);
+CASE(NS_CORNER_TOP_LEFT, true, NS_CORNER_TOP_LEFT_Y);
+CASE(NS_CORNER_TOP_RIGHT, false, NS_CORNER_TOP_RIGHT_X);
+CASE(NS_CORNER_TOP_RIGHT, true, NS_CORNER_TOP_RIGHT_Y);
+CASE(NS_CORNER_BOTTOM_RIGHT, false, NS_CORNER_BOTTOM_RIGHT_X);
+CASE(NS_CORNER_BOTTOM_RIGHT, true, NS_CORNER_BOTTOM_RIGHT_Y);
+CASE(NS_CORNER_BOTTOM_LEFT, false, NS_CORNER_BOTTOM_LEFT_X);
+CASE(NS_CORNER_BOTTOM_LEFT, true, NS_CORNER_BOTTOM_LEFT_Y);
+#undef CASE
+
+// Validation of NS_SIDE_TO_{FULL,HALF}_CORNER.
+#define CASE(side, second, result) \
+ static_assert(NS_SIDE_TO_FULL_CORNER(side, second) == result, \
+ "NS_SIDE_TO_FULL_CORNER is wrong")
+CASE(NS_SIDE_TOP, false, NS_CORNER_TOP_LEFT);
+CASE(NS_SIDE_TOP, true, NS_CORNER_TOP_RIGHT);
+
+CASE(NS_SIDE_RIGHT, false, NS_CORNER_TOP_RIGHT);
+CASE(NS_SIDE_RIGHT, true, NS_CORNER_BOTTOM_RIGHT);
+
+CASE(NS_SIDE_BOTTOM, false, NS_CORNER_BOTTOM_RIGHT);
+CASE(NS_SIDE_BOTTOM, true, NS_CORNER_BOTTOM_LEFT);
+
+CASE(NS_SIDE_LEFT, false, NS_CORNER_BOTTOM_LEFT);
+CASE(NS_SIDE_LEFT, true, NS_CORNER_TOP_LEFT);
+#undef CASE
+
+#define CASE(side, second, parallel, result) \
+ static_assert(NS_SIDE_TO_HALF_CORNER(side, second, parallel) == result, \
+ "NS_SIDE_TO_HALF_CORNER is wrong")
+CASE(NS_SIDE_TOP, false, true, NS_CORNER_TOP_LEFT_X);
+CASE(NS_SIDE_TOP, false, false, NS_CORNER_TOP_LEFT_Y);
+CASE(NS_SIDE_TOP, true, true, NS_CORNER_TOP_RIGHT_X);
+CASE(NS_SIDE_TOP, true, false, NS_CORNER_TOP_RIGHT_Y);
+
+CASE(NS_SIDE_RIGHT, false, false, NS_CORNER_TOP_RIGHT_X);
+CASE(NS_SIDE_RIGHT, false, true, NS_CORNER_TOP_RIGHT_Y);
+CASE(NS_SIDE_RIGHT, true, false, NS_CORNER_BOTTOM_RIGHT_X);
+CASE(NS_SIDE_RIGHT, true, true, NS_CORNER_BOTTOM_RIGHT_Y);
+
+CASE(NS_SIDE_BOTTOM, false, true, NS_CORNER_BOTTOM_RIGHT_X);
+CASE(NS_SIDE_BOTTOM, false, false, NS_CORNER_BOTTOM_RIGHT_Y);
+CASE(NS_SIDE_BOTTOM, true, true, NS_CORNER_BOTTOM_LEFT_X);
+CASE(NS_SIDE_BOTTOM, true, false, NS_CORNER_BOTTOM_LEFT_Y);
+
+CASE(NS_SIDE_LEFT, false, false, NS_CORNER_BOTTOM_LEFT_X);
+CASE(NS_SIDE_LEFT, false, true, NS_CORNER_BOTTOM_LEFT_Y);
+CASE(NS_SIDE_LEFT, true, false, NS_CORNER_TOP_LEFT_X);
+CASE(NS_SIDE_LEFT, true, true, NS_CORNER_TOP_LEFT_Y);
+#undef CASE
diff --git a/layout/style/nsStyleCoord.h b/layout/style/nsStyleCoord.h
new file mode 100644
index 000000000..3489a75c8
--- /dev/null
+++ b/layout/style/nsStyleCoord.h
@@ -0,0 +1,649 @@
+/* -*- 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/. */
+
+/* representation of length values in computed style data */
+
+#ifndef nsStyleCoord_h___
+#define nsStyleCoord_h___
+
+#include "nsCoord.h"
+#include "nsStyleConsts.h"
+
+namespace mozilla {
+
+class WritingMode;
+
+// Logical axis, edge and side constants for use in various places.
+enum LogicalAxis {
+ eLogicalAxisBlock = 0x0,
+ eLogicalAxisInline = 0x1
+};
+enum LogicalEdge {
+ eLogicalEdgeStart = 0x0,
+ eLogicalEdgeEnd = 0x1
+};
+enum LogicalSide {
+ eLogicalSideBStart = (eLogicalAxisBlock << 1) | eLogicalEdgeStart, // 0x0
+ eLogicalSideBEnd = (eLogicalAxisBlock << 1) | eLogicalEdgeEnd, // 0x1
+ eLogicalSideIStart = (eLogicalAxisInline << 1) | eLogicalEdgeStart, // 0x2
+ eLogicalSideIEnd = (eLogicalAxisInline << 1) | eLogicalEdgeEnd // 0x3
+};
+
+} // namespace mozilla
+
+enum nsStyleUnit : uint8_t {
+ eStyleUnit_Null = 0, // (no value) value is not specified
+ eStyleUnit_Normal = 1, // (no value)
+ eStyleUnit_Auto = 2, // (no value)
+ eStyleUnit_None = 3, // (no value)
+ eStyleUnit_Percent = 10, // (float) 1.0 == 100%
+ eStyleUnit_Factor = 11, // (float) a multiplier
+ eStyleUnit_Degree = 12, // (float) angle in degrees
+ eStyleUnit_Grad = 13, // (float) angle in grads
+ eStyleUnit_Radian = 14, // (float) angle in radians
+ eStyleUnit_Turn = 15, // (float) angle in turns
+ eStyleUnit_FlexFraction = 16, // (float) <flex> in fr units
+ eStyleUnit_Coord = 20, // (nscoord) value is twips
+ eStyleUnit_Integer = 30, // (int) value is simple integer
+ eStyleUnit_Enumerated = 32, // (int) value has enumerated meaning
+
+ // The following are reference counted allocated types.
+ eStyleUnit_Calc = 40, // (Calc*) calc() toplevel; always present
+ // to distinguish 50% from calc(50%), etc.
+
+ eStyleUnit_MAX = 40 // highest valid nsStyleUnit value
+};
+
+typedef union {
+ int32_t mInt; // nscoord is a int32_t for now
+ float mFloat;
+ // An mPointer is a reference counted pointer. Currently this can only
+ // ever be an nsStyleCoord::Calc*.
+ void* mPointer;
+} nsStyleUnion;
+
+/**
+ * Class that hold a single size specification used by the style
+ * system. The size specification consists of two parts -- a number
+ * and a unit. The number is an integer, a floating point value, an
+ * nscoord, or undefined, and the unit is an nsStyleUnit. Checking
+ * the unit is a must before asking for the value in any particular
+ * form.
+ */
+ /** <div rustbindgen private accessor="unsafe"></div> */
+class nsStyleCoord {
+public:
+ // Non-reference counted calc() value. See nsStyleStruct.h for some uses
+ // of this.
+ struct CalcValue {
+ // Every calc() expression evaluates to a length plus a percentage.
+ nscoord mLength;
+ float mPercent;
+ bool mHasPercent; // whether there was any % syntax, even if 0
+
+ bool operator==(const CalcValue& aOther) const {
+ return mLength == aOther.mLength &&
+ mPercent == aOther.mPercent &&
+ mHasPercent == aOther.mHasPercent;
+ }
+ bool operator!=(const CalcValue& aOther) const {
+ return !(*this == aOther);
+ }
+
+ nscoord ToLength() const {
+ MOZ_ASSERT(!mHasPercent);
+ return mLength;
+ }
+
+ // If this returns true the value is definitely zero. It it returns false
+ // it might be zero. So it's best used for conservative optimization.
+ bool IsDefinitelyZero() const { return mLength == 0 && mPercent == 0; }
+ };
+
+ // Reference counted calc() value. This is the type that is used to store
+ // the calc() value in nsStyleCoord.
+ struct Calc final : public CalcValue {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Calc)
+ Calc() {}
+
+ private:
+ Calc(const Calc&) = delete;
+ ~Calc() {}
+ Calc& operator=(const Calc&) = delete;
+ };
+
+ explicit nsStyleCoord(nsStyleUnit aUnit = eStyleUnit_Null);
+ enum CoordConstructorType { CoordConstructor };
+ inline nsStyleCoord(nscoord aValue, CoordConstructorType);
+ nsStyleCoord(int32_t aValue, nsStyleUnit aUnit);
+ nsStyleCoord(float aValue, nsStyleUnit aUnit);
+ inline nsStyleCoord(const nsStyleCoord& aCopy);
+ inline nsStyleCoord(const nsStyleUnion& aValue, nsStyleUnit aUnit);
+ ~nsStyleCoord() { Reset(); }
+
+ nsStyleCoord& operator=(const nsStyleCoord& aOther)
+ {
+ if (this != &aOther) {
+ SetValue(mUnit, mValue, aOther);
+ }
+ return *this;
+ }
+ bool operator==(const nsStyleCoord& aOther) const;
+ bool operator!=(const nsStyleCoord& aOther) const;
+
+ nsStyleUnit GetUnit() const {
+ NS_ASSERTION(mUnit != eStyleUnit_Null, "reading uninitialized value");
+ return mUnit;
+ }
+
+ bool IsAngleValue() const {
+ return eStyleUnit_Degree <= mUnit && mUnit <= eStyleUnit_Turn;
+ }
+
+ static bool IsCalcUnit(nsStyleUnit aUnit) {
+ return aUnit == eStyleUnit_Calc;
+ }
+
+ static bool IsPointerUnit(nsStyleUnit aUnit) {
+ return IsCalcUnit(aUnit);
+ }
+
+ bool IsCalcUnit() const {
+ return IsCalcUnit(mUnit);
+ }
+
+ bool IsPointerValue() const {
+ return IsPointerUnit(mUnit);
+ }
+
+ bool IsCoordPercentCalcUnit() const {
+ return mUnit == eStyleUnit_Coord ||
+ mUnit == eStyleUnit_Percent ||
+ IsCalcUnit();
+ }
+
+ // Does this calc() expression have any percentages inside it? Can be
+ // called only when IsCalcUnit() is true.
+ bool CalcHasPercent() const {
+ return GetCalcValue()->mHasPercent;
+ }
+
+ bool HasPercent() const {
+ return mUnit == eStyleUnit_Percent ||
+ (IsCalcUnit() && CalcHasPercent());
+ }
+
+ static bool ConvertsToLength(const nsStyleUnit aUnit,
+ const nsStyleUnion aValue) {
+ return aUnit == eStyleUnit_Coord ||
+ (IsCalcUnit(aUnit) && !AsCalcValue(aValue)->mHasPercent);
+ }
+
+ bool ConvertsToLength() const {
+ return ConvertsToLength(mUnit, mValue);
+ }
+
+ static nscoord ToLength(nsStyleUnit aUnit, nsStyleUnion aValue) {
+ MOZ_ASSERT(ConvertsToLength(aUnit, aValue));
+ if (IsCalcUnit(aUnit)) {
+ return AsCalcValue(aValue)->ToLength(); // Note: This asserts !mHasPercent
+ }
+ MOZ_ASSERT(aUnit == eStyleUnit_Coord);
+ return aValue.mInt;
+ }
+
+ nscoord ToLength() const {
+ return ToLength(GetUnit(), mValue);
+ }
+
+ // Callers must verify IsCalcUnit before calling this function.
+ static Calc* AsCalcValue(nsStyleUnion aValue) {
+ return static_cast<Calc*>(aValue.mPointer);
+ }
+
+ nscoord GetCoordValue() const;
+ int32_t GetIntValue() const;
+ float GetPercentValue() const;
+ float GetFactorValue() const;
+ float GetFactorOrPercentValue() const;
+ float GetAngleValue() const;
+ double GetAngleValueInDegrees() const;
+ double GetAngleValueInRadians() const;
+ float GetFlexFractionValue() const;
+ Calc* GetCalcValue() const;
+ uint32_t HashValue(uint32_t aHash) const;
+
+ // Sets to null and releases any refcounted objects. Only use this if the
+ // object is initialized (i.e. don't use it in nsStyleCoord constructors).
+ void Reset();
+
+ void SetCoordValue(nscoord aValue);
+ void SetIntValue(int32_t aValue, nsStyleUnit aUnit);
+ void SetPercentValue(float aValue);
+ void SetFactorValue(float aValue);
+ void SetAngleValue(float aValue, nsStyleUnit aUnit);
+ void SetFlexFractionValue(float aValue);
+ void SetNormalValue();
+ void SetAutoValue();
+ void SetNoneValue();
+ void SetCalcValue(Calc* aValue);
+
+ // Resets a coord represented by a unit/value pair.
+ static inline void Reset(nsStyleUnit& aUnit, nsStyleUnion& aValue);
+
+ // Sets a coord represented by a unit/value pair from a second
+ // unit/value pair.
+ static inline void SetValue(nsStyleUnit& aUnit,
+ nsStyleUnion& aValue,
+ nsStyleUnit aOtherUnit,
+ const nsStyleUnion& aOtherValue);
+
+ // Sets a coord represented by a unit/value pair from an nsStyleCoord.
+ static inline void SetValue(nsStyleUnit& aUnit, nsStyleUnion& aValue,
+ const nsStyleCoord& aOther);
+
+ // Like the above, but do not reset before setting.
+ static inline void InitWithValue(nsStyleUnit& aUnit,
+ nsStyleUnion& aValue,
+ nsStyleUnit aOtherUnit,
+ const nsStyleUnion& aOtherValue);
+
+ static inline void InitWithValue(nsStyleUnit& aUnit, nsStyleUnion& aValue,
+ const nsStyleCoord& aOther);
+
+private:
+ nsStyleUnit mUnit;
+ nsStyleUnion mValue;
+};
+
+/**
+ * Class that represents a set of top/right/bottom/left nsStyleCoords.
+ * This is commonly used to hold the widths of the borders, margins,
+ * or paddings of a box.
+ */
+ /** <div rustbindgen private accessor="unsafe"></div> */
+class nsStyleSides {
+public:
+ nsStyleSides();
+ nsStyleSides(const nsStyleSides&);
+ ~nsStyleSides();
+
+ nsStyleSides& operator=(const nsStyleSides& aCopy);
+ bool operator==(const nsStyleSides& aOther) const;
+ bool operator!=(const nsStyleSides& aOther) const;
+
+ inline nsStyleUnit GetUnit(mozilla::css::Side aSide) const;
+ inline nsStyleUnit GetLeftUnit() const;
+ inline nsStyleUnit GetTopUnit() const;
+ inline nsStyleUnit GetRightUnit() const;
+ inline nsStyleUnit GetBottomUnit() const;
+
+ inline nsStyleCoord Get(mozilla::css::Side aSide) const;
+ inline nsStyleCoord GetLeft() const;
+ inline nsStyleCoord GetTop() const;
+ inline nsStyleCoord GetRight() const;
+ inline nsStyleCoord GetBottom() const;
+
+ // Methods to access the units and values in terms of logical sides
+ // for a given writing mode.
+ // NOTE: The definitions are in WritingModes.h (after we have the full
+ // declaration of WritingMode available).
+ inline nsStyleUnit GetUnit(mozilla::WritingMode aWritingMode,
+ mozilla::LogicalSide aSide) const;
+ inline nsStyleUnit GetIStartUnit(mozilla::WritingMode aWritingMode) const;
+ inline nsStyleUnit GetBStartUnit(mozilla::WritingMode aWritingMode) const;
+ inline nsStyleUnit GetIEndUnit(mozilla::WritingMode aWritingMode) const;
+ inline nsStyleUnit GetBEndUnit(mozilla::WritingMode aWritingMode) const;
+
+ // Return true if either the start or end side in the axis is 'auto'.
+ inline bool HasBlockAxisAuto(mozilla::WritingMode aWritingMode) const;
+ inline bool HasInlineAxisAuto(mozilla::WritingMode aWritingMode) const;
+
+ inline nsStyleCoord Get(mozilla::WritingMode aWritingMode,
+ mozilla::LogicalSide aSide) const;
+ inline nsStyleCoord GetIStart(mozilla::WritingMode aWritingMode) const;
+ inline nsStyleCoord GetBStart(mozilla::WritingMode aWritingMode) const;
+ inline nsStyleCoord GetIEnd(mozilla::WritingMode aWritingMode) const;
+ inline nsStyleCoord GetBEnd(mozilla::WritingMode aWritingMode) const;
+
+ // Sets each side to null and releases any refcounted objects. Only use this
+ // if the object is initialized (i.e. don't use it in nsStyleSides
+ // constructors).
+ void Reset();
+
+ inline void Set(mozilla::css::Side aSide, const nsStyleCoord& aCoord);
+ inline void SetLeft(const nsStyleCoord& aCoord);
+ inline void SetTop(const nsStyleCoord& aCoord);
+ inline void SetRight(const nsStyleCoord& aCoord);
+ inline void SetBottom(const nsStyleCoord& aCoord);
+
+ nscoord ToLength(mozilla::css::Side aSide) const {
+ return nsStyleCoord::ToLength(mUnits[aSide], mValues[aSide]);
+ }
+
+ bool ConvertsToLength() const {
+ NS_FOR_CSS_SIDES(side) {
+ if (!nsStyleCoord::ConvertsToLength(mUnits[side], mValues[side])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+protected:
+ nsStyleUnit mUnits[4];
+ nsStyleUnion mValues[4];
+};
+
+/**
+ * Class that represents a set of top-left/top-right/bottom-right/bottom-left
+ * nsStyleCoord pairs. This is used to hold the dimensions of the
+ * corners of a box (for, e.g., border-radius and outline-radius).
+ */
+ /** <div rustbindgen private accessor="unsafe"></div> */
+class nsStyleCorners {
+public:
+ nsStyleCorners();
+ nsStyleCorners(const nsStyleCorners&);
+ ~nsStyleCorners();
+
+ // use compiler's version
+ nsStyleCorners& operator=(const nsStyleCorners& aCopy);
+ bool operator==(const nsStyleCorners& aOther) const;
+ bool operator!=(const nsStyleCorners& aOther) const;
+
+ // aCorner is always one of NS_CORNER_* defined in nsStyleConsts.h
+ inline nsStyleUnit GetUnit(uint8_t aHalfCorner) const;
+
+ inline nsStyleCoord Get(uint8_t aHalfCorner) const;
+
+ // Sets each corner to null and releases any refcounted objects. Only use
+ // this if the object is initialized (i.e. don't use it in nsStyleCorners
+ // constructors).
+ void Reset();
+
+ inline void Set(uint8_t aHalfCorner, const nsStyleCoord& aCoord);
+
+protected:
+ // Stored as:
+ // top-left.x, top-left.y,
+ // top-right.x, top-right.y,
+ // bottom-right.x, bottom-right.y,
+ // bottom-left.x, bottom-left.y
+ nsStyleUnit mUnits[8];
+ nsStyleUnion mValues[8];
+};
+
+
+// -------------------------
+// nsStyleCoord inlines
+//
+inline nsStyleCoord::nsStyleCoord(nscoord aValue, CoordConstructorType)
+ : mUnit(eStyleUnit_Coord)
+{
+ mValue.mInt = aValue;
+}
+
+inline nsStyleCoord::nsStyleCoord(const nsStyleCoord& aCopy)
+ : mUnit(eStyleUnit_Null)
+{
+ InitWithValue(mUnit, mValue, aCopy);
+}
+
+inline nsStyleCoord::nsStyleCoord(const nsStyleUnion& aValue, nsStyleUnit aUnit)
+ : mUnit(eStyleUnit_Null)
+{
+ InitWithValue(mUnit, mValue, aUnit, aValue);
+}
+
+inline bool nsStyleCoord::operator!=(const nsStyleCoord& aOther) const
+{
+ return !((*this) == aOther);
+}
+
+inline nscoord nsStyleCoord::GetCoordValue() const
+{
+ NS_ASSERTION((mUnit == eStyleUnit_Coord), "not a coord value");
+ if (mUnit == eStyleUnit_Coord) {
+ return mValue.mInt;
+ }
+ return 0;
+}
+
+inline int32_t nsStyleCoord::GetIntValue() const
+{
+ NS_ASSERTION((mUnit == eStyleUnit_Enumerated) ||
+ (mUnit == eStyleUnit_Integer), "not an int value");
+ if ((mUnit == eStyleUnit_Enumerated) ||
+ (mUnit == eStyleUnit_Integer)) {
+ return mValue.mInt;
+ }
+ return 0;
+}
+
+inline float nsStyleCoord::GetPercentValue() const
+{
+ NS_ASSERTION(mUnit == eStyleUnit_Percent, "not a percent value");
+ if (mUnit == eStyleUnit_Percent) {
+ return mValue.mFloat;
+ }
+ return 0.0f;
+}
+
+inline float nsStyleCoord::GetFactorValue() const
+{
+ NS_ASSERTION(mUnit == eStyleUnit_Factor, "not a factor value");
+ if (mUnit == eStyleUnit_Factor) {
+ return mValue.mFloat;
+ }
+ return 0.0f;
+}
+
+inline float nsStyleCoord::GetFactorOrPercentValue() const
+{
+ NS_ASSERTION(mUnit == eStyleUnit_Factor || mUnit == eStyleUnit_Percent,
+ "not a percent or factor value");
+ if (mUnit == eStyleUnit_Factor || mUnit == eStyleUnit_Percent) {
+ return mValue.mFloat;
+ }
+ return 0.0f;
+}
+
+inline float nsStyleCoord::GetAngleValue() const
+{
+ NS_ASSERTION(mUnit >= eStyleUnit_Degree &&
+ mUnit <= eStyleUnit_Turn, "not an angle value");
+ if (mUnit >= eStyleUnit_Degree && mUnit <= eStyleUnit_Turn) {
+ return mValue.mFloat;
+ }
+ return 0.0f;
+}
+
+inline float nsStyleCoord::GetFlexFractionValue() const
+{
+ NS_ASSERTION(mUnit == eStyleUnit_FlexFraction, "not a fr value");
+ if (mUnit == eStyleUnit_FlexFraction) {
+ return mValue.mFloat;
+ }
+ return 0.0f;
+}
+
+inline nsStyleCoord::Calc* nsStyleCoord::GetCalcValue() const
+{
+ NS_ASSERTION(IsCalcUnit(), "not a pointer value");
+ if (IsCalcUnit()) {
+ return AsCalcValue(mValue);
+ }
+ return nullptr;
+}
+
+/* static */ inline void
+nsStyleCoord::Reset(nsStyleUnit& aUnit, nsStyleUnion& aValue)
+{
+ MOZ_ASSERT(aUnit <= eStyleUnit_MAX,
+ "calling Reset on uninitialized nsStyleCoord?");
+
+ switch (aUnit) {
+ case eStyleUnit_Calc:
+ static_cast<Calc*>(aValue.mPointer)->Release();
+ break;
+ default:
+ MOZ_ASSERT(!IsPointerUnit(aUnit), "check pointer refcounting logic");
+ }
+
+ aUnit = eStyleUnit_Null;
+ aValue.mInt = 0;
+}
+
+/* static */ inline void
+nsStyleCoord::SetValue(nsStyleUnit& aUnit,
+ nsStyleUnion& aValue,
+ nsStyleUnit aOtherUnit,
+ const nsStyleUnion& aOtherValue)
+{
+ Reset(aUnit, aValue);
+ InitWithValue(aUnit, aValue, aOtherUnit, aOtherValue);
+}
+
+/* static */ inline void
+nsStyleCoord::InitWithValue(nsStyleUnit& aUnit,
+ nsStyleUnion& aValue,
+ nsStyleUnit aOtherUnit,
+ const nsStyleUnion& aOtherValue)
+{
+ aUnit = aOtherUnit;
+ aValue = aOtherValue;
+
+ switch (aUnit) {
+ case eStyleUnit_Calc:
+ static_cast<Calc*>(aValue.mPointer)->AddRef();
+ break;
+ default:
+ MOZ_ASSERT(!IsPointerUnit(aUnit), "check pointer refcounting logic");
+ }
+}
+
+/* static */ inline void
+nsStyleCoord::SetValue(nsStyleUnit& aUnit, nsStyleUnion& aValue,
+ const nsStyleCoord& aOther)
+{
+ SetValue(aUnit, aValue, aOther.mUnit, aOther.mValue);
+}
+
+/* static */ inline void
+nsStyleCoord::InitWithValue(nsStyleUnit& aUnit, nsStyleUnion& aValue,
+ const nsStyleCoord& aOther)
+{
+ InitWithValue(aUnit, aValue, aOther.mUnit, aOther.mValue);
+}
+
+
+// -------------------------
+// nsStyleSides inlines
+//
+inline bool nsStyleSides::operator!=(const nsStyleSides& aOther) const
+{
+ return !((*this) == aOther);
+}
+
+inline nsStyleUnit nsStyleSides::GetUnit(mozilla::css::Side aSide) const
+{
+ return (nsStyleUnit)mUnits[aSide];
+}
+
+inline nsStyleUnit nsStyleSides::GetLeftUnit() const
+{
+ return GetUnit(NS_SIDE_LEFT);
+}
+
+inline nsStyleUnit nsStyleSides::GetTopUnit() const
+{
+ return GetUnit(NS_SIDE_TOP);
+}
+
+inline nsStyleUnit nsStyleSides::GetRightUnit() const
+{
+ return GetUnit(NS_SIDE_RIGHT);
+}
+
+inline nsStyleUnit nsStyleSides::GetBottomUnit() const
+{
+ return GetUnit(NS_SIDE_BOTTOM);
+}
+
+inline nsStyleCoord nsStyleSides::Get(mozilla::css::Side aSide) const
+{
+ return nsStyleCoord(mValues[aSide], nsStyleUnit(mUnits[aSide]));
+}
+
+inline nsStyleCoord nsStyleSides::GetLeft() const
+{
+ return Get(NS_SIDE_LEFT);
+}
+
+inline nsStyleCoord nsStyleSides::GetTop() const
+{
+ return Get(NS_SIDE_TOP);
+}
+
+inline nsStyleCoord nsStyleSides::GetRight() const
+{
+ return Get(NS_SIDE_RIGHT);
+}
+
+inline nsStyleCoord nsStyleSides::GetBottom() const
+{
+ return Get(NS_SIDE_BOTTOM);
+}
+
+inline void nsStyleSides::Set(mozilla::css::Side aSide, const nsStyleCoord& aCoord)
+{
+ nsStyleCoord::SetValue(mUnits[aSide], mValues[aSide], aCoord);
+}
+
+inline void nsStyleSides::SetLeft(const nsStyleCoord& aCoord)
+{
+ Set(NS_SIDE_LEFT, aCoord);
+}
+
+inline void nsStyleSides::SetTop(const nsStyleCoord& aCoord)
+{
+ Set(NS_SIDE_TOP, aCoord);
+}
+
+inline void nsStyleSides::SetRight(const nsStyleCoord& aCoord)
+{
+ Set(NS_SIDE_RIGHT, aCoord);
+}
+
+inline void nsStyleSides::SetBottom(const nsStyleCoord& aCoord)
+{
+ Set(NS_SIDE_BOTTOM, aCoord);
+}
+
+// -------------------------
+// nsStyleCorners inlines
+//
+inline bool nsStyleCorners::operator!=(const nsStyleCorners& aOther) const
+{
+ return !((*this) == aOther);
+}
+
+inline nsStyleUnit nsStyleCorners::GetUnit(uint8_t aCorner) const
+{
+ return (nsStyleUnit)mUnits[aCorner];
+}
+
+inline nsStyleCoord nsStyleCorners::Get(uint8_t aCorner) const
+{
+ return nsStyleCoord(mValues[aCorner], nsStyleUnit(mUnits[aCorner]));
+}
+
+inline void nsStyleCorners::Set(uint8_t aCorner, const nsStyleCoord& aCoord)
+{
+ nsStyleCoord::SetValue(mUnits[aCorner], mValues[aCorner], aCoord);
+}
+
+#endif /* nsStyleCoord_h___ */
diff --git a/layout/style/nsStyleSet.cpp b/layout/style/nsStyleSet.cpp
new file mode 100644
index 000000000..414eee4d4
--- /dev/null
+++ b/layout/style/nsStyleSet.cpp
@@ -0,0 +1,2537 @@
+/* -*- 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/. */
+
+/*
+ * the container for the style sheets that apply to a presentation, and
+ * the internal API that the style system exposes for creating (and
+ * potentially re-creating) style contexts
+ */
+
+#include "nsStyleSet.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EnumeratedRange.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RuleProcessorCache.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "nsIDocumentInlines.h"
+#include "nsRuleWalker.h"
+#include "nsStyleContext.h"
+#include "mozilla/css/StyleRule.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsDataHashtable.h"
+#include "nsIContent.h"
+#include "nsRuleData.h"
+#include "nsRuleProcessorData.h"
+#include "nsAnimationManager.h"
+#include "nsStyleSheetService.h"
+#include "mozilla/dom/Element.h"
+#include "GeckoProfiler.h"
+#include "nsHTMLCSSStyleSheet.h"
+#include "nsHTMLStyleSheet.h"
+#include "SVGAttrAnimationRuleProcessor.h"
+#include "nsCSSRules.h"
+#include "nsPrintfCString.h"
+#include "nsIFrame.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "nsQueryObject.h"
+
+#include <inttypes.h>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsEmptyStyleRule, nsIStyleRule)
+
+/* virtual */ void
+nsEmptyStyleRule::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+}
+
+/* virtual */ bool
+nsEmptyStyleRule::MightMapInheritedStyleData()
+{
+ return false;
+}
+
+/* virtual */ bool
+nsEmptyStyleRule::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue)
+{
+ MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+ return false;
+}
+
+#ifdef DEBUG
+/* virtual */ void
+nsEmptyStyleRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indentStr;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+ fprintf_stderr(out, "%s[empty style rule] {}\n", indentStr.get());
+}
+#endif
+
+NS_IMPL_ISUPPORTS(nsInitialStyleRule, nsIStyleRule)
+
+/* virtual */ void
+nsInitialStyleRule::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ // Iterate over the property groups
+ for (nsStyleStructID sid = nsStyleStructID(0);
+ sid < nsStyleStructID_Length; sid = nsStyleStructID(sid + 1)) {
+ if (aRuleData->mSIDs & (1 << sid)) {
+ // Iterate over nsCSSValues within the property group
+ nsCSSValue * const value_start =
+ aRuleData->mValueStorage + aRuleData->mValueOffsets[sid];
+ for (nsCSSValue *value = value_start,
+ *value_end = value + nsCSSProps::PropertyCountInStruct(sid);
+ value != value_end; ++value) {
+ // If MathML is disabled take care not to set MathML properties (or we
+ // will trigger assertions in nsRuleNode)
+ if (sid == eStyleStruct_Font &&
+ !aRuleData->mPresContext->Document()->GetMathMLEnabled()) {
+ size_t index = value - value_start;
+ if (index == nsCSSProps::PropertyIndexInStruct(
+ eCSSProperty_script_level) ||
+ index == nsCSSProps::PropertyIndexInStruct(
+ eCSSProperty_script_size_multiplier) ||
+ index == nsCSSProps::PropertyIndexInStruct(
+ eCSSProperty_script_min_size) ||
+ index == nsCSSProps::PropertyIndexInStruct(
+ eCSSProperty_math_variant) ||
+ index == nsCSSProps::PropertyIndexInStruct(
+ eCSSProperty_math_display)) {
+ continue;
+ }
+ }
+ if (value->GetUnit() == eCSSUnit_Null) {
+ value->SetInitialValue();
+ }
+ }
+ }
+ }
+}
+
+/* virtual */ bool
+nsInitialStyleRule::MightMapInheritedStyleData()
+{
+ return true;
+}
+
+/* virtual */ bool
+nsInitialStyleRule::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue)
+{
+ MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+ return false;
+}
+
+#ifdef DEBUG
+/* virtual */ void
+nsInitialStyleRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indentStr;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+ fprintf_stderr(out, "%s[initial style rule] {}\n", indentStr.get());
+}
+#endif
+
+NS_IMPL_ISUPPORTS(nsDisableTextZoomStyleRule, nsIStyleRule)
+
+/* virtual */ void
+nsDisableTextZoomStyleRule::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ if (!(aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Font)))
+ return;
+
+ nsCSSValue* value = aRuleData->ValueForTextZoom();
+ if (value->GetUnit() == eCSSUnit_Null)
+ value->SetNoneValue();
+}
+
+/* virtual */ bool
+nsDisableTextZoomStyleRule::MightMapInheritedStyleData()
+{
+ return true;
+}
+
+/* virtual */ bool
+nsDisableTextZoomStyleRule::
+GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue)
+{
+ MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+ return false;
+}
+
+#ifdef DEBUG
+/* virtual */ void
+nsDisableTextZoomStyleRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indentStr;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ indentStr.AppendLiteral(" ");
+ }
+ fprintf_stderr(out, "%s[disable text zoom style rule] {}\n", indentStr.get());
+}
+#endif
+
+static const SheetType gCSSSheetTypes[] = {
+ // From lowest to highest in cascading order.
+ SheetType::Agent,
+ SheetType::User,
+ SheetType::Doc,
+ SheetType::ScopedDoc,
+ SheetType::Override
+};
+
+/* static */ bool
+nsStyleSet::IsCSSSheetType(SheetType aSheetType)
+{
+ for (SheetType type : gCSSSheetTypes) {
+ if (type == aSheetType) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsStyleSet::nsStyleSet()
+ : mRuleTree(nullptr),
+ mBatching(0),
+ mInShutdown(false),
+ mInGC(false),
+ mAuthorStyleDisabled(false),
+ mInReconstruct(false),
+ mInitFontFeatureValuesLookup(true),
+ mNeedsRestyleAfterEnsureUniqueInner(false),
+ mDirty(0),
+ mRootStyleContextCount(0),
+#ifdef DEBUG
+ mOldRootNode(nullptr),
+#endif
+ mUnusedRuleNodeCount(0)
+{
+}
+
+nsStyleSet::~nsStyleSet()
+{
+ for (SheetType type : gCSSSheetTypes) {
+ for (CSSStyleSheet* sheet : mSheets[type]) {
+ sheet->DropStyleSet(this);
+ }
+ }
+
+ // drop reference to cached rule processors
+ nsCSSRuleProcessor* rp;
+ rp = static_cast<nsCSSRuleProcessor*>(mRuleProcessors[SheetType::Agent].get());
+ if (rp) {
+ MOZ_ASSERT(rp->IsShared());
+ rp->ReleaseStyleSetRef();
+ }
+ rp = static_cast<nsCSSRuleProcessor*>(mRuleProcessors[SheetType::User].get());
+ if (rp) {
+ MOZ_ASSERT(rp->IsShared());
+ rp->ReleaseStyleSetRef();
+ }
+}
+
+size_t
+nsStyleSet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ for (SheetType type : MakeEnumeratedRange(SheetType::Count)) {
+ if (mRuleProcessors[type]) {
+ bool shared = false;
+ if (type == SheetType::Agent || type == SheetType::User) {
+ // The only two origins we consider caching rule processors for.
+ nsCSSRuleProcessor* rp =
+ static_cast<nsCSSRuleProcessor*>(mRuleProcessors[type].get());
+ shared = rp->IsShared();
+ }
+ if (!shared) {
+ n += mRuleProcessors[type]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+ // We don't own the sheets (either the nsLayoutStyleSheetCache singleton
+ // or our document owns them).
+ n += mSheets[type].ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) {
+ n += mScopedDocSheetRuleProcessors[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ n += mScopedDocSheetRuleProcessors.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ return n;
+}
+
+void
+nsStyleSet::Init(nsPresContext *aPresContext)
+{
+ mFirstLineRule = new nsEmptyStyleRule;
+ mFirstLetterRule = new nsEmptyStyleRule;
+ mPlaceholderRule = new nsEmptyStyleRule;
+ mDisableTextZoomStyleRule = new nsDisableTextZoomStyleRule;
+
+ mRuleTree = nsRuleNode::CreateRootNode(aPresContext);
+
+ // Make an explicit GatherRuleProcessors call for the levels that
+ // don't have style sheets. The other levels will have their calls
+ // triggered by DirtyRuleProcessors.
+ GatherRuleProcessors(SheetType::PresHint);
+ GatherRuleProcessors(SheetType::SVGAttrAnimation);
+ GatherRuleProcessors(SheetType::StyleAttr);
+ GatherRuleProcessors(SheetType::Animation);
+ GatherRuleProcessors(SheetType::Transition);
+}
+
+nsresult
+nsStyleSet::BeginReconstruct()
+{
+ NS_ASSERTION(!mInReconstruct, "Unmatched begin/end?");
+ NS_ASSERTION(mRuleTree, "Reconstructing before first construction?");
+ mInReconstruct = true;
+
+ // Clear any ArenaRefPtr-managed style contexts, as we don't want them
+ // held on to after the rule tree has been reconstructed.
+ PresContext()->PresShell()->ClearArenaRefPtrs(eArenaObjectID_nsStyleContext);
+
+#ifdef DEBUG
+ MOZ_ASSERT(!mOldRootNode);
+ mOldRootNode = mRuleTree;
+#endif
+
+ // Create a new rule tree root, dropping the reference to our old rule tree.
+ // After reconstruction, we will re-enable GC, and allow everything to be
+ // collected.
+ mRuleTree = nsRuleNode::CreateRootNode(mRuleTree->PresContext());
+
+ return NS_OK;
+}
+
+void
+nsStyleSet::EndReconstruct()
+{
+ NS_ASSERTION(mInReconstruct, "Unmatched begin/end?");
+ mInReconstruct = false;
+ GCRuleTrees();
+}
+
+typedef nsDataHashtable<nsPtrHashKey<nsINode>, uint32_t> ScopeDepthCache;
+
+// Returns the depth of a style scope element, with 1 being the depth of
+// a style scope element that has no ancestor style scope elements. The
+// depth does not count intervening non-scope elements.
+static uint32_t
+GetScopeDepth(nsINode* aScopeElement, ScopeDepthCache& aCache)
+{
+ nsINode* parent = aScopeElement->GetParent();
+ if (!parent || !parent->IsElementInStyleScope()) {
+ return 1;
+ }
+
+ uint32_t depth = aCache.Get(aScopeElement);
+ if (!depth) {
+ for (nsINode* n = parent; n; n = n->GetParent()) {
+ if (n->IsScopedStyleRoot()) {
+ depth = GetScopeDepth(n, aCache) + 1;
+ aCache.Put(aScopeElement, depth);
+ break;
+ }
+ }
+ }
+ return depth;
+}
+
+struct ScopedSheetOrder
+{
+ CSSStyleSheet* mSheet;
+ uint32_t mDepth;
+ uint32_t mOrder;
+
+ bool operator==(const ScopedSheetOrder& aRHS) const
+ {
+ return mDepth == aRHS.mDepth &&
+ mOrder == aRHS.mOrder;
+ }
+
+ bool operator<(const ScopedSheetOrder& aRHS) const
+ {
+ if (mDepth != aRHS.mDepth) {
+ return mDepth < aRHS.mDepth;
+ }
+ return mOrder < aRHS.mOrder;
+ }
+};
+
+// Sorts aSheets such that style sheets for ancestor scopes come
+// before those for descendant scopes, and with sheets for a single
+// scope in document order.
+static void
+SortStyleSheetsByScope(nsTArray<CSSStyleSheet*>& aSheets)
+{
+ uint32_t n = aSheets.Length();
+ if (n == 1) {
+ return;
+ }
+
+ ScopeDepthCache cache;
+
+ nsTArray<ScopedSheetOrder> sheets;
+ sheets.SetLength(n);
+
+ // For each sheet, record the depth of its scope element and its original
+ // document order.
+ for (uint32_t i = 0; i < n; i++) {
+ sheets[i].mSheet = aSheets[i];
+ sheets[i].mDepth = GetScopeDepth(aSheets[i]->GetScopeElement(), cache);
+ sheets[i].mOrder = i;
+ }
+
+ // Sort by depth first, then document order.
+ sheets.Sort();
+
+ for (uint32_t i = 0; i < n; i++) {
+ aSheets[i] = sheets[i].mSheet;
+ }
+}
+
+nsresult
+nsStyleSet::GatherRuleProcessors(SheetType aType)
+{
+ NS_ENSURE_FALSE(mInShutdown, NS_ERROR_FAILURE);
+
+ // We might be in GatherRuleProcessors because we are dropping a sheet,
+ // resulting in an nsCSSSelector being destroyed. Tell the
+ // RestyleManager for each document we're used in so that they can
+ // drop any nsCSSSelector pointers (used for eRestyle_SomeDescendants)
+ // in their mPendingRestyles.
+ if (IsCSSSheetType(aType)) {
+ ClearSelectors();
+ }
+ nsCOMPtr<nsIStyleRuleProcessor> oldRuleProcessor(mRuleProcessors[aType]);
+ nsTArray<nsCOMPtr<nsIStyleRuleProcessor>> oldScopedDocRuleProcessors;
+ if (aType == SheetType::Agent || aType == SheetType::User) {
+ // drop reference to cached rule processor
+ nsCSSRuleProcessor* rp =
+ static_cast<nsCSSRuleProcessor*>(mRuleProcessors[aType].get());
+ if (rp) {
+ MOZ_ASSERT(rp->IsShared());
+ rp->ReleaseStyleSetRef();
+ }
+ }
+ mRuleProcessors[aType] = nullptr;
+ if (aType == SheetType::ScopedDoc) {
+ for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) {
+ nsIStyleRuleProcessor* processor = mScopedDocSheetRuleProcessors[i].get();
+ Element* scope =
+ static_cast<nsCSSRuleProcessor*>(processor)->GetScopeElement();
+ scope->ClearIsScopedStyleRoot();
+ }
+
+ // Clear mScopedDocSheetRuleProcessors, but save it.
+ oldScopedDocRuleProcessors.SwapElements(mScopedDocSheetRuleProcessors);
+ }
+ if (mAuthorStyleDisabled && (aType == SheetType::Doc ||
+ aType == SheetType::ScopedDoc ||
+ aType == SheetType::StyleAttr)) {
+ // Don't regather if this level is disabled. Note that we gather
+ // preshint sheets no matter what, but then skip them for some
+ // elements later if mAuthorStyleDisabled.
+ return NS_OK;
+ }
+ switch (aType) {
+ // levels that do not contain CSS style sheets
+ case SheetType::Animation:
+ MOZ_ASSERT(mSheets[aType].IsEmpty());
+ mRuleProcessors[aType] = PresContext()->EffectCompositor()->
+ RuleProcessor(EffectCompositor::CascadeLevel::Animations);
+ return NS_OK;
+ case SheetType::Transition:
+ MOZ_ASSERT(mSheets[aType].IsEmpty());
+ mRuleProcessors[aType] = PresContext()->EffectCompositor()->
+ RuleProcessor(EffectCompositor::CascadeLevel::Transitions);
+ return NS_OK;
+ case SheetType::StyleAttr:
+ MOZ_ASSERT(mSheets[aType].IsEmpty());
+ mRuleProcessors[aType] = PresContext()->Document()->GetInlineStyleSheet();
+ return NS_OK;
+ case SheetType::PresHint:
+ MOZ_ASSERT(mSheets[aType].IsEmpty());
+ mRuleProcessors[aType] =
+ PresContext()->Document()->GetAttributeStyleSheet();
+ return NS_OK;
+ case SheetType::SVGAttrAnimation:
+ MOZ_ASSERT(mSheets[aType].IsEmpty());
+ mRuleProcessors[aType] =
+ PresContext()->Document()->GetSVGAttrAnimationRuleProcessor();
+ return NS_OK;
+ default:
+ // keep going
+ break;
+ }
+ MOZ_ASSERT(IsCSSSheetType(aType));
+ if (aType == SheetType::ScopedDoc) {
+ // Create a rule processor for each scope.
+ uint32_t count = mSheets[SheetType::ScopedDoc].Length();
+ if (count) {
+ // Gather the scoped style sheets into an array as
+ // CSSStyleSheets, and mark all of their scope elements
+ // as scoped style roots.
+ nsTArray<CSSStyleSheet*> sheets(count);
+ for (CSSStyleSheet* sheet : mSheets[SheetType::ScopedDoc]) {
+ sheets.AppendElement(sheet);
+
+ Element* scope = sheet->GetScopeElement();
+ scope->SetIsScopedStyleRoot();
+ }
+
+ // Sort the scoped style sheets so that those for the same scope are
+ // adjacent and that ancestor scopes come before descendent scopes.
+ SortStyleSheetsByScope(sheets);
+
+ // Put the old scoped rule processors in a hashtable so that we
+ // can retrieve them efficiently, even in edge cases like the
+ // simultaneous removal and addition of a large number of elements
+ // with scoped sheets.
+ nsDataHashtable<nsPtrHashKey<Element>,
+ nsCSSRuleProcessor*> oldScopedRuleProcessorHash;
+ for (size_t i = oldScopedDocRuleProcessors.Length(); i-- != 0; ) {
+ nsCSSRuleProcessor* oldRP =
+ static_cast<nsCSSRuleProcessor*>(oldScopedDocRuleProcessors[i].get());
+ Element* scope = oldRP->GetScopeElement();
+ MOZ_ASSERT(!oldScopedRuleProcessorHash.Get(scope),
+ "duplicate rule processors for same scope element?");
+ oldScopedRuleProcessorHash.Put(scope, oldRP);
+ }
+
+ uint32_t start = 0, end;
+ do {
+ // Find the range of style sheets with the same scope.
+ Element* scope = sheets[start]->GetScopeElement();
+ end = start + 1;
+ while (end < count && sheets[end]->GetScopeElement() == scope) {
+ end++;
+ }
+
+ scope->SetIsScopedStyleRoot();
+
+ // Create a rule processor for the scope.
+ nsTArray<RefPtr<CSSStyleSheet>> sheetsForScope;
+ sheetsForScope.AppendElements(sheets.Elements() + start, end - start);
+ nsCSSRuleProcessor* oldRP = oldScopedRuleProcessorHash.Get(scope);
+ mScopedDocSheetRuleProcessors.AppendElement
+ (new nsCSSRuleProcessor(Move(sheetsForScope), aType, scope, oldRP));
+
+ start = end;
+ } while (start < count);
+ }
+ return NS_OK;
+ }
+ if (!mSheets[aType].IsEmpty()) {
+ switch (aType) {
+ case SheetType::Agent:
+ case SheetType::User: {
+ // levels containing non-scoped CSS style sheets whose rule processors
+ // we want to re-use
+ nsTArray<CSSStyleSheet*> sheets(mSheets[aType].Length());
+ for (CSSStyleSheet* sheet : mSheets[aType]) {
+ sheets.AppendElement(sheet);
+ }
+ nsCSSRuleProcessor* rp =
+ RuleProcessorCache::GetRuleProcessor(sheets, PresContext());
+ if (!rp) {
+ rp = new nsCSSRuleProcessor(mSheets[aType], aType, nullptr,
+ static_cast<nsCSSRuleProcessor*>(
+ oldRuleProcessor.get()),
+ true /* aIsShared */);
+ nsTArray<css::DocumentRule*> documentRules;
+ nsDocumentRuleResultCacheKey cacheKey;
+ rp->TakeDocumentRulesAndCacheKey(PresContext(),
+ documentRules, cacheKey);
+ RuleProcessorCache::PutRuleProcessor(sheets,
+ Move(documentRules),
+ cacheKey, rp);
+ }
+ mRuleProcessors[aType] = rp;
+ rp->AddStyleSetRef();
+ break;
+ }
+ case SheetType::Doc:
+ case SheetType::Override: {
+ // levels containing non-scoped CSS stylesheets whose rule processors
+ // we don't want to re-use
+ mRuleProcessors[aType] =
+ new nsCSSRuleProcessor(mSheets[aType], aType, nullptr,
+ static_cast<nsCSSRuleProcessor*>(
+ oldRuleProcessor.get()));
+ } break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("non-CSS sheet types should be handled above");
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsStyleSet::AppendStyleSheet(SheetType aType, CSSStyleSheet* aSheet)
+{
+ NS_PRECONDITION(aSheet, "null arg");
+ NS_ASSERTION(aSheet->IsApplicable(),
+ "Inapplicable sheet being placed in style set");
+ bool present = mSheets[aType].RemoveElement(aSheet);
+ mSheets[aType].AppendElement(aSheet);
+
+ if (!present && IsCSSSheetType(aType)) {
+ aSheet->AddStyleSet(this);
+ }
+
+ return DirtyRuleProcessors(aType);
+}
+
+nsresult
+nsStyleSet::PrependStyleSheet(SheetType aType, CSSStyleSheet* aSheet)
+{
+ NS_PRECONDITION(aSheet, "null arg");
+ NS_ASSERTION(aSheet->IsApplicable(),
+ "Inapplicable sheet being placed in style set");
+ bool present = mSheets[aType].RemoveElement(aSheet);
+ mSheets[aType].InsertElementAt(0, aSheet);
+
+ if (!present && IsCSSSheetType(aType)) {
+ aSheet->AddStyleSet(this);
+ }
+
+ return DirtyRuleProcessors(aType);
+}
+
+nsresult
+nsStyleSet::RemoveStyleSheet(SheetType aType, CSSStyleSheet* aSheet)
+{
+ NS_PRECONDITION(aSheet, "null arg");
+ NS_ASSERTION(aSheet->IsComplete(),
+ "Incomplete sheet being removed from style set");
+ if (mSheets[aType].RemoveElement(aSheet)) {
+ if (IsCSSSheetType(aType)) {
+ aSheet->DropStyleSet(this);
+ }
+ }
+
+ return DirtyRuleProcessors(aType);
+}
+
+nsresult
+nsStyleSet::ReplaceSheets(SheetType aType,
+ const nsTArray<RefPtr<CSSStyleSheet>>& aNewSheets)
+{
+ bool cssSheetType = IsCSSSheetType(aType);
+ if (cssSheetType) {
+ for (CSSStyleSheet* sheet : mSheets[aType]) {
+ sheet->DropStyleSet(this);
+ }
+ }
+
+ mSheets[aType].Clear();
+ mSheets[aType].AppendElements(aNewSheets);
+
+ if (cssSheetType) {
+ for (CSSStyleSheet* sheet : mSheets[aType]) {
+ sheet->AddStyleSet(this);
+ }
+ }
+
+ return DirtyRuleProcessors(aType);
+}
+
+nsresult
+nsStyleSet::InsertStyleSheetBefore(SheetType aType, CSSStyleSheet* aNewSheet,
+ CSSStyleSheet* aReferenceSheet)
+{
+ NS_PRECONDITION(aNewSheet && aReferenceSheet, "null arg");
+ NS_ASSERTION(aNewSheet->IsApplicable(),
+ "Inapplicable sheet being placed in style set");
+
+ bool present = mSheets[aType].RemoveElement(aNewSheet);
+ int32_t idx = mSheets[aType].IndexOf(aReferenceSheet);
+ if (idx < 0)
+ return NS_ERROR_INVALID_ARG;
+
+ mSheets[aType].InsertElementAt(idx, aNewSheet);
+
+ if (!present && IsCSSSheetType(aType)) {
+ aNewSheet->AddStyleSet(this);
+ }
+
+ return DirtyRuleProcessors(aType);
+}
+
+static inline uint32_t
+DirtyBit(SheetType aType)
+{
+ return 1 << uint32_t(aType);
+}
+
+nsresult
+nsStyleSet::DirtyRuleProcessors(SheetType aType)
+{
+ if (!mBatching)
+ return GatherRuleProcessors(aType);
+
+ mDirty |= DirtyBit(aType);
+ return NS_OK;
+}
+
+bool
+nsStyleSet::GetAuthorStyleDisabled() const
+{
+ return mAuthorStyleDisabled;
+}
+
+nsresult
+nsStyleSet::SetAuthorStyleDisabled(bool aStyleDisabled)
+{
+ if (aStyleDisabled == !mAuthorStyleDisabled) {
+ mAuthorStyleDisabled = aStyleDisabled;
+ BeginUpdate();
+ mDirty |= DirtyBit(SheetType::Doc) |
+ DirtyBit(SheetType::ScopedDoc) |
+ DirtyBit(SheetType::StyleAttr);
+ return EndUpdate();
+ }
+ return NS_OK;
+}
+
+// -------- Doc Sheets
+
+nsresult
+nsStyleSet::AddDocStyleSheet(CSSStyleSheet* aSheet, nsIDocument* aDocument)
+{
+ NS_PRECONDITION(aSheet && aDocument, "null arg");
+ NS_ASSERTION(aSheet->IsApplicable(),
+ "Inapplicable sheet being placed in style set");
+
+ SheetType type = aSheet->GetScopeElement() ?
+ SheetType::ScopedDoc :
+ SheetType::Doc;
+ nsTArray<RefPtr<CSSStyleSheet>>& sheets = mSheets[type];
+
+ bool present = sheets.RemoveElement(aSheet);
+
+ size_t index = aDocument->FindDocStyleSheetInsertionPoint(sheets, aSheet);
+ sheets.InsertElementAt(index, aSheet);
+
+ if (!present) {
+ aSheet->AddStyleSet(this);
+ }
+
+ return DirtyRuleProcessors(type);
+}
+
+void
+nsStyleSet::AppendAllXBLStyleSheets(nsTArray<mozilla::CSSStyleSheet*>& aArray) const
+{
+ if (mBindingManager) {
+ // XXXheycam stylo: AppendAllSheets will need to be able to return either
+ // CSSStyleSheets or ServoStyleSheets, on request (and then here requesting
+ // CSSStyleSheets).
+ AutoTArray<StyleSheet*, 32> sheets;
+ mBindingManager->AppendAllSheets(sheets);
+ for (StyleSheet* handle : sheets) {
+ MOZ_ASSERT(handle->IsGecko(), "stylo: AppendAllSheets shouldn't give us "
+ "ServoStyleSheets yet");
+ aArray.AppendElement(handle->AsGecko());
+ }
+ }
+}
+
+nsresult
+nsStyleSet::RemoveDocStyleSheet(CSSStyleSheet* aSheet)
+{
+ bool isScoped = aSheet->GetScopeElement();
+ return RemoveStyleSheet(isScoped ? SheetType::ScopedDoc : SheetType::Doc,
+ aSheet);
+}
+
+// Batching
+void
+nsStyleSet::BeginUpdate()
+{
+ ++mBatching;
+}
+
+nsresult
+nsStyleSet::EndUpdate()
+{
+ NS_ASSERTION(mBatching > 0, "Unbalanced EndUpdate");
+ if (--mBatching) {
+ // We're not completely done yet.
+ return NS_OK;
+ }
+
+ for (SheetType type : MakeEnumeratedRange(SheetType::Count)) {
+ if (mDirty & DirtyBit(type)) {
+ nsresult rv = GatherRuleProcessors(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ mDirty = 0;
+ return NS_OK;
+}
+
+template<class T>
+static bool
+EnumRulesMatching(nsIStyleRuleProcessor* aProcessor, void* aData)
+{
+ T* data = static_cast<T*>(aData);
+ aProcessor->RulesMatching(data);
+ return true;
+}
+
+static inline bool
+IsMoreSpecificThanAnimation(nsRuleNode *aRuleNode)
+{
+ return !aRuleNode->IsRoot() &&
+ (aRuleNode->GetLevel() == SheetType::Transition ||
+ aRuleNode->IsImportantRule());
+}
+
+static nsIStyleRule*
+GetAnimationRule(nsRuleNode *aRuleNode)
+{
+ nsRuleNode *n = aRuleNode;
+ while (IsMoreSpecificThanAnimation(n)) {
+ n = n->GetParent();
+ }
+
+ if (n->IsRoot() || n->GetLevel() != SheetType::Animation) {
+ return nullptr;
+ }
+
+ return n->GetRule();
+}
+
+static nsRuleNode*
+ReplaceAnimationRule(nsRuleNode *aOldRuleNode,
+ nsIStyleRule *aOldAnimRule,
+ nsIStyleRule *aNewAnimRule)
+{
+ nsTArray<nsRuleNode*> moreSpecificNodes;
+
+ nsRuleNode *n = aOldRuleNode;
+ while (IsMoreSpecificThanAnimation(n)) {
+ moreSpecificNodes.AppendElement(n);
+ n = n->GetParent();
+ }
+
+ if (aOldAnimRule) {
+ MOZ_ASSERT(n->GetRule() == aOldAnimRule, "wrong rule");
+ MOZ_ASSERT(n->GetLevel() == SheetType::Animation,
+ "wrong level");
+ n = n->GetParent();
+ }
+
+ MOZ_ASSERT(!IsMoreSpecificThanAnimation(n) &&
+ (n->IsRoot() || n->GetLevel() != SheetType::Animation),
+ "wrong level");
+
+ if (aNewAnimRule) {
+ n = n->Transition(aNewAnimRule, SheetType::Animation, false);
+ n->SetIsAnimationRule();
+ }
+
+ for (uint32_t i = moreSpecificNodes.Length(); i-- != 0; ) {
+ nsRuleNode *oldNode = moreSpecificNodes[i];
+ n = n->Transition(oldNode->GetRule(), oldNode->GetLevel(),
+ oldNode->IsImportantRule());
+ }
+
+ return n;
+}
+
+/**
+ * |GetContext| implements sharing of style contexts (not just the data
+ * on the rule nodes) between siblings and cousins of the same
+ * generation. (It works for cousins of the same generation since
+ * |aParentContext| could itself be a shared context.)
+ */
+already_AddRefed<nsStyleContext>
+nsStyleSet::GetContext(nsStyleContext* aParentContext,
+ nsRuleNode* aRuleNode,
+ // aVisitedRuleNode may be null; if it is null
+ // it means that we don't need to force creation
+ // of a StyleIfVisited. (But if we make one
+ // because aParentContext has one, then aRuleNode
+ // should be used.)
+ nsRuleNode* aVisitedRuleNode,
+ nsIAtom* aPseudoTag,
+ CSSPseudoElementType aPseudoType,
+ Element* aElementForAnimation,
+ uint32_t aFlags)
+{
+ NS_PRECONDITION((!aPseudoTag &&
+ aPseudoType ==
+ CSSPseudoElementType::NotPseudo) ||
+ (aPseudoTag &&
+ nsCSSPseudoElements::GetPseudoType(
+ aPseudoTag, CSSEnabledState::eIgnoreEnabledState) ==
+ aPseudoType),
+ "Pseudo mismatch");
+
+ if (aVisitedRuleNode == aRuleNode) {
+ // No need to force creation of a visited style in this case.
+ aVisitedRuleNode = nullptr;
+ }
+
+ // Ensure |aVisitedRuleNode != nullptr| corresponds to the need to
+ // create an if-visited style context, and that in that case, we have
+ // parentIfVisited set correctly.
+ nsStyleContext *parentIfVisited =
+ aParentContext ? aParentContext->GetStyleIfVisited() : nullptr;
+ if (parentIfVisited) {
+ if (!aVisitedRuleNode) {
+ aVisitedRuleNode = aRuleNode;
+ }
+ } else {
+ if (aVisitedRuleNode) {
+ parentIfVisited = aParentContext;
+ }
+ }
+
+ if (aFlags & eIsLink) {
+ // If this node is a link, we want its visited's style context's
+ // parent to be the regular style context of its parent, because
+ // only the visitedness of the relevant link should influence style.
+ parentIfVisited = aParentContext;
+ }
+
+ bool relevantLinkVisited = (aFlags & eIsLink) ?
+ (aFlags & eIsVisitedLink) :
+ (aParentContext && aParentContext->RelevantLinkVisited());
+
+ RefPtr<nsStyleContext> result;
+ if (aParentContext)
+ result = aParentContext->FindChildWithRules(aPseudoTag, aRuleNode,
+ aVisitedRuleNode,
+ relevantLinkVisited);
+
+ if (!result) {
+ // |aVisitedRuleNode| may have a ref-count of zero since we are yet
+ // to create the style context that will hold an owning reference to it.
+ // As a result, we need to make sure it stays alive until that point
+ // in case something in the first call to NS_NewStyleContext triggers a
+ // GC sweep of rule nodes.
+ RefPtr<nsRuleNode> kungFuDeathGrip{aVisitedRuleNode};
+
+ result = NS_NewStyleContext(aParentContext, aPseudoTag, aPseudoType,
+ aRuleNode,
+ aFlags & eSkipParentDisplayBasedStyleFixup);
+ if (aVisitedRuleNode) {
+ RefPtr<nsStyleContext> resultIfVisited =
+ NS_NewStyleContext(parentIfVisited, aPseudoTag, aPseudoType,
+ aVisitedRuleNode,
+ aFlags & eSkipParentDisplayBasedStyleFixup);
+ resultIfVisited->SetIsStyleIfVisited();
+ result->SetStyleIfVisited(resultIfVisited.forget());
+
+ if (relevantLinkVisited) {
+ result->AddStyleBit(NS_STYLE_RELEVANT_LINK_VISITED);
+ }
+ }
+ }
+ else {
+ NS_ASSERTION(result->GetPseudoType() == aPseudoType, "Unexpected type");
+ NS_ASSERTION(result->GetPseudo() == aPseudoTag, "Unexpected pseudo");
+ }
+
+ if (aFlags & eDoAnimation) {
+
+ nsIStyleRule *oldAnimRule = GetAnimationRule(aRuleNode);
+ nsIStyleRule *animRule = nullptr;
+
+ // Ignore animations for print or print preview, and for elements
+ // that are not attached to the document tree.
+ if (PresContext()->IsDynamic() &&
+ aElementForAnimation->IsInComposedDoc()) {
+ // Update CSS animations in case the animation-name has just changed.
+ PresContext()->AnimationManager()->UpdateAnimations(result,
+ aElementForAnimation);
+ PresContext()->EffectCompositor()->UpdateEffectProperties(
+ result, aElementForAnimation, result->GetPseudoType());
+
+ animRule = PresContext()->EffectCompositor()->
+ GetAnimationRule(aElementForAnimation,
+ result->GetPseudoType(),
+ EffectCompositor::CascadeLevel::Animations,
+ result);
+ }
+
+ MOZ_ASSERT(result->RuleNode() == aRuleNode,
+ "unexpected rule node");
+ MOZ_ASSERT(!result->GetStyleIfVisited() == !aVisitedRuleNode,
+ "unexpected visited rule node");
+ MOZ_ASSERT(!aVisitedRuleNode ||
+ result->GetStyleIfVisited()->RuleNode() == aVisitedRuleNode,
+ "unexpected visited rule node");
+ MOZ_ASSERT(!aVisitedRuleNode ||
+ oldAnimRule == GetAnimationRule(aVisitedRuleNode),
+ "animation rule mismatch between rule nodes");
+ if (oldAnimRule != animRule) {
+ nsRuleNode *ruleNode =
+ ReplaceAnimationRule(aRuleNode, oldAnimRule, animRule);
+ nsRuleNode *visitedRuleNode = aVisitedRuleNode
+ ? ReplaceAnimationRule(aVisitedRuleNode, oldAnimRule, animRule)
+ : nullptr;
+ MOZ_ASSERT(!visitedRuleNode ||
+ GetAnimationRule(ruleNode) ==
+ GetAnimationRule(visitedRuleNode),
+ "animation rule mismatch between rule nodes");
+ result = GetContext(aParentContext, ruleNode, visitedRuleNode,
+ aPseudoTag, aPseudoType, nullptr,
+ aFlags & ~eDoAnimation);
+ }
+ }
+
+ if (aElementForAnimation &&
+ aElementForAnimation->IsHTMLElement(nsGkAtoms::body) &&
+ aPseudoType == CSSPseudoElementType::NotPseudo &&
+ PresContext()->CompatibilityMode() == eCompatibility_NavQuirks) {
+ nsIDocument* doc = aElementForAnimation->GetUncomposedDoc();
+ if (doc && doc->GetBodyElement() == aElementForAnimation) {
+ // Update the prescontext's body color
+ PresContext()->SetBodyTextColor(result->StyleColor()->mColor);
+ }
+ }
+
+ return result.forget();
+}
+
+void
+nsStyleSet::AddImportantRules(nsRuleNode* aCurrLevelNode,
+ nsRuleNode* aLastPrevLevelNode,
+ nsRuleWalker* aRuleWalker)
+{
+ NS_ASSERTION(aCurrLevelNode &&
+ aCurrLevelNode != aLastPrevLevelNode, "How did we get here?");
+
+ AutoTArray<nsIStyleRule*, 16> importantRules;
+ for (nsRuleNode *node = aCurrLevelNode; node != aLastPrevLevelNode;
+ node = node->GetParent()) {
+ // We guarantee that we never walk the root node here, so no need
+ // to null-check GetRule(). Furthermore, it must be a CSS rule.
+ NS_ASSERTION(RefPtr<css::Declaration>(do_QueryObject(node->GetRule())),
+ "Unexpected non-CSS rule");
+
+ nsIStyleRule* impRule =
+ static_cast<css::Declaration*>(node->GetRule())->GetImportantStyleData();
+ if (impRule)
+ importantRules.AppendElement(impRule);
+ }
+
+ NS_ASSERTION(importantRules.Length() != 0,
+ "Why did we think there were important rules?");
+
+ for (uint32_t i = importantRules.Length(); i-- != 0; ) {
+ aRuleWalker->Forward(importantRules[i]);
+ }
+}
+
+#ifdef DEBUG
+void
+nsStyleSet::AssertNoImportantRules(nsRuleNode* aCurrLevelNode,
+ nsRuleNode* aLastPrevLevelNode)
+{
+ if (!aCurrLevelNode)
+ return;
+
+ for (nsRuleNode *node = aCurrLevelNode; node != aLastPrevLevelNode;
+ node = node->GetParent()) {
+ RefPtr<css::Declaration> declaration(do_QueryObject(node->GetRule()));
+ NS_ASSERTION(declaration, "Unexpected non-CSS rule");
+
+ NS_ASSERTION(!declaration->GetImportantStyleData(),
+ "Unexpected important style source");
+ }
+}
+
+void
+nsStyleSet::AssertNoCSSRules(nsRuleNode* aCurrLevelNode,
+ nsRuleNode* aLastPrevLevelNode)
+{
+ if (!aCurrLevelNode)
+ return;
+
+ for (nsRuleNode *node = aCurrLevelNode; node != aLastPrevLevelNode;
+ node = node->GetParent()) {
+ nsIStyleRule *rule = node->GetRule();
+ RefPtr<css::Declaration> declaration(do_QueryObject(rule));
+ if (declaration) {
+ RefPtr<css::StyleRule> cssRule =
+ do_QueryObject(declaration->GetOwningRule());
+ NS_ASSERTION(!cssRule || !cssRule->Selector(),
+ "Unexpected CSS rule");
+ }
+ }
+}
+#endif
+
+// Enumerate the rules in a way that cares about the order of the rules.
+void
+nsStyleSet::FileRules(nsIStyleRuleProcessor::EnumFunc aCollectorFunc,
+ RuleProcessorData* aData, Element* aElement,
+ nsRuleWalker* aRuleWalker)
+{
+ PROFILER_LABEL("nsStyleSet", "FileRules",
+ js::ProfileEntry::Category::CSS);
+
+ NS_ASSERTION(mBatching == 0, "rule processors out of date");
+
+ // Cascading order:
+ // [least important]
+ // - UA normal rules = Agent normal
+ // - User normal rules = User normal
+ // - Presentation hints = PresHint normal
+ // - SVG Animation (highest pres hint) = SVGAttrAnimation normal
+ // - Author normal rules = Document normal
+ // - Override normal rules = Override normal
+ // - animation rules = Animation normal
+ // - Author !important rules = Document !important
+ // - Override !important rules = Override !important
+ // - User !important rules = User !important
+ // - UA !important rules = Agent !important
+ // - transition rules = Transition normal
+ // [most important]
+
+ // Save off the last rule before we start walking our agent sheets;
+ // this will be either the root or one of the restriction rules.
+ nsRuleNode* lastRestrictionRN = aRuleWalker->CurrentNode();
+
+ aRuleWalker->SetLevel(SheetType::Agent, false, true);
+ if (mRuleProcessors[SheetType::Agent])
+ (*aCollectorFunc)(mRuleProcessors[SheetType::Agent], aData);
+ nsRuleNode* lastAgentRN = aRuleWalker->CurrentNode();
+ bool haveImportantUARules = !aRuleWalker->GetCheckForImportantRules();
+
+ aRuleWalker->SetLevel(SheetType::User, false, true);
+ bool skipUserStyles =
+ aElement && aElement->IsInNativeAnonymousSubtree();
+ if (!skipUserStyles && mRuleProcessors[SheetType::User]) // NOTE: different
+ (*aCollectorFunc)(mRuleProcessors[SheetType::User], aData);
+ nsRuleNode* lastUserRN = aRuleWalker->CurrentNode();
+ bool haveImportantUserRules = !aRuleWalker->GetCheckForImportantRules();
+
+ aRuleWalker->SetLevel(SheetType::PresHint, false, false);
+ if (mRuleProcessors[SheetType::PresHint])
+ (*aCollectorFunc)(mRuleProcessors[SheetType::PresHint], aData);
+
+ aRuleWalker->SetLevel(SheetType::SVGAttrAnimation, false, false);
+ if (mRuleProcessors[SheetType::SVGAttrAnimation])
+ (*aCollectorFunc)(mRuleProcessors[SheetType::SVGAttrAnimation], aData);
+ nsRuleNode* lastSVGAttrAnimationRN = aRuleWalker->CurrentNode();
+
+ aRuleWalker->SetLevel(SheetType::Doc, false, true);
+ bool cutOffInheritance = false;
+ if (mBindingManager && aElement) {
+ // We can supply additional document-level sheets that should be walked.
+ mBindingManager->WalkRules(aCollectorFunc,
+ static_cast<ElementDependentRuleProcessorData*>(aData),
+ &cutOffInheritance);
+ }
+ if (!skipUserStyles && !cutOffInheritance && // NOTE: different
+ mRuleProcessors[SheetType::Doc])
+ (*aCollectorFunc)(mRuleProcessors[SheetType::Doc], aData);
+ nsRuleNode* lastDocRN = aRuleWalker->CurrentNode();
+ bool haveImportantDocRules = !aRuleWalker->GetCheckForImportantRules();
+ nsTArray<nsRuleNode*> lastScopedRNs;
+ nsTArray<bool> haveImportantScopedRules;
+ bool haveAnyImportantScopedRules = false;
+ if (!skipUserStyles && !cutOffInheritance &&
+ aElement && aElement->IsElementInStyleScope()) {
+ lastScopedRNs.SetLength(mScopedDocSheetRuleProcessors.Length());
+ haveImportantScopedRules.SetLength(mScopedDocSheetRuleProcessors.Length());
+ for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) {
+ aRuleWalker->SetLevel(SheetType::ScopedDoc, false, true);
+ nsCSSRuleProcessor* processor =
+ static_cast<nsCSSRuleProcessor*>(mScopedDocSheetRuleProcessors[i].get());
+ aData->mScope = processor->GetScopeElement();
+ (*aCollectorFunc)(mScopedDocSheetRuleProcessors[i], aData);
+ lastScopedRNs[i] = aRuleWalker->CurrentNode();
+ haveImportantScopedRules[i] = !aRuleWalker->GetCheckForImportantRules();
+ haveAnyImportantScopedRules = haveAnyImportantScopedRules || haveImportantScopedRules[i];
+ }
+ aData->mScope = nullptr;
+ }
+ nsRuleNode* lastScopedRN = aRuleWalker->CurrentNode();
+ aRuleWalker->SetLevel(SheetType::StyleAttr, false, true);
+ if (mRuleProcessors[SheetType::StyleAttr])
+ (*aCollectorFunc)(mRuleProcessors[SheetType::StyleAttr], aData);
+ nsRuleNode* lastStyleAttrRN = aRuleWalker->CurrentNode();
+ bool haveImportantStyleAttrRules = !aRuleWalker->GetCheckForImportantRules();
+
+ aRuleWalker->SetLevel(SheetType::Override, false, true);
+ if (mRuleProcessors[SheetType::Override])
+ (*aCollectorFunc)(mRuleProcessors[SheetType::Override], aData);
+ nsRuleNode* lastOvrRN = aRuleWalker->CurrentNode();
+ bool haveImportantOverrideRules = !aRuleWalker->GetCheckForImportantRules();
+
+ // This needs to match IsMoreSpecificThanAnimation() above.
+ aRuleWalker->SetLevel(SheetType::Animation, false, false);
+ (*aCollectorFunc)(mRuleProcessors[SheetType::Animation], aData);
+
+ if (haveAnyImportantScopedRules) {
+ for (uint32_t i = lastScopedRNs.Length(); i-- != 0; ) {
+ aRuleWalker->SetLevel(SheetType::ScopedDoc, true, false);
+ nsRuleNode* startRN = lastScopedRNs[i];
+ nsRuleNode* endRN = i == 0 ? lastDocRN : lastScopedRNs[i - 1];
+ if (haveImportantScopedRules[i]) {
+ AddImportantRules(startRN, endRN, aRuleWalker); // scoped
+ }
+#ifdef DEBUG
+ else {
+ AssertNoImportantRules(startRN, endRN);
+ }
+#endif
+ }
+ }
+#ifdef DEBUG
+ else {
+ AssertNoImportantRules(lastScopedRN, lastDocRN);
+ }
+#endif
+
+ if (haveImportantDocRules) {
+ aRuleWalker->SetLevel(SheetType::Doc, true, false);
+ AddImportantRules(lastDocRN, lastSVGAttrAnimationRN, aRuleWalker); // doc
+ }
+#ifdef DEBUG
+ else {
+ AssertNoImportantRules(lastDocRN, lastSVGAttrAnimationRN);
+ }
+#endif
+
+ if (haveImportantStyleAttrRules) {
+ aRuleWalker->SetLevel(SheetType::StyleAttr, true, false);
+ AddImportantRules(lastStyleAttrRN, lastScopedRN, aRuleWalker); // style attr
+ }
+#ifdef DEBUG
+ else {
+ AssertNoImportantRules(lastStyleAttrRN, lastScopedRN);
+ }
+#endif
+
+ if (haveImportantOverrideRules) {
+ aRuleWalker->SetLevel(SheetType::Override, true, false);
+ AddImportantRules(lastOvrRN, lastStyleAttrRN, aRuleWalker); // override
+ }
+#ifdef DEBUG
+ else {
+ AssertNoImportantRules(lastOvrRN, lastStyleAttrRN);
+ }
+#endif
+
+#ifdef DEBUG
+ AssertNoCSSRules(lastSVGAttrAnimationRN, lastUserRN);
+#endif
+
+ if (haveImportantUserRules) {
+ aRuleWalker->SetLevel(SheetType::User, true, false);
+ AddImportantRules(lastUserRN, lastAgentRN, aRuleWalker); //user
+ }
+#ifdef DEBUG
+ else {
+ AssertNoImportantRules(lastUserRN, lastAgentRN);
+ }
+#endif
+
+ if (haveImportantUARules) {
+ aRuleWalker->SetLevel(SheetType::Agent, true, false);
+ AddImportantRules(lastAgentRN, lastRestrictionRN, aRuleWalker); //agent
+ }
+#ifdef DEBUG
+ else {
+ AssertNoImportantRules(lastAgentRN, lastRestrictionRN);
+ }
+#endif
+
+#ifdef DEBUG
+ AssertNoCSSRules(lastRestrictionRN, mRuleTree);
+#endif
+
+#ifdef DEBUG
+ nsRuleNode *lastImportantRN = aRuleWalker->CurrentNode();
+#endif
+ aRuleWalker->SetLevel(SheetType::Transition, false, false);
+ (*aCollectorFunc)(mRuleProcessors[SheetType::Transition], aData);
+#ifdef DEBUG
+ AssertNoCSSRules(aRuleWalker->CurrentNode(), lastImportantRN);
+#endif
+
+}
+
+// Enumerate all the rules in a way that doesn't care about the order
+// of the rules and doesn't walk !important-rules.
+void
+nsStyleSet::WalkRuleProcessors(nsIStyleRuleProcessor::EnumFunc aFunc,
+ ElementDependentRuleProcessorData* aData,
+ bool aWalkAllXBLStylesheets)
+{
+ NS_ASSERTION(mBatching == 0, "rule processors out of date");
+
+ if (mRuleProcessors[SheetType::Agent])
+ (*aFunc)(mRuleProcessors[SheetType::Agent], aData);
+
+ bool skipUserStyles = aData->mElement->IsInNativeAnonymousSubtree();
+ if (!skipUserStyles && mRuleProcessors[SheetType::User]) // NOTE: different
+ (*aFunc)(mRuleProcessors[SheetType::User], aData);
+
+ if (mRuleProcessors[SheetType::PresHint])
+ (*aFunc)(mRuleProcessors[SheetType::PresHint], aData);
+
+ if (mRuleProcessors[SheetType::SVGAttrAnimation])
+ (*aFunc)(mRuleProcessors[SheetType::SVGAttrAnimation], aData);
+
+ bool cutOffInheritance = false;
+ if (mBindingManager) {
+ // We can supply additional document-level sheets that should be walked.
+ if (aWalkAllXBLStylesheets) {
+ mBindingManager->WalkAllRules(aFunc, aData);
+ } else {
+ mBindingManager->WalkRules(aFunc, aData, &cutOffInheritance);
+ }
+ }
+ if (!skipUserStyles && !cutOffInheritance) {
+ if (mRuleProcessors[SheetType::Doc]) // NOTE: different
+ (*aFunc)(mRuleProcessors[SheetType::Doc], aData);
+ if (aData->mElement->IsElementInStyleScope()) {
+ for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++)
+ (*aFunc)(mScopedDocSheetRuleProcessors[i], aData);
+ }
+ }
+ if (mRuleProcessors[SheetType::StyleAttr])
+ (*aFunc)(mRuleProcessors[SheetType::StyleAttr], aData);
+ if (mRuleProcessors[SheetType::Override])
+ (*aFunc)(mRuleProcessors[SheetType::Override], aData);
+ (*aFunc)(mRuleProcessors[SheetType::Animation], aData);
+ (*aFunc)(mRuleProcessors[SheetType::Transition], aData);
+}
+
+static void
+InitStyleScopes(TreeMatchContext& aTreeContext, Element* aElement)
+{
+ if (aElement->IsElementInStyleScope()) {
+ aTreeContext.InitStyleScopes(aElement->GetParentElementCrossingShadowRoot());
+ }
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveStyleFor(Element* aElement,
+ nsStyleContext* aParentContext)
+{
+ TreeMatchContext treeContext(true, nsRuleWalker::eRelevantLinkUnvisited,
+ aElement->OwnerDoc());
+ InitStyleScopes(treeContext, aElement);
+ return ResolveStyleFor(aElement, aParentContext, treeContext);
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveStyleFor(Element* aElement,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext)
+{
+ NS_ENSURE_FALSE(mInShutdown, nullptr);
+ NS_ASSERTION(aElement, "aElement must not be null");
+
+ nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled);
+ aTreeMatchContext.ResetForUnvisitedMatching();
+ ElementRuleProcessorData data(PresContext(), aElement, &ruleWalker,
+ aTreeMatchContext);
+ WalkDisableTextZoomRule(aElement, &ruleWalker);
+ FileRules(EnumRulesMatching<ElementRuleProcessorData>, &data, aElement,
+ &ruleWalker);
+
+ nsRuleNode *ruleNode = ruleWalker.CurrentNode();
+ nsRuleNode *visitedRuleNode = nullptr;
+
+ if (aTreeMatchContext.HaveRelevantLink()) {
+ aTreeMatchContext.ResetForVisitedMatching();
+ ruleWalker.Reset();
+ FileRules(EnumRulesMatching<ElementRuleProcessorData>, &data, aElement,
+ &ruleWalker);
+ visitedRuleNode = ruleWalker.CurrentNode();
+ }
+
+ uint32_t flags = eDoAnimation;
+ if (nsCSSRuleProcessor::IsLink(aElement)) {
+ flags |= eIsLink;
+ }
+ if (nsCSSRuleProcessor::GetContentState(aElement, aTreeMatchContext).
+ HasState(NS_EVENT_STATE_VISITED)) {
+ flags |= eIsVisitedLink;
+ }
+ if (aTreeMatchContext.mSkippingParentDisplayBasedStyleFixup) {
+ flags |= eSkipParentDisplayBasedStyleFixup;
+ }
+
+ return GetContext(aParentContext, ruleNode, visitedRuleNode,
+ nullptr, CSSPseudoElementType::NotPseudo,
+ aElement, flags);
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveStyleForRules(nsStyleContext* aParentContext,
+ const nsTArray< nsCOMPtr<nsIStyleRule> > &aRules)
+{
+ NS_ENSURE_FALSE(mInShutdown, nullptr);
+
+ nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled);
+ // FIXME: Perhaps this should be passed in, but it probably doesn't
+ // matter.
+ ruleWalker.SetLevel(SheetType::Doc, false, false);
+ for (uint32_t i = 0; i < aRules.Length(); i++) {
+ ruleWalker.ForwardOnPossiblyCSSRule(aRules.ElementAt(i));
+ }
+
+ return GetContext(aParentContext, ruleWalker.CurrentNode(), nullptr,
+ nullptr, CSSPseudoElementType::NotPseudo,
+ nullptr, eNoFlags);
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveStyleByAddingRules(nsStyleContext* aBaseContext,
+ const nsCOMArray<nsIStyleRule> &aRules)
+{
+ NS_ENSURE_FALSE(mInShutdown, nullptr);
+
+ nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled);
+ ruleWalker.SetCurrentNode(aBaseContext->RuleNode());
+ // This needs to be the transition sheet because that is the highest
+ // level of the cascade, and thus the only thing that makes sense if
+ // we are ever going to call ResolveStyleWithReplacement on the
+ // resulting context. It's also the right thing for the one case (the
+ // transition manager's cover rule) where we put the result of this
+ // function in the style context tree.
+ ruleWalker.SetLevel(SheetType::Transition, false, false);
+ for (int32_t i = 0; i < aRules.Count(); i++) {
+ ruleWalker.ForwardOnPossiblyCSSRule(aRules.ObjectAt(i));
+ }
+
+ nsRuleNode *ruleNode = ruleWalker.CurrentNode();
+ nsRuleNode *visitedRuleNode = nullptr;
+
+ if (aBaseContext->GetStyleIfVisited()) {
+ ruleWalker.SetCurrentNode(aBaseContext->GetStyleIfVisited()->RuleNode());
+ for (int32_t i = 0; i < aRules.Count(); i++) {
+ ruleWalker.ForwardOnPossiblyCSSRule(aRules.ObjectAt(i));
+ }
+ visitedRuleNode = ruleWalker.CurrentNode();
+ }
+
+ uint32_t flags = eNoFlags;
+ if (aBaseContext->IsLinkContext()) {
+ flags |= eIsLink;
+
+ // GetContext handles propagating RelevantLinkVisited state from the
+ // parent in non-link cases; all we need to pass in is if this link
+ // is visited.
+ if (aBaseContext->RelevantLinkVisited()) {
+ flags |= eIsVisitedLink;
+ }
+ }
+ return GetContext(aBaseContext->GetParent(), ruleNode, visitedRuleNode,
+ aBaseContext->GetPseudo(),
+ aBaseContext->GetPseudoType(),
+ nullptr, flags);
+}
+
+struct RuleNodeInfo {
+ nsIStyleRule* mRule;
+ SheetType mLevel;
+ bool mIsImportant;
+ bool mIsAnimationRule;
+};
+
+struct CascadeLevel {
+ SheetType mLevel;
+ bool mIsImportant;
+ bool mCheckForImportantRules;
+ nsRestyleHint mLevelReplacementHint;
+};
+
+static const CascadeLevel gCascadeLevels[] = {
+ { SheetType::Agent, false, false, nsRestyleHint(0) },
+ { SheetType::User, false, false, nsRestyleHint(0) },
+ { SheetType::PresHint, false, false, nsRestyleHint(0) },
+ { SheetType::SVGAttrAnimation, false, false, eRestyle_SVGAttrAnimations },
+ { SheetType::Doc, false, false, nsRestyleHint(0) },
+ { SheetType::ScopedDoc, false, false, nsRestyleHint(0) },
+ { SheetType::StyleAttr, false, true, eRestyle_StyleAttribute |
+ eRestyle_StyleAttribute_Animations },
+ { SheetType::Override, false, false, nsRestyleHint(0) },
+ { SheetType::Animation, false, false, eRestyle_CSSAnimations },
+ { SheetType::ScopedDoc, true, false, nsRestyleHint(0) },
+ { SheetType::Doc, true, false, nsRestyleHint(0) },
+ { SheetType::StyleAttr, true, false, eRestyle_StyleAttribute |
+ eRestyle_StyleAttribute_Animations },
+ { SheetType::Override, true, false, nsRestyleHint(0) },
+ { SheetType::User, true, false, nsRestyleHint(0) },
+ { SheetType::Agent, true, false, nsRestyleHint(0) },
+ { SheetType::Transition, false, false, eRestyle_CSSTransitions },
+};
+
+nsRuleNode*
+nsStyleSet::RuleNodeWithReplacement(Element* aElement,
+ Element* aPseudoElement,
+ nsRuleNode* aOldRuleNode,
+ CSSPseudoElementType aPseudoType,
+ nsRestyleHint aReplacements)
+{
+ NS_ASSERTION(mBatching == 0, "rule processors out of date");
+
+ MOZ_ASSERT(!aPseudoElement ==
+ (aPseudoType >= CSSPseudoElementType::Count ||
+ !(nsCSSPseudoElements::PseudoElementSupportsStyleAttribute(aPseudoType) ||
+ nsCSSPseudoElements::PseudoElementSupportsUserActionState(aPseudoType))),
+ "should have aPseudoElement only for certain pseudo elements");
+
+ MOZ_ASSERT(!(aReplacements & ~(eRestyle_CSSTransitions |
+ eRestyle_CSSAnimations |
+ eRestyle_SVGAttrAnimations |
+ eRestyle_StyleAttribute |
+ eRestyle_StyleAttribute_Animations |
+ eRestyle_Force |
+ eRestyle_ForceDescendants)),
+ "unexpected replacement bits");
+
+ // FIXME (perf): This should probably not rebuild the whole path, but
+ // only the path from the last change in the rule tree, like
+ // ReplaceAnimationRule in nsStyleSet.cpp does. (That could then
+ // perhaps share this code, too?)
+ // But if we do that, we'll need to pass whether we are rebuilding the
+ // rule tree from ElementRestyler::RestyleSelf to avoid taking that
+ // path when we're rebuilding the rule tree.
+
+ // This array can be hot and often grows to ~20 elements, so inline storage
+ // is best.
+ AutoTArray<RuleNodeInfo, 30> rules;
+ for (nsRuleNode* ruleNode = aOldRuleNode; !ruleNode->IsRoot();
+ ruleNode = ruleNode->GetParent()) {
+ RuleNodeInfo* curRule = rules.AppendElement();
+ curRule->mRule = ruleNode->GetRule();
+ curRule->mLevel = ruleNode->GetLevel();
+ curRule->mIsImportant = ruleNode->IsImportantRule();
+ curRule->mIsAnimationRule = ruleNode->IsAnimationRule();
+ }
+
+ nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled);
+ auto rulesIndex = rules.Length();
+
+ // We need to transfer this information between the non-!important and
+ // !important phases for the style attribute level.
+ nsRuleNode* lastScopedRN = nullptr;
+ nsRuleNode* lastStyleAttrRN = nullptr;
+ bool haveImportantStyleAttrRules = false;
+
+ for (const CascadeLevel *level = gCascadeLevels,
+ *levelEnd = ArrayEnd(gCascadeLevels);
+ level != levelEnd; ++level) {
+
+ bool doReplace = level->mLevelReplacementHint & aReplacements;
+
+ ruleWalker.SetLevel(level->mLevel, level->mIsImportant,
+ level->mCheckForImportantRules && doReplace);
+
+ if (doReplace) {
+ switch (level->mLevel) {
+ case SheetType::Animation: {
+ if (aPseudoType == CSSPseudoElementType::NotPseudo ||
+ aPseudoType == CSSPseudoElementType::before ||
+ aPseudoType == CSSPseudoElementType::after) {
+ nsIStyleRule* rule = PresContext()->EffectCompositor()->
+ GetAnimationRule(aElement, aPseudoType,
+ EffectCompositor::CascadeLevel::Animations,
+ nullptr);
+ if (rule) {
+ ruleWalker.ForwardOnPossiblyCSSRule(rule);
+ ruleWalker.CurrentNode()->SetIsAnimationRule();
+ }
+ }
+ break;
+ }
+ case SheetType::Transition: {
+ if (aPseudoType == CSSPseudoElementType::NotPseudo ||
+ aPseudoType == CSSPseudoElementType::before ||
+ aPseudoType == CSSPseudoElementType::after) {
+ nsIStyleRule* rule = PresContext()->EffectCompositor()->
+ GetAnimationRule(aElement, aPseudoType,
+ EffectCompositor::CascadeLevel::Transitions,
+ nullptr);
+ if (rule) {
+ ruleWalker.ForwardOnPossiblyCSSRule(rule);
+ ruleWalker.CurrentNode()->SetIsAnimationRule();
+ }
+ }
+ break;
+ }
+ case SheetType::SVGAttrAnimation: {
+ SVGAttrAnimationRuleProcessor* ruleProcessor =
+ static_cast<SVGAttrAnimationRuleProcessor*>(
+ mRuleProcessors[SheetType::SVGAttrAnimation].get());
+ if (ruleProcessor &&
+ aPseudoType == CSSPseudoElementType::NotPseudo) {
+ ruleProcessor->ElementRulesMatching(aElement, &ruleWalker);
+ }
+ break;
+ }
+ case SheetType::StyleAttr: {
+ if (!level->mIsImportant) {
+ // First time through, we handle the non-!important rule.
+ nsHTMLCSSStyleSheet* ruleProcessor =
+ static_cast<nsHTMLCSSStyleSheet*>(
+ mRuleProcessors[SheetType::StyleAttr].get());
+ if (ruleProcessor) {
+ lastScopedRN = ruleWalker.CurrentNode();
+ if (aPseudoType ==
+ CSSPseudoElementType::NotPseudo) {
+ ruleProcessor->ElementRulesMatching(PresContext(),
+ aElement,
+ &ruleWalker);
+ } else if (aPseudoType <
+ CSSPseudoElementType::Count &&
+ nsCSSPseudoElements::
+ PseudoElementSupportsStyleAttribute(aPseudoType)) {
+ ruleProcessor->PseudoElementRulesMatching(aPseudoElement,
+ aPseudoType,
+ &ruleWalker);
+ }
+ lastStyleAttrRN = ruleWalker.CurrentNode();
+ haveImportantStyleAttrRules =
+ !ruleWalker.GetCheckForImportantRules();
+ }
+ } else {
+ // Second time through, we handle the !important rule(s).
+ if (haveImportantStyleAttrRules) {
+ AddImportantRules(lastStyleAttrRN, lastScopedRN, &ruleWalker);
+ }
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "unexpected result from gCascadeLevels lookup");
+ break;
+ }
+ }
+
+ while (rulesIndex != 0) {
+ --rulesIndex;
+ const RuleNodeInfo& ruleInfo = rules[rulesIndex];
+
+ if (ruleInfo.mLevel != level->mLevel ||
+ ruleInfo.mIsImportant != level->mIsImportant) {
+ ++rulesIndex;
+ break;
+ }
+
+ if (!doReplace) {
+ ruleWalker.ForwardOnPossiblyCSSRule(ruleInfo.mRule);
+ if (ruleInfo.mIsAnimationRule) {
+ ruleWalker.CurrentNode()->SetIsAnimationRule();
+ }
+ }
+ }
+ }
+
+ NS_ASSERTION(rulesIndex == 0,
+ "rules are in incorrect cascading order, "
+ "which means we replaced them incorrectly");
+
+ return ruleWalker.CurrentNode();
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveStyleWithReplacement(Element* aElement,
+ Element* aPseudoElement,
+ nsStyleContext* aNewParentContext,
+ nsStyleContext* aOldStyleContext,
+ nsRestyleHint aReplacements,
+ uint32_t aFlags)
+{
+ nsRuleNode* ruleNode =
+ RuleNodeWithReplacement(aElement, aPseudoElement,
+ aOldStyleContext->RuleNode(),
+ aOldStyleContext->GetPseudoType(), aReplacements);
+
+ nsRuleNode* visitedRuleNode = nullptr;
+ nsStyleContext* oldStyleIfVisited = aOldStyleContext->GetStyleIfVisited();
+ if (oldStyleIfVisited) {
+ if (oldStyleIfVisited->RuleNode() == aOldStyleContext->RuleNode()) {
+ visitedRuleNode = ruleNode;
+ } else {
+ visitedRuleNode =
+ RuleNodeWithReplacement(aElement, aPseudoElement,
+ oldStyleIfVisited->RuleNode(),
+ oldStyleIfVisited->GetPseudoType(),
+ aReplacements);
+ }
+ }
+
+ uint32_t flags = eNoFlags;
+ if (aOldStyleContext->IsLinkContext()) {
+ flags |= eIsLink;
+
+ // GetContext handles propagating RelevantLinkVisited state from the
+ // parent in non-link cases; all we need to pass in is if this link
+ // is visited.
+ if (aOldStyleContext->RelevantLinkVisited()) {
+ flags |= eIsVisitedLink;
+ }
+ }
+
+ CSSPseudoElementType pseudoType = aOldStyleContext->GetPseudoType();
+ Element* elementForAnimation = nullptr;
+ if (!(aFlags & eSkipStartingAnimations) &&
+ (pseudoType == CSSPseudoElementType::NotPseudo ||
+ pseudoType == CSSPseudoElementType::before ||
+ pseudoType == CSSPseudoElementType::after)) {
+ // We want to compute a correct elementForAnimation to pass in
+ // because at this point the parameter is more than just the element
+ // for animation; it's also used for the SetBodyTextColor call when
+ // it's the body element.
+ // However, we only want to set the flag to call CheckAnimationRule
+ // if we're dealing with a replacement (such as style attribute
+ // replacement) that could lead to the animation property changing,
+ // and we explicitly do NOT want to call CheckAnimationRule when
+ // we're trying to do an animation-only update.
+ if (aReplacements & ~(eRestyle_CSSTransitions | eRestyle_CSSAnimations)) {
+ flags |= eDoAnimation;
+ }
+ elementForAnimation = aElement;
+#ifdef DEBUG
+ {
+ nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(elementForAnimation);
+ NS_ASSERTION(pseudoType == CSSPseudoElementType::NotPseudo ||
+ !styleFrame ||
+ styleFrame->StyleContext()->GetPseudoType() ==
+ CSSPseudoElementType::NotPseudo,
+ "aElement should be the element and not the pseudo-element");
+ }
+#endif
+ }
+
+ if (aElement && aElement->IsRootOfAnonymousSubtree()) {
+ // For anonymous subtree roots, don't tweak "display" value based on whether
+ // or not the parent is styled as a flex/grid container. (If the parent
+ // has anonymous-subtree kids, then we know it's not actually going to get
+ // a flex/grid container frame, anyway.)
+ flags |= eSkipParentDisplayBasedStyleFixup;
+ }
+
+ return GetContext(aNewParentContext, ruleNode, visitedRuleNode,
+ aOldStyleContext->GetPseudo(), pseudoType,
+ elementForAnimation, flags);
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveStyleWithoutAnimation(dom::Element* aTarget,
+ nsStyleContext* aStyleContext,
+ nsRestyleHint aWhichToRemove)
+{
+#ifdef DEBUG
+ CSSPseudoElementType pseudoType = aStyleContext->GetPseudoType();
+#endif
+ MOZ_ASSERT(pseudoType == CSSPseudoElementType::NotPseudo ||
+ pseudoType == CSSPseudoElementType::before ||
+ pseudoType == CSSPseudoElementType::after,
+ "unexpected type for animations");
+ MOZ_ASSERT(PresContext()->RestyleManager()->IsGecko(),
+ "stylo: the style set and restyle manager must have the same "
+ "StyleBackendType");
+ RestyleManager* restyleManager = PresContext()->RestyleManager()->AsGecko();
+
+ bool oldSkipAnimationRules = restyleManager->SkipAnimationRules();
+ restyleManager->SetSkipAnimationRules(true);
+
+ RefPtr<nsStyleContext> result =
+ ResolveStyleWithReplacement(aTarget, nullptr, aStyleContext->GetParent(),
+ aStyleContext, aWhichToRemove,
+ eSkipStartingAnimations);
+
+ restyleManager->SetSkipAnimationRules(oldSkipAnimationRules);
+
+ return result.forget();
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveStyleForText(nsIContent* aTextNode,
+ nsStyleContext* aParentContext)
+{
+ MOZ_ASSERT(aTextNode && aTextNode->IsNodeOfType(nsINode::eTEXT));
+ return GetContext(aParentContext, mRuleTree, nullptr,
+ nsCSSAnonBoxes::mozText,
+ CSSPseudoElementType::AnonBox, nullptr, eNoFlags);
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveStyleForOtherNonElement(nsStyleContext* aParentContext)
+{
+ return GetContext(aParentContext, mRuleTree, nullptr,
+ nsCSSAnonBoxes::mozOtherNonElement,
+ CSSPseudoElementType::AnonBox, nullptr, eNoFlags);
+}
+
+void
+nsStyleSet::WalkRestrictionRule(CSSPseudoElementType aPseudoType,
+ nsRuleWalker* aRuleWalker)
+{
+ // This needs to match GetPseudoRestriction in nsRuleNode.cpp.
+ aRuleWalker->SetLevel(SheetType::Agent, false, false);
+ if (aPseudoType == CSSPseudoElementType::firstLetter)
+ aRuleWalker->Forward(mFirstLetterRule);
+ else if (aPseudoType == CSSPseudoElementType::firstLine)
+ aRuleWalker->Forward(mFirstLineRule);
+ else if (aPseudoType == CSSPseudoElementType::placeholder)
+ aRuleWalker->Forward(mPlaceholderRule);
+}
+
+void
+nsStyleSet::WalkDisableTextZoomRule(Element* aElement, nsRuleWalker* aRuleWalker)
+{
+ aRuleWalker->SetLevel(SheetType::Agent, false, false);
+ if (aElement->IsSVGElement(nsGkAtoms::text))
+ aRuleWalker->Forward(mDisableTextZoomStyleRule);
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolvePseudoElementStyle(Element* aParentElement,
+ CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ Element* aPseudoElement)
+{
+ NS_ENSURE_FALSE(mInShutdown, nullptr);
+
+ NS_ASSERTION(aType < CSSPseudoElementType::Count,
+ "must have pseudo element type");
+ NS_ASSERTION(aParentElement, "Must have parent element");
+
+ nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled);
+ TreeMatchContext treeContext(true, nsRuleWalker::eRelevantLinkUnvisited,
+ aParentElement->OwnerDoc());
+ InitStyleScopes(treeContext, aParentElement);
+ PseudoElementRuleProcessorData data(PresContext(), aParentElement,
+ &ruleWalker, aType, treeContext,
+ aPseudoElement);
+ WalkRestrictionRule(aType, &ruleWalker);
+ FileRules(EnumRulesMatching<PseudoElementRuleProcessorData>, &data,
+ aParentElement, &ruleWalker);
+
+ nsRuleNode *ruleNode = ruleWalker.CurrentNode();
+ nsRuleNode *visitedRuleNode = nullptr;
+
+ if (treeContext.HaveRelevantLink()) {
+ treeContext.ResetForVisitedMatching();
+ ruleWalker.Reset();
+ WalkRestrictionRule(aType, &ruleWalker);
+ FileRules(EnumRulesMatching<PseudoElementRuleProcessorData>, &data,
+ aParentElement, &ruleWalker);
+ visitedRuleNode = ruleWalker.CurrentNode();
+ }
+
+ // For pseudos, |data.IsLink()| being true means that
+ // our parent node is a link.
+ uint32_t flags = eNoFlags;
+ if (aType == CSSPseudoElementType::before ||
+ aType == CSSPseudoElementType::after) {
+ flags |= eDoAnimation;
+ } else {
+ // Flex and grid containers don't expect to have any pseudo-element children
+ // aside from ::before and ::after. So if we have such a child, we're not
+ // actually in a flex/grid container, and we should skip flex/grid item
+ // style fixup.
+ flags |= eSkipParentDisplayBasedStyleFixup;
+ }
+
+ return GetContext(aParentContext, ruleNode, visitedRuleNode,
+ nsCSSPseudoElements::GetPseudoAtom(aType), aType,
+ aParentElement, flags);
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ProbePseudoElementStyle(Element* aParentElement,
+ CSSPseudoElementType aType,
+ nsStyleContext* aParentContext)
+{
+ TreeMatchContext treeContext(true, nsRuleWalker::eRelevantLinkUnvisited,
+ aParentElement->OwnerDoc());
+ InitStyleScopes(treeContext, aParentElement);
+ return ProbePseudoElementStyle(aParentElement, aType, aParentContext,
+ treeContext);
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ProbePseudoElementStyle(Element* aParentElement,
+ CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext,
+ Element* aPseudoElement)
+{
+ NS_ENSURE_FALSE(mInShutdown, nullptr);
+
+ NS_ASSERTION(aType < CSSPseudoElementType::Count,
+ "must have pseudo element type");
+ NS_ASSERTION(aParentElement, "aParentElement must not be null");
+
+ nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(aType);
+ nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled);
+ aTreeMatchContext.ResetForUnvisitedMatching();
+ PseudoElementRuleProcessorData data(PresContext(), aParentElement,
+ &ruleWalker, aType, aTreeMatchContext,
+ aPseudoElement);
+ WalkRestrictionRule(aType, &ruleWalker);
+ // not the root if there was a restriction rule
+ nsRuleNode *adjustedRoot = ruleWalker.CurrentNode();
+ FileRules(EnumRulesMatching<PseudoElementRuleProcessorData>, &data,
+ aParentElement, &ruleWalker);
+
+ nsRuleNode *ruleNode = ruleWalker.CurrentNode();
+ if (ruleNode == adjustedRoot) {
+ return nullptr;
+ }
+
+ nsRuleNode *visitedRuleNode = nullptr;
+
+ if (aTreeMatchContext.HaveRelevantLink()) {
+ aTreeMatchContext.ResetForVisitedMatching();
+ ruleWalker.Reset();
+ WalkRestrictionRule(aType, &ruleWalker);
+ FileRules(EnumRulesMatching<PseudoElementRuleProcessorData>, &data,
+ aParentElement, &ruleWalker);
+ visitedRuleNode = ruleWalker.CurrentNode();
+ }
+
+ // For pseudos, |data.IsLink()| being true means that
+ // our parent node is a link.
+ uint32_t flags = eNoFlags;
+ if (aType == CSSPseudoElementType::before ||
+ aType == CSSPseudoElementType::after) {
+ flags |= eDoAnimation;
+ } else {
+ // Flex and grid containers don't expect to have any pseudo-element children
+ // aside from ::before and ::after. So if we have such a child, we're not
+ // actually in a flex/grid container, and we should skip flex/grid item
+ // style fixup.
+ flags |= eSkipParentDisplayBasedStyleFixup;
+ }
+
+ RefPtr<nsStyleContext> result =
+ GetContext(aParentContext, ruleNode, visitedRuleNode,
+ pseudoTag, aType,
+ aParentElement, flags);
+
+ // For :before and :after pseudo-elements, having display: none or no
+ // 'content' property is equivalent to not having the pseudo-element
+ // at all.
+ if (result &&
+ (pseudoTag == nsCSSPseudoElements::before ||
+ pseudoTag == nsCSSPseudoElements::after)) {
+ const nsStyleDisplay *display = result->StyleDisplay();
+ const nsStyleContent *content = result->StyleContent();
+ // XXXldb What is contentCount for |content: ""|?
+ if (display->mDisplay == StyleDisplay::None ||
+ content->ContentCount() == 0) {
+ result = nullptr;
+ }
+ }
+
+ return result.forget();
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveAnonymousBoxStyle(nsIAtom* aPseudoTag,
+ nsStyleContext* aParentContext,
+ uint32_t aFlags)
+{
+ NS_ENSURE_FALSE(mInShutdown, nullptr);
+
+#ifdef DEBUG
+ bool isAnonBox = nsCSSAnonBoxes::IsAnonBox(aPseudoTag)
+#ifdef MOZ_XUL
+ && !nsCSSAnonBoxes::IsTreePseudoElement(aPseudoTag)
+#endif
+ ;
+ NS_PRECONDITION(isAnonBox, "Unexpected pseudo");
+#endif
+
+ nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled);
+ AnonBoxRuleProcessorData data(PresContext(), aPseudoTag, &ruleWalker);
+ FileRules(EnumRulesMatching<AnonBoxRuleProcessorData>, &data, nullptr,
+ &ruleWalker);
+
+ if (aPseudoTag == nsCSSAnonBoxes::pageContent) {
+ // Add any @page rules that are specified.
+ nsTArray<nsCSSPageRule*> rules;
+ nsTArray<css::ImportantStyleData*> importantRules;
+ AppendPageRules(rules);
+ for (uint32_t i = 0, i_end = rules.Length(); i != i_end; ++i) {
+ css::Declaration* declaration = rules[i]->Declaration();
+ declaration->SetImmutable();
+ ruleWalker.Forward(declaration);
+ css::ImportantStyleData* importantRule =
+ declaration->GetImportantStyleData();
+ if (importantRule) {
+ importantRules.AppendElement(importantRule);
+ }
+ }
+ for (uint32_t i = 0, i_end = importantRules.Length(); i != i_end; ++i) {
+ ruleWalker.Forward(importantRules[i]);
+ }
+ }
+
+ return GetContext(aParentContext, ruleWalker.CurrentNode(), nullptr,
+ aPseudoTag, CSSPseudoElementType::AnonBox,
+ nullptr, aFlags);
+}
+
+#ifdef MOZ_XUL
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveXULTreePseudoStyle(Element* aParentElement,
+ nsIAtom* aPseudoTag,
+ nsStyleContext* aParentContext,
+ nsICSSPseudoComparator* aComparator)
+{
+ NS_ENSURE_FALSE(mInShutdown, nullptr);
+
+ NS_ASSERTION(aPseudoTag, "must have pseudo tag");
+ NS_ASSERTION(nsCSSAnonBoxes::IsTreePseudoElement(aPseudoTag),
+ "Unexpected pseudo");
+
+ nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled);
+ TreeMatchContext treeContext(true, nsRuleWalker::eRelevantLinkUnvisited,
+ aParentElement->OwnerDoc());
+ InitStyleScopes(treeContext, aParentElement);
+ XULTreeRuleProcessorData data(PresContext(), aParentElement, &ruleWalker,
+ aPseudoTag, aComparator, treeContext);
+ FileRules(EnumRulesMatching<XULTreeRuleProcessorData>, &data, aParentElement,
+ &ruleWalker);
+
+ nsRuleNode *ruleNode = ruleWalker.CurrentNode();
+ nsRuleNode *visitedRuleNode = nullptr;
+
+ if (treeContext.HaveRelevantLink()) {
+ treeContext.ResetForVisitedMatching();
+ ruleWalker.Reset();
+ FileRules(EnumRulesMatching<XULTreeRuleProcessorData>, &data,
+ aParentElement, &ruleWalker);
+ visitedRuleNode = ruleWalker.CurrentNode();
+ }
+
+ return GetContext(aParentContext, ruleNode, visitedRuleNode,
+ // For pseudos, |data.IsLink()| being true means that
+ // our parent node is a link.
+ aPseudoTag, CSSPseudoElementType::XULTree,
+ nullptr, eNoFlags);
+}
+#endif
+
+bool
+nsStyleSet::AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray)
+{
+ NS_ENSURE_FALSE(mInShutdown, false);
+ NS_ASSERTION(mBatching == 0, "rule processors out of date");
+
+ nsPresContext* presContext = PresContext();
+ for (uint32_t i = 0; i < ArrayLength(gCSSSheetTypes); ++i) {
+ if (gCSSSheetTypes[i] == SheetType::ScopedDoc)
+ continue;
+ nsCSSRuleProcessor *ruleProc = static_cast<nsCSSRuleProcessor*>
+ (mRuleProcessors[gCSSSheetTypes[i]].get());
+ if (ruleProc && !ruleProc->AppendFontFaceRules(presContext, aArray))
+ return false;
+ }
+ return true;
+}
+
+nsCSSKeyframesRule*
+nsStyleSet::KeyframesRuleForName(const nsString& aName)
+{
+ NS_ENSURE_FALSE(mInShutdown, nullptr);
+ NS_ASSERTION(mBatching == 0, "rule processors out of date");
+
+ nsPresContext* presContext = PresContext();
+ for (uint32_t i = ArrayLength(gCSSSheetTypes); i-- != 0; ) {
+ if (gCSSSheetTypes[i] == SheetType::ScopedDoc)
+ continue;
+ nsCSSRuleProcessor *ruleProc = static_cast<nsCSSRuleProcessor*>
+ (mRuleProcessors[gCSSSheetTypes[i]].get());
+ if (!ruleProc)
+ continue;
+ nsCSSKeyframesRule* result =
+ ruleProc->KeyframesRuleForName(presContext, aName);
+ if (result)
+ return result;
+ }
+ return nullptr;
+}
+
+nsCSSCounterStyleRule*
+nsStyleSet::CounterStyleRuleForName(const nsAString& aName)
+{
+ NS_ENSURE_FALSE(mInShutdown, nullptr);
+ NS_ASSERTION(mBatching == 0, "rule processors out of date");
+
+ nsPresContext* presContext = PresContext();
+ for (uint32_t i = ArrayLength(gCSSSheetTypes); i-- != 0; ) {
+ if (gCSSSheetTypes[i] == SheetType::ScopedDoc)
+ continue;
+ nsCSSRuleProcessor *ruleProc = static_cast<nsCSSRuleProcessor*>
+ (mRuleProcessors[gCSSSheetTypes[i]].get());
+ if (!ruleProc)
+ continue;
+ nsCSSCounterStyleRule *result =
+ ruleProc->CounterStyleRuleForName(presContext, aName);
+ if (result)
+ return result;
+ }
+ return nullptr;
+}
+
+bool
+nsStyleSet::AppendFontFeatureValuesRules(
+ nsTArray<nsCSSFontFeatureValuesRule*>& aArray)
+{
+ NS_ENSURE_FALSE(mInShutdown, false);
+ NS_ASSERTION(mBatching == 0, "rule processors out of date");
+
+ nsPresContext* presContext = PresContext();
+ for (uint32_t i = 0; i < ArrayLength(gCSSSheetTypes); ++i) {
+ nsCSSRuleProcessor *ruleProc = static_cast<nsCSSRuleProcessor*>
+ (mRuleProcessors[gCSSSheetTypes[i]].get());
+ if (ruleProc &&
+ !ruleProc->AppendFontFeatureValuesRules(presContext, aArray))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+already_AddRefed<gfxFontFeatureValueSet>
+nsStyleSet::GetFontFeatureValuesLookup()
+{
+ if (mInitFontFeatureValuesLookup) {
+ mInitFontFeatureValuesLookup = false;
+
+ nsTArray<nsCSSFontFeatureValuesRule*> rules;
+ AppendFontFeatureValuesRules(rules);
+
+ mFontFeatureValuesLookup = new gfxFontFeatureValueSet();
+
+ uint32_t i, numRules = rules.Length();
+ for (i = 0; i < numRules; i++) {
+ nsCSSFontFeatureValuesRule *rule = rules[i];
+
+ const nsTArray<FontFamilyName>& familyList = rule->GetFamilyList().GetFontlist();
+ const nsTArray<gfxFontFeatureValueSet::FeatureValues>&
+ featureValues = rule->GetFeatureValues();
+
+ // for each family
+ size_t f, numFam;
+
+ numFam = familyList.Length();
+ for (f = 0; f < numFam; f++) {
+ mFontFeatureValuesLookup->AddFontFeatureValues(familyList[f].mName,
+ featureValues);
+ }
+ }
+ }
+
+ RefPtr<gfxFontFeatureValueSet> lookup = mFontFeatureValuesLookup;
+ return lookup.forget();
+}
+
+bool
+nsStyleSet::AppendPageRules(nsTArray<nsCSSPageRule*>& aArray)
+{
+ NS_ENSURE_FALSE(mInShutdown, false);
+ NS_ASSERTION(mBatching == 0, "rule processors out of date");
+
+ nsPresContext* presContext = PresContext();
+ for (uint32_t i = 0; i < ArrayLength(gCSSSheetTypes); ++i) {
+ if (gCSSSheetTypes[i] == SheetType::ScopedDoc)
+ continue;
+ nsCSSRuleProcessor* ruleProc = static_cast<nsCSSRuleProcessor*>
+ (mRuleProcessors[gCSSSheetTypes[i]].get());
+ if (ruleProc && !ruleProc->AppendPageRules(presContext, aArray))
+ return false;
+ }
+ return true;
+}
+
+void
+nsStyleSet::BeginShutdown()
+{
+ mInShutdown = 1;
+}
+
+void
+nsStyleSet::Shutdown()
+{
+ mRuleTree = nullptr;
+ GCRuleTrees();
+ MOZ_ASSERT(mUnusedRuleNodeList.isEmpty());
+ MOZ_ASSERT(mUnusedRuleNodeCount == 0);
+}
+
+
+void
+nsStyleSet::GCRuleTrees()
+{
+ MOZ_ASSERT(!mInReconstruct);
+ MOZ_ASSERT(!mInGC);
+ mInGC = true;
+
+ while (!mUnusedRuleNodeList.isEmpty()) {
+ nsRuleNode* node = mUnusedRuleNodeList.popFirst();
+#ifdef DEBUG
+ if (node == mOldRootNode) {
+ // Flag that we've GCed the old root, if any.
+ mOldRootNode = nullptr;
+ }
+#endif
+ node->Destroy();
+ }
+
+#ifdef DEBUG
+ NS_ASSERTION(!mOldRootNode, "Should have GCed old root node");
+ mOldRootNode = nullptr;
+#endif
+ mUnusedRuleNodeCount = 0;
+ mInGC = false;
+}
+
+already_AddRefed<nsStyleContext>
+nsStyleSet::ReparentStyleContext(nsStyleContext* aStyleContext,
+ nsStyleContext* aNewParentContext,
+ Element* aElement)
+{
+ MOZ_ASSERT(aStyleContext, "aStyleContext must not be null");
+
+ // This short-circuit is OK because we don't call TryInitatingTransition
+ // during style reresolution if the style context pointer hasn't changed.
+ if (aStyleContext->GetParent() == aNewParentContext) {
+ RefPtr<nsStyleContext> ret = aStyleContext;
+ return ret.forget();
+ }
+
+ nsIAtom* pseudoTag = aStyleContext->GetPseudo();
+ CSSPseudoElementType pseudoType = aStyleContext->GetPseudoType();
+ nsRuleNode* ruleNode = aStyleContext->RuleNode();
+
+ MOZ_ASSERT(PresContext()->RestyleManager()->IsGecko(),
+ "stylo: the style set and restyle manager must have the same "
+ "StyleBackendType");
+ NS_ASSERTION(!PresContext()->RestyleManager()->AsGecko()->SkipAnimationRules(),
+ "we no longer handle SkipAnimationRules()");
+
+ nsRuleNode* visitedRuleNode = nullptr;
+ nsStyleContext* visitedContext = aStyleContext->GetStyleIfVisited();
+ // Reparenting a style context just changes where we inherit from,
+ // not what rules we match or what our DOM looks like. In
+ // particular, it doesn't change whether this is a style context for
+ // a link.
+ if (visitedContext) {
+ visitedRuleNode = visitedContext->RuleNode();
+ }
+
+ uint32_t flags = eNoFlags;
+ if (aStyleContext->IsLinkContext()) {
+ flags |= eIsLink;
+
+ // GetContext handles propagating RelevantLinkVisited state from the
+ // parent in non-link cases; all we need to pass in is if this link
+ // is visited.
+ if (aStyleContext->RelevantLinkVisited()) {
+ flags |= eIsVisitedLink;
+ }
+ }
+
+ if (pseudoType == CSSPseudoElementType::NotPseudo ||
+ pseudoType == CSSPseudoElementType::before ||
+ pseudoType == CSSPseudoElementType::after) {
+ flags |= eDoAnimation;
+ }
+
+ if (aElement && aElement->IsRootOfAnonymousSubtree()) {
+ // For anonymous subtree roots, don't tweak "display" value based on whether
+ // or not the parent is styled as a flex/grid container. (If the parent
+ // has anonymous-subtree kids, then we know it's not actually going to get
+ // a flex/grid container frame, anyway.)
+ flags |= eSkipParentDisplayBasedStyleFixup;
+ }
+
+ return GetContext(aNewParentContext, ruleNode, visitedRuleNode,
+ pseudoTag, pseudoType,
+ aElement, flags);
+}
+
+struct MOZ_STACK_CLASS StatefulData : public StateRuleProcessorData {
+ StatefulData(nsPresContext* aPresContext, Element* aElement,
+ EventStates aStateMask, TreeMatchContext& aTreeMatchContext)
+ : StateRuleProcessorData(aPresContext, aElement, aStateMask,
+ aTreeMatchContext),
+ mHint(nsRestyleHint(0))
+ {}
+ nsRestyleHint mHint;
+};
+
+struct MOZ_STACK_CLASS StatefulPseudoElementData : public PseudoElementStateRuleProcessorData {
+ StatefulPseudoElementData(nsPresContext* aPresContext, Element* aElement,
+ EventStates aStateMask, CSSPseudoElementType aPseudoType,
+ TreeMatchContext& aTreeMatchContext, Element* aPseudoElement)
+ : PseudoElementStateRuleProcessorData(aPresContext, aElement, aStateMask,
+ aPseudoType, aTreeMatchContext,
+ aPseudoElement),
+ mHint(nsRestyleHint(0))
+ {}
+ nsRestyleHint mHint;
+};
+
+static bool SheetHasDocumentStateStyle(nsIStyleRuleProcessor* aProcessor,
+ void *aData)
+{
+ StatefulData* data = (StatefulData*)aData;
+ if (aProcessor->HasDocumentStateDependentStyle(data)) {
+ data->mHint = eRestyle_Self;
+ return false; // don't continue
+ }
+ return true; // continue
+}
+
+// Test if style is dependent on a document state.
+bool
+nsStyleSet::HasDocumentStateDependentStyle(nsIContent* aContent,
+ EventStates aStateMask)
+{
+ if (!aContent || !aContent->IsElement())
+ return false;
+
+ TreeMatchContext treeContext(false, nsRuleWalker::eLinksVisitedOrUnvisited,
+ aContent->OwnerDoc());
+ InitStyleScopes(treeContext, aContent->AsElement());
+ StatefulData data(PresContext(), aContent->AsElement(), aStateMask,
+ treeContext);
+ WalkRuleProcessors(SheetHasDocumentStateStyle, &data, true);
+ return data.mHint != 0;
+}
+
+static bool SheetHasStatefulStyle(nsIStyleRuleProcessor* aProcessor,
+ void *aData)
+{
+ StatefulData* data = (StatefulData*)aData;
+ nsRestyleHint hint = aProcessor->HasStateDependentStyle(data);
+ data->mHint = nsRestyleHint(data->mHint | hint);
+ return true; // continue
+}
+
+static bool SheetHasStatefulPseudoElementStyle(nsIStyleRuleProcessor* aProcessor,
+ void *aData)
+{
+ StatefulPseudoElementData* data = (StatefulPseudoElementData*)aData;
+ nsRestyleHint hint = aProcessor->HasStateDependentStyle(data);
+ data->mHint = nsRestyleHint(data->mHint | hint);
+ return true; // continue
+}
+
+// Test if style is dependent on content state
+nsRestyleHint
+nsStyleSet::HasStateDependentStyle(Element* aElement,
+ EventStates aStateMask)
+{
+ TreeMatchContext treeContext(false, nsRuleWalker::eLinksVisitedOrUnvisited,
+ aElement->OwnerDoc());
+ InitStyleScopes(treeContext, aElement);
+ StatefulData data(PresContext(), aElement, aStateMask, treeContext);
+ WalkRuleProcessors(SheetHasStatefulStyle, &data, false);
+ return data.mHint;
+}
+
+nsRestyleHint
+nsStyleSet::HasStateDependentStyle(Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ Element* aPseudoElement,
+ EventStates aStateMask)
+{
+ TreeMatchContext treeContext(false, nsRuleWalker::eLinksVisitedOrUnvisited,
+ aElement->OwnerDoc());
+ InitStyleScopes(treeContext, aElement);
+ StatefulPseudoElementData data(PresContext(), aElement, aStateMask,
+ aPseudoType, treeContext, aPseudoElement);
+ WalkRuleProcessors(SheetHasStatefulPseudoElementStyle, &data, false);
+ return data.mHint;
+}
+
+struct MOZ_STACK_CLASS AttributeData : public AttributeRuleProcessorData {
+ AttributeData(nsPresContext* aPresContext, Element* aElement,
+ int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType,
+ bool aAttrHasChanged, const nsAttrValue* aOtherValue,
+ TreeMatchContext& aTreeMatchContext)
+ : AttributeRuleProcessorData(aPresContext, aElement, aNameSpaceID,
+ aAttribute, aModType, aAttrHasChanged,
+ aOtherValue, aTreeMatchContext),
+ mHint(nsRestyleHint(0))
+ {}
+ nsRestyleHint mHint;
+ RestyleHintData mHintData;
+};
+
+static bool
+SheetHasAttributeStyle(nsIStyleRuleProcessor* aProcessor, void *aData)
+{
+ AttributeData* data = (AttributeData*)aData;
+ nsRestyleHint hint =
+ aProcessor->HasAttributeDependentStyle(data, data->mHintData);
+ data->mHint = nsRestyleHint(data->mHint | hint);
+ return true; // continue
+}
+
+// Test if style is dependent on content state
+nsRestyleHint
+nsStyleSet::HasAttributeDependentStyle(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ bool aAttrHasChanged,
+ const nsAttrValue* aOtherValue,
+ mozilla::RestyleHintData&
+ aRestyleHintDataResult)
+{
+ TreeMatchContext treeContext(false, nsRuleWalker::eLinksVisitedOrUnvisited,
+ aElement->OwnerDoc());
+ InitStyleScopes(treeContext, aElement);
+ AttributeData data(PresContext(), aElement, aNameSpaceID, aAttribute,
+ aModType, aAttrHasChanged, aOtherValue, treeContext);
+ WalkRuleProcessors(SheetHasAttributeStyle, &data, false);
+ if (!(data.mHint & eRestyle_Subtree)) {
+ // No point keeping the list of selectors around if we are going to
+ // restyle the whole subtree unconditionally.
+ aRestyleHintDataResult = Move(data.mHintData);
+ }
+ return data.mHint;
+}
+
+bool
+nsStyleSet::MediumFeaturesChanged()
+{
+ NS_ASSERTION(mBatching == 0, "rule processors out of date");
+
+ // We can't use WalkRuleProcessors without a content node.
+ nsPresContext* presContext = PresContext();
+ bool stylesChanged = false;
+ for (nsIStyleRuleProcessor* processor : mRuleProcessors) {
+ if (!processor) {
+ continue;
+ }
+ bool thisChanged = processor->MediumFeaturesChanged(presContext);
+ stylesChanged = stylesChanged || thisChanged;
+ }
+ for (nsIStyleRuleProcessor* processor : mScopedDocSheetRuleProcessors) {
+ bool thisChanged = processor->MediumFeaturesChanged(presContext);
+ stylesChanged = stylesChanged || thisChanged;
+ }
+
+ if (mBindingManager) {
+ bool thisChanged = false;
+ mBindingManager->MediumFeaturesChanged(presContext, &thisChanged);
+ stylesChanged = stylesChanged || thisChanged;
+ }
+
+ return stylesChanged;
+}
+
+bool
+nsStyleSet::EnsureUniqueInnerOnCSSSheets()
+{
+ AutoTArray<CSSStyleSheet*, 32> queue;
+ for (SheetType type : gCSSSheetTypes) {
+ for (CSSStyleSheet* sheet : mSheets[type]) {
+ queue.AppendElement(sheet);
+ }
+ }
+
+ if (mBindingManager) {
+ AutoTArray<StyleSheet*, 32> sheets;
+ // XXXheycam stylo: AppendAllSheets will need to be able to return either
+ // CSSStyleSheets or ServoStyleSheets, on request (and then here requesting
+ // CSSStyleSheets).
+ mBindingManager->AppendAllSheets(sheets);
+ for (StyleSheet* sheet : sheets) {
+ MOZ_ASSERT(sheet->IsGecko(), "stylo: AppendAllSheets shouldn't give us "
+ "ServoStyleSheets yet");
+ queue.AppendElement(sheet->AsGecko());
+ }
+ }
+
+ while (!queue.IsEmpty()) {
+ uint32_t idx = queue.Length() - 1;
+ CSSStyleSheet* sheet = queue[idx];
+ queue.RemoveElementAt(idx);
+
+ sheet->EnsureUniqueInner();
+
+ // Enqueue all the sheet's children.
+ sheet->AppendAllChildSheets(queue);
+ }
+
+ bool res = mNeedsRestyleAfterEnsureUniqueInner;
+ mNeedsRestyleAfterEnsureUniqueInner = false;
+ return res;
+}
+
+nsIStyleRule*
+nsStyleSet::InitialStyleRule()
+{
+ if (!mInitialStyleRule) {
+ mInitialStyleRule = new nsInitialStyleRule;
+ }
+ return mInitialStyleRule;
+}
+
+bool
+nsStyleSet::HasRuleProcessorUsedByMultipleStyleSets(SheetType aSheetType)
+{
+ MOZ_ASSERT(size_t(aSheetType) < ArrayLength(mRuleProcessors));
+ if (!IsCSSSheetType(aSheetType) || !mRuleProcessors[aSheetType]) {
+ return false;
+ }
+ nsCSSRuleProcessor* rp =
+ static_cast<nsCSSRuleProcessor*>(mRuleProcessors[aSheetType].get());
+ return rp->IsUsedByMultipleStyleSets();
+}
+
+void
+nsStyleSet::ClearSelectors()
+{
+ // We might be called before we've done our first rule tree construction.
+ if (!mRuleTree) {
+ return;
+ }
+ MOZ_ASSERT(PresContext()->RestyleManager()->IsGecko(),
+ "stylo: the style set and restyle manager must have the same "
+ "StyleBackendType");
+ PresContext()->RestyleManager()->AsGecko()->ClearSelectors();
+}
diff --git a/layout/style/nsStyleSet.h b/layout/style/nsStyleSet.h
new file mode 100644
index 000000000..879d0add3
--- /dev/null
+++ b/layout/style/nsStyleSet.h
@@ -0,0 +1,600 @@
+/* -*- 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/. */
+
+/*
+ * the container for the style sheets that apply to a presentation, and
+ * the internal API that the style system exposes for creating (and
+ * potentially re-creating) style contexts
+ */
+
+#ifndef nsStyleSet_h_
+#define nsStyleSet_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/SheetType.h"
+
+#include "nsIStyleRuleProcessor.h"
+#include "nsBindingManager.h"
+#include "nsRuleNode.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsIStyleRule.h"
+
+class gfxFontFeatureValueSet;
+class nsCSSKeyframesRule;
+class nsCSSFontFeatureValuesRule;
+class nsCSSPageRule;
+class nsCSSCounterStyleRule;
+class nsICSSPseudoComparator;
+class nsRuleWalker;
+struct ElementDependentRuleProcessorData;
+struct nsFontFaceRuleContainer;
+struct TreeMatchContext;
+
+namespace mozilla {
+class CSSStyleSheet;
+class EventStates;
+enum class CSSPseudoElementType : uint8_t;
+} // namespace mozilla
+
+class nsEmptyStyleRule final : public nsIStyleRule
+{
+private:
+ ~nsEmptyStyleRule() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+};
+
+class nsInitialStyleRule final : public nsIStyleRule
+{
+private:
+ ~nsInitialStyleRule() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+};
+
+class nsDisableTextZoomStyleRule final : public nsIStyleRule
+{
+private:
+ ~nsDisableTextZoomStyleRule() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+};
+
+// The style set object is created by the document viewer and ownership is
+// then handed off to the PresShell. Only the PresShell should delete a
+// style set.
+
+class nsStyleSet final
+{
+ public:
+ nsStyleSet();
+ ~nsStyleSet();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ void Init(nsPresContext *aPresContext);
+
+ nsRuleNode* GetRuleTree() { return mRuleTree; }
+
+ // get a style context for a non-pseudo frame.
+ already_AddRefed<nsStyleContext>
+ ResolveStyleFor(mozilla::dom::Element* aElement,
+ nsStyleContext* aParentContext);
+
+ already_AddRefed<nsStyleContext>
+ ResolveStyleFor(mozilla::dom::Element* aElement,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext);
+
+ // Get a style context (with the given parent) for the
+ // sequence of style rules in the |aRules| array.
+ already_AddRefed<nsStyleContext>
+ ResolveStyleForRules(nsStyleContext* aParentContext,
+ const nsTArray< nsCOMPtr<nsIStyleRule> > &aRules);
+
+ // Get a style context that represents aBaseContext, but as though
+ // it additionally matched the rules in the aRules array (in that
+ // order, as more specific than any other rules).
+ //
+ // One of the following must hold:
+ // 1. The resulting style context must be used only on a temporary
+ // basis, and it must never be put into the style context tree
+ // (and, in particular, we must never call
+ // ResolveStyleWithReplacement with it as the old context, which
+ // might happen if it is put in the style context tree), or
+ // 2. The additional rules must be appropriate for the transitions
+ // level of the cascade, which is the highest level of the cascade.
+ // (This is the case for one current caller, the cover rule used
+ // for CSS transitions.)
+ already_AddRefed<nsStyleContext>
+ ResolveStyleByAddingRules(nsStyleContext* aBaseContext,
+ const nsCOMArray<nsIStyleRule> &aRules);
+
+ // Resolve style by making replacements in the list of style rules as
+ // described by aReplacements, but otherwise maintaining the status
+ // quo.
+ // aPseudoElement must follow the same rules as for
+ // ResolvePseudoElementStyle, and be null for non-pseudo-element cases
+ enum { // flags for aFlags
+ // Skip starting CSS animations that result from the style.
+ eSkipStartingAnimations = (1<<0),
+ };
+ already_AddRefed<nsStyleContext>
+ ResolveStyleWithReplacement(mozilla::dom::Element* aElement,
+ mozilla::dom::Element* aPseudoElement,
+ nsStyleContext* aNewParentContext,
+ nsStyleContext* aOldStyleContext,
+ nsRestyleHint aReplacements,
+ uint32_t aFlags = 0);
+
+ // Resolve style by returning a style context with the specified
+ // animation data removed. It is allowable to remove all animation
+ // data with eRestyle_AllHintsWithAnimations, or by using any other
+ // hints that are allowed by ResolveStyleWithReplacement.
+ already_AddRefed<nsStyleContext>
+ ResolveStyleWithoutAnimation(mozilla::dom::Element* aElement,
+ nsStyleContext* aStyleContext,
+ nsRestyleHint aWhichToRemove);
+
+ // Get a style context for a text node (which no rules will match).
+ //
+ // The returned style context 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<nsStyleContext>
+ ResolveStyleForText(nsIContent* aTextNode, nsStyleContext* aParentContext);
+
+ // Get a style context for a non-element (which no rules will match)
+ // other than a text node, such as placeholder frames, and the
+ // nsFirstLetterFrame for everything after the first letter.
+ //
+ // The returned style context will have nsCSSAnonBoxes::mozOtherNonElement as
+ // its pseudo.
+ //
+ // (Perhaps mozOtherNonElement should go away and we shouldn't even
+ // create style contexts for such content nodes. However, not doing
+ // any rule matching for them is a first step.)
+ already_AddRefed<nsStyleContext>
+ ResolveStyleForOtherNonElement(nsStyleContext* aParentContext);
+
+ // Get a style context for a pseudo-element. aParentElement must be
+ // non-null. aPseudoID is the CSSPseudoElementType for the
+ // pseudo-element. aPseudoElement must be non-null if the pseudo-element
+ // type is one that allows user action pseudo-classes after it or allows
+ // style attributes; otherwise, it is ignored.
+ already_AddRefed<nsStyleContext>
+ ResolvePseudoElementStyle(mozilla::dom::Element* aParentElement,
+ mozilla::CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ mozilla::dom::Element* aPseudoElement);
+
+ // This functions just like ResolvePseudoElementStyle except that it will
+ // return nullptr if there are no explicit style rules for that
+ // pseudo element.
+ already_AddRefed<nsStyleContext>
+ ProbePseudoElementStyle(mozilla::dom::Element* aParentElement,
+ mozilla::CSSPseudoElementType aType,
+ nsStyleContext* aParentContext);
+ already_AddRefed<nsStyleContext>
+ ProbePseudoElementStyle(mozilla::dom::Element* aParentElement,
+ mozilla::CSSPseudoElementType aType,
+ nsStyleContext* aParentContext,
+ TreeMatchContext& aTreeMatchContext,
+ mozilla::dom::Element* aPseudoElement = nullptr);
+
+ /**
+ * Bit-flags that can be passed to ResolveAnonymousBoxStyle and GetContext
+ * in their parameter 'aFlags'.
+ */
+ enum {
+ eNoFlags = 0,
+ eIsLink = 1 << 0,
+ eIsVisitedLink = 1 << 1,
+ eDoAnimation = 1 << 2,
+
+ // Indicates that we should skip the flex/grid item specific chunk of
+ // ApplyStyleFixups(). This is useful if our parent has "display: flex"
+ // or "display: grid" but we can tell we're not going to honor that (e.g. if
+ // it's the outer frame of a button widget, and we're the inline frame for
+ // the button's label).
+ eSkipParentDisplayBasedStyleFixup = 1 << 3
+ };
+
+ // Get a style context for an anonymous box. aPseudoTag is the
+ // pseudo-tag to use and must be non-null. aFlags will be forwarded
+ // to a GetContext call internally.
+ already_AddRefed<nsStyleContext>
+ ResolveAnonymousBoxStyle(nsIAtom* aPseudoTag, nsStyleContext* aParentContext,
+ uint32_t aFlags = eNoFlags);
+
+#ifdef MOZ_XUL
+ // Get a style context for a XUL tree pseudo. aPseudoTag is the
+ // pseudo-tag to use and must be non-null. aParentContent must be
+ // non-null. aComparator must be non-null.
+ already_AddRefed<nsStyleContext>
+ ResolveXULTreePseudoStyle(mozilla::dom::Element* aParentElement,
+ nsIAtom* aPseudoTag,
+ nsStyleContext* aParentContext,
+ nsICSSPseudoComparator* aComparator);
+#endif
+
+ // Append all the currently-active font face rules to aArray. Return
+ // true for success and false for failure.
+ bool AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray);
+
+ // Return the winning (in the cascade) @keyframes rule for the given name.
+ nsCSSKeyframesRule* KeyframesRuleForName(const nsString& aName);
+
+ // Return the winning (in the cascade) @counter-style rule for the given name.
+ nsCSSCounterStyleRule* CounterStyleRuleForName(const nsAString& aName);
+
+ // Fetch object for looking up font feature values
+ already_AddRefed<gfxFontFeatureValueSet> GetFontFeatureValuesLookup();
+
+ // Append all the currently-active font feature values rules to aArray.
+ // Return true for success and false for failure.
+ bool AppendFontFeatureValuesRules(
+ nsTArray<nsCSSFontFeatureValuesRule*>& aArray);
+
+ // Append all the currently-active page rules to aArray. Return
+ // true for success and false for failure.
+ bool AppendPageRules(nsTArray<nsCSSPageRule*>& aArray);
+
+ // Begin ignoring style context destruction, to avoid lots of unnecessary
+ // work on document teardown.
+ void BeginShutdown();
+
+ // Free all of the data associated with this style set.
+ void Shutdown();
+
+ // Get a new style context that lives in a different parent
+ // The new context will be the same as the old if the new parent is the
+ // same as the old parent.
+ // aElement should be non-null if this is a style context for an
+ // element or pseudo-element; in the latter case it should be the
+ // real element the pseudo-element is for.
+ already_AddRefed<nsStyleContext>
+ ReparentStyleContext(nsStyleContext* aStyleContext,
+ nsStyleContext* aNewParentContext,
+ mozilla::dom::Element* aElement);
+
+ // Test if style is dependent on a document state.
+ bool HasDocumentStateDependentStyle(nsIContent* aContent,
+ mozilla::EventStates aStateMask);
+
+ // Test if style is dependent on content state
+ nsRestyleHint HasStateDependentStyle(mozilla::dom::Element* aElement,
+ mozilla::EventStates aStateMask);
+ nsRestyleHint HasStateDependentStyle(mozilla::dom::Element* aElement,
+ mozilla::CSSPseudoElementType aPseudoType,
+ mozilla::dom::Element* aPseudoElement,
+ mozilla::EventStates aStateMask);
+
+ // Test if style is dependent on the presence of an attribute.
+ nsRestyleHint HasAttributeDependentStyle(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ bool aAttrHasChanged,
+ const nsAttrValue* aOtherValue,
+ mozilla::RestyleHintData&
+ aRestyleHintDataResult);
+
+ /*
+ * Do any processing that needs to happen as a result of a change in
+ * the characteristics of the medium, and return whether style rules
+ * may have changed as a result.
+ */
+ bool MediumFeaturesChanged();
+
+ // APIs for registering objects that can supply additional
+ // rules during processing.
+ void SetBindingManager(nsBindingManager* aBindingManager)
+ {
+ mBindingManager = aBindingManager;
+ }
+
+ // APIs to manipulate the style sheet lists. The sheets in each
+ // list are stored with the most significant sheet last.
+ nsresult AppendStyleSheet(mozilla::SheetType aType,
+ mozilla::CSSStyleSheet* aSheet);
+ nsresult PrependStyleSheet(mozilla::SheetType aType,
+ mozilla::CSSStyleSheet* aSheet);
+ nsresult RemoveStyleSheet(mozilla::SheetType aType,
+ mozilla::CSSStyleSheet* aSheet);
+ nsresult ReplaceSheets(mozilla::SheetType aType,
+ const nsTArray<RefPtr<mozilla::CSSStyleSheet>>& aNewSheets);
+ nsresult InsertStyleSheetBefore(mozilla::SheetType aType,
+ mozilla::CSSStyleSheet* aNewSheet,
+ mozilla::CSSStyleSheet* aReferenceSheet);
+
+ // Enable/Disable entire author style level (Doc, ScopedDoc & PresHint levels)
+ bool GetAuthorStyleDisabled() const;
+ nsresult SetAuthorStyleDisabled(bool aStyleDisabled);
+
+ int32_t SheetCount(mozilla::SheetType aType) const {
+ return mSheets[aType].Length();
+ }
+
+ mozilla::CSSStyleSheet* StyleSheetAt(mozilla::SheetType aType,
+ int32_t aIndex) const {
+ return mSheets[aType][aIndex];
+ }
+
+ void AppendAllXBLStyleSheets(nsTArray<mozilla::CSSStyleSheet*>& aArray) const;
+
+ nsresult RemoveDocStyleSheet(mozilla::CSSStyleSheet* aSheet);
+ nsresult AddDocStyleSheet(mozilla::CSSStyleSheet* aSheet,
+ nsIDocument* aDocument);
+
+ void BeginUpdate();
+ nsresult EndUpdate();
+
+ // Methods for reconstructing the tree; BeginReconstruct basically moves the
+ // old rule tree root and style context roots out of the way,
+ // and EndReconstruct destroys the old rule tree when we're done
+ nsresult BeginReconstruct();
+ // Note: EndReconstruct should not be called if BeginReconstruct fails
+ void EndReconstruct();
+
+ bool IsInRuleTreeReconstruct() const {
+ return mInReconstruct;
+ }
+
+ void RootStyleContextAdded() {
+ ++mRootStyleContextCount;
+ }
+ void RootStyleContextRemoved() {
+ MOZ_ASSERT(mRootStyleContextCount > 0);
+ --mRootStyleContextCount;
+ }
+
+ // Return whether the rule tree has cached data such that we need to
+ // do dynamic change handling for changes that change the results of
+ // media queries or require rebuilding all style data.
+ // We don't care whether we have cached rule processors or whether
+ // they have cached rule cascades; getting the rule cascades again in
+ // order to do rule matching will get the correct rule cascade.
+ bool HasCachedStyleData() const {
+ return (mRuleTree && mRuleTree->TreeHasCachedData()) || mRootStyleContextCount > 0;
+ }
+
+ // Notify the style set that a rulenode is no longer in use, or was
+ // just created and is not in use yet.
+ static const uint32_t kGCInterval = 300;
+ void RuleNodeUnused(nsRuleNode* aNode, bool aMayGC) {
+ ++mUnusedRuleNodeCount;
+ mUnusedRuleNodeList.insertBack(aNode);
+ if (aMayGC && mUnusedRuleNodeCount >= kGCInterval && !mInGC && !mInReconstruct) {
+ GCRuleTrees();
+ }
+ }
+
+ // Notify the style set that a rulenode that wasn't in use now is
+ void RuleNodeInUse(nsRuleNode* aNode) {
+ MOZ_ASSERT(mUnusedRuleNodeCount > 0);
+ --mUnusedRuleNodeCount;
+ aNode->removeFrom(mUnusedRuleNodeList);
+ }
+
+ // Returns true if a restyle of the document is needed due to cloning
+ // sheet inners.
+ bool EnsureUniqueInnerOnCSSSheets();
+
+ // Called by CSSStyleSheet::EnsureUniqueInner to let us know it cloned
+ // its inner.
+ void SetNeedsRestyleAfterEnsureUniqueInner() {
+ mNeedsRestyleAfterEnsureUniqueInner = true;
+ }
+
+ nsIStyleRule* InitialStyleRule();
+
+ bool HasRuleProcessorUsedByMultipleStyleSets(mozilla::SheetType aSheetType);
+
+ // Tells the RestyleManager for the document using this style set
+ // to drop any nsCSSSelector pointers it has.
+ void ClearSelectors();
+
+ // Returns whether aSheetType represents a level of the cascade that uses
+ // CSSStyleSheets. See gCSSSheetTypes in nsStyleSet.cpp for the list
+ // of CSS sheet types.
+ static bool IsCSSSheetType(mozilla::SheetType aSheetType);
+
+private:
+ nsStyleSet(const nsStyleSet& aCopy) = delete;
+ nsStyleSet& operator=(const nsStyleSet& aCopy) = delete;
+
+ // Free all the rules with reference-count zero. This continues iterating
+ // over the free list until it is empty, which allows immediate collection
+ // of nodes whose reference-count drops to zero during the destruction of
+ // a child node. This allows the collection of entire trees at once, since
+ // children hold their parents alive.
+ void GCRuleTrees();
+
+ nsresult DirtyRuleProcessors(mozilla::SheetType aType);
+
+ // Update the rule processor list after a change to the style sheet list.
+ nsresult GatherRuleProcessors(mozilla::SheetType aType);
+
+ void AddImportantRules(nsRuleNode* aCurrLevelNode,
+ nsRuleNode* aLastPrevLevelNode,
+ nsRuleWalker* aRuleWalker);
+
+ // Move aRuleWalker forward by the appropriate rule if we need to add
+ // a rule due to property restrictions on pseudo-elements.
+ void WalkRestrictionRule(mozilla::CSSPseudoElementType aPseudoType,
+ nsRuleWalker* aRuleWalker);
+
+ void WalkDisableTextZoomRule(mozilla::dom::Element* aElement,
+ nsRuleWalker* aRuleWalker);
+
+#ifdef DEBUG
+ // Just like AddImportantRules except it doesn't actually add anything; it
+ // just asserts that there are no important rules between aCurrLevelNode and
+ // aLastPrevLevelNode.
+ void AssertNoImportantRules(nsRuleNode* aCurrLevelNode,
+ nsRuleNode* aLastPrevLevelNode);
+
+ // Just like AddImportantRules except it doesn't actually add anything; it
+ // just asserts that there are no CSS rules between aCurrLevelNode and
+ // aLastPrevLevelNode. Mostly useful for the preshint level.
+ void AssertNoCSSRules(nsRuleNode* aCurrLevelNode,
+ nsRuleNode* aLastPrevLevelNode);
+#endif
+
+ // Enumerate the rules in a way that cares about the order of the
+ // rules.
+ // aElement is the element the rules are for. It might be null. aData
+ // is the closure to pass to aCollectorFunc. If aContent is not null,
+ // aData must be a RuleProcessorData*
+ void FileRules(nsIStyleRuleProcessor::EnumFunc aCollectorFunc,
+ RuleProcessorData* aData, mozilla::dom::Element* aElement,
+ nsRuleWalker* aRuleWalker);
+
+ // Enumerate all the rules in a way that doesn't care about the order
+ // of the rules and break out if the enumeration is halted.
+ void WalkRuleProcessors(nsIStyleRuleProcessor::EnumFunc aFunc,
+ ElementDependentRuleProcessorData* aData,
+ bool aWalkAllXBLStylesheets);
+
+ // Helper for ResolveStyleWithReplacement
+ // aPseudoElement must follow the same rules as for
+ // ResolvePseudoElementStyle, and be null for non-pseudo-element cases
+ nsRuleNode* RuleNodeWithReplacement(mozilla::dom::Element* aElement,
+ mozilla::dom::Element* aPseudoElement,
+ nsRuleNode* aOldRuleNode,
+ mozilla::CSSPseudoElementType aPseudoType,
+ nsRestyleHint aReplacements);
+
+ already_AddRefed<nsStyleContext>
+ GetContext(nsStyleContext* aParentContext,
+ nsRuleNode* aRuleNode,
+ nsRuleNode* aVisitedRuleNode,
+ nsIAtom* aPseudoTag,
+ mozilla::CSSPseudoElementType aPseudoType,
+ mozilla::dom::Element* aElementForAnimation,
+ uint32_t aFlags);
+
+ nsPresContext* PresContext() { return mRuleTree->PresContext(); }
+
+ // The sheets in each array in mSheets are stored with the most significant
+ // sheet last.
+ // The arrays for ePresHintSheet, eStyleAttrSheet, eTransitionSheet,
+ // eAnimationSheet and eSVGAttrAnimationSheet are always empty.
+ // (FIXME: We should reduce the storage needed for them.)
+ mozilla::EnumeratedArray<mozilla::SheetType, mozilla::SheetType::Count,
+ nsTArray<RefPtr<mozilla::CSSStyleSheet>>> mSheets;
+
+ // mRuleProcessors[eScopedDocSheet] is always null; rule processors
+ // for scoped style sheets are stored in mScopedDocSheetRuleProcessors.
+ mozilla::EnumeratedArray<mozilla::SheetType, mozilla::SheetType::Count,
+ nsCOMPtr<nsIStyleRuleProcessor>> mRuleProcessors;
+
+ // Rule processors for HTML5 scoped style sheets, one per scope.
+ nsTArray<nsCOMPtr<nsIStyleRuleProcessor> > mScopedDocSheetRuleProcessors;
+
+ RefPtr<nsBindingManager> mBindingManager;
+
+ RefPtr<nsRuleNode> mRuleTree; // This is the root of our rule tree. It is a
+ // lexicographic tree of matched rules that style
+ // contexts use to look up properties.
+
+ uint16_t mBatching;
+
+ unsigned mInShutdown : 1;
+ unsigned mInGC : 1;
+ unsigned mAuthorStyleDisabled: 1;
+ unsigned mInReconstruct : 1;
+ unsigned mInitFontFeatureValuesLookup : 1;
+ unsigned mNeedsRestyleAfterEnsureUniqueInner : 1;
+ unsigned mDirty : int(mozilla::SheetType::Count); // one bit per sheet type
+
+ uint32_t mRootStyleContextCount;
+
+#ifdef DEBUG
+ // In debug builds, we stash a weak pointer here to the old root during
+ // reconstruction. During GC, we check for this pointer, and null it out
+ // when we encounter it. This allows us to assert that the old root (and
+ // thus all of its subtree) was GCed after reconstruction, which implies
+ // that there are no style contexts holding on to old rule nodes.
+ nsRuleNode* mOldRootNode;
+#endif
+
+ // Track our rule nodes with zero refcount. When this hits a threshold, we
+ // sweep and free. Keeping unused rule nodes around for a bit allows us to
+ // reuse them in many cases.
+ mozilla::LinkedList<nsRuleNode> mUnusedRuleNodeList;
+ uint32_t mUnusedRuleNodeCount;
+
+ // Empty style rules to force things that restrict which properties
+ // apply into different branches of the rule tree.
+ RefPtr<nsEmptyStyleRule> mFirstLineRule, mFirstLetterRule, mPlaceholderRule;
+
+ // Style rule which sets all properties to their initial values for
+ // determining when context-sensitive values are in use.
+ RefPtr<nsInitialStyleRule> mInitialStyleRule;
+
+ // Style rule that sets the internal -x-text-zoom property on
+ // <svg:text> elements to disable the effect of text zooming.
+ RefPtr<nsDisableTextZoomStyleRule> mDisableTextZoomStyleRule;
+
+ // whether font feature values lookup object needs initialization
+ RefPtr<gfxFontFeatureValueSet> mFontFeatureValuesLookup;
+};
+
+#ifdef MOZILLA_INTERNAL_API
+inline
+void nsRuleNode::AddRef()
+{
+ if (mRefCnt++ == 0) {
+ MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
+ "ServoStyleSets should not have rule nodes");
+ mPresContext->StyleSet()->AsGecko()->RuleNodeInUse(this);
+ }
+}
+
+inline
+void nsRuleNode::Release()
+{
+ if (--mRefCnt == 0) {
+ MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
+ "ServoStyleSets should not have rule nodes");
+ mPresContext->StyleSet()->AsGecko()->RuleNodeUnused(this, /* aMayGC = */ true);
+ }
+}
+#endif
+
+#endif
diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp
new file mode 100644
index 000000000..2f12d6201
--- /dev/null
+++ b/layout/style/nsStyleStruct.cpp
@@ -0,0 +1,4261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsStyleContext, the
+ * internal API for computed style data for an element
+ */
+
+#include "nsStyleStruct.h"
+#include "nsStyleStructInlines.h"
+#include "nsStyleConsts.h"
+#include "nsThemeConstants.h"
+#include "nsString.h"
+#include "nsPresContext.h"
+#include "nsIAppShellService.h"
+#include "nsIWidget.h"
+#include "nsCRTGlue.h"
+#include "nsCSSParser.h"
+#include "nsCSSProps.h"
+#include "nsDeviceContext.h"
+#include "nsStyleUtil.h"
+
+#include "nsCOMPtr.h"
+
+#include "nsBidiUtils.h"
+#include "nsLayoutUtils.h"
+
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "CounterStyleManager.h"
+
+#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for PlaybackDirection
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/Likely.h"
+#include "nsIURI.h"
+#include "nsIDocument.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+static_assert((((1 << nsStyleStructID_Length) - 1) &
+ ~(NS_STYLE_INHERIT_MASK)) == 0,
+ "Not enough bits in NS_STYLE_INHERIT_MASK");
+
+/* static */ const int32_t nsStyleGridLine::kMinLine;
+/* static */ const int32_t nsStyleGridLine::kMaxLine;
+
+static bool
+EqualURIs(nsIURI *aURI1, nsIURI *aURI2)
+{
+ bool eq;
+ return aURI1 == aURI2 || // handle null==null, and optimize
+ (aURI1 && aURI2 &&
+ NS_SUCCEEDED(aURI1->Equals(aURI2, &eq)) && // not equal on fail
+ eq);
+}
+
+static bool
+DefinitelyEqualURIs(css::URLValueData* aURI1,
+ css::URLValueData* aURI2)
+{
+ return aURI1 == aURI2 ||
+ (aURI1 && aURI2 && aURI1->DefinitelyEqualURIs(*aURI2));
+}
+
+static bool
+DefinitelyEqualURIsAndPrincipal(css::URLValueData* aURI1,
+ css::URLValueData* aURI2)
+{
+ return aURI1 == aURI2 ||
+ (aURI1 && aURI2 && aURI1->DefinitelyEqualURIsAndPrincipal(*aURI2));
+}
+
+static bool
+EqualImages(imgIRequest *aImage1, imgIRequest* aImage2)
+{
+ if (aImage1 == aImage2) {
+ return true;
+ }
+
+ if (!aImage1 || !aImage2) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> uri1, uri2;
+ aImage1->GetURI(getter_AddRefs(uri1));
+ aImage2->GetURI(getter_AddRefs(uri2));
+ return EqualURIs(uri1, uri2);
+}
+
+static bool
+DefinitelyEqualImages(nsStyleImageRequest* aRequest1,
+ nsStyleImageRequest* aRequest2)
+{
+ if (aRequest1 == aRequest2) {
+ return true;
+ }
+
+ if (!aRequest1 || !aRequest2) {
+ return false;
+ }
+
+ return aRequest1->DefinitelyEquals(*aRequest2);
+}
+
+// A nullsafe wrapper for strcmp. We depend on null-safety.
+static int
+safe_strcmp(const char16_t* a, const char16_t* b)
+{
+ if (!a || !b) {
+ return (int)(a - b);
+ }
+ return NS_strcmp(a, b);
+}
+
+int32_t
+StyleStructContext::AppUnitsPerDevPixel()
+{
+ return DeviceContext()->AppUnitsPerDevPixel();
+}
+
+nsDeviceContext*
+StyleStructContext::HackilyFindSomeDeviceContext()
+{
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService("@mozilla.org/appshell/appShellService;1"));
+ MOZ_ASSERT(appShell);
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ appShell->GetHiddenDOMWindow(getter_AddRefs(win));
+ return nsLayoutUtils::GetDeviceContextForScreenInfo(static_cast<nsPIDOMWindowOuter*>(win.get()));
+}
+
+static bool AreShadowArraysEqual(nsCSSShadowArray* lhs, nsCSSShadowArray* rhs);
+
+// --------------------
+// nsStyleFont
+//
+nsStyleFont::nsStyleFont(const nsFont& aFont, StyleStructContext aContext)
+ : mFont(aFont)
+ , mSize(nsStyleFont::ZoomText(aContext, mFont.size))
+ , mGenericID(kGenericFont_NONE)
+ , mScriptLevel(0)
+ , mMathVariant(NS_MATHML_MATHVARIANT_NONE)
+ , mMathDisplay(NS_MATHML_DISPLAYSTYLE_INLINE)
+ , mMinFontSizeRatio(100) // 100%
+ , mExplicitLanguage(false)
+ , mAllowZoom(true)
+ , mScriptUnconstrainedSize(mSize)
+ , mScriptMinSize(nsPresContext::CSSTwipsToAppUnits(
+ NS_POINTS_TO_TWIPS(NS_MATHML_DEFAULT_SCRIPT_MIN_SIZE_PT)))
+ , mScriptSizeMultiplier(NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER)
+ , mLanguage(GetLanguage(aContext))
+{
+ MOZ_COUNT_CTOR(nsStyleFont);
+ mFont.size = mSize;
+}
+
+nsStyleFont::nsStyleFont(const nsStyleFont& aSrc)
+ : mFont(aSrc.mFont)
+ , mSize(aSrc.mSize)
+ , mGenericID(aSrc.mGenericID)
+ , mScriptLevel(aSrc.mScriptLevel)
+ , mMathVariant(aSrc.mMathVariant)
+ , mMathDisplay(aSrc.mMathDisplay)
+ , mMinFontSizeRatio(aSrc.mMinFontSizeRatio)
+ , mExplicitLanguage(aSrc.mExplicitLanguage)
+ , mAllowZoom(aSrc.mAllowZoom)
+ , mScriptUnconstrainedSize(aSrc.mScriptUnconstrainedSize)
+ , mScriptMinSize(aSrc.mScriptMinSize)
+ , mScriptSizeMultiplier(aSrc.mScriptSizeMultiplier)
+ , mLanguage(aSrc.mLanguage)
+{
+ MOZ_COUNT_CTOR(nsStyleFont);
+}
+
+nsStyleFont::nsStyleFont(StyleStructContext aContext)
+ : nsStyleFont(*aContext.GetDefaultFont(kPresContext_DefaultVariableFont_ID),
+ aContext)
+{
+}
+
+void
+nsStyleFont::Destroy(nsPresContext* aContext) {
+ this->~nsStyleFont();
+ aContext->PresShell()->
+ FreeByObjectID(eArenaObjectID_nsStyleFont, this);
+}
+
+void
+nsStyleFont::EnableZoom(nsPresContext* aContext, bool aEnable)
+{
+ if (mAllowZoom == aEnable) {
+ return;
+ }
+ mAllowZoom = aEnable;
+ if (mAllowZoom) {
+ mSize = nsStyleFont::ZoomText(aContext, mSize);
+ mFont.size = nsStyleFont::ZoomText(aContext, mFont.size);
+ mScriptUnconstrainedSize =
+ nsStyleFont::ZoomText(aContext, mScriptUnconstrainedSize);
+ } else {
+ mSize = nsStyleFont::UnZoomText(aContext, mSize);
+ mFont.size = nsStyleFont::UnZoomText(aContext, mFont.size);
+ mScriptUnconstrainedSize =
+ nsStyleFont::UnZoomText(aContext, mScriptUnconstrainedSize);
+ }
+}
+
+nsChangeHint
+nsStyleFont::CalcDifference(const nsStyleFont& aNewData) const
+{
+ MOZ_ASSERT(mAllowZoom == aNewData.mAllowZoom,
+ "expected mAllowZoom to be the same on both nsStyleFonts");
+ if (mSize != aNewData.mSize ||
+ mFont != aNewData.mFont ||
+ mLanguage != aNewData.mLanguage ||
+ mExplicitLanguage != aNewData.mExplicitLanguage ||
+ mMathVariant != aNewData.mMathVariant ||
+ mMathDisplay != aNewData.mMathDisplay ||
+ mMinFontSizeRatio != aNewData.mMinFontSizeRatio) {
+ return NS_STYLE_HINT_REFLOW;
+ }
+
+ // XXX Should any of these cause a non-nsChangeHint_NeutralChange change?
+ if (mGenericID != aNewData.mGenericID ||
+ mScriptLevel != aNewData.mScriptLevel ||
+ mScriptUnconstrainedSize != aNewData.mScriptUnconstrainedSize ||
+ mScriptMinSize != aNewData.mScriptMinSize ||
+ mScriptSizeMultiplier != aNewData.mScriptSizeMultiplier) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ return nsChangeHint(0);
+}
+
+/* static */ nscoord
+nsStyleFont::ZoomText(StyleStructContext aContext, nscoord aSize)
+{
+ // aSize can be negative (e.g.: calc(-1px)) so we can't assert that here.
+ // The caller is expected deal with that.
+ return NSToCoordTruncClamped(float(aSize) * aContext.TextZoom());
+}
+
+/* static */ nscoord
+nsStyleFont::UnZoomText(nsPresContext *aPresContext, nscoord aSize)
+{
+ // aSize can be negative (e.g.: calc(-1px)) so we can't assert that here.
+ // The caller is expected deal with that.
+ return NSToCoordTruncClamped(float(aSize) / aPresContext->TextZoom());
+}
+
+/* static */ already_AddRefed<nsIAtom>
+nsStyleFont::GetLanguage(StyleStructContext aContext)
+{
+ RefPtr<nsIAtom> language = aContext.GetContentLanguage();
+ if (!language) {
+ // we didn't find a (usable) Content-Language, so we fall back
+ // to whatever the presContext guessed from the charset
+ // NOTE this should not be used elsewhere, because we want websites
+ // to use UTF-8 with proper language tag, instead of relying on
+ // deriving language from charset. See bug 1040668 comment 67.
+ language = aContext.GetLanguageFromCharset();
+ }
+ return language.forget();
+}
+
+static nscoord
+CalcCoord(const nsStyleCoord& aCoord, const nscoord* aEnumTable, int32_t aNumEnums)
+{
+ if (aCoord.GetUnit() == eStyleUnit_Enumerated) {
+ MOZ_ASSERT(aEnumTable, "must have enum table");
+ int32_t value = aCoord.GetIntValue();
+ if (0 <= value && value < aNumEnums) {
+ return aEnumTable[aCoord.GetIntValue()];
+ }
+ NS_NOTREACHED("unexpected enum value");
+ return 0;
+ }
+ MOZ_ASSERT(aCoord.ConvertsToLength(), "unexpected unit");
+ return nsRuleNode::ComputeCoordPercentCalc(aCoord, 0);
+}
+
+nsStyleMargin::nsStyleMargin(StyleStructContext aContext)
+{
+ MOZ_COUNT_CTOR(nsStyleMargin);
+ nsStyleCoord zero(0, nsStyleCoord::CoordConstructor);
+ NS_FOR_CSS_SIDES(side) {
+ mMargin.Set(side, zero);
+ }
+}
+
+nsStyleMargin::nsStyleMargin(const nsStyleMargin& aSrc)
+ : mMargin(aSrc.mMargin)
+{
+ MOZ_COUNT_CTOR(nsStyleMargin);
+}
+
+void
+nsStyleMargin::Destroy(nsPresContext* aContext) {
+ this->~nsStyleMargin();
+ aContext->PresShell()->
+ FreeByObjectID(eArenaObjectID_nsStyleMargin, this);
+}
+
+nsChangeHint
+nsStyleMargin::CalcDifference(const nsStyleMargin& aNewData) const
+{
+ if (mMargin == aNewData.mMargin) {
+ return nsChangeHint(0);
+ }
+ // Margin differences can't affect descendant intrinsic sizes and
+ // don't need to force children to reflow.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+}
+
+nsStylePadding::nsStylePadding(StyleStructContext aContext)
+{
+ MOZ_COUNT_CTOR(nsStylePadding);
+ nsStyleCoord zero(0, nsStyleCoord::CoordConstructor);
+ NS_FOR_CSS_SIDES(side) {
+ mPadding.Set(side, zero);
+ }
+}
+
+nsStylePadding::nsStylePadding(const nsStylePadding& aSrc)
+ : mPadding(aSrc.mPadding)
+{
+ MOZ_COUNT_CTOR(nsStylePadding);
+}
+
+void
+nsStylePadding::Destroy(nsPresContext* aContext) {
+ this->~nsStylePadding();
+ aContext->PresShell()->
+ FreeByObjectID(eArenaObjectID_nsStylePadding, this);
+}
+
+nsChangeHint
+nsStylePadding::CalcDifference(const nsStylePadding& aNewData) const
+{
+ if (mPadding == aNewData.mPadding) {
+ return nsChangeHint(0);
+ }
+ // 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).
+ return NS_STYLE_HINT_REFLOW & ~nsChangeHint_ClearDescendantIntrinsics;
+}
+
+nsStyleBorder::nsStyleBorder(StyleStructContext aContext)
+ : mBorderColors(nullptr)
+ , mBorderImageFill(NS_STYLE_BORDER_IMAGE_SLICE_NOFILL)
+ , mBorderImageRepeatH(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH)
+ , mBorderImageRepeatV(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH)
+ , mFloatEdge(StyleFloatEdge::ContentBox)
+ , mBoxDecorationBreak(StyleBoxDecorationBreak::Slice)
+ , mComputedBorder(0, 0, 0, 0)
+{
+ MOZ_COUNT_CTOR(nsStyleBorder);
+
+ NS_FOR_CSS_HALF_CORNERS (corner) {
+ mBorderRadius.Set(corner, nsStyleCoord(0, nsStyleCoord::CoordConstructor));
+ }
+
+ nscoord medium =
+ (StaticPresData::Get()->GetBorderWidthTable())[NS_STYLE_BORDER_WIDTH_MEDIUM];
+ NS_FOR_CSS_SIDES(side) {
+ mBorderImageSlice.Set(side, nsStyleCoord(1.0f, eStyleUnit_Percent));
+ mBorderImageWidth.Set(side, nsStyleCoord(1.0f, eStyleUnit_Factor));
+ mBorderImageOutset.Set(side, nsStyleCoord(0.0f, eStyleUnit_Factor));
+
+ mBorder.Side(side) = medium;
+ mBorderStyle[side] = NS_STYLE_BORDER_STYLE_NONE;
+ mBorderColor[side] = StyleComplexColor::CurrentColor();
+ }
+
+ mTwipsPerPixel = aContext.DevPixelsToAppUnits(1);
+}
+
+nsBorderColors::~nsBorderColors()
+{
+ NS_CSS_DELETE_LIST_MEMBER(nsBorderColors, this, mNext);
+}
+
+nsBorderColors*
+nsBorderColors::Clone(bool aDeep) const
+{
+ nsBorderColors* result = new nsBorderColors(mColor);
+ if (MOZ_UNLIKELY(!result)) {
+ return result;
+ }
+ if (aDeep) {
+ NS_CSS_CLONE_LIST_MEMBER(nsBorderColors, this, mNext, result, (false));
+ }
+ return result;
+}
+
+nsStyleBorder::nsStyleBorder(const nsStyleBorder& aSrc)
+ : mBorderColors(nullptr)
+ , mBorderRadius(aSrc.mBorderRadius)
+ , mBorderImageSource(aSrc.mBorderImageSource)
+ , mBorderImageSlice(aSrc.mBorderImageSlice)
+ , mBorderImageWidth(aSrc.mBorderImageWidth)
+ , mBorderImageOutset(aSrc.mBorderImageOutset)
+ , mBorderImageFill(aSrc.mBorderImageFill)
+ , mBorderImageRepeatH(aSrc.mBorderImageRepeatH)
+ , mBorderImageRepeatV(aSrc.mBorderImageRepeatV)
+ , mFloatEdge(aSrc.mFloatEdge)
+ , mBoxDecorationBreak(aSrc.mBoxDecorationBreak)
+ , mComputedBorder(aSrc.mComputedBorder)
+ , mBorder(aSrc.mBorder)
+ , mTwipsPerPixel(aSrc.mTwipsPerPixel)
+{
+ MOZ_COUNT_CTOR(nsStyleBorder);
+ if (aSrc.mBorderColors) {
+ EnsureBorderColors();
+ for (int32_t i = 0; i < 4; i++) {
+ if (aSrc.mBorderColors[i]) {
+ mBorderColors[i] = aSrc.mBorderColors[i]->Clone();
+ } else {
+ mBorderColors[i] = nullptr;
+ }
+ }
+ }
+
+ NS_FOR_CSS_SIDES(side) {
+ mBorderStyle[side] = aSrc.mBorderStyle[side];
+ mBorderColor[side] = aSrc.mBorderColor[side];
+ }
+}
+
+nsStyleBorder::~nsStyleBorder()
+{
+ MOZ_COUNT_DTOR(nsStyleBorder);
+ if (mBorderColors) {
+ for (int32_t i = 0; i < 4; i++) {
+ delete mBorderColors[i];
+ }
+ delete [] mBorderColors;
+ }
+}
+
+void
+nsStyleBorder::FinishStyle(nsPresContext* aPresContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPresContext->StyleSet()->IsServo());
+
+ mBorderImageSource.ResolveImage(aPresContext);
+}
+
+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;
+ NS_FOR_CSS_SIDES(s) {
+ nsStyleCoord coord = mBorderImageOutset.Get(s);
+ nscoord value;
+ switch (coord.GetUnit()) {
+ case eStyleUnit_Coord:
+ value = coord.GetCoordValue();
+ break;
+ case eStyleUnit_Factor:
+ value = coord.GetFactorValue() * mComputedBorder.Side(s);
+ break;
+ default:
+ NS_NOTREACHED("unexpected CSS unit for image outset");
+ value = 0;
+ break;
+ }
+ outset.Side(s) = value;
+ }
+ return outset;
+}
+
+void
+nsStyleBorder::Destroy(nsPresContext* aContext)
+{
+ this->~nsStyleBorder();
+ aContext->PresShell()->
+ FreeByObjectID(eArenaObjectID_nsStyleBorder, this);
+}
+
+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 (mTwipsPerPixel != aNewData.mTwipsPerPixel ||
+ GetComputedBorder() != aNewData.GetComputedBorder() ||
+ mFloatEdge != aNewData.mFloatEdge ||
+ mBorderImageOutset != aNewData.mBorderImageOutset ||
+ mBoxDecorationBreak != aNewData.mBoxDecorationBreak) {
+ return NS_STYLE_HINT_REFLOW;
+ }
+
+ NS_FOR_CSS_SIDES(ix) {
+ // 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.
+ NS_FOR_CSS_SIDES(ix) {
+ if (mBorderStyle[ix] != aNewData.mBorderStyle[ix] ||
+ mBorderColor[ix] != aNewData.mBorderColor[ix]) {
+ return nsChangeHint_RepaintFrame;
+ }
+ }
+
+ if (mBorderRadius != aNewData.mBorderRadius ||
+ !mBorderColors != !aNewData.mBorderColors) {
+ return nsChangeHint_RepaintFrame;
+ }
+
+ if (IsBorderImageLoaded() || aNewData.IsBorderImageLoaded()) {
+ if (mBorderImageSource != aNewData.mBorderImageSource ||
+ mBorderImageRepeatH != aNewData.mBorderImageRepeatH ||
+ mBorderImageRepeatV != aNewData.mBorderImageRepeatV ||
+ mBorderImageSlice != aNewData.mBorderImageSlice ||
+ mBorderImageFill != aNewData.mBorderImageFill ||
+ mBorderImageWidth != aNewData.mBorderImageWidth ||
+ mBorderImageOutset != aNewData.mBorderImageOutset) {
+ return nsChangeHint_RepaintFrame;
+ }
+ }
+
+ // Note that at this point if mBorderColors is non-null so is
+ // aNewData.mBorderColors
+ if (mBorderColors) {
+ NS_FOR_CSS_SIDES(ix) {
+ if (!nsBorderColors::Equal(mBorderColors[ix],
+ aNewData.mBorderColors[ix])) {
+ 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;
+ }
+
+ return nsChangeHint(0);
+}
+
+nsStyleOutline::nsStyleOutline(StyleStructContext aContext)
+ : mOutlineWidth(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated)
+ , mOutlineOffset(0)
+ , mOutlineColor(StyleComplexColor::CurrentColor())
+ , mOutlineStyle(NS_STYLE_BORDER_STYLE_NONE)
+ , mActualOutlineWidth(0)
+ , mTwipsPerPixel(aContext.DevPixelsToAppUnits(1))
+{
+ MOZ_COUNT_CTOR(nsStyleOutline);
+ // spacing values not inherited
+ nsStyleCoord zero(0, nsStyleCoord::CoordConstructor);
+ NS_FOR_CSS_HALF_CORNERS(corner) {
+ mOutlineRadius.Set(corner, zero);
+ }
+}
+
+nsStyleOutline::nsStyleOutline(const nsStyleOutline& aSrc)
+ : mOutlineRadius(aSrc.mOutlineRadius)
+ , mOutlineWidth(aSrc.mOutlineWidth)
+ , mOutlineOffset(aSrc.mOutlineOffset)
+ , mOutlineColor(aSrc.mOutlineColor)
+ , mOutlineStyle(aSrc.mOutlineStyle)
+ , mActualOutlineWidth(aSrc.mActualOutlineWidth)
+ , mTwipsPerPixel(aSrc.mTwipsPerPixel)
+{
+ MOZ_COUNT_CTOR(nsStyleOutline);
+}
+
+void
+nsStyleOutline::RecalcData()
+{
+ if (NS_STYLE_BORDER_STYLE_NONE == mOutlineStyle) {
+ mActualOutlineWidth = 0;
+ } else {
+ MOZ_ASSERT(mOutlineWidth.ConvertsToLength() ||
+ mOutlineWidth.GetUnit() == eStyleUnit_Enumerated);
+ // Clamp negative calc() to 0.
+ mActualOutlineWidth =
+ std::max(CalcCoord(mOutlineWidth,
+ StaticPresData::Get()->GetBorderWidthTable(), 3), 0);
+ mActualOutlineWidth =
+ NS_ROUND_BORDER_TO_PIXELS(mActualOutlineWidth, mTwipsPerPixel);
+ }
+}
+
+nsChangeHint
+nsStyleOutline::CalcDifference(const nsStyleOutline& aNewData) const
+{
+ if (mActualOutlineWidth != aNewData.mActualOutlineWidth ||
+ (mActualOutlineWidth > 0 &&
+ mOutlineOffset != aNewData.mOutlineOffset)) {
+ return nsChangeHint_UpdateOverflow |
+ nsChangeHint_SchedulePaint;
+ }
+
+ if (mOutlineStyle != aNewData.mOutlineStyle ||
+ mOutlineColor != aNewData.mOutlineColor ||
+ mOutlineRadius != aNewData.mOutlineRadius) {
+ if (mActualOutlineWidth > 0) {
+ return nsChangeHint_RepaintFrame;
+ }
+ return nsChangeHint_NeutralChange;
+ }
+
+ if (mOutlineWidth != aNewData.mOutlineWidth ||
+ mOutlineOffset != aNewData.mOutlineOffset ||
+ mTwipsPerPixel != aNewData.mTwipsPerPixel) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ return nsChangeHint(0);
+}
+
+// --------------------
+// nsStyleList
+//
+nsStyleList::nsStyleList(StyleStructContext aContext)
+ : mListStylePosition(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE)
+ , mCounterStyle(aContext.BuildCounterStyle(NS_LITERAL_STRING("disc")))
+{
+ MOZ_COUNT_CTOR(nsStyleList);
+ SetQuotesInitial();
+}
+
+nsStyleList::~nsStyleList()
+{
+ MOZ_COUNT_DTOR(nsStyleList);
+}
+
+nsStyleList::nsStyleList(const nsStyleList& aSource)
+ : mListStylePosition(aSource.mListStylePosition)
+ , mListStyleImage(aSource.mListStyleImage)
+ , mCounterStyle(aSource.mCounterStyle)
+ , mQuotes(aSource.mQuotes)
+ , mImageRegion(aSource.mImageRegion)
+{
+ MOZ_COUNT_CTOR(nsStyleList);
+}
+
+void
+nsStyleList::FinishStyle(nsPresContext* aPresContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPresContext->StyleSet()->IsServo());
+
+ if (mListStyleImage && !mListStyleImage->IsResolved()) {
+ mListStyleImage->Resolve(aPresContext);
+ }
+}
+
+void
+nsStyleList::SetQuotesInherit(const nsStyleList* aOther)
+{
+ mQuotes = aOther->mQuotes;
+}
+
+void
+nsStyleList::SetQuotesInitial()
+{
+ if (!sInitialQuotes) {
+ // The initial value for quotes is the en-US typographic convention:
+ // outermost are LEFT and RIGHT DOUBLE QUOTATION MARK, alternating
+ // with LEFT and RIGHT SINGLE QUOTATION MARK.
+ static const char16_t initialQuotes[8] = {
+ 0x201C, 0, 0x201D, 0, 0x2018, 0, 0x2019, 0
+ };
+
+ sInitialQuotes = new nsStyleQuoteValues;
+ sInitialQuotes->mQuotePairs.AppendElement(
+ std::make_pair(nsDependentString(&initialQuotes[0], 1),
+ nsDependentString(&initialQuotes[2], 1)));
+ sInitialQuotes->mQuotePairs.AppendElement(
+ std::make_pair(nsDependentString(&initialQuotes[4], 1),
+ nsDependentString(&initialQuotes[6], 1)));
+ }
+
+ mQuotes = sInitialQuotes;
+}
+
+void
+nsStyleList::SetQuotesNone()
+{
+ if (!sNoneQuotes) {
+ sNoneQuotes = new nsStyleQuoteValues;
+ }
+ mQuotes = sNoneQuotes;
+}
+
+void
+nsStyleList::SetQuotes(nsStyleQuoteValues::QuotePairArray&& aValues)
+{
+ mQuotes = new nsStyleQuoteValues;
+ mQuotes->mQuotePairs = Move(aValues);
+}
+
+const nsStyleQuoteValues::QuotePairArray&
+nsStyleList::GetQuotePairs() const
+{
+ return mQuotes->mQuotePairs;
+}
+
+nsChangeHint
+nsStyleList::CalcDifference(const nsStyleList& aNewData) 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 &&
+ (mQuotes || aNewData.mQuotes) &&
+ GetQuotePairs() != aNewData.GetQuotePairs()) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ if (mListStylePosition != aNewData.mListStylePosition) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ if (DefinitelyEqualImages(mListStyleImage, aNewData.mListStyleImage) &&
+ mCounterStyle == aNewData.mCounterStyle) {
+ if (mImageRegion.IsEqualInterior(aNewData.mImageRegion)) {
+ return nsChangeHint(0);
+ }
+ if (mImageRegion.width == aNewData.mImageRegion.width &&
+ mImageRegion.height == aNewData.mImageRegion.height) {
+ return NS_STYLE_HINT_VISUAL;
+ }
+ }
+ return NS_STYLE_HINT_REFLOW;
+}
+
+StaticRefPtr<nsStyleQuoteValues>
+nsStyleList::sInitialQuotes;
+
+StaticRefPtr<nsStyleQuoteValues>
+nsStyleList::sNoneQuotes;
+
+
+// --------------------
+// nsStyleXUL
+//
+nsStyleXUL::nsStyleXUL(StyleStructContext aContext)
+ : mBoxFlex(0.0f)
+ , mBoxOrdinal(1)
+ , mBoxAlign(StyleBoxAlign::Stretch)
+ , mBoxDirection(StyleBoxDirection::Normal)
+ , mBoxOrient(StyleBoxOrient::Horizontal)
+ , mBoxPack(StyleBoxPack::Start)
+ , mStretchStack(true)
+{
+ MOZ_COUNT_CTOR(nsStyleXUL);
+}
+
+nsStyleXUL::~nsStyleXUL()
+{
+ MOZ_COUNT_DTOR(nsStyleXUL);
+}
+
+nsStyleXUL::nsStyleXUL(const nsStyleXUL& aSource)
+ : mBoxFlex(aSource.mBoxFlex)
+ , mBoxOrdinal(aSource.mBoxOrdinal)
+ , mBoxAlign(aSource.mBoxAlign)
+ , mBoxDirection(aSource.mBoxDirection)
+ , mBoxOrient(aSource.mBoxOrient)
+ , mBoxPack(aSource.mBoxPack)
+ , mStretchStack(aSource.mStretchStack)
+{
+ 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 &&
+ mStretchStack == aNewData.mStretchStack) {
+ return nsChangeHint(0);
+ }
+ if (mBoxOrdinal != aNewData.mBoxOrdinal) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ return NS_STYLE_HINT_REFLOW;
+}
+
+// --------------------
+// nsStyleColumn
+//
+/* static */ const uint32_t nsStyleColumn::kMaxColumnCount;
+
+nsStyleColumn::nsStyleColumn(StyleStructContext aContext)
+ : mColumnCount(NS_STYLE_COLUMN_COUNT_AUTO)
+ , mColumnWidth(eStyleUnit_Auto)
+ , mColumnGap(eStyleUnit_Normal)
+ , mColumnRuleColor(StyleComplexColor::CurrentColor())
+ , mColumnRuleStyle(NS_STYLE_BORDER_STYLE_NONE)
+ , mColumnFill(NS_STYLE_COLUMN_FILL_BALANCE)
+ , mColumnRuleWidth((StaticPresData::Get()
+ ->GetBorderWidthTable())[NS_STYLE_BORDER_WIDTH_MEDIUM])
+ , mTwipsPerPixel(aContext.AppUnitsPerDevPixel())
+{
+ MOZ_COUNT_CTOR(nsStyleColumn);
+}
+
+nsStyleColumn::~nsStyleColumn()
+{
+ MOZ_COUNT_DTOR(nsStyleColumn);
+}
+
+nsStyleColumn::nsStyleColumn(const nsStyleColumn& aSource)
+ : mColumnCount(aSource.mColumnCount)
+ , mColumnWidth(aSource.mColumnWidth)
+ , mColumnGap(aSource.mColumnGap)
+ , mColumnRuleColor(aSource.mColumnRuleColor)
+ , mColumnRuleStyle(aSource.mColumnRuleStyle)
+ , mColumnFill(aSource.mColumnFill)
+ , mColumnRuleWidth(aSource.mColumnRuleWidth)
+ , mTwipsPerPixel(aSource.mTwipsPerPixel)
+{
+ MOZ_COUNT_CTOR(nsStyleColumn);
+}
+
+nsChangeHint
+nsStyleColumn::CalcDifference(const nsStyleColumn& aNewData) const
+{
+ if ((mColumnWidth.GetUnit() == eStyleUnit_Auto)
+ != (aNewData.mColumnWidth.GetUnit() == eStyleUnit_Auto) ||
+ mColumnCount != aNewData.mColumnCount) {
+ // 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 ||
+ mColumnGap != aNewData.mColumnGap ||
+ mColumnFill != aNewData.mColumnFill) {
+ return NS_STYLE_HINT_REFLOW;
+ }
+
+ if (GetComputedColumnRuleWidth() != aNewData.GetComputedColumnRuleWidth() ||
+ mColumnRuleStyle != aNewData.mColumnRuleStyle ||
+ mColumnRuleColor != aNewData.mColumnRuleColor) {
+ return NS_STYLE_HINT_VISUAL;
+ }
+
+ // XXX Is it right that we never check mTwipsPerPixel to return a
+ // non-nsChangeHint_NeutralChange hint?
+ if (mColumnRuleWidth != aNewData.mColumnRuleWidth ||
+ mTwipsPerPixel != aNewData.mTwipsPerPixel) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ return nsChangeHint(0);
+}
+
+// --------------------
+// nsStyleSVG
+//
+nsStyleSVG::nsStyleSVG(StyleStructContext aContext)
+ : mFill(eStyleSVGPaintType_Color) // Will be initialized to NS_RGB(0, 0, 0)
+ , mStroke(eStyleSVGPaintType_None)
+ , mStrokeDashoffset(0, nsStyleCoord::CoordConstructor)
+ , mStrokeWidth(nsPresContext::CSSPixelsToAppUnits(1), nsStyleCoord::CoordConstructor)
+ , mFillOpacity(1.0f)
+ , mStrokeMiterlimit(4.0f)
+ , mStrokeOpacity(1.0f)
+ , mClipRule(StyleFillRule::Nonzero)
+ , mColorInterpolation(NS_STYLE_COLOR_INTERPOLATION_SRGB)
+ , mColorInterpolationFilters(NS_STYLE_COLOR_INTERPOLATION_LINEARRGB)
+ , mFillRule(StyleFillRule::Nonzero)
+ , mPaintOrder(NS_STYLE_PAINT_ORDER_NORMAL)
+ , mShapeRendering(NS_STYLE_SHAPE_RENDERING_AUTO)
+ , mStrokeLinecap(NS_STYLE_STROKE_LINECAP_BUTT)
+ , mStrokeLinejoin(NS_STYLE_STROKE_LINEJOIN_MITER)
+ , mTextAnchor(NS_STYLE_TEXT_ANCHOR_START)
+ , mContextFlags((eStyleSVGOpacitySource_Normal << FILL_OPACITY_SOURCE_SHIFT) |
+ (eStyleSVGOpacitySource_Normal << STROKE_OPACITY_SOURCE_SHIFT))
+{
+ MOZ_COUNT_CTOR(nsStyleSVG);
+}
+
+nsStyleSVG::~nsStyleSVG()
+{
+ MOZ_COUNT_DTOR(nsStyleSVG);
+}
+
+nsStyleSVG::nsStyleSVG(const nsStyleSVG& aSource)
+ : mFill(aSource.mFill)
+ , mStroke(aSource.mStroke)
+ , mMarkerEnd(aSource.mMarkerEnd)
+ , mMarkerMid(aSource.mMarkerMid)
+ , mMarkerStart(aSource.mMarkerStart)
+ , 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)
+ , mTextAnchor(aSource.mTextAnchor)
+ , mContextFlags(aSource.mContextFlags)
+{
+ MOZ_COUNT_CTOR(nsStyleSVG);
+}
+
+static bool
+PaintURIChanged(const nsStyleSVGPaint& aPaint1, const nsStyleSVGPaint& aPaint2)
+{
+ if (aPaint1.Type() != aPaint2.Type()) {
+ return aPaint1.Type() == eStyleSVGPaintType_Server ||
+ aPaint2.Type() == eStyleSVGPaintType_Server;
+ }
+ return aPaint1.Type() == eStyleSVGPaintType_Server &&
+ !DefinitelyEqualURIs(aPaint1.GetPaintServer(),
+ aPaint2.GetPaintServer());
+}
+
+nsChangeHint
+nsStyleSVG::CalcDifference(const nsStyleSVG& aNewData) const
+{
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (!DefinitelyEqualURIs(mMarkerEnd, aNewData.mMarkerEnd) ||
+ !DefinitelyEqualURIs(mMarkerMid, aNewData.mMarkerMid) ||
+ !DefinitelyEqualURIs(mMarkerStart, aNewData.mMarkerStart)) {
+ // Markers currently contribute to nsSVGPathGeometryFrame::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_NeedDirtyReflow | // XXX remove me: bug 876085
+ 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 nsSVGPathGeometryFrames.
+ hint |= nsChangeHint_NeedReflow |
+ nsChangeHint_NeedDirtyReflow; // XXX remove me: bug 876085
+ }
+ if (PaintURIChanged(mFill, aNewData.mFill) ||
+ PaintURIChanged(mStroke, aNewData.mStroke)) {
+ hint |= nsChangeHint_UpdateEffects;
+ }
+ }
+
+ // Stroke currently contributes to nsSVGPathGeometryFrame::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 nsSVGPathGeometryFrame::mRect.
+ // text-anchor changes also require a reflow since it changes frames' rects.
+ if (mStrokeWidth != aNewData.mStrokeWidth ||
+ mStrokeMiterlimit != aNewData.mStrokeMiterlimit ||
+ mStrokeLinecap != aNewData.mStrokeLinecap ||
+ mStrokeLinejoin != aNewData.mStrokeLinejoin ||
+ mTextAnchor != aNewData.mTextAnchor) {
+ return hint |
+ nsChangeHint_NeedReflow |
+ nsChangeHint_NeedDirtyReflow | // XXX remove me: bug 876085
+ 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 ||
+ mContextFlags != aNewData.mContextFlags) {
+ return hint | nsChangeHint_RepaintFrame;
+ }
+
+ return hint;
+}
+
+// --------------------
+// StyleBasicShape
+
+nsCSSKeyword
+StyleBasicShape::GetShapeTypeName() const
+{
+ switch (mType) {
+ case StyleBasicShapeType::Polygon:
+ return eCSSKeyword_polygon;
+ case StyleBasicShapeType::Circle:
+ return eCSSKeyword_circle;
+ case StyleBasicShapeType::Ellipse:
+ return eCSSKeyword_ellipse;
+ case StyleBasicShapeType::Inset:
+ return eCSSKeyword_inset;
+ }
+ NS_NOTREACHED("unexpected type");
+ return eCSSKeyword_UNKNOWN;
+}
+
+// --------------------
+// nsStyleFilter
+//
+nsStyleFilter::nsStyleFilter()
+ : mType(NS_STYLE_FILTER_NONE)
+ , mDropShadow(nullptr)
+{
+ MOZ_COUNT_CTOR(nsStyleFilter);
+}
+
+nsStyleFilter::nsStyleFilter(const nsStyleFilter& aSource)
+ : mType(NS_STYLE_FILTER_NONE)
+ , mDropShadow(nullptr)
+{
+ MOZ_COUNT_CTOR(nsStyleFilter);
+ if (aSource.mType == NS_STYLE_FILTER_URL) {
+ SetURL(aSource.mURL);
+ } else if (aSource.mType == NS_STYLE_FILTER_DROP_SHADOW) {
+ SetDropShadow(aSource.mDropShadow);
+ } else if (aSource.mType != NS_STYLE_FILTER_NONE) {
+ SetFilterParameter(aSource.mFilterParameter, aSource.mType);
+ }
+}
+
+nsStyleFilter::~nsStyleFilter()
+{
+ ReleaseRef();
+ MOZ_COUNT_DTOR(nsStyleFilter);
+}
+
+nsStyleFilter&
+nsStyleFilter::operator=(const nsStyleFilter& aOther)
+{
+ if (this == &aOther) {
+ return *this;
+ }
+
+ if (aOther.mType == NS_STYLE_FILTER_URL) {
+ SetURL(aOther.mURL);
+ } else if (aOther.mType == NS_STYLE_FILTER_DROP_SHADOW) {
+ SetDropShadow(aOther.mDropShadow);
+ } else if (aOther.mType != NS_STYLE_FILTER_NONE) {
+ SetFilterParameter(aOther.mFilterParameter, aOther.mType);
+ } else {
+ ReleaseRef();
+ mType = NS_STYLE_FILTER_NONE;
+ }
+
+ return *this;
+}
+
+bool
+nsStyleFilter::operator==(const nsStyleFilter& aOther) const
+{
+ if (mType != aOther.mType) {
+ return false;
+ }
+
+ if (mType == NS_STYLE_FILTER_URL) {
+ return DefinitelyEqualURIs(mURL, aOther.mURL);
+ } else if (mType == NS_STYLE_FILTER_DROP_SHADOW) {
+ return *mDropShadow == *aOther.mDropShadow;
+ } else if (mType != NS_STYLE_FILTER_NONE) {
+ return mFilterParameter == aOther.mFilterParameter;
+ }
+
+ return true;
+}
+
+void
+nsStyleFilter::ReleaseRef()
+{
+ if (mType == NS_STYLE_FILTER_DROP_SHADOW) {
+ NS_ASSERTION(mDropShadow, "expected pointer");
+ mDropShadow->Release();
+ } else if (mType == NS_STYLE_FILTER_URL) {
+ NS_ASSERTION(mURL, "expected pointer");
+ mURL->Release();
+ }
+ mURL = nullptr;
+}
+
+void
+nsStyleFilter::SetFilterParameter(const nsStyleCoord& aFilterParameter,
+ int32_t aType)
+{
+ ReleaseRef();
+ mFilterParameter = aFilterParameter;
+ mType = aType;
+}
+
+bool
+nsStyleFilter::SetURL(css::URLValue* aURL)
+{
+ ReleaseRef();
+ mURL = aURL;
+ mURL->AddRef();
+ mType = NS_STYLE_FILTER_URL;
+ return true;
+}
+
+void
+nsStyleFilter::SetDropShadow(nsCSSShadowArray* aDropShadow)
+{
+ NS_ASSERTION(aDropShadow, "expected pointer");
+ ReleaseRef();
+ mDropShadow = aDropShadow;
+ mDropShadow->AddRef();
+ mType = NS_STYLE_FILTER_DROP_SHADOW;
+}
+
+// --------------------
+// nsStyleSVGReset
+//
+nsStyleSVGReset::nsStyleSVGReset(StyleStructContext aContext)
+ : mMask(nsStyleImageLayers::LayerType::Mask)
+ , mStopColor(NS_RGB(0, 0, 0))
+ , mFloodColor(NS_RGB(0, 0, 0))
+ , mLightingColor(NS_RGB(255, 255, 255))
+ , mStopOpacity(1.0f)
+ , mFloodOpacity(1.0f)
+ , mDominantBaseline(NS_STYLE_DOMINANT_BASELINE_AUTO)
+ , mVectorEffect(NS_STYLE_VECTOR_EFFECT_NONE)
+ , mMaskType(NS_STYLE_MASK_TYPE_LUMINANCE)
+{
+ MOZ_COUNT_CTOR(nsStyleSVGReset);
+}
+
+nsStyleSVGReset::~nsStyleSVGReset()
+{
+ MOZ_COUNT_DTOR(nsStyleSVGReset);
+}
+
+nsStyleSVGReset::nsStyleSVGReset(const nsStyleSVGReset& aSource)
+ : mMask(aSource.mMask)
+ , mClipPath(aSource.mClipPath)
+ , mStopColor(aSource.mStopColor)
+ , mFloodColor(aSource.mFloodColor)
+ , mLightingColor(aSource.mLightingColor)
+ , mStopOpacity(aSource.mStopOpacity)
+ , mFloodOpacity(aSource.mFloodOpacity)
+ , mDominantBaseline(aSource.mDominantBaseline)
+ , mVectorEffect(aSource.mVectorEffect)
+ , mMaskType(aSource.mMaskType)
+{
+ MOZ_COUNT_CTOR(nsStyleSVGReset);
+}
+
+void
+nsStyleSVGReset::Destroy(nsPresContext* aContext)
+{
+ this->~nsStyleSVGReset();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleSVGReset, this);
+}
+
+void
+nsStyleSVGReset::FinishStyle(nsPresContext* aPresContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPresContext->StyleSet()->IsServo());
+
+ mMask.ResolveImages(aPresContext);
+}
+
+nsChangeHint
+nsStyleSVGReset::CalcDifference(const nsStyleSVGReset& aNewData) const
+{
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (mClipPath != aNewData.mClipPath) {
+ hint |= nsChangeHint_UpdateEffects |
+ nsChangeHint_RepaintFrame;
+ // clip-path changes require that we update the PreEffectsBBoxProperty,
+ // which is done during overflow computation.
+ hint |= nsChangeHint_UpdateOverflow;
+ }
+
+ if (mDominantBaseline != aNewData.mDominantBaseline) {
+ // XXXjwatt: why NS_STYLE_HINT_REFLOW? Isn't that excessive?
+ hint |= NS_STYLE_HINT_REFLOW;
+ } else if (mVectorEffect != aNewData.mVectorEffect) {
+ // Stroke currently affects nsSVGPathGeometryFrame::mRect, and
+ // vector-effect affect stroke. As a result we need to reflow if
+ // vector-effect changes in order to have nsSVGPathGeometryFrame::
+ // ReflowSVG called to update its mRect. No intrinsic sizes need
+ // to change so nsChangeHint_NeedReflow is sufficient.
+ hint |= nsChangeHint_NeedReflow |
+ nsChangeHint_NeedDirtyReflow | // XXX remove me: bug 876085
+ nsChangeHint_RepaintFrame;
+ } else if (mStopColor != aNewData.mStopColor ||
+ mFloodColor != aNewData.mFloodColor ||
+ mLightingColor != aNewData.mLightingColor ||
+ mStopOpacity != aNewData.mStopOpacity ||
+ mFloodOpacity != aNewData.mFloodOpacity ||
+ mMaskType != aNewData.mMaskType) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ hint |= mMask.CalcDifference(aNewData.mMask,
+ nsStyleImageLayers::LayerType::Mask);
+
+ return hint;
+}
+
+// nsStyleSVGPaint implementation
+nsStyleSVGPaint::nsStyleSVGPaint(nsStyleSVGPaintType aType)
+ : mType(aType)
+ , mFallbackColor(NS_RGB(0, 0, 0))
+{
+ MOZ_ASSERT(aType == nsStyleSVGPaintType(0) ||
+ aType == eStyleSVGPaintType_None ||
+ aType == eStyleSVGPaintType_Color);
+ mPaint.mColor = NS_RGB(0, 0, 0);
+}
+
+nsStyleSVGPaint::nsStyleSVGPaint(const nsStyleSVGPaint& aSource)
+ : nsStyleSVGPaint(nsStyleSVGPaintType(0))
+{
+ Assign(aSource);
+}
+
+nsStyleSVGPaint::~nsStyleSVGPaint()
+{
+ Reset();
+}
+
+void
+nsStyleSVGPaint::Reset()
+{
+ switch (mType) {
+ case eStyleSVGPaintType_None:
+ break;
+ case eStyleSVGPaintType_Color:
+ mPaint.mColor = NS_RGB(0, 0, 0);
+ break;
+ case eStyleSVGPaintType_Server:
+ mPaint.mPaintServer->Release();
+ mPaint.mPaintServer = nullptr;
+ MOZ_FALLTHROUGH;
+ case eStyleSVGPaintType_ContextFill:
+ case eStyleSVGPaintType_ContextStroke:
+ mFallbackColor = NS_RGB(0, 0, 0);
+ break;
+ }
+ mType = nsStyleSVGPaintType(0);
+}
+
+nsStyleSVGPaint&
+nsStyleSVGPaint::operator=(const nsStyleSVGPaint& aOther)
+{
+ if (this != &aOther) {
+ Assign(aOther);
+ }
+ return *this;
+}
+
+void
+nsStyleSVGPaint::Assign(const nsStyleSVGPaint& aOther)
+{
+ MOZ_ASSERT(aOther.mType != nsStyleSVGPaintType(0),
+ "shouldn't copy uninitialized nsStyleSVGPaint");
+
+ switch (aOther.mType) {
+ case eStyleSVGPaintType_None:
+ SetNone();
+ break;
+ case eStyleSVGPaintType_Color:
+ SetColor(aOther.mPaint.mColor);
+ break;
+ case eStyleSVGPaintType_Server:
+ SetPaintServer(aOther.mPaint.mPaintServer,
+ aOther.mFallbackColor);
+ break;
+ case eStyleSVGPaintType_ContextFill:
+ case eStyleSVGPaintType_ContextStroke:
+ SetContextValue(aOther.mType, aOther.mFallbackColor);
+ break;
+ }
+}
+
+void
+nsStyleSVGPaint::SetNone()
+{
+ Reset();
+ mType = eStyleSVGPaintType_None;
+}
+
+void
+nsStyleSVGPaint::SetContextValue(nsStyleSVGPaintType aType,
+ nscolor aFallbackColor)
+{
+ MOZ_ASSERT(aType == eStyleSVGPaintType_ContextFill ||
+ aType == eStyleSVGPaintType_ContextStroke);
+ Reset();
+ mType = aType;
+ mFallbackColor = aFallbackColor;
+}
+
+void
+nsStyleSVGPaint::SetColor(nscolor aColor)
+{
+ Reset();
+ mType = eStyleSVGPaintType_Color;
+ mPaint.mColor = aColor;
+}
+
+void
+nsStyleSVGPaint::SetPaintServer(css::URLValue* aPaintServer,
+ nscolor aFallbackColor)
+{
+ MOZ_ASSERT(aPaintServer);
+ Reset();
+ mType = eStyleSVGPaintType_Server;
+ mPaint.mPaintServer = aPaintServer;
+ mPaint.mPaintServer->AddRef();
+ mFallbackColor = aFallbackColor;
+}
+
+bool nsStyleSVGPaint::operator==(const nsStyleSVGPaint& aOther) const
+{
+ if (mType != aOther.mType) {
+ return false;
+ }
+ switch (mType) {
+ case eStyleSVGPaintType_Color:
+ return mPaint.mColor == aOther.mPaint.mColor;
+ case eStyleSVGPaintType_Server:
+ return DefinitelyEqualURIs(mPaint.mPaintServer,
+ aOther.mPaint.mPaintServer) &&
+ mFallbackColor == aOther.mFallbackColor;
+ case eStyleSVGPaintType_ContextFill:
+ case eStyleSVGPaintType_ContextStroke:
+ return mFallbackColor == aOther.mFallbackColor;
+ default:
+ MOZ_ASSERT(mType == eStyleSVGPaintType_None,
+ "Unexpected SVG paint type");
+ return true;
+ }
+}
+
+// --------------------
+// nsStylePosition
+//
+nsStylePosition::nsStylePosition(StyleStructContext aContext)
+ : mWidth(eStyleUnit_Auto)
+ , mMinWidth(eStyleUnit_Auto)
+ , mMaxWidth(eStyleUnit_None)
+ , mHeight(eStyleUnit_Auto)
+ , mMinHeight(eStyleUnit_Auto)
+ , mMaxHeight(eStyleUnit_None)
+ , mFlexBasis(eStyleUnit_Auto)
+ , mGridAutoColumnsMin(eStyleUnit_Auto)
+ , mGridAutoColumnsMax(eStyleUnit_Auto)
+ , mGridAutoRowsMin(eStyleUnit_Auto)
+ , mGridAutoRowsMax(eStyleUnit_Auto)
+ , mGridAutoFlow(NS_STYLE_GRID_AUTO_FLOW_ROW)
+ , mBoxSizing(StyleBoxSizing::Content)
+ , mAlignContent(NS_STYLE_ALIGN_NORMAL)
+ , mAlignItems(NS_STYLE_ALIGN_NORMAL)
+ , mAlignSelf(NS_STYLE_ALIGN_AUTO)
+ , mJustifyContent(NS_STYLE_JUSTIFY_NORMAL)
+ , mJustifyItems(NS_STYLE_JUSTIFY_AUTO)
+ , mJustifySelf(NS_STYLE_JUSTIFY_AUTO)
+ , mFlexDirection(NS_STYLE_FLEX_DIRECTION_ROW)
+ , mFlexWrap(NS_STYLE_FLEX_WRAP_NOWRAP)
+ , mObjectFit(NS_STYLE_OBJECT_FIT_FILL)
+ , mOrder(NS_STYLE_ORDER_INITIAL)
+ , mFlexGrow(0.0f)
+ , mFlexShrink(1.0f)
+ , mZIndex(eStyleUnit_Auto)
+ , mGridColumnGap(nscoord(0), nsStyleCoord::CoordConstructor)
+ , mGridRowGap(nscoord(0), nsStyleCoord::CoordConstructor)
+{
+ MOZ_COUNT_CTOR(nsStylePosition);
+
+ // positioning values not inherited
+
+ mObjectPosition.SetInitialPercentValues(0.5f);
+
+ nsStyleCoord autoCoord(eStyleUnit_Auto);
+ NS_FOR_CSS_SIDES(side) {
+ mOffset.Set(side, autoCoord);
+ }
+
+ // 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.
+ // mGridTemplateAreas: nullptr for 'none'
+ // mGridTemplate{Rows,Columns}: false and empty arrays for 'none'
+ // mGrid{Column,Row}{Start,End}: false/0/empty values for 'auto'
+}
+
+nsStylePosition::~nsStylePosition()
+{
+ MOZ_COUNT_DTOR(nsStylePosition);
+}
+
+nsStylePosition::nsStylePosition(const nsStylePosition& aSource)
+ : 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)
+ , mGridAutoColumnsMin(aSource.mGridAutoColumnsMin)
+ , mGridAutoColumnsMax(aSource.mGridAutoColumnsMax)
+ , mGridAutoRowsMin(aSource.mGridAutoRowsMin)
+ , mGridAutoRowsMax(aSource.mGridAutoRowsMax)
+ , mGridAutoFlow(aSource.mGridAutoFlow)
+ , mBoxSizing(aSource.mBoxSizing)
+ , 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)
+ , 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)
+ , mGridColumnGap(aSource.mGridColumnGap)
+ , mGridRowGap(aSource.mGridRowGap)
+{
+ MOZ_COUNT_CTOR(nsStylePosition);
+}
+
+static bool
+IsAutonessEqual(const nsStyleSides& aSides1, const nsStyleSides& aSides2)
+{
+ NS_FOR_CSS_SIDES(side) {
+ if ((aSides1.GetUnit(side) == eStyleUnit_Auto) !=
+ (aSides2.GetUnit(side) == eStyleUnit_Auto)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+nsChangeHint
+nsStylePosition::CalcDifference(const nsStylePosition& aNewData,
+ const nsStyleVisibility* aOldStyleVisibility) const
+{
+ 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 (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;
+ }
+
+ // Properties that apply to flex items:
+ // XXXdholbert These should probably be more targeted (bug 819536)
+ if (mAlignSelf != aNewData.mAlignSelf ||
+ 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.
+ // - align-items can change the sizing of a flex container & the positioning
+ // of its children.
+ // - flex-wrap changes whether a flex container's children are wrapped, which
+ // impacts their sizing/positioning and hence impacts the container's size.
+ if (mAlignItems != aNewData.mAlignItems ||
+ 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 ||
+ mGridAutoColumnsMin != aNewData.mGridAutoColumnsMin ||
+ mGridAutoColumnsMax != aNewData.mGridAutoColumnsMax ||
+ mGridAutoRowsMin != aNewData.mGridAutoRowsMin ||
+ mGridAutoRowsMax != aNewData.mGridAutoRowsMax ||
+ mGridAutoFlow != aNewData.mGridAutoFlow) {
+ 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 ||
+ mGridColumnGap != aNewData.mGridColumnGap ||
+ mGridRowGap != aNewData.mGridRowGap) {
+ 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 != aNewData.mJustifyItems ||
+ mJustifySelf != aNewData.mJustifySelf) {
+ hint |= nsChangeHint_NeedReflow;
+ }
+
+ // '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 aOldStyleVisibility is null, we don't need to bother with any of
+ // these tests, since we know that the element never had its
+ // nsStyleVisibility accessed, which means it couldn't have done
+ // layout.
+ // Note that we pass an nsStyleVisibility here because we don't want
+ // to cause a new struct to be computed during
+ // nsStyleContext::CalcStyleDifference, which can lead to incorrect
+ // style data.
+ // 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, and it's easier to pass the old.
+ if (aOldStyleVisibility) {
+ bool isVertical = WritingMode(aOldStyleVisibility).IsVertical();
+ if (isVertical ? widthChanged : heightChanged) {
+ // Block-size changes can affect descendant intrinsic sizes due to
+ // replaced elements with percentage bsizes in descendants which
+ // also have percentage bsizes. This is handled via
+ // nsChangeHint_UpdateComputedBSize which clears intrinsic sizes
+ // for frames that have such replaced elements.
+ hint |= nsChangeHint_NeedReflow |
+ nsChangeHint_UpdateComputedBSize |
+ nsChangeHint_ReflowChangesSizeOrPosition;
+ }
+
+ if (isVertical ? heightChanged : widthChanged) {
+ // None of our inline-size differences can affect descendant
+ // intrinsic sizes and none of them need to force children to
+ // reflow.
+ hint |= nsChangeHint_AllReflowHints &
+ ~(nsChangeHint_ClearDescendantIntrinsics |
+ nsChangeHint_NeedDirtyReflow);
+ }
+ } else {
+ if (widthChanged || heightChanged) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+ }
+
+ // 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_AllReflowHints;
+ }
+ }
+ return hint;
+}
+
+/* static */ bool
+nsStylePosition::WidthCoordDependsOnContainer(const nsStyleCoord &aCoord)
+{
+ return aCoord.HasPercent() ||
+ (aCoord.GetUnit() == eStyleUnit_Enumerated &&
+ (aCoord.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT ||
+ aCoord.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE));
+}
+
+uint8_t
+nsStylePosition::UsedAlignSelf(nsStyleContext* aParent) const
+{
+ if (mAlignSelf != NS_STYLE_ALIGN_AUTO) {
+ return mAlignSelf;
+ }
+ if (MOZ_LIKELY(aParent)) {
+ auto parentAlignItems = aParent->StylePosition()->mAlignItems;
+ MOZ_ASSERT(!(parentAlignItems & NS_STYLE_ALIGN_LEGACY),
+ "align-items can't have 'legacy'");
+ return parentAlignItems;
+ }
+ return NS_STYLE_ALIGN_NORMAL;
+}
+
+uint8_t
+nsStylePosition::ComputedJustifyItems(nsStyleContext* aParent) const
+{
+ if (mJustifyItems != NS_STYLE_JUSTIFY_AUTO) {
+ return mJustifyItems;
+ }
+ if (MOZ_LIKELY(aParent)) {
+ auto inheritedJustifyItems =
+ aParent->StylePosition()->ComputedJustifyItems(aParent->GetParent());
+ // "If the inherited value of justify-items includes the 'legacy' keyword,
+ // 'auto' computes to the inherited value." Otherwise, 'normal'.
+ if (inheritedJustifyItems & NS_STYLE_JUSTIFY_LEGACY) {
+ return inheritedJustifyItems;
+ }
+ }
+ return NS_STYLE_JUSTIFY_NORMAL;
+}
+
+uint8_t
+nsStylePosition::UsedJustifySelf(nsStyleContext* aParent) const
+{
+ if (mJustifySelf != NS_STYLE_JUSTIFY_AUTO) {
+ return mJustifySelf;
+ }
+ if (MOZ_LIKELY(aParent)) {
+ auto inheritedJustifyItems = aParent->StylePosition()->
+ ComputedJustifyItems(aParent->GetParent());
+ return inheritedJustifyItems & ~NS_STYLE_JUSTIFY_LEGACY;
+ }
+ return NS_STYLE_JUSTIFY_NORMAL;
+}
+
+// --------------------
+// nsStyleTable
+//
+
+nsStyleTable::nsStyleTable(StyleStructContext aContext)
+ : mLayoutStrategy(NS_STYLE_TABLE_LAYOUT_AUTO)
+ , mSpan(1)
+{
+ MOZ_COUNT_CTOR(nsStyleTable);
+}
+
+nsStyleTable::~nsStyleTable()
+{
+ MOZ_COUNT_DTOR(nsStyleTable);
+}
+
+nsStyleTable::nsStyleTable(const nsStyleTable& aSource)
+ : mLayoutStrategy(aSource.mLayoutStrategy)
+ , mSpan(aSource.mSpan)
+{
+ MOZ_COUNT_CTOR(nsStyleTable);
+}
+
+nsChangeHint
+nsStyleTable::CalcDifference(const nsStyleTable& aNewData) const
+{
+ if (mSpan != aNewData.mSpan ||
+ mLayoutStrategy != aNewData.mLayoutStrategy) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ return nsChangeHint(0);
+}
+
+// -----------------------
+// nsStyleTableBorder
+
+nsStyleTableBorder::nsStyleTableBorder(StyleStructContext aContext)
+ : mBorderSpacingCol(0)
+ , mBorderSpacingRow(0)
+ , mBorderCollapse(NS_STYLE_BORDER_SEPARATE)
+ , mCaptionSide(NS_STYLE_CAPTION_SIDE_TOP)
+ , mEmptyCells(NS_STYLE_TABLE_EMPTY_CELLS_SHOW)
+{
+ MOZ_COUNT_CTOR(nsStyleTableBorder);
+}
+
+nsStyleTableBorder::~nsStyleTableBorder()
+{
+ MOZ_COUNT_DTOR(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;
+ } else {
+ return NS_STYLE_HINT_REFLOW;
+ }
+}
+
+// --------------------
+// nsStyleColor
+//
+
+nsStyleColor::nsStyleColor(StyleStructContext aContext)
+ : mColor(aContext.DefaultColor())
+{
+ MOZ_COUNT_CTOR(nsStyleColor);
+}
+
+nsStyleColor::nsStyleColor(const nsStyleColor& aSource)
+ : mColor(aSource.mColor)
+{
+ MOZ_COUNT_CTOR(nsStyleColor);
+}
+
+nsChangeHint
+nsStyleColor::CalcDifference(const nsStyleColor& aNewData) const
+{
+ if (mColor == aNewData.mColor) {
+ return nsChangeHint(0);
+ }
+ return nsChangeHint_RepaintFrame;
+}
+
+// --------------------
+// nsStyleGradient
+//
+bool
+nsStyleGradient::operator==(const nsStyleGradient& aOther) const
+{
+ MOZ_ASSERT(mSize == NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER ||
+ mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR,
+ "incorrect combination of shape and size");
+ MOZ_ASSERT(aOther.mSize == NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER ||
+ aOther.mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR,
+ "incorrect combination of shape and size");
+
+ if (mShape != aOther.mShape ||
+ mSize != aOther.mSize ||
+ mRepeating != aOther.mRepeating ||
+ mLegacySyntax != aOther.mLegacySyntax ||
+ mBgPosX != aOther.mBgPosX ||
+ mBgPosY != aOther.mBgPosY ||
+ mAngle != aOther.mAngle ||
+ mRadiusX != aOther.mRadiusX ||
+ mRadiusY != aOther.mRadiusY) {
+ return false;
+ }
+
+ if (mStops.Length() != aOther.mStops.Length()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ const auto& stop1 = mStops[i];
+ const auto& stop2 = aOther.mStops[i];
+ if (stop1.mLocation != stop2.mLocation ||
+ stop1.mIsInterpolationHint != stop2.mIsInterpolationHint ||
+ (!stop1.mIsInterpolationHint && stop1.mColor != stop2.mColor)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsStyleGradient::nsStyleGradient()
+ : mShape(NS_STYLE_GRADIENT_SHAPE_LINEAR)
+ , mSize(NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER)
+ , mRepeating(false)
+ , mLegacySyntax(false)
+{
+}
+
+bool
+nsStyleGradient::IsOpaque()
+{
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ if (NS_GET_A(mStops[i].mColor) < 255) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+nsStyleGradient::HasCalc()
+{
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ if (mStops[i].mLocation.IsCalcUnit()) {
+ return true;
+ }
+ }
+ return mBgPosX.IsCalcUnit() || mBgPosY.IsCalcUnit() || mAngle.IsCalcUnit() ||
+ mRadiusX.IsCalcUnit() || mRadiusY.IsCalcUnit();
+}
+
+
+// --------------------
+// nsStyleImageRequest
+
+/**
+ * Runnable to release the nsStyleImageRequest's mRequestProxy,
+ * mImageValue and mImageValue on the main thread, and to perform
+ * any necessary unlocking and untracking of the image.
+ */
+class StyleImageRequestCleanupTask : public mozilla::Runnable
+{
+public:
+ typedef nsStyleImageRequest::Mode Mode;
+
+ StyleImageRequestCleanupTask(Mode aModeFlags,
+ already_AddRefed<imgRequestProxy> aRequestProxy,
+ already_AddRefed<css::ImageValue> aImageValue,
+ already_AddRefed<ImageTracker> aImageTracker)
+ : mModeFlags(aModeFlags)
+ , mRequestProxy(aRequestProxy)
+ , mImageValue(aImageValue)
+ , mImageTracker(aImageTracker)
+ {
+ }
+
+ NS_IMETHOD Run() final
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mRequestProxy) {
+ return NS_OK;
+ }
+
+ if (mModeFlags & Mode::Track) {
+ MOZ_ASSERT(mImageTracker);
+ mImageTracker->Remove(mRequestProxy);
+ } else {
+ mRequestProxy->UnlockImage();
+ }
+
+ if (mModeFlags & Mode::Discard) {
+ mRequestProxy->RequestDiscard();
+ }
+
+ return NS_OK;
+ }
+
+protected:
+ virtual ~StyleImageRequestCleanupTask() { MOZ_ASSERT(NS_IsMainThread()); }
+
+private:
+ Mode mModeFlags;
+ // 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;
+ RefPtr<css::ImageValue> mImageValue;
+ RefPtr<ImageTracker> mImageTracker;
+};
+
+nsStyleImageRequest::nsStyleImageRequest(Mode aModeFlags,
+ imgRequestProxy* aRequestProxy,
+ css::ImageValue* aImageValue,
+ ImageTracker* aImageTracker)
+ : mRequestProxy(aRequestProxy)
+ , mImageValue(aImageValue)
+ , mImageTracker(aImageTracker)
+ , mModeFlags(aModeFlags)
+ , mResolved(true)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequestProxy);
+ MOZ_ASSERT(aImageValue);
+ MOZ_ASSERT(!!(aModeFlags & Mode::Track) == !!aImageTracker);
+
+ MaybeTrackAndLock();
+}
+
+nsStyleImageRequest::nsStyleImageRequest(
+ Mode aModeFlags,
+ nsStringBuffer* aURLBuffer,
+ already_AddRefed<PtrHolder<nsIURI>> aBaseURI,
+ already_AddRefed<PtrHolder<nsIURI>> aReferrer,
+ already_AddRefed<PtrHolder<nsIPrincipal>> aPrincipal)
+ : mModeFlags(aModeFlags)
+ , mResolved(false)
+{
+ mImageValue = new css::ImageValue(aURLBuffer, Move(aBaseURI),
+ Move(aReferrer), Move(aPrincipal));
+}
+
+nsStyleImageRequest::~nsStyleImageRequest()
+{
+ // We may or may not be being destroyed on the main thread. To clean
+ // up, we must untrack and unlock the image (depending on mModeFlags),
+ // and release mRequestProxy and mImageValue, all on the main thread.
+ {
+ RefPtr<StyleImageRequestCleanupTask> task =
+ new StyleImageRequestCleanupTask(mModeFlags,
+ mRequestProxy.forget(),
+ mImageValue.forget(),
+ mImageTracker.forget());
+ if (NS_IsMainThread()) {
+ task->Run();
+ } else {
+ NS_DispatchToMainThread(task.forget());
+ }
+ }
+
+ MOZ_ASSERT(!mRequestProxy);
+ MOZ_ASSERT(!mImageValue);
+ MOZ_ASSERT(!mImageTracker);
+}
+
+bool
+nsStyleImageRequest::Resolve(nsPresContext* aPresContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsResolved(), "already resolved");
+
+ mResolved = true;
+
+ // For now, just have unique nsCSSValue/ImageValue objects. We should
+ // really store the ImageValue on the Servo specified value, so that we can
+ // share imgRequestProxys that come from the same rule in the same
+ // document.
+ mImageValue->Initialize(aPresContext->Document());
+
+ nsCSSValue value;
+ value.SetImageValue(mImageValue);
+ mRequestProxy = value.GetPossiblyStaticImageValue(aPresContext->Document(),
+ aPresContext);
+
+ if (!mRequestProxy) {
+ // The URL resolution or image load failed.
+ return false;
+ }
+
+ if (mModeFlags & Mode::Track) {
+ mImageTracker = aPresContext->Document()->ImageTracker();
+ }
+
+ MaybeTrackAndLock();
+ return true;
+}
+
+void
+nsStyleImageRequest::MaybeTrackAndLock()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(IsResolved());
+ MOZ_ASSERT(mRequestProxy);
+
+ if (mModeFlags & Mode::Track) {
+ MOZ_ASSERT(mImageTracker);
+ mImageTracker->Add(mRequestProxy);
+ } else {
+ MOZ_ASSERT(!mImageTracker);
+ mRequestProxy->LockImage();
+ }
+}
+
+bool
+nsStyleImageRequest::DefinitelyEquals(const nsStyleImageRequest& aOther) const
+{
+ return DefinitelyEqualURIs(mImageValue, aOther.mImageValue);
+}
+
+// --------------------
+// CachedBorderImageData
+//
+void
+CachedBorderImageData::SetCachedSVGViewportSize(
+ const mozilla::Maybe<nsSize>& aSVGViewportSize)
+{
+ mCachedSVGViewportSize = aSVGViewportSize;
+}
+
+const mozilla::Maybe<nsSize>&
+CachedBorderImageData::GetCachedSVGViewportSize()
+{
+ return mCachedSVGViewportSize;
+}
+
+void
+CachedBorderImageData::PurgeCachedImages()
+{
+ mSubImages.Clear();
+}
+
+void
+CachedBorderImageData::SetSubImage(uint8_t aIndex, imgIContainer* aSubImage)
+{
+ mSubImages.ReplaceObjectAt(aSubImage, aIndex);
+}
+
+imgIContainer*
+CachedBorderImageData::GetSubImage(uint8_t aIndex)
+{
+ imgIContainer* subImage = nullptr;
+ if (aIndex < mSubImages.Count())
+ subImage = mSubImages[aIndex];
+ return subImage;
+}
+
+// --------------------
+// nsStyleImage
+//
+
+nsStyleImage::nsStyleImage()
+ : mType(eStyleImageType_Null)
+ , mCropRect(nullptr)
+{
+ MOZ_COUNT_CTOR(nsStyleImage);
+}
+
+nsStyleImage::~nsStyleImage()
+{
+ MOZ_COUNT_DTOR(nsStyleImage);
+ if (mType != eStyleImageType_Null) {
+ SetNull();
+ }
+}
+
+nsStyleImage::nsStyleImage(const nsStyleImage& aOther)
+ : mType(eStyleImageType_Null)
+ , mCropRect(nullptr)
+{
+ // We need our own copy constructor because we don't want
+ // to copy the reference count
+ MOZ_COUNT_CTOR(nsStyleImage);
+ DoCopy(aOther);
+}
+
+nsStyleImage&
+nsStyleImage::operator=(const nsStyleImage& aOther)
+{
+ if (this != &aOther) {
+ DoCopy(aOther);
+ }
+
+ return *this;
+}
+
+void
+nsStyleImage::DoCopy(const nsStyleImage& aOther)
+{
+ SetNull();
+
+ if (aOther.mType == eStyleImageType_Image) {
+ SetImageRequest(do_AddRef(aOther.mImage));
+ } else if (aOther.mType == eStyleImageType_Gradient) {
+ SetGradientData(aOther.mGradient);
+ } else if (aOther.mType == eStyleImageType_Element) {
+ SetElementId(aOther.mElementId);
+ }
+
+ UniquePtr<nsStyleSides> cropRectCopy;
+ if (aOther.mCropRect) {
+ cropRectCopy = MakeUnique<nsStyleSides>(*aOther.mCropRect.get());
+ }
+ SetCropRect(Move(cropRectCopy));
+}
+
+void
+nsStyleImage::SetNull()
+{
+ if (mType == eStyleImageType_Gradient) {
+ mGradient->Release();
+ } else if (mType == eStyleImageType_Image) {
+ NS_RELEASE(mImage);
+ } else if (mType == eStyleImageType_Element) {
+ free(mElementId);
+ }
+
+ mType = eStyleImageType_Null;
+ mCropRect = nullptr;
+}
+
+void
+nsStyleImage::SetImageRequest(already_AddRefed<nsStyleImageRequest> aImage)
+{
+ RefPtr<nsStyleImageRequest> image = aImage;
+
+ if (mType != eStyleImageType_Null) {
+ SetNull();
+ }
+
+ if (image) {
+ mImage = image.forget().take();
+ mType = eStyleImageType_Image;
+ }
+ if (mCachedBIData) {
+ mCachedBIData->PurgeCachedImages();
+ }
+}
+
+void
+nsStyleImage::SetGradientData(nsStyleGradient* aGradient)
+{
+ if (aGradient) {
+ aGradient->AddRef();
+ }
+
+ if (mType != eStyleImageType_Null) {
+ SetNull();
+ }
+
+ if (aGradient) {
+ mGradient = aGradient;
+ mType = eStyleImageType_Gradient;
+ }
+}
+
+void
+nsStyleImage::SetElementId(const char16_t* aElementId)
+{
+ if (mType != eStyleImageType_Null) {
+ SetNull();
+ }
+
+ if (aElementId) {
+ mElementId = NS_strdup(aElementId);
+ mType = eStyleImageType_Element;
+ }
+}
+
+void
+nsStyleImage::SetCropRect(UniquePtr<nsStyleSides> aCropRect)
+{
+ mCropRect = Move(aCropRect);
+}
+
+static int32_t
+ConvertToPixelCoord(const nsStyleCoord& aCoord, int32_t aPercentScale)
+{
+ double pixelValue;
+ switch (aCoord.GetUnit()) {
+ case eStyleUnit_Percent:
+ pixelValue = aCoord.GetPercentValue() * aPercentScale;
+ break;
+ case eStyleUnit_Factor:
+ pixelValue = aCoord.GetFactorValue();
+ break;
+ default:
+ NS_NOTREACHED("unexpected unit for image crop rect");
+ return 0;
+ }
+ MOZ_ASSERT(pixelValue >= 0, "we ensured non-negative while parsing");
+ pixelValue = std::min(pixelValue, double(INT32_MAX)); // avoid overflow
+ return NS_lround(pixelValue);
+}
+
+bool
+nsStyleImage::ComputeActualCropRect(nsIntRect& aActualCropRect,
+ bool* aIsEntireImage) const
+{
+ if (mType != eStyleImageType_Image) {
+ return false;
+ }
+
+ imgRequestProxy* req = GetImageData();
+ if (!req) {
+ return false;
+ }
+
+ nsCOMPtr<imgIContainer> imageContainer;
+ req->GetImage(getter_AddRefs(imageContainer));
+ if (!imageContainer) {
+ return false;
+ }
+
+ nsIntSize imageSize;
+ imageContainer->GetWidth(&imageSize.width);
+ imageContainer->GetHeight(&imageSize.height);
+ if (imageSize.width <= 0 || imageSize.height <= 0) {
+ return false;
+ }
+
+ int32_t left = ConvertToPixelCoord(mCropRect->GetLeft(), imageSize.width);
+ int32_t top = ConvertToPixelCoord(mCropRect->GetTop(), imageSize.height);
+ int32_t right = ConvertToPixelCoord(mCropRect->GetRight(), imageSize.width);
+ int32_t bottom = ConvertToPixelCoord(mCropRect->GetBottom(), imageSize.height);
+
+ // IntersectRect() returns an empty rect if we get negative width or height
+ nsIntRect cropRect(left, top, right - left, bottom - top);
+ nsIntRect imageRect(nsIntPoint(0, 0), imageSize);
+ aActualCropRect.IntersectRect(imageRect, cropRect);
+
+ if (aIsEntireImage) {
+ *aIsEntireImage = aActualCropRect.IsEqualInterior(imageRect);
+ }
+ return true;
+}
+
+nsresult
+nsStyleImage::StartDecoding() const
+{
+ if (mType == eStyleImageType_Image) {
+ imgRequestProxy* req = GetImageData();
+ if (!req) {
+ return NS_ERROR_FAILURE;
+ }
+ return req->StartDecoding();
+ }
+ return NS_OK;
+}
+
+bool
+nsStyleImage::IsOpaque() const
+{
+ if (!IsComplete()) {
+ return false;
+ }
+
+ if (mType == eStyleImageType_Gradient) {
+ return mGradient->IsOpaque();
+ }
+
+ if (mType == eStyleImageType_Element) {
+ return false;
+ }
+
+ MOZ_ASSERT(mType == eStyleImageType_Image, "unexpected image type");
+ MOZ_ASSERT(GetImageData(), "should've returned earlier above");
+
+ nsCOMPtr<imgIContainer> imageContainer;
+ GetImageData()->GetImage(getter_AddRefs(imageContainer));
+ MOZ_ASSERT(imageContainer, "IsComplete() said image container is ready");
+
+ // Check if the crop region of the image is opaque.
+ if (imageContainer->WillDrawOpaqueNow()) {
+ if (!mCropRect) {
+ return true;
+ }
+
+ // Must make sure if mCropRect contains at least a pixel.
+ // XXX Is this optimization worth it? Maybe I should just return false.
+ nsIntRect actualCropRect;
+ bool rv = ComputeActualCropRect(actualCropRect);
+ NS_ASSERTION(rv, "ComputeActualCropRect() can not fail here");
+ return rv && !actualCropRect.IsEmpty();
+ }
+
+ return false;
+}
+
+bool
+nsStyleImage::IsComplete() const
+{
+ switch (mType) {
+ case eStyleImageType_Null:
+ return false;
+ case eStyleImageType_Gradient:
+ case eStyleImageType_Element:
+ return true;
+ case eStyleImageType_Image: {
+ imgRequestProxy* req = GetImageData();
+ 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);
+ }
+ default:
+ NS_NOTREACHED("unexpected image type");
+ return false;
+ }
+}
+
+bool
+nsStyleImage::IsLoaded() const
+{
+ switch (mType) {
+ case eStyleImageType_Null:
+ return false;
+ case eStyleImageType_Gradient:
+ case eStyleImageType_Element:
+ return true;
+ case eStyleImageType_Image: {
+ imgRequestProxy* req = GetImageData();
+ if (!req) {
+ return false;
+ }
+ uint32_t status = imgIRequest::STATUS_ERROR;
+ return NS_SUCCEEDED(req->GetImageStatus(&status)) &&
+ !(status & imgIRequest::STATUS_ERROR) &&
+ (status & imgIRequest::STATUS_LOAD_COMPLETE);
+ }
+ default:
+ NS_NOTREACHED("unexpected image type");
+ return false;
+ }
+}
+
+static inline bool
+EqualRects(const UniquePtr<nsStyleSides>& aRect1, const UniquePtr<nsStyleSides>& aRect2)
+{
+ return aRect1 == aRect2 || /* handles null== null, and optimize */
+ (aRect1 && aRect2 && *aRect1 == *aRect2);
+}
+
+bool
+nsStyleImage::operator==(const nsStyleImage& aOther) const
+{
+ if (mType != aOther.mType) {
+ return false;
+ }
+
+ if (!EqualRects(mCropRect, aOther.mCropRect)) {
+ return false;
+ }
+
+ if (mType == eStyleImageType_Image) {
+ return DefinitelyEqualImages(mImage, aOther.mImage);
+ }
+
+ if (mType == eStyleImageType_Gradient) {
+ return *mGradient == *aOther.mGradient;
+ }
+
+ if (mType == eStyleImageType_Element) {
+ return NS_strcmp(mElementId, aOther.mElementId) == 0;
+ }
+
+ return true;
+}
+
+void
+nsStyleImage::PurgeCacheForViewportChange(
+ const mozilla::Maybe<nsSize>& aSVGViewportSize,
+ const bool aHasIntrinsicRatio) const
+{
+ EnsureCachedBIData();
+
+ // If we're redrawing with a different viewport-size than we used for our
+ // cached subimages, then we can't trust that our subimages are valid;
+ // any percent sizes/positions in our SVG doc may be different now. Purge!
+ // (We don't have to purge if the SVG document has an intrinsic ratio,
+ // though, because the actual size of elements in SVG documant's coordinate
+ // axis are fixed in this case.)
+ if (aSVGViewportSize != mCachedBIData->GetCachedSVGViewportSize() &&
+ !aHasIntrinsicRatio) {
+ mCachedBIData->PurgeCachedImages();
+ mCachedBIData->SetCachedSVGViewportSize(aSVGViewportSize);
+ }
+}
+
+// --------------------
+// 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
+};
+
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+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
+};
+#endif
+
+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)
+{
+ MOZ_COUNT_CTOR(nsStyleImageLayers);
+
+ // 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) // deep copy
+{
+ MOZ_COUNT_CTOR(nsStyleImageLayers);
+ // If the deep copy of mLayers failed, truncate the counts.
+ uint32_t count = mLayers.Length();
+ if (count != aSource.mLayers.Length()) {
+ NS_WARNING("truncating counts due to out-of-memory");
+ mAttachmentCount = std::max(mAttachmentCount, count);
+ mClipCount = std::max(mClipCount, count);
+ mOriginCount = std::max(mOriginCount, count);
+ mRepeatCount = std::max(mRepeatCount, count);
+ mPositionXCount = std::max(mPositionXCount, count);
+ mPositionYCount = std::max(mPositionYCount, count);
+ mImageCount = std::max(mImageCount, count);
+ mSizeCount = std::max(mSizeCount, count);
+ mMaskModeCount = std::max(mMaskModeCount, count);
+ mBlendModeCount = std::max(mBlendModeCount, count);
+ mCompositeCount = std::max(mCompositeCount, count);
+ }
+}
+
+nsChangeHint
+nsStyleImageLayers::CalcDifference(const nsStyleImageLayers& aNewLayers,
+ nsStyleImageLayers::LayerType aType) const
+{
+ nsChangeHint hint = nsChangeHint(0);
+
+ const nsStyleImageLayers& moreLayers =
+ mImageCount > aNewLayers.mImageCount ?
+ *this : aNewLayers;
+ const nsStyleImageLayers& lessLayers =
+ mImageCount > aNewLayers.mImageCount ?
+ aNewLayers : *this;
+
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, moreLayers) {
+ if (i < lessLayers.mImageCount) {
+ nsChangeHint layerDifference =
+ moreLayers.mLayers[i].CalcDifference(lessLayers.mLayers[i]);
+ hint |= layerDifference;
+ if (layerDifference &&
+ ((moreLayers.mLayers[i].mImage.GetType() == eStyleImageType_Element) ||
+ (lessLayers.mLayers[i].mImage.GetType() == eStyleImageType_Element))) {
+ hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
+ }
+ } else {
+ hint |= nsChangeHint_RepaintFrame;
+ if (moreLayers.mLayers[i].mImage.GetType() == eStyleImageType_Element) {
+ hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
+ }
+ }
+ }
+
+ if (aType == nsStyleImageLayers::LayerType::Mask &&
+ mImageCount != aNewLayers.mImageCount) {
+ hint |= nsChangeHint_UpdateEffects;
+ }
+
+ if (hint) {
+ return hint;
+ }
+
+ 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;
+}
+
+bool
+nsStyleImageLayers::HasLayerWithImage() const
+{
+ for (uint32_t i = 0; i < mImageCount; i++) {
+ // mLayers[i].mSourceURI can be nullptr if mask-image prop value is
+ // <element-reference> or <gradient>
+ // mLayers[i].mImage can be empty if mask-image prop value is a reference
+ // to SVG mask element.
+ // So we need to test both mSourceURI and mImage.
+ if ((mLayers[i].mSourceURI && mLayers[i].mSourceURI->GetURI()) ||
+ !mLayers[i].mImage.IsEmpty()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+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;
+
+ uint32_t count = mLayers.Length();
+ if (count != aOther.mLayers.Length()) {
+ NS_WARNING("truncating counts due to out-of-memory");
+ mAttachmentCount = std::max(mAttachmentCount, count);
+ mClipCount = std::max(mClipCount, count);
+ mOriginCount = std::max(mOriginCount, count);
+ mRepeatCount = std::max(mRepeatCount, count);
+ mPositionXCount = std::max(mPositionXCount, count);
+ mPositionYCount = std::max(mPositionYCount, count);
+ mImageCount = std::max(mImageCount, count);
+ mSizeCount = std::max(mSizeCount, count);
+ mMaskModeCount = std::max(mMaskModeCount, count);
+ mBlendModeCount = std::max(mBlendModeCount, count);
+ mCompositeCount = std::max(mCompositeCount, count);
+ }
+
+ return *this;
+}
+
+bool
+nsStyleImageLayers::IsInitialPositionForLayerType(Position aPosition, LayerType aType)
+{
+ if (aPosition.mXPosition.mPercent == 0.0f &&
+ aPosition.mXPosition.mLength == 0 &&
+ aPosition.mXPosition.mHasPercent &&
+ aPosition.mYPosition.mPercent == 0.0f &&
+ aPosition.mYPosition.mLength == 0 &&
+ aPosition.mYPosition.mHasPercent) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+Position::SetInitialPercentValues(float aPercentVal)
+{
+ mXPosition.mPercent = aPercentVal;
+ mXPosition.mLength = 0;
+ mXPosition.mHasPercent = true;
+ mYPosition.mPercent = aPercentVal;
+ mYPosition.mLength = 0;
+ mYPosition.mHasPercent = true;
+}
+
+void
+Position::SetInitialZeroValues()
+{
+ mXPosition.mPercent = 0;
+ mXPosition.mLength = 0;
+ mXPosition.mHasPercent = false;
+ mYPosition.mPercent = 0;
+ mYPosition.mLength = 0;
+ mYPosition.mHasPercent = false;
+}
+
+bool
+nsStyleImageLayers::Size::DependsOnPositioningAreaSize(const nsStyleImage& aImage) const
+{
+ MOZ_ASSERT(aImage.GetType() != eStyleImageType_Null,
+ "caller should have handled this");
+
+ // If either dimension contains a non-zero percentage, rendering for that
+ // dimension straightforwardly depends on frame size.
+ if ((mWidthType == eLengthPercentage && mWidth.mPercent != 0.0f) ||
+ (mHeightType == eLengthPercentage && mHeight.mPercent != 0.0f)) {
+ return true;
+ }
+
+ // So too for contain and cover.
+ if (mWidthType == eContain || mWidthType == eCover) {
+ return true;
+ }
+
+ // If both dimensions are fixed lengths, there's no dependency.
+ if (mWidthType == eLengthPercentage && mHeightType == eLengthPercentage) {
+ return false;
+ }
+
+ MOZ_ASSERT((mWidthType == eLengthPercentage && mHeightType == eAuto) ||
+ (mWidthType == eAuto && mHeightType == eLengthPercentage) ||
+ (mWidthType == eAuto && mHeightType == eAuto),
+ "logic error");
+
+ nsStyleImageType type = aImage.GetType();
+
+ // 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 (type == eStyleImageType_Gradient) {
+ 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 (type == eStyleImageType_Element) {
+ return true;
+ }
+
+ if (type == eStyleImageType_Image) {
+ nsCOMPtr<imgIContainer> imgContainer;
+ if (imgRequestProxy* req = aImage.GetImageData()) {
+ req->GetImage(getter_AddRefs(imgContainer));
+ }
+ if (imgContainer) {
+ CSSIntSize imageSize;
+ nsSize imageRatio;
+ bool hasWidth, hasHeight;
+ nsLayoutUtils::ComputeSizeForDrawing(imgContainer, 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 != nsSize(0, 0)) {
+ return mWidthType == mHeightType;
+ }
+
+ // Otherwise, rendering depends on frame size when the image dimensions
+ // and background-size don't complement each other.
+ return !(hasWidth && mHeightType == eLengthPercentage) &&
+ !(hasHeight && mWidthType == eLengthPercentage);
+ }
+ } else {
+ NS_NOTREACHED("missed an enum value");
+ }
+
+ // Passed the gauntlet: no dependency.
+ return false;
+}
+
+void
+nsStyleImageLayers::Size::SetInitialValues()
+{
+ mWidthType = mHeightType = eAuto;
+}
+
+bool
+nsStyleImageLayers::Size::operator==(const Size& aOther) const
+{
+ MOZ_ASSERT(mWidthType < eDimensionType_COUNT,
+ "bad mWidthType for this");
+ MOZ_ASSERT(mHeightType < eDimensionType_COUNT,
+ "bad mHeightType for this");
+ MOZ_ASSERT(aOther.mWidthType < eDimensionType_COUNT,
+ "bad mWidthType for aOther");
+ MOZ_ASSERT(aOther.mHeightType < eDimensionType_COUNT,
+ "bad mHeightType for aOther");
+
+ return mWidthType == aOther.mWidthType &&
+ mHeightType == aOther.mHeightType &&
+ (mWidthType != eLengthPercentage || mWidth == aOther.mWidth) &&
+ (mHeightType != eLengthPercentage || mHeight == aOther.mHeight);
+}
+
+nsStyleImageLayers::Layer::Layer()
+ : mClip(NS_STYLE_IMAGELAYER_CLIP_BORDER)
+ , mAttachment(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL)
+ , mBlendMode(NS_STYLE_BLEND_NORMAL)
+ , mComposite(NS_STYLE_MASK_COMPOSITE_ADD)
+ , mMaskMode(NS_STYLE_MASK_MODE_MATCH_SOURCE)
+{
+ mImage.SetNull();
+ mSize.SetInitialValues();
+}
+
+nsStyleImageLayers::Layer::~Layer()
+{
+}
+
+void
+nsStyleImageLayers::Layer::Initialize(nsStyleImageLayers::LayerType aType)
+{
+ mRepeat.SetInitialValues();
+
+ mPosition.SetInitialPercentValues(0.0f);
+
+ if (aType == LayerType::Background) {
+ mOrigin = NS_STYLE_IMAGELAYER_ORIGIN_PADDING;
+ } else {
+ MOZ_ASSERT(aType == LayerType::Mask, "unsupported layer type.");
+ mOrigin = NS_STYLE_IMAGELAYER_ORIGIN_BORDER;
+ }
+}
+
+bool
+nsStyleImageLayers::Layer::RenderingMightDependOnPositioningAreaSizeChange() const
+{
+ // Do we even have an image?
+ if (mImage.IsEmpty()) {
+ return false;
+ }
+
+ return mPosition.DependsOnPositioningAreaSize() ||
+ mSize.DependsOnPositioningAreaSize(mImage) ||
+ 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 &&
+ DefinitelyEqualURIs(mSourceURI, aOther.mSourceURI);
+}
+
+nsChangeHint
+nsStyleImageLayers::Layer::CalcDifference(const nsStyleImageLayers::Layer& aNewLayer) const
+{
+ nsChangeHint hint = nsChangeHint(0);
+ if (!DefinitelyEqualURIs(mSourceURI, aNewLayer.mSourceURI)) {
+ hint |= nsChangeHint_RepaintFrame | nsChangeHint_UpdateEffects;
+
+ // If Layer::mSourceURI links to a SVG mask, it has a fragment. Not vice
+ // versa. Here are examples of URI contains a fragment, two of them link
+ // to a SVG mask:
+ // mask:url(a.svg#maskID); // The fragment of this URI is an ID of a mask
+ // // element in a.svg.
+ // mask:url(#localMaskID); // The fragment of this URI is an ID of a mask
+ // // element in local document.
+ // mask:url(b.svg#viewBoxID); // The fragment of this URI is an ID of a
+ // // viewbox defined in b.svg.
+ // That is, if mSourceURI has a fragment, it may link to a SVG mask; If
+ // not, it "must" not link to a SVG mask.
+ bool maybeSVGMask = false;
+ if (mSourceURI) {
+ if (mSourceURI->IsLocalRef()) {
+ maybeSVGMask = true;
+ } else if (mSourceURI->GetURI()) {
+ mSourceURI->GetURI()->GetHasRef(&maybeSVGMask);
+ }
+ }
+
+ if (!maybeSVGMask) {
+ if (aNewLayer.mSourceURI) {
+ if (aNewLayer.mSourceURI->IsLocalRef()) {
+ maybeSVGMask = true;
+ } else if (aNewLayer.mSourceURI->GetURI()) {
+ aNewLayer.mSourceURI->GetURI()->GetHasRef(&maybeSVGMask);
+ }
+ }
+ }
+
+ // Return nsChangeHint_UpdateOverflow if either URI might link to an SVG
+ // mask.
+ if (maybeSVGMask) {
+ // Mask changes require that we update the PreEffectsBBoxProperty,
+ // which is done during overflow computation.
+ hint |= nsChangeHint_UpdateOverflow;
+ }
+ } 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(StyleStructContext aContext)
+ : mImage(nsStyleImageLayers::LayerType::Background)
+ , mBackgroundColor(NS_RGBA(0, 0, 0, 0))
+{
+ MOZ_COUNT_CTOR(nsStyleBackground);
+}
+
+nsStyleBackground::nsStyleBackground(const nsStyleBackground& aSource)
+ : mImage(aSource.mImage)
+ , mBackgroundColor(aSource.mBackgroundColor)
+{
+ MOZ_COUNT_CTOR(nsStyleBackground);
+}
+
+nsStyleBackground::~nsStyleBackground()
+{
+ MOZ_COUNT_DTOR(nsStyleBackground);
+}
+
+void
+nsStyleBackground::Destroy(nsPresContext* aContext)
+{
+ this->~nsStyleBackground();
+ aContext->PresShell()->
+ FreeByObjectID(eArenaObjectID_nsStyleBackground, this);
+}
+
+void
+nsStyleBackground::FinishStyle(nsPresContext* aPresContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPresContext->StyleSet()->IsServo());
+
+ mImage.ResolveImages(aPresContext);
+}
+
+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 == NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED &&
+ !layer.mImage.IsEmpty() &&
+ !nsLayoutUtils::IsTransformed(aFrame)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+nsStyleBackground::IsTransparent() const
+{
+ return BottomLayer().mImage.IsEmpty() &&
+ mImage.mImageCount == 1 &&
+ NS_GET_A(mBackgroundColor) == 0;
+}
+
+void
+nsTimingFunction::AssignFromKeyword(int32_t aTimingFunctionType)
+{
+ switch (aTimingFunctionType) {
+ case NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START:
+ mType = Type::StepStart;
+ mSteps = 1;
+ return;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("aTimingFunctionType must be a keyword value");
+ case NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END:
+ mType = Type::StepEnd;
+ mSteps = 1;
+ return;
+ case NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE:
+ case NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR:
+ case NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN:
+ case NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_OUT:
+ case NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN_OUT:
+ mType = static_cast<Type>(aTimingFunctionType);
+ break;
+ }
+
+ static_assert(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE == 0 &&
+ NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR == 1 &&
+ NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN == 2 &&
+ NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_OUT == 3 &&
+ NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN_OUT == 4,
+ "transition timing function constants not as expected");
+
+ static const float timingFunctionValues[5][4] = {
+ { 0.25f, 0.10f, 0.25f, 1.00f }, // ease
+ { 0.00f, 0.00f, 1.00f, 1.00f }, // linear
+ { 0.42f, 0.00f, 1.00f, 1.00f }, // ease-in
+ { 0.00f, 0.00f, 0.58f, 1.00f }, // ease-out
+ { 0.42f, 0.00f, 0.58f, 1.00f } // ease-in-out
+ };
+
+ MOZ_ASSERT(0 <= aTimingFunctionType && aTimingFunctionType < 5,
+ "keyword out of range");
+ mFunc.mX1 = timingFunctionValues[aTimingFunctionType][0];
+ mFunc.mY1 = timingFunctionValues[aTimingFunctionType][1];
+ mFunc.mX2 = timingFunctionValues[aTimingFunctionType][2];
+ mFunc.mY2 = timingFunctionValues[aTimingFunctionType][3];
+}
+
+StyleTransition::StyleTransition(const StyleTransition& aCopy)
+ : mTimingFunction(aCopy.mTimingFunction)
+ , mDuration(aCopy.mDuration)
+ , mDelay(aCopy.mDelay)
+ , mProperty(aCopy.mProperty)
+ , mUnknownProperty(aCopy.mUnknownProperty)
+{
+}
+
+void
+StyleTransition::SetInitialValues()
+{
+ mTimingFunction = nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE);
+ mDuration = 0.0;
+ mDelay = 0.0;
+ mProperty = eCSSPropertyExtra_all_properties;
+}
+
+void
+StyleTransition::SetUnknownProperty(nsCSSPropertyID aProperty,
+ const nsAString& aPropertyString)
+{
+ MOZ_ASSERT(nsCSSProps::LookupProperty(aPropertyString,
+ CSSEnabledState::eForAllContent) ==
+ aProperty,
+ "property and property string should match");
+ MOZ_ASSERT(aProperty == eCSSProperty_UNKNOWN ||
+ aProperty == eCSSPropertyExtra_variable,
+ "should be either unknown or custom property");
+ mProperty = aProperty;
+ mUnknownProperty = NS_Atomize(aPropertyString);
+}
+
+bool
+StyleTransition::operator==(const StyleTransition& aOther) const
+{
+ return mTimingFunction == aOther.mTimingFunction &&
+ mDuration == aOther.mDuration &&
+ mDelay == aOther.mDelay &&
+ mProperty == aOther.mProperty &&
+ (mProperty != eCSSProperty_UNKNOWN ||
+ mUnknownProperty == aOther.mUnknownProperty);
+}
+
+StyleAnimation::StyleAnimation(const StyleAnimation& aCopy)
+ : mTimingFunction(aCopy.mTimingFunction)
+ , mDuration(aCopy.mDuration)
+ , mDelay(aCopy.mDelay)
+ , mName(aCopy.mName)
+ , mDirection(aCopy.mDirection)
+ , mFillMode(aCopy.mFillMode)
+ , mPlayState(aCopy.mPlayState)
+ , mIterationCount(aCopy.mIterationCount)
+{
+}
+
+void
+StyleAnimation::SetInitialValues()
+{
+ mTimingFunction = nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE);
+ mDuration = 0.0;
+ mDelay = 0.0;
+ mName = EmptyString();
+ mDirection = dom::PlaybackDirection::Normal;
+ mFillMode = dom::FillMode::None;
+ mPlayState = NS_STYLE_ANIMATION_PLAY_STATE_RUNNING;
+ mIterationCount = 1.0f;
+}
+
+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;
+}
+
+// --------------------
+// nsStyleDisplay
+//
+nsStyleDisplay::nsStyleDisplay(StyleStructContext aContext)
+ : mDisplay(StyleDisplay::Inline)
+ , mOriginalDisplay(StyleDisplay::Inline)
+ , mContain(NS_STYLE_CONTAIN_NONE)
+ , mAppearance(NS_THEME_NONE)
+ , mPosition(NS_STYLE_POSITION_STATIC)
+ , mFloat(StyleFloat::None)
+ , mOriginalFloat(StyleFloat::None)
+ , mBreakType(StyleClear::None)
+ , mBreakInside(NS_STYLE_PAGE_BREAK_AUTO)
+ , mBreakBefore(false)
+ , mBreakAfter(false)
+ , mOverflowX(NS_STYLE_OVERFLOW_VISIBLE)
+ , mOverflowY(NS_STYLE_OVERFLOW_VISIBLE)
+ , mOverflowClipBox(NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX)
+ , mResize(NS_STYLE_RESIZE_NONE)
+ , mOrient(StyleOrient::Inline)
+ , mIsolation(NS_STYLE_ISOLATION_AUTO)
+ , mTopLayer(NS_STYLE_TOP_LAYER_NONE)
+ , mWillChangeBitField(0)
+ , mTouchAction(NS_STYLE_TOUCH_ACTION_AUTO)
+ , mScrollBehavior(NS_STYLE_SCROLL_BEHAVIOR_AUTO)
+ , mScrollSnapTypeX(NS_STYLE_SCROLL_SNAP_TYPE_NONE)
+ , mScrollSnapTypeY(NS_STYLE_SCROLL_SNAP_TYPE_NONE)
+ , mScrollSnapPointsX(eStyleUnit_None)
+ , mScrollSnapPointsY(eStyleUnit_None)
+ , mBackfaceVisibility(NS_STYLE_BACKFACE_VISIBILITY_VISIBLE)
+ , mTransformStyle(NS_STYLE_TRANSFORM_STYLE_FLAT)
+ , mTransformBox(NS_STYLE_TRANSFORM_BOX_BORDER_BOX)
+ , mSpecifiedTransform(nullptr)
+ , mTransformOrigin{ {0.5f, eStyleUnit_Percent}, // Transform is centered on origin
+ {0.5f, eStyleUnit_Percent},
+ {0, nsStyleCoord::CoordConstructor} }
+ , mChildPerspective(eStyleUnit_None)
+ , mPerspectiveOrigin{ {0.5f, eStyleUnit_Percent},
+ {0.5f, eStyleUnit_Percent} }
+ , mVerticalAlign(NS_STYLE_VERTICAL_ALIGN_BASELINE, eStyleUnit_Enumerated)
+ , 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)
+{
+ MOZ_COUNT_CTOR(nsStyleDisplay);
+
+ // Initial value for mScrollSnapDestination is "0px 0px"
+ mScrollSnapDestination.SetInitialZeroValues();
+
+ mTransitions[0].SetInitialValues();
+ mAnimations[0].SetInitialValues();
+}
+
+nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource)
+ : mBinding(aSource.mBinding)
+ , mDisplay(aSource.mDisplay)
+ , mOriginalDisplay(aSource.mOriginalDisplay)
+ , mContain(aSource.mContain)
+ , mAppearance(aSource.mAppearance)
+ , mPosition(aSource.mPosition)
+ , mFloat(aSource.mFloat)
+ , mOriginalFloat(aSource.mOriginalFloat)
+ , mBreakType(aSource.mBreakType)
+ , mBreakInside(aSource.mBreakInside)
+ , mBreakBefore(aSource.mBreakBefore)
+ , mBreakAfter(aSource.mBreakAfter)
+ , mOverflowX(aSource.mOverflowX)
+ , mOverflowY(aSource.mOverflowY)
+ , mOverflowClipBox(aSource.mOverflowClipBox)
+ , mResize(aSource.mResize)
+ , mOrient(aSource.mOrient)
+ , mIsolation(aSource.mIsolation)
+ , mTopLayer(aSource.mTopLayer)
+ , mWillChangeBitField(aSource.mWillChangeBitField)
+ , mWillChange(aSource.mWillChange)
+ , mTouchAction(aSource.mTouchAction)
+ , mScrollBehavior(aSource.mScrollBehavior)
+ , mScrollSnapTypeX(aSource.mScrollSnapTypeX)
+ , mScrollSnapTypeY(aSource.mScrollSnapTypeY)
+ , mScrollSnapPointsX(aSource.mScrollSnapPointsX)
+ , mScrollSnapPointsY(aSource.mScrollSnapPointsY)
+ , mScrollSnapDestination(aSource.mScrollSnapDestination)
+ , mScrollSnapCoordinate(aSource.mScrollSnapCoordinate)
+ , mBackfaceVisibility(aSource.mBackfaceVisibility)
+ , mTransformStyle(aSource.mTransformStyle)
+ , mTransformBox(aSource.mTransformBox)
+ , mSpecifiedTransform(aSource.mSpecifiedTransform)
+ , mTransformOrigin{ aSource.mTransformOrigin[0],
+ aSource.mTransformOrigin[1],
+ aSource.mTransformOrigin[2] }
+ , mChildPerspective(aSource.mChildPerspective)
+ , mPerspectiveOrigin{ aSource.mPerspectiveOrigin[0],
+ aSource.mPerspectiveOrigin[1] }
+ , mVerticalAlign(aSource.mVerticalAlign)
+ , mTransitions(aSource.mTransitions)
+ , mTransitionTimingFunctionCount(aSource.mTransitionTimingFunctionCount)
+ , mTransitionDurationCount(aSource.mTransitionDurationCount)
+ , mTransitionDelayCount(aSource.mTransitionDelayCount)
+ , mTransitionPropertyCount(aSource.mTransitionPropertyCount)
+ , mAnimations(aSource.mAnimations)
+ , mAnimationTimingFunctionCount(aSource.mAnimationTimingFunctionCount)
+ , mAnimationDurationCount(aSource.mAnimationDurationCount)
+ , mAnimationDelayCount(aSource.mAnimationDelayCount)
+ , mAnimationNameCount(aSource.mAnimationNameCount)
+ , mAnimationDirectionCount(aSource.mAnimationDirectionCount)
+ , mAnimationFillModeCount(aSource.mAnimationFillModeCount)
+ , mAnimationPlayStateCount(aSource.mAnimationPlayStateCount)
+ , mAnimationIterationCountCount(aSource.mAnimationIterationCountCount)
+ , mShapeOutside(aSource.mShapeOutside)
+{
+ MOZ_COUNT_CTOR(nsStyleDisplay);
+}
+
+nsChangeHint
+nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
+{
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (!DefinitelyEqualURIsAndPrincipal(mBinding, aNewData.mBinding)
+ || mPosition != aNewData.mPosition
+ || mDisplay != aNewData.mDisplay
+ || mContain != aNewData.mContain
+ || (mFloat == StyleFloat::None) != (aNewData.mFloat == StyleFloat::None)
+ || mOverflowX != aNewData.mOverflowX
+ || mOverflowY != aNewData.mOverflowY
+ || mScrollBehavior != aNewData.mScrollBehavior
+ || mScrollSnapTypeX != aNewData.mScrollSnapTypeX
+ || mScrollSnapTypeY != aNewData.mScrollSnapTypeY
+ || mScrollSnapPointsX != aNewData.mScrollSnapPointsX
+ || mScrollSnapPointsY != aNewData.mScrollSnapPointsY
+ || mScrollSnapDestination != aNewData.mScrollSnapDestination
+ || mTopLayer != aNewData.mTopLayer
+ || mResize != aNewData.mResize) {
+ hint |= nsChangeHint_ReconstructFrame;
+ }
+
+ /* Note: When mScrollBehavior, mScrollSnapTypeX, mScrollSnapTypeY,
+ * mScrollSnapPointsX, mScrollSnapPointsY, or mScrollSnapDestination are
+ * changed, nsChangeHint_NeutralChange is not sufficient to enter
+ * nsCSSFrameConstructor::PropagateScrollToViewport. By using the same hint
+ * as used when the overflow css property changes,
+ * nsChangeHint_ReconstructFrame, PropagateScrollToViewport will be called.
+ *
+ * The scroll-behavior css property is not expected to change often (the
+ * CSSOM-View DOM methods are likely to be used in those cases); however,
+ * if this does become common perhaps a faster-path might be worth while.
+ */
+
+ if ((mAppearance == NS_THEME_TEXTFIELD &&
+ aNewData.mAppearance != NS_THEME_TEXTFIELD) ||
+ (mAppearance != NS_THEME_TEXTFIELD &&
+ aNewData.mAppearance == NS_THEME_TEXTFIELD)) {
+ // This is for <input type=number> where we allow authors to specify a
+ // |-moz-appearance:textfield| to get a control without a spinner. (The
+ // spinner is present for |-moz-appearance:number-input| but also other
+ // values such as 'none'.) We need to reframe since we want to use
+ // nsTextControlFrame instead of nsNumberControlFrame if the author
+ // specifies 'textfield'.
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ if (mFloat != aNewData.mFloat) {
+ // Changing which side we float on doesn't affect descendants directly
+ hint |= nsChangeHint_AllReflowHints &
+ ~(nsChangeHint_ClearDescendantIntrinsics |
+ nsChangeHint_NeedDirtyReflow);
+ }
+
+ if (mVerticalAlign != aNewData.mVerticalAlign) {
+ // 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.
+ if (mBreakType != aNewData.mBreakType
+ || mBreakInside != aNewData.mBreakInside
+ || mBreakBefore != aNewData.mBreakBefore
+ || mBreakAfter != aNewData.mBreakAfter
+ || mAppearance != aNewData.mAppearance
+ || mOrient != aNewData.mOrient
+ || mOverflowClipBox != aNewData.mOverflowClipBox) {
+ 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()) {
+ // We do not need to apply nsChangeHint_UpdateTransformLayer since
+ // nsChangeHint_RepaintFrame will forcibly invalidate the frame area and
+ // ensure layers are rebuilt (or removed).
+ hint |= nsChangeHint_UpdateContainingBlock |
+ nsChangeHint_AddOrRemoveTransform |
+ nsChangeHint_UpdateOverflow |
+ nsChangeHint_RepaintFrame;
+ } 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 = nsChangeHint(0);
+
+ if (!mSpecifiedTransform != !aNewData.mSpecifiedTransform ||
+ (mSpecifiedTransform &&
+ *mSpecifiedTransform != *aNewData.mSpecifiedTransform)) {
+ transformHint |= nsChangeHint_UpdateTransformLayer;
+
+ if (mSpecifiedTransform &&
+ aNewData.mSpecifiedTransform) {
+ transformHint |= nsChangeHint_UpdatePostTransformOverflow;
+ } else {
+ transformHint |= nsChangeHint_UpdateOverflow;
+ }
+ }
+
+ const nsChangeHint kUpdateOverflowAndRepaintHint =
+ nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
+ for (uint8_t index = 0; index < 3; ++index) {
+ if (mTransformOrigin[index] != aNewData.mTransformOrigin[index]) {
+ transformHint |= nsChangeHint_UpdateTransformLayer |
+ nsChangeHint_UpdatePostTransformOverflow;
+ break;
+ }
+ }
+
+ for (uint8_t index = 0; index < 2; ++index) {
+ if (mPerspectiveOrigin[index] != aNewData.mPerspectiveOrigin[index]) {
+ transformHint |= kUpdateOverflowAndRepaintHint;
+ break;
+ }
+ }
+
+ if (HasPerspectiveStyle() != aNewData.HasPerspectiveStyle()) {
+ // A change from/to being a containing block for position:fixed.
+ hint |= nsChangeHint_UpdateContainingBlock;
+ }
+
+ if (mChildPerspective != aNewData.mChildPerspective ||
+ mTransformStyle != aNewData.mTransformStyle ||
+ mTransformBox != aNewData.mTransformBox) {
+ transformHint |= kUpdateOverflowAndRepaintHint;
+ }
+
+ if (mBackfaceVisibility != aNewData.mBackfaceVisibility) {
+ transformHint |= nsChangeHint_RepaintFrame;
+ }
+
+ if (transformHint) {
+ if (HasTransformStyle()) {
+ hint |= transformHint;
+ } else {
+ hint |= nsChangeHint_NeutralChange;
+ }
+ }
+ }
+
+ // Note that the HasTransformStyle() != aNewData.HasTransformStyle()
+ // test above handles relevant changes in the
+ // NS_STYLE_WILL_CHANGE_TRANSFORM bit, which in turn handles frame
+ // reconstruction for changes in the containing block of
+ // fixed-positioned elements.
+ uint8_t willChangeBitsChanged =
+ mWillChangeBitField ^ aNewData.mWillChangeBitField;
+ if (willChangeBitsChanged & (NS_STYLE_WILL_CHANGE_STACKING_CONTEXT |
+ NS_STYLE_WILL_CHANGE_SCROLL |
+ NS_STYLE_WILL_CHANGE_OPACITY)) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ if (willChangeBitsChanged & NS_STYLE_WILL_CHANGE_FIXPOS_CB) {
+ 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;
+ }
+
+ // 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 style context change anyway.
+
+ // Note: Likewise, for animation-*, the animation manager gets
+ // notified about every new style context 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.
+
+ if (!hint &&
+ (mOriginalDisplay != aNewData.mOriginalDisplay ||
+ mOriginalFloat != aNewData.mOriginalFloat ||
+ 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 ||
+ mScrollSnapCoordinate != aNewData.mScrollSnapCoordinate ||
+ mShapeOutside != aNewData.mShapeOutside)) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ return hint;
+}
+
+// --------------------
+// nsStyleVisibility
+//
+
+nsStyleVisibility::nsStyleVisibility(StyleStructContext aContext)
+ : mDirection(aContext.GetBidi() == IBMBIDI_TEXTDIRECTION_RTL
+ ? NS_STYLE_DIRECTION_RTL
+ : NS_STYLE_DIRECTION_LTR)
+ , mVisible(NS_STYLE_VISIBILITY_VISIBLE)
+ , mImageRendering(NS_STYLE_IMAGE_RENDERING_AUTO)
+ , mWritingMode(NS_STYLE_WRITING_MODE_HORIZONTAL_TB)
+ , mTextOrientation(NS_STYLE_TEXT_ORIENTATION_MIXED)
+ , mColorAdjust(NS_STYLE_COLOR_ADJUST_ECONOMY)
+{
+ MOZ_COUNT_CTOR(nsStyleVisibility);
+}
+
+nsStyleVisibility::nsStyleVisibility(const nsStyleVisibility& aSource)
+ : mImageOrientation(aSource.mImageOrientation)
+ , mDirection(aSource.mDirection)
+ , mVisible(aSource.mVisible)
+ , mImageRendering(aSource.mImageRendering)
+ , mWritingMode(aSource.mWritingMode)
+ , mTextOrientation(aSource.mTextOrientation)
+ , mColorAdjust(aSource.mColorAdjust)
+{
+ 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).
+ hint |= nsChangeHint_ReconstructFrame;
+ } else {
+ if ((mImageOrientation != aNewData.mImageOrientation)) {
+ hint |= nsChangeHint_AllReflowHints |
+ nsChangeHint_RepaintFrame;
+ }
+ if (mVisible != aNewData.mVisible) {
+ if ((NS_STYLE_VISIBILITY_COLLAPSE == mVisible) ||
+ (NS_STYLE_VISIBILITY_COLLAPSE == aNewData.mVisible)) {
+ hint |= NS_STYLE_HINT_REFLOW;
+ } else {
+ hint |= NS_STYLE_HINT_VISUAL;
+ }
+ }
+ if (mTextOrientation != aNewData.mTextOrientation) {
+ hint |= NS_STYLE_HINT_REFLOW;
+ }
+ if (mImageRendering != aNewData.mImageRendering) {
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ if (mColorAdjust != aNewData.mColorAdjust) {
+ // color-adjust only affects media where dynamic changes can't happen.
+ hint |= nsChangeHint_NeutralChange;
+ }
+ }
+ return hint;
+}
+
+nsStyleContentData::~nsStyleContentData()
+{
+ MOZ_COUNT_DTOR(nsStyleContentData);
+ MOZ_ASSERT(!mImageTracked,
+ "nsStyleContentData being destroyed while still tracking image!");
+ if (mType == eStyleContentType_Image) {
+ NS_IF_RELEASE(mContent.mImage);
+ } else if (mType == eStyleContentType_Counter ||
+ mType == eStyleContentType_Counters) {
+ mContent.mCounters->Release();
+ } else if (mContent.mString) {
+ free(mContent.mString);
+ }
+}
+
+nsStyleContentData::nsStyleContentData(const nsStyleContentData& aOther)
+ : mType(aOther.mType)
+#ifdef DEBUG
+ , mImageTracked(false)
+#endif
+{
+ MOZ_COUNT_CTOR(nsStyleContentData);
+ if (mType == eStyleContentType_Image) {
+ mContent.mImage = aOther.mContent.mImage;
+ NS_IF_ADDREF(mContent.mImage);
+ } else if (mType == eStyleContentType_Counter ||
+ mType == eStyleContentType_Counters) {
+ mContent.mCounters = aOther.mContent.mCounters;
+ mContent.mCounters->AddRef();
+ } else if (aOther.mContent.mString) {
+ mContent.mString = NS_strdup(aOther.mContent.mString);
+ } else {
+ mContent.mString = nullptr;
+ }
+}
+
+nsStyleContentData&
+nsStyleContentData::operator=(const nsStyleContentData& aOther)
+{
+ if (this == &aOther) {
+ return *this;
+ }
+ this->~nsStyleContentData();
+ new (this) nsStyleContentData(aOther);
+
+ return *this;
+}
+
+bool
+nsStyleContentData::operator==(const nsStyleContentData& aOther) const
+{
+ if (mType != aOther.mType) {
+ return false;
+ }
+ if (mType == eStyleContentType_Image) {
+ if (!mContent.mImage || !aOther.mContent.mImage) {
+ return mContent.mImage == aOther.mContent.mImage;
+ }
+ bool eq;
+ nsCOMPtr<nsIURI> thisURI, otherURI;
+ mContent.mImage->GetURI(getter_AddRefs(thisURI));
+ aOther.mContent.mImage->GetURI(getter_AddRefs(otherURI));
+ return thisURI == otherURI || // handles null==null
+ (thisURI && otherURI &&
+ NS_SUCCEEDED(thisURI->Equals(otherURI, &eq)) &&
+ eq);
+ }
+ if (mType == eStyleContentType_Counter ||
+ mType == eStyleContentType_Counters) {
+ return *mContent.mCounters == *aOther.mContent.mCounters;
+ }
+ return safe_strcmp(mContent.mString, aOther.mContent.mString) == 0;
+}
+
+void
+nsStyleContentData::TrackImage(ImageTracker* aImageTracker)
+{
+ // Sanity
+ MOZ_ASSERT(!mImageTracked, "Already tracking image!");
+ MOZ_ASSERT(mType == eStyleContentType_Image,
+ "Trying to do image tracking on non-image!");
+ MOZ_ASSERT(mContent.mImage,
+ "Can't track image when there isn't one!");
+
+ aImageTracker->Add(mContent.mImage);
+
+ // Mark state
+#ifdef DEBUG
+ mImageTracked = true;
+#endif
+}
+
+void
+nsStyleContentData::UntrackImage(ImageTracker* aImageTracker)
+{
+ // Sanity
+ MOZ_ASSERT(mImageTracked, "Image not tracked!");
+ MOZ_ASSERT(mType == eStyleContentType_Image,
+ "Trying to do image tracking on non-image!");
+ MOZ_ASSERT(mContent.mImage,
+ "Can't untrack image when there isn't one!");
+
+ aImageTracker->Remove(mContent.mImage);
+
+ // Mark state
+#ifdef DEBUG
+ mImageTracked = false;
+#endif
+}
+
+
+//-----------------------
+// nsStyleContent
+//
+
+nsStyleContent::nsStyleContent(StyleStructContext aContext)
+{
+ MOZ_COUNT_CTOR(nsStyleContent);
+}
+
+nsStyleContent::~nsStyleContent()
+{
+ MOZ_COUNT_DTOR(nsStyleContent);
+}
+
+void
+nsStyleContent::Destroy(nsPresContext* aContext)
+{
+ // Unregister any images we might have with the document.
+ for (auto& content : mContents) {
+ if (content.mType == eStyleContentType_Image && content.mContent.mImage) {
+ content.UntrackImage(aContext->Document()->ImageTracker());
+ }
+ }
+
+ this->~nsStyleContent();
+ aContext->PresShell()->FreeByObjectID(eArenaObjectID_nsStyleContent, this);
+}
+
+nsStyleContent::nsStyleContent(const nsStyleContent& aSource)
+ : mContents(aSource.mContents)
+ , mIncrements(aSource.mIncrements)
+ , mResets(aSource.mResets)
+{
+ MOZ_COUNT_CTOR(nsStyleContent);
+}
+
+nsChangeHint
+nsStyleContent::CalcDifference(const nsStyleContent& aNewData) const
+{
+ // In ElementRestyler::Restyle we assume that if there's no existing
+ // ::before or ::after and we don't have to restyle children of the
+ // node then we can't end up with a ::before or ::after due to the
+ // restyle of the node itself. That's not quite true, but the only
+ // exception to the above is when the 'content' property of the node
+ // changes and the pseudo-element inherits the changed value. Since
+ // the code here triggers a frame change on the node in that case,
+ // the optimization in ElementRestyler::Restyle is ok. But if we ever
+ // change this code to not reconstruct frames on changes to the
+ // 'content' property, then we will need to revisit the optimization
+ // in ElementRestyler::Restyle.
+
+ // 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 (mContents != aNewData.mContents ||
+ mIncrements != aNewData.mIncrements ||
+ mResets != aNewData.mResets) {
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ return nsChangeHint(0);
+}
+
+// --------------------
+// nsStyleTextReset
+//
+
+nsStyleTextReset::nsStyleTextReset(StyleStructContext aContext)
+ : mTextDecorationLine(NS_STYLE_TEXT_DECORATION_LINE_NONE)
+ , mTextDecorationStyle(NS_STYLE_TEXT_DECORATION_STYLE_SOLID)
+ , mUnicodeBidi(NS_STYLE_UNICODE_BIDI_NORMAL)
+ , mInitialLetterSink(0)
+ , mInitialLetterSize(0.0f)
+ , mTextDecorationColor(StyleComplexColor::CurrentColor())
+{
+ MOZ_COUNT_CTOR(nsStyleTextReset);
+}
+
+nsStyleTextReset::nsStyleTextReset(const nsStyleTextReset& aSource)
+{
+ MOZ_COUNT_CTOR(nsStyleTextReset);
+ *this = aSource;
+}
+
+nsStyleTextReset::~nsStyleTextReset()
+{
+ MOZ_COUNT_DTOR(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) {
+ // 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);
+}
+
+// Returns true if the given shadow-arrays are equal.
+static bool
+AreShadowArraysEqual(nsCSSShadowArray* lhs,
+ nsCSSShadowArray* rhs)
+{
+ if (lhs == rhs) {
+ return true;
+ }
+
+ if (!lhs || !rhs || lhs->Length() != rhs->Length()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < lhs->Length(); ++i) {
+ if (*lhs->ShadowAt(i) != *rhs->ShadowAt(i)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// --------------------
+// nsStyleText
+//
+
+nsStyleText::nsStyleText(StyleStructContext aContext)
+ : mTextAlign(NS_STYLE_TEXT_ALIGN_START)
+ , mTextAlignLast(NS_STYLE_TEXT_ALIGN_AUTO)
+ , mTextAlignTrue(false)
+ , mTextAlignLastTrue(false)
+ , mTextTransform(NS_STYLE_TEXT_TRANSFORM_NONE)
+ , mWhiteSpace(NS_STYLE_WHITESPACE_NORMAL)
+ , mWordBreak(NS_STYLE_WORDBREAK_NORMAL)
+ , mOverflowWrap(NS_STYLE_OVERFLOWWRAP_NORMAL)
+ , mHyphens(NS_STYLE_HYPHENS_MANUAL)
+ , mRubyAlign(NS_STYLE_RUBY_ALIGN_SPACE_AROUND)
+ , mRubyPosition(NS_STYLE_RUBY_POSITION_OVER)
+ , mTextSizeAdjust(NS_STYLE_TEXT_SIZE_ADJUST_AUTO)
+ , mTextCombineUpright(NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE)
+ , mControlCharacterVisibility(nsCSSParser::ControlCharVisibilityDefault())
+ , mTextEmphasisStyle(NS_STYLE_TEXT_EMPHASIS_STYLE_NONE)
+ , mTextRendering(NS_STYLE_TEXT_RENDERING_AUTO)
+ , mTabSize(NS_STYLE_TABSIZE_INITIAL)
+ , mTextEmphasisColor(StyleComplexColor::CurrentColor())
+ , mWebkitTextFillColor(StyleComplexColor::CurrentColor())
+ , mWebkitTextStrokeColor(StyleComplexColor::CurrentColor())
+ , mWordSpacing(0, nsStyleCoord::CoordConstructor)
+ , mLetterSpacing(eStyleUnit_Normal)
+ , mLineHeight(eStyleUnit_Normal)
+ , mTextIndent(0, nsStyleCoord::CoordConstructor)
+ , mWebkitTextStrokeWidth(0, nsStyleCoord::CoordConstructor)
+ , mTextShadow(nullptr)
+{
+ MOZ_COUNT_CTOR(nsStyleText);
+ nsCOMPtr<nsIAtom> language = aContext.GetContentLanguage();
+ mTextEmphasisPosition = language &&
+ nsStyleUtil::MatchesLanguagePrefix(language, u"zh") ?
+ NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH :
+ NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT;
+}
+
+nsStyleText::nsStyleText(const nsStyleText& aSource)
+ : mTextAlign(aSource.mTextAlign)
+ , mTextAlignLast(aSource.mTextAlignLast)
+ , mTextAlignTrue(false)
+ , mTextAlignLastTrue(false)
+ , mTextTransform(aSource.mTextTransform)
+ , mWhiteSpace(aSource.mWhiteSpace)
+ , mWordBreak(aSource.mWordBreak)
+ , mOverflowWrap(aSource.mOverflowWrap)
+ , mHyphens(aSource.mHyphens)
+ , mRubyAlign(aSource.mRubyAlign)
+ , mRubyPosition(aSource.mRubyPosition)
+ , mTextSizeAdjust(aSource.mTextSizeAdjust)
+ , mTextCombineUpright(aSource.mTextCombineUpright)
+ , mControlCharacterVisibility(aSource.mControlCharacterVisibility)
+ , mTextEmphasisPosition(aSource.mTextEmphasisPosition)
+ , mTextEmphasisStyle(aSource.mTextEmphasisStyle)
+ , mTextRendering(aSource.mTextRendering)
+ , mTabSize(aSource.mTabSize)
+ , mTextEmphasisColor(aSource.mTextEmphasisColor)
+ , mWebkitTextFillColor(aSource.mWebkitTextFillColor)
+ , mWebkitTextStrokeColor(aSource.mWebkitTextStrokeColor)
+ , mWordSpacing(aSource.mWordSpacing)
+ , mLetterSpacing(aSource.mLetterSpacing)
+ , mLineHeight(aSource.mLineHeight)
+ , mTextIndent(aSource.mTextIndent)
+ , mWebkitTextStrokeWidth(aSource.mWebkitTextStrokeWidth)
+ , mTextShadow(aSource.mTextShadow)
+ , mTextEmphasisStyleString(aSource.mTextEmphasisStyleString)
+{
+ MOZ_COUNT_CTOR(nsStyleText);
+}
+
+nsStyleText::~nsStyleText()
+{
+ MOZ_COUNT_DTOR(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 ||
+ mControlCharacterVisibility != aNewData.mControlCharacterVisibility) {
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ if ((mTextAlign != aNewData.mTextAlign) ||
+ (mTextAlignLast != aNewData.mTextAlignLast) ||
+ (mTextAlignTrue != aNewData.mTextAlignTrue) ||
+ (mTextAlignLastTrue != aNewData.mTextAlignLastTrue) ||
+ (mTextTransform != aNewData.mTextTransform) ||
+ (mWhiteSpace != aNewData.mWhiteSpace) ||
+ (mWordBreak != aNewData.mWordBreak) ||
+ (mOverflowWrap != aNewData.mOverflowWrap) ||
+ (mHyphens != aNewData.mHyphens) ||
+ (mRubyAlign != aNewData.mRubyAlign) ||
+ (mRubyPosition != aNewData.mRubyPosition) ||
+ (mTextSizeAdjust != aNewData.mTextSizeAdjust) ||
+ (mLetterSpacing != aNewData.mLetterSpacing) ||
+ (mLineHeight != aNewData.mLineHeight) ||
+ (mTextIndent != aNewData.mTextIndent) ||
+ (mWordSpacing != aNewData.mWordSpacing) ||
+ (mTabSize != aNewData.mTabSize)) {
+ return NS_STYLE_HINT_REFLOW;
+ }
+
+ if (HasTextEmphasis() != aNewData.HasTextEmphasis() ||
+ (HasTextEmphasis() &&
+ 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_NeedDirtyReflow | // XXX remove me: bug 876085
+ nsChangeHint_RepaintFrame;
+ }
+
+ if (!AreShadowArraysEqual(mTextShadow, aNewData.mTextShadow) ||
+ mTextEmphasisStyle != aNewData.mTextEmphasisStyle ||
+ mTextEmphasisStyleString != aNewData.mTextEmphasisStyleString ||
+ mWebkitTextStrokeWidth != aNewData.mWebkitTextStrokeWidth) {
+ hint |= nsChangeHint_UpdateSubtreeOverflow |
+ nsChangeHint_SchedulePaint |
+ nsChangeHint_RepaintFrame;
+
+ // We don't add any other hints below.
+ return hint;
+ }
+
+ if (mTextEmphasisColor != aNewData.mTextEmphasisColor ||
+ mWebkitTextFillColor != aNewData.mWebkitTextFillColor ||
+ mWebkitTextStrokeColor != aNewData.mWebkitTextStrokeColor) {
+ hint |= nsChangeHint_SchedulePaint |
+ nsChangeHint_RepaintFrame;
+ }
+
+ if (hint) {
+ return hint;
+ }
+
+ if (mTextEmphasisPosition != aNewData.mTextEmphasisPosition) {
+ return nsChangeHint_NeutralChange;
+ }
+
+ return nsChangeHint(0);
+}
+
+LogicalSide
+nsStyleText::TextEmphasisSide(WritingMode aWM) const
+{
+ MOZ_ASSERT(
+ (!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT) !=
+ !(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT)) &&
+ (!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER) !=
+ !(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER)));
+ mozilla::Side side = aWM.IsVertical() ?
+ (mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT
+ ? eSideLeft : eSideRight) :
+ (mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER
+ ? eSideTop : eSideBottom);
+ LogicalSide result = aWM.LogicalSideForPhysicalSide(side);
+ MOZ_ASSERT(IsBlock(result));
+ return result;
+}
+
+//-----------------------
+// nsStyleUserInterface
+//
+
+nsCursorImage::nsCursorImage()
+ : mHaveHotspot(false)
+ , mHotspotX(0.0f)
+ , mHotspotY(0.0f)
+{
+}
+
+nsCursorImage::nsCursorImage(const nsCursorImage& aOther)
+ : mHaveHotspot(aOther.mHaveHotspot)
+ , mHotspotX(aOther.mHotspotX)
+ , mHotspotY(aOther.mHotspotY)
+{
+ SetImage(aOther.GetImage());
+}
+
+nsCursorImage::~nsCursorImage()
+{
+ SetImage(nullptr);
+}
+
+nsCursorImage&
+nsCursorImage::operator=(const nsCursorImage& aOther)
+{
+ if (this != &aOther) {
+ mHaveHotspot = aOther.mHaveHotspot;
+ mHotspotX = aOther.mHotspotX;
+ mHotspotY = aOther.mHotspotY;
+ SetImage(aOther.GetImage());
+ }
+
+ return *this;
+}
+
+bool
+nsCursorImage::operator==(const nsCursorImage& aOther) const
+{
+ NS_ASSERTION(mHaveHotspot ||
+ (mHotspotX == 0 && mHotspotY == 0),
+ "expected mHotspot{X,Y} to be 0 when mHaveHotspot is false");
+ NS_ASSERTION(aOther.mHaveHotspot ||
+ (aOther.mHotspotX == 0 && aOther.mHotspotY == 0),
+ "expected mHotspot{X,Y} to be 0 when mHaveHotspot is false");
+ return mHaveHotspot == aOther.mHaveHotspot &&
+ mHotspotX == aOther.mHotspotX &&
+ mHotspotY == aOther.mHotspotY &&
+ EqualImages(mImage, aOther.mImage);
+}
+
+nsStyleUserInterface::nsStyleUserInterface(StyleStructContext aContext)
+ : mUserInput(StyleUserInput::Auto)
+ , mUserModify(StyleUserModify::ReadOnly)
+ , mUserFocus(StyleUserFocus::None)
+ , mPointerEvents(NS_STYLE_POINTER_EVENTS_AUTO)
+ , mCursor(NS_STYLE_CURSOR_AUTO)
+{
+ MOZ_COUNT_CTOR(nsStyleUserInterface);
+}
+
+nsStyleUserInterface::nsStyleUserInterface(const nsStyleUserInterface& aSource)
+ : mUserInput(aSource.mUserInput)
+ , mUserModify(aSource.mUserModify)
+ , mUserFocus(aSource.mUserFocus)
+ , mPointerEvents(aSource.mPointerEvents)
+ , mCursor(aSource.mCursor)
+ , mCursorImages(aSource.mCursorImages)
+{
+ MOZ_COUNT_CTOR(nsStyleUserInterface);
+}
+
+nsStyleUserInterface::~nsStyleUserInterface()
+{
+ MOZ_COUNT_DTOR(nsStyleUserInterface);
+}
+
+nsChangeHint
+nsStyleUserInterface::CalcDifference(const nsStyleUserInterface& aNewData) const
+{
+ nsChangeHint hint = nsChangeHint(0);
+ if (mCursor != aNewData.mCursor) {
+ hint |= nsChangeHint_UpdateCursor;
+ }
+
+ // We could do better. But it wouldn't be worth it, URL-specified cursors are
+ // rare.
+ if (mCursorImages != aNewData.mCursorImages) {
+ hint |= nsChangeHint_UpdateCursor;
+ }
+
+ if (mPointerEvents != aNewData.mPointerEvents) {
+ // nsSVGPathGeometryFrame's mRect depends on stroke _and_ on the value
+ // of pointer-events. See nsSVGPathGeometryFrame::ReflowSVG's use of
+ // GetHitTestFlags. (Only a reflow, no visual change.)
+ hint |= nsChangeHint_NeedReflow |
+ nsChangeHint_NeedDirtyReflow; // XXX remove me: bug 876085
+ }
+
+ if (mUserModify != aNewData.mUserModify) {
+ hint |= NS_STYLE_HINT_VISUAL;
+ }
+
+ if (mUserInput != aNewData.mUserInput) {
+ if (StyleUserInput::None == mUserInput ||
+ StyleUserInput::None == aNewData.mUserInput) {
+ hint |= nsChangeHint_ReconstructFrame;
+ } else {
+ hint |= nsChangeHint_NeutralChange;
+ }
+ }
+
+ if (mUserFocus != aNewData.mUserFocus) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ return hint;
+}
+
+//-----------------------
+// nsStyleUIReset
+//
+
+nsStyleUIReset::nsStyleUIReset(StyleStructContext aContext)
+ : mUserSelect(StyleUserSelect::Auto)
+ , mForceBrokenImageIcon(0)
+ , mIMEMode(NS_STYLE_IME_MODE_AUTO)
+ , mWindowDragging(StyleWindowDragging::Default)
+ , mWindowShadow(NS_STYLE_WINDOW_SHADOW_DEFAULT)
+{
+ MOZ_COUNT_CTOR(nsStyleUIReset);
+}
+
+nsStyleUIReset::nsStyleUIReset(const nsStyleUIReset& aSource)
+ : mUserSelect(aSource.mUserSelect)
+ , mForceBrokenImageIcon(aSource.mForceBrokenImageIcon)
+ , mIMEMode(aSource.mIMEMode)
+ , mWindowDragging(aSource.mWindowDragging)
+ , mWindowShadow(aSource.mWindowShadow)
+{
+ MOZ_COUNT_CTOR(nsStyleUIReset);
+}
+
+nsStyleUIReset::~nsStyleUIReset()
+{
+ MOZ_COUNT_DTOR(nsStyleUIReset);
+}
+
+nsChangeHint
+nsStyleUIReset::CalcDifference(const nsStyleUIReset& aNewData) const
+{
+ // ignore mIMEMode
+ if (mForceBrokenImageIcon != aNewData.mForceBrokenImageIcon) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ 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.
+ return NS_STYLE_HINT_REFLOW;
+ }
+ if (mUserSelect != aNewData.mUserSelect) {
+ return NS_STYLE_HINT_VISUAL;
+ }
+
+ if (mWindowDragging != aNewData.mWindowDragging) {
+ return nsChangeHint_SchedulePaint;
+ }
+
+ return nsChangeHint(0);
+}
+
+//-----------------------
+// nsStyleVariables
+//
+
+nsStyleVariables::nsStyleVariables(StyleStructContext aContext)
+{
+ MOZ_COUNT_CTOR(nsStyleVariables);
+}
+
+nsStyleVariables::nsStyleVariables(const nsStyleVariables& aSource)
+ : mVariables(aSource.mVariables)
+{
+ MOZ_COUNT_CTOR(nsStyleVariables);
+}
+
+nsStyleVariables::~nsStyleVariables()
+{
+ MOZ_COUNT_DTOR(nsStyleVariables);
+}
+
+nsChangeHint
+nsStyleVariables::CalcDifference(const nsStyleVariables& aNewData) const
+{
+ return nsChangeHint(0);
+}
+
+//-----------------------
+// nsStyleEffects
+//
+
+nsStyleEffects::nsStyleEffects(StyleStructContext aContext)
+ : mBoxShadow(nullptr)
+ , mClip(0, 0, 0, 0)
+ , mOpacity(1.0f)
+ , mClipFlags(NS_STYLE_CLIP_AUTO)
+ , mMixBlendMode(NS_STYLE_BLEND_NORMAL)
+{
+ MOZ_COUNT_CTOR(nsStyleEffects);
+}
+
+nsStyleEffects::nsStyleEffects(const nsStyleEffects& aSource)
+ : mFilters(aSource.mFilters)
+ , mBoxShadow(aSource.mBoxShadow)
+ , mClip(aSource.mClip)
+ , mOpacity(aSource.mOpacity)
+ , mClipFlags(aSource.mClipFlags)
+ , mMixBlendMode(aSource.mMixBlendMode)
+{
+ MOZ_COUNT_CTOR(nsStyleEffects);
+}
+
+nsStyleEffects::~nsStyleEffects()
+{
+ MOZ_COUNT_DTOR(nsStyleEffects);
+}
+
+nsChangeHint
+nsStyleEffects::CalcDifference(const nsStyleEffects& aNewData) const
+{
+ nsChangeHint hint = nsChangeHint(0);
+
+ if (!AreShadowArraysEqual(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 (mClipFlags != aNewData.mClipFlags) {
+ hint |= nsChangeHint_AllReflowHints |
+ nsChangeHint_RepaintFrame;
+ }
+
+ if (!mClip.IsEqualInterior(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) {
+ // 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 {
+ hint |= nsChangeHint_UpdateOpacityLayer;
+ 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 (!hint &&
+ !mClip.IsEqualEdges(aNewData.mClip)) {
+ hint |= nsChangeHint_NeutralChange;
+ }
+
+ return hint;
+}
diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h
new file mode 100644
index 000000000..ca5d03056
--- /dev/null
+++ b/layout/style/nsStyleStruct.h
@@ -0,0 +1,4002 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsStyleContext, the
+ * internal API for computed style data for an element
+ */
+
+#ifndef nsStyleStruct_h___
+#define nsStyleStruct_h___
+
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CSSVariableValues.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/SheetType.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/StyleComplexColor.h"
+#include "mozilla/StyleStructContext.h"
+#include "mozilla/UniquePtr.h"
+#include "nsColor.h"
+#include "nsCoord.h"
+#include "nsMargin.h"
+#include "nsFont.h"
+#include "nsStyleCoord.h"
+#include "nsStyleConsts.h"
+#include "nsChangeHint.h"
+#include "nsPresContext.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "nsCSSValue.h"
+#include "imgRequestProxy.h"
+#include "Orientation.h"
+#include "CounterStyleManager.h"
+#include <cstddef> // offsetof()
+#include <utility>
+#include "X11UndefineNone.h"
+
+class nsIFrame;
+class nsIURI;
+class nsStyleContext;
+class nsTextFrame;
+class imgIContainer;
+struct nsStyleVisibility;
+namespace mozilla {
+namespace dom {
+class ImageTracker;
+} // namespace dom
+} // namespace mozilla
+
+// Includes nsStyleStructID.
+#include "nsStyleStructFwd.h"
+
+// Bits for each struct.
+// NS_STYLE_INHERIT_BIT defined in nsStyleStructFwd.h
+#define NS_STYLE_INHERIT_MASK 0x000ffffff
+
+// Bits for inherited structs.
+#define NS_STYLE_INHERITED_STRUCT_MASK \
+ ((nsStyleStructID_size_t(1) << nsStyleStructID_Inherited_Count) - 1)
+// Bits for reset structs.
+#define NS_STYLE_RESET_STRUCT_MASK \
+ (((nsStyleStructID_size_t(1) << nsStyleStructID_Reset_Count) - 1) \
+ << nsStyleStructID_Inherited_Count)
+
+// Additional bits for nsStyleContext's mBits:
+// See nsStyleContext::HasTextDecorationLines
+#define NS_STYLE_HAS_TEXT_DECORATION_LINES 0x001000000
+// See nsStyleContext::HasPseudoElementData.
+#define NS_STYLE_HAS_PSEUDO_ELEMENT_DATA 0x002000000
+// See nsStyleContext::RelevantLinkIsVisited
+#define NS_STYLE_RELEVANT_LINK_VISITED 0x004000000
+// See nsStyleContext::IsStyleIfVisited
+#define NS_STYLE_IS_STYLE_IF_VISITED 0x008000000
+// See nsStyleContext::HasChildThatUsesGrandancestorStyle
+#define NS_STYLE_CHILD_USES_GRANDANCESTOR_STYLE 0x010000000
+// See nsStyleContext::IsShared
+#define NS_STYLE_IS_SHARED 0x020000000
+// See nsStyleContext::AssertStructsNotUsedElsewhere
+// (This bit is currently only used in #ifdef DEBUG code.)
+#define NS_STYLE_IS_GOING_AWAY 0x040000000
+// See nsStyleContext::ShouldSuppressLineBreak
+#define NS_STYLE_SUPPRESS_LINEBREAK 0x080000000
+// See nsStyleContext::IsInDisplayNoneSubtree
+#define NS_STYLE_IN_DISPLAY_NONE_SUBTREE 0x100000000
+// See nsStyleContext::FindChildWithRules
+#define NS_STYLE_INELIGIBLE_FOR_SHARING 0x200000000
+// See nsStyleContext::HasChildThatUsesResetStyle
+#define NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE 0x400000000
+// See nsStyleContext::IsTextCombined
+#define NS_STYLE_IS_TEXT_COMBINED 0x800000000
+// See nsStyleContext::GetPseudoEnum
+#define NS_STYLE_CONTEXT_TYPE_SHIFT 36
+
+// Additional bits for nsRuleNode's mDependentBits:
+#define NS_RULE_NODE_IS_ANIMATION_RULE 0x01000000
+// Free bit 0x02000000
+#define NS_RULE_NODE_USED_DIRECTLY 0x04000000
+#define NS_RULE_NODE_IS_IMPORTANT 0x08000000
+#define NS_RULE_NODE_LEVEL_MASK 0xf0000000
+#define NS_RULE_NODE_LEVEL_SHIFT 28
+
+// Additional bits for nsRuleNode's mNoneBits:
+#define NS_RULE_NODE_HAS_ANIMATION_DATA 0x80000000
+
+static_assert(int(mozilla::SheetType::Count) - 1 <=
+ (NS_RULE_NODE_LEVEL_MASK >> NS_RULE_NODE_LEVEL_SHIFT),
+ "NS_RULE_NODE_LEVEL_MASK cannot fit SheetType");
+
+static_assert(NS_STYLE_INHERIT_MASK == (1 << nsStyleStructID_Length) - 1,
+ "NS_STYLE_INHERIT_MASK is not correct");
+
+static_assert((NS_RULE_NODE_IS_ANIMATION_RULE & NS_STYLE_INHERIT_MASK) == 0,
+ "NS_RULE_NODE_IS_ANIMATION_RULE must not overlap the style struct bits.");
+
+namespace mozilla {
+
+struct Position {
+ using Coord = nsStyleCoord::CalcValue;
+
+ Coord mXPosition, mYPosition;
+
+ // Initialize nothing
+ Position() {}
+
+ // Sets both mXPosition and mYPosition to the given percent value for the
+ // initial property-value (e.g. 0.0f for "0% 0%", or 0.5f for "50% 50%")
+ void SetInitialPercentValues(float aPercentVal);
+
+ // Sets both mXPosition and mYPosition to 0 (app units) for the
+ // initial property-value as a length with no percentage component.
+ void SetInitialZeroValues();
+
+ // True if the effective background image position described by this depends
+ // on the size of the corresponding frame.
+ bool DependsOnPositioningAreaSize() const {
+ return mXPosition.mPercent != 0.0f || mYPosition.mPercent != 0.0f;
+ }
+
+ bool operator==(const Position& aOther) const {
+ return mXPosition == aOther.mXPosition &&
+ mYPosition == aOther.mYPosition;
+ }
+ bool operator!=(const Position& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+} // namespace mozilla
+
+// The lifetime of these objects is managed by the presshell's arena.
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleFont
+{
+ nsStyleFont(const nsFont& aFont, StyleStructContext aContext);
+ nsStyleFont(const nsStyleFont& aStyleFont);
+ explicit nsStyleFont(StyleStructContext aContext);
+ ~nsStyleFont() {
+ MOZ_COUNT_DTOR(nsStyleFont);
+ }
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ nsChangeHint CalcDifference(const nsStyleFont& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return NS_STYLE_HINT_REFLOW |
+ nsChangeHint_NeutralChange;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ /**
+ * Return aSize multiplied by the current text zoom factor (in aPresContext).
+ * aSize is allowed to be negative, but the caller is expected to deal with
+ * negative results. The result is clamped to nscoord_MIN .. nscoord_MAX.
+ */
+ static nscoord ZoomText(StyleStructContext aContext, nscoord aSize);
+ /**
+ * Return aSize divided by the current text zoom factor (in aPresContext).
+ * aSize is allowed to be negative, but the caller is expected to deal with
+ * negative results. The result is clamped to nscoord_MIN .. nscoord_MAX.
+ */
+ static nscoord UnZoomText(nsPresContext* aPresContext, nscoord aSize);
+ static already_AddRefed<nsIAtom> GetLanguage(StyleStructContext aPresContext);
+
+ void* operator new(size_t sz, nsStyleFont* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleFont, sz);
+ }
+ void Destroy(nsPresContext* aContext);
+
+ void EnableZoom(nsPresContext* aContext, bool aEnable);
+
+ nsFont mFont; // [inherited]
+ nscoord mSize; // [inherited] 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.
+ uint8_t mGenericID; // [inherited] generic CSS font family, if any;
+ // value is a kGenericFont_* constant, see nsFont.h.
+
+ // MathML scriptlevel support
+ int8_t mScriptLevel; // [inherited]
+ // MathML mathvariant support
+ uint8_t mMathVariant; // [inherited]
+ // MathML displaystyle support
+ uint8_t mMathDisplay; // [inherited]
+
+ // allow different min font-size for certain cases
+ uint8_t mMinFontSizeRatio; // [inherited] percent * 100
+
+ // was mLanguage set based on a lang attribute in the document?
+ bool mExplicitLanguage; // [inherited]
+
+ // should calls to ZoomText() and UnZoomText() be made to the font
+ // size on this nsStyleFont?
+ bool mAllowZoom; // [inherited]
+
+ // The value mSize would have had if scriptminsize had never been applied
+ nscoord mScriptUnconstrainedSize;
+ nscoord mScriptMinSize; // [inherited] length
+ float mScriptSizeMultiplier; // [inherited]
+ nsCOMPtr<nsIAtom> mLanguage; // [inherited]
+};
+
+struct nsStyleGradientStop
+{
+ nsStyleCoord mLocation; // percent, coord, calc, none
+ nscolor mColor;
+ bool mIsInterpolationHint;
+
+ // Use ==/!= on nsStyleGradient instead of on the gradient stop.
+ bool operator==(const nsStyleGradientStop&) const = delete;
+ bool operator!=(const nsStyleGradientStop&) const = delete;
+};
+
+class nsStyleGradient final
+{
+public:
+ nsStyleGradient();
+ uint8_t mShape; // NS_STYLE_GRADIENT_SHAPE_*
+ uint8_t mSize; // NS_STYLE_GRADIENT_SIZE_*;
+ // not used (must be FARTHEST_CORNER) for linear shape
+ bool mRepeating;
+ bool mLegacySyntax;
+
+ nsStyleCoord mBgPosX; // percent, coord, calc, none
+ nsStyleCoord mBgPosY; // percent, coord, calc, none
+ nsStyleCoord mAngle; // none, angle
+
+ nsStyleCoord mRadiusX; // percent, coord, calc, none
+ nsStyleCoord mRadiusY; // percent, coord, calc, none
+
+ // stops are in the order specified in the stylesheet
+ nsTArray<nsStyleGradientStop> mStops;
+
+ bool operator==(const nsStyleGradient& aOther) const;
+ bool operator!=(const nsStyleGradient& aOther) const {
+ return !(*this == aOther);
+ }
+
+ bool IsOpaque();
+ bool HasCalc();
+ uint32_t Hash(PLDHashNumber aHash);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsStyleGradient)
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsStyleGradient() {}
+
+ nsStyleGradient(const nsStyleGradient& aOther) = delete;
+ nsStyleGradient& operator=(const nsStyleGradient& aOther) = delete;
+};
+
+/**
+ * A wrapper for an imgRequestProxy that supports off-main-thread creation
+ * and equality comparison.
+ *
+ * An nsStyleImageRequest can be created in two ways:
+ *
+ * 1. Using the constructor that takes an imgRequestProxy. This must
+ * be called from the main thread. The nsStyleImageRequest is
+ * immediately considered "resolved", and the get() method that
+ * returns the imgRequestProxy can be called.
+ *
+ * 2. Using the constructor that takes the URL, base URI, referrer
+ * and principal that can be used to inititiate an image load and
+ * produce an imgRequestProxy later. This can be called from
+ * any thread. The nsStyleImageRequest is not considered "resolved"
+ * at this point, and the Resolve() method must be called later
+ * to initiate the image load and make calls to get() valid.
+ *
+ * Calls to TrackImage(), UntrackImage(), LockImage(), UnlockImage() and
+ * RequestDiscard() are made to the imgRequestProxy and ImageTracker as
+ * appropriate, according to the mode flags passed in to the constructor.
+ *
+ * The main thread constructor takes a pointer to the css::ImageValue that
+ * is the specified url() value, while the off-main-thread constructor
+ * creates a new css::ImageValue to represent the url() information passed
+ * to the constructor. This ImageValue is held on to for the comparisons done
+ * in DefinitelyEquals(), so that we don't need to call into the non-OMT-safe
+ * Equals() on the nsIURI objects returned from imgRequestProxy::GetURI().
+ */
+class nsStyleImageRequest
+{
+public:
+ // Flags describing whether the imgRequestProxy must be tracked in the
+ // ImageTracker, whether LockImage/UnlockImage calls will be made
+ // when obtaining and releasing the imgRequestProxy, and whether
+ // RequestDiscard will be called on release.
+ enum class Mode : uint8_t {
+ // The imgRequestProxy will be added to the ImageTracker when resolved
+ // Without this flag, the nsStyleImageRequest itself will call LockImage/
+ // UnlockImage on the imgRequestProxy, rather than leaving locking to the
+ // ImageTracker to manage.
+ //
+ // This flag is currently used by all nsStyleImageRequests except
+ // those for list-style-image and cursor.
+ Track = 0x1,
+
+ // The imgRequestProxy will have its RequestDiscard method called when
+ // the nsStyleImageRequest is going away.
+ //
+ // This is currently used only for cursor images.
+ Discard = 0x2,
+ };
+
+ // Must be called from the main thread.
+ //
+ // aImageTracker must be non-null iff aModeFlags contains Track.
+ nsStyleImageRequest(Mode aModeFlags,
+ imgRequestProxy* aRequestProxy,
+ mozilla::css::ImageValue* aImageValue,
+ mozilla::dom::ImageTracker* aImageTracker);
+
+ // Can be called from any thread, but Resolve() must be called later
+ // on the main thread before get() can be used.
+ nsStyleImageRequest(
+ Mode aModeFlags,
+ nsStringBuffer* aURLBuffer,
+ already_AddRefed<mozilla::PtrHolder<nsIURI>> aBaseURI,
+ already_AddRefed<mozilla::PtrHolder<nsIURI>> aReferrer,
+ already_AddRefed<mozilla::PtrHolder<nsIPrincipal>> aPrincipal);
+
+ bool Resolve(nsPresContext* aPresContext);
+ bool IsResolved() const { return mResolved; }
+
+ imgRequestProxy* get() {
+ MOZ_ASSERT(IsResolved(), "Resolve() must be called first");
+ MOZ_ASSERT(NS_IsMainThread());
+ return mRequestProxy.get();
+ }
+ const imgRequestProxy* get() const {
+ return const_cast<nsStyleImageRequest*>(this)->get();
+ }
+
+ // Returns whether the ImageValue objects in the two nsStyleImageRequests
+ // return true from URLValueData::DefinitelyEqualURIs.
+ bool DefinitelyEquals(const nsStyleImageRequest& aOther) const;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsStyleImageRequest);
+
+private:
+ ~nsStyleImageRequest();
+ nsStyleImageRequest& operator=(const nsStyleImageRequest& aOther) = delete;
+
+ void MaybeTrackAndLock();
+
+ RefPtr<imgRequestProxy> mRequestProxy;
+ RefPtr<mozilla::css::ImageValue> mImageValue;
+ RefPtr<mozilla::dom::ImageTracker> mImageTracker;
+
+ Mode mModeFlags;
+ bool mResolved;
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsStyleImageRequest::Mode)
+
+enum nsStyleImageType {
+ eStyleImageType_Null,
+ eStyleImageType_Image,
+ eStyleImageType_Gradient,
+ eStyleImageType_Element
+};
+
+struct CachedBorderImageData
+{
+ // Caller are expected to ensure that the value of aSVGViewportSize is
+ // different from the cached one since the method won't do the check.
+ void SetCachedSVGViewportSize(const mozilla::Maybe<nsSize>& aSVGViewportSize);
+ const mozilla::Maybe<nsSize>& GetCachedSVGViewportSize();
+ void PurgeCachedImages();
+ void SetSubImage(uint8_t aIndex, imgIContainer* aSubImage);
+ imgIContainer* GetSubImage(uint8_t aIndex);
+
+private:
+ // If this is a SVG border-image, we save the size of the SVG viewport that
+ // we used when rasterizing any cached border-image subimages. (The viewport
+ // size matters for percent-valued sizes & positions in inner SVG doc).
+ mozilla::Maybe<nsSize> mCachedSVGViewportSize;
+ nsCOMArray<imgIContainer> mSubImages;
+};
+
+/**
+ * Represents a paintable image of one of the following types.
+ * (1) A real image loaded from an external source.
+ * (2) A CSS linear or radial gradient.
+ * (3) An element within a document, or an <img>, <video>, or <canvas> element
+ * not in a document.
+ * (*) Optionally a crop rect can be set to paint a partial (rectangular)
+ * region of an image. (Currently, this feature is only supported with an
+ * image of type (1)).
+ */
+struct nsStyleImage
+{
+ nsStyleImage();
+ ~nsStyleImage();
+ nsStyleImage(const nsStyleImage& aOther);
+ nsStyleImage& operator=(const nsStyleImage& aOther);
+
+ void SetNull();
+ void SetImageRequest(already_AddRefed<nsStyleImageRequest> aImage);
+ void SetGradientData(nsStyleGradient* aGradient);
+ void SetElementId(const char16_t* aElementId);
+ void SetCropRect(mozilla::UniquePtr<nsStyleSides> aCropRect);
+
+ void ResolveImage(nsPresContext* aContext) {
+ MOZ_ASSERT(mType != eStyleImageType_Image || mImage);
+ if (mType == eStyleImageType_Image && !mImage->IsResolved()) {
+ mImage->Resolve(aContext);
+ }
+ }
+
+ nsStyleImageType GetType() const {
+ return mType;
+ }
+ nsStyleImageRequest* GetImageRequest() const {
+ MOZ_ASSERT(mType == eStyleImageType_Image, "Data is not an image!");
+ MOZ_ASSERT(mImage);
+ return mImage;
+ }
+ imgRequestProxy* GetImageData() const {
+ return GetImageRequest()->get();
+ }
+ nsStyleGradient* GetGradientData() const {
+ NS_ASSERTION(mType == eStyleImageType_Gradient, "Data is not a gradient!");
+ return mGradient;
+ }
+ const char16_t* GetElementId() const {
+ NS_ASSERTION(mType == eStyleImageType_Element, "Data is not an element!");
+ return mElementId;
+ }
+ const mozilla::UniquePtr<nsStyleSides>& GetCropRect() const {
+ NS_ASSERTION(mType == eStyleImageType_Image,
+ "Only image data can have a crop rect");
+ return mCropRect;
+ }
+
+ /**
+ * Compute the actual crop rect in pixels, using the source image bounds.
+ * The computation involves converting percentage unit to pixel unit and
+ * clamping each side value to fit in the source image bounds.
+ * @param aActualCropRect the computed actual crop rect.
+ * @param aIsEntireImage true iff |aActualCropRect| is identical to the
+ * source image bounds.
+ * @return true iff |aActualCropRect| holds a meaningful value.
+ */
+ bool ComputeActualCropRect(nsIntRect& aActualCropRect,
+ bool* aIsEntireImage = nullptr) const;
+
+ /**
+ * Starts the decoding of a image.
+ */
+ nsresult StartDecoding() const;
+ /**
+ * @return true if the item is definitely opaque --- i.e., paints every
+ * pixel within its bounds opaquely, and the bounds contains at least a pixel.
+ */
+ bool IsOpaque() const;
+ /**
+ * @return true if this image is fully loaded, and its size is calculated;
+ * always returns true if |mType| is |eStyleImageType_Gradient| or
+ * |eStyleImageType_Element|.
+ */
+ bool IsComplete() const;
+ /**
+ * @return true if this image is loaded without error;
+ * always returns true if |mType| is |eStyleImageType_Gradient| or
+ * |eStyleImageType_Element|.
+ */
+ bool IsLoaded() const;
+ /**
+ * @return true if it is 100% confident that this image contains no pixel
+ * to draw.
+ */
+ bool IsEmpty() const {
+ // There are some other cases when the image will be empty, for example
+ // when the crop rect is empty. However, checking the emptiness of crop
+ // rect is non-trivial since each side value can be specified with
+ // percentage unit, which can not be evaluated until the source image size
+ // is available. Therefore, we currently postpone the evaluation of crop
+ // rect until the actual rendering time --- alternatively until GetOpaqueRegion()
+ // is called.
+ return mType == eStyleImageType_Null;
+ }
+
+ bool operator==(const nsStyleImage& aOther) const;
+ bool operator!=(const nsStyleImage& aOther) const {
+ return !(*this == aOther);
+ }
+
+ bool ImageDataEquals(const nsStyleImage& aOther) const
+ {
+ return GetType() == eStyleImageType_Image &&
+ aOther.GetType() == eStyleImageType_Image &&
+ GetImageData() == aOther.GetImageData();
+ }
+
+ // These methods are used for the caller to caches the sub images created
+ // during a border-image paint operation
+ inline void SetSubImage(uint8_t aIndex, imgIContainer* aSubImage) const;
+ inline imgIContainer* GetSubImage(uint8_t aIndex) const;
+ void PurgeCacheForViewportChange(
+ const mozilla::Maybe<nsSize>& aSVGViewportSize,
+ const bool aHasIntrinsicRatio) const;
+
+private:
+ void DoCopy(const nsStyleImage& aOther);
+ void EnsureCachedBIData() const;
+
+ // This variable keeps some cache data for border image and is lazily
+ // allocated since it is only used in border image case.
+ mozilla::UniquePtr<CachedBorderImageData> mCachedBIData;
+
+ nsStyleImageType mType;
+ union {
+ nsStyleImageRequest* mImage;
+ nsStyleGradient* mGradient;
+ char16_t* mElementId;
+ };
+
+ // This is _currently_ used only in conjunction with eStyleImageType_Image.
+ mozilla::UniquePtr<nsStyleSides> mCropRect;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleColor
+{
+ explicit nsStyleColor(StyleStructContext aContext);
+ nsStyleColor(const nsStyleColor& aOther);
+ ~nsStyleColor() {
+ MOZ_COUNT_DTOR(nsStyleColor);
+ }
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ nscolor CalcComplexColor(const mozilla::StyleComplexColor& aColor) const {
+ return mozilla::LinearBlendColors(aColor.mColor, mColor,
+ aColor.mForegroundRatio);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleColor& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_RepaintFrame;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants at all.
+ return nsChangeHint(0);
+ }
+
+ void* operator new(size_t sz, nsStyleColor* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleColor, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleColor();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleColor, this);
+ }
+
+ // Don't add ANY members to this struct! We can achieve caching in the rule
+ // tree (rather than the style tree) by letting color stay by itself! -dwh
+ nscolor mColor; // [inherited]
+};
+
+/**
+ * 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& aOther) { *this = aOther; }
+ nsStyleAutoArray& operator=(const nsStyleAutoArray& aOther) {
+ mFirstElement = aOther.mFirstElement;
+ mOtherElements = aOther.mOtherElements;
+ return *this;
+ }
+
+ 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;
+};
+
+struct nsStyleImageLayers {
+ // Indices into kBackgroundLayerTable and kMaskLayerTable
+ enum {
+ shorthand = 0,
+ color,
+ image,
+ repeat,
+ positionX,
+ positionY,
+ clip,
+ origin,
+ size,
+ attachment,
+ maskMode,
+ composite
+ };
+
+ enum class LayerType : uint8_t {
+ Background = 0,
+ Mask
+ };
+
+ explicit nsStyleImageLayers(LayerType aType);
+ nsStyleImageLayers(const nsStyleImageLayers &aSource);
+ ~nsStyleImageLayers() {
+ MOZ_COUNT_DTOR(nsStyleImageLayers);
+ }
+
+ static bool IsInitialPositionForLayerType(mozilla::Position aPosition, LayerType aType);
+
+ struct Size;
+ friend struct Size;
+ struct Size {
+ struct Dimension : public nsStyleCoord::CalcValue {
+ nscoord ResolveLengthPercentage(nscoord aAvailable) const {
+ double d = double(mPercent) * double(aAvailable) + double(mLength);
+ if (d < 0.0) {
+ return 0;
+ }
+ return NSToCoordRoundWithClamp(float(d));
+ }
+ };
+ Dimension mWidth, mHeight;
+
+ bool IsInitialValue() const {
+ return mWidthType == eAuto && mHeightType == eAuto;
+ }
+
+ nscoord ResolveWidthLengthPercentage(const nsSize& aBgPositioningArea) const {
+ MOZ_ASSERT(mWidthType == eLengthPercentage,
+ "resolving non-length/percent dimension!");
+ return mWidth.ResolveLengthPercentage(aBgPositioningArea.width);
+ }
+
+ nscoord ResolveHeightLengthPercentage(const nsSize& aBgPositioningArea) const {
+ MOZ_ASSERT(mHeightType == eLengthPercentage,
+ "resolving non-length/percent dimension!");
+ return mHeight.ResolveLengthPercentage(aBgPositioningArea.height);
+ }
+
+ // Except for eLengthPercentage, Dimension types which might change
+ // how a layer is painted when the corresponding frame's dimensions
+ // change *must* precede all dimension types which are agnostic to
+ // frame size; see DependsOnDependsOnPositioningAreaSizeSize.
+ enum DimensionType {
+ // If one of mWidth and mHeight is eContain or eCover, then both are.
+ // NOTE: eContain and eCover *must* be equal to NS_STYLE_BG_SIZE_CONTAIN
+ // and NS_STYLE_BG_SIZE_COVER (in kBackgroundSizeKTable).
+ eContain, eCover,
+
+ eAuto,
+ eLengthPercentage,
+ eDimensionType_COUNT
+ };
+ uint8_t mWidthType, mHeightType;
+
+ // True if the effective image size described by this depends on the size of
+ // the corresponding frame, when aImage (which must not have null type) is
+ // the background image.
+ bool DependsOnPositioningAreaSize(const nsStyleImage& aImage) const;
+
+ // Initialize nothing
+ Size() {}
+
+ // Initialize to initial values
+ void SetInitialValues();
+
+ bool operator==(const Size& aOther) const;
+ bool operator!=(const Size& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+
+ struct Repeat;
+ friend struct Repeat;
+ struct Repeat {
+ uint8_t mXRepeat, mYRepeat;
+
+ // Initialize nothing
+ Repeat() {}
+
+ bool IsInitialValue() const {
+ return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
+ mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+ }
+
+ bool DependsOnPositioningAreaSize() const {
+ return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
+ mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_SPACE;
+ }
+
+ // Initialize to initial values
+ void SetInitialValues() {
+ mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+ mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+ }
+
+ bool operator==(const Repeat& aOther) const {
+ return mXRepeat == aOther.mXRepeat &&
+ mYRepeat == aOther.mYRepeat;
+ }
+ bool operator!=(const Repeat& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+
+ struct Layer;
+ friend struct Layer;
+ struct Layer {
+ nsStyleImage mImage; // [reset]
+ RefPtr<mozilla::css::URLValueData> mSourceURI; // [reset]
+ // mask-only property
+ // This property is used for mask layer only.
+ // For a background layer, it should always
+ // be the initial value, which is nullptr.
+ // Store mask-image URI so that we can resolve
+ // SVG mask path later. (Might be a URLValue
+ // or an ImageValue.)
+ mozilla::Position mPosition; // [reset]
+ Size mSize; // [reset]
+ uint8_t mClip; // [reset] See nsStyleConsts.h
+ MOZ_INIT_OUTSIDE_CTOR
+ uint8_t mOrigin; // [reset] See nsStyleConsts.h
+ uint8_t mAttachment; // [reset] See nsStyleConsts.h
+ // background-only property
+ // This property is used for background layer
+ // only. For a mask layer, it should always
+ // be the initial value, which is
+ // NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL.
+ uint8_t mBlendMode; // [reset] See nsStyleConsts.h
+ // background-only property
+ // This property is used for background layer
+ // only. For a mask layer, it should always
+ // be the initial value, which is
+ // NS_STYLE_BLEND_NORMAL.
+ uint8_t mComposite; // [reset] See nsStyleConsts.h
+ // mask-only property
+ // This property is used for mask layer only.
+ // For a background layer, it should always
+ // be the initial value, which is
+ // NS_STYLE_COMPOSITE_MODE_ADD.
+ uint8_t mMaskMode; // [reset] See nsStyleConsts.h
+ // mask-only property
+ // This property is used for mask layer only.
+ // For a background layer, it should always
+ // be the initial value, which is
+ // NS_STYLE_MASK_MODE_MATCH_SOURCE.
+ Repeat mRepeat; // [reset] See nsStyleConsts.h
+
+ // 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(nsPresContext* aContext) {
+ if (mImage.GetType() == eStyleImageType_Image) {
+ mImage.ResolveImage(aContext);
+ }
+ }
+
+ // 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,
+ mClipCount,
+ mOriginCount,
+ mRepeatCount,
+ mPositionXCount,
+ mPositionYCount,
+ mImageCount,
+ mSizeCount,
+ mMaskModeCount,
+ mBlendModeCount,
+ 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(nsPresContext* aContext) {
+ for (uint32_t i = 0; i < mImageCount; ++i) {
+ mLayers[i].ResolveImage(aContext);
+ }
+ }
+
+ nsChangeHint CalcDifference(const nsStyleImageLayers& aNewLayers,
+ nsStyleImageLayers::LayerType aType) const;
+
+ bool HasLayerWithImage() const;
+ nsStyleImageLayers& operator=(const nsStyleImageLayers& aOther);
+
+ 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 {
+ explicit nsStyleBackground(StyleStructContext aContext);
+ nsStyleBackground(const nsStyleBackground& aOther);
+ ~nsStyleBackground();
+
+ // Resolves and tracks the images in mImage. Only called with a Servo-backed
+ // style system, where those images must be resolved later than the OMT
+ // nsStyleBackground constructor call.
+ void FinishStyle(nsPresContext* aPresContext);
+
+ void* operator new(size_t sz, nsStyleBackground* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleBackground, sz);
+ }
+ void Destroy(nsPresContext* aContext);
+
+ nsChangeHint CalcDifference(const nsStyleBackground& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_UpdateEffects |
+ nsChangeHint_RepaintFrame |
+ nsChangeHint_UpdateBackgroundPosition |
+ nsChangeHint_NeutralChange;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants at all.
+ return nsChangeHint(0);
+ }
+
+ // True if this background is completely transparent.
+ bool IsTransparent() 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;
+ nscolor mBackgroundColor; // [reset]
+};
+
+#define NS_SPACING_MARGIN 0
+#define NS_SPACING_PADDING 1
+#define NS_SPACING_BORDER 2
+
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleMargin
+{
+ explicit nsStyleMargin(StyleStructContext aContext);
+ nsStyleMargin(const nsStyleMargin& aMargin);
+ ~nsStyleMargin() {
+ MOZ_COUNT_DTOR(nsStyleMargin);
+ }
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleMargin* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleMargin, sz);
+ }
+ void Destroy(nsPresContext* aContext);
+
+ nsChangeHint CalcDifference(const nsStyleMargin& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference can return all of the reflow hints sometimes not
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint(0);
+ }
+
+ bool GetMargin(nsMargin& aMargin) const
+ {
+ if (!mMargin.ConvertsToLength()) {
+ return false;
+ }
+
+ NS_FOR_CSS_SIDES(side) {
+ aMargin.Side(side) = mMargin.ToLength(side);
+ }
+ return true;
+ }
+
+ // 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;
+
+ nsStyleSides mMargin; // [reset] coord, percent, calc, auto
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePadding
+{
+ explicit nsStylePadding(StyleStructContext aContext);
+ nsStylePadding(const nsStylePadding& aPadding);
+ ~nsStylePadding() {
+ MOZ_COUNT_DTOR(nsStylePadding);
+ }
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStylePadding* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStylePadding, sz);
+ }
+ void Destroy(nsPresContext* aContext);
+
+ nsChangeHint CalcDifference(const nsStylePadding& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return NS_STYLE_HINT_REFLOW & ~nsChangeHint_ClearDescendantIntrinsics;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference can return nsChangeHint_ClearAncestorIntrinsics as
+ // a hint not handled for descendants. We could (and perhaps
+ // should) return nsChangeHint_NeedReflow and
+ // nsChangeHint_ReflowChangesSizeOrPosition as always handled for
+ // descendants, but since they're always returned in conjunction
+ // with nsChangeHint_ClearAncestorIntrinsics (which is not), it
+ // won't ever lead to any optimization in
+ // nsStyleContext::CalcStyleDifference.
+ return nsChangeHint(0);
+ }
+
+ nsStyleSides mPadding; // [reset] coord, percent, calc
+
+ bool IsWidthDependent() const {
+ return !mPadding.ConvertsToLength();
+ }
+
+ bool GetPadding(nsMargin& aPadding) const
+ {
+ if (!mPadding.ConvertsToLength()) {
+ return false;
+ }
+
+ NS_FOR_CSS_SIDES(side) {
+ // Clamp negative calc() to 0.
+ aPadding.Side(side) = std::max(mPadding.ToLength(side), 0);
+ }
+ return true;
+ }
+};
+
+struct nsBorderColors
+{
+ nsBorderColors* mNext;
+ nscolor mColor;
+
+ nsBorderColors() : mNext(nullptr), mColor(NS_RGB(0,0,0)) {}
+ explicit nsBorderColors(const nscolor& aColor) : mNext(nullptr), mColor(aColor) {}
+ ~nsBorderColors();
+
+ nsBorderColors* Clone() const { return Clone(true); }
+
+ static bool Equal(const nsBorderColors* c1,
+ const nsBorderColors* c2) {
+ if (c1 == c2) {
+ return true;
+ }
+ while (c1 && c2) {
+ if (c1->mColor != c2->mColor) {
+ return false;
+ }
+ c1 = c1->mNext;
+ c2 = c2->mNext;
+ }
+ // both should be nullptr if these are equal, otherwise one
+ // has more colors than another
+ return !c1 && !c2;
+ }
+
+private:
+ nsBorderColors* Clone(bool aDeep) const;
+};
+
+struct nsCSSShadowItem
+{
+ nscoord mXOffset;
+ nscoord mYOffset;
+ nscoord mRadius;
+ nscoord mSpread;
+
+ nscolor mColor;
+ bool mHasColor; // Whether mColor should be used
+ bool mInset;
+
+ nsCSSShadowItem() : mHasColor(false) {
+ MOZ_COUNT_CTOR(nsCSSShadowItem);
+ }
+ ~nsCSSShadowItem() {
+ MOZ_COUNT_DTOR(nsCSSShadowItem);
+ }
+
+ bool operator==(const nsCSSShadowItem& aOther) const {
+ return (mXOffset == aOther.mXOffset &&
+ mYOffset == aOther.mYOffset &&
+ mRadius == aOther.mRadius &&
+ mHasColor == aOther.mHasColor &&
+ mSpread == aOther.mSpread &&
+ mInset == aOther.mInset &&
+ (!mHasColor || mColor == aOther.mColor));
+ }
+ bool operator!=(const nsCSSShadowItem& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+class nsCSSShadowArray final
+{
+public:
+ void* operator new(size_t aBaseSize, uint32_t aArrayLen) {
+ // We can allocate both this nsCSSShadowArray and the
+ // actual array in one allocation. The amount of memory to
+ // allocate is equal to the class's size + the number of bytes for all
+ // but the first array item (because aBaseSize includes one
+ // item, see the private declarations)
+ return ::operator new(aBaseSize +
+ (aArrayLen - 1) * sizeof(nsCSSShadowItem));
+ }
+
+ explicit nsCSSShadowArray(uint32_t aArrayLen) :
+ mLength(aArrayLen)
+ {
+ MOZ_COUNT_CTOR(nsCSSShadowArray);
+ for (uint32_t i = 1; i < mLength; ++i) {
+ // Make sure we call the constructors of each nsCSSShadowItem
+ // (the first one is called for us because we declared it under private)
+ new (&mArray[i]) nsCSSShadowItem();
+ }
+ }
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsCSSShadowArray() {
+ MOZ_COUNT_DTOR(nsCSSShadowArray);
+ for (uint32_t i = 1; i < mLength; ++i) {
+ mArray[i].~nsCSSShadowItem();
+ }
+ }
+
+public:
+ uint32_t Length() const { return mLength; }
+ nsCSSShadowItem* ShadowAt(uint32_t i) {
+ MOZ_ASSERT(i < mLength, "Accessing too high an index in the text shadow array!");
+ return &mArray[i];
+ }
+ const nsCSSShadowItem* ShadowAt(uint32_t i) const {
+ MOZ_ASSERT(i < mLength, "Accessing too high an index in the text shadow array!");
+ return &mArray[i];
+ }
+
+ bool HasShadowWithInset(bool aInset) {
+ for (uint32_t i = 0; i < mLength; ++i) {
+ if (mArray[i].mInset == aInset) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool operator==(const nsCSSShadowArray& aOther) const {
+ if (mLength != aOther.Length()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < mLength; ++i) {
+ if (ShadowAt(i) != aOther.ShadowAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCSSShadowArray)
+
+private:
+ uint32_t mLength;
+ nsCSSShadowItem mArray[1]; // This MUST be the last item
+};
+
+// 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))
+// Outline offset is rounded to the nearest integer number of pixels, but values
+// between zero and one device pixels are always rounded up to one device pixel.
+// Note that the offset can be negative.
+#define NS_ROUND_OFFSET_TO_PIXELS(l,tpp) \
+ (((l) == 0) ? 0 : \
+ ((l) > 0) ? std::max( (tpp), ((l) + ((tpp) / 2)) / (tpp) * (tpp)) : \
+ std::min(-(tpp), ((l) - ((tpp) / 2)) / (tpp) * (tpp)))
+
+// Returns if the given border style type is visible or not
+static bool IsVisibleBorderStyle(uint8_t aStyle)
+{
+ return (aStyle != NS_STYLE_BORDER_STYLE_NONE &&
+ aStyle != NS_STYLE_BORDER_STYLE_HIDDEN);
+}
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleBorder
+{
+ explicit nsStyleBorder(StyleStructContext aContext);
+ nsStyleBorder(const nsStyleBorder& aBorder);
+ ~nsStyleBorder();
+
+ // Resolves and tracks mBorderImageSource. Only called with a Servo-backed
+ // style system, where those images must be resolved later than the OMT
+ // nsStyleBorder constructor call.
+ void FinishStyle(nsPresContext* aPresContext);
+
+ void* operator new(size_t sz, nsStyleBorder* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleBorder, sz);
+ }
+ void Destroy(nsPresContext* aContext);
+
+ nsChangeHint CalcDifference(const nsStyleBorder& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return NS_STYLE_HINT_REFLOW |
+ nsChangeHint_UpdateOverflow |
+ nsChangeHint_BorderStyleNoneChange |
+ nsChangeHint_NeutralChange;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ void EnsureBorderColors() {
+ if (!mBorderColors) {
+ mBorderColors = new nsBorderColors*[4];
+ if (mBorderColors) {
+ for (int32_t i = 0; i < 4; i++) {
+ mBorderColors[i] = nullptr;
+ }
+ }
+ }
+ }
+
+ void ClearBorderColors(mozilla::css::Side aSide) {
+ if (mBorderColors && mBorderColors[aSide]) {
+ delete mBorderColors[aSide];
+ mBorderColors[aSide] = nullptr;
+ }
+ }
+
+ // 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::css::Side aSide) const
+ {
+ return IsVisibleBorderStyle(mBorderStyle[aSide]);
+ }
+
+ // aBorderWidth is in twips
+ void SetBorderWidth(mozilla::css::Side aSide, nscoord aBorderWidth)
+ {
+ nscoord roundedWidth =
+ NS_ROUND_BORDER_TO_PIXELS(aBorderWidth, mTwipsPerPixel);
+ 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.IsEmpty();
+ }
+
+ // 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::css::Side aSide) const
+ {
+ return GetComputedBorder().Side(aSide);
+ }
+
+ uint8_t GetBorderStyle(mozilla::css::Side aSide) const
+ {
+ NS_ASSERTION(aSide <= NS_SIDE_LEFT, "bad side");
+ return mBorderStyle[aSide];
+ }
+
+ void SetBorderStyle(mozilla::css::Side aSide, uint8_t aStyle)
+ {
+ NS_ASSERTION(aSide <= NS_SIDE_LEFT, "bad side");
+ mBorderStyle[aSide] = aStyle;
+ mComputedBorder.Side(aSide) =
+ (HasVisibleStyle(aSide) ? mBorder.Side(aSide) : 0);
+ }
+
+ inline bool IsBorderImageLoaded() const
+ {
+ return mBorderImageSource.IsLoaded();
+ }
+
+ void ResolveImage(nsPresContext* aContext)
+ {
+ if (mBorderImageSource.GetType() == eStyleImageType_Image) {
+ mBorderImageSource.ResolveImage(aContext);
+ }
+ }
+
+ nsMargin GetImageOutset() const;
+
+ void GetCompositeColors(int32_t aIndex, nsBorderColors** aColors) const
+ {
+ if (!mBorderColors) {
+ *aColors = nullptr;
+ } else {
+ *aColors = mBorderColors[aIndex];
+ }
+ }
+
+ void AppendBorderColor(int32_t aIndex, nscolor aColor)
+ {
+ NS_ASSERTION(aIndex >= 0 && aIndex <= 3, "bad side for composite border color");
+ nsBorderColors* colorEntry = new nsBorderColors(aColor);
+ if (!mBorderColors[aIndex]) {
+ mBorderColors[aIndex] = colorEntry;
+ } else {
+ nsBorderColors* last = mBorderColors[aIndex];
+ while (last->mNext) {
+ last = last->mNext;
+ }
+ last->mNext = colorEntry;
+ }
+ }
+
+ imgIRequest* GetBorderImageRequest() const
+ {
+ if (mBorderImageSource.GetType() == eStyleImageType_Image) {
+ return mBorderImageSource.GetImageData();
+ }
+ return nullptr;
+ }
+
+public:
+ nsBorderColors** mBorderColors; // [reset] composite (stripe) colors
+ nsStyleCorners mBorderRadius; // [reset] coord, percent
+ nsStyleImage mBorderImageSource; // [reset]
+ nsStyleSides mBorderImageSlice; // [reset] factor, percent
+ nsStyleSides mBorderImageWidth; // [reset] length, factor, percent, auto
+ nsStyleSides mBorderImageOutset; // [reset] length, factor
+
+ uint8_t mBorderImageFill; // [reset]
+ uint8_t mBorderImageRepeatH; // [reset] see nsStyleConsts.h
+ uint8_t mBorderImageRepeatV; // [reset]
+ mozilla::StyleFloatEdge mFloatEdge; // [reset]
+ mozilla::StyleBoxDecorationBreak mBoxDecorationBreak; // [reset]
+
+protected:
+ uint8_t mBorderStyle[4]; // [reset] See nsStyleConsts.h
+
+public:
+ // [reset] the colors to use for a simple border.
+ // not used for -moz-border-colors
+ union {
+ struct {
+ mozilla::StyleComplexColor mBorderTopColor;
+ mozilla::StyleComplexColor mBorderRightColor;
+ mozilla::StyleComplexColor mBorderBottomColor;
+ mozilla::StyleComplexColor mBorderLeftColor;
+ };
+ mozilla::StyleComplexColor mBorderColor[4];
+ };
+
+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;
+
+private:
+ nscoord mTwipsPerPixel;
+
+ nsStyleBorder& operator=(const nsStyleBorder& aOther) = delete;
+};
+
+#define ASSERT_BORDER_COLOR_FIELD(side_) \
+ static_assert(offsetof(nsStyleBorder, mBorder##side_##Color) == \
+ offsetof(nsStyleBorder, mBorderColor) + \
+ size_t(mozilla::eSide##side_) * \
+ sizeof(mozilla::StyleComplexColor), \
+ "mBorder" #side_ "Color must be at same offset " \
+ "as mBorderColor[mozilla::eSide" #side_ "]")
+ASSERT_BORDER_COLOR_FIELD(Top);
+ASSERT_BORDER_COLOR_FIELD(Right);
+ASSERT_BORDER_COLOR_FIELD(Bottom);
+ASSERT_BORDER_COLOR_FIELD(Left);
+#undef ASSERT_BORDER_COLOR_FIELD
+
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleOutline
+{
+ explicit nsStyleOutline(StyleStructContext aContext);
+ nsStyleOutline(const nsStyleOutline& aOutline);
+ ~nsStyleOutline() {
+ MOZ_COUNT_DTOR(nsStyleOutline);
+ }
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleOutline* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleOutline, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleOutline();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleOutline, this);
+ }
+
+ void RecalcData();
+ nsChangeHint CalcDifference(const nsStyleOutline& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_UpdateOverflow |
+ nsChangeHint_SchedulePaint |
+ nsChangeHint_RepaintFrame |
+ nsChangeHint_NeutralChange;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants at all.
+ return nsChangeHint(0);
+ }
+
+ nsStyleCorners mOutlineRadius; // [reset] coord, percent, calc
+
+ // 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.)
+ nsStyleCoord mOutlineWidth; // [reset] coord, enum (see nsStyleConsts.h)
+ nscoord mOutlineOffset; // [reset]
+ mozilla::StyleComplexColor mOutlineColor; // [reset]
+ uint8_t mOutlineStyle; // [reset] See nsStyleConsts.h
+
+ nscoord GetOutlineWidth() const
+ {
+ return mActualOutlineWidth;
+ }
+
+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;
+ nscoord mTwipsPerPixel;
+};
+
+
+/**
+ * An object that allows sharing of arrays that store 'quotes' property
+ * values. This is particularly important for inheritance, where we want
+ * to share the same 'quotes' value with a parent style context.
+ */
+class nsStyleQuoteValues
+{
+public:
+ typedef nsTArray<std::pair<nsString, nsString>> QuotePairArray;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsStyleQuoteValues);
+ QuotePairArray mQuotePairs;
+
+private:
+ ~nsStyleQuoteValues() {}
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleList
+{
+ explicit nsStyleList(StyleStructContext aContext);
+ nsStyleList(const nsStyleList& aStyleList);
+ ~nsStyleList();
+
+ void FinishStyle(nsPresContext* aPresContext);
+
+ void* operator new(size_t sz, nsStyleList* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleList, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleList();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleList, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleList& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame |
+ NS_STYLE_HINT_REFLOW;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ static void Shutdown() {
+ sInitialQuotes = nullptr;
+ sNoneQuotes = nullptr;
+ }
+
+ imgRequestProxy* GetListStyleImage() const
+ {
+ return mListStyleImage ? mListStyleImage->get() : nullptr;
+ }
+
+ void GetListStyleType(nsSubstring& aType) const { mCounterStyle->GetStyleName(aType); }
+ mozilla::CounterStyle* GetCounterStyle() const
+ {
+ return mCounterStyle.get();
+ }
+ void SetCounterStyle(mozilla::CounterStyle* aStyle)
+ {
+ // NB: This function is called off-main-thread during parallel restyle, but
+ // only with builtin styles that use dummy refcounting.
+ MOZ_ASSERT(NS_IsMainThread() || !aStyle->IsDependentStyle());
+ mCounterStyle = aStyle;
+ }
+ void SetListStyleType(const nsSubstring& aType,
+ nsPresContext* aPresContext)
+ {
+ SetCounterStyle(aPresContext->CounterStyleManager()->BuildCounterStyle(aType));
+ }
+
+ const nsStyleQuoteValues::QuotePairArray& GetQuotePairs() const;
+
+ void SetQuotesInherit(const nsStyleList* aOther);
+ void SetQuotesInitial();
+ void SetQuotesNone();
+ void SetQuotes(nsStyleQuoteValues::QuotePairArray&& aValues);
+
+ uint8_t mListStylePosition; // [inherited]
+ RefPtr<nsStyleImageRequest> mListStyleImage; // [inherited]
+private:
+ RefPtr<mozilla::CounterStyle> mCounterStyle; // [inherited]
+ RefPtr<nsStyleQuoteValues> mQuotes; // [inherited]
+ nsStyleList& operator=(const nsStyleList& aOther) = delete;
+public:
+ nsRect mImageRegion; // [inherited] the rect to use within an image
+
+private:
+ // nsStyleQuoteValues objects representing two common values, for sharing.
+ static mozilla::StaticRefPtr<nsStyleQuoteValues> sInitialQuotes;
+ static mozilla::StaticRefPtr<nsStyleQuoteValues> sNoneQuotes;
+};
+
+struct nsStyleGridLine
+{
+ // http://dev.w3.org/csswg/css-grid/#typedef-grid-line
+ // XXXmats we could optimize memory size here
+ bool mHasSpan;
+ int32_t mInteger; // 0 means not provided
+ nsString mLineName; // Empty string means not provided.
+
+ // These are the limits that we choose to clamp grid line numbers to.
+ // http://dev.w3.org/csswg/css-grid/#overlarge-grids
+ // mInteger is clamped to this range:
+ static const int32_t kMinLine = -10000;
+ static const int32_t kMaxLine = 10000;
+
+ nsStyleGridLine()
+ : mHasSpan(false)
+ , mInteger(0)
+ // mLineName get its default constructor, the empty string
+ {
+ }
+
+ nsStyleGridLine(const nsStyleGridLine& aOther)
+ {
+ (*this) = aOther;
+ }
+
+ void operator=(const nsStyleGridLine& aOther)
+ {
+ mHasSpan = aOther.mHasSpan;
+ mInteger = aOther.mInteger;
+ mLineName = aOther.mLineName;
+ }
+
+ bool operator!=(const nsStyleGridLine& aOther) const
+ {
+ return mHasSpan != aOther.mHasSpan ||
+ mInteger != aOther.mInteger ||
+ mLineName != aOther.mLineName;
+ }
+
+ void SetToInteger(uint32_t value)
+ {
+ mHasSpan = false;
+ mInteger = value;
+ mLineName.Truncate();
+ }
+
+ void SetAuto()
+ {
+ mHasSpan = false;
+ mInteger = 0;
+ mLineName.Truncate();
+ }
+
+ bool IsAuto() const
+ {
+ bool haveInitialValues = mInteger == 0 && mLineName.IsEmpty();
+ MOZ_ASSERT(!(haveInitialValues && mHasSpan),
+ "should not have 'span' when other components are "
+ "at their initial values");
+ return haveInitialValues;
+ }
+};
+
+// Computed value of the grid-template-columns or grid-template-rows property
+// (but *not* grid-template-areas.)
+// http://dev.w3.org/csswg/css-grid/#track-sizing
+//
+// This represents either:
+// * none:
+// mIsSubgrid is false, all three arrays are empty
+// * <track-list>:
+// mIsSubgrid is false,
+// mMinTrackSizingFunctions and mMaxTrackSizingFunctions
+// are of identical non-zero size,
+// and mLineNameLists is one element longer than that.
+// (Delimiting N columns requires N+1 lines:
+// one before each track, plus one at the very end.)
+//
+// An omitted <line-names> is still represented in mLineNameLists,
+// as an empty sub-array.
+//
+// A <track-size> specified as a single <track-breadth> is represented
+// as identical min and max sizing functions.
+// A 'fit-content(size)' <track-size> is represented as eStyleUnit_None
+// in the min sizing function and 'size' in the max sizing function.
+//
+// The units for nsStyleCoord are:
+// * eStyleUnit_Percent represents a <percentage>
+// * eStyleUnit_FlexFraction represents a <flex> flexible fraction
+// * eStyleUnit_Coord represents a <length>
+// * eStyleUnit_Enumerated represents min-content or max-content
+// * subgrid <line-name-list>?:
+// mIsSubgrid is true,
+// mLineNameLists may or may not be empty,
+// mMinTrackSizingFunctions and mMaxTrackSizingFunctions are empty.
+//
+// If mRepeatAutoIndex != -1 then that index is an <auto-repeat> and
+// mIsAutoFill == true means it's an 'auto-fill', otherwise 'auto-fit'.
+// mRepeatAutoLineNameListBefore is the list of line names before the track
+// size, mRepeatAutoLineNameListAfter the names after. (They are empty
+// when there is no <auto-repeat> track, i.e. when mRepeatAutoIndex == -1).
+// When mIsSubgrid is true, mRepeatAutoLineNameListBefore contains the line
+// names and mRepeatAutoLineNameListAfter is empty.
+struct nsStyleGridTemplate
+{
+ nsTArray<nsTArray<nsString>> mLineNameLists;
+ nsTArray<nsStyleCoord> mMinTrackSizingFunctions;
+ nsTArray<nsStyleCoord> mMaxTrackSizingFunctions;
+ nsTArray<nsString> mRepeatAutoLineNameListBefore;
+ nsTArray<nsString> mRepeatAutoLineNameListAfter;
+ int16_t mRepeatAutoIndex; // -1 or the track index for an auto-fill/fit track
+ bool mIsAutoFill : 1;
+ bool mIsSubgrid : 1;
+
+ nsStyleGridTemplate()
+ : mRepeatAutoIndex(-1)
+ , mIsAutoFill(false)
+ , mIsSubgrid(false)
+ {
+ }
+
+ inline bool operator!=(const nsStyleGridTemplate& aOther) const {
+ return
+ mIsSubgrid != aOther.mIsSubgrid ||
+ mLineNameLists != aOther.mLineNameLists ||
+ mMinTrackSizingFunctions != aOther.mMinTrackSizingFunctions ||
+ mMaxTrackSizingFunctions != aOther.mMaxTrackSizingFunctions ||
+ mIsAutoFill != aOther.mIsAutoFill ||
+ mRepeatAutoIndex != aOther.mRepeatAutoIndex ||
+ mRepeatAutoLineNameListBefore != aOther.mRepeatAutoLineNameListBefore ||
+ mRepeatAutoLineNameListAfter != aOther.mRepeatAutoLineNameListAfter;
+ }
+
+ bool HasRepeatAuto() const {
+ return mRepeatAutoIndex != -1;
+ }
+
+ bool IsRepeatAutoIndex(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < uint32_t(2*nsStyleGridLine::kMaxLine));
+ return int32_t(aIndex) == mRepeatAutoIndex;
+ }
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePosition
+{
+ explicit nsStylePosition(StyleStructContext aContext);
+ nsStylePosition(const nsStylePosition& aOther);
+ ~nsStylePosition();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStylePosition* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStylePosition, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStylePosition();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStylePosition, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStylePosition& aNewData,
+ const nsStyleVisibility* aOldStyleVisibility) const;
+ static nsChangeHint MaxDifference() {
+ return NS_STYLE_HINT_REFLOW |
+ nsChangeHint_NeutralChange |
+ nsChangeHint_RecomputePosition |
+ nsChangeHint_UpdateParentOverflow |
+ nsChangeHint_UpdateComputedBSize;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference can return all of the reflow hints that are
+ // sometimes handled for descendants as hints not handled for
+ // descendants.
+ return nsChangeHint(0);
+ }
+
+ /**
+ * Return the used value for 'align-self' given our parent StyleContext
+ * aParent (or null for the root).
+ */
+ uint8_t UsedAlignSelf(nsStyleContext* aParent) const;
+
+ /**
+ * Return the computed value for 'justify-items' given our parent StyleContext
+ * aParent (or null for the root).
+ */
+ uint8_t ComputedJustifyItems(nsStyleContext* aParent) const;
+
+ /**
+ * Return the used value for 'justify-self' given our parent StyleContext
+ * aParent (or null for the root).
+ */
+ uint8_t UsedJustifySelf(nsStyleContext* aParent) const;
+
+ mozilla::Position mObjectPosition; // [reset]
+ nsStyleSides mOffset; // [reset] coord, percent, calc, auto
+ nsStyleCoord mWidth; // [reset] coord, percent, enum, calc, auto
+ nsStyleCoord mMinWidth; // [reset] coord, percent, enum, calc
+ nsStyleCoord mMaxWidth; // [reset] coord, percent, enum, calc, none
+ nsStyleCoord mHeight; // [reset] coord, percent, calc, auto
+ nsStyleCoord mMinHeight; // [reset] coord, percent, calc
+ nsStyleCoord mMaxHeight; // [reset] coord, percent, calc, none
+ nsStyleCoord mFlexBasis; // [reset] coord, percent, enum, calc, auto
+ nsStyleCoord mGridAutoColumnsMin; // [reset] coord, percent, enum, calc, flex
+ nsStyleCoord mGridAutoColumnsMax; // [reset] coord, percent, enum, calc, flex
+ nsStyleCoord mGridAutoRowsMin; // [reset] coord, percent, enum, calc, flex
+ nsStyleCoord mGridAutoRowsMax; // [reset] coord, percent, enum, calc, flex
+ uint8_t mGridAutoFlow; // [reset] enumerated. See nsStyleConsts.h
+ mozilla::StyleBoxSizing mBoxSizing; // [reset] see nsStyleConsts.h
+
+ uint16_t mAlignContent; // [reset] fallback value in the high byte
+ uint8_t mAlignItems; // [reset] see nsStyleConsts.h
+ uint8_t mAlignSelf; // [reset] see nsStyleConsts.h
+ uint16_t mJustifyContent; // [reset] fallback value in the high byte
+private:
+ friend class nsRuleNode;
+
+ // mJustifyItems should only be read via ComputedJustifyItems(), which
+ // lazily resolves its "auto" value. nsRuleNode needs direct access so
+ // it can set mJustifyItems' value when populating this struct.
+ uint8_t mJustifyItems; // [reset] see nsStyleConsts.h
+public:
+ uint8_t mJustifySelf; // [reset] see nsStyleConsts.h
+ uint8_t mFlexDirection; // [reset] see nsStyleConsts.h
+ uint8_t mFlexWrap; // [reset] see nsStyleConsts.h
+ uint8_t mObjectFit; // [reset] see nsStyleConsts.h
+ int32_t mOrder; // [reset] integer
+ float mFlexGrow; // [reset] float
+ float mFlexShrink; // [reset] float
+ nsStyleCoord mZIndex; // [reset] integer, auto
+ nsStyleGridTemplate mGridTemplateColumns;
+ nsStyleGridTemplate mGridTemplateRows;
+
+ // nullptr for 'none'
+ RefPtr<mozilla::css::GridTemplateAreasValue> mGridTemplateAreas;
+
+ nsStyleGridLine mGridColumnStart;
+ nsStyleGridLine mGridColumnEnd;
+ nsStyleGridLine mGridRowStart;
+ nsStyleGridLine mGridRowEnd;
+ nsStyleCoord mGridColumnGap; // [reset] coord, percent, calc
+ nsStyleCoord mGridRowGap; // [reset] coord, percent, calc
+
+ // FIXME: Logical-coordinate equivalents to these WidthDepends... and
+ // HeightDepends... methods have been introduced (see below); we probably
+ // want to work towards removing the physical methods, and using the logical
+ // ones in all cases.
+
+ bool WidthDependsOnContainer() const
+ {
+ return mWidth.GetUnit() == eStyleUnit_Auto ||
+ WidthCoordDependsOnContainer(mWidth);
+ }
+
+ // NOTE: For a flex item, "min-width:auto" is supposed to behave like
+ // "min-content", which does depend on the container, so you might think we'd
+ // need a special case for "flex item && min-width:auto" here. However,
+ // we don't actually need that special-case code, because flex items are
+ // explicitly supposed to *ignore* their min-width (i.e. behave like it's 0)
+ // until the flex container explicitly considers it. So -- since the flex
+ // container doesn't rely on this method, we don't need to worry about
+ // special behavior for flex items' "min-width:auto" values here.
+ bool MinWidthDependsOnContainer() const
+ { return WidthCoordDependsOnContainer(mMinWidth); }
+ bool MaxWidthDependsOnContainer() const
+ { return WidthCoordDependsOnContainer(mMaxWidth); }
+
+ // Note that these functions count 'auto' as depending on the
+ // container since that's the case for absolutely positioned elements.
+ // However, some callers do not care about this case and should check
+ // for it, since it is the most common case.
+ // FIXME: We should probably change the assumption to be the other way
+ // around.
+ // Consider this as part of moving to the logical-coordinate APIs.
+ bool HeightDependsOnContainer() const
+ {
+ return mHeight.GetUnit() == eStyleUnit_Auto || // CSS 2.1, 10.6.4, item (5)
+ HeightCoordDependsOnContainer(mHeight);
+ }
+
+ // NOTE: The comment above MinWidthDependsOnContainer about flex items
+ // applies here, too.
+ bool MinHeightDependsOnContainer() const
+ { return HeightCoordDependsOnContainer(mMinHeight); }
+ bool MaxHeightDependsOnContainer() const
+ { return HeightCoordDependsOnContainer(mMaxHeight); }
+
+ bool OffsetHasPercent(mozilla::css::Side aSide) const
+ {
+ return mOffset.Get(aSide).HasPercent();
+ }
+
+ // 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 nsStyleCoord& ISize(mozilla::WritingMode aWM);
+ inline nsStyleCoord& MinISize(mozilla::WritingMode aWM);
+ inline nsStyleCoord& MaxISize(mozilla::WritingMode aWM);
+ inline nsStyleCoord& BSize(mozilla::WritingMode aWM);
+ inline nsStyleCoord& MinBSize(mozilla::WritingMode aWM);
+ inline nsStyleCoord& MaxBSize(mozilla::WritingMode aWM);
+ inline const nsStyleCoord& ISize(mozilla::WritingMode aWM) const;
+ inline const nsStyleCoord& MinISize(mozilla::WritingMode aWM) const;
+ inline const nsStyleCoord& MaxISize(mozilla::WritingMode aWM) const;
+ inline const nsStyleCoord& BSize(mozilla::WritingMode aWM) const;
+ inline const nsStyleCoord& MinBSize(mozilla::WritingMode aWM) const;
+ inline const nsStyleCoord& MaxBSize(mozilla::WritingMode aWM) const;
+ inline bool ISizeDependsOnContainer(mozilla::WritingMode aWM) const;
+ inline bool MinISizeDependsOnContainer(mozilla::WritingMode aWM) const;
+ inline bool MaxISizeDependsOnContainer(mozilla::WritingMode aWM) const;
+ inline bool BSizeDependsOnContainer(mozilla::WritingMode aWM) const;
+ inline bool MinBSizeDependsOnContainer(mozilla::WritingMode aWM) const;
+ inline bool MaxBSizeDependsOnContainer(mozilla::WritingMode aWM) const;
+
+private:
+ static bool WidthCoordDependsOnContainer(const nsStyleCoord &aCoord);
+ static bool HeightCoordDependsOnContainer(const nsStyleCoord &aCoord)
+ { return aCoord.HasPercent(); }
+};
+
+struct nsStyleTextOverflowSide
+{
+ nsStyleTextOverflowSide() : mType(NS_STYLE_TEXT_OVERFLOW_CLIP) {}
+
+ bool operator==(const nsStyleTextOverflowSide& aOther) const {
+ return mType == aOther.mType &&
+ (mType != NS_STYLE_TEXT_OVERFLOW_STRING ||
+ mString == aOther.mString);
+ }
+ bool operator!=(const nsStyleTextOverflowSide& aOther) const {
+ return !(*this == aOther);
+ }
+
+ nsString mString;
+ uint8_t mType;
+};
+
+struct nsStyleTextOverflow
+{
+ nsStyleTextOverflow() : mLogicalDirections(true) {}
+ bool operator==(const nsStyleTextOverflow& aOther) const {
+ return mLeft == aOther.mLeft && mRight == aOther.mRight;
+ }
+ bool operator!=(const nsStyleTextOverflow& aOther) const {
+ return !(*this == aOther);
+ }
+
+ // Returns the value to apply on the left side.
+ const nsStyleTextOverflowSide& GetLeft(uint8_t aDirection) const {
+ NS_ASSERTION(aDirection == NS_STYLE_DIRECTION_LTR ||
+ aDirection == NS_STYLE_DIRECTION_RTL, "bad direction");
+ return !mLogicalDirections || aDirection == NS_STYLE_DIRECTION_LTR ?
+ mLeft : mRight;
+ }
+
+ // Returns the value to apply on the right side.
+ const nsStyleTextOverflowSide& GetRight(uint8_t aDirection) const {
+ NS_ASSERTION(aDirection == NS_STYLE_DIRECTION_LTR ||
+ aDirection == NS_STYLE_DIRECTION_RTL, "bad direction");
+ return !mLogicalDirections || aDirection == NS_STYLE_DIRECTION_LTR ?
+ mRight : mLeft;
+ }
+
+ // Returns the first value that was specified.
+ const nsStyleTextOverflowSide* GetFirstValue() const {
+ return mLogicalDirections ? &mRight : &mLeft;
+ }
+
+ // Returns the second value, or null if there was only one value specified.
+ const nsStyleTextOverflowSide* GetSecondValue() const {
+ return mLogicalDirections ? nullptr : &mRight;
+ }
+
+ nsStyleTextOverflowSide mLeft; // start side when mLogicalDirections is true
+ nsStyleTextOverflowSide mRight; // end side when mLogicalDirections is true
+ bool mLogicalDirections; // true when only one value was specified
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTextReset
+{
+ explicit nsStyleTextReset(StyleStructContext aContext);
+ nsStyleTextReset(const nsStyleTextReset& aOther);
+ ~nsStyleTextReset();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleTextReset* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleTextReset, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleTextReset();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleTextReset, this);
+ }
+
+ // Note the difference between this and
+ // nsStyleContext::HasTextDecorationLines.
+ bool HasTextDecorationLines() const {
+ return mTextDecorationLine != NS_STYLE_TEXT_DECORATION_LINE_NONE &&
+ mTextDecorationLine != NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL;
+ }
+
+ nsChangeHint CalcDifference(const nsStyleTextReset& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint(
+ NS_STYLE_HINT_REFLOW |
+ nsChangeHint_UpdateSubtreeOverflow);
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ nsStyleTextOverflow mTextOverflow; // [reset] enum, string
+
+ uint8_t mTextDecorationLine; // [reset] see nsStyleConsts.h
+ uint8_t mTextDecorationStyle; // [reset] see nsStyleConsts.h
+ uint8_t mUnicodeBidi; // [reset] see nsStyleConsts.h
+ nscoord mInitialLetterSink; // [reset] 0 means normal
+ float mInitialLetterSize; // [reset] 0.0f means normal
+ mozilla::StyleComplexColor mTextDecorationColor; // [reset]
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleText
+{
+ explicit nsStyleText(StyleStructContext aContext);
+ nsStyleText(const nsStyleText& aOther);
+ ~nsStyleText();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleText* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleText, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleText();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleText, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleText& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame |
+ NS_STYLE_HINT_REFLOW |
+ nsChangeHint_UpdateSubtreeOverflow |
+ nsChangeHint_NeutralChange;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ uint8_t mTextAlign; // [inherited] see nsStyleConsts.h
+ uint8_t mTextAlignLast; // [inherited] see nsStyleConsts.h
+ bool mTextAlignTrue : 1; // [inherited] see nsStyleConsts.h
+ bool mTextAlignLastTrue : 1; // [inherited] see nsStyleConsts.h
+ uint8_t mTextTransform; // [inherited] see nsStyleConsts.h
+ uint8_t mWhiteSpace; // [inherited] see nsStyleConsts.h
+ uint8_t mWordBreak; // [inherited] see nsStyleConsts.h
+ uint8_t mOverflowWrap; // [inherited] see nsStyleConsts.h
+ uint8_t mHyphens; // [inherited] see nsStyleConsts.h
+ uint8_t mRubyAlign; // [inherited] see nsStyleConsts.h
+ uint8_t mRubyPosition; // [inherited] see nsStyleConsts.h
+ uint8_t mTextSizeAdjust; // [inherited] see nsStyleConsts.h
+ uint8_t mTextCombineUpright; // [inherited] see nsStyleConsts.h
+ uint8_t mControlCharacterVisibility; // [inherited] see nsStyleConsts.h
+ uint8_t mTextEmphasisPosition; // [inherited] see nsStyleConsts.h
+ uint8_t mTextEmphasisStyle; // [inherited] see nsStyleConsts.h
+ uint8_t mTextRendering; // [inherited] see nsStyleConsts.h
+ int32_t mTabSize; // [inherited] see nsStyleConsts.h
+ mozilla::StyleComplexColor mTextEmphasisColor; // [inherited]
+ mozilla::StyleComplexColor mWebkitTextFillColor; // [inherited]
+ mozilla::StyleComplexColor mWebkitTextStrokeColor; // [inherited]
+
+ nsStyleCoord mWordSpacing; // [inherited] coord, percent, calc
+ nsStyleCoord mLetterSpacing; // [inherited] coord, normal
+ nsStyleCoord mLineHeight; // [inherited] coord, factor, normal
+ nsStyleCoord mTextIndent; // [inherited] coord, percent, calc
+ nsStyleCoord mWebkitTextStrokeWidth; // [inherited] coord
+
+ RefPtr<nsCSSShadowArray> mTextShadow; // [inherited] nullptr in case of a zero-length
+
+ nsString mTextEmphasisStyleString; // [inherited]
+
+ bool WhiteSpaceIsSignificant() const {
+ return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_SPACE;
+ }
+
+ bool NewlineIsSignificantStyle() const {
+ return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE;
+ }
+
+ bool WhiteSpaceOrNewlineIsSignificant() const {
+ return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_SPACE;
+ }
+
+ bool TabIsSignificant() const {
+ return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP;
+ }
+
+ bool WhiteSpaceCanWrapStyle() const {
+ return mWhiteSpace == NS_STYLE_WHITESPACE_NORMAL ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
+ mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE;
+ }
+
+ bool WordCanWrapStyle() const {
+ return WhiteSpaceCanWrapStyle() &&
+ mOverflowWrap == NS_STYLE_OVERFLOWWRAP_BREAK_WORD;
+ }
+
+ bool HasTextEmphasis() const {
+ return !mTextEmphasisStyleString.IsEmpty();
+ }
+
+ bool HasWebkitTextStroke() const {
+ return mWebkitTextStrokeWidth.GetCoordValue() > 0;
+ }
+
+ // These are defined in nsStyleStructInlines.h.
+ inline bool HasTextShadow() const;
+ inline nsCSSShadowArray* GetTextShadow() const;
+
+ // 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 nsStyleImageOrientation
+{
+ static nsStyleImageOrientation CreateAsAngleAndFlip(double aRadians,
+ bool aFlip) {
+ uint8_t orientation(0);
+
+ // Compute the final angle value, rounding to the closest quarter turn.
+ double roundedAngle = fmod(aRadians, 2 * M_PI);
+ if (roundedAngle < 0.25 * M_PI) { orientation = ANGLE_0; }
+ else if (roundedAngle < 0.75 * M_PI) { orientation = ANGLE_90; }
+ else if (roundedAngle < 1.25 * M_PI) { orientation = ANGLE_180;}
+ else if (roundedAngle < 1.75 * M_PI) { orientation = ANGLE_270;}
+ else { orientation = ANGLE_0; }
+
+ // Add a bit for 'flip' if needed.
+ if (aFlip) {
+ orientation |= FLIP_MASK;
+ }
+
+ return nsStyleImageOrientation(orientation);
+ }
+
+ static nsStyleImageOrientation CreateAsFlip() {
+ return nsStyleImageOrientation(FLIP_MASK);
+ }
+
+ static nsStyleImageOrientation CreateAsFromImage() {
+ return nsStyleImageOrientation(FROM_IMAGE_MASK);
+ }
+
+ // The default constructor yields 0 degrees of rotation and no flip.
+ nsStyleImageOrientation() : mOrientation(0) { }
+
+ bool IsDefault() const { return mOrientation == 0; }
+ bool IsFlipped() const { return mOrientation & FLIP_MASK; }
+ bool IsFromImage() const { return mOrientation & FROM_IMAGE_MASK; }
+ bool SwapsWidthAndHeight() const {
+ uint8_t angle = mOrientation & ORIENTATION_MASK;
+ return (angle == ANGLE_90) || (angle == ANGLE_270);
+ }
+
+ mozilla::image::Angle Angle() const {
+ switch (mOrientation & ORIENTATION_MASK) {
+ case ANGLE_0: return mozilla::image::Angle::D0;
+ case ANGLE_90: return mozilla::image::Angle::D90;
+ case ANGLE_180: return mozilla::image::Angle::D180;
+ case ANGLE_270: return mozilla::image::Angle::D270;
+ default:
+ NS_NOTREACHED("Unexpected angle");
+ return mozilla::image::Angle::D0;
+ }
+ }
+
+ nsStyleCoord AngleAsCoord() const {
+ switch (mOrientation & ORIENTATION_MASK) {
+ case ANGLE_0: return nsStyleCoord(0.0f, eStyleUnit_Degree);
+ case ANGLE_90: return nsStyleCoord(90.0f, eStyleUnit_Degree);
+ case ANGLE_180: return nsStyleCoord(180.0f, eStyleUnit_Degree);
+ case ANGLE_270: return nsStyleCoord(270.0f, eStyleUnit_Degree);
+ default:
+ NS_NOTREACHED("Unexpected angle");
+ return nsStyleCoord();
+ }
+ }
+
+ bool operator==(const nsStyleImageOrientation& aOther) const {
+ return aOther.mOrientation == mOrientation;
+ }
+
+ bool operator!=(const nsStyleImageOrientation& aOther) const {
+ return !(*this == aOther);
+ }
+
+protected:
+ enum Bits {
+ ORIENTATION_MASK = 0x1 | 0x2, // The bottom two bits are the angle.
+ FLIP_MASK = 0x4, // Whether the image should be flipped.
+ FROM_IMAGE_MASK = 0x8, // Whether the image's inherent orientation
+ }; // should be used.
+
+ enum Angles {
+ ANGLE_0 = 0,
+ ANGLE_90 = 1,
+ ANGLE_180 = 2,
+ ANGLE_270 = 3,
+ };
+
+ explicit nsStyleImageOrientation(uint8_t aOrientation)
+ : mOrientation(aOrientation)
+ { }
+
+ uint8_t mOrientation;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleVisibility
+{
+ explicit nsStyleVisibility(StyleStructContext aContext);
+ nsStyleVisibility(const nsStyleVisibility& aVisibility);
+ ~nsStyleVisibility() {
+ MOZ_COUNT_DTOR(nsStyleVisibility);
+ }
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleVisibility* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleVisibility, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleVisibility();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleVisibility, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleVisibility& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame |
+ NS_STYLE_HINT_REFLOW |
+ nsChangeHint_NeutralChange;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ nsStyleImageOrientation mImageOrientation; // [inherited]
+ uint8_t mDirection; // [inherited] see nsStyleConsts.h NS_STYLE_DIRECTION_*
+ uint8_t mVisible; // [inherited]
+ uint8_t mImageRendering; // [inherited] see nsStyleConsts.h
+ uint8_t mWritingMode; // [inherited] see nsStyleConsts.h
+ uint8_t mTextOrientation; // [inherited] see nsStyleConsts.h
+ uint8_t mColorAdjust; // [inherited] see nsStyleConsts.h
+
+ bool IsVisible() const {
+ return (mVisible == NS_STYLE_VISIBILITY_VISIBLE);
+ }
+
+ bool IsVisibleOrCollapsed() const {
+ return ((mVisible == NS_STYLE_VISIBILITY_VISIBLE) ||
+ (mVisible == NS_STYLE_VISIBILITY_COLLAPSE));
+ }
+};
+
+struct nsTimingFunction
+{
+
+ enum class Type {
+ Ease, // ease
+ Linear, // linear
+ EaseIn, // ease-in
+ EaseOut, // ease-out
+ EaseInOut, // ease-in-out
+ StepStart, // step-start and steps(..., start)
+ StepEnd, // step-end, steps(..., end) and steps(...)
+ CubicBezier, // cubic-bezier()
+ };
+
+ // Whether the timing function type is represented by a spline,
+ // and thus will have mFunc filled in.
+ static bool IsSplineType(Type aType)
+ {
+ return aType != Type::StepStart && aType != Type::StepEnd;
+ }
+
+ explicit nsTimingFunction(int32_t aTimingFunctionType
+ = NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE)
+ {
+ AssignFromKeyword(aTimingFunctionType);
+ }
+
+ nsTimingFunction(float x1, float y1, float x2, float y2)
+ : mType(Type::CubicBezier)
+ {
+ mFunc.mX1 = x1;
+ mFunc.mY1 = y1;
+ mFunc.mX2 = x2;
+ mFunc.mY2 = y2;
+ }
+
+ enum class Keyword { Implicit, Explicit };
+
+ nsTimingFunction(Type aType, uint32_t aSteps)
+ : mType(aType)
+ {
+ MOZ_ASSERT(mType == Type::StepStart || mType == Type::StepEnd,
+ "wrong type");
+ mSteps = aSteps;
+ }
+
+ nsTimingFunction(const nsTimingFunction& aOther)
+ {
+ *this = aOther;
+ }
+
+ Type mType;
+ union {
+ struct {
+ float mX1;
+ float mY1;
+ float mX2;
+ float mY2;
+ } mFunc;
+ struct {
+ uint32_t mSteps;
+ };
+ };
+
+ nsTimingFunction&
+ operator=(const nsTimingFunction& aOther)
+ {
+ if (&aOther == this) {
+ return *this;
+ }
+
+ mType = aOther.mType;
+
+ if (HasSpline()) {
+ mFunc.mX1 = aOther.mFunc.mX1;
+ mFunc.mY1 = aOther.mFunc.mY1;
+ mFunc.mX2 = aOther.mFunc.mX2;
+ mFunc.mY2 = aOther.mFunc.mY2;
+ } else {
+ mSteps = aOther.mSteps;
+ }
+
+ return *this;
+ }
+
+ bool operator==(const nsTimingFunction& aOther) const
+ {
+ if (mType != aOther.mType) {
+ return false;
+ }
+ if (HasSpline()) {
+ return mFunc.mX1 == aOther.mFunc.mX1 && mFunc.mY1 == aOther.mFunc.mY1 &&
+ mFunc.mX2 == aOther.mFunc.mX2 && mFunc.mY2 == aOther.mFunc.mY2;
+ }
+ return mSteps == aOther.mSteps;
+ }
+
+ bool operator!=(const nsTimingFunction& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ bool HasSpline() const { return IsSplineType(mType); }
+
+private:
+ void AssignFromKeyword(int32_t aTimingFunctionType);
+};
+
+namespace mozilla {
+
+struct StyleTransition
+{
+ StyleTransition() { /* leaves uninitialized; see also SetInitialValues */ }
+ explicit StyleTransition(const StyleTransition& aCopy);
+
+ void SetInitialValues();
+
+ // Delay and Duration are in milliseconds
+
+ const nsTimingFunction& GetTimingFunction() const { return mTimingFunction; }
+ float GetDelay() const { return mDelay; }
+ float GetDuration() const { return mDuration; }
+ nsCSSPropertyID GetProperty() const { return mProperty; }
+ nsIAtom* GetUnknownProperty() const { return mUnknownProperty; }
+
+ float GetCombinedDuration() const {
+ // http://dev.w3.org/csswg/css-transitions/#combined-duration
+ return std::max(mDuration, 0.0f) + mDelay;
+ }
+
+ void SetTimingFunction(const nsTimingFunction& aTimingFunction)
+ { mTimingFunction = aTimingFunction; }
+ void SetDelay(float aDelay) { mDelay = aDelay; }
+ void SetDuration(float aDuration) { mDuration = aDuration; }
+ void SetProperty(nsCSSPropertyID aProperty)
+ {
+ NS_ASSERTION(aProperty != eCSSProperty_UNKNOWN &&
+ aProperty != eCSSPropertyExtra_variable,
+ "invalid property");
+ mProperty = aProperty;
+ }
+ void SetUnknownProperty(nsCSSPropertyID aProperty,
+ const nsAString& aPropertyString);
+ void CopyPropertyFrom(const StyleTransition& aOther)
+ {
+ mProperty = aOther.mProperty;
+ mUnknownProperty = aOther.mUnknownProperty;
+ }
+
+ nsTimingFunction& TimingFunctionSlot() { return mTimingFunction; }
+
+ bool operator==(const StyleTransition& aOther) const;
+ bool operator!=(const StyleTransition& aOther) const
+ { return !(*this == aOther); }
+
+private:
+ nsTimingFunction mTimingFunction;
+ float mDuration;
+ float mDelay;
+ nsCSSPropertyID mProperty;
+ nsCOMPtr<nsIAtom> mUnknownProperty; // used when mProperty is
+ // eCSSProperty_UNKNOWN or
+ // eCSSPropertyExtra_variable
+};
+
+struct StyleAnimation
+{
+ StyleAnimation() { /* leaves uninitialized; see also SetInitialValues */ }
+ explicit StyleAnimation(const StyleAnimation& aCopy);
+
+ void SetInitialValues();
+
+ // Delay and Duration are in milliseconds
+
+ const nsTimingFunction& GetTimingFunction() const { return mTimingFunction; }
+ float GetDelay() const { return mDelay; }
+ float GetDuration() const { return mDuration; }
+ const nsString& GetName() const { return mName; }
+ dom::PlaybackDirection GetDirection() const { return mDirection; }
+ dom::FillMode GetFillMode() const { return mFillMode; }
+ uint8_t GetPlayState() const { return mPlayState; }
+ float GetIterationCount() const { return mIterationCount; }
+
+ void SetTimingFunction(const nsTimingFunction& aTimingFunction)
+ { mTimingFunction = aTimingFunction; }
+ void SetDelay(float aDelay) { mDelay = aDelay; }
+ void SetDuration(float aDuration) { mDuration = aDuration; }
+ void SetName(const nsSubstring& aName) { mName = aName; }
+ void SetDirection(dom::PlaybackDirection aDirection) { mDirection = aDirection; }
+ void SetFillMode(dom::FillMode aFillMode) { mFillMode = aFillMode; }
+ void SetPlayState(uint8_t aPlayState) { mPlayState = aPlayState; }
+ void SetIterationCount(float aIterationCount)
+ { mIterationCount = aIterationCount; }
+
+ nsTimingFunction& TimingFunctionSlot() { return mTimingFunction; }
+
+ bool operator==(const StyleAnimation& aOther) const;
+ bool operator!=(const StyleAnimation& aOther) const
+ { return !(*this == aOther); }
+
+private:
+ nsTimingFunction mTimingFunction;
+ float mDuration;
+ float mDelay;
+ nsString mName; // empty string for 'none'
+ dom::PlaybackDirection mDirection;
+ dom::FillMode mFillMode;
+ uint8_t mPlayState;
+ float mIterationCount; // mozilla::PositiveInfinity<float>() means infinite
+};
+
+class StyleBasicShape final
+{
+public:
+ explicit StyleBasicShape(StyleBasicShapeType type)
+ : mType(type),
+ mFillRule(StyleFillRule::Nonzero)
+ {
+ mPosition.SetInitialPercentValues(0.5f);
+ }
+
+ StyleBasicShapeType GetShapeType() const { return mType; }
+ nsCSSKeyword GetShapeTypeName() const;
+
+ StyleFillRule GetFillRule() const { return mFillRule; }
+ void SetFillRule(StyleFillRule aFillRule)
+ {
+ MOZ_ASSERT(mType == StyleBasicShapeType::Polygon, "expected polygon");
+ mFillRule = aFillRule;
+ }
+
+ Position& GetPosition() {
+ MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
+ mType == StyleBasicShapeType::Ellipse,
+ "expected circle or ellipse");
+ return mPosition;
+ }
+ const Position& GetPosition() const {
+ MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
+ mType == StyleBasicShapeType::Ellipse,
+ "expected circle or ellipse");
+ return mPosition;
+ }
+
+ bool HasRadius() const {
+ MOZ_ASSERT(mType == StyleBasicShapeType::Inset, "expected inset");
+ nsStyleCoord zero;
+ zero.SetCoordValue(0);
+ NS_FOR_CSS_HALF_CORNERS(corner) {
+ if (mRadius.Get(corner) != zero) {
+ return true;
+ }
+ }
+ return false;
+ }
+ nsStyleCorners& GetRadius() {
+ MOZ_ASSERT(mType == StyleBasicShapeType::Inset, "expected inset");
+ return mRadius;
+ }
+ const nsStyleCorners& GetRadius() const {
+ MOZ_ASSERT(mType == StyleBasicShapeType::Inset, "expected inset");
+ return mRadius;
+ }
+
+ // mCoordinates has coordinates for polygon or radii for
+ // ellipse and circle.
+ nsTArray<nsStyleCoord>& Coordinates()
+ {
+ return mCoordinates;
+ }
+
+ const nsTArray<nsStyleCoord>& Coordinates() const
+ {
+ return mCoordinates;
+ }
+
+ bool operator==(const StyleBasicShape& aOther) const
+ {
+ return mType == aOther.mType &&
+ mFillRule == aOther.mFillRule &&
+ mCoordinates == aOther.mCoordinates &&
+ mPosition == aOther.mPosition &&
+ mRadius == aOther.mRadius;
+ }
+ bool operator!=(const StyleBasicShape& aOther) const {
+ return !(*this == aOther);
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StyleBasicShape);
+
+private:
+ ~StyleBasicShape() {}
+
+ StyleBasicShapeType mType;
+ StyleFillRule mFillRule;
+
+ // mCoordinates has coordinates for polygon or radii for
+ // ellipse and circle.
+ // (top, right, bottom, left) for inset
+ nsTArray<nsStyleCoord> mCoordinates;
+ // position of center for ellipse or circle
+ Position mPosition;
+ // corner radii for inset (0 if not set)
+ nsStyleCorners mRadius;
+};
+
+template<typename ReferenceBox>
+struct StyleShapeSource
+{
+ StyleShapeSource()
+ : mURL(nullptr)
+ {}
+
+ StyleShapeSource(const StyleShapeSource& aSource)
+ : StyleShapeSource()
+ {
+ if (aSource.mType == StyleShapeSourceType::URL) {
+ SetURL(aSource.mURL);
+ } else if (aSource.mType == StyleShapeSourceType::Shape) {
+ SetBasicShape(aSource.mBasicShape, aSource.mReferenceBox);
+ } else if (aSource.mType == StyleShapeSourceType::Box) {
+ SetReferenceBox(aSource.mReferenceBox);
+ }
+ }
+
+ ~StyleShapeSource()
+ {
+ ReleaseRef();
+ }
+
+ StyleShapeSource& operator=(const StyleShapeSource& aOther)
+ {
+ if (this == &aOther) {
+ return *this;
+ }
+
+ if (aOther.mType == StyleShapeSourceType::URL) {
+ SetURL(aOther.mURL);
+ } else if (aOther.mType == StyleShapeSourceType::Shape) {
+ SetBasicShape(aOther.mBasicShape, aOther.mReferenceBox);
+ } else if (aOther.mType == StyleShapeSourceType::Box) {
+ SetReferenceBox(aOther.mReferenceBox);
+ } else {
+ ReleaseRef();
+ mReferenceBox = ReferenceBox::NoBox;
+ mType = StyleShapeSourceType::None;
+ }
+ return *this;
+ }
+
+ bool operator==(const StyleShapeSource& aOther) const
+ {
+ if (mType != aOther.mType) {
+ return false;
+ }
+
+ if (mType == StyleShapeSourceType::URL) {
+ return mURL->Equals(*aOther.mURL);
+ } else if (mType == StyleShapeSourceType::Shape) {
+ return *mBasicShape == *aOther.mBasicShape &&
+ mReferenceBox == aOther.mReferenceBox;
+ } else if (mType == StyleShapeSourceType::Box) {
+ return mReferenceBox == aOther.mReferenceBox;
+ }
+
+ return true;
+ }
+
+ bool operator!=(const StyleShapeSource& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ StyleShapeSourceType GetType() const
+ {
+ return mType;
+ }
+
+ css::URLValue* GetURL() const
+ {
+ MOZ_ASSERT(mType == StyleShapeSourceType::URL, "Wrong shape source type!");
+ return mURL;
+ }
+
+ bool SetURL(css::URLValue* aValue)
+ {
+ MOZ_ASSERT(aValue);
+ ReleaseRef();
+ mURL = aValue;
+ mURL->AddRef();
+ mType = StyleShapeSourceType::URL;
+ return true;
+ }
+
+ StyleBasicShape* GetBasicShape() const
+ {
+ MOZ_ASSERT(mType == StyleShapeSourceType::Shape, "Wrong shape source type!");
+ return mBasicShape;
+ }
+
+ void SetBasicShape(StyleBasicShape* aBasicShape,
+ ReferenceBox aReferenceBox)
+ {
+ NS_ASSERTION(aBasicShape, "expected pointer");
+ ReleaseRef();
+ mBasicShape = aBasicShape;
+ mBasicShape->AddRef();
+ mReferenceBox = aReferenceBox;
+ mType = StyleShapeSourceType::Shape;
+ }
+
+ ReferenceBox GetReferenceBox() const
+ {
+ MOZ_ASSERT(mType == StyleShapeSourceType::Box ||
+ mType == StyleShapeSourceType::Shape,
+ "Wrong shape source type!");
+ return mReferenceBox;
+ }
+
+ void SetReferenceBox(ReferenceBox aReferenceBox)
+ {
+ ReleaseRef();
+ mReferenceBox = aReferenceBox;
+ mType = StyleShapeSourceType::Box;
+ }
+
+private:
+ void ReleaseRef()
+ {
+ if (mType == StyleShapeSourceType::Shape) {
+ NS_ASSERTION(mBasicShape, "expected pointer");
+ mBasicShape->Release();
+ } else if (mType == StyleShapeSourceType::URL) {
+ NS_ASSERTION(mURL, "expected pointer");
+ mURL->Release();
+ }
+ // Both mBasicShape and mURL are pointers in a union. Nulling one of them
+ // nulls both of them.
+ mURL = nullptr;
+ }
+
+ void* operator new(size_t) = delete;
+
+ union {
+ StyleBasicShape* mBasicShape;
+ css::URLValue* mURL;
+ };
+ StyleShapeSourceType mType = StyleShapeSourceType::None;
+ ReferenceBox mReferenceBox = ReferenceBox::NoBox;
+};
+
+using StyleClipPath = StyleShapeSource<StyleClipPathGeometryBox>;
+using StyleShapeOutside = StyleShapeSource<StyleShapeOutsideShapeBox>;
+
+} // namespace mozilla
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
+{
+ explicit nsStyleDisplay(StyleStructContext aContext);
+ nsStyleDisplay(const nsStyleDisplay& aOther);
+ ~nsStyleDisplay() {
+ MOZ_COUNT_DTOR(nsStyleDisplay);
+ }
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleDisplay* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleDisplay, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleDisplay();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleDisplay, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleDisplay& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ // All the parts of FRAMECHANGE are present in CalcDifference.
+ return nsChangeHint(nsChangeHint_ReconstructFrame |
+ NS_STYLE_HINT_REFLOW |
+ nsChangeHint_UpdateTransformLayer |
+ nsChangeHint_UpdateOverflow |
+ nsChangeHint_UpdatePostTransformOverflow |
+ nsChangeHint_UpdateContainingBlock |
+ nsChangeHint_AddOrRemoveTransform |
+ nsChangeHint_NeutralChange);
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference can return all of the reflow hints that are
+ // sometimes handled for descendants as hints not handled for
+ // descendants.
+ return nsChangeHint(0);
+ }
+
+ // We guarantee that if mBinding is non-null, so are mBinding->GetURI() and
+ // mBinding->mOriginPrincipal.
+ RefPtr<mozilla::css::URLValue> mBinding; // [reset]
+ mozilla::StyleDisplay mDisplay; // [reset] see nsStyleConsts.h SyleDisplay
+ mozilla::StyleDisplay mOriginalDisplay; // [reset] saved mDisplay for
+ // position:absolute/fixed
+ // and float:left/right;
+ // otherwise equal to
+ // mDisplay
+ uint8_t mContain; // [reset] see nsStyleConsts.h NS_STYLE_CONTAIN_*
+ uint8_t mAppearance; // [reset]
+ uint8_t mPosition; // [reset] see nsStyleConsts.h
+
+ // [reset] See StyleFloat in nsStyleConsts.h.
+ mozilla::StyleFloat mFloat;
+ // [reset] Save mFloat for position:absolute/fixed; otherwise equal to mFloat.
+ mozilla::StyleFloat mOriginalFloat;
+
+ mozilla::StyleClear mBreakType; // [reset]
+ uint8_t mBreakInside; // [reset] NS_STYLE_PAGE_BREAK_AUTO/AVOID
+ bool mBreakBefore; // [reset]
+ bool mBreakAfter; // [reset]
+ uint8_t mOverflowX; // [reset] see nsStyleConsts.h
+ uint8_t mOverflowY; // [reset] see nsStyleConsts.h
+ uint8_t mOverflowClipBox; // [reset] see nsStyleConsts.h
+ uint8_t mResize; // [reset] see nsStyleConsts.h
+ mozilla::StyleOrient mOrient; // [reset] see nsStyleConsts.h
+ uint8_t mIsolation; // [reset] see nsStyleConsts.h
+ uint8_t mTopLayer; // [reset] see nsStyleConsts.h
+ uint8_t mWillChangeBitField; // [reset] see nsStyleConsts.h. Stores a
+ // bitfield representation of the properties
+ // that are frequently queried. This should
+ // match mWillChange. Also tracks if any of the
+ // properties in the will-change list require
+ // a stacking context.
+ nsTArray<nsString> mWillChange;
+
+ uint8_t mTouchAction; // [reset] see nsStyleConsts.h
+ uint8_t mScrollBehavior; // [reset] see nsStyleConsts.h NS_STYLE_SCROLL_BEHAVIOR_*
+ uint8_t mScrollSnapTypeX; // [reset] see nsStyleConsts.h NS_STYLE_SCROLL_SNAP_TYPE_*
+ uint8_t mScrollSnapTypeY; // [reset] see nsStyleConsts.h NS_STYLE_SCROLL_SNAP_TYPE_*
+ nsStyleCoord mScrollSnapPointsX; // [reset]
+ nsStyleCoord mScrollSnapPointsY; // [reset]
+ mozilla::Position mScrollSnapDestination; // [reset]
+ nsTArray<mozilla::Position> mScrollSnapCoordinate; // [reset]
+
+ // mSpecifiedTransform is the list of transform functions as
+ // specified, or null to indicate there is no transform. (inherit or
+ // initial are replaced by an actual list of transform functions, or
+ // null, as appropriate.)
+ uint8_t mBackfaceVisibility;
+ uint8_t mTransformStyle;
+ uint8_t mTransformBox; // [reset] see nsStyleConsts.h
+ RefPtr<nsCSSValueSharedList> mSpecifiedTransform; // [reset]
+ nsStyleCoord mTransformOrigin[3]; // [reset] percent, coord, calc, 3rd param is coord, calc only
+ nsStyleCoord mChildPerspective; // [reset] none, coord
+ nsStyleCoord mPerspectiveOrigin[2]; // [reset] percent, coord, calc
+
+ nsStyleCoord mVerticalAlign; // [reset] coord, percent, calc, enum (see nsStyleConsts.h)
+
+ nsStyleAutoArray<mozilla::StyleTransition> mTransitions; // [reset]
+
+ // The number of elements in mTransitions that are not from repeating
+ // a list due to another property being longer.
+ uint32_t mTransitionTimingFunctionCount,
+ mTransitionDurationCount,
+ mTransitionDelayCount,
+ mTransitionPropertyCount;
+
+ nsStyleAutoArray<mozilla::StyleAnimation> mAnimations; // [reset]
+
+ // The number of elements in mAnimations that are not from repeating
+ // a list due to another property being longer.
+ uint32_t mAnimationTimingFunctionCount,
+ mAnimationDurationCount,
+ mAnimationDelayCount,
+ mAnimationNameCount,
+ mAnimationDirectionCount,
+ mAnimationFillModeCount,
+ mAnimationPlayStateCount,
+ mAnimationIterationCountCount;
+
+ mozilla::StyleShapeOutside mShapeOutside; // [reset]
+
+ bool IsBlockInsideStyle() const {
+ return mozilla::StyleDisplay::Block == mDisplay ||
+ mozilla::StyleDisplay::ListItem == mDisplay ||
+ mozilla::StyleDisplay::InlineBlock == mDisplay ||
+ mozilla::StyleDisplay::TableCaption == mDisplay;
+ // Should TABLE_CELL be included here? They have
+ // block frames nested inside of them.
+ // (But please audit all callers before changing.)
+ }
+
+ bool IsBlockOutsideStyle() const {
+ return mozilla::StyleDisplay::Block == mDisplay ||
+ mozilla::StyleDisplay::Flex == mDisplay ||
+ mozilla::StyleDisplay::WebkitBox == mDisplay ||
+ mozilla::StyleDisplay::Grid == mDisplay ||
+ mozilla::StyleDisplay::ListItem == mDisplay ||
+ mozilla::StyleDisplay::Table == mDisplay;
+ }
+
+ static bool IsDisplayTypeInlineOutside(mozilla::StyleDisplay aDisplay) {
+ return mozilla::StyleDisplay::Inline == aDisplay ||
+ mozilla::StyleDisplay::InlineBlock == aDisplay ||
+ mozilla::StyleDisplay::InlineTable == aDisplay ||
+ mozilla::StyleDisplay::InlineBox == aDisplay ||
+ mozilla::StyleDisplay::InlineFlex == aDisplay ||
+ mozilla::StyleDisplay::WebkitInlineBox == aDisplay ||
+ mozilla::StyleDisplay::InlineGrid == aDisplay ||
+ mozilla::StyleDisplay::InlineXulGrid == aDisplay ||
+ mozilla::StyleDisplay::InlineStack == aDisplay ||
+ mozilla::StyleDisplay::Ruby == aDisplay ||
+ mozilla::StyleDisplay::RubyBase == aDisplay ||
+ mozilla::StyleDisplay::RubyBaseContainer == aDisplay ||
+ mozilla::StyleDisplay::RubyText == aDisplay ||
+ mozilla::StyleDisplay::RubyTextContainer == aDisplay ||
+ mozilla::StyleDisplay::Contents == aDisplay;
+ }
+
+ bool IsInlineOutsideStyle() const {
+ return IsDisplayTypeInlineOutside(mDisplay);
+ }
+
+ bool IsOriginalDisplayInlineOutsideStyle() const {
+ return IsDisplayTypeInlineOutside(mOriginalDisplay);
+ }
+
+ bool IsInnerTableStyle() const {
+ return mozilla::StyleDisplay::TableCaption == mDisplay ||
+ mozilla::StyleDisplay::TableCell == mDisplay ||
+ mozilla::StyleDisplay::TableRow == mDisplay ||
+ mozilla::StyleDisplay::TableRowGroup == mDisplay ||
+ mozilla::StyleDisplay::TableHeaderGroup == mDisplay ||
+ mozilla::StyleDisplay::TableFooterGroup == mDisplay ||
+ mozilla::StyleDisplay::TableColumn == mDisplay ||
+ mozilla::StyleDisplay::TableColumnGroup == mDisplay;
+ }
+
+ bool IsFloatingStyle() const {
+ return mozilla::StyleFloat::None != mFloat;
+ }
+
+ bool IsAbsolutelyPositionedStyle() const {
+ return NS_STYLE_POSITION_ABSOLUTE == mPosition ||
+ NS_STYLE_POSITION_FIXED == mPosition;
+ }
+
+ bool IsRelativelyPositionedStyle() const {
+ return NS_STYLE_POSITION_RELATIVE == mPosition ||
+ NS_STYLE_POSITION_STICKY == mPosition;
+ }
+ bool IsPositionForcingStackingContext() const {
+ return NS_STYLE_POSITION_STICKY == mPosition ||
+ NS_STYLE_POSITION_FIXED == mPosition;
+ }
+
+ static bool IsRubyDisplayType(mozilla::StyleDisplay aDisplay) {
+ return mozilla::StyleDisplay::Ruby == aDisplay ||
+ mozilla::StyleDisplay::RubyBase == aDisplay ||
+ mozilla::StyleDisplay::RubyBaseContainer == aDisplay ||
+ mozilla::StyleDisplay::RubyText == aDisplay ||
+ mozilla::StyleDisplay::RubyTextContainer == aDisplay;
+ }
+
+ bool IsRubyDisplayType() const {
+ return IsRubyDisplayType(mDisplay);
+ }
+
+ bool IsOutOfFlowStyle() const {
+ return (IsAbsolutelyPositionedStyle() || IsFloatingStyle());
+ }
+
+ bool IsScrollableOverflow() const {
+ // mOverflowX and mOverflowY always match when one of them is
+ // NS_STYLE_OVERFLOW_VISIBLE or NS_STYLE_OVERFLOW_CLIP.
+ return mOverflowX != NS_STYLE_OVERFLOW_VISIBLE &&
+ mOverflowX != NS_STYLE_OVERFLOW_CLIP;
+ }
+
+ bool IsContainPaint() const {
+ return NS_STYLE_CONTAIN_PAINT & mContain;
+ }
+
+ /* Returns whether the element has the -moz-transform property
+ * or a related property. */
+ bool HasTransformStyle() const {
+ return mSpecifiedTransform != nullptr ||
+ mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D ||
+ (mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM);
+ }
+
+ bool HasPerspectiveStyle() const {
+ return mChildPerspective.GetUnit() == eStyleUnit_Coord;
+ }
+
+ bool BackfaceIsHidden() const {
+ return mBackfaceVisibility == NS_STYLE_BACKFACE_VISIBILITY_HIDDEN;
+ }
+
+ // 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 IsBlockInside(const nsIFrame* aContextFrame) const;
+ inline bool IsBlockOutside(const nsIFrame* aContextFrame) const;
+ inline bool IsInlineOutside(const nsIFrame* aContextFrame) const;
+ inline bool IsOriginalDisplayInlineOutside(const nsIFrame* aContextFrame) const;
+ inline mozilla::StyleDisplay GetDisplay(const nsIFrame* aContextFrame) const;
+ inline bool IsFloating(const nsIFrame* aContextFrame) const;
+ inline bool IsRelativelyPositioned(const nsIFrame* aContextFrame) const;
+ inline bool IsAbsolutelyPositioned(const nsIFrame* aContextFrame) const;
+
+ // These methods are defined in nsStyleStructInlines.h.
+
+ /**
+ * Returns whether the element is a containing block for its
+ * absolutely positioned descendants.
+ * aContextFrame is the frame for which this is the nsStyleDisplay.
+ */
+ inline bool IsAbsPosContainingBlock(const nsIFrame* aContextFrame) const;
+
+ /**
+ * The same as IsAbsPosContainingBlock, except skipping the tests that
+ * are based on the frame rather than the style context (thus
+ * potentially returning a false positive).
+ */
+ template<class StyleContextLike>
+ inline bool IsAbsPosContainingBlockForAppropriateFrame(
+ StyleContextLike* aStyleContext) const;
+
+ /**
+ * 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 is a containing block for its fixed-pos
+ * descendants.
+ * aContextFrame is the frame for which this is the nsStyleDisplay.
+ */
+ inline bool IsFixedPosContainingBlock(const nsIFrame* aContextFrame) const;
+
+ /**
+ * The same as IsFixedPosContainingBlock, except skipping the tests that
+ * are based on the frame rather than the style context (thus
+ * potentially returning a false positive).
+ */
+ template<class StyleContextLike>
+ inline bool IsFixedPosContainingBlockForAppropriateFrame(
+ StyleContextLike* aStyleContext) const;
+
+private:
+ // Helpers for above functions, which do some but not all of the tests
+ // for them (since transform must be tested separately for each).
+ template<class StyleContextLike>
+ inline bool HasAbsPosContainingBlockStyleInternal(
+ StyleContextLike* aStyleContext) const;
+ template<class StyleContextLike>
+ inline bool HasFixedPosContainingBlockStyleInternal(
+ StyleContextLike* aStyleContext) const;
+
+public:
+ // Return the 'float' and 'clear' properties, with inline-{start,end} values
+ // resolved to {left,right} according to the given writing mode. These are
+ // defined in WritingModes.h.
+ inline mozilla::StyleFloat PhysicalFloats(mozilla::WritingMode aWM) const;
+ inline mozilla::StyleClear PhysicalBreakType(mozilla::WritingMode aWM) const;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTable
+{
+ explicit nsStyleTable(StyleStructContext aContext);
+ nsStyleTable(const nsStyleTable& aOther);
+ ~nsStyleTable();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleTable* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleTable, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleTable();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleTable, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleTable& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint(0);
+ }
+
+ uint8_t mLayoutStrategy;// [reset] see nsStyleConsts.h NS_STYLE_TABLE_LAYOUT_*
+ int32_t mSpan; // [reset] the number of columns spanned by a colgroup or col
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTableBorder
+{
+ explicit nsStyleTableBorder(StyleStructContext aContext);
+ nsStyleTableBorder(const nsStyleTableBorder& aOther);
+ ~nsStyleTableBorder();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleTableBorder* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleTableBorder, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleTableBorder();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleTableBorder, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleTableBorder& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame |
+ NS_STYLE_HINT_REFLOW;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ nscoord mBorderSpacingCol;// [inherited]
+ nscoord mBorderSpacingRow;// [inherited]
+ uint8_t mBorderCollapse;// [inherited]
+ uint8_t mCaptionSide; // [inherited]
+ uint8_t mEmptyCells; // [inherited]
+};
+
+enum nsStyleContentType {
+ eStyleContentType_String = 1,
+ eStyleContentType_Image = 10,
+ eStyleContentType_Attr = 20,
+ eStyleContentType_Counter = 30,
+ eStyleContentType_Counters = 31,
+ eStyleContentType_OpenQuote = 40,
+ eStyleContentType_CloseQuote = 41,
+ eStyleContentType_NoOpenQuote = 42,
+ eStyleContentType_NoCloseQuote = 43,
+ eStyleContentType_AltContent = 50,
+ eStyleContentType_Uninitialized
+};
+
+struct nsStyleContentData
+{
+ nsStyleContentType mType;
+ union {
+ char16_t *mString;
+ imgRequestProxy *mImage;
+ nsCSSValue::Array* mCounters;
+ } mContent;
+#ifdef DEBUG
+ bool mImageTracked;
+#endif
+
+ nsStyleContentData()
+ : mType(eStyleContentType_Uninitialized)
+#ifdef DEBUG
+ , mImageTracked(false)
+#endif
+ {
+ MOZ_COUNT_CTOR(nsStyleContentData);
+ mContent.mString = nullptr;
+ }
+ nsStyleContentData(const nsStyleContentData&);
+
+ ~nsStyleContentData();
+ nsStyleContentData& operator=(const nsStyleContentData& aOther);
+ bool operator==(const nsStyleContentData& aOther) const;
+
+ bool operator!=(const nsStyleContentData& aOther) const {
+ return !(*this == aOther);
+ }
+
+ void TrackImage(mozilla::dom::ImageTracker* aImageTracker);
+ void UntrackImage(mozilla::dom::ImageTracker* aImageTracker);
+
+ void SetImage(imgRequestProxy* aRequest)
+ {
+ MOZ_ASSERT(!mImageTracked,
+ "Setting a new image without untracking the old one!");
+ MOZ_ASSERT(mType == eStyleContentType_Image, "Wrong type!");
+ NS_IF_ADDREF(mContent.mImage = aRequest);
+ }
+};
+
+struct nsStyleCounterData
+{
+ nsString mCounter;
+ int32_t mValue;
+
+ bool operator==(const nsStyleCounterData& aOther) const {
+ return mValue == aOther.mValue && mCounter == aOther.mCounter;
+ }
+
+ bool operator!=(const nsStyleCounterData& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleContent
+{
+ explicit nsStyleContent(StyleStructContext aContext);
+ nsStyleContent(const nsStyleContent& aContent);
+ ~nsStyleContent();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleContent* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleContent, sz);
+ }
+ void Destroy(nsPresContext* aContext);
+
+ nsChangeHint CalcDifference(const nsStyleContent& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame |
+ NS_STYLE_HINT_REFLOW;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ uint32_t ContentCount() const { return mContents.Length(); } // [reset]
+
+ const nsStyleContentData& ContentAt(uint32_t aIndex) const {
+ return mContents[aIndex];
+ }
+
+ nsStyleContentData& ContentAt(uint32_t aIndex) { return mContents[aIndex]; }
+
+ void AllocateContents(uint32_t aCount) {
+ // We need to run the destructors of the elements of mContents, so we
+ // delete and reallocate even if aCount == mContentCount. (If
+ // nsStyleContentData had its members private and managed their
+ // ownership on setting, we wouldn't need this, but that seems
+ // unnecessary at this point.)
+ mContents.Clear();
+ mContents.SetLength(aCount);
+ }
+
+ uint32_t CounterIncrementCount() const { return mIncrements.Length(); } // [reset]
+ const nsStyleCounterData& CounterIncrementAt(uint32_t aIndex) const {
+ return mIncrements[aIndex];
+ }
+
+ void AllocateCounterIncrements(uint32_t aCount) {
+ mIncrements.Clear();
+ mIncrements.SetLength(aCount);
+ }
+
+ void SetCounterIncrementAt(uint32_t aIndex, const nsString& aCounter, int32_t aIncrement) {
+ mIncrements[aIndex].mCounter = aCounter;
+ mIncrements[aIndex].mValue = aIncrement;
+ }
+
+ uint32_t CounterResetCount() const { return mResets.Length(); } // [reset]
+ const nsStyleCounterData& CounterResetAt(uint32_t aIndex) const {
+ return mResets[aIndex];
+ }
+
+ void AllocateCounterResets(uint32_t aCount) {
+ mResets.Clear();
+ mResets.SetLength(aCount);
+ }
+
+ void SetCounterResetAt(uint32_t aIndex, const nsString& aCounter, int32_t aValue) {
+ mResets[aIndex].mCounter = aCounter;
+ mResets[aIndex].mValue = aValue;
+ }
+
+protected:
+ nsTArray<nsStyleContentData> mContents;
+ nsTArray<nsStyleCounterData> mIncrements;
+ nsTArray<nsStyleCounterData> mResets;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleUIReset
+{
+ explicit nsStyleUIReset(StyleStructContext aContext);
+ nsStyleUIReset(const nsStyleUIReset& aOther);
+ ~nsStyleUIReset();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleUIReset* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleUIReset, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleUIReset();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleUIReset, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleUIReset& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame |
+ NS_STYLE_HINT_REFLOW;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ mozilla::StyleUserSelect mUserSelect; // [reset](selection-style)
+ uint8_t mForceBrokenImageIcon; // [reset] (0 if not forcing, otherwise forcing)
+ uint8_t mIMEMode; // [reset]
+ mozilla::StyleWindowDragging mWindowDragging; // [reset]
+ uint8_t mWindowShadow; // [reset]
+};
+
+struct nsCursorImage
+{
+ bool mHaveHotspot;
+ float mHotspotX, mHotspotY;
+
+ nsCursorImage();
+ nsCursorImage(const nsCursorImage& aOther);
+ ~nsCursorImage();
+
+ nsCursorImage& operator=(const nsCursorImage& aOther);
+
+ bool operator==(const nsCursorImage& aOther) const;
+ bool operator!=(const nsCursorImage& aOther) const
+ {
+ return !(*this == aOther);
+ }
+
+ void SetImage(imgIRequest *aImage) {
+ if (mImage) {
+ mImage->UnlockImage();
+ mImage->RequestDiscard();
+ }
+ mImage = aImage;
+ if (mImage) {
+ mImage->LockImage();
+ }
+ }
+ imgIRequest* GetImage() const {
+ return mImage;
+ }
+
+private:
+ nsCOMPtr<imgIRequest> mImage;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleUserInterface
+{
+ explicit nsStyleUserInterface(StyleStructContext aContext);
+ nsStyleUserInterface(const nsStyleUserInterface& aOther);
+ ~nsStyleUserInterface();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleUserInterface* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleUserInterface, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleUserInterface();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleUserInterface, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleUserInterface& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame |
+ nsChangeHint_NeedReflow |
+ nsChangeHint_NeedDirtyReflow |
+ NS_STYLE_HINT_VISUAL |
+ nsChangeHint_UpdateCursor |
+ nsChangeHint_NeutralChange;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow;
+ }
+
+ mozilla::StyleUserInput mUserInput; // [inherited]
+ mozilla::StyleUserModify mUserModify; // [inherited] (modify-content)
+ mozilla::StyleUserFocus mUserFocus; // [inherited] (auto-select)
+ uint8_t mPointerEvents; // [inherited] see nsStyleConsts.h
+
+ uint8_t mCursor; // [inherited] See nsStyleConsts.h
+ nsTArray<nsCursorImage> mCursorImages; // [inherited] images and coords
+
+ inline uint8_t GetEffectivePointerEvents(nsIFrame* aFrame) const;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleXUL
+{
+ explicit nsStyleXUL(StyleStructContext aContext);
+ nsStyleXUL(const nsStyleXUL& aSource);
+ ~nsStyleXUL();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleXUL* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleXUL, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleXUL();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleXUL, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleXUL& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame |
+ NS_STYLE_HINT_REFLOW;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ float mBoxFlex; // [reset] see nsStyleConsts.h
+ uint32_t mBoxOrdinal; // [reset] see nsStyleConsts.h
+ mozilla::StyleBoxAlign mBoxAlign; // [reset]
+ mozilla::StyleBoxDirection mBoxDirection; // [reset]
+ mozilla::StyleBoxOrient mBoxOrient; // [reset]
+ mozilla::StyleBoxPack mBoxPack; // [reset]
+ bool mStretchStack; // [reset] see nsStyleConsts.h
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleColumn
+{
+ explicit nsStyleColumn(StyleStructContext aContext);
+ nsStyleColumn(const nsStyleColumn& aSource);
+ ~nsStyleColumn();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleColumn* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleColumn, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleColumn();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleColumn, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleColumn& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_ReconstructFrame |
+ NS_STYLE_HINT_REFLOW |
+ nsChangeHint_NeutralChange;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ /**
+ * This is the maximum number of columns we can process. It's used in both
+ * nsColumnSetFrame and nsRuleNode.
+ */
+ static const uint32_t kMaxColumnCount = 1000;
+
+ uint32_t mColumnCount; // [reset] see nsStyleConsts.h
+ nsStyleCoord mColumnWidth; // [reset] coord, auto
+ nsStyleCoord mColumnGap; // [reset] coord, normal
+
+ mozilla::StyleComplexColor mColumnRuleColor; // [reset]
+ uint8_t mColumnRuleStyle; // [reset]
+ uint8_t mColumnFill; // [reset] see nsStyleConsts.h
+
+ void SetColumnRuleWidth(nscoord aWidth) {
+ mColumnRuleWidth = NS_ROUND_BORDER_TO_PIXELS(aWidth, mTwipsPerPixel);
+ }
+
+ nscoord GetComputedColumnRuleWidth() const {
+ return (IsVisibleBorderStyle(mColumnRuleStyle) ? mColumnRuleWidth : 0);
+ }
+
+protected:
+ nscoord mColumnRuleWidth; // [reset] coord
+ nscoord mTwipsPerPixel;
+};
+
+enum nsStyleSVGPaintType {
+ eStyleSVGPaintType_None = 1,
+ eStyleSVGPaintType_Color,
+ eStyleSVGPaintType_Server,
+ eStyleSVGPaintType_ContextFill,
+ eStyleSVGPaintType_ContextStroke
+};
+
+enum nsStyleSVGOpacitySource : uint8_t {
+ eStyleSVGOpacitySource_Normal,
+ eStyleSVGOpacitySource_ContextFillOpacity,
+ eStyleSVGOpacitySource_ContextStrokeOpacity
+};
+
+class nsStyleSVGPaint
+{
+public:
+ explicit nsStyleSVGPaint(nsStyleSVGPaintType aType = nsStyleSVGPaintType(0));
+ nsStyleSVGPaint(const nsStyleSVGPaint& aSource);
+ ~nsStyleSVGPaint();
+
+ nsStyleSVGPaint& operator=(const nsStyleSVGPaint& aOther);
+
+ nsStyleSVGPaintType Type() const { return mType; }
+
+ void SetNone();
+ void SetColor(nscolor aColor);
+ void SetPaintServer(mozilla::css::URLValue* aPaintServer,
+ nscolor aFallbackColor);
+ void SetContextValue(nsStyleSVGPaintType aType,
+ nscolor aFallbackColor);
+
+ nscolor GetColor() const {
+ MOZ_ASSERT(mType == eStyleSVGPaintType_Color);
+ return mPaint.mColor;
+ }
+
+ mozilla::css::URLValue* GetPaintServer() const {
+ MOZ_ASSERT(mType == eStyleSVGPaintType_Server);
+ return mPaint.mPaintServer;
+ }
+
+ nscolor GetFallbackColor() const {
+ MOZ_ASSERT(mType == eStyleSVGPaintType_Server ||
+ mType == eStyleSVGPaintType_ContextFill ||
+ mType == eStyleSVGPaintType_ContextStroke);
+ return mFallbackColor;
+ }
+
+ bool operator==(const nsStyleSVGPaint& aOther) const;
+ bool operator!=(const nsStyleSVGPaint& aOther) const {
+ return !(*this == aOther);
+ }
+
+private:
+ void Reset();
+ void Assign(const nsStyleSVGPaint& aOther);
+
+ union {
+ nscolor mColor;
+ mozilla::css::URLValue* mPaintServer;
+ } mPaint;
+ nsStyleSVGPaintType mType;
+ nscolor mFallbackColor;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleSVG
+{
+ explicit nsStyleSVG(StyleStructContext aContext);
+ nsStyleSVG(const nsStyleSVG& aSource);
+ ~nsStyleSVG();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleSVG* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleSVG, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleSVG();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleSVG, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleSVG& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_UpdateEffects |
+ nsChangeHint_NeedReflow |
+ nsChangeHint_NeedDirtyReflow | // XXX remove me: bug 876085
+ nsChangeHint_RepaintFrame;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns nsChangeHint_NeedReflow as a hint
+ // not handled for descendants, and never returns
+ // nsChangeHint_ClearAncestorIntrinsics at all.
+ return nsChangeHint_NeedReflow;
+ }
+
+ nsStyleSVGPaint mFill; // [inherited]
+ nsStyleSVGPaint mStroke; // [inherited]
+ RefPtr<mozilla::css::URLValue> mMarkerEnd; // [inherited]
+ RefPtr<mozilla::css::URLValue> mMarkerMid; // [inherited]
+ RefPtr<mozilla::css::URLValue> mMarkerStart; // [inherited]
+ nsTArray<nsStyleCoord> mStrokeDasharray; // [inherited] coord, percent, factor
+
+ nsStyleCoord mStrokeDashoffset; // [inherited] coord, percent, factor
+ nsStyleCoord mStrokeWidth; // [inherited] coord, percent, factor
+
+ float mFillOpacity; // [inherited]
+ float mStrokeMiterlimit; // [inherited]
+ float mStrokeOpacity; // [inherited]
+
+ mozilla::StyleFillRule mClipRule; // [inherited]
+ uint8_t mColorInterpolation; // [inherited] see nsStyleConsts.h
+ uint8_t mColorInterpolationFilters; // [inherited] see nsStyleConsts.h
+ mozilla::StyleFillRule mFillRule; // [inherited] see nsStyleConsts.h
+ uint8_t mPaintOrder; // [inherited] see nsStyleConsts.h
+ uint8_t mShapeRendering; // [inherited] see nsStyleConsts.h
+ uint8_t mStrokeLinecap; // [inherited] see nsStyleConsts.h
+ uint8_t mStrokeLinejoin; // [inherited] see nsStyleConsts.h
+ uint8_t mTextAnchor; // [inherited] see nsStyleConsts.h
+
+ nsStyleSVGOpacitySource FillOpacitySource() const {
+ uint8_t value = (mContextFlags & FILL_OPACITY_SOURCE_MASK) >>
+ FILL_OPACITY_SOURCE_SHIFT;
+ return nsStyleSVGOpacitySource(value);
+ }
+ nsStyleSVGOpacitySource StrokeOpacitySource() const {
+ uint8_t value = (mContextFlags & STROKE_OPACITY_SOURCE_MASK) >>
+ STROKE_OPACITY_SOURCE_SHIFT;
+ return nsStyleSVGOpacitySource(value);
+ }
+ bool StrokeDasharrayFromObject() const {
+ return mContextFlags & STROKE_DASHARRAY_CONTEXT;
+ }
+ bool StrokeDashoffsetFromObject() const {
+ return mContextFlags & STROKE_DASHOFFSET_CONTEXT;
+ }
+ bool StrokeWidthFromObject() const {
+ return mContextFlags & STROKE_WIDTH_CONTEXT;
+ }
+
+ void SetFillOpacitySource(nsStyleSVGOpacitySource aValue) {
+ mContextFlags = (mContextFlags & ~FILL_OPACITY_SOURCE_MASK) |
+ (aValue << FILL_OPACITY_SOURCE_SHIFT);
+ }
+ void SetStrokeOpacitySource(nsStyleSVGOpacitySource aValue) {
+ mContextFlags = (mContextFlags & ~STROKE_OPACITY_SOURCE_MASK) |
+ (aValue << STROKE_OPACITY_SOURCE_SHIFT);
+ }
+ void SetStrokeDasharrayFromObject(bool aValue) {
+ mContextFlags = (mContextFlags & ~STROKE_DASHARRAY_CONTEXT) |
+ (aValue ? STROKE_DASHARRAY_CONTEXT : 0);
+ }
+ void SetStrokeDashoffsetFromObject(bool aValue) {
+ mContextFlags = (mContextFlags & ~STROKE_DASHOFFSET_CONTEXT) |
+ (aValue ? STROKE_DASHOFFSET_CONTEXT : 0);
+ }
+ void SetStrokeWidthFromObject(bool aValue) {
+ mContextFlags = (mContextFlags & ~STROKE_WIDTH_CONTEXT) |
+ (aValue ? STROKE_WIDTH_CONTEXT : 0);
+ }
+
+ bool HasMarker() const {
+ return mMarkerStart || mMarkerMid || mMarkerEnd;
+ }
+
+ /**
+ * Returns true if the stroke is not "none" and the stroke-opacity is greater
+ * than zero. This ignores stroke-widths as that depends on the context.
+ */
+ bool HasStroke() const {
+ return mStroke.Type() != eStyleSVGPaintType_None && mStrokeOpacity > 0;
+ }
+
+ /**
+ * Returns true if the fill is not "none" and the fill-opacity is greater
+ * than zero.
+ */
+ bool HasFill() const {
+ return mFill.Type() != eStyleSVGPaintType_None && mFillOpacity > 0;
+ }
+
+private:
+ // Flags to represent the use of context-fill and context-stroke
+ // for fill-opacity or stroke-opacity, and context-value for stroke-dasharray,
+ // stroke-dashoffset and stroke-width.
+ enum {
+ FILL_OPACITY_SOURCE_MASK = 0x03, // fill-opacity: context-{fill,stroke}
+ STROKE_OPACITY_SOURCE_MASK = 0x0C, // stroke-opacity: context-{fill,stroke}
+ STROKE_DASHARRAY_CONTEXT = 0x10, // stroke-dasharray: context-value
+ STROKE_DASHOFFSET_CONTEXT = 0x20, // stroke-dashoffset: context-value
+ STROKE_WIDTH_CONTEXT = 0x40, // stroke-width: context-value
+ FILL_OPACITY_SOURCE_SHIFT = 0,
+ STROKE_OPACITY_SOURCE_SHIFT = 2,
+ };
+
+ uint8_t mContextFlags; // [inherited]
+};
+
+struct nsStyleFilter
+{
+ nsStyleFilter();
+ nsStyleFilter(const nsStyleFilter& aSource);
+ ~nsStyleFilter();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ nsStyleFilter& operator=(const nsStyleFilter& aOther);
+
+ bool operator==(const nsStyleFilter& aOther) const;
+ bool operator!=(const nsStyleFilter& aOther) const {
+ return !(*this == aOther);
+ }
+
+ uint32_t GetType() const {
+ return mType;
+ }
+
+ const nsStyleCoord& GetFilterParameter() const {
+ NS_ASSERTION(mType != NS_STYLE_FILTER_DROP_SHADOW &&
+ mType != NS_STYLE_FILTER_URL &&
+ mType != NS_STYLE_FILTER_NONE, "wrong filter type");
+ return mFilterParameter;
+ }
+ void SetFilterParameter(const nsStyleCoord& aFilterParameter,
+ int32_t aType);
+
+ mozilla::css::URLValue* GetURL() const {
+ MOZ_ASSERT(mType == NS_STYLE_FILTER_URL, "wrong filter type");
+ return mURL;
+ }
+
+ bool SetURL(mozilla::css::URLValue* aValue);
+
+ nsCSSShadowArray* GetDropShadow() const {
+ NS_ASSERTION(mType == NS_STYLE_FILTER_DROP_SHADOW, "wrong filter type");
+ return mDropShadow;
+ }
+ void SetDropShadow(nsCSSShadowArray* aDropShadow);
+
+private:
+ void ReleaseRef();
+
+ uint32_t mType; // see NS_STYLE_FILTER_* constants in nsStyleConsts.h
+ nsStyleCoord mFilterParameter; // coord, percent, factor, angle
+ union {
+ mozilla::css::URLValue* mURL;
+ nsCSSShadowArray* mDropShadow;
+ };
+};
+
+template<>
+struct nsTArray_CopyChooser<nsStyleFilter>
+{
+ typedef nsTArray_CopyWithConstructors<nsStyleFilter> Type;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleSVGReset
+{
+ explicit nsStyleSVGReset(StyleStructContext aContext);
+ nsStyleSVGReset(const nsStyleSVGReset& aSource);
+ ~nsStyleSVGReset();
+
+ // Resolves and tracks the images in mMask. Only called with a Servo-backed
+ // style system, where those images must be resolved later than the OMT
+ // nsStyleSVGReset constructor call.
+ void FinishStyle(nsPresContext* aPresContext);
+
+ void* operator new(size_t sz, nsStyleSVGReset* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleSVGReset, sz);
+ }
+ void Destroy(nsPresContext* aContext);
+
+ nsChangeHint CalcDifference(const nsStyleSVGReset& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_UpdateEffects |
+ nsChangeHint_UpdateOverflow |
+ nsChangeHint_NeutralChange |
+ nsChangeHint_RepaintFrame |
+ nsChangeHint_UpdateBackgroundPosition |
+ NS_STYLE_HINT_REFLOW;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ bool HasClipPath() const {
+ return mClipPath.GetType() != mozilla::StyleShapeSourceType::None;
+ }
+
+ bool HasNonScalingStroke() const {
+ return mVectorEffect == NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE;
+ }
+
+ nsStyleImageLayers mMask;
+ mozilla::StyleClipPath mClipPath; // [reset]
+ nscolor mStopColor; // [reset]
+ nscolor mFloodColor; // [reset]
+ nscolor mLightingColor; // [reset]
+
+ float mStopOpacity; // [reset]
+ float mFloodOpacity; // [reset]
+
+ uint8_t mDominantBaseline; // [reset] see nsStyleConsts.h
+ uint8_t mVectorEffect; // [reset] see nsStyleConsts.h
+ uint8_t mMaskType; // [reset] see nsStyleConsts.h
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleVariables
+{
+ explicit nsStyleVariables(StyleStructContext aContext);
+ nsStyleVariables(const nsStyleVariables& aSource);
+ ~nsStyleVariables();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleVariables* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleVariables, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleVariables();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleVariables, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleVariables& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint(0);
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns nsChangeHint_NeedReflow or
+ // nsChangeHint_ClearAncestorIntrinsics at all.
+ return nsChangeHint(0);
+ }
+
+ mozilla::CSSVariableValues mVariables;
+};
+
+struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleEffects
+{
+ explicit nsStyleEffects(StyleStructContext aContext);
+ nsStyleEffects(const nsStyleEffects& aSource);
+ ~nsStyleEffects();
+ void FinishStyle(nsPresContext* aPresContext) {}
+
+ void* operator new(size_t sz, nsStyleEffects* aSelf) { return aSelf; }
+ void* operator new(size_t sz, nsPresContext* aContext) {
+ return aContext->PresShell()->
+ AllocateByObjectID(mozilla::eArenaObjectID_nsStyleEffects, sz);
+ }
+ void Destroy(nsPresContext* aContext) {
+ this->~nsStyleEffects();
+ aContext->PresShell()->
+ FreeByObjectID(mozilla::eArenaObjectID_nsStyleEffects, this);
+ }
+
+ nsChangeHint CalcDifference(const nsStyleEffects& aNewData) const;
+ static nsChangeHint MaxDifference() {
+ return nsChangeHint_AllReflowHints |
+ nsChangeHint_UpdateOverflow |
+ nsChangeHint_SchedulePaint |
+ nsChangeHint_RepaintFrame |
+ nsChangeHint_UpdateOpacityLayer |
+ nsChangeHint_UpdateUsesOpacity |
+ nsChangeHint_UpdateContainingBlock |
+ nsChangeHint_UpdateEffects |
+ nsChangeHint_NeutralChange;
+ }
+ static nsChangeHint DifferenceAlwaysHandledForDescendants() {
+ // CalcDifference never returns the reflow hints that are sometimes
+ // handled for descendants as hints not handled for descendants.
+ return nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ bool HasFilters() const {
+ return !mFilters.IsEmpty();
+ }
+
+ nsTArray<nsStyleFilter> mFilters; // [reset]
+ RefPtr<nsCSSShadowArray> mBoxShadow; // [reset] nullptr for 'none'
+ nsRect mClip; // [reset] offsets from UL border edge
+ float mOpacity; // [reset]
+ uint8_t mClipFlags; // [reset] see nsStyleConsts.h
+ uint8_t mMixBlendMode; // [reset] see nsStyleConsts.h
+};
+
+#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="UniquePtr">
+ *
+ * TODO(Emilio): This is a workaround and we should be able to get rid of this
+ * one.
+ */
+template<typename T, typename Deleter = mozilla::DefaultDelete<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 {
+ T* mBuffer;
+public:
+ // The existence of a destructor here prevents bindgen from deriving the Clone
+ // trait via a simple memory copy.
+ ~nsTArray_Simple() {};
+};
+
+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>);
+
+#endif /* nsStyleStruct_h___ */
diff --git a/layout/style/nsStyleStructFwd.h b/layout/style/nsStyleStructFwd.h
new file mode 100644
index 000000000..4156013bf
--- /dev/null
+++ b/layout/style/nsStyleStructFwd.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Forward declarations to avoid including all of nsStyleStruct.h.
+ */
+
+#ifndef nsStyleStructFwd_h_
+#define nsStyleStructFwd_h_
+
+enum nsStyleStructID {
+
+/*
+ * Define the constants eStyleStruct_Font, etc.
+ *
+ * The C++ standard, section 7.2, guarantees that enums begin with 0 and
+ * increase by 1.
+ *
+ * We separate the IDs of Reset and Inherited structs so that we can use
+ * the IDs as indices (offset by nsStyleStructID_*_Start) into arrays of
+ * one type or the other.
+ */
+
+nsStyleStructID_None = -1,
+nsStyleStructID_Inherited_Start = 0,
+// a dummy value so the value after it is the same as ..._Inherited_Start
+nsStyleStructID_DUMMY1 = nsStyleStructID_Inherited_Start - 1,
+
+#define STYLE_STRUCT_INHERITED(name, checkdata_cb) \
+ eStyleStruct_##name,
+#define STYLE_STRUCT_RESET(name, checkdata_cb)
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+
+nsStyleStructID_Reset_Start,
+// a dummy value so the value after it is the same as ..._Reset_Start
+nsStyleStructID_DUMMY2 = nsStyleStructID_Reset_Start - 1,
+
+#define STYLE_STRUCT_RESET(name, checkdata_cb) \
+ eStyleStruct_##name,
+#define STYLE_STRUCT_INHERITED(name, checkdata_cb)
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT_INHERITED
+#undef STYLE_STRUCT_RESET
+
+// one past the end; length of 0-based list
+nsStyleStructID_Length,
+
+nsStyleStructID_Inherited_Count =
+ nsStyleStructID_Reset_Start - nsStyleStructID_Inherited_Start,
+nsStyleStructID_Reset_Count =
+ nsStyleStructID_Length - nsStyleStructID_Reset_Start,
+
+};
+
+// A bit corresponding to each struct ID
+#define NS_STYLE_INHERIT_BIT(sid_) (1 << uint64_t(eStyleStruct_##sid_))
+
+typedef decltype(nsStyleStructID(0) + nsStyleStructID(0)) nsStyleStructID_size_t;
+
+#endif /* nsStyleStructFwd_h_ */
diff --git a/layout/style/nsStyleStructInlines.h b/layout/style/nsStyleStructInlines.h
new file mode 100644
index 000000000..9cb5e1a8a
--- /dev/null
+++ b/layout/style/nsStyleStructInlines.h
@@ -0,0 +1,264 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * 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
+
+inline void
+nsStyleImage::EnsureCachedBIData() const
+{
+ if (!mCachedBIData) {
+ const_cast<nsStyleImage*>(this)->mCachedBIData =
+ mozilla::MakeUnique<CachedBorderImageData>();
+ }
+}
+
+inline void
+nsStyleImage::SetSubImage(uint8_t aIndex, imgIContainer* aSubImage) const
+{
+ EnsureCachedBIData();
+ mCachedBIData->SetSubImage(aIndex, aSubImage);
+}
+
+inline imgIContainer*
+nsStyleImage::GetSubImage(uint8_t aIndex) const
+{
+ return (mCachedBIData) ? mCachedBIData->GetSubImage(aIndex) : nullptr;
+}
+
+bool
+nsStyleText::HasTextShadow() const
+{
+ return mTextShadow;
+}
+
+nsCSSShadowArray*
+nsStyleText::GetTextShadow() const
+{
+ return mTextShadow;
+}
+
+bool
+nsStyleText::NewlineIsSignificant(const nsTextFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
+ return NewlineIsSignificantStyle() &&
+ !aContextFrame->ShouldSuppressLineBreak() &&
+ !aContextFrame->StyleContext()->IsTextCombined();
+}
+
+bool
+nsStyleText::WhiteSpaceCanWrap(const nsIFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
+ return WhiteSpaceCanWrapStyle() && !aContextFrame->IsSVGText() &&
+ !aContextFrame->StyleContext()->IsTextCombined();
+}
+
+bool
+nsStyleText::WordCanWrap(const nsIFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
+ return WordCanWrapStyle() && !aContextFrame->IsSVGText();
+}
+
+bool
+nsStyleDisplay::IsBlockInside(const nsIFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
+ if (aContextFrame->IsSVGText()) {
+ return aContextFrame->GetType() == nsGkAtoms::blockFrame;
+ }
+ return IsBlockInsideStyle();
+}
+
+bool
+nsStyleDisplay::IsBlockOutside(const nsIFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
+ if (aContextFrame->IsSVGText()) {
+ return aContextFrame->GetType() == nsGkAtoms::blockFrame;
+ }
+ return IsBlockOutsideStyle();
+}
+
+bool
+nsStyleDisplay::IsInlineOutside(const nsIFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
+ if (aContextFrame->IsSVGText()) {
+ return aContextFrame->GetType() != nsGkAtoms::blockFrame;
+ }
+ return IsInlineOutsideStyle();
+}
+
+bool
+nsStyleDisplay::IsOriginalDisplayInlineOutside(const nsIFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
+ if (aContextFrame->IsSVGText()) {
+ return aContextFrame->GetType() != nsGkAtoms::blockFrame;
+ }
+ return IsOriginalDisplayInlineOutsideStyle();
+}
+
+mozilla::StyleDisplay
+nsStyleDisplay::GetDisplay(const nsIFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
+ if (aContextFrame->IsSVGText() && mDisplay != mozilla::StyleDisplay::None) {
+ return aContextFrame->GetType() == nsGkAtoms::blockFrame ?
+ 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->IsSVGText();
+}
+
+// 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->IsFrameOfType(nsIFrame::eSupportsCSSTransforms);
+}
+
+template<class StyleContextLike>
+bool
+nsStyleDisplay::HasFixedPosContainingBlockStyleInternal(
+ StyleContextLike* aStyleContext) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_FIXPOS_CB set on them.
+ NS_ASSERTION(aStyleContext->StyleDisplay() == this,
+ "unexpected aStyleContext");
+ return IsContainPaint() ||
+ HasPerspectiveStyle() ||
+ (mWillChangeBitField & NS_STYLE_WILL_CHANGE_FIXPOS_CB) ||
+ aStyleContext->StyleEffects()->HasFilters();
+}
+
+template<class StyleContextLike>
+bool
+nsStyleDisplay::IsFixedPosContainingBlockForAppropriateFrame(
+ StyleContextLike* aStyleContext) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_FIXPOS_CB set on them.
+ return HasFixedPosContainingBlockStyleInternal(aStyleContext) ||
+ HasTransformStyle();
+}
+
+bool
+nsStyleDisplay::IsFixedPosContainingBlock(const nsIFrame* aContextFrame) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_FIXPOS_CB set on them.
+ if (!HasFixedPosContainingBlockStyleInternal(aContextFrame->StyleContext()) &&
+ !HasTransform(aContextFrame)) {
+ return false;
+ }
+ return !aContextFrame->IsSVGText();
+}
+
+template<class StyleContextLike>
+bool
+nsStyleDisplay::HasAbsPosContainingBlockStyleInternal(
+ StyleContextLike* aStyleContext) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_ABSPOS_CB set on them.
+ NS_ASSERTION(aStyleContext->StyleDisplay() == this,
+ "unexpected aStyleContext");
+ return IsAbsolutelyPositionedStyle() ||
+ IsRelativelyPositionedStyle() ||
+ (mWillChangeBitField & NS_STYLE_WILL_CHANGE_ABSPOS_CB);
+}
+
+template<class StyleContextLike>
+bool
+nsStyleDisplay::IsAbsPosContainingBlockForAppropriateFrame(StyleContextLike* aStyleContext) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_ABSPOS_CB set on them.
+ return HasAbsPosContainingBlockStyleInternal(aStyleContext) ||
+ IsFixedPosContainingBlockForAppropriateFrame(aStyleContext);
+}
+
+bool
+nsStyleDisplay::IsAbsPosContainingBlock(const nsIFrame* aContextFrame) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_ABSPOS_CB set on them.
+ nsStyleContext* sc = aContextFrame->StyleContext();
+ if (!HasAbsPosContainingBlockStyleInternal(sc) &&
+ !HasFixedPosContainingBlockStyleInternal(sc) &&
+ !HasTransform(aContextFrame)) {
+ return false;
+ }
+ return !aContextFrame->IsSVGText();
+}
+
+bool
+nsStyleDisplay::IsRelativelyPositioned(const nsIFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
+ return IsRelativelyPositionedStyle() && !aContextFrame->IsSVGText();
+}
+
+bool
+nsStyleDisplay::IsAbsolutelyPositioned(const nsIFrame* aContextFrame) const
+{
+ NS_ASSERTION(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
+ return IsAbsolutelyPositionedStyle() && !aContextFrame->IsSVGText();
+}
+
+uint8_t
+nsStyleUserInterface::GetEffectivePointerEvents(nsIFrame* aFrame) const
+{
+ if (aFrame->GetContent() && !aFrame->GetContent()->GetParent()) {
+ // The root element has a cluster of frames associated with it
+ // (root scroll frame, canvas frame, the actual primary frame). Make
+ // those take their pointer-events value from the root element's primary
+ // frame.
+ nsIFrame* f = aFrame->GetContent()->GetPrimaryFrame();
+ if (f) {
+ return f->StyleUserInterface()->mPointerEvents;
+ }
+ }
+ return mPointerEvents;
+}
+
+bool
+nsStyleBackground::HasLocalBackground() const
+{
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, mImage) {
+ const nsStyleImageLayers::Layer& layer = mImage.mLayers[i];
+ if (!layer.mImage.IsEmpty() &&
+ layer.mAttachment == NS_STYLE_IMAGELAYER_ATTACHMENT_LOCAL) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#endif /* !defined(nsStyleStructInlines_h_) */
diff --git a/layout/style/nsStyleTransformMatrix.cpp b/layout/style/nsStyleTransformMatrix.cpp
new file mode 100644
index 000000000..3cc166707
--- /dev/null
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -0,0 +1,1061 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A class used for intermediate representations of the -moz-transform property.
+ */
+
+#include "nsStyleTransformMatrix.h"
+#include "nsCSSValue.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsRuleNode.h"
+#include "nsSVGUtils.h"
+#include "nsCSSKeywords.h"
+#include "mozilla/StyleAnimationValue.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
+
+void
+TransformReferenceBox::EnsureDimensionsAreCached()
+{
+ if (mIsCached) {
+ return;
+ }
+
+ MOZ_ASSERT(mFrame);
+
+ mIsCached = true;
+
+ if (mFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
+ if (!nsLayoutUtils::SVGTransformBoxEnabled()) {
+ mX = -mFrame->GetPosition().x;
+ mY = -mFrame->GetPosition().y;
+ Size contextSize = nsSVGUtils::GetContextSize(mFrame);
+ mWidth = nsPresContext::CSSPixelsToAppUnits(contextSize.width);
+ mHeight = nsPresContext::CSSPixelsToAppUnits(contextSize.height);
+ } else
+ if (mFrame->StyleDisplay()->mTransformBox ==
+ NS_STYLE_TRANSFORM_BOX_FILL_BOX) {
+ // Percentages in transforms resolve against the SVG bbox, and the
+ // transform is relative to the top-left of the SVG bbox.
+ gfxRect bbox = nsSVGUtils::GetBBox(const_cast<nsIFrame*>(mFrame));
+ nsRect bboxInAppUnits =
+ nsLayoutUtils::RoundGfxRectToAppRect(bbox,
+ mFrame->PresContext()->AppUnitsPerCSSPixel());
+ // 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.
+ mX = bboxInAppUnits.x - mFrame->GetPosition().x;
+ mY = bboxInAppUnits.y - mFrame->GetPosition().y;
+ mWidth = bboxInAppUnits.width;
+ mHeight = bboxInAppUnits.height;
+ } else {
+ // The value 'border-box' is treated as 'view-box' for SVG content.
+ MOZ_ASSERT(mFrame->StyleDisplay()->mTransformBox ==
+ NS_STYLE_TRANSFORM_BOX_VIEW_BOX ||
+ mFrame->StyleDisplay()->mTransformBox ==
+ NS_STYLE_TRANSFORM_BOX_BORDER_BOX,
+ "Unexpected value for 'transform-box'");
+ // 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.
+ mX = -mFrame->GetPosition().x;
+ mY = -mFrame->GetPosition().y;
+ Size contextSize = nsSVGUtils::GetContextSize(mFrame);
+ mWidth = nsPresContext::CSSPixelsToAppUnits(contextSize.width);
+ mHeight = nsPresContext::CSSPixelsToAppUnits(contextSize.height);
+ }
+ return;
+ }
+
+ // 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
+
+ mX = 0;
+ mY = 0;
+ mWidth = rect.Width();
+ mHeight = rect.Height();
+}
+
+void
+TransformReferenceBox::Init(const nsSize& aDimensions)
+{
+ MOZ_ASSERT(!mFrame && !mIsCached);
+
+ mX = 0;
+ mY = 0;
+ mWidth = aDimensions.width;
+ mHeight = aDimensions.height;
+ mIsCached = true;
+}
+
+float
+ProcessTranslatePart(const nsCSSValue& aValue,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox* aRefBox,
+ TransformReferenceBox::DimensionGetter aDimensionGetter)
+{
+ nscoord offset = 0;
+ float percent = 0.0f;
+
+ if (aValue.GetUnit() == eCSSUnit_Percent) {
+ percent = aValue.GetPercentValue();
+ } else if (aValue.GetUnit() == eCSSUnit_Pixel ||
+ aValue.GetUnit() == eCSSUnit_Number) {
+ // Handle this here (even though nsRuleNode::CalcLength handles it
+ // fine) so that callers are allowed to pass a null style context
+ // and pres context to SetToTransformFunction if they know (as
+ // StyleAnimationValue does) that all lengths within the transform
+ // function have already been computed to pixels and percents.
+ //
+ // Raw numbers are treated as being pixels.
+ //
+ // Don't convert to aValue to AppUnits here to avoid precision issues.
+ return aValue.GetFloatValue();
+ } else if (aValue.IsCalcUnit()) {
+ nsRuleNode::ComputedCalc result =
+ nsRuleNode::SpecifiedCalcToComputedCalc(aValue, aContext, aPresContext,
+ aConditions);
+ percent = result.mPercent;
+ offset = result.mLength;
+ } else {
+ offset = nsRuleNode::CalcLength(aValue, aContext, aPresContext,
+ aConditions);
+ }
+
+ float translation = NSAppUnitsToFloatPixels(offset,
+ nsPresContext::AppUnitsPerCSSPixel());
+ // We want to avoid calling aDimensionGetter if there's no percentage to be
+ // resolved (for performance reasons - see TransformReferenceBox).
+ if (percent != 0.0f && aRefBox && !aRefBox->IsEmpty()) {
+ translation += percent *
+ NSAppUnitsToFloatPixels((aRefBox->*aDimensionGetter)(),
+ nsPresContext::AppUnitsPerCSSPixel());
+ }
+ return translation;
+}
+
+/**
+ * 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 nsCSSValue::Array* aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aRefBox)
+{
+ NS_PRECONDITION(aData->Count() == 7, "Invalid array!");
+
+ gfxMatrix result;
+
+ /* Take the first four elements out of the array as floats and store
+ * them.
+ */
+ result._11 = aData->Item(1).GetFloatValue();
+ result._12 = aData->Item(2).GetFloatValue();
+ result._21 = aData->Item(3).GetFloatValue();
+ result._22 = aData->Item(4).GetFloatValue();
+
+ /* The last two elements have their length parts stored in aDelta
+ * and their percent parts stored in aX[0] and aY[1].
+ */
+ result._31 = ProcessTranslatePart(aData->Item(5),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Width);
+ result._32 = ProcessTranslatePart(aData->Item(6),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Height);
+
+ aMatrix = result * aMatrix;
+}
+
+static void
+ProcessMatrix3D(Matrix4x4& aMatrix,
+ const nsCSSValue::Array* aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aRefBox)
+{
+ NS_PRECONDITION(aData->Count() == 17, "Invalid array!");
+
+ Matrix4x4 temp;
+
+ temp._11 = aData->Item(1).GetFloatValue();
+ temp._12 = aData->Item(2).GetFloatValue();
+ temp._13 = aData->Item(3).GetFloatValue();
+ temp._14 = aData->Item(4).GetFloatValue();
+ temp._21 = aData->Item(5).GetFloatValue();
+ temp._22 = aData->Item(6).GetFloatValue();
+ temp._23 = aData->Item(7).GetFloatValue();
+ temp._24 = aData->Item(8).GetFloatValue();
+ temp._31 = aData->Item(9).GetFloatValue();
+ temp._32 = aData->Item(10).GetFloatValue();
+ temp._33 = aData->Item(11).GetFloatValue();
+ temp._34 = aData->Item(12).GetFloatValue();
+ temp._44 = aData->Item(16).GetFloatValue();
+
+ temp._41 = ProcessTranslatePart(aData->Item(13),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Width);
+ temp._42 = ProcessTranslatePart(aData->Item(14),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Height);
+ temp._43 = ProcessTranslatePart(aData->Item(15),
+ aContext, aPresContext, aConditions,
+ nullptr);
+
+ aMatrix = temp * aMatrix;
+}
+
+/* Helper function to process two matrices that we need to interpolate between */
+void
+ProcessInterpolateMatrix(Matrix4x4& aMatrix,
+ const nsCSSValue::Array* aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aRefBox,
+ bool* aContains3dTransform)
+{
+ NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
+
+ Matrix4x4 matrix1, matrix2;
+ if (aData->Item(1).GetUnit() == eCSSUnit_List) {
+ matrix1 = nsStyleTransformMatrix::ReadTransforms(aData->Item(1).GetListValue(),
+ aContext, aPresContext,
+ aConditions,
+ aRefBox, nsPresContext::AppUnitsPerCSSPixel(),
+ aContains3dTransform);
+ }
+ if (aData->Item(2).GetUnit() == eCSSUnit_List) {
+ matrix2 = ReadTransforms(aData->Item(2).GetListValue(),
+ aContext, aPresContext,
+ aConditions,
+ aRefBox, nsPresContext::AppUnitsPerCSSPixel(),
+ aContains3dTransform);
+ }
+ double progress = aData->Item(3).GetPercentValue();
+
+ aMatrix =
+ StyleAnimationValue::InterpolateTransformMatrix(matrix1, matrix2, progress)
+ * aMatrix;
+}
+
+/* Helper function to process a translatex function. */
+static void
+ProcessTranslateX(Matrix4x4& aMatrix,
+ const nsCSSValue::Array* aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aRefBox)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+
+ Point3D temp;
+
+ temp.x = ProcessTranslatePart(aData->Item(1),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Width);
+ aMatrix.PreTranslate(temp);
+}
+
+/* Helper function to process a translatey function. */
+static void
+ProcessTranslateY(Matrix4x4& aMatrix,
+ const nsCSSValue::Array* aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aRefBox)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+
+ Point3D temp;
+
+ temp.y = ProcessTranslatePart(aData->Item(1),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Height);
+ aMatrix.PreTranslate(temp);
+}
+
+static void
+ProcessTranslateZ(Matrix4x4& aMatrix,
+ const nsCSSValue::Array* aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+
+ Point3D temp;
+
+ temp.z = ProcessTranslatePart(aData->Item(1), aContext,
+ aPresContext, aConditions,
+ nullptr);
+ aMatrix.PreTranslate(temp);
+}
+
+/* Helper function to process a translate function. */
+static void
+ProcessTranslate(Matrix4x4& aMatrix,
+ const nsCSSValue::Array* aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aRefBox)
+{
+ NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Invalid array!");
+
+ Point3D temp;
+
+ temp.x = ProcessTranslatePart(aData->Item(1),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Width);
+
+ /* If we read in a Y component, set it appropriately */
+ if (aData->Count() == 3) {
+ temp.y = ProcessTranslatePart(aData->Item(2),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Height);
+ }
+ aMatrix.PreTranslate(temp);
+}
+
+static void
+ProcessTranslate3D(Matrix4x4& aMatrix,
+ const nsCSSValue::Array* aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aRefBox)
+{
+ NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
+
+ Point3D temp;
+
+ temp.x = ProcessTranslatePart(aData->Item(1),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Width);
+
+ temp.y = ProcessTranslatePart(aData->Item(2),
+ aContext, aPresContext, aConditions,
+ &aRefBox, &TransformReferenceBox::Height);
+
+ temp.z = ProcessTranslatePart(aData->Item(3),
+ aContext, aPresContext, aConditions,
+ nullptr);
+
+ 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);
+}
+
+/* Process a scalex function. */
+static void
+ProcessScaleX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Bad array!");
+ ProcessScaleHelper(aMatrix, aData->Item(1).GetFloatValue(), 1.0f, 1.0f);
+}
+
+/* Process a scaley function. */
+static void
+ProcessScaleY(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Bad array!");
+ ProcessScaleHelper(aMatrix, 1.0f, aData->Item(1).GetFloatValue(), 1.0f);
+}
+
+static void
+ProcessScaleZ(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Bad array!");
+ ProcessScaleHelper(aMatrix, 1.0f, 1.0f, aData->Item(1).GetFloatValue());
+}
+
+static void
+ProcessScale3D(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_PRECONDITION(aData->Count() == 4, "Bad array!");
+ ProcessScaleHelper(aMatrix,
+ aData->Item(1).GetFloatValue(),
+ aData->Item(2).GetFloatValue(),
+ aData->Item(3).GetFloatValue());
+}
+
+/* Process a scale function. */
+static void
+ProcessScale(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Bad array!");
+ /* We either have one element or two. If we have one, it's for both X and Y.
+ * Otherwise it's one for each.
+ */
+ const nsCSSValue& scaleX = aData->Item(1);
+ const nsCSSValue& scaleY = (aData->Count() == 2 ? scaleX :
+ aData->Item(2));
+
+ ProcessScaleHelper(aMatrix,
+ scaleX.GetFloatValue(),
+ scaleY.GetFloatValue(),
+ 1.0f);
+}
+
+/* Helper function that, given a set of angles, constructs the appropriate
+ * skew matrix.
+ */
+static void
+ProcessSkewHelper(Matrix4x4& aMatrix, double aXAngle, double aYAngle)
+{
+ aMatrix.SkewXY(aXAngle, aYAngle);
+}
+
+/* Function that converts a skewx transform into a matrix. */
+static void
+ProcessSkewX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_ASSERTION(aData->Count() == 2, "Bad array!");
+ ProcessSkewHelper(aMatrix, aData->Item(1).GetAngleValueInRadians(), 0.0);
+}
+
+/* Function that converts a skewy transform into a matrix. */
+static void
+ProcessSkewY(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_ASSERTION(aData->Count() == 2, "Bad array!");
+ ProcessSkewHelper(aMatrix, 0.0, aData->Item(1).GetAngleValueInRadians());
+}
+
+/* Function that converts a skew transform into a matrix. */
+static void
+ProcessSkew(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_ASSERTION(aData->Count() == 2 || aData->Count() == 3, "Bad array!");
+
+ double xSkew = aData->Item(1).GetAngleValueInRadians();
+ double ySkew = (aData->Count() == 2
+ ? 0.0 : aData->Item(2).GetAngleValueInRadians());
+
+ ProcessSkewHelper(aMatrix, xSkew, ySkew);
+}
+
+/* Function that converts a rotate transform into a matrix. */
+static void
+ProcessRotateZ(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+ double theta = aData->Item(1).GetAngleValueInRadians();
+ aMatrix.RotateZ(theta);
+}
+
+static void
+ProcessRotateX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+ double theta = aData->Item(1).GetAngleValueInRadians();
+ aMatrix.RotateX(theta);
+}
+
+static void
+ProcessRotateY(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+ double theta = aData->Item(1).GetAngleValueInRadians();
+ aMatrix.RotateY(theta);
+}
+
+static void
+ProcessRotate3D(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
+{
+ NS_PRECONDITION(aData->Count() == 5, "Invalid array!");
+
+ double theta = aData->Item(4).GetAngleValueInRadians();
+ float x = aData->Item(1).GetFloatValue();
+ float y = aData->Item(2).GetFloatValue();
+ float z = aData->Item(3).GetFloatValue();
+
+ Matrix4x4 temp;
+ temp.SetRotateAxisAngle(x, y, z, theta);
+
+ aMatrix = temp * aMatrix;
+}
+
+static void
+ProcessPerspective(Matrix4x4& aMatrix,
+ const nsCSSValue::Array* aData,
+ nsStyleContext *aContext,
+ nsPresContext *aPresContext,
+ RuleNodeCacheConditions& aConditions)
+{
+ NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+
+ float depth = ProcessTranslatePart(aData->Item(1), aContext,
+ aPresContext, aConditions, nullptr);
+ ApplyPerspectiveToMatrix(aMatrix, depth);
+}
+
+
+/**
+ * SetToTransformFunction is essentially a giant switch statement that fans
+ * out to many smaller helper functions.
+ */
+static void
+MatrixForTransformFunction(Matrix4x4& aMatrix,
+ const nsCSSValue::Array * aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aRefBox,
+ bool* aContains3dTransform)
+{
+ MOZ_ASSERT(aContains3dTransform);
+ NS_PRECONDITION(aData, "Why did you want to get data from a null array?");
+ // It's OK if aContext and aPresContext are null if the caller already
+ // knows that all length units have been converted to pixels (as
+ // StyleAnimationValue does).
+
+
+ /* Get the keyword for the transform. */
+ switch (TransformFunctionOf(aData)) {
+ case eCSSKeyword_translatex:
+ ProcessTranslateX(aMatrix, aData, aContext, aPresContext,
+ aConditions, aRefBox);
+ break;
+ case eCSSKeyword_translatey:
+ ProcessTranslateY(aMatrix, aData, aContext, aPresContext,
+ aConditions, aRefBox);
+ break;
+ case eCSSKeyword_translatez:
+ *aContains3dTransform = true;
+ ProcessTranslateZ(aMatrix, aData, aContext, aPresContext,
+ aConditions);
+ break;
+ case eCSSKeyword_translate:
+ ProcessTranslate(aMatrix, aData, aContext, aPresContext,
+ aConditions, aRefBox);
+ break;
+ case eCSSKeyword_translate3d:
+ *aContains3dTransform = true;
+ ProcessTranslate3D(aMatrix, aData, aContext, aPresContext,
+ aConditions, aRefBox);
+ break;
+ case eCSSKeyword_scalex:
+ ProcessScaleX(aMatrix, aData);
+ break;
+ case eCSSKeyword_scaley:
+ ProcessScaleY(aMatrix, aData);
+ break;
+ case eCSSKeyword_scalez:
+ *aContains3dTransform = true;
+ ProcessScaleZ(aMatrix, aData);
+ break;
+ case eCSSKeyword_scale:
+ ProcessScale(aMatrix, aData);
+ break;
+ case eCSSKeyword_scale3d:
+ *aContains3dTransform = true;
+ ProcessScale3D(aMatrix, aData);
+ break;
+ case eCSSKeyword_skewx:
+ ProcessSkewX(aMatrix, aData);
+ break;
+ case eCSSKeyword_skewy:
+ ProcessSkewY(aMatrix, aData);
+ break;
+ case eCSSKeyword_skew:
+ ProcessSkew(aMatrix, aData);
+ break;
+ case eCSSKeyword_rotatex:
+ *aContains3dTransform = true;
+ ProcessRotateX(aMatrix, aData);
+ break;
+ case eCSSKeyword_rotatey:
+ *aContains3dTransform = true;
+ ProcessRotateY(aMatrix, aData);
+ break;
+ case eCSSKeyword_rotatez:
+ *aContains3dTransform = true;
+ MOZ_FALLTHROUGH;
+ case eCSSKeyword_rotate:
+ ProcessRotateZ(aMatrix, aData);
+ break;
+ case eCSSKeyword_rotate3d:
+ *aContains3dTransform = true;
+ ProcessRotate3D(aMatrix, aData);
+ break;
+ case eCSSKeyword_matrix:
+ ProcessMatrix(aMatrix, aData, aContext, aPresContext,
+ aConditions, aRefBox);
+ break;
+ case eCSSKeyword_matrix3d:
+ *aContains3dTransform = true;
+ ProcessMatrix3D(aMatrix, aData, aContext, aPresContext,
+ aConditions, aRefBox);
+ break;
+ case eCSSKeyword_interpolatematrix:
+ ProcessInterpolateMatrix(aMatrix, aData, aContext, aPresContext,
+ aConditions, aRefBox,
+ aContains3dTransform);
+ break;
+ case eCSSKeyword_perspective:
+ *aContains3dTransform = true;
+ ProcessPerspective(aMatrix, aData, aContext, aPresContext,
+ aConditions);
+ break;
+ default:
+ NS_NOTREACHED("Unknown transform function!");
+ }
+}
+
+/**
+ * Return the transform function, as an nsCSSKeyword, for the given
+ * nsCSSValue::Array from a transform list.
+ */
+nsCSSKeyword
+TransformFunctionOf(const nsCSSValue::Array* aData)
+{
+ MOZ_ASSERT(aData->Item(0).GetUnit() == eCSSUnit_Enumerated);
+ return aData->Item(0).GetKeywordValue();
+}
+
+void
+SetIdentityMatrix(nsCSSValue::Array* aMatrix)
+{
+ MOZ_ASSERT(aMatrix, "aMatrix should be non-null");
+
+ nsCSSKeyword tfunc = TransformFunctionOf(aMatrix);
+ MOZ_ASSERT(tfunc == eCSSKeyword_matrix ||
+ tfunc == eCSSKeyword_matrix3d,
+ "Only accept matrix and matrix3d");
+
+ if (tfunc == eCSSKeyword_matrix) {
+ MOZ_ASSERT(aMatrix->Count() == 7, "Invalid matrix");
+ Matrix m;
+ for (size_t i = 0; i < 6; ++i) {
+ aMatrix->Item(i + 1).SetFloatValue(m.components[i], eCSSUnit_Number);
+ }
+ return;
+ }
+
+ MOZ_ASSERT(aMatrix->Count() == 17, "Invalid matrix3d");
+ Matrix4x4 m;
+ for (size_t i = 0; i < 16; ++i) {
+ aMatrix->Item(i + 1).SetFloatValue(m.components[i], eCSSUnit_Number);
+ }
+}
+
+Matrix4x4
+ReadTransforms(const nsCSSValueList* aList,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aRefBox,
+ float aAppUnitsPerMatrixUnit,
+ bool* aContains3dTransform)
+{
+ Matrix4x4 result;
+
+ for (const nsCSSValueList* curr = aList; curr != nullptr; curr = curr->mNext) {
+ const nsCSSValue &currElem = curr->mValue;
+ if (currElem.GetUnit() != eCSSUnit_Function) {
+ NS_ASSERTION(currElem.GetUnit() == eCSSUnit_None &&
+ !aList->mNext,
+ "stream should either be a list of functions or a "
+ "lone None");
+ continue;
+ }
+ NS_ASSERTION(currElem.GetArrayValue()->Count() >= 1,
+ "Incoming function is too short!");
+
+ /* Read in a single transform matrix. */
+ MatrixForTransformFunction(result, currElem.GetArrayValue(), aContext,
+ aPresContext, aConditions, aRefBox,
+ aContains3dTransform);
+ }
+
+ float scale = float(nsPresContext::AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
+ result.PreScale(1/scale, 1/scale, 1/scale);
+ result.PostScale(scale, scale, scale);
+
+ return result;
+}
+
+/*
+ * The relevant section of the transitions specification:
+ * http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
+ * defers all of the details to the 2-D and 3-D transforms specifications.
+ * For the 2-D transforms specification (all that's relevant for us, right
+ * now), the relevant section is:
+ * http://dev.w3.org/csswg/css3-2d-transforms/#animation
+ * This, in turn, refers to the unmatrix program in Graphics Gems,
+ * available from http://tog.acm.org/resources/GraphicsGems/ , and in
+ * particular as the file GraphicsGems/gemsii/unmatrix.c
+ * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
+ *
+ * The unmatrix reference is for general 3-D transform matrices (any of the
+ * 16 components can have any value).
+ *
+ * For CSS 2-D transforms, we have a 2-D matrix with the bottom row constant:
+ *
+ * [ A C E ]
+ * [ B D F ]
+ * [ 0 0 1 ]
+ *
+ * For that case, I believe the algorithm in unmatrix reduces to:
+ *
+ * (1) If A * D - B * C == 0, the matrix is singular. Fail.
+ *
+ * (2) Set translation components (Tx and Ty) to the translation parts of
+ * the matrix (E and F) and then ignore them for the rest of the time.
+ * (For us, E and F each actually consist of three constants: a
+ * length, a multiplier for the width, and a multiplier for the
+ * height. This actually requires its own decomposition, but I'll
+ * keep that separate.)
+ *
+ * (3) Let the X scale (Sx) be sqrt(A^2 + B^2). Then divide both A and B
+ * by it.
+ *
+ * (4) Let the XY shear (K) be A * C + B * D. From C, subtract A times
+ * the XY shear. From D, subtract B times the XY shear.
+ *
+ * (5) Let the Y scale (Sy) be sqrt(C^2 + D^2). Divide C, D, and the XY
+ * shear (K) by it.
+ *
+ * (6) At this point, A * D - B * C is either 1 or -1. If it is -1,
+ * negate the XY shear (K), the X scale (Sx), and A, B, C, and D.
+ * (Alternatively, we could negate the XY shear (K) and the Y scale
+ * (Sy).)
+ *
+ * (7) Let the rotation be R = atan2(B, A).
+ *
+ * Then the resulting decomposed transformation is:
+ *
+ * translate(Tx, Ty) rotate(R) skewX(atan(K)) scale(Sx, Sy)
+ *
+ * An interesting result of this is that all of the simple transform
+ * functions (i.e., all functions other than matrix()), in isolation,
+ * decompose back to themselves except for:
+ * 'skewY(φ)', which is 'matrix(1, tan(φ), 0, 1, 0, 0)', which decomposes
+ * to 'rotate(φ) skewX(φ) scale(sec(φ), cos(φ))' since (ignoring the
+ * alternate sign possibilities that would get fixed in step 6):
+ * In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
+ * Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
+ * In step 4, the XY shear is sin(φ).
+ * Thus, after step 4, C = -cos(φ)sin(φ) and D = 1 - sin²(φ) = cos²(φ).
+ * Thus, in step 5, the Y scale is sqrt(cos²(φ)(sin²(φ) + cos²(φ)) = cos(φ).
+ * Thus, after step 5, C = -sin(φ), D = cos(φ), and the XY shear is tan(φ).
+ * Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
+ * In step 7, the rotation is thus φ.
+ *
+ * skew(θ, φ), which is matrix(1, tan(φ), tan(θ), 1, 0, 0), which decomposes
+ * to 'rotate(φ) skewX(θ + φ) scale(sec(φ), cos(φ))' since (ignoring
+ * the alternate sign possibilities that would get fixed in step 6):
+ * In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
+ * Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
+ * In step 4, the XY shear is cos(φ)tan(θ) + sin(φ).
+ * Thus, after step 4,
+ * C = tan(θ) - cos(φ)(cos(φ)tan(θ) + sin(φ)) = tan(θ)sin²(φ) - cos(φ)sin(φ)
+ * D = 1 - sin(φ)(cos(φ)tan(θ) + sin(φ)) = cos²(φ) - sin(φ)cos(φ)tan(θ)
+ * Thus, in step 5, the Y scale is sqrt(C² + D²) =
+ * sqrt(tan²(θ)(sin⁴(φ) + sin²(φ)cos²(φ)) -
+ * 2 tan(θ)(sin³(φ)cos(φ) + sin(φ)cos³(φ)) +
+ * (sin²(φ)cos²(φ) + cos⁴(φ))) =
+ * sqrt(tan²(θ)sin²(φ) - 2 tan(θ)sin(φ)cos(φ) + cos²(φ)) =
+ * cos(φ) - tan(θ)sin(φ) (taking the negative of the obvious solution so
+ * we avoid flipping in step 6).
+ * After step 5, C = -sin(φ) and D = cos(φ), and the XY shear is
+ * (cos(φ)tan(θ) + sin(φ)) / (cos(φ) - tan(θ)sin(φ)) =
+ * (dividing both numerator and denominator by cos(φ))
+ * (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)) = tan(θ + φ).
+ * (See http://en.wikipedia.org/wiki/List_of_trigonometric_identities .)
+ * Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
+ * In step 7, the rotation is thus φ.
+ *
+ * To check this result, we can multiply things back together:
+ *
+ * [ cos(φ) -sin(φ) ] [ 1 tan(θ + φ) ] [ sec(φ) 0 ]
+ * [ sin(φ) cos(φ) ] [ 0 1 ] [ 0 cos(φ) ]
+ *
+ * [ cos(φ) cos(φ)tan(θ + φ) - sin(φ) ] [ sec(φ) 0 ]
+ * [ sin(φ) sin(φ)tan(θ + φ) + cos(φ) ] [ 0 cos(φ) ]
+ *
+ * but since tan(θ + φ) = (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)),
+ * cos(φ)tan(θ + φ) - sin(φ)
+ * = cos(φ)(tan(θ) + tan(φ)) - sin(φ) + sin(φ)tan(θ)tan(φ)
+ * = cos(φ)tan(θ) + sin(φ) - sin(φ) + sin(φ)tan(θ)tan(φ)
+ * = cos(φ)tan(θ) + sin(φ)tan(θ)tan(φ)
+ * = tan(θ) (cos(φ) + sin(φ)tan(φ))
+ * = tan(θ) sec(φ) (cos²(φ) + sin²(φ))
+ * = tan(θ) sec(φ)
+ * and
+ * sin(φ)tan(θ + φ) + cos(φ)
+ * = sin(φ)(tan(θ) + tan(φ)) + cos(φ) - cos(φ)tan(θ)tan(φ)
+ * = tan(θ) (sin(φ) - sin(φ)) + sin(φ)tan(φ) + cos(φ)
+ * = sec(φ) (sin²(φ) + cos²(φ))
+ * = sec(φ)
+ * so the above is:
+ * [ cos(φ) tan(θ) sec(φ) ] [ sec(φ) 0 ]
+ * [ sin(φ) sec(φ) ] [ 0 cos(φ) ]
+ *
+ * [ 1 tan(θ) ]
+ * [ tan(φ) 1 ]
+ */
+
+/*
+ * Decompose2DMatrix implements the above decomposition algorithm.
+ */
+
+bool
+Decompose2DMatrix(const Matrix& aMatrix,
+ Point3D& aScale,
+ ShearArray& aShear,
+ gfxQuaternion& aRotate,
+ Point3D& aTranslate)
+{
+ float A = aMatrix._11,
+ B = aMatrix._12,
+ C = aMatrix._21,
+ D = aMatrix._22;
+ if (A * D == B * C) {
+ // singular matrix
+ return false;
+ }
+
+ float scaleX = sqrt(A * A + B * B);
+ A /= scaleX;
+ B /= scaleX;
+
+ float XYshear = A * C + B * D;
+ C -= A * XYshear;
+ D -= B * XYshear;
+
+ float scaleY = sqrt(C * C + D * D);
+ C /= scaleY;
+ D /= scaleY;
+ XYshear /= scaleY;
+
+ // A*D - B*C should now be 1 or -1
+ NS_ASSERTION(0.99 < Abs(A*D - B*C) && Abs(A*D - B*C) < 1.01,
+ "determinant should now be 1 or -1");
+ if (A * D < B * C) {
+ A = -A;
+ B = -B;
+ C = -C;
+ D = -D;
+ XYshear = -XYshear;
+ scaleX = -scaleX;
+ }
+
+ float rotate = atan2f(B, A);
+ aRotate = gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2));
+ aShear[ShearType::XYSHEAR] = XYshear;
+ aScale.x = scaleX;
+ aScale.y = scaleY;
+ aTranslate.x = aMatrix._31;
+ aTranslate.y = aMatrix._32;
+ return true;
+}
+
+/**
+ * Implementation of the unmatrix algorithm, specified by:
+ *
+ * http://dev.w3.org/csswg/css3-2d-transforms/#unmatrix
+ *
+ * This, in turn, refers to the unmatrix program in Graphics Gems,
+ * available from http://tog.acm.org/resources/GraphicsGems/ , and in
+ * particular as the file GraphicsGems/gemsii/unmatrix.c
+ * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
+ */
+bool
+Decompose3DMatrix(const Matrix4x4& aMatrix,
+ Point3D& aScale,
+ ShearArray& aShear,
+ gfxQuaternion& aRotate,
+ Point3D& aTranslate,
+ Point4D& aPerspective)
+{
+ Matrix4x4 local = aMatrix;
+
+ if (local[3][3] == 0) {
+ return false;
+ }
+ /* Normalize the matrix */
+ local.Normalize();
+
+ /**
+ * perspective is used to solve for perspective, but it also provides
+ * an easy way to test for singularity of the upper 3x3 component.
+ */
+ Matrix4x4 perspective = local;
+ Point4D empty(0, 0, 0, 1);
+ perspective.SetTransposedVector(3, empty);
+
+ if (perspective.Determinant() == 0.0) {
+ return false;
+ }
+
+ /* First, isolate perspective. */
+ if (local[0][3] != 0 || local[1][3] != 0 ||
+ local[2][3] != 0) {
+ /* aPerspective is the right hand side of the equation. */
+ aPerspective = local.TransposedVector(3);
+
+ /**
+ * Solve the equation by inverting perspective and multiplying
+ * aPerspective by the inverse.
+ */
+ perspective.Invert();
+ aPerspective = perspective.TransposeTransform4D(aPerspective);
+
+ /* Clear the perspective partition */
+ local.SetTransposedVector(3, empty);
+ } else {
+ aPerspective = Point4D(0, 0, 0, 1);
+ }
+
+ /* Next take care of translation */
+ for (int i = 0; i < 3; i++) {
+ aTranslate[i] = local[3][i];
+ local[3][i] = 0;
+ }
+
+ /* Now get scale and shear. */
+
+ /* Compute X scale factor and normalize first row. */
+ aScale.x = local[0].Length();
+ local[0] /= aScale.x;
+
+ /* Compute XY shear factor and make 2nd local orthogonal to 1st. */
+ aShear[ShearType::XYSHEAR] = local[0].DotProduct(local[1]);
+ local[1] -= local[0] * aShear[ShearType::XYSHEAR];
+
+ /* Now, compute Y scale and normalize 2nd local. */
+ aScale.y = local[1].Length();
+ local[1] /= aScale.y;
+ aShear[ShearType::XYSHEAR] /= aScale.y;
+
+ /* Compute XZ and YZ shears, make 3rd local orthogonal */
+ aShear[ShearType::XZSHEAR] = local[0].DotProduct(local[2]);
+ local[2] -= local[0] * aShear[ShearType::XZSHEAR];
+ aShear[ShearType::YZSHEAR] = local[1].DotProduct(local[2]);
+ local[2] -= local[1] * aShear[ShearType::YZSHEAR];
+
+ /* Next, get Z scale and normalize 3rd local. */
+ aScale.z = local[2].Length();
+ local[2] /= aScale.z;
+
+ aShear[ShearType::XZSHEAR] /= aScale.z;
+ aShear[ShearType::YZSHEAR] /= aScale.z;
+
+ /**
+ * At this point, the matrix (in locals) is orthonormal.
+ * Check for a coordinate system flip. If the determinant
+ * is -1, then negate the matrix and the scaling factors.
+ */
+ if (local[0].DotProduct(local[1].CrossProduct(local[2])) < 0) {
+ aScale *= -1;
+ for (int i = 0; i < 3; i++) {
+ local[i] *= -1;
+ }
+ }
+
+ /* Now, get the rotations out */
+ aRotate = gfxQuaternion(local);
+
+ return true;
+}
+
+Matrix
+CSSValueArrayTo2DMatrix(nsCSSValue::Array* aArray)
+{
+ MOZ_ASSERT(aArray &&
+ TransformFunctionOf(aArray) == eCSSKeyword_matrix &&
+ aArray->Count() == 7);
+ Matrix m(aArray->Item(1).GetFloatValue(),
+ aArray->Item(2).GetFloatValue(),
+ aArray->Item(3).GetFloatValue(),
+ aArray->Item(4).GetFloatValue(),
+ aArray->Item(5).GetFloatValue(),
+ aArray->Item(6).GetFloatValue());
+ return m;
+}
+
+Matrix4x4
+CSSValueArrayTo3DMatrix(nsCSSValue::Array* aArray)
+{
+ MOZ_ASSERT(aArray &&
+ TransformFunctionOf(aArray) == eCSSKeyword_matrix3d &&
+ aArray->Count() == 17);
+ gfx::Float array[16];
+ for (size_t i = 0; i < 16; ++i) {
+ array[i] = aArray->Item(i+1).GetFloatValue();
+ }
+ Matrix4x4 m(array);
+ return m;
+}
+
+} // namespace nsStyleTransformMatrix
diff --git a/layout/style/nsStyleTransformMatrix.h b/layout/style/nsStyleTransformMatrix.h
new file mode 100644
index 000000000..bde87c7ad
--- /dev/null
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A class representing three matrices that can be used for style transforms.
+ */
+
+#ifndef nsStyleTransformMatrix_h_
+#define nsStyleTransformMatrix_h_
+
+#include "mozilla/EnumeratedArray.h"
+#include "nsCSSValue.h"
+
+#include <limits>
+
+class nsIFrame;
+class nsStyleContext;
+class nsPresContext;
+struct gfxQuaternion;
+struct nsRect;
+namespace mozilla {
+class RuleNodeCacheConditions;
+} // namespace mozilla
+
+/**
+ * A helper to generate gfxMatrixes from css transform functions.
+ */
+namespace nsStyleTransformMatrix {
+
+ // Function for applying perspective() transform function. We treat
+ // any value smaller than epsilon as perspective(infinity), which
+ // follows CSSWG's resolution on perspective(0). See bug 1316236.
+ inline void ApplyPerspectiveToMatrix(mozilla::gfx::Matrix4x4& aMatrix,
+ float aDepth)
+ {
+ if (aDepth >= std::numeric_limits<float>::epsilon()) {
+ aMatrix.Perspective(aDepth);
+ }
+ }
+
+ /**
+ * 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)();
+
+ explicit TransformReferenceBox()
+ : mFrame(nullptr)
+ , mIsCached(false)
+ {}
+
+ explicit TransformReferenceBox(const nsIFrame* aFrame)
+ : mFrame(aFrame)
+ , mIsCached(false)
+ {
+ MOZ_ASSERT(mFrame);
+ }
+
+ explicit TransformReferenceBox(const nsIFrame* aFrame,
+ const nsSize& aFallbackDimensions)
+ {
+ mFrame = aFrame;
+ mIsCached = false;
+ if (!mFrame) {
+ Init(aFallbackDimensions);
+ }
+ }
+
+ void Init(const nsIFrame* aFrame) {
+ MOZ_ASSERT(!mFrame && !mIsCached);
+ mFrame = aFrame;
+ }
+
+ void Init(const nsSize& aDimensions);
+
+ /**
+ * 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 mX;
+ }
+ nscoord Y() {
+ EnsureDimensionsAreCached();
+ return mY;
+ }
+
+ /**
+ * The size of the reference box.
+ */
+ nscoord Width() {
+ EnsureDimensionsAreCached();
+ return mWidth;
+ }
+ nscoord Height() {
+ EnsureDimensionsAreCached();
+ return mHeight;
+ }
+
+ 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;
+ nscoord mX, mY, mWidth, mHeight;
+ bool mIsCached;
+ };
+
+ /**
+ * Return the transform function, as an nsCSSKeyword, for the given
+ * nsCSSValue::Array from a transform list.
+ */
+ nsCSSKeyword TransformFunctionOf(const nsCSSValue::Array* aData);
+
+ void SetIdentityMatrix(nsCSSValue::Array* aMatrix);
+
+ float ProcessTranslatePart(const nsCSSValue& aValue,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ mozilla::RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox* aRefBox,
+ TransformReferenceBox::DimensionGetter aDimensionGetter = nullptr);
+
+ void
+ ProcessInterpolateMatrix(mozilla::gfx::Matrix4x4& aMatrix,
+ const nsCSSValue::Array* aData,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ mozilla::RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aBounds,
+ bool* aContains3dTransform);
+
+ /**
+ * Given an nsCSSValueList containing -moz-transform functions,
+ * returns a matrix containing the value of those functions.
+ *
+ * @param aData The nsCSSValueList containing the transform functions
+ * @param aContext The style context, used for unit conversion.
+ * @param aPresContext The presentation context, used for unit conversion.
+ * @param aConditions Set to uncachable (by calling SetUncacheable()) if the
+ * result cannot be cached in the rule tree, otherwise untouched.
+ * @param aBounds The frame's bounding rectangle.
+ * @param aAppUnitsPerMatrixUnit The number of app units per device pixel.
+ * @param aContains3dTransform [out] Set to true if aList contains at least
+ * one 3d transform function (as defined in the CSS transforms
+ * specification), false otherwise.
+ *
+ * aContext and aPresContext may be null if all of the (non-percent)
+ * length values in aData are already known to have been converted to
+ * eCSSUnit_Pixel (as they are in an StyleAnimationValue)
+ */
+ mozilla::gfx::Matrix4x4 ReadTransforms(const nsCSSValueList* aList,
+ nsStyleContext* aContext,
+ nsPresContext* aPresContext,
+ mozilla::RuleNodeCacheConditions& aConditions,
+ TransformReferenceBox& aBounds,
+ float aAppUnitsPerMatrixUnit,
+ bool* aContains3dTransform);
+
+ // Shear type for decomposition.
+ enum class ShearType {
+ XYSHEAR,
+ XZSHEAR,
+ YZSHEAR,
+ Count
+ };
+ using ShearArray =
+ mozilla::EnumeratedArray<ShearType, ShearType::Count, float>;
+
+ /*
+ * Implements the 2d transform matrix decomposition algorithm.
+ */
+ bool Decompose2DMatrix(const mozilla::gfx::Matrix& aMatrix,
+ mozilla::gfx::Point3D& aScale,
+ ShearArray& aShear,
+ gfxQuaternion& aRotate,
+ mozilla::gfx::Point3D& aTranslate);
+ /*
+ * Implements the 3d transform matrix decomposition algorithm.
+ */
+ bool Decompose3DMatrix(const mozilla::gfx::Matrix4x4& aMatrix,
+ mozilla::gfx::Point3D& aScale,
+ ShearArray& aShear,
+ gfxQuaternion& aRotate,
+ mozilla::gfx::Point3D& aTranslate,
+ mozilla::gfx::Point4D& aPerspective);
+
+ mozilla::gfx::Matrix CSSValueArrayTo2DMatrix(nsCSSValue::Array* aArray);
+ mozilla::gfx::Matrix4x4 CSSValueArrayTo3DMatrix(nsCSSValue::Array* aArray);
+} // namespace nsStyleTransformMatrix
+
+#endif
diff --git a/layout/style/nsStyleUtil.cpp b/layout/style/nsStyleUtil.cpp
new file mode 100644
index 000000000..840cd03c3
--- /dev/null
+++ b/layout/style/nsStyleUtil.cpp
@@ -0,0 +1,783 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStyleUtil.h"
+#include "nsStyleConsts.h"
+
+#include "nsIContent.h"
+#include "nsCSSProps.h"
+#include "nsRuleNode.h"
+#include "nsROCSSPrimitiveValue.h"
+#include "nsStyleStruct.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIURI.h"
+#include "nsPrintfCString.h"
+
+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::ValueIncludes(const nsSubstring& aValueList,
+ const nsSubstring& 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)
+{
+ NS_PRECONDITION(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 && *in < 0xA0)) {
+ // Escape U+0000 through U+001F and U+007F through U+009F numerically.
+ aReturn.AppendPrintf("\\%hx ", *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("\\%hx ", *in);
+ ++in;
+ }
+
+ for (; in != end; ++in) {
+ char16_t ch = *in;
+ if (ch == 0x00) {
+ aReturn.Append(char16_t(0xFFFD));
+ } else if (ch < 0x20 || (0x7F <= ch && ch < 0xA0)) {
+ // Escape U+0000 through U+001F and U+007F through U+009F numerically.
+ aReturn.AppendPrintf("\\%hx ", *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);
+ }
+ }
+}
+
+// unquoted family names must be a sequence of idents
+// so escape any parts that require escaping
+static void
+AppendUnquotedFamilyName(const nsAString& aFamilyName, nsAString& aResult)
+{
+ const char16_t *p, *p_end;
+ aFamilyName.BeginReading(p);
+ aFamilyName.EndReading(p_end);
+
+ bool moreThanOne = false;
+ while (p < p_end) {
+ const char16_t* identStart = p;
+ while (++p != p_end && *p != ' ')
+ /* nothing */ ;
+
+ nsDependentSubstring ident(identStart, p);
+ if (!ident.IsEmpty()) {
+ if (moreThanOne) {
+ aResult.Append(' ');
+ }
+ nsStyleUtil::AppendEscapedCSSIdent(ident, aResult);
+ moreThanOne = true;
+ }
+
+ ++p;
+ }
+}
+
+/* static */ void
+nsStyleUtil::AppendEscapedCSSFontFamilyList(
+ const mozilla::FontFamilyList& aFamilyList,
+ nsAString& aResult)
+{
+ const nsTArray<FontFamilyName>& fontlist = aFamilyList.GetFontlist();
+ size_t i, len = fontlist.Length();
+ for (i = 0; i < len; i++) {
+ if (i != 0) {
+ aResult.Append(',');
+ }
+ const FontFamilyName& name = fontlist[i];
+ switch (name.mType) {
+ case eFamily_named:
+ AppendUnquotedFamilyName(name.mName, aResult);
+ break;
+ case eFamily_named_quoted:
+ AppendEscapedCSSString(name.mName, aResult);
+ break;
+ default:
+ name.AppendToString(aResult);
+ }
+ }
+}
+
+
+/* static */ void
+nsStyleUtil::AppendBitmaskCSSValue(nsCSSPropertyID aProperty,
+ int32_t aMaskedValue,
+ int32_t aFirstMask,
+ int32_t aLastMask,
+ nsAString& aResult)
+{
+ for (int32_t mask = aFirstMask; mask <= aLastMask; mask <<= 1) {
+ if (mask & aMaskedValue) {
+ AppendASCIItoUTF16(nsCSSProps::LookupPropertyValue(aProperty, mask),
+ aResult);
+ aMaskedValue &= ~mask;
+ if (aMaskedValue) { // more left
+ aResult.Append(char16_t(' '));
+ }
+ }
+ }
+ MOZ_ASSERT(aMaskedValue == 0, "unexpected bit remaining in bitfield");
+}
+
+/* static */ void
+nsStyleUtil::AppendAngleValue(const nsStyleCoord& aAngle, nsAString& aResult)
+{
+ MOZ_ASSERT(aAngle.IsAngleValue(), "Should have angle value");
+
+ // Append number.
+ AppendCSSNumber(aAngle.GetAngleValue(), aResult);
+
+ // Append unit.
+ switch (aAngle.GetUnit()) {
+ case eStyleUnit_Degree: aResult.AppendLiteral("deg"); break;
+ case eStyleUnit_Grad: aResult.AppendLiteral("grad"); break;
+ case eStyleUnit_Radian: aResult.AppendLiteral("rad"); break;
+ case eStyleUnit_Turn: aResult.AppendLiteral("turn"); break;
+ default: NS_NOTREACHED("unrecognized angle unit");
+ }
+}
+
+/* static */ void
+nsStyleUtil::AppendPaintOrderValue(uint8_t aValue,
+ nsAString& aResult)
+{
+ static_assert
+ (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
+ "SVGStyleStruct::mPaintOrder and local variables not big enough");
+
+ if (aValue == NS_STYLE_PAINT_ORDER_NORMAL) {
+ aResult.AppendLiteral("normal");
+ return;
+ }
+
+ // Append the minimal value necessary for the given paint order.
+ static_assert(NS_STYLE_PAINT_ORDER_LAST_VALUE == 3,
+ "paint-order values added; check serialization");
+
+ // The following relies on the default order being the order of the
+ // constant values.
+
+ const uint8_t MASK = (1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1;
+
+ uint32_t lastPositionToSerialize = 0;
+ for (uint32_t position = NS_STYLE_PAINT_ORDER_LAST_VALUE - 1;
+ position > 0;
+ position--) {
+ uint8_t component =
+ (aValue >> (position * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK;
+ uint8_t earlierComponent =
+ (aValue >> ((position - 1) * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK;
+ if (component < earlierComponent) {
+ lastPositionToSerialize = position - 1;
+ break;
+ }
+ }
+
+ for (uint32_t position = 0; position <= lastPositionToSerialize; position++) {
+ if (position > 0) {
+ aResult.Append(' ');
+ }
+ uint8_t component = aValue & MASK;
+ switch (component) {
+ case NS_STYLE_PAINT_ORDER_FILL:
+ aResult.AppendLiteral("fill");
+ break;
+
+ case NS_STYLE_PAINT_ORDER_STROKE:
+ aResult.AppendLiteral("stroke");
+ break;
+
+ case NS_STYLE_PAINT_ORDER_MARKERS:
+ aResult.AppendLiteral("markers");
+ break;
+
+ default:
+ NS_NOTREACHED("unexpected paint-order component value");
+ }
+ aValue >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
+ }
+}
+
+/* static */ void
+nsStyleUtil::AppendFontFeatureSettings(const nsTArray<gfxFontFeature>& aFeatures,
+ nsAString& aResult)
+{
+ for (uint32_t i = 0, numFeat = aFeatures.Length(); i < numFeat; i++) {
+ const gfxFontFeature& feat = aFeatures[i];
+
+ if (i != 0) {
+ aResult.AppendLiteral(", ");
+ }
+
+ // output tag
+ char tag[7];
+ tag[0] = '"';
+ tag[1] = (feat.mTag >> 24) & 0xff;
+ tag[2] = (feat.mTag >> 16) & 0xff;
+ tag[3] = (feat.mTag >> 8) & 0xff;
+ tag[4] = feat.mTag & 0xff;
+ tag[5] = '"';
+ tag[6] = 0;
+ aResult.AppendASCII(tag);
+
+ // output value, if necessary
+ if (feat.mValue == 0) {
+ // 0 ==> off
+ aResult.AppendLiteral(" off");
+ } else if (feat.mValue > 1) {
+ aResult.Append(' ');
+ aResult.AppendInt(feat.mValue);
+ }
+ // else, omit value if 1, implied by default
+ }
+}
+
+/* static */ void
+nsStyleUtil::AppendFontFeatureSettings(const nsCSSValue& aSrc,
+ nsAString& aResult)
+{
+ nsCSSUnit unit = aSrc.GetUnit();
+
+ if (unit == eCSSUnit_Normal) {
+ aResult.AppendLiteral("normal");
+ return;
+ }
+
+ NS_PRECONDITION(unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep,
+ "improper value unit for font-feature-settings:");
+
+ nsTArray<gfxFontFeature> featureSettings;
+ nsRuleNode::ComputeFontFeatures(aSrc.GetPairListValue(), featureSettings);
+ AppendFontFeatureSettings(featureSettings, aResult);
+}
+
+/* static */ void
+nsStyleUtil::GetFunctionalAlternatesName(int32_t aFeature,
+ nsAString& aFeatureName)
+{
+ aFeatureName.Truncate();
+ nsCSSKeyword key =
+ nsCSSProps::ValueToKeywordEnum(aFeature,
+ nsCSSProps::kFontVariantAlternatesFuncsKTable);
+
+ NS_ASSERTION(key != eCSSKeyword_UNKNOWN, "bad alternate feature type");
+ AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(key), aFeatureName);
+}
+
+/* static */ void
+nsStyleUtil::SerializeFunctionalAlternates(
+ const nsTArray<gfxAlternateValue>& aAlternates,
+ nsAString& aResult)
+{
+ nsAutoString funcName, funcParams;
+ uint32_t numValues = aAlternates.Length();
+
+ uint32_t feature = 0;
+ for (uint32_t i = 0; i < numValues; i++) {
+ const gfxAlternateValue& v = aAlternates.ElementAt(i);
+ if (feature != v.alternate) {
+ feature = v.alternate;
+ if (!funcName.IsEmpty() && !funcParams.IsEmpty()) {
+ if (!aResult.IsEmpty()) {
+ aResult.Append(char16_t(' '));
+ }
+
+ // append the previous functional value
+ aResult.Append(funcName);
+ aResult.Append(char16_t('('));
+ aResult.Append(funcParams);
+ aResult.Append(char16_t(')'));
+ }
+
+ // function name
+ GetFunctionalAlternatesName(v.alternate, funcName);
+ NS_ASSERTION(!funcName.IsEmpty(), "unknown property value name");
+
+ // function params
+ funcParams.Truncate();
+ AppendEscapedCSSIdent(v.value, funcParams);
+ } else {
+ if (!funcParams.IsEmpty()) {
+ funcParams.AppendLiteral(", ");
+ }
+ AppendEscapedCSSIdent(v.value, funcParams);
+ }
+ }
+
+ // append the previous functional value
+ if (!funcName.IsEmpty() && !funcParams.IsEmpty()) {
+ if (!aResult.IsEmpty()) {
+ aResult.Append(char16_t(' '));
+ }
+
+ aResult.Append(funcName);
+ aResult.Append(char16_t('('));
+ aResult.Append(funcParams);
+ aResult.Append(char16_t(')'));
+ }
+}
+
+/* static */ void
+nsStyleUtil::ComputeFunctionalAlternates(const nsCSSValueList* aList,
+ nsTArray<gfxAlternateValue>& aAlternateValues)
+{
+ gfxAlternateValue v;
+
+ aAlternateValues.Clear();
+ for (const nsCSSValueList* curr = aList; curr != nullptr; curr = curr->mNext) {
+ // list contains function units
+ if (curr->mValue.GetUnit() != eCSSUnit_Function) {
+ continue;
+ }
+
+ // element 0 is the propval in ident form
+ const nsCSSValue::Array *func = curr->mValue.GetArrayValue();
+
+ // lookup propval
+ nsCSSKeyword key = func->Item(0).GetKeywordValue();
+ NS_ASSERTION(key != eCSSKeyword_UNKNOWN, "unknown alternate property value");
+
+ int32_t alternate;
+ if (key == eCSSKeyword_UNKNOWN ||
+ !nsCSSProps::FindKeyword(key,
+ nsCSSProps::kFontVariantAlternatesFuncsKTable,
+ alternate)) {
+ NS_NOTREACHED("keyword not a font-variant-alternates value");
+ continue;
+ }
+ v.alternate = alternate;
+
+ // other elements are the idents associated with the propval
+ // append one alternate value for each one
+ uint32_t numElems = func->Count();
+ for (uint32_t i = 1; i < numElems; i++) {
+ const nsCSSValue& value = func->Item(i);
+ NS_ASSERTION(value.GetUnit() == eCSSUnit_Ident,
+ "weird unit found in variant alternate");
+ if (value.GetUnit() != eCSSUnit_Ident) {
+ continue;
+ }
+ value.GetStringValue(v.value);
+ aAlternateValues.AppendElement(v);
+ }
+ }
+}
+
+static void
+AppendSerializedUnicodePoint(uint32_t aCode, nsACString& aBuf)
+{
+ aBuf.Append(nsPrintfCString("%0X", aCode));
+}
+
+// A unicode-range: descriptor is represented as an array of integers,
+// to be interpreted as a sequence of pairs: min max min max ...
+// It is in source order. (Possibly it should be sorted and overlaps
+// consolidated, but right now we don't do that.)
+/* static */ void
+nsStyleUtil::AppendUnicodeRange(const nsCSSValue& aValue, nsAString& aResult)
+{
+ NS_PRECONDITION(aValue.GetUnit() == eCSSUnit_Null ||
+ aValue.GetUnit() == eCSSUnit_Array,
+ "improper value unit for unicode-range:");
+ aResult.Truncate();
+ if (aValue.GetUnit() != eCSSUnit_Array)
+ return;
+
+ nsCSSValue::Array const & sources = *aValue.GetArrayValue();
+ nsAutoCString buf;
+
+ MOZ_ASSERT(sources.Count() % 2 == 0,
+ "odd number of entries in a unicode-range: array");
+
+ for (uint32_t i = 0; i < sources.Count(); i += 2) {
+ uint32_t min = sources[i].GetIntValue();
+ uint32_t max = sources[i+1].GetIntValue();
+
+ // We don't try to replicate the U+XX?? notation.
+ buf.AppendLiteral("U+");
+ AppendSerializedUnicodePoint(min, buf);
+
+ if (min != max) {
+ buf.Append('-');
+ AppendSerializedUnicodePoint(max, buf);
+ }
+ buf.AppendLiteral(", ");
+ }
+ buf.Truncate(buf.Length() - 2); // remove the last comma-space
+ CopyASCIItoUTF16(buf, aResult);
+}
+
+/* static */ void
+nsStyleUtil::AppendSerializedFontSrc(const nsCSSValue& aValue,
+ nsAString& aResult)
+{
+ // A src: descriptor is represented as an array value; each entry in
+ // the array can be eCSSUnit_URL, eCSSUnit_Local_Font, or
+ // eCSSUnit_Font_Format. Blocks of eCSSUnit_Font_Format may appear
+ // only after one of the first two. (css3-fonts only contemplates
+ // annotating URLs with formats, but we handle the general case.)
+
+ NS_PRECONDITION(aValue.GetUnit() == eCSSUnit_Array,
+ "improper value unit for src:");
+
+ const nsCSSValue::Array& sources = *aValue.GetArrayValue();
+ size_t i = 0;
+
+ while (i < sources.Count()) {
+ nsAutoString formats;
+
+ if (sources[i].GetUnit() == eCSSUnit_URL) {
+ aResult.AppendLiteral("url(");
+ nsDependentString url(sources[i].GetOriginalURLValue());
+ nsStyleUtil::AppendEscapedCSSString(url, aResult);
+ aResult.Append(')');
+ } else if (sources[i].GetUnit() == eCSSUnit_Local_Font) {
+ aResult.AppendLiteral("local(");
+ nsDependentString local(sources[i].GetStringBufferValue());
+ nsStyleUtil::AppendEscapedCSSString(local, aResult);
+ aResult.Append(')');
+ } else {
+ NS_NOTREACHED("entry in src: descriptor with improper unit");
+ i++;
+ continue;
+ }
+
+ i++;
+ formats.Truncate();
+ while (i < sources.Count() &&
+ sources[i].GetUnit() == eCSSUnit_Font_Format) {
+ formats.Append('"');
+ formats.Append(sources[i].GetStringBufferValue());
+ formats.AppendLiteral("\", ");
+ i++;
+ }
+ if (formats.Length() > 0) {
+ formats.Truncate(formats.Length() - 2); // remove the last comma
+ aResult.AppendLiteral(" format(");
+ aResult.Append(formats);
+ aResult.Append(')');
+ }
+ aResult.AppendLiteral(", ");
+ }
+ aResult.Truncate(aResult.Length() - 2); // remove the last comma-space
+}
+
+/* static */ void
+nsStyleUtil::AppendStepsTimingFunction(nsTimingFunction::Type aType,
+ uint32_t aSteps,
+ nsAString& aResult)
+{
+ MOZ_ASSERT(aType == nsTimingFunction::Type::StepStart ||
+ aType == nsTimingFunction::Type::StepEnd);
+
+ aResult.AppendLiteral("steps(");
+ aResult.AppendInt(aSteps);
+ if (aType == nsTimingFunction::Type::StepStart) {
+ aResult.AppendLiteral(", start)");
+ } else {
+ aResult.AppendLiteral(")");
+ }
+}
+
+/* static */ void
+nsStyleUtil::AppendCubicBezierTimingFunction(float aX1, float aY1,
+ float aX2, float aY2,
+ nsAString& aResult)
+{
+ // set the value from the cubic-bezier control points
+ // (We could try to regenerate the keywords if we want.)
+ aResult.AppendLiteral("cubic-bezier(");
+ aResult.AppendFloat(aX1);
+ aResult.AppendLiteral(", ");
+ aResult.AppendFloat(aY1);
+ aResult.AppendLiteral(", ");
+ aResult.AppendFloat(aX2);
+ aResult.AppendLiteral(", ");
+ aResult.AppendFloat(aY2);
+ aResult.Append(')');
+}
+
+/* static */ void
+nsStyleUtil::AppendCubicBezierKeywordTimingFunction(
+ nsTimingFunction::Type aType,
+ nsAString& aResult)
+{
+ switch (aType) {
+ case nsTimingFunction::Type::Ease:
+ case nsTimingFunction::Type::Linear:
+ case nsTimingFunction::Type::EaseIn:
+ case nsTimingFunction::Type::EaseOut:
+ case nsTimingFunction::Type::EaseInOut: {
+ nsCSSKeyword keyword = nsCSSProps::ValueToKeywordEnum(
+ static_cast<int32_t>(aType),
+ nsCSSProps::kTransitionTimingFunctionKTable);
+ AppendASCIItoUTF16(nsCSSKeywords::GetStringValue(keyword),
+ aResult);
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected aType");
+ break;
+ }
+}
+
+/* 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 */ bool
+nsStyleUtil::IsSignificantChild(nsIContent* aChild, bool aTextIsSignificant,
+ bool aWhitespaceIsSignificant)
+{
+ NS_ASSERTION(!aWhitespaceIsSignificant || aTextIsSignificant,
+ "Nonsensical arguments");
+
+ bool isText = aChild->IsNodeOfType(nsINode::eTEXT);
+
+ if (!isText && !aChild->IsNodeOfType(nsINode::eCOMMENT) &&
+ !aChild->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) {
+ return true;
+ }
+
+ return aTextIsSignificant && isText && aChild->TextLength() != 0 &&
+ (aWhitespaceIsSignificant ||
+ !aChild->TextIsOnlyWhitespace());
+}
+
+// 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 Position::Coord& 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.mLength != 0) {
+ return true;
+ }
+
+ // 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.
+ if (aCoord.mHasPercent &&
+ (aCoord.mPercent < 0.0f || aCoord.mPercent > 1.0f)) {
+ return true;
+ }
+ return false;
+}
+
+
+/* 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 == NS_STYLE_OBJECT_FIT_COVER ||
+ objectFit == NS_STYLE_OBJECT_FIT_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.mXPosition) ||
+ ObjectPositionCoordMightCauseOverflow(objectPosistion.mYPosition)) {
+ return true;
+ }
+
+ return false;
+}
+
+
+/* static */ bool
+nsStyleUtil::CSPAllowsInlineStyle(nsIContent* aContent,
+ nsIPrincipal* aPrincipal,
+ nsIURI* aSourceURI,
+ uint32_t aLineNumber,
+ const nsSubstring& aStyleText,
+ nsresult* aRv)
+{
+ nsresult rv;
+
+ if (aRv) {
+ *aRv = NS_OK;
+ }
+
+ MOZ_ASSERT(!aContent || aContent->NodeInfo()->NameAtom() == nsGkAtoms::style,
+ "aContent passed to CSPAllowsInlineStyle "
+ "for an element that is not <style>");
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ rv = aPrincipal->GetCsp(getter_AddRefs(csp));
+
+ if (NS_FAILED(rv)) {
+ if (aRv)
+ *aRv = rv;
+ return false;
+ }
+
+ if (!csp) {
+ // No CSP --> the style is allowed
+ return true;
+ }
+
+ // query the nonce
+ nsAutoString nonce;
+ if (aContent) {
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
+ }
+
+ bool allowInlineStyle = true;
+ rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_STYLESHEET,
+ nonce,
+ false, // aParserCreated only applies to scripts
+ aStyleText, aLineNumber,
+ &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 000000000..e5b7a055f
--- /dev/null
+++ b/layout/style/nsStyleUtil.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsStyleUtil_h___
+#define nsStyleUtil_h___
+
+#include "nsCoord.h"
+#include "nsCSSPropertyID.h"
+#include "nsString.h"
+#include "nsTArrayForwardDeclare.h"
+#include "gfxFontFamilyList.h"
+#include "nsStyleStruct.h"
+#include "nsCRT.h"
+
+class nsCSSValue;
+class nsStringComparator;
+class nsStyleCoord;
+class nsIContent;
+class nsIPrincipal;
+class nsIURI;
+struct gfxFontFeature;
+struct gfxAlternateValue;
+struct nsCSSValueList;
+
+// Style utility functions
+class nsStyleUtil {
+public:
+
+ static bool DashMatchCompare(const nsAString& aAttributeValue,
+ const nsAString& aSelectorValue,
+ const nsStringComparator& aComparator);
+
+ static bool ValueIncludes(const nsSubstring& aValueList,
+ const nsSubstring& 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);
+
+ static void
+ AppendEscapedCSSFontFamilyList(const mozilla::FontFamilyList& aFamilyList,
+ nsAString& aResult);
+
+ // Append a bitmask-valued property's value(s) (space-separated) to aResult.
+ static void AppendBitmaskCSSValue(nsCSSPropertyID aProperty,
+ int32_t aMaskedValue,
+ int32_t aFirstMask,
+ int32_t aLastMask,
+ nsAString& aResult);
+
+ static void AppendAngleValue(const nsStyleCoord& aValue, nsAString& aResult);
+
+ static void AppendPaintOrderValue(uint8_t aValue, nsAString& aResult);
+
+ static void AppendFontFeatureSettings(const nsTArray<gfxFontFeature>& aFeatures,
+ nsAString& aResult);
+
+ static void AppendFontFeatureSettings(const nsCSSValue& src,
+ nsAString& aResult);
+
+ static void AppendUnicodeRange(const nsCSSValue& aValue, nsAString& aResult);
+
+ static void AppendCSSNumber(float aNumber, nsAString& aResult)
+ {
+ aResult.AppendFloat(aNumber);
+ }
+
+ static void AppendStepsTimingFunction(nsTimingFunction::Type aType,
+ uint32_t aSteps,
+ nsAString& aResult);
+ static void AppendCubicBezierTimingFunction(float aX1, float aY1,
+ float aX2, float aY2,
+ nsAString& aResult);
+ static void AppendCubicBezierKeywordTimingFunction(
+ nsTimingFunction::Type aType,
+ nsAString& aResult);
+
+ static void AppendSerializedFontSrc(const nsCSSValue& aValue,
+ nsAString& aResult);
+
+ // convert bitmask value to keyword name for a functional alternate
+ static void GetFunctionalAlternatesName(int32_t aFeature,
+ nsAString& aFeatureName);
+
+ // Append functional font-variant-alternates values to string
+ static void
+ SerializeFunctionalAlternates(const nsTArray<gfxAlternateValue>& aAlternates,
+ nsAString& aResult);
+
+ // List of functional font-variant-alternates values to feature/value pairs
+ static void
+ ComputeFunctionalAlternates(const nsCSSValueList* aList,
+ nsTArray<gfxAlternateValue>& aAlternateValues);
+
+ /*
+ * 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 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);
+
+ /*
+ * Does this child count as significant for selector matching?
+ */
+ static bool IsSignificantChild(nsIContent* aChild,
+ bool aTextIsSignificant,
+ 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 this principal 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 aPrincipal
+ * The principal of the of the document (*not* of the style sheet).
+ * The document's principal is where any Content Security Policy that
+ * should be used to block or allow inline styles will be located.
+ * @param aSourceURI
+ * URI of document containing inline style (for reporting violations)
+ * @param aLineNumber
+ * Line 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(nsIContent* aContent,
+ nsIPrincipal* aPrincipal,
+ nsIURI* aSourceURI,
+ uint32_t aLineNumber,
+ const nsSubstring& aStyleText,
+ nsresult* aRv);
+
+ template<size_t N>
+ static bool MatchesLanguagePrefix(const char16_t* aLang, size_t aLen,
+ const char16_t (&aPrefix)[N])
+ {
+ return !nsCRT::strncmp(aLang, aPrefix, N - 1) &&
+ (aLen == N - 1 || aLang[N - 1] == '-');
+ }
+
+ template<size_t N>
+ static bool MatchesLanguagePrefix(const nsIAtom* 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 000000000..4a1a5b7ad
--- /dev/null
+++ b/layout/style/nsTransitionManager.cpp
@@ -0,0 +1,1047 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Code to start and animate CSS transitions. */
+
+#include "nsTransitionManager.h"
+#include "nsAnimationManager.h"
+#include "mozilla/dom/CSSTransitionBinding.h"
+
+#include "nsIContent.h"
+#include "nsStyleContext.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/TimeStamp.h"
+#include "nsRefreshDriver.h"
+#include "nsRuleProcessorData.h"
+#include "nsRuleWalker.h"
+#include "nsCSSPropertyIDSet.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/dom/Element.h"
+#include "nsIFrame.h"
+#include "Layers.h"
+#include "FrameLayerBuilder.h"
+#include "nsCSSProps.h"
+#include "nsCSSPseudoElements.h"
+#include "nsDisplayList.h"
+#include "nsStyleChangeList.h"
+#include "nsStyleSet.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "nsDOMMutationObserver.h"
+
+using mozilla::TimeStamp;
+using mozilla::TimeDuration;
+using mozilla::dom::Animation;
+using mozilla::dom::AnimationPlayState;
+using mozilla::dom::CSSTransition;
+using mozilla::dom::KeyframeEffectReadOnly;
+
+using namespace mozilla;
+using namespace mozilla::css;
+
+typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
+
+namespace {
+struct TransitionEventParams {
+ EventMessage mMessage;
+ StickyTimeDuration mElapsedTime;
+ TimeStamp mTimeStamp;
+};
+} // anonymous namespace
+
+double
+ElementPropertyTransition::CurrentValuePortion() const
+{
+ MOZ_ASSERT(!GetLocalTime().IsNull(),
+ "Getting the value portion of an animation that's not being "
+ "sampled");
+
+ // Transitions use a fill mode of 'backwards' so GetComputedTiming will
+ // never return a null time progress due to being *before* the animation
+ // interval. However, it might be possible that we're behind on flushing
+ // causing us to get called *after* the animation interval. So, just in
+ // case, we override the fill mode to 'both' to ensure the progress
+ // is never null.
+ TimingParams timingToUse = SpecifiedTiming();
+ timingToUse.mFill = dom::FillMode::Both;
+ ComputedTiming computedTiming = GetComputedTiming(&timingToUse);
+
+ MOZ_ASSERT(!computedTiming.mProgress.IsNull(),
+ "Got a null progress for a fill mode of 'both'");
+ MOZ_ASSERT(mKeyframes.Length() == 2,
+ "Should have two animation keyframes for a transition");
+ return ComputedTimingFunction::GetPortion(mKeyframes[0].mTimingFunction,
+ computedTiming.mProgress.Value(),
+ computedTiming.mBeforeFlag);
+}
+
+void
+ElementPropertyTransition::UpdateStartValueFromReplacedTransition()
+{
+ if (!mReplacedTransition) {
+ return;
+ }
+ MOZ_ASSERT(nsCSSProps::PropHasFlags(TransitionProperty(),
+ CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
+ "The transition property should be able to be run on the "
+ "compositor");
+ MOZ_ASSERT(mTarget && mTarget->mElement->OwnerDoc(),
+ "We should have a valid document at this moment");
+
+ dom::DocumentTimeline* timeline = mTarget->mElement->OwnerDoc()->Timeline();
+ ComputedTiming computedTiming = GetComputedTimingAt(
+ dom::CSSTransition::GetCurrentTimeAt(*timeline,
+ TimeStamp::Now(),
+ mReplacedTransition->mStartTime,
+ mReplacedTransition->mPlaybackRate),
+ mReplacedTransition->mTiming,
+ mReplacedTransition->mPlaybackRate);
+
+ if (!computedTiming.mProgress.IsNull()) {
+ double valuePosition =
+ ComputedTimingFunction::GetPortion(mReplacedTransition->mTimingFunction,
+ computedTiming.mProgress.Value(),
+ computedTiming.mBeforeFlag);
+ StyleAnimationValue startValue;
+ if (StyleAnimationValue::Interpolate(mProperties[0].mProperty,
+ mReplacedTransition->mFromValue,
+ mReplacedTransition->mToValue,
+ valuePosition, startValue)) {
+ MOZ_ASSERT(mProperties.Length() == 1 &&
+ mProperties[0].mSegments.Length() == 1,
+ "The transition should have one property and one segment");
+ nsCSSValue cssValue;
+ DebugOnly<bool> uncomputeResult =
+ StyleAnimationValue::UncomputeValue(mProperties[0].mProperty,
+ startValue,
+ cssValue);
+ mProperties[0].mSegments[0].mFromValue = Move(startValue);
+ MOZ_ASSERT(uncomputeResult, "UncomputeValue should not fail");
+ MOZ_ASSERT(mKeyframes.Length() == 2,
+ "Transitions should have exactly two animation keyframes");
+ MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
+ "Transitions should have exactly one property in their first "
+ "frame");
+ mKeyframes[0].mPropertyValues[0].mValue = cssValue;
+ }
+ }
+
+ mReplacedTransition.reset();
+}
+
+////////////////////////// CSSTransition ////////////////////////////
+
+JSObject*
+CSSTransition::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::CSSTransitionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+CSSTransition::GetTransitionProperty(nsString& aRetVal) const
+{
+ MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
+ "Transition Property should be initialized");
+ aRetVal =
+ NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
+}
+
+AnimationPlayState
+CSSTransition::PlayStateFromJS() const
+{
+ FlushStyle();
+ return Animation::PlayStateFromJS();
+}
+
+void
+CSSTransition::PlayFromJS(ErrorResult& aRv)
+{
+ FlushStyle();
+ Animation::PlayFromJS(aRv);
+}
+
+void
+CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
+{
+ if (mNeedsNewAnimationIndexWhenRun &&
+ PlayState() != AnimationPlayState::Idle) {
+ mAnimationIndex = sNextAnimationIndex++;
+ mNeedsNewAnimationIndexWhenRun = false;
+ }
+
+ Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
+}
+
+void
+CSSTransition::QueueEvents()
+{
+ if (!mEffect ||
+ !mOwningElement.IsSet()) {
+ return;
+ }
+
+ dom::Element* owningElement;
+ CSSPseudoElementType owningPseudoType;
+ mOwningElement.GetElement(owningElement, owningPseudoType);
+ MOZ_ASSERT(owningElement, "Owning element should be set");
+
+ nsPresContext* presContext = mOwningElement.GetRenderedPresContext();
+ if (!presContext) {
+ return;
+ }
+
+ ComputedTiming computedTiming = mEffect->GetComputedTiming();
+ const StickyTimeDuration zeroDuration;
+ StickyTimeDuration intervalStartTime =
+ std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration), zeroDuration);
+ StickyTimeDuration intervalEndTime =
+ std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration), zeroDuration);
+
+ // TimeStamps to use for ordering the events when they are dispatched. We
+ // use a TimeStamp so we can compare events produced by different elements,
+ // perhaps even with different timelines.
+ // The zero timestamp is for transitionrun events where we ignore the delay
+ // for the purpose of ordering events.
+ TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
+ TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
+
+ TransitionPhase currentPhase;
+ if (mPendingState != PendingState::NotPending &&
+ (mPreviousTransitionPhase == TransitionPhase::Idle ||
+ mPreviousTransitionPhase == TransitionPhase::Pending))
+ {
+ currentPhase = TransitionPhase::Pending;
+ } else {
+ currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
+ }
+
+ AutoTArray<TransitionEventParams, 3> events;
+ switch (mPreviousTransitionPhase) {
+ case TransitionPhase::Idle:
+ if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
+
+ case TransitionPhase::Pending:
+ case TransitionPhase::Before:
+ if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
+
+ case TransitionPhase::Active:
+ if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalEndTime,
+ endTimeStamp });
+ } else if (currentPhase == TransitionPhase::Before) {
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalStartTime,
+ startTimeStamp });
+ }
+ break;
+
+ case TransitionPhase::After:
+ if (currentPhase == TransitionPhase::Before) {
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalStartTime,
+ endTimeStamp });
+ }
+ break;
+ }
+ mPreviousTransitionPhase = currentPhase;
+
+ nsTransitionManager* manager = presContext->TransitionManager();
+ for (const TransitionEventParams& evt : events) {
+ manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType,
+ evt.mMessage,
+ TransitionProperty(),
+ evt.mElapsedTime,
+ evt.mTimeStamp,
+ this));
+ }
+}
+
+void
+CSSTransition::Tick()
+{
+ Animation::Tick();
+ QueueEvents();
+}
+
+nsCSSPropertyID
+CSSTransition::TransitionProperty() const
+{
+ MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
+ "Transition property should be initialized");
+ return mTransitionProperty;
+}
+
+StyleAnimationValue
+CSSTransition::ToValue() const
+{
+ MOZ_ASSERT(!mTransitionToValue.IsNull(),
+ "Transition ToValue should be initialized");
+ return mTransitionToValue;
+}
+
+bool
+CSSTransition::HasLowerCompositeOrderThan(const CSSTransition& aOther) const
+{
+ MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
+ "Should only be called for CSS transitions that are sorted "
+ "as CSS transitions (i.e. tied to CSS markup)");
+
+ // 0. Object-equality case
+ if (&aOther == this) {
+ return false;
+ }
+
+ // 1. Sort by document order
+ if (!mOwningElement.Equals(aOther.mOwningElement)) {
+ return mOwningElement.LessThan(aOther.mOwningElement);
+ }
+
+ // 2. (Same element and pseudo): Sort by transition generation
+ if (mAnimationIndex != aOther.mAnimationIndex) {
+ return mAnimationIndex < aOther.mAnimationIndex;
+ }
+
+ // 3. (Same transition generation): Sort by transition property
+ return nsCSSProps::GetStringValue(TransitionProperty()) <
+ nsCSSProps::GetStringValue(aOther.TransitionProperty());
+}
+
+/* static */ Nullable<TimeDuration>
+CSSTransition::GetCurrentTimeAt(const DocumentTimeline& aTimeline,
+ const TimeStamp& aBaseTime,
+ const TimeDuration& aStartTime,
+ double aPlaybackRate)
+{
+ Nullable<TimeDuration> result;
+
+ Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
+ if (!timelineTime.IsNull()) {
+ result.SetValue((timelineTime.Value() - aStartTime)
+ .MultDouble(aPlaybackRate));
+ }
+
+ return result;
+}
+
+void
+CSSTransition::SetEffectFromStyle(AnimationEffectReadOnly* aEffect)
+{
+ Animation::SetEffectNoUpdate(aEffect);
+
+ // Initialize transition property.
+ ElementPropertyTransition* pt = aEffect ? aEffect->AsTransition() : nullptr;
+ if (eCSSProperty_UNKNOWN == mTransitionProperty && pt) {
+ mTransitionProperty = pt->TransitionProperty();
+ mTransitionToValue = pt->ToValue();
+ }
+}
+
+////////////////////////// nsTransitionManager ////////////////////////////
+
+NS_IMPL_CYCLE_COLLECTION(nsTransitionManager, mEventDispatcher)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTransitionManager, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTransitionManager, Release)
+
+static inline bool
+ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
+ nsStyleContext* aStyleContext,
+ StyleAnimationValue& aComputedValue)
+{
+ return (nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_Discrete ||
+ aProperty == eCSSProperty_visibility) &&
+ StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
+ aComputedValue);
+}
+
+void
+nsTransitionManager::StyleContextChanged(dom::Element *aElement,
+ nsStyleContext *aOldStyleContext,
+ RefPtr<nsStyleContext>* aNewStyleContext /* inout */)
+{
+ nsStyleContext* newStyleContext = *aNewStyleContext;
+
+ NS_PRECONDITION(aOldStyleContext->GetPseudo() == newStyleContext->GetPseudo(),
+ "pseudo type mismatch");
+
+ if (mInAnimationOnlyStyleUpdate) {
+ // If we're doing an animation-only style update, return, since the
+ // purpose of an animation-only style update is to update only the
+ // animation styles so that we don't consider style changes
+ // resulting from changes in the animation time for starting a
+ // transition.
+ return;
+ }
+
+ if (!mPresContext->IsDynamic()) {
+ // For print or print preview, ignore transitions.
+ return;
+ }
+
+ if (aOldStyleContext->HasPseudoElementData() !=
+ newStyleContext->HasPseudoElementData()) {
+ // If the old style context and new style context differ in terms of
+ // whether they're inside ::first-letter, ::first-line, or similar,
+ // bail. We can't hit this codepath for normal style changes
+ // involving moving frames around the boundaries of these
+ // pseudo-elements since we don't call StyleContextChanged from
+ // ReparentStyleContext. However, we can hit this codepath during
+ // the handling of transitions that start across reframes.
+ //
+ // While there isn't an easy *perfect* way to handle this case, err
+ // on the side of missing some transitions that we ought to have
+ // rather than having bogus transitions that we shouldn't.
+ //
+ // We could consider changing this handling, although it's worth
+ // thinking about whether the code below could do anything weird in
+ // this case.
+ return;
+ }
+
+ // NOTE: Things in this function (and ConsiderInitiatingTransition)
+ // should never call PeekStyleData because we don't preserve gotten
+ // structs across reframes.
+
+ // Return sooner (before the startedAny check below) for the most
+ // common case: no transitions specified or running.
+ const nsStyleDisplay *disp = newStyleContext->StyleDisplay();
+ CSSPseudoElementType pseudoType = newStyleContext->GetPseudoType();
+ if (pseudoType != CSSPseudoElementType::NotPseudo) {
+ if (pseudoType != CSSPseudoElementType::before &&
+ pseudoType != CSSPseudoElementType::after) {
+ return;
+ }
+
+ NS_ASSERTION((pseudoType == CSSPseudoElementType::before &&
+ aElement->IsGeneratedContentContainerForBefore()) ||
+ (pseudoType == CSSPseudoElementType::after &&
+ aElement->IsGeneratedContentContainerForAfter()),
+ "Unexpected aElement coming through");
+
+ // Else the element we want to use from now on is the element the
+ // :before or :after is attached to.
+ aElement = aElement->GetParent()->AsElement();
+ }
+
+ CSSTransitionCollection* collection =
+ CSSTransitionCollection::GetAnimationCollection(aElement, pseudoType);
+ if (!collection &&
+ disp->mTransitionPropertyCount == 1 &&
+ disp->mTransitions[0].GetCombinedDuration() <= 0.0f) {
+ return;
+ }
+
+ MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(),
+ "ServoRestyleManager should not use nsTransitionManager "
+ "for transitions");
+ if (collection &&
+ collection->mCheckGeneration ==
+ mPresContext->RestyleManager()->AsGecko()->GetAnimationGeneration()) {
+ // When we start a new transition, we immediately post a restyle.
+ // If the animation generation on the collection is current, that
+ // means *this* is that restyle, since we bump the animation
+ // generation on the restyle manager whenever there's a real style
+ // change (i.e., one where mInAnimationOnlyStyleUpdate isn't true,
+ // which causes us to return above). Thus we shouldn't do anything.
+ return;
+ }
+ if (newStyleContext->GetParent() &&
+ newStyleContext->GetParent()->HasPseudoElementData()) {
+ // Ignore transitions on things that inherit properties from
+ // pseudo-elements.
+ // FIXME (Bug 522599): Add tests for this.
+ return;
+ }
+
+ NS_WARNING_ASSERTION(
+ !mPresContext->EffectCompositor()->HasThrottledStyleUpdates(),
+ "throttled animations not up to date");
+
+ // Compute what the css-transitions spec calls the "after-change
+ // style", which is the new style without any data from transitions,
+ // but still inheriting from data that contains transitions that are
+ // not stopping or starting right now.
+ RefPtr<nsStyleContext> afterChangeStyle;
+ if (collection) {
+ MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
+ "ServoStyleSets should not use nsTransitionManager "
+ "for transitions");
+ nsStyleSet* styleSet = mPresContext->StyleSet()->AsGecko();
+ afterChangeStyle =
+ styleSet->ResolveStyleWithoutAnimation(aElement, newStyleContext,
+ eRestyle_CSSTransitions);
+ } else {
+ afterChangeStyle = newStyleContext;
+ }
+
+ nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
+
+ DebugOnly<bool> startedAny = false;
+ // We don't have to update transitions if display:none, although we will
+ // cancel them after restyling.
+ if (!afterChangeStyle->IsInDisplayNoneSubtree()) {
+ startedAny = UpdateTransitions(disp, aElement, collection,
+ aOldStyleContext, afterChangeStyle);
+ }
+
+ MOZ_ASSERT(!startedAny || collection,
+ "must have element transitions if we started any transitions");
+
+ EffectCompositor::CascadeLevel cascadeLevel =
+ EffectCompositor::CascadeLevel::Transitions;
+
+ if (collection) {
+ collection->UpdateCheckGeneration(mPresContext);
+ mPresContext->EffectCompositor()->MaybeUpdateAnimationRule(aElement,
+ pseudoType,
+ cascadeLevel,
+ newStyleContext);
+ }
+
+ // We want to replace the new style context with the after-change style.
+ *aNewStyleContext = afterChangeStyle;
+ if (collection) {
+ // Since we have transition styles, we have to undo this replacement.
+ // The check of collection->mCheckGeneration against the restyle
+ // manager's GetAnimationGeneration() will ensure that we don't go
+ // through the rest of this function again when we do.
+ mPresContext->EffectCompositor()->PostRestyleForAnimation(aElement,
+ pseudoType,
+ cascadeLevel);
+ }
+}
+
+bool
+nsTransitionManager::UpdateTransitions(
+ const nsStyleDisplay* aDisp,
+ dom::Element* aElement,
+ CSSTransitionCollection*& aElementTransitions,
+ nsStyleContext* aOldStyleContext,
+ nsStyleContext* aNewStyleContext)
+{
+ MOZ_ASSERT(aDisp, "Null nsStyleDisplay");
+ 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 |whichStarted|).
+ bool startedAny = false;
+ nsCSSPropertyIDSet whichStarted;
+ for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
+ const StyleTransition& t = aDisp->mTransitions[i];
+ // Check the combined duration (combination of delay and duration)
+ // first, since it defaults to zero, which means we can ignore the
+ // transition.
+ if (t.GetCombinedDuration() > 0.0f) {
+ // We might have something to transition. See if any of the
+ // properties in question changed and are animatable.
+ // FIXME: Would be good to find a way to share code between this
+ // interpretation of transition-property and the one below.
+ nsCSSPropertyID property = t.GetProperty();
+ if (property == eCSSPropertyExtra_no_properties ||
+ property == eCSSPropertyExtra_variable ||
+ property == eCSSProperty_UNKNOWN) {
+ // Nothing to do, but need to exclude this from cases below.
+ } else if (property == eCSSPropertyExtra_all_properties) {
+ for (nsCSSPropertyID p = nsCSSPropertyID(0);
+ p < eCSSProperty_COUNT_no_shorthands;
+ p = nsCSSPropertyID(p + 1)) {
+ ConsiderInitiatingTransition(p, t, aElement, aElementTransitions,
+ aOldStyleContext, aNewStyleContext,
+ &startedAny, &whichStarted);
+ }
+ } else if (nsCSSProps::IsShorthand(property)) {
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property,
+ CSSEnabledState::eForAllContent)
+ {
+ ConsiderInitiatingTransition(*subprop, t, aElement,
+ aElementTransitions,
+ aOldStyleContext, aNewStyleContext,
+ &startedAny, &whichStarted);
+ }
+ } else {
+ ConsiderInitiatingTransition(property, t, aElement, aElementTransitions,
+ aOldStyleContext, aNewStyleContext,
+ &startedAny, &whichStarted);
+ }
+ }
+ }
+
+ // 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.
+ // Note that we also do the latter set of work in
+ // nsTransitionManager::PruneCompletedTransitions.
+ if (aElementTransitions) {
+ bool checkProperties =
+ aDisp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
+ nsCSSPropertyIDSet allTransitionProperties;
+ if (checkProperties) {
+ for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
+ const StyleTransition& t = aDisp->mTransitions[i];
+ // FIXME: Would be good to find a way to share code between this
+ // interpretation of transition-property and the one above.
+ nsCSSPropertyID property = t.GetProperty();
+ if (property == eCSSPropertyExtra_no_properties ||
+ property == eCSSPropertyExtra_variable ||
+ property == eCSSProperty_UNKNOWN) {
+ // Nothing to do, but need to exclude this from cases below.
+ } else if (property == eCSSPropertyExtra_all_properties) {
+ for (nsCSSPropertyID p = nsCSSPropertyID(0);
+ p < eCSSProperty_COUNT_no_shorthands;
+ p = nsCSSPropertyID(p + 1)) {
+ allTransitionProperties.AddProperty(p);
+ }
+ } else if (nsCSSProps::IsShorthand(property)) {
+ CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
+ subprop, property, CSSEnabledState::eForAllContent) {
+ allTransitionProperties.AddProperty(*subprop);
+ }
+ } else {
+ allTransitionProperties.AddProperty(property);
+ }
+ }
+ }
+
+ OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
+ size_t i = animations.Length();
+ MOZ_ASSERT(i != 0, "empty transitions list?");
+ StyleAnimationValue currentValue;
+ do {
+ --i;
+ CSSTransition* anim = animations[i];
+ // properties no longer in 'transition-property'
+ if ((checkProperties &&
+ !allTransitionProperties.HasProperty(anim->TransitionProperty())) ||
+ // 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
+ !ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
+ aNewStyleContext, currentValue) ||
+ currentValue != anim->ToValue()) {
+ // stop the transition
+ if (anim->HasCurrentEffect()) {
+ EffectSet* effectSet =
+ EffectSet::GetEffectSet(aElement,
+ aNewStyleContext->GetPseudoType());
+ if (effectSet) {
+ effectSet->UpdateAnimationGeneration(mPresContext);
+ }
+ }
+ anim->CancelFromStyle();
+ animations.RemoveElementAt(i);
+ }
+ } while (i != 0);
+
+ if (animations.IsEmpty()) {
+ aElementTransitions->Destroy();
+ aElementTransitions = nullptr;
+ }
+ }
+
+ return startedAny;
+}
+
+void
+nsTransitionManager::ConsiderInitiatingTransition(
+ nsCSSPropertyID aProperty,
+ const StyleTransition& aTransition,
+ dom::Element* aElement,
+ CSSTransitionCollection*& aElementTransitions,
+ nsStyleContext* aOldStyleContext,
+ nsStyleContext* aNewStyleContext,
+ bool* aStartedAny,
+ nsCSSPropertyIDSet* aWhichStarted)
+{
+ // IsShorthand itself will assert if aProperty is not a property.
+ MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
+ "property out of range");
+ NS_ASSERTION(!aElementTransitions ||
+ aElementTransitions->mElement == aElement, "Element mismatch");
+
+ // Ignore disabled properties. We can arrive here if the transition-property
+ // is 'all' and the disabled property has a default value which derives value
+ // from another property, e.g. color.
+ if (!nsCSSProps::IsEnabled(aProperty, CSSEnabledState::eForAllContent)) {
+ return;
+ }
+
+ if (aWhichStarted->HasProperty(aProperty)) {
+ // A later item in transition-property already started a
+ // transition for this property, so we ignore this one.
+ // See comment above and
+ // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
+ return;
+ }
+
+ if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
+ return;
+ }
+
+ dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
+
+ StyleAnimationValue startValue, endValue, dummyValue;
+ bool haveValues =
+ ExtractNonDiscreteComputedValue(aProperty, aOldStyleContext, startValue) &&
+ ExtractNonDiscreteComputedValue(aProperty, aNewStyleContext, endValue);
+
+ bool haveChange = startValue != endValue;
+
+ bool shouldAnimate =
+ haveValues &&
+ haveChange &&
+ // Check that we can interpolate between these values
+ // (If this is ever a performance problem, we could add a
+ // CanInterpolate method, but it seems fine for now.)
+ StyleAnimationValue::Interpolate(aProperty, startValue, endValue,
+ 0.5, dummyValue);
+
+ bool haveCurrentTransition = false;
+ size_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
+ const ElementPropertyTransition *oldPT = nullptr;
+ if (aElementTransitions) {
+ OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
+ for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
+ if (animations[i]->TransitionProperty() == aProperty) {
+ haveCurrentTransition = true;
+ currentIndex = i;
+ oldPT = animations[i]->GetEffect()
+ ? animations[i]->GetEffect()->AsTransition()
+ : nullptr;
+ break;
+ }
+ }
+ }
+
+ // 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 (haveCurrentTransition && haveValues &&
+ aElementTransitions->mAnimations[currentIndex]->ToValue() == endValue) {
+ // GetAnimationRule already called RestyleForAnimation.
+ return;
+ }
+
+ if (!shouldAnimate) {
+ if (haveCurrentTransition) {
+ // 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.
+ OwningCSSTransitionPtrArray& animations =
+ aElementTransitions->mAnimations;
+ animations[currentIndex]->CancelFromStyle();
+ oldPT = nullptr; // Clear pointer so it doesn't dangle
+ animations.RemoveElementAt(currentIndex);
+ EffectSet* effectSet =
+ EffectSet::GetEffectSet(aElement, aNewStyleContext->GetPseudoType());
+ if (effectSet) {
+ effectSet->UpdateAnimationGeneration(mPresContext);
+ }
+
+ if (animations.IsEmpty()) {
+ aElementTransitions->Destroy();
+ // |aElementTransitions| is now a dangling pointer!
+ aElementTransitions = nullptr;
+ }
+ // GetAnimationRule already called RestyleForAnimation.
+ }
+ return;
+ }
+
+ const nsTimingFunction &tf = aTransition.GetTimingFunction();
+ float delay = aTransition.GetDelay();
+ float duration = aTransition.GetDuration();
+ if (duration < 0.0) {
+ // The spec says a negative duration is treated as zero.
+ duration = 0.0;
+ }
+
+ StyleAnimationValue startForReversingTest = startValue;
+ double reversePortion = 1.0;
+
+ // If the new transition reverses an existing one, we'll need to
+ // handle the timing differently.
+ // FIXME: Move mStartForReversingTest, mReversePortion to CSSTransition,
+ // and set the timing function on transitions as an effect-level
+ // easing (rather than keyframe-level easing). (Bug 1292001)
+ if (haveCurrentTransition &&
+ aElementTransitions->mAnimations[currentIndex]->HasCurrentEffect() &&
+ oldPT &&
+ oldPT->mStartForReversingTest == endValue) {
+ // Compute the appropriate negative transition-delay such that right
+ // now we'd end up at the current position.
+ double valuePortion =
+ oldPT->CurrentValuePortion() * oldPT->mReversePortion +
+ (1.0 - oldPT->mReversePortion);
+ // 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 (delay < 0.0f) {
+ delay *= valuePortion;
+ }
+
+ duration *= valuePortion;
+
+ startForReversingTest = oldPT->ToValue();
+ reversePortion = valuePortion;
+ }
+
+ TimingParams timing;
+ timing.mDuration.emplace(StickyTimeDuration::FromMilliseconds(duration));
+ timing.mDelay = TimeDuration::FromMilliseconds(delay);
+ timing.mIterations = 1.0;
+ timing.mDirection = dom::PlaybackDirection::Normal;
+ timing.mFill = dom::FillMode::Backwards;
+
+ // aElement is non-null here, so we emplace it directly.
+ Maybe<OwningAnimationTarget> target;
+ target.emplace(aElement, aNewStyleContext->GetPseudoType());
+ KeyframeEffectParams effectOptions;
+ RefPtr<ElementPropertyTransition> pt =
+ new ElementPropertyTransition(aElement->OwnerDoc(), target, timing,
+ startForReversingTest, reversePortion,
+ effectOptions);
+
+ pt->SetKeyframes(GetTransitionKeyframes(aNewStyleContext, aProperty,
+ Move(startValue), Move(endValue), tf),
+ aNewStyleContext);
+
+ MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(),
+ "ServoRestyleManager should not use nsTransitionManager "
+ "for transitions");
+
+ RefPtr<CSSTransition> animation =
+ new CSSTransition(mPresContext->Document()->GetScopeObject());
+ animation->SetOwningElement(
+ OwningElementRef(*aElement, aNewStyleContext->GetPseudoType()));
+ animation->SetTimelineNoUpdate(timeline);
+ animation->SetCreationSequence(
+ mPresContext->RestyleManager()->AsGecko()->GetAnimationGeneration());
+ animation->SetEffectFromStyle(pt);
+ animation->PlayFromStyle();
+
+ if (!aElementTransitions) {
+ bool createdCollection = false;
+ aElementTransitions =
+ CSSTransitionCollection::GetOrCreateAnimationCollection(
+ aElement, aNewStyleContext->GetPseudoType(), &createdCollection);
+ if (!aElementTransitions) {
+ MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
+ NS_WARNING("allocating collection failed");
+ return;
+ }
+
+ if (createdCollection) {
+ AddElementCollection(aElementTransitions);
+ }
+ }
+
+ OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
+#ifdef DEBUG
+ for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
+ MOZ_ASSERT(
+ i == currentIndex || animations[i]->TransitionProperty() != aProperty,
+ "duplicate transitions for property");
+ }
+#endif
+ if (haveCurrentTransition) {
+ // 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.
+ if (oldPT &&
+ oldPT->IsCurrent() &&
+ oldPT->IsRunningOnCompositor() &&
+ !oldPT->GetAnimation()->GetStartTime().IsNull() &&
+ timeline == oldPT->GetAnimation()->GetTimeline()) {
+ const AnimationPropertySegment& segment =
+ oldPT->Properties()[0].mSegments[0];
+ pt->mReplacedTransition.emplace(
+ ElementPropertyTransition::ReplacedTransitionProperties({
+ oldPT->GetAnimation()->GetStartTime().Value(),
+ oldPT->GetAnimation()->PlaybackRate(),
+ oldPT->SpecifiedTiming(),
+ segment.mTimingFunction,
+ segment.mFromValue,
+ segment.mToValue
+ })
+ );
+ }
+ animations[currentIndex]->CancelFromStyle();
+ oldPT = nullptr; // Clear pointer so it doesn't dangle
+ animations[currentIndex] = animation;
+ } else {
+ if (!animations.AppendElement(animation)) {
+ NS_WARNING("out of memory");
+ return;
+ }
+ }
+
+ EffectSet* effectSet =
+ EffectSet::GetEffectSet(aElement, aNewStyleContext->GetPseudoType());
+ if (effectSet) {
+ effectSet->UpdateAnimationGeneration(mPresContext);
+ }
+
+ *aStartedAny = true;
+ aWhichStarted->AddProperty(aProperty);
+}
+
+static Keyframe&
+AppendKeyframe(double aOffset, nsCSSPropertyID aProperty,
+ StyleAnimationValue&& aValue, nsTArray<Keyframe>& aKeyframes)
+{
+ Keyframe& frame = *aKeyframes.AppendElement();
+ frame.mOffset.emplace(aOffset);
+ PropertyValuePair& pv = *frame.mPropertyValues.AppendElement();
+ pv.mProperty = aProperty;
+ DebugOnly<bool> uncomputeResult =
+ StyleAnimationValue::UncomputeValue(aProperty, Move(aValue), pv.mValue);
+ MOZ_ASSERT(uncomputeResult,
+ "Unable to get specified value from computed value");
+ return frame;
+}
+
+nsTArray<Keyframe>
+nsTransitionManager::GetTransitionKeyframes(
+ nsStyleContext* aStyleContext,
+ nsCSSPropertyID aProperty,
+ StyleAnimationValue&& aStartValue,
+ StyleAnimationValue&& aEndValue,
+ const nsTimingFunction& aTimingFunction)
+{
+ nsTArray<Keyframe> keyframes(2);
+
+ Keyframe& fromFrame = AppendKeyframe(0.0, aProperty, Move(aStartValue),
+ keyframes);
+ if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
+ fromFrame.mTimingFunction.emplace();
+ fromFrame.mTimingFunction->Init(aTimingFunction);
+ }
+
+ AppendKeyframe(1.0, aProperty, Move(aEndValue), keyframes);
+
+ return keyframes;
+}
+
+void
+nsTransitionManager::PruneCompletedTransitions(mozilla::dom::Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ nsStyleContext* aNewStyleContext)
+{
+ CSSTransitionCollection* collection =
+ CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
+ if (!collection) {
+ return;
+ }
+
+ // Remove any finished transitions whose style doesn't match the new
+ // style.
+ // This is similar to some of the work that happens near the end of
+ // nsTransitionManager::StyleContextChanged.
+ // FIXME (bug 1158431): Really, we should also cancel running
+ // transitions whose destination doesn't match as well.
+ OwningCSSTransitionPtrArray& animations = collection->mAnimations;
+ size_t i = animations.Length();
+ MOZ_ASSERT(i != 0, "empty transitions list?");
+ do {
+ --i;
+ CSSTransition* anim = animations[i];
+
+ if (anim->HasCurrentEffect()) {
+ continue;
+ }
+
+ // Since effect is a finished transition, we know it didn't
+ // influence style.
+ StyleAnimationValue currentValue;
+ if (!ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
+ aNewStyleContext, currentValue) ||
+ currentValue != anim->ToValue()) {
+ anim->CancelFromStyle();
+ animations.RemoveElementAt(i);
+ }
+ } while (i != 0);
+
+ if (collection->mAnimations.IsEmpty()) {
+ collection->Destroy();
+ // |collection| is now a dangling pointer!
+ collection = nullptr;
+ }
+}
+
+void
+nsTransitionManager::StopTransitionsForElement(
+ mozilla::dom::Element* aElement,
+ mozilla::CSSPseudoElementType aPseudoType)
+{
+ MOZ_ASSERT(aElement);
+ CSSTransitionCollection* collection =
+ CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
+ if (!collection) {
+ return;
+ }
+
+ nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
+ collection->Destroy();
+}
diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h
new file mode 100644
index 000000000..56ec61572
--- /dev/null
+++ b/layout/style/nsTransitionManager.h
@@ -0,0 +1,447 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Code to start and animate CSS transitions. */
+
+#ifndef nsTransitionManager_h_
+#define nsTransitionManager_h_
+
+#include "mozilla/ComputedTiming.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+#include "AnimationCommon.h"
+#include "nsCSSProps.h"
+
+class nsIGlobalObject;
+class nsStyleContext;
+class nsPresContext;
+class nsCSSPropertyIDSet;
+
+namespace mozilla {
+enum class CSSPseudoElementType : uint8_t;
+struct Keyframe;
+struct StyleTransition;
+} // namespace mozilla
+
+/*****************************************************************************
+ * Per-Element data *
+ *****************************************************************************/
+
+namespace mozilla {
+
+struct ElementPropertyTransition : public dom::KeyframeEffectReadOnly
+{
+ ElementPropertyTransition(nsIDocument* aDocument,
+ Maybe<OwningAnimationTarget>& aTarget,
+ const TimingParams &aTiming,
+ StyleAnimationValue aStartForReversingTest,
+ double aReversePortion,
+ const KeyframeEffectParams& aEffectOptions)
+ : dom::KeyframeEffectReadOnly(aDocument, aTarget, aTiming, aEffectOptions)
+ , mStartForReversingTest(aStartForReversingTest)
+ , mReversePortion(aReversePortion)
+ { }
+
+ ElementPropertyTransition* AsTransition() override { return this; }
+ const ElementPropertyTransition* AsTransition() const override
+ {
+ return this;
+ }
+
+ nsCSSPropertyID TransitionProperty() const {
+ MOZ_ASSERT(mKeyframes.Length() == 2,
+ "Transitions should have exactly two animation keyframes. "
+ "Perhaps we are using an un-initialized transition?");
+ MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
+ "Transitions should have exactly one property in their first "
+ "frame");
+ return mKeyframes[0].mPropertyValues[0].mProperty;
+ }
+
+ StyleAnimationValue ToValue() const {
+ // If we failed to generate properties from the transition frames,
+ // return a null value but also show a warning since we should be
+ // detecting that kind of situation in advance and not generating a
+ // transition in the first place.
+ if (mProperties.Length() < 1 ||
+ mProperties[0].mSegments.Length() < 1) {
+ NS_WARNING("Failed to generate transition property values");
+ return StyleAnimationValue();
+ }
+ return mProperties[0].mSegments[0].mToValue;
+ }
+
+ // This is the start value to be used for a check for whether a
+ // transition is being reversed. Normally the same as
+ // mProperties[0].mSegments[0].mFromValue, except when this transition
+ // started as the reversal of another in-progress transition.
+ // Needed so we can handle two reverses in a row.
+ StyleAnimationValue mStartForReversingTest;
+ // Likewise, the portion (in value space) of the "full" reversed
+ // transition that we're actually covering. For example, if a :hover
+ // effect has a transition that moves the element 10px to the right
+ // (by changing 'left' from 0px to 10px), and the mouse moves in to
+ // the element (starting the transition) but then moves out after the
+ // transition has advanced 4px, the second transition (from 10px/4px
+ // to 0px) will have mReversePortion of 0.4. (If the mouse then moves
+ // in again when the transition is back to 2px, the mReversePortion
+ // for the third transition (from 0px/2px to 10px) will be 0.8.
+ double mReversePortion;
+
+ // Compute the portion of the *value* space that we should be through
+ // at the current time. (The input to the transition timing function
+ // has time units, the output has value units.)
+ double CurrentValuePortion() const;
+
+ // For a new transition interrupting an existing transition on the
+ // compositor, update the start value to match the value of the replaced
+ // transitions at the current time.
+ void UpdateStartValueFromReplacedTransition();
+
+ struct ReplacedTransitionProperties {
+ TimeDuration mStartTime;
+ double mPlaybackRate;
+ TimingParams mTiming;
+ Maybe<ComputedTimingFunction> mTimingFunction;
+ StyleAnimationValue mFromValue, mToValue;
+ };
+ Maybe<ReplacedTransitionProperties> mReplacedTransition;
+};
+
+namespace dom {
+
+class CSSTransition final : public Animation
+{
+public:
+ explicit CSSTransition(nsIGlobalObject* aGlobal)
+ : dom::Animation(aGlobal)
+ , mPreviousTransitionPhase(TransitionPhase::Idle)
+ , mNeedsNewAnimationIndexWhenRun(false)
+ , mTransitionProperty(eCSSProperty_UNKNOWN)
+ {
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ CSSTransition* AsCSSTransition() override { return this; }
+ const CSSTransition* AsCSSTransition() const override { return this; }
+
+ // CSSTransition interface
+ void GetTransitionProperty(nsString& aRetVal) const;
+
+ // Animation interface overrides
+ virtual AnimationPlayState PlayStateFromJS() const override;
+ virtual void PlayFromJS(ErrorResult& aRv) override;
+
+ // A variant of Play() that avoids posting style updates since this method
+ // is expected to be called whilst already updating style.
+ void PlayFromStyle()
+ {
+ ErrorResult rv;
+ PlayNoUpdate(rv, Animation::LimitBehavior::Continue);
+ // play() should not throw when LimitBehavior is Continue
+ MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing transition");
+ }
+
+ void CancelFromStyle() override
+ {
+ // The animation index to use for compositing will be established when
+ // this transition next transitions out of the idle state but we still
+ // update it now so that the sort order of this transition remains
+ // defined until that moment.
+ //
+ // See longer explanation in CSSAnimation::CancelFromStyle.
+ mAnimationIndex = sNextAnimationIndex++;
+ mNeedsNewAnimationIndexWhenRun = true;
+
+ Animation::CancelFromStyle();
+
+ // It is important we do this *after* calling CancelFromStyle().
+ // This is because CancelFromStyle() will end up posting a restyle and
+ // that restyle should target the *transitions* level of the cascade.
+ // However, once we clear the owning element, CascadeLevel() will begin
+ // returning CascadeLevel::Animations.
+ mOwningElement = OwningElementRef();
+ }
+
+ void SetEffectFromStyle(AnimationEffectReadOnly* aEffect);
+
+ void Tick() override;
+
+ nsCSSPropertyID TransitionProperty() const;
+ StyleAnimationValue ToValue() const;
+
+ bool HasLowerCompositeOrderThan(const CSSTransition& aOther) const;
+ EffectCompositor::CascadeLevel CascadeLevel() const override
+ {
+ return IsTiedToMarkup() ?
+ EffectCompositor::CascadeLevel::Transitions :
+ EffectCompositor::CascadeLevel::Animations;
+ }
+
+ void SetCreationSequence(uint64_t aIndex)
+ {
+ MOZ_ASSERT(IsTiedToMarkup());
+ mAnimationIndex = aIndex;
+ }
+
+ // Sets the owning element which is used for determining the composite
+ // oder of CSSTransition objects generated from CSS markup.
+ //
+ // @see mOwningElement
+ void SetOwningElement(const OwningElementRef& aElement)
+ {
+ mOwningElement = aElement;
+ }
+ // True for transitions that are generated from CSS markup and continue to
+ // reflect changes to that markup.
+ bool IsTiedToMarkup() const { return mOwningElement.IsSet(); }
+
+ // Return the animation current time based on a given TimeStamp, a given
+ // start time and a given playbackRate on a given timeline. This is useful
+ // when we estimate the current animated value running on the compositor
+ // because the animation on the compositor may be running ahead while
+ // main-thread is busy.
+ static Nullable<TimeDuration> GetCurrentTimeAt(
+ const DocumentTimeline& aTimeline,
+ const TimeStamp& aBaseTime,
+ const TimeDuration& aStartTime,
+ double aPlaybackRate);
+
+protected:
+ virtual ~CSSTransition()
+ {
+ MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared "
+ "before a CSS transition is destroyed");
+ }
+
+ // Animation overrides
+ void UpdateTiming(SeekFlag aSeekFlag,
+ SyncNotifyFlag aSyncNotifyFlag) override;
+
+ void QueueEvents();
+
+ // The (pseudo-)element whose computed transition-property refers to this
+ // transition (if any).
+ //
+ // This is used for determining the relative composite order of transitions
+ // generated from CSS markup.
+ //
+ // Typically this will be the same as the target element of the keyframe
+ // effect associated with this transition. However, it can differ in the
+ // following circumstances:
+ //
+ // a) If script removes or replaces the effect of this transition,
+ // b) If this transition is cancelled (e.g. by updating the
+ // transition-property or removing the owning element from the document),
+ // c) If this object is generated from script using the CSSTransition
+ // constructor.
+ //
+ // For (b) and (c) the owning element will return !IsSet().
+ OwningElementRef mOwningElement;
+
+ // The 'transition phase' used to determine which transition events need
+ // to be queued on this tick.
+ // See: https://drafts.csswg.org/css-transitions-2/#transition-phase
+ enum class TransitionPhase {
+ Idle = static_cast<int>(ComputedTiming::AnimationPhase::Null),
+ Before = static_cast<int>(ComputedTiming::AnimationPhase::Before),
+ Active = static_cast<int>(ComputedTiming::AnimationPhase::Active),
+ After = static_cast<int>(ComputedTiming::AnimationPhase::After),
+ Pending
+ };
+ TransitionPhase mPreviousTransitionPhase;
+
+ // When true, indicates that when this transition next leaves the idle state,
+ // its animation index should be updated.
+ bool mNeedsNewAnimationIndexWhenRun;
+
+ // Store the transition property and to-value here since we need that
+ // information in order to determine if there is an existing transition
+ // for a given style change. We can't store that information on the
+ // ElementPropertyTransition (effect) however since it can be replaced
+ // using the Web Animations API.
+ nsCSSPropertyID mTransitionProperty;
+ StyleAnimationValue mTransitionToValue;
+};
+
+} // namespace dom
+
+template <>
+struct AnimationTypeTraits<dom::CSSTransition>
+{
+ static nsIAtom* ElementPropertyAtom()
+ {
+ return nsGkAtoms::transitionsProperty;
+ }
+ static nsIAtom* BeforePropertyAtom()
+ {
+ return nsGkAtoms::transitionsOfBeforeProperty;
+ }
+ static nsIAtom* AfterPropertyAtom()
+ {
+ return nsGkAtoms::transitionsOfAfterProperty;
+ }
+};
+
+struct TransitionEventInfo {
+ RefPtr<dom::Element> mElement;
+ RefPtr<dom::Animation> mAnimation;
+ InternalTransitionEvent mEvent;
+ TimeStamp mTimeStamp;
+
+ TransitionEventInfo(dom::Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ EventMessage aMessage,
+ nsCSSPropertyID aProperty,
+ StickyTimeDuration aDuration,
+ const TimeStamp& aTimeStamp,
+ dom::Animation* aAnimation)
+ : mElement(aElement)
+ , mAnimation(aAnimation)
+ , mEvent(true, aMessage)
+ , mTimeStamp(aTimeStamp)
+ {
+ // XXX Looks like nobody initialize WidgetEvent::time
+ mEvent.mPropertyName =
+ NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
+ mEvent.mElapsedTime = aDuration.ToSeconds();
+ mEvent.mPseudoElement =
+ AnimationCollection<dom::CSSTransition>::PseudoTypeAsString(aPseudoType);
+ }
+
+ // InternalTransitionEvent doesn't support copy-construction, so we need
+ // to ourselves in order to work with nsTArray
+ TransitionEventInfo(const TransitionEventInfo& aOther)
+ : mElement(aOther.mElement)
+ , mAnimation(aOther.mAnimation)
+ , mEvent(aOther.mEvent)
+ , mTimeStamp(aOther.mTimeStamp)
+ {
+ mEvent.AssignTransitionEventData(aOther.mEvent, false);
+ }
+};
+
+} // namespace mozilla
+
+class nsTransitionManager final
+ : public mozilla::CommonAnimationManager<mozilla::dom::CSSTransition>
+{
+public:
+ explicit nsTransitionManager(nsPresContext *aPresContext)
+ : mozilla::CommonAnimationManager<mozilla::dom::CSSTransition>(aPresContext)
+ , mInAnimationOnlyStyleUpdate(false)
+ {
+ }
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsTransitionManager)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsTransitionManager)
+
+ typedef mozilla::AnimationCollection<mozilla::dom::CSSTransition>
+ CSSTransitionCollection;
+
+ /**
+ * StyleContextChanged
+ *
+ * To be called from RestyleManager::TryInitiatingTransition when the
+ * style of an element has changed, to initiate transitions from
+ * that style change. For style contexts with :before and :after
+ * pseudos, aElement is expected to be the generated before/after
+ * element.
+ *
+ * It may modify the new style context (by replacing
+ * *aNewStyleContext) to cover up some of the changes for the duration
+ * of the restyling of descendants. If it does, this function will
+ * take care of causing the necessary restyle afterwards.
+ */
+ void StyleContextChanged(mozilla::dom::Element *aElement,
+ nsStyleContext *aOldStyleContext,
+ RefPtr<nsStyleContext>* aNewStyleContext /* inout */);
+
+ /**
+ * When we're resolving style for an element that previously didn't have
+ * style, we might have some old finished transitions for it, if,
+ * say, it was display:none for a while, but previously displayed.
+ *
+ * This method removes any finished transitions that don't match the
+ * new style.
+ */
+ void PruneCompletedTransitions(mozilla::dom::Element* aElement,
+ mozilla::CSSPseudoElementType aPseudoType,
+ nsStyleContext* aNewStyleContext);
+
+ void SetInAnimationOnlyStyleUpdate(bool aInAnimationOnlyUpdate) {
+ mInAnimationOnlyStyleUpdate = aInAnimationOnlyUpdate;
+ }
+
+ bool InAnimationOnlyStyleUpdate() const {
+ return mInAnimationOnlyStyleUpdate;
+ }
+
+ void QueueEvent(mozilla::TransitionEventInfo&& aEventInfo)
+ {
+ mEventDispatcher.QueueEvent(
+ mozilla::Forward<mozilla::TransitionEventInfo>(aEventInfo));
+ }
+
+ void DispatchEvents()
+ {
+ RefPtr<nsTransitionManager> kungFuDeathGrip(this);
+ mEventDispatcher.DispatchEvents(mPresContext);
+ }
+ void SortEvents() { mEventDispatcher.SortEvents(); }
+ void ClearEventQueue() { mEventDispatcher.ClearEventQueue(); }
+
+ // Stop transitions on the element. This method takes the real element
+ // rather than the element for the generated content for transitions on
+ // ::before and ::after.
+ void StopTransitionsForElement(mozilla::dom::Element* aElement,
+ mozilla::CSSPseudoElementType aPseudoType);
+
+protected:
+ virtual ~nsTransitionManager() {}
+
+ typedef nsTArray<RefPtr<mozilla::dom::CSSTransition>>
+ OwningCSSTransitionPtrArray;
+
+ // Update the transitions. It'd start new, replace, or stop current
+ // transitions if need. aDisp and aElement shouldn't be nullptr.
+ // aElementTransitions is the collection of current transitions, and it
+ // could be a nullptr if we don't have any transitions.
+ bool
+ UpdateTransitions(const nsStyleDisplay* aDisp,
+ mozilla::dom::Element* aElement,
+ CSSTransitionCollection*& aElementTransitions,
+ nsStyleContext* aOldStyleContext,
+ nsStyleContext* aNewStyleContext);
+
+ void
+ ConsiderInitiatingTransition(nsCSSPropertyID aProperty,
+ const mozilla::StyleTransition& aTransition,
+ mozilla::dom::Element* aElement,
+ CSSTransitionCollection*& aElementTransitions,
+ nsStyleContext* aOldStyleContext,
+ nsStyleContext* aNewStyleContext,
+ bool* aStartedAny,
+ nsCSSPropertyIDSet* aWhichStarted);
+
+ nsTArray<mozilla::Keyframe> GetTransitionKeyframes(
+ nsStyleContext* aStyleContext,
+ nsCSSPropertyID aProperty,
+ mozilla::StyleAnimationValue&& aStartValue,
+ mozilla::StyleAnimationValue&& aEndValue,
+ const nsTimingFunction& aTimingFunction);
+
+ bool mInAnimationOnlyStyleUpdate;
+
+ mozilla::DelayedEventDispatcher<mozilla::TransitionEventInfo>
+ mEventDispatcher;
+};
+
+#endif /* !defined(nsTransitionManager_h_) */
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 000000000..8b27c3c20
--- /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 000000000..828315b9a
--- /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 000000000..685670e01
--- /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 000000000..bad59253f
--- /dev/null
+++ b/layout/style/res/accessiblecaret-normal@2x.png
Binary files differ
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 000000000..840b21868
--- /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 000000000..b96f674c2
--- /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 000000000..d69ca4fe4
--- /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 000000000..f76cb333c
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-left@2x.png
Binary files differ
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 000000000..02dde485c
--- /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 000000000..6a95a9664
--- /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 000000000..ec6a50f0a
--- /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 000000000..c24e66d0a
--- /dev/null
+++ b/layout/style/res/accessiblecaret-tilt-right@2x.png
Binary files differ
diff --git a/layout/style/res/arrow-left.gif b/layout/style/res/arrow-left.gif
new file mode 100644
index 000000000..a8c200447
--- /dev/null
+++ b/layout/style/res/arrow-left.gif
Binary files differ
diff --git a/layout/style/res/arrow-right.gif b/layout/style/res/arrow-right.gif
new file mode 100644
index 000000000..044fe8152
--- /dev/null
+++ b/layout/style/res/arrow-right.gif
Binary files differ
diff --git a/layout/style/res/arrow.gif b/layout/style/res/arrow.gif
new file mode 100644
index 000000000..afb464122
--- /dev/null
+++ b/layout/style/res/arrow.gif
Binary files differ
diff --git a/layout/style/res/arrowd-left.gif b/layout/style/res/arrowd-left.gif
new file mode 100644
index 000000000..227def79b
--- /dev/null
+++ b/layout/style/res/arrowd-left.gif
Binary files differ
diff --git a/layout/style/res/arrowd-right.gif b/layout/style/res/arrowd-right.gif
new file mode 100644
index 000000000..999cf3983
--- /dev/null
+++ b/layout/style/res/arrowd-right.gif
Binary files differ
diff --git a/layout/style/res/arrowd.gif b/layout/style/res/arrowd.gif
new file mode 100644
index 000000000..02173c700
--- /dev/null
+++ b/layout/style/res/arrowd.gif
Binary files differ
diff --git a/layout/style/res/counterstyles.css b/layout/style/res/counterstyles.css
new file mode 100644
index 000000000..284801ef6
--- /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: alphabetic;
+ symbols: \7532 \4E59 \4E19 \4E01 \620A \5DF1 \5E9A \8F9B \58EC \7678;
+ fallback: cjk-decimal;
+ suffix: '\3001';
+}
+
+@counter-style cjk-earthly-branch {
+ system: alphabetic;
+ 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/forms.css b/layout/style/res/forms.css
new file mode 100644
index 000000000..f045540b1
--- /dev/null
+++ b/layout/style/res/forms.css
@@ -0,0 +1,1137 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 */
+@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);
+
+*|*::-moz-fieldset-content {
+ display: block; /* nsRuleNode::ComputeDisplayData overrules this in some cases */
+ unicode-bidi: inherit;
+ text-overflow: inherit;
+ overflow: inherit;
+ overflow-clip-box: inherit;
+ padding: inherit;
+ block-size: 100%; /* Need this so percentage block-sizes of kids work right */
+ /* Please keep the Multicol/Flex/Grid/Align sections below in sync with
+ ::-moz-scrolled-content in ua.css and ::-moz-button-content below. */
+ /* Multicol container */
+ -moz-column-count: inherit;
+ -moz-column-width: inherit;
+ -moz-column-gap: inherit;
+ -moz-column-rule: inherit;
+ -moz-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 */
+
+fieldset > legend {
+ padding-inline-start: 2px;
+ padding-inline-end: 2px;
+ inline-size: -moz-fit-content;
+}
+
+legend {
+ display: block;
+}
+
+fieldset {
+ display: block;
+ margin-inline-start: 2px;
+ margin-inline-end: 2px;
+ padding-block-start: 0.35em;
+ padding-block-end: 0.75em;
+ padding-inline-start: 0.625em;
+ padding-inline-end: 0.625em;
+ border: 2px groove ThreeDLightShadow;
+}
+
+label {
+ cursor: default;
+}
+
+/* default inputs, text inputs, and selects */
+
+/* Note: Values in nsNativeTheme IsWidgetStyled function
+ need to match textfield background/border values here */
+
+input {
+ -moz-appearance: textfield;
+ /* The sum of border and padding on block-start and block-end
+ must be the same here, for buttons, and for <select> (including its
+ internal padding magic) */
+ padding: 1px;
+ border: 2px inset ThreeDLightShadow;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+ font: -moz-field;
+ text-rendering: optimizeLegibility;
+ line-height: normal;
+ text-align: start;
+ text-transform: none;
+ word-spacing: normal;
+ letter-spacing: normal;
+ cursor: text;
+ -moz-binding: url("chrome://global/content/platformHTMLBindings.xml#inputFields");
+ text-indent: 0;
+ -moz-user-select: text;
+ text-shadow: none;
+ overflow-clip-box: content-box;
+}
+
+input > .anonymous-div,
+input::placeholder {
+ word-wrap: normal !important;
+ /* Make the line-height equal to the available height */
+ line-height: -moz-block-height;
+}
+
+@-moz-document url-prefix(chrome://) {
+ input.uri-element-right-align:-moz-locale-dir(rtl) {
+ direction: ltr !important;
+ text-align: right !important;
+ }
+
+ /* Make sure that the location bar's alignment in RTL mode changes according
+ to the input box direction if the user switches the text direction using
+ cmd_switchTextDirection (which applies a dir attribute to the <input>). */
+ input.uri-element-right-align[dir=ltr]:-moz-locale-dir(rtl) {
+ text-align: left !important;
+ }
+}
+
+textarea {
+ margin-block-start: 1px;
+ margin-block-end: 1px;
+ border: 2px inset ThreeDLightShadow;
+ /* The 1px inline padding is for parity with Win/IE */
+ padding-inline-start: 1px;
+ padding-inline-end: 1px;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+ font: medium -moz-fixed;
+ text-rendering: optimizeLegibility;
+ text-align: start;
+ text-transform: none;
+ word-spacing: normal;
+ letter-spacing: normal;
+ vertical-align: text-bottom;
+ cursor: text;
+ resize: both;
+ -moz-binding: url("chrome://global/content/platformHTMLBindings.xml#textAreas");
+ -moz-appearance: textfield-multiline;
+ text-indent: 0;
+ -moz-user-select: text;
+ text-shadow: none;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ overflow-clip-box: content-box;
+}
+
+textarea > scrollbar {
+ cursor: default;
+}
+
+textarea > .anonymous-div,
+input > .anonymous-div,
+input::placeholder,
+textarea::placeholder {
+ overflow: auto;
+ border: 0px !important;
+ padding: inherit !important;
+ margin: 0px;
+ text-decoration: inherit;
+ text-decoration-color: inherit;
+ text-decoration-style: inherit;
+ display: inline-block;
+ ime-mode: inherit;
+ resize: inherit;
+ -moz-control-character-visibility: visible;
+ overflow-clip-box: inherit;
+}
+
+input > .anonymous-div,
+input::placeholder {
+ white-space: pre;
+}
+
+input > .anonymous-div.wrap {
+ white-space: pre-wrap;
+}
+textarea > .anonymous-div.inherit-overflow,
+input > .anonymous-div.inherit-overflow {
+ overflow: inherit;
+}
+
+input::placeholder,
+textarea::placeholder {
+ /*
+ * Changing display to inline can leads to broken behaviour and will assert.
+ */
+ display: inline-block !important;
+
+ /*
+ * Changing resize would display a broken behaviour and will assert.
+ */
+ resize: none !important;
+
+ overflow: hidden !important;
+
+ /*
+ * The placeholder should be ignored by pointer otherwise, we might have some
+ * unexpected behavior like the resize handle not being selectable.
+ */
+ pointer-events: none !important;
+
+ opacity: 0.54;
+}
+
+textarea::placeholder {
+ white-space: pre-wrap !important;
+}
+
+input:-moz-read-write,
+textarea:-moz-read-write {
+ -moz-user-modify: read-write !important;
+}
+
+select {
+ margin: 0;
+ border-color: ThreeDLightShadow;
+ background-color: -moz-Combobox;
+ color: -moz-ComboboxText;
+ font: -moz-list;
+ /*
+ * 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.
+ */
+ line-height: normal !important;
+ white-space: nowrap !important;
+ word-wrap: normal !important;
+ text-align: start;
+ cursor: default;
+ box-sizing: border-box;
+ -moz-user-select: none;
+ -moz-appearance: menulist;
+ border-width: 2px;
+ border-style: inset;
+ text-indent: 0;
+ overflow: -moz-hidden-unscrollable;
+ text-shadow: none;
+ /* No text-decoration reaching inside, by default */
+ display: inline-block;
+ page-break-inside: avoid;
+ overflow-clip-box: padding-box !important; /* bug 992447 */
+}
+
+/* Need the "select[size][multiple]" selector to override the settings on
+ 'select[size="1"]', eg if one has <select size="1" multiple> */
+
+select[size],
+select[multiple],
+select[size][multiple] {
+ /* Different alignment and padding for listbox vs combobox */
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+ vertical-align: text-bottom;
+ padding-block-start: 1px;
+ padding-block-end: 1px;
+ padding-inline-start: 0;
+ padding-inline-end: 0;
+ -moz-appearance: listbox;
+}
+
+select[size="0"],
+select[size="1"] {
+ /* Except this is not a listbox */
+ background-color: -moz-Combobox;
+ color: -moz-ComboboxText;
+ vertical-align: baseline;
+ padding: 0;
+ -moz-appearance: menulist;
+}
+
+select > button {
+ inline-size: 12px;
+ white-space: nowrap;
+ position: static !important;
+ background-image: url("arrow.gif") !important;
+ background-repeat: no-repeat !important;
+ background-position: center !important;
+ -moz-appearance: menulist-button;
+
+ /* Make sure to size correctly if the combobox has a non-auto height. */
+ block-size: 100% ! important;
+ box-sizing: border-box ! important;
+
+ /*
+ 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 !important;
+}
+
+select > button:active {
+ background-image: url("arrowd.gif") !important;
+}
+
+select > button[orientation="left"] {
+ background-image: url("arrow-left.gif") !important;
+}
+
+select > button[orientation="right"] {
+ background-image: url("arrow-right.gif") !important;
+}
+
+select > button[orientation="left"]:active {
+ background-image: url("arrowd-left.gif") !important;
+}
+
+select > button[orientation="right"]:active {
+ background-image: url("arrowd-right.gif") !important;
+}
+
+select:empty {
+ inline-size: 2.5em;
+}
+
+*|*::-moz-display-comboboxcontrol-frame {
+ overflow: -moz-hidden-unscrollable;
+ /* This block-start/end padding plus the combobox block-start/end border need to
+ add up to the block-start/end borderpadding of text inputs and buttons */
+ padding-block-start: 1px;
+ padding-block-end: 1px;
+ padding-inline-start: 4px;
+ padding-inline-end: 0;
+ color: inherit;
+ white-space: nowrap;
+ text-align: inherit;
+ -moz-user-select: none;
+ /* Make sure to size correctly if the combobox has a non-auto block-size. */
+ block-size: 100% ! important;
+ box-sizing: border-box ! important;
+ line-height: -moz-block-height;
+}
+
+option {
+ display: block;
+ float: none !important;
+ position: static !important;
+ min-block-size: 1em;
+ line-height: normal !important;
+ -moz-user-select: none;
+ text-indent: 0;
+ white-space: nowrap !important;
+ word-wrap: normal !important;
+ text-align: match-parent;
+}
+
+select > option {
+ padding-block-start : 0;
+ padding-block-end: 0;
+ padding-inline-start: 3px;
+ padding-inline-end: 5px;
+}
+
+option:checked {
+ background-color: -moz-html-cellhighlight !important;
+ color: -moz-html-cellhighlighttext !important;
+}
+
+select:focus > option:checked,
+select:focus > optgroup > option:checked {
+ background-color: Highlight ! important;
+ color: HighlightText ! important;
+}
+
+optgroup {
+ display: block;
+ float: none !important;
+ position: static !important;
+ font: -moz-list;
+ line-height: normal !important;
+ font-style: italic;
+ font-weight: bold;
+ font-size: inherit;
+ -moz-user-select: none;
+ text-indent: 0;
+ 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: attr(label);
+}
+
+*|*::-moz-dropdown-list {
+ z-index: 2147483647;
+ background-color: inherit;
+ -moz-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:disabled /* Need the pseudo-class twice to have the specificity
+ be at least the same as select[size][multiple] above */
+{
+ -moz-user-input: disabled;
+ color: GrayText;
+ background-color: ThreeDLightShadow;
+ cursor: inherit;
+}
+
+input:disabled,
+textarea:disabled {
+ cursor: default;
+}
+
+option:disabled,
+optgroup:disabled {
+ background-color: transparent;
+}
+
+/* hidden inputs */
+input[type="hidden"] {
+ -moz-appearance: none;
+ display: none !important;
+ padding: 0;
+ border: 0;
+ cursor: auto;
+ -moz-user-focus: ignore;
+ -moz-binding: none;
+}
+
+/* image buttons */
+input[type="image"] {
+ -moz-appearance: none;
+ padding: 0;
+ border: none;
+ background-color: transparent;
+ font-family: sans-serif;
+ font-size: small;
+ cursor: pointer;
+ -moz-binding: none;
+}
+
+input[type="image"]:disabled {
+ cursor: inherit;
+}
+
+input[type="image"]:-moz-focusring {
+ /* Don't specify the outline-color, we should always use initial value. */
+ outline: 1px dotted;
+}
+
+/* file selector */
+input[type="file"] {
+ display: inline-block;
+ white-space: nowrap;
+ overflow: hidden;
+ overflow-clip-box: padding-box;
+ color: inherit;
+
+ /* Revert rules which apply on all inputs. */
+ -moz-appearance: none;
+ -moz-binding: none;
+ cursor: default;
+
+ border: none;
+ background-color: transparent;
+ padding: 0;
+}
+
+input[type="file"] > xul|label {
+ min-inline-size: 12em;
+ padding-inline-start: 5px;
+ text-align: match-parent;
+
+ color: inherit;
+ font-size: inherit;
+ letter-spacing: inherit;
+
+ /*
+ * Force the text to have LTR directionality. Otherwise filenames containing
+ * RTL characters will be reordered with chaotic results.
+ */
+ direction: ltr !important;
+}
+
+/* button part of file selector */
+input[type="file"] > button[type="button"] {
+ block-size: inherit;
+ font-size: inherit;
+ letter-spacing: inherit;
+ cursor: inherit;
+}
+
+/* colored part of the color selector button */
+input[type="color"]:-moz-system-metric(color-picker-available)::-moz-color-swatch {
+ width: 100%;
+ height: 100%;
+ min-width: 3px;
+ min-height: 3px;
+ margin-inline-start: auto;
+ margin-inline-end: auto;
+ box-sizing: border-box;
+ border: 1px solid grey;
+ display: block;
+}
+
+/* Try to make RTL <input type='file'> look nicer. */
+/* TODO: find a better solution than forcing direction: ltr on all file
+ input labels and remove this override -- bug 1161482 */
+input[type="file"]:dir(rtl) > xul|label {
+ padding-inline-start: 0px;
+ padding-inline-end: 5px;
+}
+
+/* radio buttons */
+input[type="radio"] {
+ -moz-appearance: radio;
+ margin-block-start: 3px;
+ margin-block-end: 0px;
+ margin-inline-start: 5px;
+ margin-inline-end: 3px;
+ border-radius: 100% !important;
+}
+
+/* check boxes */
+input[type="checkbox"] {
+ -moz-appearance: checkbox;
+ margin-block-start: 3px;
+ margin-block-end: 3px;
+ margin-inline-start: 4px;
+ margin-inline-end: 3px;
+ border-radius: 0 !important;
+}
+
+/* common features of radio buttons and check boxes */
+
+/* NOTE: The width, height, border-width, and padding here must all
+ add up the way nsFormControlFrame::GetIntrinsic(Width|Height)
+ expects them to, or they will not come out with total width equal
+ to total height on sites that set their 'width' or 'height' to 'auto'.
+ (Should we maybe set !important on width and height, then?) */
+input[type="radio"],
+input[type="checkbox"] {
+ box-sizing: border-box;
+ inline-size: 13px;
+ block-size: 13px;
+ cursor: default;
+ padding: 0 !important;
+ -moz-binding: none;
+ /* same colors as |input| rule, but |!important| this time. */
+ background-color: -moz-Field ! important;
+ color: -moz-FieldText ! important;
+ border: 2px inset ThreeDLightShadow ! important;
+}
+
+input[type="radio"]:disabled,
+input[type="radio"]:disabled:active,
+input[type="radio"]:disabled:hover,
+input[type="radio"]:disabled:hover:active,
+input[type="checkbox"]:disabled,
+input[type="checkbox"]:disabled:active,
+input[type="checkbox"]:disabled:hover,
+input[type="checkbox"]:disabled:hover:active {
+ padding: 1px;
+ border: 1px inset ThreeDShadow ! important;
+ /* same as above, but !important */
+ color: GrayText ! important;
+ background-color: ThreeDFace ! important;
+ cursor: inherit;
+}
+
+% On Mac, the native theme takes care of this.
+% See nsNativeThemeCocoa::ThemeDrawsFocusForWidget.
+%ifndef XP_MACOSX
+input[type="checkbox"]:-moz-focusring,
+input[type="radio"]:-moz-focusring {
+ /* Don't specify the outline-color, we should always use initial value. */
+ outline: 1px dotted;
+}
+%endif
+
+input[type="checkbox"]:hover:active,
+input[type="radio"]:hover:active {
+ background-color: ThreeDFace ! important;
+ border-style: inset !important;
+}
+
+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,
+input[type="color"]:-moz-system-metric(color-picker-available),
+input[type="reset"],
+input[type="button"],
+input[type="submit"] {
+ -moz-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>. For
+ buttons, make sure to include the -moz-focus-inner border/padding. */
+ padding-block-start: 0px;
+ padding-inline-end: 6px;
+ padding-block-end: 0px;
+ padding-inline-start: 6px;
+ border: 2px outset ThreeDLightShadow;
+ background-color: ButtonFace;
+ cursor: default;
+ box-sizing: border-box;
+ -moz-user-select: none;
+ -moz-binding: none;
+}
+
+/* Text-related properties for buttons: these ones are not shared with
+ input[type="color"] */
+button,
+input[type="reset"],
+input[type="button"],
+input[type="submit"] {
+ color: ButtonText;
+ font: -moz-button;
+ line-height: normal;
+ white-space: pre;
+ text-align: center;
+ text-shadow: none;
+ overflow-clip-box: padding-box;
+}
+
+input[type="color"]:-moz-system-metric(color-picker-available) {
+ inline-size: 64px;
+ block-size: 23px;
+}
+
+button {
+ /* Buttons should lay out like "normal" html, mostly */
+ white-space: inherit;
+ text-indent: 0;
+ /* But no text-decoration reaching inside, by default */
+ display: inline-block;
+}
+
+*|*::-moz-button-content {
+ display: block;
+ /* Please keep the Multicol/Flex/Grid/Align sections below in sync with
+ ::-moz-scrolled-content in ua.css and ::-moz-fieldset-content above. */
+ /* Multicol container */
+ -moz-column-count: inherit;
+ -moz-column-width: inherit;
+ -moz-column-gap: inherit;
+ -moz-column-rule: inherit;
+ -moz-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;
+}
+
+button:hover,
+input[type="color"]:-moz-system-metric(color-picker-available):hover,
+input[type="reset"]:hover,
+input[type="button"]:hover,
+input[type="submit"]:hover {
+ background-color: -moz-buttonhoverface;
+}
+
+button:hover,
+input[type="reset"]:hover,
+input[type="button"]:hover,
+input[type="submit"]:hover {
+ color: -moz-buttonhovertext;
+}
+
+button:active:hover,
+input[type="color"]:-moz-system-metric(color-picker-available):active:hover,
+input[type="reset"]:active:hover,
+input[type="button"]:active:hover,
+input[type="submit"]:active:hover {
+%ifndef XP_MACOSX
+ padding-block-start: 0px;
+ padding-inline-end: 5px;
+ padding-block-end: 0px;
+ padding-inline-start: 7px;
+%endif
+ border-style: inset;
+ background-color: ButtonFace;
+}
+
+button:active:hover,
+input[type="reset"]:active:hover,
+input[type="button"]:active:hover,
+input[type="submit"]:active:hover {
+ color: ButtonText;
+}
+
+button::-moz-focus-inner,
+input[type="color"]:-moz-system-metric(color-picker-available)::-moz-focus-inner,
+input[type="reset"]::-moz-focus-inner,
+input[type="button"]::-moz-focus-inner,
+input[type="submit"]::-moz-focus-inner,
+input[type="file"] > button[type="button"]::-moz-focus-inner {
+ padding-block-start: 0px;
+ padding-inline-end: 2px;
+ padding-block-end: 0px;
+ padding-inline-start: 2px;
+ border: 1px dotted transparent;
+}
+
+button:-moz-focusring::-moz-focus-inner,
+input[type="color"]:-moz-system-metric(color-picker-available):-moz-focusring::-moz-focus-inner,
+input[type="reset"]:-moz-focusring::-moz-focus-inner,
+input[type="button"]:-moz-focusring::-moz-focus-inner,
+input[type="submit"]:-moz-focusring::-moz-focus-inner,
+input[type="file"] > button[type="button"]:-moz-focusring::-moz-focus-inner {
+ border-color: ButtonText;
+}
+
+button:disabled:active, button:disabled,
+input[type="color"]:-moz-system-metric(color-picker-available):disabled:active,
+input[type="color"]:-moz-system-metric(color-picker-available):disabled,
+input[type="reset"]:disabled:active,
+input[type="reset"]:disabled,
+input[type="button"]:disabled:active,
+input[type="button"]:disabled,
+select:disabled > button,
+select:disabled > button,
+input[type="submit"]:disabled:active,
+input[type="submit"]:disabled {
+ /* The sum of border and padding on block-start and block-end
+ must be the same here and for text inputs */
+ padding-block-start: 0px;
+ padding-inline-end: 6px;
+ padding-block-end: 0px;
+ padding-inline-start: 6px;
+ border: 2px outset ThreeDLightShadow;
+ cursor: inherit;
+}
+
+button:disabled:active, button:disabled,
+input[type="reset"]:disabled:active,
+input[type="reset"]:disabled,
+input[type="button"]:disabled:active,
+input[type="button"]:disabled,
+select:disabled > button,
+select:disabled > button,
+input[type="submit"]:disabled:active,
+input[type="submit"]:disabled {
+ color: GrayText;
+}
+
+ /*
+ * 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 XULScroll frame with class 'anonymous-div' which is a
+ * 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.
+ */
+textarea > .anonymous-div,
+input > .anonymous-div,
+input::placeholder,
+textarea::placeholder,
+*|*::-moz-button-content,
+*|*::-moz-display-comboboxcontrol-frame,
+optgroup:before {
+ unicode-bidi: inherit;
+ text-overflow: inherit;
+}
+
+/**
+ * Set default style for invalid elements.
+ */
+:not(output):-moz-ui-invalid {
+ box-shadow: 0 0 1.5px 1px red;
+}
+
+:not(output):-moz-ui-invalid:-moz-focusring {
+ box-shadow: 0 0 2px 2px rgba(255,0,0,0.4);
+}
+
+output:-moz-ui-invalid {
+ color: red;
+}
+
+@media print {
+ input, textarea, select, button {
+ -moz-user-input: none !important;
+ }
+
+ input[type="file"] { height: 2em; }
+}
+
+progress {
+ -moz-appearance: progressbar;
+ display: inline-block;
+ vertical-align: -0.2em;
+
+ /* Default style in case of there is -moz-appearance: none; */
+ border: 2px solid;
+ /* #e6e6e6 is a light gray. */
+ -moz-border-top-colors: ThreeDShadow #e6e6e6;
+ -moz-border-right-colors: ThreeDHighlight #e6e6e6;
+ -moz-border-bottom-colors: ThreeDHighlight #e6e6e6;
+ -moz-border-left-colors: ThreeDShadow #e6e6e6;
+ background-color: #e6e6e6;
+}
+
+::-moz-progress-bar {
+ /* 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;
+
+ -moz-appearance: progresschunk;
+ height: 100%;
+ width: 100%;
+
+ /* Default style in case of there is -moz-appearance: none; */
+ background-color: #0064b4; /* blue */
+}
+
+meter {
+ -moz-appearance: meterbar;
+ display: inline-block;
+ vertical-align: -0.2em;
+
+ background: linear-gradient(#e6e6e6, #e6e6e6, #eeeeee 20%, #cccccc 45%, #cccccc 55%);
+}
+
+::-moz-meter-bar {
+ /* Block styles that would change the type of frame we construct. */
+ display: inline-block ! important;
+ float: none ! important;
+ position: static ! important;
+ overflow: visible ! important;
+
+ -moz-appearance: meterchunk;
+ height: 100%;
+ width: 100%;
+}
+
+:-moz-meter-optimum::-moz-meter-bar {
+ /* green. */
+ background: linear-gradient(#ad7, #ad7, #cea 20%, #7a3 45%, #7a3 55%);
+}
+:-moz-meter-sub-optimum::-moz-meter-bar {
+ /* orange. */
+ background: linear-gradient(#fe7, #fe7, #ffc 20%, #db3 45%, #db3 55%);
+}
+:-moz-meter-sub-sub-optimum::-moz-meter-bar {
+ /* red. */
+ background: linear-gradient(#f77, #f77, #fcc 20%, #d44 45%, #d44 55%);
+}
+
+input[type=range] {
+ -moz-appearance: range;
+ display: inline-block;
+ inline-size: 12em;
+ block-size: 1.3em;
+ margin-inline-start: 0.7em;
+ margin-inline-end: 0.7em;
+ margin-block-start: 0;
+ margin-block-end: 0;
+ /* Override some rules that apply on all input types: */
+ cursor: default;
+ background: none;
+ border: none;
+ -moz-binding: none; /* we don't want any of platformHTMLBindings.xml#inputFields */
+ /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
+ -moz-user-select: none ! important;
+}
+
+input[type=range][orient=block] {
+ inline-size: 1.3em;
+ block-size: 12em;
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+ margin-block-start: 0.7em;
+ margin-block-end: 0.7em;
+}
+
+input[type=range][orient=horizontal] {
+ width: 12em;
+ height: 1.3em;
+ margin: 0 0.7em;
+}
+
+input[type=range][orient=vertical] {
+ width: 1.3em;
+ height: 12em;
+ margin: 0.7em 0;
+}
+
+/**
+ * Ideally we'd also require :-moz-focusring here, but that doesn't currently
+ * work. Instead we only use the -moz-focus-outer border style if
+ * NS_EVENT_STATE_FOCUSRING is set (the check is in
+ * nsRangeFrame::BuildDisplayList).
+ */
+input[type=range]::-moz-focus-outer {
+ border: 1px dotted black;
+}
+
+/**
+ * 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.
+ */
+input[type=range]::-moz-range-track {
+ /* Prevent styling that would change the type of frame we construct. */
+ display: inline-block !important;
+ float: none !important;
+ position: static !important;
+ border: none;
+ background-color: #999;
+ inline-size: 100%;
+ block-size: 0.2em;
+ /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
+ -moz-user-select: none ! important;
+}
+
+input[type=range][orient=block]::-moz-range-track {
+ inline-size: 0.2em;
+ block-size: 100%;
+}
+
+input[type=range][orient=horizontal]::-moz-range-track {
+ width: 100%;
+ height: 0.2em;
+}
+
+input[type=range][orient=vertical]::-moz-range-track {
+ width: 0.2em;
+ height: 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 {
+ /* Prevent styling that would change the type of frame we construct. */
+ display: inline-block !important;
+ float: none !important;
+ position: static !important;
+ /* Since one of width/height will be ignored, this just sets the "other"
+ dimension.
+ */
+ width: 0.2em;
+ height: 0.2em;
+ /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
+ -moz-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 {
+ /* Native theming is atomic for range. Set -moz-appearance on the range
+ * to get rid of it. The thumb's -moz-appearance is fixed.
+ */
+ -moz-appearance: range-thumb !important;
+ /* Prevent styling that would change the type of frame we construct. */
+ display: inline-block !important;
+ float: none !important;
+ position: static !important;
+ width: 1em;
+ height: 1em;
+ border: 0.1em solid #999;
+ border-radius: 0.5em;
+ background-color: #F0F0F0;
+ /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
+ -moz-user-select: none ! important;
+}
+
+/* As a temporary workaround until bug 677302 the rule for input[type=number]
+ * has moved to number-control.css
+ */
+
+input[type=number]::-moz-number-wrapper {
+ /* Prevent styling that would change the type of frame we construct. */
+ display: flex;
+ float: none !important;
+ position: static !important;
+ block-size: 100%;
+}
+
+input[type=number]::-moz-number-text {
+ display: block; /* Flex items must be block-level. Normally we do fixup in
+ the style system to ensure this, but that fixup is disabled
+ inside of form controls. So, we hardcode display here. */
+ -moz-appearance: none;
+ /* work around autofocus bug 939248 on initial load */
+ -moz-user-modify: read-write;
+ /* This pseudo-element is also an 'input' element (nested inside and
+ * distinct from the <input type=number> element) so we need to prevent the
+ * explicit setting of 'text-align' by the general CSS rule for 'input'
+ * above. We want to inherit its value from its <input type=number>
+ * ancestor, not have that general CSS rule reset it.
+ */
+ text-align: inherit;
+ flex: 1;
+ min-inline-size: 0;
+ padding: 0;
+ border: 0;
+ margin: 0;
+}
+
+input[type=number]::-moz-number-spin-box {
+ writing-mode: horizontal-tb;
+ display: flex;
+ flex-direction: column;
+%ifdef XP_WIN
+ /* The Window's Theme's spin buttons have a very narrow minimum width, so
+ * make it something reasonable:
+ */
+ width: 16px;
+%endif
+ /* If the spin-box has auto height, it ends up enlarging the default height
+ * of the control, so we limit it to 1em here. The height doesn't affect
+ * the rendering of the spinner-buttons; it's only for layout purposes.
+ *
+ * This is a temporary hack until we implement better positioning for the
+ * spin-box in vertical mode; it works OK at default size but less well
+ * if the font-size is made substantially larger or smaller. (Bug 1175074.)
+ */
+ max-height: 1em;
+ align-self: center;
+ justify-content: center;
+}
+
+input[type=number]::-moz-number-spin-up {
+ -moz-appearance: spinner-upbutton;
+ display: block; /* bug 926670 */
+ flex: none;
+ cursor: default;
+ /* Style for when native theming is off: */
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="6" height="5"><path d="M1,4 L3,0 5,4" fill="dimgrey"/></svg>');
+ background-repeat: no-repeat;
+ background-position: center bottom;
+ border: 1px solid darkgray;
+ border-bottom: none;
+ /* [JK] I think the border-*-*-radius properties here can remain physical,
+ as we probably don't want to turn the spinner sideways in vertical writing mode */
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
+
+input[type=number]::-moz-number-spin-down {
+ -moz-appearance: spinner-downbutton;
+ display: block; /* bug 926670 */
+ flex: none;
+ cursor: default;
+ /* Style for when native theming is off: */
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="6" height="5"><path d="M1,1 L3,5 5,1" fill="dimgrey"/></svg>');
+ background-repeat: no-repeat;
+ background-position: center top;
+ border: 1px solid darkgray;
+ border-top: none;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+}
+
+input[type="number"] > div > div > div:hover {
+ /* give some indication of hover state for the up/down buttons */
+ background-color: lightblue;
+}
diff --git a/layout/style/res/html.css b/layout/style/res/html.css
new file mode 100644
index 000000000..a779461de
--- /dev/null
+++ b/layout/style/res/html.css
@@ -0,0 +1,863 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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);
+
+/* bidi */
+
+[dir] {
+ unicode-bidi: isolate;
+}
+[dir="rtl"] {
+ direction: rtl;
+}
+[dir="ltr"] {
+ direction: ltr;
+}
+
+bdi:dir(ltr), [dir="auto"]:dir(ltr) { direction: ltr; }
+bdi:dir(rtl), [dir="auto"]:dir(rtl) { direction: rtl; }
+
+/* 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,
+section,
+summary,
+table,
+tbody,
+td,
+tfoot,
+th,
+thead,
+tr,
+ul,
+xmp {
+ unicode-bidi: isolate;
+}
+
+bdi, output {
+ unicode-bidi: isolate;
+}
+bdo, bdo[dir] {
+ unicode-bidi: isolate-override;
+}
+textarea[dir="auto"], pre[dir="auto"] { unicode-bidi: plaintext; }
+
+/* blocks */
+
+article,
+aside,
+details,
+div,
+dt,
+figcaption,
+footer,
+form,
+header,
+hgroup,
+html,
+main,
+nav,
+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;
+}
+
+blockquote[type=cite] {
+ display: block;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+ padding-inline-start: 1em;
+ border-inline-start: solid;
+ border-color: blue;
+ border-width: thin;
+}
+
+span[_moz_quote=true] {
+ color: blue;
+}
+
+pre[_moz_quote=true] {
+ color: blue;
+}
+
+h1 {
+ display: block;
+ font-size: 2em;
+ font-weight: bold;
+ margin-block-start: .67em;
+ margin-block-end: .67em;
+}
+
+h2,
+:-moz-any(article, aside, nav, section)
+h1 {
+ display: block;
+ font-size: 1.5em;
+ font-weight: bold;
+ margin-block-start: .83em;
+ margin-block-end: .83em;
+}
+
+h3,
+:-moz-any(article, aside, nav, section)
+:-moz-any(article, aside, nav, section)
+h1 {
+ display: block;
+ font-size: 1.17em;
+ font-weight: bold;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+}
+
+h4,
+:-moz-any(article, aside, nav, section)
+:-moz-any(article, aside, nav, section)
+:-moz-any(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,
+:-moz-any(article, aside, nav, section)
+:-moz-any(article, aside, nav, section)
+:-moz-any(article, aside, nav, section)
+:-moz-any(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,
+:-moz-any(article, aside, nav, section)
+:-moz-any(article, aside, nav, section)
+:-moz-any(article, aside, nav, section)
+:-moz-any(article, aside, nav, section)
+:-moz-any(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"]):not([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-start-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: inherit;
+ padding: 1px;
+}
+
+th {
+ display: table-cell;
+ vertical-align: inherit;
+ font-weight: bold;
+ padding: 1px;
+}
+
+tr > form:-moz-is-html, tbody > form:-moz-is-html,
+thead > form:-moz-is-html, tfoot > form:-moz-is-html,
+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;
+ line-height: normal;
+}
+
+sup {
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+nobr {
+ white-space: nowrap;
+}
+
+mark {
+ background: yellow;
+ color: black;
+}
+
+/* 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;
+}
+
+menu[type="context"] {
+ display: none !important;
+}
+
+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 */
+:-moz-any(ul, ol, dir, menu, dl) ul,
+:-moz-any(ul, ol, dir, menu, dl) ol,
+:-moz-any(ul, ol, dir, menu, dl) dir,
+:-moz-any(ul, ol, dir, menu, dl) menu,
+:-moz-any(ul, ol, dir, menu, dl) dl {
+ margin-block-start: 0;
+ margin-block-end: 0;
+}
+
+/* 2 deep unordered lists use a circle */
+:-moz-any(ol, ul, menu, dir) ul,
+:-moz-any(ol, ul, menu, dir) menu,
+:-moz-any(ol, ul, menu, dir) dir {
+ list-style-type: circle;
+}
+
+/* 3 deep (or more) unordered lists use a square */
+:-moz-any(ol, ul, menu, dir) :-moz-any(ol, ul, menu, dir) ul,
+:-moz-any(ol, ul, menu, dir) :-moz-any(ol, ul, menu, dir) menu,
+:-moz-any(ol, ul, menu, dir) :-moz-any(ol, ul, menu, dir) dir {
+ list-style-type: square;
+}
+
+
+/* leafs */
+
+/* <hr> noshade and color attributes are handled completely by
+ * the nsHTMLHRElement attribute mapping code
+ */
+hr {
+ display: block;
+ border: 1px inset;
+ margin-block-start: 0.5em;
+ margin-block-end: 0.5em;
+ margin-inline-start: auto;
+ margin-inline-end: auto;
+ color: gray;
+ -moz-float-edge: margin-box;
+ box-sizing: border-box;
+}
+
+hr[size="1"] {
+ border-style: solid none none none;
+}
+
+img:-moz-broken::before, input:-moz-broken::before,
+img:-moz-user-disabled::before, input:-moz-user-disabled::before,
+img:-moz-loading::before, input:-moz-loading::before,
+applet:-moz-empty-except-children-with-localname(param):-moz-broken::before,
+applet:-moz-empty-except-children-with-localname(param):-moz-user-disabled::before {
+ content: -moz-alt-content !important;
+ unicode-bidi: isolate;
+}
+
+:-moz-any(object,applet):-moz-any(:-moz-broken,:-moz-user-disabled) > *|* {
+ /*
+ Inherit in the object's alignment so that if we aren't aligned explicitly
+ we'll end up in the right place vertically. See bug 36997. Note that this
+ is not !important because we _might_ be aligned explicitly.
+ */
+ vertical-align: inherit;
+}
+
+img:-moz-suppressed, input:-moz-suppressed, object:-moz-suppressed,
+embed:-moz-suppressed, applet:-moz-suppressed {
+ /*
+ Set visibility too in case the page changes display. Note that we _may_
+ want to just set visibility and not display, in general, if we find that
+ display:none breaks too many layouts. And if we decide we really do want
+ people to be able to right-click blocked images, etc, we need to set
+ neither one, and hack the painting code.... :(
+ */
+ display: none !important;
+ visibility: hidden !important;
+}
+
+img[usemap], object[usemap] {
+ color: blue;
+}
+
+frameset {
+ display: block ! important;
+ overflow: -moz-hidden-unscrollable;
+ position: static ! important;
+ float: none ! important;
+ border: none ! important;
+}
+
+link {
+ display: none;
+}
+
+frame {
+ border-radius: 0 ! important;
+}
+
+iframe {
+ border: 2px inset;
+}
+
+noframes {
+ display: none;
+}
+
+spacer {
+ position: static ! important;
+ float: none ! important;
+}
+
+canvas {
+ -moz-user-select: none;
+}
+
+/* focusable content: anything w/ tabindex >=0 is focusable, but we
+ skip drawing a focus outline on a few things that handle it
+ themselves. */
+:-moz-focusring:not(input):not(button):not(select):not(textarea):not(iframe):not(frame):not(body):not(html) {
+ /* Don't specify the outline-color, we should always use initial value. */
+ outline: 1px dotted;
+}
+
+/* hidden elements */
+base, basefont, datalist, head, meta, script, style, title,
+noembed, param, template {
+ 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: 0 !important;
+}
+
+/* media elements */
+video > xul|videocontrols, audio > xul|videocontrols {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#videoControls");
+}
+
+video:not([controls]) > xul|videocontrols,
+audio:not([controls]) > xul|videocontrols {
+ visibility: hidden;
+ -moz-binding: none;
+}
+
+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;
+}
+
+*|*::-moz-html-canvas-content {
+ display: block !important;
+ /* we want to be an absolute and fixed container */
+ transform: translate(0) !important;
+}
+
+video > .caption-box {
+ position: relative;
+ overflow: hidden;
+}
+
+/* datetime elements */
+
+input[type="time"] > xul|datetimebox {
+ display: flex;
+ -moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input");
+}
+
+/* details & summary */
+/* Need to revert Bug 1259889 Part 2 when removing details preference. */
+@supports -moz-bool-pref("dom.details_element.enabled") {
+ details > summary:first-of-type,
+ details > summary:-moz-native-anonymous {
+ display: list-item;
+ list-style: disclosure-closed inside;
+ }
+
+ details[open] > summary:first-of-type,
+ details[open] > summary:-moz-native-anonymous {
+ list-style-type: disclosure-open;
+ }
+
+ details > summary:first-of-type > *|* {
+ /* Cancel "list-style-position: inside" inherited from summary. */
+ list-style-position: initial;
+ }
+}
+
+/* emulation of non-standard HTML <marquee> tag */
+marquee {
+ inline-size: -moz-available;
+ display: inline-block;
+ vertical-align: text-bottom;
+ text-align: start;
+ -moz-binding: url('chrome://xbl-marquee/content/xbl-marquee.xml#marquee-horizontal');
+}
+
+marquee[direction="up"], marquee[direction="down"] {
+ -moz-binding: url('chrome://xbl-marquee/content/xbl-marquee.xml#marquee-vertical');
+ block-size: 200px;
+}
+
+/* PRINT ONLY rules follow */
+@media print {
+
+ marquee { -moz-binding: none; }
+
+}
+
+/* Ruby */
+
+ruby {
+ display: ruby;
+}
+rb {
+ display: ruby-base;
+ white-space: nowrap;
+}
+rp {
+ display: none;
+}
+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;
+%ifndef XP_WIN
+ /* 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;
+%endif
+}
+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: inherit;
+}
+ruby, rb, rt, rtc {
+ unicode-bidi: isolate;
+}
diff --git a/layout/style/res/noframes.css b/layout/style/res/noframes.css
new file mode 100644
index 000000000..4d1adfdc1
--- /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/noscript.css b/layout/style/res/noscript.css
new file mode 100644
index 000000000..f92b42a50
--- /dev/null
+++ b/layout/style/res/noscript.css
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 script disabled */
+
+noscript {
+ display: none !important;
+}
diff --git a/layout/style/res/number-control.css b/layout/style/res/number-control.css
new file mode 100644
index 000000000..b4c784cf2
--- /dev/null
+++ b/layout/style/res/number-control.css
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 exists purely because we need the styling for input[type=number]
+ * to apply only if the pref dom.forms.number is true. Once bug 677302 is
+ * fixed this rule can move back to forms.css.
+ */
+
+input[type="number"] {
+ -moz-appearance: number-input;
+ /* Has to revert some properties applied by the generic input rule. */
+ -moz-binding: none;
+ inline-size: 20ch; /* It'd be nice if this matched the default inline-size
+ of <input type=text>, but that's not easy to achieve
+ due to platform differences. */
+}
+
diff --git a/layout/style/res/plaintext.css b/layout/style/res/plaintext.css
new file mode 100644
index 000000000..2cc41c838
--- /dev/null
+++ b/layout/style/res/plaintext.css
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+}
diff --git a/layout/style/res/quirk.css b/layout/style/res/quirk.css
new file mode 100644
index 000000000..ba9e6d4ac
--- /dev/null
+++ b/layout/style/res/quirk.css
@@ -0,0 +1,203 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@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 ul, li ol, li dir, li menu {
+ list-style-position: outside;
+}
+
+/* undo previous two rules for properly nested lists */
+ ul ul, ul ol, ul dir, ul menu, ul li,
+ ol ul, ol ol, ol dir, ol menu, ol li,
+ dir ul, dir ol, dir dir, dir menu, dir li,
+menu ul, menu ol, menu dir, menu menu, menu li {
+ list-style-position: inherit;
+}
+
+
+/* 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;
+}
+
+/*
+ * Make table borders gray for compatibility with what other browsers do
+ * in all modes, rather than using the foreground color.
+ */
+table, td, th, tr, thead, tbody, tfoot, colgroup, col {
+ border-color: gray;
+}
+
+
+/* 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 RuleHash and things will be much more
+ * efficient.
+ */
+body > p:-moz-first-node, td > p:-moz-first-node, th > p:-moz-first-node,
+body > dl:-moz-first-node, td > dl:-moz-first-node, th > dl:-moz-first-node,
+body > multicol:-moz-first-node, td > multicol:-moz-first-node, th > multicol:-moz-first-node,
+body > blockquote:-moz-first-node, td > blockquote:-moz-first-node, th > blockquote:-moz-first-node,
+body > h1:-moz-first-node, td > h1:-moz-first-node, th > h1:-moz-first-node,
+body > h2:-moz-first-node, td > h2:-moz-first-node, th > h2:-moz-first-node,
+body > h3:-moz-first-node, td > h3:-moz-first-node, th > h3:-moz-first-node,
+body > h4:-moz-first-node, td > h4:-moz-first-node, th > h4:-moz-first-node,
+body > h5:-moz-first-node, td > h5:-moz-first-node, th > h5:-moz-first-node,
+body > h6:-moz-first-node, td > h6:-moz-first-node, th > h6:-moz-first-node,
+body > listing:-moz-first-node, td > listing:-moz-first-node, th > listing:-moz-first-node,
+body > plaintext:-moz-first-node, td > plaintext:-moz-first-node, th > plaintext:-moz-first-node,
+body > xmp:-moz-first-node, td > xmp:-moz-first-node, th > xmp:-moz-first-node,
+body > pre:-moz-first-node, td > pre:-moz-first-node, th > pre:-moz-first-node,
+body > ul:-moz-first-node, td > ul:-moz-first-node, th > ul:-moz-first-node,
+body > menu:-moz-first-node, td > menu:-moz-first-node, th > menu:-moz-first-node,
+body > dir:-moz-first-node, td > dir:-moz-first-node, th > dir:-moz-first-node,
+body > ol:-moz-first-node, td > ol:-moz-first-node, th > 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
+ */
+body > p:-moz-only-whitespace:-moz-first-node,
+td > p:-moz-only-whitespace:-moz-first-node, th > p:-moz-only-whitespace:-moz-first-node,
+body > dl:-moz-only-whitespace:-moz-first-node, td > dl:-moz-only-whitespace:-moz-first-node,
+th > dl:-moz-only-whitespace:-moz-first-node, body > multicol:-moz-only-whitespace:-moz-first-node,
+td > multicol:-moz-only-whitespace:-moz-first-node, th > multicol:-moz-only-whitespace:-moz-first-node,
+body > blockquote:-moz-only-whitespace:-moz-first-node, td > blockquote:-moz-only-whitespace:-moz-first-node,
+th > blockquote:-moz-only-whitespace:-moz-first-node, body > h1:-moz-only-whitespace:-moz-first-node,
+td > h1:-moz-only-whitespace:-moz-first-node, th > h1:-moz-only-whitespace:-moz-first-node,
+body > h2:-moz-only-whitespace:-moz-first-node, td > h2:-moz-only-whitespace:-moz-first-node,
+th > h2:-moz-only-whitespace:-moz-first-node, body > h3:-moz-only-whitespace:-moz-first-node,
+td > h3:-moz-only-whitespace:-moz-first-node, th > h3:-moz-only-whitespace:-moz-first-node,
+body > h4:-moz-only-whitespace:-moz-first-node, td > h4:-moz-only-whitespace:-moz-first-node,
+th > h4:-moz-only-whitespace:-moz-first-node, body > h5:-moz-only-whitespace:-moz-first-node,
+td > h5:-moz-only-whitespace:-moz-first-node, th > h5:-moz-only-whitespace:-moz-first-node,
+body > h6:-moz-only-whitespace:-moz-first-node, td > h6:-moz-only-whitespace:-moz-first-node,
+th > h6:-moz-only-whitespace:-moz-first-node, body > listing:-moz-only-whitespace:-moz-first-node,
+td > listing:-moz-only-whitespace:-moz-first-node, th > listing:-moz-only-whitespace:-moz-first-node,
+body > plaintext:-moz-only-whitespace:-moz-first-node, td > plaintext:-moz-only-whitespace:-moz-first-node,
+th > plaintext:-moz-only-whitespace:-moz-first-node, body > xmp:-moz-only-whitespace:-moz-first-node,
+td > xmp:-moz-only-whitespace:-moz-first-node, th > xmp:-moz-only-whitespace:-moz-first-node,
+body > pre:-moz-only-whitespace:-moz-first-node, td > pre:-moz-only-whitespace:-moz-first-node,
+th > pre:-moz-only-whitespace:-moz-first-node, body > ul:-moz-only-whitespace:-moz-first-node,
+td > ul:-moz-only-whitespace:-moz-first-node, th > ul:-moz-only-whitespace:-moz-first-node,
+body > menu:-moz-only-whitespace:-moz-first-node, td > menu:-moz-only-whitespace:-moz-first-node,
+th > menu:-moz-only-whitespace:-moz-first-node, body > dir:-moz-only-whitespace:-moz-first-node,
+td > dir:-moz-only-whitespace:-moz-first-node, th > dir:-moz-only-whitespace:-moz-first-node,
+body > ol:-moz-only-whitespace:-moz-first-node, td > ol:-moz-only-whitespace:-moz-first-node,
+th > ol:-moz-only-whitespace:-moz-first-node {
+ margin-block-end: 0;
+}
+
+td > p:-moz-only-whitespace:-moz-last-node, th > p:-moz-only-whitespace:-moz-last-node,
+td > dl:-moz-only-whitespace:-moz-last-node, th > dl:-moz-only-whitespace:-moz-last-node,
+td > multicol:-moz-only-whitespace:-moz-last-node, th > multicol:-moz-only-whitespace:-moz-last-node,
+td > blockquote:-moz-only-whitespace:-moz-last-node, th > blockquote:-moz-only-whitespace:-moz-last-node,
+td > h1:-moz-only-whitespace:-moz-last-node, th > h1:-moz-only-whitespace:-moz-last-node,
+td > h2:-moz-only-whitespace:-moz-last-node, th > h2:-moz-only-whitespace:-moz-last-node,
+td > h3:-moz-only-whitespace:-moz-last-node, th > h3:-moz-only-whitespace:-moz-last-node,
+td > h4:-moz-only-whitespace:-moz-last-node, th > h4:-moz-only-whitespace:-moz-last-node,
+td > h5:-moz-only-whitespace:-moz-last-node, th > h5:-moz-only-whitespace:-moz-last-node,
+td > h6:-moz-only-whitespace:-moz-last-node, th > h6:-moz-only-whitespace:-moz-last-node,
+td > listing:-moz-only-whitespace:-moz-last-node, th > listing:-moz-only-whitespace:-moz-last-node,
+td > plaintext:-moz-only-whitespace:-moz-last-node, th > plaintext:-moz-only-whitespace:-moz-last-node,
+td > xmp:-moz-only-whitespace:-moz-last-node, th > xmp:-moz-only-whitespace:-moz-last-node,
+td > pre:-moz-only-whitespace:-moz-last-node, th > pre:-moz-only-whitespace:-moz-last-node,
+td > ul:-moz-only-whitespace:-moz-last-node, th > ul:-moz-only-whitespace:-moz-last-node,
+td > menu:-moz-only-whitespace:-moz-last-node, th > menu:-moz-only-whitespace:-moz-last-node,
+td > dir:-moz-only-whitespace:-moz-last-node, th > dir:-moz-only-whitespace:-moz-last-node,
+td > ol:-moz-only-whitespace:-moz-last-node, th > ol:-moz-only-whitespace:-moz-last-node {
+ margin-block-start: 0;
+}
+
+
+/* Quirk: DD not in DL has text-indent instead of margin (b=5119) */
+
+:not(dl) > dd {
+ display: inline;
+ margin: 0;
+}
+
+:not(dl) > dd:before {
+ display: inline;
+ white-space: pre;
+ font-size: 1px;
+ line-height: 0;
+ content: "\A ";
+ margin-inline-end: 40px;
+}
+
+
+/* quirk to indent nested DL elements (b=8749) */
+
+dl > dl {
+ display: block;
+ margin-inline-start: 40px;
+}
+
+
+/* Quirk: Make floated images have a margin (b=58899) */
+img[align=left]:dir(ltr), img[align=right]:dir(rtl) {
+ margin-inline-end: 3px;
+}
+
+img[align=right]:dir(ltr), img[align=left]:dir(rtl) {
+ margin-inline-start: 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/ua.css b/layout/style/res/ua.css
new file mode 100644
index 000000000..931b32eb8
--- /dev/null
+++ b/layout/style/res/ua.css
@@ -0,0 +1,473 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 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 !important;
+ box-sizing: border-box; /* XXX do we really want this? */
+}
+
+*|*::-moz-inline-table {
+ display: inline-table !important;
+ box-sizing: border-box; /* XXX do we really want this? */
+}
+
+*|*::-moz-table-wrapper {
+ display: inherit !important; /* table or inline-table */
+ margin: inherit ! important;
+ padding: 0 ! important;
+ border: none ! important;
+ 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;
+ /* 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;
+}
+
+*|*::-moz-table-row {
+ display: table-row !important;
+}
+
+/* The ::-moz-table-column pseudo-element is for extra columns at the end
+ of a table. */
+*|*::-moz-table-column {
+ display: table-column !important;
+}
+
+*|*::-moz-table-column-group {
+ display: table-column-group !important;
+}
+
+*|*::-moz-table-row-group {
+ display: table-row-group !important;
+}
+
+*|*::-moz-table-cell {
+ display: table-cell !important;
+ 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;
+}
+
+/* Lists */
+
+*|*::-moz-list-bullet, *|*::-moz-list-number {
+ display: inline;
+ vertical-align: baseline;
+ font-variant-numeric: tabular-nums;
+ /* Prevent the element from being selected when clicking on the marker. */
+ -moz-user-select: none;
+}
+
+/* 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 */
+
+*|*:any-link {
+ cursor: pointer;
+}
+
+*|*:any-link:-moz-focusring {
+ /* Don't specify the outline-color, we should always use initial value. */
+ outline: 1px dotted;
+}
+
+/* Miscellaneous */
+
+*|*::-moz-anonymous-block, *|*::-moz-cell-content {
+ display: block !important;
+ position: static !important;
+ unicode-bidi: inherit;
+ text-overflow: inherit;
+ overflow-clip-box: inherit;
+}
+
+*|*::-moz-anonymous-block, *|*::-moz-anonymous-positioned-block {
+ /* we currently inherit from the inline that is split */
+ outline: inherit;
+ outline-offset: inherit;
+ clip-path: inherit;
+ filter: inherit;
+ mask: inherit;
+ opacity: inherit;
+ text-decoration: inherit;
+ -moz-box-ordinal-group: inherit !important;
+ overflow-clip-box: inherit;
+}
+
+*|*::-moz-xul-anonymous-block {
+ display: block ! important;
+ position: static ! important;
+ float: none ! important;
+ -moz-box-ordinal-group: inherit !important;
+ text-overflow: inherit;
+ overflow-clip-box: inherit;
+}
+
+*|*::-moz-scrolled-content, *|*::-moz-scrolled-canvas,
+*|*::-moz-scrolled-page-sequence {
+ /* 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 Multicol/Flex/Grid/Align sections below in sync with
+ ::-moz-fieldset-content/::-moz-button-content in forms.css */
+ /* Multicol container */
+ -moz-column-count: inherit;
+ -moz-column-width: inherit;
+ -moz-column-gap: inherit;
+ -moz-column-rule: inherit;
+ -moz-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;
+ /* Do not change these. nsCSSFrameConstructor depends on them to create a good
+ frame tree. */
+ position: static !important;
+ float: none !important;
+ overflow-clip-box: inherit;
+}
+
+*|*::-moz-viewport, *|*::-moz-viewport-scroll, *|*::-moz-canvas, *|*::-moz-scrolled-canvas {
+ display: block !important;
+ background-color: inherit;
+}
+
+*|*::-moz-viewport-scroll {
+ overflow: auto;
+%ifdef XP_WIN
+ resize: both;
+%endif
+}
+
+*|*::-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;
+ /* inherit the outer frame's display, otherwise we turn into an inline */
+ display: inherit !important;
+ /* Carry through our parent's height so that %-height children get
+ their heights set */
+ height: 100%;
+}
+
+*|*::-moz-anonymous-flex-item,
+*|*::-moz-anonymous-grid-item {
+ /* Anonymous blocks that wrap contiguous runs of text
+ * inside of a flex or grid container. */
+ display: block;
+}
+
+*|*::-moz-page-sequence, *|*::-moz-scrolled-page-sequence {
+ /* Collection of pages in print/print preview. Visual styles may only appear
+ * in print preview. */
+ display: block !important;
+ background: linear-gradient(#606060, #8a8a8a) fixed;
+ height: 100%;
+}
+
+*|*::-moz-page {
+ /* Individual page in print/print preview. Visual styles may only appear
+ * in print preview. */
+ display: block !important;
+ background: white;
+ box-shadow: 5px 5px 8px #202020;
+ margin: 0.125in 0.25in;
+}
+
+*|*::-moz-pagecontent {
+ display: block !important;
+ margin: auto;
+}
+
+*|*::-moz-pagebreak {
+ display: block !important;
+}
+
+*|*::-moz-anonymous-positioned-block {
+ display: block !important;
+ position: inherit; /* relative or sticky */
+ top: inherit;
+ left: inherit;
+ bottom: inherit;
+ right: inherit;
+ z-index: inherit;
+ clip: inherit;
+ opacity: inherit;
+ unicode-bidi: inherit;
+ text-overflow: inherit;
+}
+
+/* Printing */
+
+@media print {
+
+ * {
+ cursor: default !important;
+ }
+
+}
+
+*|*:fullscreen:not(:root) {
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 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;
+}
+
+/* Selectors here should match the check in
+ * nsViewportFrame.cpp:ShouldInTopLayerForFullscreen() */
+*|*:fullscreen:not(:root):not(:-moz-browser-frame) {
+ -moz-top-layer: top !important;
+}
+
+*|*::backdrop {
+ -moz-top-layer: top !important;
+ display: block;
+ position: fixed;
+ top: 0; left: 0;
+ right: 0; bottom: 0;
+}
+
+*|*:-moz-full-screen: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;
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret {
+ /* Add transition effect to make caret size changing smoother. */
+ transition-duration: 250ms;
+ transition-property: width, height, margin-left;
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret,
+div:-moz-native-anonymous.moz-accessiblecaret > #text-overlay,
+div:-moz-native-anonymous.moz-accessiblecaret > #image,
+div:-moz-native-anonymous.moz-accessiblecaret > #bar {
+ position: absolute;
+ z-index: 2147483647;
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret > #text-overlay,
+div:-moz-native-anonymous.moz-accessiblecaret > #image {
+ top: 0;
+ width: 100%;
+
+ /* Override this property in moz-custom-content-container to make dummy touch
+ * listener work. */
+ pointer-events: auto;
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret > #image {
+ background-position: center top;
+ background-size: 100%;
+ background-repeat: no-repeat;
+ background-origin: content-box;
+ height: 100%;
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret > #bar {
+ margin-left: 49%;
+ background-color: #008aa0;
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret.no-bar > #bar {
+ display: none;
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret.normal > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-normal@1x.png");
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret.left > #text-overlay,
+div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
+ margin-left: -39%;
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-left@1x.png");
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret.right > #text-overlay,
+div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
+ margin-left: 41%;
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-right@1x.png");
+}
+
+div:-moz-native-anonymous.moz-accessiblecaret.none {
+ display: none;
+}
+
+@media (min-resolution: 1.5dppx) {
+ div:-moz-native-anonymous.moz-accessiblecaret.normal > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-normal@1.5x.png");
+ }
+
+ div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-left@1.5x.png");
+ }
+
+ div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-right@1.5x.png");
+ }
+}
+
+@media (min-resolution: 2dppx) {
+ div:-moz-native-anonymous.moz-accessiblecaret.normal > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-normal@2x.png");
+ }
+
+ div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-left@2x.png");
+ }
+
+ div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-right@2x.png");
+ }
+}
+
+@media (min-resolution: 2.25dppx) {
+ div:-moz-native-anonymous.moz-accessiblecaret.normal > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-normal@2.25x.png");
+ }
+
+ div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-left@2.25x.png");
+ }
+
+ div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
+ background-image: url("resource://gre-resources/accessiblecaret-tilt-right@2.25x.png");
+ }
+}
+
+/* Custom content container in the CanvasFrame, positioned on top of everything
+ everything else, not reacting to pointer events. */
+div:-moz-native-anonymous.moz-custom-content-container {
+ pointer-events: none;
+ -moz-top-layer: top;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
diff --git a/layout/style/res/viewsource.css b/layout/style/res/viewsource.css
new file mode 100644
index 000000000..e586dc053
--- /dev/null
+++ b/layout/style/res/viewsource.css
@@ -0,0 +1,101 @@
+@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 {
+ background-color: white;
+ color: black;
+ 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;
+ -moz-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 {
+ color: purple;
+ font-weight: bold;
+}
+.highlight .end-tag {
+ color: purple;
+ font-weight: bold;
+}
+.highlight .comment {
+ color: green;
+ font-style: italic;
+}
+.highlight .cdata {
+ color: #CC0066;
+}
+.highlight .doctype {
+ 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 {
+ color: black;
+ font-weight: bold;
+}
+.highlight .attribute-value {
+ color: blue;
+ font-weight: normal;
+}
+.highlight .markupdeclaration {
+ color: steelblue;
+ font-style: italic;
+}
+span:not(.error), a:not(.error) {
+ unicode-bidi: embed;
+}
+span[id] {
+ unicode-bidi: isolate;
+}
+.highlight .error,
+.highlight .error > :-moz-any(.start-tag, .end-tag, .comment, .cdata, .doctype,
+ .pi, .entity, .attribute-name, .attribute-value) {
+ color: red;
+ font-weight: bold;
+}
diff --git a/layout/style/test/BitPattern.woff b/layout/style/test/BitPattern.woff
new file mode 100644
index 000000000..e4e824405
--- /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 000000000..718032f61
--- /dev/null
+++ b/layout/style/test/ListCSSProperties.cpp
@@ -0,0 +1,193 @@
+/* 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"
+
+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(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
+ stylestruct_, stylestructoffset_, animtype_) \
+ { #name_, #method_, pref_ },
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+
+#include "nsCSSPropList.h"
+
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+#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(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
+ stylestruct_, stylestructoffset_, animtype_) \
+ #name_,
+#define CSS_PROP_LIST_INCLUDE_LOGICAL
+
+#include "nsCSSPropList.h"
+
+#undef CSS_PROP_LIST_INCLUDE_LOGICAL
+#undef CSS_PROP
+#undef CSS_PROP_LIST_EXCLUDE_INTERNAL
+
+};
+
+const PropertyInfo gShorthandProperties[] = {
+
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_
+// Need an extra level of macro nesting to force expansion of method_
+// params before they get pasted.
+#define LISTCSSPROPERTIES_INNER_MACRO(method_) #method_
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+ { #name_, LISTCSSPROPERTIES_INNER_MACRO(method_), pref_ },
+
+#include "nsCSSPropList.h"
+
+#undef CSS_PROP_SHORTHAND
+#undef LISTCSSPROPERTIES_INNER_MACRO
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+
+#define CSS_PROP_ALIAS(name_, id_, method_, pref_) \
+ { #name_, #method_, pref_ },
+
+#include "nsCSSPropAliasList.h"
+
+#undef CSS_PROP_ALIAS
+
+};
+
+/* see gLonghandPropertiesWithDOMProp */
+const char* gShorthandPropertiesWithDOMProp[] = {
+
+#define CSS_PROP_LIST_EXCLUDE_INTERNAL
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+ #name_,
+
+#include "nsCSSPropList.h"
+
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LIST_EXCLUDE_INTERNAL
+
+#define CSS_PROP_ALIAS(name_, id_, method_, pref_) \
+ #name_,
+
+#include "nsCSSPropAliasList.h"
+
+#undef CSS_PROP_ALIAS
+
+};
+
+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-system-font",
+ "-x-text-zoom",
+ "-moz-control-character-visibility",
+ "-moz-script-level", // parsed by UA sheets only
+ "-moz-script-size-multiplier",
+ "-moz-script-min-size",
+ "-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-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 000000000..3f407c6f0
--- /dev/null
+++ b/layout/style/test/ParseCSS.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// 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_InitXPCOM2(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/TestCSSPropertyLookup.cpp b/layout/style/test/TestCSSPropertyLookup.cpp
new file mode 100644
index 000000000..60a15311c
--- /dev/null
+++ b/layout/style/test/TestCSSPropertyLookup.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdio.h>
+#include "plstr.h"
+#include "nsCSSProps.h"
+#include "nsCSSKeywords.h"
+#include "nsString.h"
+#include "nsXPCOM.h"
+
+using namespace mozilla;
+
+static const char* const kJunkNames[] = {
+ nullptr,
+ "",
+ "123",
+ "backgroundz",
+ "zzzzzz",
+ "#@$&@#*@*$@$#"
+};
+
+static bool
+TestProps()
+{
+ bool success = true;
+ nsCSSPropertyID id;
+ nsCSSPropertyID index;
+
+ // Everything appears to assert if we don't do this first...
+ nsCSSProps::AddRefTable();
+
+ // First make sure we can find all of the tags that are supposed to
+ // be in the table. Futz with the case to make sure any case will
+ // work
+ extern const char* const kCSSRawProperties[];
+ const char*const* et = &kCSSRawProperties[0];
+ const char*const* end = &kCSSRawProperties[eCSSProperty_COUNT];
+ index = eCSSProperty_UNKNOWN;
+ while (et < end) {
+ char tagName[100];
+ PL_strcpy(tagName, *et);
+ index = nsCSSPropertyID(int32_t(index) + 1);
+
+ id = nsCSSProps::LookupProperty(nsCString(tagName),
+ CSSEnabledState::eIgnoreEnabledState);
+ if (id == eCSSProperty_UNKNOWN) {
+ printf("bug: can't find '%s'\n", tagName);
+ success = false;
+ }
+ if (id != index) {
+ printf("bug: name='%s' id=%d index=%d\n", tagName, id, index);
+ success = false;
+ }
+
+ // fiddle with the case to make sure we can still find it
+ if (('a' <= tagName[0]) && (tagName[0] <= 'z')) {
+ tagName[0] = tagName[0] - 32;
+ }
+ id = nsCSSProps::LookupProperty(NS_ConvertASCIItoUTF16(tagName),
+ CSSEnabledState::eIgnoreEnabledState);
+ if (id < 0) {
+ printf("bug: can't find '%s'\n", tagName);
+ success = false;
+ }
+ if (index != id) {
+ printf("bug: name='%s' id=%d index=%d\n", tagName, id, index);
+ success = false;
+ }
+ et++;
+ }
+
+ // Now make sure we don't find some garbage
+ for (int i = 0; i < (int) (sizeof(kJunkNames) / sizeof(const char*)); i++) {
+ const char* const tag = kJunkNames[i];
+ id = nsCSSProps::LookupProperty(nsAutoCString(tag),
+ CSSEnabledState::eIgnoreEnabledState);
+ if (id >= 0) {
+ printf("bug: found '%s'\n", tag ? tag : "(null)");
+ success = false;
+ }
+ }
+
+ nsCSSProps::ReleaseTable();
+ return success;
+}
+
+bool
+TestKeywords()
+{
+ nsCSSKeywords::AddRefTable();
+
+ bool success = true;
+ nsCSSKeyword id;
+ nsCSSKeyword index;
+
+ extern const char* const kCSSRawKeywords[];
+
+ // First make sure we can find all of the tags that are supposed to
+ // be in the table. Futz with the case to make sure any case will
+ // work
+ const char*const* et = &kCSSRawKeywords[0];
+ const char*const* end = &kCSSRawKeywords[eCSSKeyword_COUNT - 1];
+ index = eCSSKeyword_UNKNOWN;
+ while (et < end) {
+ char tagName[512];
+ char* underscore = &(tagName[0]);
+
+ PL_strcpy(tagName, *et);
+ while (*underscore) {
+ if (*underscore == '_') {
+ *underscore = '-';
+ }
+ underscore++;
+ }
+ index = nsCSSKeyword(int32_t(index) + 1);
+
+ id = nsCSSKeywords::LookupKeyword(nsCString(tagName));
+ if (id <= eCSSKeyword_UNKNOWN) {
+ printf("bug: can't find '%s'\n", tagName);
+ success = false;
+ }
+ if (id != index) {
+ printf("bug: name='%s' id=%d index=%d\n", tagName, id, index);
+ success = false;
+ }
+
+ // fiddle with the case to make sure we can still find it
+ if (('a' <= tagName[0]) && (tagName[0] <= 'z')) {
+ tagName[0] = tagName[0] - 32;
+ }
+ id = nsCSSKeywords::LookupKeyword(nsCString(tagName));
+ if (id <= eCSSKeyword_UNKNOWN) {
+ printf("bug: can't find '%s'\n", tagName);
+ success = false;
+ }
+ if (id != index) {
+ printf("bug: name='%s' id=%d index=%d\n", tagName, id, index);
+ success = false;
+ }
+ et++;
+ }
+
+ // Now make sure we don't find some garbage
+ for (int i = 0; i < (int) (sizeof(kJunkNames) / sizeof(const char*)); i++) {
+ const char* const tag = kJunkNames[i];
+ id = nsCSSKeywords::LookupKeyword(nsAutoCString(tag));
+ if (eCSSKeyword_UNKNOWN < id) {
+ printf("bug: found '%s'\n", tag ? tag : "(null)");
+ success = false;
+ }
+ }
+
+ nsCSSKeywords::ReleaseTable();
+ return success;
+}
+
+int
+main(void)
+{
+ nsresult rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, 2);
+
+ bool testOK = true;
+ testOK &= TestProps();
+ testOK &= TestKeywords();
+
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ENSURE_SUCCESS(rv, 2);
+
+ return testOK ? 0 : 1;
+}
diff --git a/layout/style/test/additional_sheets_helper.html b/layout/style/test/additional_sheets_helper.html
new file mode 100644
index 000000000..306ddbf5b
--- /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 000000000..131e9b755
--- /dev/null
+++ b/layout/style/test/animation_utils.js
@@ -0,0 +1,707 @@
+//----------------------------------------------------------------------
+//
+// 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, false);
+ gElem.addEventListener("animationiteration", listener, false);
+ gElem.addEventListener("animationend", listener, false);
+ }
+
+ 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.parentNode.removeChild(div);
+ style.parentNode.removeChild(style);
+ if (utils.isTestControllingRefreshes) {
+ utils.restoreNormalRefresh();
+ }
+ };
+
+ return waitForDocumentLoad()
+ .then(loadPaintListener)
+ .then(function() {
+ // Put refresh driver under test control and trigger animation
+ utils.advanceTimeAndRefresh(0);
+ div.style.animation = animationName + " 10s";
+
+ // Trigger style flush
+ div.clientTop;
+ return waitForPaints();
+ }).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 waitForPaints() {
+ return new Promise(function(resolve, reject) {
+ waitForAllPaintsFlushed(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);
+ } else {
+ return Promise.resolve(next.value)
+ .then(step, function(err) { throw err; });
+ }
+ }
+
+ // Put refresh driver under test control
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
+
+ // Run test
+ generator = aTestFunc();
+ return step()
+ .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
+};
+
+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
+ const omtaProperties = [ "transform", "opacity" ];
+ if (omtaProperties.indexOf(property) === -1) {
+ ok(false, property + " is not an OMTA property");
+ return;
+ }
+ var isTransform = property == "transform";
+ var normalize = isTransform ? convertTo3dMatrix : parseFloat;
+ var compare = isTransform ?
+ matricesRoughlyEqual :
+ function(a, b, error) { return Math.abs(a - b) <= error; };
+ var normalizedToString = isTransform ?
+ convert3dMatrixToString :
+ JSON.stringify;
+
+ // 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;
+
+ 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);
+ if (actualValue === null) {
+ ok(false, desc + ": should return a valid result - got " + actualStr);
+ return;
+ }
+ okOrTodo(compare(expectedValue, actualValue, tolerance),
+ desc + " - got " + actualStr + ", expected " +
+ normalizedToString(expectedValue));
+
+ // For compositor animations do an additional check that they match
+ // the value calculated on the main thread
+ if (actualStr === compositorStr) {
+ var computedValue = normalize(computedStr);
+ if (computedValue === null) {
+ ok(false, desc + ": test framework should parse computed style" +
+ " - got " + computedStr);
+ return;
+ }
+ okOrTodo(compare(computedValue, actualValue, 0),
+ desc + ": OMTA style and computed style should be equal" +
+ " - OMTA " + actualStr + ", computed " + computedStr);
+ }
+ };
+
+ window.matricesRoughlyEqual = function(a, b, tolerance) {
+ 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);
+ } else {
+ 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)
+ ];
+ } else {
+ return null;
+ }
+ }
+
+ // 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 [
+ [
+ obj.a || obj.sx || obj.m11 || 1,
+ obj.b || obj.m12 || 0,
+ obj.m13 || 0,
+ obj.m14 || 0
+ ], [
+ obj.c || obj.m21 || 0,
+ obj.d || obj.sy || obj.m22 || 1,
+ obj.m23 || 0,
+ obj.m24 || 0
+ ], [
+ obj.m31 || 0,
+ obj.m32 || 0,
+ 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,
+ 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(", ") + ")";
+ } else {
+ 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.ini b/layout/style/test/browser.ini
new file mode 100644
index 000000000..4e5e47aac
--- /dev/null
+++ b/layout/style/test/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ bug453896_iframe.html
+ media_queries_iframe.html
+ newtab_share_rule_processors.html
+
+[browser_bug453896.js]
+[browser_newtab_share_rule_processors.js]
diff --git a/layout/style/test/browser_bug453896.js b/layout/style/test/browser_bug453896.js
new file mode 100644
index 000000000..0084ae33d
--- /dev/null
+++ b/layout/style/test/browser_bug453896.js
@@ -0,0 +1,13 @@
+add_task(function* () {
+ let uri = getRootDirectory(gTestPath) + "bug453896_iframe.html";
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: uri
+ }, function*(browser) {
+ return ContentTask.spawn(browser, null, function* () {
+ var fake_window = { ok: ok };
+ content.wrappedJSObject.run(fake_window);
+ });
+ });
+});
diff --git a/layout/style/test/browser_newtab_share_rule_processors.js b/layout/style/test/browser_newtab_share_rule_processors.js
new file mode 100644
index 000000000..810f5f86e
--- /dev/null
+++ b/layout/style/test/browser_newtab_share_rule_processors.js
@@ -0,0 +1,38 @@
+var theTab;
+var theBrowser;
+
+function listener(evt) {
+ if (evt.target == theBrowser.contentDocument) {
+ doTest();
+ }
+}
+
+function test() {
+ waitForExplicitFinish();
+ var testURL = getRootDirectory(gTestPath) + "newtab_share_rule_processors.html";
+ theTab = gBrowser.addTab(testURL);
+ theBrowser = gBrowser.getBrowserForTab(theTab);
+ theBrowser.addEventListener("load", listener, true);
+}
+
+function doTest() {
+ theBrowser.removeEventListener("load", listener, true);
+ var winUtils = theBrowser.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ // The initial set of agent-level sheets should have a rule processor that's
+ // also being used by another document.
+ ok(winUtils.hasRuleProcessorUsedByMultipleStyleSets(Ci.nsIStyleSheetService.AGENT_SHEET),
+ "agent sheet rule processor is used by multiple style sets");
+ // Document-level sheets currently never get shared rule processors.
+ ok(!winUtils.hasRuleProcessorUsedByMultipleStyleSets(Ci.nsIStyleSheetService.AUTHOR_SHEET),
+ "author sheet rule processor is not used by multiple style sets");
+ // Adding a unique style sheet to the agent level will cause it to have a
+ // rule processor that is unique.
+ theBrowser.contentWindow.wrappedJSObject.addAgentSheet();
+ ok(!winUtils.hasRuleProcessorUsedByMultipleStyleSets(Ci.nsIStyleSheetService.AGENT_SHEET),
+ "agent sheet rule processor is not used by multiple style sets after " +
+ "having a unique sheet added to it");
+ gBrowser.removeTab(theTab);
+ finish();
+}
diff --git a/layout/style/test/bug453896_iframe.html b/layout/style/test/bug453896_iframe.html
new file mode 100644
index 000000000..c65388924
--- /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; version=1.7">
+
+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") == "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 000000000..5a730b055
--- /dev/null
+++ b/layout/style/test/bug517224.sjs
@@ -0,0 +1,24 @@
+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 000000000..95dc612d5
--- /dev/null
+++ b/layout/style/test/bug732209-css.sjs
@@ -0,0 +1,19 @@
+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 000000000..f12204a2d
--- /dev/null
+++ b/layout/style/test/ccd-quirks.html
@@ -0,0 +1,124 @@
+<!-- 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>
+</body></html>
diff --git a/layout/style/test/ccd-standards.html b/layout/style/test/ccd-standards.html
new file mode 100644
index 000000000..ae6f443a2
--- /dev/null
+++ b/layout/style/test/ccd-standards.html
@@ -0,0 +1,123 @@
+<!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>
+</body></html>
diff --git a/layout/style/test/ccd.sjs b/layout/style/test/ccd.sjs
new file mode 100644
index 000000000..058393e82
--- /dev/null
+++ b/layout/style/test/ccd.sjs
@@ -0,0 +1,73 @@
+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 000000000..4336f4abd
--- /dev/null
+++ b/layout/style/test/chrome/bug418986-2.js
@@ -0,0 +1,314 @@
+// # Bug 418986, part 2.
+
+/* jshint esnext:true */
+/* jshint loopfunc:true */
+/* global window, screen, ok, SpecialPowers, matchMedia */
+
+// 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 = [
+ "-moz-mac-graphite-theme",
+ // Not available on most OSs.
+// "-moz-maemo-classic",
+ "-moz-scrollbar-end-backward",
+ "-moz-scrollbar-end-forward",
+ "-moz-scrollbar-start-backward",
+ "-moz-scrollbar-start-forward",
+ "-moz-scrollbar-thumb-proportional",
+ "-moz-touch-enabled",
+ "-moz-windows-compositor",
+ "-moz-windows-default-theme",
+ "-moz-windows-glass",
+];
+
+// Possible values for '-moz-os-version'
+var windows_versions = [
+ "windows-xp",
+ "windows-vista",
+ "windows-win7",
+ "windows-win8",
+ "windows-win10",
+];
+
+// Possible values for '-moz-windows-theme'
+var windows_themes = [
+ "aero",
+ "aero-lite",
+ "luna-blue",
+ "luna-olive",
+ "luna-silver",
+ "royale",
+ "generic",
+ "zune"
+];
+
+// Read the current OS.
+var OS = SpecialPowers.Services.appinfo.OS;
+
+// If we are using Windows, add an extra toggle only
+// available on that OS.
+if (OS === "WINNT") {
+ suppressed_toggles.push("-moz-windows-classic");
+}
+
+// __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 (resisting) {
+ ok(!exists, key + " should not exist.");
+ } else {
+ ok(exists, key + " should exist.");
+ }
+ });
+};
+
+// __testWindowsSpecific__.
+// Runs a media query on the queryName with the given possible matching values.
+var testWindowsSpecific = function (resisting, queryName, possibleValues) {
+ let foundValue = null;
+ possibleValues.forEach(function (val) {
+ if (keyValMatches(queryName, val)) {
+ foundValue = val;
+ }
+ });
+ if (resisting) {
+ ok(!foundValue, queryName + " should have no match");
+ } else {
+ ok(foundValue, foundValue ? ("Match found: '" + queryName + ":" + foundValue + "'")
+ : "Should have a match for '" + queryName + "'");
+ }
+};
+
+// __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 lines = "";
+ expected_values.forEach(
+ function ([key, offVal, onVal]) {
+ let val = resisting ? onVal : offVal;
+ if (val) {
+ lines += "<div class='spoof' id='" + key + "'>" + key + "</div>\n";
+ }
+ });
+ suppressed_toggles.forEach(
+ function (key) {
+ lines += "<div class='suppress' id='" + key + "'>" + key + "</div>\n";
+ });
+ if (OS === "WINNT") {
+ lines += "<div class='windows' id='-moz-os-version'>-moz-os-version</div>";
+ lines += "<div class='windows' id='-moz-windows-theme'>-moz-windows-theme</div>";
+ }
+ return lines;
+};
+
+// __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) {
+ lines += suppressedMediaQueryCSSLine(key, resisting ? "red" : "green");
+ });
+ if (OS === "WINNT") {
+ lines += ".windows { background-color: " + (resisting ? "green" : "red") + ";}\n";
+ lines += windows_versions.map(val => "(-moz-os-version: " + val + ")").join(", ") +
+ " { #-moz-os-version { background-color: " + (resisting ? "red" : "green") + ";} }\n";
+ lines += windows_themes.map(val => "(-moz-windows-theme: " + val + ")").join(",") +
+ " { #-moz-windows-theme { background-color: " + (resisting ? "red" : "green") + ";} }\n";
+ }
+ return lines;
+};
+
+// __green__.
+// Returns the computed color style corresponding to green.
+var green = (function () {
+ let temp = document.createElement("span");
+ temp.style.backgroundColor = "green";
+ return getComputedStyle(temp).backgroundColor;
+})();
+
+// __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").innerHTML = generateHtmlLines(resisting);
+ document.getElementById("test-css").innerHTML = 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.createElement("div");
+ div.style.MozOsxFontSmoothing = "unset";
+ let readBack = window.getComputedStyle(div).MozOsxFontSmoothing;
+ 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. A generator function
+// to be used with SpawnTask.js.
+var testMediaQueriesInPictureElements = function* (resisting) {
+ let lines = "";
+ for (let [key, offVal, onVal] of expected_values) {
+ let expected = resisting ? onVal : offVal;
+ if (expected) {
+ let query = constructQuery(key, expected);
+ lines += "<picture>\n";
+ lines += " <source srcset='/tests/layout/style/test/chrome/match.png' media='" + query + "' />\n";
+ lines += " <img title='" + key + ":" + expected + "' class='testImage' src='/tests/layout/style/test/chrome/mismatch.png' alt='" + key + "' />\n";
+ lines += "</picture><br/>\n";
+ }
+ }
+ document.getElementById("pictures").innerHTML = lines;
+ var testImages = document.getElementsByClassName("testImage");
+ yield sleep(0);
+ for (let testImage of testImages) {
+ ok(testImage.currentSrc.endsWith("/match.png"), "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. A generator function to be used
+// with SpawnTask.js.
+var test = function* (isContent) {
+ for (prefValue of [false, true]) {
+ yield pushPref("privacy.resistFingerprinting", prefValue);
+ let resisting = prefValue && isContent;
+ expected_values.forEach(
+ function ([key, offVal, onVal]) {
+ testMatch(key, resisting ? onVal : offVal);
+ });
+ testToggles(resisting);
+ if (OS === "WINNT") {
+ testWindowsSpecific(resisting, "-moz-os-version", windows_versions);
+ testWindowsSpecific(resisting, "-moz-windows-theme", windows_themes);
+ }
+ testCSS(resisting);
+ if (OS === "Darwin") {
+ testOSXFontSmoothing(resisting);
+ }
+ yield 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 000000000..bda339f77
--- /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 000000000..e4395da3f
--- /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.xul b/layout/style/test/chrome/bug535806-xul.xul
new file mode 100644
index 000000000..3d9a82b91
--- /dev/null
+++ b/layout/style/test/chrome/bug535806-xul.xul
@@ -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.ini b/layout/style/test/chrome/chrome.ini
new file mode 100644
index 000000000..e34fce671
--- /dev/null
+++ b/layout/style/test/chrome/chrome.ini
@@ -0,0 +1,21 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ bug418986-2.js
+ bug535806-css.css
+ bug535806-html.html
+ bug535806-xul.xul
+ hover_helper.html
+ match.png
+ mismatch.png
+
+[test_author_specified_style.html]
+[test_bug418986-2.xul]
+[test_bug1157097.html]
+[test_bug1160724.xul]
+[test_bug535806.xul]
+[test_display_mode.html]
+[test_display_mode_reflow.html]
+tags = fullscreen
+[test_hover.html]
+[test_moz_document_rules.html]
diff --git a/layout/style/test/chrome/hover_empty.html b/layout/style/test/chrome/hover_empty.html
new file mode 100644
index 000000000..7879e1ce9
--- /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 000000000..37e50f69e
--- /dev/null
+++ b/layout/style/test/chrome/hover_helper.html
@@ -0,0 +1,270 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for :hover</title>
+ <script type="application/javascript" 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, false);
+ setTimeout(str, 100);
+ };
+ iframe.contentWindow.addEventListener("resize", handler, false);
+}
+
+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, "transparent",
+ ":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, "transparent",
+ ":hover does not apply");
+ is(getComputedStyle(divone.firstChild, "").backgroundColor, "transparent",
+ ":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, "transparent",
+ ":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, "transparent",
+ ":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, "transparent",
+ ":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, "transparent",
+ ":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, "transparent",
+ ":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, "transparent",
+ ":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/match.png b/layout/style/test/chrome/match.png
new file mode 100644
index 000000000..d3f299bf5
--- /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 000000000..8f9da3f00
--- /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 000000000..8b331b19e
--- /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_author_specified_style.html b/layout/style/test/chrome/test_author_specified_style.html
new file mode 100644
index 000000000..6b5e4f30e
--- /dev/null
+++ b/layout/style/test/chrome/test_author_specified_style.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>Test for CSSStyleDeclaration.getAuthoredPropertyValue()</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<script>
+var values = [
+ // specified value // returned from getAuthoredPropertyValue()
+ "#12F", "#12f",
+ "#1122FF", "#1122ff",
+ "rgb(10,20,30)", "rgb(10, 20, 30)",
+ "Rgb(300,20,30)", "rgb(255, 20, 30)",
+ "rgba(10,20,30,0.250)", "rgba(10, 20, 30, 0.25)",
+ "OrangeRed", "OrangeRed",
+ "rgb(10%,25%,99%)", "rgb(10%, 25%, 99%)",
+ "rgb(6.66667%,0%,0.0%)", "rgb(6.66667%, 0%, 0%)",
+ "HSL(0,25%,75%)", "hsl(0, 25%, 75%)",
+ "hsl(60,0%,0%)", "hsl(60, 0%, 0%)",
+ "hsla(60,50%,50%,0.1250)", "hsla(60, 50%, 50%, 0.125)",
+ "rgba(0,0,0,0)", "rgba(0, 0, 0, 0)",
+ "rgba(50,50,50,1)", "rgb(50, 50, 50)",
+ "rgba(50%,50%,50%,1)", "rgb(50%, 50%, 50%)",
+ "hsla(0,25%,75%,1)", "hsl(0, 25%, 75%)",
+];
+
+var properties = [
+ // property to test with // fixed suffix to ignore from getAuthoredPropertyValue()
+ "color", "",
+ "background-color", "",
+ "background", " none repeat scroll 0% 0%"
+];
+
+function runTest() {
+ var span = document.createElement("span");
+ for (var j = 0; j < properties.length; j += 2) {
+ var propertyName = properties[j];
+ var expectedSuffix = properties[j + 1];
+ for (var i = 0; i < values.length; i += 2) {
+ var value = values[i];
+ var expected = values[i + 1];
+ span.setAttribute("style", propertyName + ": " + value);
+ is(span.style.getAuthoredPropertyValue(propertyName), expected + expectedSuffix, "specified " + value);
+ }
+ }
+
+ // also test a custom property
+ span.setAttribute("style", "--color: rgb(10%,25%,99%)");
+ is(span.style.getAuthoredPropertyValue("--color"), " rgb(10%,25%,99%)", "specified --color");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] },
+ runTest);
+</script>
diff --git a/layout/style/test/chrome/test_bug1157097.html b/layout/style/test/chrome/test_bug1157097.html
new file mode 100644
index 000000000..748a9eed2
--- /dev/null
+++ b/layout/style/test/chrome/test_bug1157097.html
@@ -0,0 +1,31 @@
+<!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>
+var Ci = Components.interfaces;
+var windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+function run() {
+ windowUtils.postRestyleSelfEvent(document.querySelector("p"));
+ document.querySelectorAll("span")[0].className = "";
+ document.querySelectorAll("b")[0].className = "inline-block";
+ document.querySelectorAll("span")[1].className = "blue";
+ 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_bug1160724.xul b/layout/style/test/chrome/test_bug1160724.xul
new file mode 100644
index 000000000..8a2b48617
--- /dev/null
+++ b/layout/style/test/chrome/test_bug1160724.xul
@@ -0,0 +1,76 @@
+<?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"?>
+
+<?xml-stylesheet href="data:text/css,:root{--test:9px}" type="text/css"?>
+
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1160724
+-->
+<window title="Mozilla Bug 1160724" onload="test()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1160724"
+ target="_blank">Mozilla Bug 1160724</a>
+ </body>
+
+ <script type="application/javascript">
+ <![CDATA[
+ var errorLogged = false;
+ const serv = Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService);
+ var listener = {
+ QueryInterface(iid) {
+ if (!iid.equals(Components.interfaces.nsISupports) &&
+ !iid.equals(Components.interfaces.nsIConsoleListener)) {
+ throw Components.results.NS_NOINTERFACE;
+ }
+ return this;
+ },
+
+ observe(msg) {
+ if (msg.toString().indexOf("transform") != -1) {
+ errorLogged = true;
+ }
+ }
+ };
+ serv.registerListener(listener);
+ ]]>
+ </script>
+
+ <vbox id="w" style="-moz-binding: url(#binding)">
+ <vbox id="v" style="display: none; transform: translateY(var(--test));" />
+ </vbox>
+
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="binding">
+ <implementation>
+ <constructor>this.firstChild</constructor>
+ </implementation>
+ </binding>
+ </bindings>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 1160724 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function test() {
+ var v = document.getElementById("v");
+ is(getComputedStyle(v, "").transform, "matrix(1, 0, 0, 1, 0, 9)");
+
+ // nsIConsoleListeners are notified by a runnable.
+ setTimeout(() => {
+ ok(!errorLogged, "Should be no errors");
+ serv.unregisterListener(listener);
+ SimpleTest.finish();
+ })
+ }
+ ]]>
+ </script>
+</window>
diff --git a/layout/style/test/chrome/test_bug418986-2.xul b/layout/style/test/chrome/test_bug418986-2.xul
new file mode 100644
index 000000000..2e5f8e687
--- /dev/null
+++ b/layout/style/test/chrome/test_bug418986-2.xul
@@ -0,0 +1,30 @@
+<?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 type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.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;version=1.7" src="bug418986-2.js"></script>
+ <!-- test code goes here -->
+ <script type="text/javascript;version=1.7">
+ // Run all tests now.
+ window.onload = function () {
+ add_task(function* () {
+ yield test(false);
+ });
+ };
+ </script>
+</window>
diff --git a/layout/style/test/chrome/test_bug535806.xul b/layout/style/test/chrome/test_bug535806.xul
new file mode 100644
index 000000000..1c0b45c63
--- /dev/null
+++ b/layout/style/test/chrome/test_bug535806.xul
@@ -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 type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=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");
+ }, false);
+
+ function htmlLoaded() {
+ $("f").setAttribute("src", "bug535806-xul.xul");
+ }
+
+ function xulLoaded() {
+ var doc = $("f").contentDocument;
+ is(doc.defaultView.getComputedStyle(doc.getElementById("s"), null).color,
+ "rgb(0, 128, 0)");
+ SimpleTest.finish();
+ }
+
+
+ ]]>
+ </script>
+</window>
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 000000000..244eefea2
--- /dev/null
+++ b/layout/style/test/chrome/test_display_mode.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1104916
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.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">
+
+/** Test for Display Mode **/
+SimpleTest.waitForExplicitFinish();
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function waitOneEvent(element, name) {
+ return new Promise(function(resolve, reject) {
+ element.addEventListener(name, function listener() {
+ element.removeEventListener(name, listener);
+ resolve();
+ });
+ });
+}
+
+add_task(function* () {
+ yield waitOneEvent(window, "load");
+
+ 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") == "underline";
+ }
+
+ function shouldApply(q) {
+ ok(queryApplies(q), q + " should apply");
+ }
+
+ function shouldNotApply(q) {
+ ok(!queryApplies(q), q + " should not apply");
+ }
+
+ 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("VK_F11", {});
+ yield fullScreenEntered;
+ shouldApply("all and (display-mode: fullscreen)");
+ shouldNotApply("all and (display-mode: browser)");
+ var fullScreenExited = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("VK_F11", {});
+ yield fullScreenExited;
+ 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();
+ yield 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();
+ yield fullScreenExited;
+ ok(!document.mozFullScreenElement, "window exited fullscreen");
+ shouldNotApply("all and (display-mode: fullscreen)");
+ shouldApply("all and (display-mode: browser)");
+});
+ </script>
+</head>
+<body>
+<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/media_queries_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/test_display_mode_reflow.html b/layout/style/test/chrome/test_display_mode_reflow.html
new file mode 100644
index 000000000..23546578f
--- /dev/null
+++ b/layout/style/test/chrome/test_display_mode_reflow.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1256084
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.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">
+
+/** Test for Display Mode **/
+SimpleTest.waitForExplicitFinish();
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function waitOneEvent(element, name) {
+ return new Promise(function(resolve, reject) {
+ element.addEventListener(name, function listener() {
+ element.removeEventListener(name, listener);
+ resolve();
+ });
+ });
+}
+
+add_task(function* () {
+ yield waitOneEvent(window, "load");
+
+ 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("VK_F11", {});
+ yield fullScreenEntered;
+ ok(offsetTop !== secondDiv.offsetTop, "offset top changes");
+ var fullScreenExited = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("VK_F11", {});
+ yield fullScreenExited;
+ 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();
+ yield fullScreenEntered
+ ok(offsetTop !== secondDiv.offsetTop, "offset top changes");
+ fullScreenExited = waitOneEvent(document, "mozfullscreenchange");
+ document.mozCancelFullScreen();
+ yield fullScreenExited;
+ ok(offsetTop === secondDiv.offsetTop, "offset top returns to original value");
+});
+ </script>
+</head>
+<body>
+<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/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/test_hover.html b/layout/style/test/chrome/test_hover.html
new file mode 100644
index 000000000..192562a8b
--- /dev/null
+++ b/layout/style/test/chrome/test_hover.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for :hover</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body 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 000000000..87fba4055
--- /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 type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body 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; version=1.8">
+
+var [gStyleSheetService, gIOService] = (function() {
+ return [
+ Components.classes["@mozilla.org/content/style-sheet-service;1"]
+ .getService(Components.interfaces.nsIStyleSheetService),
+ Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ ];
+})();
+function set_user_sheet(sheeturi)
+{
+ var uri = gIOService.newURI(sheeturi, null, null);
+ gStyleSheetService.loadAndRegisterSheet(uri, gStyleSheetService.USER_SHEET);
+}
+function remove_user_sheet(sheeturi)
+{
+ var uri = gIOService.newURI(sheeturi, null, null);
+ 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/css_properties_like_longhand.js b/layout/style/test/css_properties_like_longhand.js
new file mode 100644
index 000000000..60b8fd583
--- /dev/null
+++ b/layout/style/test/css_properties_like_longhand.js
@@ -0,0 +1,3 @@
+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 000000000..da672d03b
--- /dev/null
+++ b/layout/style/test/descriptor_database.js
@@ -0,0 +1,72 @@
+/* -*- 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" ],
+ invalid_values: [ "107", "399", "401", "699", "710", "bolder", "lighter" ]
+ },
+ "src": {
+ domProp: null,
+ values: [
+ "url(404.ttf)",
+ "url(\"404.eot\")",
+ "url(\'404.otf\')",
+ "url(404.ttf) format(\"truetype\")",
+ "url(404.ttf) format(\"truetype\", \"opentype\")",
+ "url(404.ttf) format(\"truetype\", \"opentype\"), url(\'404.eot\')",
+ "local(Times New Roman)",
+ "local(\'Times New Roman\')",
+ "local(\"Times New Roman\")",
+ "local(\"serif\")",
+ "url(404.ttf) format(\"truetype\", \"unknown\"), local(Times New Roman), url(\'404.eot\')",
+ ],
+ invalid_values: [
+ "url(404.ttf) format(truetype)",
+ "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",
+ ]
+ },
+ "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+100-17F,U+200-17F", "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,200-17F", "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/display_mode_reflow_iframe.html b/layout/style/test/display_mode_reflow_iframe.html
new file mode 100644
index 000000000..c05880ce7
--- /dev/null
+++ b/layout/style/test/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/empty.html b/layout/style/test/empty.html
new file mode 100644
index 000000000..734c5a1c0
--- /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 000000000..9d4dfa1fe
--- /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_effect_timing_duration.html b/layout/style/test/file_animations_effect_timing_duration.html
new file mode 100644
index 000000000..545784665
--- /dev/null
+++ b/layout/style/test/file_animations_effect_timing_duration.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <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>
+ <script>
+ var ok = opener.ok.bind(opener);
+ var is = opener.is.bind(opener);
+ var todo = opener.todo.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(function() {
+ finish();
+ });
+}, finish, opener.SpecialPowers);
+
+addAsyncAnimTest(function *() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)', easing: "steps(2, start)" },
+ { transform: 'translate(100px)' } ], 4000);
+ yield waitForPaints();
+
+ advance_clock(500);
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ animation.effect.timing.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);
+
+ yield 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(function *() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)', easing: "steps(2, end)" },
+ { transform: 'translate(100px)' } ], 4000);
+ yield waitForPaints();
+
+ advance_clock(1000);
+ animation.effect.timing.duration = 2000;
+ advance_clock(0);
+ yield 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/file_animations_effect_timing_enddelay.html b/layout/style/test/file_animations_effect_timing_enddelay.html
new file mode 100644
index 000000000..bd7c5084f
--- /dev/null
+++ b/layout/style/test/file_animations_effect_timing_enddelay.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <style type="text/css">
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+ <script>
+ var ok = opener.ok.bind(opener);
+ var is = opener.is.bind(opener);
+ var todo = opener.todo.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(function() {
+ finish();
+ });
+}, finish, opener.SpecialPowers);
+
+addAsyncAnimTest(function *() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, fill: 'none' });
+ yield waitForPaints();
+
+ advance_clock(100);
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ animation.effect.timing.endDelay = 1000;
+
+ yield waitForPaints();
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "Animation remains on compositor when endDelay is changed");
+
+ advance_clock(1000);
+ yield waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is updated on main thread");
+
+ done_div();
+});
+
+addAsyncAnimTest(function *() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, endDelay: -500, fill: 'none' });
+ yield waitForPaints();
+
+ advance_clock(400);
+ yield 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);
+ yield 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);
+ yield 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);
+ yield 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(function *() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, endDelay: 1000, fill: 'forwards' });
+ yield waitForPaints();
+
+ advance_clock(1500);
+ yield waitForPaints();
+ omta_is(div, "transform", { tx: 100 }, RunningOn.MainThread,
+ "The end delay is performed on the main thread");
+
+ done_div();
+});
+
+addAsyncAnimTest(function *() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, endDelay: -500, fill: 'forwards' });
+ yield waitForPaints();
+
+ advance_clock(400);
+ yield 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);
+ yield 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);
+ yield 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);
+ yield 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/file_animations_effect_timing_iterations.html b/layout/style/test/file_animations_effect_timing_iterations.html
new file mode 100644
index 000000000..1c1c63f90
--- /dev/null
+++ b/layout/style/test/file_animations_effect_timing_iterations.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <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>
+ <script>
+ var ok = opener.ok.bind(opener);
+ var is = opener.is.bind(opener);
+ var todo = opener.todo.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(function() {
+ finish();
+ });
+}, finish, opener.SpecialPowers);
+
+addAsyncAnimTest(function *() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' },
+ { transform: 'translate(100px)' } ],
+ { duration: 4000,
+ iterations: 2
+ });
+ yield waitForPaints();
+
+ advance_clock(6000);
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ animation.effect.timing.iterations = 1;
+ advance_clock(0);
+
+ yield waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is on MainThread");
+
+ animation.effect.timing.iterations = 3;
+
+ advance_clock(0);
+ yield 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/file_animations_iterationstart.html b/layout/style/test/file_animations_iterationstart.html
new file mode 100644
index 000000000..d1d8529ce
--- /dev/null
+++ b/layout/style/test/file_animations_iterationstart.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <style type="text/css">
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+ <script>
+ var ok = opener.ok.bind(opener);
+ var is = opener.is.bind(opener);
+ var todo = opener.todo.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(function() {
+ finish();
+ });
+}, finish, opener.SpecialPowers);
+
+
+addAsyncAnimTest(function *() {
+ var [ div ] = new_div("test");
+ var animation = div.animate(
+ { transform: ["translate(0px)", "translate(100px)"] },
+ { iterationStart: 0.5, duration: 10000, fill: "both"}
+ );
+ yield waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, "Start of Animation");
+
+ advance_clock(4000);
+ yield waitForPaints();
+ omta_is(div, "transform", { tx: 90 }, RunningOn.Compositor, "40% of Animation");
+
+ advance_clock(6000);
+ yield waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread, "End of Animation");
+
+ done_div();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/file_animations_pausing.html b/layout/style/test/file_animations_pausing.html
new file mode 100644
index 000000000..ce4a639c5
--- /dev/null
+++ b/layout/style/test/file_animations_pausing.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <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>
+ <script>
+ var ok = opener.ok.bind(opener);
+ var is = opener.is.bind(opener);
+ var todo = opener.todo.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(function() {
+ finish();
+ });
+}, finish, opener.SpecialPowers);
+
+addAsyncAnimTest(function *() {
+ var [ div, cs ] = new_div("animation: anim 10s 2 linear alternate");
+
+ // Animation is initially running on compositor
+ yield 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);
+ yield 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);
+ yield 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/file_animations_playbackrate.html b/layout/style/test/file_animations_playbackrate.html
new file mode 100644
index 000000000..93206594e
--- /dev/null
+++ b/layout/style/test/file_animations_playbackrate.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <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>
+ <script>
+ var ok = opener.ok.bind(opener);
+ var is = opener.is.bind(opener);
+ var todo = opener.todo.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(function() {
+ finish();
+ });
+}, finish, opener.SpecialPowers);
+
+addAsyncAnimTest(function *() {
+ var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards");
+ var animation = div.getAnimations()[0];
+ animation.playbackRate = 10;
+
+ advance_clock(300);
+
+ yield waitForPaints();
+ omta_is(div, "transform", { tx: 30 }, RunningOn.Compositor,
+ "at 300ms");
+ done_div();
+});
+
+addAsyncAnimTest(function *() {
+ var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards");
+ var animation = div.getAnimations()[0];
+ advance_clock(300);
+ yield waitForPaints();
+
+ animation.playbackRate = 0;
+
+ yield 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(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)
+
+ yield 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(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)
+
+ yield 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/file_animations_styles_on_event.html b/layout/style/test/file_animations_styles_on_event.html
new file mode 100644
index 000000000..b9cdb430c
--- /dev/null
+++ b/layout/style/test/file_animations_styles_on_event.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript"
+ src="animation_utils.js"></script>
+ <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>
+ <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>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+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();
+ finish();
+ }, false);
+
+ 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/file_animations_with_disabled_properties.html b/layout/style/test/file_animations_with_disabled_properties.html
new file mode 100644
index 000000000..4b69c7f60
--- /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;
+ -webkit-text-fill-color: green;
+ }
+ to {
+ left: 100px;
+ -webkit-text-fill-color: blue;
+ }
+ }
+ </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('webkitTextFillColor'),
+ '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('webkitTextFillColor'),
+ '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 000000000..3223a5690
--- /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 000000000..95208dbc5
--- /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_bug645998-1.css b/layout/style/test/file_bug645998-1.css
new file mode 100644
index 000000000..328e6ed79
--- /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 000000000..2d5edbe21
--- /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 000000000..8f12ba6f5
--- /dev/null
+++ b/layout/style/test/file_bug829816.css
Binary files differ
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 000000000..51dbbbee9
--- /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_transitions_replacement_on_busy_frame.html b/layout/style/test/file_transitions_replacement_on_busy_frame.html
new file mode 100644
index 000000000..c1678ab31
--- /dev/null
+++ b/layout/style/test/file_transitions_replacement_on_busy_frame.html
@@ -0,0 +1,93 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1167519
+-->
+<head>
+ <meta charset=utf-8>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/paint_listener.js"></script>
+ <style>
+ #target {
+ height: 100px;
+ width: 100px;
+ background: green;
+ transition: transform 100s linear;
+ }
+ </style>
+</head>
+<body>
+<div id="target"></div>
+<script>
+'use strict';
+
+var ok = opener.ok.bind(opener);
+var isnot = opener.isnot.bind(opener);
+
+function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+}
+
+var OMTAPrefKey = "layers.offmainthreadcomposition.async-animations";
+var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
+ opener.SpecialPowers.getBoolPref(OMTAPrefKey);
+window.addEventListener("load", function() {
+ if (!omtaEnabled) {
+ ok(true, "Skipping the test since OMTA is disabled");
+ finish();
+ return;
+ }
+
+ var div = document.getElementById("target");
+ // Start first transition
+ div.style.transform = "translateX(300px)";
+ getComputedStyle(div);
+
+ // Wait for a paint to ensure that the first transition has started.
+ waitForAllPaints(function() {
+ var previousPropertyValue;
+ var previousKeyframeValue;
+ var anim;
+ requestAnimationFrame(function() {
+ // Start second transition
+ div.style.transform = "translateX(0px)";
+ getComputedStyle(div).transform;
+
+ anim = div.getAnimations()[0];
+ var properties = SpecialPowers.wrap(anim.effect).getProperties();
+ previousPropertyValue = properties[0].values[0].value;
+ previousKeyframeValue = anim.effect.getKeyframes()[0].transform;
+ });
+
+ requestAnimationFrame(function() {
+ // 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.
+ var 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).
+ waitForAllPaints(function() {
+ var properties = SpecialPowers.wrap(anim.effect).getProperties();
+ var currentPropertyValue = properties[0].values[0].value;
+ isnot(currentPropertyValue, previousPropertyValue,
+ "From value of transition is updated since the moment when " +
+ "it was generated");
+ isnot(anim.effect.getKeyframes()[0].transform, previousKeyframeValue,
+ "Keyframe value of transition is updated since the moment when " +
+ "it was generated");
+ finish();
+ });
+ });
+ });
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/file_transitions_with_disabled_properties.html b/layout/style/test/file_transitions_with_disabled_properties.html
new file mode 100644
index 000000000..75305f09b
--- /dev/null
+++ b/layout/style/test/file_transitions_with_disabled_properties.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<head>
+ <meta charset=utf-8>
+ <style>
+ #display {
+ transition: all 0.01s;
+ }
+ </style>
+ <script>
+ 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';
+
+/*
+ * This tests for transitions generated on the -webkit-text-fill-color property.
+ * This property has an initial value of 'currentcolor' so by triggering a
+ * transition on the 'color' property we also--at least at the point when
+ * this test was written--trigger a transition on the -webkit-text-fill-color
+ * property (that behavior may change in bug 1260543).
+ *
+ * However, before beginning the test we disable -webkit-text-fill-color by
+ * setting layout.css.prefixes.webkit to false. This code tests that we don't
+ * end up triggering a transition on the (disabled) property in that case.
+ */
+
+var display = document.getElementById('display');
+display.style.color = 'green';
+
+var transitionedProperties =
+ display.getAnimations().map(transition => transition.transitionProperty);
+
+ok(!transitionedProperties.includes('-webkit-text-fill-color'),
+ 'We should not fire transitions for properties disabled by prefs');
+
+finish();
+</script>
+</body>
diff --git a/layout/style/test/flexbox_layout_testcases.js b/layout/style/test/flexbox_layout_testcases.js
new file mode 100644
index 000000000..719583630
--- /dev/null
+++ b/layout/style/test/flexbox_layout_testcases.js
@@ -0,0 +1,1398 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et: */
+
+/*
+ * This Source Code is subject to the terms of the Mozilla Public License
+ * version 2.0 (the "License"). You can obtain a copy of the License 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 000000000..7196c5e71
--- /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/.
+
+from __future__ import print_function
+
+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/media_queries_dynamic_xbl_binding.xml b/layout/style/test/media_queries_dynamic_xbl_binding.xml
new file mode 100644
index 000000000..4b8515539
--- /dev/null
+++ b/layout/style/test/media_queries_dynamic_xbl_binding.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="binding">
+ <resources>
+ <stylesheet src="media_queries_dynamic_xbl_style.css" />
+ </resources>
+ <content>
+ <html:div xmlns:html="http://www.w3.org/1999/xhtml">
+ <children/>
+ </html:div>
+ </content>
+ </binding>
+</bindings>
diff --git a/layout/style/test/media_queries_dynamic_xbl_iframe.html b/layout/style/test/media_queries_dynamic_xbl_iframe.html
new file mode 100644
index 000000000..50e8008fa
--- /dev/null
+++ b/layout/style/test/media_queries_dynamic_xbl_iframe.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<style type="text/css">
+body { -moz-binding: url(media_queries_dynamic_xbl_binding.xml#binding); }
+</style>
+<p id="para">Hello</p>
diff --git a/layout/style/test/media_queries_dynamic_xbl_style.css b/layout/style/test/media_queries_dynamic_xbl_style.css
new file mode 100644
index 000000000..5c99c07ee
--- /dev/null
+++ b/layout/style/test/media_queries_dynamic_xbl_style.css
@@ -0,0 +1,6 @@
+@media (orientation: portrait) {
+ div { color: purple; }
+}
+@media (orientation: landscape) {
+ div { color: blue; }
+}
diff --git a/layout/style/test/media_queries_iframe.html b/layout/style/test/media_queries_iframe.html
new file mode 100644
index 000000000..141ecdcd9
--- /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/mochitest.ini b/layout/style/test/mochitest.ini
new file mode 100644
index 000000000..406c6f901
--- /dev/null
+++ b/layout/style/test/mochitest.ini
@@ -0,0 +1,312 @@
+[DEFAULT]
+support-files =
+ animation_utils.js
+ ccd-quirks.html
+ ccd.sjs
+ ccd-standards.html
+ chrome/bug418986-2.js
+ chrome/match.png
+ chrome/mismatch.png
+ descriptor_database.js
+ display_mode_reflow_iframe.html
+ empty.html
+ media_queries_dynamic_xbl_binding.xml
+ media_queries_dynamic_xbl_iframe.html
+ media_queries_dynamic_xbl_style.css
+ media_queries_iframe.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
+ 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
+ xbl_bindings.xml
+
+[test_acid3_test46.html]
+[test_addSheet.html]
+support-files = additional_sheets_helper.html
+[test_additional_sheets.html]
+support-files = additional_sheets_helper.html
+[test_all_shorthand.html]
+[test_animations.html]
+skip-if = toolkit == 'android'
+[test_animations_async_tests.html]
+support-files = ../../reftests/fonts/Ahem.ttf file_animations_async_tests.html
+[test_animations_dynamic_changes.html]
+[test_animations_effect_timing_duration.html]
+support-files = file_animations_effect_timing_duration.html
+[test_animations_effect_timing_enddelay.html]
+support-files = file_animations_effect_timing_enddelay.html
+[test_animations_effect_timing_iterations.html]
+support-files = file_animations_effect_timing_iterations.html
+[test_animations_event_order.html]
+[test_animations_event_handler_attribute.html]
+[test_animations_iterationstart.html]
+support-files = file_animations_iterationstart.html
+[test_animations_omta.html]
+[test_animations_omta_start.html]
+[test_animations_pausing.html]
+support-files = file_animations_pausing.html
+[test_animations_playbackrate.html]
+support-files = file_animations_playbackrate.html
+[test_animations_styles_on_event.html]
+support-files = file_animations_styles_on_event.html
+[test_animations_with_disabled_properties.html]
+support-files = file_animations_with_disabled_properties.html
+[test_any_dynamic.html]
+[test_at_rule_parse_serialize.html]
+[test_attribute_selector_eof_behavior.html]
+[test_background_blend_mode.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]
+skip-if = toolkit == 'android'
+[test_bug379741.html]
+[test_bug382027.html]
+[test_bug383075.html]
+[test_bug387615.html]
+[test_bug389464.html]
+[test_bug391034.html]
+[test_bug391221.html]
+[test_bug397427.html]
+[test_bug399349.html]
+[test_bug401046.html]
+skip-if = true # Bug 701060
+[test_bug405818.html]
+[test_bug412901.html]
+skip-if = android_version == '18' # bug 1147986
+[test_bug413958.html]
+[test_bug418986-2.html]
+[test_bug437915.html]
+[test_bug450191.html]
+[test_bug453896_deck.html]
+support-files = bug453896_iframe.html
+[test_bug470769.html]
+[test_bug499655.html]
+[test_bug499655.xhtml]
+[test_bug511909.html]
+[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_bug652486.html]
+[test_bug657143.html]
+[test_bug664955.html]
+[test_bug667520.html]
+[test_bug645998.html]
+support-files = file_bug645998-1.css file_bug645998-2.css
+[test_bug716226.html]
+[test_bug732153.html]
+[test_bug732209.html]
+support-files = bug732209-css.sjs
+[test_bug765590.html]
+[test_bug771043.html]
+[test_bug795520.html]
+[test_bug798567.html]
+[test_bug798843_pref.html]
+[test_bug829816.html]
+[test_bug874919.html]
+support-files = file_bug829816.css
+[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_cascade.html]
+[test_ch_ex_no_infloops.html]
+[test_change_hint_optimizations.html]
+[test_clip-path_polygon.html]
+[test_compute_data_with_start_struct.html]
+skip-if = toolkit == 'android'
+[test_computed_style.html]
+[test_computed_style_min_size_auto.html]
+[test_computed_style_no_pseudo.html]
+[test_computed_style_prefs.html]
+[test_condition_text.html]
+[test_condition_text_assignment.html]
+[test_contain_formatting_context.html]
+[test_counter_descriptor_storage.html]
+[test_counter_style.html]
+[test_css_cross_domain.html]
+skip-if = toolkit == 'android' #bug 536603
+[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_supports.html]
+[test_css_supports_variables.html]
+[test_default_bidi_css.html]
+[test_default_computed_style.html]
+[test_descriptor_storage.html]
+[test_descriptor_syntax_errors.html]
+[test_dont_use_document_colors.html]
+[test_dynamic_change_causing_reflow.html]
+[test_exposed_prop_accessors.html]
+[test_extra_inherit_initial.html]
+[test_align_justify_computed_values.html]
+[test_flexbox_child_display_values.xhtml]
+[test_flexbox_flex_grow_and_shrink.html]
+[test_flexbox_flex_shorthand.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]
+[test_font_face_parser.html]
+[test_font_family_parsing.html]
+[test_font_feature_values_parsing.html]
+[test_font_loading_api.html]
+support-files =
+ BitPattern.woff
+ file_font_loading_api_vframe.html
+[test_garbage_at_end_of_declarations.html]
+[test_grid_container_shorthands.html]
+[test_grid_item_shorthands.html]
+[test_grid_shorthand_serialization.html]
+[test_grid_computed_values.html]
+[test_group_insertRule.html]
+[test_hover_quirk.html]
+[test_html_attribute_computed_values.html]
+[test_ident_escaping.html]
+[test_inherit_computation.html]
+skip-if = toolkit == 'android'
+[test_inherit_storage.html]
+[test_initial_computation.html]
+skip-if = toolkit == 'android'
+[test_initial_storage.html]
+[test_keyframes_rules.html]
+[test_load_events_on_stylesheets.html]
+[test_logical_properties.html]
+[test_media_queries.html]
+skip-if = android_version == '18' #debug-only failure; timed out #Android 4.3 aws only; bug 1030419
+[test_media_queries_dynamic.html]
+[test_media_queries_dynamic_xbl.html]
+[test_media_query_list.html]
+[test_moz_device_pixel_ratio.html]
+[test_namespace_rule.html]
+[test_of_type_selectors.xhtml]
+[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_pointer-events.html]
+[test_position_float_display.html]
+[test_position_sticky.html]
+[test_priority_preservation.html]
+[test_property_database.html]
+[test_property_syntax_errors.html]
+[test_pseudoelement_state.html]
+[test_pseudoelement_parsing.html]
+[test_redundant_font_download.html]
+support-files = redundant_font_download.sjs
+[test_rem_unit.html]
+[test_restyles_in_smil_animation.html]
+[test_root_node_display.html]
+[test_rule_insertion.html]
+[test_rule_serialization.html]
+[test_rules_out_of_sheets.html]
+[test_selectors.html]
+skip-if = toolkit == 'android' #bug 775227
+[test_selectors_on_anonymous_content.html]
+[test_setPropertyWithNull.html]
+[test_shorthand_property_getters.html]
+[test_specified_value_serialization.html]
+[test_style_attribute_quirks.html]
+[test_style_attribute_standards.html]
+[test_style_struct_copy_constructors.html]
+[test_supports_rules.html]
+[test_system_font_serialization.html]
+[test_text_decoration_shorthands.html]
+[test_transitions_and_reframes.html]
+[test_transitions_and_restyles.html]
+[test_transitions_and_zoom.html]
+[test_transitions_cancel_near_end.html]
+[test_transitions_computed_values.html]
+[test_transitions_computed_value_combinations.html]
+[test_transitions_events.html]
+[test_transitions.html]
+skip-if = (android_version == '18' && debug) # bug 1159532
+[test_transitions_bug537151.html]
+[test_transitions_dynamic_changes.html]
+[test_transitions_per_property.html]
+skip-if = toolkit == 'android' #bug 775227
+[test_transitions_replacement_on_busy_frame.html]
+support-files = file_transitions_replacement_on_busy_frame.html
+[test_transitions_step_functions.html]
+[test_transitions_with_disabled_properties.html]
+support-files = file_transitions_with_disabled_properties.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_unprefixing_service.html]
+support-files = unprefixing_service_iframe.html unprefixing_service_utils.js
+[test_unprefixing_service_prefs.html]
+support-files = unprefixing_service_iframe.html unprefixing_service_utils.js
+[test_value_cloning.html]
+skip-if = toolkit == 'android' #bug 775227
+[test_value_computation.html]
+skip-if = toolkit == 'android'
+[test_value_storage.html]
+[test_variable_serialization_computed.html]
+[test_variable_serialization_specified.html]
+[test_variables.html]
+support-files = support/external-variable-url.css
+[test_video_object_fit.html]
+[test_viewport_units.html]
+[test_visited_image_loading.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_visited_image_loading_empty.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_visited_lying.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_visited_pref.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_visited_reftests.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_webkit_device_pixel_ratio.html]
+[test_webkit_flex_display.html]
+[test_asyncopen2.html]
+[test_align_shorthand_serialization.html]
diff --git a/layout/style/test/moz.build b/layout/style/test/moz.build
new file mode 100644
index 000000000..ca61f607a
--- /dev/null
+++ b/layout/style/test/moz.build
@@ -0,0 +1,126 @@
+# -*- 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.
+#
+# TestCSSPropertyLookup.cpp needs the internal XPCOM APIs and so cannot
+# be built with libxul enabled.
+#
+#CPP_UNIT_TESTS = TestCSSPropertyLookup.cpp
+#LIBS += ../nsCSSKeywords.$(OBJ_SUFFIX) ../nsCSSProps.$(OBJ_SUFFIX) $(XPCOM_LIBS)
+
+HAS_MISC_RULE = True
+
+HostSimplePrograms([
+ 'host_ListCSSProperties',
+])
+
+MOCHITEST_MANIFESTS += [
+ 'mochitest.ini',
+]
+XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini']
+BROWSER_CHROME_MANIFESTS += ['browser.ini']
+MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini']
+
+TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test.chrome += [
+ 'chrome/moz_document_helper.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/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/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/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/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-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/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/svg/as-image/lime100x100.svg',
+ '/layout/reftests/svg/as-image/svg-image-visited-1-helper.svg',
+ '/layout/reftests/svg/as-image/svg-image-visited-2-helper.svg',
+ '/layout/reftests/svg/pseudo-classes-02-ref.svg',
+ '/layout/reftests/svg/pseudo-classes-02.svg',
+]
+
+DEFINES['MOZILLA_INTERNAL_API'] = True
+if CONFIG['MOZ_ENABLE_MASK_AS_SHORTHAND']:
+ HOST_DEFINES['MOZ_ENABLE_MASK_AS_SHORTHAND'] = True
+
+if CONFIG['COMPILE_ENVIRONMENT']:
+ GENERATED_FILES += ['css_properties.js']
+ GENERATED_FILES['css_properties.js'].script = 'gen-css-properties.py'
+ GENERATED_FILES['css_properties.js'].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/neverending_font_load.sjs b/layout/style/test/neverending_font_load.sjs
new file mode 100644
index 000000000..7bf419aaf
--- /dev/null
+++ b/layout/style/test/neverending_font_load.sjs
@@ -0,0 +1,6 @@
+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 000000000..386ffbe35
--- /dev/null
+++ b/layout/style/test/neverending_stylesheet_load.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response)
+{
+ response.processAsync();
+ response.setHeader("Content-Type", "text/css", false);
+ response.write("");
+}
diff --git a/layout/style/test/newtab_share_rule_processors.html b/layout/style/test/newtab_share_rule_processors.html
new file mode 100644
index 000000000..bdfed1145
--- /dev/null
+++ b/layout/style/test/newtab_share_rule_processors.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<style>
+p { color: blue; }
+</style>
+<p>Hello</p>
+<script>
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(Ci.nsIStyleSheetService);
+var io = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+var winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+function addAgentSheet() {
+ var sheetURI = io.newURI("data:text/css,p{background-color:yellow}", null, null);
+ var sheet = sss.preloadSheet(sheetURI, Ci.nsIStyleSheetService.AGENT_SHEET);
+ winUtils.addSheet(sheet, Ci.nsIDOMWindowUtils.AGENT_SHEET);
+}
+</script>
diff --git a/layout/style/test/post-redirect-1.css b/layout/style/test/post-redirect-1.css
new file mode 100644
index 000000000..c2ae5d6eb
--- /dev/null
+++ b/layout/style/test/post-redirect-1.css
@@ -0,0 +1 @@
+#one { color: green; background: url("#"); }
diff --git a/layout/style/test/post-redirect-2.css b/layout/style/test/post-redirect-2.css
new file mode 100644
index 000000000..0a75299ce
--- /dev/null
+++ b/layout/style/test/post-redirect-2.css
@@ -0,0 +1 @@
+#two { color: green; background: url("#"); }
diff --git a/layout/style/test/post-redirect-3.css b/layout/style/test/post-redirect-3.css
new file mode 100644
index 000000000..b33887ae3
--- /dev/null
+++ b/layout/style/test/post-redirect-3.css
@@ -0,0 +1 @@
+#three { color: green; background: url("#"); }
diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js
new file mode 100644
index 000000000..9c69e7d10
--- /dev/null
+++ b/layout/style/test/property_database.js
@@ -0,0 +1,7917 @@
+/* -*- 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/. */
+
+// 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;
+
+// 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.)
+// get_computed: if present, the property's computed value shows up on
+// another property, and this is a function used to get it
+// 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'.
+ var div = document.createElement("div");
+ div.setAttribute("style", "font: initial");
+ return getComputedStyle(div, "").fontFamily == "sans-serif";
+}
+var gInitialFontFamilyIsSansSerif = initial_font_family_is_sans_serif();
+
+// shared by background-image and border-image-source
+var validGradientAndElementValues = [
+ "-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(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)",
+
+ "-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(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(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(0, 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(-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-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))",
+
+ "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(43px, red, blue)",
+ "radial-gradient(43px 43px, red, blue)",
+ "radial-gradient(50% 50%, red, blue)",
+ "radial-gradient(43px 50%, red, blue)",
+ "radial-gradient(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)",
+
+ "-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(top left 45deg, red, blue)",
+ "-moz-radial-gradient(0 0, 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(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "-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(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-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(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-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(-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)",
+
+ "-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)",
+
+ "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)",
+
+ "-moz-repeating-radial-gradient(farthest-corner, red, blue)",
+ "-moz-repeating-radial-gradient(circle, red, blue)",
+ "-moz-repeating-radial-gradient(ellipse closest-corner, 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)",
+
+ "-moz-image-rect(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), 2, 10, 10, 2)",
+ "-moz-image-rect(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), 10%, 50%, 30%, 0%)",
+ "-moz-image-rect(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), 10, 50%, 30%, 0)",
+
+ "-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)"
+];
+var invalidGradientAndElementValues = [
+ "-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 */
+ "-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",
+ "linear-gradient(red -99, yellow, green, blue 120%)",
+ /* Unitless 0 is invalid as an <angle> */
+ "-moz-linear-gradient(top left 0, red, blue)",
+ "-moz-linear-gradient(5px 5px 0, red, blue)",
+ "linear-gradient(0, red, blue)",
+ /* Invalid color, calc() or -moz-image-rect() 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)",
+ /* 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(40%, -100px -10%, 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)",
+
+ "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)",
+
+ /* 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)",
+
+
+ "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)",
+
+ /* 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)"
+];
+var unbalancedGradientAndElementValues = [
+ "-moz-element(#a()",
+];
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.webkit")) {
+ // Extend gradient lists with valid/invalid webkit-prefixed expressions:
+ validGradientAndElementValues.push(
+ // 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)",
+ "-webkit-gradient(radial, 1 2, -1.5, center center, +99999.9999)",
+
+ // 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(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)",
+ "-webkit-linear-gradient(0 red, blue)",
+
+ // 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(left, 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%)"
+ );
+
+ invalidGradientAndElementValues.push(
+ // 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, calc(1) 2, 3 4)",
+ "-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
+
+ // 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, calc(8), 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()",
+
+ // 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)"
+ );
+}
+
+var gCSSProperties = {
+ "animation": {
+ domProp: "animation",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ 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" ]
+ },
+ "animation-delay": {
+ domProp: "animationDelay",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "0s", "0ms" ],
+ other_values: [ "1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s"],
+ invalid_values: [ "0", "0px" ]
+ },
+ "animation-direction": {
+ domProp: "animationDirection",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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" ]
+ },
+ "animation-duration": {
+ domProp: "animationDuration",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "0s", "0ms" ],
+ other_values: [ "1s", "250ms", "1s, 250ms, 2.3s"],
+ invalid_values: [ "0", "0px", "-1ms", "-2s" ]
+ },
+ "animation-fill-mode": {
+ domProp: "animationFillMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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,
+ initial_values: [ "1" ],
+ other_values: [ "infinite", "0", "0.5", "7.75", "-0.0", "1, 2, 3", "infinite, 2", "1, infinite" ],
+ // 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,
+ 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" ]
+ },
+ "animation-play-state": {
+ domProp: "animationPlayState",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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,
+ 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)" ],
+ 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)" ]
+ },
+ "-moz-appearance": {
+ domProp: "MozAppearance",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "radio", "menulist" ],
+ invalid_values: []
+ },
+ "-moz-binding": {
+ domProp: "MozBinding",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "url(foo.xml)" ],
+ invalid_values: []
+ },
+ "-moz-border-bottom-colors": {
+ domProp: "MozBorderBottomColors",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "red green", "red #fc3", "#ff00cc", "currentColor", "blue currentColor orange currentColor" ],
+ invalid_values: [ "red none", "red inherit", "red, green", "none red", "inherit red", "ff00cc" ]
+ },
+ "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-end-color": {
+ domProp: "borderInlineEndColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ initial_values: [ "currentColor" ],
+ other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ],
+ invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000" ]
+ },
+ "border-inline-end-style": {
+ domProp: "borderInlineEndStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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-end-width": {
+ domProp: "borderInlineEndWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ 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,
+ initial_values: [ "none" ],
+ other_values: [
+ "url('border.png')"
+ ].concat(validGradientAndElementValues),
+ invalid_values: [
+ "url('border.png') url('border.png')",
+ ].concat(invalidGradientAndElementValues),
+ unbalanced_values: [
+ ].concat(unbalancedGradientAndElementValues)
+ },
+ "border-image-slice": {
+ domProp: "borderImageSlice",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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,
+ 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" ],
+ 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,
+ 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,
+ 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" ]
+ },
+ "-moz-border-left-colors": {
+ domProp: "MozBorderLeftColors",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "red green", "red #fc3", "#ff00cc", "currentColor", "blue currentColor orange currentColor" ],
+ invalid_values: [ "red none", "red inherit", "red, green", "none red", "inherit red", "ff00cc" ]
+ },
+ "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)" ]
+ },
+ "border-bottom-left-radius": {
+ domProp: "borderBottomLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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)" ]
+ },
+ "border-bottom-right-radius": {
+ domProp: "borderBottomRightRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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)" ]
+ },
+ "border-top-left-radius": {
+ domProp: "borderTopLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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)" ]
+ },
+ "border-top-right-radius": {
+ domProp: "borderTopRightRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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)" ]
+ },
+ "-moz-border-right-colors": {
+ domProp: "MozBorderRightColors",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "red green", "red #fc3", "#ff00cc", "currentColor", "blue currentColor orange currentColor" ],
+ invalid_values: [ "red none", "red inherit", "red, green", "none red", "inherit red", "ff00cc" ]
+ },
+ "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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ 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-border-top-colors": {
+ domProp: "MozBorderTopColors",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "red green", "red #fc3", "#ff00cc", "currentColor", "blue currentColor orange currentColor" ],
+ invalid_values: [ "red none", "red inherit", "red, green", "none red", "inherit red", "ff00cc" ]
+ },
+ "-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-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" ],
+ },
+ "color-adjust": {
+ domProp: "colorAdjust",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "economy" ],
+ other_values: [ "exact" ],
+ invalid_values: []
+ },
+ "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" ]
+ },
+ "-moz-columns": {
+ domProp: "MozColumns",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "columns",
+ subproperties: [ "column-count", "column-width" ]
+ },
+ "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" ]
+ },
+ "-moz-column-count": {
+ domProp: "MozColumnCount",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "column-count",
+ subproperties: [ "column-count" ]
+ },
+ "column-fill": {
+ domProp: "columnFill",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "balance" ],
+ other_values: [ "auto" ],
+ invalid_values: [ "2px", "dotted", "5em" ]
+ },
+ "-moz-column-fill": {
+ domProp: "MozColumnFill",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "column-fill",
+ subproperties: [ "column-fill" ]
+ },
+ "column-gap": {
+ domProp: "columnGap",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "normal", "1em", "calc(-2em + 3em)" ],
+ other_values: [ "2px", "4em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0px)",
+ "calc(0pt)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: [ "3%", "-1px", "4" ]
+ },
+ "-moz-column-gap": {
+ domProp: "MozColumnGap",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "column-gap",
+ subproperties: [ "column-gap" ]
+ },
+ "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" ]
+ },
+ "-moz-column-rule": {
+ domProp: "MozColumnRule",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "column-rule",
+ subproperties: [ "column-rule-width", "column-rule-style", "column-rule-color" ]
+ },
+ "column-rule-width": {
+ domProp: "columnRuleWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { "-moz-column-rule-style": "solid" },
+ initial_values: [
+ "medium",
+ "3px",
+ "-moz-calc(3px)",
+ "-moz-calc(5em + 3px - 5em)",
+ "calc(3px)",
+ "calc(5em + 3px - 5em)",
+ ],
+ other_values: [ "thin", "15px",
+ /* valid -moz-calc() values */
+ "-moz-calc(-2px)",
+ "-moz-calc(2px)",
+ "-moz-calc(3em)",
+ "-moz-calc(3em + 2px)",
+ "-moz-calc( 3em + 2px)",
+ "-moz-calc(3em + 2px )",
+ "-moz-calc( 3em + 2px )",
+ "-moz-calc(3*25px)",
+ "-moz-calc(3 *25px)",
+ "-moz-calc(3 * 25px)",
+ "-moz-calc(3* 25px)",
+ "-moz-calc(25px*3)",
+ "-moz-calc(25px *3)",
+ "-moz-calc(25px* 3)",
+ "-moz-calc(25px * 3)",
+ "-moz-calc(25px * 3 / 4)",
+ "-moz-calc((25px * 3) / 4)",
+ "-moz-calc(25px * (3 / 4))",
+ "-moz-calc(3 * 25px / 4)",
+ "-moz-calc((3 * 25px) / 4)",
+ "-moz-calc(3 * (25px / 4))",
+ "-moz-calc(3em + 25px * 3 / 4)",
+ "-moz-calc(3em + (25px * 3) / 4)",
+ "-moz-calc(3em + 25px * (3 / 4))",
+ "-moz-calc(25px * 3 / 4 + 3em)",
+ "-moz-calc((25px * 3) / 4 + 3em)",
+ "-moz-calc(25px * (3 / 4) + 3em)",
+ "-moz-calc(3em + (25px * 3 / 4))",
+ "-moz-calc(3em + ((25px * 3) / 4))",
+ "-moz-calc(3em + (25px * (3 / 4)))",
+ "-moz-calc((25px * 3 / 4) + 3em)",
+ "-moz-calc(((25px * 3) / 4) + 3em)",
+ "-moz-calc((25px * (3 / 4)) + 3em)",
+ "-moz-calc(3*25px + 1in)",
+ "-moz-calc(1in - 3em + 2px)",
+ "-moz-calc(1in - (3em + 2px))",
+ "-moz-calc((1in - 3em) + 2px)",
+ "-moz-calc(50px/2)",
+ "-moz-calc(50px/(2 - 1))",
+ "-moz-calc(-3px)",
+ /* numeric reduction cases */
+ "-moz-calc(5 * 3 * 2em)",
+ "-moz-calc(2em * 5 * 3)",
+ "-moz-calc((5 * 3) * 2em)",
+ "-moz-calc(2em * (5 * 3))",
+ "-moz-calc((5 + 3) * 2em)",
+ "-moz-calc(2em * (5 + 3))",
+ "-moz-calc(2em / (5 + 3))",
+ "-moz-calc(2em * (5*2 + 3))",
+ "-moz-calc(2em * ((5*2) + 3))",
+ "-moz-calc(2em * (5*(2 + 3)))",
+
+ "-moz-calc((5 + 7) * 3em)",
+ "-moz-calc((5em + 3em) - 2em)",
+ "-moz-calc((5em - 3em) + 2em)",
+ "-moz-calc(2em - (5em - 3em))",
+ "-moz-calc(2em + (5em - 3em))",
+ "-moz-calc(2em - (5em + 3em))",
+ "-moz-calc(2em + (5em + 3em))",
+ "-moz-calc(2em + 5em - 3em)",
+ "-moz-calc(2em - 5em - 3em)",
+ "-moz-calc(2em + 5em + 3em)",
+ "-moz-calc(2em - 5em + 3em)",
+
+ "-moz-calc(2em / 4 * 3)",
+ "-moz-calc(2em * 4 / 3)",
+ "-moz-calc(2em * 4 * 3)",
+ "-moz-calc(2em / 4 / 3)",
+ "-moz-calc(4 * 2em / 3)",
+ "-moz-calc(4 / 3 * 2em)",
+
+ "-moz-calc((2em / 4) * 3)",
+ "-moz-calc((2em * 4) / 3)",
+ "-moz-calc((2em * 4) * 3)",
+ "-moz-calc((2em / 4) / 3)",
+ "-moz-calc((4 * 2em) / 3)",
+ "-moz-calc((4 / 3) * 2em)",
+
+ "-moz-calc(2em / (4 * 3))",
+ "-moz-calc(2em * (4 / 3))",
+ "-moz-calc(2em * (4 * 3))",
+ "-moz-calc(2em / (4 / 3))",
+ "-moz-calc(4 * (2em / 3))",
+
+ // Valid cases with unitless zero (which is never
+ // a length).
+ "-moz-calc(0 * 2em)",
+ "-moz-calc(2em * 0)",
+ "-moz-calc(3em + 0 * 2em)",
+ "-moz-calc(3em + 2em * 0)",
+ "-moz-calc((0 + 2) * 2em)",
+ "-moz-calc((2 + 0) * 2em)",
+ // And test zero lengths while we're here.
+ "-moz-calc(2 * 0px)",
+ "-moz-calc(0 * 0px)",
+ "-moz-calc(2 * 0em)",
+ "-moz-calc(0 * 0em)",
+ "-moz-calc(0px * 0)",
+ "-moz-calc(0px * 2)",
+
+ /* 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))",
+
+ // 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 -moz-calc() values */
+ "-moz-calc(2em+ 2px)",
+ "-moz-calc(2em +2px)",
+ "-moz-calc(2em+2px)",
+ "-moz-calc(2em- 2px)",
+ "-moz-calc(2em -2px)",
+ "-moz-calc(2em-2px)",
+ /* 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)",
+ "calc(min(5px))",
+ "-moz-max(5px)",
+ "calc(max(5px))",
+ "-moz-min(5px,2em)",
+ "calc(min(5px,2em))",
+ "-moz-max(5px,2em)",
+ "calc(max(5px,2em))",
+ "calc(50px/(2 - 2))",
+ "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)",
+ ]
+ },
+ "-moz-column-rule-width": {
+ domProp: "MozColumnRuleWidth",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "column-rule-width",
+ subproperties: [ "column-rule-width" ]
+ },
+ "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" ]
+ },
+ "-moz-column-rule-style": {
+ domProp: "MozColumnRuleStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "column-rule-style",
+ subproperties: [ "column-rule-style" ]
+ },
+ "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" ]
+ },
+ "-moz-column-rule-color": {
+ domProp: "MozColumnRuleColor",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "column-rule-color",
+ subproperties: [ "column-rule-color" ]
+ },
+ "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%" ]
+ },
+ "-moz-column-width": {
+ domProp: "MozColumnWidth",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "column-width",
+ subproperties: [ "column-width" ]
+ },
+ "-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: []
+ },
+ "-moz-image-region": {
+ domProp: "MozImageRegion",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "rect(3px 20px 15px 4px)", "rect(17px, 21px, 33px, 2px)" ],
+ invalid_values: [ "rect(17px, 21px, 33, 2px)" ]
+ },
+ "margin-inline-end": {
+ domProp: "marginInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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-type": {
+ domProp: "maskType",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "luminance" ],
+ other_values: [ "alpha" ],
+ invalid_values: [],
+ },
+ "-moz-outline-radius": {
+ domProp: "MozOutlineRadius",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"},
+ subproperties: [ "-moz-outline-radius-bottomleft", "-moz-outline-radius-bottomright", "-moz-outline-radius-topleft", "-moz-outline-radius-topright" ],
+ initial_values: [ "0", "0px", "calc(-2px)", "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" ]
+ },
+ "-moz-outline-radius-bottomleft": {
+ domProp: "MozOutlineRadiusBottomleft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"},
+ initial_values: [ "0", "0px", "calc(-2px)", "calc(0px)" ],
+ 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" ]
+ },
+ "-moz-outline-radius-bottomright": {
+ domProp: "MozOutlineRadiusBottomright",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"},
+ initial_values: [ "0", "0px", "calc(-2px)", "calc(0px)" ],
+ 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" ]
+ },
+ "-moz-outline-radius-topleft": {
+ domProp: "MozOutlineRadiusTopleft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"},
+ initial_values: [ "0", "0px", "calc(-2px)", "calc(0px)" ],
+ 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" ]
+ },
+ "-moz-outline-radius-topright": {
+ domProp: "MozOutlineRadiusTopright",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"},
+ initial_values: [ "0", "0px", "calc(-2px)", "calc(0px)" ],
+ 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" ]
+ },
+ "padding-inline-end": {
+ domProp: "paddingInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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,
+ prerequisites: { "display": "block", "overflow": "auto" },
+ initial_values: [ "none" ],
+ other_values: [ "both", "horizontal", "vertical" ],
+ invalid_values: []
+ },
+ "-moz-stack-sizing": {
+ domProp: "MozStackSizing",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "stretch-to-fit" ],
+ other_values: [ "ignore" ],
+ invalid_values: []
+ },
+ "-moz-tab-size": {
+ domProp: "MozTabSize",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "8" ],
+ other_values: [ "0", "3", "99", "12000" ],
+ invalid_values: [ "-1", "-808", "3.0", "17.5" ]
+ },
+ "-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)", "scaley(10)",
+ "scale(10)", "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(-2, 3, -7)", "scalez(4)",
+ "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)",
+ ],
+ 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)", "scale(150%)",
+ "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%))",
+ "translate(10px, calc(min(5px,10%)))",
+ "translate(calc(max(5px,10%)), 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-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-focus": {
+ domProp: "MozUserFocus",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "normal", "ignore", "select-all", "select-before", "select-after", "select-same", "select-menu" ],
+ invalid_values: []
+ },
+ "-moz-user-input": {
+ domProp: "MozUserInput",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "none", "enabled", "disabled" ],
+ 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_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "none", "text", "element", "elements", "all", "toggle", "tri-state", "-moz-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 scroll none transparent repeat",
+ "50% transparent",
+ "transparent 50%",
+ "50%",
+ "-moz-radial-gradient(10% bottom, #ffffff, black) scroll no-repeat",
+ "-moz-linear-gradient(10px 10px -45deg, red, blue) repeat",
+ "-moz-linear-gradient(10px 10px -0.125turn, red, blue) repeat",
+ "-moz-repeating-radial-gradient(10% bottom, #ffffff, black) scroll no-repeat",
+ "-moz-repeating-linear-gradient(10px 10px -45deg, red, blue) 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",
+ "url(404.png), -moz-linear-gradient(20px 20px -45deg, blue, green), -moz-element(#a) black",
+ "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",
+ ],
+ invalid_values: [
+ /* mixes with keywords have to be in correct order */
+ "50% left", "top 50%",
+ /* 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",
+ "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)",
+ /* bug 513395: old syntax for gradients */
+ "-moz-radial-gradient(10% bottom, 30px, 20px 20px, 10px, from(#ffffff), to(black)) scroll no-repeat",
+ "-moz-linear-gradient(10px 10px, 20px 20px, from(red), to(blue)) repeat",
+ /* 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",
+ /* error inside functions */
+ "-moz-image-rect(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), rubbish, 50%, 30%, 0) transparent",
+ "-moz-element(#a rubbish) black",
+ "text",
+ "text border-box",
+ "content-box text text",
+ "padding-box text url(404.png) text",
+ ]
+ },
+ "background-attachment": {
+ domProp: "backgroundAttachment",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "scroll" ],
+ other_values: [ "fixed", "local", "scroll,scroll", "fixed, scroll", "scroll, fixed, local, scroll", "fixed, fixed" ],
+ invalid_values: []
+ },
+ "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,
+ initial_values: [ "border-box" ],
+ other_values: [ "content-box", "padding-box", "border-box, padding-box", "padding-box, padding-box, padding-box", "border-box, border-box" ],
+ invalid_values: [ "margin-box", "border-box border-box" ]
+ },
+ "background-color": {
+ domProp: "backgroundColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "transparent", "rgba(255, 127, 15, 0)", "hsla(240, 97%, 50%, 0.0)", "rgba(0, 0, 0, 0)", "rgba(255,255,255,-3.7)" ],
+ 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)" ],
+ 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,
+ 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(validGradientAndElementValues),
+ invalid_values: [
+ ].concat(invalidGradientAndElementValues),
+ unbalanced_values: [
+ ].concat(unbalancedGradientAndElementValues)
+ },
+ "background-origin": {
+ domProp: "backgroundOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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" ]
+ },
+ "background-position": {
+ domProp: "backgroundPosition",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ 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,
+ 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,
+ 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,
+ 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,
+ 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", "-moz-border-top-colors", "-moz-border-right-colors", "-moz-border-bottom-colors", "-moz-border-left-colors", "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,
+ 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,
+ /* 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,
+ 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,
+ 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,
+ /* 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,
+ 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,
+ 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,
+ /* 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,
+ 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,
+ 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,
+ /* 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,
+ 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,
+ 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)" ]
+ },
+ "caption-side": {
+ domProp: "captionSide",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "top" ],
+ other_values: [ "bottom", "left", "right", "top-outside", "bottom-outside" ],
+ invalid_values: []
+ },
+ "clear": {
+ domProp: "clear",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "left", "right", "both" ],
+ 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,
+ /* 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%)",
+ ],
+ other_values: [ "green", "#f3c", "#fed292", "rgba(45,300,12,2)", "transparent", "-moz-nativehyperlinktext", "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%)",
+ ],
+ 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% /)",
+ ],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a", "fff": "#ffffff", "ffffff": "#ffffff", },
+ },
+ "content": {
+ domProp: "content",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* XXX needs to be on pseudo-elements */
+ 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)", "counter(foo, none)", "counters(bar, '.', none)", "attr(\\32)", "attr(\\2)", "attr(-\\2)", "attr(-\\32)", "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'))" ],
+ invalid_values: [ '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'" ]
+ },
+ "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" ]
+ },
+ "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" ],
+ invalid_values: [ "url(foo.png)", "url(foo.png) 5 5" ]
+ },
+ "direction": {
+ domProp: "direction",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "ltr" ],
+ other_values: [ "rtl" ],
+ invalid_values: []
+ },
+ "display": {
+ domProp: "display",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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-block",
+ "table",
+ "inline-table",
+ "table-row-group",
+ "table-header-group",
+ "table-footer-group",
+ "table-row",
+ "table-column-group",
+ "table-column",
+ "table-cell",
+ "table-caption",
+ "ruby",
+ "ruby-base",
+ "ruby-base-container",
+ "ruby-text",
+ "ruby-text-container",
+ "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,
+ initial_values: [ "none" ],
+ other_values: [ "left", "right" ],
+ 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-synthesis", "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",
+ // Gecko-specific system fonts
+ "-moz-window", "-moz-document", "-moz-desktop", "-moz-info", "-moz-dialog", "-moz-button", "-moz-pull-down-menu", "-moz-list", "-moz-field", "-moz-workspace",
+ // 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,
+ 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,
+ 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"'
+ ],
+ 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,
+ initial_values: [ "auto" ],
+ other_values: [ "normal", "none" ],
+ invalid_values: [ "on" ]
+ },
+ "font-language-override": {
+ domProp: "fontLanguageOverride",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "normal" ],
+ other_values: [ "'ENG'", "'TRK'", "\"TRK\"", "'N\\'Ko'" ],
+ invalid_values: [ "TRK", "ja" ]
+ },
+ "font-size": {
+ domProp: "fontSize",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "medium",
+ "1rem",
+ "calc(1rem)",
+ "calc(0.75rem + 200% - 125% + 0.25rem - 75%)"
+ ],
+ other_values: [ "large", "2em", "50%", "xx-small", "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,
+ initial_values: [ "none" ],
+ other_values: [ "0.3", "0.5", "0.7", "0.0", "0", "3" ],
+ invalid_values: [ "-0.3", "-1" ]
+ },
+ "font-stretch": {
+ domProp: "fontStretch",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ 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,
+ initial_values: [ "normal" ],
+ other_values: [ "italic", "oblique" ],
+ invalid_values: []
+ },
+ "font-synthesis": {
+ domProp: "fontSynthesis",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "weight style" ],
+ other_values: [ "none", "weight", "style" ],
+ invalid_values: [ "weight none", "style none", "none style", "weight 10px", "weight weight", "style style" ]
+ },
+ "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,
+ 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,
+ 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,
+ 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,
+ 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,
+ 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,
+ initial_values: [ "normal" ],
+ other_values: [ "super", "sub" ],
+ invalid_values: [ "normal sub", "super sub" ]
+ },
+ "font-weight": {
+ domProp: "fontWeight",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "normal", "400" ],
+ other_values: [ "bold", "100", "200", "300", "500", "600", "700", "800", "900", "bolder", "lighter" ],
+ invalid_values: [ "0", "100.0", "107", "399", "401", "699", "710", "1000" ]
+ },
+ "height": {
+ domProp: "height",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: test zero, and test calc clamping */
+ initial_values: [ " auto",
+ // these four keywords compute to the initial value when the
+ // writing mode is horizontal, and that's the context we're testing in
+ "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+ ],
+ /* computed value tests for height test more with display:block */
+ prerequisites: { "display": "block" },
+ other_values: [ "15px", "3em", "15%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "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,
+ initial_values: [ "normal" ],
+ other_values: [ "0", "0px", "1em", "2px", "-3px",
+ "calc(0px)", "calc(1em)", "calc(1em + 3px)",
+ "calc(15px / 2)", "calc(15px/2)", "calc(-3px)"
+ ],
+ invalid_values: [],
+ quirks_values: { "5": "5px" },
+ },
+ "line-height": {
+ domProp: "lineHeight",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ /*
+ * 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'. -moz-block-height requires height
+ * on a block.
+ */
+ 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", "-moz-block-height", "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,\\\\)",
+ ],
+ invalid_values: []
+ },
+ "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,
+ /* 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,
+ /* 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,
+ /* 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,
+ /* 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",
+ // these four keywords compute to the initial value when the
+ // writing mode is horizontal, and that's the context we're testing in
+ "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+ ],
+ other_values: [ "30px", "50%", "0",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "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
+ "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "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)",
+ // these four keywords compute to the initial value when the
+ // writing mode is horizontal, and that's the context we're testing in
+ "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+ ],
+ other_values: [ "30px", "50%",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "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
+ "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [ "none" ],
+ quirks_values: { "5": "5px" },
+ },
+
+ "opacity": {
+ domProp: "opacity",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "1", "17", "397.376", "3e1", "3e+1", "3e0", "3e+0", "3e-0" ],
+ other_values: [ "0", "0.4", "0.0000", "-3", "3e-1" ],
+ 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,
+ 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,
+ // 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,
+ 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", "-moz-hidden-unscrollable", "-moz-scrollbars-none" ],
+ invalid_values: []
+ },
+ "overflow-x": {
+ domProp: "overflowX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { "display": "block", "overflow-y": "visible", "contain": "none" },
+ initial_values: [ "visible" ],
+ other_values: [ "auto", "scroll", "hidden", "-moz-hidden-unscrollable" ],
+ invalid_values: []
+ },
+ "overflow-y": {
+ domProp: "overflowY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { "display": "block", "overflow-x": "visible", "contain": "none" },
+ initial_values: [ "visible" ],
+ other_values: [ "auto", "scroll", "hidden", "-moz-hidden-unscrollable" ],
+ invalid_values: []
+ },
+ "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" ],
+ unbalanced_values: [ "1px calc(" ],
+ quirks_values: { "5": "5px", "3px 6px 2 5px": "3px 6px 2px 5px" },
+ },
+ "padding-bottom": {
+ domProp: "paddingBottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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,
+ 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,
+ 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,
+ 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_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "always", "avoid", "left", "right" ],
+ invalid_values: []
+ },
+ "page-break-before": {
+ domProp: "pageBreakBefore",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "always", "avoid", "left", "right" ],
+ invalid_values: []
+ },
+ "page-break-inside": {
+ domProp: "pageBreakInside",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "avoid" ],
+ invalid_values: [ "left", "right" ]
+ },
+ "pointer-events": {
+ domProp: "pointerEvents",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ 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: [ '"\u201C" "\u201D" "\u2018" "\u2019"',
+ '"\\201C" "\\201D" "\\2018" "\\2019"' ],
+ other_values: [ "none", "'\"' '\"'" ],
+ 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,
+ initial_values: [ "over" ],
+ other_values: [ "under" ],
+ invalid_values: [
+ "left", "right", "auto", "none", "not_a_position",
+ "over left", "right under", "0", "100px", "50%"
+ ]
+ },
+ "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,
+ // 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" ]
+ },
+ "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-decoration": {
+ domProp: "textDecoration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ subproperties: [ "text-decoration-color", "text-decoration-line", "text-decoration-style" ],
+ initial_values: [ "none" ],
+ other_values: [ "underline", "overline", "line-through", "blink", "blink line-through underline", "underline overline line-through blink", "-moz-anchor-decoration", "blink -moz-anchor-decoration",
+ "underline red solid", "underline #ff0000", "solid underline", "red underline", "#ff0000 underline", "dotted underline" ],
+ 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" ]
+ },
+ "text-decoration-color": {
+ domProp: "textDecorationColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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,
+ initial_values: [ "none" ],
+ other_values: [ "underline", "overline", "line-through", "blink", "blink line-through underline", "underline overline line-through blink", "-moz-anchor-decoration", "blink -moz-anchor-decoration" ],
+ 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,
+ initial_values: [ "solid" ],
+ other_values: [ "double", "dotted", "dashed", "wavy", "-moz-none" ],
+ invalid_values: [ "none", "groove", "ridge", "inset", "outset", "solid dashed", "wave" ]
+ },
+ "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" ],
+ other_values: [ "over left", "left over", "under left", "left under", "under right", "right 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,
+ 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']
+ },
+ "text-shadow": {
+ domProp: "textShadow",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ 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)"
+ ]
+ },
+ "text-transform": {
+ domProp: "textTransform",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "capitalize", "uppercase", "lowercase", "full-width" ],
+ invalid_values: []
+ },
+ "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,
+ 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" ],
+ 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" ]
+ },
+ "transition-delay": {
+ domProp: "transitionDelay",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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,
+ 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,
+ 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" ]
+ },
+ "transition-timing-function": {
+ domProp: "transitionTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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)" ],
+ 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)" ]
+ },
+ "unicode-bidi": {
+ domProp: "unicodeBidi",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "normal" ],
+ other_values: [ "embed", "bidi-override", "isolate", "plaintext", "isolate-override", "-moz-isolate", "-moz-plaintext", "-moz-isolate-override" ],
+ invalid_values: [ "auto", "none" ]
+ },
+ "vertical-align": {
+ domProp: "verticalAlign",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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" },
+ },
+ "visibility": {
+ domProp: "visibility",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "visible" ],
+ other_values: [ "hidden", "collapse" ],
+ invalid_values: []
+ },
+ "white-space": {
+ domProp: "whiteSpace",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "normal" ],
+ other_values: [ "pre", "nowrap", "pre-wrap", "pre-line", "-moz-pre-space" ],
+ invalid_values: []
+ },
+ "width": {
+ domProp: "width",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* computed value tests for width test more with display:block */
+ prerequisites: { "display": "block" },
+ 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
+ "-moz-max-content", "-moz-min-content", "-moz-fit-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 -moz-calc() values */
+ "-moz-calc(-2px)",
+ "-moz-calc(2px)",
+ "-moz-calc(50%)",
+ "-moz-calc(50% + 2px)",
+ "-moz-calc( 50% + 2px)",
+ "-moz-calc(50% + 2px )",
+ "-moz-calc( 50% + 2px )",
+ "-moz-calc(50% - -2px)",
+ "-moz-calc(2px - -50%)",
+ "-moz-calc(3*25px)",
+ "-moz-calc(3 *25px)",
+ "-moz-calc(3 * 25px)",
+ "-moz-calc(3* 25px)",
+ "-moz-calc(25px*3)",
+ "-moz-calc(25px *3)",
+ "-moz-calc(25px* 3)",
+ "-moz-calc(25px * 3)",
+ "-moz-calc(3*25px + 50%)",
+ "-moz-calc(50% - 3em + 2px)",
+ "-moz-calc(50% - (3em + 2px))",
+ "-moz-calc((50% - 3em) + 2px)",
+ "-moz-calc(2em)",
+ "-moz-calc(50%)",
+ "-moz-calc(50px/2)",
+ "-moz-calc(50px/(2 - 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))",
+ ],
+ invalid_values: [ "none", "-2px",
+ /* invalid -moz-calc() values */
+ "-moz-calc(50%+ 2px)",
+ "-moz-calc(50% +2px)",
+ "-moz-calc(50%+2px)",
+ /* invalid calc() values */
+ "calc(50%+ 2px)",
+ "calc(50% +2px)",
+ "calc(50%+2px)",
+ "-moz-min()",
+ "calc(min())",
+ "-moz-max()",
+ "calc(max())",
+ "-moz-min(5px)",
+ "calc(min(5px))",
+ "-moz-max(5px)",
+ "calc(max(5px))",
+ "-moz-min(5px,2em)",
+ "calc(min(5px,2em))",
+ "-moz-max(5px,2em)",
+ "calc(max(5px,2em))",
+ "calc(50px/(2 - 2))",
+ /* 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,
+ 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: [ "url(#mypath)", "url('404.svg#mypath')" ],
+ invalid_values: []
+ },
+ "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: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "use-script", "no-change", "reset-size", "ideographic", "alphabetic", "hanging", "mathematical", "central", "middle", "text-after-edge", "text-before-edge" ],
+ invalid_values: []
+ },
+ "fill": {
+ domProp: "fill",
+ inherited: true,
+ 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", "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,
+ initial_values: [ "1", "2.8", "1.000", "context-fill-opacity", "context-stroke-opacity" ],
+ other_values: [ "0", "0.3", "-7.3" ],
+ invalid_values: []
+ },
+ "fill-rule": {
+ domProp: "fillRule",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "nonzero" ],
+ other_values: [ "evenodd" ],
+ invalid_values: []
+ },
+ "filter": {
+ domProp: "filter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "url(#myfilt)" ],
+ invalid_values: [ "url(#myfilt) none" ]
+ },
+ "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" ],
+ other_values: [ "0", "0.3", "-7.3" ],
+ invalid_values: []
+ },
+ "image-rendering": {
+ domProp: "imageRendering",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "optimizeSpeed", "optimizeQuality", "-moz-crisp-edges" ],
+ 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: []
+ },
+ "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" ],
+ other_values: [ "0", "0.3", "-7.3" ],
+ invalid_values: []
+ },
+ "stroke": {
+ domProp: "stroke",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ 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,
+ initial_values: [ "none", "context-value" ],
+ other_values: [ "5px,3px,2px", "5px 3px 2px", " 5px ,3px\t, 2px ", "1px", "5%", "3em" ],
+ invalid_values: [ "-5px,3px,2px", "5px,3px,-2px" ]
+ },
+ "stroke-dashoffset": {
+ domProp: "strokeDashoffset",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "0", "-0px", "0em", "context-value" ],
+ other_values: [ "3px", "3%", "1em" ],
+ invalid_values: []
+ },
+ "stroke-linecap": {
+ domProp: "strokeLinecap",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "butt" ],
+ other_values: [ "round", "square" ],
+ invalid_values: []
+ },
+ "stroke-linejoin": {
+ domProp: "strokeLinejoin",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "miter" ],
+ other_values: [ "round", "bevel" ],
+ invalid_values: []
+ },
+ "stroke-miterlimit": {
+ domProp: "strokeMiterlimit",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "4" ],
+ other_values: [ "1", "7", "5000", "1.1" ],
+ invalid_values: [ "0.9", "0", "-1", "3px", "-0.3" ]
+ },
+ "stroke-opacity": {
+ domProp: "strokeOpacity",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "1", "2.8", "1.000", "context-fill-opacity", "context-stroke-opacity" ],
+ other_values: [ "0", "0.3", "-7.3" ],
+ invalid_values: []
+ },
+ "stroke-width": {
+ domProp: "strokeWidth",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "1px", "context-value" ],
+ other_values: [ "0", "0px", "-0em", "17px", "0.2em" ],
+ invalid_values: [ "-0.1px", "-3px" ]
+ },
+ "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,
+ initial_values: [ "auto" ],
+ other_values: [ "optimizeSpeed", "optimizeLegibility", "geometricPrecision" ],
+ invalid_values: []
+ },
+ "vector-effect": {
+ domProp: "vectorEffect",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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" ]
+ },
+ "align-content": {
+ domProp: "alignContent",
+ 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",
+ "first baseline", "last baseline", "baseline", "stretch", "start safe",
+ "unsafe end", "unsafe end stretch", "end safe space-evenly" ],
+ 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" ]
+ },
+ "align-items": {
+ domProp: "alignItems",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "normal" ],
+ // Can't test 'left'/'right' here since that computes to 'start' for blocks.
+ other_values: [ "end", "flex-start", "flex-end", "self-start", "self-end",
+ "center", "stretch", "first baseline", "last baseline", "baseline",
+ "unsafe left", "start", "center unsafe", "safe right", "center safe" ],
+ invalid_values: [ "space-between", "abc", "5%", "legacy", "legacy end",
+ "end legacy", "unsafe", "unsafe baseline", "normal unsafe",
+ "safe left unsafe", "safe stretch", "end end", "auto" ]
+ },
+ "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", "right safe",
+ "unsafe center", "self-start", "self-end safe" ],
+ invalid_values: [ "space-between", "abc", "30px", "stretch safe", "safe" ]
+ },
+ "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",
+ "first baseline", "last baseline", "baseline", "stretch", "start safe",
+ "unsafe end", "unsafe end stretch", "end safe space-evenly" ],
+ 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: [ "auto", "normal" ],
+ other_values: [ "end", "flex-start", "flex-end", "self-start", "self-end",
+ "center", "left", "right", "first baseline", "last baseline",
+ "baseline", "stretch", "start", "legacy left", "right legacy",
+ "legacy center", "unsafe right", "left unsafe", "safe right",
+ "center safe" ],
+ invalid_values: [ "space-between", "abc", "30px", "legacy", "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", "baseline", "first baseline",
+ "last baseline", "stretch", "left unsafe", "unsafe right",
+ "safe right", "center safe" ],
+ 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", "end baseline", "end end",
+ "space-between flex-end", "last baseline start",
+ "space-evenly", "flex-start", "end", "left" ],
+ invalid_values: [ "none", "center safe", "unsafe start", "right / end" ]
+ },
+ "place-items": {
+ domProp: "placeItems",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [ "align-items", "justify-items" ],
+ initial_values: [ "normal" ],
+ other_values: [ "normal center", "end baseline", "end auto",
+ "end", "right", "baseline", "start last baseline",
+ "left flex-end", "last baseline start", "stretch" ],
+ invalid_values: [ "space-between", "start space-evenly", "none", "end/end",
+ "center safe", "auto start", "end legacy left" ]
+ },
+ "place-self": {
+ domProp: "placeSelf",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [ "align-self", "justify-self" ],
+ initial_values: [ "auto" ],
+ other_values: [ "normal start", "end first baseline", "end auto",
+ "end", "right", "normal", "baseline", "start baseline",
+ "left self-end", "last baseline start", "stretch" ],
+ invalid_values: [ "space-between", "start space-evenly", "none", "end safe",
+ "auto legacy left", "legacy left", "auto/auto" ]
+ },
+ "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",
+ "-moz-max-content",
+ "1 -moz-max-content",
+ "1 2 -moz-max-content",
+ "-moz-max-content 1",
+ "-moz-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: This is cribbed directly from the "width" chunk, since this
+ // property takes the exact same values as width (albeit 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: [ "15px", "3em", "15%", "-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))"
+ ],
+ 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)",
+ "calc(min(5px))",
+ "-moz-max(5px)",
+ "calc(max(5px))",
+ "-moz-min(5px,2em)",
+ "calc(min(5px,2em))",
+ "-moz-max(5px,2em)",
+ "calc(max(5px,2em))",
+ "calc(50px/(2 - 2))",
+ // 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-transform": {
+ domProp: "MozTransform",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform",
+ subproperties: [ "transform" ],
+ // NOTE: We specify other_values & invalid_values explicitly here (instead
+ // of deferring to "transform") because we accept some legacy syntax as
+ // valid for "-moz-transform" but not for "transform".
+ 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)", "scaley(10)",
+ "scale(10)", "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))",
+ /* valid only when prefixed */
+ "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)",
+ "matrix(1, 0, 0, 1, calc(5px * 3), calc(10% - 3px))",
+ "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(-2, 3, -7)", "scalez(4)",
+ "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)",
+ /* valid only when prefixed */
+ "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)",
+ ],
+ 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)", "scale(150%)",
+ "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%)",
+ /* invalid calc() values */
+ "translatey(-moz-min(5px,10%))",
+ "translatex(-moz-max(5px,10%))",
+ "translate(10px, calc(min(5px,10%)))",
+ "translate(calc(max(5px,10%)), 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)",
+ ],
+ },
+ "-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" ],
+ },
+ "-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-transition": {
+ domProp: "MozTransition",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ 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,
+ alias_for: "transition-delay",
+ subproperties: [ "transition-delay" ],
+ },
+ "-moz-transition-duration": {
+ domProp: "MozTransitionDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transition-duration",
+ subproperties: [ "transition-duration" ],
+ },
+ "-moz-transition-property": {
+ domProp: "MozTransitionProperty",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transition-property",
+ subproperties: [ "transition-property" ],
+ },
+ "-moz-transition-timing-function": {
+ domProp: "MozTransitionTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transition-timing-function",
+ subproperties: [ "transition-timing-function" ],
+ },
+ "-moz-animation": {
+ domProp: "MozAnimation",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ 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,
+ alias_for: "animation-delay",
+ subproperties: [ "animation-delay" ],
+ },
+ "-moz-animation-direction": {
+ domProp: "MozAnimationDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-direction",
+ subproperties: [ "animation-direction" ],
+ },
+ "-moz-animation-duration": {
+ domProp: "MozAnimationDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-duration",
+ subproperties: [ "animation-duration" ],
+ },
+ "-moz-animation-fill-mode": {
+ domProp: "MozAnimationFillMode",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-fill-mode",
+ subproperties: [ "animation-fill-mode" ],
+ },
+ "-moz-animation-iteration-count": {
+ domProp: "MozAnimationIterationCount",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-iteration-count",
+ subproperties: [ "animation-iteration-count" ],
+ },
+ "-moz-animation-name": {
+ domProp: "MozAnimationName",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-name",
+ subproperties: [ "animation-name" ],
+ },
+ "-moz-animation-play-state": {
+ domProp: "MozAnimationPlayState",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-play-state",
+ subproperties: [ "animation-play-state" ],
+ },
+ "-moz-animation-timing-function": {
+ domProp: "MozAnimationTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-timing-function",
+ subproperties: [ "animation-timing-function" ],
+ },
+ "-moz-font-feature-settings": {
+ domProp: "MozFontFeatureSettings",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "font-feature-settings",
+ subproperties: [ "font-feature-settings" ],
+ },
+ "-moz-font-language-override": {
+ domProp: "MozFontLanguageOverride",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ 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" ],
+ },
+ "-moz-text-align-last": {
+ domProp: "MozTextAlignLast",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "text-align-last",
+ subproperties: [ "text-align-last" ],
+ },
+ // 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 */
+ },
+ "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" ]
+ },
+ "block-size": {
+ domProp: "blockSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ get_computed: logical_axis_prop_get_computed,
+ /* XXX testing auto has prerequisites */
+ initial_values: [ "auto" ],
+ prerequisites: { "display": "block" },
+ other_values: [ "15px", "3em", "15%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [ "none", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ],
+ },
+ "border-block-end-color": {
+ domProp: "borderBlockEndColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ initial_values: [ "currentColor" ],
+ other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ],
+ invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000" ]
+ },
+ "border-block-end-style": {
+ domProp: "borderBlockEndStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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-end-width": {
+ domProp: "borderBlockEndWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ 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,
+ alias_for: "border-inline-end-color",
+ subproperties: [ "border-inline-end-color" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+ "-moz-border-end-style": {
+ domProp: "MozBorderEndStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "border-inline-end-style",
+ subproperties: [ "border-inline-end-style" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+ "-moz-border-end-width": {
+ domProp: "MozBorderEndWidth",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "border-inline-end-width",
+ subproperties: [ "border-inline-end-width" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+ "-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,
+ alias_for: "border-inline-start-color",
+ subproperties: [ "border-inline-start-color" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+ "-moz-border-start-style": {
+ domProp: "MozBorderStartStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "border-inline-start-style",
+ subproperties: [ "border-inline-start-style" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+ "-moz-border-start-width": {
+ domProp: "MozBorderStartWidth",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "border-inline-start-width",
+ subproperties: [ "border-inline-start-width" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+ "inline-size": {
+ domProp: "inlineSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ get_computed: logical_axis_prop_get_computed,
+ /* XXX testing auto has prerequisites */
+ initial_values: [ "auto" ],
+ prerequisites: { "display": "block" },
+ 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
+ "-moz-max-content", "-moz-min-content", "-moz-fit-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%)",
+ ],
+ invalid_values: [ "none" ],
+ },
+ "margin-block-end": {
+ domProp: "marginBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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,
+ alias_for: "margin-inline-end",
+ subproperties: [ "margin-inline-end" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+ "-moz-margin-start": {
+ domProp: "MozMarginStart",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "margin-inline-start",
+ subproperties: [ "margin-inline-start" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+ "max-block-size": {
+ domProp: "maxBlockSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ get_computed: logical_axis_prop_get_computed,
+ prerequisites: { "display": "block" },
+ initial_values: [ "none" ],
+ other_values: [ "30px", "50%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [ "auto", "5", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ]
+ },
+ "max-inline-size": {
+ domProp: "maxInlineSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ get_computed: logical_axis_prop_get_computed,
+ 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
+ "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [ "auto", "5" ]
+ },
+ "min-block-size": {
+ domProp: "minBlockSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ get_computed: logical_axis_prop_get_computed,
+ prerequisites: { "display": "block" },
+ initial_values: [ "auto", "0", "calc(0em)", "calc(-2px)" ],
+ other_values: [ "30px", "50%",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [ "none", "5", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ]
+ },
+ "min-inline-size": {
+ domProp: "minInlineSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ get_computed: logical_axis_prop_get_computed,
+ 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
+ "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [ "none", "5" ]
+ },
+ "offset-block-end": {
+ domProp: "offsetBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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: []
+ },
+ "offset-block-start": {
+ domProp: "offsetBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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: []
+ },
+ "offset-inline-end": {
+ domProp: "offsetInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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: []
+ },
+ "offset-inline-start": {
+ domProp: "offsetInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ /* 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ 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,
+ logical: true,
+ get_computed: logical_box_prop_get_computed,
+ 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,
+ alias_for: "padding-inline-end",
+ subproperties: [ "padding-inline-end" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+ "-moz-padding-start": {
+ domProp: "MozPaddingStart",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "padding-inline-start",
+ subproperties: [ "padding-inline-start" ],
+ get_computed: logical_box_prop_get_computed,
+ },
+} // end of gCSSProperties
+
+function logical_axis_prop_get_computed(cs, property)
+{
+ // Use defaults for these two properties in case the vertical text
+ // pref (which they live behind) is turned off.
+ var writingMode = cs.getPropertyValue("writing-mode") || "horizontal-tb";
+ var orientation = writingMode.substring(0, writingMode.indexOf("-"));
+
+ var mappings = {
+ "block-size": { horizontal: "height",
+ vertical: "width",
+ sideways: "width" },
+ "inline-size": { horizontal: "width",
+ vertical: "height",
+ sideways: "height" },
+ "max-block-size": { horizontal: "max-height",
+ vertical: "max-width",
+ sideways: "max-width" },
+ "max-inline-size": { horizontal: "max-width",
+ vertical: "max-height",
+ sideways: "max-height" },
+ "min-block-size": { horizontal: "min-height",
+ vertical: "min-width",
+ sideways: "min-width" },
+ "min-inline-size": { horizontal: "min-width",
+ vertical: "min-height",
+ sideways: "min-height" },
+ };
+
+ if (!mappings[property]) {
+ throw "unexpected property " + property;
+ }
+
+ var prop = mappings[property][orientation];
+ if (!prop) {
+ throw "unexpected writing mode " + writingMode;
+ }
+
+ return cs.getPropertyValue(prop);
+}
+
+function logical_box_prop_get_computed(cs, property)
+{
+ // http://dev.w3.org/csswg/css-writing-modes-3/#logical-to-physical
+
+ // Use default for writing-mode in case the vertical text
+ // pref (which it lives behind) is turned off.
+ var writingMode = cs.getPropertyValue("writing-mode") || "horizontal-tb";
+
+ var direction = cs.getPropertyValue("direction");
+
+ // keys in blockMappings are writing-mode values
+ var blockMappings = {
+ "horizontal-tb": { "start": "top", "end": "bottom" },
+ "vertical-rl": { "start": "right", "end": "left" },
+ "vertical-lr": { "start": "left", "end": "right" },
+ "sideways-rl": { "start": "right", "end": "left" },
+ "sideways-lr": { "start": "left", "end": "right" },
+ };
+
+ // keys in inlineMappings are regular expressions that match against
+ // a {writing-mode,direction} pair as a space-separated string
+ var inlineMappings = {
+ "horizontal-tb ltr": { "start": "left", "end": "right" },
+ "horizontal-tb rtl": { "start": "right", "end": "left" },
+ "vertical-.. ltr": { "start": "bottom", "end": "top" },
+ "vertical-.. rtl": { "start": "top", "end": "bottom" },
+ "vertical-.. ltr": { "start": "top", "end": "bottom" },
+ "vertical-.. rtl": { "start": "bottom", "end": "top" },
+ "sideways-lr ltr": { "start": "bottom", "end": "top" },
+ "sideways-lr rtl": { "start": "top", "end": "bottom" },
+ "sideways-rl ltr": { "start": "top", "end": "bottom" },
+ "sideways-rl rtl": { "start": "bottom", "end": "top" },
+ };
+
+ var blockMapping = blockMappings[writingMode];
+ var inlineMapping;
+
+ // test each regular expression in inlineMappings against the
+ // {writing-mode,direction} pair
+ var key = `${writingMode} ${direction}`;
+ for (var k in inlineMappings) {
+ if (new RegExp(k).test(key)) {
+ inlineMapping = inlineMappings[k];
+ break;
+ }
+ }
+
+ if (!blockMapping || !inlineMapping) {
+ throw "Unexpected writing mode property values";
+ }
+
+ function physicalize(aProperty, aMapping, aLogicalPrefix) {
+ for (var logicalSide in aMapping) {
+ var physicalSide = aMapping[logicalSide];
+ logicalSide = aLogicalPrefix + logicalSide;
+ aProperty = aProperty.replace(logicalSide, physicalSide);
+ }
+ return aProperty;
+ }
+
+ if (/^-moz-/.test(property)) {
+ property = physicalize(property.substring(5), inlineMapping, "");
+ } else if (/^offset-(block|inline)-(start|end)/.test(property)) {
+ property = property.substring(7); // we want "top" not "offset-top", e.g.
+ property = physicalize(property, blockMapping, "block-");
+ property = physicalize(property, inlineMapping, "inline-");
+ } else if (/-(block|inline)-(start|end)/.test(property)) {
+ property = physicalize(property, blockMapping, "block-");
+ property = physicalize(property, inlineMapping, "inline-");
+ } else {
+ throw "Unexpected property";
+ }
+ return cs.getPropertyValue(property);
+}
+
+// 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_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(" ; ");
+ }
+ if (info.get_computed)
+ return info.get_computed(cs, property);
+ return cs.getPropertyValue(property);
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.touch_action.enabled")) {
+ gCSSProperties["touch-action"] = {
+ domProp: "touchAction",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none", "pan-x", "pan-y", "pan-x pan-y", "pan-y pan-x", "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",
+ "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 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 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",
+ "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",
+ "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 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"]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.text-combine-upright.enabled")) {
+ gCSSProperties["text-combine-upright"] = {
+ domProp: "textCombineUpright",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ 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" ]
+ };
+ if (IsCSSPropertyPrefEnabled("layout.css.text-combine-upright-digits.enabled")) {
+ gCSSProperties["text-combine-upright"].other_values.push(
+ "digits", "digits 2", "digits 3", "digits 4", "digits 3");
+ }
+}
+
+if (IsCSSPropertyPrefEnabled("svg.paint-order.enabled")) {
+ gCSSProperties["paint-order"] = {
+ domProp: "paintOrder",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "normal" ],
+ other_values: [ "fill", "fill stroke", "fill stroke markers", "stroke markers fill" ],
+ invalid_values: [ "fill stroke markers fill", "fill normal" ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("svg.transform-box.enabled")) {
+ gCSSProperties["transform-box"] = {
+ domProp: "transformBox",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "border-box" ],
+ other_values: [ "fill-box", "view-box" ],
+ invalid_values: []
+ };
+}
+
+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)",
+ "polygon(nonzero, 20px 20px, 30% 30%, 30px 30px)",
+ "polygon(evenodd, 20px 20px, 30% 30%, 30px 30px)",
+
+ "content-box",
+ "padding-box",
+ "border-box",
+ "margin-box",
+
+ "polygon(0 0) content-box",
+ "border-box polygon(0 0)",
+ "padding-box polygon( 0 20px , 30px 20% ) ",
+ "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",
+
+ "circle()",
+ "circle(at center)",
+ "circle(at top 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 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 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))",
+
+ "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))",
+
+ "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",
+];
+
+if (IsCSSPropertyPrefEnabled("layout.css.clip-path-shapes.enabled")) {
+ gCSSProperties["clip-path"] = {
+ domProp: "clipPath",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [
+ // SVG reference clip-path
+ "url(#my-clip-path)",
+
+ "fill-box",
+ "stroke-box",
+ "view-box",
+
+ "polygon(evenodd, 20pt 20cm) fill-box",
+ "polygon(evenodd, 20ex 20pc) stroke-box",
+ "polygon(evenodd, 20rem 20in) view-box",
+ ].concat(basicShapeOtherValues),
+ invalid_values: basicShapeInvalidValues,
+ unbalanced_values: basicShapeUnbalancedValues,
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.shape-outside.enabled")) {
+ gCSSProperties["shape-outside"] = {
+ domProp: "shapeOutside",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [
+ "url(#my-shape-outside)",
+ ].concat(basicShapeOtherValues),
+ invalid_values: basicShapeInvalidValues,
+ unbalanced_values: basicShapeUnbalancedValues,
+ };
+}
+
+
+if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) {
+ gCSSProperties["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(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(0)",
+ "brightness(50%)",
+ "brightness(1)",
+ "brightness(1.0)",
+ "brightness(2)",
+ "brightness(350%)",
+ "brightness(4.567)",
+
+ "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(0)",
+ "grayscale(50%)",
+ "grayscale(1)",
+ "grayscale(1.0)",
+ "grayscale(2)",
+ "grayscale(350%)",
+ "grayscale(4.567)",
+
+ "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(0)",
+ "invert(50%)",
+ "invert(1)",
+ "invert(1.0)",
+ "invert(2)",
+ "invert(350%)",
+ "invert(4.567)",
+
+ "opacity(0)",
+ "opacity(50%)",
+ "opacity(1)",
+ "opacity(1.0)",
+ "opacity(2)",
+ "opacity(350%)",
+ "opacity(4.567)",
+
+ "saturate(0)",
+ "saturate(50%)",
+ "saturate(1)",
+ "saturate(1.0)",
+ "saturate(2)",
+ "saturate(350%)",
+ "saturate(4.567)",
+
+ "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()",
+ "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()",
+ "brightness(0.5 0.5)",
+ "brightness(0.5,)",
+ "brightness(0.5, 0.5)",
+ "brightness(#my-filter)",
+ "brightness(10px)",
+ "brightness(-1)",
+
+ "contrast()",
+ "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)",
+
+ "grayscale()",
+ "grayscale(0.5 0.5)",
+ "grayscale(0.5,)",
+ "grayscale(0.5, 0.5)",
+ "grayscale(#my-filter)",
+ "grayscale(10px)",
+ "grayscale(-1)",
+
+ "hue-rotate()",
+ "hue-rotate(0)",
+ "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()",
+ "invert(0.5 0.5)",
+ "invert(0.5,)",
+ "invert(0.5, 0.5)",
+ "invert(#my-filter)",
+ "invert(10px)",
+ "invert(-1)",
+
+ "opacity()",
+ "opacity(0.5 0.5)",
+ "opacity(0.5,)",
+ "opacity(0.5, 0.5)",
+ "opacity(#my-filter)",
+ "opacity(10px)",
+ "opacity(-1)",
+
+ "saturate()",
+ "saturate(0.5 0.5)",
+ "saturate(0.5,)",
+ "saturate(0.5, 0.5)",
+ "saturate(#my-filter)",
+ "saturate(10px)",
+ "saturate(-1)",
+
+ "sepia()",
+ "sepia(0.5 0.5)",
+ "sepia(0.5,)",
+ "sepia(0.5, 0.5)",
+ "sepia(#my-filter)",
+ "sepia(10px)",
+ "sepia(-1)",
+ ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.grid.enabled")) {
+ var isGridTemplateSubgridValueEnabled =
+ IsCSSPropertyPrefEnabled("layout.css.grid-template-subgrid-value.enabled");
+
+ 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%)",
+ ],
+ 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)",
+ ]
+ };
+ 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,minmax(1%,auto))",
+ "repeat(auto-fill,minmax(1em,min-content)) minmax(min-content,0)",
+ "repeat(auto-fill,minmax(max-content,1mm))",
+ "fit-content(1px) 1fr",
+ "[a] fit-content(calc(1px - 99%)) [b]",
+ "[a] fit-content(10%) [b c] fit-content(1em)",
+ ],
+ 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-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, 0 0)",
+ "repeat(auto-fill, 0 [] 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)",
+ ],
+ unbalanced_values: [
+ "(foo] 40px",
+ ]
+ };
+ if (isGridTemplateSubgridValueEnabled) {
+ gCSSProperties["grid-template-columns"].other_values.push(
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=981300
+ "[none auto 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 [x] repeat( Auto-fill, [a b c]) []",
+ "subgrid [x] repeat(auto-fill, []) [y z]"
+ );
+ gCSSProperties["grid-template-columns"].invalid_values.push(
+ "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-fit,[])",
+ "subgrid [] repeat(auto-fit,[])",
+ "subgrid [a] repeat(auto-fit,[])",
+ "subgrid repeat(auto-fill, 1px)",
+ "subgrid repeat(auto-fill, 1px [])",
+ "subgrid repeat(Auto-fill, [a] [b c] [] [d])",
+ "subgrid repeat(auto-fill, []) repeat(auto-fill, [])"
+ );
+ }
+ 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",
+ ],
+ 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"
+ ]
+ };
+ if (isGridTemplateSubgridValueEnabled) {
+ gCSSProperties["grid-template"].other_values.push(
+ "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]"
+ );
+ gCSSProperties["grid-template"].invalid_values.push(
+ "subgrid []",
+ "subgrid [] / 'fizz'",
+ "subgrid / '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",
+ "grid-column-gap",
+ "grid-row-gap",
+ ],
+ initial_values: [
+ "none",
+ "none / none",
+ ],
+ other_values: [
+ "auto-flow 40px / none",
+ "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 40px 100px / 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["grid-column-gap"] = {
+ domProp: "gridColumnGap",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "0" ],
+ 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-row-gap"] = {
+ domProp: "gridRowGap",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "0" ],
+ 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-gap"] = {
+ domProp: "gridGap",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [ "grid-column-gap", "grid-row-gap" ],
+ initial_values: [ "0", "0 0" ],
+ other_values: [ "1ch 0", "1px 1%", "1em 1px", "calc(1px) calc(1%)" ],
+ invalid_values: [ "-1px", "1px -1px", "1px 1px 1px", "inherit 1px",
+ "1px auto" ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.display-contents.enabled")) {
+ gCSSProperties["display"].other_values.push("contents");
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.contain.enabled")) {
+ gCSSProperties["contain"] = {
+ domProp: "contain",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [
+ "strict",
+ "layout",
+ "style",
+ "layout style",
+ "style layout",
+ "paint",
+ "layout paint",
+ "paint layout",
+ "style paint",
+ "paint style",
+ "layout style paint",
+ "layout paint style",
+ "style paint layout",
+ "paint style layout",
+ ],
+ invalid_values: [
+ "none strict",
+ "strict layout",
+ "strict layout style",
+ "layout strict",
+ "layout style strict",
+ "layout style paint strict",
+ "paint strict",
+ "style strict",
+ "paint paint",
+ "strict strict",
+ "auto",
+ "10px",
+ "0",
+ ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.image-orientation.enabled")) {
+ gCSSProperties["image-orientation"] = {
+ domProp: "imageOrientation",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [
+ "0deg",
+ "0grad",
+ "0rad",
+ "0turn",
+
+ // Rounded initial values.
+ "-90deg",
+ "15deg",
+ "360deg",
+ ],
+ other_values: [
+ "0deg flip",
+ "90deg",
+ "90deg flip",
+ "180deg",
+ "180deg flip",
+ "270deg",
+ "270deg flip",
+ "flip",
+ "from-image",
+
+ // Grad units.
+ "0grad flip",
+ "100grad",
+ "100grad flip",
+ "200grad",
+ "200grad flip",
+ "300grad",
+ "300grad flip",
+
+ // Radian units.
+ "0rad flip",
+ "1.57079633rad",
+ "1.57079633rad flip",
+ "3.14159265rad",
+ "3.14159265rad flip",
+ "4.71238898rad",
+ "4.71238898rad flip",
+
+ // Turn units.
+ "0turn flip",
+ "0.25turn",
+ "0.25turn flip",
+ "0.5turn",
+ "0.5turn flip",
+ "0.75turn",
+ "0.75turn flip",
+
+ // Rounded values.
+ "-45deg flip",
+ "65deg flip",
+ "400deg flip",
+ ],
+ invalid_values: [
+ "none",
+ "0deg none",
+ "flip 0deg",
+ "flip 0deg",
+ "0",
+ "0 flip",
+ "flip 0",
+ "0deg from-image",
+ "from-image 0deg",
+ "flip from-image",
+ "from-image flip",
+ ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.initial-letter.enabled")) {
+ gCSSProperties["initial-letter"] = {
+ domProp: "initialLetter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ 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,
+ initial_values: [ "auto" ],
+ other_values: [ "grayscale" ],
+ invalid_values: [ "none", "subpixel-antialiased", "antialiased" ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.mix-blend-mode.enabled")) {
+ gCSSProperties["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"],
+ invalid_values: []
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.isolation.enabled")) {
+ gCSSProperties["isolation"] = {
+ domProp: "isolation",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: ["isolate"],
+ invalid_values: []
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.background-blend-mode.enabled")) {
+ gCSSProperties["background-blend-mode"] = {
+ domProp: "backgroundBlendMode",
+ 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" ],
+ invalid_values: ["none", "10px", "multiply multiply"]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.object-fit-and-position.enabled")) {
+ gCSSProperties["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%" ]
+ };
+ gCSSProperties["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 top 15px",
+ "left 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" ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.overflow-clip-box.enabled")) {
+ gCSSProperties["overflow-clip-box"] = {
+ domProp: "overflowClipBox",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "padding-box" ],
+ other_values: [ "content-box" ],
+ invalid_values: [ "none", "auto", "border-box", "0" ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.box-decoration-break.enabled")) {
+ gCSSProperties["box-decoration-break"] = {
+ domProp: "boxDecorationBreak",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "slice" ],
+ other_values: [ "clone" ],
+ invalid_values: [ "auto", "none", "1px" ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.scroll-behavior.property-enabled")) {
+ gCSSProperties["scroll-behavior"] = {
+ domProp: "scrollBehavior",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "smooth" ],
+ invalid_values: [ "none", "1px" ]
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.scroll-snap.enabled")) {
+ gCSSProperties["scroll-snap-coordinate"] = {
+ domProp: "scrollSnapCoordinate",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "25% 25%", "top", "0px 100px, 10em 50%",
+ "top left, top right, bottom left, bottom right, center",
+ "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%)",
+ "calc(20%) calc(3*25px), center"],
+ invalid_values: [ "auto", "default" ]
+ }
+ gCSSProperties["scroll-snap-destination"] = {
+ domProp: "scrollSnapDestination",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "0px 0px" ],
+ other_values: [ "25% 25%", "6px 5px", "20% 3em", "0in 1in",
+ "top", "right", "top left", "top right", "center",
+ "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: [ "auto", "none", "default" ]
+ }
+ gCSSProperties["scroll-snap-points-x"] = {
+ domProp: "scrollSnapPointsX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "repeat(100%)", "repeat(120px)", "repeat(calc(3*25px))" ],
+ invalid_values: [ "auto", "1px", "left", "rgb(1,2,3)" ]
+ }
+ gCSSProperties["scroll-snap-points-y"] = {
+ domProp: "scrollSnapPointsY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "repeat(100%)", "repeat(120px)", "repeat(calc(3*25px))" ],
+ invalid_values: [ "auto", "1px", "top", "rgb(1,2,3)" ]
+ }
+ gCSSProperties["scroll-snap-type"] = {
+ domProp: "scrollSnapType",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [ "scroll-snap-type-x", "scroll-snap-type-y" ],
+ initial_values: [ "none" ],
+ other_values: [ "mandatory", "proximity" ],
+ invalid_values: [ "auto", "1px" ]
+ };
+ gCSSProperties["scroll-snap-type-x"] = {
+ domProp: "scrollSnapTypeX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: ["mandatory", "proximity"],
+ invalid_values: [ "auto", "1px" ]
+ };
+ gCSSProperties["scroll-snap-type-y"] = {
+ domProp: "scrollSnapTypeY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: ["mandatory", "proximity"],
+ invalid_values: [ "auto", "1px" ]
+ };
+}
+
+function SupportsMaskShorthand() {
+ return "maskImage" in document.documentElement.style;
+}
+
+if (SupportsMaskShorthand()) {
+ gCSSProperties["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)",
+ "-moz-radial-gradient(10% bottom, #ffffff, black) add no-repeat",
+ "-moz-linear-gradient(10px 10px -45deg, red, blue) repeat",
+ "-moz-linear-gradient(10px 10px -0.125turn, red, blue) repeat",
+ "-moz-repeating-radial-gradient(10% bottom, #ffffff, black) add no-repeat",
+ "-moz-repeating-linear-gradient(10px 10px -45deg, red, blue) 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",
+ "url(404.png), -moz-linear-gradient(20px 20px -45deg, blue, green), -moz-element(#a) 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)",
+ ],
+ invalid_values: [
+ /* mixes with keywords have to be in correct order */
+ "50% left", "top 50%",
+ /* no quirks mode colors */
+ "-moz-radial-gradient(10% bottom, ffffff, black) add no-repeat",
+ /* no quirks mode lengths */
+ "-moz-linear-gradient(10 10px -45deg, red, blue) repeat",
+ "-moz-linear-gradient(10px 10 -45deg, red, blue) repeat",
+ "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",
+ "alpha padding-box url(404.png) border-box",
+ "alpha padding-box url(404.png) padding-box",
+ "-moz-element(#a rubbish)",
+ "left top / match-source"
+ ]
+ };
+ gCSSProperties["mask-clip"] = {
+ domProp: "maskClip",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "border-box" ],
+ other_values: [ "content-box", "padding-box", "border-box, padding-box", "padding-box, padding-box, padding-box", "border-box, border-box" ],
+ invalid_values: [ "margin-box", "content-box content-box" ]
+ };
+ gCSSProperties["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(validGradientAndElementValues),
+ invalid_values: [
+ ].concat(invalidGradientAndElementValues),
+ unbalanced_values: [
+ ].concat(unbalancedGradientAndElementValues)
+ };
+ gCSSProperties["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" ]
+ };
+ gCSSProperties["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" ]
+ };
+ gCSSProperties["mask-origin"] = {
+ domProp: "maskOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "border-box" ],
+ other_values: [ "padding-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" ]
+ };
+ gCSSProperties["mask-position"] = {
+ domProp: "maskPosition",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ 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: [ "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)"],
+ };
+ gCSSProperties["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%"],
+ };
+ gCSSProperties["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%"],
+ };
+ gCSSProperties["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" ]
+ };
+ gCSSProperties["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)" ]
+ };
+} else {
+ gCSSProperties["mask"] = {
+ domProp: "mask",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "none" ],
+ other_values: [ "url(#mymask)" ],
+ invalid_values: []
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.webkit")) {
+ gCSSProperties["-webkit-animation"] = {
+ domProp: "webkitAnimation",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "animation",
+ subproperties: [ "animation-name", "animation-duration", "animation-timing-function", "animation-delay", "animation-direction", "animation-fill-mode", "animation-iteration-count", "animation-play-state" ],
+ };
+ gCSSProperties["-webkit-animation-delay"] = {
+ domProp: "webkitAnimationDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-delay",
+ subproperties: [ "animation-delay" ],
+ };
+ gCSSProperties["-webkit-animation-direction"] = {
+ domProp: "webkitAnimationDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-direction",
+ subproperties: [ "animation-direction" ],
+ };
+ gCSSProperties["-webkit-animation-duration"] = {
+ domProp: "webkitAnimationDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-duration",
+ subproperties: [ "animation-duration" ],
+ };
+ gCSSProperties["-webkit-animation-fill-mode"] = {
+ domProp: "webkitAnimationFillMode",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-fill-mode",
+ subproperties: [ "animation-fill-mode" ],
+ };
+ gCSSProperties["-webkit-animation-iteration-count"] = {
+ domProp: "webkitAnimationIterationCount",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-iteration-count",
+ subproperties: [ "animation-iteration-count" ],
+ };
+ gCSSProperties["-webkit-animation-name"] = {
+ domProp: "webkitAnimationName",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-name",
+ subproperties: [ "animation-name" ],
+ };
+ gCSSProperties["-webkit-animation-play-state"] = {
+ domProp: "webkitAnimationPlayState",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-play-state",
+ subproperties: [ "animation-play-state" ],
+ };
+ gCSSProperties["-webkit-animation-timing-function"] = {
+ domProp: "webkitAnimationTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "animation-timing-function",
+ subproperties: [ "animation-timing-function" ],
+ };
+ gCSSProperties["-webkit-filter"] = {
+ domProp: "webkitFilter",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "filter",
+ subproperties: [ "filter" ],
+ };
+ gCSSProperties["-webkit-text-fill-color"] = {
+ domProp: "webkitTextFillColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ 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)" ]
+ };
+ gCSSProperties["-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" ]
+ };
+ gCSSProperties["-webkit-text-stroke-color"] = {
+ domProp: "webkitTextStrokeColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ 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)" ]
+ };
+ gCSSProperties["-webkit-text-stroke-width"] = {
+ domProp: "webkitTextStrokeWidth",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ 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%" ]
+ },
+ gCSSProperties["-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" ],
+ };
+ gCSSProperties["-webkit-transform"] = {
+ domProp: "webkitTransform",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform",
+ subproperties: [ "transform" ],
+ };
+ gCSSProperties["-webkit-transform-origin"] = {
+ domProp: "webkitTransformOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-origin",
+ subproperties: [ "transform-origin" ],
+ };
+ gCSSProperties["-webkit-transform-style"] = {
+ domProp: "webkitTransformStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-style",
+ subproperties: [ "transform-style" ],
+ };
+ gCSSProperties["-webkit-backface-visibility"] = {
+ domProp: "webkitBackfaceVisibility",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "backface-visibility",
+ subproperties: [ "backface-visibility" ],
+ };
+ gCSSProperties["-webkit-perspective"] = {
+ domProp: "webkitPerspective",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective",
+ subproperties: [ "perspective" ],
+ };
+ gCSSProperties["-webkit-perspective-origin"] = {
+ domProp: "webkitPerspectiveOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective-origin",
+ subproperties: [ "perspective-origin" ],
+ };
+ gCSSProperties["-webkit-transition"] = {
+ domProp: "webkitTransition",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "transition",
+ subproperties: [ "transition-property", "transition-duration", "transition-timing-function", "transition-delay" ],
+ };
+ gCSSProperties["-webkit-transition-delay"] = {
+ domProp: "webkitTransitionDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transition-delay",
+ subproperties: [ "transition-delay" ],
+ };
+ gCSSProperties["-webkit-transition-duration"] = {
+ domProp: "webkitTransitionDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transition-duration",
+ subproperties: [ "transition-duration" ],
+ };
+ gCSSProperties["-webkit-transition-property"] = {
+ domProp: "webkitTransitionProperty",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transition-property",
+ subproperties: [ "transition-property" ],
+ };
+ gCSSProperties["-webkit-transition-timing-function"] = {
+ domProp: "webkitTransitionTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transition-timing-function",
+ subproperties: [ "transition-timing-function" ],
+ };
+ gCSSProperties["-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" ],
+ };
+ gCSSProperties["-webkit-border-top-left-radius"] = {
+ domProp: "webkitBorderTopLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "border-top-left-radius",
+ subproperties: [ "border-top-left-radius" ],
+ };
+ gCSSProperties["-webkit-border-top-right-radius"] = {
+ domProp: "webkitBorderTopRightRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "border-top-right-radius",
+ subproperties: [ "border-top-right-radius" ],
+ };
+ gCSSProperties["-webkit-border-bottom-left-radius"] = {
+ domProp: "webkitBorderBottomLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "border-bottom-left-radius",
+ subproperties: [ "border-bottom-left-radius" ],
+ };
+ gCSSProperties["-webkit-border-bottom-right-radius"] = {
+ domProp: "webkitBorderBottomRightRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "border-bottom-right-radius",
+ subproperties: [ "border-bottom-right-radius" ],
+ };
+ gCSSProperties["-webkit-background-clip"] = {
+ domProp: "webkitBackgroundClip",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "background-clip",
+ subproperties: [ "background-clip" ],
+ };
+ gCSSProperties["-webkit-background-origin"] = {
+ domProp: "webkitBackgroundOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "background-origin",
+ subproperties: [ "background-origin" ],
+ };
+ gCSSProperties["-webkit-background-size"] = {
+ domProp: "webkitBackgroundSize",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "background-size",
+ subproperties: [ "background-size" ],
+ };
+ gCSSProperties["-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" ],
+ };
+ gCSSProperties["-webkit-box-shadow"] = {
+ domProp: "webkitBoxShadow",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "box-shadow",
+ subproperties: [ "box-shadow" ],
+ };
+ gCSSProperties["-webkit-box-sizing"] = {
+ domProp: "webkitBoxSizing",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "box-sizing",
+ subproperties: [ "box-sizing" ],
+ };
+ gCSSProperties["-webkit-box-flex"] = {
+ domProp: "webkitBoxFlex",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-flex",
+ subproperties: [ "-moz-box-flex" ],
+ };
+ gCSSProperties["-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" ],
+ };
+ gCSSProperties["-webkit-box-orient"] = {
+ domProp: "webkitBoxOrient",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-orient",
+ subproperties: [ "-moz-box-orient" ],
+ };
+ gCSSProperties["-webkit-box-direction"] = {
+ domProp: "webkitBoxDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-direction",
+ subproperties: [ "-moz-box-direction" ],
+ };
+ gCSSProperties["-webkit-box-align"] = {
+ domProp: "webkitBoxAlign",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-align",
+ subproperties: [ "-moz-box-align" ],
+ };
+ gCSSProperties["-webkit-box-pack"] = {
+ domProp: "webkitBoxPack",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-pack",
+ subproperties: [ "-moz-box-pack" ],
+ };
+ gCSSProperties["-webkit-flex-direction"] = {
+ domProp: "webkitFlexDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-direction",
+ subproperties: [ "flex-direction" ],
+ };
+ gCSSProperties["-webkit-flex-wrap"] = {
+ domProp: "webkitFlexWrap",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-wrap",
+ subproperties: [ "flex-wrap" ],
+ };
+ gCSSProperties["-webkit-flex-flow"] = {
+ domProp: "webkitFlexFlow",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "flex-flow",
+ subproperties: [ "flex-direction", "flex-wrap" ],
+ };
+ gCSSProperties["-webkit-order"] = {
+ domProp: "webkitOrder",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "order",
+ subproperties: [ "order" ],
+ };
+ gCSSProperties["-webkit-flex"] = {
+ domProp: "webkitFlex",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "flex",
+ subproperties: [ "flex-grow", "flex-shrink", "flex-basis" ],
+ };
+ gCSSProperties["-webkit-flex-grow"] = {
+ domProp: "webkitFlexGrow",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-grow",
+ subproperties: [ "flex-grow" ],
+ };
+ gCSSProperties["-webkit-flex-shrink"] = {
+ domProp: "webkitFlexShrink",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-shrink",
+ subproperties: [ "flex-shrink" ],
+ };
+ gCSSProperties["-webkit-flex-basis"] = {
+ domProp: "webkitFlexBasis",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-basis",
+ subproperties: [ "flex-basis" ],
+ };
+ gCSSProperties["-webkit-justify-content"] = {
+ domProp: "webkitJustifyContent",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "justify-content",
+ subproperties: [ "justify-content" ],
+ };
+ gCSSProperties["-webkit-align-items"] = {
+ domProp: "webkitAlignItems",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "align-items",
+ subproperties: [ "align-items" ],
+ };
+ gCSSProperties["-webkit-align-self"] = {
+ domProp: "webkitAlignSelf",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "align-self",
+ subproperties: [ "align-self" ],
+ };
+ gCSSProperties["-webkit-align-content"] = {
+ domProp: "webkitAlignContent",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "align-content",
+ subproperties: [ "align-content" ],
+ };
+ gCSSProperties["-webkit-user-select"] = {
+ domProp: "webkitUserSelect",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-user-select",
+ subproperties: [ "-moz-user-select" ],
+ };
+
+ if (SupportsMaskShorthand()) {
+ gCSSProperties["-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" ],
+ };
+ gCSSProperties["-webkit-mask-clip"] = {
+ domProp: "webkitMaskClip",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-clip",
+ subproperties: [ "mask-clip" ],
+ };
+
+ gCSSProperties["-webkit-mask-composite"] = {
+ domProp: "webkitMaskComposite",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-composite",
+ subproperties: [ "mask-composite" ],
+ };
+
+ gCSSProperties["-webkit-mask-image"] = {
+ domProp: "webkitMaskImage",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-image",
+ subproperties: [ "mask-image" ],
+ };
+ gCSSProperties["-webkit-mask-origin"] = {
+ domProp: "webkitMaskOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-origin",
+ subproperties: [ "mask-origin" ],
+ };
+ gCSSProperties["-webkit-mask-position"] = {
+ domProp: "webkitMaskPosition",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-position",
+ subproperties: [ "mask-position" ],
+ };
+ gCSSProperties["-webkit-mask-position-x"] = {
+ domProp: "webkitMaskPositionX",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-position-x",
+ subproperties: [ "mask-position-x" ],
+ };
+ gCSSProperties["-webkit-mask-position-y"] = {
+ domProp: "webkitMaskPositionY",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-position-y",
+ subproperties: [ "mask-position-y" ],
+ };
+ gCSSProperties["-webkit-mask-repeat"] = {
+ domProp: "webkitMaskRepeat",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-repeat",
+ subproperties: [ "mask-repeat" ],
+ };
+ gCSSProperties["-webkit-mask-size"] = {
+ domProp: "webkitMaskSize",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-size",
+ subproperties: [ "mask-size" ],
+ };
+ }
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.unset-value.enabled")) {
+ gCSSProperties["animation"].invalid_values.push("2s unset");
+ gCSSProperties["animation-direction"].invalid_values.push("normal, unset", "unset, normal");
+ gCSSProperties["animation-name"].invalid_values.push("bounce, unset", "unset, bounce");
+ gCSSProperties["-moz-border-bottom-colors"].invalid_values.push("red unset", "unset red");
+ gCSSProperties["-moz-border-left-colors"].invalid_values.push("red unset", "unset red");
+ gCSSProperties["border-radius"].invalid_values.push("unset 2px", "unset / 2px", "2px unset", "2px / unset");
+ gCSSProperties["border-bottom-left-radius"].invalid_values.push("unset 2px", "2px unset");
+ gCSSProperties["border-bottom-right-radius"].invalid_values.push("unset 2px", "2px unset");
+ gCSSProperties["border-top-left-radius"].invalid_values.push("unset 2px", "2px unset");
+ gCSSProperties["border-top-right-radius"].invalid_values.push("unset 2px", "2px unset");
+ gCSSProperties["-moz-border-right-colors"].invalid_values.push("red unset", "unset red");
+ gCSSProperties["-moz-border-top-colors"].invalid_values.push("red unset", "unset red");
+ gCSSProperties["-moz-outline-radius"].invalid_values.push("unset 2px", "unset / 2px", "2px unset", "2px / unset");
+ gCSSProperties["-moz-outline-radius-bottomleft"].invalid_values.push("unset 2px", "2px unset");
+ gCSSProperties["-moz-outline-radius-bottomright"].invalid_values.push("unset 2px", "2px unset");
+ gCSSProperties["-moz-outline-radius-topleft"].invalid_values.push("unset 2px", "2px unset");
+ gCSSProperties["-moz-outline-radius-topright"].invalid_values.push("unset 2px", "2px unset");
+ gCSSProperties["background-image"].invalid_values.push("-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)");
+ gCSSProperties["box-shadow"].invalid_values.push("unset, 2px 2px", "2px 2px, unset", "inset unset");
+ gCSSProperties["text-overflow"].invalid_values.push('"hello" unset', 'unset "hello"', 'clip unset', 'unset clip', 'unset inherit', 'unset none', 'initial unset');
+ gCSSProperties["text-shadow"].invalid_values.push("unset, 2px 2px", "2px 2px, unset");
+ gCSSProperties["transition"].invalid_values.push("2s unset");
+ gCSSProperties["transition-property"].invalid_values.push("unset, color", "color, unset");
+ if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) {
+ gCSSProperties["filter"].invalid_values.push("drop-shadow(unset, 2px 2px)", "drop-shadow(2px 2px, unset)");
+ }
+ if (IsCSSPropertyPrefEnabled("layout.css.text-align-unsafe-value.enabled")) {
+ gCSSProperties["text-align"].other_values.push("true left");
+ } else {
+ gCSSProperties["text-align"].invalid_values.push("true left");
+ }
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.float-logical-values.enabled")) {
+ gCSSProperties["float"].other_values.push("inline-start");
+ gCSSProperties["float"].other_values.push("inline-end");
+ gCSSProperties["clear"].other_values.push("inline-start");
+ gCSSProperties["clear"].other_values.push("inline-end");
+} else {
+ gCSSProperties["float"].invalid_values.push("inline-start");
+ gCSSProperties["float"].invalid_values.push("inline-end");
+ gCSSProperties["clear"].invalid_values.push("inline-start");
+ gCSSProperties["clear"].invalid_values.push("inline-end");
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.background-clip-text.enabled")) {
+ gCSSProperties["background-clip"].other_values.push(
+ "text",
+ "content-box, text",
+ "text, border-box",
+ "text, text"
+ );
+ gCSSProperties["background"].other_values.push(
+ "url(404.png) green padding-box text",
+ "content-box text url(404.png) blue"
+ );
+} else {
+ gCSSProperties["background-clip"].invalid_values.push(
+ "text",
+ "content-box, text",
+ "text, border-box",
+ "text, text"
+ );
+ gCSSProperties["background"].invalid_values.push(
+ "url(404.png) green padding-box text",
+ "content-box text url(404.png) blue"
+ );
+}
+
+// Copy aliased properties' fields from their alias targets.
+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]
+ }
+ });
+ }
+ }
+}
+
+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" ],
+ invalid_values: []
+ };
+}
diff --git a/layout/style/test/redirect.sjs b/layout/style/test/redirect.sjs
new file mode 100644
index 000000000..b6249cadf
--- /dev/null
+++ b/layout/style/test/redirect.sjs
@@ -0,0 +1,5 @@
+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 000000000..7eb7d8f8d
--- /dev/null
+++ b/layout/style/test/redundant_font_download.sjs
@@ -0,0 +1,60 @@
+const BinaryOutputStream =
+ Components.Constructor("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream");
+
+// this is simply a hex dump of a red square .PNG image
+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)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [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);
+
+ var log = getState("bug-879963-request-log") || "";
+
+ var 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, RED_SQUARE.length);
+ } else if (query["q"] == "image") {
+ log = log + ";" + query["q"];
+ response.setHeader("Content-Type", "image/png", false);
+ stream.writeByteArray(RED_SQUARE, RED_SQUARE.length);
+ } 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/style_attribute_tests.js b/layout/style/test/style_attribute_tests.js
new file mode 100644
index 000000000..be24baf95
--- /dev/null
+++ b/layout/style/test/style_attribute_tests.js
@@ -0,0 +1,27 @@
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("load", runTests, false);
+
+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/external-variable-url.css b/layout/style/test/support/external-variable-url.css
new file mode 100644
index 000000000..b13150428
--- /dev/null
+++ b/layout/style/test/support/external-variable-url.css
@@ -0,0 +1,3 @@
+div {
+ --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 000000000..89850f0c5
--- /dev/null
+++ b/layout/style/test/test_acid3_test46.html
@@ -0,0 +1,141 @@
+<!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 type="text/javascript" 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) {
+ var p = doc.createElement('p');
+ p.id = names[i];
+ doc.body.appendChild(p);
+ }
+ var count = 0;
+ var check = function (c, e) {
+ count += 1;
+ var 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 000000000..e112378a9
--- /dev/null
+++ b/layout/style/test/test_addSheet.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for addSheet</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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; version=1.8">
+
+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, null);
+ is(cs.getPropertyValue('color'), "rgb(0, 0, 0)", "should have default color");
+ var windowUtils = SpecialPowers.wrap(win)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+ 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;}", null, null);
+ 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 000000000..f1b8f6ed8
--- /dev/null
+++ b/layout/style/test/test_additional_sheets.html
@@ -0,0 +1,314 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for additional sheets</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=737003">Mozilla Bug 737003</a>
+<iframe id="iframe" src="additional_sheets_helper.html"></iframe>
+<pre id="test">
+<script type="application/javascript; version=1.8">
+
+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), null, null);
+ var windowUtils = SpecialPowers.wrap(win)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+ windowUtils.loadSheet(uri, windowUtils[type]);
+}
+
+function removeSheet(win, style, type)
+{
+ var uri = gIOService.newURI(getUri(style), null, null);
+ var windowUtils = SpecialPowers.wrap(win)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+ 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), null, null);
+ gSSService.loadAndRegisterSheet(uri, gSSService[type]);
+ is(gSSService.sheetRegistered(uri, gSSService[type]), true);
+}
+
+function unregisterSheet(win, style, type)
+{
+ var uri = gIOService.newURI(getUri(style), null, null);
+ 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.parentNode.removeChild(node);
+}
+
+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, null);
+ 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'), 'transparent', 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 000000000..3cd4b8b0e
--- /dev/null
+++ b/layout/style/test/test_align_justify_computed_values.html
@@ -0,0 +1,529 @@
+<!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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="position:relative">
+<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 style context.)
+
+ 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 style context.)
+
+ 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 elem = document.body;
+ var child = document.getElementById("display");
+ var abs = document.getElementById("absChild");
+ is(getComputedAlignItems(elem), 'normal', "default align-items value for block container");
+ is(getComputedAlignSelf(child), 'auto', "default align-self value for block child");
+ is(getComputedAlignSelf(abs), 'auto', "default align-self value for block container abs.pos. child");
+ elem.style.alignItems = "end";
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for block container abs.pos. child");
+ elem.style.alignItems = "left";
+ is(getComputedAlignItems(elem), 'left', "align-items:left computes to itself for a block");
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto persists for block child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for block container abs.pos. child");
+ elem.style.alignItems = "right";
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for block container abs.pos. child");
+ elem.style.alignItems = "right safe";
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child");
+ elem.style.alignItems = "";
+ child.style.alignSelf = "left";
+ is(getComputedAlignSelf(child), 'left', "align-self:left computes to left for block child");
+ child.style.alignSelf = "right";
+ is(getComputedAlignSelf(child), 'right', "align-self:right computes to right for block child");
+ child.style.alignSelf = "";
+ abs.style.alignSelf = "right";
+ is(getComputedAlignSelf(abs), 'right', "align-self:right computes to right 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), 'left', "align-items:left computes to itself for flex container");
+ // XXX TODO: add left/right tests (bug 1221565)
+ 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), 'left', "align-items:left computes to itself for grid container");
+ 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(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 safe";
+ is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child");
+ item.style.alignSelf = "left";
+ is(getComputedAlignSelf(item), 'left', "align-self:left computes to left on grid item");
+ item.style.alignSelf = "right";
+ is(getComputedAlignSelf(item), 'right', "align-self:right computes to right on grid item");
+ item.style.alignSelf = "right safe";
+ is(getComputedAlignSelf(item), 'right safe', "align-self:'right safe' computes to 'right safe' on grid item");
+ item.style.alignSelf = "";
+ abs.style.alignSelf = "right";
+ is(getComputedAlignSelf(abs), 'right', "align-self:right computes to right on grid container abs.pos. child");
+ abs.style.alignSelf = "";
+ elem.style.alignItems = "";
+ item.style.alignSelf = "";
+ }
+ testGridAlignItemsSelf(document.getElementById("gridContainer"));
+ testGridAlignItemsSelf(document.getElementById("gridContainerFlex"));
+
+ //
+ // justify-items/self tests:
+ //
+ //// Block tests
+ var elem = document.body;
+ var child = document.getElementById("display");
+ var abs = document.getElementById("absChild");
+ is(getComputedJustifyItems(elem), 'normal', "default justify-items value for block container");
+ is(getComputedJustifySelf(child), 'auto', "default justify-self value for block container child");
+ is(getComputedJustifySelf(abs), 'auto', "default justify-self value for block container abs.pos. child");
+ elem.style.justifyItems = "end";
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for block container abs.pos. child");
+ elem.style.justifyItems = "left";
+ is(getComputedJustifyItems(elem), 'left', "justify-items:left computes to itself on a block");
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for block container abs.pos. child");
+ elem.style.justifyItems = "right";
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for block container abs.pos. child");
+ elem.style.justifyItems = "right safe";
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ elem.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 = "";
+ abs.style.justifySelf = "right";
+ is(getComputedJustifySelf(abs), '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 = "right safe";
+ 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 = "right safe";
+ 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 = "right safe";
+ is(getComputedJustifySelf(item), 'right safe', "justify-self:'right safe' computes to 'right safe' 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
+ var elem = document.body;
+ var child = document.getElementById("display");
+ var abs = document.getElementById("absChild");
+ is(getComputedAlignContent(elem), 'normal', "default align-content value for block container");
+ is(getComputedAlignContent(child), 'normal', "default align-content value for block child");
+ is(getComputedAlignContent(abs), 'normal', "default align-content value for block container abs.pos. child");
+ elem.style.alignContent = "end";
+ is(getComputedAlignContent(child), 'normal', "default align-content isn't affected by parent align-content value for in-flow child");
+ is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child");
+ elem.style.alignContent = "left";
+ is(getComputedAlignContent(elem), 'left', "align-content:left computes to left on block child");
+ is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child");
+ elem.style.alignContent = "right";
+ is(getComputedAlignContent(elem), 'right', "align-content:right computes to right on block child");
+ is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child");
+ elem.style.alignContent = "right safe";
+ is(getComputedAlignContent(elem), 'right safe', "align-content:'right safe' computes to 'align-content:right safe'");
+ elem.style.alignContent = "";
+ child.style.alignContent = "left";
+ is(getComputedAlignContent(child), 'left', "align-content:left computes to left on block child");
+ child.style.alignContent = "right";
+ is(getComputedAlignContent(child), 'right', "align-content:right computes to right on block child");
+ child.style.alignContent = "left safe";
+ is(getComputedAlignContent(child), 'left safe', "align-content:left computes to 'left safe' on block child");
+ child.style.alignContent = "";
+ abs.style.alignContent = "right";
+ is(getComputedAlignContent(abs), 'right', "align-content:right computes to right on block container abs.pos. child");
+ abs.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 = "flex-end safe";
+ is(getComputedAlignContent(elem), 'flex-end safe', "align-content:'flex-end safe' 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");
+ // XXX TODO: add left/right tests (bug 1221565)
+ 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 = "end safe";
+ is(getComputedAlignContent(elem), 'end safe', "align-content:'end safe' 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 = "left";
+ is(getComputedAlignContent(elem), 'left', "align-content:left computes to left on grid container");
+ is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for grid container abs.pos. child");
+ elem.style.alignContent = "right";
+ is(getComputedAlignContent(elem), 'right', "align-content:right computes to right on grid container");
+ is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for grid container abs.pos. child");
+ elem.style.alignContent = "right safe";
+ item.style.alignContent = "inherit";
+ abs.style.alignContent = "inherit";
+ is(getComputedAlignContent(elem), 'right safe', "align-content:'right safe' computes to 'align-content:right safe' on grid container");
+ is(getComputedAlignContent(item), 'right safe', "align-content:'right safe' inherits as 'align-content:right safe' to grid item");
+ is(getComputedAlignContent(abs), 'right safe', "align-content:'right safe' inherits as 'align-content:right safe' to grid container abs.pos. child");
+ item.style.alignContent = "left";
+ is(getComputedAlignContent(item), 'left', "align-content:left computes to left on grid item");
+ item.style.alignContent = "right";
+ is(getComputedAlignContent(item), 'right', "align-content:right computes to right on grid item");
+ item.style.alignContent = "right safe";
+ is(getComputedAlignContent(item), 'right safe', "align-content:'right safe' computes to 'right safe' on grid item");
+ item.style.alignContent = "";
+ abs.style.alignContent = "right";
+ is(getComputedAlignContent(abs), 'right', "align-content:right computes to right on grid container abs.pos. child");
+ abs.style.alignContent = "";
+ elem.style.alignContent = "";
+ item.style.alignContent = "";
+ }
+ testGridAlignContent(document.getElementById("gridContainer"));
+ testGridAlignContent(document.getElementById("gridContainerFlex"));
+
+
+ //
+ // justify-content tests:
+ //
+ //// Block tests
+ var elem = document.body;
+ var child = document.getElementById("display");
+ var abs = document.getElementById("absChild");
+ is(getComputedJustifyContent(elem), 'normal', "default justify-content value for block container");
+ is(getComputedJustifyContent(child), 'normal', "default justify-content value for block child");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content value for block container abs.pos. child");
+ elem.style.justifyContent = "end";
+ is(getComputedJustifyContent(child), 'normal', "default justify-content isn't affected by parent justify-content value for in-flow child");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child");
+ elem.style.justifyContent = "left";
+ is(getComputedJustifyContent(elem), 'left', "justify-content:left computes to left on block child");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child");
+ elem.style.justifyContent = "right";
+ is(getComputedJustifyContent(elem), 'right', "justify-content:right computes to right on block child");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child");
+ elem.style.justifyContent = "right safe";
+ is(getComputedJustifyContent(elem), 'right safe', "justify-content:'right safe' computes to 'justify-content:right safe'");
+ elem.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 = "left safe";
+ is(getComputedJustifyContent(child), 'left safe', "justify-content:left computes to 'left safe' on block child");
+ child.style.justifyContent = "";
+ abs.style.justifyContent = "right";
+ is(getComputedJustifyContent(abs), 'right', "justify-content:right computes to right on block container abs.pos. child");
+ abs.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 = "flex-end safe";
+ is(getComputedJustifyContent(elem), 'flex-end safe', "justify-content:'flex-end safe' 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 = "end safe";
+ is(getComputedJustifyContent(elem), 'end safe', "justify-content:'end safe' 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 = "right safe";
+ item.style.justifyContent = "inherit";
+ abs.style.justifyContent = "inherit";
+ is(getComputedJustifyContent(elem), 'right safe', "justify-content:'right safe' computes to 'justify-content:right safe' on grid container");
+ is(getComputedJustifyContent(item), 'right safe', "justify-content:'right safe' inherits as 'justify-content:right safe' to grid item");
+ is(getComputedJustifyContent(abs), 'right safe', "justify-content:'right safe' inherits as 'justify-content:right safe' 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 = "right safe";
+ is(getComputedJustifyContent(item), 'right safe', "justify-content:'right safe' computes to 'right safe' 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_align_shorthand_serialization.html b/layout/style/test/test_align_shorthand_serialization.html
new file mode 100644
index 000000000..95a3f4814
--- /dev/null
+++ b/layout/style/test/test_align_shorthand_serialization.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test serialization of CSS Align shorthand properties</title>
+ <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com">
+ <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 = {
+ alignContent: "normal",
+ alignItems: "normal",
+ alignSelf: "auto",
+ justifyContent: "normal",
+ justifyItems: "auto",
+ justifySelf: "auto",
+};
+
+var place_content_test_cases = [
+ {
+ alignContent: "center",
+ shorthand: "center normal",
+ },
+ {
+ alignContent: "baseline right safe",
+ shorthand: "",
+ },
+ {
+ justifyContent: "start safe",
+ shorthand: "",
+ },
+ {
+ justifyContent: "space-evenly start",
+ shorthand: "",
+ },
+ {
+ alignContent: "start",
+ justifyContent: "end",
+ shorthand: "start end",
+ },
+];
+
+var place_items_test_cases = [
+ {
+ alignItems: "center",
+ shorthand: "center auto",
+ },
+ {
+ alignItems: "baseline",
+ shorthand: "baseline auto",
+ },
+ {
+ justifyItems: "start safe",
+ shorthand: "",
+ },
+ {
+ justifyItems: "stretch",
+ shorthand: "normal stretch",
+ },
+ {
+ justifyItems: "left legacy",
+ shorthand: "",
+ },
+ {
+ alignItems: "stretch",
+ justifyItems: "end",
+ shorthand: "stretch end",
+ },
+];
+
+var place_self_test_cases = [
+ {
+ alignSelf: "right",
+ shorthand: "right auto",
+ },
+ {
+ alignSelf: "self-end safe",
+ shorthand: "",
+ },
+ {
+ justifySelf: "unsafe start",
+ shorthand: "",
+ },
+ {
+ justifySelf: "last baseline start",
+ shorthand: "",
+ },
+ {
+ alignSelf: "baseline",
+ justifySelf: "last baseline",
+ shorthand: "baseline last baseline",
+ },
+];
+
+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));
+ });
+}
+
+run_tests(place_content_test_cases, "placeContent", [
+ "alignContent", "justifyContent"]);
+run_tests(place_items_test_cases, "placeItems", [
+ "alignItems", "justifyItems"]);
+run_tests(place_self_test_cases, "placeSelf", [
+ "alignSelf", "justifySelf"]);
+
+</script>
+</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 000000000..6185778cc
--- /dev/null
+++ b/layout/style/test/test_all_shorthand.html
@@ -0,0 +1,159 @@
+<!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>
+
+<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();
+SpecialPowers.pushPrefEnv({ "set": [["layout.css.all-shorthand.enabled", true],
+ ["layout.css.unset-value.enabled", true]] }, runTest);
+</script>
diff --git a/layout/style/test/test_animations.html b/layout/style/test/test_animations.html
new file mode 100644
index 000000000..eaccba122
--- /dev/null
+++ b/layout/style/test/test_animations.html
@@ -0,0 +1,2047 @@
+<!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 type="application/javascript" 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% { line-height: 3; 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 { -moz-transform: rotate(0deg) translateX(0px) scaleX(1)
+ translate(0px) scale3d(1, 1, 1); }
+ to { -moz-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 }
+ }
+ </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);
+
+/*
+ * 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(cs.paddingTop, "60px", "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: "" },
+ { type: 'animationiteration', 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
+// select { line-height: !important }
+// If that rule changes, we should rewrite it to depend on a different rule.
+var select;
+[ select, cs ] = new_element("select", "");
+var default_line_height = cs.lineHeight;
+done_element();
+[ select, cs ] = new_element("select",
+ "animation: uaoverride 2s linear infinite");
+is(cs.lineHeight, default_line_height,
+ "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.lineHeight, default_line_height,
+ "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("-moz-animation: primitives1 2s linear forwards");
+is(cs.getPropertyValue("-moz-transform"), "matrix(1, 0, 0, 1, 0, 0)",
+ "primitives1 at 0s");
+advance_clock(1000);
+is(cs.getPropertyValue("-moz-transform"),
+ "matrix(-0.707107, 0.707107, -0.707107, -0.707107, 0, 0)",
+ "primitives1 at 1s");
+advance_clock(1000);
+is(cs.getPropertyValue("-moz-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.parentNode.removeChild(dyn_sheet_elt);
+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.parentNode.removeChild(ancestor);
+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();
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</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 000000000..5d328c7aa
--- /dev/null
+++ b/layout/style/test/test_animations_async_tests.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1086937</title>
+ <script type="application/javascript" 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() {
+ SpecialPowers.pushPrefEnv(
+ {"set": [['layout.css.font-loading-api.enabled', true]]},
+ function() { 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 000000000..a68d734dc
--- /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 type="application/javascript" 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 000000000..30d77f20a
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_duration.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for animation.effect.timing on compositor</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ { "set": [[ "dom.animations-api.core.enabled", true]] },
+ function() {
+ window.open("file_animations_effect_timing_duration.html");
+ });
+</script>
+</pre>
+</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 000000000..d4ad918dd
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_enddelay.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for animation.effect.timing.endDelay on compositor</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ { "set": [[ "dom.animations-api.core.enabled", true]] },
+ function() {
+ window.open("file_animations_effect_timing_enddelay.html");
+ });
+</script>
+</pre>
+</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 000000000..625c52867
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_iterations.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for animation.effect.timing.iterations on compositor</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ { "set": [[ "dom.animations-api.core.enabled", true]] },
+ function() {
+ window.open("file_animations_effect_timing_iterations.html");
+ });
+</script>
+</pre>
+</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 000000000..e5def2b34
--- /dev/null
+++ b/layout/style/test/test_animations_event_handler_attribute.html
@@ -0,0 +1,147 @@
+<!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 type="application/javascript"
+ 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);
+
+// 1. Test CSS Animation event handlers.
+
+var targets = createAndRegisterTargets([ 'onanimationstart',
+ 'onanimationiteration',
+ 'onanimationend' ]);
+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(); });
+
+// 2. Test CSS Transition event handlers.
+
+var targets = createAndRegisterTargets([ 'ontransitionend' ]);
+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("transitionend", targets);
+
+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 000000000..5af7639cc
--- /dev/null
+++ b/layout/style/test/test_animations_event_order.html
@@ -0,0 +1,585 @@
+<!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 type="application/javascript"
+ 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';
+
+// 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',
+ 'transitionend' ]
+ .forEach(event =>
+ gDisplay.addEventListener(event,
+ event => gEventsReceived.push(event),
+ false));
+
+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);
+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], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionend 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], 'transitionend' ],
+ [ divs[2], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionend 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], 'transitionend' ],
+ [ divs[0], '::before', 'transitionend' ],
+ [ divs[0], '::after', 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionend 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[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Sorting of transitionend 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[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Sorting of transitionend 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', 'transitionend' ],
+ [ 'opacity', 'transitionend' ],
+ 'Sorting of transitionend 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], '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([ '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[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Simultaneous transitionend on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+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 000000000..d6a54f3b2
--- /dev/null
+++ b/layout/style/test/test_animations_iterationstart.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1248338
+-->
+<head>
+ <title>Test for iterationStart on compositor animations (Bug 1248338)</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1248338">Mozilla Bug 1248338</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ { "set": [[ "dom.animations-api.core.enabled", true]] },
+ function() {
+ window.open("file_animations_iterationstart.html");
+ });
+</script>
+</pre>
+</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 000000000..4b276c896
--- /dev/null
+++ b/layout/style/test/test_animations_omta.html
@@ -0,0 +1,2392 @@
+<!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 type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ 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 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";
+
+/** 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.slice(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(function *() {
+ new_div("animation: transform-anim linear 300s");
+
+ yield waitForPaintsFlushed();
+
+ advance_clock(200000);
+ omta_is("transform", { tx: 100 * 2 / 3 }, RunningOn.Compositor,
+ "OMTA animation is animating as expected");
+ done_div();
+});
+
+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();
+
+ yield 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
+ yield 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
+ yield 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.
+ yield 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(function *() {
+ new_div("animation: anim1 linear 10s");
+ yield 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";
+ yield 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(function *() {
+ new_div("animation: anim1 linear 10s");
+ yield 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";
+ yield 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";
+ yield 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(function *() {
+ new_div("animation: anim1 linear 10s, anim2 linear 10s");
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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);
+ yield 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";
+ yield 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";
+ yield 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(function *() {
+ // 100px at 0%, 50px at 50%, 150px at 100%
+ new_div("transform: translate(100px); " +
+ "animation: kf1 ease 1s alternate infinite");
+ yield 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");
+ yield 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");
+ yield 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");
+ yield 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");
+ yield 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");
+ yield 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");
+ yield 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(function *() {
+ new_div("animation: kf_tf1 ease-in 10s alternate infinite");
+ yield 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");
+ yield 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(function *() {
+ new_div("animation: anim2 ease-in-out 10s");
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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(function *() {
+ new_div("animation: anim2 ease-in 10s 0.3 forwards");
+ yield 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
+ yield 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");
+ yield 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);
+ yield 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);
+ yield 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");
+ yield 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);
+ yield 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(function *() {
+ new_div("animation: anim2 ease-in 10s infinite");
+ gDiv.style.animationDirection = "normal";
+ yield waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 0s");
+ gDiv.style.animationDirection = "reverse";
+ yield waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 0s");
+ gDiv.style.animationDirection = "alternate";
+ yield waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 0s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ yield waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 0s");
+ advance_clock(2000);
+ gDiv.style.animationDirection = "normal";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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(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";
+ yield 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";
+ yield 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";
+ yield 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";
+ yield 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 = "";
+ yield 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);
+ yield 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";
+ yield 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
+ yield 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
+ yield 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
+ yield 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
+ yield 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)
+ yield 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
+ yield 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);
+ yield 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(function *() {
+ // test positive delay
+ new_div("animation: anim2 1s 0.5s ease-out");
+ yield 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);
+ yield 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");
+ yield 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";
+ yield 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";
+ yield waitForPaintsFlushed();
+ advance_clock(200);
+ omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 600ms");
+ advance_clock(200);
+ yield waitForPaints();
+ omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor,
+ "dynamic delay delay test at 800ms");
+ advance_clock(1000);
+ yield waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "dynamic delay delay test at 1800ms (1)");
+ gDiv.style.animationDelay = "1.5s";
+ yield 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";
+ yield 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");
+ yield 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";
+ yield 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";
+ yield waitForPaintsFlushed();
+ advance_clock(100);
+ yield 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";
+ yield 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)");
+ yield waitForPaintsFlushed();
+ advance_clock(300);
+ gDiv.style.transform = "translate(100px)";
+ gDiv.style.animation = "kf1 1s -0.1s ease-in";
+ yield 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();
+ yield 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: "" },
+ { type: 'animationiteration', 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(function *() {
+ new_div("");
+ listen();
+ gDiv.id = "withbefore";
+ yield 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";
+ yield 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(function *() {
+ new_div("animation: multiprop 1s ease-in-out alternate infinite");
+ yield 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(function *() {
+ new_div("animation: anim2 1s linear");
+ yield 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(function *() {
+ new_div("animation: always_fifty 1s linear infinite; " +
+ "transform: translate(200px)");
+ yield 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;");
+ yield 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(function *() {
+ new_div("animation: kf3 1s linear forwards");
+ yield waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "bug 686656 test 1 at 0ms");
+ advance_clock(250);
+ gDisplay.style.color = "blue";
+ yield 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);
+ yield 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(function *() {
+ new_div("animation: kf3 1s linear forwards");
+ yield waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "bug 686656 test 2 at 0ms");
+ advance_clock(250);
+ gDisplay.style.overflow = "scroll";
+ yield 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);
+ yield 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(function *() {
+ new_div("animation: cascade 1s linear forwards; position: relative");
+ yield 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);
+ yield 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(function *() {
+ new_div("animation: cascade2 8s linear forwards");
+ yield 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);
+ yield waitForPaints();
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ "cascade2 test at 8s");
+ done_div();
+});
+
+addAsyncAnimTest(function *() {
+ new_div("animation: primitives1 2s linear forwards");
+ yield 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);
+ yield waitForPaints();
+ omta_is("transform", [ 0, -1, 1, 0, 0, 0 ], RunningOn.MainThread,
+ "primitives1 at 0s");
+ done_div();
+});
+
+addAsyncAnimTest(function *() {
+ new_div("animation: important1 1s linear forwards");
+ yield 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);
+ yield waitForPaints();
+ omta_is("opacity", 0.8, RunningOn.Either, "important1 test at 1s");
+ done_div();
+});
+
+addAsyncAnimTest(function *() {
+ new_div("animation: important2 1s linear forwards");
+ yield 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);
+ yield 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(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;");
+ yield 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;");
+ yield 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(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");
+ yield 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";
+ yield waitForPaintsFlushed();
+ omta_is("transform", { tx: 125 }, RunningOn.Compositor,
+ "dynamic rule change test, change in @keyframes name applies");
+ dyn2.appendRule("50% { transform: translate(0px) }");
+ yield 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)";
+ yield waitForPaintsFlushed();
+ omta_is("transform", { tx: -50 }, RunningOn.Compositor,
+ "dynamic rule change test, keyframe style set");
+ dyn2.name = "dyn2";
+ yield 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%";
+ yield waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "dynamic rule change test, change in keyframe keyText");
+ dyn1.deleteRule("25%");
+ yield waitForPaintsFlushed();
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor,
+ "dynamic rule change test, @keyframes deleteRule");
+ done_div();
+ dyn_sheet_elt.parentNode.removeChild(dyn_sheet_elt);
+ dyn_sheet_elt = null;
+ dyn_sheet = null;
+});
+
+/*
+ * Bug 1004361 - CSS animations with short duration sometimes don't dispatch
+ * a start event
+ */
+addAsyncAnimTest(function *() {
+ new_div("animation: anim2 1s 0.1s");
+ listen();
+ yield 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(function *() {
+ new_div("transform: translate(0, 200px); animation: anim4 0s 1s both");
+ listen();
+ yield waitForPaintsFlushed();
+ advance_clock(0);
+ omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+ "transform during backwards fill of zero-duration animation");
+ advance_clock(2000); // Skip over animation
+ yield 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(function *() {
+ new_div("transform: translate(0, 200px); animation: anim4 0s 1s both");
+ listen();
+ yield waitForPaintsFlushed();
+ advance_clock(0);
+ // Seek to exactly the point where the animation starts and stops
+ advance_clock(1000);
+ yield 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(function *() {
+ new_div("transform: translate(0, 200px); " +
+ "animation: anim4 0s 1s both infinite");
+ listen();
+ yield waitForPaintsFlushed();
+ advance_clock(0);
+ omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+ "transform during backwards fill of infinitely repeating " +
+ "zero-duration animation");
+ advance_clock(2000);
+ yield 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(function *() {
+ new_div("transform: translate(0, 200px); " +
+ "animation: anim4 0s -1s both reverse 12.7 linear");
+ listen();
+ yield 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(function *() {
+ new_div("margin-right: 200px; animation: empty 2s 1s both");
+ listen();
+ advance_clock(0);
+ yield 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(function *() {
+ new_div("margin-right: 200px; animation: empty 0s 1s both");
+ listen();
+ yield 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(function *() {
+ new_div("animation: nearlyempty 1s both linear");
+ yield 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");
+ yield 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(function *() {
+ new_div("animation: always_fifty 1s both linear");
+ yield waitForPaintsFlushed();
+ advance_clock(500);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is animating on compositor");
+
+ // Update animation name
+ listen();
+ gDiv.style.animationName = "empty";
+ yield 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(function *() {
+ var isb2g = SpecialPowers.Services.appinfo.name == "B2G";
+ if (isb2g) {
+ todo(false, "no global history on B2G; can't run test");
+ return;
+ }
+
+ var div1 = document.createElement("div");
+ div1.classList.add("target");
+ div1.style.height = "10px";
+ div1.style.animation = "anim2 linear 1s";
+
+ var visitedLink = document.createElement("a");
+ visitedLink.setAttribute("href", window.top.location.href);
+ 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", window.top.location.href);
+ refVisitedLink.classList.add("visitedLink");
+
+ gDisplay.appendChild(div1);
+ gDisplay.appendChild(visitedLink);
+ gDisplay.appendChild(refVisitedLink);
+
+ // Wait for visited link coloring.
+ yield waitForVisitedLinkColoring(refVisitedLink,
+ "background-color", "rgb(0, 0, 255)");
+
+ // Wait for animations to start.
+ yield 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(function *() {
+ new_div("animation: anim4 linear 10s");
+ yield 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";
+ yield 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 = "";
+ yield 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(function *() {
+ new_div("animation: anim4 linear 10s");
+ var ancestor = document.createElement("div");
+ gDiv.parentNode.insertBefore(ancestor, gDiv);
+ ancestor.appendChild(gDiv);
+ yield 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";
+ yield 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 = "";
+ yield 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.parentNode.removeChild(ancestor);
+ done_div();
+});
+
+// Bug 1125455 - Transitions should not run when animations are running.
+addAsyncAnimTest(function *() {
+ new_div("transition: opacity 2s linear; opacity: 0.8");
+ yield waitForPaintsFlushed();
+ omta_is("opacity", 0.8, RunningOn.MainThread,
+ "initial opacity");
+ gDiv.style.opacity = "0.2";
+ yield 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";
+ yield 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 847287 - Test that changes of when an animation is dynamically
+// overridden work correctly.
+addAsyncAnimTest(function *() {
+ // anim2 and anim3 are both animations from opacity 0 to 1
+
+ new_div("animation: anim2 1s linear forwards; opacity: 0.5 ! important");
+ yield 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");
+ yield 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");
+ yield 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.
+ yield 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)");
+ yield 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)";
+ yield 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.
+ yield 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)");
+ yield 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)";
+ yield 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.
+ yield 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.
+ yield 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");
+ yield 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(function *() {
+ new_div("transform: translate(100px); transition: transform 10s 5s linear");
+ yield waitForPaintsFlushed();
+ gDiv.style.transform = "translate(200px)";
+ yield 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);
+ yield 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.
+ yield waitForPaints();
+ omta_is("transform", { tx: 200 }, RunningOn.MainThread,
+ "transition runs on main thread at end of active interval");
+
+ done_div();
+});
+
+</script>
+</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 000000000..235c32342
--- /dev/null
+++ b/layout/style/test/test_animations_omta_start.html
@@ -0,0 +1,189 @@
+<!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 type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ 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() {
+ var 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 000000000..65dee5b29
--- /dev/null
+++ b/layout/style/test/test_animations_pausing.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1070745
+-->
+<head>
+ <title>Test for play() and pause() on animations (Bug 1070745)</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1070745">Mozilla Bug 1070745</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ { "set": [[ "dom.animations-api.core.enabled", true]] },
+ function() {
+ window.open("file_animations_pausing.html");
+ });
+</script>
+</pre>
+</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 000000000..16deca02c
--- /dev/null
+++ b/layout/style/test/test_animations_playbackrate.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1175751
+-->
+<head>
+ <title>Test for Animation.playbackRate on compositor animations (Bug 1175751)</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175751">Mozilla Bug 1175751</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ { "set": [[ "dom.animations-api.core.enabled", true]] },
+ function() {
+ window.open("file_animations_playbackrate.html");
+ });
+</script>
+</pre>
+</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 000000000..da7680727
--- /dev/null
+++ b/layout/style/test/test_animations_styles_on_event.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1228137
+-->
+<head>
+ <title>Test that mouse movement immediately after finish() should involve restyling for finished state(Bug 1228137)</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1219236">Mozilla Bug 1228137</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ { "set": [[ "dom.animations-api.core.enabled", true]] },
+ function() {
+ window.open("file_animations_styles_on_event.html");
+ });
+</script>
+</pre>
+</body>
+</html>
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 000000000..2ac631169
--- /dev/null
+++ b/layout/style/test/test_animations_with_disabled_properties.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265611
+-->
+<head>
+ <title>Test CSS animations ignore disabled properties (Bug 1265611)</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265611">Mozilla Bug
+ 1265611</a>
+<pre id="test">
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+/*
+ * This test relies on the fact that the -webkit-text-fill-color property
+ * is disabled by the layout.css.prefixes.webkit pref. If we ever remove that
+ * pref we will need to substitute some other pref:property combination.
+ */
+SpecialPowers.pushPrefEnv(
+ { 'set': [[ 'dom.animations-api.core.enabled', true ],
+ [ 'layout.css.prefixes.webkit', 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 000000000..ae3276d86
--- /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 type="application/javascript" 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: 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.textDecoration, "underline", "should match first rule");
+ is(cs.zIndex, "auto", "should not match second rule");
+ p.removeAttribute("id");
+ is(cs.textDecoration, "none", "should not match first rule");
+ is(cs.zIndex, "auto", "should not match second rule");
+ p.setAttribute("foo", "v");
+ is(cs.textDecoration, "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_asyncopen2.html b/layout/style/test/test_asyncopen2.html
new file mode 100644
index 000000000..6dda6848a
--- /dev/null
+++ b/layout/style/test/test_asyncopen2.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1195173
+-->
+<head>
+ <title>Bug 1195173 - Test asyncOpen2 security exception</title>
+ <script type="application/javascript" 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 asyncOpen2 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 000000000..3723e335b
--- /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 type="application/javascript" 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 000000000..76635f9ed
--- /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_background_blend_mode.html b/layout/style/test/test_background_blend_mode.html
new file mode 100644
index 000000000..443fe1940
--- /dev/null
+++ b/layout/style/test/test_background_blend_mode.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for miscellaneous computed style issues</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">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.parentNode.removeChild(p);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ "set": [["layout.css.background-blend-mode.enabled", true]] },
+ test_bug_841601);
+
+</script>
+</pre>
+</body>
+</html>
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 000000000..c8bd5e86c
--- /dev/null
+++ b/layout/style/test/test_box_size_keywords.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1122253
+-->
+<head>
+ <title>Test for Bug 1122253</title>
+ <script type="text/javascript" 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>
+
+<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 **/
+
+// Test that the -moz-available, -moz-min-content, -moz-max-content, and
+// -moz-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: "-moz-min-content", computed_value: "90px", },
+ { orientation: "horizontal", property: "width", specified_value: "-moz-max-content", computed_value: "280px", },
+ { orientation: "horizontal", property: "width", specified_value: "-moz-fit-content", computed_value: "200px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "-moz-min-content", computed_value: "90px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "-moz-max-content", computed_value: "280px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "-moz-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: "-moz-min-content", computed_value: "-moz-min-content", },
+ { orientation: "horizontal", property: "min-width", specified_value: "-moz-max-content", computed_value: "-moz-max-content", },
+ { orientation: "horizontal", property: "min-width", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-min-content", computed_value: "-moz-min-content", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-max-content", computed_value: "-moz-max-content", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", },
+ { orientation: "horizontal", property: "max-width", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "max-width", specified_value: "-moz-min-content", computed_value: "-moz-min-content", },
+ { orientation: "horizontal", property: "max-width", specified_value: "-moz-max-content", computed_value: "-moz-max-content", },
+ { orientation: "horizontal", property: "max-width", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-min-content", computed_value: "-moz-min-content", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-max-content", computed_value: "-moz-max-content", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", },
+ { orientation: "vertical", property: "height", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "vertical", property: "height", specified_value: "-moz-min-content", computed_value: "90px", },
+ { orientation: "vertical", property: "height", specified_value: "-moz-max-content", computed_value: "280px", },
+ { orientation: "vertical", property: "height", specified_value: "-moz-fit-content", computed_value: "200px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "-moz-min-content", computed_value: "90px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "-moz-max-content", computed_value: "280px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "-moz-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: "-moz-min-content", computed_value: "-moz-min-content", },
+ { orientation: "vertical", property: "min-height", specified_value: "-moz-max-content", computed_value: "-moz-max-content", },
+ { orientation: "vertical", property: "min-height", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-min-content", computed_value: "-moz-min-content", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-max-content", computed_value: "-moz-max-content", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", },
+ { orientation: "vertical", property: "max-height", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "max-height", specified_value: "-moz-min-content", computed_value: "-moz-min-content", },
+ { orientation: "vertical", property: "max-height", specified_value: "-moz-max-content", computed_value: "-moz-max-content", },
+ { orientation: "vertical", property: "max-height", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-min-content", computed_value: "-moz-min-content", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-max-content", computed_value: "-moz-max-content", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-fit-content", computed_value: "-moz-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: "-moz-min-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "-moz-max-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "-moz-fit-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-min-content", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-max-content", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-fit-content", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-available", computed_value: "0px", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-min-content", computed_value: "0px", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-max-content", computed_value: "0px", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-fit-content", computed_value: "0px", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-min-content", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-max-content", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-fit-content", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-available", computed_value: "none", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-min-content", computed_value: "none", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-max-content", computed_value: "none", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-fit-content", computed_value: "none", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-min-content", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-max-content", computed_value: "30px", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-fit-content", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-available", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-min-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-max-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-fit-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-min-content", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-max-content", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-fit-content", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-available", computed_value: "0px", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-min-content", computed_value: "0px", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-max-content", computed_value: "0px", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-fit-content", computed_value: "0px", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-min-content", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-max-content", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-fit-content", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-available", computed_value: "none", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-min-content", computed_value: "none", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-max-content", computed_value: "none", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-fit-content", computed_value: "none", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-min-content", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-max-content", computed_value: "30px", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-fit-content", computed_value: "30px", },
+];
+
+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 000000000..bce171682
--- /dev/null
+++ b/layout/style/test/test_bug1055933.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1055933
+-->
+<head>
+ <title>Test for Bug 1055933</title>
+ <script type="text/javascript" src="/MochiKit/packed.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..3b5a217e4
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 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 000000000..d0f66aa46
--- /dev/null
+++ b/layout/style/test/test_bug1112014.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1112014
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1112014</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="property_database.js"></script>
+ <script type="application/javascript;version=1.7">
+
+ let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
+ .getService(SpecialPowers.Ci.inIDOMUtils);
+
+ SimpleTest.requestLongerTimeout(2);
+
+ // This holds a canonical test value for each TYPE_ constant.
+ let testValues = {
+ TYPE_LENGTH: "10px",
+ TYPE_PERCENTAGE: "50%",
+ TYPE_COLOR: "rgb(3,3,3)",
+ TYPE_URL: "url(mozilla.org)",
+ TYPE_ANGLE: "90deg",
+ TYPE_FREQUENCY: "10kHz",
+ TYPE_TIME: "1000ms",
+ TYPE_GRADIENT: "linear-gradient( 45deg, blue, red )",
+ TYPE_TIMING_FUNCTION: "cubic-bezier(0.1, 0.7, 1.0, 0.1)",
+ TYPE_IMAGE_RECT: "-moz-image-rect(url(firefox.jpg), 5%, 5%, 10%, 10%)",
+ TYPE_NUMBER: "42"
+ };
+
+ // The canonical test values don't work for all properties, in
+ // particular some shorthand properties. For these cases we have
+ // override values.
+ let overrideValues = {
+ "font": {
+ TYPE_LENGTH: "10px san-serif",
+ TYPE_PERCENTAGE: "50% san-serif",
+ TYPE_NUMBER: "24px/1.5 san-serif"
+ },
+ "border-image": {
+ TYPE_LENGTH: "url(/somewhere) 30% / 30px stretch",
+ TYPE_IMAGE_RECT: testValues.TYPE_IMAGE_RECT + " 30 30 stretch"
+ },
+ "-moz-border-image": {
+ TYPE_LENGTH: "url(/somewhere) 30% / 30px stretch",
+ TYPE_IMAGE_RECT: testValues.TYPE_IMAGE_RECT + " 30 30 stretch"
+ },
+ "-webkit-border-image": {
+ TYPE_LENGTH: "url(/somewhere) 30% / 30px stretch",
+ TYPE_IMAGE_RECT: testValues.TYPE_IMAGE_RECT + " 30 30 stretch"
+ },
+ "box-shadow": {
+ TYPE_LENGTH: "2px 2px",
+ TYPE_COLOR: testValues.TYPE_COLOR + " 2px 2px"
+ },
+ "-webkit-box-shadow": {
+ TYPE_LENGTH: "2px 2px",
+ TYPE_COLOR: testValues.TYPE_COLOR + " 2px 2px"
+ },
+ "text-shadow": {
+ TYPE_LENGTH: "2px 2px",
+ TYPE_COLOR: testValues.TYPE_COLOR + " 2px 2px"
+ },
+ "font-weight": {
+ TYPE_NUMBER: "400"
+ },
+ "grid-template": {
+ TYPE_LENGTH: "'something' 23px",
+ TYPE_PERCENTAGE: "'something' 23%"
+ },
+ "grid": {
+ TYPE_LENGTH: "'something' 23px",
+ TYPE_PERCENTAGE: "'something' 23%"
+ },
+ };
+
+
+ // 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 = [];
+ for (let tc in utils) {
+ if (/TYPE_/.test(tc)) {
+ if (!(tc in testValues)) {
+ reps.push(tc);
+ }
+ }
+ }
+ 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 =
+ utils.cssPropertySupportsType(propertyName, utils[iter]);
+ let parsed = utils.cssPropertyIsValid(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(!utils.cssPropertySupportsType("--variable", utils.TYPE_COLOR),
+ "cssPropertySupportsType returns false for variable");
+
+ </script>
+</head>
+<body>
+<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 000000000..5d8152440
--- /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, "rgb(255, 0, 0)");
+ 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, "rgb(0, 128, 0)");
+
+ // 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, "rgb(255, 0, 0)");
+ f.remove();
+ is(cs.color, "rgb(0, 128, 0)");
+
+ // 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, "rgb(255, 0, 0)");
+ 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, "rgb(255, 0, 0)");
+ 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, "rgb(255, 0, 0)");
+ e.appendChild(f);
+ is(cs.color, "rgb(0, 128, 0)");
+
+ // 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, "rgb(255, 0, 0)");
+ e.className = "g";
+ is(cs.color, "rgb(0, 128, 0)");
+
+ // 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, "rgb(255, 0, 0)");
+ e.className = "j";
+ is(cs.color, "rgb(0, 128, 0)");
+
+ 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 000000000..8981d56e0
--- /dev/null
+++ b/layout/style/test/test_bug1232829.html
@@ -0,0 +1,38 @@
+<!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");
+ frameDoc.defaultView.history.back();
+ 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 000000000..9636780f4
--- /dev/null
+++ b/layout/style/test/test_bug1292447.html
@@ -0,0 +1,377 @@
+<!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 type="text/javascript" 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 id="outlineradius1" style="-moz-outline-radius: 160px"></div>
+ <div id="outlineradius2" style="-moz-outline-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 id="outlineradius3" style="-moz-outline-radius: 160px"></div>
+ <div id="outlineradius4" style="-moz-outline-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("-moz-outline-radius-topleft", "outlineradius", 160, 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-", 320, 0, true);
+doATest("width", "minmaxwidth6-", 320, 0, true);
+doATest("width", "minmaxwidth7-", 600, 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-", 320, 0, true);
+doATest("height", "minmaxheight6-", 320, 0, true);
+doATest("height", "minmaxheight7-", 600, 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, cssProp, decl, coordVal, prettyName) {
+ is(decl[camelProp], coordVal + "px", prettyName);
+ is(decl.getPropertyCSSValue(cssProp).cssValueType,
+ CSSValue.CSS_PRIMITIVE_VALUE, prettyName + " is primitive value");
+ is(decl.getPropertyCSSValue(cssProp).primitiveType,
+ CSSPrimitiveValue.CSS_PX, prettyName + " is px");
+ is(decl.getPropertyCSSValue(cssProp).cssText, coordVal + "px",
+ prettyName + " cssText");
+ /* Since floats are only accurate to like 6 decimal places, round to 3 decimal
+ places here. */
+ is(round(decl.getPropertyCSSValue(cssProp)
+ .getFloatValue(CSSPrimitiveValue.CSS_PX),
+ 3), coordVal, prettyName + " as float value");
+}
+
+function percentValueTest(camelProp, cssProp, decl, percentVal, prettyName) {
+ is(decl[camelProp], percentVal + "%", prettyName);
+ is(decl.getPropertyCSSValue(cssProp).cssValueType,
+ CSSValue.CSS_PRIMITIVE_VALUE, prettyName + " is primitive value");
+ is(decl.getPropertyCSSValue(cssProp).primitiveType,
+ CSSPrimitiveValue.CSS_PERCENTAGE, prettyName + " is percent");
+ is(decl.getPropertyCSSValue(cssProp).cssText, percentVal + "%",
+ prettyName + " cssText");
+ /* Since floats are only accurate to like 6 decimal places, round to 3 decimal
+ places here. */
+ is(round(decl.getPropertyCSSValue(cssProp)
+ .getFloatValue(CSSPrimitiveValue.CSS_PERCENTAGE),
+ 3), percentVal, prettyName + " as float value");
+}
+
+function doATest(propName, idBase, coordVal, percentVal, resolveToUsedVal = false) {
+ var cssCamelPropName = "";
+ var parts = propName.split("-");
+ ok(parts.length > 0, "prop name", "Empty css prop name");
+ var i;
+ if (parts[0]) {
+ i = 0;
+ } else {
+ is(parts[1], "moz", "Testing an extension property that's not -moz");
+ ok(parts.length > 2, "prop name 2", "Bogus -moz prop name");
+ 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, propName,
+ style(idBase + "1"), coordVal,
+ propName + " of " + idBase + "1");
+
+ if (!$(idBase + "2")) {
+ // Nothing else to do here
+ return
+ }
+
+ /* Test $(id)-2 */
+ if (resolveToUsedVal) {
+ coordValueTest(cssCamelPropName, propName,
+ style(idBase + "2"), coordVal,
+ propName + " of " + idBase + "2");
+ } else {
+ percentValueTest(cssCamelPropName, propName,
+ style(idBase + "2"), percentVal,
+ propName + " of " + idBase + "2");
+ }
+
+ if (percentVal) {
+ /* Test $(id)-3 */
+ coordValueTest(cssCamelPropName, propName,
+ style(idBase + "3"), coordVal,
+ propName + " of " + idBase + "3");
+
+ /* Test $(id)-4 */
+ percentValueTest(cssCamelPropName, propName,
+ style(idBase + "4"), percentVal,
+ propName + " of " + idBase + "4");
+ }
+}
+</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 000000000..18ad66aa9
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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_bug200089.html b/layout/style/test/test_bug200089.html
new file mode 100644
index 000000000..39ab78d59
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..25965b567
--- /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 type="text/javascript" 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 000000000..e3d1f6280
--- /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 type="text/javascript" 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 = "transparent";
+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.parentNode.removeChild(node);
+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 000000000..746e7da0a
--- /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 type="text/javascript" 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 000000000..f387428e8
--- /dev/null
+++ b/layout/style/test/test_bug319381.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=319381
+-->
+<head>
+ <title>Test for Bug 319381</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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");
+}
+
+function cval() {
+ return document.defaultView.getComputedStyle($('t'), "").
+ getPropertyCSSValue("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");
+ isnot(cval(), null, "Simple property as CSSValue");
+}
+
+$('t').style.overflow = mozVals[0];
+is($('t').style.getPropertyValue("overflow-y"), "scroll", "Roundtrip");
+is($('t').style.getPropertyValue("overflow-x"), "hidden", "Roundtrip");
+is($('t').style.overflow, "", "Shorthand read directly");
+is(c(), "", "Shorthand computed");
+is(cval(), null, "Shorthand as CSSValue");
+
+$('t').style.overflow = mozVals[1];
+is($('t').style.getPropertyValue("overflow-x"), "scroll", "Roundtrip");
+is($('t').style.getPropertyValue("overflow-y"), "hidden", "Roundtrip");
+is($('t').style.overflow, "", "Shorthand read directly");
+is(c(), "", "Shorthand computed");
+is(cval(), null, "Shorthand as CSSValue");
+
+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, "", "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");
+ isnot(cval(), null, "Shorthand computation as CSSValue");
+ } else if (vals[j] == "visible" && vals[i] == "auto") {
+ is(c(), vals[i], "Shorthand computation");
+ isnot(cval(), null, "Shorthand computation as CSSValue");
+ } else {
+ is(c(), "", "Shorthand computation");
+ is(cval(), null, "Shorthand computation as CSSValue");
+ }
+ }
+}
+
+</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 000000000..2cfb63044
--- /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 type="text/javascript" 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 000000000..dfdc4028b
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..208a79e3e
--- /dev/null
+++ b/layout/style/test/test_bug372770.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372770
+-->
+<head>
+ <title>Test for Bug 372770</title>
+ <script type="text/javascript" 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);
+}
+
+// This code is only here because of bug 372783. Once that's fixed, this test
+// for "rgba(0, 0, 0, 0)" will fail.
+style1.color = "rgba(0, 0, 0, 0)";
+style2.color = "rgba(0, 0, 0, 0)";
+is(style1.color, "transparent",
+ "Inline style should give transparent for rgba(0,0,0,0)");
+is(style2.color, "transparent",
+ "Rule style should give transparent for rgba(0,0,0,0)");
+todo(style1.color == "rgba(0, 0, 0, 0)",
+ "Inline style should round-trip black transparent color correctly");
+todo(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 000000000..07053303c
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..43f60577c
--- /dev/null
+++ b/layout/style/test/test_bug377947.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377947
+-->
+<head>
+ <title>Test for Bug 377947</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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-language-override": "normal", // has to be default value
+ "font-kerning": "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-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 000000000..2a6c392ea
--- /dev/null
+++ b/layout/style/test/test_bug379440.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=379440
+-->
+<head>
+ <title>Test for Bug 379440</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ #display > * { 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(\"http://example.com/\"), crosshair",
+ "Drop unloadable URIs");
+is(cur("t2"), "crosshair", "Drop unloadable URIs again");
+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 000000000..fc99be1ff
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..f9a88b17f
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..c970a802f
--- /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 type="text/javascript" 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 000000000..daa61f342
--- /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 type="text/javascript" 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 000000000..05e2faf63
--- /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 type="text/javascript" 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 000000000..e98f68f7a
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..f5cb9ce3b
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391221">Mozilla Bug 391221</a>
+<p id="display">
+ <div id="width-ref" style="width: 2ch"></div>
+</p>
+<div id="content" style="display: none">
+
+<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 000000000..3175d0e92
--- /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 type="text/javascript" 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#\")",
+ "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#\")",
+ "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#\")",
+ "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 000000000..8e61f7d3b
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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.116667px");
+is(a4.left, "-0.216667px");
+
+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.45px");
+
+</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 000000000..34491eaa8
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ #display 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 000000000..fb1c98708
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <link rel="stylesheet" type="text/css" href="data:text/css,%23myDiv{color:green;}">
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin/global.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/global.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/global.css",
+ "Unexpected href for linked chrome sheet before cloning");
+ is(document.styleSheets[4].href,
+ "chrome://global/skin/global.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/global.css",
+ "Unexpected href for linked chrome sheet after cloning");
+ is(document.styleSheets[4].href,
+ "chrome://global/skin/global.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 000000000..6443d25c3
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..273e4466e
--- /dev/null
+++ b/layout/style/test/test_bug413958.html
@@ -0,0 +1,78 @@
+<!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>
+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: 14,
+ sourceLine: "#s1{nosuchprop:auto; color:black}" },
+ { errorMessage: /Unknown property \u2018nosuchprop\u2019/,
+ lineNumber: 2, columnNumber: 14, sourceLine:
+ "#s2{nosuchprop:auto; color:black}invalid?sel{}#s3{color:black}" },
+ { errorMessage: /Ruleset ignored due to bad selector/,
+ lineNumber: 2, columnNumber: 40, sourceLine:
+ "#s2{nosuchprop:auto; color:black}invalid?sel{}#s3{color:black}" } ],
+ [ { errorMessage: /parsing value for \u2018width\u2019/,
+ lineNumber: 0, columnNumber: 6,
+ sourceLine: "width:200;color:black" } ],
+ [ { errorMessage: /parsing value for \u2018width\u2019/,
+ lineNumber: 0, columnNumber: 0,
+ sourceLine: "200" } ],
+];
+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 000000000..aa9ff8af1
--- /dev/null
+++ b/layout/style/test/test_bug418986-2.html
@@ -0,0 +1,33 @@
+<!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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="test-css"></style>
+ <script type="text/javascript;version=1.7" src="chrome/bug418986-2.js"></script>
+ <script type="text/javascript;version=1.7">
+ // Run all tests now.
+ window.onload = function () {
+ add_task(function* () {
+ yield 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 000000000..595c0a643
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ div.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": "none", "visibility": "visible" },
+ true: { str: "", "text-decoration": "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", "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 000000000..6471bcc73
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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_bug453896_deck.html b/layout/style/test/test_bug453896_deck.html
new file mode 100644
index 000000000..603063241
--- /dev/null
+++ b/layout/style/test/test_bug453896_deck.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=453896
+-->
+<head>
+ <title>Test for Bug 453896</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=453896">Mozilla Bug 453896</a>
+<div id="display">
+
+<div style="display:-moz-deck; height: 300px; width: 300px;">
+ <iframe src="about:blank"></iframe>
+ <iframe id="subdoc" src="bug453896_iframe.html"></iframe>
+ <iframe src="about:blank"></iframe>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 453896 **/
+
+function run()
+{
+ var iframe = document.getElementById("subdoc");
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+
+ subwin.run(window);
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</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 000000000..cb32f47ec
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..385ee43fa
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..539c3fa36
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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_bug511909.html b/layout/style/test/test_bug511909.html
new file mode 100644
index 000000000..060636a9f
--- /dev/null
+++ b/layout/style/test/test_bug511909.html
@@ -0,0 +1,205 @@
+<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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/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() {
+ 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/test_bug517224.html b/layout/style/test/test_bug517224.html
new file mode 100644
index 000000000..e748f9e20
--- /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 type="application/javascript" 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 000000000..a86b3ed0b
--- /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 type="application/javascript" 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 000000000..66768a742
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..ac04c3563
--- /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 type="application/javascript" 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, "").textDecoration; }
+
+ 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 000000000..5ea1d826f
--- /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 type="application/javascript" 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 000000000..795b2ce0c
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=580685">Mozilla Bug 580685</a>
+<p id="display">
+ <iframe id="f" src="data:text/html,<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 000000000..6a6d373af
--- /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 000000000..c6c9d6ad4
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ div { 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.slice(document.getElementsByTagName("div"));
+ cases.forEach(function(aCase, aIndex) {
+ aCase.className = "after";
+ });
+ window.setTimeout(function() {
+ cases.forEach(function(aCase, aIndex) {
+ is(window.getComputedStyle(aCase, null)
+ .getPropertyValue("background-color"),
+ "transparent",
+ aCase.textContent);
+ });
+ SimpleTest.finish();
+ }, 1);
+}, false);
+
+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 000000000..f511185ed
--- /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 type="application/javascript" 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 000000000..9abb7041c
--- /dev/null
+++ b/layout/style/test/test_bug652486.html
@@ -0,0 +1,212 @@
+<!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 and Bug 1039488</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=652486">Mozilla Bug 652486</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1039488">Mozilla Bug 1039488</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 and Bug 1039488 **/
+
+function c() {
+ return document.defaultView.getComputedStyle($('t'), "").
+ getPropertyValue("text-decoration");
+}
+
+var tests = [
+ // When only text-decoration was specified, text-decoration should look like
+ // a longhand property.
+ { decoration: "none",
+ line: null, color: null, style: null,
+ expectedValue: "none", expectedCSSValue: "none" },
+ { decoration: "underline",
+ line: null, color: null, style: null,
+ expectedValue: "underline", expectedCSSValue: "underline" },
+ { decoration: "overline",
+ line: null, color: null, style: null,
+ expectedValue: "overline", expectedCSSValue: "overline" },
+ { decoration: "line-through",
+ line: null, color: null, style: null,
+ expectedValue: "line-through", expectedCSSValue: "line-through" },
+ { decoration: "blink",
+ line: null, color: null, style: null,
+ expectedValue: "blink", expectedCSSValue: "blink" },
+ { decoration: "underline overline",
+ line: null, color: null, style: null,
+ expectedValue: "underline overline",
+ expectedCSSValue: "underline overline" },
+ { decoration: "underline line-through",
+ line: null, color: null, style: null,
+ expectedValue: "underline line-through",
+ expectedCSSValue: "underline line-through" },
+ { decoration: "blink underline",
+ line: null, color: null, style: null,
+ expectedValue: "underline blink",
+ expectedCSSValue: "underline blink" },
+ { decoration: "underline blink",
+ line: null, color: null, style: null,
+ expectedValue: "underline blink",
+ expectedCSSValue: "underline blink" },
+
+ // When only text-decoration-line or text-blink was specified,
+ // text-decoration should look like a longhand property.
+ { decoration: null,
+ line: "blink", color: null, style: null,
+ expectedValue: "blink", expectedCSSValue: "blink" },
+ { decoration: null,
+ line: "underline", color: null, style: null,
+ expectedValue: "underline", expectedCSSValue: "underline" },
+ { decoration: null,
+ line: "overline", color: null, style: null,
+ expectedValue: "overline", expectedCSSValue: "overline" },
+ { decoration: null,
+ line: "line-through", color: null, style: null,
+ expectedValue: "line-through", expectedCSSValue: "line-through" },
+ { decoration: null,
+ line: "blink underline", color: null, style: null,
+ expectedValue: "underline blink", expectedCSSValue: "underline blink" },
+
+ // 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)", expectedCSSValue: [ "blink", [0, 0, 0] ] },
+ { decoration: "underline",
+ line: null, color: "black", style: null,
+ expectedValue: "underline rgb(0, 0, 0)", expectedCSSValue: [ "underline", [0, 0, 0] ] },
+ { decoration: "overline",
+ line: null, color: "#ff0000", style: null,
+ expectedValue: "overline rgb(255, 0, 0)", expectedCSSValue: [ "overline", [255, 0, 0] ] },
+ { decoration: "line-through",
+ line: null, color: "initial", style: null,
+ expectedValue: "line-through", expectedCSSValue: "line-through" },
+ { decoration: "blink underline",
+ line: null, color: "currentColor", style: null,
+ expectedValue: "underline blink", expectedCSSValue: "underline blink" },
+ { decoration: "underline line-through",
+ line: null, color: "currentcolor", style: null,
+ expectedValue: "underline line-through",
+ expectedCSSValue: "underline line-through" },
+
+ // 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", expectedCSSValue: [ "blink", "-moz-none" ] },
+ { decoration: "underline",
+ line: null, color: null, style: "dotted",
+ expectedValue: "underline dotted", expectedCSSValue: [ "underline", "dotted" ] },
+ { decoration: "overline",
+ line: null, color: null, style: "dashed",
+ expectedValue: "overline dashed", expectedCSSValue: [ "overline", "dashed" ] },
+ { decoration: "line-through",
+ line: null, color: null, style: "double",
+ expectedValue: "line-through double", expectedCSSValue: [ "line-through", "double" ] },
+ { decoration: "blink underline",
+ line: null, color: null, style: "wavy",
+ expectedValue: "underline blink wavy", expectedCSSValue: [ "underline blink", "wavy" ] },
+ { decoration: "underline blink overline line-through",
+ line: null, color: null, style: "solid",
+ expectedValue: "underline overline line-through blink",
+ expectedCSSValue: "underline overline line-through blink" },
+ { decoration: "line-through overline underline",
+ line: null, color: null, style: "initial",
+ expectedValue: "underline overline line-through",
+ expectedCSSValue: "underline overline line-through" }
+];
+
+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;
+}
+
+function testCSSValue(testname, test, dec) {
+ var val = document.defaultView.getComputedStyle($('t'), "").
+ getPropertyCSSValue("text-decoration");
+ isnot(val, null, testname + " (CSS value): " + dec);
+
+ if (typeof test.expectedCSSValue == "string") {
+ is(val.getStringValue(), test.expectedCSSValue, testname + " (CSS value): " + dec);
+ return;
+ }
+
+ is(val.length, test.expectedCSSValue.length, testname + " (CSS value length): " + dec);
+ for (var i = 0; i < val.length; i ++) {
+ var actual = val[i];
+ var expected = test.expectedCSSValue[i];
+ if (typeof expected == "string") {
+ is(actual.getStringValue(), expected, testname + " (CSS value [" + i + "] value): " + dec);
+ } else if (typeof expected == "object") {
+ var rgb = actual.getRGBColorValue();
+ is(rgb.red.getFloatValue(CSSPrimitiveValue.CSS_NUMBER), expected[0], testname + " (CSS value [" + i + "] red): " + dec);
+ is(rgb.green.getFloatValue(CSSPrimitiveValue.CSS_NUMBER), expected[1], testname + " (CSS value [" + i + "] green): " + dec);
+ is(rgb.blue.getFloatValue(CSSPrimitiveValue.CSS_NUMBER), expected[2], testname + " (CSS value [" + i + "] blue): " + dec);
+ }
+ }
+}
+
+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);
+ testCSSValue("Test1", test, dec);
+
+ clearStyleObject();
+
+ $('t').setAttribute("style", dec);
+
+ is(c(), test.expectedValue, "Test2 (computed value): " + dec);
+ testCSSValue("Test2", test, 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 000000000..4b9980eb7
--- /dev/null
+++ b/layout/style/test/test_bug657143.html
@@ -0,0 +1,132 @@
+<!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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 = [], svgA = [], customA = [];
+
+// Partition the list of property names into four lists:
+//
+// cssA: regular, non-SVG properties
+// mozA: '-moz-' prefixed properties
+// svgA: SVG 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:
+ // Assume that the first SVG property is 'clip-path'.
+ if (prop == "clip-path") {
+ list = svgA;
+ }
+ // fall through
+ case svgA:
+ 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(),
+ svgB = svgA.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');
+is(svgA.toString(), svgB.toString(), 'SVG 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(!svgA.find(isWebkitPrefixed), 'Compatible -webkit- CSS properties should not be in the SVG property list');
+ok(!svgA.find(isMozPrefixed), 'Experimental -moz- CSS properties should not be in the SVG property list');
+ok(!svgA.find(isCustom), 'Custom CSS properties should not be in the SVG 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_bug664955.html b/layout/style/test/test_bug664955.html
new file mode 100644
index 000000000..a7e31c557
--- /dev/null
+++ b/layout/style/test/test_bug664955.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664955
+-->
+<head>
+ <title>Test for Bug 664955</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=664955">Mozilla Bug 664955</a>
+<p id="display" style="font-size: 10000000000px"> <!-- must be superior to nscoord_MAX * 60 -->
+<span id="larger" style="font-size: larger">
+<span id="larger-again" style="font-size: larger">
+</span>
+</span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 664955 **/
+var parentSize = document.defaultView.getComputedStyle($('display'), "").fontSize;
+var largerSize = document.defaultView.getComputedStyle($('larger'), "").fontSize;
+var largerAgainSize = document.defaultView.getComputedStyle($('larger-again'), "").fontSize;
+
+is(parentSize, largerSize, "font size is larger than max font size");
+is(parentSize, largerAgainSize, "font size is larger than max font size");
+
+</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 000000000..b6909eedb
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..25df9ffd8
--- /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 type="application/javascript" 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 000000000..892029886
--- /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 type="application/javascript" 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 000000000..0a6c4353d
--- /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 type="application/javascript" 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 000000000..a88ae0a30
--- /dev/null
+++ b/layout/style/test/test_bug73586.html
@@ -0,0 +1,192 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=73586
+-->
+<head>
+ <title>Test for Bug 73586</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ 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;
+ }
+}
+
+var display = document.getElementById("display");
+
+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: underline; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).textDecoration, (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 000000000..9641f5944
--- /dev/null
+++ b/layout/style/test/test_bug74880.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=74880
+-->
+<head>
+ <title>Test for Bug 74880</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ /* 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" ];
+var gColorValues = [ "inherit", "initial", "currentColor", "green" ];
+var gStyleValues = [ "inherit", "initial", "double", "dashed" ];
+
+if (SpecialPowers.getBoolPref("layout.css.unset-value.enabled")) {
+ gLengthValues.push("unset");
+ gColorValues.push("unset");
+ gStyleValues.push("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 000000000..1e2445804
--- /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 type="application/javascript" 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 000000000..ca9d0cfb6
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 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 000000000..98e9b7120
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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_bug798567.html b/layout/style/test/test_bug798567.html
new file mode 100644
index 000000000..b8e5dd0ea
--- /dev/null
+++ b/layout/style/test/test_bug798567.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=798567
+-->
+<head>
+ <title>Test for Bug 798567</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <div id="some_div" style="opacity: 0.5">bar</div>
+ <div style="cursor: crosshair" id="div_cursor">foobar</div>
+ <script type="application/javascript">
+ var elm = document.getElementById("some_div");
+ var cs = getComputedStyle(elm);
+ var cssValue = cs.getPropertyCSSValue("opacity");
+ is(cssValue.getFloatValue(CSSPrimitiveValue.CSS_NUMBER), 0.5, "wrong opacity of some_div");
+
+ elm = document.getElementById("div_cursor");
+ cs = getComputedStyle(elm);
+ cssValue = cs.getPropertyCSSValue("cursor");
+ ok(cssValue[0], "element with set cursor should have a a computed style");
+ </script>
+</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 000000000..f403741c1
--- /dev/null
+++ b/layout/style/test/test_bug798843_pref.html
@@ -0,0 +1,57 @@
+<!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 = {
+ "fill" : "context-stroke none",
+ "stroke" : "context-fill none",
+ "fillOpacity" : "context-stroke-opacity",
+ "strokeOpacity" : "context-fill-opacity",
+ "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 000000000..df6100e29
--- /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 type="application/javascript" 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 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",
+ "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",
+ "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 000000000..df9facd74
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..b8896999e
--- /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 type="application/javascript" 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 000000000..a67db56ee
--- /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 000000000..c7104c7c4
--- /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 type="text/javascript" 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.removeChild(elt.firstChild);
+}
+
+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) {
+ var 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) {
+ var div = divs[i];
+ is(bg(div), ORANGE, "should be orange");
+ is(color(div), MAROON, "should be maroon");
+}
+
+for (i in divs) {
+ var div = divs[i];
+ var e = document.createEvent("MouseEvents");
+ e.initEvent("click", true, true);
+ div.dispatchEvent(e);
+}
+
+for (i in divs) {
+ var 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 000000000..0a5d27a8b
--- /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 type="text/javascript" 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 000000000..e8684e935
--- /dev/null
+++ b/layout/style/test/test_ch_ex_no_infloops.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=678671
+-->
+<head>
+ <title>Test for Bug 678671</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="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 000000000..7de8d2431
--- /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 type="application/javascript" 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 000000000..9f25accd1
--- /dev/null
+++ b/layout/style/test/test_clip-path_polygon.html
@@ -0,0 +1,41 @@
+<html>
+<head>
+<style>
+body {padding: 0;margin:0;}
+div {
+ width: 200px;
+ height: 200px;
+ position: fixed;
+ top: 50px;
+ left: 50px;
+ margin: 50;
+ padding: 50;
+ 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 type="text/javascript" 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");
+if (SpecialPowers.getBoolPref("layout.css.clip-path-shapes.enabled")) {
+ 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> \ No newline at end of file
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 000000000..fab111e34
--- /dev/null
+++ b/layout/style/test/test_compute_data_with_start_struct.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for correct handling of aStartStruct parameter to nsRuleNode::Compute*Data</title>
+ <script type="text/javascript" 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 is to test that nsRuleNode::Compute*Data
+ * functions are written correctly. In particular, in these functions,
+ * when the specified value of a property has unit eCSSUnit_Null,
+ * touching the computed data is forbidden. This is because we
+ * sometimes 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.
+ *
+ * If one of these tests fails, you should look for a case where the
+ * property's code in nsRuleNode::Compute*Data touches the computed
+ * value when the specified value has eCSSUnit_Null, and fix it.
+ *
+ * The test works by maintaining one style rule that has every CSS
+ * property specified, and a second style rule that has different values
+ * for every property, an element that matches only the first rule (so
+ * that we'll have a cached struct), and *later* an element that matches
+ * both rules (for whose computation we'll find the struct cached from
+ * the first element). (It does this twice, once with the overriding in
+ * each direction, because one of the cases might match the incorrect
+ * overwriting.) Then, in the second style rule, it unsets each
+ * property, one at a time, and checks that the computation works
+ * correctly. (The reason to want every property set is to hit a case
+ * where we can store the data in the rule tree... though this isn't
+ * guaranteed.)
+ */
+
+function xfail_computecheck(prop, roundnum) {
+ return false;
+}
+
+function xfail_test(prop, roundnum) {
+ return false;
+}
+
+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 round(lower_set, higher_set, roundnum) {
+
+ for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.subproperties || info.get_computed)
+ continue;
+ gRule1.style.setProperty(prop, info[lower_set][0], "");
+ gRule2.style.setProperty(prop, info[higher_set][0], "");
+ }
+
+ for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.subproperties || info.get_computed)
+ continue;
+
+ 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);
+ (xfail_computecheck(prop, roundnum) ? todo_isnot : isnot)(higher_set_val, lower_set_val, "initial and other values of " + prop + " are different");
+ gRule2.style.removeProperty(prop);
+ (xfail_test(prop, roundnum) ? todo_is : is)(gTest.getPropertyValue(prop), lower_set_val, prop + " is not touched when its value comes from aStartStruct");
+
+ gRule2.style.setProperty(prop, info[higher_set][0], "");
+ if ("prerequisites" in info) {
+ for (var prereq in info.prerequisites) {
+ gRule2.style.setProperty(prereq, gCSSProperties[prereq][higher_set][0], "");
+ }
+ }
+ }
+}
+
+round("other_values", "initial_values", 1);
+round("initial_values", "other_values", 2);
+
+</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 000000000..938a3fcf5
--- /dev/null
+++ b/layout/style/test/test_computed_style.html
@@ -0,0 +1,413 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for miscellaneous computed style issues</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">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
+ // and -moz-outline-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 */
+ p.style.MozOutlineRadiusTopleft = "1.5625%"; /* 1/64 == 4px 2px */
+ p.style.MozOutlineRadiusTopright = "5px";
+ p.style.MozOutlineRadiusBottomright = "5px 3px";
+ p.style.MozOutlineRadiusBottomleft = "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");
+ is(cs.MozOutlineRadiusTopleft, "1.5625%",
+ "computed value of % outline-radius, with frame");
+ is(cs.MozOutlineRadiusTopright, "5px",
+ "computed value of px outline-radius, with frame");
+ is(cs.MozOutlineRadiusBottomright, "5px 3px",
+ "computed value of px outline-radius, with frame");
+ is(cs.MozOutlineRadiusBottomleft, "1.5625% 3.125%",
+ "computed value of % outline-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");
+ is(cs.MozOutlineRadiusTopleft, "1.5625%",
+ "computed value of % outline-radius, without frame");
+ is(cs.MozOutlineRadiusTopright, "5px",
+ "computed value of px outline-radius, without frame");
+ is(cs.MozOutlineRadiusBottomright, "5px 3px",
+ "computed value of px outline-radius, without frame");
+ is(cs.MozOutlineRadiusBottomleft, "1.5625% 3.125%",
+ "computed value of % outline-radius, without frame");
+
+ p.parentNode.removeChild(p);
+})();
+
+(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.parentNode.removeChild(p);
+})();
+
+(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(3px + 0%) 0px",
+ "computed 0% calc x"],
+ [ "0 calc(3px + 2% - 2%)", "0px calc(3px + 0%)",
+ "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.parentNode.removeChild(p);
+})();
+
+(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(3px + 0%) 0px",
+ "computed 0% calc horizontal"],
+ [ "0 calc(3px + 2% - 2%)", "0px calc(3px + 0%)",
+ "computed 0% calc vertical"],
+ [ "calc(3px - 5px) calc(6px - 9px)",
+ "calc(-2px) calc(-3px)", "negative pixel width" ],
+ [ "", "auto 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.parentNode.removeChild(p);
+})();
+
+(function test_bug_716628() {
+ // Test that various gradient styles round-trip correctly
+ var backgroundImages = [
+ [ "-moz-radial-gradient(10% bottom, #ffffff, black)",
+ "radial-gradient(at 10% 100%, rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient 1" ],
+ [ "-moz-radial-gradient(#ffffff, black)",
+ "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient 2" ],
+ [ "-moz-radial-gradient(cover, #ffffff, black)",
+ "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient 3" ],
+ [ "-moz-radial-gradient(top left -45deg, #ffffff, black)",
+ "-moz-radial-gradient(0% 0% -45deg, rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient with angle in degrees" ],
+ [ "-moz-linear-gradient(red, blue)",
+ "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient 1" ],
+ [ "-moz-linear-gradient(to bottom, red, blue)",
+ "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient 2" ],
+ [ "-moz-linear-gradient(to right, red, blue)",
+ "linear-gradient(to right, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient 3" ],
+ [ "-moz-linear-gradient(10px 10px -45deg, red, blue)",
+ "-moz-linear-gradient(10px 10px -45deg, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient with angle in degrees" ],
+ [ "-moz-linear-gradient(10px 10px -0.125turn, red, blue)",
+ "-moz-linear-gradient(10px 10px -0.125turn, 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.parentNode.removeChild(p);
+})();
+
+(function test_bug_1235015() {
+ if (!("maskImage" in document.documentElement.style)) {
+ return;
+ }
+
+ // "masks" object contains non-initial mask longhand values.
+ var emptyMasks = {
+ // More then one <mask-reference>, or any mask-image value other then
+ // <mask-source>,
+ "mask-image": [
+ "url(#mask1), url(#mask2)",
+ "linear-gradient(red, yellow)",
+ "-moz-element(#test)"
+ ],
+ // any mask-clip value other than "border-box".
+ "mask-clip": [
+ "content-box", "padding-box", "margin-box", "fill-box", "stroke-box",
+ "view-box", "no-clip"
+ ],
+ // any mask-origin value other than "border-box".
+ "mask-origin": [
+ "content-box", "padding-box", "margin-box", "fill-box", "stroke-box",
+ "view-box"
+ ],
+ // any mask-composite value other than "add".
+ "mask-composite": [
+ "subtract", "intersect", "exclude"
+ ],
+ // any mask-mode value other than "match-source".
+ "mask-mode": [
+ "alpha", "luminance"
+ ],
+ // any mask-position value other then "0%" "top" "left"
+ // "center center".
+ "mask-position": [
+ "0%", "center", "right", "bottom", "50%", "100%"
+ ],
+ // any mask-repeat value other then "repeat" "repeat repeat".
+ "mask-repeat": [
+ "repeat-x", "repeat-y", "no-repeat", "space", "round"
+ ],
+ // any mask-size value other then "auto" "auto auto".
+ "mask-size": [
+ "10px", "100%", "cover", "contain", "auto 5px"
+ ],
+ };
+
+ // "masks" object contains initial mask longhand values.
+ var nonEmptyMasks = {
+ "mask-image": [
+ "url(#mask1)", "none"
+ ],
+ "mask-clip": [
+ "border-box"
+ ],
+ "mask-origin": [
+ "border-box"
+ ],
+ "mask-composite": [
+ "add"
+ ],
+ "mask-mode": [
+ "match-source"
+ ],
+ "mask-position": [
+ "0% 0%", "left top"
+ ],
+ "mask-repeat": [
+ "repeat", "repeat repeat"
+ ],
+ "mask-size": [
+ "auto", "auto auto"
+ ],
+ };
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var prop in emptyMasks) {
+ var subProp = emptyMasks[prop];
+ for (var i = 0; i < subProp.length; i++) {
+ p.style.mask = subProp[i];
+ is(cs.mask, "", "computed value of " + subProp[i] + " mask");
+ }
+ }
+
+ for (var prop in nonEmptyMasks) {
+ var subProp = nonEmptyMasks[prop];
+ for (var i = 0; i < subProp.length; i++) {
+ p.style.mask = subProp[i];
+ isnot(cs.mask, "", "computed value of " + subProp[i] + " mask");
+ }
+ }
+ p.parentNode.removeChild(p);
+})();
+
+(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 = {
+ "mask" : "",
+ "markerStart" : "",
+ "markerMid" : "",
+ "markerEnd" : "",
+ "clipPath" : "",
+ "filter" : "",
+ "fill" : " transparent",
+ "stroke" : " transparent",
+ };
+
+ for (var prop in testStyles) {
+ p.style[prop] = localURL;
+ is(cs[prop], localURL + testStyles[prop], "computed value of " + prop);
+ p.style[prop] = nonLocalURL;
+ is(cs[prop], resolvedNonLocalURL + testStyles[prop], "computed value of " + prop);
+ }
+
+ p.parentNode.removeChild(p);
+})();
+
+(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)"],
+ // css-color-4
+ ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgba(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgb(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsla(0deg 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsl(0 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ];
+
+ 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]);
+ }
+
+ p.remove();
+})();
+
+</script>
+</pre>
+</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 000000000..762172ee3
--- /dev/null
+++ b/layout/style/test/test_computed_style_min_size_auto.html
@@ -0,0 +1,133 @@
+<!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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 in a horizontal flex container, "min-width: auto"
+ // computes to "auto".
+ checkElemMinSizes("horizontal-flex-item", "auto", "0px");
+ checkElemMinSizes("horizontal-flex-item-OH", "0px", "0px");
+
+ // ...and for a flex item in a vertical flex container, "min-height: auto"
+ // computes to "auto".
+ checkElemMinSizes("vertical-flex-item", "0px", "auto");
+ checkElemMinSizes("vertical-flex-item-OH", "0px", "0px");
+
+ // ...and for a grid item, "min-width: auto" and min-height both
+ // compute to "auto".
+ checkElemMinSizes("grid-item", "auto", "auto");
+ checkElemMinSizes("grid-item-OH", "0px", "0px");
+}
+
+// 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_pseudo.html b/layout/style/test/test_computed_style_no_pseudo.html
new file mode 100644
index 000000000..11ae16c75
--- /dev/null
+++ b/layout/style/test/test_computed_style_no_pseudo.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=505515
+-->
+<head>
+ <title>Test for Bug 505515</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display { color: black; background: white; }
+ #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(span, "").color, "rgb(0, 0, 0)",
+ "span should be black");
+ is(getComputedStyle(p, "").color, "rgb(0, 0, 0)",
+ "p should be black too");
+
+ 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 000000000..163176237
--- /dev/null
+++ b/layout/style/test/test_computed_style_prefs.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that preffed off properties do not appear in computed style</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=919594">Mozilla Bug 919594</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript; version=1.7">
+
+/** 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.indexOf(prop) == -1, 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.indexOf(prop) != -1, 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.text-combine-upright.enabled": ["text-combine-upright"],
+ "layout.css.image-orientation.enabled": ["image-orientation"],
+ "layout.css.mix-blend-mode.enabled": ["mix-blend-mode"],
+ "layout.css.isolation.enabled": [ "isolation"],
+ "layout.css.touch_action.enabled": ["touch-action"],
+ "svg.transform-box.enabled": ["transform-box"]
+};
+
+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 000000000..9ab60758d
--- /dev/null
+++ b/layout/style/test/test_condition_text.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814907
+-->
+<head>
+ <title>Test for Bug 814907</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+ @-moz-document url(http://www.example.com/) {}
+ @-moz-document url('http://www.example.com/') {}
+ @-moz-document url("http://www.example.com/") {}
+ @-moz-document url-prefix('http://www.example.com/') {}
+ @-moz-document url-prefix("http://www.example.com/") {}
+ @-moz-document domain('example.com') {}
+ @-moz-document domain("example.com") {}
+ @-moz-document regexp('http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/') {}
+ @-moz-document regexp("http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/") {}
+
+ @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 = [
+ "url(\"http://www.example.com/\")",
+ "url(\"http://www.example.com/\")",
+ "url(\"http://www.example.com/\")",
+ "url-prefix(\"http://www.example.com/\")",
+ "url-prefix(\"http://www.example.com/\")",
+ "domain(\"example.com\")",
+ "domain(\"example.com\")",
+ "regexp(\"http://www.w3.org/TR/\\\\d{4}/[^/]*-CSS2-\\\\d{8}/\")",
+ "regexp(\"http://www.w3.org/TR/\\\\d{4}/[^/]*-CSS2-\\\\d{8}/\")",
+ "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_condition_text_assignment.html b/layout/style/test/test_condition_text_assignment.html
new file mode 100644
index 000000000..dc1ef923d
--- /dev/null
+++ b/layout/style/test/test_condition_text_assignment.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815021
+-->
+<head>
+ <title>Test for Bug 815021</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+ #a { text-transform: none }
+ @media all {
+ #a { text-transform: lowercase }
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815021">Mozilla Bug 815021</a>
+<p id="display"><span id=a></span></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 815021 **/
+
+var sheet = document.getElementById("style").sheet;
+var rule = sheet.cssRules[1];
+var a = document.getElementById("a");
+
+function stylesApplied() {
+ return window.getComputedStyle(a, "").textTransform == "lowercase";
+}
+
+is(rule.type, CSSRule.MEDIA_RULE, "initial @media rule type");
+is(rule.conditionText, "all", "initial @media rule conditionText");
+ok(stylesApplied(), "initial @media rule applied");
+
+// [value to set, value to check, whether styles should be applied]
+var media = [
+ ["not all", "not all", false],
+ ["ALL ", "all", true],
+ ["unknown", "unknown", false],
+ ["(min-width:1px)", "(min-width: 1px)", true],
+ ["(bad syntax", "not all", false],
+ ["(max-width: 1px), (color)", "(max-width: 1px), (color)", true]
+];
+
+for (var i = 0; i < media.length; i++) {
+ rule.conditionText = media[i][0];
+ is(rule.conditionText, media[i][1], "value of conditionText #" + i);
+ ok(rule.cssText.startsWith("@media " + media[i][1]), "value of cssText #" + i);
+ ok(stylesApplied() == media[i][2], "styles applied #" + i);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_contain_formatting_context.html b/layout/style/test/test_contain_formatting_context.html
new file mode 100644
index 000000000..928cc35f5
--- /dev/null
+++ b/layout/style/test/test_contain_formatting_context.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1170781
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test that 'contain: paint' updates 'display' correctly</title>
+ <script type="application/javascript" 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>
+ <div id="test" style="contain: paint"></div>
+ <script>
+ // This mapping is currently mostly based off of a bugzilla comment by Tab
+ // Atkins Jr. in bug 1179349. Ultimately, it should be based on a
+ // specification.
+
+ // XXX 'ruby[-*]' and 'run-in' might need to be added here.
+ var expectedChanges = {
+ 'inline' : 'inline-block',
+ };
+ var displayInfo = gCSSProperties["display"];
+ var test = document.getElementById('test');
+ var displayVals = displayInfo.initial_values.concat(displayInfo.other_values);
+ for (dispVal of displayVals) {
+ test.style.display = dispVal;
+ if (expectedChanges.hasOwnProperty(dispVal)) {
+ is(getComputedStyle(test).display, expectedChanges[dispVal],
+ `'contain: paint' should change 'display: ${dispVal}' to ` +
+ `'display: ${expectedChanges[dispVal]}'`);
+ } else {
+ is(getComputedStyle(test).display, dispVal,
+ `'contain: paint' should not change 'display: ${dispVal}'`);
+ }
+ }
+ </script>
+</body>
+</html>
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 000000000..8262d8017
--- /dev/null
+++ b/layout/style/test/test_counter_descriptor_storage.html
@@ -0,0 +1,267 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for parsing, storage and serialization of CSS @counter-style descriptor values</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 1",
+ symbols: "x"
+ },
+ tests: {
+ system: [
+ [null, "", "symbolic"],
+ ["fixed 0"],
+ ["fixed 1", "fixed", "FixeD"],
+ ["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 000000000..c248494f5
--- /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 type="application/javascript" 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_css_cross_domain.html b/layout/style/test/test_css_cross_domain.html
new file mode 100644
index 000000000..055398dec
--- /dev/null
+++ b/layout/style/test/test_css_cross_domain.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=524223 -->
+<head>
+ <title>Test cross-domain CSS loading</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ 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>
+<iframe id="quirks" src="ccd-quirks.html"></iframe>
+</div>
+
+<div class="column">
+<h2>Standards</h2>
+<iframe id="standards" src="ccd-standards.html"></iframe>
+</div>
+
+<script type="application/javascript">
+
+/** 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");
+
+ is(color, "rgb(0, 255, 0)", ifr.id + " " + cases[i].id);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+window.onload = function() {
+ check_iframe(document.getElementById("quirks"));
+ check_iframe(document.getElementById("standards"));
+ SimpleTest.finish();
+};
+</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 000000000..b54b031da
--- /dev/null
+++ b/layout/style/test/test_css_eof_handling.html
@@ -0,0 +1,278 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS EOF handling</title>
+ <script type="text/javascript" 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/"
+ },
+ {
+ name: "@-moz-document 1",
+ ref: "@-moz-document domain('example.com') {}",
+ tst: "@-moz-document domain('example.com') {"
+ },
+ {
+ name: "@-moz-document 2",
+ ref: "@-moz-document domain('example.com') { p {} }",
+ tst: "@-moz-document domain('example.com') { p {"
+ }
+];
+
+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 000000000..00ec240c7
--- /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 type="application/javascript" 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('\x80\x2D\x5F\xA9'), '\\80 \x2D\x5F\xA9', "escapingFailed Char: \\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 000000000..b28c88c86
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..67105d61f
--- /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_supports.html b/layout/style/test/test_css_supports.html
new file mode 100644
index 000000000..fa5b3fdcb
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 !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",
+ "(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 000000000..25618bdf6
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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(--))",
+ "(color: var(--a,/**/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(--",
+ "(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: 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,/**/))",
+ "(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))",
+
+ "(--a: var(--b,))",
+ "(--a: var(--b,/**/))",
+ "(--a: var(--b,!))",
+ "(--a: var(--b,!important))",
+ "((--a: var(--b) !important !important))",
+ "(--a: var(--b,;))",
+ "(--a: var(--b);)",
+ "(--a:)",
+ "(--a: var(1px))",
+ "(--a: a))",
+ "(--a: \"\n",
+ "(--a: url(\"\n",
+ "(--a: var(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", " var(--b)"],
+ ["--a", "var(--b)"],
+ ["--a", "var(--b) "],
+ ["--a", "var( --b ) "],
+ ["--a", "var(--b, )"],
+ ["--a", "var(--b,/**/a)"],
+ ["--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", "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 ", "\\"],
+ ["--", "a"],
+ ];
+
+ var failingDeclarations = [
+ ["color", "var(--a,)"],
+ ["color", "var(--a,/**/)"],
+ ["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)"],
+
+ ["--a", "var(--b,)"],
+ ["--a", "var(--b,/**/)"],
+ ["--a", "var(--b,!)"],
+ ["--a", "var(--b,!important)"],
+ ["--a", "var(--b) !important !important"],
+ ["--a", "var(--b,;)"],
+ ["--a", "var(--b);"],
+ ["--a", ""],
+ ["--a", "var(1px)"],
+ ["(VAR-a", "a"],
+ ["--a", "a)"],
+ ["--a", "\"\n"],
+ ["--a", "url(\"\n"],
+ ["--a", "var(--b))"],
+ ["--a", "var(b)"],
+ ];
+
+ 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();
+SpecialPowers.pushPrefEnv({ "set": [["layout.css.variables.enabled", true]] }, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_csslexer.js b/layout/style/test/test_csslexer.js
new file mode 100644
index 000000000..a71c02d8f
--- /dev/null
+++ b/layout/style/test/test_csslexer.js
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function test_lexer(domutils, cssText, tokenTypes) {
+ let lexer = domutils.getCSSLexer(cssText);
+ let reconstructed = '';
+ let lastTokenEnd = 0;
+ let i = 0;
+ while (true) {
+ let token = lexer.nextToken();
+ if (!token) {
+ break;
+ }
+ let combined = token.tokenType;
+ if (token.text)
+ combined += ":" + token.text;
+ equal(combined, tokenTypes[i]);
+ ok(token.endOffset > token.startOffset);
+ equal(token.startOffset, lastTokenEnd);
+ lastTokenEnd = token.endOffset;
+ reconstructed += cssText.substring(token.startOffset, token.endOffset);
+ ++i;
+ }
+ // Ensure that we saw the correct number of tokens.
+ equal(i, tokenTypes.length);
+ // Ensure that the reported offsets cover all the text.
+ equal(reconstructed, cssText);
+}
+
+var LEX_TESTS = [
+ ["simple", ["ident:simple"]],
+ ["simple: { hi; }",
+ ["ident:simple", "symbol::",
+ "whitespace", "symbol:{",
+ "whitespace", "ident:hi",
+ "symbol:;", "whitespace",
+ "symbol:}"]],
+ ["/* whatever */", ["comment"]],
+ ["'string'", ["string:string"]],
+ ['"string"', ["string:string"]],
+ ["rgb(1,2,3)", ["function:rgb", "number",
+ "symbol:,", "number",
+ "symbol:,", "number",
+ "symbol:)"]],
+ ["@media", ["at:media"]],
+ ["#hibob", ["id:hibob"]],
+ ["#123", ["hash:123"]],
+ ["23px", ["dimension:px"]],
+ ["23%", ["percentage"]],
+ ["url(http://example.com)", ["url:http://example.com"]],
+ ["url('http://example.com')", ["url:http://example.com"]],
+ ["url( 'http://example.com' )",
+ ["url:http://example.com"]],
+ // In CSS Level 3, this is an ordinary URL, not a BAD_URL.
+ ["url(http://example.com", ["url:http://example.com"]],
+ // See bug 1153981 to understand why this gets a SYMBOL token.
+ ["url(http://example.com @", ["bad_url:http://example.com", "symbol:@"]],
+ ["quo\\ting", ["ident:quoting"]],
+ ["'bad string\n", ["bad_string:bad string", "whitespace"]],
+ ["~=", ["includes"]],
+ ["|=", ["dashmatch"]],
+ ["^=", ["beginsmatch"]],
+ ["$=", ["endsmatch"]],
+ ["*=", ["containsmatch"]],
+
+ // URANGE may be on the way out, and it isn't used by devutils, so
+ // let's skip it.
+
+ ["<!-- html comment -->", ["htmlcomment", "whitespace", "ident:html",
+ "whitespace", "ident:comment", "whitespace",
+ "htmlcomment"]],
+
+ // earlier versions of CSS had "bad comment" tokens, but in level 3,
+ // unterminated comments are just comments.
+ ["/* bad comment", ["comment"]]
+];
+
+function test_lexer_linecol(domutils, cssText, locations) {
+ let lexer = domutils.getCSSLexer(cssText);
+ let i = 0;
+ while (true) {
+ let token = lexer.nextToken();
+ let startLine = lexer.lineNumber;
+ let startColumn = lexer.columnNumber;
+
+ // We do this in a bit of a funny way so that we can also test the
+ // location of the EOF.
+ let combined = ":" + startLine + ":" + startColumn;
+ if (token)
+ combined = token.tokenType + combined;
+
+ equal(combined, locations[i]);
+ ++i;
+
+ if (!token) {
+ break;
+ }
+ }
+ // Ensure that we saw the correct number of tokens.
+ equal(i, locations.length);
+}
+
+function test_lexer_eofchar(domutils, cssText, argText, expectedAppend,
+ expectedNoAppend) {
+ let lexer = domutils.getCSSLexer(cssText);
+ while (lexer.nextToken()) {
+ // Nothing.
+ }
+
+ do_print("EOF char test, input = " + cssText);
+
+ let result = lexer.performEOFFixup(argText, true);
+ equal(result, expectedAppend);
+
+ result = lexer.performEOFFixup(argText, false);
+ equal(result, expectedNoAppend);
+}
+
+var LINECOL_TESTS = [
+ ["simple", ["ident:0:0", ":0:6"]],
+ ["\n stuff", ["whitespace:0:0", "ident:1:4", ":1:9"]],
+ ['"string with \\\nnewline" \r\n', ["string:0:0", "whitespace:1:8",
+ ":2:0"]]
+];
+
+var EOFCHAR_TESTS = [
+ ["hello", "hello"],
+ ["hello \\", "hello \\\\", "hello \\\uFFFD"],
+ ["'hello", "'hello'"],
+ ["\"hello", "\"hello\""],
+ ["'hello\\", "'hello\\\\'", "'hello'"],
+ ["\"hello\\", "\"hello\\\\\"", "\"hello\""],
+ ["/*hello", "/*hello*/"],
+ ["/*hello*", "/*hello*/"],
+ ["/*hello\\", "/*hello\\*/"],
+ ["url(hello", "url(hello)"],
+ ["url('hello", "url('hello')"],
+ ["url(\"hello", "url(\"hello\")"],
+ ["url(hello\\", "url(hello\\\\)", "url(hello\\\uFFFD)"],
+ ["url('hello\\", "url('hello\\\\')", "url('hello')"],
+ ["url(\"hello\\", "url(\"hello\\\\\")", "url(\"hello\")"],
+];
+
+function run_test()
+{
+ let domutils = Components.classes["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Components.interfaces.inIDOMUtils);
+
+ let text, result;
+ for ([text, result] of LEX_TESTS) {
+ test_lexer(domutils, text, result);
+ }
+
+ for ([text, result] of LINECOL_TESTS) {
+ test_lexer_linecol(domutils, text, result);
+ }
+
+ for ([text, expectedAppend, expectedNoAppend] of EOFCHAR_TESTS) {
+ if (!expectedNoAppend) {
+ expectedNoAppend = expectedAppend;
+ }
+ test_lexer_eofchar(domutils, text, text, expectedAppend, expectedNoAppend);
+ }
+
+ // Ensure that passing a different inputString to performEOFFixup
+ // doesn't cause an assertion trying to strip a backslash from the
+ // end of an empty string.
+ test_lexer_eofchar(domutils, "'\\", "", "\\'", "'");
+}
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 000000000..bb4db8653
--- /dev/null
+++ b/layout/style/test/test_default_bidi_css.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+ <script type="application/javascript" 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);
+ }
+ 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 000000000..60f3dcab0
--- /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 type="application/javascript" 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, "transparent",
+ "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 000000000..50017f642
--- /dev/null
+++ b/layout/style/test/test_descriptor_storage.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS @font-face descriptor values</title>
+ <script type="text/javascript" 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 + ";\n";
+ 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);
+
+SpecialPowers.pushPrefEnv({ set: [["layout.css.font-display.enabled", true]] },
+ 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 000000000..952625c92
--- /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 type="text/javascript" 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_dont_use_document_colors.html b/layout/style/test/test_dont_use_document_colors.html
new file mode 100644
index 000000000..4ea47c88b
--- /dev/null
+++ b/layout/style/test/test_dont_use_document_colors.html
@@ -0,0 +1,186 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for preference not to use document colors</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ #one, #three { background: blue; color: yellow; border: thin solid red; -moz-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;
+ -moz-border-top-colors: blue aqua fuchsia;
+ -moz-border-right-colors: aqua blue fuchsia;
+ -moz-border-bottom-colors: blue fuchsia aqua;
+ -moz-border-left-colors: fuchsia blue blue;
+ }
+
+ /* 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>
+<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>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+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"), "");
+
+SpecialPowers.pushPrefEnv({'set': [['browser.display.document_color_use', 1]]}, part1);
+
+var transparentBackgroundColor;
+var inputBackgroundColor, inputColor, inputBorderTopColor;
+var inputBorderRightColor, inputBorderLeftColor, inputBorderBottomColor;
+
+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.MozColumnRuleColor, cs2.MozColumnRuleColor,
+ "-moz-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.MozColumnRuleColor, cs3.MozColumnRuleColor,
+ "-moz-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");
+ isnot(cs7.MozBorderTopColors, cs2.MozBorderTopColors,
+ "-moz-border-top-colors applies");
+ isnot(cs7.MozBorderRightColors, cs2.MozBorderRightColors,
+ "-moz-border-right-colors applies");
+ isnot(cs7.MozBorderBottomColors, cs2.MozBorderBottomColors,
+ "-moz-border-bottom-colors applies");
+ isnot(cs7.MozBorderLeftColors, cs2.MozBorderLeftColors,
+ "-moz-border-left-colors 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");
+ transparentBackgroundColor = cs2.backgroundColor;
+ inputBackgroundColor = cs4.backgroundColor;
+ inputColor = cs4.color;
+ inputBorderTopColor = cs4.borderTopColor;
+ inputBorderRightColor = cs4.borderRightColor;
+ inputBorderLeftColor = cs4.borderLeftColor;
+ inputBorderBottomColor = cs4.borderBottomColor;
+ SpecialPowers.pushPrefEnv({'set': [['browser.display.document_color_use', 2]]}, part2);
+}
+
+function part2()
+{
+ isnot(cs1.backgroundColor, cs2.backgroundColor, "background-color transparency preserved (opaque)");
+ is(cs2.backgroundColor, transparentBackgroundColor, "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(cs7.MozBorderTopColors, cs2.MozBorderTopColors,
+ "-moz-border-top-colors is blocked");
+ is(cs7.MozBorderRightColors, cs2.MozBorderRightColors,
+ "-moz-border-right-colors is blocked");
+ is(cs7.MozBorderBottomColors, cs2.MozBorderBottomColors,
+ "-moz-border-bottom-colors is blocked");
+ is(cs7.MozBorderLeftColors, cs2.MozBorderLeftColors,
+ "-moz-border-left-colors is blocked");
+ is(cs1.borderBottomColor, cs2.borderBottomColor,
+ "border-bottom-color is blocked");
+ is(cs1.MozColumnRuleColor, cs2.MozColumnRuleColor,
+ "-moz-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, cs1.backgroundColor, "background-color transparency preserved (opaque)");
+ is(cs3.color, cs4.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, inputBackgroundColor, "background-color not broken on inputs");
+ is(cs4.color, inputColor, "color not broken on inputs");
+ is(cs4.borderTopColor, inputBorderTopColor, "border-top-color not broken on inputs");
+ is(cs4.borderRightColor, inputBorderRightColor,
+ "border-right-color not broken on inputs");
+ is(cs4.borderLeftColor, inputBorderLeftColor,
+ "border-left-color not broken on inputs");
+ is(cs4.borderBottomColor, inputBorderBottomColor,
+ "border-bottom-color not broken on inputs");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
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 000000000..a941191f6
--- /dev/null
+++ b/layout/style/test/test_dynamic_change_causing_reflow.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1131371
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1131371</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1131371">Mozilla Bug 1131371</a>
+<div id="display">
+ <div id="content">
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+"use strict";
+
+/** Test for Bug 1131371 **/
+
+/**
+ * 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",
+ },
+
+ // 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 'display' should cause frame construction and reflow.
+ {
+ beforeStyle: "display: inline",
+ afterStyle: "display: table",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+];
+
+// 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;
+ }
+
+ // 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) {
+ gElem.setAttribute("style", aTestcase.beforeStyle);
+ msgPrefix += "from '" + aTestcase.beforeStyle + "' ";
+ }
+ msgPrefix += "to '" + aTestcase.afterStyle + "' ";
+
+ // Establish initial counts:
+ let unusedVal = gElem.offsetHeight; // flush layout
+ let origFramesConstructed = gUtils.framesConstructed;
+ let origFramesReflowed = gUtils.framesReflowed;
+
+ // Make the change and flush:
+ gElem.setAttribute("style", aTestcase.afterStyle);
+ unusedVal = gElem.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!
+ gElem.removeAttribute("style");
+}
+
+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 000000000..937afa7b8
--- /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 type="text/javascript" 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 000000000..8a94a0515
--- /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 type="text/javascript" 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;version=1.7">
+
+/*
+ * 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_flexbox_child_display_values.xhtml b/layout/style/test/test_flexbox_child_display_values.xhtml
new file mode 100644
index 000000000..08308d38e
--- /dev/null
+++ b/layout/style/test/test_flexbox_child_display_values.xhtml
@@ -0,0 +1,189 @@
+<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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783415">Mozilla Bug 783415</a>
+<div id="display">
+ <div id="wrapper"></div>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+<![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",
+ "-moz-box",
+ "-moz-inline-box",
+ "-moz-grid",
+ "-moz-inline-grid",
+ "-moz-grid-group",
+ "-moz-grid-line",
+ "-moz-stack",
+ "-moz-inline-stack",
+ "-moz-deck",
+ "-moz-popup",
+ "-moz-groupbox",
+ ];
+
+ 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 000000000..ef6fd901d
--- /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 type="application/javascript" 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 000000000..24bffed94
--- /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 type="application/javascript" 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;version=1.7">
+"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": "2e+7"
+ },
+
+ // 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": "-moz-fit-content",
+ "flex-basis": "-moz-fit-content",
+ },
+ {
+ "flex": "calc(5px + 6px)",
+ "flex-basis": "11px",
+ },
+ {
+ "flex": "calc(15% + 30px)",
+ "flex-basis": "calc(30px + 15%)",
+ },
+
+ // 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 -moz-fit-content",
+ "flex-grow": "5",
+ "flex-basis": "-moz-fit-content",
+ },
+ {
+ "flex": "calc(5% + 10px) 3",
+ "flex-grow": "3",
+ "flex-basis": "calc(10px + 5%)",
+ },
+
+ // 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, null).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_layout.html b/layout/style/test/test_flexbox_layout.html
new file mode 100644
index 000000000..c484030be
--- /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 type="application/javascript" 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;version=1.7">
+"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, null).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 000000000..49552c645
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" 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;version=1.7">
+"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 000000000..f838fed0e
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" 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;version=1.7">
+"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 000000000..36d1b8560
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" 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;version=1.7">
+"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 000000000..f00983b31
--- /dev/null
+++ b/layout/style/test/test_flexbox_reflow_counts.html
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1142686
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1142686</title>
+ <script type="application/javascript" 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;version=1.7">
+"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");
+
+// Remove contents of inner block, and remove manual styling:
+function cleanup()
+{
+ outerFlex.style = midFlex.style = innerBlock.style = "";
+ while (innerBlock.firstChild) {
+ innerBlock.removeChild(innerBlock.firstChild);
+ }
+}
+
+// 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() {
+ midFlex.style.border = innerBlock.style.border = "3px solid black";
+ },
+ },
+ {
+ label : "padding on flex items",
+ addTestStyle : function() {
+ midFlex.style.padding = innerBlock.style.padding = "5px";
+ },
+ },
+ {
+ label : "margin on flex items",
+ addTestStyle : function() {
+ midFlex.style.margin = innerBlock.style.margin = "2px";
+ },
+ },
+];
+
+// 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_font_face_parser.html b/layout/style/test/test_font_face_parser.html
new file mode 100644
index 000000000..75eba7293
--- /dev/null
+++ b/layout/style/test/test_font_face_parser.html
@@ -0,0 +1,382 @@
+<!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 type="text/javascript" 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\") format(\"truetype\", \"opentype\");"),
+ d: { "src" : "url(\"/fonts/Mouse\") format(\"truetype\", \"opentype\")" } },
+
+ { 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\")" } },
+
+ // 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);"), 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: {} },
+
+ // 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+??????"),
+ d: { "unicode-range" : "U+0-10FFFF" }, noncanonical: true },
+ { rule: _("unicode-range: U+590-5ff;"),
+ d: { "unicode-range" : "U+590-5FF" }, noncanonical: true },
+ { rule: _("unicode-range: U+A0000-12FFFF"),
+ d: { "unicode-range" : "U+A0000-10FFFF" }, 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: {} },
+
+ // 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 ignored
+ // but do not invalidate the descriptor
+ { rule: _("unicode-range: U+A5, U+90-30"),
+ d: { "unicode-range" : "U+A5" }, noncanonical: true },
+ { rule: _("unicode-range: U+A5, U+220043"),
+ d: { "unicode-range" : "U+A5" }, noncanonical: true },
+
+ // 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;
+
+ // 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();
+SpecialPowers.pushPrefEnv({ set: [["layout.css.font-display.enabled", true]] },
+ 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 000000000..526491ebc
--- /dev/null
+++ b/layout/style/test/test_font_family_parsing.html
@@ -0,0 +1,276 @@
+<!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 }
+
+];
+
+if (window.SpecialPowers && SpecialPowers.getBoolPref("layout.css.unset-value.enabled")) {
+ testFontFamilyLists.push(
+ { 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_feature_values_parsing.html b/layout/style/test/test_font_feature_values_parsing.html
new file mode 100644
index 000000000..a5d4a6834
--- /dev/null
+++ b/layout/style/test/test_font_feature_values_parsing.html
@@ -0,0 +1,356 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>@font-feature-values rule 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-feature-values" />
+ <meta name="assert" content="tests that valid @font-feature-values rules parse and invalid ones don't" />
+ <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=549861 -->
+ <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">
+var gPrefix = "";
+var kFontFeatureValuesRuleType = 14;
+
+function ruleName() { return "@" + gPrefix + "font-feature-values"; }
+function makeRule(f, v) {
+ return ruleName() + " " + f + " { " + v + " }";
+}
+
+function _()
+{
+ var i, decl = [];
+ for (i = 0; i < arguments.length; i++) {
+ decl.push(arguments[i]);
+ }
+ return makeRule("bongo", decl.join(" "));
+}
+
+// note: because of bugs in the way family names are serialized,
+// 'serializationSame' only implies that the value definition block
+// is the same (i.e. not including the family name list)
+
+var testrules = [
+
+ /* basic syntax */
+ { rule: ruleName() + ";", invalid: true },
+ { rule: ruleName() + " bongo;", invalid: true },
+ { rule: ruleName().replace("values", "value") + " {;}", invalid: true },
+ { rule: ruleName().replace("feature", "features") + " {;}", invalid: true },
+ { rule: makeRule("bongo", ""), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", ";"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", ",;"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", ";,"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", ",;,"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset;"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset,;"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset abc;"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { ;;abc }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc;; }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc: }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc,: }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc:, }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc:,; }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { a,b }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { a;b }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { a:;b: }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { a:,;b: }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { a:1,;b: }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc 1 2 3 }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc:, 1 2 3 }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc: 1 2 3a }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@styleset { abc: 1 2 3, def: 1; }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@blah @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@blah } @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true },
+ { rule: makeRule("bongo", "@blah , @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true },
+ { rule: ruleName() + " bongo { @styleset { abc: 1 2 3; }", serialization: _("@styleset { abc: 1 2 3; }") },
+ { rule: ruleName() + " bongo { @styleset { abc: 1 2 3 }", serialization: _("@styleset { abc: 1 2 3; }") },
+ { rule: ruleName() + " bongo { @styleset { abc: 1 2 3;", serialization: _("@styleset { abc: 1 2 3; }") },
+ { rule: ruleName() + " bongo { @styleset { abc: 1 2 3", serialization: _("@styleset { abc: 1 2 3; }") },
+ { rule: _("@styleset { ok-1: 1; }"), serializationSame: true },
+ { rule: _("@annotation { ok-1: 3; }"), serializationSame: true },
+ { rule: _("@stylistic { blah: 3; }"), serializationSame: true },
+ { rule: makeRule("bongo", "\n@styleset\n { blah: 3; super-blah: 4 5;\n more-blah: 5 6 7;\n }"), serializationSame: true },
+ { rule: makeRule("bongo", "\n@styleset\n {\n blah:\n 3\n;\n super-blah:\n 4\n 5\n;\n more-blah:\n 5 6\n 7;\n }"), serializationSame: true },
+
+ /* limits on number of values */
+ { rule: _("@stylistic { blah: 1; }"), serializationSame: true },
+ { rule: _("@styleset { blah: 1 2 3 4; }"), serializationSame: true },
+ { rule: _("@character-variant { blah: 1 2; }"), serializationSame: true },
+ { rule: _("@swash { blah: 1; }"), serializationSame: true },
+ { rule: _("@ornaments { blah: 1; }"), serializationSame: true },
+ { rule: _("@annotation { blah: 1; }"), serializationSame: true },
+
+ /* values ignored when used */
+ { rule: _("@styleset { blah: 0; }"), serializationSame: true },
+ { rule: _("@styleset { blah: 120 124; }"), serializationSame: true },
+ { rule: _("@character-variant { blah: 0; }"), serializationSame: true },
+ { rule: _("@character-variant { blah: 111; }"), serializationSame: true },
+ { rule: _("@character-variant { blah: 111 13; }"), serializationSame: true },
+
+ /* invalid value name */
+ { rulesrc: ["styleset { blah: 1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["stylistic { blah: 1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["character-variant { blah: 1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["swash { blah: 1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["ornaments { blah: 1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["annotation { blah: 1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@bongo { blah: 1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@bongo { blah: 1 2 3 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@bongo { blah: 1 2 3; burp: 1;;; }"], serializationNoValueDefn: true },
+
+ /* values */
+ { rulesrc: ["@styleset { blah: -1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: 1 -1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: 1.5 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: 15px }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: red }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: (1) }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah:(1) }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah:, 1 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: <1> }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: 1! }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: 1,, }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: 1 1 1 1; }"], serializationSame: true },
+
+ /* limits on number of values */
+ { rulesrc: ["@stylistic { blah: 1 2 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@character-variant { blah: 1 2 3 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@swash { blah: 1 2 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@ornaments { blah: 1 2 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@annotation { blah: 1 2 }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { blah: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19; }"], serializationSame: true },
+
+ /* family names */
+ { rule: makeRule("bongo", "@styleset { blah: 1; }"), serializationSame: true },
+ { rule: makeRule("\"bongo\"", "@styleset { blah: 1; }"), serializationSame: true },
+ { rule: makeRule("'bongo'", "@styleset { blah: 1; }"), serializationSame: true },
+ { rule: makeRule("\\62 ongo", "@styleset { blah: 1; }"), serializationSame: true },
+ { rule: makeRule("bongo, super bongo, bongo the supreme", "@styleset { blah: 1; }"), serializationSame: true },
+ { rule: makeRule("bongo,, super bongo", "@styleset { blah: 1; }"), invalid: true },
+ { rule: makeRule("bongo,*", "@styleset { blah: 1; }"), invalid: true },
+ { rule: makeRule("bongo, sans-serif", "@styleset { blah: 1; }"), invalid: true },
+ { rule: makeRule("serif, sans-serif", "@styleset { blah: 1; }"), invalid: true },
+ { rule: makeRule("'serif', 'sans-serif'", "@styleset { blah: 1; }"), serializationSame: true },
+ { rule: makeRule("bongo, \"super bongo\", 'bongo the supreme'", "@styleset { blah: 1; }"), serializationSame: true },
+ { rule: makeRule("毎日カレーを食べたい!", "@styleset { blah: 1; }"), serializationSame: true },
+ { rule: makeRule("毎日カレーを食べたい!, 納豆嫌い", "@styleset { blah: 1; }"), serializationSame: true },
+
+ { rule: makeRule("bongo, \"super\" bongo, bongo the supreme", "@styleset { blah: 1; }"), invalid: true },
+ { rule: makeRule("--bongo", "@styleset { blah: 1; }"), invalid: true },
+
+ /* ident tests */
+ { rule: _("@styleset { blah: 1; blah: 1; }"), serializationSame: true },
+ { rule: _("@styleset { blah: 1; de-blah: 1; blah: 2; }"), serializationSame: true },
+ { rule: _("@styleset { \\tra-la: 1; }"), serialization: _("@styleset { tra-la: 1; }") },
+ { rule: _("@styleset { b\\lah: 1; }"), serialization: _("@styleset { blah: 1; }") },
+ { rule: _("@styleset { \\62 lah: 1; }"), serialization: _("@styleset { blah: 1; }") },
+ { rule: _("@styleset { \\:blah: 1; }"), serialization: _("@styleset { \\:blah: 1; }") },
+ { rule: _("@styleset { \\;blah: 1; }"), serialization: _("@styleset { \\;blah: 1; }") },
+ { rule: _("@styleset { complex\\20 blah: 1; }"), serialization: _("@styleset { complex\\ blah: 1; }") },
+ { rule: _("@styleset { complex\\ blah: 1; }"), serializationSame: true },
+ { rule: _("@styleset { Håkon: 1; }"), serializationSame: true },
+ { rule: _("@styleset { Åквариум: 1; }"), serializationSame: true },
+ { rule: _("@styleset { \\1f449\\1f4a9\\1f448: 1; }"), serialization: _("@styleset { 👉💩👈: 1; }") },
+ { rule: _("@styleset { 魅力: 1; }"), serializationSame: true },
+ { rule: _("@styleset { 毎日カレーを食べたい!: 1; }"), serializationSame: true },
+ /* from http://en.wikipedia.org/wiki/Metal_umlaut */
+ { rule: _("@styleset { TECHNICIÄNS\\ ÖF\\ SPÅCE\\ SHIP\\ EÅRTH\\ THIS\\ IS\\ YÖÜR\\ CÄPTÅIN\\ SPEÄKING\\ YÖÜR\\ ØÅPTÅIN\\ IS\\ DEA̋D: 1; }"), serializationSame: true },
+
+ { rulesrc: ["@styleset { 123blah: 1; }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { :123blah 1; }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { :123blah: 1; }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { ?123blah: 1; }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { \"blah\": 1; }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { complex blah: 1; }"], serializationNoValueDefn: true },
+ { rulesrc: ["@styleset { complex\\ blah: 1; }"], serializationNoValueDefn: true }
+
+];
+
+// test that invalid value declarations don't affect the parsing of surrounding
+// declarations. So before + invalid + after should match the serialization
+// given in s.
+
+var gSurroundingTests = [
+ // -- invalid, valid ==> valid
+ { before: "", after: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }") },
+
+ // -- valid, invalid ==> valid
+ { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }", after: "", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }") },
+
+ // -- valid, invalid, valid ==> valid, valid
+ { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", after: "@character-variant { whatchamacallit-2: 23 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; } @character-variant { whatchamacallit-2: 23 4; }") },
+
+ // -- invalid, valid, invalid ==> valid
+ { between: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }") }
+];
+
+/* strip out just values, along with empty value blocks (e.g. @swash { })*/
+function valuesText(ruletext)
+{
+ var t = ruletext.replace(/@[a-zA-Z0-9\-]+[ \n]*{[ \n]*}/g, "");
+ t = t.replace(/[ \n]+/g, " ");
+ t = t.replace(/^[^{]+{[ \n]*/, "");
+ t = t.replace(/[ \n]*}[^}]*$/, "");
+ t = t.replace(/[ \n]*;/g, ";");
+ return t;
+}
+
+function testParse(rulesrc)
+{
+ var sheet = document.styleSheets[1];
+ var rule = _.apply(this, rulesrc);
+
+ while(sheet.cssRules.length > 0)
+ sheet.deleteRule(0);
+ try {
+ sheet.insertRule(rule, 0);
+ } catch (e) {
+ return e.toString();
+ }
+
+ if (sheet.cssRules.length == 1 && sheet.cssRules[0].type == kFontFeatureValuesRuleType) {
+ return sheet.cssRules[0].cssText.replace(/[ \n]+/g, " ");
+ }
+
+ return "";
+}
+
+function testOneRule(testrule) {
+ var sheet = document.styleSheets[1];
+ var rule;
+
+ if ("rulesrc" in testrule) {
+ rule = _.apply(this, testrule.rulesrc);
+ } else {
+ rule = testrule.rule;
+ }
+
+ var parseErr = false;
+ var expectedErr = false;
+ var invalid = false;
+ if ("invalid" in testrule && testrule.invalid) invalid = true;
+
+ while(sheet.cssRules.length > 0)
+ sheet.deleteRule(0);
+ try {
+ sheet.insertRule(rule, 0);
+ } catch (e) {
+ expectedErr = (e.name == "SyntaxError"
+ && e instanceof DOMException
+ && e.code == DOMException.SYNTAX_ERR
+ && invalid);
+ parseErr = true;
+ }
+
+ test(function() {
+ assert_true(!parseErr || expectedErr, "unexpected syntax error");
+ if (!parseErr) {
+ assert_equals(sheet.cssRules.length, 1, "bad rule count");
+ assert_equals(sheet.cssRules[0].type, kFontFeatureValuesRuleType, "bad rule type");
+ }
+ }, "basic parse tests - " + rule);
+
+ var sanitizedRule = rule.replace(/[ \n]+/g, " ");
+ if (parseErr) {
+ return;
+ }
+
+ // should result in one @font-feature-values rule constructed
+
+ // serialization matches expectation
+ // -- note: due to inconsistent font family serialization problems,
+ // only the serialization of the values is tested currently
+
+ var ruleValues = valuesText(rule);
+ var serialized = sheet.cssRules[0].cssText;
+ var serializedValues = valuesText(serialized);
+ var haveSerialization = true;
+
+ if (testrule.serializationSame) {
+ test(function() {
+ assert_equals(serializedValues, ruleValues, "canonical cssText serialization doesn't match");
+ }, "serialization check - " + rule);
+ } else if ("serialization" in testrule) {
+ var s = valuesText(testrule.serialization);
+ test(function() {
+ assert_equals(serializedValues, s, "non-canonical cssText serialization doesn't match - ");
+ }, "serialization check - " + rule);
+ } else if (testrule.serializationNoValueDefn) {
+ test(function() {
+ assert_equals(serializedValues, "", "cssText serialization should have no value defintions - ");
+ }, "no value definitions in serialization - " + rule);
+
+ haveSerialization = false;
+
+ if ("rulesrc" in testrule) {
+ test(function() {
+ var j, rulesrc = testrule.rulesrc;
+
+ // invalid value definitions shouldn't affect the parsing of valid
+ // definitions before or after an invalid one
+ for (var j = 0; j < gSurroundingTests.length; j++) {
+ var t = gSurroundingTests[j];
+ var srulesrc = [];
+
+ if ("between" in t) {
+ srulesrc = srulesrc.concat(rulesrc);
+ srulesrc = srulesrc.concat(t.between);
+ srulesrc = srulesrc.concat(rulesrc);
+ } else {
+ if (t.before != "")
+ srulesrc = srulesrc.concat(t.before);
+ srulesrc = srulesrc.concat(rulesrc);
+ if (t.after != "")
+ srulesrc = srulesrc.concat(t.after);
+ }
+
+ var result = testParse(srulesrc);
+ assert_equals(valuesText(result), valuesText(t.s), "invalid declarations should not affect valid ones - ");
+ }
+ }, "invalid declarations don't affect valid ones - " + rule);
+ }
+ }
+
+ // if serialization non-empty, serialization should round-trip to itself
+ if (haveSerialization) {
+ var roundTripText = testParse([serializedValues]);
+ test(function() {
+ assert_equals(valuesText(roundTripText), serializedValues,
+ "serialization should round-trip to itself - ");
+ }, "serialization round-trip - " + rule);
+ }
+}
+
+function testFontFeatureValuesRuleParsing() {
+ var i;
+ for (i = 0; i < testrules.length; i++) {
+ var testrule = testrules[i];
+ var rule;
+
+ if ("rulesrc" in testrule) {
+ rule = _.apply(this, testrule.rulesrc);
+ } else {
+ rule = testrule.rule;
+ }
+
+ testOneRule(testrule);
+ //test(function() { testOneRule(testrule); }, "parsing " + rule);
+ }
+}
+
+testFontFeatureValuesRuleParsing();
+</script>
+</body></html>
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 000000000..26c5a2a85
--- /dev/null
+++ b/layout/style/test/test_font_loading_api.html
@@ -0,0 +1,1897 @@
+<!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="start()">
+<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) {
+ var xhr;
+ 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 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 starts out resolved with the
+ // FontFaceSet.
+ var p = Promise.resolve();
+ sourceDocuments.forEach(function({ doc, what }) {
+ p = p.then(function() {
+ return is_resolved_with(doc.fonts.ready, doc.fonts, "initial value of 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() {
+
+ // XXX Disabled this sub-test due to intermittent failures (bug 1076803).
+ return;
+
+ // (TEST 18) Test passing valid 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.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();
+
+ 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 i = 0;
+ var faces = [];
+ sourceWindows.forEach(function({ win, what: whatWin }) {
+ var f = new win.FontFace("test" + ++i, "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 }) {
+ var all = Array.from(doc.fonts);
+ is(expected[what].length, all.length, "expected FontFaceSet size (TEST 48) (" + what + ")");
+ for (var i = 0; i < expected[what].length; i++) {
+ is(expected[what][i], all[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();
+
+ });
+}
+
+function start() {
+ if (SpecialPowers.getBoolPref("layout.css.font-loading-api.enabled")) {
+ SpecialPowers.pushPrefEnv({ set: [["layout.css.font-display.enabled", true]] },
+ runTest);
+ } else {
+ ok(true, "CSS Font Loading API is not enabled.");
+ }
+}
+
+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 000000000..5be02a295
--- /dev/null
+++ b/layout/style/test/test_garbage_at_end_of_declarations.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test handling of garbage at the end of CSS declarations</title>
+ <script type="text/javascript" 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">
+
+/** 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;
+var gUnsetValueEnabled = SpecialPowers.getBoolPref("layout.css.unset-value.enabled");
+
+/*
+ * 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 }
+};
+
+gAllowsExtraUnusual["-moz-transition"] = gAllowsExtraUnusual["transition"];
+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");
+ if (gUnsetValueEnabled)
+ 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();
+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 000000000..de77fec34
--- /dev/null
+++ b/layout/style/test/test_grid_computed_values.html
@@ -0,0 +1,106 @@
+<!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() {
+ assert_equals(getComputedStyle(gridElement).gridTemplateColumns,
+ "10px 10px 10px");
+ assert_equals(getComputedStyle(gridElement, "").gridTemplateRows,
+ "400px");
+ }, "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 000000000..741404bc4
--- /dev/null
+++ b/layout/style/test/test_grid_container_shorthands.html
@@ -0,0 +1,282 @@
+<!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 isGridTemplateSubgridValueEnabled =
+ SpecialPowers.getBoolPref("layout.css.grid-template-subgrid-value.enabled");
+
+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: "20px",
+ },
+ {
+ specified: "40px/[a]repeat(1, 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] 20px",
+ },
+ {
+ specified: "40px/repeat(1, [a] 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] 20px",
+ },
+ {
+ specified: "40px/[a]repeat(2, [b]20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a b] 20px [b] 20px",
+ },
+ {
+ specified: "40px/[a]repeat(2, 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] 20px 20px",
+ },
+ {
+ specified: "40px/repeat(2, [a] 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] 20px [a] 20px",
+ },
+ {
+ specified: "40px/[a]repeat(2, [b]20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a b] 20px [b] 20px",
+ },
+ {
+ specified: "40px/repeat(2, 20px[a])",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "20px [a] 20px [a]",
+ },
+ {
+ specified: "40px/repeat(2, 20px[a]) [b]",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "20px [a] 20px [a b]",
+ },
+ {
+ specified: "40px/repeat(2, [a] 20px[b]) [c]",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] 20px [b a] 20px [b c]",
+ },
+ {
+ specified: "40px/[a] repeat(3, [b c] 20px [d] 100px [e f]) [g]",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a b c] 20px [d] 100px [e f b c] 20px [d] 100px [e f 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",
+ gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
+ gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
+ },
+ {
+ specified: "subgrid / subgrid",
+ gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
+ gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
+ },
+ {
+ specified: "subgrid [foo] / subgrid",
+ gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
+ gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid [foo]" : "none",
+ },
+ {
+ specified: "subgrid [foo] repeat(3, [] [a b] [c]) / subgrid",
+ gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
+ gridTemplateRows: isGridTemplateSubgridValueEnabled ?
+ "subgrid [foo] [] [a b] [c] [] [a b] [c] [] [a b] [c]" : "none",
+ },
+ {
+ // Test that the number of lines is clamped to kMaxLine = 10000.
+ specified: "subgrid [foo] repeat(999999999, [a]) / subgrid",
+ gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
+ // Array(n).join(s) is a hack for the non-standard s.repeat(n - 1) .
+ // [foo] + 9999 [a] gives us 10000 lines.
+ gridTemplateRows: isGridTemplateSubgridValueEnabled ?
+ "subgrid [foo]" + Array(10000).join(" [a]") : "none",
+ },
+ {
+ specified: "subgrid [bar]/ subgrid [] [foo",
+ gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid [] [foo]" : "none",
+ gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid [bar]" : "none",
+ },
+ {
+ 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: "row 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: "row 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 000000000..a50be6112
--- /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 000000000..dcb6f8880
--- /dev/null
+++ b/layout/style/test/test_grid_shorthand_serialization.html
@@ -0,0 +1,236 @@
+<!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 isGridTemplateSubgridValueEnabled =
+ SpecialPowers.getBoolPref("layout.css.grid-template-subgrid-value.enabled");
+
+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: isGridTemplateSubgridValueEnabled ? "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: "[foo] \"a\" 20px [bar]",
+ },
+ {
+ gridTemplateAreas: "\"a a\"",
+ gridTemplateColumns: "repeat(2, 100px)",
+ gridTemplateRows: "auto",
+ shorthand: "\"a a\" auto / 100px 100px",
+ },
+ // 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 / 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",
+ },
+ {
+ gridAutoFlow: "row",
+ gridRowGap: "0px",
+ shorthand: "none / none",
+ },
+ {
+ gridAutoFlow: "row",
+ gridRowGap: "1px",
+ shorthand: "",
+ },
+ {
+ gridAutoFlow: "row",
+ gridColumnGap: "1px",
+ shorthand: "",
+ },
+]);
+
+var grid_important_test_cases = [
+ {
+ "grid-auto-flow": "row",
+ "grid-row-gap": "0px",
+ shorthand: "",
+ },
+ {
+ "grid-auto-flow": "row",
+ "grid-column-gap": "1px",
+ 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", "gridColumnGap", "gridRowGap"]);
+
+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", "grid-column-gap", "grid-row-gap"]);
+
+</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 000000000..85edc2a1a
--- /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_quirk.html b/layout/style/test/test_hover_quirk.html
new file mode 100644
index 000000000..c14b741c3
--- /dev/null
+++ b/layout/style/test/test_hover_quirk.html
@@ -0,0 +1,82 @@
+<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";
+ }
+
+ /* 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;
+ }
+ </style>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" 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);
+ 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="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>
+ </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 000000000..3f7013cc1
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">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 000000000..8d7f32ffa
--- /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 type="application/javascript" 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_inherit_computation.html b/layout/style/test/test_inherit_computation.html
new file mode 100644
index 000000000..a4407418c
--- /dev/null
+++ b/layout/style/test/test_inherit_computation.html
@@ -0,0 +1,159 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for computation of CSS 'inherit' on all properties and 'unset' on inherited properties</title>
+ <script type="text/javascript" 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 **/
+
+// 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)];
+
+var gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled");
+
+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 && gTestUnset)
+ 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 + "'");
+ 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);
+ }
+ }
+ });
+}
+
+for (var prop in gCSSProperties) {
+ 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 000000000..f78c70558
--- /dev/null
+++ b/layout/style/test/test_inherit_storage.html
@@ -0,0 +1,116 @@
+<!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 type="text/javascript" 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;
+
+var gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled");
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ var keywords = ["inherit"];
+ if (info.inherited && gTestUnset)
+ 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 {
+ 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(property, 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 " + property + " 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 000000000..8da53ed45
--- /dev/null
+++ b/layout/style/test/test_initial_computation.html
@@ -0,0 +1,163 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for computation of CSS 'initial' on all properties and 'unset' on reset properties</title>
+ <script type="text/javascript" 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 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;
+
+var gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled");
+
+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 && gTestUnset)
+ 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's important (given the current design of 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'll have a cached aStartStruct from the parent in the rule
+ // tree (caching the "other" value), so we'll make sure we don't get
+ // the initial value from the luck of default-initialization.
+ // This means that it's important that we set the prereqs on
+ // gRule1.style rather than on gElement.style.
+ 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);
+ }
+
+ // FIXME: Something (maybe with the -moz-binding values in
+ // test_value_computation.html, but may as well do it here to match)
+ // causes gElementF's frame to get lost. Force it to get recreated
+ // after each property.
+ gElementF.parentNode.style.display = "none";
+ get_computed_value(getComputedStyle(gElementF, ""), "width");
+ gElementF.parentNode.style.display = "";
+ get_computed_value(getComputedStyle(gElementF, ""), "width");
+ });
+}
+
+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 000000000..f75c0d68c
--- /dev/null
+++ b/layout/style/test/test_initial_storage.html
@@ -0,0 +1,130 @@
+<!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 type="text/javascript" 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;
+
+var gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled");
+
+/**
+ * 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 && gTestUnset)
+ 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 {
+ 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(property, 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 " + property + " 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_keyframes_rules.html b/layout/style/test/test_keyframes_rules.html
new file mode 100644
index 000000000..8446f22dd
--- /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 type="application/javascript" 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_load_events_on_stylesheets.html b/layout/style/test/test_load_events_on_stylesheets.html
new file mode 100644
index 000000000..c6dedbed1
--- /dev/null
+++ b/layout/style/test/test_load_events_on_stylesheets.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=185236
+-->
+<head>
+ <title>Test for Bug 185236</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script>
+ var pendingEventCounter = 0;
+ var messagePosted = false;
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ is(messagePosted, true, "Should have gotten onmessage event");
+ 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 type="application/javascript">
+/** Test for Bug 185236 **/
+// Verify that there are no in-flight sheet loads right now; we should have
+// waited for them when we hit the script tag
+is(pendingEventCounter, 0, "There should be no pending events");
+
+// Test sheet that will already be complete when we write it out
+++pendingEventCounter;
+
+// Make sure that a postMessage we do right now fires after the onload handler
+// for the stylesheet. If we ever change the timing of sheet onload, we will
+// need to change that.
+window.onmessage = function() {
+ messagePosted = true;
+ // There are 4 pending events: two from the two direct example.com loads,
+ // and 2 from the two data:text/css loads that import things
+ is(pendingEventCounter, 4, "Load event for sheet should have fired");
+}
+window.postMessage("", "*");
+
+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
+is(document.styleSheets[2].cssRules.length, 1, "Should have one rule");
+
+// Make sure the load event for that stylesheet has not fired yet
+is(pendingEventCounter, 1, "There should be one pending event");
+
+++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, 2, "There should be two pending events");
+
+++pendingEventCounter;
+document.write('<link rel="stylesheet" href="http://www.example.com"\
+ onload="--pendingEventCounter;\
+ ok(false, \'Load event firing on broken stylesheet\')"\
+ onerror="--pendingEventCounter;\
+ ok(true, \'Error event firing on broken stylesheet\')">');
+
+++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');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet');
+}
+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);
+
+// Make sure we have that last stylesheet
+is(document.styleSheets.length, 7, "Should have seven stylesheets here");
+
+// Make sure that the sixth stylesheet is all loaded
+// If we ever switch away from sync loading of already-complete sheets, this
+// test will need adjusting
+is(document.styleSheets[6].cssRules.length, 1, "Should have one rule");
+
+++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');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet');
+}
+document.body.appendChild(link);
+
+// Make sure the load events for all those stylesheets have not fired yet
+is(pendingEventCounter, 7, "There should be one pending event");
+
+</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 000000000..f1265496d
--- /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|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/.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_media_queries.html b/layout/style/test/test_media_queries.html
new file mode 100644
index 000000000..1edac15ae
--- /dev/null
+++ b/layout/style/test/test_media_queries.html
@@ -0,0 +1,845 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=156716
+-->
+<head>
+ <title>Test for Bug 156716</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 SpecialPowers.DOMWindowUtils.screenPixelsPerCSSPixel;
+}
+
+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") == "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");
+ }
+
+ /*
+ * Functions to test whether a single media expression is parseable.
+ */
+ function expression_is_parseable(e) {
+ style.setAttribute("media", "all and (" + e + ")");
+ return style.sheet.media.mediaText != "not all";
+ }
+
+ function expression_should_be_parseable(e) {
+ ok(expression_is_parseable(e),
+ "expression " + e + " should be parseable");
+ test_serialization("all and (" + e + ")", false, false);
+ }
+
+ function expression_should_not_be_parseable(e) {
+ ok(!expression_is_parseable(e),
+ "expression " + e + " should not be parseable");
+ }
+
+ // 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_parseable(min_name);
+ expression_should_not_be_parseable(max_name);
+ }
+
+ function test_serialization(q, test_application, should_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) {
+ var applies = body_cs.getPropertyValue("text-decoration") == "underline";
+ is(applies, should_apply,
+ "Media query '" + q + "' should " + (should_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>";
+ var docurl = "data:text/html," + escape(htmldoc);
+ post_clone_test(docurl, 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].parentNode.removeChild(links[0]);
+
+ var ser3 = clonedsheet.cssRules[0].media.mediaText;
+ is(ser3, ser1, "cloning query '" + q + "' should not change " +
+ "serialization");
+ if (test_application) {
+ var applies = clonewin.getComputedStyle(clonedoc.body, "").
+ textDecoration == "underline";
+ is(applies, should_apply,
+ "Media query '" + q + "' should " + (should_apply ? "" : "NOT ") +
+ "apply after cloning");
+ }
+ });
+ }
+
+ // The no-type syntax doesn't mix with the not and only keywords.
+ query_should_be_parseable("(orientation)");
+ query_should_not_be_parseable("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_be_parseable("(-moz-device-orientation)");
+ query_should_not_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 feature;
+ var i;
+ for (i in features) {
+ feature = features[i];
+ expression_should_be_parseable(feature);
+ expression_should_be_parseable(feature + ": 0");
+ expression_should_be_parseable(feature + ": 0px");
+ expression_should_be_parseable(feature + ": 0em");
+ expression_should_be_parseable(feature + ": -0");
+ expression_should_be_parseable("min-" + feature + ": -0");
+ expression_should_be_parseable("max-" + feature + ": -0");
+ expression_should_be_parseable(feature + ": -0cm");
+ expression_should_be_parseable(feature + ": 1px");
+ expression_should_be_parseable(feature + ": 0.001mm");
+ expression_should_be_parseable(feature + ": 100000px");
+ expression_should_not_be_parseable(feature + ": -1px");
+ expression_should_not_be_parseable("min-" + feature + ": -1px");
+ expression_should_not_be_parseable("max-" + feature + ": -1px");
+ expression_should_not_be_parseable(feature + ": -0.00001mm");
+ expression_should_not_be_parseable(feature + ": -100000em");
+ expression_should_not_be_parseable("min-" + feature);
+ expression_should_not_be_parseable("max-" + feature);
+ }
+
+ var mediatypes = ["browser", "minimal-ui", "standalone", "fullscreen"];
+
+ mediatypes.forEach(function(type) {
+ expression_should_be_parseable("display-mode: " + type);
+ });
+
+ expression_should_not_be_parseable("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_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)");
+ }
+
+ 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_parseable("orientation");
+ expression_should_be_parseable("orientation: portrait");
+ expression_should_be_parseable("orientation: landscape");
+ expression_should_not_be_parseable("min-orientation");
+ expression_should_not_be_parseable("min-orientation: portrait");
+ expression_should_not_be_parseable("min-orientation: landscape");
+ expression_should_not_be_parseable("max-orientation");
+ expression_should_not_be_parseable("max-orientation: portrait");
+ expression_should_not_be_parseable("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_parseable("-moz-device-orientation");
+ expression_should_be_parseable("-moz-device-orientation: portrait");
+ expression_should_be_parseable("-moz-device-orientation: landscape");
+ expression_should_not_be_parseable("min--moz-device-orientation");
+ expression_should_not_be_parseable("min--moz-device-orientation: portrait");
+ expression_should_not_be_parseable("min--moz-device-orientation: landscape");
+ expression_should_not_be_parseable("max--moz-device-orientation");
+ expression_should_not_be_parseable("max--moz-device-orientation: portrait");
+ expression_should_not_be_parseable("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)");
+
+ 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_parseable("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_parseable("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_parseable("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_parseable("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_parseable("-webkit-device-pixel-ratio: 1.0");
+ expression_should_not_be_parseable("device-pixel-ratio: 1.0");
+ expression_should_be_parseable("-webkit-min-device-pixel-ratio: 1.0");
+ expression_should_not_be_parseable("min-device-pixel-ratio: 1.0");
+ expression_should_be_parseable("-webkit-max-device-pixel-ratio: 1.0");
+ expression_should_not_be_parseable("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_parseable(feature + ": 1/1");
+ expression_should_be_parseable(feature + ": 1 /1");
+ expression_should_be_parseable(feature + ": 1 / \t\n1");
+ expression_should_be_parseable(feature + ": 1/\r1");
+ expression_should_not_be_parseable(feature + ": 1");
+ expression_should_not_be_parseable(feature + ": 0.5");
+ expression_should_not_be_parseable(feature + ": 1.0/1");
+ expression_should_not_be_parseable(feature + ": 1/1.0");
+ expression_should_not_be_parseable(feature + ": 1.0/1.0");
+ expression_should_not_be_parseable(feature + ": 0/1");
+ expression_should_not_be_parseable(feature + ": 1/0");
+ expression_should_not_be_parseable(feature + ": 0/0");
+ expression_should_not_be_parseable(feature + ": -1/1");
+ expression_should_not_be_parseable(feature + ": 1/-1");
+ expression_should_not_be_parseable(feature + ": -1/-1");
+ }
+
+ 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_parseable("max-color");
+ expression_should_not_be_parseable("min-color");
+ (is_color ? should_not_apply : should_apply)("all and (monochrome)");
+ expression_should_not_be_parseable("max-monochrome");
+ expression_should_not_be_parseable("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_parseable(feature + ": 1");
+ expression_should_be_parseable(feature + ": 327");
+ expression_should_be_parseable(feature + ": 0");
+ expression_should_not_be_parseable(feature + ": 1.0");
+ expression_should_not_be_parseable(feature + ": -1");
+ expression_should_not_be_parseable(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_parseable(feature + ": 3dpi");
+ expression_should_be_parseable(feature + ":3dpi");
+ expression_should_be_parseable(feature + ": 3.0dpi");
+ expression_should_be_parseable(feature + ": 3.4dpi");
+ expression_should_be_parseable(feature + "\t: 120dpcm");
+ expression_should_be_parseable(feature + ": 1dppx");
+ expression_should_be_parseable(feature + ": 1.5dppx");
+ expression_should_be_parseable(feature + ": 2.0dppx");
+ expression_should_not_be_parseable(feature + ": 0dpi");
+ expression_should_not_be_parseable(feature + ": -3dpi");
+ expression_should_not_be_parseable(feature + ": 0dppx");
+ }
+
+ // 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_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_parseable("scan");
+ expression_should_be_parseable("scan: progressive");
+ expression_should_be_parseable("scan:interlace");
+ expression_should_not_be_parseable("min-scan:interlace");
+ expression_should_not_be_parseable("scan: 1");
+ expression_should_not_be_parseable("max-scan");
+ expression_should_not_be_parseable("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_parseable("grid");
+ expression_should_be_parseable("grid: 0");
+ expression_should_be_parseable("grid: 1");
+ expression_should_be_parseable("grid: 1");
+ expression_should_not_be_parseable("min-grid");
+ expression_should_not_be_parseable("min-grid:0");
+ expression_should_not_be_parseable("max-grid: 1");
+ expression_should_not_be_parseable("grid: 2");
+ expression_should_not_be_parseable("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)");
+
+ // System metrics
+ expression_should_be_parseable("-moz-scrollbar-start-backward");
+ expression_should_be_parseable("-moz-scrollbar-start-forward");
+ expression_should_be_parseable("-moz-scrollbar-end-backward");
+ expression_should_be_parseable("-moz-scrollbar-end-forward");
+ expression_should_be_parseable("-moz-scrollbar-thumb-proportional");
+ expression_should_be_parseable("-moz-overlay-scrollbars");
+ expression_should_be_parseable("-moz-windows-default-theme");
+ expression_should_be_parseable("-moz-mac-graphite-theme");
+ expression_should_be_parseable("-moz-mac-yosemite-theme");
+ expression_should_be_parseable("-moz-windows-compositor");
+ expression_should_be_parseable("-moz-windows-classic");
+ expression_should_be_parseable("-moz-windows-glass");
+ expression_should_be_parseable("-moz-touch-enabled");
+ expression_should_be_parseable("-moz-swipe-animation-enabled");
+
+ expression_should_be_parseable("-moz-scrollbar-start-backward: 0");
+ expression_should_be_parseable("-moz-scrollbar-start-forward: 0");
+ expression_should_be_parseable("-moz-scrollbar-end-backward: 0");
+ expression_should_be_parseable("-moz-scrollbar-end-forward: 0");
+ expression_should_be_parseable("-moz-scrollbar-thumb-proportional: 0");
+ expression_should_be_parseable("-moz-overlay-scrollbars: 0");
+ expression_should_be_parseable("-moz-windows-default-theme: 0");
+ expression_should_be_parseable("-moz-mac-graphite-theme: 0");
+ expression_should_be_parseable("-moz-mac-yosemite-theme: 0");
+ expression_should_be_parseable("-moz-windows-compositor: 0");
+ expression_should_be_parseable("-moz-windows-classic: 0");
+ expression_should_be_parseable("-moz-windows-glass: 0");
+ expression_should_be_parseable("-moz-touch-enabled: 0");
+ expression_should_be_parseable("-moz-swipe-animation-enabled: 0");
+
+ expression_should_be_parseable("-moz-scrollbar-start-backward: 1");
+ expression_should_be_parseable("-moz-scrollbar-start-forward: 1");
+ expression_should_be_parseable("-moz-scrollbar-end-backward: 1");
+ expression_should_be_parseable("-moz-scrollbar-end-forward: 1");
+ expression_should_be_parseable("-moz-scrollbar-thumb-proportional: 1");
+ expression_should_be_parseable("-moz-overlay-scrollbars: 1");
+ expression_should_be_parseable("-moz-windows-default-theme: 1");
+ expression_should_be_parseable("-moz-mac-graphite-theme: 1");
+ expression_should_be_parseable("-moz-mac-yosemite-theme: 1");
+ expression_should_be_parseable("-moz-windows-compositor: 1");
+ expression_should_be_parseable("-moz-windows-classic: 1");
+ expression_should_be_parseable("-moz-windows-glass: 1");
+ expression_should_be_parseable("-moz-touch-enabled: 1");
+ expression_should_be_parseable("-moz-swipe-animation-enabled: 1");
+
+ expression_should_not_be_parseable("-moz-scrollbar-start-backward: -1");
+ expression_should_not_be_parseable("-moz-scrollbar-start-forward: -1");
+ expression_should_not_be_parseable("-moz-scrollbar-end-backward: -1");
+ expression_should_not_be_parseable("-moz-scrollbar-end-forward: -1");
+ expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: -1");
+ expression_should_not_be_parseable("-moz-overlay-scrollbars: -1");
+ expression_should_not_be_parseable("-moz-windows-default-theme: -1");
+ expression_should_not_be_parseable("-moz-mac-graphite-theme: -1");
+ expression_should_not_be_parseable("-moz-mac-yosemite-theme: -1");
+ expression_should_not_be_parseable("-moz-windows-compositor: -1");
+ expression_should_not_be_parseable("-moz-windows-classic: -1");
+ expression_should_not_be_parseable("-moz-windows-glass: -1");
+ expression_should_not_be_parseable("-moz-touch-enabled: -1");
+ expression_should_not_be_parseable("-moz-swipe-animation-enabled: -1");
+
+ expression_should_not_be_parseable("-moz-scrollbar-start-backward: true");
+ expression_should_not_be_parseable("-moz-scrollbar-start-forward: true");
+ expression_should_not_be_parseable("-moz-scrollbar-end-backward: true");
+ expression_should_not_be_parseable("-moz-scrollbar-end-forward: true");
+ expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: true");
+ expression_should_not_be_parseable("-moz-overlay-scrollbars: true");
+ expression_should_not_be_parseable("-moz-windows-default-theme: true");
+ expression_should_not_be_parseable("-moz-mac-graphite-theme: true");
+ expression_should_not_be_parseable("-moz-mac-yosemite-theme: true");
+ expression_should_not_be_parseable("-moz-windows-compositor: true");
+ expression_should_not_be_parseable("-moz-windows-classic: true");
+ expression_should_not_be_parseable("-moz-windows-glass: true");
+ expression_should_not_be_parseable("-moz-touch-enabled: true");
+ expression_should_not_be_parseable("-moz-swipe-animation-enabled: true");
+
+ // windows theme media queries
+ expression_should_be_parseable("-moz-windows-theme: aero");
+ expression_should_be_parseable("-moz-windows-theme: aero-lite");
+ expression_should_be_parseable("-moz-windows-theme: luna-blue");
+ expression_should_be_parseable("-moz-windows-theme: luna-olive");
+ expression_should_be_parseable("-moz-windows-theme: luna-silver");
+ expression_should_be_parseable("-moz-windows-theme: royale");
+ expression_should_be_parseable("-moz-windows-theme: generic");
+ expression_should_be_parseable("-moz-windows-theme: zune");
+ expression_should_be_parseable("-moz-windows-theme: garbage");
+ expression_should_not_be_parseable("-moz-windows-theme: ''");
+ expression_should_not_be_parseable("-moz-windows-theme: ");
+
+ // os version media queries (currently windows only)
+ expression_should_be_parseable("-moz-os-version: windows-xp");
+ expression_should_be_parseable("-moz-os-version: windows-vista");
+ expression_should_be_parseable("-moz-os-version: windows-win7");
+ expression_should_be_parseable("-moz-os-version: windows-win8");
+ expression_should_be_parseable("-moz-os-version: windows-win10");
+ expression_should_not_be_parseable("-moz-os-version: ");
+
+ // OpenType SVG media features
+ query_should_be_parseable("(-moz-is-glyph)");
+ query_should_not_be_parseable("not (-moz-is-glyph)");
+ query_should_not_be_parseable("only (-moz-is-glyph)");
+ query_should_be_parseable("all and (-moz-is-glyph)");
+ query_should_be_parseable("not all and (-moz-is-glyph)");
+ query_should_be_parseable("only all and (-moz-is-glyph)");
+
+ query_should_be_parseable("(-moz-is-glyph:0)");
+ query_should_not_be_parseable("not (-moz-is-glyph:0)");
+ query_should_not_be_parseable("only (-moz-is-glyph:0)");
+ query_should_be_parseable("all and (-moz-is-glyph:0)");
+ query_should_be_parseable("not all and (-moz-is-glyph:0)");
+ query_should_be_parseable("only all and (-moz-is-glyph:0)");
+
+ query_should_be_parseable("(-moz-is-glyph:1)");
+ query_should_not_be_parseable("not (-moz-is-glyph:1)");
+ query_should_not_be_parseable("only (-moz-is-glyph:1)");
+ query_should_be_parseable("all and (-moz-is-glyph:1)");
+ query_should_be_parseable("not all and (-moz-is-glyph:1)");
+ query_should_be_parseable("only all and (-moz-is-glyph:1)");
+
+ query_should_not_be_parseable("(min--moz-is-glyph:0)");
+ query_should_not_be_parseable("(max--moz-is-glyph:0)");
+ query_should_not_be_parseable("(min--moz-is-glyph:1)");
+ query_should_not_be_parseable("(max--moz-is-glyph:1)");
+
+ should_apply("not all and (-moz-is-glyph)");
+ should_apply("(-moz-is-glyph:0)");
+ should_apply("not all and (-moz-is-glyph:1)");
+ should_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)");
+
+ // 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");
+
+ 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(docurl, testfunc)
+{
+ posted_items.push({docurl: docurl, testfunc: 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 docurl = posted_items[0].docurl;
+ iframe.onload = handle_iframe_onload;
+ iframe.src = docurl;
+}
+
+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 000000000..6c9159186
--- /dev/null
+++ b/layout/style/test/test_media_queries_dynamic.html
@@ -0,0 +1,213 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=473400
+-->
+<head>
+ <title>Test for Bug 473400</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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; version=1.7">
+
+/** 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") == "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 elementsRestyled, framesConstructed, framesReflowed;
+ function reset_change_counters()
+ {
+ elementsRestyled = utils.elementsRestyled;
+ 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 restyles = utils.elementsRestyled - elementsRestyled;
+ var constructs = utils.framesConstructed - framesConstructed;
+ var reflows = utils.framesReflowed - framesReflowed;
+
+ (expected.restyle ? isnot : is)(restyles, 0, "restyle count: " + 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",
+ // FIXME: We restyle here because
+ // nsIPresShell::RestyleForCSSRuleChanges posts a restyle, but it's
+ // probably avoidable if we wanted to avoid it.
+ { restyle: true, 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",
+ // FIXME: We restyle here because
+ // nsIPresShell::RestyleForCSSRuleChanges posts a restyle, but it's
+ // probably avoidable if we wanted to avoid it.
+ { restyle: true, construct: false, reflow: false });
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_media_queries_dynamic_xbl.html b/layout/style/test/test_media_queries_dynamic_xbl.html
new file mode 100644
index 000000000..f7bbde18e
--- /dev/null
+++ b/layout/style/test/test_media_queries_dynamic_xbl.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=156716
+-->
+<head>
+ <title>Test for Bug 156716</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=156716">Mozilla Bug 156716</a>
+<iframe id="display" src="media_queries_dynamic_xbl_iframe.html"></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 156716 **/
+
+function run() {
+ var iframe = document.getElementById("display");
+
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+ var p = subdoc.getElementById("para");
+
+ iframe.setAttribute("style", "height: 300px; width: 100px");
+ is(subwin.getComputedStyle(p, "").color, "rgb(128, 0, 128)",
+ "should be purple when portait");
+ iframe.setAttribute("style", "height: 100px; width: 300px");
+ is(subwin.getComputedStyle(p, "").color, "rgb(0, 0, 255)",
+ "should be blue when landscape");
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+
+</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 000000000..b50771bf6
--- /dev/null
+++ b/layout/style/test/test_media_query_list.html
@@ -0,0 +1,367 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=542058
+-->
+<head>
+ <title>Test for MediaQueryList (Bug 542058)</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 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
+
+ function setup_mql(str) {
+ var obj = {
+ str: str,
+ mql: subwin.matchMedia(str),
+ notifyCount: 0,
+ listener: function(mql) {
+ is(mql, obj.mql,
+ "correct argument to listener: " + obj.str);
+ ++obj.notifyCount;
+ // Test the last match result only on odd
+ // notifications.
+ if (obj.notifyCount & 1) {
+ obj.lastOddMatchResult = mql.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
+
+ 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
+
+ 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
+
+ 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);
+
+ 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.
+ (function() {
+ var received = [];
+ var received_mql = [];
+ function listener1(mql) {
+ received.push(1);
+ received_mql.push(mql);
+ }
+ function listener2(mql) {
+ received.push(2);
+ received_mql.push(mql);
+ }
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+
+ var 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
+
+ is(JSON.stringify(received), "[1,2]", "duplicate listeners removed");
+ received = [];
+ mql.removeListener(listener1);
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+
+ is(JSON.stringify(received), "[2]", "listener removal");
+ received = [];
+ mql.addListener(listener1);
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+
+ is(JSON.stringify(received), "[2,1]", "listeners notified in order");
+ received = [];
+ mql.addListener(listener2);
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+
+ 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
+
+ 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
+
+ // 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(mql) {
+ received.push(3);
+ received_mql.push(mql);
+ mql.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
+
+ // mql(1, 3, 2) mql2(1)
+ is(JSON.stringify(received), "[1,3,2,1]",
+ "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)");
+ is(received_mql[2], mql,
+ "notification order (removal tests)");
+ is(received_mql[3], mql2,
+ "notification order (removal tests)");
+ received = [];
+ received_mql = [];
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+
+ // 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 716751: null-dereference crash */
+ (function() {
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+
+ var mql = subwin.matchMedia("(min-width: 150px)");
+ SimpleTest.doesThrow(function() {
+ mql.addListener(null);
+ }, "expected an exception");
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ // With the bug, we crash here. No need for test assertions.
+
+ SimpleTest.doesThrow(function() {
+ mql.removeListener(null);
+ }, "expected an exception");
+ SimpleTest.doesThrow(function() {
+ mql.removeListener(null);
+ }, "expected an exception");
+ })();
+
+ /* Bug 753777: test that things work in a freshly-created iframe */
+ (function() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+
+ is(iframe.contentWindow.matchMedia("(min-width: 1px)").matches, true,
+ "(min-width: 1px) should match in newly-created iframe");
+ is(iframe.contentWindow.matchMedia("(max-width: 1px)").matches, false,
+ "(max-width: 1px) should not match in newly-created iframe");
+
+ document.body.removeChild(iframe);
+ })();
+
+ /* Bug 716751: listeners lost due to GC */
+ var gc_received = [];
+ (function() {
+ var received = [];
+ var listener1 = function(mql) {
+ gc_received.push(1);
+ }
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+
+ var 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
+
+ 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);
+ })();
+
+ function step2() {
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+
+ is(JSON.stringify(gc_received), "[1,1]", "GC test: after notification 2");
+
+ bug1270626();
+ }
+
+ /* Bug 1270626: listeners that throw exceptions */
+ function bug1270626() {
+ var throwingListener = function(mql) {
+ throw "error";
+ }
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+
+ 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
+
+ is(SimpleTest.isExpectingUncaughtException(), false,
+ "should have gotten an uncaught exception");
+
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</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 000000000..d3a8cc27c
--- /dev/null
+++ b/layout/style/test/test_moz_device_pixel_ratio.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=474356
+-->
+<head>
+ <title>Test for Bug 474356</title>
+ <script type="text/javascript" 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), null).visibility == "visible";
+ }
+
+ function getScreenPixelsPerCSSPixel() {
+ return SpecialPowers.DOMWindowUtils.screenPixelsPerCSSPixel;
+ }
+
+ var screenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel();
+ 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_namespace_rule.html b/layout/style/test/test_namespace_rule.html
new file mode 100644
index 000000000..2cf4c4fc5
--- /dev/null
+++ b/layout/style/test/test_namespace_rule.html
@@ -0,0 +1,462 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS Namespace rules</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<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";
+var style_text;
+
+function run() {
+ var iframe = $("iframe");
+ var ifwin = iframe.contentWindow;
+ var ifdoc = iframe.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);
+ var style_text = ifdoc.createCDATASection("");
+ style_elem.appendChild(style_text);
+ return style_text;
+ }
+
+ 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_of_type_selectors.xhtml b/layout/style/test/test_of_type_selectors.xhtml
new file mode 100644
index 000000000..7ab286bde
--- /dev/null
+++ b/layout/style/test/test_of_type_selectors.xhtml
@@ -0,0 +1,98 @@
+<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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 (var 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 + " }";
+ var 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_page_parser.html b/layout/style/test/test_page_parser.html
new file mode 100644
index 000000000..8c94be0bf
--- /dev/null
+++ b/layout/style/test/test_page_parser.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=115199 -->
+<head>
+ <meta charset="UTF-8">
+ <title>Test of @page parser</title>
+ <script type="text/javascript" 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++) {
+ 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
+ && !('expected' in testset[curTest]),
+ testset[curTest].rule + " syntax error thrown", e);
+ }
+
+ try {
+ if (testset[curTest].expected) {
+ is(sheet.cssRules.length, 1,
+ testset[curTest].rule + " rule count");
+ is(sheet.cssRules[0].type, CSSRule.PAGE_RULE,
+ testset[curTest].rule + " rule type");
+
+ 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 {
+ 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-page)");
+ isnot(sheet.cssRules[0].type, CSSRule.PAGE_RULE,
+ testset[curTest].rule + " rule type (1 non-page)");
+ }
+ }
+ } 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 000000000..74737fd5e
--- /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 000000000..e083aad6c
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">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 000000000..ebaf5aa5d
--- /dev/null
+++ b/layout/style/test/test_parse_rule.html
@@ -0,0 +1,256 @@
+<!DOCTYPE html>
+<html lang=en>
+<script type="text/javascript" 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();
+
+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 = "";
+ var base = 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(base, 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();
+};
+
+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 000000000..aa167398f
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..384d4dfa6
--- /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;version=1.8">
+// 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.
+
+// 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.
+const 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{ '<t>'}" , o: "declaration but found \u2018'<s>'\u2019." },
+ // _Bad_String
+ { i: "x{ '<t>\n}", o: "declaration but found \u2018'<s>\u2019." },
+ // _URL
+ { i: "x{ url('<t>')}", o: "declaration but found \u2018url('<s>')\u2019." },
+ // _Bad_URL
+ { i: "x{ url('<t>'.)}" , o: "declaration but found \u2018url('<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 \\80 \\81 \\82 ",
+ s: "\\7f \\80 \\81 \\82 " },
+ { t: "\\\x83\\\x84\\\x85\\\x86", i: "\\83 \\84 \\85 \\86 ",
+ s: "\\83 \\84 \\85 \\86 " },
+ { t: "\\\x87\\\x88\\\x89\\\x8A", i: "\\87 \\88 \\89 \\8a ",
+ s: "\\87 \\88 \\89 \\8a " },
+ { t: "\\\x8B\\\x8C\\\x8D\\\x8E", i: "\\8b \\8c \\8d \\8e ",
+ s: "\\8b \\8c \\8d \\8e " },
+ { t: "\\\x8F\\\x90\\\x91\\\x92", i: "\\8f \\90 \\91 \\92 ",
+ s: "\\8f \\90 \\91 \\92 " },
+ { t: "\\\x93\\\x94\\\x95\\\x96", i: "\\93 \\94 \\95 \\96 ",
+ s: "\\93 \\94 \\95 \\96 " },
+ { t: "\\\x97\\\x98\\\x99\\\x9A", i: "\\97 \\98 \\99 \\9a ",
+ s: "\\97 \\98 \\99 \\9a " },
+ { t: "\\\x9B\\\x9C\\\x9D\\\x9E\\\x9F", i: "\\9b \\9c \\9d \\9e \\9f ",
+ s: "\\9b \\9c \\9d \\9e \\9f " },
+
+ // CSS doesn't bother with the full Unicode rules for identifiers,
+ // instead declaring that any code point greater than or equal to
+ // U+00A0 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]));
+
+ 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 000000000..37f9ec83f
--- /dev/null
+++ b/layout/style/test/test_pixel_lengths.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that pixel lengths don't change based on DPI</title>
+ <script type="text/javascript" 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 id="mozmm" style="width:25.4mozmm; height:25.4mozmm; background:cyan;">mozmm</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);
+
+var mozmmSize;
+function test1() {
+ var mozmm = document.getElementById("mozmm");
+ mozmmSize = mozmm.getBoundingClientRect().width;
+ is(Math.round(mozmmSize), Math.round(mozmm.getBoundingClientRect().height),
+ "mozmm div should be square");
+
+ checkPixelRelativeUnits();
+
+ SpecialPowers.pushPrefEnv({'set': [['layout.css.dpi', dpi=192]]}, test2);
+}
+
+function test2() {
+ // At 192 dpi, a one-inch box should be twice the number of device pixels,
+ // and since we haven't changed the device-pixels-per-CSS-pixel ratio, the
+ // mozmm box should be twice the size in CSS pixels.
+ check("mozmm", mozmmSize*2);
+ checkPixelRelativeUnits();
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</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 000000000..73db14351
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style 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;version=1.8">
+
+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, false);
+ 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 000000000..03d3eb26b
--- /dev/null
+++ b/layout/style/test/test_position_float_display.html
@@ -0,0 +1,107 @@
+<!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 type="application/javascript" 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",
+ "ruby": "block",
+ "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",
+ // Note: this is sometimes block
+ "list-item": "list-item"
+};
+
+function test_display_value(val)
+{
+ var floatLeftElem = document.getElementById("float-left");
+ floatLeftElem.style.display = val;
+ var floatLeftConversion = window.getComputedStyle(floatLeftElem, null).display;
+ floatLeftElem.style.display = "";
+
+ var floatRightElem = document.getElementById("float-right");
+ floatRightElem.style.display = val;
+ var floatRightConversion = window.getComputedStyle(floatRightElem, null).display;
+ floatRightElem.style.display = "";
+
+ var posAbsoluteElem = document.getElementById("position-absolute");
+ posAbsoluteElem.style.display = val;
+ var posAbsoluteConversion = window.getComputedStyle(posAbsoluteElem, null).display;
+ posAbsoluteElem.style.display = "";
+
+ var posFixedElem = document.getElementById("position-fixed");
+ posFixedElem.style.display = val;
+ var posFixedConversion = window.getComputedStyle(posFixedElem, null).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 000000000..9deb92333
--- /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 type="application/javascript" 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_priority_preservation.html b/layout/style/test/test_priority_preservation.html
new file mode 100644
index 000000000..080a4651c
--- /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 type="application/javascript" 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 000000000..30e6618fc
--- /dev/null
+++ b/layout/style/test/test_property_database.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test that property_database.js contains all supported CSS properties</title>
+ <script type="text/javascript" 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_SHORTHAND_AND_LONGHAND,
+ "'" + prop.name + "' listed as CSS_TYPE_TRUE_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 are listed as logical properties.
+ */
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ if (entry.logical) {
+ is(entry.type, CSS_TYPE_LONGHAND,
+ "property '" + prop + "' is listed as CSS_TYPE_LONGHAND due to its " +
+ "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 000000000..be1127def
--- /dev/null
+++ b/layout/style/test/test_property_syntax_errors.html
@@ -0,0 +1,153 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test that we reject syntax errors listed in property_database.js</title>
+ <script type="text/javascript" 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.requestLongerTimeout(2);
+SimpleTest.waitForExplicitFinish();
+
+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 gQuirksDeclaration = document.getElementById("quirks").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_pseudoelement_parsing.html b/layout/style/test/test_pseudoelement_parsing.html
new file mode 100644
index 000000000..b6fcf783f
--- /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 000000000..ad4bf5242
--- /dev/null
+++ b/layout/style/test/test_pseudoelement_state.html
@@ -0,0 +1,164 @@
+<!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 src="data:text/html,<!DOCTYPE html><style></style><div></div>"></iframe>
+
+<script>
+var gIsAndroid = navigator.appVersion.indexOf("Android") != -1;
+
+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; }' },
+
+ // ::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", runTests);
+</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 000000000..8349d692b
--- /dev/null
+++ b/layout/style/test/test_redundant_font_download.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=879963 -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for bug 879963</title>
+ <script type="application/javascript" 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
+ 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 'delay' ms until it has seen a specific request,
+ // or until maxTime ms have passed
+ function waitForRequest(request, delay, maxTime, func) {
+ timeLimit = Date.now() + maxTime;
+ var intervalId = window.setInterval(function() {
+ var req = getLastRequest();
+ if (req == request || Date.now() > timeLimit) {
+ window.clearInterval(intervalId);
+ func();
+ }
+ }.bind(this), delay);
+ }
+
+ // 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", 10, 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", 10, 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", 10, 1000, continueTest3);
+ }
+
+ function continueTest3() {
+ is(getRequestLog(), "init;font;image", "should NOT have re-requested the font");
+
+ SimpleTest.finish();
+ }
+
+ waitForRequest("init", 10, 5000, startTest);
+
+ </script>
+
+</body>
+
+</html>
diff --git a/layout/style/test/test_rem_unit.html b/layout/style/test/test_rem_unit.html
new file mode 100644
index 000000000..8d18f9d50
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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_restyles_in_smil_animation.html b/layout/style/test/test_restyles_in_smil_animation.html
new file mode 100644
index 000000000..8e18da053
--- /dev/null
+++ b/layout/style/test/test_restyles_in_smil_animation.html
@@ -0,0 +1,113 @@
+<!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/SpawnTask.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";
+
+function waitForAnimationFrames(frameCount) {
+ return new Promise(function(resolve, reject) {
+ function handleFrame() {
+ if (--frameCount <= 0) {
+ resolve();
+ } else {
+ window.requestAnimationFrame(handleFrame); // wait another frame
+ }
+ }
+ window.requestAnimationFrame(handleFrame);
+ });
+}
+
+function observeStyling(frameCount) {
+ var Ci = SpecialPowers.Ci;
+ var docShell =
+ SpecialPowers.wrap(window).QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+
+ docShell.recordProfileTimelineMarkers = true;
+ docShell.popProfileTimelineMarkers();
+
+ return new Promise(function(resolve) {
+ return waitForAnimationFrames(frameCount).then(function() {
+ var markers = docShell.popProfileTimelineMarkers();
+ docShell.recordProfileTimelineMarkers = false;
+ var stylingMarkers = markers.filter(function(marker, index) {
+ return marker.restyleHint == "eRestyle_SVGAttrAnimations";
+ });
+ resolve(stylingMarkers);
+ });
+ });
+}
+
+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(function* smil_is_in_display_none_subtree() {
+ yield 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);
+
+ yield waitForPaintFlushed();
+
+ var displayMarkers = yield observeStyling(5);
+ // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
+ // element is newly associated with an nsIFrame.
+ ok(displayMarkers.length == 5 || displayMarkers.length == 4,
+ "should restyle in most frames");
+
+ var div = document.getElementById("target-div");
+
+ div.style.display = "none";
+ getComputedStyle(div).display;
+ yield waitForPaintFlushed();
+
+ var displayNoneMarkers = yield observeStyling(5);
+ is(displayNoneMarkers.length, 0, "should never restyle if display:none");
+
+ div.style.display = "";
+ getComputedStyle(div).display;
+ yield waitForPaintFlushed();
+
+ var displayAgainMarkers = yield observeStyling(5);
+ // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
+ // element is newly associated with an nsIFrame.
+ ok(displayMarkers.length == 5 || displayMarkers.length == 4,
+ "should restyle again");
+
+ yield ensureElementRemoval(animate);
+});
+</script>
+</body>
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 000000000..547fb0e48
--- /dev/null
+++ b/layout/style/test/test_root_node_display.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=969460
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 969460</title>
+ <script type="application/javascript" 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, null).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, null).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 'display:list-item' 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 000000000..a55ec08c5
--- /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 type="application/javascript" 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"],
+ ["@-moz-document", "url-prefix('')", "url-prefix('zzz')"],
+ ["@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, "").textDecoration ==
+ (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);
+
+ // 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_rule_serialization.html b/layout/style/test/test_rule_serialization.html
new file mode 100644
index 000000000..eba9a3e6f
--- /dev/null
+++ b/layout/style/test/test_rule_serialization.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css" 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}/") {}' },
+];
+
+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");
+}
+
+</script>
+</pre>
+</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 000000000..ab692239a
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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(rule) {
+ return keep_rule_alive_by_matching(
+ get_only_child(rule));
+ });
+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 000000000..7294a879f
--- /dev/null
+++ b/layout/style/test/test_selectors.html
@@ -0,0 +1,1297 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS Selectors</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<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() {
+
+ 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");
+ }
+ 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 (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 ser1 = style_text.parentNode.sheet.cssRules[idx].selectorText;
+ style_text.data = namespaces + 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 = 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 clonedoc = cloneiframe.contentDocument;
+ var clonewin = cloneiframe.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].parentNode.removeChild(links[0]);
+
+ var should_match = match_fn(clonedoc);
+ var should_not_match = notmatch_fn(clonedoc);
+
+ 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(clonewin.getComputedStyle(e, "").zIndex, String(zi),
+ "element in " + body_contents + " matched clone of " +
+ selector);
+ }
+ for (var i = 0; i < should_not_match.length; ++i) {
+ var e = should_not_match[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 clonedoc = cloneiframe.contentDocument;
+ var clonewin = cloneiframe.contentWindow;
+ var links = clonedoc.getElementsByTagName("link");
+ // cause a clone
+ links[1].sheet.insertRule("#nonexistent { color: purple}", 0);
+ // remove the uncloned sheet
+ links[0].parentNode.removeChild(links[0]);
+
+ 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");
+
+ // [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]");
+
+
+ // 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_parseable(":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_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_parseable(":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_parseable(":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_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_parseable(":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_parseable(":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_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_parseable(":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_parseable(":nth-child( -/**/2/**/n/**/+/**/4 )");
+ test_balanced_unparseable(":nth-child( -/**/ 2/**/n/**/+/**/4 )");
+ test_balanced_unparseable(":nth-child( -/**/2 /**/n/**/+/**/4 )");
+ test_balanced_unparseable(":nth-child( -/**/2/**/ n/**/+/**/4 )");
+ test_parseable(":nth-child( -/**/2/**/n /**/+/**/4 )");
+ test_parseable(":nth-child( -/**/2/**/n/**/ +/**/4 )");
+ test_parseable(":nth-child(+1/**/n-1)");
+ test_parseable(":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 non CSS2 pseudo-element.
+ should_serialize_to("input::-moz-placeholder", "input::-moz-placeholder");
+
+ // 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_parseable(":-moz-locale-dir(ltr)");
+ test_parseable(":-moz-locale-dir(rtl)");
+ test_parseable(":-moz-locale-dir(rTl)");
+ test_parseable(":-moz-locale-dir(LTR)");
+ test_parseable(":-moz-locale-dir(other)");
+ if (document.body.matches(":-moz-locale-dir(ltr)")) {
+ test_selector_in_html("a:-moz-locale-dir(LTr)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:-moz-locale-dir(ltR)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:-moz-locale-dir(LTR)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:-moz-locale-dir(RTl)", single_a,
+ empty_set, set_single);
+ } else {
+ test_selector_in_html("a:-moz-locale-dir(RTl)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:-moz-locale-dir(rtL)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:-moz-locale-dir(RTL)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:-moz-locale-dir(LTr)", single_a,
+ empty_set, set_single);
+ }
+ test_selector_in_html("a:-moz-locale-dir(other)", single_a,
+ empty_set, set_single);
+
+ 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 -moz-lwtheme and -moz-lwtheme-[darktext|brighttext]
+ test_parseable(":-moz-lwtheme");
+ test_parseable(":-moz-lwtheme-brighttext");
+ test_parseable(":-moz-lwtheme-darktext");
+
+ test_parseable(":-moz-tree-row(selected)");
+ test_parseable("::-moz-tree-row(selected)");
+ test_parseable(":-moz-tree-row(selected focus)");
+ test_parseable(":-moz-tree-row(selected , focus)");
+ test_parseable("::-moz-tree-row(selected ,focus)");
+ test_parseable(":-moz-tree-row(selected, focus)");
+ test_parseable("::-moz-tree-row(selected,focus)");
+ test_parseable(":-moz-tree-row(selected focus)");
+ test_parseable("::-moz-tree-row(selected , focus)");
+ test_parseable("::-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-user-disabled");
+ test_unbalanced_unparseable(":-moz-suppressed");
+ 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-native-anonymous");
+
+ // 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(()))");
+
+ // :-moz-any()
+ test_balanced_unparseable(":-moz-any()");
+ test_balanced_unparseable(":-moz-any(div p)");
+ test_balanced_unparseable(":-moz-any(div ~ p)");
+ test_balanced_unparseable(":-moz-any(div~p)");
+ test_balanced_unparseable(":-moz-any(div + p)");
+ test_balanced_unparseable(":-moz-any(div+p)");
+ test_balanced_unparseable(":-moz-any(div > p)");
+ test_balanced_unparseable(":-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_balanced_unparseable(":-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_selector_in_html(":-moz-table-border-nonzero",
+ "<p></p>" +
+ "<p border='2'></p>" +
+ "<table border='2'></table>" +
+ "<table border></table>" +
+ "<table></table>" +
+ "<table frame='border'></table>" +
+ "<table border='0'></table>" +
+ "<table border='0pt'></table>" +
+ "<table border='3pt'></table>",
+ bodychildset([2, 3, 8]),
+ bodychildset([0, 1, 4, 5, 6, 7]));
+
+ // 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_balanced_unparseable("::-moz-color-swatch:not(.foo)");
+ test_balanced_unparseable("::-moz-color-swatch:first-child");
+ test_balanced_unparseable("::-moz-color-swatch:hover#foo");
+ test_balanced_unparseable(".foo::after:not(.bar) ~ h3");
+
+ 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_selectors_on_anonymous_content.html b/layout/style/test/test_selectors_on_anonymous_content.html
new file mode 100644
index 000000000..a4975f6b8
--- /dev/null
+++ b/layout/style/test/test_selectors_on_anonymous_content.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS Selectors</title>
+ <!--
+ Separate from test_selectors.html so we don't need to deal with
+ waiting for the binding document to load.
+ -->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ #display { -moz-binding: url(xbl_bindings.xml#onedivchild); }
+
+ </style>
+</head>
+<body onload="run()">
+<div id="display"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+
+ function setup_style() {
+ var style_elem = document.createElement("style");
+ style_elem.setAttribute("type", "text/css");
+ document.getElementsByTagName("head")[0].appendChild(style_elem);
+ var style_text = document.createTextNode("");
+ style_elem.appendChild(style_text);
+ return style_text;
+ }
+
+ var style_text = setup_style();
+
+ var gCounter = 0;
+
+ function test_selector(selector, matches_docdiv, matches_anondiv)
+ {
+ var zi = ++gCounter;
+ style_text.data = selector + "{ z-index: " + zi + " }";
+
+ var doc_div = document.getElementById("display");
+ var anon_div = SpecialPowers.wrap(document).getAnonymousNodes(doc_div)[0];
+ var should_match = [];
+ var should_not_match = [];
+ (matches_docdiv ? should_match : should_not_match).push(doc_div);
+ (matches_anondiv ? should_match : should_not_match).push(anon_div);
+
+ for (var i = 0; i < should_match.length; ++i) {
+ var e = should_match[i];
+ is(SpecialPowers.wrap(window).getComputedStyle(e, "").zIndex, String(zi),
+ "element matched " + selector);
+ }
+ for (var i = 0; i < should_not_match.length; ++i) {
+ var e = should_not_match[i];
+ is(SpecialPowers.wrap(window).getComputedStyle(e, "").zIndex, "auto",
+ "element did not match " + selector);
+ }
+
+ style_text.data = "";
+ }
+
+ // Test that the root of an XBL1 anonymous content subtree doesn't
+ // match :nth-child().
+ test_selector("div.anondiv", false, true);
+ test_selector("div:nth-child(odd)", true, false);
+ test_selector("div:nth-child(even)", false, false);
+
+ SimpleTest.finish();
+}
+
+</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 000000000..74de52cbe
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 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_shorthand_property_getters.html b/layout/style/test/test_shorthand_property_getters.html
new file mode 100644
index 000000000..da4d8de3a
--- /dev/null
+++ b/layout/style/test/test_shorthand_property_getters.html
@@ -0,0 +1,268 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=376075
+-->
+<head>
+ <title>Test for Bug 376075</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 = ";-moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none; -moz-border-left-colors: none; 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");
+ok(e.style.border == "medium solid" ||
+ e.style.border == "solid medium",
+ "implied default color omitted serializing border");
+ok(e.style.borderLeft == "medium solid" ||
+ e.style.borderLeft == "solid medium",
+ "implied default color omitted serializing border-left");
+ok(e.style.cssText == "border: medium solid;" ||
+ e.style.cssText == "border: solid medium;",
+ "implied default color omitted serializing declaration");
+e.setAttribute("style", "border-right: medium solid");
+ok(e.style.borderRight == "medium solid" ||
+ e.style.borderRight == "solid medium",
+ "implied default color omitted serializing border-right");
+ok(e.style.borderRight == "medium solid" ||
+ e.style.borderRight == "solid medium",
+ "implied default color omitted serializing border-right");
+ok(e.style.cssText == "border-right: medium solid;" ||
+ e.style.cssText == "border-right: solid medium;",
+ "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", "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;");
+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;");
+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;");
+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;");
+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;");
+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;");
+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;");
+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;");
+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;");
+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;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+
+// Check that the 'border' shorthand resets 'border-image' and
+// '-moz-border-*-colors', 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 5 / 5 5 5 5 / 5 5 5 5 repeat repeat; border-left: medium solid green;',
+ "border-left does NOT reset border-image");
+e.setAttribute("style", 'border-image: url("foo.png") 5 5 5 5; border: medium solid green');
+is(e.style.cssText,
+ 'border: medium solid green;',
+ "border DOES reset border-image");
+e.setAttribute("style", '-moz-border-left-colors: fuchsia blue; border-left: medium solid green');
+is(e.style.cssText,
+ '-moz-border-left-colors: fuchsia blue; border-left: medium solid green;',
+ "border-left does NOT reset -moz-border-left-colors");
+e.setAttribute("style", '-moz-border-left-colors: fuchsia blue; border: medium solid green');
+is(e.style.cssText,
+ 'border: medium solid green;',
+ "border DOES reset -moz-border-left-colors");
+
+// Test that the color goes at the beginning of the last item of the
+// background shorthand.
+// FIXME: Really the "repeat scroll 0% 0%" shouldn't be there.
+e.setAttribute("style", "background: url(foo.png) blue");
+is(e.style.cssText,
+ "background: blue url(\"foo.png\") repeat scroll 0% 0%;",
+ "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\") repeat scroll 0% 0%, blue url(\"foo.png\") repeat scroll 0% 0%;",
+ "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 000000000..edf84fe8d
--- /dev/null
+++ b/layout/style/test/test_specified_value_serialization.html
@@ -0,0 +1,105 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test for miscellaneous specified value issues</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">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.MozTransform = arr[0];
+ is(document.documentElement.style.MozTransform, arr[1],
+ "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)",
+ "expected case canonicalization of transform functions");
+
+ elt.setAttribute("style",
+ "font-variant-alternates: SWASH(fOo) stYLIStiC(Bar)");
+ is(elt.style.getPropertyValue("font-variant-alternates"),
+ "swash(fOo) stylistic(Bar)",
+ "expected case canonicalization of transform 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)"],
+ // css-color-4
+ ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgba(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgb(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsla(0deg 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsl(0 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ];
+
+ 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]);
+ }
+
+ p.remove();
+})();
+
+</script>
+</pre>
+</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 000000000..d0941042f
--- /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 type="application/javascript" 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 000000000..d49e7ecd9
--- /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 type="application/javascript" 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 000000000..b020b0844
--- /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 type="text/javascript" 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_supports_rules.html b/layout/style/test/test_supports_rules.html
new file mode 100644
index 000000000..834671375
--- /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 type="application/javascript" 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, expected_match) {
+ style.textContent = "#display1, #display2 { text-decoration: overline }\n" +
+ "@supports " + condition + "{\n" +
+ " #display1 { text-decoration: line-through }\n" +
+ "}\n" +
+ "#display2 { text-decoration: underline }\n";
+ is(cs1.textDecoration,
+ expected_match ? "line-through" : "overline",
+ "@supports condition \"" + condition + "\" should " +
+ (expected_match ? "" : "NOT ") + "match");
+ is(cs2.textDecoration, "underline",
+ "@supports condition \"" + condition + "\" 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)", false);
+ 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)", false);
+ check_balanced_condition("(color: green)and (color: blue)", false);
+ 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)", false);
+ check_balanced_condition("(color: green)or (color: blue)", false);
+
+ 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 000000000..65b016836
--- /dev/null
+++ b/layout/style/test/test_system_font_serialization.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=475214
+-->
+<head>
+ <title>Test for Bug 475214</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=475214">Mozilla Bug 475214</a>
+<p id="display"></p>
+<div id="content">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** 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.cssText, "font: menu; font-family: inherit;", "serialize system font and font-family");
+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.cssText, "font: caption; line-height: 3;", "serialize system font and font-family");
+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: menu; font-weight: -moz-use-system-font ! important");
+is(e.style.cssText, "font: menu; font-weight: -moz-use-system-font ! important;", "serialize system font and subproperty that is important");
+is(e.style.font, "", "font getter returns nothing");
+
+e.setAttribute("style", "font: inherit; font-family: Helvetica;");
+
+var cssTextStr = "font-style: inherit; font-weight: inherit; font-size: inherit; line-height: inherit; font-size-adjust: inherit; font-stretch: inherit; font-feature-settings: inherit; font-language-override: inherit; font-kerning: inherit; font-synthesis: inherit; font-variant: inherit;";
+
+is(e.style.cssText, cssTextStr + " font-family: Helvetica;", "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 000000000..aff21e1d8
--- /dev/null
+++ b/layout/style/test/test_text_decoration_shorthands.html
@@ -0,0 +1,93 @@
+<!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",
+};
+
+// 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: "#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",
+ },
+];
+
+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"]);
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions.html b/layout/style/test/test_transitions.html
new file mode 100644
index 000000000..de7943a49
--- /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 type="application/javascript" 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() {
+ var 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 (var tf in timingFunctions) {
+ var p = document.createElement("p");
+ var 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 (var itime = 2; itime < 8; itime += 2) {
+ var p = document.createElement("p");
+ var 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) {
+ var 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 (var d = -4; d <= 4; ++d) {
+ var p = document.createElement("p");
+ var delay = d + "s";
+ var 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 (var d = -4; d <= 4; ++d) {
+ var p = document.createElement("p");
+ var delay = d + "s";
+ var 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)
+{
+ var p = document.createElement("p");
+ var 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 (var i in descendant_tests) {
+ var 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 (var i in number_tests) {
+ var test = number_tests[i];
+ var p = document.createElement("p");
+ p.setAttribute("style", test.style);
+ var 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)
+{
+ var 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)
+{
+ var 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, "").color;
+
+// 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 (var 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 (var test in tftests) {
+ var p = tftests[test][0];
+ p.style.textIndent = "100px";
+}
+for (var test in interrupt_tests) {
+ var p = interrupt_tests[test][0];
+ p.style.textIndent = "100px";
+}
+for (var d in delay_tests) {
+ var p = delay_tests[d];
+ p.style.marginLeft = "100px";
+}
+for (var d in delay_zero_tests) {
+ var p = delay_zero_tests[d];
+ p.style.marginLeft = "100px";
+}
+reset_test.style.marginLeft = "100px";
+reset_test_reference.style.marginLeft = "100px";
+for (var i in descendant_tests) {
+ var test = descendant_tests[i];
+ test.parentNode.style.textIndent = "150px";
+ test.parentNode.style.letterSpacing = "5px";
+}
+for (var i in number_tests) {
+ var 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 (var i in pseudo_element_tests) {
+ var test = pseudo_element_tests[i];
+ test.element.classList.add("started");
+}
+lateref.style.textIndent = "1000px";
+
+// flush style changes
+x = getComputedStyle(div, "").color;
+
+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 (var 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 (var 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)
+{
+ var tf = timingFunctions["ease-out"];
+ for (var d in delay_tests) {
+ var 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 (var i = 1; i <= 8; ++i) {
+ add_future_call(i, check_delay_test);
+}
+
+function check_delay_zero_test(time)
+{
+ for (var d in delay_zero_tests) {
+ var 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 (var 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 (var 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 ];
+ var tf = timingFunctions["ease"];
+
+ var time = px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC;
+
+ for (var i in descendant_tests) {
+ var 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()
+{
+ var tf = timingFunctions["ease"];
+ for (var d in number_tests) {
+ var test = number_tests[d];
+ var 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 (var i in display_tests) {
+ var 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)
+{
+ var tf = timingFunctions["ease-in-out"];
+ for (var i in pseudo_element_tests) {
+ var 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 000000000..b4213dbdd
--- /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 type="application/javascript" 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 000000000..68085a712
--- /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 type="application/javascript" 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 000000000..265339af4
--- /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 type="application/javascript" 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, false);
+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_bug537151.html b/layout/style/test/test_transitions_bug537151.html
new file mode 100644
index 000000000..a3640e393
--- /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 type="application/javascript" 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, false);
+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 000000000..4fca67ada
--- /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 type="text/javascript" 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.slice(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();
+ }
+ }, false);
+ });
+
+ cases.forEach(aCase => aCase.className = 'another' );
+
+ window.setTimeout(() => cases[0].className = '', 500);
+ window.setTimeout(() => cases[1].className = cases[2].className = '', 250);
+
+}, false);
+
+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 000000000..f0421eeb4
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..c84b8e6a8
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..b74c50a49
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 = "font";
+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, false);
+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 000000000..6df8ab30c
--- /dev/null
+++ b/layout/style/test/test_transitions_events.html
@@ -0,0 +1,282 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=531585
+-->
+<head>
+ <title>Test for Bug 531585 (transitionend event)</title>
+ <script type="application/javascript" 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 */
+ -moz-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_stops = false;
+var got_before = false;
+var got_after = false;
+
+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 (!did_stops &&
+ (event.target == $("five") || event.target == $("six"))) {
+ todo(false,
+ "timeout to stop transitions firing later than it should be");
+ return;
+ }
+ ok(false,
+ "unexpected event on " + event.target.nodeName +
+ " element with id '" + event.target.id + "' " +
+ "elapsedTime=" + event.elapsedTime +
+ " propertyName='" + event.propertyName + "'");
+ }
+ }, false);
+
+$("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();
+ }, false);
+
+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();
+ }, false);
+
+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");
+
+setTimeout(function() {
+ if (cs("five") != "rgb(0, 255, 0)" &&
+ cs("six") != "rgb(0, 255, 0)") {
+ // The transition hasn't finished already.
+ did_stops = true;
+ }
+ $("five").style.transitionProperty = "margin-left";
+ $("six").style.transitionDuration = "0s";
+ $("six").style.transitionDelay = "0s";
+ $("six").style.color = "blue";
+ }, 100);
+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 000000000..29e2ae24c
--- /dev/null
+++ b/layout/style/test/test_transitions_per_property.html
@@ -0,0 +1,2565 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script type="application/javascript" 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 id="transformTest" style="height:100px; width:200px; background-color:blue;">
+</div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** 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 = {
+ "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 ],
+ "-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-gap": [ test_length_transition,
+ test_length_clamped ],
+ "column-rule-color": [ test_color_transition,
+ test_true_currentcolor_transition ],
+ "column-rule-width": [ test_length_transition,
+ test_length_clamped ],
+ "column-width": [ test_length_transition,
+ test_length_clamped ],
+ "-moz-image-region": [ test_rect_transition ],
+ "-moz-outline-radius-bottomleft": [ test_radius_transition ],
+ "-moz-outline-radius-bottomright": [ test_radius_transition ],
+ "-moz-outline-radius-topleft": [ test_radius_transition ],
+ "-moz-outline-radius-topright": [ test_radius_transition ],
+ "background-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "background-position": [ test_background_position_transition,
+ // FIXME: We don't currently test clamping,
+ // since background-position uses calc() as
+ // an intermediate form.
+ /* 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,
+ // FIXME: We don't currently test clamping,
+ // since background-size uses calc() as an
+ // intermediate form.
+ /* test_length_percent_pair_clamped */ ],
+ "border-bottom-color": [ test_color_transition,
+ test_true_currentcolor_transition ],
+ "border-bottom-width": [ test_length_transition,
+ test_length_clamped ],
+ "border-left-color": [ test_color_transition,
+ test_true_currentcolor_transition ],
+ "border-left-width": [ test_length_transition,
+ test_length_clamped ],
+ "border-right-color": [ test_color_transition,
+ test_true_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_true_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 ],
+ "clip": [ test_rect_transition ],
+ "clip-path": [ test_clip_path_transition ],
+ "color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "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 ],
+ "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_font_stretch ],
+ "font-weight": [ test_font_weight ],
+ "grid-column-gap": [ test_grid_gap ],
+ "grid-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 ],
+ "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 ],
+ "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_true_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 ],
+ "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 ],
+ // NOTE: when calc() is supported on 'stroke-dashoffset', we should
+ // add test_length_percent_calc_transition.
+ "stroke-dashoffset": [ test_length_transition_svg, test_percent_transition,
+ test_length_unclamped_svg, test_percent_unclamped ],
+ "stroke-miterlimit": [ test_float_aboveOne_transition,
+ test_float_aboveOne_clamped ],
+ "stroke-opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ // NOTE: when calc() is supported on 'stroke-width', we should add
+ // test_length_percent_calc_transition.
+ "stroke-width": [ test_length_transition_svg, test_percent_transition,
+ test_length_clamped_svg, test_percent_clamped ],
+ "text-decoration": [ test_color_shorthand_transition,
+ test_true_currentcolor_shorthand_transition ],
+ "text-decoration-color": [ test_color_transition,
+ test_true_currentcolor_transition ],
+ "text-emphasis-color": [ test_color_transition,
+ test_true_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 ],
+ "z-index": [ test_integer_transition, test_pos_integer_or_auto_transition ],
+ "-webkit-text-fill-color": [ test_color_transition,
+ test_true_currentcolor_transition ],
+ "-webkit-text-stroke-color": [ test_color_transition,
+ test_true_currentcolor_transition ]
+};
+
+if (SupportsMaskShorthand()) {
+ supported_properties["mask-position"] = [ test_background_position_transition,
+ // FIXME: We don't currently test clamping,
+ // since mask-position uses calc() as
+ // an intermediate form.
+ /* test_length_percent_pair_unclamped */ ];
+ supported_properties["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 */ ];
+ supported_properties["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 */ ];
+ supported_properties["mask-size"] = [ test_background_size_transition,
+ // FIXME: We don't currently test clamping,
+ // since mask-size uses calc() as an
+ // intermediate form.
+ /* test_length_percent_pair_clamped */ ];
+}
+
+var div = document.getElementById("display");
+var OMTAdiv = document.getElementById("transformTest");
+var cs = getComputedStyle(div, "");
+var OMTACs = getComputedStyle(OMTAdiv, "");
+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: 'rotate(45deg) rotate(-45deg)',
+ end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
+ expected: 'matrix(1, 0, 0, 1, -2.5, 15)',
+ round_error_ok: true },
+ { start: 'rotate(45deg) rotate(-45deg)',
+ 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: 'matrix(1, 0, -0.25, 1, 0, 0)',
+ round_error_ok: true },
+ // 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 },
+ // matrix decomposition of skewY
+ { start: 'skewY(60deg)', end: 'skewY(-60deg) translateX(0)',
+ /* rotate(30deg) skewX(60deg)/2 scale(2, 0.5) */
+ expected: computeMatrix('rotate(30deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'),
+ round_error_ok: true },
+
+ // 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 vs. matrix decomposition
+ { 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: 'skewY(45deg) rotate(90deg) translate(0)',
+ end: 'skewY(-45deg) rotate(90deg)',
+ expected: 'matrix(0, 1, -1, -0.5, 0, 0)',
+ round_error_ok: true },
+ { start: 'skewX(45deg) rotate(90deg)',
+ end: 'skewX(-45deg) rotate(90deg)',
+ expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' },
+ { start: 'skewX(-60deg) rotate(90deg) translate(0)',
+ end: 'skewX(60deg) rotate(90deg)',
+ expected: computeMatrix('rotate(120deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'),
+ round_error_ok: true },
+];
+
+var clipPathTests = [
+ { start: "none", end: "none",
+ expected: ["none"] },
+ // none to shape
+ { start: "none",
+ end: "circle(500px at 500px 500px) border-box",
+ expected: ["circle", ["500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"]
+ },
+ { start: "none",
+ end: "ellipse(500px 500px at 500px 500px) border-box",
+ expected: ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"]
+ },
+ { start: "none",
+ end: "polygon(evenodd, 500px 500px, 500px 500px) border-box",
+ expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "border-box"]
+ },
+ { start: "none",
+ end: "inset(500px 500px 500px 500px round 500px 500px) border-box",
+ expected: ["inset", ["500px round 500px"], "border-box"]
+ },
+ // matching functions
+ { start: "circle(100px)", end: "circle(500px)",
+ expected: ["circle", ["200px at 50% 50%"]] },
+ { start: "ellipse(100px 100px)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["200px 200px at 50% 50%"]] },
+ { start: "circle(100px at 100px 100px) border-box",
+ end: "circle(500px at 500px 500px) border-box",
+ expected: ["circle", ["200px at calc(200px + 0%) calc(200px + 0%)"], "border-box"]
+ },
+ { start: "ellipse(100px 100px at 100px 100px) border-box",
+ end: "ellipse(500px 500px at 500px 500px) border-box",
+ expected: ["ellipse", ["200px 200px at calc(200px + 0%) calc(200px + 0%)"], "border-box"]
+ },
+ { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box",
+ end: "polygon(evenodd, 500px 500px, 500px 500px) border-box",
+ expected: ["polygon", ["evenodd, 200px 200px, 200px 200px"], "border-box"]
+ },
+ { start: "inset(100px 100px 100px 100px round 100px 100px) border-box",
+ end: "inset(500px 500px 500px 500px round 500px 500px) border-box",
+ expected: ["inset", ["200px round 200px"], "border-box"]
+ },
+ // matching functions percentage
+ { start: "circle(100%)", end: "circle(500%)",
+ expected: ["circle", ["200% at 50% 50%"]] },
+ { start: "ellipse(100% 100%)", end: "ellipse(500% 500%)",
+ expected: ["ellipse", ["200% 200% at 50% 50%"]] },
+ { start: "circle(100% at 100% 100%) border-box",
+ end: "circle(500% at 500% 500%) border-box",
+ expected: ["circle", ["200% at 200% 200%"], "border-box"]
+ },
+ { start: "ellipse(100% 100% at 100% 100%) border-box",
+ end: "ellipse(500% 500% at 500% 500%) border-box",
+ expected: ["ellipse", ["200% 200% at 200% 200%"], "border-box"]
+ },
+ { start: "polygon(evenodd, 100% 100%, 100% 100%) border-box",
+ end: "polygon(evenodd, 500% 500%, 500% 500%) border-box",
+ expected: ["polygon", ["evenodd, 200% 200%, 200% 200%"], "border-box"]
+ },
+ { start: "inset(100% 100% 100% 100% round 100% 100%) border-box",
+ end: "inset(500% 500% 500% 500% round 500% 500%) border-box",
+ expected: ["inset", ["200% round 200%"], "border-box"] },
+ // matching functions with calc() values
+ { start: "circle(calc(80px + 20px))", end: "circle(calc(200px + 300px))",
+ expected: ["circle", ["200px at 50% 50%"]] },
+ { start: "circle(calc(80% + 20%))", end: "circle(calc(200% + 300%))",
+ expected: ["circle", ["calc(0px + 200%) at 50% 50%"]] },
+ { start: "circle(calc(10px + 20%))", end: "circle(calc(50px + 40%))",
+ expected: ["circle", ["calc(20px + 25%) at 50% 50%"]] },
+ // matching functions with interpolation between percentage/pixel values
+ { start: "circle(20px)", end: "circle(100%)",
+ expected: ["circle", ["calc(15px + 25%) at 50% 50%"]] },
+ { start: "ellipse(100% 100px at 8px 20%) border-box",
+ end: "ellipse(40px 4% at 80% 60px) border-box",
+ expected: ["ellipse", ["calc(10px + 75%) calc(75px + 1%) at " +
+ "calc(6px + 20%) calc(15px + 15%)"],
+ "border-box"] },
+ // no interpolation for keywords
+ { start: "circle()", end: "circle(50px)",
+ expected: ["circle", ["50px at 50% 50%"]] },
+ { start: "circle(closest-side)", end: "circle(500px)",
+ expected: ["circle", ["500px at 50% 50%"]] },
+ { start: "circle(farthest-side)", end: "circle(500px)",
+ expected: ["circle", ["500px at 50% 50%"]] },
+ { start: "circle(500px)", end: "circle(farthest-side)",
+ expected: ["circle", ["farthest-side at 50% 50%"]]},
+ { start: "circle(500px)", end: "circle(closest-side)",
+ expected: ["circle", ["closest-side at 50% 50%"]]},
+ { start: "ellipse()", end: "ellipse(50px 50px)",
+ expected: ["ellipse", ["50px 50px at 50% 50%"]] },
+ { start: "ellipse(closest-side closest-side)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px at 50% 50%"]] },
+ { start: "ellipse(farthest-side closest-side)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px at 50% 50%"]] },
+ { start: "ellipse(farthest-side farthest-side)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px at 50% 50%"]] },
+ { start: "ellipse(500px 500px)", end: "ellipse(farthest-side farthest-side)",
+ expected: ["ellipse", ["farthest-side farthest-side at 50% 50%"]] },
+ { start: "ellipse(500px 500px)", end: "ellipse(closest-side closest-side)",
+ expected: ["ellipse", ["closest-side closest-side at 50% 50%"]] },
+ // mismatching boxes
+ { start: "circle(100px at 100px 100px) border-box",
+ end: "circle(500px at 500px 500px) content-box",
+ expected: ["circle", ["500px at calc(500px + 0%) calc(500px + 0%)"], "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 calc(500px + 0%) calc(500px + 0%)"], "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) border-box",
+ end: "ellipse(500px 500px at 500px 500px) border-box",
+ expected: ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"]
+ },
+ { start: "inset(0px round 20px)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px at 50% 50%"]]
+ },
+ // shape to reference box
+ { start: "circle(20px)", end: "content-box", expected: ["content-box"] },
+ { start: "content-box", end: "circle(20px)", expected: ["circle", ["20px at 50% 50%"]] },
+ // url to shape
+ { start: "circle(20px)", end: "url('#a')", expected: ["url", ["\"#a\""]] },
+ { start: "url('#a')", end: "circle(20px)", expected: ["circle", ["20px at 50% 50%"]] },
+ // url to none
+ { start: "none", end: "url('#a')", expected: ["url", ["\"#a\""]] },
+ { start: "url('#a')", end: "none", expected: ["none"] },
+
+];
+
+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", Math.PI.toFixed(5)+"rad"] },
+ { start: "hue-rotate(0grad)", end: "hue-rotate(800grad)",
+ expected: ["hue-rotate", "200grad"] },
+ { start: "hue-rotate(0turn)", end: "hue-rotate(2turn)",
+ expected: ["hue-rotate", "0.5turn"] },
+ { start: "hue-rotate(0deg)", end: "hue-rotate("+4*Math.PI+"rad)",
+ expected: ["hue-rotate", Math.PI.toFixed(5)+"rad"] },
+ { start: "hue-rotate(0turn)", end: "hue-rotate(800grad)",
+ expected: ["hue-rotate", Math.PI.toFixed(5)+"rad"] },
+ { start: "hue-rotate(0grad)", end: "hue-rotate("+4*Math.PI+"rad)",
+ expected: ["hue-rotate", Math.PI.toFixed(5)+"rad"] },
+ { start: "hue-rotate(0grad)", end: "hue-rotate(0turn)",
+ expected: ["hue-rotate", "0rad"] },
+ // 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", "rgb(0, 0, 0) 6px 6px 0px"] },
+ { start: "blur(75px)",
+ end: "blur(25px) drop-shadow(8px 8px)",
+ expected: ["blur", 62.5, "drop-shadow", "rgb(0, 0, 0) 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 (var 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) &&
+ info.type != CSS_TYPE_TRUE_SHORTHAND &&
+ !("alias_for" in info) &&
+ !prop.match(/^transition-/) &&
+ // FIXME (Bug 119078): THIS SHOULD REALLY NOT BE NEEDED!
+ prop != "-moz-binding" &&
+ 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) {
+ var 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 (var i = 0; i < all_values.length; ++i) {
+ for (var 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_svg_or_units(prop, numbers_are_pixels) {
+ 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), numbers_are_pixels ? "6" : "6px",
+ "length-valued property " + prop + ": interpolation of lengths");
+ check_distance(prop, "4px", "6px", "12px");
+}
+
+function test_length_transition(prop) {
+ test_length_transition_svg_or_units(prop, false);
+}
+
+function test_length_transition_svg(prop) {
+ test_length_transition_svg_or_units(prop, true);
+}
+
+function test_length_clamped(prop) {
+ test_length_clamped_or_unclamped(prop, true, false);
+}
+
+function test_length_unclamped(prop) {
+ test_length_clamped_or_unclamped(prop, false, false);
+}
+
+function test_length_clamped_svg(prop) {
+ test_length_clamped_or_unclamped(prop, true, true);
+}
+
+function test_length_unclamped_svg(prop) {
+ test_length_clamped_or_unclamped(prop, false, true);
+}
+
+function test_length_clamped_or_unclamped(prop, is_clamped, numbers_are_pixels) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px", "");
+ is(cs.getPropertyValue(prop), "0px",
+ "length-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100px", "");
+ (is_clamped ? is : isnot)(cs.getPropertyValue(prop),
+ numbers_are_pixels ? "0" : "0px",
+ "length-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+// 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) (e.g. stroke-miterlimit)
+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_aboveOne_clamped(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",
+ "float-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "5", "");
+ is(cs.getPropertyValue(prop), "1",
+ "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", "");
+}
+
+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)");
+}
+
+function test_color_transition(prop, get_color=(x => x), is_shorthand=false) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "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, "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, "rgb(255, 28, 0)", "rgb(210, 42, 32)",
+ "rgb(75, 84, 128)");
+ }
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "rgb(0, 255, 0)", "");
+ var vals = cs.getPropertyValue(prop).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, "rgb(255, 0, 128)", "");
+ // FIXME: Once we support non-sRGB colors, these tests will need fixing.
+ vals = cs.getPropertyValue(prop).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, get_color=(x => x), is_shorthand=false) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "rgb(128, 64, 0)", "");
+ (prop == "color" ? div.parentNode : div).style.
+ setProperty("color", "rgb(0, 0, 128)", "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(128, 64, 0)",
+ "color-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "currentColor", "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(96, 48, 32)",
+ "color-valued property " + prop + ": interpolation of currentColor");
+
+ if (!is_shorthand) {
+ check_distance(prop, "rgb(128, 64, 0)", "rgb(96, 48, 32)",
+ "currentColor");
+ }
+
+ (prop == "color" ? div.parentNode : div).style.removeProperty("color");
+}
+
+function test_true_currentcolor_transition(prop, get_color=(x => x), is_shorthand=false) {
+ const msg_prefix = `color-valued property ${prop}: `;
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty("color", "rgb(128, 0, 0)", "");
+ div.style.setProperty(prop, "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, "currentcolor", "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(32, 0, 96)",
+ msg_prefix + "interpolation of rgb color and currentcolor");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty("color", "rgb(128, 0, 0)", "");
+ div.style.setProperty(prop, "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, "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", "");
+ div.style.setProperty("color", "rgba(128, 0, 0, 0.6)", "");
+ div.style.setProperty(prop, "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, "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.
+
+ div.style.removeProperty("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_from_shorthand_value, true);
+}
+
+function test_currentcolor_shorthand_transition(prop) {
+ test_currentcolor_transition(prop, get_color_from_shorthand_value, true);
+}
+
+function test_true_currentcolor_shorthand_transition(prop) {
+ test_true_currentcolor_transition(prop, get_color_from_shorthand_value, true);
+}
+
+function test_clip_path_equals(computedValStr, expectedList)
+{
+ // Check simple case "none"
+ if (computedValStr == "none" && computedValStr == expectedList[0]) {
+ return true;
+ }
+ 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;
+ } else {
+ ok(false, "expected clip-path 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 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 (var 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_clip_path_transition(prop) {
+ if (!SpecialPowers.getBoolPref("layout.css.clip-path-shapes.enabled")) {
+ return;
+ }
+ for (var i in clipPathTests) {
+ var test = clipPathTests[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(test_clip_path_equals(actual, test.expected),
+ "Clip-path property is " + actual + " expected values of " +
+ test.expected);
+ }
+}
+
+function test_filter_transition(prop) {
+ if (!SpecialPowers.getBoolPref("layout.css.filters.enabled")) {
+ return;
+ }
+ for (var 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 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");
+ }
+
+ var defaultColor = cs.getPropertyValue("color") + " ";
+ div.style.setProperty(prop, "2px 2px 2px", "");
+ is(cs.getPropertyValue(prop), defaultColor + "2px 2px 2px" + spreadStr,
+ "shadow-valued property " + prop + ": non-interpolable cases");
+ 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");
+
+ 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");
+ }
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_dasharray_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "3", "");
+ is(cs.getPropertyValue(prop), "3",
+ "dasharray-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "15px", "");
+ is(cs.getPropertyValue(prop), "6",
+ "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), "6, 8px, 4, 4",
+ "dasharray-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty(prop, "14, 12,16,16px", "");
+ is(cs.getPropertyValue(prop), "8, 9, 7, 7",
+ "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), "8, 16, 4",
+ "dasharray-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty(prop, "4,8,12,16", "");
+ is(cs.getPropertyValue(prop), "7, 14, 6, 10, 13, 5, 9, 16, 4, 8, 15, 7",
+ "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), "2, 50%, 6, 10",
+ "dasharray-valued property " + prop + ": non-interpolability of mixed units");
+ div.style.setProperty(prop, "6,30%,2,2", "");
+ is(cs.getPropertyValue(prop), "3, 45%, 5, 8",
+ "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), "0, 0%",
+ "dasharray-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "5, 25%", "");
+ is(cs.getPropertyValue(prop), "0, 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(16px + 4.6875%) calc(4px + 9.375%)",
+ "radius-valued property " + prop + ": interpolation of radius with mixed units");
+ check_distance(prop, "6.25% 12.5%",
+ "calc(16px + 4.6875%) calc(4px + 9.375%)",
+ "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), "-1",
+ "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_stretch(prop) {
+ is(prop, "font-stretch", "only designed for one property");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "normal", "");
+ is(cs.getPropertyValue(prop), "normal",
+ "font-stretch property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "ultra-expanded", "");
+ is(cs.getPropertyValue(prop), "semi-expanded",
+ "font-stretch property " + prop + ": interpolation of font-stretches");
+ check_distance(prop, "normal", "semi-expanded", "ultra-expanded");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "expanded", "");
+ is(cs.getPropertyValue(prop), "expanded",
+ "font-stretch property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "extra-condensed", "");
+ is(cs.getPropertyValue(prop), "normal",
+ "font-stretch property " + prop + ": interpolation of font-stretches");
+ check_distance(prop, "expanded", "semi-expanded", "condensed");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "ultra-condensed", "");
+ is(cs.getPropertyValue(prop), "ultra-condensed",
+ "font-stretch property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "ultra-expanded", "");
+ is(cs.getPropertyValue(prop), "ultra-condensed",
+ "font-stretch property " + prop + ": clamping of values");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "ultra-expanded", "");
+ is(cs.getPropertyValue(prop), "ultra-expanded",
+ "font-stretch property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "ultra-condensed", "");
+ is(cs.getPropertyValue(prop), "ultra-expanded",
+ "font-stretch property " + prop + ": clamping of values");
+ 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), "500",
+ "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, "100", "");
+ is(cs.getPropertyValue(prop), "100",
+ "font-weight property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "900", "");
+ is(cs.getPropertyValue(prop), "100",
+ "font-weight property " + prop + ": clamping of values");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "900", "");
+ is(cs.getPropertyValue(prop), "900",
+ "font-weight property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100", "");
+ is(cs.getPropertyValue(prop), "900",
+ "font-weight property " + prop + ": clamping of values");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_grid_gap(prop) {
+ if (!SpecialPowers.getBoolPref("layout.css.grid.enabled")) {
+ return;
+ }
+ test_length_transition(prop);
+ test_length_clamped(prop);
+ test_percent_transition(prop);
+ test_percent_clamped(prop);
+}
+
+function test_pos_integer_or_auto_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, "11", "");
+ is(cs.getPropertyValue(prop), "5",
+ "integer-valued property " + prop + ": interpolation of integers");
+ check_distance(prop, "4", "6", "12");
+ div.style.setProperty(prop, "auto", "");
+ is(cs.getPropertyValue(prop), "auto",
+ "integer-valued property " + prop + ": auto 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_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%", "");
+ is(cs.getPropertyValue(prop), "0px 0px",
+ "length+percent-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "30px 25%", "");
+ (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0px 0px",
+ "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)");
+ if (prop == "clip") {
+ 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 test_visibility_transition(prop) {
+ function do_test(from_value, to_value, interp_value) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, from_value, "");
+ is(cs.getPropertyValue(prop), from_value,
+ "visibility property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, to_value, "");
+ is(cs.getPropertyValue(prop), interp_value,
+ "visibility property " + prop + ": interpolation of visibility");
+ }
+
+ do_test("visible", "hidden", "visible");
+ do_test("hidden", "visible", "visible");
+ do_test("hidden", "collapse", "collapse"); /* not interpolable */
+ do_test("collapse", "hidden", "hidden"); /* not interpolable */
+ do_test("visible", "collapse", "visible");
+ do_test("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 not be zero");
+ is(get_distance(prop, "hidden", "hidden"), 0,
+ "distance between hidden and hidden should not be zero");
+ is(get_distance(prop, "collapse", "collapse"), 0,
+ "distance between collapse and collapse should not be zero");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ function do_negative_test(from_value, to_value, interpolable) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, from_value, "");
+ is(cs.getPropertyValue(prop), from_value,
+ "visibility 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,
+ "visibility property " + prop + ": clamping of negatives");
+ }
+ do_negative_test("visible", "hidden", true);
+ do_negative_test("hidden", "visible", true);
+ do_negative_test("hidden", "collapse", false);
+ do_negative_test("collapse", "hidden", false);
+ do_negative_test("visible", "collapse", true);
+ do_negative_test("collapse", "visible", true);
+
+ div.style.setProperty("transition-delay", "-3s", "");
+ div.style.setProperty("transition-timing-function", FUNC_OVERONE, "");
+
+ function do_overone_test(from_value, to_value) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, from_value, "");
+ is(cs.getPropertyValue(prop), from_value,
+ "visibility property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, to_value, "");
+ is(cs.getPropertyValue(prop), to_value,
+ "visibility property " + prop + ": clamping of over-ones");
+ }
+ do_overone_test("visible", "hidden");
+ do_overone_test("hidden", "visible");
+ do_overone_test("hidden", "collapse");
+ do_overone_test("collapse", "hidden");
+ do_overone_test("visible", "collapse");
+ do_overone_test("collapse", "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(-20px + 100%) 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(-5px + 80%) calc(3px + 60%)",
+ "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(-20px + 100%)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(40px + 20%)", "");
+ is(cs.getPropertyValue(prop), "calc(-5px + 80%)",
+ "property " + prop + ": interpolation of edge keywords w/ offsets & calc");
+ check_distance(prop, `${endEdge} 20px`,
+ "calc(-5px + 80%)",
+ "calc(40px + 20%)");
+
+ div.style.setProperty(prop, "10px, 50px, 30px", "");
+ is(cs.getPropertyValue(prop), "10px, 50px, 30px",
+ "property " + prop + ": computed value before transition");
+ 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(prop, "10px, 50%, 30%, 5px", "");
+ is(cs.getPropertyValue(prop), "10px, 50%, 30%, 5px",
+ "property " + prop + ": computed value before transition");
+ 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(prop, "20%, 8px", "");
+ is(cs.getPropertyValue(prop), "20%, 8px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty(prop, "12px, 40%", "");
+ is(cs.getPropertyValue(prop), "calc(3px + 15%), calc(6px + 10%)",
+ "property " + prop + ": interpolation that computes to calc()");
+ check_distance(prop, "20%, 8px",
+ "calc(3px + 15%), calc(6px + 10%)",
+ "12px, 40%");
+ div.style.setProperty(prop, "calc(20% + 40px), 8px, calc(20px + 12%)", "");
+ is(cs.getPropertyValue(prop), "calc(40px + 20%), 8px, calc(20px + 12%)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty(prop, "12px, calc(20%), calc(8px + 20%)", "");
+ is(cs.getPropertyValue(prop), "calc(33px + 15%), calc(6px + 5%), calc(17px + 14%)",
+ "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(3px + 15%) calc(5px + 30%)",
+ "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(9px + 5%) calc(15px + 10%)",
+ "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(40px + 10%) 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(30px + 15%) calc(14px + 15%)",
+ "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(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(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(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(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(prop, "20% 40%, 8px 12px", "");
+ is(cs.getPropertyValue(prop), "20% 40%, 8px 12px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty(prop, "12px 20px, 40% 16%", "");
+ is(cs.getPropertyValue(prop), "calc(3px + 15%) calc(5px + 30%), calc(6px + 10%) calc(9px + 4%)",
+ "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(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)", "");
+ is(cs.getPropertyValue(prop), "calc(40px + 20%) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty(prop, "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)", "");
+ is(cs.getPropertyValue(prop), "calc(33px + 15%) calc(30px + 35%), calc(6px + 5%) calc(4px + 24%), calc(17px + 14%) calc(28px + 10%)",
+ "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 (var 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 (var 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 (var i = 0; i < 6; ++i) {
+ // 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[i]) - Number(expected_split[i])) >
+ 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 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.
+ OMTAdiv.style.setProperty("transition-duration", "300s", "");
+ OMTAdiv.style.setProperty("transition-timing-function", "linear", "");
+ addAsyncTransformTests();
+ addAsyncOpacityTest();
+ addAsyncDelayTest();
+
+ runAllAsyncAnimTests().then(function() {
+ OMTAdiv.style.removeProperty("transition");
+ SimpleTest.finish();
+ });
+}
+
+function addAsyncTransformTests() {
+ transformTests.forEach(function(test) {
+ addAsyncAnimTest(function () { return runTransformTest(test); } );
+ });
+}
+
+function *runTransformTest(test) {
+ 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");
+ yield 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);
+ yield waitForPaints();
+ winUtils.advanceTimeAndRefresh(100000);
+ yield waitForPaints();
+ } else {
+ winUtils.advanceTimeAndRefresh(200000);
+ yield 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(function *() {
+ 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");
+
+ yield 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(function *() {
+ 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);
+ yield 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 000000000..a9e197d16
--- /dev/null
+++ b/layout/style/test/test_transitions_replacement_on_busy_frame.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1167519
+-->
+<head>
+ <title>Test for bug 1167519</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1167519">Mozilla Bug
+ 1167519</a>
+<pre id="test">
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ { 'set': [[ 'dom.animations-api.core.enabled', true ]] },
+ function() {
+ window.open('file_transitions_replacement_on_busy_frame.html');
+ });
+</script>
+</pre>
+</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 000000000..920b48af3
--- /dev/null
+++ b/layout/style/test/test_transitions_step_functions.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script type="application/javascript" 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);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_with_disabled_properties.html b/layout/style/test/test_transitions_with_disabled_properties.html
new file mode 100644
index 000000000..fc0965f42
--- /dev/null
+++ b/layout/style/test/test_transitions_with_disabled_properties.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265611
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for bug 1265611</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265611">Mozilla Bug
+ 1265611</a>
+
+<pre id="test">
+<script>
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({'set': [['layout.css.prefixes.webkit', false],
+ ['dom.animations-api.core.enabled', true]] },
+ () => window.open('file_transitions_with_disabled_properties.html'));
+</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 000000000..d2daae944
--- /dev/null
+++ b/layout/style/test/test_unclosed_parentheses.html
@@ -0,0 +1,289 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575672
+-->
+<head>
+ <title>Test for Bug 575672</title>
+ <script type="application/javascript" 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: -moz-linear-gradient(",
+ "background-image: -moz-linear-gradient( ",
+ "background-image: -moz-linear-gradient(red, blue",
+ "background-image: -moz-linear-gradient(red, yellow, blue",
+ "background-image: -moz-linear-gradient(red 1px, yellow 5px, blue 10px",
+ "background-image: -moz-linear-gradient(red, yellow, rgb(0, 0, 255)",
+ "background-image: -moz-linear-gradient(to",
+ "background-image: -moz-linear-gradient(to top",
+ "background-image: -moz-linear-gradient(to top left",
+ "background-image: -moz-linear-gradient(to top left,",
+ "background-image: -moz-repeating-linear-gradient(to top left, red, blue",
+ "background-image: -moz-linear-gradient(to top left, red, yellow, blue",
+ "background-image: -moz-linear-gradient(to top left, red 1px, yellow 5px, blue 10px",
+ "background-image: -moz-linear-gradient(to top left, red, yellow, rgb(0, 0, 255)",
+ "background-image: -moz-repeating-linear-gradient(top left, red, blue",
+ "background-image: -moz-linear-gradient(top left, red, yellow, blue",
+ "background-image: -moz-linear-gradient(top left, red 1px, yellow 5px, blue 10px",
+ "background-image: -moz-linear-gradient(top left, 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: 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: -moz-radial-gradient(",
+ "background-image: -moz-radial-gradient( ",
+ "background-image: -moz-radial-gradient(top left 45deg, red, blue",
+ "background-image: -moz-radial-gradient(cover, red, blue",
+ "background-image: -moz-repeating-radial-gradient(circle, red, blue",
+ "background-image: -moz-radial-gradient(ellipse closest-corner, red, hsl(240, 50%, 50%)",
+ "background-image: -moz-radial-gradient(farthest-side circle, red, blue",
+ "background-image: -moz-image-rect(",
+ "background-image: -moz-image-rect( ",
+ "background-image: -moz-image-rect(url(foo.jpg)",
+ "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10",
+ "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10 ",
+ "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10,",
+ "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, ",
+ "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, 10",
+ "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, 10 ",
+ "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, 10,",
+ "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, 10, ",
+ "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 000000000..43622e2ae
--- /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 000000000..9ad800f13
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">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", "-moz-transform: rotate(" + test + ")");
+ is(p.style.getPropertyValue("-moz-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. (-moz-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 000000000..bb8984726
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">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 000000000..6db3f0c7d
--- /dev/null
+++ b/layout/style/test/test_units_length.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for serialization and equivalence of length units</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">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;
+ is(cm1, cm2, test + " should compute to the same as " + equiv);
+ }
+}
+
+// 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 000000000..e9d3e77bd
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">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_unprefixing_service.html b/layout/style/test/test_unprefixing_service.html
new file mode 100644
index 000000000..c489e2ac0
--- /dev/null
+++ b/layout/style/test/test_unprefixing_service.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1107378
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1107378</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript;version=1.7" src="unprefixing_service_utils.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=1107378">Mozilla Bug 1107378</a>
+<div id="display">
+ <iframe id="testIframe"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+"use strict";
+SimpleTest.waitForExplicitFinish();
+
+/**
+ * This test checks that unprefixing is enabled for whitelisted domains, and
+ * that it's disabled for non-whitelisted domains.
+ *
+ * We do this using an iframe, in which we load a test file at a test domain,
+ * and we have the iframe report back to us (using postMessage) about
+ * whether unprefixing is working.
+ *
+ * High-level overview of the process here:
+ * - First, we tweak prefs to enable unprefixing & enable the test-only
+ * entries in our unprefixing whitelist.
+ * - The rest of this test is driven by the "startNextTest()" method.
+ * This method pops a hostname to test and loads a URL from that host
+ * in the iframe.
+ * - We then listen for test-results from the iframe, using the postMessage
+ * handler in unprefixing_service_utils.js.
+ * - When the iframe indicates that it's done, we call "startNextTest()"
+ * again to pop the next host & load *that* in the iframe.
+ * - When nothing remains to be popped, we're done.
+ */
+
+const IFRAME_TESTFILE = "unprefixing_service_iframe.html";
+
+// This function gets invoked when our iframe finishes a given round of testing.
+function startNextTest()
+{
+ // Test the next whitelisted host, if any remain.
+ if (gWhitelistedHosts.length > 0) {
+ let host = gWhitelistedHosts.pop();
+ info("Verifying that CSS Unprefixing Service is active, " +
+ "at whitelisted test-host '" + host + "'");
+ testHost(host, true);
+ return;
+ }
+
+ // Test the next not-whitelisted host, if any remain.
+ if (gNotWhitelistedHosts.length > 0) {
+ let host = gNotWhitelistedHosts.pop();
+ info("Verifying that CSS Unprefixing Service is inactive, " +
+ "at non-whitelisted test-host '" + host + "'");
+ testHost(host, false);
+ return;
+ }
+
+ // Both arrays empty --> we're done.
+ SimpleTest.finish();
+}
+
+function begin()
+{
+ // Before we start loading things in iframes, set up postMessage handler.
+ registerPostMessageListener(startNextTest);
+
+ // Turn on prefs & start the first test!
+ SpecialPowers.pushPrefEnv(
+ { set: [[PREF_UNPREFIXING_SERVICE, true],
+ [PREF_INCLUDE_TEST_DOMAINS, true],
+ // Make sure *native* -webkit prefix support is turned off. It's
+ // not whitelist-restricted, so if we left it enabled, it'd prevent
+ // us from being able to detect CSSUnprefixingService's domain
+ // whitelisting in this test.
+ ["layout.css.prefixes.webkit", false]]},
+ startNextTest);
+}
+
+begin();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_unprefixing_service_prefs.html b/layout/style/test/test_unprefixing_service_prefs.html
new file mode 100644
index 000000000..329dce2a6
--- /dev/null
+++ b/layout/style/test/test_unprefixing_service_prefs.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1132743
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1132743</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript;version=1.7" src="unprefixing_service_utils.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=1132743">Mozilla Bug 1132743</a>
+<div id="display">
+ <iframe id="testIframe"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+"use strict";
+SimpleTest.waitForExplicitFinish();
+
+/**
+ * This test checks that our CSS unprefixing prefs are effective.
+ *
+ * We do this using an iframe, in which we load a test file at a test domain
+ * (whose whitelist-status depends on a pref), and we have the iframe report
+ * back to us (using postMessage) about whether unprefixing is working.
+ *
+ * High-level overview of the process here (starting with begin()):
+ * - First, we ensure that the pref...
+ * "layout.css.unprefixing-service.include-test-domains"
+ * ...is *unset* by default. (No point exposing it in about:config).
+ * - Then, we test that (as a result of this pref being unset) the
+ * unprefixing service is *inactive* at our test-domain, by default.
+ * - Then, via a series of calls to "startNextTest()"/"testHost()", we re-test
+ * the same test-domain with a variety of pref configurations, to ensure
+ * that unprefixing only happens there when we've preffed on the service
+ * *and* we've enabled the testing entries in the whiteslist.
+ */
+
+const IFRAME_TESTFILE = "unprefixing_service_iframe.html";
+
+// Just test the first host in our known-whitelisted-hosts list.
+const WHITELISTED_TEST_HOST = gWhitelistedHosts[0];
+
+// Configurations of our prefs to test.
+// Each is a 3-entry array, whose entries mean:
+// (1) should we enable the CSS Unprefixing Service pref?
+// (2) should we enable the "include test domains in whitelist" pref?
+// (3) in this pref-configuration, should we expect to see unprefixing active
+// on our whitelisted test-domain?
+//
+// As you can see, the only configuration which should produce unprefixing
+// activity is when *both* prefs are enabled.
+let gTestConfigs = [
+ [false, false, false],
+ [false, true, false],
+ [true, false, false],
+ [true, true, true],
+];
+
+// Test that a particular configuration of prefs will activate or inactivate
+// the CSS unprefixing service, for styles loaded from WHITELISTED_TEST_HOST.
+// aTestConfig is described above, in documentation for gTestConfigs.
+function testConfig(aTestConfig)
+{
+ if (aTestConfig.length != 3) {
+ ok(false, "bug in test; need 3 entries. see gTestConfigs documentation");
+ }
+
+ info("Verifying that CSS Unprefixing Service is " +
+ (aTestConfig[2] ? "active" : "inactive") +
+ " at test host, with prefs: " +
+ PREF_UNPREFIXING_SERVICE + "=" + aTestConfig[0] + ", " +
+ PREF_INCLUDE_TEST_DOMAINS + "=" + aTestConfig[1]);
+
+ SpecialPowers.pushPrefEnv(
+ { set:
+ [[PREF_UNPREFIXING_SERVICE, aTestConfig[0]],
+ [PREF_INCLUDE_TEST_DOMAINS, aTestConfig[1]]]
+ },
+ function() {
+ testHost(WHITELISTED_TEST_HOST, aTestConfig[2]);
+ });
+}
+
+// This function gets invoked when our iframe finishes a given round of testing.
+function startNextTest()
+{
+ if (gTestConfigs.length > 0) {
+ // Grab the next test-config, and kick off a test for it.
+ testConfig(gTestConfigs.pop());
+ return;
+ }
+
+ // Array empty --> we're done.
+ SimpleTest.finish();
+}
+
+function begin()
+{
+ // First, check that PREF_INCLUDE_TEST_DOMAINS is unset:
+ try {
+ let val = SpecialPowers.getBoolPref(PREF_INCLUDE_TEST_DOMAINS);
+ ok(false, "The test pref '" + PREF_INCLUDE_TEST_DOMAINS +
+ "' should be unspecified by default");
+ } catch(e) { /* Good, we threw; pref is unset. */ }
+
+ // Before we start loading things in iframes, set up postMessage handler.
+ registerPostMessageListener(startNextTest);
+
+ // To kick things off, we don't set any prefs; we just test the default state
+ // (which should have the "include test domains" pref implicitly disabled, &
+ // hence unprefixing should end up being disabled in our iframe). Subsequent
+ // tests are kicked off via postMessage-triggered calls to startNextTest(),
+ // which will tweak prefs and re-test.
+ info("Verifying that CSS Unprefixing Service is inactive at test host, " +
+ "with default pref configuration");
+ testHost(WHITELISTED_TEST_HOST, false);
+}
+
+// Before we start, make sure *native* -webkit prefix support is turned off.
+// It's not whitelist-restricted (and behaves slightly differently), so if we
+// left it enabled, it'd prevent us from being able to detect
+// CSSUnprefixingService's domain whitelisting in this test.
+SpecialPowers.pushPrefEnv({ set: [["layout.css.prefixes.webkit", false]]},
+ begin);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_value_cloning.html b/layout/style/test/test_value_cloning.html
new file mode 100644
index 000000000..5582b8303
--- /dev/null
+++ b/layout/style/test/test_value_cloning.html
@@ -0,0 +1,182 @@
+<!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 type="text/javascript" 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 gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled");
+var test_queue = [];
+var iframe = document.getElementById("iframe");
+
+SimpleTest.waitForExplicitFinish();
+
+for (var prop in gCSSProperties) {
+ var 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 (gTestUnset) {
+ 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 (var idx in info.initial_values) {
+ test_queue.push({ prop: prop, value: info.initial_values[idx] });
+ }
+ for (var 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 (var idx = 0; idx < test_queue.length; ++idx) {
+ var current_item = test_queue[idx];
+
+ var 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 (var 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 ifdoc = iframe.contentDocument;
+
+ for (var 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 = iframe.contentWindow.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.parentNode.removeChild(firstlink);
+
+ // Force a flush
+ ifdoc.body.style.display="none";
+ var ow = ifdoc.body.offsetWidth;
+ ifdoc.body.style.display="";
+
+ for (var 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 000000000..024b26210
--- /dev/null
+++ b/layout/style/test/test_value_computation.html
@@ -0,0 +1,249 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for computation of values in property database</title>
+ <script type="text/javascript" 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">
+ var numAssertions = 9;
+
+ // these are from the additional margin-inline-{start,end} tests
+ numAssertions += 2;
+
+ SimpleTest.expectAssertions(numAssertions);
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(2);
+
+ 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 values in property database **/
+
+var gBadComputed = {
+ // These values are treated as auto.
+ "page-break-after": [ "avoid" ],
+ "page-break-before": [ "avoid" ],
+
+ // This is the only SVG-length property (i.e., length allowing
+ // unitless lengths) whose initial value is zero.
+ "stroke-dashoffset": [ "0", "-moz-objectValue" ],
+};
+
+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 ((property in gBadComputed) &&
+ gBadComputed[property].indexOf(value) != -1)
+ return true;
+
+ if (!has_frame && (property in gBadComputedNoFrame) &&
+ gBadComputedNoFrame[property].indexOf(value) != -1)
+ 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" ],
+};
+
+function swap_when_frame(property, value) {
+ return (property in gSwapInitialWhenHaveFrame) &&
+ gSwapInitialWhenHaveFrame[property].indexOf(value) != -1;
+}
+
+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's important for values that are supposed to compute to the
+ // initial value (given the current design of 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'll have a cached aStartStruct from the parent in the rule
+ // tree (caching the "other" value), so we'll make sure we don't get
+ // the initial value from the luck of default-initialization.
+ // This means that it's important that we set the prereqs on
+ // gRule1.style rather than on gElement.style.
+ 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);
+ }
+
+ // FIXME: Something (maybe with the -moz-binding values) causes
+ // gElementF's frame to get lost. Force it to get recreated after
+ // each property.
+ gElementF.parentNode.style.display = "none";
+ get_computed_value(getComputedStyle(gElementF, ""), "width");
+ gElementF.parentNode.style.display = "";
+ get_computed_value(getComputedStyle(gElementF, ""), "width");
+}
+
+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 000000000..5e7fa6b69
--- /dev/null
+++ b/layout/style/test/test_value_storage.html
@@ -0,0 +1,352 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS values</title>
+ <script type="text/javascript" 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": true,
+ "icon": true,
+ "menu": true,
+ "message-box": true,
+ "small-caption": true,
+ "status-bar": true,
+ "-moz-window": true,
+ "-moz-document": true,
+ "-moz-desktop": true,
+ "-moz-info": true,
+ "-moz-dialog": true,
+ "-moz-button": true,
+ "-moz-pull-down-menu": true,
+ "-moz-list": true,
+ "-moz-field": true,
+ "-moz-workspace": true,
+};
+
+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].indexOf(value) != -1)
+ 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.indexOf("Android") >= 0) {
+ 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(property, 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 " + property + " 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;
+
+ 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)
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ if (value_has_variable_reference &&
+ (!info.alias_for || info.type == CSS_TYPE_TRUE_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) {
+ expected_serialization = info.alias_for + ": " + step1val + ";";
+ } else {
+ expected_serialization = property + ": " + 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 + ": " + 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.
+ (property != "font" || !(value in gSystemFont)) &&
+ // Likewise for special compatibility values of transform
+ (property != "-moz-transform" || !value.match(/^matrix.*(px|em|%)/)) &&
+ !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 + ": " + value + "'");
+ }
+
+ 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) {
+ 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.
+ ok(SpecialPowers.getBoolPref("layout.css.variables.enabled"), "pref not set #1");
+ 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);
+
+SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] },
+ 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 000000000..c93ae8938
--- /dev/null
+++ b/layout/style/test/test_variable_serialization_computed.html
@@ -0,0 +1,82 @@
+<!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: /* abc ", "--a", " /* abc */"],
+ ["--a: /* abc *", "--a", " /* abc */"],
+ ["--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);
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] },
+ 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 000000000..62321eaaf
--- /dev/null
+++ b/layout/style/test/test_variable_serialization_specified.html
@@ -0,0 +1,117 @@
+<!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();
+SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] },
+ runTest);
+</script>
diff --git a/layout/style/test/test_variables.html b/layout/style/test/test_variables.html
new file mode 100644
index 000000000..a1dd341a7
--- /dev/null
+++ b/layout/style/test/test_variables.html
@@ -0,0 +1,125 @@
+<!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>
+
+<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=969756
+ var test6 = document.getElementById("test6");
+ test6.textContent = "p { font: var(--var6) hangul mongolian; font-size-adjust: none; }";
+ var declaration = test6.sheet.cssRules[0].style;
+ test6.style.color = "white";
+ is(declaration.getPropertyValue("-x-system-font"), " var(--var6) hangul mongolian");
+ },
+
+ 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 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();
+SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true ]] },
+ 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 000000000..d19a6750c
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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_units.html b/layout/style/test/test_viewport_units.html
new file mode 100644
index 000000000..d1d35b964
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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, false);
+
+</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 000000000..1a18d7ac3
--- /dev/null
+++ b/layout/style/test/test_visited_image_loading.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557287
+-->
+<head>
+ <title>Test for Bug 557287</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557287">Mozilla Bug 147777</a>
+<iframe id="display" src="visited_image_loading_frame.html"></iframe>
+<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, false);
+
+function run()
+{
+ var frame = document.getElementById("display");
+ subdoc = frame.contentDocument;
+ subwin = frame.contentWindow;
+ 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, false);
+ // 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, false);
+ var s = document.createElement("script");
+ s.src = "visited_image_loading.sjs?waitforresult";
+ document.body.appendChild(s);
+}
+
+</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 000000000..42b7c7aff
--- /dev/null
+++ b/layout/style/test/test_visited_image_loading_empty.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557287
+-->
+<head>
+ <title>Test for Bug 557287</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557287">Mozilla Bug 147777</a>
+<iframe id="display" src="visited_image_loading_frame_empty.html"></iframe>
+<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, false);
+
+function run()
+{
+ var frame = document.getElementById("display");
+ subdoc = frame.contentDocument;
+ subwin = frame.contentWindow;
+ 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, false);
+ // 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, false);
+ var s = document.createElement("script");
+ s.src = "visited_image_loading.sjs?waitforresult";
+ document.body.appendChild(s);
+}
+
+</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 000000000..1d5fe8c7f
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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, false);
+
+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 = window.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 000000000..3526b833f
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" 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, false);
+
+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 = window.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 000000000..af6e63b9f
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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",
+ "!= 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",
+ "!= 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-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-1-helper.svg lime100x100.svg",
+ "!= svg-image-visited-2-helper.svg lime100x100.svg",
+ // 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",
+];
+
+// Maintain a reference count of how many things we're waiting for until
+// we can say the tests are done.
+var gDelayCount = 0;
+function AddFinishDependency()
+ { ++gDelayCount; }
+function RemoveFinishDependency()
+ { if (--gDelayCount == 0) SimpleTest.finish(); }
+
+// 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 loadVisitedPage()
+{
+ var element = document.createElement("iframe");
+ element.addEventListener("load", visitedPageLoad, false);
+ element.src = "css-visited/visited-page.html";
+ document.body.appendChild(element);
+ AddFinishDependency();
+}
+
+function visitedPageLoad()
+{
+ for (var i = 0; i < gTests.length; ++i) {
+ startTest(i);
+ }
+ RemoveFinishDependency();
+}
+
+function takeSnapshot(iframe_element)
+{
+ return snapshotWindow(iframe_element.contentWindow, false);
+}
+
+function passes(op, shot1, shot2)
+{
+ var [correct, s1, s2] = compareSnapshots(shot1, shot2, op == "==");
+ return correct;
+}
+
+function startTest(i)
+{
+ var testLine = gTests[i];
+ var splitData = testLine.split(" ");
+ var testData =
+ { op: splitData[0], test: splitData[1], reference: splitData[2] };
+ var tries = 0;
+
+ // Maintain state specific to this test in the closure exposed to all
+ // the functions nested inside this one.
+
+ function startIframe(url)
+ {
+ var element = document.createElement("iframe");
+ element.addEventListener("load", handleLoad, false);
+ // smaller than normal reftests, but enough for these
+ element.setAttribute("style", "width: 30em; height: 10em");
+ element.src = "css-visited/" + url;
+ document.body.appendChild(element);
+ function handleLoad(event)
+ {
+ iframe.loaded = true;
+ if (iframe == reference) {
+ reference.snapshot = takeSnapshot(element);
+ }
+ var other = (iframe == test) ? reference : test;
+ if (other.loaded) {
+ // 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.
+ setTimeout(checkTest, 100);
+ }
+ }
+ function checkTest()
+ {
+ var test_snapshot = takeSnapshot(test.element);
+ if (passes(testData.op, test_snapshot, reference.snapshot)) {
+ if (tries > gMaxPassingTries) {
+ gMaxPassingTries = tries;
+ }
+ report(true);
+ } else {
+ ++tries;
+ if (tries > gMaxPassingTries * gTimeoutFactor) {
+ info("Giving up after " + tries + " tries, " +
+ "maxp=" + gMaxPassingTries +
+ "fact=" + gTimeoutFactor);
+ report(false);
+ } else {
+ // Links might not have been colored yet. Try again in 100ms.
+ setTimeout(checkTest, 100);
+ }
+ }
+ }
+ function report(result)
+ {
+ ok(result, "(" + i + ") " +
+ testData.op + " " + testData.test + " " + testData.reference);
+ RemoveFinishDependency();
+ }
+ var iframe = { element: element, loaded: false };
+
+ return iframe;
+ }
+
+ AddFinishDependency();
+ var test = startIframe(testData.test);
+ var reference = startIframe(testData.reference);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+loadVisitedPage();
+
+</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 000000000..ed655bd8a
--- /dev/null
+++ b/layout/style/test/test_webkit_device_pixel_ratio.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1176968
+-->
+<head>
+ <title>Test for Bug 1176968</title>
+ <script type="text/javascript" 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), null).visibility == "visible";
+ }
+
+ function getScreenPixelsPerCSSPixel() {
+ return SpecialPowers.DOMWindowUtils.screenPixelsPerCSSPixel;
+ }
+
+ var screenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel();
+ 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 000000000..b84d16608
--- /dev/null
+++ b/layout/style/test/test_webkit_flex_display.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1274096
+-->
+<head>
+ <title>Test for Bug 1274096</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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();
+SpecialPowers.pushPrefEnv(
+ {"set": [["layout.css.prefixes.webkit", true]]}
+).then(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/unprefixing_service_iframe.html b/layout/style/test/unprefixing_service_iframe.html
new file mode 100644
index 000000000..8edeb20dc
--- /dev/null
+++ b/layout/style/test/unprefixing_service_iframe.html
@@ -0,0 +1,394 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Helper file for testing CSS Unprefixing Service</title>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css">
+ #wrapper {
+ width: 500px;
+ }
+ </style>
+</head>
+<body>
+<div id="wrapper">
+ <div id="content"></div>
+</div>
+
+<script type="application/javascript;version=1.7">
+"use strict";
+
+/** Helper file for testing the CSS Unprefixing Service **/
+
+/* Testcases for CSS Unprefixing service.
+ *
+ * Each testcase MUST have the following fields:
+ * - decl: A CSS declaration with prefixed style, to be tested via elem.style.
+ * - targetPropName: The name of the property whose value should be
+ * affected by |decl|.
+ *
+ * And will have EITHER:
+ * - isInvalid: If set to something truthy, this implies that |decl| is
+ * invalid and should have no effect on |targetPropName|'s
+ * computed or specified style.
+ *
+ * ...OR:
+ * - expectedDOMStyleVal: The value that we expect to find in the specified
+ * style -- in elem.style.[targetPropName].
+ * - expectedCompStyleVal: The value that we expect to find in the computed
+ * style -- in getComputedStyle(...)[targetPropName]
+ * If omitted, this is assumed to be the same as
+ * expectedDOMStyleVal. (Usually they'll be the same.)
+ */
+const gTestcases = [
+ { decl: "-webkit-box-flex:5",
+ targetPropName: "flex-grow",
+ expectedDOMStyleVal: "5" },
+
+ /* If author happens to specify modern flexbox style after prefixed style,
+ make sure the modern stuff is preserved. */
+ { decl: "-webkit-box-flex:4;flex-grow:6",
+ targetPropName: "flex-grow",
+ expectedDOMStyleVal: "6" },
+
+ /* Tests for handling !important: */
+ { decl: "-webkit-box-flex:3!important;",
+ targetPropName: "flex-grow",
+ expectedDOMStyleVal: "3" },
+ { decl: "-webkit-box-flex:2!important;flex-grow:1",
+ targetPropName: "flex-grow",
+ expectedDOMStyleVal: "2" },
+
+ { decl: "-webkit-box-flex:1!important bogusText;",
+ targetPropName: "flex-grow",
+ isInvalid: true },
+
+ // Make sure we handle weird capitalization in property & value, too:
+ { decl: "-WEBKIT-BoX-aLign: baSELine",
+ targetPropName: "align-items",
+ expectedDOMStyleVal: "baseline" },
+
+ { decl: "display:-webkit-box",
+ targetPropName: "display",
+ expectedDOMStyleVal: "flex" },
+
+ { decl: "display:-webkit-box; display:-moz-box;",
+ targetPropName: "display",
+ expectedDOMStyleVal: "flex" },
+
+ { decl: "display:-webkit-foobar; display:-moz-box;",
+ targetPropName: "display",
+ expectedDOMStyleVal: "-moz-box" },
+
+ // -webkit-box-align: baseline | center | end | start | stretch
+ // ...maps to:
+ // align-items: baseline | center | flex-end | flex-start | stretch
+ { decl: "-webkit-box-align: baseline",
+ targetPropName: "align-items",
+ expectedDOMStyleVal: "baseline" },
+ { decl: "-webkit-box-align: center",
+ targetPropName: "align-items",
+ expectedDOMStyleVal: "center" },
+ { decl: "-webkit-box-align: end",
+ targetPropName: "align-items",
+ expectedDOMStyleVal: "flex-end" },
+ { decl: "-webkit-box-align: start",
+ targetPropName: "align-items",
+ expectedDOMStyleVal: "flex-start" },
+ { decl: "-webkit-box-align: stretch",
+ targetPropName: "align-items",
+ expectedDOMStyleVal: "stretch" },
+
+ // -webkit-box-direction is not supported, because it's unused & would be
+ // complicated to support. See note in CSSUnprefixingService.js for more.
+
+ // -webkit-box-ordinal-group: <number> maps directly to "order".
+ { decl: "-webkit-box-ordinal-group: 2",
+ targetPropName: "order",
+ expectedDOMStyleVal: "2" },
+ { decl: "-webkit-box-ordinal-group: 6000",
+ targetPropName: "order",
+ expectedDOMStyleVal: "6000" },
+
+ // -webkit-box-orient: horizontal | inline-axis | vertical | block-axis
+ // ...maps to:
+ // flex-direction: row | row | column | column
+ { decl: "-webkit-box-orient: horizontal",
+ targetPropName: "flex-direction",
+ expectedDOMStyleVal: "row" },
+ { decl: "-webkit-box-orient: inline-axis",
+ targetPropName: "flex-direction",
+ expectedDOMStyleVal: "row" },
+ { decl: "-webkit-box-orient: vertical",
+ targetPropName: "flex-direction",
+ expectedDOMStyleVal: "column" },
+ { decl: "-webkit-box-orient: block-axis",
+ targetPropName: "flex-direction",
+ expectedDOMStyleVal: "column" },
+
+ // -webkit-box-pack: start | center | end | justify
+ // ... maps to:
+ // justify-content: flex-start | center | flex-end | space-between
+ { decl: "-webkit-box-pack: start",
+ targetPropName: "justify-content",
+ expectedDOMStyleVal: "flex-start" },
+ { decl: "-webkit-box-pack: center",
+ targetPropName: "justify-content",
+ expectedDOMStyleVal: "center" },
+ { decl: "-webkit-box-pack: end",
+ targetPropName: "justify-content",
+ expectedDOMStyleVal: "flex-end" },
+ { decl: "-webkit-box-pack: justify",
+ targetPropName: "justify-content",
+ expectedDOMStyleVal: "space-between" },
+
+ // -webkit-transform: <transform> maps directly to "transform"
+ { decl: "-webkit-transform: matrix(1, 2, 3, 4, 5, 6)",
+ targetPropName: "transform",
+ expectedDOMStyleVal: "matrix(1, 2, 3, 4, 5, 6)" },
+
+ // -webkit-transform-origin: <value> maps directly to "transform-origin"
+ { decl: "-webkit-transform-origin: 0 0",
+ targetPropName: "transform-origin",
+ expectedDOMStyleVal: "0px 0px 0px",
+ expectedCompStyleVal: "0px 0px" },
+
+ { decl: "-webkit-transform-origin: 100% 0",
+ targetPropName: "transform-origin",
+ expectedDOMStyleVal: "100% 0px 0px",
+ expectedCompStyleVal: "500px 0px" },
+
+ // -webkit-transition: <property> maps directly to "transition"
+ { decl: "-webkit-transition: width 1s linear 2s",
+ targetPropName: "transition",
+ expectedDOMStyleVal: "width 1s linear 2s" },
+
+ // -webkit-transition **with** -webkit-prefixed property in value.
+ { decl: "-webkit-transition: -webkit-transform 1s linear 2s",
+ targetPropName: "transition",
+ expectedDOMStyleVal: "transform 1s linear 2s" },
+ // (Re-test to check that it sets the "transition-property" subproperty.)
+ { decl: "-webkit-transition: -webkit-transform 1s linear 2s",
+ targetPropName: "transition-property",
+ expectedDOMStyleVal: "transform" },
+
+ // Same as previous test, except with "-webkit-transform" in the
+ // middle of the value instead of at the beginning (still valid):
+ { decl: "-webkit-transition: 1s -webkit-transform linear 2s",
+ targetPropName: "transition",
+ expectedDOMStyleVal: "transform 1s linear 2s" },
+ { decl: "-webkit-transition: 1s -webkit-transform linear 2s",
+ targetPropName: "transition-property",
+ expectedDOMStyleVal: "transform" },
+
+ // -webkit-gradient(linear, ...) expressions:
+ { decl: "background-image: -webkit-gradient(linear,0 0,0 100%,from(rgb(1, 2, 3)),to(rgb(104, 105, 106)))",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "linear-gradient(180deg, rgb(1, 2, 3) 0%, rgb(104, 105, 106) 100%)"},
+ { decl: "background-image: -webkit-gradient(linear, left top, right bottom, from(rgb(1, 2, 3)), to(rgb(201, 202, 203)))",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "linear-gradient(135deg, rgb(1, 2, 3) 0%, rgb(201, 202, 203) 100%)"},
+
+ { decl: "background-image: -webkit-gradient(linear, left center, right center, from(rgb(1, 2, 3)), to(rgb(201, 202, 203)))",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "linear-gradient(to right, rgb(1, 2, 3) 0%, rgb(201, 202, 203) 100%)"},
+
+ { decl: "background-image: -webkit-gradient(linear, left center, right center, from(rgb(0, 0, 0)), color-stop(30%, rgb(255, 0, 0)), color-stop(60%, rgb(0, 255, 0)), to(rgb(0, 0, 255)))",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "linear-gradient(to right, rgb(0, 0, 0) 0%, rgb(255, 0, 0) 30%, rgb(0, 255, 0) 60%, rgb(0, 0, 255) 100%)"},
+
+ // -webkit-gradient(radial, ...) expressions:
+ { decl: "background-image: -webkit-gradient(radial, center center, 0, center center, 50, from(black), to(white)",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "radial-gradient(50px at center center , black 0%, white 100%)",
+ // XXXdholbert Note: unnecessary space, see bug 1160063----^
+ expectedCompStyleVal: "radial-gradient(50px, rgb(0, 0, 0) 0%, rgb(255, 255, 255) 100%)", },
+
+ { decl: "background-image: -webkit-gradient(radial, left bottom, 0, center center, 50, from(yellow), color-stop(20%, orange), color-stop(40%, red), color-stop(60%, green), color-stop(80%, blue), to(purple))",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "radial-gradient(50px at left bottom , yellow 0%, orange 20%, red 40%, green 60%, blue 80%, purple 100%)",
+ // XXXdholbert Note: unnecessary space, see bug 1160063--^
+ expectedCompStyleVal: "radial-gradient(50px at 0% 100%, rgb(255, 255, 0) 0%, rgb(255, 165, 0) 20%, rgb(255, 0, 0) 40%, rgb(0, 128, 0) 60%, rgb(0, 0, 255) 80%, rgb(128, 0, 128) 100%)" },
+
+ // -webkit-linear-gradient(...) expressions:
+ { decl: "background-image: -webkit-linear-gradient(top, blue, green)",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "linear-gradient(to bottom, blue, green)",
+ expectedCompStyleVal: "linear-gradient(rgb(0, 0, 255), rgb(0, 128, 0))", },
+
+ { decl: "background-image: -webkit-linear-gradient(left, blue, green)",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "linear-gradient(to right, blue, green)",
+ expectedCompStyleVal: "linear-gradient(to right, rgb(0, 0, 255), rgb(0, 128, 0))", },
+
+ { decl: "background-image: -webkit-linear-gradient(left bottom, blue, green)",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "linear-gradient(to right top, blue, green)",
+ expectedCompStyleVal: "linear-gradient(to top right, rgb(0, 0, 255), rgb(0, 128, 0))", },
+
+ { decl: "background-image: -webkit-linear-gradient(130deg, blue, green)",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "linear-gradient(320deg, blue, green)",
+ expectedCompStyleVal: "linear-gradient(320deg, rgb(0, 0, 255), rgb(0, 128, 0))", },
+
+ // -webkit-radial-gradient(...) expressions:
+ { decl: "background-image: -webkit-radial-gradient(#000, #fff)",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "radial-gradient(rgb(0, 0, 0), rgb(255, 255, 255))", },
+
+ { decl: "background-image: -webkit-radial-gradient(bottom right, white, black)",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "radial-gradient(at right bottom , white, black)",
+ // XXXdholbert Note: unnecessary space---------------^ see bug 1160063
+ expectedCompStyleVal: "radial-gradient(at 100% 100%, rgb(255, 255, 255), rgb(0, 0, 0))", },
+
+ // Combination of unprefixed & prefixed gradient styles in a single 'background-image' expression
+ { decl: "background-image: -webkit-linear-gradient(black, white), radial-gradient(blue, purple), -webkit-gradient(linear,0 0,0 100%,from(red),to(orange))",
+ targetPropName: "background-image",
+ expectedDOMStyleVal: "linear-gradient(black, white), radial-gradient(blue, purple), linear-gradient(180deg, red 0%, orange 100%)",
+ expectedCompStyleVal: "linear-gradient(rgb(0, 0, 0), rgb(255, 255, 255)), radial-gradient(rgb(0, 0, 255), rgb(128, 0, 128)), linear-gradient(180deg, rgb(255, 0, 0) 0%, rgb(255, 165, 0) 100%)", },
+
+];
+
+function getComputedStyleWrapper(elem, prop)
+{
+ return window.getComputedStyle(elem, null).getPropertyValue(prop);
+}
+
+// Shims for "is()" and "ok()", which defer to parent window using postMessage:
+function is(aActual, aExpected, aDesc)
+{
+ // Add URL to description:
+ aDesc += " (iframe url: '" + window.location + "')";
+
+ window.parent.postMessage({type: "is",
+ actual: aActual,
+ expected: aExpected,
+ desc: aDesc}, "*");
+}
+
+function ok(aCondition, aDesc)
+{
+ // Add URL to description:
+ aDesc += " (iframe url: '" + window.location + "')";
+
+ window.parent.postMessage({type: "ok",
+ condition: aCondition,
+ desc: aDesc}, "*");
+}
+
+// Main test function to use, to test a given unprefixed CSS property.
+// The argument aTestcase should be an entry from gTestcases above.
+function runOneTest(aTestcase)
+{
+ let elem = document.getElementById("content");
+
+ // (self-test/sanity-check:)
+ if (!aTestcase.decl || !aTestcase.targetPropName) {
+ ok(false, "Bug in test; missing 'decl' or 'targetPropName' field");
+ }
+
+ // Populate testcase's implied fields:
+ if (aTestcase.isInvalid) {
+ // (self-test/sanity-check:)
+ if (aTestcase.expectedDOMStyleVal || aTestcase.expectedCompStyleVal) {
+ ok(false, "Bug in test; testcase w/ 'isInvalid' field also provided " +
+ "an expected*Val field, but should not have");
+ }
+ aTestcase.expectedDOMStyleVal = '';
+ aTestcase.expectedCompStyleVal = // initial computed style:
+ getComputedStyleWrapper(elem, aTestcase.targetPropName);
+ } else {
+ // (self-test/sanity-check:)
+ if (!aTestcase.expectedDOMStyleVal) {
+ ok(false, "Bug in test; testcase must provide expectedDOMStyleVal " +
+ "(or set isInvalid if it's testing an invalid decl)");
+ }
+ // If expected computed style is unspecified, we assume it should match
+ // expected DOM style:
+ if (!aTestcase.expectedCompStyleVal) {
+ aTestcase.expectedCompStyleVal = aTestcase.expectedDOMStyleVal;
+ }
+ }
+
+ elem.setAttribute("style", aTestcase.decl);
+
+ // Check that DOM elem.style has the expected value:
+ is(elem.style[aTestcase.targetPropName], aTestcase.expectedDOMStyleVal,
+ "Checking if CSS Unprefixing Service produced expected result " +
+ "in elem.style['" + aTestcase.targetPropName + "'] " +
+ "when given decl '" + aTestcase.decl + "'");
+
+ // Check that computed style has the expected value:
+ // (only for longhand properties; shorthands aren't in computed style)
+ if (gCSSProperties[aTestcase.targetPropName].type == CSS_TYPE_LONGHAND) {
+ let computedValue = getComputedStyleWrapper(elem, aTestcase.targetPropName);
+ is(computedValue, aTestcase.expectedCompStyleVal,
+ "Checking if CSS Unprefixing Service produced expected result " +
+ "in computed value of property '" + aTestcase.targetPropName + "' " +
+ "when given decl '" + aTestcase.decl + "'");
+ }
+
+ elem.removeAttribute("style");
+}
+
+// Function used to quickly test that unprefixing is off:
+function testUnprefixingDisabled()
+{
+ let elem = document.getElementById("content");
+
+ let initialFlexGrow = getComputedStyleWrapper(elem, "flex-grow");
+ elem.setAttribute("style", "-webkit-box-flex:5");
+ is(getComputedStyleWrapper(elem, "flex-grow"), initialFlexGrow,
+ "'-webkit-box-flex' shouldn't affect computed 'flex-grow' " +
+ "when CSS Unprefixing Service is inactive");
+
+ let initialDisplay = getComputedStyleWrapper(elem, "display");
+ elem.setAttribute("style", "display:-webkit-box");
+ is(getComputedStyleWrapper(elem, "display"), initialDisplay,
+ "'display:-webkit-box' shouldn't affect computed 'display' " +
+ "when CSS Unprefixing Service is inactive");
+
+ elem.style.display = "-webkit-box";
+ is(getComputedStyleWrapper(elem, "display"), initialDisplay,
+ "Setting elem.style.display to '-webkit-box' shouldn't affect computed " +
+ "'display' when CSS Unprefixing Service is inactive");
+}
+
+// Focused test that CSS Unprefixing Service is functioning properly
+// on direct tweaks to elem.style.display:
+function testStyleDisplayDirectly()
+{
+ let elem = document.getElementById("content");
+ elem.style.display = "-webkit-box";
+
+ is(elem.style.display, "flex",
+ "Setting elem.style.display to '-webkit-box' should produce 'flex' " +
+ "in elem.style.display, when CSS Unprefixing Service is active");
+ is(getComputedStyleWrapper(elem, "display"), "flex",
+ "Setting elem.style.display to '-webkit-box' should produce 'flex' " +
+ "in computed style, when CSS Unprefixing Service is active");
+
+ // clean up:
+ elem.style.display = "";
+}
+
+function startTest()
+{
+ if (window.location.hash === "#expectEnabled") {
+ testStyleDisplayDirectly();
+ gTestcases.forEach(runOneTest);
+ } else if (window.location.hash === "#expectDisabled") {
+ testUnprefixingDisabled();
+ } else {
+ ok(false,
+ "Need a recognized 'window.location.hash' to indicate expectation. " +
+ "Got: '" + window.location.hash + "'");
+ }
+ window.parent.postMessage({type: "testComplete"}, "*");
+}
+
+startTest();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/unprefixing_service_utils.js b/layout/style/test/unprefixing_service_utils.js
new file mode 100644
index 000000000..cd17d20d0
--- /dev/null
+++ b/layout/style/test/unprefixing_service_utils.js
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Shared data & functionality used in tests for CSS Unprefixing Service.
+
+// Whitelisted hosts:
+// (per implementation of nsPrincipal::IsOnCSSUnprefixingWhitelist())
+var gWhitelistedHosts = [
+ // test1.example.org is on the whitelist.
+ "test1.example.org",
+ // test2.example.org is on the "allow all subdomains" whitelist.
+ "test2.example.org",
+ "sub1.test2.example.org",
+ "sub2.test2.example.org"
+];
+
+// *NOT* whitelisted hosts:
+var gNotWhitelistedHosts = [
+ // Though test1.example.org is on the whitelist, its subdomains are not.
+ "sub1.test1.example.org",
+ // mochi.test is not on the whitelist.
+ "mochi.test:8888"
+];
+
+// Names of prefs:
+const PREF_UNPREFIXING_SERVICE =
+ "layout.css.unprefixing-service.enabled";
+const PREF_INCLUDE_TEST_DOMAINS =
+ "layout.css.unprefixing-service.include-test-domains";
+
+// Helper-function to make unique URLs in testHost():
+var gCounter = 0;
+function getIncreasingCounter() {
+ return gCounter++;
+}
+
+// This function tests a particular host in our iframe.
+// @param aHost The host to be tested
+// @param aExpectEnabled Should we expect unprefixing to be enabled for host?
+function testHost(aHost, aExpectEnabled) {
+ // Build the URL:
+ let url = window.location.protocol; // "http:" or "https:"
+ url += "//";
+ url += aHost;
+
+ // Append the path-name, up to the actual filename (the final "/"):
+ const re = /(.*\/).*/;
+ url += window.location.pathname.replace(re, "$1");
+ url += IFRAME_TESTFILE;
+ // In case this is the same URL as last time, we add "?N" for some unique N,
+ // to make each URL different, so that the iframe actually (re)loads:
+ url += "?" + getIncreasingCounter();
+ // We give the URL a #suffix to indicate to the test whether it should expect
+ // that unprefixing is enabled or disabled:
+ url += (aExpectEnabled ? "#expectEnabled" : "#expectDisabled");
+
+ let iframe = document.getElementById("testIframe");
+ iframe.contentWindow.location = url;
+ // The iframe will report its results back via postMessage.
+ // Our caller had better have set up a postMessage listener.
+}
+
+// Register a postMessage() handler, to allow our cross-origin iframe to
+// communicate back to the main page's mochitest functionality.
+// The handler expects postMessage to be called with an object like:
+// { type: ["is"|"ok"|"testComplete"], ... }
+// The "is" and "ok" types will trigger the corresponding function to be
+// called in the main page, with named arguments provided in the payload.
+// The "testComplete" type will trigger the passed-in aTestCompleteCallback
+// function to be invoked (e.g. to advance to the next testcase, or to finish
+// the overall test, as-appropriate).
+function registerPostMessageListener(aTestCompleteCallback) {
+ let receiveMessage = function(event) {
+ if (event.data.type === "is") {
+ is(event.data.actual, event.data.expected, event.data.desc);
+ } else if (event.data.type === "ok") {
+ ok(event.data.condition, event.data.desc);
+ } else if (event.data.type === "testComplete") {
+ aTestCompleteCallback();
+ } else {
+ ok(false, "unrecognized data in postMessage call");
+ }
+ };
+
+ window.addEventListener("message", receiveMessage, false);
+}
diff --git a/layout/style/test/unstyled-frame.css b/layout/style/test/unstyled-frame.css
new file mode 100644
index 000000000..e69de29bb
--- /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 000000000..833b4f112
--- /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 000000000..82767f9b2
--- /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 000000000..86b7c54ac
--- /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 000000000..fd71a3cd3
--- /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 000000000..ad1dac758
--- /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 000000000..31da176e4
--- /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 000000000..880305439
--- /dev/null
+++ b/layout/style/test/visited_image_loading.sjs
@@ -0,0 +1,60 @@
+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 000000000..f919f5eb7
--- /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 000000000..21579bb9c
--- /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/test/xbl_bindings.xml b/layout/style/test/xbl_bindings.xml
new file mode 100644
index 000000000..90d68ae06
--- /dev/null
+++ b/layout/style/test/xbl_bindings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <binding id="onedivchild">
+ <content><html:div class="anondiv" /></content>
+ </binding>
+
+</bindings>
diff --git a/layout/style/test/xpcshell.ini b/layout/style/test/xpcshell.ini
new file mode 100644
index 000000000..5019e0a48
--- /dev/null
+++ b/layout/style/test/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[test_csslexer.js]
diff --git a/layout/style/xbl-marquee/jar.mn b/layout/style/xbl-marquee/jar.mn
new file mode 100644
index 000000000..9247cb4a1
--- /dev/null
+++ b/layout/style/xbl-marquee/jar.mn
@@ -0,0 +1,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/.
+
+toolkit.jar:
+% content xbl-marquee %content/xbl-marquee/ contentaccessible=yes
+ content/xbl-marquee/xbl-marquee.xml
+ content/xbl-marquee/xbl-marquee.css
diff --git a/layout/style/xbl-marquee/moz.build b/layout/style/xbl-marquee/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/layout/style/xbl-marquee/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/layout/style/xbl-marquee/xbl-marquee.css b/layout/style/xbl-marquee/xbl-marquee.css
new file mode 100644
index 000000000..e6d3ee94b
--- /dev/null
+++ b/layout/style/xbl-marquee/xbl-marquee.css
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* PRINT ONLY rules */
+@media print {
+
+ marquee > * > * {
+ margin: 0 !important;
+ padding: 0 !important;
+ } /* This hack is needed until bug 119078 gets fixed */
+}
diff --git a/layout/style/xbl-marquee/xbl-marquee.xml b/layout/style/xbl-marquee/xbl-marquee.xml
new file mode 100644
index 000000000..bb837624d
--- /dev/null
+++ b/layout/style/xbl-marquee/xbl-marquee.xml
@@ -0,0 +1,733 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="marqueeBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+
+ <binding id="marquee" bindToUntrustedContent="true">
+
+ <resources>
+ <stylesheet src="chrome://xbl-marquee/content/xbl-marquee.css"/>
+ </resources>
+ <implementation>
+
+ <property name="scrollAmount" exposeToUntrustedContent="true">
+ <getter>
+ <![CDATA[
+ this._mutationActor(this._mutationObserver.takeRecords());
+ return this._scrollAmount;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var val = parseInt(val);
+ if (val < 0) {
+ return;
+ }
+ if (isNaN(val)) {
+ val = 0;
+ }
+ this.setAttribute("scrollamount", val);
+ ]]>
+ </setter>
+ </property>
+
+ <property name="scrollDelay" exposeToUntrustedContent="true">
+ <getter>
+ <![CDATA[
+ this._mutationActor(this._mutationObserver.takeRecords());
+ var val = parseInt(this.getAttribute("scrolldelay"));
+
+ if (val <= 0 || isNaN(val)) {
+ return this._scrollDelay;
+ }
+
+ return val;
+ ]]>
+ </getter>
+ <setter>
+ var val = parseInt(val);
+ if (val > 0 ) {
+ this.setAttribute("scrolldelay", val);
+ }
+ </setter>
+ </property>
+
+ <property name="trueSpeed" exposeToUntrustedContent="true">
+ <getter>
+ <![CDATA[
+ if (!this.hasAttribute("truespeed")) {
+ return false;
+ }
+
+ return true;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val) {
+ this.setAttribute("truespeed", "");
+ } else {
+ this.removeAttribute('truespeed');
+ }
+ ]]>
+ </setter>
+ </property>
+
+ <property name="direction" exposeToUntrustedContent="true">
+ <getter>
+ this._mutationActor(this._mutationObserver.takeRecords());
+ return this._direction;
+ </getter>
+ <setter>
+ <![CDATA[
+ if (typeof val == 'string') {
+ val = val.toLowerCase();
+ } else {
+ return;
+ }
+ if (val != 'left' && val != 'right' && val != 'up' && val != 'down') {
+ val = 'left';
+ }
+
+ this.setAttribute("direction", val);
+ ]]>
+ </setter>
+ </property>
+
+ <property name="behavior" exposeToUntrustedContent="true">
+ <getter>
+ this._mutationActor(this._mutationObserver.takeRecords());
+ return this._behavior;
+ </getter>
+ <setter>
+ if (typeof val == 'string') {
+ val = val.toLowerCase();
+ }
+ if (val == "alternate" || val == "slide" || val == 'scroll') {
+ this.setAttribute("behavior", val);
+ }
+ </setter>
+ </property>
+
+
+ <property name="loop" exposeToUntrustedContent="true">
+ <getter>
+ <![CDATA[
+ this._mutationActor(this._mutationObserver.takeRecords());
+ return this._loop;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var val = parseInt(val);
+ if (val == -1 || val > 0) {
+ this.setAttribute("loop", val);
+ }
+ ]]>
+ </setter>
+ </property>
+
+
+ <property name="onstart" exposeToUntrustedContent="true">
+ <getter>
+ return this.getAttribute("onstart");
+ </getter>
+ <setter>
+ this._setEventListener("start", val, true);
+ this.setAttribute("onstart", val);
+ </setter>
+ </property>
+
+ <property name="onfinish" exposeToUntrustedContent="true">
+ <getter>
+ return this.getAttribute("onfinish");
+ </getter>
+ <setter>
+ this._setEventListener("finish", val, true);
+ this.setAttribute("onfinish", val);
+ </setter>
+ </property>
+
+ <property name="onbounce" exposeToUntrustedContent="true">
+ <getter>
+ return this.getAttribute("onbounce");
+ </getter>
+ <setter>
+ this._setEventListener("bounce", val, true);
+ this.setAttribute("onbounce", val);
+ </setter>
+ </property>
+
+ <property name="outerDiv"
+ onget="return document.getAnonymousNodes(this)[0]"
+ />
+
+ <property name="innerDiv"
+ onget="return document.getAnonymousElementByAttribute(this, 'class', 'innerDiv');"
+ />
+
+ <property name="height" exposeToUntrustedContent="true"
+ onget="return this.getAttribute('height');"
+ onset="this.setAttribute('height', val);"
+ />
+
+ <property name="width" exposeToUntrustedContent="true"
+ onget="return this.getAttribute('width');"
+ onset="this.setAttribute('width', val);"
+ />
+
+ <method name="_set_scrollDelay">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ aValue = parseInt(aValue);
+ if (aValue <= 0) {
+ return;
+ } else if (isNaN(aValue)) {
+ this._scrollDelay = 85;
+ } else if (aValue < 60) {
+ if (this.trueSpeed == true) {
+ this._scrollDelay = aValue;
+ } else {
+ this._scrollDelay = 60;
+ }
+ } else {
+ this._scrollDelay = aValue;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_set_scrollAmount">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ aValue = parseInt(aValue);
+ if (isNaN(aValue)) {
+ this._scrollAmount = 6;
+ } else if (aValue < 0) {
+ return;
+ } else {
+ this._scrollAmount = aValue;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_set_behavior">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (typeof aValue == 'string') {
+ aValue = aValue.toLowerCase();
+ }
+ if (aValue != 'alternate' && aValue != 'slide' && aValue != 'scroll') {
+ this._behavior = 'scroll';
+ } else {
+ this._behavior = aValue;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_set_direction">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (typeof aValue == 'string') {
+ aValue = aValue.toLowerCase();
+ }
+ if (aValue != 'left' && aValue != 'right' && aValue != 'up' && aValue != 'down') {
+ aValue = 'left';
+ }
+
+ if (aValue != this._direction) {
+ this.startNewDirection = true;
+ }
+ this._direction = aValue;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_set_loop">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var aValue = parseInt(aValue);
+ if (aValue == 0) {
+ return;
+ }
+ if (isNaN(aValue) || aValue <= -1) {
+ aValue = -1;
+ }
+ this._loop = aValue;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setEventListener">
+ <parameter name="aName"/>
+ <parameter name="aValue"/>
+ <parameter name="aIgnoreNextCall"/>
+ <body>
+ <![CDATA[
+ // _setEventListener is only used for setting the attribute event
+ // handlers, which we want to ignore if our document is sandboxed
+ // without the allow-scripts keyword.
+ if (document.hasScriptsBlockedBySandbox) {
+ return true;
+ }
+
+ // attribute event handlers should only be added if the
+ // document's CSP allows it.
+ if (!document.inlineScriptAllowedByCSP) {
+ return true;
+ }
+
+ if (this._ignoreNextCall) {
+ return this._ignoreNextCall = false;
+ }
+
+ if (aIgnoreNextCall) {
+ this._ignoreNextCall = true;
+ }
+
+ if (typeof this["_on" + aName] == 'function') {
+ this.removeEventListener(aName, this["_on" + aName], false);
+ }
+
+ switch (typeof aValue)
+ {
+ case "function":
+ this["_on" + aName] = aValue;
+ this.addEventListener(aName, this["_on" + aName], false);
+ break;
+
+ case "string":
+ if (!aIgnoreNextCall) {
+ try {
+ // Function Xrays make this simple and safe. \o/
+ this["_on" + aName] = new window.Function("event", aValue);
+ }
+ catch(e) {
+ return false;
+ }
+ this.addEventListener(aName, this["_on" + aName], false);
+ }
+ else {
+ this["_on" + aName] = aValue;
+ }
+ break;
+
+ case "object":
+ this["_on" + aName] = aValue;
+ break;
+
+ default:
+ this._ignoreNextCall = false;
+ throw new Error("Invalid argument for Marquee::on" + aName);
+ }
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_fireEvent">
+ <parameter name="aName"/>
+ <parameter name="aBubbles"/>
+ <parameter name="aCancelable"/>
+ <body>
+ <![CDATA[
+ var e = document.createEvent("Events");
+ e.initEvent(aName, aBubbles, aCancelable);
+ this.dispatchEvent(e);
+ ]]>
+ </body>
+ </method>
+
+ <method name="start" exposeToUntrustedContent="true">
+ <body>
+ <![CDATA[
+ if (this.runId == 0) {
+ var myThis = this;
+ var lambda = function myTimeOutFunction(){myThis._doMove(false);}
+ this.runId = window.setTimeout(lambda, this._scrollDelay - this._deltaStartStop);
+ this._deltaStartStop = 0;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="stop" exposeToUntrustedContent="true">
+ <body>
+ <![CDATA[
+ if (this.runId != 0) {
+ this._deltaStartStop = Date.now()- this._lastMoveDate;
+ clearTimeout(this.runId);
+ }
+
+ this.runId = 0;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_doMove">
+ <parameter name="aResetPosition"/>
+ <body>
+ <![CDATA[
+ this._lastMoveDate = Date.now();
+
+ //startNewDirection is true at first load and whenever the direction is changed
+ if (this.startNewDirection) {
+ this.startNewDirection = false; //we only want this to run once every scroll direction change
+
+ var corrvalue = 0;
+
+ switch (this._direction)
+ {
+ case "up":
+ var height = document.defaultView.getComputedStyle(this, "").height;
+ this.outerDiv.style.height = height;
+ if (this.originalHeight > this.outerDiv.offsetHeight) {
+ corrvalue = this.originalHeight - this.outerDiv.offsetHeight;
+ }
+ this.innerDiv.style.padding = height + " 0";
+ this.dirsign = 1;
+ this.startAt = (this._behavior == 'alternate') ? (this.originalHeight - corrvalue) : 0;
+ this.stopAt = (this._behavior == 'alternate' || this._behavior == 'slide') ?
+ (parseInt(height) + corrvalue) : (this.originalHeight + parseInt(height));
+ break;
+
+ case "down":
+ var height = document.defaultView.getComputedStyle(this, "").height;
+ this.outerDiv.style.height = height;
+ if (this.originalHeight > this.outerDiv.offsetHeight) {
+ corrvalue = this.originalHeight - this.outerDiv.offsetHeight;
+ }
+ this.innerDiv.style.padding = height + " 0";
+ this.dirsign = -1;
+ this.startAt = (this._behavior == 'alternate') ?
+ (parseInt(height) + corrvalue) : (this.originalHeight + parseInt(height));
+ this.stopAt = (this._behavior == 'alternate' || this._behavior == 'slide') ?
+ (this.originalHeight - corrvalue) : 0;
+ break;
+
+ case "right":
+ if (this.innerDiv.offsetWidth > this.outerDiv.offsetWidth) {
+ corrvalue = this.innerDiv.offsetWidth - this.outerDiv.offsetWidth;
+ }
+ this.dirsign = -1;
+ this.stopAt = (this._behavior == 'alternate' || this._behavior == 'slide') ?
+ (this.innerDiv.offsetWidth - corrvalue) : 0;
+ this.startAt = this.outerDiv.offsetWidth + ((this._behavior == 'alternate') ?
+ corrvalue : (this.innerDiv.offsetWidth + this.stopAt));
+ break;
+
+ case "left":
+ default:
+ if (this.innerDiv.offsetWidth > this.outerDiv.offsetWidth) {
+ corrvalue = this.innerDiv.offsetWidth - this.outerDiv.offsetWidth;
+ }
+ this.dirsign = 1;
+ this.startAt = (this._behavior == 'alternate') ? (this.innerDiv.offsetWidth - corrvalue) : 0;
+ this.stopAt = this.outerDiv.offsetWidth +
+ ((this._behavior == 'alternate' || this._behavior == 'slide') ?
+ corrvalue : (this.innerDiv.offsetWidth + this.startAt));
+ }
+
+ if (aResetPosition) {
+ this.newPosition = this.startAt;
+ this._fireEvent("start", false, false);
+ }
+ } //end if
+
+ this.newPosition = this.newPosition + (this.dirsign * this._scrollAmount);
+
+ if ((this.dirsign == 1 && this.newPosition > this.stopAt) ||
+ (this.dirsign == -1 && this.newPosition < this.stopAt))
+ {
+ switch (this._behavior)
+ {
+ case 'alternate':
+ // lets start afresh
+ this.startNewDirection = true;
+
+ // swap direction
+ const swap = {left: "right", down: "up", up: "down", right: "left"};
+ this._direction = swap[this._direction];
+ this.newPosition = this.stopAt;
+
+ if ((this._direction == "up") || (this._direction == "down")) {
+ this.outerDiv.scrollTop = this.newPosition;
+ } else {
+ this.outerDiv.scrollLeft = this.newPosition;
+ }
+
+ if (this._loop != 1) {
+ this._fireEvent("bounce", false, true);
+ }
+ break;
+
+ case 'slide':
+ if (this._loop > 1) {
+ this.newPosition = this.startAt;
+ }
+ break;
+
+ default:
+ this.newPosition = this.startAt;
+
+ if ((this._direction == "up") || (this._direction == "down")) {
+ this.outerDiv.scrollTop = this.newPosition;
+ } else {
+ this.outerDiv.scrollLeft = this.newPosition;
+ }
+
+ //dispatch start event, even when this._loop == 1, comp. with IE6
+ this._fireEvent("start", false, false);
+ }
+
+ if (this._loop > 1) {
+ this._loop--;
+ } else if (this._loop == 1) {
+ if ((this._direction == "up") || (this._direction == "down")) {
+ this.outerDiv.scrollTop = this.stopAt;
+ } else {
+ this.outerDiv.scrollLeft = this.stopAt;
+ }
+ this.stop();
+ this._fireEvent("finish", false, true);
+ return;
+ }
+ }
+ else {
+ if ((this._direction == "up") || (this._direction == "down")) {
+ this.outerDiv.scrollTop = this.newPosition;
+ } else {
+ this.outerDiv.scrollLeft = this.newPosition;
+ }
+ }
+
+ var myThis = this;
+ var lambda = function myTimeOutFunction(){myThis._doMove(false);}
+ this.runId = window.setTimeout(lambda, this._scrollDelay);
+ ]]>
+ </body>
+ </method>
+
+ <method name="init">
+ <body>
+ <![CDATA[
+ this.stop();
+
+ if ((this._direction != "up") && (this._direction != "down")) {
+ var width = window.getComputedStyle(this, "").width;
+ this.innerDiv.parentNode.style.margin = '0 ' + width;
+
+ //XXX Adding the margin sometimes causes the marquee to widen,
+ // see testcase from bug bug 364434:
+ // https://bugzilla.mozilla.org/attachment.cgi?id=249233
+ // Just add a fixed width with current marquee's width for now
+ if (width != window.getComputedStyle(this, "").width) {
+ var width = window.getComputedStyle(this, "").width;
+ this.outerDiv.style.width = width;
+ this.innerDiv.parentNode.style.margin = '0 ' + width;
+ }
+ }
+ else {
+ // store the original height before we add padding
+ this.innerDiv.style.padding = 0;
+ this.originalHeight = this.innerDiv.offsetHeight;
+ }
+
+ this._doMove(true);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_mutationActor">
+ <parameter name="aMutations"/>
+ <body>
+ <![CDATA[
+ while (aMutations.length > 0) {
+ var mutation = aMutations.shift();
+ var attrName = mutation.attributeName.toLowerCase();
+ var oldValue = mutation.oldValue;
+ var target = mutation.target;
+ var newValue = target.getAttribute(attrName);
+
+ if (oldValue != newValue) {
+ switch (attrName) {
+ case "loop":
+ target._set_loop(newValue);
+ if (target.rundId == 0) {
+ target.start();
+ }
+ break;
+ case "scrollamount":
+ target._set_scrollAmount(newValue);
+ break;
+ case "scrolldelay":
+ target._set_scrollDelay(newValue);
+ target.stop();
+ target.start();
+ break;
+ case "truespeed":
+ //needed to update target._scrollDelay
+ var myThis = target;
+ var lambda = function() {myThis._set_scrollDelay(myThis.getAttribute('scrolldelay'));}
+ window.setTimeout(lambda, 0);
+ break;
+ case "behavior":
+ target._set_behavior(newValue);
+ target.startNewDirection = true;
+ if ((oldValue == "slide" && target.newPosition == target.stopAt) ||
+ newValue == "alternate" || newValue == "slide") {
+ target.stop();
+ target._doMove(true);
+ }
+ break;
+ case "direction":
+ if (!newValue) {
+ newValue = "left";
+ }
+ target._set_direction(newValue);
+ break;
+ case "width":
+ case "height":
+ target.startNewDirection = true;
+ break;
+ case "onstart":
+ target._setEventListener("start", newValue);
+ break;
+ case "onfinish":
+ target._setEventListener("finish", newValue);
+ break;
+ case "onbounce":
+ target._setEventListener("bounce", newValue);
+ break;
+ }
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ // Set up state.
+ this._scrollAmount = 6;
+ this._scrollDelay = 85;
+ this._direction = "left";
+ this._behavior = "scroll";
+ this._loop = -1;
+ this.dirsign = 1;
+ this.startAt = 0;
+ this.stopAt = 0;
+ this.newPosition = 0;
+ this.runId = 0;
+ this.originalHeight = 0;
+ this.startNewDirection = true;
+
+ // hack needed to fix js error, see bug 386470
+ var myThis = this;
+ var lambda = function myScopeFunction() { if (myThis.init) myThis.init(); }
+
+ this._set_direction(this.getAttribute('direction'));
+ this._set_behavior(this.getAttribute('behavior'));
+ this._set_scrollDelay(this.getAttribute('scrolldelay'));
+ this._set_scrollAmount(this.getAttribute('scrollamount'));
+ this._set_loop(this.getAttribute('loop'));
+ this._setEventListener("start", this.getAttribute("onstart"));
+ this._setEventListener("finish", this.getAttribute("onfinish"));
+ this._setEventListener("bounce", this.getAttribute("onbounce"));
+ this.startNewDirection = true;
+
+ this._mutationObserver = new MutationObserver(this._mutationActor);
+ this._mutationObserver.observe(this, { attributes: true,
+ attributeOldValue: true,
+ attributeFilter: ['loop', 'scrollamount', 'scrolldelay', '', 'truespeed', 'behavior',
+ 'direction', 'width', 'height', 'onstart', 'onfinish', 'onbounce'] });
+
+ // init needs to be run after the page has loaded in order to calculate
+ // the correct height/width
+ if (document.readyState == "complete") {
+ lambda();
+ } else {
+ window.addEventListener("load", lambda, false);
+ }
+ ]]>
+ </constructor>
+ </implementation>
+
+ </binding>
+
+ <binding id="marquee-horizontal" bindToUntrustedContent="true"
+ extends="chrome://xbl-marquee/content/xbl-marquee.xml#marquee"
+ inheritstyle="false">
+
+ <!-- White-space isn't allowed because a marquee could be
+ inside 'white-space: pre' -->
+ <content>
+ <html:div style="display: -moz-box; overflow: hidden; width: -moz-available;"
+ ><html:div style="display: -moz-box;"
+ ><html:div class="innerDiv" style="display: table; border-spacing: 0;"
+ ><html:div
+ ><children
+ /></html:div
+ ></html:div
+ ></html:div
+ ></html:div>
+ </content>
+
+ </binding>
+
+ <binding id="marquee-vertical" bindToUntrustedContent="true"
+ extends="chrome://xbl-marquee/content/xbl-marquee.xml#marquee"
+ inheritstyle="false">
+
+ <!-- White-space isn't allowed because a marquee could be
+ inside 'white-space: pre' -->
+ <content>
+ <html:div style="overflow: hidden; width: -moz-available;"
+ ><html:div class="innerDiv"
+ ><children
+ /></html:div
+ ></html:div>
+ </content>
+
+ </binding>
+
+ <binding id="marquee-horizontal-editable" bindToUntrustedContent="true"
+ inheritstyle="false">
+
+ <!-- White-space isn't allowed because a marquee could be
+ inside 'white-space: pre' -->
+ <content>
+ <html:div style="display: inline-block; overflow: auto; width: -moz-available;"
+ ><children
+ /></html:div>
+ </content>
+
+ </binding>
+
+ <binding id="marquee-vertical-editable" bindToUntrustedContent="true"
+ inheritstyle="false">
+
+ <!-- White-space isn't allowed because a marquee could be
+ inside 'white-space: pre' -->
+ <content>
+ <html:div style="overflow: auto; height: inherit; width: -moz-available;"
+ ><children/></html:div>
+ </content>
+
+ </binding>
+
+</bindings>