ComputedStyle.cpp (21171B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* the interface (to internal code) for retrieving computed style data */ 8 9 #include "mozilla/ComputedStyle.h" 10 11 #include "RubyUtils.h" 12 #include "mozilla/ComputedStyleInlines.h" 13 #include "mozilla/DebugOnly.h" 14 #include "mozilla/Maybe.h" 15 #include "mozilla/Preferences.h" 16 #include "mozilla/ProfilerLabels.h" 17 #include "mozilla/ReflowInput.h" 18 #include "mozilla/ToString.h" 19 #include "mozilla/dom/Document.h" 20 #include "nsCOMPtr.h" 21 #include "nsCSSAnonBoxes.h" 22 #include "nsCSSPseudoElements.h" 23 #include "nsCSSVisitedDependentPropList.h" 24 #include "nsCoord.h" 25 #include "nsFontMetrics.h" 26 #include "nsLayoutUtils.h" 27 #include "nsPresContext.h" 28 #include "nsPrintfCString.h" 29 #include "nsString.h" 30 #include "nsStyleConsts.h" 31 #include "nsStyleStruct.h" 32 #include "nsStyleStructInlines.h" 33 #include "nsStyleStructList.h" 34 #include "nsWindowSizes.h" 35 36 // Ensure the binding function declarations in ComputedStyle.h matches 37 // those in ServoBindings.h. 38 #include "mozilla/ServoBindings.h" 39 40 namespace mozilla { 41 42 ComputedStyle::ComputedStyle(PseudoStyleType aPseudoType, 43 ServoComputedDataForgotten aComputedValues) 44 : mSource(aComputedValues), mPseudoType(aPseudoType) {} 45 46 // If a struct returned nsChangeHint_UpdateContainingBlock, that means that one 47 // property's influence on whether we're a containing block for abs-pos or 48 // fixed-pos elements has changed. 49 // 50 // However, we only need to return the hint if the overall computation of 51 // whether we establish a containing block has really changed. 52 static bool ContainingBlockMayHaveChanged(const ComputedStyle& aOldStyle, 53 const ComputedStyle& aNewStyle) { 54 const auto& oldDisp = *aOldStyle.StyleDisplay(); 55 const auto& newDisp = *aNewStyle.StyleDisplay(); 56 57 if (oldDisp.IsPositionedStyle() != newDisp.IsPositionedStyle()) { 58 // XXX This check can probably be moved to after the fixedCB check, since 59 // IsPositionedStyle() is also only relevant for non-svg text frame 60 // subtrees. 61 return true; 62 } 63 64 const bool fixedCB = aOldStyle.IsFixedPosContainingBlockForNonSVGTextFrames(); 65 if (fixedCB != aNewStyle.IsFixedPosContainingBlockForNonSVGTextFrames()) { 66 return true; 67 } 68 // If we were both before and after a fixed-pos containing-block that means 69 // that everything else doesn't matter, since all the other conditions are a 70 // subset of this. 71 if (fixedCB) { 72 return false; 73 } 74 75 // Note that neither of these two following sets of frames 76 // (transform-supporting and layout-and-paint-supporting frames) is a subset 77 // of the other, because table frames support contain: layout/paint but not 78 // transforms (which are instead inherited to the table wrapper), and quite a 79 // few frame types support transforms but not contain: layout/paint (e.g., 80 // table rows and row groups, many SVG frames). 81 if (oldDisp.IsFixedPosContainingBlockForTransformSupportingFrames() != 82 newDisp.IsFixedPosContainingBlockForTransformSupportingFrames()) { 83 return true; 84 } 85 if (oldDisp 86 .IsFixedPosContainingBlockForContainLayoutAndPaintSupportingFrames() != 87 newDisp 88 .IsFixedPosContainingBlockForContainLayoutAndPaintSupportingFrames()) { 89 return true; 90 } 91 return false; 92 } 93 94 nsChangeHint ComputedStyle::CalcStyleDifference(const ComputedStyle& aNewStyle, 95 uint32_t* aEqualStructs) const { 96 AUTO_PROFILER_LABEL_HOT("ComputedStyle::CalcStyleDifference", LAYOUT); 97 static_assert(StyleStructConstants::kStyleStructCount <= 32, 98 "aEqualStructs is not big enough"); 99 100 *aEqualStructs = 0; 101 102 nsChangeHint hint = nsChangeHint(0); 103 // We must always ensure that we populate the structs on the new style 104 // context that are filled in on the old context, so that if we get 105 // two style changes in succession, the second of which causes a real 106 // style change, the PeekStyleData doesn't return null (implying that 107 // nobody ever looked at that struct's data). In other words, we 108 // can't skip later structs if we get a big change up front, because 109 // we could later get a small change in one of those structs that we 110 // don't want to miss. 111 112 DebugOnly<uint32_t> structsFound = 0; 113 114 DebugOnly<int> styleStructCount = 0; 115 116 // Servo's optimization to stop the cascade when there are no style changes 117 // that children need to be recascade for relies on comparing all of the 118 // structs, not just those that are returned from PeekStyleData, although 119 // if PeekStyleData does return null we could avoid to accumulate any change 120 // hints for those structs. 121 // 122 // FIXME(emilio): Reintroduce that optimization either for all kind of structs 123 // after bug 1368290 with a weak parent pointer from text, or just for reset 124 // structs. 125 #define STYLE_STRUCT_BIT(name_) \ 126 StyleStructConstants::BitFor(StyleStructID::name_) 127 128 #define EXPAND(...) __VA_ARGS__ 129 #define DO_STRUCT_DIFFERENCE_WITH_ARGS(struct_, extra_args_) \ 130 PR_BEGIN_MACRO \ 131 const nsStyle##struct_* this##struct_ = Style##struct_(); \ 132 structsFound |= STYLE_STRUCT_BIT(struct_); \ 133 \ 134 const nsStyle##struct_* other##struct_ = aNewStyle.Style##struct_(); \ 135 if (this##struct_ == other##struct_) { \ 136 /* The very same struct, so we know that there will be no */ \ 137 /* differences. */ \ 138 *aEqualStructs |= STYLE_STRUCT_BIT(struct_); \ 139 } else { \ 140 nsChangeHint difference = \ 141 this##struct_->CalcDifference(*other##struct_ EXPAND extra_args_); \ 142 hint |= difference; \ 143 if (!difference) { \ 144 *aEqualStructs |= STYLE_STRUCT_BIT(struct_); \ 145 } \ 146 } \ 147 styleStructCount++; \ 148 PR_END_MACRO 149 #define DO_STRUCT_DIFFERENCE(struct_) \ 150 DO_STRUCT_DIFFERENCE_WITH_ARGS(struct_, ()) 151 152 // FIXME: The order of these DO_STRUCT_DIFFERENCE calls is no longer 153 // significant. With a small amount of effort, we could replace them with a 154 // #include "nsStyleStructList.h". 155 DO_STRUCT_DIFFERENCE_WITH_ARGS(Display, (, *this)); 156 DO_STRUCT_DIFFERENCE(XUL); 157 DO_STRUCT_DIFFERENCE(Column); 158 DO_STRUCT_DIFFERENCE(Content); 159 DO_STRUCT_DIFFERENCE(UI); 160 DO_STRUCT_DIFFERENCE(Visibility); 161 DO_STRUCT_DIFFERENCE(Outline); 162 DO_STRUCT_DIFFERENCE(TableBorder); 163 DO_STRUCT_DIFFERENCE(Table); 164 DO_STRUCT_DIFFERENCE(UIReset); 165 DO_STRUCT_DIFFERENCE(Text); 166 DO_STRUCT_DIFFERENCE_WITH_ARGS(List, (, *this)); 167 DO_STRUCT_DIFFERENCE(SVGReset); 168 DO_STRUCT_DIFFERENCE(SVG); 169 DO_STRUCT_DIFFERENCE_WITH_ARGS(Position, (, *this)); 170 DO_STRUCT_DIFFERENCE(Font); 171 DO_STRUCT_DIFFERENCE(Margin); 172 DO_STRUCT_DIFFERENCE(Padding); 173 DO_STRUCT_DIFFERENCE(Border); 174 DO_STRUCT_DIFFERENCE(TextReset); 175 DO_STRUCT_DIFFERENCE(Effects); 176 DO_STRUCT_DIFFERENCE(Background); 177 DO_STRUCT_DIFFERENCE(Page); 178 179 #undef DO_STRUCT_DIFFERENCE 180 #undef DO_STRUCT_DIFFERENCE_WITH_ARGS 181 #undef EXPAND 182 183 MOZ_ASSERT(styleStructCount == StyleStructConstants::kStyleStructCount, 184 "missing a call to DO_STRUCT_DIFFERENCE"); 185 186 // Note that we do not check whether this->RelevantLinkVisited() != 187 // aNewContext->RelevantLinkVisited(); we don't need to since 188 // nsCSSFrameConstructor::DoContentStateChanged always adds 189 // nsChangeHint_RepaintFrame for ElementState::VISITED changes (and 190 // needs to, since HasStateDependentStyle probably doesn't work right 191 // for ElementState::VISITED). Hopefully this doesn't actually 192 // expose whether links are visited to performance tests since all 193 // link coloring happens asynchronously at a time when it's hard for 194 // the page to measure. 195 // However, we do need to compute the larger of the changes that can 196 // happen depending on whether the link is visited or unvisited, since 197 // doing only the one that's currently appropriate would expose which 198 // links are in history to easy performance measurement. Therefore, 199 // here, we add nsChangeHint_RepaintFrame hints (the maximum for 200 // things that can depend on :visited) for the properties on which we 201 // call GetVisitedDependentColor. 202 const ComputedStyle* thisVis = GetStyleIfVisited(); 203 const ComputedStyle* otherVis = aNewStyle.GetStyleIfVisited(); 204 if (!thisVis != !otherVis) { 205 // One style has a style-if-visited and the other doesn't. 206 // Presume a difference. 207 #define CLEAR_STRUCT_BIT(name_, fields_) \ 208 *aEqualStructs &= ~STYLE_STRUCT_BIT(name_); 209 FOR_EACH_VISITED_DEPENDENT_STYLE_STRUCT(CLEAR_STRUCT_BIT) 210 #undef CLEAR_STRUCT_BIT 211 hint |= nsChangeHint_RepaintFrame; 212 } else if (thisVis) { 213 // Both styles have a style-if-visited. 214 bool change = false; 215 216 // NB: Calling Peek on |this|, not |thisVis|, since callers may look 217 // at a struct on |this| without looking at the same struct on 218 // |thisVis| (including this function if we skip one of these checks 219 // due to change being true already or due to the old style not having a 220 // style-if-visited), but not the other way around. 221 #define STYLE_FIELD(name_) thisVisStruct->name_ != otherVisStruct->name_ 222 #define CHECK_VISITED_STYLE_STRUCT(name_, fields_) \ 223 { \ 224 const nsStyle##name_* thisVisStruct = thisVis->Style##name_(); \ 225 const nsStyle##name_* otherVisStruct = otherVis->Style##name_(); \ 226 if (MOZ_FOR_EACH_SEPARATED(STYLE_FIELD, (||), (), fields_)) { \ 227 *aEqualStructs &= ~STYLE_STRUCT_BIT(name_); \ 228 change = true; \ 229 } \ 230 } 231 FOR_EACH_VISITED_DEPENDENT_STYLE_STRUCT(CHECK_VISITED_STYLE_STRUCT) 232 #undef CHECK_VISITED_STYLE_STRUCT 233 #undef STYLE_FIELD 234 #undef STYLE_STRUCT_BIT 235 236 if (change) { 237 hint |= nsChangeHint_RepaintFrame; 238 } 239 } 240 241 if (hint & nsChangeHint_UpdateContainingBlock) { 242 if (!ContainingBlockMayHaveChanged(*this, aNewStyle)) { 243 // While some styles that cause the frame to be a containing block 244 // has changed, the overall result cannot have changed (no matter 245 // what the frame type is). 246 hint &= ~nsChangeHint_UpdateContainingBlock; 247 } 248 } 249 250 if (HasAuthorSpecifiedBorderOrBackground() != 251 aNewStyle.HasAuthorSpecifiedBorderOrBackground()) { 252 const StyleAppearance appearance = StyleDisplay()->EffectiveAppearance(); 253 if (appearance != StyleAppearance::None && 254 nsLayoutUtils::AuthorSpecifiedBorderBackgroundDisablesTheming( 255 appearance)) { 256 // A background-specified change may cause padding to change, so we may 257 // need to reflow. We use the same hint here as we do for "appearance" 258 // changes. 259 hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame; 260 } 261 } 262 263 MOZ_ASSERT(NS_IsHintSubset(hint, nsChangeHint_AllHints), 264 "Added a new hint without bumping AllHints?"); 265 return hint & ~nsChangeHint_NeutralChange; 266 } 267 268 #ifdef DEBUG 269 void ComputedStyle::List(FILE* out, int32_t aIndent) { 270 nsAutoCString str; 271 // Indent 272 int32_t ix; 273 for (ix = aIndent; --ix >= 0;) { 274 str.AppendLiteral(" "); 275 } 276 str.Append(nsPrintfCString("%p(%d) parent=%p ", (void*)this, 0, nullptr)); 277 if (mPseudoType != PseudoStyleType::NotPseudo) { 278 str.Append(nsPrintfCString("%s ", ToString(mPseudoType).c_str())); 279 } 280 281 fprintf_stderr(out, "%s{ServoComputedData}\n", str.get()); 282 } 283 #endif 284 285 template <typename Func> 286 static nscolor GetVisitedDependentColorInternal(const ComputedStyle& aStyle, 287 Func aColorFunc) { 288 nscolor colors[2]; 289 colors[0] = aColorFunc(aStyle); 290 if (const ComputedStyle* visitedStyle = aStyle.GetStyleIfVisited()) { 291 colors[1] = aColorFunc(*visitedStyle); 292 return ComputedStyle::CombineVisitedColors(colors, 293 aStyle.RelevantLinkVisited()); 294 } 295 return colors[0]; 296 } 297 298 static nscolor ExtractColor(const ComputedStyle& aStyle, 299 const StyleAbsoluteColor& aColor) { 300 return aColor.ToColor(); 301 } 302 303 static nscolor ExtractColor(const ComputedStyle& aStyle, 304 const StyleColor& aColor) { 305 return aColor.CalcColor(aStyle); 306 } 307 308 // Currently caret-color, the only property in the list which is a ColorOrAuto, 309 // always maps auto to currentcolor. 310 static nscolor ExtractColor(const ComputedStyle& aStyle, 311 const StyleColorOrAuto& aColor) { 312 if (aColor.IsAuto()) { 313 return ExtractColor(aStyle, StyleColor::CurrentColor()); 314 } 315 return ExtractColor(aStyle, aColor.AsColor()); 316 } 317 318 static nscolor ExtractColor(const ComputedStyle& aStyle, 319 const StyleSVGPaint& aPaintServer) { 320 return aPaintServer.kind.IsColor() 321 ? ExtractColor(aStyle, aPaintServer.kind.AsColor()) 322 : NS_RGBA(0, 0, 0, 0); 323 } 324 325 #define STYLE_FIELD(struct_, field_) aField == &struct_::field_ || 326 #define GENERATE_VISITED_COLOR_TEMPLATE(name_, fields_) \ 327 template <> \ 328 nscolor ComputedStyle::GetVisitedDependentColor( \ 329 decltype(nsStyle##name_::MOZ_ARG_1 fields_) nsStyle##name_::* aField) \ 330 const { \ 331 MOZ_ASSERT(MOZ_FOR_EACH(STYLE_FIELD, (nsStyle##name_, ), fields_) false, \ 332 "Getting visited-dependent color for a field in nsStyle" #name_ \ 333 " which is not listed in nsCSSVisitedDependentPropList.h"); \ 334 return GetVisitedDependentColorInternal( \ 335 *this, [aField](const ComputedStyle& aStyle) { \ 336 return ExtractColor(aStyle, aStyle.Style##name_()->*aField); \ 337 }); \ 338 } 339 FOR_EACH_VISITED_DEPENDENT_STYLE_STRUCT(GENERATE_VISITED_COLOR_TEMPLATE) 340 #undef GENERATE_VISITED_COLOR_TEMPLATE 341 #undef STYLE_FIELD 342 343 struct ColorIndexSet { 344 uint8_t colorIndex, alphaIndex; 345 }; 346 347 static const ColorIndexSet gVisitedIndices[2] = {{0, 0}, {1, 0}}; 348 349 /* static */ 350 nscolor ComputedStyle::CombineVisitedColors(nscolor* aColors, 351 bool aLinkIsVisited) { 352 if (NS_GET_A(aColors[1]) == 0) { 353 // If the style-if-visited is transparent, then just use the 354 // unvisited style rather than using the (meaningless) color 355 // components of the visited style along with a potentially 356 // non-transparent alpha value. 357 aLinkIsVisited = false; 358 } 359 360 // NOTE: We want this code to have as little timing dependence as 361 // possible on whether this->RelevantLinkVisited() is true. 362 const ColorIndexSet& set = gVisitedIndices[aLinkIsVisited ? 1 : 0]; 363 364 nscolor colorColor = aColors[set.colorIndex]; 365 nscolor alphaColor = aColors[set.alphaIndex]; 366 return NS_RGBA(NS_GET_R(colorColor), NS_GET_G(colorColor), 367 NS_GET_B(colorColor), NS_GET_A(alphaColor)); 368 } 369 370 #ifdef DEBUG 371 /* static */ const char* ComputedStyle::StructName(StyleStructID aSID) { 372 switch (aSID) { 373 # define CASE_STRUCT(name_) \ 374 case StyleStructID::name_: \ 375 return #name_; 376 FOR_EACH_STYLE_STRUCT(CASE_STRUCT, CASE_STRUCT) 377 # undef CASE_STRUCT 378 default: 379 return "Unknown"; 380 } 381 } 382 383 /* static */ 384 Maybe<StyleStructID> ComputedStyle::LookupStruct(const nsACString& aName) { 385 # define CHECK_STRUCT(name_) \ 386 if (aName.EqualsLiteral(#name_)) return Some(StyleStructID::name_); 387 FOR_EACH_STYLE_STRUCT(CHECK_STRUCT, CHECK_STRUCT) 388 # undef CHECK_STRUCT 389 return Nothing(); 390 } 391 #endif // DEBUG 392 393 ComputedStyle* ComputedStyle::GetCachedLazyPseudoStyle( 394 PseudoStyleType aPseudo) const { 395 MOZ_ASSERT(PseudoStyle::IsPseudoElement(aPseudo)); 396 397 if (nsCSSPseudoElements::PseudoElementSupportsUserActionState(aPseudo)) { 398 return nullptr; 399 } 400 401 return mCachedInheritingStyles.Lookup(aPseudo); 402 } 403 404 MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoComputedValuesMallocEnclosingSizeOf) 405 406 void ComputedStyle::AddSizeOfIncludingThis(nsWindowSizes& aSizes, 407 size_t* aCVsSize) const { 408 // Note: |this| sits within a servo_arc::Arc, i.e. it is preceded by a 409 // refcount. So we need to measure it with a function that can handle an 410 // interior pointer. We use ServoComputedValuesMallocEnclosingSizeOf to 411 // clearly identify in DMD's output the memory measured here. 412 *aCVsSize += ServoComputedValuesMallocEnclosingSizeOf(this); 413 mSource.AddSizeOfExcludingThis(aSizes); 414 mCachedInheritingStyles.AddSizeOfIncludingThis(aSizes, aCVsSize); 415 } 416 417 #ifdef DEBUG 418 bool ComputedStyle::EqualForCachedAnonymousContentStyle( 419 const ComputedStyle& aOther) const { 420 // One thing we can't add UA rules to prevent is different -x-lang 421 // values being inherited in. So we use this FFI function function rather 422 // than rely on CalcStyleDifference, which can't tell us which specific 423 // properties have changed. 424 return Servo_ComputedValues_EqualForCachedAnonymousContentStyle(this, 425 &aOther); 426 } 427 428 void ComputedStyle::DumpMatchedRules() const { 429 Servo_ComputedValues_DumpMatchedRules(this); 430 } 431 #endif 432 433 bool ComputedStyle::HasAnchorPosReference() const { 434 const auto* pos = StylePosition(); 435 if (pos->mPositionAnchor.IsIdent()) { 436 // Short circuit if there's an explicit default anchor defined, 437 // even if it may not end up being referenced. If this early return is 438 // removed, we'll need to handle mPositionArea explicitly. 439 return true; 440 } 441 442 if (pos->mPositionAnchor.IsAuto()) { 443 if (!pos->mPositionArea.IsNone()) { 444 // Position area is relative to an anchor. 445 return true; 446 } 447 448 // Check if anchor-center is used in alignment properties, directly 449 // accessing members rather than using UsedAlign* because legacy values 450 // can't resolve to anchor-center. 451 const auto alignSelfValue = 452 pos->mAlignSelf._0 & ~StyleAlignFlags::FLAG_BITS; 453 const auto justifySelfValue = 454 pos->mJustifySelf._0 & ~StyleAlignFlags::FLAG_BITS; 455 if (alignSelfValue == StyleAlignFlags::ANCHOR_CENTER || 456 justifySelfValue == StyleAlignFlags::ANCHOR_CENTER) { 457 return true; 458 } 459 } 460 461 // Now check if any property that can use anchor() or anchor-size() 462 // does use any. Note that it's valid to specify e.g. left: anchor(left); 463 // but without specifying position-anchor, in which case the function 464 // makes no anchor reference. 465 return pos->mOffset.Any([](const StyleInset& aInset) { 466 return aInset.HasAnchorPositioningFunction(); 467 }) || pos->mWidth.HasAnchorPositioningFunction() || 468 pos->mHeight.HasAnchorPositioningFunction() || 469 pos->mMinWidth.HasAnchorPositioningFunction() || 470 pos->mMinHeight.HasAnchorPositioningFunction() || 471 pos->mMaxWidth.HasAnchorPositioningFunction() || 472 pos->mMaxHeight.HasAnchorPositioningFunction() || 473 StyleMargin()->mMargin.Any([](const ::mozilla::StyleMargin& aMargin) { 474 return aMargin.HasAnchorPositioningFunction(); 475 }); 476 } 477 478 // XXX: This is a broad-stroke method to return true if the referenced anchors 479 // in two computed style may differ. Since we do not get the actual anchor 480 // names, we do not know if the difference is just a position/size/margin change 481 // or if indeed the anchors changed. So we will get false positives here, hence 482 // "maybe". 483 bool ComputedStyle::MaybeAnchorPosReferencesDiffer( 484 const ComputedStyle* aOther) const { 485 if (!HasAnchorPosReference() || !aOther->HasAnchorPosReference()) { 486 return true; 487 } 488 489 const auto* pos = StylePosition(); 490 const auto* otherPos = aOther->StylePosition(); 491 if (pos->mOffset != otherPos->mOffset || pos->mWidth != otherPos->mWidth || 492 pos->mHeight != otherPos->mHeight || 493 pos->mMinWidth != otherPos->mMinWidth || 494 pos->mMinHeight != otherPos->mMinHeight || 495 pos->mMaxWidth != otherPos->mMaxWidth || 496 pos->mMaxHeight != otherPos->mMaxHeight || 497 pos->mPositionAnchor != otherPos->mPositionAnchor) { 498 return true; 499 } 500 501 if (StyleMargin()->mMargin != aOther->StyleMargin()->mMargin) { 502 return true; 503 } 504 505 return false; 506 } 507 508 } // namespace mozilla